From 60f29560f3bfe69bec13aa6187d7a9ddf0fd0eec Mon Sep 17 00:00:00 2001 From: Mike Rubits Date: Mon, 7 Aug 2023 14:47:28 -0500 Subject: [PATCH] Initial commit --- LICENSE.txt | 340 ++ README.md | 1850 +++++++++++ fgd/GameConfig.cfg | 262 ++ fgd/Quake2.fgd | 1673 ++++++++++ fgd/ericw_tools.fgd | 213 ++ fgd/kexlights.fgd | 31 + original/baseq2/g_ai.c | 1117 +++++++ original/baseq2/g_chase.c | 175 ++ original/baseq2/g_cmds.c | 992 ++++++ original/baseq2/g_combat.c | 576 ++++ original/baseq2/g_func.c | 2048 ++++++++++++ original/baseq2/g_items.c | 2216 +++++++++++++ original/baseq2/g_local.h | 1114 +++++++ original/baseq2/g_main.c | 442 +++ original/baseq2/g_misc.c | 1878 +++++++++++ original/baseq2/g_monster.c | 740 +++++ original/baseq2/g_phys.c | 961 ++++++ original/baseq2/g_save.c | 770 +++++ original/baseq2/g_spawn.c | 984 ++++++ original/baseq2/g_svcmds.c | 300 ++ original/baseq2/g_target.c | 809 +++++ original/baseq2/g_trigger.c | 598 ++++ original/baseq2/g_turret.c | 432 +++ original/baseq2/g_utils.c | 568 ++++ original/baseq2/g_weapon.c | 918 ++++++ original/baseq2/game.h | 235 ++ original/baseq2/m_actor.c | 609 ++++ original/baseq2/m_actor.h | 506 +++ original/baseq2/m_berserk.c | 457 +++ original/baseq2/m_berserk.h | 269 ++ original/baseq2/m_boss2.c | 679 ++++ original/baseq2/m_boss2.h | 206 ++ original/baseq2/m_boss3.c | 76 + original/baseq2/m_boss31.c | 749 +++++ original/baseq2/m_boss31.h | 213 ++ original/baseq2/m_boss32.c | 913 ++++++ original/baseq2/m_boss32.h | 516 +++ original/baseq2/m_brain.c | 676 ++++ original/baseq2/m_brain.h | 247 ++ original/baseq2/m_chick.c | 677 ++++ original/baseq2/m_chick.h | 313 ++ original/baseq2/m_flash.c | 488 +++ original/baseq2/m_flipper.c | 403 +++ original/baseq2/m_flipper.h | 185 ++ original/baseq2/m_float.c | 663 ++++ original/baseq2/m_float.h | 273 ++ original/baseq2/m_flyer.c | 626 ++++ original/baseq2/m_flyer.h | 182 ++ original/baseq2/m_gladiator.c | 387 +++ original/baseq2/m_gladiator.h | 115 + original/baseq2/m_gunner.c | 628 ++++ original/baseq2/m_gunner.h | 234 ++ original/baseq2/m_hover.c | 620 ++++ original/baseq2/m_hover.h | 230 ++ original/baseq2/m_infantry.c | 607 ++++ original/baseq2/m_infantry.h | 232 ++ original/baseq2/m_insane.c | 693 ++++ original/baseq2/m_insane.h | 307 ++ original/baseq2/m_medic.c | 769 +++++ original/baseq2/m_medic.h | 262 ++ original/baseq2/m_move.c | 556 ++++ original/baseq2/m_mutant.c | 663 ++++ original/baseq2/m_mutant.h | 174 + original/baseq2/m_parasite.c | 552 ++++ original/baseq2/m_parasite.h | 143 + original/baseq2/m_player.h | 224 ++ original/baseq2/m_rider.h | 66 + original/baseq2/m_soldier.c | 1299 ++++++++ original/baseq2/m_soldier.h | 500 +++ original/baseq2/m_supertank.c | 717 +++++ original/baseq2/m_supertank.h | 279 ++ original/baseq2/m_tank.c | 856 +++++ original/baseq2/m_tank.h | 319 ++ original/baseq2/p_client.c | 1815 +++++++++++ original/baseq2/p_hud.c | 571 ++++ original/baseq2/p_trail.c | 146 + original/baseq2/p_view.c | 1087 +++++++ original/baseq2/p_weapon.c | 1434 +++++++++ original/baseq2/q_shared.c | 1418 +++++++++ original/baseq2/q_shared.h | 1200 +++++++ original/ctf/g_ai.c | 1117 +++++++ original/ctf/g_chase.c | 157 + original/ctf/g_cmds.c | 1066 +++++++ original/ctf/g_combat.c | 596 ++++ original/ctf/g_ctf.c | 4197 +++++++++++++++++++++++++ original/ctf/g_ctf.h | 192 ++ original/ctf/g_func.c | 2047 ++++++++++++ original/ctf/g_items.c | 2446 ++++++++++++++ original/ctf/g_local.h | 1153 +++++++ original/ctf/g_main.c | 429 +++ original/ctf/g_misc.c | 1909 +++++++++++ original/ctf/g_monster.c | 740 +++++ original/ctf/g_phys.c | 959 ++++++ original/ctf/g_save.c | 744 +++++ original/ctf/g_spawn.c | 989 ++++++ original/ctf/g_svcmds.c | 300 ++ original/ctf/g_target.c | 809 +++++ original/ctf/g_trigger.c | 598 ++++ original/ctf/g_utils.c | 570 ++++ original/ctf/g_weapon.c | 921 ++++++ original/ctf/game.h | 242 ++ original/ctf/m_move.c | 556 ++++ original/ctf/m_player.h | 225 ++ original/ctf/p_client.c | 1741 ++++++++++ original/ctf/p_hud.c | 544 ++++ original/ctf/p_menu.c | 256 ++ original/ctf/p_menu.h | 49 + original/ctf/p_trail.c | 146 + original/ctf/p_view.c | 1129 +++++++ original/ctf/p_weapon.c | 1465 +++++++++ original/ctf/q_shared.c | 1419 +++++++++ original/ctf/q_shared.h | 1200 +++++++ original/rogue/dm_ball.c | 680 ++++ original/rogue/dm_tag.c | 335 ++ original/rogue/g_ai.c | 1598 ++++++++++ original/rogue/g_chase.c | 158 + original/rogue/g_cmds.c | 1038 ++++++ original/rogue/g_combat.c | 949 ++++++ original/rogue/g_func.c | 2827 +++++++++++++++++ original/rogue/g_items.c | 3211 +++++++++++++++++++ original/rogue/g_local.h | 1455 +++++++++ original/rogue/g_main.c | 414 +++ original/rogue/g_misc.c | 2001 ++++++++++++ original/rogue/g_monster.c | 981 ++++++ original/rogue/g_newai.c | 2000 ++++++++++++ original/rogue/g_newdm.c | 386 +++ original/rogue/g_newfnc.c | 349 ++ original/rogue/g_newtarg.c | 354 +++ original/rogue/g_newtrig.c | 178 ++ original/rogue/g_newweap.c | 2283 ++++++++++++++ original/rogue/g_phys.c | 1124 +++++++ original/rogue/g_save.c | 800 +++++ original/rogue/g_spawn.c | 1776 +++++++++++ original/rogue/g_sphere.c | 791 +++++ original/rogue/g_svcmds.c | 283 ++ original/rogue/g_target.c | 827 +++++ original/rogue/g_trigger.c | 672 ++++ original/rogue/g_turret.c | 605 ++++ original/rogue/g_utils.c | 714 +++++ original/rogue/g_weapon.c | 911 ++++++ original/rogue/game.def | 2 + original/rogue/game.dsp | 1211 +++++++ original/rogue/game.h | 222 ++ original/rogue/game.plg | 154 + original/rogue/m_actor.c | 592 ++++ original/rogue/m_actor.h | 489 +++ original/rogue/m_berserk.c | 560 ++++ original/rogue/m_berserk.h | 264 ++ original/rogue/m_boss2.c | 777 +++++ original/rogue/m_boss2.h | 189 ++ original/rogue/m_boss3.c | 59 + original/rogue/m_boss31.c | 736 +++++ original/rogue/m_boss31.h | 196 ++ original/rogue/m_boss32.c | 900 ++++++ original/rogue/m_boss32.h | 499 +++ original/rogue/m_brain.c | 703 +++++ original/rogue/m_brain.h | 230 ++ original/rogue/m_carrier.c | 1307 ++++++++ original/rogue/m_carrier.h | 86 + original/rogue/m_chick.c | 948 ++++++ original/rogue/m_chick.h | 296 ++ original/rogue/m_flash.c | 471 +++ original/rogue/m_flipper.c | 386 +++ original/rogue/m_flipper.h | 168 + original/rogue/m_float.c | 706 +++++ original/rogue/m_float.h | 256 ++ original/rogue/m_flyer.c | 874 +++++ original/rogue/m_flyer.h | 165 + original/rogue/m_gladiator.c | 385 +++ original/rogue/m_gladiator.h | 98 + original/rogue/m_gunner.c | 1064 +++++++ original/rogue/m_gunner.h | 227 ++ original/rogue/m_hover.c | 792 +++++ original/rogue/m_hover.h | 213 ++ original/rogue/m_infantry.c | 824 +++++ original/rogue/m_infantry.h | 228 ++ original/rogue/m_insane.c | 676 ++++ original/rogue/m_insane.h | 290 ++ original/rogue/m_medic.c | 1801 +++++++++++ original/rogue/m_medic.h | 245 ++ original/rogue/m_move.c | 847 +++++ original/rogue/m_move2.c | 737 +++++ original/rogue/m_mutant.c | 726 +++++ original/rogue/m_mutant.h | 163 + original/rogue/m_parasite.c | 676 ++++ original/rogue/m_parasite.h | 135 + original/rogue/m_player.h | 207 ++ original/rogue/m_rider.h | 68 + original/rogue/m_soldier.c | 1783 +++++++++++ original/rogue/m_soldier.h | 483 +++ original/rogue/m_stalker.c | 1216 +++++++ original/rogue/m_stalker.h | 101 + original/rogue/m_supertank.c | 727 +++++ original/rogue/m_supertank.h | 262 ++ original/rogue/m_tank.c | 1035 ++++++ original/rogue/m_tank.h | 302 ++ original/rogue/m_turret.c | 1040 ++++++ original/rogue/m_turret.h | 24 + original/rogue/m_widow.c | 1716 ++++++++++ original/rogue/m_widow.h | 177 ++ original/rogue/m_widow2.c | 1794 +++++++++++ original/rogue/m_widow2.h | 134 + original/rogue/p_client.c | 2199 +++++++++++++ original/rogue/p_hud.c | 601 ++++ original/rogue/p_trail.c | 129 + original/rogue/p_view.c | 1178 +++++++ original/rogue/p_weapon.c | 2273 +++++++++++++ original/rogue/q_shared.c | 1404 +++++++++ original/rogue/q_shared.h | 1190 +++++++ original/rogue/rogue.def | 2 + original/rogue/rogue.dsp | 2023 ++++++++++++ original/xatrix/g_ai.c | 1100 +++++++ original/xatrix/g_chase.c | 158 + original/xatrix/g_cmds.c | 1062 +++++++ original/xatrix/g_combat.c | 562 ++++ original/xatrix/g_func.c | 2218 +++++++++++++ original/xatrix/g_items.c | 2448 ++++++++++++++ original/xatrix/g_local.h | 1138 +++++++ original/xatrix/g_main.c | 394 +++ original/xatrix/g_misc.c | 2179 +++++++++++++ original/xatrix/g_monster.c | 875 ++++++ original/xatrix/g_phys.c | 958 ++++++ original/xatrix/g_save.c | 752 +++++ original/xatrix/g_spawn.c | 1022 ++++++ original/xatrix/g_svcmds.c | 283 ++ original/xatrix/g_target.c | 884 ++++++ original/xatrix/g_trigger.c | 692 ++++ original/xatrix/g_turret.c | 415 +++ original/xatrix/g_utils.c | 551 ++++ original/xatrix/g_weapon.c | 1491 +++++++++ original/xatrix/game.h | 218 ++ original/xatrix/m_actor.c | 592 ++++ original/xatrix/m_actor.h | 489 +++ original/xatrix/m_berserk.c | 440 +++ original/xatrix/m_berserk.h | 252 ++ original/xatrix/m_boss2.c | 662 ++++ original/xatrix/m_boss2.h | 189 ++ original/xatrix/m_boss3.c | 59 + original/xatrix/m_boss31.c | 732 +++++ original/xatrix/m_boss31.h | 196 ++ original/xatrix/m_boss32.c | 896 ++++++ original/xatrix/m_boss32.h | 499 +++ original/xatrix/m_boss5.c | 707 +++++ original/xatrix/m_brain.c | 895 ++++++ original/xatrix/m_brain.h | 230 ++ original/xatrix/m_chick.c | 672 ++++ original/xatrix/m_chick.h | 296 ++ original/xatrix/m_fixbot.c | 1330 ++++++++ original/xatrix/m_fixbot.h | 220 ++ original/xatrix/m_flash.c | 471 +++ original/xatrix/m_flipper.c | 386 +++ original/xatrix/m_flipper.h | 168 + original/xatrix/m_float.c | 646 ++++ original/xatrix/m_float.h | 256 ++ original/xatrix/m_flyer.c | 609 ++++ original/xatrix/m_flyer.h | 165 + original/xatrix/m_gekk.c | 1582 ++++++++++ original/xatrix/m_gekk.h | 358 +++ original/xatrix/m_gladb.c | 382 +++ original/xatrix/m_gladiator.c | 371 +++ original/xatrix/m_gladiator.h | 98 + original/xatrix/m_gunner.c | 611 ++++ original/xatrix/m_gunner.h | 217 ++ original/xatrix/m_hover.c | 603 ++++ original/xatrix/m_hover.h | 213 ++ original/xatrix/m_infantry.c | 594 ++++ original/xatrix/m_infantry.h | 215 ++ original/xatrix/m_insane.c | 676 ++++ original/xatrix/m_insane.h | 290 ++ original/xatrix/m_medic.c | 752 +++++ original/xatrix/m_medic.h | 245 ++ original/xatrix/m_move.c | 568 ++++ original/xatrix/m_mutant.c | 646 ++++ original/xatrix/m_mutant.h | 157 + original/xatrix/m_parasite.c | 535 ++++ original/xatrix/m_parasite.h | 126 + original/xatrix/m_player.h | 207 ++ original/xatrix/m_rider.h | 68 + original/xatrix/m_soldier.c | 2610 +++++++++++++++ original/xatrix/m_soldier.h | 483 +++ original/xatrix/m_soldierh.h | 483 +++ original/xatrix/m_supertank.c | 707 +++++ original/xatrix/m_supertank.h | 262 ++ original/xatrix/m_tank.c | 839 +++++ original/xatrix/m_tank.h | 302 ++ original/xatrix/p_client.c | 1857 +++++++++++ original/xatrix/p_hud.c | 571 ++++ original/xatrix/p_trail.c | 129 + original/xatrix/p_view.c | 1091 +++++++ original/xatrix/p_weapon.c | 1867 +++++++++++ original/xatrix/q_shared.c | 1401 +++++++++ original/xatrix/q_shared.h | 1183 +++++++ original/xatrix/xatrix.def | 2 + original/xatrix/xatrix.dsp | 1751 +++++++++++ rerelease/bg_local.h | 263 ++ rerelease/bots/bot_debug.cpp | 178 ++ rerelease/bots/bot_debug.h | 6 + rerelease/bots/bot_exports.cpp | 151 + rerelease/bots/bot_exports.h | 9 + rerelease/bots/bot_includes.h | 9 + rerelease/bots/bot_think.cpp | 23 + rerelease/bots/bot_think.h | 7 + rerelease/bots/bot_utils.cpp | 532 ++++ rerelease/bots/bot_utils.h | 10 + rerelease/cg_local.h | 14 + rerelease/cg_main.cpp | 124 + rerelease/cg_screen.cpp | 1781 +++++++++++ rerelease/ctf/g_ctf.cpp | 3832 ++++++++++++++++++++++ rerelease/ctf/g_ctf.h | 162 + rerelease/ctf/p_ctf_menu.cpp | 282 ++ rerelease/ctf/p_ctf_menu.h | 41 + rerelease/g_ai.cpp | 1769 +++++++++++ rerelease/g_chase.cpp | 171 + rerelease/g_cmds.cpp | 1741 ++++++++++ rerelease/g_combat.cpp | 911 ++++++ rerelease/g_func.cpp | 2897 +++++++++++++++++ rerelease/g_items.cpp | 4019 +++++++++++++++++++++++ rerelease/g_local.h | 3500 +++++++++++++++++++++ rerelease/g_main.cpp | 1051 +++++++ rerelease/g_misc.cpp | 2485 +++++++++++++++ rerelease/g_monster.cpp | 1630 ++++++++++ rerelease/g_phys.cpp | 1044 ++++++ rerelease/g_save.cpp | 2473 +++++++++++++++ rerelease/g_spawn.cpp | 1714 ++++++++++ rerelease/g_statusbar.h | 63 + rerelease/g_svcmds.cpp | 302 ++ rerelease/g_target.cpp | 2059 ++++++++++++ rerelease/g_trigger.cpp | 1333 ++++++++ rerelease/g_turret.cpp | 661 ++++ rerelease/g_utils.cpp | 546 ++++ rerelease/g_weapon.cpp | 1217 +++++++ rerelease/game.h | 2315 ++++++++++++++ rerelease/game.sln | 25 + rerelease/game.vcxproj | 246 ++ rerelease/game.vcxproj.filters | 265 ++ rerelease/m_actor.cpp | 564 ++++ rerelease/m_actor.h | 492 +++ rerelease/m_arachnid.cpp | 385 +++ rerelease/m_arachnid.h | 142 + rerelease/m_berserk.cpp | 822 +++++ rerelease/m_berserk.h | 266 ++ rerelease/m_boss2.cpp | 768 +++++ rerelease/m_boss2.h | 192 ++ rerelease/m_boss3.cpp | 61 + rerelease/m_boss31.cpp | 720 +++++ rerelease/m_boss31.h | 199 ++ rerelease/m_boss32.cpp | 905 ++++++ rerelease/m_boss32.h | 502 +++ rerelease/m_brain.cpp | 798 +++++ rerelease/m_brain.h | 233 ++ rerelease/m_chick.cpp | 869 +++++ rerelease/m_chick.h | 299 ++ rerelease/m_flash.h | 619 ++++ rerelease/m_flipper.cpp | 384 +++ rerelease/m_flipper.h | 171 + rerelease/m_float.cpp | 716 +++++ rerelease/m_float.h | 259 ++ rerelease/m_flyer.cpp | 784 +++++ rerelease/m_flyer.h | 161 + rerelease/m_gladiator.cpp | 489 +++ rerelease/m_gladiator.h | 101 + rerelease/m_guardian.cpp | 523 +++ rerelease/m_guardian.h | 225 ++ rerelease/m_guncmdr.cpp | 1473 +++++++++ rerelease/m_gunner.cpp | 920 ++++++ rerelease/m_gunner.h | 809 +++++ rerelease/m_hover.cpp | 662 ++++ rerelease/m_hover.h | 216 ++ rerelease/m_infantry.cpp | 928 ++++++ rerelease/m_infantry.h | 280 ++ rerelease/m_insane.cpp | 692 ++++ rerelease/m_insane.h | 293 ++ rerelease/m_medic.cpp | 1643 ++++++++++ rerelease/m_medic.h | 248 ++ rerelease/m_move.cpp | 1497 +++++++++ rerelease/m_mutant.cpp | 739 +++++ rerelease/m_mutant.h | 167 + rerelease/m_parasite.cpp | 965 ++++++ rerelease/m_parasite.h | 139 + rerelease/m_player.h | 209 ++ rerelease/m_rider.h | 71 + rerelease/m_shambler.cpp | 598 ++++ rerelease/m_shambler.h | 106 + rerelease/m_soldier.cpp | 2042 ++++++++++++ rerelease/m_soldier.h | 585 ++++ rerelease/m_supertank.cpp | 731 +++++ rerelease/m_supertank.h | 265 ++ rerelease/m_tank.cpp | 1162 +++++++ rerelease/m_tank.h | 305 ++ rerelease/p_client.cpp | 3792 ++++++++++++++++++++++ rerelease/p_hud.cpp | 1124 +++++++ rerelease/p_move.cpp | 1695 ++++++++++ rerelease/p_trail.cpp | 153 + rerelease/p_view.cpp | 1527 +++++++++ rerelease/p_weapon.cpp | 1907 +++++++++++ rerelease/q_std.cpp | 287 ++ rerelease/q_std.h | 217 ++ rerelease/q_vec3.h | 549 ++++ rerelease/rogue/g_rogue_combat.cpp | 146 + rerelease/rogue/g_rogue_func.cpp | 430 +++ rerelease/rogue/g_rogue_items.cpp | 228 ++ rerelease/rogue/g_rogue_misc.cpp | 27 + rerelease/rogue/g_rogue_monster.cpp | 109 + rerelease/rogue/g_rogue_newai.cpp | 1613 ++++++++++ rerelease/rogue/g_rogue_newdm.cpp | 360 +++ rerelease/rogue/g_rogue_newfnc.cpp | 325 ++ rerelease/rogue/g_rogue_newtarg.cpp | 324 ++ rerelease/rogue/g_rogue_newtrig.cpp | 189 ++ rerelease/rogue/g_rogue_newweap.cpp | 1644 ++++++++++ rerelease/rogue/g_rogue_phys.cpp | 121 + rerelease/rogue/g_rogue_spawn.cpp | 367 +++ rerelease/rogue/g_rogue_sphere.cpp | 710 +++++ rerelease/rogue/g_rogue_utils.cpp | 47 + rerelease/rogue/m_rogue_carrier.cpp | 1238 ++++++++ rerelease/rogue/m_rogue_carrier.h | 88 + rerelease/rogue/m_rogue_stalker.cpp | 1041 ++++++ rerelease/rogue/m_rogue_stalker.h | 104 + rerelease/rogue/m_rogue_turret.cpp | 1083 +++++++ rerelease/rogue/m_rogue_turret.h | 27 + rerelease/rogue/m_rogue_widow.cpp | 1390 ++++++++ rerelease/rogue/m_rogue_widow.h | 180 ++ rerelease/rogue/m_rogue_widow2.cpp | 1635 ++++++++++ rerelease/rogue/m_rogue_widow2.h | 137 + rerelease/rogue/p_rogue_weapon.cpp | 446 +++ rerelease/rogue/rogue_dm_ball.cpp | 687 ++++ rerelease/rogue/rogue_dm_tag.cpp | 297 ++ rerelease/vcpkg.json | 9 + rerelease/xatrix/g_xatrix_func.cpp | 178 ++ rerelease/xatrix/g_xatrix_items.cpp | 31 + rerelease/xatrix/g_xatrix_misc.cpp | 143 + rerelease/xatrix/g_xatrix_monster.cpp | 142 + rerelease/xatrix/g_xatrix_target.cpp | 99 + rerelease/xatrix/g_xatrix_weapon.cpp | 605 ++++ rerelease/xatrix/m_xatrix_fixbot.cpp | 1401 +++++++++ rerelease/xatrix/m_xatrix_fixbot.h | 223 ++ rerelease/xatrix/m_xatrix_gekk.cpp | 1686 ++++++++++ rerelease/xatrix/m_xatrix_gekk.h | 361 +++ rerelease/xatrix/p_xatrix_weapon.cpp | 165 + 438 files changed, 317574 insertions(+) create mode 100644 LICENSE.txt create mode 100644 README.md create mode 100644 fgd/GameConfig.cfg create mode 100644 fgd/Quake2.fgd create mode 100644 fgd/ericw_tools.fgd create mode 100644 fgd/kexlights.fgd create mode 100644 original/baseq2/g_ai.c create mode 100644 original/baseq2/g_chase.c create mode 100644 original/baseq2/g_cmds.c create mode 100644 original/baseq2/g_combat.c create mode 100644 original/baseq2/g_func.c create mode 100644 original/baseq2/g_items.c create mode 100644 original/baseq2/g_local.h create mode 100644 original/baseq2/g_main.c create mode 100644 original/baseq2/g_misc.c create mode 100644 original/baseq2/g_monster.c create mode 100644 original/baseq2/g_phys.c create mode 100644 original/baseq2/g_save.c create mode 100644 original/baseq2/g_spawn.c create mode 100644 original/baseq2/g_svcmds.c create mode 100644 original/baseq2/g_target.c create mode 100644 original/baseq2/g_trigger.c create mode 100644 original/baseq2/g_turret.c create mode 100644 original/baseq2/g_utils.c create mode 100644 original/baseq2/g_weapon.c create mode 100644 original/baseq2/game.h create mode 100644 original/baseq2/m_actor.c create mode 100644 original/baseq2/m_actor.h create mode 100644 original/baseq2/m_berserk.c create mode 100644 original/baseq2/m_berserk.h create mode 100644 original/baseq2/m_boss2.c create mode 100644 original/baseq2/m_boss2.h create mode 100644 original/baseq2/m_boss3.c create mode 100644 original/baseq2/m_boss31.c create mode 100644 original/baseq2/m_boss31.h create mode 100644 original/baseq2/m_boss32.c create mode 100644 original/baseq2/m_boss32.h create mode 100644 original/baseq2/m_brain.c create mode 100644 original/baseq2/m_brain.h create mode 100644 original/baseq2/m_chick.c create mode 100644 original/baseq2/m_chick.h create mode 100644 original/baseq2/m_flash.c create mode 100644 original/baseq2/m_flipper.c create mode 100644 original/baseq2/m_flipper.h create mode 100644 original/baseq2/m_float.c create mode 100644 original/baseq2/m_float.h create mode 100644 original/baseq2/m_flyer.c create mode 100644 original/baseq2/m_flyer.h create mode 100644 original/baseq2/m_gladiator.c create mode 100644 original/baseq2/m_gladiator.h create mode 100644 original/baseq2/m_gunner.c create mode 100644 original/baseq2/m_gunner.h create mode 100644 original/baseq2/m_hover.c create mode 100644 original/baseq2/m_hover.h create mode 100644 original/baseq2/m_infantry.c create mode 100644 original/baseq2/m_infantry.h create mode 100644 original/baseq2/m_insane.c create mode 100644 original/baseq2/m_insane.h create mode 100644 original/baseq2/m_medic.c create mode 100644 original/baseq2/m_medic.h create mode 100644 original/baseq2/m_move.c create mode 100644 original/baseq2/m_mutant.c create mode 100644 original/baseq2/m_mutant.h create mode 100644 original/baseq2/m_parasite.c create mode 100644 original/baseq2/m_parasite.h create mode 100644 original/baseq2/m_player.h create mode 100644 original/baseq2/m_rider.h create mode 100644 original/baseq2/m_soldier.c create mode 100644 original/baseq2/m_soldier.h create mode 100644 original/baseq2/m_supertank.c create mode 100644 original/baseq2/m_supertank.h create mode 100644 original/baseq2/m_tank.c create mode 100644 original/baseq2/m_tank.h create mode 100644 original/baseq2/p_client.c create mode 100644 original/baseq2/p_hud.c create mode 100644 original/baseq2/p_trail.c create mode 100644 original/baseq2/p_view.c create mode 100644 original/baseq2/p_weapon.c create mode 100644 original/baseq2/q_shared.c create mode 100644 original/baseq2/q_shared.h create mode 100644 original/ctf/g_ai.c create mode 100644 original/ctf/g_chase.c create mode 100644 original/ctf/g_cmds.c create mode 100644 original/ctf/g_combat.c create mode 100644 original/ctf/g_ctf.c create mode 100644 original/ctf/g_ctf.h create mode 100644 original/ctf/g_func.c create mode 100644 original/ctf/g_items.c create mode 100644 original/ctf/g_local.h create mode 100644 original/ctf/g_main.c create mode 100644 original/ctf/g_misc.c create mode 100644 original/ctf/g_monster.c create mode 100644 original/ctf/g_phys.c create mode 100644 original/ctf/g_save.c create mode 100644 original/ctf/g_spawn.c create mode 100644 original/ctf/g_svcmds.c create mode 100644 original/ctf/g_target.c create mode 100644 original/ctf/g_trigger.c create mode 100644 original/ctf/g_utils.c create mode 100644 original/ctf/g_weapon.c create mode 100644 original/ctf/game.h create mode 100644 original/ctf/m_move.c create mode 100644 original/ctf/m_player.h create mode 100644 original/ctf/p_client.c create mode 100644 original/ctf/p_hud.c create mode 100644 original/ctf/p_menu.c create mode 100644 original/ctf/p_menu.h create mode 100644 original/ctf/p_trail.c create mode 100644 original/ctf/p_view.c create mode 100644 original/ctf/p_weapon.c create mode 100644 original/ctf/q_shared.c create mode 100644 original/ctf/q_shared.h create mode 100644 original/rogue/dm_ball.c create mode 100644 original/rogue/dm_tag.c create mode 100644 original/rogue/g_ai.c create mode 100644 original/rogue/g_chase.c create mode 100644 original/rogue/g_cmds.c create mode 100644 original/rogue/g_combat.c create mode 100644 original/rogue/g_func.c create mode 100644 original/rogue/g_items.c create mode 100644 original/rogue/g_local.h create mode 100644 original/rogue/g_main.c create mode 100644 original/rogue/g_misc.c create mode 100644 original/rogue/g_monster.c create mode 100644 original/rogue/g_newai.c create mode 100644 original/rogue/g_newdm.c create mode 100644 original/rogue/g_newfnc.c create mode 100644 original/rogue/g_newtarg.c create mode 100644 original/rogue/g_newtrig.c create mode 100644 original/rogue/g_newweap.c create mode 100644 original/rogue/g_phys.c create mode 100644 original/rogue/g_save.c create mode 100644 original/rogue/g_spawn.c create mode 100644 original/rogue/g_sphere.c create mode 100644 original/rogue/g_svcmds.c create mode 100644 original/rogue/g_target.c create mode 100644 original/rogue/g_trigger.c create mode 100644 original/rogue/g_turret.c create mode 100644 original/rogue/g_utils.c create mode 100644 original/rogue/g_weapon.c create mode 100644 original/rogue/game.def create mode 100644 original/rogue/game.dsp create mode 100644 original/rogue/game.h create mode 100644 original/rogue/game.plg create mode 100644 original/rogue/m_actor.c create mode 100644 original/rogue/m_actor.h create mode 100644 original/rogue/m_berserk.c create mode 100644 original/rogue/m_berserk.h create mode 100644 original/rogue/m_boss2.c create mode 100644 original/rogue/m_boss2.h create mode 100644 original/rogue/m_boss3.c create mode 100644 original/rogue/m_boss31.c create mode 100644 original/rogue/m_boss31.h create mode 100644 original/rogue/m_boss32.c create mode 100644 original/rogue/m_boss32.h create mode 100644 original/rogue/m_brain.c create mode 100644 original/rogue/m_brain.h create mode 100644 original/rogue/m_carrier.c create mode 100644 original/rogue/m_carrier.h create mode 100644 original/rogue/m_chick.c create mode 100644 original/rogue/m_chick.h create mode 100644 original/rogue/m_flash.c create mode 100644 original/rogue/m_flipper.c create mode 100644 original/rogue/m_flipper.h create mode 100644 original/rogue/m_float.c create mode 100644 original/rogue/m_float.h create mode 100644 original/rogue/m_flyer.c create mode 100644 original/rogue/m_flyer.h create mode 100644 original/rogue/m_gladiator.c create mode 100644 original/rogue/m_gladiator.h create mode 100644 original/rogue/m_gunner.c create mode 100644 original/rogue/m_gunner.h create mode 100644 original/rogue/m_hover.c create mode 100644 original/rogue/m_hover.h create mode 100644 original/rogue/m_infantry.c create mode 100644 original/rogue/m_infantry.h create mode 100644 original/rogue/m_insane.c create mode 100644 original/rogue/m_insane.h create mode 100644 original/rogue/m_medic.c create mode 100644 original/rogue/m_medic.h create mode 100644 original/rogue/m_move.c create mode 100644 original/rogue/m_move2.c create mode 100644 original/rogue/m_mutant.c create mode 100644 original/rogue/m_mutant.h create mode 100644 original/rogue/m_parasite.c create mode 100644 original/rogue/m_parasite.h create mode 100644 original/rogue/m_player.h create mode 100644 original/rogue/m_rider.h create mode 100644 original/rogue/m_soldier.c create mode 100644 original/rogue/m_soldier.h create mode 100644 original/rogue/m_stalker.c create mode 100644 original/rogue/m_stalker.h create mode 100644 original/rogue/m_supertank.c create mode 100644 original/rogue/m_supertank.h create mode 100644 original/rogue/m_tank.c create mode 100644 original/rogue/m_tank.h create mode 100644 original/rogue/m_turret.c create mode 100644 original/rogue/m_turret.h create mode 100644 original/rogue/m_widow.c create mode 100644 original/rogue/m_widow.h create mode 100644 original/rogue/m_widow2.c create mode 100644 original/rogue/m_widow2.h create mode 100644 original/rogue/p_client.c create mode 100644 original/rogue/p_hud.c create mode 100644 original/rogue/p_trail.c create mode 100644 original/rogue/p_view.c create mode 100644 original/rogue/p_weapon.c create mode 100644 original/rogue/q_shared.c create mode 100644 original/rogue/q_shared.h create mode 100644 original/rogue/rogue.def create mode 100644 original/rogue/rogue.dsp create mode 100644 original/xatrix/g_ai.c create mode 100644 original/xatrix/g_chase.c create mode 100644 original/xatrix/g_cmds.c create mode 100644 original/xatrix/g_combat.c create mode 100644 original/xatrix/g_func.c create mode 100644 original/xatrix/g_items.c create mode 100644 original/xatrix/g_local.h create mode 100644 original/xatrix/g_main.c create mode 100644 original/xatrix/g_misc.c create mode 100644 original/xatrix/g_monster.c create mode 100644 original/xatrix/g_phys.c create mode 100644 original/xatrix/g_save.c create mode 100644 original/xatrix/g_spawn.c create mode 100644 original/xatrix/g_svcmds.c create mode 100644 original/xatrix/g_target.c create mode 100644 original/xatrix/g_trigger.c create mode 100644 original/xatrix/g_turret.c create mode 100644 original/xatrix/g_utils.c create mode 100644 original/xatrix/g_weapon.c create mode 100644 original/xatrix/game.h create mode 100644 original/xatrix/m_actor.c create mode 100644 original/xatrix/m_actor.h create mode 100644 original/xatrix/m_berserk.c create mode 100644 original/xatrix/m_berserk.h create mode 100644 original/xatrix/m_boss2.c create mode 100644 original/xatrix/m_boss2.h create mode 100644 original/xatrix/m_boss3.c create mode 100644 original/xatrix/m_boss31.c create mode 100644 original/xatrix/m_boss31.h create mode 100644 original/xatrix/m_boss32.c create mode 100644 original/xatrix/m_boss32.h create mode 100644 original/xatrix/m_boss5.c create mode 100644 original/xatrix/m_brain.c create mode 100644 original/xatrix/m_brain.h create mode 100644 original/xatrix/m_chick.c create mode 100644 original/xatrix/m_chick.h create mode 100644 original/xatrix/m_fixbot.c create mode 100644 original/xatrix/m_fixbot.h create mode 100644 original/xatrix/m_flash.c create mode 100644 original/xatrix/m_flipper.c create mode 100644 original/xatrix/m_flipper.h create mode 100644 original/xatrix/m_float.c create mode 100644 original/xatrix/m_float.h create mode 100644 original/xatrix/m_flyer.c create mode 100644 original/xatrix/m_flyer.h create mode 100644 original/xatrix/m_gekk.c create mode 100644 original/xatrix/m_gekk.h create mode 100644 original/xatrix/m_gladb.c create mode 100644 original/xatrix/m_gladiator.c create mode 100644 original/xatrix/m_gladiator.h create mode 100644 original/xatrix/m_gunner.c create mode 100644 original/xatrix/m_gunner.h create mode 100644 original/xatrix/m_hover.c create mode 100644 original/xatrix/m_hover.h create mode 100644 original/xatrix/m_infantry.c create mode 100644 original/xatrix/m_infantry.h create mode 100644 original/xatrix/m_insane.c create mode 100644 original/xatrix/m_insane.h create mode 100644 original/xatrix/m_medic.c create mode 100644 original/xatrix/m_medic.h create mode 100644 original/xatrix/m_move.c create mode 100644 original/xatrix/m_mutant.c create mode 100644 original/xatrix/m_mutant.h create mode 100644 original/xatrix/m_parasite.c create mode 100644 original/xatrix/m_parasite.h create mode 100644 original/xatrix/m_player.h create mode 100644 original/xatrix/m_rider.h create mode 100644 original/xatrix/m_soldier.c create mode 100644 original/xatrix/m_soldier.h create mode 100644 original/xatrix/m_soldierh.h create mode 100644 original/xatrix/m_supertank.c create mode 100644 original/xatrix/m_supertank.h create mode 100644 original/xatrix/m_tank.c create mode 100644 original/xatrix/m_tank.h create mode 100644 original/xatrix/p_client.c create mode 100644 original/xatrix/p_hud.c create mode 100644 original/xatrix/p_trail.c create mode 100644 original/xatrix/p_view.c create mode 100644 original/xatrix/p_weapon.c create mode 100644 original/xatrix/q_shared.c create mode 100644 original/xatrix/q_shared.h create mode 100644 original/xatrix/xatrix.def create mode 100644 original/xatrix/xatrix.dsp create mode 100644 rerelease/bg_local.h create mode 100644 rerelease/bots/bot_debug.cpp create mode 100644 rerelease/bots/bot_debug.h create mode 100644 rerelease/bots/bot_exports.cpp create mode 100644 rerelease/bots/bot_exports.h create mode 100644 rerelease/bots/bot_includes.h create mode 100644 rerelease/bots/bot_think.cpp create mode 100644 rerelease/bots/bot_think.h create mode 100644 rerelease/bots/bot_utils.cpp create mode 100644 rerelease/bots/bot_utils.h create mode 100644 rerelease/cg_local.h create mode 100644 rerelease/cg_main.cpp create mode 100644 rerelease/cg_screen.cpp create mode 100644 rerelease/ctf/g_ctf.cpp create mode 100644 rerelease/ctf/g_ctf.h create mode 100644 rerelease/ctf/p_ctf_menu.cpp create mode 100644 rerelease/ctf/p_ctf_menu.h create mode 100644 rerelease/g_ai.cpp create mode 100644 rerelease/g_chase.cpp create mode 100644 rerelease/g_cmds.cpp create mode 100644 rerelease/g_combat.cpp create mode 100644 rerelease/g_func.cpp create mode 100644 rerelease/g_items.cpp create mode 100644 rerelease/g_local.h create mode 100644 rerelease/g_main.cpp create mode 100644 rerelease/g_misc.cpp create mode 100644 rerelease/g_monster.cpp create mode 100644 rerelease/g_phys.cpp create mode 100644 rerelease/g_save.cpp create mode 100644 rerelease/g_spawn.cpp create mode 100644 rerelease/g_statusbar.h create mode 100644 rerelease/g_svcmds.cpp create mode 100644 rerelease/g_target.cpp create mode 100644 rerelease/g_trigger.cpp create mode 100644 rerelease/g_turret.cpp create mode 100644 rerelease/g_utils.cpp create mode 100644 rerelease/g_weapon.cpp create mode 100644 rerelease/game.h create mode 100644 rerelease/game.sln create mode 100644 rerelease/game.vcxproj create mode 100644 rerelease/game.vcxproj.filters create mode 100644 rerelease/m_actor.cpp create mode 100644 rerelease/m_actor.h create mode 100644 rerelease/m_arachnid.cpp create mode 100644 rerelease/m_arachnid.h create mode 100644 rerelease/m_berserk.cpp create mode 100644 rerelease/m_berserk.h create mode 100644 rerelease/m_boss2.cpp create mode 100644 rerelease/m_boss2.h create mode 100644 rerelease/m_boss3.cpp create mode 100644 rerelease/m_boss31.cpp create mode 100644 rerelease/m_boss31.h create mode 100644 rerelease/m_boss32.cpp create mode 100644 rerelease/m_boss32.h create mode 100644 rerelease/m_brain.cpp create mode 100644 rerelease/m_brain.h create mode 100644 rerelease/m_chick.cpp create mode 100644 rerelease/m_chick.h create mode 100644 rerelease/m_flash.h create mode 100644 rerelease/m_flipper.cpp create mode 100644 rerelease/m_flipper.h create mode 100644 rerelease/m_float.cpp create mode 100644 rerelease/m_float.h create mode 100644 rerelease/m_flyer.cpp create mode 100644 rerelease/m_flyer.h create mode 100644 rerelease/m_gladiator.cpp create mode 100644 rerelease/m_gladiator.h create mode 100644 rerelease/m_guardian.cpp create mode 100644 rerelease/m_guardian.h create mode 100644 rerelease/m_guncmdr.cpp create mode 100644 rerelease/m_gunner.cpp create mode 100644 rerelease/m_gunner.h create mode 100644 rerelease/m_hover.cpp create mode 100644 rerelease/m_hover.h create mode 100644 rerelease/m_infantry.cpp create mode 100644 rerelease/m_infantry.h create mode 100644 rerelease/m_insane.cpp create mode 100644 rerelease/m_insane.h create mode 100644 rerelease/m_medic.cpp create mode 100644 rerelease/m_medic.h create mode 100644 rerelease/m_move.cpp create mode 100644 rerelease/m_mutant.cpp create mode 100644 rerelease/m_mutant.h create mode 100644 rerelease/m_parasite.cpp create mode 100644 rerelease/m_parasite.h create mode 100644 rerelease/m_player.h create mode 100644 rerelease/m_rider.h create mode 100644 rerelease/m_shambler.cpp create mode 100644 rerelease/m_shambler.h create mode 100644 rerelease/m_soldier.cpp create mode 100644 rerelease/m_soldier.h create mode 100644 rerelease/m_supertank.cpp create mode 100644 rerelease/m_supertank.h create mode 100644 rerelease/m_tank.cpp create mode 100644 rerelease/m_tank.h create mode 100644 rerelease/p_client.cpp create mode 100644 rerelease/p_hud.cpp create mode 100644 rerelease/p_move.cpp create mode 100644 rerelease/p_trail.cpp create mode 100644 rerelease/p_view.cpp create mode 100644 rerelease/p_weapon.cpp create mode 100644 rerelease/q_std.cpp create mode 100644 rerelease/q_std.h create mode 100644 rerelease/q_vec3.h create mode 100644 rerelease/rogue/g_rogue_combat.cpp create mode 100644 rerelease/rogue/g_rogue_func.cpp create mode 100644 rerelease/rogue/g_rogue_items.cpp create mode 100644 rerelease/rogue/g_rogue_misc.cpp create mode 100644 rerelease/rogue/g_rogue_monster.cpp create mode 100644 rerelease/rogue/g_rogue_newai.cpp create mode 100644 rerelease/rogue/g_rogue_newdm.cpp create mode 100644 rerelease/rogue/g_rogue_newfnc.cpp create mode 100644 rerelease/rogue/g_rogue_newtarg.cpp create mode 100644 rerelease/rogue/g_rogue_newtrig.cpp create mode 100644 rerelease/rogue/g_rogue_newweap.cpp create mode 100644 rerelease/rogue/g_rogue_phys.cpp create mode 100644 rerelease/rogue/g_rogue_spawn.cpp create mode 100644 rerelease/rogue/g_rogue_sphere.cpp create mode 100644 rerelease/rogue/g_rogue_utils.cpp create mode 100644 rerelease/rogue/m_rogue_carrier.cpp create mode 100644 rerelease/rogue/m_rogue_carrier.h create mode 100644 rerelease/rogue/m_rogue_stalker.cpp create mode 100644 rerelease/rogue/m_rogue_stalker.h create mode 100644 rerelease/rogue/m_rogue_turret.cpp create mode 100644 rerelease/rogue/m_rogue_turret.h create mode 100644 rerelease/rogue/m_rogue_widow.cpp create mode 100644 rerelease/rogue/m_rogue_widow.h create mode 100644 rerelease/rogue/m_rogue_widow2.cpp create mode 100644 rerelease/rogue/m_rogue_widow2.h create mode 100644 rerelease/rogue/p_rogue_weapon.cpp create mode 100644 rerelease/rogue/rogue_dm_ball.cpp create mode 100644 rerelease/rogue/rogue_dm_tag.cpp create mode 100644 rerelease/vcpkg.json create mode 100644 rerelease/xatrix/g_xatrix_func.cpp create mode 100644 rerelease/xatrix/g_xatrix_items.cpp create mode 100644 rerelease/xatrix/g_xatrix_misc.cpp create mode 100644 rerelease/xatrix/g_xatrix_monster.cpp create mode 100644 rerelease/xatrix/g_xatrix_target.cpp create mode 100644 rerelease/xatrix/g_xatrix_weapon.cpp create mode 100644 rerelease/xatrix/m_xatrix_fixbot.cpp create mode 100644 rerelease/xatrix/m_xatrix_fixbot.h create mode 100644 rerelease/xatrix/m_xatrix_gekk.cpp create mode 100644 rerelease/xatrix/m_xatrix_gekk.h create mode 100644 rerelease/xatrix/p_xatrix_weapon.cpp diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..d60c31a --- /dev/null +++ b/LICENSE.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..97f7e26 --- /dev/null +++ b/README.md @@ -0,0 +1,1850 @@ +# Quake II Rerelease Game Source + +This repository contains the game code for the 2023 rerelease of Quake II, for users who wish to mod the game, along with the original game code that was use for reference. Mods can be loaded into the rerelease the same way as the original game: launch the game with `+set game mymod` or type `game mymod` into the console while the game is running. We recommend installing mods into your `%USERPROFILE%\Saved Games\Nightdive Studios\Quake II` directory to ensure the original game files do not get modified. + +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. + +The rerelease of Quake II uses a new version of the API to communicate between the server & the game module. It also introduces a very thin "client game" module, akin to Quake III Arena's cgame module, to allow for extended modding opportunities that change previously hardcoded client behavior. It also has a new network protocol, version 2023. + +This codebase is a combination of the separate game modules that were part of the original game: baseq2, ctf, rogue, and xatrix. It requires a C++17 compiler. In cases of conflicting spawnflags, maps were modified in order to resolve issues, so original expansion pack maps may not load correctly with this DLL. The combined FGD as used for development is also available for users wishing to make new maps. A modified TrenchBroom Quake II `gameconfig.cfg` is included as there are modified textureflags. + +Because the game export interface has changed, existing mods may be able to be moved over to support the API changes. However, in order to support all expansion packs under one codebase and new features in the rerelease, there have been some major changes to structure and layout, so old mods wishing to use the new codebase may need to be rewritten. + +The API changes discussed here are written from the perspective of the game DLL. + +## Compiling + +The game DLL has only been tested with Clang, VS2019 and VS2022. + +The code can compile under both C++17 and C++20. Using C++20 allows you to skip `fmtlib` as a dependency. + +### Required preprocessor definitions +* `GAME_INCLUDE` must be defined, tells `game.h` that this is the game DLL compiling it. +* `KEX_Q2GAME_EXPORTS` must be defined, tells `game.h` that we are exporting the `GetGameAPI` function. +* `KEX_Q2GAME_DYNAMIC` must be defined. The game DLL supports static linking on console platforms, but is always dynamic on PC. +* `NO_FMT_SOURCE`: this is only here because of a limitation in the internal build system. It must be defined. + +### Optional preprocessor definitions +* `KEX_Q2_GAME`: must be defined if compiling for Kex. This changes the behavior of the `say` command to go through the lobby. +* `KEX_Q2GAME_IMPORTS`: only used by engine, tells `game.h` that we are importing `GetGameAPI`. +* `USE_CPP20_FORMAT`: if explicitly defined, C++20's `` library will be used instead of `fmtlib`; otherwise `fmtlib` usage will be autodetected. + + +### Dependencies +* [fmtlib](https://github.com/fmtlib/fmt): If `USE_CPP20_FORMAT` is not set, the library needs to be available in the `fmt` subdirectory. +* [jsoncpp](https://github.com/open-source-parsers/jsoncpp): Must be placed inside `json` subdirectory. + +Both of these can also be installed via vcpkg: `vcpkg install jsoncpp:x64-windows fmt:x64-windows` + +### Windows (Visual Studio 2019 / 2022): +* We recommend placing the source in a subfolder within a mod directory. For example, alongside `baseq2`, make a folder called `mymod`, enter that folder, make a folder called `src`, and copying the contents of the `rerelease` directory into the newly-created `src` subfolder. +* Open `game.sln` +* Build solution + +Debugging the DLL is possible when attaching to the engine EXE. Note that if you are using VS2022 Hot Reload, due to an internal Hot Reload issue, current edits will be lost when disconnecting from the server, or changing levels using the `map` command. + +## 40hz Tickrate Support + +As part of this release, all internal logic in the game DLL has been adjusted to run at 40hz compared to the original 10hz of the original engine. This provides a better gameplay experience, and allows maps and game logic to run at more precise steps than the original 100ms. 40hz was chosen as it is a multiple of the original 10hz, operates at an integral 25ms step, and was the best balance between bandwidth and CPU concerns around the original tech. + +## Print Adjustments + +As part of the API cleanup, the game DLL no longer uses varargs in any of its functions. Varargs are compiler-specific and not very portable, so instead, the onus is on the caller to handle formatting. As a bonus, this allows the game DLL to more easily hook in modern formatting providers; the game DLL uses `fmt` almost exclusively. Several built-in types, like `edict_t` and `vec3_t`, can be formatted directly. + +## Math Changes + +Since C++ is now used in the game DLL, math functions were made constexpr where appropriate, and operator overloads are used to make math easier to work with and closer to QuakeC. For instance, `VectorMA(a, s, b, c)` can now be written as `c = a + (b * s)`, which expresses the operation better. + +## Type Changes + +`qboolean`, which was aliased to `int32_t`, is now instead aliased to `bool`. This type should be equivalent to C's `_Bool`. + +## Info Keys + +In the original Quake II, all infokey values are purely ASCII with upper bits stripped. Kex and the Quake II rerelease engine supports UTF-8 for things like player names, which necessated a change to the way info keys work. Instead of implementing a whole UTF-8 system in the game DLL, these functions are now imports, so the engine is in control of the parsing and string management. + +Userinfo variables are now suffixed with a number for split screen support. For instance, `name` and `skin` have become `name_0` and `skin_0` for the first player, `name_1` and `skin_1` for the second player, and so on. + +## Extensions + +In an attempt to remain compatible with future community engines, all engine interfaces contain stubbed functions for GetExtension. This is currently unused and will only return nullptr, however other engines may wish to support returning named structs containing extra function pointers based on string parameters. This is similar to `getextension` that has become standard in many QuakeC environments. + +Conforming engines should return nullptr if an extension is not supported, otherwise they should return a non null pointer value that is relevant to the requested feature. Supporting engines should use namespaces for features to prevent name collisions around common and possibly incompatible implementations. + +## Player Movement + +Player movement ("pmove") is now handled as an export in both `game_export_t` *and* `cgame_export_t`. This allows a game module to modify physics while still working with client prediction. Pmove also received several upgrades, such as more bits for flags and full float positioning. + +Because a lot of movement quirks in Quake II were indirectly caused by the compression, these behaviors were retained. Trick jumping is now an explicit movement type, to allow for things like the Mega Health box jumps to still work. Some fixes were made, like jumping higher below the 0 z point of the map. + +## Frame visibility + +As part of network improvements, some changes were made to the "entity is visible to client in frame" methods: +* Split-screen support since all clients share a frame. +* Entities with shadows will be visible if their shadows may be visible to a player. +* Sound attenuation culling is now calculated formulaically to match the sound system, and takes loop_attenuation into account. + +## Entity linkage + +To fix a legacy bug where lasers only relied on one position for culling, RF_BEAM entities now set their `absmin` & `absmax` properly. This can cause them to be a bit inflated if they are angled, but should not cause any issues otherwise. + +In a similar vein, `gi.linkentity` no longer automatically sets `old_origin` for beams. This is to make it a bit easier to work with beams, as otherwise you'd be forced to link twice to get it linked into the world correctly. This *might* break old mods that depends on this behavior. + +## Audio positioning + +Entity spatialization underwent an overhaul (`CL_GetEntitySoundOrigin` mainly). + +* Brush models will use the closest point on the bmodel's absmin/absmax to the listener's origin. This allows moving brush models with sounds to make consistent sounds, and be full volume if you are inside of them. +* Beams now support `s.sound`, and plays their sound on the nearest point between the two beam origins. + +As a secondary fix to the above, `S_StartSound` has slightly different logic now surrounding what origin to pick when playing a sound: + +```cpp +if (entnum < MAX_EDICTS && (origin || fixed_origin)) +{ + // [Paril-KEX] if we can currently see the entity in question + // and it's a bmodel or beam, don't use a fixed origin so it sounds correct + if (!fixed_origin && entnum > 1 && (cl_entities[entnum].serverframe == cl.frame.serverframe || + (cl_entities[entnum].current.solid == PACKED_SOLID_BSP || (cl_entities[entnum].current.renderfx & RF_BEAM)))) + { + ps->fixed_origin = false; + } + else + { + VectorCopy (origin, ps->origin); + ps->fixed_origin = true; + } +} +else + ps->fixed_origin = false; +``` + +`fixed_origin` is set to `flags & SND_EXPLICIT_POS` for svc_sound packets, and is `false` otherwise. If the playsounds' `fixed_origin` field is set, then the `ps->origin` value will *always* be used over automatically trying to determine its position. + +## Client entity adjustments + +* Beam origin points are interpolated if they exist between frames. +* `TE_ELECTRIC_SPARKS` and `TE_SCREEN_SPARKS`/`TE_SHIELD_SPARKS` will only play sounds once on any given frame. +* Entities will never play the same footstep sound twice in a row. +* Beams now squash the ends of their beams so they don't intersect walls or end early. +* Alpha and transparency settings now get copied over to all sub-modelindices. +* Lightramps are now interpolated, which looks nicer and helps with epilepsy. +* `delta_angles` are interpolated now, although this is never used at all in the game. +* `screen_blend` and `damage_blend` are interpolated now, but only if the frame prior didn't have a clear color. + +## Configstrings + +Configstrings have been overhauled. There is now a theoretical maximum of `32767` entries, although you are still bound by the game APIs value of `MAX_CONFIGSTRINGS`. + +The maximum length of a configstring has increased from `64` to `96`. + +The API now canonizes that certain spans (`CS_STATUSBAR` and `CS_GENERAL`) can span multiple lines. A `CS_SIZE` function is provided to calculate the total size (in bytes) that can be written to for a given configstring ID. + +A convenience function, `CS_REMAP`, is provided to help remap old configstring IDs to new ones. This is used in our engine to provide old demo support. + +`MAX_MODELS`, `MAX_SOUNDS` and `MAX_IMAGES` have been increased from 256 to 8192, 2048, and 512, respectively. + +### CS_STATUSBAR + +This entry now spans entries 5 to 58 instead of 5 to 28, giving you an effective size of 5184 bytes (minus one for the null terminator) for the statusbar strings, up from 1536 bytes. + +### CS_SHADOWLIGHTS + +This new span each consists of a shadow light entry. Its format is semicolon-separated numerical values, in the following type & order: + +* int entity_order +* int type (0 = point, 1 = cone) +* float radius +* int resolution +* float intensity +* float fade_start +* float fade_end +* int lightstyle +* float coneangle +* float direction_x +* float direction_y +* float direction_z + +### CS_WHEEL_WEAPONS + +This new span consists of entries for the weapon wheel. It consists of pipe-separated integral values, in the following order: + +* CS_ITEMS item index +* CS_IMAGES image index +* CS_WHEEL_AMMO ammo index (or -1 for no ammo) +* minimum ammo to use weapon +* whether the weapon is on the powerup wheel or the weapon wheel +* additional sort integer +* quantity to warn on low ammo on +* whether this weapon is droppable or not + +### CS_WHEEL_AMMO + +This new span consists of entries for the weapon wheel ammo types. It consists of pipe-separated integral values, in the following order: + +* CS_ITEMS item index +* CS_IMAGES image index + +### CS_WHEEL_POWERUPS + +This new span consists of entries for the powerup wheel. It consists of pipe-separated integral values, in the following order: + +* CS_ITEMS image index +* CS_IMAGES image index +* if 1, it is a togglable powerup instead of having a count +* additional sort integer +* whether we can drop this powerup or not +* CS_WHEEL_AMMO ammo index, if applicable (-1 for no ammo) + +### CS_CD_LOOP_COUNT + +Integer which determines how many times to loop the music before switching to the ambient track. Leave blank to use the clients' preferred value, otherwise it is forced to this value (a value of zero means never switch to ambient track). + +### CS_GAME_STYLE + +Inform the client about the type of game being played. + +# Structures + +Quake II has two main shared structures: `gclient_t` and `edict_t`. These split off into various other shared structures that have to be the same between the game & server. + +Like the original release, the "shared" aspects of these structs must be identical, and are stored in `game.h` as `edict_shared_t` and `gclient_shared_t` respectively. + +The structure changes will be listed from the bottom-up. Full listings of the structures can be found in the source. + +## cvar_flags_t + +### CVAR_USER_PROFILE (bit 5) + +This is a new flag that is solely for the client; it indicates that a cvar is treated like userinfo for the purposes of storage, but is not sent to the server like userinfo usually is. For example, this flag is applied to `cl_run_N`, which controls the individual Always Run flags for each split screen player. + +## contents_t + +### CONTENTS_PROJECTILECLIP (bit 14) + +This new content flag will be collided against by `CONTENTS_PROJECTILE` entities. + +### CONTENTS_PLAYER (bit 30) + +This special content type flag is set only on player entities, and allows tracing to exclude/include them. + +### CONTENTS_PROJECTILE (bit 31) + +This special content type flag is set only on projectiles, and allows tracing to exclude/include them. + +## surfflags_t + +### SURF_ALPHATEST (bit 25) + +This bit is widely supported by other engines and is supported in the rerelease. + +### SURF_N64_UV (bit 28) + +This flag is specific to N64, and halves texture sizes. + +### SURF_N64_SCROLL_X (bit 29) + +This flag is specific to N64, and causes textures to scroll in the X axis. + +### SURF_N64_SCROLL_Y (bit 30) + +This flag is specific to N64, and causes textures to scroll in the Y axis. + +### SURF_N64_SCROLL_FLIP (bit 31) + +This flag is specific to N64, and flips the scroll axis. + +## csurface_t + +This structure has undergone canonization of the 3.2x changes by Zoid. + +### char[32] name + +Uses the proper name length now. + +### uint32_t id + +This value must represent a unique ID that corresponds to its texinfo. The same ID must always reference the same texinfo, but they don't necessarily have to be sequential. Zero must always mean "no texinfo". + +This is used by the client for indexing footstep sounds. + +### char[16] material + +The material ID for this texinfo, from the corresponding `.mat` file. + +## trace_t + +### csurface_t *surface + +The only change is a contractual one: this value must never be null. + +### cplane_t plane2 / csurface_t *surface2 + +When a trace impacts multiple planes at the destination, the collision system will now report both of them. The "second best" plane and surface are stored here. `surface2` *must* be null if a second surface was not hit. + +This is used to solve some epsilon issues with the player movement system. + +## cvar_t + +### int32_t modified_count + +The old `qboolean modified;` has been changed into an integral value. This value is increased when the cvar has been changed, but is **never** zero. The reason for this is so that "is cvar modified" checks always succeed on the first check, assuming you initialize the last modified value to 0. + +The function `Cvar_WasModified` is provided as a convenience function to perform this task for you. + +### int32_t integer + +A common extension to Quake II, the integral value is stored at the end of the struct for you to use. + +## player_state_t + +### int32_t gunskin + +This is a new value which sets the skin number used on the weapon. + +### int32_t gunrate + +This value sets the frame rate (in hz) of the players' weapon. For backwards compatibility, a value of 0 is equivalent to a value of 10. This ia mainly used for the Haste powerup, but in theory it could be used for future mods to have higher tickrate weapons in general. + +### float[4] screen_blend / damage_blend + +The full-screen `blend` value was split into two values: `screen_blend` and `damage_blend`. + +`screen_blend` is the same as the original one, and is a full-screen color change. It is mainly used now for full-screen fades. To reduce the amount of screen flashing, the base game avoids flashing the screen whenever possible. + +`damage_blend` is a new type of blend that occurs around the edge of the screen; this is used to replace many events that previously would flash the full screen. + +### refdef_flags_t rdflags + +The only adjustment to `rdflags` was the addition of a new flag: `RDF_NO_WEAPON_LERP`. This occupies bit 4, and can be used to temporarily disable interpolation on weapons. + +### short[64] stats + +`MAX_STATS` was increased from 32 to 64. Note that because stats are now handled by the game & cgame modules, you are not limited to a short for the purposes of packing down data/ + +### uint8_t team_id + +For teamplay-oriented games, the player's team is sent in player state. While the client could derive this from entity state in theory, in practice that's a bit ugly since the players' entity may not even be visible (for instance if you've been gibbed), so this was the cleaner approach. + +## usercmd_t + +The fields `upmove`, `impulse` and `lightlevel` have been removed. + +### button_t buttons + +#### BUTTON_HOLSTER (bit 2) + +This button corresponds to the new `+holster` command, which will keep the weapon holstered until depressed. It is used by the weapon wheel to allow the player to start switching weapons before the weapon wheel is dismissed. + +#### BUTTON_JUMP (bit 3) +#### BUTTON_CROUCH (bit 4) + +These two new bits replace `usercmd_t::upmove`, and determine the players' jumping and crouch states. + +### vec3_t angles + +These are now full float precision, allowing for players to aim more precisely. + +### float forwardmove / sidemove + +These are now full float, to allow controller inputs to be more precise. + +### uint32_t server_frame + +New entry sent along with every usercmd, which tells the server which server frame that the input was depressed on. This is used for integrity checks, as well as for anti-lag hitscan. + +## pmove_state_t + +### pmtype_t pm_type + +Two new pmove types have been added before `PM_SPECTATOR`, offsetting it and its subsequent entries by 2. + +#### PM_GRAPPLE (1) + +Used for the grappling hook; it informs client prediction that you should be pulled towards `velocity` and are not affected by gravity. + +#### PM_NOCLIP (2) + +This is what `PM_SPECTATOR` used to be, and prevents all clipping. + +#### PM_SPECTATOR (3) + +This value now represents spectator mode; you cannot enter walls, but can go through brush entities. + +### vec3_t origin / vec3_t velocity / vec3_t delta_angles + +These fields now have full float precision versus the original release. See [Pmove](#pmove) for more details. + +### pmflags_t pm_flags + +This type has had its capacity increased to `int16`. The following flags are new or adjusted: + +#### PMF_NO_POSITIONAL_PREDICTION + +This flag was originally called `PMF_NO_PREDICTION`; it now only disables prediction on origin, allowing angles to be predicted. **This is a backwards-incompatible change**, but should have very minimal impact on running old mods. This improves the feeling of the grappling hook. + +#### PMF_ON_LADDER + +This bit is used to signal back to the game that we are currently attached to a ladder. + +#### PMF_NO_ANGULAR_PREDICTION + +The angular equivalent of `PMF_NO_POSITIONAL_PREDICTION`. + +#### PMF_IGNORE_PLAYER_COLLISION + +This flag is input only, and tells Pmove to ignore `CONTENTS_PLAYER` contents. + +#### PMF_TIME_TRICK + +If set, then `pm_time` is the time remaining to start a trick jump. + +### uint16_t pm_time + +`pm_time` is now expressed in milliseconds instead of 8 * ms; since the code clamped subtractions on this to 1, it meant that high framerate players experienced slightly different physics, and in the case of trick jumps, had a smaller time gap to perform them. + +### int8_t viewheight + +A new field describing the viewheight output; this is for crouch prediction. + +## pmove_t + +The field `viewheight` has been removed, since it is now part of `pmove_state_t`. + +### touch_list_t touch + +The list of touched entities has been replaced with a list of traces, allowing the game to react better to touches. + +### cplane_t groundplane + +The plane that you're standing on is now returned by pmove. + +### edict_t *player + +An opaque handle to the player object, passed back to `trace`. + +### trace / clip + +`trace` is now sent the `passent` and `contentmask`, so it can perform more complex tracing routines. + +`clip` is also now available to pmove, should you need it. It is currently only used in spectator movement, to clip solely against the world and nothing else. + +### vec3_t viewoffset + +The player's viewoffset is now passed in, to allow for accurate blending. Pmove is now semi-responsible for screen blends. + +### vec3_t screen_blend + +An output variable containing full-screen blends to apply to the view. + +### refdef_flags_t rdflags + +An output variable containing flags that should be merged with the server's representation. + +### bool jump_sound + +An output variable to tell the game to play a jumping sound. + +### float impact_delta + +When new ground is achieved, the impact is stored here for fall damage checks. + +## edict_shared_t + +NOTE: the following members of the old `edict_t` struct have been removed, and were moved server-side: +* `link_t area` +* `int num_clusters` +* `int clusternums[]` +* `int headnode` + +### sv_entity_t sv + +Most of the meat of the bot system is contained in the server code, and doesn't have direct access to the games' representation of the state of the game. + +Bots use this thin interpretation of the game state data about entities to understand how to use entities to its advantage - similar to how the client receives a thin portion of entities to understand how to render them. + +### bool linked + +This boolean indicates whether the entity is currently linked into the world or not. It is the replacement of checking for `area.prev` being non-null. + +### svflags_t svflags + +For new functionality, some new flags were added to `svflags`. **This may cause backwards-incompatibility in older mods that have modified this enum!** This enum is server-specific, so it is always incorrect for mods to modify this. + +#### SVF_PLAYER + +This flag causes the object to be treated as `CONTENTS_PLAYER` for collision. All players have this flag. + +#### SVF_BOT + +This flag marks the entity as a bot. + +#### SVF_NOBOTS + +This flag tells the bot subsystem to ignore this entity. + +#### SVF_RESPAWNING + +This flag is a hint to the bot subsystem to inform it about how items respawn. + +#### SVF_PROJECTILE + +This flag treats the entity as `CONTENTS_PROJECTILE` for collision. + +#### SVF_INSTANCED + +This flag marks the entity as being instanced - it may be invisible or hidden for certain players. + +#### SVF_DOOR + +This flag is for the bot subsystem, and informs it that this entity is a door. + +#### SVF_NOCULL + +This flag overrides the client frame building culling routines, causing an entity to always be sent regardless of where it is (ignoring PVS/PHS, essentially). Its only use in our code is to keep no-attenuation looping speakers in frame always. + +#### SVF_HULL + +This flag adjusts the servers' method of clipping movement to entities. Normally, only `SOLID_BSP` entities will use their proper clipping bounds for collision, but if this is set on a `SOLID_TRIGGER` brush entity, traces will have to collide with the actual BSP tree of the trigger instead of solely touching its bounding box. + +This is used in our game DLL to allow for certain triggers (like `trigger_gravity` or `trigger_flashlight`) to be activated when you are actually touching their brushes, allowing for angled triggers to finally exist. + +## entity_state_t + +### uint32_t number + +Number was changed to `uint32_t` (from `int32_t`) to better represent its use and to only have to catch out of bounds in one direction. + +### int32_t skinnum + +Skinnum now packs a bit more data into it. + +```cpp +// [Paril-KEX] player s.skinnum's encode additional data +union player_skinnum_t +{ + int32_t skinnum; + struct { + uint8_t client_num; // client index + uint8_t vwep_index; // vwep index + int8_t viewheight; // viewheight + uint8_t team_index : 4; // team #; note that teams are 1-indexed here, with 0 meaning no team + // (spectators in CTF would be 0, for instance) + uint8_t poi_icon : 4; // poi icon; 0 default friendly, 1 dead, others unused + }; +}; +``` + +### effects_t effects + +The type `effects_t` was changed from `uint32_t` to `uint64_t` since we have way more effects to express. + +#### EF_BOB (bit 4) + +Bit was unused in Quake II. This was repurposed into a weapon bobbing effect, similar to Quake III. + +#### EF_POWERSCREEN (bit 9) + +This effect uses a different model that is scaled to the monster's size now. + +#### EF_DUALFIRE (bit 32) + +This bit is used for a special effect, similar to `EF_QUAD`, but for Dualfire Damage. + +#### EF_HOLOGRAM (bit 33) + +This bit is used for the N64 hologram effect; it adds a spinning ball of green particles around the object. + +#### EF_FLASHLIGHT (bit 34) + +This bit marks a player entity as having a flashlight enabled. The effect itself is rendered separately by the client. + +#### EF_BARREL_EXPLODING (bit 35) + +This effect is used before an explobox explodes; it emits steam particles from the barrel, as if it is experiencing a decompression event. + +#### EF_TELEPORTER2 (bit 36) + +This effect is used for the teleporter FX in the N64. + +#### EF_GRENADE_LIGHT (bit 37) + +This effect creates a small light on monster grenades, to make them slightly easier to track visually. + +### EF_FIREBALL (EF_ROCKET | EF_GIB) + +This mutually-exclusive bit combo did nothing in the original game, since these special trails could only render one or the other. In the rerelease, it will render a fireball trail that begins yellow and large, tapering off into an orange trail, to mimick the effect on N64. + +### renderfx_t renderfx + +#### RF_NO_ORIGIN_LERP (bit 6) + +This effect had a confusing name originally. Its name now reflects what it does: it disables origin interpolation. + +#### RF_BEAM (bit 7) + +You can now create custom segmented beams by setting a non-one modelindex on beams. + +#### RF_CUSTOMSKIN (bit 8) + +This effect was unused originally. It is now implemented and works as intended: specifying a `skinnum` will change the skin on the model to the skin specified in `CS_IMAGES + skinnum`. For `RF_FLARE`, `frame` must be used instead however, as `skinnum` is used for color data. + +#### RF_NOSHADOW (bit 13) + +This effect was client-only originally. + +#### RF_CASTSHADOW (bit 14) + +This effect marks an entity that casts light in the world; it is only used by `dynamic_light` (or dynamic `light` entities), and should not be used otherwise. + +#### RF_SHELL_LITE_GREEN (bit 19) + +This is the equivalent shell color for `EF_DUALFIRE`. + +#### RF_CUSTOM_LIGHT (bit 20) + +This flag creates a custom dynamic light at the position of the object. Its used in the N64 campaign, as it has custom light entities (`target_light`). `s.frame` is the light's radius, and `s.skinnum` is the light's current color (packed RGB). + +#### RF_FLARE (bit 21) + +This flag marks an entity as being rendered with a flare instead of the usual entity rendering. Flares overload some fields: +* `s.renderfx & RF_SHELL_RED` causes the flare to have an outer red rim. +* `s.renderfx & RF_SHELL_GREEN` causes the flare to have an outer green rim. +* `s.renderfx & RF_SHELL_BLUE` causes the flare to have an outer blue rim. +* `s.renderfx & RF_FLARE_LOCK_ANGLE` causes the flare to not rotate towards the viewer. +* `s.renderfx & RF_CUSTOMSKIN` causes the flare to use the custom image index in `s.frame`. +* `s.modelindex2` is the start distance of fading the flare out. +* `s.modelindex3` is the end distance of fading the flare out. +* `s.skinnum` is the RGBA of the flare. + +#### RF_OLD_FRAME_LERP (bit 22) + +This flag signals that `s.old_frame` should be used for the next frame and respected by the client. This can be used for custom frame interpolation; its use in this engine is specific to fixing interpolation bugs on certain monster animations. + +#### RF_DOT_SHADOW (bit 23) + +Draw a blob shadow underneath the entity. + +#### RF_LOW_PRIORITY (bit 24) + +This flag marks an entity as low priority; if the renderer runs out of entity slots, these entities will be eligible for replacement. For instance, a monster is more important than a gib, so gibs are marked low priority so they can be replaced by a monster if the limit is reached. + +#### RF_NO_LOD (bit 25) + +The original MD2 models will be used for LOD. Setting this bit prevents this behavior. + +#### RF_NO_STEREO (bit 2) + +This is an overloaded flag that only applies to non-rendered entities that contain sounds. If set, stereo panning is disabled on this entity. + +#### RF_STAIR_STEP (bit 26) + +The tick rate increase caused a bit of a visual bug with monsters and players: they now stepped up steps within 0.025 seconds instead of 0.1, causing jarring hitching. To fix this, entities set this flag when they detect they have stepped up a stair, and the client will interpolate their height difference over 0.1 seconds. + +#### RF_BEAM_LIGHTNING (RF_BEAM | RF_GLOW) + +This mutually-exclusive bit combo causes a laser to become a lightning bolt, for N64 support. + +### uint32_t solid + +This was changed from `int32_t` to `uint32_t`, and now packs more data into it to better represent bounding boxes to clients. + +For backwards compatibility, 31 is still the magic value used for BSP entities. The actual packed data, however, is now as follows: + +```cpp +union solid_packed_t +{ + struct { + uint8_t x; + uint8_t y; + uint8_t zd; // always negative + uint8_t zu; // encoded as + 32 + } p; + + uint32_t u; +}; + +// packing: + +packed.p.x = ent->maxs[0]; +packed.p.y = ent->maxs[1]; +packed.p.zd = -ent->mins[2]; +packed.p.zu = ent->maxs[2] + 32; + +// unpacking: +packed.u = state->solid; +ent->mins[0] = -packed.p.x; ent->maxs[0] = packed.p.x; +ent->mins[1] = -packed.p.y; ent->maxs[1] = packed.p.y; +ent->mins[2] = -packed.p.zd; ent->maxs[2] = packed.p.zu - 32; +``` + +This is similar to Quake III Arena, and essentially allows any integral bbox to make it to the clients unchanged. + +### entity_event_t event + +Two new events were added: + +#### EV_OTHER_FOOTSTEP (8) + +Allows non-players to send footsteps. They have idle attenuation, whereas regular footsteps have normal attenuation. + +#### EV_LADDER_STEP (9) + +Ladder climbing 'footstep' event. + +### float alpha + +This value allows you to specify exact transparency values for entities. For backwards compatibility, setting it to zero should be equivalent to an unchanged value, but any non-zero value should be respected as changed. + +### float scale + +This value allows you to scale an entity by the given amount. For backwards compatibility, setting it to zero should be equivalent to an unchanged value, but any non-zero value should be respected as changed. + +### uint8_t instance_bits + +This value is not meant to be set directly by the game code, but will have non-zero bits set for split-screen players that cannot see this entity. + +### float loop_volume / loop_attenuation + +Looping noises can now have volume and attenuation explicitly specified. For both, a value of zero indicates default/unchanged, for backwards compatibility. For `loop_attenuation`, a value of `-1` indicates full level audio (like `ATTN_NONE`). + +### int32_t owner + +An entity's owner is now networked, allowing for it to ignore collision properly. + +### int32_t old_frame + +Only sent when `renderfx & RF_OLD_FRAME_LERP` - indicates that this frame is the frame to lerp from. + +# Import/Exports + +## Game Import + +### (read-only) tick_rate / frame_time_s / frame_time_ms + +This holds the server's tick variables. They will be set at the start of the server, before [PreInit](#preinit). +`tick_rate` stores the tick rate, in hz. +`frame_time_s` is the time a game frame will take in seconds. +`frame_time_ms` is the time a game frame will take in ms. + +These are provided pre-calculated for convenience. + +### Broadcast_Print + +This function writes `message` with the print type of `printlevel` to all players. See [Print Adjustments](#print-adjustments). This is kept for compatibility purposes, [Loc_Print](#loc_print) replaces it. + +### Com_Print + +This function writes `message` to the server. See [Print Adjustments](#print-adjustments). + +### Client_Print + +This function writes out `message` with the print type of `printlevel` to the specified `ent` player. See [Print Adjustments](#print-adjustments). This is kept for compatibility purposes, [Loc_Print](#loc_print) replaces it. + +### Center_Print + +This function writes `message` to the specified `ent` player in the center of their screen. See [Print Adjustments](#print-adjustments). This is kept for compatibility purposes, [Loc_Print](#loc_print) replaces it. + +### sound / positioned_sound + +The `channel` enum has a single new flag: + +#### CHAN_FORCE_POS (bit 5) + +If set (and an origin is **not** supplied), the entity's origin will be forced to be used as the origin point of the sound even if there is a better position available. + +### local_sound + +This function was introduced to deal with some split-screen issues that popped up. It's designed to mimick `localsound` of QuakeC; it will directly send a sound packet to the specified player, using a `dupe_key` if supplied (see [unicast](#unicast)). + +See [sound](#sound--positioned_sound) for info about the channels. + +### get_configstring + +This function fetches a configstring from the servers' current configstring data. + +### Com_Error + +See [Print Adjustments](#print-adjustments). + +### clip + +This is a new function designed to fit a specific purpose: it will test if the box specified by `mins` & `maxs`, moved from `start` to `end`, will clip against the specified `entity` with the given `contentmask`. As an example, you could use this to detect if an entity is actually intersecting a brush in a trigger instead of just being within its bounding box. + +### inPVS / inPHS + +This function now accepts a boolean, `portals`, which changes whether or not it should ignore areaportals. + +### BoxEdicts + +This function was modified with a simple filtering callback, greatly extending its purpose and removing some limitations that would occur with previous uses. The filter callback is called for every entity discovered, and you can choose to include or skip entities that it finds, or even completely abort the search. In addition, you can now call the function with a 0 `maxcount`, and the function will still continue to filter and find entities, reporting the final count. To match the old behavior, if a non-zero `maxcount` is supplied, the return count will cap out at `maxcount`. + +Note that it is disallowed to modify world links (linkentity/unlinkentity, etc) in a filter callback, it can only be used for filtering. + +### unicast + +The `dupe_key` parameter is new, and is to solve a very peculiar issue with split screen players. When unicast is used to spawn effects or sounds, it may not be desirable to replay the same effect on multiple split screens, since split screen players are all the same client and share views. For example, if you do a unicast for a `TE_BLASTER` somewhere in the world for every player, for a split screen client with 4 players, that effect will play 4 times - even though all four players are viewing the same world. The game DLL also has no knowledge or understanding of split screen, so there's no way for the game to work around it. + +Instead of having the game need to know this kind of implementation detail and prevent double-sending, for effects that are going to potentially be sent to multiple players that *may* be on a split screen, you can specify a dupe key value. This value, when non-zero, will be marked as "already sent" for that client, and won't be sent again for the next packet if it was already tripped. The game DLL provides the `GetUnicastKey` global which will give you a rolling value to directly pass into unicast or local_sound. + +### WriteFloat + +Implemented; this was stubbed out of the old code. + +### WritePosition + +Now sends full float positions. + +### WriteDir / WriteAngle + +Unchanged - WriteAngle is for compressed angles, when high precision is not necessary. + +### WriteEntity + +New function to write an entity, to make it easier to write them without needing to WriteShort directly. + +### GetExtension + +See [Extensions](#extensions). + +### Bot_RegisterEdict + +Informs the bot subsystem that an entity needs to be registered. + +### Bot_UnRegisterEdict + +Informs the bot subsystem that an entity needs to be unregistered. + +### Bot_MoveToPoint + +Forces a bot to move to the specified point. + +### Bot_FollowActor + +Forces a bot to follow the specified actor. + +### GetPathToGoal + +The main pathfinding function; with the given pathfinding `request`, you'll be given `info` about the operation, the path, etc. + +### Loc_Print + +The new primary entry point for printing. This function replaces all of the others (except Com_Print). +For basic usage, it can be called on an entity (or nullptr for broadcasting) with the correct `level`, with the message to send in `base`, and nullptr `args` along with 0 `num_args`. For actual localized messages, however, you can send additional arguments via the `args`/`num_args` parameters which are sent to the client for further processing. + +In addition to localization, `level` now has new values and bit flags. + +#### PRINT_TYPEWRITER (4) + +Causes the message to be printed out one at a time, like a typewriter. Used for objectives, similar to the N64 version. + +#### PRINT_CENTER (5) + +An instant centerprint, like the legacy centerprints. + +#### PRINT_TTS (6) + +Identical to `PRINT_HIGH` in importance, but additionally causes text to speech narration to activate if enabled on the client. + +#### PRINT_BROADCAST (bit 3) + +Message will be sent to all players. + +#### PRINT_NO_NOTIFY (bit 4) + +Message will not be sent to the notify system. + +### Draw_Line / Draw_Point / Draw_Circle / Draw_Bounds / Draw_Sphere / Draw_OrientedWorldText / Draw_StaticWorldText / Draw_Cylinder / Draw_Ray + +These functions are debugging aids that only render on the server. + +### ReportMatchDetails_Multicast + +This function is solely for platforms that need match result data. + +### ServerFrame + +Returns the server's frame number. + +### SendToClipBoard + +Copy data to the server's clipboard, useful for debugging. + +### Info_ValueForKey / Info_RemoveKey / Info_SetValueForKey + +See [Info Keys](#info-keys). + +## Save Games + +One of the major changes to this release of Quake II is the save system. Instead of storing pointer offsets and copies of memory, the level & game data is written to UTF-8 JSON. This makes save data much easier to navigate for a human & developer that wants to look into a bug, while also being quick and efficient for storage. + +The save system, as a result, no longer interfaces with the filesystem at all. Other mods are not required to use JSON, any text format will work as the server and client do not interact with the data. + +## Game Export + +### (read-only) apiversion + +The version # reported by the server. + +### PreInit + +This function is called before InitGame, and should be where you initialize your mod's latched cvars. This can be used to fix any conflicting latched cvars, which will be "locked in" after this is called. + +### SpawnEntities + +All three parameters are now properly marked const. + +### WriteGameJson + +See [Save Games](#save-games). + +### ReadGameJson + +See [Save Games](#save-games). + +### WriteLevelJson + +This function is now informed whether the level write is from a level transition or a manual save. +See [Save Games](#save-games). + +### ReadLevelJson + +See [Save Games](#save-games). + +### CanSave + +This new export now dictates whether the game is saveable or not. + +### ClientChooseSlot + +ClientChooseSlot is intended to take in a bunch of information about the client that is connecting, and choose which `edict_t` entity this player should occupy. It is used in the rerelease to reorder players consistently throughout coop games, and ensure that everybody always gets the correct slot. + +Callers are given the player's `userinfo` and `social_id` (the social ID is a unique value per player on certain platforms), which you can use to find the correct slot from the current saved client data. You're also told whether the client `isBot`, which should always use non-saved available slots first. The `ignore` field will give you a list of slots up to `num_ignore` entities that are already occupied or were reported as such, so they can be safely skipped over. Finally, the `cinematic` parameters will tell you whether the loaded map is a video, which in most cases reordering will not be necessary. + +### ClientConnect + +The function is now given the `social_id` and `isBot` state of the connecting client. + +### RunFrame + +This function now receives a boolean to tell whether the call is from the main game loop, or from some other source (the game is settling, or running frames to advance level transitions). If the latter is occurring, you can use this boolean to speed up level transitions by skipping logic that is not necessary but is CPU-intensive, such as enemies searching for players to attack. + +### PrepFrame + +This function used to be in the server, but is now controlled by the game DLL. It's ran after the game has execute a frame & has sent the packet data over to all players. Things like hit markers and one-shot events are cleared in here. + +### edict_size / num_edicts / max_edicts + +These were changed to size_t and uint32_t/uint32_t respectively, to better represent their use. + +### server_flags + +This is an integer shared between server and game, which stores bits for special states that the server cares about. + +### Pmove + +See [Pmove](#pmove). + +### GetExtension + +See [Extensions](#extensions). + +### Bot_SetWeapon + +Called by the bot subsystem to switch weapons. + +### Bot_TriggerEdict + +Called by the bot subsystem to trigger an entity. + +### Bot_UseItem + +Called by the bot subsystem to use an item. + +### Bot_GetItemID + +Fetch an item ID by a classname; for the bot subsystem. + +### Entity_IsVisibleToPlayer + +This function is for item instancing; the rerelease of Quake II supports instanced items, which will display only for the players who haven't picked it up yet. For online players, this simply removes the item if you've gotten it, but for split screen players it will show a ghost where the entity was on players that have already picked it up. + +### GetShadowLightData + +This function fetches data for the given shadow light for building client frames. + +## Player State + +In the original client, player state was often accessed directly to perform various tasks or render things. Much of this has been moved into the cgame module to allow increased customization. + +## Client Game Import + +### (read-only) tick_rate +### (read-only) frame_time_s +### (read-only) frame_time_ms + +This holds the server's tick variables. They will be set at the start of the client, before Init. +`tick_rate` stores the tick rate, in hz. +`frame_time_s` is the time a game frame will take in seconds. +`frame_time_ms` is the time a game frame will take in ms. + +These are provided pre-calculated for convenience. + +### Com_Print + +Print a debug message to the client. + +### get_configstring + +Fetch the given configstring data from the client. + +### Com_Error + +Abort error for client. + +### TagMalloc / TagFree / FreeTags + +Same as server. + +### cvar / cvar_set / cvar_forceset + +Same as server. + +### AddCommandString + +Push command(s) into the command buffer on the client side. + +### GetExtension + +See [Extensions](#extensions). + +### CL_FrameValid + +Returns true if the current frame being rendered is valid. + +### CL_FrameTime + +Returns the current frame time delta. + +### CL_ClientTime + +Returns the client's current time (server-bound). + +### CL_ClientRealTime + +Returns the client's current real, unbound time. + +### CL_ServerFrame + +Returns the client's server frame. + +### CL_ServerProtocol + +Returns the client's connected server protocol. + +### CL_GetClientName + +Returns a UTF-8 string containing the givern player's name. + +### CL_GetClientPic + +Returns a string containing the given player's icon. + +### CL_GetClientDogtag + +Returns a string containing the given player's dogtag. + +### CL_GetKeyBinding + +Returns a key binding for the given key. Returns an empty string if the key is unbound. + +### Draw_RegisterPic + +Precache the given image. + +### Draw_GetPicSize + +Returns the size of the given image. + +### SCR_DrawChar + +Draw the given conchars char at the specified position. A `shadow` parameter has been added to draw a drop shadow. + +### SCR_DrawPic + +Draw the given pic at the specified position. + +### SCR_DrawColorPic + +Draw the given pic at the specified location, with the specified color. + +### SCR_SetAltTypeface + +Change whether the alternate (accessibility) typeface is in use or not. + +### SCR_DrawFontString + +Draw a string to the screen, using the Kex KFONT which includes non-latin characters. + +### SCR_MeasureFontString + +Measure the size of the string as it would be rendered. + +### SCR_FontLineHeight + +Returns the line height of the font. + +### CL_GetTextInput + +Returns a pointer to the current text input, and whether this input is for team say or not. + +### CL_GetWarnAmmoCount + +For the given weapon ID, get the amount that is considered to be low ammo. + +### Localize + +Localize the given string and arguments to an output buffer. + +### SCR_DrawBind + +Draw a user bind to the screen, returns the Y offset from rendering. + +### CL_InAutoDemoLoop + +Returns true if the engine is running the attract demo loop. + +## Client Game Export + +### (read-only) apiversion + +API version. + +### Init / Shutdown + +Lifecycle functions for the client game. Note that the cgame does not control UI, so the cgame only exists when you are connected and in-game. + +### DrawHUD + +This function is called by the client when their HUD needs to be rendered. +- `isplit` contains the split screen index of the player. +- `data` contains a pointer to some transient information from the server. This includes currently active layout, and the player's active inventory when the inventory is open. +- `hud_vrect` contains the unpadded rectangle of the HUD being rendered. +- `hud_safe` contains the size of the safe area. Only x and y are set, w and h are unused. +- `scale` is the integral scale of the HUD being rendered. +- `playernum` is the player's client index. +- `ps` is a pointer to the player's current player state. + +### TouchPics + +Function called for precaching images used by the HUD. + +### LayoutFlags + +For the given player state, return the `layout_flags_t` that would match it. + +### GetActiveWeaponWheelWeapon / GetOwnedWeaponWheelWeapons / GetWeaponWheelAmmoCount / GetPowerupWheelCount + +The weapon wheel is in the client, but uses these callbacks to fetch data from `player_state_t`. + +### GetHitMarkerDamage + +Returns how much damage was done for this player. + +### Pmove + +See [Pmove](#pmove). + +### ParseConfigString + +When a configstring is received, the cgame is also notified of changes. The cgame module can react to configstring updates here. + +### ParseCenterPrint + +When a centerprint-like message is received by the client, it is sent to the cgame via this function. +- `isplit` is the split screen player it was sent to. +- `instant` is true if the message is a centerprint that is drawn without the typewriter effect. + +### ClearNotify + +The client will call this when the notification area should be cleared. + +### ClearCenterprint + +The client will call this when centerprints should be cleared. + +### NotifyMessage + +When a notify message is received, the client will send it to this function. +- `isplit` is the split screen player it was sent to. +- `is_chat` is true if it was a chat-like message. + +### GetMonsterFlashOffset + +To simplify the server to client muzzleflash communication, the cgame now exports muzzleflash origins via this function. + +### GetExtension + +See [Extensions](#extensions). + +# Quake II server protocol - version 2023 + +The Quake II rerelease features an updated server protocol. Most of the messages are backwards compatible, but some needed adjustments to work with new or changed features, or raised limits. + +This document will only outline the changes since the original release, rather than the whole protocol. + +## (out of band, client <-> server) challenges + +The out of band challenges have been removed. + +## (out of band, client -> server) connect + +The `connect` message is similar to the original, but has redundant information removed. Port and challenge are handled at a lower level, so that information is not included. The `connect` message is in the following format: + +`connect {protocol} {num split} {socials...} {userinfo...}` + +- `protocol` is 2023 +- `num split` is the number of split screen players +- `socials` is `num split` number of arguments containing each players' social identifiers +- `userinfo` is the clients' userinfo string, split up by groups of 510 characters each (since command arguments have a maximum length). This can often span 2 or more arguments, since each userinfo var has a value per player. See [Info Keys](#info-keys). + +## (out of band, server -> client) client_connect + +This message is sent when the server accepts the connection. It is in the following format: + +`client_connect {protocol}` + +- `protocol` is 2023 + +The protocol version is sent mainly for backward compatibility with demos. + +## (packet, server -> client) svc_muzzleflash (1) + +The following enum values are now accepted. + +### MZ_BFG2 (19) + +Secondary muzzleflash for the BFG, sent when the BFG actually fires. + +### MZ_PHALANX2 (20) + +Secondary muzzleflash for the Phalanx, sent for the second projectile. + +### MZ_PROX (31) + +Sent when the Prox Launcher is fired. + +### MZ_ETF_RIFLE_2 (32) + +Sent when the other barrel of the ETF Rifle is fired. + +## (packet, server -> client) svc_muzzleflash2 (2) + +### MZ2_BOSS2_MACHINEGUN_L2 / MZ2_BOSS2_MACHINEGUN_R2 (74 / 134) + +These two values were just copies of L1/R1, but were repurposed to make Hyperblaster-specific sounds for the new Hornet. + +The following enum values are now accepted. + +### MZ2_SOLDIER_RIPPER_1 - MZ2_SOLDIER_HYPERGUN_8 (211 - 226) + +Muzzleflashes for the ripper & blue hyperblaster guards. + +### MZ2_GUARDIAN_BLASTER - MZ2_ARACHNID_RAIL_UP2 (227 - 231) + +Muzzleflashes for the PSX monsters. + +### MZ2_INFANTRY_MACHINEGUN_14 - MZ2_INFANTRY_MACHINEGUN_21 (232 - 239) + +Muzzleflashes for the Infantry's run-attack animation. + +### MZ2_GUNCMDR_CHAINGUN_1 - MZ2_GUNCMDR_GRENADE_CROUCH_3 (240 - 250) + +Muzzleflashes for the Gunner Commander. + +### MZ2_SOLDIER_BLASTER_9 - MZ2_SOLDIER_HYPERGUN_9 (251 - 255) + +Muzzleflashes for the guards' new prone-firing animation. + +## (packet, server -> client) svc_muzzleflash3 (32) + +This packet was necessitated from running out of bits in svc_muzzleflash2. The only difference is the byte for `id` is a ushort. + +### MZ2_GUNNER_GRENADE2_1 - MZ2_GUNNER_GRENADE2_4 (256 - 259) + +Alternate firing animation for the Gunner's grenade launcher. + +### MZ2_INFANTRY_MACHINEGUN_22 (260) + +Alternate firing animation for the Infantry. + +### MZ2_SUPERTANK_GRENADE_1 (261 - 262) + +Supertank's grenade launcher. + +### MZ2_HOVER_BLASTER_2 / MZ2_DAEDALUS_BLASTER_2 (263 / 264) + +The Icarus and Daedalus' opposite side blaster. + +### MZ2_MEDIC_HYPERBLASTER1_1 - MZ2_MEDIC_HYPERBLASTER1_12 / MZ2_MEDIC_HYPERBLASTER2_1 - MZ2_MEDIC_HYPERBLASTER2_12 (265 - 276 / 277 - 288) + +The Medic and Medic Commander's Hyperblaster firing animation sweep. + +## (packet, server -> client) svc_temp_entity (3) + +As documented in [WritePosition](#writeposition), WritePos now writes full float precision, so ReadPos has to read full float. + +### TE_SPLASH (10) + +The "color/splash" enumeration accepts a new value: + +#### SPLASH_ELECTRIC (7) + +A spark used exclusively in N64, which spawns blue/white particles and makes sparking noises. + +The following new enum values are accepted: + +### TE_RAILTRAIL2 (31) + +This effect was unused in Quake II, and was retooled to a lighter railgun effect used for Instagib mode. + +### TE_BLUEHYPERBLASTER (56) +"Correct" version of the old buggy `TE_BLUEHYPERBLASTER`, which is now `TE_BLUEHYPERBLASTER_DUMMY`. +- ReadPos +- ReadDir + +### TE_BFG_ZAP (57) +Laser when an entity has been zapped by a BFG explosion. +- ReadPos (start) +- ReadPos (end) + +### TE_BERSERK_SLAM (58) +Large blue flash & particles at impact point towards a direction. +- ReadPos +- ReadDir + +### TE_GRAPPLE_CABLE_2 (59) +The grappling hook in Quake II 3.20 used a larger message that didn't allow the cable to render like other player-derived beams. +- ReadEntity +- ReadPos (start) +- ReadPos (end) + +### TE_POWER_SPLASH (60) +Effect sent when a power shield evaporates. +- ReadEntity +- ReadByte (1 for screen, 0 for armor) + +### TE_LIGHTNING_BEAM (61) +A lightning bolt that originates from the player, like the heat beam. Unused. +- ReadEntity +- ReadPos (start) +- ReadPos (end) + +### TE_EXPLOSION1_NL / TE_EXPLOSION2_NL (62 / 63) +Variants of explosion that don't include any dynamic light. +- ReadPos + +## (packet, server -> client) svc_sound (9) + +Since `MAX_EDICTS` is now 8192, this packet required changes to support higher entity numbers. `MAX_SOUNDS` being increased to 1024 also necessitated the sound index changing from byte to ushort. + +- ReadByte (flags) +- ReadShort (soundindex) +- [if flags & SND_VOLUME] ReadByte (volume) +- [if flags & SND_ATTENUATION] ReadByte (attenuation) +- [if flags & SND_OFFSET] ReadByte (offset) +- [if flags & SND_ENT] + - [if flags & SND_LARGE_ENT] ReadLong (entchan) + - [if !(flags & SND_LARGE_ENT)] ReadShort (entchan) +- [if flags & SND_POS] ReadPos (origin) + +`entchan` is encoded as such: +``` +struct sndchan_t +{ + uint8_t channel : 3; + uint32_t entity : 29; +} +``` + +`flags` contains the following bits: +- SND_VOLUME (bit 0) +- SND_ATTENUATION (bit 1) +- SND_POS (bit 2) +- SND_ENT (bit 3) +- SND_OFFSET (bit 4) +- SND_EXPLICIT_POS (bit 5) +- SND_LARGE_ENT (bit 6) + +Note that `SND_POS` is **always** set. This is to fix a legacy bug where sounds played on entities outside of your PVS will play at the origin instead of their real location. The client should pick the real position if the entity is in their frame, but otherwise fall back to the sound packets' position. + +## (packet, server -> client) svc_print (10) + +This packet now supports `PRINT_TYPEWRITER` and `PRINT_CENTER` values. See [Loc_Print](#loc_print). + +## (packet, server -> client) svc_stufftext (11) + +For security reasons, this packet will only allow commands things to be executed. + +## (packet, server -> client) svc_serverdata (12) + +- ReadLong (protocol) +- ReadLong (spawncount) +- ReadByte (0 = game, 1 = demo, 2 = server demo) +- ReadByte (tickrate) +- ReadString (gamedir) +- ReadShort[N] (playernums; see below) +- ReadString (level name) + +To parse `playernums`, read the first short and check its value. If it is -2, then read an additional short, which is the number of split screen entities to follow. Read that number of shorts to get each entity number for each split screen player. Otherwise, the value returned by the initial ReadShort is the playernum of the client. + +The special value -1 will be used in cinematics, to indicate that the player has no entity. + +## (packet, server -> client) svc_frame (20) + +- ReadLong (serverframe) +- ReadLong (deltaframe) +- ReadByte (surpressCount) + +For each player in this client's `numSplit` the following data is parsed: + +- ReadByte (areabits length) +- ReadData (using above byte) +- ReadByte (value will be `svc_playerinfo`) +- ParsePlayerState (see [svc_playerinfo](#svc_playerinfo-17)) + +Then, back to `svc_frame` data: + +- client entity `event`s should all be cleared back to `EV_NONE` +- ReadByte (value will be `svc_packetentities`) +- ParsePacketEntities (see [svc_packetentities](#svc_packetentities-18)) + +### svc_playerinfo (17) + +#### Bits + +```c +#define PS_M_TYPE (1<<0) +#define PS_M_ORIGIN (1<<1) +#define PS_M_VELOCITY (1<<2) +#define PS_M_TIME (1<<3) +#define PS_M_FLAGS (1<<4) +#define PS_M_GRAVITY (1<<5) +#define PS_M_DELTA_ANGLES (1<<6) + +#define PS_VIEWOFFSET (1<<7) +#define PS_VIEWANGLES (1<<8) +#define PS_KICKANGLES (1<<9) +#define PS_BLEND (1<<10) +#define PS_FOV (1<<11) +#define PS_WEAPONINDEX (1<<12) +#define PS_WEAPONFRAME (1<<13) +#define PS_RDFLAGS (1<<14) + +#define PS_MOREBITS (1<<15) + +// [Paril-KEX] +#define PS_DAMAGE_BLEND (1<<16) +#define PS_TEAM_ID (1<<17) +``` + +#### Data + +- ReadUShort (flags) +- [if flags & PS_MOREBITS] flags |= ReadUShort \<\< 16 +- [if flags & PS_M_TYPE] ReadByte (pm_type) +- [if flags & PS_M_ORIGIN] ReadPos (pm_origin) +- [if flags & PS_M_VELOCITY] ReadPos (pm_velocity) +- [if flags & PS_M_TIME] ReadUShort (pm_time) +- [if flags & PS_M_FLAGS] ReadUShort (pm_flags) +- [if flags & PS_M_GRAVITY] ReadShort (pm_gravity) +- [if flags & PS_M_DELTA_ANGLES] ReadPos (pm_delta_angles) +- [if flags & PS_VIEWOFFSET]: +```cpp + viewoffset_x = ReadShort() * (1.f / 16.f) + viewoffset_y = ReadShort() * (1.f / 16.f) + viewoffset_z = ReadShort() * (1.f / 16.f) + viewheight = ReadChar() // note: not in protocol 2022 +``` +- [if flags & PS_VIEWANGLES] ReadPos (viewangles) +- [if flags & PS_KICKANGLES] +```cpp +kick_angles_x = ReadShort() / 1024.f +kick_angles_y = ReadShort() / 1024.f +kick_angles_z = ReadShort() / 1024.f +``` +- [if flags & PS_WEAPONINDEX] +```cpp +gunindex_temp = ReadUShort() +gunskin = (gunindex_temp & 0xE000) >> 13 +gunindex = gunindex_temp & ~0xE000 +``` +- [if flags & PS_WEAPONFRAME] +```cpp +#define GUNBIT_OFFSET_X (1<<0) +#define GUNBIT_OFFSET_Y (1<<1) +#define GUNBIT_OFFSET_Z (1<<2) +#define GUNBIT_ANGLES_X (1<<3) +#define GUNBIT_ANGLES_Y (1<<4) +#define GUNBIT_ANGLES_Z (1<<5) +#define GUNBIT_GUNRATE (1<<6) +``` +```cpp +gunframe_temp = ReadUShort() +gun_bits = (gunframe_temp & 0xFE00) >> 9 +gunframe = (gunframe_temp & ~0xFE00) + +[if gun_bits & GUNBIT_OFFSET_X] gunoffset_x = ReadFloat() +[if gun_bits & GUNBIT_OFFSET_Y] gunoffset_y = ReadFloat() +[if gun_bits & GUNBIT_OFFSET_Z] gunoffset_z = ReadFloat() +[if gun_bits & GUNBIT_ANGLES_X] gunangles_x = ReadFloat() +[if gun_bits & GUNBIT_ANGLES_Y] gunangles_y = ReadFloat() +[if gun_bits & GUNBIT_ANGLES_Z] gunangles_z = ReadFloat() +[if gun_bits & GUNBIT_GUNRATE] gunrate = ReadByte() +``` +- [if flags & PS_BLEND] +```cpp +screen_blend_r = ReadByte() / 255.f +screen_blend_g = ReadByte() / 255.f +screen_blend_b = ReadByte() / 255.f +screen_blend_a = ReadByte() / 255.f +``` +- [if flags & PS_FOV] ReadByte(fov) +- [if flags & PS_RDFLAGS] ReadByte(rdflags) +- ReadLong(statbits) +```cpp +for (i = 0; i < 32; i++) + if (statbits & (1 << i)) + ReadShort(stats[i]) +``` +- ReadLong(morestatbits) +```cpp +for (i = 32; i < 64; i++) + if (morestatbits & (1 << (i - 32))) + ReadShort(stats[i]) +``` +- [if flags & PS_DAMAGE_BLEND] +```cpp +damage_blend_r = ReadByte() / 255.f +damage_blend_g = ReadByte() / 255.f +damage_blend_b = ReadByte() / 255.f +damage_blend_a = ReadByte() / 255.f +``` +- [if flags & PS_TEAM_ID] +```cpp +team_id = ReadByte() +``` + +### svc_packetentities (18) + +#### Bits +```c + +// try to pack the common update flags into the first byte +#define U_ORIGIN1 (1<<0) +#define U_ORIGIN2 (1<<1) +#define U_ANGLE2 (1<<2) +#define U_ANGLE3 (1<<3) +#define U_FRAME8 (1<<4) // frame is a byte +#define U_EVENT (1<<5) +#define U_REMOVE (1<<6) // REMOVE this entity, don't add it +#define U_MOREBITS1 (1<<7) // read one additional byte + +// second byte +#define U_NUMBER16 (1<<8) // NUMBER8 is implicit if not set +#define U_ORIGIN3 (1<<9) +#define U_ANGLE1 (1<<10) +#define U_MODEL (1<<11) +#define U_RENDERFX8 (1<<12) // fullbright, etc +#define U_EFFECTS8 (1<<14) // autorotate, trails, etc +#define U_MOREBITS2 (1<<15) // read one additional byte + +// third byte +#define U_SKIN8 (1<<16) +#define U_FRAME16 (1<<17) // frame is a short +#define U_RENDERFX16 (1<<18) // 8 + 16 = 32 +#define U_EFFECTS16 (1<<19) // 8 + 16 = 32 +#define U_MODEL2 (1<<20) // weapons, flags, etc +#define U_MODEL3 (1<<21) +#define U_MODEL4 (1<<22) +#define U_MOREBITS3 (1<<23) // read one additional byte + +// fourth byte +#define U_OLDORIGIN (1<<24) // FIXME: get rid of this +#define U_SKIN16 (1<<25) +#define U_SOUND (1<<26) +#define U_SOLID (1<<27) +#define U_MODEL16 (1<<28) +#define U_EFFECTS64 (1<<29) // [Edward-KEX] +#define U_ALPHA (1<<30) // [Paril-KEX] +#define U_MOREBITS4 (1<<31) // [Paril-KEX] read one additional byte +#define U_SCALE (1ull<<32ull) // [Paril-KEX] +#define U_INSTANCE (1ull<<33ull) // [Paril-KEX] +#define U_OWNER (1ull<<34ull) // [Paril-KEX] +#define U_OLDFRAME (1ull<<35ull) // [Paril-KEX] +``` + +#### Data + +The regular process for deltaing entities has not changed, but the data bits have. + +ParseEntityBits: +- ReadByte(bits) +- [if bits & U_MOREBITS1] bits |= ReadByte() \<\< 8 +- [if bits & U_MOREBITS2] bits |= ReadByte() \<\< 16 +- [if bits & U_MOREBITS3] bits |= ReadByte() \<\< 24 +- [if bits & U_MOREBITS4] bits |= ReadByte() \<\< 32 +- [if bits & U_NUMBER16] ReadShort(number) [else] ReadByte(number) + +ParseDelta: +- [if bits & U_MODEL16] +```cpp +ReadShort(modelindex) +ReadShort(modelindex2) +ReadShort(modelindex3) +ReadShort(modelindex4) +``` +- [else] +```cpp +ReadByte(modelindex) +ReadByte(modelindex2) +ReadByte(modelindex3) +ReadByte(modelindex4) +``` +- [if bits & U_FRAME8] ReadByte(frame) +- [if bits & U_FRAME16] ReadShort(frame) +- [if bits & (U_SKIN8 | U_SKIN16) == (U_SKIN8 | U_SKIN16)] ReadLong(skinnum) +- [elseif bits & U_SKIN8] ReadByte(skinnum) +- [elseif bits & U_SKIN16] ReadUShort(skinnum) +- [if bits & (U_EFFECTS8 | U_EFFECTS16 | U_EFFECTS64)] +```cpp +// if 64-bit effects are sent, the low bits are sent first +// and the high bits come after. +[if bits & U_EFFECTS64] ReadULong(loeffects) + +[if bits & (U_EFFECTS8 | U_EFFECTS16) == (U_EFFECTS8 | U_EFFECTS16)] ReadULong(effects) +[elseif bits & U_EFFECTS16] ReadUShort(effects) +[else] ReadByte(effects) + +[if bits & U_EFFECTS64] effects = (effects << 32) | loeffects +``` +- [if bits & (U_RENDERFX8 | U_RENDERFX16) == (U_RENDERFX8 | U_RENDERFX16)] ReadLong(effects) +- [elseif bits & renderfx] ReadByte(renderfx) +- [elseif bits & U_RENDERFX16] ReadShort(renderfx) +- [if bits & U_SOLID] ReadULong(solid) +```cpp +// note: for the protocol in the demos (2022), if `solid` is zero, +// then the following reads are lower precision, using ReadShort() * (1.f / 8.f) +[if bits & U_ORIGIN1] ReadFloat(origin_x) +[if bits & U_ORIGIN2] ReadFloat(origin_y) +[if bits & U_ORIGIN3] ReadFloat(origin_z) +[if bits & U_OLDORIGIN] ReadPos(oldorigin) +``` +- [if bits & U_ANGLE1] ReadFloat(angle_x) +- [if bits & U_ANGLE2] ReadFloat(angle_y) +- [if bits & U_ANGLE3] ReadFloat(angle_z) +- [if bits & U_SOUND] ReadUShort(temp_sound) +```cpp +bool has_volume = temp_sound & 0x4000 +bool has_attenuation = temp_sound & 0x8000; + +// the sound index takes up 14 bits +sound = temp_sound & ~(0x4000 | 0x8000) + +[if has_volume] loop_volume = ReadByte() / 255.f +[else] loop_volume = 1.f + +[if has_attn] loop_attenuation = ReadByte() +[else] loop_attenuation = ATTN_STATIC +``` +- [if bits & U_EVENT] ReadByte(event) [else] event = 0 +- [if bits & U_ALPHA] alpha = ReadByte() / 255.f +- [if bits & U_SCALE] scale = ReadByte() / 16.f +- [if bits & U_INSTANCE] ReadByte(instance_bits) +- [if bits & U_OWNER] ReadShort(owner) +- [if bits & U_OLDFRAME] ReadUShort(old_frame) + +## (packet, server -> client) svc_splitclient (21) + +This packet indicates to the client which split screen player the next messages are directed towards, for unicast messages. + +- ReadByte (isplit) + +Note that `isplit` will be offset by 1 (that is to say, a value of 1 indicates split screen client 0). + +## (packet, server -> client) svc_configblast (22) + +Compressed configstring data. This is to make connection faster by sending fewer packets. + +- ReadShort (compressed size) +- ReadShort (uncompressed size) +- ReadByte[compressed size] (buffer) + +The received `buffer` is directly passed through to zlib's `uncompress`. After decompression, until the buffer is exhausted, the following data repeats: +- ReadUShort (index) +- ReadString (str) + +## (packet, server -> client) svc_spawnbaselineblast (23) + +Compressed baseline data. This is to make connection faster by sending fewer packets. + +- ReadShort (compressed size) +- ReadShort (uncompressed size) +- ReadByte[compressed size] (buffer) + +The received `buffer` is directly passed through to zlib's `uncompress`. After decompression, until the buffer is exhausted, read in the data contained in a `svc_spawnbaseline` packet. + +## (packet, server -> client) svc_level_restart (24) + +Sent when the server executes a `restart_level` command. The client should be prepared to do a "soft wipe" of their state, but might want to defer it until the full frame is read since effects might come in after this command is executed. + +This message's data contains configstrings that were changed by restarting the level. The following should be repeated until an exit condition is met: + +- ReadShort (id) +- [if id is -1, exit] +- ReadString (str) + +## (packet, server -> client) svc_damage (25) + +This message is sent after accumulating damage on a player. It gives the player a rough idea of the damage they're receiving and from where. + +- ReadByte (count) + +For `count` number of loops, read the following: + +- ReadByte (encoded) +- ReadDir + +`encoded` is in the following format: +``` +struct packed_damage_t +{ + uint8_t damage : 5; + uint8_t health : 1; + uint8_t armor : 1; + uint8_t shield : 1; +} +``` + +`health` provides a `1,0,0` addition to color. +`armor` provides a `1,1,1` addition to color. +`shield` provides a `0,1,0` addition to color. + +The `damage` value is also divided by 3, so multiplying it by 3 will get you an approximation of the real damage amount. + +The color is then normalized. + +## (packet, server -> client) svc_locprint (26) + +This packet is the new entry point for prints. + +- ReadByte (flags) +- ReadString (base) +- ReadByte (num args) +- ReadString[num args] (args) + +The `base` string is a `fmtlib` formatted string. + +The information in [Print Adjustments](#print-adjustments) and [Loc_Print](#loc_print) explains how formatting works. + +## (packet, server -> client) svc_fog (27) + +```cpp +enum bits_t : uint16_t +{ + // global fog + BIT_DENSITY = bit_v<0>, + BIT_R = bit_v<1>, + BIT_G = bit_v<2>, + BIT_B = bit_v<3>, + BIT_TIME = bit_v<4>, // if set, the transition takes place over N milliseconds + + // height fog + BIT_HEIGHTFOG_FALLOFF = bit_v<5>, + BIT_HEIGHTFOG_DENSITY = bit_v<6>, + BIT_MORE_BITS = bit_v<7>, // read additional bit + BIT_HEIGHTFOG_START_R = bit_v<8>, + BIT_HEIGHTFOG_START_G = bit_v<9>, + BIT_HEIGHTFOG_START_B = bit_v<10>, + BIT_HEIGHTFOG_START_DIST= bit_v<11>, + BIT_HEIGHTFOG_END_R = bit_v<12>, + BIT_HEIGHTFOG_END_G = bit_v<13>, + BIT_HEIGHTFOG_END_B = bit_v<14>, + BIT_HEIGHTFOG_END_DIST = bit_v<15> +}; +``` + +- ReadByte (bits) +- [if bits & BIT_MORE_BITS] ReadByte (morebits), bits |= (morebits \<\< 8) +- [if bits & BIT_DENSITY] ReadFloat (density) +- [if bits & BIT_DENSITY] ReadByte (skyfactor) +- [if bits & BIT_R] ReadByte (red) +- [if bits & BIT_G] ReadByte (green) +- [if bits & BIT_B] ReadByte (blue) +- [if bits & BIT_TIME] ReadUShort (time) +- [if bits & BIT_HEIGHTFOG_FALLOFF] ReadFloat (heightfog falloff) +- [if bits & BIT_HEIGHTFOG_DENSITY] ReadFloat (heightfog density) +- [if bits & BIT_HEIGHTFOG_START_R] ReadByte (heightfog start red) +- [if bits & BIT_HEIGHTFOG_START_G] ReadByte (heightfog start green) +- [if bits & BIT_HEIGHTFOG_START_B] ReadByte (heightfog start blue) +- [if bits & BIT_HEIGHTFOG_START_DIST] ReadLong (heightfog start distance) +- [if bits & BIT_HEIGHTFOG_END_R] ReadByte (heightfog end red) +- [if bits & BIT_HEIGHTFOG_END_G] ReadByte (heightfog end green) +- [if bits & BIT_HEIGHTFOG_END_B] ReadByte (heightfog end blue) +- [if bits & BIT_HEIGHTFOG_END_DIST] ReadLong (heightfog end distance) + +## (packet, server -> client) svc_waitingforplayers (28) + +Sent when there are players waiting to join before the game can start (or zero if all players are in). + +- ReadByte (count) + +## (packet, server -> client) svc_bot_chat (29) + +Bots talking to players. + +- ReadString (bot name) +- ReadShort (client index, or 256 if no particular player) +- ReadString (loc string) + +## (packet, server -> client) svc_poi (30) + +Spawn a POI. + +```cpp +enum svc_poi_flags +{ + POI_FLAG_NONE = 0, + POI_FLAG_HIDE_ON_AIM = 1, // hide the POI if we get close to it with our aim +}; +``` + +- ReadUShort (key) +- ReadUShort (time) +- ReadPos (pos) +- ReadUShort (image index) +- ReadByte (palette index) +- ReadByte (flags) + +If a non-zero `key` is specified, only one of that POI key can exist at any given time. If `time` is 0xFFFF, the POI that matches the key will be removed. + +If `time` is zero, the POI will last forever, `key` should be set in order to allow the POI to be cleaned up later. + +## (packet, server -> client) svc_help_path (31) + +Spawns the Compass help path effect at the given location. + +- ReadByte (start) +- ReadPos (pos) +- ReadDir (dir) + +## (packet, server -> client) svc_achievement (32) + +- ReadString (id) + +## (packet, client -> server) clc_stringcmd (4) + +- ReadByte (isplit) +- ReadString (s) + +Note that `isplit` is offset by 1, so `1` is the first split screen client. \ No newline at end of file diff --git a/fgd/GameConfig.cfg b/fgd/GameConfig.cfg new file mode 100644 index 0000000..9768e6c --- /dev/null +++ b/fgd/GameConfig.cfg @@ -0,0 +1,262 @@ +{ + "version": 4, + "name": "Quake 2", + "icon": "Icon.png", + "fileformats": [ + { "format": "Quake2" }, + { "format": "Quake2 (Valve)"} + ], + "filesystem": { + "searchpath": "baseq2", + "packageformat": { "extension": "pak", "format": "idpak" } + }, + "textures": { + "package": { "type": "directory", "root": "textures" }, + "format": { "extension": "wal", "format": "wal" }, + "palette": "pics/colormap.pcx", + "attribute": "_tb_textures" + }, + "entities": { + "definitions": [ "Quake2.fgd" ], + "defaultcolor": "0.6 0.6 0.6 1.0", + "modelformats": [ "md2" ], + "scale": [ scale ] + }, + "tags": { + "brush": [ + { + "name": "Trigger", + "attribs": [ "transparent" ], + "match": "classname", + "pattern": "trigger*", + "texture": "trigger" + }, + { + "name": "Areaportal", + "attribs": [ "transparent" ], + "match": "classname", + "pattern": "func_areaportal", + "texture": "trigger" + } + ], + "brushface": [ + { + "name": "Playerclip", + "attribs": [ "transparent" ], + "match": "contentflag", + "flags": [ "playerclip" ] + }, + { + "name": "Monsterclip", + "attribs": [ "transparent" ], + "match": "contentflag", + "flags": [ "monsterclip" ] + }, + { + "name": "Detail", + "match": "contentflag", + "flags": [ "detail" ] + }, + { + "name": "Liquid", + "match": "contentflag", + "flags": [ "lava", "slime", "water" ] + }, + { + "name": "Skip", + "attribs": [ "transparent" ], + "match": "texture", + "pattern": "skip" + }, + { + "name": "Hint", + "attribs": [ "transparent" ], + "match": "texture", + "pattern": "hint*" + }, + { + "name": "Transparent", + "attribs": [ "transparent" ], + "match": "surfaceflag", + "flags": [ "trans33", "trans66" ] + } + ] + }, + "faceattribs": { + "surfaceflags": [ + { + "name": "light", + "description": "Emit light from the surface, brightness is specified in the 'value' field" + }, // 0 + { + "name": "slick", + "description": "The surface is slippery" + }, // 1 + { + "name": "sky", + "description": "The surface is sky, the texture will not be drawn, but the background sky box is used instead" + }, // 2 + { + "name": "warp", + "description": "The surface warps (like water textures do)" + }, // 3 + { + "name": "trans33", + "description": "The surface is 33% transparent" + }, // 4 + { + "name": "trans66", + "description": "The surface is 66% transparent" + }, // 5 + { + "name": "flowing", + "description": "The texture wraps in a downward 'flowing' pattern (warp must also be set)" + }, // 6 + { + "name": "nodraw", + "description": "Used for non-fixed-size brush triggers and clip brushes" + }, // 7 + { + "name": "hint", + "description": "Make a primary bsp splitter" + }, // 8 + { + "name": "skip", + "description": "Completely ignore, allowing non-closed brushes" + }, // 9 + { "unused": true }, // 10 + { "unused": true }, // 11 + { "unused": true }, // 12 + { "unused": true }, // 13 + { "unused": true }, // 14 + { "unused": true }, // 15 + { "unused": true }, // 16 + { "unused": true }, // 17 + { "unused": true }, // 18 + { "unused": true }, // 19 + { "unused": true }, // 20 + { "unused": true }, // 21 + { "unused": true }, // 22 + { "unused": true }, // 23 + { "unused": true }, // 24 + { + "name": "alphatest", + "description": "Alpha test/fence" + }, // 25 + { "unused": true }, // 26 + { "unused": true }, // 27 + { "unused": true }, // 28 + { + "name": "scrollx", + "description": "(N64) Scroll X direction, slower than flowing surfaces" + }, // 29 + { + "name": "scrolly", + "description": "(N64) Scroll Y direction, slower than flowing surfaces" + }, // 30 + { + "name": "scrollflip", + "description": "(N64) Flip scroll direction" + } // 31 + ], + "contentflags": [ + { + "name": "solid", + "description": "Default for all brushes" + }, // 1 << 0 + { + "name": "window", + "description": "Brush is a window (not really used)" + }, // 1 << 1 + { + "name": "aux", + "description": "Unused by the engine" + }, // 1 << 2 + { + "name": "lava", + "description": "The brush is lava" + }, // 1 << 3 + { + "name": "slime", + "description": "The brush is slime" + }, // 1 << 4 + { + "name": "water", + "description": "The brush is water" + }, // 1 << 5 + { + "name": "mist", + "description": "The brush is non-solid" + }, // 1 << 6 + { "unused": true }, // 1 << 7 + { "unused": true }, // 1 << 8 + { "unused": true }, // 1 << 9 + { "unused": true }, // 1 << 10 + { "unused": true }, // 1 << 11 + { "unused": true }, // 1 << 12 + { "unused": true }, // 1 << 13 + { + "name": "projclip", + "description": "Projectiles cannot pass through the brush (players/monsters can)" + }, // 1 << 14 + { "unused": true }, // 1 << 15 + { + "name": "playerclip", + "description": "Player cannot pass through the brush (other things can)" + }, // 1 << 16 + { + "name": "monsterclip", + "description": "Monster cannot pass through the brush (player and other things can)" + }, // 1 << 17 + { + "name": "current_0", + "description": "Brush has a current in direction of 0 degrees" + }, // 1 << 18 + { + "name": "current_90", + "description": "Brush has a current in direction of 90 degrees" + }, // 1 << 19 + { + "name": "current_180", + "description": "Brush has a current in direction of 180 degrees" + }, // 1 << 20 + { + "name": "current_270", + "description": "Brush has a current in direction of 270 degrees" + }, // 1 << 21 + { + "name": "current_up", + "description": "Brush has a current in the up direction" + }, // 1 << 22 + { + "name": "current_dn", + "description": "Brush has a current in the down direction" + }, // 1 << 23 + { + "name": "origin", + "description": "Special brush used for specifying origin of rotation for rotating brushes" + }, // 1 << 24 + { + "name": "internal", + "description": "Do not set" + }, // 1 << 25 + { + "name": "internal", + "description": "Do not set" + }, // 1 << 26 + { + "name": "detail", + "description": "Detail brush" + }, // 1 << 27 + { + "name": "translucent", + "description": "Use for opaque water that does not block vis" + }, // 1 << 28 + { + "name": "ladder", + "description": "Brushes with this flag allow a player to move up and down a vertical surface" + } // 1 << 29 + ] + }, + "softMapBounds":"-4096 -4096 -4096 4096 4096 4096" +} diff --git a/fgd/Quake2.fgd b/fgd/Quake2.fgd new file mode 100644 index 0000000..ce08c0b --- /dev/null +++ b/fgd/Quake2.fgd @@ -0,0 +1,1673 @@ +// +// Quake 2 game definition file (.fgd) +// +// Originally written by by autolycus +// Special thanks to: Disruptor, Zoid, Zaphod, Imaginos, EutecTic, xaGe. +// +// Last updated by Paril for re-release + +// Includes stuff from the tools for lighting etc +@include "ericw_tools.fgd" + +// +// worldspawn +// + +// 0302 - added "nextmap" key +@SolidClass base(EWT_base_Worldspawn) = worldspawn : "World entity" +[ + sky(string) : "Environment map name" + skyaxis(vector) : "Vector axis for rotating sky" + skyrotate(string) : "Speed of rotation (degrees/second)" + skyautorotate(integer) : "Disable to set sky rotation manually" : 1 + sounds(integer) : "CD Track Number" : 1 + gravity(integer) : "Gravity" : 800 + instantitems(integer) : "Instant Item Use" : 0 + message(string) : "Level name" + nextmap(string) : "Next map (DM only)" + start_items(string) : "Starting items: a semi-colon delimited list of items and amounts to be given to player if they have a clear inventory." + achievement(string) : "If an EOU is triggered from this map, this is the achievement to give out." + + fog_density(float) : "density value of fog, 0-1" + fog_color(color) : "color value of fog, 3d vector with values between 0-1 (r g b)" + fog_sky_factor(float) : "sky factor value of fog, 0-1" + heightfog_falloff(float) : "falloff value of heightfog, 0-1" + heightfog_density(float) : "density value of heightfog, 0-1" + heightfog_start_color(color) : "start color of heightfog, 3d vector with values between 0-1 (r g b)" + heightfog_end_color(color) : "end color of heightfog, 3d vector with values between 0-1 (r g b)" + heightfog_start_dist(float) : "start distance value of heightfog, in world units" + heightfog_end_dist(float) : "start distance value of heightfog, in world units" +] + +// +// base marker definitions +// + +// EditorFlags apply to everything - all entities should inherit it. +@baseclass = EditorFlags [ + spawnflags(Flags) = + [ + 256 : "Not in Easy" : 0 + 512 : "Not in Normal" : 0 + 1024 : "Not in Hard" : 0 + 2048 : "Not in Deathmatch" : 0 + // Paril: Rogue + 4096 : "Not in Coop" : 0 + 8192 : "Reserved Editor Flag" : 0 + // Paril: Kex + 16384 : "Coop Only" : 0 + 32768 : "Reserved Editor Flag" : 0 + ] +] + +// Entity can take angle or angles. +@baseclass = Angleable [ + angle(choices) : "Facing angle (yaw)" : 0 = + [ + -1 : "Up" + -2 : "Down" + ] + angles(vector) : "pitch yaw roll" : "0 0 0" : "Facing angles" +] + +// Entity can be targeted by stuff. +@baseclass = Targetable [ + targetname(target_source) : "Name for firing" +] + +// Entity will call G_UseTargets +@baseclass = UseTargets [ + target(target_destination) : "Target to fire" + delay(float) : "Target delay" + message(string): "Message on activation" + killtarget(target_destination) : "Targets to fully delete" +] + +// Entity supports pointing at something, but won't call G_UseTargets +@baseclass = Target [ + target(target_destination) : "Target to fire" +] + +// Entity supports "team" +@baseclass = Teamable [ + team(string) : "Team identifier" +] + +// Entity supports brush model animations +@baseclass = BmodelAnim [ + bmodel_anim_start(integer) : "Start frame of animation" + bmodel_anim_end(integer) : "End frame of animation" + bmodel_anim_style(choices) : "Animation style" : 0 = + [ + 0 : "Forwards" + 1 : "Backwards" + 2 : "Random" + ] + bmodel_anim_speed(integer) : "Animation speed, in milliseconds" + bmodel_anim_nowrap(integer) : "If set, frames are clamped rather than wrapping" + + bmodel_anim_alt_start(integer) : "Start frame of alternate animation" + bmodel_anim_alt_end(integer) : "End frame of alternate animation" + bmodel_anim_alt_style(choices) : "Alternate animation style" : 0 = + [ + 0 : "Forwards" + 1 : "Backwards" + 2 : "Random" + ] + bmodel_anim_alt_speed(integer) : "Alternate animation speed, in milliseconds" + bmodel_anim_alt_nowrap(integer) : "If set, alternate frames are clamped rather than wrapping" +] + +// Entity supports brush model sound modifications +@baseclass = BmodelSounds [ + noise_start(string) : "Noise to play on activation" + noise_middle(string) : "Noise to play while traveling" + noise_end(string) : "Noise to play on end" +] + +// Entity supports noise key +@baseclass = Noise [ + noise(string) : "Noise to play" +] + +// Entity supports alpha key +@baseclass = Alpha [ + alpha(float) : "Alpha" +] + +// Entity supports scale key +@baseclass = Scale [ + scale(float) : "Scale" +] + +// Includes stuff related to the kex dynamic lights +@include "kexlights.fgd" + +// +// player start, deathmatch, coop, deathmatch intermission +// + +@baseclass base(EditorFlags, Angleable) size(-16 -16 -24, 16 16 32) color(0 255 0) model({ "path": ":models/monsters/insane/tris.md2", "frame":209, "skin":1}) = PlayerClass [] + +@PointClass base(PlayerClass) = info_player_start : "Player start" [] +@PointClass base(PlayerClass) = info_player_deathmatch : "Player deathmatch start" [] +@PointClass base(PlayerClass) = info_player_coop : "Player cooperative start" [ + targetname(string) : "Spawn point name, to be matched to previous levels' target_changelevel" +] + +@PointClass base(PlayerClass) = info_player_coop_lava : "Smart Water lava spawn point" [] + +@PointClass base(PlayerClass, Targetable) = info_player_intermission : "Deathmatch intermission point" [] + +@PointClass base(PlayerClass) = info_player_team1 : "CTF Red start" [] +@PointClass base(PlayerClass) = info_player_team2 : "CTF Blue start" [] + +// Notes on the 'team' key: First of all, it's really only useful in DM because it creates a +// random respawn pattern. Let's say that in one spot, you want to have the shotgun, Quad +// damage and mega health item to respawn in alternance. Place all of them in approximately +// the same location, team them and voila! The FIRST item that you place in the map will be +// the team MASTER - the others will be SLAVES. In DM play, the Master will be the first one +// to spawn. Once the Master is picked up, the respawn pattern becomes RANDOM: it could be +// the same or one of the other 2. If you try to use this in a Single Player map, it's +// pretty useless because only the team MASTER spawns and the others never appear obviously. +// Also, in CTF, only the master appears. No idea why. +@BaseClass base(EditorFlags, Angleable, UseTargets, Targetable) color(76 76 255) size(-15 -15 -15, 15 15 15) = Item [ + team(string) : "Team" + spawnflags(Flags) = + [ + 1 : "Trigger Spawn" : 0 + 2 : "Disable Pickup" : 0 + 4 : "Toss Item On Spawn" : 0 + ] +] + +@BaseClass base(Item) color(76 76 255) = Ammo [] +@BaseClass base(Item) color(255 76 76) = Weapons [] +@BaseClass base(Item) color(76 255 255) size(-16 -16 -16, 16 16 16) = Items [] +@BaseClass base(Item) color(0 128 204) size(-16 -16 -16, 16 16 16) = Keys [] + +@PointClass base(Ammo) model({ "path": ":models/items/ammo/shells/medium/tris.md2" }) = ammo_shells : "Shotgun ammo" [] +@PointClass base(Ammo) model({ "path": ":models/items/ammo/bullets/medium/tris.md2" }) = ammo_bullets : "Machine/Chain gun ammo" [] +@PointClass base(Ammo) model({ "path": ":models/items/ammo/cells/medium/tris.md2" }) = ammo_cells : "Blaster/BFG ammo" [] +@PointClass base(Ammo) model({ "path": ":models/items/ammo/grenades/medium/tris.md2" }) = ammo_grenades : "Grenades" [] +@PointClass base(Ammo) model({ "path": ":models/items/ammo/rockets/medium/tris.md2" }) = ammo_rockets : "Rockets" [] +@PointClass base(Ammo) model({ "path": ":models/items/ammo/slugs/medium/tris.md2" }) = ammo_slugs : "Rail gun ammo" [] + +@PointClass base(Ammo) model({ "path": ":models/objects/ammo/tris.md2" }) = ammo_magslug : "Phalanx ammo" [] +@PointClass base(Ammo) model({ "path": ":models/ammo/am_flechette/tris.md2" }) = ammo_flechettes : "ETF Rifle ammo" [] +@PointClass base(Ammo) model({ "path": ":models/ammo/am_flechette/tris.md2" }) = ammo_nails : "ETF Rifle ammo" [] +@PointClass base(Ammo) model({ "path": ":models/ammo/am_prox/tris.md2" }) = ammo_prox : "Prox Launcher ammo" [] +@PointClass base(Ammo) model({ "path": ":models/ammo/am_disr/tris.md2" }) = ammo_disruptor : "Disruptor ammo" [] +@PointClass base(Ammo) model({ "path": ":models/weapons/g_trap/tris.md2" }) = ammo_trap : "Traps" [] +@PointClass base(Ammo) model({ "path": ":models/ammo/am_tesl/tris.md2" }) = ammo_tesla : "Teslas" [] +@PointClass base(Ammo) model({ "path": ":models/weapons/g_nuke/tris.md2" }) = ammo_nuke : "A-M Bomb (usable nuke); DM only" [] + +@PointClass base(Weapons) model({ "path": ":models/weapons/g_shotg/tris.md2" }) = weapon_shotgun : "Shotgun" [] +@PointClass base(Weapons) model({ "path": ":models/weapons/g_shotg2/tris.md2" }) = weapon_supershotgun : "Super shotgun" [] +@PointClass base(Weapons) model({ "path": ":models/weapons/g_machn/tris.md2" }) = weapon_machinegun : "Machinegun" [] +@PointClass base(Weapons) model({ "path": ":models/weapons/g_chain/tris.md2" }) = weapon_chaingun : "Chain gun" [] +@PointClass base(Weapons) model({ "path": ":models/weapons/g_launch/tris.md2" }) = weapon_grenadelauncher : "Grenade launcher" [] +@PointClass base(Weapons) model({ "path": ":models/weapons/g_rocket/tris.md2" }) = weapon_rocketlauncher : "Rocket launcher" [] +@PointClass base(Weapons) model({ "path": ":models/weapons/g_hyperb/tris.md2" }) = weapon_hyperblaster : "Hyperblaster" [] +@PointClass base(Weapons) model({ "path": ":models/weapons/g_rail/tris.md2" }) = weapon_railgun : "Rail gun" [] +@PointClass base(Weapons) model({ "path": ":models/weapons/g_bfg/tris.md2" }) = weapon_bfg : "Big Freakin Gun!" [] + +@PointClass base(Weapons) model({ "path": ":models/weapons/g_chainf/tris.md2" }) = weapon_chainfist : "Chainfist" [] +@PointClass base(Weapons) model({ "path": ":models/weapons/g_etf_rifle/tris.md2" }) = weapon_etf_rifle : "ETF Rifle" [] +@PointClass base(Weapons) model({ "path": ":models/weapons/g_etf_rifle/tris.md2" }) = weapon_nailgun : "ETF Rifle" [] +@PointClass base(Weapons) model({ "path": ":models/weapons/g_plaunch/tris.md2" }) = weapon_proxlauncher : "Prox Launcher" [] +@PointClass base(Weapons) model({ "path": ":models/weapons/g_boom/tris.md2" }) = weapon_boomer : "Ionripper" [] +@PointClass base(Weapons) model({ "path": ":models/weapons/g_beamer/tris.md2" }) = weapon_plasmabeam : "Plasma Beam" [] +@PointClass base(Weapons) model({ "path": ":models/weapons/g_beamer/tris.md2" }) = weapon_heatbeam : "Plasma Beam" [] +@PointClass base(Weapons) model({ "path": ":models/weapons/g_shotx/tris.md2" }) = weapon_phalanx : "Phalanx" [] +@PointClass base(Weapons) model({ "path": ":models/weapons/g_dist/tris.md2" }) = weapon_disintegrator : "Disruptor" [] + +@PointClass base(Items) model({ "path": ":models/items/adrenal/tris.md2" }) = item_adrenaline : "+1 to max health" [] +@PointClass base(Items) model({ "path": ":models/items/c_head/tris.md2" }) = item_ancient_head : "+2 to max health" [] +@PointClass base(Items) model({ "path": ":models/items/armor/body/tris.md2" }) = item_armor_body : "Body armor" [] +@PointClass base(Items) model({ "path": ":models/items/armor/combat/tris.md2" }) = item_armor_combat : "Combat armor" [] +@PointClass base(Items) model({ "path": ":models/items/armor/jacket/tris.md2" }) = item_armor_jacket : "Jacket armor" [] +@PointClass base(Items) model({ "path": ":models/items/armor/shard/tris.md2" }) = item_armor_shard : "Armor shard" [] +@PointClass base(Items) model({ "path": ":models/items/band/tris.md2" }) = item_bandolier : "Equipment belt" [] +@PointClass base(Items) model({ "path": ":models/items/breather/tris.md2" }) = item_breather : "Underwater breather" [] +@PointClass base(Items) model({ "path": ":models/items/enviro/tris.md2" }) = item_enviro : "Enviro-Suit" [] +@PointClass base(Items) model({ "path": ":models/items/healing/medium/tris.md2" }) = item_health : "+10 health" [] +@PointClass base(Items) model({ "path": ":models/items/healing/stimpack/tris.md2" }) = item_health_small : "+2 health" [] +@PointClass base(Items) model({ "path": ":models/items/healing/large/tris.md2" }) = item_health_large : "+25 health" [] +@PointClass base(Items) model({ "path": ":models/items/mega_h/tris.md2" }) = item_health_mega : "+100 health" [] +@PointClass base(Items) model({ "path": ":models/items/invulner/tris.md2" }) = item_invulnerability : "Invulnerability" [] +@PointClass base(Items) model({ "path": ":models/items/pack/tris.md2" }) = item_pack : "Heavy backpack" [] +@PointClass base(Items) model({ "path": ":models/items/armor/screen/tris.md2" }) = item_power_screen : "Power screen" [] +@PointClass base(Items) model({ "path": ":models/items/armor/shield/tris.md2" }) = item_power_shield : "Power shield" [] +@PointClass base(Items) model({ "path": ":models/items/quaddama/tris.md2" }) = item_quad : "Quad damage" [] +@PointClass base(Items) model({ "path": ":models/items/silencer/tris.md2" }) = item_silencer : "Silencer" [] +@PointClass base(Items) model({ "path": ":models/items/silencer/tris.md2" }) = item_flashlight : "Flashlight" [] + +@PointClass base(Items) model({ "path": ":models/items/quadfire/tris.md2" }) = item_quadfire : "DualFire Damage" [] +@PointClass base(Items) model({ "path": ":models/items/goggles/tris.md2" }) = item_ir_goggles : "IR Goggles" [] +@PointClass base(Items) model({ "path": ":models/items/ddamage/tris.md2" }) = item_double : "Double Damage" [] + +@PointClass base(Items) model({ "path": ":models/items/vengnce/tris.md2" }) = item_sphere_vengeance : "vengeance sphere" [] +@PointClass base(Items) model({ "path": ":models/items/hunter/tris.md2" }) = item_sphere_hunter : "hunter sphere" [] +@PointClass base(Items) model({ "path": ":models/items/defender/tris.md2" }) = item_sphere_defender : "defender sphere" [] +@PointClass base(Items) model({ "path": ":models/items/dopple/tris.md2" }) = item_doppleganger : "Doppleganger" [] + + +@PointClass base(Keys) model({ "path": ":models/items/keys/target/tris.md2" }) = key_airstrike_target : "Tank commander's head" [] +@PointClass base(Keys) model({ "path": ":models/items/keys/key/tris.md2" }) = key_blue_key : "Normal door key - blue" [] +@PointClass base(Keys) model({ "path": ":models/monsters/commandr/head/tris.md2" }) = key_commander_head : "Tank commander's head (key)" [] +@PointClass base(Keys) model({ "path": ":models/items/keys/data_cd/tris.md2" }) = key_data_cd : "Data CD key for computer centers" [] +@PointClass base(Keys) model({ "path": ":models/items/keys/spinner/tris.md2" }) = key_data_spinner : "Data Spinner key for city computer" [] +@PointClass base(Keys) model({ "path": ":models/items/keys/pass/tris.md2" }) = key_pass : "Security pass for secret level" [] + +@PointClass base(Keys) model({ "path": ":models/items/keys/power/tris.md2" }) = key_power_cube : "Warehouse circuits" [] +@PointClass base(Keys) model({ "path": ":models/items/keys/pyramid/tris.md2" }) = key_pyramid : "Pyramid Key for entrance to jail3" [] +@PointClass base(Keys) model({ "path": ":models/items/keys/red_key/tris.md2" }) = key_red_key : "normal door key - red" [] +// Expansions +@PointClass base(Keys) model({ "path": ":models/items/keys/green_key/tris.md2" }) = key_green_key : "normal door key - green" [] +@PointClass base(Keys) model({ "path": ":models/weapons/g_nuke/tris.md2" }) = key_nuke_container : "Antimatter Pod (nuke key part 1)" [] +@PointClass base(Keys) model({ "path": ":models/weapons/g_nuke/tris.md2" }) = key_nuke : "Antimatter Bomb (nuke key part 2)" [] +// N64 +@PointClass base(Keys) model({ "path": ":models/items/n64/charge/tris.md2" }) = key_explosive_charges : "Explosive Charge" [] +@PointClass base(Keys) model({ "path": ":models/items/n64/yellow_key/tris.md2" }) = key_yellow_key : "Yellow Key" [] +@PointClass base(Keys) model({ "path": ":models/items/n64/power_core/tris.md2" }) = key_power_core : "Power Core" [] +// CTF +@PointClass base(Keys) model({ "path": ":players/male/flag1.md2" }) = item_flag_team1 : "CTF Flag (red)" [] +@PointClass base(Keys) model({ "path": ":players/male/flag2.md2" }) = item_flag_team2 : "CTF Flag (blue)" [] + + +// Keep in mind when using func_areaportal that it must +// *completely* separate two areas. otherwise, you will +// get an error message and the areaportal will not work +@SolidClass base(EditorFlags, Targetable) = func_areaportal : "Area portal (Vis blocker)" [] + +@SolidClass base(EditorFlags, Angleable, UseTargets, Targetable, EWT_base_BModel, BmodelAnim, BmodelSounds) color(0 128 204) = func_button : "Button" +[ + speed(float) : "Speed" : "40" + wait(choices) : "Wait before reset" : 1 = + [ + -1 : "Never Return" + ] + lip(float) : "Lip remaining after move" : "4" + health(float) : "Health (shootable)" + sounds(choices) : "Sounds" : 0 = + [ + 0 : "Audible" + 1 : "Silent" + ] +] + +@PointClass base(EditorFlags, Targetable, UseTargets) color(0 0 255) size(-8 -8 -8, 8 8 8) = func_clock : "Clock" +[ + spawnflags(Flags) = + [ + 1 : "Timer Up" : 0 + 2 : "Timer Down" : 0 + 4 : "Start Off" : 0 + 8 : "Multi Use" : 0 + ] + count(integer) : "Clock Count" : 3600 + pathtarget(target_destination) : "Target to fire" + target(target_destination) : "Should be targeting a target_string" + style(choices) : "Style" : 0 = + [ + 0 : "xx" + 1 : "xx:xx" + 2 : "xx:xx:xx" + ] +] + +@PointClass base(EditorFlags, Targetable) color(0 0 255) size(-8 -8 -8, 8 8 8) = target_string : "func_clock holder for time string" +[ + team(string) : "target_characters to team with" +] + +@SolidClass base(EditorFlags, Targetable, EWT_base_BModel) color(0 0 255) = target_character : "single character for target_string" +[ + team(string) : "Team" + count(integer) : "Position in the string" +] + + +@SolidClass base(Angleable, EditorFlags, Targetable, UseTargets, Teamable, EWT_base_BModel, BmodelSounds) color(0 128 204) = func_door : "Door" +[ + spawnflags(Flags) = + [ + 1 : "Start Open" : 0 + 4 : "Crusher" : 0 + 8 : "No Monsters" : 0 + 16 : "Animated" : 0 + 32 : "Toggle" : 0 + 64 : "Animated Fast" : 0 + ] + health(float) : "Health (shootable)" + speed(float) : "Speed" : "100" + wait(choices) : "Wait before close" : 3 = + [ + -1 : "Stay open" + ] + lip(float) : "Lip remaining after move" : "8" + dmg(float) : "Damage when blocked" : "2" + sounds(choices) : "Sounds" : 0 = + [ + 0 : "Audible" + 1 : "Silent" + ] +] + +@SolidClass base(Angleable, EditorFlags, Targetable, UseTargets, EWT_base_BModel, BmodelSounds) color(0 128 204) = func_door_rotating : "Rotating Door" +[ + spawnflags(Flags) = + [ + 1 : "Start Open" : 0 + 2 : "Reverse" : 0 + 4 : "Crusher" : 0 + 8 : "No Monsters" : 0 + 16 : "Animated" : 0 + 32 : "Toggle" : 0 + 64 : "X Axis" : 0 + 128 : "Y Axis" : 0 + 65536 : "Inactive (must be triggered)" : 0 + 131072 : "Safe Open (opens opposite dir. if activator facing 'angles')" : 0 + ] + team(string) : "Team" + distance(float) : "Degrees of rotation" : "90" + health(float) : "Health (shootable)" + speed(float) : "Speed" : "100" + wait(choices) : "Wait before close" : 3 = + [ + -1 : "Stay open" + ] + dmg(float) : "Damage when blocked" : "2" + sounds(choices) : "Sounds" : 0 = + [ + 0 : "Audible" + 1 : "Silent" + ] +] + +@SolidClass base(Angleable, EditorFlags, Targetable, UseTargets, EWT_base_BModel, BmodelSounds) color(0 128 204) = func_door_secret : "Secret Door" +[ + spawnflags(Flags) = + [ + 1 : "Always shoot" : 0 + 2 : "1st Left" : 0 + 4 : "1st Down" : 0 + ] + dmg(float) : "Damage when blocked" : "2" + wait(choices) : "Wait before close" : 5 = + [ + -1 : "Stay open" + ] +] + +// not visible in DM mode +@SolidClass base(EditorFlags, Targetable, UseTargets, EWT_base_BModel) color(0 128 204) = func_explosive : "Exploding/Breakable brush" +[ + spawnflags(Flags) = + [ + 1 : "Trigger Spawn" : 0 + 2 : "Animated" : 0 + 4 : "Animated Fast" : 0 + ] + health(float) : "Health" : "100" + mass(float) : "Mass (debris)" : "75" + dmg(float) : "Damage" : "0" + sounds(choices) : "Sounds" : 0 = + [ + 0 : "Silent" + 1 : "Glass Break" + ] +] + +@SolidClass base(EditorFlags, Targetable) color(255 0 0) = func_killbox : "Instant death" +[ + spawnflags(Flags) = + [ + 2 : "Level Restart Required" : 0 + 4 : "Exact Trigger Collision" : 0 + ] +] + +@SolidClass base(EditorFlags, Targetable, EWT_base_BModel) color (0 128 204) = func_object : "Solid bmodel, will fall if its support is removed" +[ + spawnflags(Flags) = + [ + 1 : "Trigger Spawn" : 0 + 2 : "Animated" : 0 + 4 : "Animated Fast" : 0 + ] + dmg(float) : "Crush damage" : "100" +] + +@SolidClass base(EditorFlags, Targetable, EWT_base_BModel, BmodelSounds) color(0 128 204) = func_rotating : "Rotating brush" +[ + spawnflags(Flags) = + [ + 1 : "Start On" : 0 + 2 : "Reverse" : 0 + 4 : "X Axis" : 0 + 8 : "Y Axis" : 0 + 16 : "Pain on Touch" : 0 + 32 : "Block Stops" : 0 + 64 : "Animated" : 0 + 128 : "Animated Fast" : 0 + 65536 : "Acceleration" : 0 + ] + team(string) : "Team" + speed(float) : "Speed" : "100" + dmg(float) : "Damage when blocked" : "2" + accel(float) : "Acceleration speed, when flag is enabled" : "1" + decel(float) : "Deceleration speed, when flag is enabled" : "1" +] + +@PointClass base(EditorFlags, Targetable, UseTargets) color(76 25 153) size(-8 -8 -8, 8 8 8) = func_timer : "Timer" +[ + spawnflags(Flags) = + [ + 1 : "Start On" : 0 + ] + wait(float) : "Base wait time" : "1" + random(float) : "Wait variance (+/-) - should be <= wait" + delay(float) : "Delay before firing when used" + pausetime(float) : "Additional delay for START_ON timers" +] + +// 0219 - added "team" key +@SolidClass base(EditorFlags, Targetable, UseTargets, Teamable, EWT_base_BModel) color(0 128 204) = func_train : "Moving platform" +[ + spawnflags(Flags) = + [ + 1 : "Start On" : 0 + 2 : "Toggle" : 0 + 4 : "Block Stops" : 0 + 8 : "Move Teamchain" : 0 + 16 : "Fix Offset (fixes legacy -1,-1,-1 offset)" : 0 + 32 : "Use Origin" : 0 + ] + pathtarget(target_destination) : "Target to fire when we reach a point" + target(target_destination) : "First corner to be placed at" + speed(float) : "Speed" : "100" + dmg(float) : "Damage when blocked" : "2" + noise(string) : "Travel noise (path/file.wav)" +] + +@SolidClass base(EditorFlags, Targetable, EWT_base_BModel) color(0 128 204) = func_wall : "Solid Wall" +[ + spawnflags(Flags) = + [ + 1 : "Trigger Spawn" : 0 + 2 : "Toggle" : 0 + 4 : "Start On" : 0 + 8 : "Animated" : 0 + 16 : "Animated Fast" : 0 + ] +] + +@SolidClass base(EditorFlags, Targetable, Teamable, EWT_base_BModel, BmodelSounds) color(0 128 204) = func_water : "Moveable water" +[ + spawnflags(Flags) = + [ + 1 : "Start Open" : 0 + 2 : "Smart Water (player position aware rising)" : 0 + ] + speed(float) : "Speed" : "25" + wait(choices) : "Wait before return" : -1 = + [ + -1 : "Toggle" + ] + lip(float) : "Lip remaining after move" + sounds(Choices) : "Sounds" : 1 = + [ + 0 : "No Sounds" + 1 : "Water" + 2 : "Water" + ] + accel(float) : "divisor of lowest player's distance, to determine rising speed" : "20" +] + +@PointClass base(Targetable) color(128 0 0) size(-2 -2 -2, 2 2 2) = info_null : "Compiler-only target (spotlights, etc)" [] +@PointClass base(EditorFlags, Targetable) color(0 128 0) size(-4 -4 -4, 4 4 4) = info_notnull : "Game target" [] + +// expansion +@PointClass base(info_notnull) = info_teleport_destination : "Teleport Destination" [] + +@PointClass base(EditorFlags, Targetable, Target, EWT_base_PointLight) color(0 255 0) size(-8 -8 -8, 8 8 8) = light : "Light" +[ + spawnflags(Flags) = + [ + 1 : "Start Off" : 0 + 2 : "Allow In Deathmatch" : 0 + ] + style(Choices) : "Style" : 0 = + [ + 0 : "Normal" + 1 : "Flicker #1" + 6 : "Flicker #2" + 2 : "Slow Strong Pulse" + 3 : "Candle #1" + 7 : "Candle #2" + 8 : "Candle #3" + 4 : "Fast Strobe" + 5 : "Gentle Pulse #1" + 9 : "Slow Strobe" + 10 : "Fluorescent Flicker" + 11 : "Slow pulse, no black" + ] +] + +@PointClass base(EditorFlags, Targetable, Target) color(0 255 0) size(-2 -2 -12, 2 2 12) model({ "path": ":models/objects/minelite/light1/tris.md2", "skin": 0 }) = light_mine1 : "Clean fluorescent light fixture" [] +@PointClass base(EditorFlags, Targetable, Target) color(0 255 0) size(-2 -2 -12, 2 2 12) model({ "path": ":models/objects/minelite/light2/tris.md2", "skin": 0 }) = light_mine2 : "Dusty fluorescent light fixture" [] + +@PointClass base(EditorFlags) color(255 128 0) size(-4 -4 0, 4 4 246) model({ "path": ":models/objects/banner/tris.md2" }) = misc_banner : "Flowing banner" [] +@PointClass base(EditorFlags, Targetable, Scale, Alpha) color(255 128 0) size(-8 -8 -8, 8 8 8) model({ "path": ":models/objects/black/tris.md2" }) = misc_blackhole : "Blackhole" [ + spawnflags(Flags) = + [ + 1 : "Auto-Noise (N64)" : 0 + ] +] + +// Expansion +@PointClass base(EditorFlags, Targetable) color(255 128 0) size(-8 -8 -8, 8 8 8) model({ "path": ":models/objects/core/tris.md2" }) = misc_nuke_core : "Nuke Core (must be targeted to appear)" [] + +@PointClass base(EditorFlags) color(255 128 0) size(-8 -8 -8, 8 8 8) model({ "path": ":models/items/spawngro2/tris.md2", "frame": 2 }) = target_orb : "Large Orb" [] + +@PointClass base(EditorFlags) color(255 128 0) size(-8 -8 -8, 8 8 8) model({ "path": ":models/items/spawngro2/tris.md2", "frame": 1 }) = target_blacklight : "Large Orb (black light)" [] + +@PointClass base(EditorFlags) color(255 128 0) size(-16 -16 0, 16 16 16) model({{ + spawnflags & 32 -> { "path": ":models/deadbods/dude/tris.md2", "frame": 5 }, + spawnflags & 16 -> { "path": ":models/deadbods/dude/tris.md2", "frame": 4 }, + spawnflags & 8 -> { "path": ":models/deadbods/dude/tris.md2", "frame": 3 }, + spawnflags & 4 -> { "path": ":models/deadbods/dude/tris.md2", "frame": 2 }, + spawnflags & 2 -> { "path": ":models/deadbods/dude/tris.md2", "frame": 1 }, + ":models/deadbods/dude/tris.md2" + }}) = misc_deadsoldier : "Dead guys! 6 of em!" +[ + spawnflags(Flags) = + [ + 1 : "On Back" : 0 + 2 : "On Stomach" : 0 + 4 : "Back, Decap" : 0 + 8 : "Fetal Position" : 0 + 16 : "Sitting, Decap" : 0 + 32 : "Impaled" : 0 + ] +] + +// The following three entities are eye-candy - they don't do anything +@PointClass base(EditorFlags) color(255 128 0) size(-32 -32 -16, 32 32 32) model({ "path": ":models/monsters/tank/tris.md2", "frame":254, "skin": 2}) = misc_eastertank : "Tank sitting down. Make him a chair out of brushes." [] +@PointClass base(EditorFlags) color(255 128 0) size(-32 -32 0, 32 32 32) model({ "path": ":models/monsters/bitch/tris.md2", "frame":208}) = misc_easterchick : "Chick #1 sitting: Place her near misc_eastertank." [] +@PointClass base(EditorFlags) color(255 128 0) size(-32 -32 0, 32 32 32) model({ "path": ":models/monsters/bitch/tris.md2", "frame":248}) = misc_easterchick2 : "Chick #2 sitting w/ different pose. Can be placed close to misc_eastertank's for full effect." [] + +@PointClass base(EditorFlags) color(0 128 204) size(-16 -16 0, 16 16 40) model({ "path": ":models/objects/barrels/tris.md2" }) = misc_explobox : "Large exploding box" +[ + mass(float) : "Mass" : "100" + health(float) : "Health" : "80" + dmg(float) : "Damage" : "150" +] + +// set angle for gib direction, otherwise it just drops +@PointClass base(EditorFlags) color(255 0 0) size(-8 -8 -8, 8 8 8) model({ "path": ":models/objects/gibs/arm/tris.md2" }) = misc_gib_arm : "arm gib, use with target_spawner" [] +@PointClass base(EditorFlags) color(255 0 0) size(-8 -8 -8, 8 8 8) model({ "path": ":models/objects/gibs/head/tris.md2" }) = misc_gib_head : "head gib, use with target_spawner" [] +@PointClass base(EditorFlags) color(255 0 0) size(-8 -8 -8, 8 8 8) model({ "path": ":models/objects/gibs/arm/tris.md2" }) = misc_gib_leg : "leg gib, use with target_spawner" [] + + +@PointClass base(EditorFlags, Targetable) color(255 128 0) size(-64 -64 0, 64 64 128) + model( + {{ + spawnflags & 32 -> { "path": ":models/objects/satellite/tris.md2", "frame": 38 }, + ":models/objects/satellite/tris.md2" + }} + ) = +misc_satellite_dish : "Satellite Dish" +[ + spawnflags(Flags) = + [ + 32 : "(editor only) Show End Position" : 0 + ] +] + +@PointClass base(EditorFlags, Targetable, Target) color(255 128 0) size(-16 -16 0, 16 16 32) model({ "path": ":models/ships/strogg1/tris.md2" }) = misc_strogg_ship : "Strogg ship for flybys" +[ + speed(float) : "Speed" : "300" +] + +@PointClass base(EditorFlags, Target) color(255 0 0) size(-32 -32 -24, 32 32 -16) model({ "path": ":models/objects/dmspot/tris.md2", "skin": 1 }) = misc_teleporter : "Teleporter: To hide the teleport pads, place them units 10 units into a brush." +[ + spawnflags(Flags) = + [ + 1 : "No Sound" : 0 + 2 : "No Effect" : 0 + 4 : "N64 Effect" : 0 + ] +] + +@PointClass base(EditorFlags, Targetable) color(255 0 0) size(-32 -32 -24, 32 32 -16) model({ "path": ":models/objects/dmspot/tris.md2", "skin": 0 }) = misc_teleporter_dest : "Teleport Destination: To hide the teleport pads, place them units 10 units into a brush or use an info_notnull." [] + +@PointClass base(EditorFlags) color(255 128 0) size(-176 -120 -24, 176 120 72) model({ "path": ":models/ships/bigviper/tris.md2" }) = misc_bigviper : "Large stationary Viper" [] + +@PointClass base(misc_strogg_ship) model({ "path": ":models/ships/viper/tris.md2" }) = misc_viper : "Viper for flybys" [] + +@PointClass base(EditorFlags, Targetable) color(255 0 0) size(-8 -8 -8, 8 8 8) model({ "path": ":models/objects/bomb/tris.md2" }) = misc_viper_bomb : "Viper Bomb" +[ + dmg(float) : "Damage" +] + +// Expansion +@PointClass base(misc_strogg_ship) model({ "path": ":models/ships/viper/tris.md2" }) = misc_crashviper : "Viper that crashes" +[ +] + +@PointClass base(EditorFlags, Targetable, Target) color(255 0 0) size(-8 -8 -8, 8 8 8) model({ "path": ":models/objects/bomb/tris.md2" }) = misc_viper_missile : "Viper Missile" +[ + dmg(float) : "Damage" : "250" +] + +@PointClass base(misc_strogg_ship) model({ "path": ":models/objects/ship/tris.md2" }) = misc_transport : "Xatrix Transport" +[ +] + + +// +// Monsters! +// + +@BaseClass base(EditorFlags, Targetable, Target, Scale, Alpha) color(255 128 0) size(-16 -16 -24, 16 16 32) = Monsters +[ + spawnflags(Flags) = + [ + 1 : "Ambush" : 0 + 2 : "Trigger Spawn" : 0 + 4 : "Ambush Override (don't use)" : 0 + 65536 : "Spawn Dead" : 0 + 131072 : "Super Step" : 0 + 262144 : "drop to ground" : 0 + ] + combattarget(target_destination) : "Point combat target" + deathtarget(target_destination) : "Entity to trigger at death" + healthtarget(target_destination) : "Entity to trigger when health hits target" + itemtarget(target_destination) : "Entity to trigger when item dropped by this monster is picked up" + killtarget(target_destination) : "Entity to remove at death" + item(string) : "Spawn Item" + health_multiplier(float) : "Set health based on multiplier of base health" : "1.0" + dead_frame(integer) : "Frame to spawn dead monsters on" : 0 + power_armor_power(integer) : "Override power armor amount" + power_armor_type(Choices) : "Override power armor type" : 0 = + [ + 0 : "None" + 1 : "Screen" + 2 : "Shield" + ] +] + +@PointClass base(Monsters) color(255 128 0) size(-16 -16 -24, 16 16 32) model({{ + spawnflags & 32 -> { "path": ":models/monsters/insane/tris.md2", "frame": 0 }, + spawnflags & 16 -> { "path": ":models/monsters/insane/tris.md2", "frame": 74 }, + spawnflags & 8 -> { "path": ":models/monsters/insane/tris.md2", "frame": 252 }, + spawnflags & 4 -> { "path": ":models/monsters/insane/tris.md2", "frame": 38 }, + spawnflags & 2 -> { "path": ":models/monsters/insane/tris.md2", "frame": 0 }, + ":models/monsters/insane/tris.md2" + }}) = misc_insane : "Insane Soldiers" +[ + spawnflags(Flags) = + [ + 4 : "Crawl" : 0 + 8 : "Crucified" : 0 + 16 : "Stand Ground" : 0 + 32 : "Always Stand" : 0 + 64 : "Quiet" : 0 + ] +] + +@PointClass base(Monsters) model({ "path": ":models/monsters/berserk/tris.md2" }) = monster_berserk : "Berserker" [ + spawnflags(Flags) = + [ + 8 : "No jumping" : 0 + ] +] +@PointClass base(Monsters) size(-56 -56 0, 56 56 80) model({ "path": ":models/monsters/boss2/tris.md2" }) = monster_boss2 : "Hornet" +[ + spawnflags(Flags) = + [ + 8 : "N64 (hyperblaster, staggered rockets)" : 0 + ] +] + +// Just fidgets in one spot and teleports away when triggered +// +// 0221 - removed Monsters class inheritance +@PointClass base(EditorFlags, Targetable) size(-32 -32 0, 32 32 80) model({ "path": ":models/monsters/boss3/rider/tris.md2", "frame": 414}) = monster_boss3_stand : "Stationnary Makron" [] +@PointClass base(Monsters) model({ "path": ":models/monsters/brain/tris.md2" }) = monster_brain : "Brains" +[ + spawnflags(Flags) = + [ + 1 : "Ambush" : 0 + 2 : "Trigger Spawn" : 0 + 8 : "no laser eyes" : 0 + ] +] +@PointClass base(Monsters) model({ "path": ":models/monsters/bitch/tris.md2" }) size(-16 -16 0, 16 16 56) = monster_chick : "Iron Maiden" [] +@PointClass base(EditorFlags, Targetable) color(255 128 0) size(-32 -32 0, 32 32 48) model({ "path": ":models/monsters/commandr/tris.md2" }) = monster_commander_body : "Tank commander's decapitated body" [] +@PointClass base(Monsters) model({ "path": ":models/monsters/flipper/tris.md2" }) = monster_flipper : "Barracuda shark" [] +@PointClass base(Monsters) size(-24 -24 -24, 24 24 48) model({ "path": ":models/monsters/float/tris.md2" }) = monster_floater : "Technician" +[ + spawnflags(Flags) = + [ + 1 : "Ambush" : 0 + 2 : "Trigger Spawn" : 0 + 8 : "Disguise as barrel" : 0 + ] +] +@PointClass base(Monsters) model({ "path": ":models/monsters/flyer/tris.md2" }) = monster_flyer : "Flyer" [] +@PointClass base(Monsters) size(-32 -32 -24, 32 32 64) model({ "path": ":models/monsters/gladiatr/tris.md2" }) = monster_gladiator : "Gladiator" [] +@PointClass base(Monsters) model({ "path": ":models/monsters/gunner/tris.md2" }) = monster_gunner : "Gunner" +[ + spawnflags(Flags) = + [ + 8 : "No jumping" : 0 + ] +] +@PointClass base(Monsters) model({ "path": ":models/monsters/gunner/tris.md2", "frame": 249 }) = monster_guncmdr : "Gunner Commander" +[ + spawnflags(Flags) = + [ + 8 : "No jumping" : 0 + ] +] +@PointClass base(Monsters) model({ "path": ":models/monsters/hover/tris.md2" }) = monster_hover : "Icarus" [] +@PointClass base(Monsters) model({ "path": ":models/monsters/infantry/tris.md2", "frame": 64}) = monster_infantry : "Infantry" +[ + spawnflags(Flags) = + [ + 8 : "No jumping" : 0 + ] +] +@PointClass base(Monsters) size(-80 -80 0, 90 90 140) model({ "path": ":models/monsters/boss3/jorg/tris.md2" }) = monster_jorg : "Jorg" [] + +// 0221 - this entity can only spawn once the Jorg dies ... im sure someone will change that tho +@PointClass base(Monsters) size(-32 -32 0, 32 32 80) model({ "path": ":models/monsters/boss3/rider/tris.md2", "frame": 414}) = monster_makron : "Makron" [] +@PointClass base(Monsters) model({ "path": ":models/monsters/medic/tris.md2" }) = monster_medic : "Medic" [] +@PointClass base(Monsters) size(-32 -32 -24, 32 32 32) + model( + {{ + spawnflags & 65536 -> { "path": ":models/monsters/mutant/tris.md2", "skin": 1, "frame": 33 }, + ":models/monsters/mutant/tris.md2" + }} + ) = +monster_mutant : "Mutant" +[ + spawnflags(Flags) = + [ + 8 : "No jumping" : 0 + ] +] +@PointClass base(Monsters) model({ "path": ":models/monsters/parasite/tris.md2" }) = monster_parasite : "Parasite" +[ + spawnflags(Flags) = + [ + 8 : "No jumping" : 0 + ] +] +@PointClass base(Monsters) model({ "path": ":models/monsters/soldier/tris.md2", "skin": 0 }) = monster_soldier_light : "Light Soldier" +[ + spawnflags(Flags) = + [ + 1 : "Ambush" : 0 + 2 : "Trigger Spawn" : 0 + 8 : "Blind" : 0 + ] +] +@PointClass base(Monsters) model({ "path": ":models/monsters/soldier/tris.md2", "skin": 2 }) = monster_soldier : "Soldier" +[ + spawnflags(Flags) = + [ + 1 : "Ambush" : 0 + 2 : "Trigger Spawn" : 0 + 8 : "Blind" : 0 + ] +] +@PointClass base(Monsters) model({ "path": ":models/monsters/soldier/tris.md2", "skin": 4 }) = monster_soldier_ss : "SS Soldier" +[ + spawnflags(Flags) = + [ + 1 : "Ambush" : 0 + 2 : "Trigger Spawn" : 0 + 8 : "Blind" : 0 + ] +] +@PointClass base(Monsters) size(-32 -32 -16, 32 32 72) model({ "path": ":models/monsters/tank/tris.md2" }) = monster_tank : "Tank" [] +@PointClass base(Monsters) size(-32 -32 -16, 32 32 72) model({ "path": ":models/monsters/tank/tris.md2", "skin": 2 }) = monster_tank_commander : "Tank Commander" +[ + speed(float) : "homing missile speed" : "0.0" + spawnflags(Flags) = + [ + 8 : "N64" : 0 + 16 : "Heat-Seeking Missiles" : 0 + ] +] +@PointClass base(Monsters) size(-64 -64 0, 64 64 72) model({ "path": ":models/monsters/boss1/tris.md2" }) = monster_supertank : "Super Tank Boss" +[ + spawnflags(Flags) = + [ + 1 : "Ambush" : 0 + 2 : "Trigger Spawn" : 0 + 8 : "Power Shield" : 0 + ] +] + +// Expansions +@PointClass base(Monsters) model({ "path": ":models/monsters/soldier/tris.md2", "skin": 6 }) = monster_soldier_ripper : "Ripper Soldier" +[ + spawnflags(Flags) = + [ + 1 : "Ambush" : 0 + 2 : "Trigger Spawn" : 0 + 8 : "Blind" : 0 + ] +] +@PointClass base(Monsters) model({ "path": ":models/monsters/soldier/tris.md2", "skin": 8 }) = monster_soldier_hypergun : "Hypergun Soldier" +[ + spawnflags(Flags) = + [ + 1 : "Ambush" : 0 + 2 : "Trigger Spawn" : 0 + 8 : "Blind" : 0 + ] +] +@PointClass base(Monsters) model({ "path": ":models/monsters/soldier/tris.md2", "skin": 10 }) = monster_soldier_lasergun : "Lasergun Soldier" +[ + spawnflags(Flags) = + [ + 1 : "Ambush" : 0 + 2 : "Trigger Spawn" : 0 + 8 : "Blind" : 0 + ] +] + +@PointClass base(Monsters) size(-32 -32 -24, 32 32 24) model({ "path": ":models/monsters/fixbot/tris.md2" }) = monster_fixbot : "Fixbot" +[ + spawnflags(Flags) = + [ + 1 : "Ambush" : 0 + 2 : "Trigger Spawn" : 0 + 4 : "Fix stuff" : 0 + 8 : "Takeoff" : 0 + 16 : "Landing" : 0 + ] +] + +@SolidClass base(EditorFlags, Targetable) color (0 128 204) = func_object_repair : "Fixbot repair object" +[ + delay(float) : "delay between sparks" : "1" +] + +@PointClass base(Monsters) size(-24 -24 -24, 24 24 24) + model( + {{ + spawnflags & 65536 -> { "path": ":models/monsters/gekk/tris.md2", "skin": 2, "frame": 141 }, + ":models/monsters/gekk/tris.md2" + }} + ) = +monster_gekk : "Gekk" +[ + spawnflags(Flags) = + [ + 1 : "Ambush" : 0 + 2 : "Trigger Spawn" : 0 + 8 : "Chanting" : 0 + 16: "No jumping" : 0 + 32: "No swimming" : 0 + ] +] +@PointClass base(Monsters) model({ "path": ":models/monsters/bitch/tris.md2", "skin": 2 }) size(-16 -16 0, 16 16 56) = monster_chick_heat : "Iron Maiden (heat-seeking missile)" [] +@PointClass base(Monsters) size(-32 -32 -24, 32 32 64) model({ "path": ":models/monsters/gladiatr/tris.md2", "skin": 2 }) = monster_gladb : "Gladiator (phalanx)" [] + +@PointClass base(Monsters) size(-64 -64 0, 64 64 72) model({ "path": ":models/monsters/boss1/tris.md2", "skin": 2 }) = monster_boss5 : "Super Tank Boss (power armor)" [] + +@PointClass base(info_notnull, Target, Targetable) = hint_path : "Monster Path" +[ + spawnflags(Flags) = + [ + 1 : "Endpoint" : 0 + ] + wait(float) : "wait for this amount of time" : "0" +] + +@PointClass base(Monsters) model({ "path": ":models/monsters/hover/tris.md2", "skin": 2 }) = monster_daedalus : "Daedalus" [] + +@PointClass base(Monsters) size(-56 -56 -44, 56 56 44) model({ "path": ":models/monsters/carrier/tris.md2" }) = monster_carrier : "Carrier" +[ + reinforcements(string): "Semicolon separated list of entity classes and cost of monster to spawn" : "monster_flyer 1;monster_flyer 1;monster_flyer 1;monster_kamikaze 1" + monster_slots(integer) : "How many points to spend on monster spawns. Increases by up to 2x for Hard skill" : 3 +] + +@PointClass base(Monsters) size(-40 -40 0, 40 40 144) model({ "path": ":models/monsters/blackwidow/tris.md2" }) = monster_widow : "Black Widow (on foot)" [] +//Why is there two of these with no difference? +//@PointClass base(Monsters) size(-70 -70 0, 70 70 144) model({ "path": ":models/monsters/blackwidow2/tris.md2" }) = monster_widow : "Black Widow (spider)" [] + +@PointClass base(Monsters) model({ "path": ":models/monsters/medic/tris.md2", "skin": 2 }) = monster_medic_commander : "Medic Commander" +[ + reinforcements(string): "Semicolon separated list of entity classes and cost of monster to spawn" : "monster_soldier_light 1;monster_soldier 2;monster_soldier_ss 2;monster_infantry 3;monster_gunner 4;monster_medic 5;monster_gladiator 6" + monster_slots(integer) : "How many points to spend on monster spawns. Increases by up to 2x for Hard skill" : 3 +] +@PointClass base(Monsters) model({ "path": ":models/monsters/flyer/tris.md2" }) = monster_kamikaze : "Flyer (kamikaze)" [] + +@PointClass base(Monsters) size(-12 -12 -12, 12 12 12) model({ "path": ":models/monsters/turret/tris.md2" }) = monster_turret : "Turret Monster" +[ + spawnflags(Flags) = + [ + 1 : "Ambush" : 0 + 2 : "Trigger Spawn" : 0 + 8 : "Blaster" : 0 + 16 : "Chaingun" : 0 + 32 : "Rocket" : 0 + 128: "Wall Unit (must be targeted)" : 0 + 262144 : "No Lasersight (beware)" : 0 + ] +] + +@PointClass base(Monsters) size(-28 -28 -18, 28 28 18) model({ "path": ":models/monsters/stalker/tris.md2" }) = monster_stalker : "Stalker" +[ + spawnflags(Flags) = + [ + 1 : "Ambush" : 0 + 2 : "Trigger Spawn" : 0 + 8 : "Start On Roof" : 0 + 16 : "No Jumping" : 0 + ] +] + +// using a "wait" value of -1 on a path corner causes a func_train to go silent between +// itself and the next path corner when the train is restarted. The train's sound will +// resume as soon as it reaches a path corner with a "wait" value other than -1 +@PointClass base(EditorFlags, Targetable, UseTargets) color(128 76 0) size(-8 -8 -8, 8 8 8) = path_corner : "Path marker" +[ + spawnflags(Flags) = + [ + 1 : "Teleport" : 0 + ] + target(target_destination) : "Next path target" + pathtarget(target_destination) : "Event to trigger" + wait(choices) : "Wait" : 0 = + [ + -1 : "Wait for retrigger" + ] +] + +@PointClass base(path_corner) color(128 76 9) size(-8 -8 -8, 8 8 8) = point_combat : "Point of combat - should be the first/only target of a monster" +[ + spawnflags(Flags) = + [ + 1 : "Hold" : 0 + ] +] + +@PointClass base(EditorFlags, Targetable, Angleable) color(255 0 0) size(-8 -8 -8, 8 8 8) = target_blaster : "Blaster (use angles to point it)" +[ + spawnflags(Flags) = + [ + 1 : "No Trail" : 0 + 2 : "No Effects" : 0 + ] + dmg(float) : "Damage" : "15" + speed(float) : "Speed" : "1000" +] + +// set "map" value to "mapname$playername" where playername equals +// the targetname of a corresponding info_player_start in the +// next map. To play a cinematic before starting the level, the +// "map" value should be "cinemeatic.cin+mapname$playername". Note +// that a playername is not required if the corresponding info_player_start +// doesn't have a targetname. If you want this to be designated as the last +// level of a unit, place an asterix (*) before the map name. +@PointClass base(EditorFlags, Targetable, Target) color(255 0 0) size(-8 -8 -8, 8 8 8) = target_changelevel : "Change level" +[ + spawnflags(Flags) = + [ + 8 : "Clear Inventory" : 0 + 16 : "No End of Unit" : 0 + 32 : "Fade out" : 0 + 64 : "Immediate Leave" : 0 + ] + map(string) : "Next map; append $targetname to spawn at matched targetname" + target(target_destination) : "Name of info_landmark in the next map to link this exit to" +] + +@PointClass base(EditorFlags, Targetable) color(128 128 128) size(-8 -8 -8, 8 8 8) = target_crosslevel_trigger : "Cross-level trigger" +[ + spawnflags(Flags) = + [ + 1 : "Trigger 1" : 0 + 2 : "Trigger 2" : 0 + 4 : "Trigger 3" : 0 + 8 : "Trigger 4" : 0 + 16 : "Trigger 5" : 0 + 32 : "Trigger 6" : 0 + 64 : "Trigger 7" : 0 + 128 : "Trigger 8" : 0 + + 65536 : "Trigger 9" : 0 + 131072 : "Trigger 10" : 0 + 262144 : "Trigger 11" : 0 + 524288 : "Trigger 12" : 0 + 1048576 : "Trigger 13" : 0 + 2097152 : "Trigger 14" : 0 + 4194304 : "Trigger 15" : 0 + 8388608 : "Trigger 16" : 0 + ] +] + +@PointClass base(EditorFlags, Targetable, UseTargets) color(128 128 128) size(-8 -8 -8, 8 8 8) = target_crosslevel_target : "Cross-level trigger" +[ + spawnflags(Flags) = + [ + 1 : "Trigger 1" : 0 + 2 : "Trigger 2" : 0 + 4 : "Trigger 3" : 0 + 8 : "Trigger 4" : 0 + 16 : "Trigger 5" : 0 + 32 : "Trigger 6" : 0 + 64 : "Trigger 7" : 0 + 128 : "Trigger 8" : 0 + + 65536 : "Trigger 9" : 0 + 131072 : "Trigger 10" : 0 + 262144 : "Trigger 11" : 0 + 524288 : "Trigger 12" : 0 + 1048576 : "Trigger 13" : 0 + 2097152 : "Trigger 14" : 0 + 4194304 : "Trigger 15" : 0 + 8388608 : "Trigger 16" : 0 + ] +] + +@PointClass base(target_crosslevel_trigger) color(128 128 128) size(-8 -8 -8, 8 8 8) = target_crossunit_trigger : "Cross-unit trigger" +[ +] + +@PointClass base(target_crosslevel_target) color(128 128 128) size(-8 -8 -8, 8 8 8) = target_crossunit_target : "Cross-unit target" +[ +] + +@PointClass base(EditorFlags, Targetable) color(255 0 0) size(-8 -8 -8, 8 8 8) = target_earthquake : "Level wide earthquake" +[ + spawnflags(Flags) = + [ + 1 : "Silent" : 0 + 2 : "Toggle" : 0 + 8 : "One Shot" : 0 + ] + + speed(float) : "Severity of quake" : "200" + count(float) : "Duration" : "5" +] + +@PointClass base(EditorFlags, Targetable, UseTargets) color(255 0 0) size(-8 -8 -8, 8 8 8) = target_explosion : "Explosion" +[ + delay(float) : "Delay before explosion" + dmg(float) : "Radius damage" : "0" +] + +@PointClass base(EditorFlags, Targetable, UseTargets) color(255 0 255) size(-8 -8 -8, 8 8 8) = target_goal : "Counts a goal completed" +[ + spawnflags(Flags) = + [ + 1 : "Keep Music" : 0 + ] +] + +@PointClass base(EditorFlags, Targetable) color(255 0 255) size(-8 -8 -8, 8 8 8) = target_healthbar : "Monster Health bar" +[ + delay(float) : "Delay before healthbar disappears after death" : "0.0" + message(string) : "Boss name" : "" + target(string): "Boss" : "" +] + +@PointClass base(EditorFlags, Targetable) color(255 0 255) size(-8 -8 -8, 8 8 8) = target_help : "Computer help message" +[ + spawnflags(Flags) = + [ + 1 : "Main Onjective" : 0 + 2 : "Set POI on use" : 0 + ] + message(string) : "Computer message" +] + +// if no color spawnflags are set, the laser color defaults to dim gray (and hard to see) +// setting the damage to 0 makes it use the default damage of 1 +// setting the damage to a negative number will actually give health +@PointClass base(EditorFlags, Targetable, Target, Angleable) color(0 128 204) size(-8 -8 -8, 8 8 8) = target_laser : "Laser" +[ + spawnflags(Flags) = + [ + 1 : "Start On" : 0 + 2 : "Red" : 0 + 4 : "Green" : 0 + 8 : "Blue" : 0 + 16 : "Yellow" : 0 + 32 : "Orange" : 0 + 64 : "Fat" : 0 + 128 : "Stop on windows" : 0 + 65536 : "Lightning" : 0 + ] + dmg(float) : "Damage" + rgba(integer) : "Laser colors; four palette indices separated by spaces" +] + +// Expansion +@PointClass base(EditorFlags, Targetable) color(255 0 0) size(-8 -8 -8, 8 8 8) = misc_nuke : "Nuke (kill all entities)" +[ +] + +@PointClass base(EditorFlags, Targetable, Target) color(0 128 204) size(-8 -8 -8, 8 8 8) = target_mal_laser : "Laser (Xatrix; Mal Laser)" +[ + spawnflags(Flags) = + [ + 1 : "Start On" : 0 + 2 : "Red" : 0 + 4 : "Green" : 0 + 8 : "Blue" : 0 + 16 : "Yellow" : 0 + 32 : "Orange" : 0 + 64 : "Fat" : 0 + ] + delay(float) : "Delay" : "0.1" + wait(float) : "Wait" : "0.1" + dmg(float) : "Damage" +] + +@PointClass base(EditorFlags, Targetable, Target) color(0 128 204) size(-8 -8 -8, 8 8 8) = target_lightramp : "Light ramp" +[ + spawnflags(Flags) = + [ + 1 : "Toggle" : 0 + ] + speed(float) : "Speed" + message(string) : "start/end light level" +] + +@PointClass base(EditorFlags, Targetable, UseTargets) color(255 0 255) size(-8 -8 -8, 8 8 8) = target_secret : "Counts a secret found" +[ + message(string) : "Message to print" + noise(string) : "Noise to play" : "misc/secret.wav" +] + +// set speed and angle, otherwise spawned object drops +@PointClass base(EditorFlags, Targetable, Angleable) color(255 0 0) size(-8 -8 -8, 8 8 8) = target_spawner : "Monster/Item spawner" +[ + target(target_destination) : "Monster/Item to spawn" + speed(float) : "Speed" +] + +@PointClass base(EditorFlags, Targetable) color(255 0 0) size(-8 -8 -8, 8 8 8) = target_speaker : "Sound player" +[ + spawnflags(Flags) = + [ + 1 : "Looped On" : 0 + 2 : "Looped Off" : 0 + 4 : "Reliable" : 0 + 8 : "No Stereo Panning" : 0 + ] + noise(string) : "Sound (path/file.wav)" + attenuation(Choices) : "Attenuation" : 0 = + [ + -1 : "None, send to whole level" + 0 : "Default (1 for non-looped, 3 for looped)" + 1 : "Normal fighting sounds" + 2 : "Idle sound level" + 3 : "Ambient sound level" + ] + volume(float) : "Volume (0.0 - 1.0)" : "1" +] + + +@PointClass base(EditorFlags) color(255 0 0) size(-8 -8 -8, 8 8 8) = misc_amb4 : "Amb4 speaker" +[ +] + +@PointClass base(EditorFlags, Targetable) color(255 0 0) size(-8 -8 -8, 8 8 8) = target_music : "Change music" +[ + sounds(integer) : "CD Track Number" : 1 +] + + +// "sounds" values other than 1 are silent. leaving in the other +// options for availability to mods/fixes +// +@PointClass base(EditorFlags, Targetable) color(255 0 0) size(-8 -8 -8, 8 8 8) = target_splash : "Creates a splash when used" +[ + sounds(choices) : "Type of splash" : 2 = + [ + 1 : "Sparks" + 2 : "Blue water" + 3 : "Brown water" + 4 : "Slime" + 5 : "Lava" + 6 : "Blood" + 7 : "N64 Sparks" + ] + count(integer) : "Number of pixels in splash (1 - 255)" + dmg(float) : "Radius damage" +] + +// eye candy... Particles #2 (style 22) is quite cool +@PointClass base(EditorFlags, Targetable) color(255 0 0) size(-8 -8 -8, 8 8 8) = target_temp_entity : "Temp entity" +[ + style(choices) : "Style" : 22 = + [ + 6 : "Grenade explosion #1" + 8 : "Grenade explosion #2" + 18 : "Grenade explosion #3 (underwater)" + 20 : "Green Fireball (BFG small)" + 21 : "Particles #1 (BFG big)" + 22 : "Particles #2 (boss teleport)" + 35 : "Plain explosion" + 45 : "chainfist smoke" + 47 : "Tracker Explosion (black particles)" + 48 : "Teleport effect #1" + 49 : "Teleport effect #2" + 51 : "Nuke Blast" + 52 : "Widow Splash" + 58 : "Berserk Slam" + ] +] + +@PointClass base(EditorFlags, UseTargets) color(128 128 128) size(-8 -8 -8, 8 8 8) = trigger_always : "Always triggers" [] + +@PointClass base(EditorFlags, Targetable, UseTargets) color(128 128 128) = trigger_counter : "Counter" +[ + spawnflags(Flags) = + [ + 1 : "No Message" : 0 + ] + count(integer) : "Count before trigger" : 2 +] + +@PointClass base(EditorFlags, Targetable, Target) color(76 25 153) = trigger_elevator : "Elevator trigger; note that whoever targets this must have a pathtarget set" [] + +@SolidClass base(EditorFlags, Targetable) color(128 128 128) = trigger_hurt : "Hurts on touch" +[ + spawnflags(Flags) = + [ + 1 : "Start Off" : 0 + 2 : "Toggle" : 0 + 4 : "Silent" : 0 + 8 : "No Protection" : 0 + 16 : "Slow hurt" : 0 + 32 : "No Players" : 0 + 64 : "No Monsters" : 0 + 128 : "Clipped (must touch brushes, not just aabb)" : 0 + ] + dmg(float) : "Damage" : "5" +] + +@PointClass base(EditorFlags, Targetable, UseTargets) color(128 128 128) size(-8 -8 -8, 8 8 8) = trigger_key : "Triggers with key" +[ + item(string) : "Item classname" : "key_blue_key" +] + +@SolidClass base(EditorFlags, Targetable) color(128 128 128) = trigger_monsterjump : "Makes monsters jump" +[ + spawnflags(Flags) = + [ + 1 : "Toggle" : 0 + 2 : "Start Off" : 0 + 4 : "Clipped (must touch brushes, not just aabb)" : 0 + ] + + speed(float) : "Speed thrown forward" : "200" + height(float) : "Height thrown upward" : "200" +] + +@PointClass base(EditorFlags, Targetable, UseTargets) color(128 128 128) = trigger_relay : "Relay trigger" [] + +@SolidClass base(trigger_relay) = trigger_once : "Single fire trigger" +[ + spawnflags(Flags) = + [ + 4 : "Triggered" : 0 + 32 : "Clipped (must exactly touch brushes, not just aabb)" : 0 + ] +] + +@SolidClass base(trigger_once) = trigger_multiple : "Multiple fire trigger" +[ + spawnflags(Flags) = + [ + 1 : "Monster" : 0 + 2 : "Not Player" : 0 + 4 : "Triggered (triggering will enable; starts off)" : 0 + 8 : "Toggled (triggering will toggle between on/off; starts off)" : 0 + 16 : "Latched (will trigger when an entity first enters & last leaves)" : 0 + 32 : "Clipped (must exactly touch brushes, not just aabb)" : 0 + ] + wait(float) : "Seconds between triggers" : "0" +] + +@SolidClass base(EditorFlags) color(128 128 128) = trigger_push : "Push trigger" +[ + spawnflags(Flags) = + [ + 1 : "Push Once" : 0 + 2 : "Wait & Effect" : 0 + 4 : "Silent" : 0 + 8 : "Start Off" : 0 + 16 : "Clipped (must exactly touch brushes, not just aabb)" : 0 + ] + speed(float) : "Speed of push" : "1000" + wait(float) : "Time to wait between pushes" : "10" +] + +// Expansion +@SolidClass base(EditorFlags) color(128 128 128) = trigger_gravity : "Gravity trigger" +[ + spawnflags(Flags) = + [ + 1 : "Toggle" : 0 + 2 : "Start Off" : 0 + 4 : "Clipped (must exactly touch brushes, not just aabb)" : 0 + ] + gravity(float) : "Gravity (standard = 1.0)" : "1" +] + +@SolidClass base(EditorFlags, Targetable, Target, EWT_base_BModel, Teamable) color(128 255 128) = turret_breach : "Turret breach" +[ + speed(float) : "Speed" : "50" + dmg(float) : "Damage" : "10" + minpitch(float) : "Miminum pitch angle" : "-30" + maxpitch(float) : "Maximum pitch angle" : "30" + minyaw(float) : "Minimum yaw angle" : "0" + maxyaw(float) : "Maximum yaw angle" : "360" +] + +@SolidClass base(EditorFlags, EWT_base_BModel, Teamable) color(128 255 128) = turret_base : "Turret base" +[ +] + +@PointClass base(EditorFlags) color(128 255 128) size(-16 -16 -24, 16 16 32) model({ "path": ":models/monsters/infantry/tris.md2"}) = turret_driver : "Turret driver" [ target(target_destination) : "Target (turret_breach)" ] + +// Expansions +@PointClass base(EditorFlags) color(128 255 128) size(-4 -4 -4, 4 4 4) = turret_invisible_brain : "Turret invisible brain" +[ + delay(float) : "Delay between firing; leave unset for skill ramping" : "0" + target(target_destination) : "Target (turret_breach)" + killtarget(target_destination) : "Entity to be attacked" +] + +@SolidClass base(EWT_base_BModel) color(0 128 204) = func_group : "group" [] + +// Paril: CTF +@PointClass base(EditorFlags) color(255 128 0) size(-4 -4 0, 4 4 246) model({ "path": ":models/ctf/banner/tris.md2" }) = misc_ctf_banner : "Flowing CTF banner" +[ + spawnflags(Flags) = + [ + 1 : "Blue Team" : 0 + ] +] + +@PointClass base(EditorFlags) color(255 128 0) size(-4 -4 0, 4 4 246) model({ "path": ":models/ctf/banner/small.md2" }) = misc_ctf_small_banner : "Smaller flowing CTF banner" +[ + spawnflags(Flags) = + [ + 1 : "Blue Team" : 0 + ] +] + +// Expansions +@PointClass base(EditorFlags, Targetable) color(255 0 0) size(-8 -8 -8, 8 8 8) = target_killplayers : "Nuke (kill all players)" [] + +@PointClass base(EditorFlags, Targetable) color(255 0 0) size(-8 -8 -8, 8 8 8) = target_anger : "Anger (make monster angry at something)" +[ + killtarget(target_destination) : "Target to be angry at" + target(target_destination) : "Target(s) to make angry" +] + +@PointClass base(EditorFlags, Targetable, Target) color(255 0 0) size(-8 -8 -8, 8 8 8) = target_steam : "Steam Emitter" +[ + speed(float) : "velocity of particles" : "75" + count(integer) : "number of particles" : 32 + sounds(integer) : "color of particles (palette index)" : 8 + wait(float) : "seconds to run before stopping" +] + +@SolidClass base(EditorFlags) color(128 128 128) = trigger_disguise : "Disguise" +[ + spawnflags(Flags) = + [ + 2 : "Start On" : 0 + 4 : "Remove" : 0 + ] +] + +@SolidClass base(EditorFlags, Targetable, Target, Angleable) color(128 128 128) = trigger_teleport : "Teleport" +[ + spawnflags(Flags) = + [ + 8 : "Start On" : 0 + ] +] + +@SolidClass base(func_door_secret) color(0 128 204) = func_door_secret2 : "Secret Door (Rogue)" [] + +@SolidClass base(EditorFlags, Targetable, EWT_base_BModel) color(0 128 204) = func_force_wall : "Force Wall" +[ + spawnflags(Flags) = + [ + 1 : "Start On" : 0 + ] + style(integer) : "Color (palette index)" : 208 +] + +@SolidClass base(EditorFlags, Targetable, EWT_base_BModel) color(0 128 204) = func_plat : "OG Platform" +[ + spawnflags(Flags) = + [ + 1 : "Plat Low Trigger" : 0 + 2 : "No Monsters" : 0 + ] + speed(float) : "Speed" : "200" + accel(float) : "Acceleration" : "50" + decel(float) : "Acceleration" : "50" + lip(float) : "Lip remaining after move" : "8" + height(float) : "Movement distance" + dmg(float) : "Damage" : "2" +] + +@SolidClass base(EditorFlags, Targetable, EWT_base_BModel) color(0 128 204) = func_plat2 : "Callable Platform" +[ + spawnflags(Flags) = + [ + 1 : "Plat Low Trigger" : 0 + 2 : "Toggle" : 0 + 4 : "Top" : 0 + 8 : "Start Enabled" : 0 + 32 : "Box Lift" : 0 + ] + speed(float) : "Speed" : "200" + accel(float) : "Acceleration" : "50" + decel(float) : "Acceleration" : "50" + lip(float) : "Lip remaining after move" : "0" + height(float) : "Movement distance" + dmg(float) : "Damage" : "2" +] + +@PointClass base(EditorFlags) = misc_flare : "(N64) Light Flare" +[ + spawnflags(Flags) = + [ + 1 : "Red rim" : 0 + 2 : "Green rim" : 0 + 4 : "Blue rim" : 0 + 8 : "Fixed rotation" : 0 + ] + image(string) : "image" : "" : "The path to a custom flare image to use instead of the default" + radius(float) : "1.0" : "Scale multiplier" + rgba(color255) : "Color" : "255 255 255 255" : "Flare color" + fade_start_dist(integer) : "Distance in units that fade begins, should be smaller than fade_end_dist" : 96 + fade_end_dist(integer) : "Distance in units that fade ends, should be greater than fade_start_dist" : 384 +] + +@PointClass base(EditorFlags, Targetable) = target_camera : "(N64) Camera" +[ + speed(float) : "Speed" : "75" +] + +@PointClass base(EditorFlags, Targetable) = info_landmark : "Landmark - connect one level to another with a seamless transition" +[ +] + +@PointClass base(Monsters) size(-32 -32 -24, 32 32 64) model({ "path": ":models/monsters/shambler/tris.md2" }) = monster_shambler : "Shambler" +[ +] + +@SolidClass base(EditorFlags) = trigger_flashlight : "Enable/Disable Flashlight" +[ + spawnflags(Flags) = + [ + 1 : "Clipped (must touch brushes, not just aabb)" : 0 + ] + style(choices) : "style" : 0 = + [ + 0 : "Default" + 1 : "Always turn on" + 2 : "Always turn off" + ] + angles(string) : "pitch yaw roll" : "Player traveling this direction will enable light, against it will disable." +] + +@PointClass base(EditorFlags) = misc_lavaball : "(N64) Lavaball" +[ +] + +@PointClass base(EditorFlags) = misc_hologram : "(N64) Ship Hologram" +[ +] + +@SolidClass base(EditorFlags) = trigger_fog : "Fog Transitions" +[ + spawnflags(Flags) = + [ + 1 : "Affect global fog" : 0 + 2 : "Affect height fog" : 0 + 4 : "Instaneous transition" : 0 + 8 : "Force use (regardless of direction)" : 0 + 16 : "Blend" : 0 + ] + angles(string) : "angles" : "0 0 0" : "Direction of player travel for start/end" + target(target_destination) : "info_notnull target for fog params" + delay(float) : "Delay": "0.5" : "Time in seconds after transition starts to fully end" + wait(float) : "Wait" : "0.0" : "Time in seconds before a re-trigger will occur; mainly for Blend" + fog_density(float) : "density value of fog, 0-1" + fog_color(color) : "color value of fog, 3d vector with values between 0-1 (r g b)" + fog_sky_factor(float) : "sky_factor value of fog, 0-1" + fog_density_off(float) : "transition density value of fog, 0-1" + fog_color_off(color) : "transition color value of fog, 3d vector with values between 0-1 (r g b)" + fog_sky_factor_off(float) : "transition sky_factor value of fog, 0-1" + + heightfog_falloff(float) : "falloff value of heightfog, 0-1" + heightfog_density(float) : "density value of heightfog, 0-1" + heightfog_start_color(color) : "start color of heightfog, 3d vector with values between 0-1 (r g b)" + heightfog_end_color(color) : "end color of heightfog, 3d vector with values between 0-1 (r g b)" + heightfog_start_dist(float) : "start distance value of heightfog, in world units" + heightfog_end_dist(float) : "start distance value of heightfog, in world units" + + heightfog_falloff_off(float) : "transition falloff value of heightfog, 0-1" + heightfog_density_off(float) : "transition density value of heightfog, 0-1" + heightfog_start_color_off(color) : "transition start color of heightfog, 3d vector with values between 0-1 (r g b)" + heightfog_end_color_off(color) : "transition end color of heightfog, 3d vector with values between 0-1 (r g b)" + heightfog_start_dist_off(float) : "transition start distance value of heightfog, in world units" + heightfog_end_dist_off(float) : "transition start distance value of heightfog, in world units" +] + +@SolidClass base(EditorFlags, Angleable, UseTargets, EWT_base_BModel) color(0 128 204) = func_eye : "Camera-like brush model" +[ + pathtarget(target_destination) : "point to an info_notnull (which gets freed after spawn) attached to the bmodel to automatically set the eye_position" + eye_position(string) : "x y z" : "Offset from the origin brush's center of the camera where the eye is located" + radius(float) : "512" : "detection radius for players" + speed(float) : "45" : "how fast, in degrees per second, we should move on each axis to reach the target" + vision_cone(float) : "0.5" : "how wide the cone of vision should be" + wait(float) : "the amount of time to wait after losing target before returning to neutral angles" +] + +@SolidClass base(EditorFlags, Targetable, EWT_base_BModel, BmodelAnim) color(0 128 204) = func_animation : "Similar to func_wall, but triggering it will toggle animation state rather than going on/off." +[ + spawnflags(Flags) = + [ + 1 : "Start in Alternate" : 0 + ] +] + +@SolidClass base(EditorFlags, Targetable, UseTargets, EWT_base_BModel) color(128 128 128) = trigger_coop_relay : "Co-op relay trigger; will only fire if all entities are inside of the bounds, otherwise prints messages." +[ + spawnflags(Flags) = + [ + 1 : "Auto-Fire" : 0 + ] + + message(string) : "Trigger message to send to the triggerer" + message2(string) : "Trigger message to send to the players not in the bounds" + wait(float) : "1.0" : "Time in seconds for auto-fire triggers to check bounds" +] + +@PointClass base(Monsters) size(-96 -96 -66, 96 96 62) model({ "path": ":models/monsters/guardian/tris.md2" }) = monster_guardian : "Guardian (PSX)" [] +@PointClass base(Monsters) size(-48 -48 -20, 48 48 48) model({ "path": ":models/monsters/arachnid/tris.md2" }) = monster_arachnid : "Arachnid (PSX)" [] + +@PointClass base(EditorFlags, Targetable, Teamable) color(255 0 0) size(-4 -4 -4, 4 4 4) = target_poi : "Compass POI" +[ + spawnflags(Flags) = + [ + 1 : "Pick nearest to player (teamed)" : 0 + 2 : "Dummy" : 0 + 4 : "Dynamic (teamed)" : 0 + ] + + count(integer) : "stage value; if set, will only allow activation if last stage activated is <= this value" : 0 + style(integer) : "teamed POIs only; the lowest styled POI will take priority" : 0 + image(string) : "Image to display at the POI" : "friend" +] + +@PointClass base(Angleable, EditorFlags, Target, Targetable) color(255 255 0) size(-8 -8 -8, 8 8 8) = info_world_text : "Info_World_Text" +[ + spawnflags(Flags) = + [ + 1 : "Start Off" : 0 + 2 : "Trigger Once" : 0 + 4 : "Remove On Trigger" : 0 + ] + + angle(float) : "Direction. -3 = Track Player. -2 = Down, -1 = Up, 0 - 360 = Yaw" : "-3" + radius(float) : "Text Size" : "0.2" + sounds(integer) : "Color Of Text. 0 = White, 1 = Red, 2 = Blue, 3 = Green, 4 = Yellow, 5 = Black, 6 = Cyan, 7 = Orange" : 0 +] + +@PointClass base(EditorFlags, Target, Targetable) = info_nav_lock : "Flip locked flag on targeted doors" +[ +] + + +@PointClass base(EditorFlags, Targetable, UseTargets) color(128 128 128) = trigger_health_relay : "Health relay trigger" +[ + speed(float) : "Percent of health that must be reached to fire" +] + +@PointClass base(EditorFlags, Targetable) = target_autosave : "Autosave" +[ +] + + +@PointClass base(EditorFlags, Targetable) = target_sky : "Change sky parameters" +[ + sky(string) : "Environment map name" + skyaxis(vector) : "Vector axis for rotating sky" + skyrotate(string) : "Speed of rotation (degrees/second)" + skyautorotate(integer) : "Disable to set sky rotation manually" : 1 +] + +@PointClass base(EditorFlags, Targetable) color(255 128 255) size(-16 -16 -24, 16 16 32) model({ "path": ":models/monsters/insane/tris.md2", "frame":209, "skin":1}) = misc_player_mannequin : "Player Mannequin" +[ + distance(integer) : "Gesture To Use When Triggered. 0 = FlipOff, 1 = Salute, 2 = Taunt, 3 = Wave, 4 = Point" : 1 + height(integer) : "Type Of Model To Use. 1 = Female, 2 = Male, 3 = Cyborg" : 1 + radius(float) : "Scale To Use For Model" + goals(string) : "Name Of MD2 Player Weapon Model To Hold. EX: 'w_hyperblaster'" + image(string) : "Name Of PCX Player Skin To Use. EX: 'venus'" +] diff --git a/fgd/ericw_tools.fgd b/fgd/ericw_tools.fgd new file mode 100644 index 0000000..a4c5543 --- /dev/null +++ b/fgd/ericw_tools.fgd @@ -0,0 +1,213 @@ +@BaseClass = EWT_base_Worldspawn +[ + light(float) : "Ambient light" : "0" : "Set a global minimum light level of 'n' across the whole map. This is an easy way to eliminate completely dark areas of the level, however you may lose some contrast as a result, so use with care. Default 0" + _minlight_color(color) : "Minlight color R G B" : "255 255 255" : "Specify red(r), green(g) and blue(b) components for the colour of the minunlight. RGB component values are between 0 and 255 (between 0 and 1 is also accepted). Default is white light (255 255 255)" + _minlightMottle(choices) : "Use mottle effect on minlight surfaces" : 1 = + [ + 0 : "Off" + 1 : "On" + ] + + _dist(float) : "Global light scale" : "1" : "Scales the fade distance of all lights by a factor of n. If n is more than 1 lights fade more quickly with distance and if n is less than 1, lights fade more slowly with distance and light reaches further" + _range(float) : "Global light range" : "0.5" : "Scales the brightness range of all lights without affecting their fade discance. Values of n more than 0.5 makes lights brighter and n less than 0.5 makes lights less bright. The same effect can be achieved on individual lights by adjusting both the 'light' and 'wait' attributes" + _gamma(float) : "Lightmap gamma" : "1" : "Adjust brightness of final lightmap. Default 1, >1 is brighter, <1 is darker" + _anglescale(float) : "Light angle scale" : "0.5" : "Sets a scaling factor for how much influence the angle of incidence of sunlight on a surface has on the brightness of the surface. n must be between 0.0 and 1.0. Smaller values mean less attenuation, with zero meaning that angle of incidence has no effect at all on the brightness. Default 0.5" + + _sunlight(float) : "Sunlight" : "0" : "Set the brightness of the sunlight coming from an unseen sun in the sky. Sky brushes (or more accurately bsp leafs with sky contents) will emit sunlight at an angle specified by the _sun_mangle key. Default 0" + _sun_mangle(string) : "Sun mangle (Yaw pitch roll)" : "0 -90 0" : "Specifies the direction of sunlight using yaw(x), pitch(y) and roll(z) in degrees. Yaw specifies the angle around the Z-axis from 0 to 359 degrees and pitch specifies the angle from 90 (straight up) to -90 (straight down). Roll has no effect, so use any value (e.g. 0). Default is straight down (0 -90 0)" + _sunlight_penumbra(float) : "Sunlight penumbra in degrees" : "0" : "Specifies the penumbra width, in degrees, of sunlight. Useful values are 3-4 for a gentle soft edge, or 10-20+ for more diffuse sunlight. Default is 0" + _sunlight_color(color) : "Sunlight color R G B" : "255 255 255" : "Specify red(r), green(g) and blue(b) components for the colour of the sunlight. RGB component values are between 0 and 255 (between 0 and 1 is also accepted). Default is white light (255 255 255)" + _sunlight2(float) : "Sunlight hemisphere brightness" : "0" : "Set the brightness of a large dome of lights positioned around the map (16K unit radius). Useful for simulating higly diffused light (e.g. cloudy skies) in outdoor areas. Default 0" + _sunlight2_color(color) : "Sunlight hemisphere color R G B" : "255 255 255" : "Specifies the colour of _sunlight2, same format as _sunlight_color. Default is white light (255 255 255)" + _sunlight3(float) : "Sunlight ground brightness" : "0" : "Set the brightness of a large dome of lights positioned around the map (16K unit radius). Useful for simulating higly diffused light (e.g. cloudy skies) in outdoor areas. Default 0" + _sunlight3_color(color) : "Sunlight ground color R G B" : "255 255 255" : "Specifies the colour of _sunlight2, same format as _sunlight_color. Default is white light (255 255 255)" + + + + _dirt(choices) : "Dirt mapping (AO)" : 0 : "1 enables dirtmapping (ambient occlusion) on all lights, borrowed from q3map2. This adds shadows to corners and crevices. You can override the global setting for specific lights with the _dirt light entity key or _sunlight_dirt, _sunlight2_dirt, and _minlight_dirt worldspawn keys. Default is no dirtmapping (-1)" = + [ + -1 : "Force off" + 0 : "Compiler settings" + 1 : "Force on" + ] + _sunlight_dirt(choices) : "Sunlight dirt" : 0 : "1 enables dirtmapping (ambient occlusion) on sunlight, -1 to disable (making it illuminate the dirtmapping shadows). Default is to use the value of '_dirt'" = + [ + -1 : "Force off" + 0 : "Compiler settings" + 1 : "Force on" + ] + _sunlight2_dirt(choices) : "Sunlight 2 dirt" : 0 : "1 enables dirtmapping (ambient occlusion) on sunlight2, -1 to disable. Default is to use the value of '_dirt'" = + [ + -1 : "Force off" + 0 : "Compiler settings" + 1 : "Force on" + ] + _minlight_dirt(choices) : "Minlight dirt" : 0 : "1 enables dirtmapping (ambient occlusion) on minlight, -1 to disable. Default is to use the value of '_dirt'" = + [ + -1 : "Force off" + 0 : "Compiler settings" + 1 : "Force on" + ] + _dirtmode(choices) : "Dirt mode" : 0 : "Choose between ordered (0, default) and randomized (1) dirtmapping." = + [ + 0 : "Ordered" + 1 : "Random" + ] + _dirtdepth(float) : "Dirt depth" : "128" : "Maximum depth of occlusion checking for dirtmapping, default 128." + _dirtscale(float) : "Dirt scale" : "1" : "Scale factor used in dirt calculations, default 1. Lower values (e.g. 0.5) make the dirt fainter, 2.0 would create much darker shadows" + _dirtgain(float) : "Dirt gain" : "1" : "Exponent used in dirt calculation, default 1. Lower values (e.g. 0.5) make the shadows darker and stretch further away from corners" + + _bounce(float) : "Light bounce" : "0" + _bouncescale(float) : "Light bounce scale" : "1" : "Scales brightness of bounce lighting." + _bouncecolorscale(float) : "Light bounce color scale" : "0" : "Weight for bounce lighting to use texture colors from the map: 0=ignore map textures (default), 1=multiply bounce light color by texture color." + +] + +@BaseClass = EWT_base_PointLight +[ + light(float) : "Brightness" : "300" : "Set the light intensity. Negative values are also allowed and will cause the entity to subtract light cast by other entities." + _color(color) : "Light color" + wait(float) : "Fade distance multiplier" : "1" : "Scale the fade distance of the light by 'n'. Values of n more than 1 make the light fade more quickly with distance, and values less than 1 make the light fade more slowly (and thus reach further)." + delay(choices) : "Attenuation" : : "Select an attenuation formula for the light" = [ + 0 : "Linear falloff (Default)" + 1 : "Inverse distance falloff" + 2 : "Inverse distance squared" + 3 : "No falloff" + 4 : "Local minlight" + 5 : "Inverse distance 2" + ] + mangle(string) : "Spotlight direction" : "0 -90 0" : "Turns the light into a spotlight and specifies the direction of light using yaw(x), pitch(y) and roll(z) in degrees. Yaw specifies the angle around the Z-axis from 0 to 359 degrees and pitch specifies the angle from 90 (straight up) to -90 (straight down). Roll has no effect, so use any value (e.g. 0). Often easier than the 'target' method" + angle(float) : "Spotlight cone angle" : "40" : "Specifies the angle in degrees for a spotlight cone." + _dirt(choices) : "Ambient occlusion (override)" : 0 : "Overrides the worldspawn setting of '_dirt' for this particular light." = [ + 0 : "Worldspawn decides" + -1 : "Force off" + 1 : "Force on" + ] + _dirtscale(float) : "AO scale (override)" : "1.0" : "Override the global '_dirtscale' or '_dirtgain' settings to change how this light is affected by dirtmapping (ambient occlusion). See descriptions of these keys in the worldspawn section" + _deviance(float) : "Deviance" : "0" : "Split up the light into a sphere of randomly positioned lights within radius 'n' (in world units). Useful to give shadows a wider penumbra. '_samples' specifies the number of lights in the sphere. The 'light' value is automatically scaled down for most lighting formulas (except linear and non-additive minlight) to attempt to keep the brightness equal. Default is 0, do not split up lights" + _samples(float) : "No. of Deviance lights" : "16" : "Number of lights to use for '_deviance'. Default 16 (only used if '_deviance' is set)" + _surface(string) : "Surface light texture name" : : "Makes surfaces with the given texture name emit light, by using this light as a template which is copied across those surfaces. Lights are spaced about 128 units (though possibly closer due to bsp splitting) apart and positioned 2 units above the surfaces" + _surface_spotlight(choices) : "Surface spotlight" : 0 : "Is this surface light a spotlight? If so, orient it so it points away from the surface normal" = [ + 0 : "No" + 1 : "Yes" + ] + _surface_offset(float) : "Surface light offset" : "2" : "Controls the offset lights are placed above surfaces for '_surface'. Default 2" + _softangle(float) : "Spotlight soft angle" : : "Specifies the angle in degrees for an inner spotlight cone (must be less than the 'angle' cone. Creates a softer transition between the full brightness of the inner cone to the edge of the outer cone. Default 0 (disabled)" + _anglescale(float) : "Light angle scale" : : "Sets a scaling factor for how much influence the angle of incidence of light on a surface has on the brightness of the surface. n must be between 0.0 and 1.0. Smaller values mean less attenuation, with zero meaning that angle of incidence has no effect at all on the brightness. Default 0.5" + _project_texture(string) : "Project a texture" + _project_mangle(string) : "Projection texture direction" + _project_fov(float) : "Projection texture FOV" : "90" + + _light_channel_mask(flags) = [ + 0 : "Legacy Behavior" : 1 + 1 : "Channel 1" + 2 : "Channel 2" + 4 : "Channel 3" + 8 : "Channel 4" + 16 : "Channel 5" + 32 : "Channel 6" + 64 : "Channel 7" + 128 : "Channel 8" + 256 : "Channel 9" + 512 : "Channel 10" + 1024 : "Channel 11" + 2048 : "Channel 12" + 4096 : "Channel 13" + 8192 : "Channel 14" + 16384 : "Channel 15" + 32768 : "Channel 16" + 65536 : "Channel 17" + 131072 : "Channel 18" + 262144 : "Channel 19" + 524288 : "Channel 20" + 1048576 : "Channel 21" + 2097152 : "Channel 22" + 4194304 : "Channel 23" + 8388608 : "Channel 24" + ] + + _shadow_channel_mask(flags) = [ + 0 : "Legacy Behavior" : 1 + 1 : "Channel 1" + 2 : "Channel 2" + 4 : "Channel 3" + 8 : "Channel 4" + 16 : "Channel 5" + 32 : "Channel 6" + 64 : "Channel 7" + 128 : "Channel 8" + 256 : "Channel 9" + 512 : "Channel 10" + 1024 : "Channel 11" + 2048 : "Channel 12" + 4096 : "Channel 13" + 8192 : "Channel 14" + 16384 : "Channel 15" + 32768 : "Channel 16" + 65536 : "Channel 17" + 131072 : "Channel 18" + 262144 : "Channel 19" + 524288 : "Channel 20" + 1048576 : "Channel 21" + 2097152 : "Channel 22" + 4194304 : "Channel 23" + 8388608 : "Channel 24" + ] +] + +@BaseClass = EWT_base_BModel +[ + _dirt(float) : "Dirt mapping (override)" + _minlight(float) : "Min light for all surface" + _minlight_exclude(string) : "Exclude texture from minlight" + _mincolor(color) : "Min light color" + _shadow(choices) : "Cast Shadows" = [ + 0 : "Do Nothing" + 1 : "Cast shadows" + ] + _shadowself(choices) : "Self Shadow" = [ + 0 : "Do Nothing" + 1 : "Cast shadows on Self" + ] + _phong(choices) : "Phong shading" = [ + 0 : "Disabled" + 1 : "Smooth shading" + ] + _phong_angle(float) : "Phong smoothing angle" : "89" + _mirrorinside(choices) : "Double sided faces" = [ + 0 : "Disabled" + 1 : "Double sided" + ] + _object_channel_mask(flags) = [ + 0 : "Legacy Behavior" : 1 + 1 : "Channel 1" + 2 : "Channel 2" + 4 : "Channel 3" + 8 : "Channel 4" + 16 : "Channel 5" + 32 : "Channel 6" + 64 : "Channel 7" + 128 : "Channel 8" + 256 : "Channel 9" + 512 : "Channel 10" + 1024 : "Channel 11" + 2048 : "Channel 12" + 4096 : "Channel 13" + 8192 : "Channel 14" + 16384 : "Channel 15" + 32768 : "Channel 16" + 65536 : "Channel 17" + 131072 : "Channel 18" + 262144 : "Channel 19" + 524288 : "Channel 20" + 1048576 : "Channel 21" + 2097152 : "Channel 22" + 4194304 : "Channel 23" + 8388608 : "Channel 24" + ] +] + + +@SolidClass base(EWT_base_BModel) color(0 128 204) = func_detail : "func_detail" [] +@SolidClass base(EWT_base_BModel) color(0 128 204) = func_detail_wall : "func_detail_wall" [] +@SolidClass base(EWT_base_BModel) color(0 128 204) = func_detail_illusionary : "func_detail_illusionary" [] \ No newline at end of file diff --git a/fgd/kexlights.fgd b/fgd/kexlights.fgd new file mode 100644 index 0000000..2a44501 --- /dev/null +++ b/fgd/kexlights.fgd @@ -0,0 +1,31 @@ + + +@PointClass size(-8 -8 -8, 8 8 8) color(255 128 0) base(EditorFlags, Target, Targetable) = dynamic_light : "Dynamic KEX light" [ + _color(color) : "Light color" + shadowlightradius(float) : "Sets the radius of a dynamic shadow light. Becomes a dynamic light if this is bigger than 0" : "0" + shadowlightintensity(float) : "Intensity scalar for the light." : "1.0" + shadowlightstartfadedistance(float) : "The distance in world units (from player's view position) in which fading begins" : "0" + shadowlightendfadedistance(float) : "The distance in world units (from player's view position) in which the light is fully faded out and not rendered" : "0" + shadowlightconeangle(float) : "Sets the outer cone angle if this light is a spot light." : "45" + shadowlightstyle(choices) : "Appearance" : "0" = [ + 0 : "Normal" + 10: "Fluorescent flicker" + 2 : "Slow, strong pulse" + 11: "Slow pulse, noblack" + 5 : "Gentle pulse" + 1 : "Flicker A" + 6 : "Flicker B" + 3 : "Candle A" + 7 : "Candle B" + 8 : "Candle C" + 4 : "Fast strobe" + 9 : "Slow strobe" + // New styles below here + ] + spawnflags(Flags) = + [ + 1 : "Start Off" : 0 + ] + shadowlightstyletarget(target_destination) : "Light entity to pick up style ID from" + shadowlightresolution(integer) : "Shadow map resolution; leave blank to automatically determine from radius" +] \ No newline at end of file diff --git a/original/baseq2/g_ai.c b/original/baseq2/g_ai.c new file mode 100644 index 0000000..c5ea318 --- /dev/null +++ b/original/baseq2/g_ai.c @@ -0,0 +1,1117 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +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. + +*/ +// g_ai.c + +#include "g_local.h" + +qboolean FindTarget (edict_t *self); +extern cvar_t *maxclients; + +qboolean ai_checkattack (edict_t *self, float dist); + +qboolean enemy_vis; +qboolean enemy_infront; +int enemy_range; +float enemy_yaw; + +//============================================================================ + + +/* +================= +AI_SetSightClient + +Called once each frame to set level.sight_client to the +player to be checked for in findtarget. + +If all clients are either dead or in notarget, sight_client +will be null. + +In coop games, sight_client will cycle between the clients. +================= +*/ +void AI_SetSightClient (void) +{ + edict_t *ent; + int start, check; + + if (level.sight_client == NULL) + start = 1; + else + start = level.sight_client - g_edicts; + + check = start; + while (1) + { + check++; + if (check > game.maxclients) + check = 1; + ent = &g_edicts[check]; + if (ent->inuse + && ent->health > 0 + && !(ent->flags & FL_NOTARGET) ) + { + level.sight_client = ent; + return; // got one + } + if (check == start) + { + level.sight_client = NULL; + return; // nobody to see + } + } +} + +//============================================================================ + +/* +============= +ai_move + +Move the specified distance at current facing. +This replaces the QC functions: ai_forward, ai_back, ai_pain, and ai_painforward +============== +*/ +void ai_move (edict_t *self, float dist) +{ + M_walkmove (self, self->s.angles[YAW], dist); +} + + +/* +============= +ai_stand + +Used for standing around and looking for players +Distance is for slight position adjustments needed by the animations +============== +*/ +void ai_stand (edict_t *self, float dist) +{ + vec3_t v; + + if (dist) + M_walkmove (self, self->s.angles[YAW], dist); + + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + { + if (self->enemy) + { + VectorSubtract (self->enemy->s.origin, self->s.origin, v); + self->ideal_yaw = vectoyaw(v); + if (self->s.angles[YAW] != self->ideal_yaw && self->monsterinfo.aiflags & AI_TEMP_STAND_GROUND) + { + self->monsterinfo.aiflags &= ~(AI_STAND_GROUND | AI_TEMP_STAND_GROUND); + self->monsterinfo.run (self); + } + M_ChangeYaw (self); + ai_checkattack (self, 0); + } + else + FindTarget (self); + return; + } + + if (FindTarget (self)) + return; + + if (level.time > self->monsterinfo.pausetime) + { + self->monsterinfo.walk (self); + return; + } + + if (!(self->spawnflags & 1) && (self->monsterinfo.idle) && (level.time > self->monsterinfo.idle_time)) + { + if (self->monsterinfo.idle_time) + { + self->monsterinfo.idle (self); + self->monsterinfo.idle_time = level.time + 15 + random() * 15; + } + else + { + self->monsterinfo.idle_time = level.time + random() * 15; + } + } +} + + +/* +============= +ai_walk + +The monster is walking it's beat +============= +*/ +void ai_walk (edict_t *self, float dist) +{ + M_MoveToGoal (self, dist); + + // check for noticing a player + if (FindTarget (self)) + return; + + if ((self->monsterinfo.search) && (level.time > self->monsterinfo.idle_time)) + { + if (self->monsterinfo.idle_time) + { + self->monsterinfo.search (self); + self->monsterinfo.idle_time = level.time + 15 + random() * 15; + } + else + { + self->monsterinfo.idle_time = level.time + random() * 15; + } + } +} + + +/* +============= +ai_charge + +Turns towards target and advances +Use this call with a distnace of 0 to replace ai_face +============== +*/ +void ai_charge (edict_t *self, float dist) +{ + vec3_t v; + + VectorSubtract (self->enemy->s.origin, self->s.origin, v); + self->ideal_yaw = vectoyaw(v); + M_ChangeYaw (self); + + if (dist) + M_walkmove (self, self->s.angles[YAW], dist); +} + + +/* +============= +ai_turn + +don't move, but turn towards ideal_yaw +Distance is for slight position adjustments needed by the animations +============= +*/ +void ai_turn (edict_t *self, float dist) +{ + if (dist) + M_walkmove (self, self->s.angles[YAW], dist); + + if (FindTarget (self)) + return; + + M_ChangeYaw (self); +} + + +/* + +.enemy +Will be world if not currently angry at anyone. + +.movetarget +The next path spot to walk toward. If .enemy, ignore .movetarget. +When an enemy is killed, the monster will try to return to it's path. + +.hunt_time +Set to time + something when the player is in sight, but movement straight for +him is blocked. This causes the monster to use wall following code for +movement direction instead of sighting on the player. + +.ideal_yaw +A yaw angle of the intended direction, which will be turned towards at up +to 45 deg / state. If the enemy is in view and hunt_time is not active, +this will be the exact line towards the enemy. + +.pausetime +A monster will leave it's stand state and head towards it's .movetarget when +time > .pausetime. + +walkmove(angle, speed) primitive is all or nothing +*/ + +/* +============= +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 +============= +*/ +int range (edict_t *self, edict_t *other) +{ + vec3_t v; + float len; + + VectorSubtract (self->s.origin, other->s.origin, v); + len = VectorLength (v); + if (len < MELEE_DISTANCE) + return RANGE_MELEE; + if (len < 500) + return RANGE_NEAR; + if (len < 1000) + return RANGE_MID; + return RANGE_FAR; +} + +/* +============= +visible + +returns 1 if the entity is visible to self, even if not infront () +============= +*/ +qboolean visible (edict_t *self, edict_t *other) +{ + vec3_t spot1; + vec3_t spot2; + trace_t trace; + + VectorCopy (self->s.origin, spot1); + spot1[2] += self->viewheight; + VectorCopy (other->s.origin, spot2); + spot2[2] += other->viewheight; + trace = gi.trace (spot1, vec3_origin, vec3_origin, spot2, self, MASK_OPAQUE); + + if (trace.fraction == 1.0) + return true; + return false; +} + + +/* +============= +infront + +returns 1 if the entity is in front (in sight) of self +============= +*/ +qboolean infront (edict_t *self, edict_t *other) +{ + vec3_t vec; + float dot; + vec3_t forward; + + AngleVectors (self->s.angles, forward, NULL, NULL); + VectorSubtract (other->s.origin, self->s.origin, vec); + VectorNormalize (vec); + dot = DotProduct (vec, forward); + + if (dot > 0.3) + return true; + return false; +} + + +//============================================================================ + +void HuntTarget (edict_t *self) +{ + vec3_t vec; + + self->goalentity = self->enemy; + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + self->monsterinfo.stand (self); + else + self->monsterinfo.run (self); + VectorSubtract (self->enemy->s.origin, self->s.origin, vec); + self->ideal_yaw = vectoyaw(vec); + // wait a while before first attack + if (!(self->monsterinfo.aiflags & AI_STAND_GROUND)) + AttackFinished (self, 1); +} + +void FoundTarget (edict_t *self) +{ + // let other monsters see this monster for a while + if (self->enemy->client) + { + level.sight_entity = self; + level.sight_entity_framenum = level.framenum; + level.sight_entity->light_level = 128; + } + + self->show_hostile = level.time + 1; // wake up other monsters + + VectorCopy(self->enemy->s.origin, self->monsterinfo.last_sighting); + self->monsterinfo.trail_time = level.time; + + if (!self->combattarget) + { + HuntTarget (self); + return; + } + + self->goalentity = self->movetarget = G_PickTarget(self->combattarget); + if (!self->movetarget) + { + self->goalentity = self->movetarget = self->enemy; + HuntTarget (self); + gi.dprintf("%s at %s, combattarget %s not found\n", self->classname, vtos(self->s.origin), self->combattarget); + return; + } + + // clear out our combattarget, these are a one shot deal + self->combattarget = NULL; + self->monsterinfo.aiflags |= AI_COMBAT_POINT; + + // clear the targetname, that point is ours! + self->movetarget->targetname = NULL; + self->monsterinfo.pausetime = 0; + + // run for it + self->monsterinfo.run (self); +} + + +/* +=========== +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. +============ +*/ +qboolean FindTarget (edict_t *self) +{ + edict_t *client; + qboolean heardit; + int r; + + if (self->monsterinfo.aiflags & AI_GOOD_GUY) + { + if (self->goalentity && self->goalentity->inuse && self->goalentity->classname) + { + if (strcmp(self->goalentity->classname, "target_actor") == 0) + return false; + } + + //FIXME look for monsters? + return false; + } + + // if we're going to a combat point, just proceed + if (self->monsterinfo.aiflags & AI_COMBAT_POINT) + return false; + +// if the first spawnflag bit is set, the monster will only wake up on +// really seeing the player, not another monster getting angry or hearing +// something + +// revised behavior so they will wake up if they "see" a player make a noise +// but not weapon impact/explosion noises + + heardit = false; + if ((level.sight_entity_framenum >= (level.framenum - 1)) && !(self->spawnflags & 1) ) + { + client = level.sight_entity; + if (client->enemy == self->enemy) + { + return false; + } + } + else if (level.sound_entity_framenum >= (level.framenum - 1)) + { + client = level.sound_entity; + heardit = true; + } + else if (!(self->enemy) && (level.sound2_entity_framenum >= (level.framenum - 1)) && !(self->spawnflags & 1) ) + { + client = level.sound2_entity; + heardit = true; + } + else + { + client = level.sight_client; + if (!client) + return false; // no clients to get mad at + } + + // if the entity went away, forget it + if (!client->inuse) + return false; + + if (client == self->enemy) + return true; // JDC false; + + if (client->client) + { + if (client->flags & FL_NOTARGET) + return false; + } + else if (client->svflags & SVF_MONSTER) + { + if (!client->enemy) + return false; + if (client->enemy->flags & FL_NOTARGET) + return false; + } + else if (heardit) + { + if (client->owner->flags & FL_NOTARGET) + return false; + } + else + return false; + + if (!heardit) + { + r = range (self, client); + + if (r == RANGE_FAR) + return false; + +// this is where we would check invisibility + + // is client in an spot too dark to be seen? + if (client->light_level <= 5) + return false; + + if (!visible (self, client)) + { + return false; + } + + if (r == RANGE_NEAR) + { + if (client->show_hostile < level.time && !infront (self, client)) + { + return false; + } + } + else if (r == RANGE_MID) + { + if (!infront (self, client)) + { + return false; + } + } + + self->enemy = client; + + if (strcmp(self->enemy->classname, "player_noise") != 0) + { + self->monsterinfo.aiflags &= ~AI_SOUND_TARGET; + + if (!self->enemy->client) + { + self->enemy = self->enemy->enemy; + if (!self->enemy->client) + { + self->enemy = NULL; + return false; + } + } + } + } + else // heardit + { + vec3_t temp; + + if (self->spawnflags & 1) + { + if (!visible (self, client)) + return false; + } + else + { + if (!gi.inPHS(self->s.origin, client->s.origin)) + return false; + } + + VectorSubtract (client->s.origin, self->s.origin, temp); + + if (VectorLength(temp) > 1000) // too far to hear + { + return false; + } + + // check area portals - if they are different and not connected then we can't hear it + if (client->areanum != self->areanum) + if (!gi.AreasConnected(self->areanum, client->areanum)) + return false; + + self->ideal_yaw = vectoyaw(temp); + M_ChangeYaw (self); + + // hunt the sound for a bit; hopefully find the real player + self->monsterinfo.aiflags |= AI_SOUND_TARGET; + self->enemy = client; + } + +// +// got one +// + FoundTarget (self); + + if (!(self->monsterinfo.aiflags & AI_SOUND_TARGET) && (self->monsterinfo.sight)) + self->monsterinfo.sight (self, self->enemy); + + return true; +} + + +//============================================================================= + +/* +============ +FacingIdeal + +============ +*/ +qboolean FacingIdeal(edict_t *self) +{ + float delta; + + delta = anglemod(self->s.angles[YAW] - self->ideal_yaw); + if (delta > 45 && delta < 315) + return false; + return true; +} + + +//============================================================================= + +qboolean M_CheckAttack (edict_t *self) +{ + vec3_t spot1, spot2; + float chance; + trace_t tr; + + if (self->enemy->health > 0) + { + // see if any entities are in the way of the shot + VectorCopy (self->s.origin, spot1); + spot1[2] += self->viewheight; + VectorCopy (self->enemy->s.origin, spot2); + spot2[2] += self->enemy->viewheight; + + tr = gi.trace (spot1, NULL, NULL, spot2, self, CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_SLIME|CONTENTS_LAVA|CONTENTS_WINDOW); + + // do we have a clear shot? + if (tr.ent != self->enemy) + return false; + } + + // melee attack + if (enemy_range == RANGE_MELEE) + { + // don't always melee in easy mode + if (skill->value == 0 && (rand()&3) ) + return false; + if (self->monsterinfo.melee) + self->monsterinfo.attack_state = AS_MELEE; + else + self->monsterinfo.attack_state = AS_MISSILE; + return true; + } + +// missile attack + if (!self->monsterinfo.attack) + return false; + + if (level.time < self->monsterinfo.attack_finished) + return false; + + if (enemy_range == RANGE_FAR) + return false; + + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + { + chance = 0.4; + } + else if (enemy_range == RANGE_MELEE) + { + chance = 0.2; + } + else if (enemy_range == RANGE_NEAR) + { + chance = 0.1; + } + else if (enemy_range == RANGE_MID) + { + chance = 0.02; + } + else + { + return false; + } + + if (skill->value == 0) + chance *= 0.5; + else if (skill->value >= 2) + chance *= 2; + + if (random () < chance) + { + self->monsterinfo.attack_state = AS_MISSILE; + self->monsterinfo.attack_finished = level.time + 2*random(); + return true; + } + + if (self->flags & FL_FLY) + { + if (random() < 0.3) + self->monsterinfo.attack_state = AS_SLIDING; + else + self->monsterinfo.attack_state = AS_STRAIGHT; + } + + return false; +} + + +/* +============= +ai_run_melee + +Turn and close until within an angle to launch a melee attack +============= +*/ +void ai_run_melee(edict_t *self) +{ + self->ideal_yaw = enemy_yaw; + M_ChangeYaw (self); + + if (FacingIdeal(self)) + { + self->monsterinfo.melee (self); + self->monsterinfo.attack_state = AS_STRAIGHT; + } +} + + +/* +============= +ai_run_missile + +Turn in place until within an angle to launch a missile attack +============= +*/ +void ai_run_missile(edict_t *self) +{ + self->ideal_yaw = enemy_yaw; + M_ChangeYaw (self); + + if (FacingIdeal(self)) + { + self->monsterinfo.attack (self); + self->monsterinfo.attack_state = AS_STRAIGHT; + } +}; + + +/* +============= +ai_run_slide + +Strafe sideways, but stay at aproximately the same range +============= +*/ +void ai_run_slide(edict_t *self, float distance) +{ + float ofs; + + self->ideal_yaw = enemy_yaw; + M_ChangeYaw (self); + + if (self->monsterinfo.lefty) + ofs = 90; + else + ofs = -90; + + if (M_walkmove (self, self->ideal_yaw + ofs, distance)) + return; + + self->monsterinfo.lefty = 1 - self->monsterinfo.lefty; + M_walkmove (self, self->ideal_yaw - ofs, distance); +} + + +/* +============= +ai_checkattack + +Decides if we're going to attack or do something else +used by ai_run and ai_stand +============= +*/ +qboolean ai_checkattack (edict_t *self, float dist) +{ + vec3_t temp; + qboolean hesDeadJim; + +// this causes monsters to run blindly to the combat point w/o firing + if (self->goalentity) + { + if (self->monsterinfo.aiflags & AI_COMBAT_POINT) + return false; + + if (self->monsterinfo.aiflags & AI_SOUND_TARGET) + { + if ((level.time - self->enemy->teleport_time) > 5.0) + { + if (self->goalentity == self->enemy) + if (self->movetarget) + self->goalentity = self->movetarget; + else + self->goalentity = NULL; + self->monsterinfo.aiflags &= ~AI_SOUND_TARGET; + if (self->monsterinfo.aiflags & AI_TEMP_STAND_GROUND) + self->monsterinfo.aiflags &= ~(AI_STAND_GROUND | AI_TEMP_STAND_GROUND); + } + else + { + self->show_hostile = level.time + 1; + return false; + } + } + } + + enemy_vis = false; + +// see if the enemy is dead + hesDeadJim = false; + if ((!self->enemy) || (!self->enemy->inuse)) + { + hesDeadJim = true; + } + else if (self->monsterinfo.aiflags & AI_MEDIC) + { + if (self->enemy->health > 0) + { + hesDeadJim = true; + self->monsterinfo.aiflags &= ~AI_MEDIC; + } + } + else + { + if (self->monsterinfo.aiflags & AI_BRUTAL) + { + if (self->enemy->health <= -80) + hesDeadJim = true; + } + else + { + if (self->enemy->health <= 0) + hesDeadJim = true; + } + } + + if (hesDeadJim) + { + self->enemy = NULL; + // FIXME: look all around for other targets + if (self->oldenemy && self->oldenemy->health > 0) + { + self->enemy = self->oldenemy; + self->oldenemy = NULL; + HuntTarget (self); + } + else + { + if (self->movetarget) + { + self->goalentity = self->movetarget; + self->monsterinfo.walk (self); + } + else + { + // we need the pausetime otherwise the stand code + // will just revert to walking with no target and + // the monsters will wonder around aimlessly trying + // to hunt the world entity + self->monsterinfo.pausetime = level.time + 100000000; + self->monsterinfo.stand (self); + } + return true; + } + } + + self->show_hostile = level.time + 1; // wake up other monsters + +// check knowledge of enemy + enemy_vis = visible(self, self->enemy); + if (enemy_vis) + { + self->monsterinfo.search_time = level.time + 5; + VectorCopy (self->enemy->s.origin, self->monsterinfo.last_sighting); + } + +// look for other coop players here +// if (coop && self->monsterinfo.search_time < level.time) +// { +// if (FindTarget (self)) +// return true; +// } + + enemy_infront = infront(self, self->enemy); + enemy_range = range(self, self->enemy); + VectorSubtract (self->enemy->s.origin, self->s.origin, temp); + enemy_yaw = vectoyaw(temp); + + + // JDC self->ideal_yaw = enemy_yaw; + + if (self->monsterinfo.attack_state == AS_MISSILE) + { + ai_run_missile (self); + return true; + } + if (self->monsterinfo.attack_state == AS_MELEE) + { + ai_run_melee (self); + return true; + } + + // if enemy is not currently visible, we will never attack + if (!enemy_vis) + return false; + + return self->monsterinfo.checkattack (self); +} + + +/* +============= +ai_run + +The monster has an enemy it is trying to kill +============= +*/ +void ai_run (edict_t *self, float dist) +{ + vec3_t v; + edict_t *tempgoal; + edict_t *save; + qboolean new; + edict_t *marker; + float d1, d2; + trace_t tr; + vec3_t v_forward, v_right; + float left, center, right; + vec3_t left_target, right_target; + + // if we're going to a combat point, just proceed + if (self->monsterinfo.aiflags & AI_COMBAT_POINT) + { + M_MoveToGoal (self, dist); + return; + } + + if (self->monsterinfo.aiflags & AI_SOUND_TARGET) + { + VectorSubtract (self->s.origin, self->enemy->s.origin, v); + if (VectorLength(v) < 64) + { + self->monsterinfo.aiflags |= (AI_STAND_GROUND | AI_TEMP_STAND_GROUND); + self->monsterinfo.stand (self); + return; + } + + M_MoveToGoal (self, dist); + + if (!FindTarget (self)) + return; + } + + if (ai_checkattack (self, dist)) + return; + + if (self->monsterinfo.attack_state == AS_SLIDING) + { + ai_run_slide (self, dist); + return; + } + + if (enemy_vis) + { +// if (self.aiflags & AI_LOST_SIGHT) +// dprint("regained sight\n"); + M_MoveToGoal (self, dist); + self->monsterinfo.aiflags &= ~AI_LOST_SIGHT; + VectorCopy (self->enemy->s.origin, self->monsterinfo.last_sighting); + self->monsterinfo.trail_time = level.time; + return; + } + + // coop will change to another enemy if visible + if (coop->value) + { // FIXME: insane guys get mad with this, which causes crashes! + if (FindTarget (self)) + return; + } + + if ((self->monsterinfo.search_time) && (level.time > (self->monsterinfo.search_time + 20))) + { + M_MoveToGoal (self, dist); + self->monsterinfo.search_time = 0; +// dprint("search timeout\n"); + return; + } + + save = self->goalentity; + tempgoal = G_Spawn(); + self->goalentity = tempgoal; + + new = false; + + if (!(self->monsterinfo.aiflags & AI_LOST_SIGHT)) + { + // just lost sight of the player, decide where to go first +// dprint("lost sight of player, last seen at "); dprint(vtos(self.last_sighting)); dprint("\n"); + self->monsterinfo.aiflags |= (AI_LOST_SIGHT | AI_PURSUIT_LAST_SEEN); + self->monsterinfo.aiflags &= ~(AI_PURSUE_NEXT | AI_PURSUE_TEMP); + new = true; + } + + if (self->monsterinfo.aiflags & AI_PURSUE_NEXT) + { + self->monsterinfo.aiflags &= ~AI_PURSUE_NEXT; +// dprint("reached current goal: "); dprint(vtos(self.origin)); dprint(" "); dprint(vtos(self.last_sighting)); dprint(" "); dprint(ftos(vlen(self.origin - self.last_sighting))); dprint("\n"); + + // give ourself more time since we got this far + self->monsterinfo.search_time = level.time + 5; + + if (self->monsterinfo.aiflags & AI_PURSUE_TEMP) + { +// dprint("was temp goal; retrying original\n"); + self->monsterinfo.aiflags &= ~AI_PURSUE_TEMP; + marker = NULL; + VectorCopy (self->monsterinfo.saved_goal, self->monsterinfo.last_sighting); + new = true; + } + else if (self->monsterinfo.aiflags & AI_PURSUIT_LAST_SEEN) + { + self->monsterinfo.aiflags &= ~AI_PURSUIT_LAST_SEEN; + marker = PlayerTrail_PickFirst (self); + } + else + { + marker = PlayerTrail_PickNext (self); + } + + if (marker) + { + VectorCopy (marker->s.origin, self->monsterinfo.last_sighting); + self->monsterinfo.trail_time = marker->timestamp; + self->s.angles[YAW] = self->ideal_yaw = marker->s.angles[YAW]; +// dprint("heading is "); dprint(ftos(self.ideal_yaw)); dprint("\n"); + +// debug_drawline(self.origin, self.last_sighting, 52); + new = true; + } + } + + VectorSubtract (self->s.origin, self->monsterinfo.last_sighting, v); + d1 = VectorLength(v); + if (d1 <= dist) + { + self->monsterinfo.aiflags |= AI_PURSUE_NEXT; + dist = d1; + } + + VectorCopy (self->monsterinfo.last_sighting, self->goalentity->s.origin); + + if (new) + { +// gi.dprintf("checking for course correction\n"); + + tr = gi.trace(self->s.origin, self->mins, self->maxs, self->monsterinfo.last_sighting, self, MASK_PLAYERSOLID); + if (tr.fraction < 1) + { + VectorSubtract (self->goalentity->s.origin, self->s.origin, v); + d1 = VectorLength(v); + center = tr.fraction; + d2 = d1 * ((center+1)/2); + self->s.angles[YAW] = self->ideal_yaw = vectoyaw(v); + AngleVectors(self->s.angles, v_forward, v_right, NULL); + + VectorSet(v, d2, -16, 0); + G_ProjectSource (self->s.origin, v, v_forward, v_right, left_target); + tr = gi.trace(self->s.origin, self->mins, self->maxs, left_target, self, MASK_PLAYERSOLID); + left = tr.fraction; + + VectorSet(v, d2, 16, 0); + G_ProjectSource (self->s.origin, v, v_forward, v_right, right_target); + tr = gi.trace(self->s.origin, self->mins, self->maxs, right_target, self, MASK_PLAYERSOLID); + right = tr.fraction; + + center = (d1*center)/d2; + if (left >= center && left > right) + { + if (left < 1) + { + VectorSet(v, d2 * left * 0.5, -16, 0); + G_ProjectSource (self->s.origin, v, v_forward, v_right, left_target); +// gi.dprintf("incomplete path, go part way and adjust again\n"); + } + VectorCopy (self->monsterinfo.last_sighting, self->monsterinfo.saved_goal); + self->monsterinfo.aiflags |= AI_PURSUE_TEMP; + VectorCopy (left_target, self->goalentity->s.origin); + VectorCopy (left_target, self->monsterinfo.last_sighting); + VectorSubtract (self->goalentity->s.origin, self->s.origin, v); + self->s.angles[YAW] = self->ideal_yaw = vectoyaw(v); +// gi.dprintf("adjusted left\n"); +// debug_drawline(self.origin, self.last_sighting, 152); + } + else if (right >= center && right > left) + { + if (right < 1) + { + VectorSet(v, d2 * right * 0.5, 16, 0); + G_ProjectSource (self->s.origin, v, v_forward, v_right, right_target); +// gi.dprintf("incomplete path, go part way and adjust again\n"); + } + VectorCopy (self->monsterinfo.last_sighting, self->monsterinfo.saved_goal); + self->monsterinfo.aiflags |= AI_PURSUE_TEMP; + VectorCopy (right_target, self->goalentity->s.origin); + VectorCopy (right_target, self->monsterinfo.last_sighting); + VectorSubtract (self->goalentity->s.origin, self->s.origin, v); + self->s.angles[YAW] = self->ideal_yaw = vectoyaw(v); +// gi.dprintf("adjusted right\n"); +// debug_drawline(self.origin, self.last_sighting, 152); + } + } +// else gi.dprintf("course was fine\n"); + } + + M_MoveToGoal (self, dist); + + G_FreeEdict(tempgoal); + + if (self) + self->goalentity = save; +} diff --git a/original/baseq2/g_chase.c b/original/baseq2/g_chase.c new file mode 100644 index 0000000..b486a4e --- /dev/null +++ b/original/baseq2/g_chase.c @@ -0,0 +1,175 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +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. + +*/ +#include "g_local.h" + +void UpdateChaseCam(edict_t *ent) +{ + vec3_t o, ownerv, goal; + edict_t *targ; + vec3_t forward, right; + trace_t trace; + int i; + vec3_t oldgoal; + vec3_t angles; + + // is our chase target gone? + if (!ent->client->chase_target->inuse + || ent->client->chase_target->client->resp.spectator) { + edict_t *old = ent->client->chase_target; + ChaseNext(ent); + if (ent->client->chase_target == old) { + ent->client->chase_target = NULL; + ent->client->ps.pmove.pm_flags &= ~PMF_NO_PREDICTION; + return; + } + } + + targ = ent->client->chase_target; + + VectorCopy(targ->s.origin, ownerv); + VectorCopy(ent->s.origin, oldgoal); + + ownerv[2] += targ->viewheight; + + VectorCopy(targ->client->v_angle, angles); + if (angles[PITCH] > 56) + angles[PITCH] = 56; + AngleVectors (angles, forward, right, NULL); + VectorNormalize(forward); + VectorMA(ownerv, -30, forward, o); + + if (o[2] < targ->s.origin[2] + 20) + o[2] = targ->s.origin[2] + 20; + + // jump animation lifts + if (!targ->groundentity) + o[2] += 16; + + trace = gi.trace(ownerv, vec3_origin, vec3_origin, o, targ, MASK_SOLID); + + VectorCopy(trace.endpos, goal); + + VectorMA(goal, 2, forward, goal); + + // pad for floors and ceilings + VectorCopy(goal, o); + o[2] += 6; + trace = gi.trace(goal, vec3_origin, vec3_origin, o, targ, MASK_SOLID); + if (trace.fraction < 1) { + VectorCopy(trace.endpos, goal); + goal[2] -= 6; + } + + VectorCopy(goal, o); + o[2] -= 6; + trace = gi.trace(goal, vec3_origin, vec3_origin, o, targ, MASK_SOLID); + if (trace.fraction < 1) { + VectorCopy(trace.endpos, goal); + goal[2] += 6; + } + + if (targ->deadflag) + ent->client->ps.pmove.pm_type = PM_DEAD; + else + ent->client->ps.pmove.pm_type = PM_FREEZE; + + VectorCopy(goal, ent->s.origin); + for (i=0 ; i<3 ; i++) + ent->client->ps.pmove.delta_angles[i] = ANGLE2SHORT(targ->client->v_angle[i] - ent->client->resp.cmd_angles[i]); + + if (targ->deadflag) { + ent->client->ps.viewangles[ROLL] = 40; + ent->client->ps.viewangles[PITCH] = -15; + ent->client->ps.viewangles[YAW] = targ->client->killer_yaw; + } else { + VectorCopy(targ->client->v_angle, ent->client->ps.viewangles); + VectorCopy(targ->client->v_angle, ent->client->v_angle); + } + + ent->viewheight = 0; + ent->client->ps.pmove.pm_flags |= PMF_NO_PREDICTION; + gi.linkentity(ent); +} + +void ChaseNext(edict_t *ent) +{ + int i; + edict_t *e; + + if (!ent->client->chase_target) + return; + + i = ent->client->chase_target - g_edicts; + do { + i++; + if (i > maxclients->value) + i = 1; + e = g_edicts + i; + if (!e->inuse) + continue; + if (!e->client->resp.spectator) + break; + } while (e != ent->client->chase_target); + + ent->client->chase_target = e; + ent->client->update_chase = true; +} + +void ChasePrev(edict_t *ent) +{ + int i; + edict_t *e; + + if (!ent->client->chase_target) + return; + + i = ent->client->chase_target - g_edicts; + do { + i--; + if (i < 1) + i = maxclients->value; + e = g_edicts + i; + if (!e->inuse) + continue; + if (!e->client->resp.spectator) + break; + } while (e != ent->client->chase_target); + + ent->client->chase_target = e; + ent->client->update_chase = true; +} + +void GetChaseTarget(edict_t *ent) +{ + int i; + edict_t *other; + + for (i = 1; i <= maxclients->value; i++) { + other = g_edicts + i; + if (other->inuse && !other->client->resp.spectator) { + ent->client->chase_target = other; + ent->client->update_chase = true; + UpdateChaseCam(ent); + return; + } + } + gi.centerprintf(ent, "No other players to chase."); +} + diff --git a/original/baseq2/g_cmds.c b/original/baseq2/g_cmds.c new file mode 100644 index 0000000..f71fa89 --- /dev/null +++ b/original/baseq2/g_cmds.c @@ -0,0 +1,992 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +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. + +*/ +#include "g_local.h" +#include "m_player.h" + + +char *ClientTeam (edict_t *ent) +{ + char *p; + static char value[512]; + + value[0] = 0; + + if (!ent->client) + return value; + + strcpy(value, Info_ValueForKey (ent->client->pers.userinfo, "skin")); + p = strchr(value, '/'); + if (!p) + return value; + + if ((int)(dmflags->value) & DF_MODELTEAMS) + { + *p = 0; + return value; + } + + // if ((int)(dmflags->value) & DF_SKINTEAMS) + return ++p; +} + +qboolean OnSameTeam (edict_t *ent1, edict_t *ent2) +{ + char ent1Team [512]; + char ent2Team [512]; + + if (!((int)(dmflags->value) & (DF_MODELTEAMS | DF_SKINTEAMS))) + return false; + + strcpy (ent1Team, ClientTeam (ent1)); + strcpy (ent2Team, ClientTeam (ent2)); + + if (strcmp(ent1Team, ent2Team) == 0) + return true; + return false; +} + + +void SelectNextItem (edict_t *ent, int itflags) +{ + gclient_t *cl; + int i, index; + gitem_t *it; + + cl = ent->client; + + if (cl->chase_target) { + ChaseNext(ent); + return; + } + + // scan for the next valid one + for (i=1 ; i<=MAX_ITEMS ; i++) + { + index = (cl->pers.selected_item + i)%MAX_ITEMS; + if (!cl->pers.inventory[index]) + continue; + it = &itemlist[index]; + if (!it->use) + continue; + if (!(it->flags & itflags)) + continue; + + cl->pers.selected_item = index; + return; + } + + cl->pers.selected_item = -1; +} + +void SelectPrevItem (edict_t *ent, int itflags) +{ + gclient_t *cl; + int i, index; + gitem_t *it; + + cl = ent->client; + + if (cl->chase_target) { + ChasePrev(ent); + return; + } + + // scan for the next valid one + for (i=1 ; i<=MAX_ITEMS ; i++) + { + index = (cl->pers.selected_item + MAX_ITEMS - i)%MAX_ITEMS; + if (!cl->pers.inventory[index]) + continue; + it = &itemlist[index]; + if (!it->use) + continue; + if (!(it->flags & itflags)) + continue; + + cl->pers.selected_item = index; + return; + } + + cl->pers.selected_item = -1; +} + +void ValidateSelectedItem (edict_t *ent) +{ + gclient_t *cl; + + cl = ent->client; + + if (cl->pers.inventory[cl->pers.selected_item]) + return; // valid + + SelectNextItem (ent, -1); +} + + +//================================================================================= + +/* +================== +Cmd_Give_f + +Give items to a client +================== +*/ +void Cmd_Give_f (edict_t *ent) +{ + char *name; + gitem_t *it; + int index; + int i; + qboolean give_all; + edict_t *it_ent; + + if (deathmatch->value && !sv_cheats->value) + { + gi.cprintf (ent, PRINT_HIGH, "You must run the server with '+set cheats 1' to enable this command.\n"); + return; + } + + name = gi.args(); + + if (Q_stricmp(name, "all") == 0) + give_all = true; + else + give_all = false; + + if (give_all || Q_stricmp(gi.argv(1), "health") == 0) + { + if (gi.argc() == 3) + ent->health = atoi(gi.argv(2)); + else + ent->health = ent->max_health; + if (!give_all) + return; + } + + if (give_all || Q_stricmp(name, "weapons") == 0) + { + for (i=0 ; ipickup) + continue; + if (!(it->flags & IT_WEAPON)) + continue; + ent->client->pers.inventory[i] += 1; + } + if (!give_all) + return; + } + + if (give_all || Q_stricmp(name, "ammo") == 0) + { + for (i=0 ; ipickup) + continue; + if (!(it->flags & IT_AMMO)) + continue; + Add_Ammo (ent, it, 1000); + } + if (!give_all) + return; + } + + if (give_all || Q_stricmp(name, "armor") == 0) + { + gitem_armor_t *info; + + it = FindItem("Jacket Armor"); + ent->client->pers.inventory[ITEM_INDEX(it)] = 0; + + it = FindItem("Combat Armor"); + ent->client->pers.inventory[ITEM_INDEX(it)] = 0; + + it = FindItem("Body Armor"); + info = (gitem_armor_t *)it->info; + ent->client->pers.inventory[ITEM_INDEX(it)] = info->max_count; + + if (!give_all) + return; + } + + if (give_all || Q_stricmp(name, "Power Shield") == 0) + { + it = FindItem("Power Shield"); + it_ent = G_Spawn(); + it_ent->classname = it->classname; + SpawnItem (it_ent, it); + Touch_Item (it_ent, ent, NULL, NULL); + if (it_ent->inuse) + G_FreeEdict(it_ent); + + if (!give_all) + return; + } + + if (give_all) + { + for (i=0 ; ipickup) + continue; + if (it->flags & (IT_ARMOR|IT_WEAPON|IT_AMMO)) + continue; + ent->client->pers.inventory[i] = 1; + } + return; + } + + it = FindItem (name); + if (!it) + { + name = gi.argv(1); + it = FindItem (name); + if (!it) + { + gi.cprintf (ent, PRINT_HIGH, "unknown item\n"); + return; + } + } + + if (!it->pickup) + { + gi.cprintf (ent, PRINT_HIGH, "non-pickup item\n"); + return; + } + + index = ITEM_INDEX(it); + + if (it->flags & IT_AMMO) + { + if (gi.argc() == 3) + ent->client->pers.inventory[index] = atoi(gi.argv(2)); + else + ent->client->pers.inventory[index] += it->quantity; + } + else + { + it_ent = G_Spawn(); + it_ent->classname = it->classname; + SpawnItem (it_ent, it); + Touch_Item (it_ent, ent, NULL, NULL); + if (it_ent->inuse) + G_FreeEdict(it_ent); + } +} + + +/* +================== +Cmd_God_f + +Sets client to godmode + +argv(0) god +================== +*/ +void Cmd_God_f (edict_t *ent) +{ + char *msg; + + if (deathmatch->value && !sv_cheats->value) + { + gi.cprintf (ent, PRINT_HIGH, "You must run the server with '+set cheats 1' to enable this command.\n"); + return; + } + + ent->flags ^= FL_GODMODE; + if (!(ent->flags & FL_GODMODE) ) + msg = "godmode OFF\n"; + else + msg = "godmode ON\n"; + + gi.cprintf (ent, PRINT_HIGH, msg); +} + + +/* +================== +Cmd_Notarget_f + +Sets client to notarget + +argv(0) notarget +================== +*/ +void Cmd_Notarget_f (edict_t *ent) +{ + char *msg; + + if (deathmatch->value && !sv_cheats->value) + { + gi.cprintf (ent, PRINT_HIGH, "You must run the server with '+set cheats 1' to enable this command.\n"); + return; + } + + ent->flags ^= FL_NOTARGET; + if (!(ent->flags & FL_NOTARGET) ) + msg = "notarget OFF\n"; + else + msg = "notarget ON\n"; + + gi.cprintf (ent, PRINT_HIGH, msg); +} + + +/* +================== +Cmd_Noclip_f + +argv(0) noclip +================== +*/ +void Cmd_Noclip_f (edict_t *ent) +{ + char *msg; + + if (deathmatch->value && !sv_cheats->value) + { + gi.cprintf (ent, PRINT_HIGH, "You must run the server with '+set cheats 1' to enable this command.\n"); + return; + } + + if (ent->movetype == MOVETYPE_NOCLIP) + { + ent->movetype = MOVETYPE_WALK; + msg = "noclip OFF\n"; + } + else + { + ent->movetype = MOVETYPE_NOCLIP; + msg = "noclip ON\n"; + } + + gi.cprintf (ent, PRINT_HIGH, msg); +} + + +/* +================== +Cmd_Use_f + +Use an inventory item +================== +*/ +void Cmd_Use_f (edict_t *ent) +{ + int index; + gitem_t *it; + char *s; + + s = gi.args(); + it = FindItem (s); + if (!it) + { + gi.cprintf (ent, PRINT_HIGH, "unknown item: %s\n", s); + return; + } + if (!it->use) + { + gi.cprintf (ent, PRINT_HIGH, "Item is not usable.\n"); + return; + } + index = ITEM_INDEX(it); + if (!ent->client->pers.inventory[index]) + { + gi.cprintf (ent, PRINT_HIGH, "Out of item: %s\n", s); + return; + } + + it->use (ent, it); +} + + +/* +================== +Cmd_Drop_f + +Drop an inventory item +================== +*/ +void Cmd_Drop_f (edict_t *ent) +{ + int index; + gitem_t *it; + char *s; + + s = gi.args(); + it = FindItem (s); + if (!it) + { + gi.cprintf (ent, PRINT_HIGH, "unknown item: %s\n", s); + return; + } + if (!it->drop) + { + gi.cprintf (ent, PRINT_HIGH, "Item is not dropable.\n"); + return; + } + index = ITEM_INDEX(it); + if (!ent->client->pers.inventory[index]) + { + gi.cprintf (ent, PRINT_HIGH, "Out of item: %s\n", s); + return; + } + + it->drop (ent, it); +} + + +/* +================= +Cmd_Inven_f +================= +*/ +void Cmd_Inven_f (edict_t *ent) +{ + int i; + gclient_t *cl; + + cl = ent->client; + + cl->showscores = false; + cl->showhelp = false; + + if (cl->showinventory) + { + cl->showinventory = false; + return; + } + + cl->showinventory = true; + + gi.WriteByte (svc_inventory); + for (i=0 ; ipers.inventory[i]); + } + gi.unicast (ent, true); +} + +/* +================= +Cmd_InvUse_f +================= +*/ +void Cmd_InvUse_f (edict_t *ent) +{ + gitem_t *it; + + ValidateSelectedItem (ent); + + if (ent->client->pers.selected_item == -1) + { + gi.cprintf (ent, PRINT_HIGH, "No item to use.\n"); + return; + } + + it = &itemlist[ent->client->pers.selected_item]; + if (!it->use) + { + gi.cprintf (ent, PRINT_HIGH, "Item is not usable.\n"); + return; + } + it->use (ent, it); +} + +/* +================= +Cmd_WeapPrev_f +================= +*/ +void Cmd_WeapPrev_f (edict_t *ent) +{ + gclient_t *cl; + int i, index; + gitem_t *it; + int selected_weapon; + + cl = ent->client; + + if (!cl->pers.weapon) + return; + + selected_weapon = ITEM_INDEX(cl->pers.weapon); + + // scan for the next valid one + for (i=1 ; i<=MAX_ITEMS ; i++) + { + index = (selected_weapon + i)%MAX_ITEMS; + if (!cl->pers.inventory[index]) + continue; + it = &itemlist[index]; + if (!it->use) + continue; + if (! (it->flags & IT_WEAPON) ) + continue; + it->use (ent, it); + if (cl->pers.weapon == it) + return; // successful + } +} + +/* +================= +Cmd_WeapNext_f +================= +*/ +void Cmd_WeapNext_f (edict_t *ent) +{ + gclient_t *cl; + int i, index; + gitem_t *it; + int selected_weapon; + + cl = ent->client; + + if (!cl->pers.weapon) + return; + + selected_weapon = ITEM_INDEX(cl->pers.weapon); + + // scan for the next valid one + for (i=1 ; i<=MAX_ITEMS ; i++) + { + index = (selected_weapon + MAX_ITEMS - i)%MAX_ITEMS; + if (!cl->pers.inventory[index]) + continue; + it = &itemlist[index]; + if (!it->use) + continue; + if (! (it->flags & IT_WEAPON) ) + continue; + it->use (ent, it); + if (cl->pers.weapon == it) + return; // successful + } +} + +/* +================= +Cmd_WeapLast_f +================= +*/ +void Cmd_WeapLast_f (edict_t *ent) +{ + gclient_t *cl; + int index; + gitem_t *it; + + cl = ent->client; + + if (!cl->pers.weapon || !cl->pers.lastweapon) + return; + + index = ITEM_INDEX(cl->pers.lastweapon); + if (!cl->pers.inventory[index]) + return; + it = &itemlist[index]; + if (!it->use) + return; + if (! (it->flags & IT_WEAPON) ) + return; + it->use (ent, it); +} + +/* +================= +Cmd_InvDrop_f +================= +*/ +void Cmd_InvDrop_f (edict_t *ent) +{ + gitem_t *it; + + ValidateSelectedItem (ent); + + if (ent->client->pers.selected_item == -1) + { + gi.cprintf (ent, PRINT_HIGH, "No item to drop.\n"); + return; + } + + it = &itemlist[ent->client->pers.selected_item]; + if (!it->drop) + { + gi.cprintf (ent, PRINT_HIGH, "Item is not dropable.\n"); + return; + } + it->drop (ent, it); +} + +/* +================= +Cmd_Kill_f +================= +*/ +void Cmd_Kill_f (edict_t *ent) +{ + if((level.time - ent->client->respawn_time) < 5) + return; + ent->flags &= ~FL_GODMODE; + ent->health = 0; + meansOfDeath = MOD_SUICIDE; + player_die (ent, ent, ent, 100000, vec3_origin); +} + +/* +================= +Cmd_PutAway_f +================= +*/ +void Cmd_PutAway_f (edict_t *ent) +{ + ent->client->showscores = false; + ent->client->showhelp = false; + ent->client->showinventory = false; +} + + +int PlayerSort (void const *a, void const *b) +{ + int anum, bnum; + + anum = *(int *)a; + bnum = *(int *)b; + + anum = game.clients[anum].ps.stats[STAT_FRAGS]; + bnum = game.clients[bnum].ps.stats[STAT_FRAGS]; + + if (anum < bnum) + return -1; + if (anum > bnum) + return 1; + return 0; +} + +/* +================= +Cmd_Players_f +================= +*/ +void Cmd_Players_f (edict_t *ent) +{ + int i; + int count; + char small[64]; + char large[1280]; + int index[256]; + + count = 0; + for (i = 0 ; i < maxclients->value ; i++) + if (game.clients[i].pers.connected) + { + index[count] = i; + count++; + } + + // sort by frags + qsort (index, count, sizeof(index[0]), PlayerSort); + + // print information + large[0] = 0; + + for (i = 0 ; i < count ; i++) + { + Com_sprintf (small, sizeof(small), "%3i %s\n", + game.clients[index[i]].ps.stats[STAT_FRAGS], + game.clients[index[i]].pers.netname); + if (strlen (small) + strlen(large) > sizeof(large) - 100 ) + { // can't print all of them in one packet + strcat (large, "...\n"); + break; + } + strcat (large, small); + } + + gi.cprintf (ent, PRINT_HIGH, "%s\n%i players\n", large, count); +} + +/* +================= +Cmd_Wave_f +================= +*/ +void Cmd_Wave_f (edict_t *ent) +{ + int i; + + i = atoi (gi.argv(1)); + + // can't wave when ducked + if (ent->client->ps.pmove.pm_flags & PMF_DUCKED) + return; + + if (ent->client->anim_priority > ANIM_WAVE) + return; + + ent->client->anim_priority = ANIM_WAVE; + + switch (i) + { + case 0: + gi.cprintf (ent, PRINT_HIGH, "flipoff\n"); + ent->s.frame = FRAME_flip01-1; + ent->client->anim_end = FRAME_flip12; + break; + case 1: + gi.cprintf (ent, PRINT_HIGH, "salute\n"); + ent->s.frame = FRAME_salute01-1; + ent->client->anim_end = FRAME_salute11; + break; + case 2: + gi.cprintf (ent, PRINT_HIGH, "taunt\n"); + ent->s.frame = FRAME_taunt01-1; + ent->client->anim_end = FRAME_taunt17; + break; + case 3: + gi.cprintf (ent, PRINT_HIGH, "wave\n"); + ent->s.frame = FRAME_wave01-1; + ent->client->anim_end = FRAME_wave11; + break; + case 4: + default: + gi.cprintf (ent, PRINT_HIGH, "point\n"); + ent->s.frame = FRAME_point01-1; + ent->client->anim_end = FRAME_point12; + break; + } +} + +/* +================== +Cmd_Say_f +================== +*/ +void Cmd_Say_f (edict_t *ent, qboolean team, qboolean arg0) +{ + int i, j; + edict_t *other; + char *p; + char text[2048]; + gclient_t *cl; + + if (gi.argc () < 2 && !arg0) + return; + + if (!((int)(dmflags->value) & (DF_MODELTEAMS | DF_SKINTEAMS))) + team = false; + + if (team) + Com_sprintf (text, sizeof(text), "(%s): ", ent->client->pers.netname); + else + Com_sprintf (text, sizeof(text), "%s: ", ent->client->pers.netname); + + if (arg0) + { + strcat (text, gi.argv(0)); + strcat (text, " "); + strcat (text, gi.args()); + } + else + { + p = gi.args(); + + if (*p == '"') + { + p++; + p[strlen(p)-1] = 0; + } + strcat(text, p); + } + + // don't let text be too long for malicious reasons + if (strlen(text) > 150) + text[150] = 0; + + strcat(text, "\n"); + + if (flood_msgs->value) { + cl = ent->client; + + if (level.time < cl->flood_locktill) { + gi.cprintf(ent, PRINT_HIGH, "You can't talk for %d more seconds\n", + (int)(cl->flood_locktill - level.time)); + return; + } + i = cl->flood_whenhead - flood_msgs->value + 1; + if (i < 0) + i = (sizeof(cl->flood_when)/sizeof(cl->flood_when[0])) + i; + if (cl->flood_when[i] && + level.time - cl->flood_when[i] < flood_persecond->value) { + cl->flood_locktill = level.time + flood_waitdelay->value; + gi.cprintf(ent, PRINT_CHAT, "Flood protection: You can't talk for %d seconds.\n", + (int)flood_waitdelay->value); + return; + } + cl->flood_whenhead = (cl->flood_whenhead + 1) % + (sizeof(cl->flood_when)/sizeof(cl->flood_when[0])); + cl->flood_when[cl->flood_whenhead] = level.time; + } + + if (dedicated->value) + gi.cprintf(NULL, PRINT_CHAT, "%s", text); + + for (j = 1; j <= game.maxclients; j++) + { + other = &g_edicts[j]; + if (!other->inuse) + continue; + if (!other->client) + continue; + if (team) + { + if (!OnSameTeam(ent, other)) + continue; + } + gi.cprintf(other, PRINT_CHAT, "%s", text); + } +} + +void Cmd_PlayerList_f(edict_t *ent) +{ + int i; + char st[80]; + char text[1400]; + edict_t *e2; + + // connect time, ping, score, name + *text = 0; + for (i = 0, e2 = g_edicts + 1; i < maxclients->value; i++, e2++) { + if (!e2->inuse) + continue; + + Com_sprintf(st, sizeof(st), "%02d:%02d %4d %3d %s%s\n", + (level.framenum - e2->client->resp.enterframe) / 600, + ((level.framenum - e2->client->resp.enterframe) % 600)/10, + e2->client->ping, + e2->client->resp.score, + e2->client->pers.netname, + e2->client->resp.spectator ? " (spectator)" : ""); + if (strlen(text) + strlen(st) > sizeof(text) - 50) { + sprintf(text+strlen(text), "And more...\n"); + gi.cprintf(ent, PRINT_HIGH, "%s", text); + return; + } + strcat(text, st); + } + gi.cprintf(ent, PRINT_HIGH, "%s", text); +} + + +/* +================= +ClientCommand +================= +*/ +void ClientCommand (edict_t *ent) +{ + char *cmd; + + if (!ent->client) + return; // not fully in game yet + + cmd = gi.argv(0); + + if (Q_stricmp (cmd, "players") == 0) + { + Cmd_Players_f (ent); + return; + } + if (Q_stricmp (cmd, "say") == 0) + { + Cmd_Say_f (ent, false, false); + return; + } + if (Q_stricmp (cmd, "say_team") == 0) + { + Cmd_Say_f (ent, true, false); + return; + } + if (Q_stricmp (cmd, "score") == 0) + { + Cmd_Score_f (ent); + return; + } + if (Q_stricmp (cmd, "help") == 0) + { + Cmd_Help_f (ent); + return; + } + + if (level.intermissiontime) + return; + + if (Q_stricmp (cmd, "use") == 0) + Cmd_Use_f (ent); + else if (Q_stricmp (cmd, "drop") == 0) + Cmd_Drop_f (ent); + else if (Q_stricmp (cmd, "give") == 0) + Cmd_Give_f (ent); + else if (Q_stricmp (cmd, "god") == 0) + Cmd_God_f (ent); + else if (Q_stricmp (cmd, "notarget") == 0) + Cmd_Notarget_f (ent); + else if (Q_stricmp (cmd, "noclip") == 0) + Cmd_Noclip_f (ent); + else if (Q_stricmp (cmd, "inven") == 0) + Cmd_Inven_f (ent); + else if (Q_stricmp (cmd, "invnext") == 0) + SelectNextItem (ent, -1); + else if (Q_stricmp (cmd, "invprev") == 0) + SelectPrevItem (ent, -1); + else if (Q_stricmp (cmd, "invnextw") == 0) + SelectNextItem (ent, IT_WEAPON); + else if (Q_stricmp (cmd, "invprevw") == 0) + SelectPrevItem (ent, IT_WEAPON); + else if (Q_stricmp (cmd, "invnextp") == 0) + SelectNextItem (ent, IT_POWERUP); + else if (Q_stricmp (cmd, "invprevp") == 0) + SelectPrevItem (ent, IT_POWERUP); + else if (Q_stricmp (cmd, "invuse") == 0) + Cmd_InvUse_f (ent); + else if (Q_stricmp (cmd, "invdrop") == 0) + Cmd_InvDrop_f (ent); + else if (Q_stricmp (cmd, "weapprev") == 0) + Cmd_WeapPrev_f (ent); + else if (Q_stricmp (cmd, "weapnext") == 0) + Cmd_WeapNext_f (ent); + else if (Q_stricmp (cmd, "weaplast") == 0) + Cmd_WeapLast_f (ent); + else if (Q_stricmp (cmd, "kill") == 0) + Cmd_Kill_f (ent); + else if (Q_stricmp (cmd, "putaway") == 0) + Cmd_PutAway_f (ent); + else if (Q_stricmp (cmd, "wave") == 0) + Cmd_Wave_f (ent); + else if (Q_stricmp(cmd, "playerlist") == 0) + Cmd_PlayerList_f(ent); + else // anything that doesn't match a command will be a chat + Cmd_Say_f (ent, false, true); +} diff --git a/original/baseq2/g_combat.c b/original/baseq2/g_combat.c new file mode 100644 index 0000000..a921bdb --- /dev/null +++ b/original/baseq2/g_combat.c @@ -0,0 +1,576 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +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. + +*/ +// g_combat.c + +#include "g_local.h" + +/* +============ +CanDamage + +Returns true if the inflictor can directly damage the target. Used for +explosions and melee attacks. +============ +*/ +qboolean CanDamage (edict_t *targ, edict_t *inflictor) +{ + vec3_t dest; + trace_t trace; + +// bmodels need special checking because their origin is 0,0,0 + if (targ->movetype == MOVETYPE_PUSH) + { + VectorAdd (targ->absmin, targ->absmax, dest); + VectorScale (dest, 0.5, dest); + trace = gi.trace (inflictor->s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID); + if (trace.fraction == 1.0) + return true; + if (trace.ent == targ) + return true; + return false; + } + + trace = gi.trace (inflictor->s.origin, vec3_origin, vec3_origin, targ->s.origin, inflictor, MASK_SOLID); + if (trace.fraction == 1.0) + return true; + + VectorCopy (targ->s.origin, dest); + dest[0] += 15.0; + dest[1] += 15.0; + trace = gi.trace (inflictor->s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID); + if (trace.fraction == 1.0) + return true; + + VectorCopy (targ->s.origin, dest); + dest[0] += 15.0; + dest[1] -= 15.0; + trace = gi.trace (inflictor->s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID); + if (trace.fraction == 1.0) + return true; + + VectorCopy (targ->s.origin, dest); + dest[0] -= 15.0; + dest[1] += 15.0; + trace = gi.trace (inflictor->s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID); + if (trace.fraction == 1.0) + return true; + + VectorCopy (targ->s.origin, dest); + dest[0] -= 15.0; + dest[1] -= 15.0; + trace = gi.trace (inflictor->s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID); + if (trace.fraction == 1.0) + return true; + + + return false; +} + + +/* +============ +Killed +============ +*/ +void Killed (edict_t *targ, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + if (targ->health < -999) + targ->health = -999; + + targ->enemy = attacker; + + if ((targ->svflags & SVF_MONSTER) && (targ->deadflag != DEAD_DEAD)) + { +// targ->svflags |= SVF_DEADMONSTER; // now treat as a different content type + if (!(targ->monsterinfo.aiflags & AI_GOOD_GUY)) + { + level.killed_monsters++; + if (coop->value && attacker->client) + attacker->client->resp.score++; + // medics won't heal monsters that they kill themselves + if (strcmp(attacker->classname, "monster_medic") == 0) + targ->owner = attacker; + } + } + + if (targ->movetype == MOVETYPE_PUSH || targ->movetype == MOVETYPE_STOP || targ->movetype == MOVETYPE_NONE) + { // doors, triggers, etc + targ->die (targ, inflictor, attacker, damage, point); + return; + } + + if ((targ->svflags & SVF_MONSTER) && (targ->deadflag != DEAD_DEAD)) + { + targ->touch = NULL; + monster_death_use (targ); + } + + targ->die (targ, inflictor, attacker, damage, point); +} + + +/* +================ +SpawnDamage +================ +*/ +void SpawnDamage (int type, vec3_t origin, vec3_t normal, int damage) +{ + if (damage > 255) + damage = 255; + gi.WriteByte (svc_temp_entity); + gi.WriteByte (type); +// gi.WriteByte (damage); + gi.WritePosition (origin); + gi.WriteDir (normal); + gi.multicast (origin, MULTICAST_PVS); +} + + +/* +============ +T_Damage + +targ entity that is being damaged +inflictor entity that is causing the damage +attacker entity that caused the inflictor to damage targ + example: targ=monster, inflictor=rocket, attacker=player + +dir direction of the attack +point point at which the damage is being inflicted +normal normal vector from that point +damage amount of damage being inflicted +knockback force to be applied against targ as a result of the damage + +dflags these flags are used to control how T_Damage works + DAMAGE_RADIUS damage was indirect (from a nearby explosion) + DAMAGE_NO_ARMOR armor does not protect from this damage + DAMAGE_ENERGY damage is from an energy based weapon + DAMAGE_NO_KNOCKBACK do not affect velocity, just view angles + DAMAGE_BULLET damage is from a bullet (used for ricochets) + DAMAGE_NO_PROTECTION kills godmode, armor, everything +============ +*/ +static int CheckPowerArmor (edict_t *ent, vec3_t point, vec3_t normal, int damage, int dflags) +{ + gclient_t *client; + int save; + int power_armor_type; + int index; + int damagePerCell; + int pa_te_type; + int power; + int power_used; + + if (!damage) + return 0; + + client = ent->client; + + if (dflags & DAMAGE_NO_ARMOR) + return 0; + + if (client) + { + power_armor_type = PowerArmorType (ent); + if (power_armor_type != POWER_ARMOR_NONE) + { + index = ITEM_INDEX(FindItem("Cells")); + power = client->pers.inventory[index]; + } + } + else if (ent->svflags & SVF_MONSTER) + { + power_armor_type = ent->monsterinfo.power_armor_type; + power = ent->monsterinfo.power_armor_power; + } + else + return 0; + + if (power_armor_type == POWER_ARMOR_NONE) + return 0; + if (!power) + return 0; + + if (power_armor_type == POWER_ARMOR_SCREEN) + { + vec3_t vec; + float dot; + vec3_t forward; + + // only works if damage point is in front + AngleVectors (ent->s.angles, forward, NULL, NULL); + VectorSubtract (point, ent->s.origin, vec); + VectorNormalize (vec); + dot = DotProduct (vec, forward); + if (dot <= 0.3) + return 0; + + damagePerCell = 1; + pa_te_type = TE_SCREEN_SPARKS; + damage = damage / 3; + } + else + { + damagePerCell = 2; + pa_te_type = TE_SHIELD_SPARKS; + damage = (2 * damage) / 3; + } + + save = power * damagePerCell; + if (!save) + return 0; + if (save > damage) + save = damage; + + SpawnDamage (pa_te_type, point, normal, save); + ent->powerarmor_time = level.time + 0.2; + + power_used = save / damagePerCell; + + if (client) + client->pers.inventory[index] -= power_used; + else + ent->monsterinfo.power_armor_power -= power_used; + return save; +} + +static int CheckArmor (edict_t *ent, vec3_t point, vec3_t normal, int damage, int te_sparks, int dflags) +{ + gclient_t *client; + int save; + int index; + gitem_t *armor; + + if (!damage) + return 0; + + client = ent->client; + + if (!client) + return 0; + + if (dflags & DAMAGE_NO_ARMOR) + return 0; + + index = ArmorIndex (ent); + if (!index) + return 0; + + armor = GetItemByIndex (index); + + if (dflags & DAMAGE_ENERGY) + save = ceil(((gitem_armor_t *)armor->info)->energy_protection*damage); + else + save = ceil(((gitem_armor_t *)armor->info)->normal_protection*damage); + if (save >= client->pers.inventory[index]) + save = client->pers.inventory[index]; + + if (!save) + return 0; + + client->pers.inventory[index] -= save; + SpawnDamage (te_sparks, point, normal, save); + + return save; +} + +void M_ReactToDamage (edict_t *targ, edict_t *attacker) +{ + if (!(attacker->client) && !(attacker->svflags & SVF_MONSTER)) + return; + + if (attacker == targ || attacker == targ->enemy) + return; + + // if we are a good guy monster and our attacker is a player + // or another good guy, do not get mad at them + if (targ->monsterinfo.aiflags & AI_GOOD_GUY) + { + if (attacker->client || (attacker->monsterinfo.aiflags & AI_GOOD_GUY)) + return; + } + + // we now know that we are not both good guys + + // if attacker is a client, get mad at them because he's good and we're not + if (attacker->client) + { + targ->monsterinfo.aiflags &= ~AI_SOUND_TARGET; + + // this can only happen in coop (both new and old enemies are clients) + // only switch if can't see the current enemy + if (targ->enemy && targ->enemy->client) + { + if (visible(targ, targ->enemy)) + { + targ->oldenemy = attacker; + return; + } + targ->oldenemy = targ->enemy; + } + targ->enemy = attacker; + if (!(targ->monsterinfo.aiflags & AI_DUCKED)) + FoundTarget (targ); + return; + } + + // it's the same base (walk/swim/fly) type and a different classname and it's not a tank + // (they spray too much), get mad at them + if (((targ->flags & (FL_FLY|FL_SWIM)) == (attacker->flags & (FL_FLY|FL_SWIM))) && + (strcmp (targ->classname, attacker->classname) != 0) && + (strcmp(attacker->classname, "monster_tank") != 0) && + (strcmp(attacker->classname, "monster_supertank") != 0) && + (strcmp(attacker->classname, "monster_makron") != 0) && + (strcmp(attacker->classname, "monster_jorg") != 0) ) + { + if (targ->enemy && targ->enemy->client) + targ->oldenemy = targ->enemy; + targ->enemy = attacker; + if (!(targ->monsterinfo.aiflags & AI_DUCKED)) + FoundTarget (targ); + } + // if they *meant* to shoot us, then shoot back + else if (attacker->enemy == targ) + { + if (targ->enemy && targ->enemy->client) + targ->oldenemy = targ->enemy; + targ->enemy = attacker; + if (!(targ->monsterinfo.aiflags & AI_DUCKED)) + FoundTarget (targ); + } + // otherwise get mad at whoever they are mad at (help our buddy) unless it is us! + else if (attacker->enemy && attacker->enemy != targ) + { + if (targ->enemy && targ->enemy->client) + targ->oldenemy = targ->enemy; + targ->enemy = attacker->enemy; + if (!(targ->monsterinfo.aiflags & AI_DUCKED)) + FoundTarget (targ); + } +} + +qboolean CheckTeamDamage (edict_t *targ, edict_t *attacker) +{ + //FIXME make the next line real and uncomment this block + // if ((ability to damage a teammate == OFF) && (targ's team == attacker's team)) + return false; +} + +void T_Damage (edict_t *targ, edict_t *inflictor, edict_t *attacker, vec3_t dir, vec3_t point, vec3_t normal, int damage, int knockback, int dflags, int mod) +{ + gclient_t *client; + int take; + int save; + int asave; + int psave; + int te_sparks; + + if (!targ->takedamage) + return; + + // friendly fire avoidance + // if enabled you can't hurt teammates (but you can hurt yourself) + // knockback still occurs + if ((targ != attacker) && ((deathmatch->value && ((int)(dmflags->value) & (DF_MODELTEAMS | DF_SKINTEAMS))) || coop->value)) + { + if (OnSameTeam (targ, attacker)) + { + if ((int)(dmflags->value) & DF_NO_FRIENDLY_FIRE) + damage = 0; + else + mod |= MOD_FRIENDLY_FIRE; + } + } + meansOfDeath = mod; + + // easy mode takes half damage + if (skill->value == 0 && deathmatch->value == 0 && targ->client) + { + damage *= 0.5; + if (!damage) + damage = 1; + } + + client = targ->client; + + if (dflags & DAMAGE_BULLET) + te_sparks = TE_BULLET_SPARKS; + else + te_sparks = TE_SPARKS; + + VectorNormalize(dir); + +// bonus damage for suprising a monster + if (!(dflags & DAMAGE_RADIUS) && (targ->svflags & SVF_MONSTER) && (attacker->client) && (!targ->enemy) && (targ->health > 0)) + damage *= 2; + + if (targ->flags & FL_NO_KNOCKBACK) + knockback = 0; + +// figure momentum add + if (!(dflags & DAMAGE_NO_KNOCKBACK)) + { + if ((knockback) && (targ->movetype != MOVETYPE_NONE) && (targ->movetype != MOVETYPE_BOUNCE) && (targ->movetype != MOVETYPE_PUSH) && (targ->movetype != MOVETYPE_STOP)) + { + vec3_t kvel; + float mass; + + if (targ->mass < 50) + mass = 50; + else + mass = targ->mass; + + if (targ->client && attacker == targ) + VectorScale (dir, 1600.0 * (float)knockback / mass, kvel); // the rocket jump hack... + else + VectorScale (dir, 500.0 * (float)knockback / mass, kvel); + + VectorAdd (targ->velocity, kvel, targ->velocity); + } + } + + take = damage; + save = 0; + + // check for godmode + if ( (targ->flags & FL_GODMODE) && !(dflags & DAMAGE_NO_PROTECTION) ) + { + take = 0; + save = damage; + SpawnDamage (te_sparks, point, normal, save); + } + + // check for invincibility + if ((client && client->invincible_framenum > level.framenum ) && !(dflags & DAMAGE_NO_PROTECTION)) + { + if (targ->pain_debounce_time < level.time) + { + gi.sound(targ, CHAN_ITEM, gi.soundindex("items/protect4.wav"), 1, ATTN_NORM, 0); + targ->pain_debounce_time = level.time + 2; + } + take = 0; + save = damage; + } + + psave = CheckPowerArmor (targ, point, normal, take, dflags); + take -= psave; + + asave = CheckArmor (targ, point, normal, take, te_sparks, dflags); + take -= asave; + + //treat cheat/powerup savings the same as armor + asave += save; + + // team damage avoidance + if (!(dflags & DAMAGE_NO_PROTECTION) && CheckTeamDamage (targ, attacker)) + return; + +// do the damage + if (take) + { + if ((targ->svflags & SVF_MONSTER) || (client)) + SpawnDamage (TE_BLOOD, point, normal, take); + else + SpawnDamage (te_sparks, point, normal, take); + + + targ->health = targ->health - take; + + if (targ->health <= 0) + { + if ((targ->svflags & SVF_MONSTER) || (client)) + targ->flags |= FL_NO_KNOCKBACK; + Killed (targ, inflictor, attacker, take, point); + return; + } + } + + if (targ->svflags & SVF_MONSTER) + { + M_ReactToDamage (targ, attacker); + if (!(targ->monsterinfo.aiflags & AI_DUCKED) && (take)) + { + targ->pain (targ, attacker, knockback, take); + // nightmare mode monsters don't go into pain frames often + if (skill->value == 3) + targ->pain_debounce_time = level.time + 5; + } + } + else if (client) + { + if (!(targ->flags & FL_GODMODE) && (take)) + targ->pain (targ, attacker, knockback, take); + } + else if (take) + { + if (targ->pain) + targ->pain (targ, attacker, knockback, take); + } + + // add to the damage inflicted on a player this frame + // the total will be turned into screen blends and view angle kicks + // at the end of the frame + if (client) + { + client->damage_parmor += psave; + client->damage_armor += asave; + client->damage_blood += take; + client->damage_knockback += knockback; + VectorCopy (point, client->damage_from); + } +} + + +/* +============ +T_RadiusDamage +============ +*/ +void T_RadiusDamage (edict_t *inflictor, edict_t *attacker, float damage, edict_t *ignore, float radius, int mod) +{ + float points; + edict_t *ent = NULL; + vec3_t v; + vec3_t dir; + + while ((ent = findradius(ent, inflictor->s.origin, radius)) != NULL) + { + if (ent == ignore) + continue; + if (!ent->takedamage) + continue; + + VectorAdd (ent->mins, ent->maxs, v); + VectorMA (ent->s.origin, 0.5, v, v); + VectorSubtract (inflictor->s.origin, v, v); + points = damage - 0.5 * VectorLength (v); + if (ent == attacker) + points = points * 0.5; + if (points > 0) + { + if (CanDamage (ent, inflictor)) + { + VectorSubtract (ent->s.origin, inflictor->s.origin, dir); + T_Damage (ent, inflictor, attacker, dir, inflictor->s.origin, vec3_origin, (int)points, (int)points, DAMAGE_RADIUS, mod); + } + } + } +} diff --git a/original/baseq2/g_func.c b/original/baseq2/g_func.c new file mode 100644 index 0000000..48703a3 --- /dev/null +++ b/original/baseq2/g_func.c @@ -0,0 +1,2048 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +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. + +*/ +#include "g_local.h" + +/* +========================================================= + + PLATS + + movement options: + + linear + smooth start, hard stop + smooth start, smooth stop + + start + end + acceleration + speed + deceleration + begin sound + end sound + target fired when reaching end + wait at end + + object characteristics that use move segments + --------------------------------------------- + movetype_push, or movetype_stop + action when touched + action when blocked + action when used + disabled? + auto trigger spawning + + +========================================================= +*/ + +#define PLAT_LOW_TRIGGER 1 + +#define STATE_TOP 0 +#define STATE_BOTTOM 1 +#define STATE_UP 2 +#define STATE_DOWN 3 + +#define DOOR_START_OPEN 1 +#define DOOR_REVERSE 2 +#define DOOR_CRUSHER 4 +#define DOOR_NOMONSTER 8 +#define DOOR_TOGGLE 32 +#define DOOR_X_AXIS 64 +#define DOOR_Y_AXIS 128 + + +// +// Support routines for movement (changes in origin using velocity) +// + +void Move_Done (edict_t *ent) +{ + VectorClear (ent->velocity); + ent->moveinfo.endfunc (ent); +} + +void Move_Final (edict_t *ent) +{ + if (ent->moveinfo.remaining_distance == 0) + { + Move_Done (ent); + return; + } + + VectorScale (ent->moveinfo.dir, ent->moveinfo.remaining_distance / FRAMETIME, ent->velocity); + + ent->think = Move_Done; + ent->nextthink = level.time + FRAMETIME; +} + +void Move_Begin (edict_t *ent) +{ + float frames; + + if ((ent->moveinfo.speed * FRAMETIME) >= ent->moveinfo.remaining_distance) + { + Move_Final (ent); + return; + } + VectorScale (ent->moveinfo.dir, ent->moveinfo.speed, ent->velocity); + frames = floor((ent->moveinfo.remaining_distance / ent->moveinfo.speed) / FRAMETIME); + ent->moveinfo.remaining_distance -= frames * ent->moveinfo.speed * FRAMETIME; + ent->nextthink = level.time + (frames * FRAMETIME); + ent->think = Move_Final; +} + +void Think_AccelMove (edict_t *ent); + +void Move_Calc (edict_t *ent, vec3_t dest, void(*func)(edict_t*)) +{ + VectorClear (ent->velocity); + VectorSubtract (dest, ent->s.origin, ent->moveinfo.dir); + ent->moveinfo.remaining_distance = VectorNormalize (ent->moveinfo.dir); + ent->moveinfo.endfunc = func; + + if (ent->moveinfo.speed == ent->moveinfo.accel && ent->moveinfo.speed == ent->moveinfo.decel) + { + if (level.current_entity == ((ent->flags & FL_TEAMSLAVE) ? ent->teammaster : ent)) + { + Move_Begin (ent); + } + else + { + ent->nextthink = level.time + FRAMETIME; + ent->think = Move_Begin; + } + } + else + { + // accelerative + ent->moveinfo.current_speed = 0; + ent->think = Think_AccelMove; + ent->nextthink = level.time + FRAMETIME; + } +} + + +// +// Support routines for angular movement (changes in angle using avelocity) +// + +void AngleMove_Done (edict_t *ent) +{ + VectorClear (ent->avelocity); + ent->moveinfo.endfunc (ent); +} + +void AngleMove_Final (edict_t *ent) +{ + vec3_t move; + + if (ent->moveinfo.state == STATE_UP) + VectorSubtract (ent->moveinfo.end_angles, ent->s.angles, move); + else + VectorSubtract (ent->moveinfo.start_angles, ent->s.angles, move); + + if (VectorCompare (move, vec3_origin)) + { + AngleMove_Done (ent); + return; + } + + VectorScale (move, 1.0/FRAMETIME, ent->avelocity); + + ent->think = AngleMove_Done; + ent->nextthink = level.time + FRAMETIME; +} + +void AngleMove_Begin (edict_t *ent) +{ + vec3_t destdelta; + float len; + float traveltime; + float frames; + + // set destdelta to the vector needed to move + if (ent->moveinfo.state == STATE_UP) + VectorSubtract (ent->moveinfo.end_angles, ent->s.angles, destdelta); + else + VectorSubtract (ent->moveinfo.start_angles, ent->s.angles, destdelta); + + // calculate length of vector + len = VectorLength (destdelta); + + // divide by speed to get time to reach dest + traveltime = len / ent->moveinfo.speed; + + if (traveltime < FRAMETIME) + { + AngleMove_Final (ent); + return; + } + + frames = floor(traveltime / FRAMETIME); + + // scale the destdelta vector by the time spent traveling to get velocity + VectorScale (destdelta, 1.0 / traveltime, ent->avelocity); + + // set nextthink to trigger a think when dest is reached + ent->nextthink = level.time + frames * FRAMETIME; + ent->think = AngleMove_Final; +} + +void AngleMove_Calc (edict_t *ent, void(*func)(edict_t*)) +{ + VectorClear (ent->avelocity); + ent->moveinfo.endfunc = func; + if (level.current_entity == ((ent->flags & FL_TEAMSLAVE) ? ent->teammaster : ent)) + { + AngleMove_Begin (ent); + } + else + { + ent->nextthink = level.time + FRAMETIME; + ent->think = AngleMove_Begin; + } +} + + +/* +============== +Think_AccelMove + +The team has completed a frame of movement, so +change the speed for the next frame +============== +*/ +#define AccelerationDistance(target, rate) (target * ((target / rate) + 1) / 2) + +void plat_CalcAcceleratedMove(moveinfo_t *moveinfo) +{ + float accel_dist; + float decel_dist; + + moveinfo->move_speed = moveinfo->speed; + + if (moveinfo->remaining_distance < moveinfo->accel) + { + moveinfo->current_speed = moveinfo->remaining_distance; + return; + } + + accel_dist = AccelerationDistance (moveinfo->speed, moveinfo->accel); + decel_dist = AccelerationDistance (moveinfo->speed, moveinfo->decel); + + if ((moveinfo->remaining_distance - accel_dist - decel_dist) < 0) + { + float f; + + f = (moveinfo->accel + moveinfo->decel) / (moveinfo->accel * moveinfo->decel); + moveinfo->move_speed = (-2 + sqrt(4 - 4 * f * (-2 * moveinfo->remaining_distance))) / (2 * f); + decel_dist = AccelerationDistance (moveinfo->move_speed, moveinfo->decel); + } + + moveinfo->decel_distance = decel_dist; +}; + +void plat_Accelerate (moveinfo_t *moveinfo) +{ + // are we decelerating? + if (moveinfo->remaining_distance <= moveinfo->decel_distance) + { + if (moveinfo->remaining_distance < moveinfo->decel_distance) + { + if (moveinfo->next_speed) + { + moveinfo->current_speed = moveinfo->next_speed; + moveinfo->next_speed = 0; + return; + } + if (moveinfo->current_speed > moveinfo->decel) + moveinfo->current_speed -= moveinfo->decel; + } + return; + } + + // are we at full speed and need to start decelerating during this move? + if (moveinfo->current_speed == moveinfo->move_speed) + if ((moveinfo->remaining_distance - moveinfo->current_speed) < moveinfo->decel_distance) + { + float p1_distance; + float p2_distance; + float distance; + + p1_distance = moveinfo->remaining_distance - moveinfo->decel_distance; + p2_distance = moveinfo->move_speed * (1.0 - (p1_distance / moveinfo->move_speed)); + distance = p1_distance + p2_distance; + moveinfo->current_speed = moveinfo->move_speed; + moveinfo->next_speed = moveinfo->move_speed - moveinfo->decel * (p2_distance / distance); + return; + } + + // are we accelerating? + if (moveinfo->current_speed < moveinfo->speed) + { + float old_speed; + float p1_distance; + float p1_speed; + float p2_distance; + float distance; + + old_speed = moveinfo->current_speed; + + // figure simple acceleration up to move_speed + moveinfo->current_speed += moveinfo->accel; + if (moveinfo->current_speed > moveinfo->speed) + moveinfo->current_speed = moveinfo->speed; + + // are we accelerating throughout this entire move? + if ((moveinfo->remaining_distance - moveinfo->current_speed) >= moveinfo->decel_distance) + return; + + // during this move we will accelrate from current_speed to move_speed + // and cross over the decel_distance; figure the average speed for the + // entire move + p1_distance = moveinfo->remaining_distance - moveinfo->decel_distance; + p1_speed = (old_speed + moveinfo->move_speed) / 2.0; + p2_distance = moveinfo->move_speed * (1.0 - (p1_distance / p1_speed)); + distance = p1_distance + p2_distance; + moveinfo->current_speed = (p1_speed * (p1_distance / distance)) + (moveinfo->move_speed * (p2_distance / distance)); + moveinfo->next_speed = moveinfo->move_speed - moveinfo->decel * (p2_distance / distance); + return; + } + + // we are at constant velocity (move_speed) + return; +}; + +void Think_AccelMove (edict_t *ent) +{ + ent->moveinfo.remaining_distance -= ent->moveinfo.current_speed; + + if (ent->moveinfo.current_speed == 0) // starting or blocked + plat_CalcAcceleratedMove(&ent->moveinfo); + + plat_Accelerate (&ent->moveinfo); + + // will the entire move complete on next frame? + if (ent->moveinfo.remaining_distance <= ent->moveinfo.current_speed) + { + Move_Final (ent); + return; + } + + VectorScale (ent->moveinfo.dir, ent->moveinfo.current_speed*10, ent->velocity); + ent->nextthink = level.time + FRAMETIME; + ent->think = Think_AccelMove; +} + + +void plat_go_down (edict_t *ent); + +void plat_hit_top (edict_t *ent) +{ + if (!(ent->flags & FL_TEAMSLAVE)) + { + if (ent->moveinfo.sound_end) + gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, ent->moveinfo.sound_end, 1, ATTN_STATIC, 0); + ent->s.sound = 0; + } + ent->moveinfo.state = STATE_TOP; + + ent->think = plat_go_down; + ent->nextthink = level.time + 3; +} + +void plat_hit_bottom (edict_t *ent) +{ + if (!(ent->flags & FL_TEAMSLAVE)) + { + if (ent->moveinfo.sound_end) + gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, ent->moveinfo.sound_end, 1, ATTN_STATIC, 0); + ent->s.sound = 0; + } + ent->moveinfo.state = STATE_BOTTOM; +} + +void plat_go_down (edict_t *ent) +{ + if (!(ent->flags & FL_TEAMSLAVE)) + { + if (ent->moveinfo.sound_start) + gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, ent->moveinfo.sound_start, 1, ATTN_STATIC, 0); + ent->s.sound = ent->moveinfo.sound_middle; + } + ent->moveinfo.state = STATE_DOWN; + Move_Calc (ent, ent->moveinfo.end_origin, plat_hit_bottom); +} + +void plat_go_up (edict_t *ent) +{ + if (!(ent->flags & FL_TEAMSLAVE)) + { + if (ent->moveinfo.sound_start) + gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, ent->moveinfo.sound_start, 1, ATTN_STATIC, 0); + ent->s.sound = ent->moveinfo.sound_middle; + } + ent->moveinfo.state = STATE_UP; + Move_Calc (ent, ent->moveinfo.start_origin, plat_hit_top); +} + +void plat_blocked (edict_t *self, edict_t *other) +{ + if (!(other->svflags & SVF_MONSTER) && (!other->client) ) + { + // give it a chance to go away on it's own terms (like gibs) + T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, 100000, 1, 0, MOD_CRUSH); + // if it's still there, nuke it + if (other) + BecomeExplosion1 (other); + return; + } + + T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH); + + if (self->moveinfo.state == STATE_UP) + plat_go_down (self); + else if (self->moveinfo.state == STATE_DOWN) + plat_go_up (self); +} + + +void Use_Plat (edict_t *ent, edict_t *other, edict_t *activator) +{ + if (ent->think) + return; // already down + plat_go_down (ent); +} + + +void Touch_Plat_Center (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (!other->client) + return; + + if (other->health <= 0) + return; + + ent = ent->enemy; // now point at the plat, not the trigger + if (ent->moveinfo.state == STATE_BOTTOM) + plat_go_up (ent); + else if (ent->moveinfo.state == STATE_TOP) + ent->nextthink = level.time + 1; // the player is still on the plat, so delay going down +} + +void plat_spawn_inside_trigger (edict_t *ent) +{ + edict_t *trigger; + vec3_t tmin, tmax; + +// +// middle trigger +// + trigger = G_Spawn(); + trigger->touch = Touch_Plat_Center; + trigger->movetype = MOVETYPE_NONE; + trigger->solid = SOLID_TRIGGER; + trigger->enemy = ent; + + tmin[0] = ent->mins[0] + 25; + tmin[1] = ent->mins[1] + 25; + tmin[2] = ent->mins[2]; + + tmax[0] = ent->maxs[0] - 25; + tmax[1] = ent->maxs[1] - 25; + tmax[2] = ent->maxs[2] + 8; + + tmin[2] = tmax[2] - (ent->pos1[2] - ent->pos2[2] + st.lip); + + if (ent->spawnflags & PLAT_LOW_TRIGGER) + tmax[2] = tmin[2] + 8; + + if (tmax[0] - tmin[0] <= 0) + { + tmin[0] = (ent->mins[0] + ent->maxs[0]) *0.5; + tmax[0] = tmin[0] + 1; + } + if (tmax[1] - tmin[1] <= 0) + { + tmin[1] = (ent->mins[1] + ent->maxs[1]) *0.5; + tmax[1] = tmin[1] + 1; + } + + VectorCopy (tmin, trigger->mins); + VectorCopy (tmax, trigger->maxs); + + gi.linkentity (trigger); +} + + +/*QUAKED func_plat (0 .5 .8) ? PLAT_LOW_TRIGGER +speed default 150 + +Plats are always drawn in the extended position, so they will light correctly. + +If the plat is the target of another trigger or button, it will start out disabled in the extended position until it is trigger, when it will lower and become a normal plat. + +"speed" overrides default 200. +"accel" overrides default 500 +"lip" overrides default 8 pixel lip + +If the "height" key is set, that will determine the amount the plat moves, instead of being implicitly determoveinfoned by the model's height. + +Set "sounds" to one of the following: +1) base fast +2) chain slow +*/ +void SP_func_plat (edict_t *ent) +{ + VectorClear (ent->s.angles); + ent->solid = SOLID_BSP; + ent->movetype = MOVETYPE_PUSH; + + gi.setmodel (ent, ent->model); + + ent->blocked = plat_blocked; + + if (!ent->speed) + ent->speed = 20; + else + ent->speed *= 0.1; + + if (!ent->accel) + ent->accel = 5; + else + ent->accel *= 0.1; + + if (!ent->decel) + ent->decel = 5; + else + ent->decel *= 0.1; + + if (!ent->dmg) + ent->dmg = 2; + + if (!st.lip) + st.lip = 8; + + // pos1 is the top position, pos2 is the bottom + VectorCopy (ent->s.origin, ent->pos1); + VectorCopy (ent->s.origin, ent->pos2); + if (st.height) + ent->pos2[2] -= st.height; + else + ent->pos2[2] -= (ent->maxs[2] - ent->mins[2]) - st.lip; + + ent->use = Use_Plat; + + plat_spawn_inside_trigger (ent); // the "start moving" trigger + + if (ent->targetname) + { + ent->moveinfo.state = STATE_UP; + } + else + { + VectorCopy (ent->pos2, ent->s.origin); + gi.linkentity (ent); + ent->moveinfo.state = STATE_BOTTOM; + } + + ent->moveinfo.speed = ent->speed; + ent->moveinfo.accel = ent->accel; + ent->moveinfo.decel = ent->decel; + ent->moveinfo.wait = ent->wait; + VectorCopy (ent->pos1, ent->moveinfo.start_origin); + VectorCopy (ent->s.angles, ent->moveinfo.start_angles); + VectorCopy (ent->pos2, ent->moveinfo.end_origin); + VectorCopy (ent->s.angles, ent->moveinfo.end_angles); + + ent->moveinfo.sound_start = gi.soundindex ("plats/pt1_strt.wav"); + ent->moveinfo.sound_middle = gi.soundindex ("plats/pt1_mid.wav"); + ent->moveinfo.sound_end = gi.soundindex ("plats/pt1_end.wav"); +} + +//==================================================================== + +/*QUAKED func_rotating (0 .5 .8) ? START_ON REVERSE X_AXIS Y_AXIS TOUCH_PAIN STOP ANIMATED ANIMATED_FAST +You need to have an origin brush as part of this entity. The center of that brush will be +the point around which it is rotated. It will rotate around the Z axis by default. You can +check either the X_AXIS or Y_AXIS box to change that. + +"speed" determines how fast it moves; default value is 100. +"dmg" damage to inflict when blocked (2 default) + +REVERSE will cause the it to rotate in the opposite direction. +STOP mean it will stop moving instead of pushing entities +*/ + +void rotating_blocked (edict_t *self, edict_t *other) +{ + T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH); +} + +void rotating_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (self->avelocity[0] || self->avelocity[1] || self->avelocity[2]) + T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH); +} + +void rotating_use (edict_t *self, edict_t *other, edict_t *activator) +{ + if (!VectorCompare (self->avelocity, vec3_origin)) + { + self->s.sound = 0; + VectorClear (self->avelocity); + self->touch = NULL; + } + else + { + self->s.sound = self->moveinfo.sound_middle; + VectorScale (self->movedir, self->speed, self->avelocity); + if (self->spawnflags & 16) + self->touch = rotating_touch; + } +} + +void SP_func_rotating (edict_t *ent) +{ + ent->solid = SOLID_BSP; + if (ent->spawnflags & 32) + ent->movetype = MOVETYPE_STOP; + else + ent->movetype = MOVETYPE_PUSH; + + // set the axis of rotation + VectorClear(ent->movedir); + if (ent->spawnflags & 4) + ent->movedir[2] = 1.0; + else if (ent->spawnflags & 8) + ent->movedir[0] = 1.0; + else // Z_AXIS + ent->movedir[1] = 1.0; + + // check for reverse rotation + if (ent->spawnflags & 2) + VectorNegate (ent->movedir, ent->movedir); + + if (!ent->speed) + ent->speed = 100; + if (!ent->dmg) + ent->dmg = 2; + +// ent->moveinfo.sound_middle = "doors/hydro1.wav"; + + ent->use = rotating_use; + if (ent->dmg) + ent->blocked = rotating_blocked; + + if (ent->spawnflags & 1) + ent->use (ent, NULL, NULL); + + if (ent->spawnflags & 64) + ent->s.effects |= EF_ANIM_ALL; + if (ent->spawnflags & 128) + ent->s.effects |= EF_ANIM_ALLFAST; + + gi.setmodel (ent, ent->model); + gi.linkentity (ent); +} + +/* +====================================================================== + +BUTTONS + +====================================================================== +*/ + +/*QUAKED func_button (0 .5 .8) ? +When a button is touched, it moves some distance in the direction of it's angle, triggers all of it's targets, waits some time, then returns to it's original position where it can be triggered again. + +"angle" determines the opening direction +"target" all entities with a matching targetname will be used +"speed" override the default 40 speed +"wait" override the default 1 second wait (-1 = never return) +"lip" override the default 4 pixel lip remaining at end of move +"health" if set, the button must be killed instead of touched +"sounds" +1) silent +2) steam metal +3) wooden clunk +4) metallic click +5) in-out +*/ + +void button_done (edict_t *self) +{ + self->moveinfo.state = STATE_BOTTOM; + self->s.effects &= ~EF_ANIM23; + self->s.effects |= EF_ANIM01; +} + +void button_return (edict_t *self) +{ + self->moveinfo.state = STATE_DOWN; + + Move_Calc (self, self->moveinfo.start_origin, button_done); + + self->s.frame = 0; + + if (self->health) + self->takedamage = DAMAGE_YES; +} + +void button_wait (edict_t *self) +{ + self->moveinfo.state = STATE_TOP; + self->s.effects &= ~EF_ANIM01; + self->s.effects |= EF_ANIM23; + + G_UseTargets (self, self->activator); + self->s.frame = 1; + if (self->moveinfo.wait >= 0) + { + self->nextthink = level.time + self->moveinfo.wait; + self->think = button_return; + } +} + +void button_fire (edict_t *self) +{ + if (self->moveinfo.state == STATE_UP || self->moveinfo.state == STATE_TOP) + return; + + self->moveinfo.state = STATE_UP; + if (self->moveinfo.sound_start && !(self->flags & FL_TEAMSLAVE)) + gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_start, 1, ATTN_STATIC, 0); + Move_Calc (self, self->moveinfo.end_origin, button_wait); +} + +void button_use (edict_t *self, edict_t *other, edict_t *activator) +{ + self->activator = activator; + button_fire (self); +} + +void button_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (!other->client) + return; + + if (other->health <= 0) + return; + + self->activator = other; + button_fire (self); +} + +void button_killed (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + self->activator = attacker; + self->health = self->max_health; + self->takedamage = DAMAGE_NO; + button_fire (self); +} + +void SP_func_button (edict_t *ent) +{ + vec3_t abs_movedir; + float dist; + + G_SetMovedir (ent->s.angles, ent->movedir); + ent->movetype = MOVETYPE_STOP; + ent->solid = SOLID_BSP; + gi.setmodel (ent, ent->model); + + if (ent->sounds != 1) + ent->moveinfo.sound_start = gi.soundindex ("switches/butn2.wav"); + + if (!ent->speed) + ent->speed = 40; + if (!ent->accel) + ent->accel = ent->speed; + if (!ent->decel) + ent->decel = ent->speed; + + if (!ent->wait) + ent->wait = 3; + if (!st.lip) + st.lip = 4; + + VectorCopy (ent->s.origin, ent->pos1); + abs_movedir[0] = fabs(ent->movedir[0]); + abs_movedir[1] = fabs(ent->movedir[1]); + abs_movedir[2] = fabs(ent->movedir[2]); + dist = abs_movedir[0] * ent->size[0] + abs_movedir[1] * ent->size[1] + abs_movedir[2] * ent->size[2] - st.lip; + VectorMA (ent->pos1, dist, ent->movedir, ent->pos2); + + ent->use = button_use; + ent->s.effects |= EF_ANIM01; + + if (ent->health) + { + ent->max_health = ent->health; + ent->die = button_killed; + ent->takedamage = DAMAGE_YES; + } + else if (! ent->targetname) + ent->touch = button_touch; + + ent->moveinfo.state = STATE_BOTTOM; + + ent->moveinfo.speed = ent->speed; + ent->moveinfo.accel = ent->accel; + ent->moveinfo.decel = ent->decel; + ent->moveinfo.wait = ent->wait; + VectorCopy (ent->pos1, ent->moveinfo.start_origin); + VectorCopy (ent->s.angles, ent->moveinfo.start_angles); + VectorCopy (ent->pos2, ent->moveinfo.end_origin); + VectorCopy (ent->s.angles, ent->moveinfo.end_angles); + + gi.linkentity (ent); +} + +/* +====================================================================== + +DOORS + + spawn a trigger surrounding the entire team unless it is + already targeted by another + +====================================================================== +*/ + +/*QUAKED func_door (0 .5 .8) ? START_OPEN x CRUSHER NOMONSTER ANIMATED TOGGLE ANIMATED_FAST +TOGGLE wait in both the start and end states for a trigger event. +START_OPEN the door to moves to its destination when spawned, and operate in reverse. It is used to temporarily or permanently close off an area when triggered (not useful for touch or takedamage doors). +NOMONSTER monsters will not trigger this door + +"message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet +"angle" determines the opening direction +"targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door. +"health" if set, door must be shot open +"speed" movement speed (100 default) +"wait" wait before returning (3 default, -1 = never return) +"lip" lip remaining at end of move (8 default) +"dmg" damage to inflict when blocked (2 default) +"sounds" +1) silent +2) light +3) medium +4) heavy +*/ + +void door_use_areaportals (edict_t *self, qboolean open) +{ + edict_t *t = NULL; + + if (!self->target) + return; + + while ((t = G_Find (t, FOFS(targetname), self->target))) + { + if (Q_stricmp(t->classname, "func_areaportal") == 0) + { + gi.SetAreaPortalState (t->style, open); + } + } +} + +void door_go_down (edict_t *self); + +void door_hit_top (edict_t *self) +{ + if (!(self->flags & FL_TEAMSLAVE)) + { + if (self->moveinfo.sound_end) + gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_end, 1, ATTN_STATIC, 0); + self->s.sound = 0; + } + self->moveinfo.state = STATE_TOP; + if (self->spawnflags & DOOR_TOGGLE) + return; + if (self->moveinfo.wait >= 0) + { + self->think = door_go_down; + self->nextthink = level.time + self->moveinfo.wait; + } +} + +void door_hit_bottom (edict_t *self) +{ + if (!(self->flags & FL_TEAMSLAVE)) + { + if (self->moveinfo.sound_end) + gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_end, 1, ATTN_STATIC, 0); + self->s.sound = 0; + } + self->moveinfo.state = STATE_BOTTOM; + door_use_areaportals (self, false); +} + +void door_go_down (edict_t *self) +{ + if (!(self->flags & FL_TEAMSLAVE)) + { + if (self->moveinfo.sound_start) + gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_start, 1, ATTN_STATIC, 0); + self->s.sound = self->moveinfo.sound_middle; + } + if (self->max_health) + { + self->takedamage = DAMAGE_YES; + self->health = self->max_health; + } + + self->moveinfo.state = STATE_DOWN; + if (strcmp(self->classname, "func_door") == 0) + Move_Calc (self, self->moveinfo.start_origin, door_hit_bottom); + else if (strcmp(self->classname, "func_door_rotating") == 0) + AngleMove_Calc (self, door_hit_bottom); +} + +void door_go_up (edict_t *self, edict_t *activator) +{ + if (self->moveinfo.state == STATE_UP) + return; // already going up + + if (self->moveinfo.state == STATE_TOP) + { // reset top wait time + if (self->moveinfo.wait >= 0) + self->nextthink = level.time + self->moveinfo.wait; + return; + } + + if (!(self->flags & FL_TEAMSLAVE)) + { + if (self->moveinfo.sound_start) + gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_start, 1, ATTN_STATIC, 0); + self->s.sound = self->moveinfo.sound_middle; + } + self->moveinfo.state = STATE_UP; + if (strcmp(self->classname, "func_door") == 0) + Move_Calc (self, self->moveinfo.end_origin, door_hit_top); + else if (strcmp(self->classname, "func_door_rotating") == 0) + AngleMove_Calc (self, door_hit_top); + + G_UseTargets (self, activator); + door_use_areaportals (self, true); +} + +void door_use (edict_t *self, edict_t *other, edict_t *activator) +{ + edict_t *ent; + + if (self->flags & FL_TEAMSLAVE) + return; + + if (self->spawnflags & DOOR_TOGGLE) + { + if (self->moveinfo.state == STATE_UP || self->moveinfo.state == STATE_TOP) + { + // trigger all paired doors + for (ent = self ; ent ; ent = ent->teamchain) + { + ent->message = NULL; + ent->touch = NULL; + door_go_down (ent); + } + return; + } + } + + // trigger all paired doors + for (ent = self ; ent ; ent = ent->teamchain) + { + ent->message = NULL; + ent->touch = NULL; + door_go_up (ent, activator); + } +}; + +void Touch_DoorTrigger (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (other->health <= 0) + return; + + if (!(other->svflags & SVF_MONSTER) && (!other->client)) + return; + + if ((self->owner->spawnflags & DOOR_NOMONSTER) && (other->svflags & SVF_MONSTER)) + return; + + if (level.time < self->touch_debounce_time) + return; + self->touch_debounce_time = level.time + 1.0; + + door_use (self->owner, other, other); +} + +void Think_CalcMoveSpeed (edict_t *self) +{ + edict_t *ent; + float min; + float time; + float newspeed; + float ratio; + float dist; + + if (self->flags & FL_TEAMSLAVE) + return; // only the team master does this + + // find the smallest distance any member of the team will be moving + min = fabs(self->moveinfo.distance); + for (ent = self->teamchain; ent; ent = ent->teamchain) + { + dist = fabs(ent->moveinfo.distance); + if (dist < min) + min = dist; + } + + time = min / self->moveinfo.speed; + + // adjust speeds so they will all complete at the same time + for (ent = self; ent; ent = ent->teamchain) + { + newspeed = fabs(ent->moveinfo.distance) / time; + ratio = newspeed / ent->moveinfo.speed; + if (ent->moveinfo.accel == ent->moveinfo.speed) + ent->moveinfo.accel = newspeed; + else + ent->moveinfo.accel *= ratio; + if (ent->moveinfo.decel == ent->moveinfo.speed) + ent->moveinfo.decel = newspeed; + else + ent->moveinfo.decel *= ratio; + ent->moveinfo.speed = newspeed; + } +} + +void Think_SpawnDoorTrigger (edict_t *ent) +{ + edict_t *other; + vec3_t mins, maxs; + + if (ent->flags & FL_TEAMSLAVE) + return; // only the team leader spawns a trigger + + VectorCopy (ent->absmin, mins); + VectorCopy (ent->absmax, maxs); + + for (other = ent->teamchain ; other ; other=other->teamchain) + { + AddPointToBounds (other->absmin, mins, maxs); + AddPointToBounds (other->absmax, mins, maxs); + } + + // expand + mins[0] -= 60; + mins[1] -= 60; + maxs[0] += 60; + maxs[1] += 60; + + other = G_Spawn (); + VectorCopy (mins, other->mins); + VectorCopy (maxs, other->maxs); + other->owner = ent; + other->solid = SOLID_TRIGGER; + other->movetype = MOVETYPE_NONE; + other->touch = Touch_DoorTrigger; + gi.linkentity (other); + + if (ent->spawnflags & DOOR_START_OPEN) + door_use_areaportals (ent, true); + + Think_CalcMoveSpeed (ent); +} + +void door_blocked (edict_t *self, edict_t *other) +{ + edict_t *ent; + + if (!(other->svflags & SVF_MONSTER) && (!other->client) ) + { + // give it a chance to go away on it's own terms (like gibs) + T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, 100000, 1, 0, MOD_CRUSH); + // if it's still there, nuke it + if (other) + BecomeExplosion1 (other); + return; + } + + T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH); + + if (self->spawnflags & DOOR_CRUSHER) + return; + + +// if a door has a negative wait, it would never come back if blocked, +// so let it just squash the object to death real fast + if (self->moveinfo.wait >= 0) + { + if (self->moveinfo.state == STATE_DOWN) + { + for (ent = self->teammaster ; ent ; ent = ent->teamchain) + door_go_up (ent, ent->activator); + } + else + { + for (ent = self->teammaster ; ent ; ent = ent->teamchain) + door_go_down (ent); + } + } +} + +void door_killed (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + edict_t *ent; + + for (ent = self->teammaster ; ent ; ent = ent->teamchain) + { + ent->health = ent->max_health; + ent->takedamage = DAMAGE_NO; + } + door_use (self->teammaster, attacker, attacker); +} + +void door_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (!other->client) + return; + + if (level.time < self->touch_debounce_time) + return; + self->touch_debounce_time = level.time + 5.0; + + gi.centerprintf (other, "%s", self->message); + gi.sound (other, CHAN_AUTO, gi.soundindex ("misc/talk1.wav"), 1, ATTN_NORM, 0); +} + +void SP_func_door (edict_t *ent) +{ + vec3_t abs_movedir; + + if (ent->sounds != 1) + { + ent->moveinfo.sound_start = gi.soundindex ("doors/dr1_strt.wav"); + ent->moveinfo.sound_middle = gi.soundindex ("doors/dr1_mid.wav"); + ent->moveinfo.sound_end = gi.soundindex ("doors/dr1_end.wav"); + } + + G_SetMovedir (ent->s.angles, ent->movedir); + ent->movetype = MOVETYPE_PUSH; + ent->solid = SOLID_BSP; + gi.setmodel (ent, ent->model); + + ent->blocked = door_blocked; + ent->use = door_use; + + if (!ent->speed) + ent->speed = 100; + if (deathmatch->value) + ent->speed *= 2; + + if (!ent->accel) + ent->accel = ent->speed; + if (!ent->decel) + ent->decel = ent->speed; + + if (!ent->wait) + ent->wait = 3; + if (!st.lip) + st.lip = 8; + if (!ent->dmg) + ent->dmg = 2; + + // calculate second position + VectorCopy (ent->s.origin, ent->pos1); + abs_movedir[0] = fabs(ent->movedir[0]); + abs_movedir[1] = fabs(ent->movedir[1]); + abs_movedir[2] = fabs(ent->movedir[2]); + ent->moveinfo.distance = abs_movedir[0] * ent->size[0] + abs_movedir[1] * ent->size[1] + abs_movedir[2] * ent->size[2] - st.lip; + VectorMA (ent->pos1, ent->moveinfo.distance, ent->movedir, ent->pos2); + + // if it starts open, switch the positions + if (ent->spawnflags & DOOR_START_OPEN) + { + VectorCopy (ent->pos2, ent->s.origin); + VectorCopy (ent->pos1, ent->pos2); + VectorCopy (ent->s.origin, ent->pos1); + } + + ent->moveinfo.state = STATE_BOTTOM; + + if (ent->health) + { + ent->takedamage = DAMAGE_YES; + ent->die = door_killed; + ent->max_health = ent->health; + } + else if (ent->targetname && ent->message) + { + gi.soundindex ("misc/talk.wav"); + ent->touch = door_touch; + } + + ent->moveinfo.speed = ent->speed; + ent->moveinfo.accel = ent->accel; + ent->moveinfo.decel = ent->decel; + ent->moveinfo.wait = ent->wait; + VectorCopy (ent->pos1, ent->moveinfo.start_origin); + VectorCopy (ent->s.angles, ent->moveinfo.start_angles); + VectorCopy (ent->pos2, ent->moveinfo.end_origin); + VectorCopy (ent->s.angles, ent->moveinfo.end_angles); + + if (ent->spawnflags & 16) + ent->s.effects |= EF_ANIM_ALL; + if (ent->spawnflags & 64) + ent->s.effects |= EF_ANIM_ALLFAST; + + // to simplify logic elsewhere, make non-teamed doors into a team of one + if (!ent->team) + ent->teammaster = ent; + + gi.linkentity (ent); + + ent->nextthink = level.time + FRAMETIME; + if (ent->health || ent->targetname) + ent->think = Think_CalcMoveSpeed; + else + ent->think = Think_SpawnDoorTrigger; +} + + +/*QUAKED func_door_rotating (0 .5 .8) ? START_OPEN REVERSE CRUSHER NOMONSTER ANIMATED TOGGLE X_AXIS Y_AXIS +TOGGLE causes the door to wait in both the start and end states for a trigger event. + +START_OPEN the door to moves to its destination when spawned, and operate in reverse. It is used to temporarily or permanently close off an area when triggered (not useful for touch or takedamage doors). +NOMONSTER monsters will not trigger this door + +You need to have an origin brush as part of this entity. The center of that brush will be +the point around which it is rotated. It will rotate around the Z axis by default. You can +check either the X_AXIS or Y_AXIS box to change that. + +"distance" is how many degrees the door will be rotated. +"speed" determines how fast the door moves; default value is 100. + +REVERSE will cause the door to rotate in the opposite direction. + +"message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet +"angle" determines the opening direction +"targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door. +"health" if set, door must be shot open +"speed" movement speed (100 default) +"wait" wait before returning (3 default, -1 = never return) +"dmg" damage to inflict when blocked (2 default) +"sounds" +1) silent +2) light +3) medium +4) heavy +*/ + +void SP_func_door_rotating (edict_t *ent) +{ + VectorClear (ent->s.angles); + + // set the axis of rotation + VectorClear(ent->movedir); + if (ent->spawnflags & DOOR_X_AXIS) + ent->movedir[2] = 1.0; + else if (ent->spawnflags & DOOR_Y_AXIS) + ent->movedir[0] = 1.0; + else // Z_AXIS + ent->movedir[1] = 1.0; + + // check for reverse rotation + if (ent->spawnflags & DOOR_REVERSE) + VectorNegate (ent->movedir, ent->movedir); + + if (!st.distance) + { + gi.dprintf("%s at %s with no distance set\n", ent->classname, vtos(ent->s.origin)); + st.distance = 90; + } + + VectorCopy (ent->s.angles, ent->pos1); + VectorMA (ent->s.angles, st.distance, ent->movedir, ent->pos2); + ent->moveinfo.distance = st.distance; + + ent->movetype = MOVETYPE_PUSH; + ent->solid = SOLID_BSP; + gi.setmodel (ent, ent->model); + + ent->blocked = door_blocked; + ent->use = door_use; + + if (!ent->speed) + ent->speed = 100; + if (!ent->accel) + ent->accel = ent->speed; + if (!ent->decel) + ent->decel = ent->speed; + + if (!ent->wait) + ent->wait = 3; + if (!ent->dmg) + ent->dmg = 2; + + if (ent->sounds != 1) + { + ent->moveinfo.sound_start = gi.soundindex ("doors/dr1_strt.wav"); + ent->moveinfo.sound_middle = gi.soundindex ("doors/dr1_mid.wav"); + ent->moveinfo.sound_end = gi.soundindex ("doors/dr1_end.wav"); + } + + // if it starts open, switch the positions + if (ent->spawnflags & DOOR_START_OPEN) + { + VectorCopy (ent->pos2, ent->s.angles); + VectorCopy (ent->pos1, ent->pos2); + VectorCopy (ent->s.angles, ent->pos1); + VectorNegate (ent->movedir, ent->movedir); + } + + if (ent->health) + { + ent->takedamage = DAMAGE_YES; + ent->die = door_killed; + ent->max_health = ent->health; + } + + if (ent->targetname && ent->message) + { + gi.soundindex ("misc/talk.wav"); + ent->touch = door_touch; + } + + ent->moveinfo.state = STATE_BOTTOM; + ent->moveinfo.speed = ent->speed; + ent->moveinfo.accel = ent->accel; + ent->moveinfo.decel = ent->decel; + ent->moveinfo.wait = ent->wait; + VectorCopy (ent->s.origin, ent->moveinfo.start_origin); + VectorCopy (ent->pos1, ent->moveinfo.start_angles); + VectorCopy (ent->s.origin, ent->moveinfo.end_origin); + VectorCopy (ent->pos2, ent->moveinfo.end_angles); + + if (ent->spawnflags & 16) + ent->s.effects |= EF_ANIM_ALL; + + // to simplify logic elsewhere, make non-teamed doors into a team of one + if (!ent->team) + ent->teammaster = ent; + + gi.linkentity (ent); + + ent->nextthink = level.time + FRAMETIME; + if (ent->health || ent->targetname) + ent->think = Think_CalcMoveSpeed; + else + ent->think = Think_SpawnDoorTrigger; +} + + +/*QUAKED func_water (0 .5 .8) ? START_OPEN +func_water is a moveable water brush. It must be targeted to operate. Use a non-water texture at your own risk. + +START_OPEN causes the water to move to its destination when spawned and operate in reverse. + +"angle" determines the opening direction (up or down only) +"speed" movement speed (25 default) +"wait" wait before returning (-1 default, -1 = TOGGLE) +"lip" lip remaining at end of move (0 default) +"sounds" (yes, these need to be changed) +0) no sound +1) water +2) lava +*/ + +void SP_func_water (edict_t *self) +{ + vec3_t abs_movedir; + + G_SetMovedir (self->s.angles, self->movedir); + self->movetype = MOVETYPE_PUSH; + self->solid = SOLID_BSP; + gi.setmodel (self, self->model); + + switch (self->sounds) + { + default: + break; + + case 1: // water + self->moveinfo.sound_start = gi.soundindex ("world/mov_watr.wav"); + self->moveinfo.sound_end = gi.soundindex ("world/stp_watr.wav"); + break; + + case 2: // lava + self->moveinfo.sound_start = gi.soundindex ("world/mov_watr.wav"); + self->moveinfo.sound_end = gi.soundindex ("world/stp_watr.wav"); + break; + } + + // calculate second position + VectorCopy (self->s.origin, self->pos1); + abs_movedir[0] = fabs(self->movedir[0]); + abs_movedir[1] = fabs(self->movedir[1]); + abs_movedir[2] = fabs(self->movedir[2]); + self->moveinfo.distance = abs_movedir[0] * self->size[0] + abs_movedir[1] * self->size[1] + abs_movedir[2] * self->size[2] - st.lip; + VectorMA (self->pos1, self->moveinfo.distance, self->movedir, self->pos2); + + // if it starts open, switch the positions + if (self->spawnflags & DOOR_START_OPEN) + { + VectorCopy (self->pos2, self->s.origin); + VectorCopy (self->pos1, self->pos2); + VectorCopy (self->s.origin, self->pos1); + } + + VectorCopy (self->pos1, self->moveinfo.start_origin); + VectorCopy (self->s.angles, self->moveinfo.start_angles); + VectorCopy (self->pos2, self->moveinfo.end_origin); + VectorCopy (self->s.angles, self->moveinfo.end_angles); + + self->moveinfo.state = STATE_BOTTOM; + + if (!self->speed) + self->speed = 25; + self->moveinfo.accel = self->moveinfo.decel = self->moveinfo.speed = self->speed; + + if (!self->wait) + self->wait = -1; + self->moveinfo.wait = self->wait; + + self->use = door_use; + + if (self->wait == -1) + self->spawnflags |= DOOR_TOGGLE; + + self->classname = "func_door"; + + gi.linkentity (self); +} + + +#define TRAIN_START_ON 1 +#define TRAIN_TOGGLE 2 +#define TRAIN_BLOCK_STOPS 4 + +/*QUAKED func_train (0 .5 .8) ? START_ON TOGGLE BLOCK_STOPS +Trains are moving platforms that players can ride. +The targets origin specifies the min point of the train at each corner. +The train spawns at the first target it is pointing at. +If the train is the target of a button or trigger, it will not begin moving until activated. +speed default 100 +dmg default 2 +noise looping sound to play when the train is in motion + +*/ +void train_next (edict_t *self); + +void train_blocked (edict_t *self, edict_t *other) +{ + if (!(other->svflags & SVF_MONSTER) && (!other->client) ) + { + // give it a chance to go away on it's own terms (like gibs) + T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, 100000, 1, 0, MOD_CRUSH); + // if it's still there, nuke it + if (other) + BecomeExplosion1 (other); + return; + } + + if (level.time < self->touch_debounce_time) + return; + + if (!self->dmg) + return; + self->touch_debounce_time = level.time + 0.5; + T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH); +} + +void train_wait (edict_t *self) +{ + if (self->target_ent->pathtarget) + { + char *savetarget; + edict_t *ent; + + ent = self->target_ent; + savetarget = ent->target; + ent->target = ent->pathtarget; + G_UseTargets (ent, self->activator); + ent->target = savetarget; + + // make sure we didn't get killed by a killtarget + if (!self->inuse) + return; + } + + if (self->moveinfo.wait) + { + if (self->moveinfo.wait > 0) + { + self->nextthink = level.time + self->moveinfo.wait; + self->think = train_next; + } + else if (self->spawnflags & TRAIN_TOGGLE) // && wait < 0 + { + train_next (self); + self->spawnflags &= ~TRAIN_START_ON; + VectorClear (self->velocity); + self->nextthink = 0; + } + + if (!(self->flags & FL_TEAMSLAVE)) + { + if (self->moveinfo.sound_end) + gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_end, 1, ATTN_STATIC, 0); + self->s.sound = 0; + } + } + else + { + train_next (self); + } + +} + +void train_next (edict_t *self) +{ + edict_t *ent; + vec3_t dest; + qboolean first; + + first = true; +again: + if (!self->target) + { +// gi.dprintf ("train_next: no next target\n"); + return; + } + + ent = G_PickTarget (self->target); + if (!ent) + { + gi.dprintf ("train_next: bad target %s\n", self->target); + return; + } + + self->target = ent->target; + + // check for a teleport path_corner + if (ent->spawnflags & 1) + { + if (!first) + { + gi.dprintf ("connected teleport path_corners, see %s at %s\n", ent->classname, vtos(ent->s.origin)); + return; + } + first = false; + VectorSubtract (ent->s.origin, self->mins, self->s.origin); + VectorCopy (self->s.origin, self->s.old_origin); + self->s.event = EV_OTHER_TELEPORT; + gi.linkentity (self); + goto again; + } + + self->moveinfo.wait = ent->wait; + self->target_ent = ent; + + if (!(self->flags & FL_TEAMSLAVE)) + { + if (self->moveinfo.sound_start) + gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_start, 1, ATTN_STATIC, 0); + self->s.sound = self->moveinfo.sound_middle; + } + + VectorSubtract (ent->s.origin, self->mins, dest); + self->moveinfo.state = STATE_TOP; + VectorCopy (self->s.origin, self->moveinfo.start_origin); + VectorCopy (dest, self->moveinfo.end_origin); + Move_Calc (self, dest, train_wait); + self->spawnflags |= TRAIN_START_ON; +} + +void train_resume (edict_t *self) +{ + edict_t *ent; + vec3_t dest; + + ent = self->target_ent; + + VectorSubtract (ent->s.origin, self->mins, dest); + self->moveinfo.state = STATE_TOP; + VectorCopy (self->s.origin, self->moveinfo.start_origin); + VectorCopy (dest, self->moveinfo.end_origin); + Move_Calc (self, dest, train_wait); + self->spawnflags |= TRAIN_START_ON; +} + +void func_train_find (edict_t *self) +{ + edict_t *ent; + + if (!self->target) + { + gi.dprintf ("train_find: no target\n"); + return; + } + ent = G_PickTarget (self->target); + if (!ent) + { + gi.dprintf ("train_find: target %s not found\n", self->target); + return; + } + self->target = ent->target; + + VectorSubtract (ent->s.origin, self->mins, self->s.origin); + gi.linkentity (self); + + // if not triggered, start immediately + if (!self->targetname) + self->spawnflags |= TRAIN_START_ON; + + if (self->spawnflags & TRAIN_START_ON) + { + self->nextthink = level.time + FRAMETIME; + self->think = train_next; + self->activator = self; + } +} + +void train_use (edict_t *self, edict_t *other, edict_t *activator) +{ + self->activator = activator; + + if (self->spawnflags & TRAIN_START_ON) + { + if (!(self->spawnflags & TRAIN_TOGGLE)) + return; + self->spawnflags &= ~TRAIN_START_ON; + VectorClear (self->velocity); + self->nextthink = 0; + } + else + { + if (self->target_ent) + train_resume(self); + else + train_next(self); + } +} + +void SP_func_train (edict_t *self) +{ + self->movetype = MOVETYPE_PUSH; + + VectorClear (self->s.angles); + self->blocked = train_blocked; + if (self->spawnflags & TRAIN_BLOCK_STOPS) + self->dmg = 0; + else + { + if (!self->dmg) + self->dmg = 100; + } + self->solid = SOLID_BSP; + gi.setmodel (self, self->model); + + if (st.noise) + self->moveinfo.sound_middle = gi.soundindex (st.noise); + + if (!self->speed) + self->speed = 100; + + self->moveinfo.speed = self->speed; + self->moveinfo.accel = self->moveinfo.decel = self->moveinfo.speed; + + self->use = train_use; + + gi.linkentity (self); + + if (self->target) + { + // start trains on the second frame, to make sure their targets have had + // a chance to spawn + self->nextthink = level.time + FRAMETIME; + self->think = func_train_find; + } + else + { + gi.dprintf ("func_train without a target at %s\n", vtos(self->absmin)); + } +} + + +/*QUAKED trigger_elevator (0.3 0.1 0.6) (-8 -8 -8) (8 8 8) +*/ +void trigger_elevator_use (edict_t *self, edict_t *other, edict_t *activator) +{ + edict_t *target; + + if (self->movetarget->nextthink) + { +// gi.dprintf("elevator busy\n"); + return; + } + + if (!other->pathtarget) + { + gi.dprintf("elevator used with no pathtarget\n"); + return; + } + + target = G_PickTarget (other->pathtarget); + if (!target) + { + gi.dprintf("elevator used with bad pathtarget: %s\n", other->pathtarget); + return; + } + + self->movetarget->target_ent = target; + train_resume (self->movetarget); +} + +void trigger_elevator_init (edict_t *self) +{ + if (!self->target) + { + gi.dprintf("trigger_elevator has no target\n"); + return; + } + self->movetarget = G_PickTarget (self->target); + if (!self->movetarget) + { + gi.dprintf("trigger_elevator unable to find target %s\n", self->target); + return; + } + if (strcmp(self->movetarget->classname, "func_train") != 0) + { + gi.dprintf("trigger_elevator target %s is not a train\n", self->target); + return; + } + + self->use = trigger_elevator_use; + self->svflags = SVF_NOCLIENT; + +} + +void SP_trigger_elevator (edict_t *self) +{ + self->think = trigger_elevator_init; + self->nextthink = level.time + FRAMETIME; +} + + +/*QUAKED func_timer (0.3 0.1 0.6) (-8 -8 -8) (8 8 8) START_ON +"wait" base time between triggering all targets, default is 1 +"random" wait variance, default is 0 + +so, the basic time between firing is a random time between +(wait - random) and (wait + random) + +"delay" delay before first firing when turned on, default is 0 + +"pausetime" additional delay used only the very first time + and only if spawned with START_ON + +These can used but not touched. +*/ +void func_timer_think (edict_t *self) +{ + G_UseTargets (self, self->activator); + self->nextthink = level.time + self->wait + crandom() * self->random; +} + +void func_timer_use (edict_t *self, edict_t *other, edict_t *activator) +{ + self->activator = activator; + + // if on, turn it off + if (self->nextthink) + { + self->nextthink = 0; + return; + } + + // turn it on + if (self->delay) + self->nextthink = level.time + self->delay; + else + func_timer_think (self); +} + +void SP_func_timer (edict_t *self) +{ + if (!self->wait) + self->wait = 1.0; + + self->use = func_timer_use; + self->think = func_timer_think; + + if (self->random >= self->wait) + { + self->random = self->wait - FRAMETIME; + gi.dprintf("func_timer at %s has random >= wait\n", vtos(self->s.origin)); + } + + if (self->spawnflags & 1) + { + self->nextthink = level.time + 1.0 + st.pausetime + self->delay + self->wait + crandom() * self->random; + self->activator = self; + } + + self->svflags = SVF_NOCLIENT; +} + + +/*QUAKED func_conveyor (0 .5 .8) ? START_ON TOGGLE +Conveyors are stationary brushes that move what's on them. +The brush should be have a surface with at least one current content enabled. +speed default 100 +*/ + +void func_conveyor_use (edict_t *self, edict_t *other, edict_t *activator) +{ + if (self->spawnflags & 1) + { + self->speed = 0; + self->spawnflags &= ~1; + } + else + { + self->speed = self->count; + self->spawnflags |= 1; + } + + if (!(self->spawnflags & 2)) + self->count = 0; +} + +void SP_func_conveyor (edict_t *self) +{ + if (!self->speed) + self->speed = 100; + + if (!(self->spawnflags & 1)) + { + self->count = self->speed; + self->speed = 0; + } + + self->use = func_conveyor_use; + + gi.setmodel (self, self->model); + self->solid = SOLID_BSP; + gi.linkentity (self); +} + + +/*QUAKED func_door_secret (0 .5 .8) ? always_shoot 1st_left 1st_down +A secret door. Slide back and then to the side. + +open_once doors never closes +1st_left 1st move is left of arrow +1st_down 1st move is down from arrow +always_shoot door is shootebale even if targeted + +"angle" determines the direction +"dmg" damage to inflic when blocked (default 2) +"wait" how long to hold in the open position (default 5, -1 means hold) +*/ + +#define SECRET_ALWAYS_SHOOT 1 +#define SECRET_1ST_LEFT 2 +#define SECRET_1ST_DOWN 4 + +void door_secret_move1 (edict_t *self); +void door_secret_move2 (edict_t *self); +void door_secret_move3 (edict_t *self); +void door_secret_move4 (edict_t *self); +void door_secret_move5 (edict_t *self); +void door_secret_move6 (edict_t *self); +void door_secret_done (edict_t *self); + +void door_secret_use (edict_t *self, edict_t *other, edict_t *activator) +{ + // make sure we're not already moving + if (!VectorCompare(self->s.origin, vec3_origin)) + return; + + Move_Calc (self, self->pos1, door_secret_move1); + door_use_areaportals (self, true); +} + +void door_secret_move1 (edict_t *self) +{ + self->nextthink = level.time + 1.0; + self->think = door_secret_move2; +} + +void door_secret_move2 (edict_t *self) +{ + Move_Calc (self, self->pos2, door_secret_move3); +} + +void door_secret_move3 (edict_t *self) +{ + if (self->wait == -1) + return; + self->nextthink = level.time + self->wait; + self->think = door_secret_move4; +} + +void door_secret_move4 (edict_t *self) +{ + Move_Calc (self, self->pos1, door_secret_move5); +} + +void door_secret_move5 (edict_t *self) +{ + self->nextthink = level.time + 1.0; + self->think = door_secret_move6; +} + +void door_secret_move6 (edict_t *self) +{ + Move_Calc (self, vec3_origin, door_secret_done); +} + +void door_secret_done (edict_t *self) +{ + if (!(self->targetname) || (self->spawnflags & SECRET_ALWAYS_SHOOT)) + { + self->health = 0; + self->takedamage = DAMAGE_YES; + } + door_use_areaportals (self, false); +} + +void door_secret_blocked (edict_t *self, edict_t *other) +{ + if (!(other->svflags & SVF_MONSTER) && (!other->client) ) + { + // give it a chance to go away on it's own terms (like gibs) + T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, 100000, 1, 0, MOD_CRUSH); + // if it's still there, nuke it + if (other) + BecomeExplosion1 (other); + return; + } + + if (level.time < self->touch_debounce_time) + return; + self->touch_debounce_time = level.time + 0.5; + + T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH); +} + +void door_secret_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + self->takedamage = DAMAGE_NO; + door_secret_use (self, attacker, attacker); +} + +void SP_func_door_secret (edict_t *ent) +{ + vec3_t forward, right, up; + float side; + float width; + float length; + + ent->moveinfo.sound_start = gi.soundindex ("doors/dr1_strt.wav"); + ent->moveinfo.sound_middle = gi.soundindex ("doors/dr1_mid.wav"); + ent->moveinfo.sound_end = gi.soundindex ("doors/dr1_end.wav"); + + ent->movetype = MOVETYPE_PUSH; + ent->solid = SOLID_BSP; + gi.setmodel (ent, ent->model); + + ent->blocked = door_secret_blocked; + ent->use = door_secret_use; + + if (!(ent->targetname) || (ent->spawnflags & SECRET_ALWAYS_SHOOT)) + { + ent->health = 0; + ent->takedamage = DAMAGE_YES; + ent->die = door_secret_die; + } + + if (!ent->dmg) + ent->dmg = 2; + + if (!ent->wait) + ent->wait = 5; + + ent->moveinfo.accel = + ent->moveinfo.decel = + ent->moveinfo.speed = 50; + + // calculate positions + AngleVectors (ent->s.angles, forward, right, up); + VectorClear (ent->s.angles); + side = 1.0 - (ent->spawnflags & SECRET_1ST_LEFT); + if (ent->spawnflags & SECRET_1ST_DOWN) + width = fabs(DotProduct(up, ent->size)); + else + width = fabs(DotProduct(right, ent->size)); + length = fabs(DotProduct(forward, ent->size)); + if (ent->spawnflags & SECRET_1ST_DOWN) + VectorMA (ent->s.origin, -1 * width, up, ent->pos1); + else + VectorMA (ent->s.origin, side * width, right, ent->pos1); + VectorMA (ent->pos1, length, forward, ent->pos2); + + if (ent->health) + { + ent->takedamage = DAMAGE_YES; + ent->die = door_killed; + ent->max_health = ent->health; + } + else if (ent->targetname && ent->message) + { + gi.soundindex ("misc/talk.wav"); + ent->touch = door_touch; + } + + ent->classname = "func_door"; + + gi.linkentity (ent); +} + + +/*QUAKED func_killbox (1 0 0) ? +Kills everything inside when fired, irrespective of protection. +*/ +void use_killbox (edict_t *self, edict_t *other, edict_t *activator) +{ + KillBox (self); +} + +void SP_func_killbox (edict_t *ent) +{ + gi.setmodel (ent, ent->model); + ent->use = use_killbox; + ent->svflags = SVF_NOCLIENT; +} + diff --git a/original/baseq2/g_items.c b/original/baseq2/g_items.c new file mode 100644 index 0000000..e81e835 --- /dev/null +++ b/original/baseq2/g_items.c @@ -0,0 +1,2216 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +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. + +*/ +#include "g_local.h" + + +qboolean Pickup_Weapon (edict_t *ent, edict_t *other); +void Use_Weapon (edict_t *ent, gitem_t *inv); +void Drop_Weapon (edict_t *ent, gitem_t *inv); + +void Weapon_Blaster (edict_t *ent); +void Weapon_Shotgun (edict_t *ent); +void Weapon_SuperShotgun (edict_t *ent); +void Weapon_Machinegun (edict_t *ent); +void Weapon_Chaingun (edict_t *ent); +void Weapon_HyperBlaster (edict_t *ent); +void Weapon_RocketLauncher (edict_t *ent); +void Weapon_Grenade (edict_t *ent); +void Weapon_GrenadeLauncher (edict_t *ent); +void Weapon_Railgun (edict_t *ent); +void Weapon_BFG (edict_t *ent); + +gitem_armor_t jacketarmor_info = { 25, 50, .30, .00, ARMOR_JACKET}; +gitem_armor_t combatarmor_info = { 50, 100, .60, .30, ARMOR_COMBAT}; +gitem_armor_t bodyarmor_info = {100, 200, .80, .60, ARMOR_BODY}; + +static int jacket_armor_index; +static int combat_armor_index; +static int body_armor_index; +static int power_screen_index; +static int power_shield_index; + +#define HEALTH_IGNORE_MAX 1 +#define HEALTH_TIMED 2 + +void Use_Quad (edict_t *ent, gitem_t *item); +static int quad_drop_timeout_hack; + +//====================================================================== + +/* +=============== +GetItemByIndex +=============== +*/ +gitem_t *GetItemByIndex (int index) +{ + if (index == 0 || index >= game.num_items) + return NULL; + + return &itemlist[index]; +} + + +/* +=============== +FindItemByClassname + +=============== +*/ +gitem_t *FindItemByClassname (char *classname) +{ + int i; + gitem_t *it; + + it = itemlist; + for (i=0 ; iclassname) + continue; + if (!Q_stricmp(it->classname, classname)) + return it; + } + + return NULL; +} + +/* +=============== +FindItem + +=============== +*/ +gitem_t *FindItem (char *pickup_name) +{ + int i; + gitem_t *it; + + it = itemlist; + for (i=0 ; ipickup_name) + continue; + if (!Q_stricmp(it->pickup_name, pickup_name)) + return it; + } + + return NULL; +} + +//====================================================================== + +void DoRespawn (edict_t *ent) +{ + if (ent->team) + { + edict_t *master; + int count; + int choice; + + master = ent->teammaster; + + for (count = 0, ent = master; ent; ent = ent->chain, count++) + ; + + choice = rand() % count; + + for (count = 0, ent = master; count < choice; ent = ent->chain, count++) + ; + } + + ent->svflags &= ~SVF_NOCLIENT; + ent->solid = SOLID_TRIGGER; + gi.linkentity (ent); + + // send an effect + ent->s.event = EV_ITEM_RESPAWN; +} + +void SetRespawn (edict_t *ent, float delay) +{ + ent->flags |= FL_RESPAWN; + ent->svflags |= SVF_NOCLIENT; + ent->solid = SOLID_NOT; + ent->nextthink = level.time + delay; + ent->think = DoRespawn; + gi.linkentity (ent); +} + + +//====================================================================== + +qboolean Pickup_Powerup (edict_t *ent, edict_t *other) +{ + int quantity; + + quantity = other->client->pers.inventory[ITEM_INDEX(ent->item)]; + if ((skill->value == 1 && quantity >= 2) || (skill->value >= 2 && quantity >= 1)) + return false; + + if ((coop->value) && (ent->item->flags & IT_STAY_COOP) && (quantity > 0)) + return false; + + other->client->pers.inventory[ITEM_INDEX(ent->item)]++; + + if (deathmatch->value) + { + if (!(ent->spawnflags & DROPPED_ITEM) ) + SetRespawn (ent, ent->item->quantity); + if (((int)dmflags->value & DF_INSTANT_ITEMS) || ((ent->item->use == Use_Quad) && (ent->spawnflags & DROPPED_PLAYER_ITEM))) + { + if ((ent->item->use == Use_Quad) && (ent->spawnflags & DROPPED_PLAYER_ITEM)) + quad_drop_timeout_hack = (ent->nextthink - level.time) / FRAMETIME; + ent->item->use (other, ent->item); + } + } + + return true; +} + +void Drop_General (edict_t *ent, gitem_t *item) +{ + Drop_Item (ent, item); + ent->client->pers.inventory[ITEM_INDEX(item)]--; + ValidateSelectedItem (ent); +} + + +//====================================================================== + +qboolean Pickup_Adrenaline (edict_t *ent, edict_t *other) +{ + if (!deathmatch->value) + other->max_health += 1; + + if (other->health < other->max_health) + other->health = other->max_health; + + if (!(ent->spawnflags & DROPPED_ITEM) && (deathmatch->value)) + SetRespawn (ent, ent->item->quantity); + + return true; +} + +qboolean Pickup_AncientHead (edict_t *ent, edict_t *other) +{ + other->max_health += 2; + + if (!(ent->spawnflags & DROPPED_ITEM) && (deathmatch->value)) + SetRespawn (ent, ent->item->quantity); + + return true; +} + +qboolean Pickup_Bandolier (edict_t *ent, edict_t *other) +{ + gitem_t *item; + int index; + + if (other->client->pers.max_bullets < 250) + other->client->pers.max_bullets = 250; + if (other->client->pers.max_shells < 150) + other->client->pers.max_shells = 150; + if (other->client->pers.max_cells < 250) + other->client->pers.max_cells = 250; + if (other->client->pers.max_slugs < 75) + other->client->pers.max_slugs = 75; + + item = FindItem("Bullets"); + if (item) + { + index = ITEM_INDEX(item); + other->client->pers.inventory[index] += item->quantity; + if (other->client->pers.inventory[index] > other->client->pers.max_bullets) + other->client->pers.inventory[index] = other->client->pers.max_bullets; + } + + item = FindItem("Shells"); + if (item) + { + index = ITEM_INDEX(item); + other->client->pers.inventory[index] += item->quantity; + if (other->client->pers.inventory[index] > other->client->pers.max_shells) + other->client->pers.inventory[index] = other->client->pers.max_shells; + } + + if (!(ent->spawnflags & DROPPED_ITEM) && (deathmatch->value)) + SetRespawn (ent, ent->item->quantity); + + return true; +} + +qboolean Pickup_Pack (edict_t *ent, edict_t *other) +{ + gitem_t *item; + int index; + + if (other->client->pers.max_bullets < 300) + other->client->pers.max_bullets = 300; + if (other->client->pers.max_shells < 200) + other->client->pers.max_shells = 200; + if (other->client->pers.max_rockets < 100) + other->client->pers.max_rockets = 100; + if (other->client->pers.max_grenades < 100) + other->client->pers.max_grenades = 100; + if (other->client->pers.max_cells < 300) + other->client->pers.max_cells = 300; + if (other->client->pers.max_slugs < 100) + other->client->pers.max_slugs = 100; + + item = FindItem("Bullets"); + if (item) + { + index = ITEM_INDEX(item); + other->client->pers.inventory[index] += item->quantity; + if (other->client->pers.inventory[index] > other->client->pers.max_bullets) + other->client->pers.inventory[index] = other->client->pers.max_bullets; + } + + item = FindItem("Shells"); + if (item) + { + index = ITEM_INDEX(item); + other->client->pers.inventory[index] += item->quantity; + if (other->client->pers.inventory[index] > other->client->pers.max_shells) + other->client->pers.inventory[index] = other->client->pers.max_shells; + } + + item = FindItem("Cells"); + if (item) + { + index = ITEM_INDEX(item); + other->client->pers.inventory[index] += item->quantity; + if (other->client->pers.inventory[index] > other->client->pers.max_cells) + other->client->pers.inventory[index] = other->client->pers.max_cells; + } + + item = FindItem("Grenades"); + if (item) + { + index = ITEM_INDEX(item); + other->client->pers.inventory[index] += item->quantity; + if (other->client->pers.inventory[index] > other->client->pers.max_grenades) + other->client->pers.inventory[index] = other->client->pers.max_grenades; + } + + item = FindItem("Rockets"); + if (item) + { + index = ITEM_INDEX(item); + other->client->pers.inventory[index] += item->quantity; + if (other->client->pers.inventory[index] > other->client->pers.max_rockets) + other->client->pers.inventory[index] = other->client->pers.max_rockets; + } + + item = FindItem("Slugs"); + if (item) + { + index = ITEM_INDEX(item); + other->client->pers.inventory[index] += item->quantity; + if (other->client->pers.inventory[index] > other->client->pers.max_slugs) + other->client->pers.inventory[index] = other->client->pers.max_slugs; + } + + if (!(ent->spawnflags & DROPPED_ITEM) && (deathmatch->value)) + SetRespawn (ent, ent->item->quantity); + + return true; +} + +//====================================================================== + +void Use_Quad (edict_t *ent, gitem_t *item) +{ + int timeout; + + ent->client->pers.inventory[ITEM_INDEX(item)]--; + ValidateSelectedItem (ent); + + if (quad_drop_timeout_hack) + { + timeout = quad_drop_timeout_hack; + quad_drop_timeout_hack = 0; + } + else + { + timeout = 300; + } + + if (ent->client->quad_framenum > level.framenum) + ent->client->quad_framenum += timeout; + else + ent->client->quad_framenum = level.framenum + timeout; + + gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage.wav"), 1, ATTN_NORM, 0); +} + +//====================================================================== + +void Use_Breather (edict_t *ent, gitem_t *item) +{ + ent->client->pers.inventory[ITEM_INDEX(item)]--; + ValidateSelectedItem (ent); + + if (ent->client->breather_framenum > level.framenum) + ent->client->breather_framenum += 300; + else + ent->client->breather_framenum = level.framenum + 300; + +// gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage.wav"), 1, ATTN_NORM, 0); +} + +//====================================================================== + +void Use_Envirosuit (edict_t *ent, gitem_t *item) +{ + ent->client->pers.inventory[ITEM_INDEX(item)]--; + ValidateSelectedItem (ent); + + if (ent->client->enviro_framenum > level.framenum) + ent->client->enviro_framenum += 300; + else + ent->client->enviro_framenum = level.framenum + 300; + +// gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage.wav"), 1, ATTN_NORM, 0); +} + +//====================================================================== + +void Use_Invulnerability (edict_t *ent, gitem_t *item) +{ + ent->client->pers.inventory[ITEM_INDEX(item)]--; + ValidateSelectedItem (ent); + + if (ent->client->invincible_framenum > level.framenum) + ent->client->invincible_framenum += 300; + else + ent->client->invincible_framenum = level.framenum + 300; + + gi.sound(ent, CHAN_ITEM, gi.soundindex("items/protect.wav"), 1, ATTN_NORM, 0); +} + +//====================================================================== + +void Use_Silencer (edict_t *ent, gitem_t *item) +{ + ent->client->pers.inventory[ITEM_INDEX(item)]--; + ValidateSelectedItem (ent); + ent->client->silencer_shots += 30; + +// gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage.wav"), 1, ATTN_NORM, 0); +} + +//====================================================================== + +qboolean Pickup_Key (edict_t *ent, edict_t *other) +{ + if (coop->value) + { + if (strcmp(ent->classname, "key_power_cube") == 0) + { + if (other->client->pers.power_cubes & ((ent->spawnflags & 0x0000ff00)>> 8)) + return false; + other->client->pers.inventory[ITEM_INDEX(ent->item)]++; + other->client->pers.power_cubes |= ((ent->spawnflags & 0x0000ff00) >> 8); + } + else + { + if (other->client->pers.inventory[ITEM_INDEX(ent->item)]) + return false; + other->client->pers.inventory[ITEM_INDEX(ent->item)] = 1; + } + return true; + } + other->client->pers.inventory[ITEM_INDEX(ent->item)]++; + return true; +} + +//====================================================================== + +qboolean Add_Ammo (edict_t *ent, gitem_t *item, int count) +{ + int index; + int max; + + if (!ent->client) + return false; + + if (item->tag == AMMO_BULLETS) + max = ent->client->pers.max_bullets; + else if (item->tag == AMMO_SHELLS) + max = ent->client->pers.max_shells; + else if (item->tag == AMMO_ROCKETS) + max = ent->client->pers.max_rockets; + else if (item->tag == AMMO_GRENADES) + max = ent->client->pers.max_grenades; + else if (item->tag == AMMO_CELLS) + max = ent->client->pers.max_cells; + else if (item->tag == AMMO_SLUGS) + max = ent->client->pers.max_slugs; + else + return false; + + index = ITEM_INDEX(item); + + if (ent->client->pers.inventory[index] == max) + return false; + + ent->client->pers.inventory[index] += count; + + if (ent->client->pers.inventory[index] > max) + ent->client->pers.inventory[index] = max; + + return true; +} + +qboolean Pickup_Ammo (edict_t *ent, edict_t *other) +{ + int oldcount; + int count; + qboolean weapon; + + weapon = (ent->item->flags & IT_WEAPON); + if ( (weapon) && ( (int)dmflags->value & DF_INFINITE_AMMO ) ) + count = 1000; + else if (ent->count) + count = ent->count; + else + count = ent->item->quantity; + + oldcount = other->client->pers.inventory[ITEM_INDEX(ent->item)]; + + if (!Add_Ammo (other, ent->item, count)) + return false; + + if (weapon && !oldcount) + { + if (other->client->pers.weapon != ent->item && ( !deathmatch->value || other->client->pers.weapon == FindItem("blaster") ) ) + other->client->newweapon = ent->item; + } + + if (!(ent->spawnflags & (DROPPED_ITEM | DROPPED_PLAYER_ITEM)) && (deathmatch->value)) + SetRespawn (ent, 30); + return true; +} + +void Drop_Ammo (edict_t *ent, gitem_t *item) +{ + edict_t *dropped; + int index; + + index = ITEM_INDEX(item); + dropped = Drop_Item (ent, item); + if (ent->client->pers.inventory[index] >= item->quantity) + dropped->count = item->quantity; + else + dropped->count = ent->client->pers.inventory[index]; + + if (ent->client->pers.weapon && + ent->client->pers.weapon->tag == AMMO_GRENADES && + item->tag == AMMO_GRENADES && + ent->client->pers.inventory[index] - dropped->count <= 0) { + gi.cprintf (ent, PRINT_HIGH, "Can't drop current weapon\n"); + G_FreeEdict(dropped); + return; + } + + ent->client->pers.inventory[index] -= dropped->count; + ValidateSelectedItem (ent); +} + + +//====================================================================== + +void MegaHealth_think (edict_t *self) +{ + if (self->owner->health > self->owner->max_health) + { + self->nextthink = level.time + 1; + self->owner->health -= 1; + return; + } + + if (!(self->spawnflags & DROPPED_ITEM) && (deathmatch->value)) + SetRespawn (self, 20); + else + G_FreeEdict (self); +} + +qboolean Pickup_Health (edict_t *ent, edict_t *other) +{ + if (!(ent->style & HEALTH_IGNORE_MAX)) + if (other->health >= other->max_health) + return false; + + other->health += ent->count; + + if (!(ent->style & HEALTH_IGNORE_MAX)) + { + if (other->health > other->max_health) + other->health = other->max_health; + } + + if (ent->style & HEALTH_TIMED) + { + ent->think = MegaHealth_think; + ent->nextthink = level.time + 5; + ent->owner = other; + ent->flags |= FL_RESPAWN; + ent->svflags |= SVF_NOCLIENT; + ent->solid = SOLID_NOT; + } + else + { + if (!(ent->spawnflags & DROPPED_ITEM) && (deathmatch->value)) + SetRespawn (ent, 30); + } + + return true; +} + +//====================================================================== + +int ArmorIndex (edict_t *ent) +{ + if (!ent->client) + return 0; + + if (ent->client->pers.inventory[jacket_armor_index] > 0) + return jacket_armor_index; + + if (ent->client->pers.inventory[combat_armor_index] > 0) + return combat_armor_index; + + if (ent->client->pers.inventory[body_armor_index] > 0) + return body_armor_index; + + return 0; +} + +qboolean Pickup_Armor (edict_t *ent, edict_t *other) +{ + int old_armor_index; + gitem_armor_t *oldinfo; + gitem_armor_t *newinfo; + int newcount; + float salvage; + int salvagecount; + + // get info on new armor + newinfo = (gitem_armor_t *)ent->item->info; + + old_armor_index = ArmorIndex (other); + + // handle armor shards specially + if (ent->item->tag == ARMOR_SHARD) + { + if (!old_armor_index) + other->client->pers.inventory[jacket_armor_index] = 2; + else + other->client->pers.inventory[old_armor_index] += 2; + } + + // if player has no armor, just use it + else if (!old_armor_index) + { + other->client->pers.inventory[ITEM_INDEX(ent->item)] = newinfo->base_count; + } + + // use the better armor + else + { + // get info on old armor + if (old_armor_index == jacket_armor_index) + oldinfo = &jacketarmor_info; + else if (old_armor_index == combat_armor_index) + oldinfo = &combatarmor_info; + else // (old_armor_index == body_armor_index) + oldinfo = &bodyarmor_info; + + if (newinfo->normal_protection > oldinfo->normal_protection) + { + // calc new armor values + salvage = oldinfo->normal_protection / newinfo->normal_protection; + salvagecount = salvage * other->client->pers.inventory[old_armor_index]; + newcount = newinfo->base_count + salvagecount; + if (newcount > newinfo->max_count) + newcount = newinfo->max_count; + + // zero count of old armor so it goes away + other->client->pers.inventory[old_armor_index] = 0; + + // change armor to new item with computed value + other->client->pers.inventory[ITEM_INDEX(ent->item)] = newcount; + } + else + { + // calc new armor values + salvage = newinfo->normal_protection / oldinfo->normal_protection; + salvagecount = salvage * newinfo->base_count; + newcount = other->client->pers.inventory[old_armor_index] + salvagecount; + if (newcount > oldinfo->max_count) + newcount = oldinfo->max_count; + + // if we're already maxed out then we don't need the new armor + if (other->client->pers.inventory[old_armor_index] >= newcount) + return false; + + // update current armor value + other->client->pers.inventory[old_armor_index] = newcount; + } + } + + if (!(ent->spawnflags & DROPPED_ITEM) && (deathmatch->value)) + SetRespawn (ent, 20); + + return true; +} + +//====================================================================== + +int PowerArmorType (edict_t *ent) +{ + if (!ent->client) + return POWER_ARMOR_NONE; + + if (!(ent->flags & FL_POWER_ARMOR)) + return POWER_ARMOR_NONE; + + if (ent->client->pers.inventory[power_shield_index] > 0) + return POWER_ARMOR_SHIELD; + + if (ent->client->pers.inventory[power_screen_index] > 0) + return POWER_ARMOR_SCREEN; + + return POWER_ARMOR_NONE; +} + +void Use_PowerArmor (edict_t *ent, gitem_t *item) +{ + int index; + + if (ent->flags & FL_POWER_ARMOR) + { + ent->flags &= ~FL_POWER_ARMOR; + gi.sound(ent, CHAN_AUTO, gi.soundindex("misc/power2.wav"), 1, ATTN_NORM, 0); + } + else + { + index = ITEM_INDEX(FindItem("cells")); + if (!ent->client->pers.inventory[index]) + { + gi.cprintf (ent, PRINT_HIGH, "No cells for power armor.\n"); + return; + } + ent->flags |= FL_POWER_ARMOR; + gi.sound(ent, CHAN_AUTO, gi.soundindex("misc/power1.wav"), 1, ATTN_NORM, 0); + } +} + +qboolean Pickup_PowerArmor (edict_t *ent, edict_t *other) +{ + int quantity; + + quantity = other->client->pers.inventory[ITEM_INDEX(ent->item)]; + + other->client->pers.inventory[ITEM_INDEX(ent->item)]++; + + if (deathmatch->value) + { + if (!(ent->spawnflags & DROPPED_ITEM) ) + SetRespawn (ent, ent->item->quantity); + // auto-use for DM only if we didn't already have one + if (!quantity) + ent->item->use (other, ent->item); + } + + return true; +} + +void Drop_PowerArmor (edict_t *ent, gitem_t *item) +{ + if ((ent->flags & FL_POWER_ARMOR) && (ent->client->pers.inventory[ITEM_INDEX(item)] == 1)) + Use_PowerArmor (ent, item); + Drop_General (ent, item); +} + +//====================================================================== + +/* +=============== +Touch_Item +=============== +*/ +void Touch_Item (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + qboolean taken; + + if (!other->client) + return; + if (other->health < 1) + return; // dead people can't pickup + if (!ent->item->pickup) + return; // not a grabbable item? + + taken = ent->item->pickup(ent, other); + + if (taken) + { + // flash the screen + other->client->bonus_alpha = 0.25; + + // show icon and name on status bar + other->client->ps.stats[STAT_PICKUP_ICON] = gi.imageindex(ent->item->icon); + other->client->ps.stats[STAT_PICKUP_STRING] = CS_ITEMS+ITEM_INDEX(ent->item); + other->client->pickup_msg_time = level.time + 3.0; + + // change selected item + if (ent->item->use) + other->client->pers.selected_item = other->client->ps.stats[STAT_SELECTED_ITEM] = ITEM_INDEX(ent->item); + + if (ent->item->pickup == Pickup_Health) + { + if (ent->count == 2) + gi.sound(other, CHAN_ITEM, gi.soundindex("items/s_health.wav"), 1, ATTN_NORM, 0); + else if (ent->count == 10) + gi.sound(other, CHAN_ITEM, gi.soundindex("items/n_health.wav"), 1, ATTN_NORM, 0); + else if (ent->count == 25) + gi.sound(other, CHAN_ITEM, gi.soundindex("items/l_health.wav"), 1, ATTN_NORM, 0); + else // (ent->count == 100) + gi.sound(other, CHAN_ITEM, gi.soundindex("items/m_health.wav"), 1, ATTN_NORM, 0); + } + else if (ent->item->pickup_sound) + { + gi.sound(other, CHAN_ITEM, gi.soundindex(ent->item->pickup_sound), 1, ATTN_NORM, 0); + } + } + + if (!(ent->spawnflags & ITEM_TARGETS_USED)) + { + G_UseTargets (ent, other); + ent->spawnflags |= ITEM_TARGETS_USED; + } + + if (!taken) + return; + + if (!((coop->value) && (ent->item->flags & IT_STAY_COOP)) || (ent->spawnflags & (DROPPED_ITEM | DROPPED_PLAYER_ITEM))) + { + if (ent->flags & FL_RESPAWN) + ent->flags &= ~FL_RESPAWN; + else + G_FreeEdict (ent); + } +} + +//====================================================================== + +static void drop_temp_touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (other == ent->owner) + return; + + Touch_Item (ent, other, plane, surf); +} + +static void drop_make_touchable (edict_t *ent) +{ + ent->touch = Touch_Item; + if (deathmatch->value) + { + ent->nextthink = level.time + 29; + ent->think = G_FreeEdict; + } +} + +edict_t *Drop_Item (edict_t *ent, gitem_t *item) +{ + edict_t *dropped; + vec3_t forward, right; + vec3_t offset; + + dropped = G_Spawn(); + + dropped->classname = item->classname; + dropped->item = item; + dropped->spawnflags = DROPPED_ITEM; + dropped->s.effects = item->world_model_flags; + dropped->s.renderfx = RF_GLOW; + VectorSet (dropped->mins, -15, -15, -15); + VectorSet (dropped->maxs, 15, 15, 15); + gi.setmodel (dropped, dropped->item->world_model); + dropped->solid = SOLID_TRIGGER; + dropped->movetype = MOVETYPE_TOSS; + dropped->touch = drop_temp_touch; + dropped->owner = ent; + + if (ent->client) + { + trace_t trace; + + AngleVectors (ent->client->v_angle, forward, right, NULL); + VectorSet(offset, 24, 0, -16); + G_ProjectSource (ent->s.origin, offset, forward, right, dropped->s.origin); + trace = gi.trace (ent->s.origin, dropped->mins, dropped->maxs, + dropped->s.origin, ent, CONTENTS_SOLID); + VectorCopy (trace.endpos, dropped->s.origin); + } + else + { + AngleVectors (ent->s.angles, forward, right, NULL); + VectorCopy (ent->s.origin, dropped->s.origin); + } + + VectorScale (forward, 100, dropped->velocity); + dropped->velocity[2] = 300; + + dropped->think = drop_make_touchable; + dropped->nextthink = level.time + 1; + + gi.linkentity (dropped); + + return dropped; +} + +void Use_Item (edict_t *ent, edict_t *other, edict_t *activator) +{ + ent->svflags &= ~SVF_NOCLIENT; + ent->use = NULL; + + if (ent->spawnflags & ITEM_NO_TOUCH) + { + ent->solid = SOLID_BBOX; + ent->touch = NULL; + } + else + { + ent->solid = SOLID_TRIGGER; + ent->touch = Touch_Item; + } + + gi.linkentity (ent); +} + +//====================================================================== + +/* +================ +droptofloor +================ +*/ +void droptofloor (edict_t *ent) +{ + trace_t tr; + vec3_t dest; + float *v; + + v = tv(-15,-15,-15); + VectorCopy (v, ent->mins); + v = tv(15,15,15); + VectorCopy (v, ent->maxs); + + if (ent->model) + gi.setmodel (ent, ent->model); + else + gi.setmodel (ent, ent->item->world_model); + ent->solid = SOLID_TRIGGER; + ent->movetype = MOVETYPE_TOSS; + ent->touch = Touch_Item; + + v = tv(0,0,-128); + VectorAdd (ent->s.origin, v, dest); + + tr = gi.trace (ent->s.origin, ent->mins, ent->maxs, dest, ent, MASK_SOLID); + if (tr.startsolid) + { + gi.dprintf ("droptofloor: %s startsolid at %s\n", ent->classname, vtos(ent->s.origin)); + G_FreeEdict (ent); + return; + } + + VectorCopy (tr.endpos, ent->s.origin); + + if (ent->team) + { + ent->flags &= ~FL_TEAMSLAVE; + ent->chain = ent->teamchain; + ent->teamchain = NULL; + + ent->svflags |= SVF_NOCLIENT; + ent->solid = SOLID_NOT; + if (ent == ent->teammaster) + { + ent->nextthink = level.time + FRAMETIME; + ent->think = DoRespawn; + } + } + + if (ent->spawnflags & ITEM_NO_TOUCH) + { + ent->solid = SOLID_BBOX; + ent->touch = NULL; + ent->s.effects &= ~EF_ROTATE; + ent->s.renderfx &= ~RF_GLOW; + } + + if (ent->spawnflags & ITEM_TRIGGER_SPAWN) + { + ent->svflags |= SVF_NOCLIENT; + ent->solid = SOLID_NOT; + ent->use = Use_Item; + } + + gi.linkentity (ent); +} + + +/* +=============== +PrecacheItem + +Precaches all data needed for a given item. +This will be called for each item spawned in a level, +and for each item in each client's inventory. +=============== +*/ +void PrecacheItem (gitem_t *it) +{ + char *s, *start; + char data[MAX_QPATH]; + int len; + gitem_t *ammo; + + if (!it) + return; + + if (it->pickup_sound) + gi.soundindex (it->pickup_sound); + if (it->world_model) + gi.modelindex (it->world_model); + if (it->view_model) + gi.modelindex (it->view_model); + if (it->icon) + gi.imageindex (it->icon); + + // parse everything for its ammo + if (it->ammo && it->ammo[0]) + { + ammo = FindItem (it->ammo); + if (ammo != it) + PrecacheItem (ammo); + } + + // parse the space seperated precache string for other items + s = it->precaches; + if (!s || !s[0]) + return; + + while (*s) + { + start = s; + while (*s && *s != ' ') + s++; + + len = s-start; + if (len >= MAX_QPATH || len < 5) + gi.error ("PrecacheItem: %s has bad precache string", it->classname); + memcpy (data, start, len); + data[len] = 0; + if (*s) + s++; + + // determine type based on extension + if (!strcmp(data+len-3, "md2")) + gi.modelindex (data); + else if (!strcmp(data+len-3, "sp2")) + gi.modelindex (data); + else if (!strcmp(data+len-3, "wav")) + gi.soundindex (data); + if (!strcmp(data+len-3, "pcx")) + gi.imageindex (data); + } +} + +/* +============ +SpawnItem + +Sets the clipping size and plants the object on the floor. + +Items can't be immediately dropped to floor, because they might +be on an entity that hasn't spawned yet. +============ +*/ +void SpawnItem (edict_t *ent, gitem_t *item) +{ + PrecacheItem (item); + + if (ent->spawnflags) + { + if (strcmp(ent->classname, "key_power_cube") != 0) + { + ent->spawnflags = 0; + gi.dprintf("%s at %s has invalid spawnflags set\n", ent->classname, vtos(ent->s.origin)); + } + } + + // some items will be prevented in deathmatch + if (deathmatch->value) + { + if ( (int)dmflags->value & DF_NO_ARMOR ) + { + if (item->pickup == Pickup_Armor || item->pickup == Pickup_PowerArmor) + { + G_FreeEdict (ent); + return; + } + } + if ( (int)dmflags->value & DF_NO_ITEMS ) + { + if (item->pickup == Pickup_Powerup) + { + G_FreeEdict (ent); + return; + } + } + if ( (int)dmflags->value & DF_NO_HEALTH ) + { + if (item->pickup == Pickup_Health || item->pickup == Pickup_Adrenaline || item->pickup == Pickup_AncientHead) + { + G_FreeEdict (ent); + return; + } + } + if ( (int)dmflags->value & DF_INFINITE_AMMO ) + { + if ( (item->flags == IT_AMMO) || (strcmp(ent->classname, "weapon_bfg") == 0) ) + { + G_FreeEdict (ent); + return; + } + } + } + + if (coop->value && (strcmp(ent->classname, "key_power_cube") == 0)) + { + ent->spawnflags |= (1 << (8 + level.power_cubes)); + level.power_cubes++; + } + + // don't let them drop items that stay in a coop game + if ((coop->value) && (item->flags & IT_STAY_COOP)) + { + item->drop = NULL; + } + + ent->item = item; + ent->nextthink = level.time + 2 * FRAMETIME; // items start after other solids + ent->think = droptofloor; + ent->s.effects = item->world_model_flags; + ent->s.renderfx = RF_GLOW; + if (ent->model) + gi.modelindex (ent->model); +} + +//====================================================================== + +gitem_t itemlist[] = +{ + { + NULL + }, // leave index 0 alone + + // + // ARMOR + // + +/*QUAKED item_armor_body (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "item_armor_body", + Pickup_Armor, + NULL, + NULL, + NULL, + "misc/ar1_pkup.wav", + "models/items/armor/body/tris.md2", EF_ROTATE, + NULL, +/* icon */ "i_bodyarmor", +/* pickup */ "Body Armor", +/* width */ 3, + 0, + NULL, + IT_ARMOR, + 0, + &bodyarmor_info, + ARMOR_BODY, +/* precache */ "" + }, + +/*QUAKED item_armor_combat (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "item_armor_combat", + Pickup_Armor, + NULL, + NULL, + NULL, + "misc/ar1_pkup.wav", + "models/items/armor/combat/tris.md2", EF_ROTATE, + NULL, +/* icon */ "i_combatarmor", +/* pickup */ "Combat Armor", +/* width */ 3, + 0, + NULL, + IT_ARMOR, + 0, + &combatarmor_info, + ARMOR_COMBAT, +/* precache */ "" + }, + +/*QUAKED item_armor_jacket (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "item_armor_jacket", + Pickup_Armor, + NULL, + NULL, + NULL, + "misc/ar1_pkup.wav", + "models/items/armor/jacket/tris.md2", EF_ROTATE, + NULL, +/* icon */ "i_jacketarmor", +/* pickup */ "Jacket Armor", +/* width */ 3, + 0, + NULL, + IT_ARMOR, + 0, + &jacketarmor_info, + ARMOR_JACKET, +/* precache */ "" + }, + +/*QUAKED item_armor_shard (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "item_armor_shard", + Pickup_Armor, + NULL, + NULL, + NULL, + "misc/ar2_pkup.wav", + "models/items/armor/shard/tris.md2", EF_ROTATE, + NULL, +/* icon */ "i_jacketarmor", +/* pickup */ "Armor Shard", +/* width */ 3, + 0, + NULL, + IT_ARMOR, + 0, + NULL, + ARMOR_SHARD, +/* precache */ "" + }, + + +/*QUAKED item_power_screen (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "item_power_screen", + Pickup_PowerArmor, + Use_PowerArmor, + Drop_PowerArmor, + NULL, + "misc/ar3_pkup.wav", + "models/items/armor/screen/tris.md2", EF_ROTATE, + NULL, +/* icon */ "i_powerscreen", +/* pickup */ "Power Screen", +/* width */ 0, + 60, + NULL, + IT_ARMOR, + 0, + NULL, + 0, +/* precache */ "" + }, + +/*QUAKED item_power_shield (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "item_power_shield", + Pickup_PowerArmor, + Use_PowerArmor, + Drop_PowerArmor, + NULL, + "misc/ar3_pkup.wav", + "models/items/armor/shield/tris.md2", EF_ROTATE, + NULL, +/* icon */ "i_powershield", +/* pickup */ "Power Shield", +/* width */ 0, + 60, + NULL, + IT_ARMOR, + 0, + NULL, + 0, +/* precache */ "misc/power2.wav misc/power1.wav" + }, + + + // + // WEAPONS + // + +/* weapon_blaster (.3 .3 1) (-16 -16 -16) (16 16 16) +always owned, never in the world +*/ + { + "weapon_blaster", + NULL, + Use_Weapon, + NULL, + Weapon_Blaster, + "misc/w_pkup.wav", + NULL, 0, + "models/weapons/v_blast/tris.md2", +/* icon */ "w_blaster", +/* pickup */ "Blaster", + 0, + 0, + NULL, + IT_WEAPON|IT_STAY_COOP, + WEAP_BLASTER, + NULL, + 0, +/* precache */ "weapons/blastf1a.wav misc/lasfly.wav" + }, + +/*QUAKED weapon_shotgun (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "weapon_shotgun", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_Shotgun, + "misc/w_pkup.wav", + "models/weapons/g_shotg/tris.md2", EF_ROTATE, + "models/weapons/v_shotg/tris.md2", +/* icon */ "w_shotgun", +/* pickup */ "Shotgun", + 0, + 1, + "Shells", + IT_WEAPON|IT_STAY_COOP, + WEAP_SHOTGUN, + NULL, + 0, +/* precache */ "weapons/shotgf1b.wav weapons/shotgr1b.wav" + }, + +/*QUAKED weapon_supershotgun (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "weapon_supershotgun", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_SuperShotgun, + "misc/w_pkup.wav", + "models/weapons/g_shotg2/tris.md2", EF_ROTATE, + "models/weapons/v_shotg2/tris.md2", +/* icon */ "w_sshotgun", +/* pickup */ "Super Shotgun", + 0, + 2, + "Shells", + IT_WEAPON|IT_STAY_COOP, + WEAP_SUPERSHOTGUN, + NULL, + 0, +/* precache */ "weapons/sshotf1b.wav" + }, + +/*QUAKED weapon_machinegun (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "weapon_machinegun", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_Machinegun, + "misc/w_pkup.wav", + "models/weapons/g_machn/tris.md2", EF_ROTATE, + "models/weapons/v_machn/tris.md2", +/* icon */ "w_machinegun", +/* pickup */ "Machinegun", + 0, + 1, + "Bullets", + IT_WEAPON|IT_STAY_COOP, + WEAP_MACHINEGUN, + NULL, + 0, +/* precache */ "weapons/machgf1b.wav weapons/machgf2b.wav weapons/machgf3b.wav weapons/machgf4b.wav weapons/machgf5b.wav" + }, + +/*QUAKED weapon_chaingun (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "weapon_chaingun", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_Chaingun, + "misc/w_pkup.wav", + "models/weapons/g_chain/tris.md2", EF_ROTATE, + "models/weapons/v_chain/tris.md2", +/* icon */ "w_chaingun", +/* pickup */ "Chaingun", + 0, + 1, + "Bullets", + IT_WEAPON|IT_STAY_COOP, + WEAP_CHAINGUN, + NULL, + 0, +/* precache */ "weapons/chngnu1a.wav weapons/chngnl1a.wav weapons/machgf3b.wav` weapons/chngnd1a.wav" + }, + +/*QUAKED ammo_grenades (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "ammo_grenades", + Pickup_Ammo, + Use_Weapon, + Drop_Ammo, + Weapon_Grenade, + "misc/am_pkup.wav", + "models/items/ammo/grenades/medium/tris.md2", 0, + "models/weapons/v_handgr/tris.md2", +/* icon */ "a_grenades", +/* pickup */ "Grenades", +/* width */ 3, + 5, + "grenades", + IT_AMMO|IT_WEAPON, + WEAP_GRENADES, + NULL, + AMMO_GRENADES, +/* precache */ "weapons/hgrent1a.wav weapons/hgrena1b.wav weapons/hgrenc1b.wav weapons/hgrenb1a.wav weapons/hgrenb2a.wav " + }, + +/*QUAKED weapon_grenadelauncher (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "weapon_grenadelauncher", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_GrenadeLauncher, + "misc/w_pkup.wav", + "models/weapons/g_launch/tris.md2", EF_ROTATE, + "models/weapons/v_launch/tris.md2", +/* icon */ "w_glauncher", +/* pickup */ "Grenade Launcher", + 0, + 1, + "Grenades", + IT_WEAPON|IT_STAY_COOP, + WEAP_GRENADELAUNCHER, + NULL, + 0, +/* precache */ "models/objects/grenade/tris.md2 weapons/grenlf1a.wav weapons/grenlr1b.wav weapons/grenlb1b.wav" + }, + +/*QUAKED weapon_rocketlauncher (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "weapon_rocketlauncher", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_RocketLauncher, + "misc/w_pkup.wav", + "models/weapons/g_rocket/tris.md2", EF_ROTATE, + "models/weapons/v_rocket/tris.md2", +/* icon */ "w_rlauncher", +/* pickup */ "Rocket Launcher", + 0, + 1, + "Rockets", + IT_WEAPON|IT_STAY_COOP, + WEAP_ROCKETLAUNCHER, + NULL, + 0, +/* precache */ "models/objects/rocket/tris.md2 weapons/rockfly.wav weapons/rocklf1a.wav weapons/rocklr1b.wav models/objects/debris2/tris.md2" + }, + +/*QUAKED weapon_hyperblaster (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "weapon_hyperblaster", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_HyperBlaster, + "misc/w_pkup.wav", + "models/weapons/g_hyperb/tris.md2", EF_ROTATE, + "models/weapons/v_hyperb/tris.md2", +/* icon */ "w_hyperblaster", +/* pickup */ "HyperBlaster", + 0, + 1, + "Cells", + IT_WEAPON|IT_STAY_COOP, + WEAP_HYPERBLASTER, + NULL, + 0, +/* precache */ "weapons/hyprbu1a.wav weapons/hyprbl1a.wav weapons/hyprbf1a.wav weapons/hyprbd1a.wav misc/lasfly.wav" + }, + +/*QUAKED weapon_railgun (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "weapon_railgun", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_Railgun, + "misc/w_pkup.wav", + "models/weapons/g_rail/tris.md2", EF_ROTATE, + "models/weapons/v_rail/tris.md2", +/* icon */ "w_railgun", +/* pickup */ "Railgun", + 0, + 1, + "Slugs", + IT_WEAPON|IT_STAY_COOP, + WEAP_RAILGUN, + NULL, + 0, +/* precache */ "weapons/rg_hum.wav" + }, + +/*QUAKED weapon_bfg (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "weapon_bfg", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_BFG, + "misc/w_pkup.wav", + "models/weapons/g_bfg/tris.md2", EF_ROTATE, + "models/weapons/v_bfg/tris.md2", +/* icon */ "w_bfg", +/* pickup */ "BFG10K", + 0, + 50, + "Cells", + IT_WEAPON|IT_STAY_COOP, + WEAP_BFG, + NULL, + 0, +/* precache */ "sprites/s_bfg1.sp2 sprites/s_bfg2.sp2 sprites/s_bfg3.sp2 weapons/bfg__f1y.wav weapons/bfg__l1a.wav weapons/bfg__x1b.wav weapons/bfg_hum.wav" + }, + + // + // AMMO ITEMS + // + +/*QUAKED ammo_shells (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "ammo_shells", + Pickup_Ammo, + NULL, + Drop_Ammo, + NULL, + "misc/am_pkup.wav", + "models/items/ammo/shells/medium/tris.md2", 0, + NULL, +/* icon */ "a_shells", +/* pickup */ "Shells", +/* width */ 3, + 10, + NULL, + IT_AMMO, + 0, + NULL, + AMMO_SHELLS, +/* precache */ "" + }, + +/*QUAKED ammo_bullets (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "ammo_bullets", + Pickup_Ammo, + NULL, + Drop_Ammo, + NULL, + "misc/am_pkup.wav", + "models/items/ammo/bullets/medium/tris.md2", 0, + NULL, +/* icon */ "a_bullets", +/* pickup */ "Bullets", +/* width */ 3, + 50, + NULL, + IT_AMMO, + 0, + NULL, + AMMO_BULLETS, +/* precache */ "" + }, + +/*QUAKED ammo_cells (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "ammo_cells", + Pickup_Ammo, + NULL, + Drop_Ammo, + NULL, + "misc/am_pkup.wav", + "models/items/ammo/cells/medium/tris.md2", 0, + NULL, +/* icon */ "a_cells", +/* pickup */ "Cells", +/* width */ 3, + 50, + NULL, + IT_AMMO, + 0, + NULL, + AMMO_CELLS, +/* precache */ "" + }, + +/*QUAKED ammo_rockets (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "ammo_rockets", + Pickup_Ammo, + NULL, + Drop_Ammo, + NULL, + "misc/am_pkup.wav", + "models/items/ammo/rockets/medium/tris.md2", 0, + NULL, +/* icon */ "a_rockets", +/* pickup */ "Rockets", +/* width */ 3, + 5, + NULL, + IT_AMMO, + 0, + NULL, + AMMO_ROCKETS, +/* precache */ "" + }, + +/*QUAKED ammo_slugs (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "ammo_slugs", + Pickup_Ammo, + NULL, + Drop_Ammo, + NULL, + "misc/am_pkup.wav", + "models/items/ammo/slugs/medium/tris.md2", 0, + NULL, +/* icon */ "a_slugs", +/* pickup */ "Slugs", +/* width */ 3, + 10, + NULL, + IT_AMMO, + 0, + NULL, + AMMO_SLUGS, +/* precache */ "" + }, + + + // + // POWERUP ITEMS + // +/*QUAKED item_quad (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "item_quad", + Pickup_Powerup, + Use_Quad, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/quaddama/tris.md2", EF_ROTATE, + NULL, +/* icon */ "p_quad", +/* pickup */ "Quad Damage", +/* width */ 2, + 60, + NULL, + IT_POWERUP, + 0, + NULL, + 0, +/* precache */ "items/damage.wav items/damage2.wav items/damage3.wav" + }, + +/*QUAKED item_invulnerability (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "item_invulnerability", + Pickup_Powerup, + Use_Invulnerability, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/invulner/tris.md2", EF_ROTATE, + NULL, +/* icon */ "p_invulnerability", +/* pickup */ "Invulnerability", +/* width */ 2, + 300, + NULL, + IT_POWERUP, + 0, + NULL, + 0, +/* precache */ "items/protect.wav items/protect2.wav items/protect4.wav" + }, + +/*QUAKED item_silencer (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "item_silencer", + Pickup_Powerup, + Use_Silencer, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/silencer/tris.md2", EF_ROTATE, + NULL, +/* icon */ "p_silencer", +/* pickup */ "Silencer", +/* width */ 2, + 60, + NULL, + IT_POWERUP, + 0, + NULL, + 0, +/* precache */ "" + }, + +/*QUAKED item_breather (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "item_breather", + Pickup_Powerup, + Use_Breather, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/breather/tris.md2", EF_ROTATE, + NULL, +/* icon */ "p_rebreather", +/* pickup */ "Rebreather", +/* width */ 2, + 60, + NULL, + IT_STAY_COOP|IT_POWERUP, + 0, + NULL, + 0, +/* precache */ "items/airout.wav" + }, + +/*QUAKED item_enviro (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "item_enviro", + Pickup_Powerup, + Use_Envirosuit, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/enviro/tris.md2", EF_ROTATE, + NULL, +/* icon */ "p_envirosuit", +/* pickup */ "Environment Suit", +/* width */ 2, + 60, + NULL, + IT_STAY_COOP|IT_POWERUP, + 0, + NULL, + 0, +/* precache */ "items/airout.wav" + }, + +/*QUAKED item_ancient_head (.3 .3 1) (-16 -16 -16) (16 16 16) +Special item that gives +2 to maximum health +*/ + { + "item_ancient_head", + Pickup_AncientHead, + NULL, + NULL, + NULL, + "items/pkup.wav", + "models/items/c_head/tris.md2", EF_ROTATE, + NULL, +/* icon */ "i_fixme", +/* pickup */ "Ancient Head", +/* width */ 2, + 60, + NULL, + 0, + 0, + NULL, + 0, +/* precache */ "" + }, + +/*QUAKED item_adrenaline (.3 .3 1) (-16 -16 -16) (16 16 16) +gives +1 to maximum health +*/ + { + "item_adrenaline", + Pickup_Adrenaline, + NULL, + NULL, + NULL, + "items/pkup.wav", + "models/items/adrenal/tris.md2", EF_ROTATE, + NULL, +/* icon */ "p_adrenaline", +/* pickup */ "Adrenaline", +/* width */ 2, + 60, + NULL, + 0, + 0, + NULL, + 0, +/* precache */ "" + }, + +/*QUAKED item_bandolier (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "item_bandolier", + Pickup_Bandolier, + NULL, + NULL, + NULL, + "items/pkup.wav", + "models/items/band/tris.md2", EF_ROTATE, + NULL, +/* icon */ "p_bandolier", +/* pickup */ "Bandolier", +/* width */ 2, + 60, + NULL, + 0, + 0, + NULL, + 0, +/* precache */ "" + }, + +/*QUAKED item_pack (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "item_pack", + Pickup_Pack, + NULL, + NULL, + NULL, + "items/pkup.wav", + "models/items/pack/tris.md2", EF_ROTATE, + NULL, +/* icon */ "i_pack", +/* pickup */ "Ammo Pack", +/* width */ 2, + 180, + NULL, + 0, + 0, + NULL, + 0, +/* precache */ "" + }, + + // + // KEYS + // +/*QUAKED key_data_cd (0 .5 .8) (-16 -16 -16) (16 16 16) +key for computer centers +*/ + { + "key_data_cd", + Pickup_Key, + NULL, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/keys/data_cd/tris.md2", EF_ROTATE, + NULL, + "k_datacd", + "Data CD", + 2, + 0, + NULL, + IT_STAY_COOP|IT_KEY, + 0, + NULL, + 0, +/* precache */ "" + }, + +/*QUAKED key_power_cube (0 .5 .8) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN NO_TOUCH +warehouse circuits +*/ + { + "key_power_cube", + Pickup_Key, + NULL, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/keys/power/tris.md2", EF_ROTATE, + NULL, + "k_powercube", + "Power Cube", + 2, + 0, + NULL, + IT_STAY_COOP|IT_KEY, + 0, + NULL, + 0, +/* precache */ "" + }, + +/*QUAKED key_pyramid (0 .5 .8) (-16 -16 -16) (16 16 16) +key for the entrance of jail3 +*/ + { + "key_pyramid", + Pickup_Key, + NULL, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/keys/pyramid/tris.md2", EF_ROTATE, + NULL, + "k_pyramid", + "Pyramid Key", + 2, + 0, + NULL, + IT_STAY_COOP|IT_KEY, + 0, + NULL, + 0, +/* precache */ "" + }, + +/*QUAKED key_data_spinner (0 .5 .8) (-16 -16 -16) (16 16 16) +key for the city computer +*/ + { + "key_data_spinner", + Pickup_Key, + NULL, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/keys/spinner/tris.md2", EF_ROTATE, + NULL, + "k_dataspin", + "Data Spinner", + 2, + 0, + NULL, + IT_STAY_COOP|IT_KEY, + 0, + NULL, + 0, +/* precache */ "" + }, + +/*QUAKED key_pass (0 .5 .8) (-16 -16 -16) (16 16 16) +security pass for the security level +*/ + { + "key_pass", + Pickup_Key, + NULL, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/keys/pass/tris.md2", EF_ROTATE, + NULL, + "k_security", + "Security Pass", + 2, + 0, + NULL, + IT_STAY_COOP|IT_KEY, + 0, + NULL, + 0, +/* precache */ "" + }, + +/*QUAKED key_blue_key (0 .5 .8) (-16 -16 -16) (16 16 16) +normal door key - blue +*/ + { + "key_blue_key", + Pickup_Key, + NULL, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/keys/key/tris.md2", EF_ROTATE, + NULL, + "k_bluekey", + "Blue Key", + 2, + 0, + NULL, + IT_STAY_COOP|IT_KEY, + 0, + NULL, + 0, +/* precache */ "" + }, + +/*QUAKED key_red_key (0 .5 .8) (-16 -16 -16) (16 16 16) +normal door key - red +*/ + { + "key_red_key", + Pickup_Key, + NULL, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/keys/red_key/tris.md2", EF_ROTATE, + NULL, + "k_redkey", + "Red Key", + 2, + 0, + NULL, + IT_STAY_COOP|IT_KEY, + 0, + NULL, + 0, +/* precache */ "" + }, + +/*QUAKED key_commander_head (0 .5 .8) (-16 -16 -16) (16 16 16) +tank commander's head +*/ + { + "key_commander_head", + Pickup_Key, + NULL, + Drop_General, + NULL, + "items/pkup.wav", + "models/monsters/commandr/head/tris.md2", EF_GIB, + NULL, +/* icon */ "k_comhead", +/* pickup */ "Commander's Head", +/* width */ 2, + 0, + NULL, + IT_STAY_COOP|IT_KEY, + 0, + NULL, + 0, +/* precache */ "" + }, + +/*QUAKED key_airstrike_target (0 .5 .8) (-16 -16 -16) (16 16 16) +tank commander's head +*/ + { + "key_airstrike_target", + Pickup_Key, + NULL, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/keys/target/tris.md2", EF_ROTATE, + NULL, +/* icon */ "i_airstrike", +/* pickup */ "Airstrike Marker", +/* width */ 2, + 0, + NULL, + IT_STAY_COOP|IT_KEY, + 0, + NULL, + 0, +/* precache */ "" + }, + + { + NULL, + Pickup_Health, + NULL, + NULL, + NULL, + "items/pkup.wav", + NULL, 0, + NULL, +/* icon */ "i_health", +/* pickup */ "Health", +/* width */ 3, + 0, + NULL, + 0, + 0, + NULL, + 0, +/* precache */ "items/s_health.wav items/n_health.wav items/l_health.wav items/m_health.wav" + }, + + // end of list marker + {NULL} +}; + + +/*QUAKED item_health (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ +void SP_item_health (edict_t *self) +{ + if ( deathmatch->value && ((int)dmflags->value & DF_NO_HEALTH) ) + { + G_FreeEdict (self); + return; + } + + self->model = "models/items/healing/medium/tris.md2"; + self->count = 10; + SpawnItem (self, FindItem ("Health")); + gi.soundindex ("items/n_health.wav"); +} + +/*QUAKED item_health_small (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ +void SP_item_health_small (edict_t *self) +{ + if ( deathmatch->value && ((int)dmflags->value & DF_NO_HEALTH) ) + { + G_FreeEdict (self); + return; + } + + self->model = "models/items/healing/stimpack/tris.md2"; + self->count = 2; + SpawnItem (self, FindItem ("Health")); + self->style = HEALTH_IGNORE_MAX; + gi.soundindex ("items/s_health.wav"); +} + +/*QUAKED item_health_large (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ +void SP_item_health_large (edict_t *self) +{ + if ( deathmatch->value && ((int)dmflags->value & DF_NO_HEALTH) ) + { + G_FreeEdict (self); + return; + } + + self->model = "models/items/healing/large/tris.md2"; + self->count = 25; + SpawnItem (self, FindItem ("Health")); + gi.soundindex ("items/l_health.wav"); +} + +/*QUAKED item_health_mega (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ +void SP_item_health_mega (edict_t *self) +{ + if ( deathmatch->value && ((int)dmflags->value & DF_NO_HEALTH) ) + { + G_FreeEdict (self); + return; + } + + self->model = "models/items/mega_h/tris.md2"; + self->count = 100; + SpawnItem (self, FindItem ("Health")); + gi.soundindex ("items/m_health.wav"); + self->style = HEALTH_IGNORE_MAX|HEALTH_TIMED; +} + + +void InitItems (void) +{ + game.num_items = sizeof(itemlist)/sizeof(itemlist[0]) - 1; +} + + + +/* +=============== +SetItemNames + +Called by worldspawn +=============== +*/ +void SetItemNames (void) +{ + int i; + gitem_t *it; + + for (i=0 ; ipickup_name); + } + + jacket_armor_index = ITEM_INDEX(FindItem("Jacket Armor")); + combat_armor_index = ITEM_INDEX(FindItem("Combat Armor")); + body_armor_index = ITEM_INDEX(FindItem("Body Armor")); + power_screen_index = ITEM_INDEX(FindItem("Power Screen")); + power_shield_index = ITEM_INDEX(FindItem("Power Shield")); +} diff --git a/original/baseq2/g_local.h b/original/baseq2/g_local.h new file mode 100644 index 0000000..c5e9cc6 --- /dev/null +++ b/original/baseq2/g_local.h @@ -0,0 +1,1114 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +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. + +*/ +// g_local.h -- local definitions for game module + +#include "q_shared.h" + +// define GAME_INCLUDE so that game.h does not define the +// short, server-visible gclient_t and edict_t structures, +// because we define the full size ones in this file +#define GAME_INCLUDE +#include "game.h" + +// the "gameversion" client command will print this plus compile date +#define GAMEVERSION "baseq2" + +// protocol bytes that can be directly added to messages +#define svc_muzzleflash 1 +#define svc_muzzleflash2 2 +#define svc_temp_entity 3 +#define svc_layout 4 +#define svc_inventory 5 +#define svc_stufftext 11 + +//================================================================== + +// view pitching times +#define DAMAGE_TIME 0.5 +#define FALL_TIME 0.3 + + +// edict->spawnflags +// these are set with checkboxes on each entity in the map editor +#define SPAWNFLAG_NOT_EASY 0x00000100 +#define SPAWNFLAG_NOT_MEDIUM 0x00000200 +#define SPAWNFLAG_NOT_HARD 0x00000400 +#define SPAWNFLAG_NOT_DEATHMATCH 0x00000800 +#define SPAWNFLAG_NOT_COOP 0x00001000 + +// edict->flags +#define FL_FLY 0x00000001 +#define FL_SWIM 0x00000002 // implied immunity to drowining +#define FL_IMMUNE_LASER 0x00000004 +#define FL_INWATER 0x00000008 +#define FL_GODMODE 0x00000010 +#define FL_NOTARGET 0x00000020 +#define FL_IMMUNE_SLIME 0x00000040 +#define FL_IMMUNE_LAVA 0x00000080 +#define FL_PARTIALGROUND 0x00000100 // not all corners are valid +#define FL_WATERJUMP 0x00000200 // player jumping out of water +#define FL_TEAMSLAVE 0x00000400 // not the first on the team +#define FL_NO_KNOCKBACK 0x00000800 +#define FL_POWER_ARMOR 0x00001000 // power armor (if any) is active +#define FL_RESPAWN 0x80000000 // used for item respawning + + +#define FRAMETIME 0.1 + +// memory tags to allow dynamic memory to be cleaned up +#define TAG_GAME 765 // clear when unloading the dll +#define TAG_LEVEL 766 // clear when loading a new level + + +#define MELEE_DISTANCE 80 + +#define BODY_QUEUE_SIZE 8 + +typedef enum +{ + DAMAGE_NO, + DAMAGE_YES, // will take damage if hit + DAMAGE_AIM // auto targeting recognizes this +} damage_t; + +typedef enum +{ + WEAPON_READY, + WEAPON_ACTIVATING, + WEAPON_DROPPING, + WEAPON_FIRING +} weaponstate_t; + +typedef enum +{ + AMMO_BULLETS, + AMMO_SHELLS, + AMMO_ROCKETS, + AMMO_GRENADES, + AMMO_CELLS, + AMMO_SLUGS +} ammo_t; + + +//deadflag +#define DEAD_NO 0 +#define DEAD_DYING 1 +#define DEAD_DEAD 2 +#define DEAD_RESPAWNABLE 3 + +//range +#define RANGE_MELEE 0 +#define RANGE_NEAR 1 +#define RANGE_MID 2 +#define RANGE_FAR 3 + +//gib types +#define GIB_ORGANIC 0 +#define GIB_METALLIC 1 + +//monster ai flags +#define AI_STAND_GROUND 0x00000001 +#define AI_TEMP_STAND_GROUND 0x00000002 +#define AI_SOUND_TARGET 0x00000004 +#define AI_LOST_SIGHT 0x00000008 +#define AI_PURSUIT_LAST_SEEN 0x00000010 +#define AI_PURSUE_NEXT 0x00000020 +#define AI_PURSUE_TEMP 0x00000040 +#define AI_HOLD_FRAME 0x00000080 +#define AI_GOOD_GUY 0x00000100 +#define AI_BRUTAL 0x00000200 +#define AI_NOSTEP 0x00000400 +#define AI_DUCKED 0x00000800 +#define AI_COMBAT_POINT 0x00001000 +#define AI_MEDIC 0x00002000 +#define AI_RESURRECTING 0x00004000 + +//monster attack state +#define AS_STRAIGHT 1 +#define AS_SLIDING 2 +#define AS_MELEE 3 +#define AS_MISSILE 4 + +// armor types +#define ARMOR_NONE 0 +#define ARMOR_JACKET 1 +#define ARMOR_COMBAT 2 +#define ARMOR_BODY 3 +#define ARMOR_SHARD 4 + +// power armor types +#define POWER_ARMOR_NONE 0 +#define POWER_ARMOR_SCREEN 1 +#define POWER_ARMOR_SHIELD 2 + +// handedness values +#define RIGHT_HANDED 0 +#define LEFT_HANDED 1 +#define CENTER_HANDED 2 + + +// game.serverflags values +#define SFL_CROSS_TRIGGER_1 0x00000001 +#define SFL_CROSS_TRIGGER_2 0x00000002 +#define SFL_CROSS_TRIGGER_3 0x00000004 +#define SFL_CROSS_TRIGGER_4 0x00000008 +#define SFL_CROSS_TRIGGER_5 0x00000010 +#define SFL_CROSS_TRIGGER_6 0x00000020 +#define SFL_CROSS_TRIGGER_7 0x00000040 +#define SFL_CROSS_TRIGGER_8 0x00000080 +#define SFL_CROSS_TRIGGER_MASK 0x000000ff + + +// noise types for PlayerNoise +#define PNOISE_SELF 0 +#define PNOISE_WEAPON 1 +#define PNOISE_IMPACT 2 + + +// edict->movetype values +typedef enum +{ +MOVETYPE_NONE, // never moves +MOVETYPE_NOCLIP, // origin and angles change with no interaction +MOVETYPE_PUSH, // no clip to world, push on box contact +MOVETYPE_STOP, // no clip to world, stops on box contact + +MOVETYPE_WALK, // gravity +MOVETYPE_STEP, // gravity, special edge handling +MOVETYPE_FLY, +MOVETYPE_TOSS, // gravity +MOVETYPE_FLYMISSILE, // extra size to monsters +MOVETYPE_BOUNCE +} movetype_t; + + + +typedef struct +{ + int base_count; + int max_count; + float normal_protection; + float energy_protection; + int armor; +} gitem_armor_t; + + +// gitem_t->flags +#define IT_WEAPON 1 // use makes active weapon +#define IT_AMMO 2 +#define IT_ARMOR 4 +#define IT_STAY_COOP 8 +#define IT_KEY 16 +#define IT_POWERUP 32 + +// gitem_t->weapmodel for weapons indicates model index +#define WEAP_BLASTER 1 +#define WEAP_SHOTGUN 2 +#define WEAP_SUPERSHOTGUN 3 +#define WEAP_MACHINEGUN 4 +#define WEAP_CHAINGUN 5 +#define WEAP_GRENADES 6 +#define WEAP_GRENADELAUNCHER 7 +#define WEAP_ROCKETLAUNCHER 8 +#define WEAP_HYPERBLASTER 9 +#define WEAP_RAILGUN 10 +#define WEAP_BFG 11 + +typedef struct gitem_s +{ + char *classname; // spawning name + qboolean (*pickup)(struct edict_s *ent, struct edict_s *other); + void (*use)(struct edict_s *ent, struct gitem_s *item); + void (*drop)(struct edict_s *ent, struct gitem_s *item); + void (*weaponthink)(struct edict_s *ent); + char *pickup_sound; + char *world_model; + int world_model_flags; + char *view_model; + + // client side info + char *icon; + char *pickup_name; // for printing on pickup + int count_width; // number of digits to display by icon + + int quantity; // for ammo how much, for weapons how much is used per shot + char *ammo; // for weapons + int flags; // IT_* flags + + int weapmodel; // weapon model index (for weapons) + + void *info; + int tag; + + char *precaches; // string of all models, sounds, and images this item will use +} gitem_t; + + + +// +// this structure is left intact through an entire game +// it should be initialized at dll load time, and read/written to +// the server.ssv file for savegames +// +typedef struct +{ + char helpmessage1[512]; + char helpmessage2[512]; + int helpchanged; // flash F1 icon if non 0, play sound + // and increment only if 1, 2, or 3 + + gclient_t *clients; // [maxclients] + + // can't store spawnpoint in level, because + // it would get overwritten by the savegame restore + char spawnpoint[512]; // needed for coop respawns + + // store latched cvars here that we want to get at often + int maxclients; + int maxentities; + + // cross level triggers + int serverflags; + + // items + int num_items; + + qboolean autosaved; +} game_locals_t; + + +// +// this structure is cleared as each map is entered +// it is read/written to the level.sav file for savegames +// +typedef struct +{ + int framenum; + float time; + + char level_name[MAX_QPATH]; // the descriptive name (Outer Base, etc) + char mapname[MAX_QPATH]; // the server name (base1, etc) + char nextmap[MAX_QPATH]; // go here when fraglimit is hit + + // intermission state + float intermissiontime; // time the intermission was started + char *changemap; + int exitintermission; + vec3_t intermission_origin; + vec3_t intermission_angle; + + edict_t *sight_client; // changed once each frame for coop games + + edict_t *sight_entity; + int sight_entity_framenum; + edict_t *sound_entity; + int sound_entity_framenum; + edict_t *sound2_entity; + int sound2_entity_framenum; + + int pic_health; + + int total_secrets; + int found_secrets; + + int total_goals; + int found_goals; + + int total_monsters; + int killed_monsters; + + edict_t *current_entity; // entity running from G_RunFrame + int body_que; // dead bodies + + int power_cubes; // ugly necessity for coop +} level_locals_t; + + +// spawn_temp_t is only used to hold entity field values that +// can be set from the editor, but aren't actualy present +// in edict_t during gameplay +typedef struct +{ + // world vars + char *sky; + float skyrotate; + vec3_t skyaxis; + char *nextmap; + + int lip; + int distance; + int height; + char *noise; + float pausetime; + char *item; + char *gravity; + + float minyaw; + float maxyaw; + float minpitch; + float maxpitch; +} spawn_temp_t; + + +typedef struct +{ + // fixed data + vec3_t start_origin; + vec3_t start_angles; + vec3_t end_origin; + vec3_t end_angles; + + int sound_start; + int sound_middle; + int sound_end; + + float accel; + float speed; + float decel; + float distance; + + float wait; + + // state data + int state; + vec3_t dir; + float current_speed; + float move_speed; + float next_speed; + float remaining_distance; + float decel_distance; + void (*endfunc)(edict_t *); +} moveinfo_t; + + +typedef struct +{ + void (*aifunc)(edict_t *self, float dist); + float dist; + void (*thinkfunc)(edict_t *self); +} mframe_t; + +typedef struct +{ + int firstframe; + int lastframe; + mframe_t *frame; + void (*endfunc)(edict_t *self); +} mmove_t; + +typedef struct +{ + mmove_t *currentmove; + int aiflags; + int nextframe; + float scale; + + void (*stand)(edict_t *self); + void (*idle)(edict_t *self); + void (*search)(edict_t *self); + void (*walk)(edict_t *self); + void (*run)(edict_t *self); + void (*dodge)(edict_t *self, edict_t *other, float eta); + void (*attack)(edict_t *self); + void (*melee)(edict_t *self); + void (*sight)(edict_t *self, edict_t *other); + qboolean (*checkattack)(edict_t *self); + + float pausetime; + float attack_finished; + + vec3_t saved_goal; + float search_time; + float trail_time; + vec3_t last_sighting; + int attack_state; + int lefty; + float idle_time; + int linkcount; + + int power_armor_type; + int power_armor_power; +} monsterinfo_t; + + + +extern game_locals_t game; +extern level_locals_t level; +extern game_import_t gi; +extern game_export_t globals; +extern spawn_temp_t st; + +extern int sm_meat_index; +extern int snd_fry; + +extern int jacket_armor_index; +extern int combat_armor_index; +extern int body_armor_index; + + +// means of death +#define MOD_UNKNOWN 0 +#define MOD_BLASTER 1 +#define MOD_SHOTGUN 2 +#define MOD_SSHOTGUN 3 +#define MOD_MACHINEGUN 4 +#define MOD_CHAINGUN 5 +#define MOD_GRENADE 6 +#define MOD_G_SPLASH 7 +#define MOD_ROCKET 8 +#define MOD_R_SPLASH 9 +#define MOD_HYPERBLASTER 10 +#define MOD_RAILGUN 11 +#define MOD_BFG_LASER 12 +#define MOD_BFG_BLAST 13 +#define MOD_BFG_EFFECT 14 +#define MOD_HANDGRENADE 15 +#define MOD_HG_SPLASH 16 +#define MOD_WATER 17 +#define MOD_SLIME 18 +#define MOD_LAVA 19 +#define MOD_CRUSH 20 +#define MOD_TELEFRAG 21 +#define MOD_FALLING 22 +#define MOD_SUICIDE 23 +#define MOD_HELD_GRENADE 24 +#define MOD_EXPLOSIVE 25 +#define MOD_BARREL 26 +#define MOD_BOMB 27 +#define MOD_EXIT 28 +#define MOD_SPLASH 29 +#define MOD_TARGET_LASER 30 +#define MOD_TRIGGER_HURT 31 +#define MOD_HIT 32 +#define MOD_TARGET_BLASTER 33 +#define MOD_FRIENDLY_FIRE 0x8000000 + +extern int meansOfDeath; + + +extern edict_t *g_edicts; + +#define FOFS(x) (int)&(((edict_t *)0)->x) +#define STOFS(x) (int)&(((spawn_temp_t *)0)->x) +#define LLOFS(x) (int)&(((level_locals_t *)0)->x) +#define CLOFS(x) (int)&(((gclient_t *)0)->x) + +#define random() ((rand () & 0x7fff) / ((float)0x7fff)) +#define crandom() (2.0 * (random() - 0.5)) + +extern cvar_t *maxentities; +extern cvar_t *deathmatch; +extern cvar_t *coop; +extern cvar_t *dmflags; +extern cvar_t *skill; +extern cvar_t *fraglimit; +extern cvar_t *timelimit; +extern cvar_t *password; +extern cvar_t *spectator_password; +extern cvar_t *needpass; +extern cvar_t *g_select_empty; +extern cvar_t *dedicated; + +extern cvar_t *filterban; + +extern cvar_t *sv_gravity; +extern cvar_t *sv_maxvelocity; + +extern cvar_t *gun_x, *gun_y, *gun_z; +extern cvar_t *sv_rollspeed; +extern cvar_t *sv_rollangle; + +extern cvar_t *run_pitch; +extern cvar_t *run_roll; +extern cvar_t *bob_up; +extern cvar_t *bob_pitch; +extern cvar_t *bob_roll; + +extern cvar_t *sv_cheats; +extern cvar_t *maxclients; +extern cvar_t *maxspectators; + +extern cvar_t *flood_msgs; +extern cvar_t *flood_persecond; +extern cvar_t *flood_waitdelay; + +extern cvar_t *sv_maplist; + +#define world (&g_edicts[0]) + +// item spawnflags +#define ITEM_TRIGGER_SPAWN 0x00000001 +#define ITEM_NO_TOUCH 0x00000002 +// 6 bits reserved for editor flags +// 8 bits used as power cube id bits for coop games +#define DROPPED_ITEM 0x00010000 +#define DROPPED_PLAYER_ITEM 0x00020000 +#define ITEM_TARGETS_USED 0x00040000 + +// +// fields are needed for spawning from the entity string +// and saving / loading games +// +#define FFL_SPAWNTEMP 1 +#define FFL_NOSPAWN 2 + +typedef enum { + F_INT, + F_FLOAT, + F_LSTRING, // string on disk, pointer in memory, TAG_LEVEL + F_GSTRING, // string on disk, pointer in memory, TAG_GAME + F_VECTOR, + F_ANGLEHACK, + F_EDICT, // index on disk, pointer in memory + F_ITEM, // index on disk, pointer in memory + F_CLIENT, // index on disk, pointer in memory + F_FUNCTION, + F_MMOVE, + F_IGNORE +} fieldtype_t; + +typedef struct +{ + char *name; + int ofs; + fieldtype_t type; + int flags; +} field_t; + + +extern field_t fields[]; +extern gitem_t itemlist[]; + + +// +// g_cmds.c +// +void Cmd_Help_f (edict_t *ent); +void Cmd_Score_f (edict_t *ent); + +// +// g_items.c +// +void PrecacheItem (gitem_t *it); +void InitItems (void); +void SetItemNames (void); +gitem_t *FindItem (char *pickup_name); +gitem_t *FindItemByClassname (char *classname); +#define ITEM_INDEX(x) ((x)-itemlist) +edict_t *Drop_Item (edict_t *ent, gitem_t *item); +void SetRespawn (edict_t *ent, float delay); +void ChangeWeapon (edict_t *ent); +void SpawnItem (edict_t *ent, gitem_t *item); +void Think_Weapon (edict_t *ent); +int ArmorIndex (edict_t *ent); +int PowerArmorType (edict_t *ent); +gitem_t *GetItemByIndex (int index); +qboolean Add_Ammo (edict_t *ent, gitem_t *item, int count); +void Touch_Item (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf); + +// +// g_utils.c +// +qboolean KillBox (edict_t *ent); +void G_ProjectSource (vec3_t point, vec3_t distance, vec3_t forward, vec3_t right, vec3_t result); +edict_t *G_Find (edict_t *from, int fieldofs, char *match); +edict_t *findradius (edict_t *from, vec3_t org, float rad); +edict_t *G_PickTarget (char *targetname); +void G_UseTargets (edict_t *ent, edict_t *activator); +void G_SetMovedir (vec3_t angles, vec3_t movedir); + +void G_InitEdict (edict_t *e); +edict_t *G_Spawn (void); +void G_FreeEdict (edict_t *e); + +void G_TouchTriggers (edict_t *ent); +void G_TouchSolids (edict_t *ent); + +char *G_CopyString (char *in); + +float *tv (float x, float y, float z); +char *vtos (vec3_t v); + +float vectoyaw (vec3_t vec); +void vectoangles (vec3_t vec, vec3_t angles); + +// +// g_combat.c +// +qboolean OnSameTeam (edict_t *ent1, edict_t *ent2); +qboolean CanDamage (edict_t *targ, edict_t *inflictor); +void T_Damage (edict_t *targ, edict_t *inflictor, edict_t *attacker, vec3_t dir, vec3_t point, vec3_t normal, int damage, int knockback, int dflags, int mod); +void T_RadiusDamage (edict_t *inflictor, edict_t *attacker, float damage, edict_t *ignore, float radius, int mod); + +// damage flags +#define DAMAGE_RADIUS 0x00000001 // damage was indirect +#define DAMAGE_NO_ARMOR 0x00000002 // armour does not protect from this damage +#define DAMAGE_ENERGY 0x00000004 // damage is from an energy based weapon +#define DAMAGE_NO_KNOCKBACK 0x00000008 // do not affect velocity, just view angles +#define DAMAGE_BULLET 0x00000010 // damage is from a bullet (used for ricochets) +#define DAMAGE_NO_PROTECTION 0x00000020 // armor, shields, invulnerability, and godmode have no effect + +#define DEFAULT_BULLET_HSPREAD 300 +#define DEFAULT_BULLET_VSPREAD 500 +#define DEFAULT_SHOTGUN_HSPREAD 1000 +#define DEFAULT_SHOTGUN_VSPREAD 500 +#define DEFAULT_DEATHMATCH_SHOTGUN_COUNT 12 +#define DEFAULT_SHOTGUN_COUNT 12 +#define DEFAULT_SSHOTGUN_COUNT 20 + +// +// g_monster.c +// +void monster_fire_bullet (edict_t *self, vec3_t start, vec3_t dir, int damage, int kick, int hspread, int vspread, int flashtype); +void monster_fire_shotgun (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int hspread, int vspread, int count, int flashtype); +void monster_fire_blaster (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int flashtype, int effect); +void monster_fire_grenade (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, int flashtype); +void monster_fire_rocket (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int flashtype); +void monster_fire_railgun (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int flashtype); +void monster_fire_bfg (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, int kick, float damage_radius, int flashtype); +void M_droptofloor (edict_t *ent); +void monster_think (edict_t *self); +void walkmonster_start (edict_t *self); +void swimmonster_start (edict_t *self); +void flymonster_start (edict_t *self); +void AttackFinished (edict_t *self, float time); +void monster_death_use (edict_t *self); +void M_CatagorizePosition (edict_t *ent); +qboolean M_CheckAttack (edict_t *self); +void M_FlyCheck (edict_t *self); +void M_CheckGround (edict_t *ent); + +// +// g_misc.c +// +void ThrowHead (edict_t *self, char *gibname, int damage, int type); +void ThrowClientHead (edict_t *self, int damage); +void ThrowGib (edict_t *self, char *gibname, int damage, int type); +void BecomeExplosion1(edict_t *self); + +// +// g_ai.c +// +void AI_SetSightClient (void); + +void ai_stand (edict_t *self, float dist); +void ai_move (edict_t *self, float dist); +void ai_walk (edict_t *self, float dist); +void ai_turn (edict_t *self, float dist); +void ai_run (edict_t *self, float dist); +void ai_charge (edict_t *self, float dist); +int range (edict_t *self, edict_t *other); + +void FoundTarget (edict_t *self); +qboolean infront (edict_t *self, edict_t *other); +qboolean visible (edict_t *self, edict_t *other); +qboolean FacingIdeal(edict_t *self); + +// +// g_weapon.c +// +void ThrowDebris (edict_t *self, char *modelname, float speed, vec3_t origin); +qboolean fire_hit (edict_t *self, vec3_t aim, int damage, int kick); +void fire_bullet (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int hspread, int vspread, int mod); +void fire_shotgun (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int hspread, int vspread, int count, int mod); +void fire_blaster (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, int effect, qboolean hyper); +void fire_grenade (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, float timer, float damage_radius); +void fire_grenade2 (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, float timer, float damage_radius, qboolean held); +void fire_rocket (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, float damage_radius, int radius_damage); +void fire_rail (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick); +void fire_bfg (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, float damage_radius); + +// +// g_ptrail.c +// +void PlayerTrail_Init (void); +void PlayerTrail_Add (vec3_t spot); +void PlayerTrail_New (vec3_t spot); +edict_t *PlayerTrail_PickFirst (edict_t *self); +edict_t *PlayerTrail_PickNext (edict_t *self); +edict_t *PlayerTrail_LastSpot (void); + +// +// g_client.c +// +void respawn (edict_t *ent); +void BeginIntermission (edict_t *targ); +void PutClientInServer (edict_t *ent); +void InitClientPersistant (gclient_t *client); +void InitClientResp (gclient_t *client); +void InitBodyQue (void); +void ClientBeginServerFrame (edict_t *ent); + +// +// g_player.c +// +void player_pain (edict_t *self, edict_t *other, float kick, int damage); +void player_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point); + +// +// g_svcmds.c +// +void ServerCommand (void); +qboolean SV_FilterPacket (char *from); + +// +// p_view.c +// +void ClientEndServerFrame (edict_t *ent); + +// +// p_hud.c +// +void MoveClientToIntermission (edict_t *client); +void G_SetStats (edict_t *ent); +void G_SetSpectatorStats (edict_t *ent); +void G_CheckChaseStats (edict_t *ent); +void ValidateSelectedItem (edict_t *ent); +void DeathmatchScoreboardMessage (edict_t *client, edict_t *killer); + +// +// g_pweapon.c +// +void PlayerNoise(edict_t *who, vec3_t where, int type); + +// +// m_move.c +// +qboolean M_CheckBottom (edict_t *ent); +qboolean M_walkmove (edict_t *ent, float yaw, float dist); +void M_MoveToGoal (edict_t *ent, float dist); +void M_ChangeYaw (edict_t *ent); + +// +// g_phys.c +// +void G_RunEntity (edict_t *ent); + +// +// g_main.c +// +void SaveClientData (void); +void FetchClientEntData (edict_t *ent); + +// +// g_chase.c +// +void UpdateChaseCam(edict_t *ent); +void ChaseNext(edict_t *ent); +void ChasePrev(edict_t *ent); +void GetChaseTarget(edict_t *ent); + +//============================================================================ + +// client_t->anim_priority +#define ANIM_BASIC 0 // stand / run +#define ANIM_WAVE 1 +#define ANIM_JUMP 2 +#define ANIM_PAIN 3 +#define ANIM_ATTACK 4 +#define ANIM_DEATH 5 +#define ANIM_REVERSE 6 + + +// client data that stays across multiple level loads +typedef struct +{ + char userinfo[MAX_INFO_STRING]; + char netname[16]; + int hand; + + qboolean connected; // a loadgame will leave valid entities that + // just don't have a connection yet + + // values saved and restored from edicts when changing levels + int health; + int max_health; + int savedFlags; + + int selected_item; + int inventory[MAX_ITEMS]; + + // ammo capacities + int max_bullets; + int max_shells; + int max_rockets; + int max_grenades; + int max_cells; + int max_slugs; + + gitem_t *weapon; + gitem_t *lastweapon; + + int power_cubes; // used for tracking the cubes in coop games + int score; // for calculating total unit score in coop games + + int game_helpchanged; + int helpchanged; + + qboolean spectator; // client is a spectator +} client_persistant_t; + +// client data that stays across deathmatch respawns +typedef struct +{ + client_persistant_t coop_respawn; // what to set client->pers to on a respawn + int enterframe; // level.framenum the client entered the game + int score; // frags, etc + vec3_t cmd_angles; // angles sent over in the last command + + qboolean spectator; // client is a spectator +} client_respawn_t; + +// this structure is cleared on each PutClientInServer(), +// except for 'client->pers' +struct gclient_s +{ + // known to server + player_state_t ps; // communicated by server to clients + int ping; + + // private to game + client_persistant_t pers; + client_respawn_t resp; + pmove_state_t old_pmove; // for detecting out-of-pmove changes + + qboolean showscores; // set layout stat + qboolean showinventory; // set layout stat + qboolean showhelp; + qboolean showhelpicon; + + int ammo_index; + + int buttons; + int oldbuttons; + int latched_buttons; + + qboolean weapon_thunk; + + gitem_t *newweapon; + + // sum up damage over an entire frame, so + // shotgun blasts give a single big kick + int damage_armor; // damage absorbed by armor + int damage_parmor; // damage absorbed by power armor + int damage_blood; // damage taken out of health + int damage_knockback; // impact damage + vec3_t damage_from; // origin for vector calculation + + float killer_yaw; // when dead, look at killer + + weaponstate_t weaponstate; + vec3_t kick_angles; // weapon kicks + vec3_t kick_origin; + float v_dmg_roll, v_dmg_pitch, v_dmg_time; // damage kicks + float fall_time, fall_value; // for view drop on fall + float damage_alpha; + float bonus_alpha; + vec3_t damage_blend; + vec3_t v_angle; // aiming direction + float bobtime; // so off-ground doesn't change it + vec3_t oldviewangles; + vec3_t oldvelocity; + + float next_drown_time; + int old_waterlevel; + int breather_sound; + + int machinegun_shots; // for weapon raising + + // animation vars + int anim_end; + int anim_priority; + qboolean anim_duck; + qboolean anim_run; + + // powerup timers + float quad_framenum; + float invincible_framenum; + float breather_framenum; + float enviro_framenum; + + qboolean grenade_blew_up; + float grenade_time; + int silencer_shots; + int weapon_sound; + + float pickup_msg_time; + + float flood_locktill; // locked from talking + float flood_when[10]; // when messages were said + int flood_whenhead; // head pointer for when said + + float respawn_time; // can respawn when time > this + + edict_t *chase_target; // player we are chasing + qboolean update_chase; // need to update chase info? +}; + + +struct edict_s +{ + entity_state_t s; + struct gclient_s *client; // NULL if not a player + // the server expects the first part + // of gclient_s to be a player_state_t + // but the rest of it is opaque + + qboolean inuse; + int linkcount; + + // FIXME: move these fields to a server private sv_entity_t + link_t area; // linked to a division node or leaf + + int num_clusters; // if -1, use headnode instead + int clusternums[MAX_ENT_CLUSTERS]; + int headnode; // unused if num_clusters != -1 + int areanum, areanum2; + + //================================ + + int svflags; + vec3_t mins, maxs; + vec3_t absmin, absmax, size; + solid_t solid; + int clipmask; + edict_t *owner; + + + // DO NOT MODIFY ANYTHING ABOVE THIS, THE SERVER + // EXPECTS THE FIELDS IN THAT ORDER! + + //================================ + int movetype; + int flags; + + char *model; + float freetime; // sv.time when the object was freed + + // + // only used locally in game, not by server + // + char *message; + char *classname; + int spawnflags; + + float timestamp; + + float angle; // set in qe3, -1 = up, -2 = down + char *target; + char *targetname; + char *killtarget; + char *team; + char *pathtarget; + char *deathtarget; + char *combattarget; + edict_t *target_ent; + + float speed, accel, decel; + vec3_t movedir; + vec3_t pos1, pos2; + + vec3_t velocity; + vec3_t avelocity; + int mass; + float air_finished; + float gravity; // per entity gravity multiplier (1.0 is normal) + // use for lowgrav artifact, flares + + edict_t *goalentity; + edict_t *movetarget; + float yaw_speed; + float ideal_yaw; + + float nextthink; + void (*prethink) (edict_t *ent); + void (*think)(edict_t *self); + void (*blocked)(edict_t *self, edict_t *other); //move to moveinfo? + void (*touch)(edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf); + void (*use)(edict_t *self, edict_t *other, edict_t *activator); + void (*pain)(edict_t *self, edict_t *other, float kick, int damage); + void (*die)(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point); + + float touch_debounce_time; // are all these legit? do we need more/less of them? + float pain_debounce_time; + float damage_debounce_time; + float fly_sound_debounce_time; //move to clientinfo + float last_move_time; + + int health; + int max_health; + int gib_health; + int deadflag; + qboolean show_hostile; + + float powerarmor_time; + + char *map; // target_changelevel + + int viewheight; // height above origin where eyesight is determined + int takedamage; + int dmg; + int radius_dmg; + float dmg_radius; + int sounds; //make this a spawntemp var? + int count; + + edict_t *chain; + edict_t *enemy; + edict_t *oldenemy; + edict_t *activator; + edict_t *groundentity; + int groundentity_linkcount; + edict_t *teamchain; + edict_t *teammaster; + + edict_t *mynoise; // can go in client only + edict_t *mynoise2; + + int noise_index; + int noise_index2; + float volume; + float attenuation; + + // timing variables + float wait; + float delay; // before firing targets + float random; + + float teleport_time; + + int watertype; + int waterlevel; + + vec3_t move_origin; + vec3_t move_angles; + + // move this to clientinfo? + int light_level; + + int style; // also used as areaportal number + + gitem_t *item; // for bonus items + + // common data blocks + moveinfo_t moveinfo; + monsterinfo_t monsterinfo; +}; + diff --git a/original/baseq2/g_main.c b/original/baseq2/g_main.c new file mode 100644 index 0000000..b6985e7 --- /dev/null +++ b/original/baseq2/g_main.c @@ -0,0 +1,442 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +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. + +*/ + +#include "g_local.h" + +game_locals_t game; +level_locals_t level; +game_import_t gi; +game_export_t globals; +spawn_temp_t st; + +int sm_meat_index; +int snd_fry; +int meansOfDeath; + +edict_t *g_edicts; + +cvar_t *deathmatch; +cvar_t *coop; +cvar_t *dmflags; +cvar_t *skill; +cvar_t *fraglimit; +cvar_t *timelimit; +cvar_t *password; +cvar_t *spectator_password; +cvar_t *needpass; +cvar_t *maxclients; +cvar_t *maxspectators; +cvar_t *maxentities; +cvar_t *g_select_empty; +cvar_t *dedicated; + +cvar_t *filterban; + +cvar_t *sv_maxvelocity; +cvar_t *sv_gravity; + +cvar_t *sv_rollspeed; +cvar_t *sv_rollangle; +cvar_t *gun_x; +cvar_t *gun_y; +cvar_t *gun_z; + +cvar_t *run_pitch; +cvar_t *run_roll; +cvar_t *bob_up; +cvar_t *bob_pitch; +cvar_t *bob_roll; + +cvar_t *sv_cheats; + +cvar_t *flood_msgs; +cvar_t *flood_persecond; +cvar_t *flood_waitdelay; + +cvar_t *sv_maplist; + +void SpawnEntities (char *mapname, char *entities, char *spawnpoint); +void ClientThink (edict_t *ent, usercmd_t *cmd); +qboolean ClientConnect (edict_t *ent, char *userinfo); +void ClientUserinfoChanged (edict_t *ent, char *userinfo); +void ClientDisconnect (edict_t *ent); +void ClientBegin (edict_t *ent); +void ClientCommand (edict_t *ent); +void RunEntity (edict_t *ent); +void WriteGame (char *filename, qboolean autosave); +void ReadGame (char *filename); +void WriteLevel (char *filename); +void ReadLevel (char *filename); +void InitGame (void); +void G_RunFrame (void); + + +//=================================================================== + + +void ShutdownGame (void) +{ + gi.dprintf ("==== ShutdownGame ====\n"); + + gi.FreeTags (TAG_LEVEL); + gi.FreeTags (TAG_GAME); +} + + +/* +================= +GetGameAPI + +Returns a pointer to the structure with all entry points +and global variables +================= +*/ +game_export_t *GetGameAPI (game_import_t *import) +{ + gi = *import; + + globals.apiversion = GAME_API_VERSION; + globals.Init = InitGame; + globals.Shutdown = ShutdownGame; + globals.SpawnEntities = SpawnEntities; + + globals.WriteGame = WriteGame; + globals.ReadGame = ReadGame; + globals.WriteLevel = WriteLevel; + globals.ReadLevel = ReadLevel; + + globals.ClientThink = ClientThink; + globals.ClientConnect = ClientConnect; + globals.ClientUserinfoChanged = ClientUserinfoChanged; + globals.ClientDisconnect = ClientDisconnect; + globals.ClientBegin = ClientBegin; + globals.ClientCommand = ClientCommand; + + globals.RunFrame = G_RunFrame; + + globals.ServerCommand = ServerCommand; + + globals.edict_size = sizeof(edict_t); + + return &globals; +} + +#ifndef GAME_HARD_LINKED +// this is only here so the functions in q_shared.c and q_shwin.c can link +void Sys_Error (char *error, ...) +{ + va_list argptr; + char text[1024]; + + va_start (argptr, error); + vsprintf (text, error, argptr); + va_end (argptr); + + gi.error (ERR_FATAL, "%s", text); +} + +void Com_Printf (char *msg, ...) +{ + va_list argptr; + char text[1024]; + + va_start (argptr, msg); + vsprintf (text, msg, argptr); + va_end (argptr); + + gi.dprintf ("%s", text); +} + +#endif + +//====================================================================== + + +/* +================= +ClientEndServerFrames +================= +*/ +void ClientEndServerFrames (void) +{ + int i; + edict_t *ent; + + // calc the player views now that all pushing + // and damage has been added + for (i=0 ; ivalue ; i++) + { + ent = g_edicts + 1 + i; + if (!ent->inuse || !ent->client) + continue; + ClientEndServerFrame (ent); + } + +} + +/* +================= +CreateTargetChangeLevel + +Returns the created target changelevel +================= +*/ +edict_t *CreateTargetChangeLevel(char *map) +{ + edict_t *ent; + + ent = G_Spawn (); + ent->classname = "target_changelevel"; + Com_sprintf(level.nextmap, sizeof(level.nextmap), "%s", map); + ent->map = level.nextmap; + return ent; +} + +/* +================= +EndDMLevel + +The timelimit or fraglimit has been exceeded +================= +*/ +void EndDMLevel (void) +{ + edict_t *ent; + char *s, *t, *f; + static const char *seps = " ,\n\r"; + + // stay on same level flag + if ((int)dmflags->value & DF_SAME_LEVEL) + { + BeginIntermission (CreateTargetChangeLevel (level.mapname) ); + return; + } + + // see if it's in the map list + if (*sv_maplist->string) { + s = strdup(sv_maplist->string); + f = NULL; + t = strtok(s, seps); + while (t != NULL) { + if (Q_stricmp(t, level.mapname) == 0) { + // it's in the list, go to the next one + t = strtok(NULL, seps); + if (t == NULL) { // end of list, go to first one + if (f == NULL) // there isn't a first one, same level + BeginIntermission (CreateTargetChangeLevel (level.mapname) ); + else + BeginIntermission (CreateTargetChangeLevel (f) ); + } else + BeginIntermission (CreateTargetChangeLevel (t) ); + free(s); + return; + } + if (!f) + f = t; + t = strtok(NULL, seps); + } + free(s); + } + + if (level.nextmap[0]) // go to a specific map + BeginIntermission (CreateTargetChangeLevel (level.nextmap) ); + else { // search for a changelevel + ent = G_Find (NULL, FOFS(classname), "target_changelevel"); + if (!ent) + { // the map designer didn't include a changelevel, + // so create a fake ent that goes back to the same level + BeginIntermission (CreateTargetChangeLevel (level.mapname) ); + return; + } + BeginIntermission (ent); + } +} + + +/* +================= +CheckNeedPass +================= +*/ +void CheckNeedPass (void) +{ + int need; + + // if password or spectator_password has changed, update needpass + // as needed + if (password->modified || spectator_password->modified) + { + password->modified = spectator_password->modified = false; + + need = 0; + + if (*password->string && Q_stricmp(password->string, "none")) + need |= 1; + if (*spectator_password->string && Q_stricmp(spectator_password->string, "none")) + need |= 2; + + gi.cvar_set("needpass", va("%d", need)); + } +} + +/* +================= +CheckDMRules +================= +*/ +void CheckDMRules (void) +{ + int i; + gclient_t *cl; + + if (level.intermissiontime) + return; + + if (!deathmatch->value) + return; + + if (timelimit->value) + { + if (level.time >= timelimit->value*60) + { + gi.bprintf (PRINT_HIGH, "Timelimit hit.\n"); + EndDMLevel (); + return; + } + } + + if (fraglimit->value) + { + for (i=0 ; ivalue ; i++) + { + cl = game.clients + i; + if (!g_edicts[i+1].inuse) + continue; + + if (cl->resp.score >= fraglimit->value) + { + gi.bprintf (PRINT_HIGH, "Fraglimit hit.\n"); + EndDMLevel (); + return; + } + } + } +} + + +/* +============= +ExitLevel +============= +*/ +void ExitLevel (void) +{ + int i; + edict_t *ent; + char command [256]; + + Com_sprintf (command, sizeof(command), "gamemap \"%s\"\n", level.changemap); + gi.AddCommandString (command); + level.changemap = NULL; + level.exitintermission = 0; + level.intermissiontime = 0; + ClientEndServerFrames (); + + // clear some things before going to next level + for (i=0 ; ivalue ; i++) + { + ent = g_edicts + 1 + i; + if (!ent->inuse) + continue; + if (ent->health > ent->client->pers.max_health) + ent->health = ent->client->pers.max_health; + } + +} + +/* +================ +G_RunFrame + +Advances the world by 0.1 seconds +================ +*/ +void G_RunFrame (void) +{ + int i; + edict_t *ent; + + level.framenum++; + level.time = level.framenum*FRAMETIME; + + // choose a client for monsters to target this frame + AI_SetSightClient (); + + // exit intermissions + + if (level.exitintermission) + { + ExitLevel (); + return; + } + + // + // treat each object in turn + // even the world gets a chance to think + // + ent = &g_edicts[0]; + for (i=0 ; iinuse) + continue; + + level.current_entity = ent; + + VectorCopy (ent->s.origin, ent->s.old_origin); + + // if the ground entity moved, make sure we are still on it + if ((ent->groundentity) && (ent->groundentity->linkcount != ent->groundentity_linkcount)) + { + ent->groundentity = NULL; + if ( !(ent->flags & (FL_SWIM|FL_FLY)) && (ent->svflags & SVF_MONSTER) ) + { + M_CheckGround (ent); + } + } + + if (i > 0 && i <= maxclients->value) + { + ClientBeginServerFrame (ent); + continue; + } + + G_RunEntity (ent); + } + + // see if it is time to end a deathmatch + CheckDMRules (); + + // see if needpass needs updated + CheckNeedPass (); + + // build the playerstate_t structures for all players + ClientEndServerFrames (); +} + diff --git a/original/baseq2/g_misc.c b/original/baseq2/g_misc.c new file mode 100644 index 0000000..d4b2db5 --- /dev/null +++ b/original/baseq2/g_misc.c @@ -0,0 +1,1878 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +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. + +*/ +// g_misc.c + +#include "g_local.h" + + +/*QUAKED func_group (0 0 0) ? +Used to group brushes together just for editor convenience. +*/ + +//===================================================== + +void Use_Areaportal (edict_t *ent, edict_t *other, edict_t *activator) +{ + ent->count ^= 1; // toggle state +// gi.dprintf ("portalstate: %i = %i\n", ent->style, ent->count); + gi.SetAreaPortalState (ent->style, ent->count); +} + +/*QUAKED func_areaportal (0 0 0) ? + +This is a non-visible object that divides the world into +areas that are seperated when this portal is not activated. +Usually enclosed in the middle of a door. +*/ +void SP_func_areaportal (edict_t *ent) +{ + ent->use = Use_Areaportal; + ent->count = 0; // always start closed; +} + +//===================================================== + + +/* +================= +Misc functions +================= +*/ +void VelocityForDamage (int damage, vec3_t v) +{ + v[0] = 100.0 * crandom(); + v[1] = 100.0 * crandom(); + v[2] = 200.0 + 100.0 * random(); + + if (damage < 50) + VectorScale (v, 0.7, v); + else + VectorScale (v, 1.2, v); +} + +void ClipGibVelocity (edict_t *ent) +{ + if (ent->velocity[0] < -300) + ent->velocity[0] = -300; + else if (ent->velocity[0] > 300) + ent->velocity[0] = 300; + if (ent->velocity[1] < -300) + ent->velocity[1] = -300; + else if (ent->velocity[1] > 300) + ent->velocity[1] = 300; + if (ent->velocity[2] < 200) + ent->velocity[2] = 200; // always some upwards + else if (ent->velocity[2] > 500) + ent->velocity[2] = 500; +} + + +/* +================= +gibs +================= +*/ +void gib_think (edict_t *self) +{ + self->s.frame++; + self->nextthink = level.time + FRAMETIME; + + if (self->s.frame == 10) + { + self->think = G_FreeEdict; + self->nextthink = level.time + 8 + random()*10; + } +} + +void gib_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + vec3_t normal_angles, right; + + if (!self->groundentity) + return; + + self->touch = NULL; + + if (plane) + { + gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/fhit3.wav"), 1, ATTN_NORM, 0); + + vectoangles (plane->normal, normal_angles); + AngleVectors (normal_angles, NULL, right, NULL); + vectoangles (right, self->s.angles); + + if (self->s.modelindex == sm_meat_index) + { + self->s.frame++; + self->think = gib_think; + self->nextthink = level.time + FRAMETIME; + } + } +} + +void gib_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + G_FreeEdict (self); +} + +void ThrowGib (edict_t *self, char *gibname, int damage, int type) +{ + edict_t *gib; + vec3_t vd; + vec3_t origin; + vec3_t size; + float vscale; + + gib = G_Spawn(); + + VectorScale (self->size, 0.5, size); + VectorAdd (self->absmin, size, origin); + gib->s.origin[0] = origin[0] + crandom() * size[0]; + gib->s.origin[1] = origin[1] + crandom() * size[1]; + gib->s.origin[2] = origin[2] + crandom() * size[2]; + + gi.setmodel (gib, gibname); + gib->solid = SOLID_NOT; + gib->s.effects |= EF_GIB; + gib->flags |= FL_NO_KNOCKBACK; + gib->takedamage = DAMAGE_YES; + gib->die = gib_die; + + if (type == GIB_ORGANIC) + { + gib->movetype = MOVETYPE_TOSS; + gib->touch = gib_touch; + vscale = 0.5; + } + else + { + gib->movetype = MOVETYPE_BOUNCE; + vscale = 1.0; + } + + VelocityForDamage (damage, vd); + VectorMA (self->velocity, vscale, vd, gib->velocity); + ClipGibVelocity (gib); + gib->avelocity[0] = random()*600; + gib->avelocity[1] = random()*600; + gib->avelocity[2] = random()*600; + + gib->think = G_FreeEdict; + gib->nextthink = level.time + 10 + random()*10; + + gi.linkentity (gib); +} + +void ThrowHead (edict_t *self, char *gibname, int damage, int type) +{ + vec3_t vd; + float vscale; + + self->s.skinnum = 0; + self->s.frame = 0; + VectorClear (self->mins); + VectorClear (self->maxs); + + self->s.modelindex2 = 0; + gi.setmodel (self, gibname); + self->solid = SOLID_NOT; + self->s.effects |= EF_GIB; + self->s.effects &= ~EF_FLIES; + self->s.sound = 0; + self->flags |= FL_NO_KNOCKBACK; + self->svflags &= ~SVF_MONSTER; + self->takedamage = DAMAGE_YES; + self->die = gib_die; + + if (type == GIB_ORGANIC) + { + self->movetype = MOVETYPE_TOSS; + self->touch = gib_touch; + vscale = 0.5; + } + else + { + self->movetype = MOVETYPE_BOUNCE; + vscale = 1.0; + } + + VelocityForDamage (damage, vd); + VectorMA (self->velocity, vscale, vd, self->velocity); + ClipGibVelocity (self); + + self->avelocity[YAW] = crandom()*600; + + self->think = G_FreeEdict; + self->nextthink = level.time + 10 + random()*10; + + gi.linkentity (self); +} + + +void ThrowClientHead (edict_t *self, int damage) +{ + vec3_t vd; + char *gibname; + + if (rand()&1) + { + gibname = "models/objects/gibs/head2/tris.md2"; + self->s.skinnum = 1; // second skin is player + } + else + { + gibname = "models/objects/gibs/skull/tris.md2"; + self->s.skinnum = 0; + } + + self->s.origin[2] += 32; + self->s.frame = 0; + gi.setmodel (self, gibname); + VectorSet (self->mins, -16, -16, 0); + VectorSet (self->maxs, 16, 16, 16); + + self->takedamage = DAMAGE_NO; + self->solid = SOLID_NOT; + self->s.effects = EF_GIB; + self->s.sound = 0; + self->flags |= FL_NO_KNOCKBACK; + + self->movetype = MOVETYPE_BOUNCE; + VelocityForDamage (damage, vd); + VectorAdd (self->velocity, vd, self->velocity); + + if (self->client) // bodies in the queue don't have a client anymore + { + self->client->anim_priority = ANIM_DEATH; + self->client->anim_end = self->s.frame; + } + else + { + self->think = NULL; + self->nextthink = 0; + } + + gi.linkentity (self); +} + + +/* +================= +debris +================= +*/ +void debris_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + G_FreeEdict (self); +} + +void ThrowDebris (edict_t *self, char *modelname, float speed, vec3_t origin) +{ + edict_t *chunk; + vec3_t v; + + chunk = G_Spawn(); + VectorCopy (origin, chunk->s.origin); + gi.setmodel (chunk, modelname); + v[0] = 100 * crandom(); + v[1] = 100 * crandom(); + v[2] = 100 + 100 * crandom(); + VectorMA (self->velocity, speed, v, chunk->velocity); + chunk->movetype = MOVETYPE_BOUNCE; + chunk->solid = SOLID_NOT; + chunk->avelocity[0] = random()*600; + chunk->avelocity[1] = random()*600; + chunk->avelocity[2] = random()*600; + chunk->think = G_FreeEdict; + chunk->nextthink = level.time + 5 + random()*5; + chunk->s.frame = 0; + chunk->flags = 0; + chunk->classname = "debris"; + chunk->takedamage = DAMAGE_YES; + chunk->die = debris_die; + gi.linkentity (chunk); +} + + +void BecomeExplosion1 (edict_t *self) +{ + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_EXPLOSION1); + gi.WritePosition (self->s.origin); + gi.multicast (self->s.origin, MULTICAST_PVS); + + G_FreeEdict (self); +} + + +void BecomeExplosion2 (edict_t *self) +{ + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_EXPLOSION2); + gi.WritePosition (self->s.origin); + gi.multicast (self->s.origin, MULTICAST_PVS); + + G_FreeEdict (self); +} + + +/*QUAKED path_corner (.5 .3 0) (-8 -8 -8) (8 8 8) TELEPORT +Target: next path corner +Pathtarget: gets used when an entity that has + this path_corner targeted touches it +*/ + +void path_corner_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + vec3_t v; + edict_t *next; + + if (other->movetarget != self) + return; + + if (other->enemy) + return; + + if (self->pathtarget) + { + char *savetarget; + + savetarget = self->target; + self->target = self->pathtarget; + G_UseTargets (self, other); + self->target = savetarget; + } + + if (self->target) + next = G_PickTarget(self->target); + else + next = NULL; + + if ((next) && (next->spawnflags & 1)) + { + VectorCopy (next->s.origin, v); + v[2] += next->mins[2]; + v[2] -= other->mins[2]; + VectorCopy (v, other->s.origin); + next = G_PickTarget(next->target); + other->s.event = EV_OTHER_TELEPORT; + } + + other->goalentity = other->movetarget = next; + + if (self->wait) + { + other->monsterinfo.pausetime = level.time + self->wait; + other->monsterinfo.stand (other); + return; + } + + if (!other->movetarget) + { + other->monsterinfo.pausetime = level.time + 100000000; + other->monsterinfo.stand (other); + } + else + { + VectorSubtract (other->goalentity->s.origin, other->s.origin, v); + other->ideal_yaw = vectoyaw (v); + } +} + +void SP_path_corner (edict_t *self) +{ + if (!self->targetname) + { + gi.dprintf ("path_corner with no targetname at %s\n", vtos(self->s.origin)); + G_FreeEdict (self); + return; + } + + self->solid = SOLID_TRIGGER; + self->touch = path_corner_touch; + VectorSet (self->mins, -8, -8, -8); + VectorSet (self->maxs, 8, 8, 8); + self->svflags |= SVF_NOCLIENT; + gi.linkentity (self); +} + + +/*QUAKED point_combat (0.5 0.3 0) (-8 -8 -8) (8 8 8) Hold +Makes this the target of a monster and it will head here +when first activated before going after the activator. If +hold is selected, it will stay here. +*/ +void point_combat_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + edict_t *activator; + + if (other->movetarget != self) + return; + + if (self->target) + { + other->target = self->target; + other->goalentity = other->movetarget = G_PickTarget(other->target); + if (!other->goalentity) + { + gi.dprintf("%s at %s target %s does not exist\n", self->classname, vtos(self->s.origin), self->target); + other->movetarget = self; + } + self->target = NULL; + } + else if ((self->spawnflags & 1) && !(other->flags & (FL_SWIM|FL_FLY))) + { + other->monsterinfo.pausetime = level.time + 100000000; + other->monsterinfo.aiflags |= AI_STAND_GROUND; + other->monsterinfo.stand (other); + } + + if (other->movetarget == self) + { + other->target = NULL; + other->movetarget = NULL; + other->goalentity = other->enemy; + other->monsterinfo.aiflags &= ~AI_COMBAT_POINT; + } + + if (self->pathtarget) + { + char *savetarget; + + savetarget = self->target; + self->target = self->pathtarget; + if (other->enemy && other->enemy->client) + activator = other->enemy; + else if (other->oldenemy && other->oldenemy->client) + activator = other->oldenemy; + else if (other->activator && other->activator->client) + activator = other->activator; + else + activator = other; + G_UseTargets (self, activator); + self->target = savetarget; + } +} + +void SP_point_combat (edict_t *self) +{ + if (deathmatch->value) + { + G_FreeEdict (self); + return; + } + self->solid = SOLID_TRIGGER; + self->touch = point_combat_touch; + VectorSet (self->mins, -8, -8, -16); + VectorSet (self->maxs, 8, 8, 16); + self->svflags = SVF_NOCLIENT; + gi.linkentity (self); +}; + + +/*QUAKED viewthing (0 .5 .8) (-8 -8 -8) (8 8 8) +Just for the debugging level. Don't use +*/ +void TH_viewthing(edict_t *ent) +{ + ent->s.frame = (ent->s.frame + 1) % 7; + ent->nextthink = level.time + FRAMETIME; +} + +void SP_viewthing(edict_t *ent) +{ + gi.dprintf ("viewthing spawned\n"); + + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_BBOX; + ent->s.renderfx = RF_FRAMELERP; + VectorSet (ent->mins, -16, -16, -24); + VectorSet (ent->maxs, 16, 16, 32); + ent->s.modelindex = gi.modelindex ("models/objects/banner/tris.md2"); + gi.linkentity (ent); + ent->nextthink = level.time + 0.5; + ent->think = TH_viewthing; + return; +} + + +/*QUAKED info_null (0 0.5 0) (-4 -4 -4) (4 4 4) +Used as a positional target for spotlights, etc. +*/ +void SP_info_null (edict_t *self) +{ + G_FreeEdict (self); +}; + + +/*QUAKED info_notnull (0 0.5 0) (-4 -4 -4) (4 4 4) +Used as a positional target for lightning. +*/ +void SP_info_notnull (edict_t *self) +{ + VectorCopy (self->s.origin, self->absmin); + VectorCopy (self->s.origin, self->absmax); +}; + + +/*QUAKED light (0 1 0) (-8 -8 -8) (8 8 8) START_OFF +Non-displayed light. +Default light value is 300. +Default style is 0. +If targeted, will toggle between on and off. +Default _cone value is 10 (used to set size of light for spotlights) +*/ + +#define START_OFF 1 + +static void light_use (edict_t *self, edict_t *other, edict_t *activator) +{ + if (self->spawnflags & START_OFF) + { + gi.configstring (CS_LIGHTS+self->style, "m"); + self->spawnflags &= ~START_OFF; + } + else + { + gi.configstring (CS_LIGHTS+self->style, "a"); + self->spawnflags |= START_OFF; + } +} + +void SP_light (edict_t *self) +{ + // no targeted lights in deathmatch, because they cause global messages + if (!self->targetname || deathmatch->value) + { + G_FreeEdict (self); + return; + } + + if (self->style >= 32) + { + self->use = light_use; + if (self->spawnflags & START_OFF) + gi.configstring (CS_LIGHTS+self->style, "a"); + else + gi.configstring (CS_LIGHTS+self->style, "m"); + } +} + + +/*QUAKED func_wall (0 .5 .8) ? TRIGGER_SPAWN TOGGLE START_ON ANIMATED ANIMATED_FAST +This is just a solid wall if not inhibited + +TRIGGER_SPAWN the wall will not be present until triggered + it will then blink in to existance; it will + kill anything that was in it's way + +TOGGLE only valid for TRIGGER_SPAWN walls + this allows the wall to be turned on and off + +START_ON only valid for TRIGGER_SPAWN walls + the wall will initially be present +*/ + +void func_wall_use (edict_t *self, edict_t *other, edict_t *activator) +{ + if (self->solid == SOLID_NOT) + { + self->solid = SOLID_BSP; + self->svflags &= ~SVF_NOCLIENT; + KillBox (self); + } + else + { + self->solid = SOLID_NOT; + self->svflags |= SVF_NOCLIENT; + } + gi.linkentity (self); + + if (!(self->spawnflags & 2)) + self->use = NULL; +} + +void SP_func_wall (edict_t *self) +{ + self->movetype = MOVETYPE_PUSH; + gi.setmodel (self, self->model); + + if (self->spawnflags & 8) + self->s.effects |= EF_ANIM_ALL; + if (self->spawnflags & 16) + self->s.effects |= EF_ANIM_ALLFAST; + + // just a wall + if ((self->spawnflags & 7) == 0) + { + self->solid = SOLID_BSP; + gi.linkentity (self); + return; + } + + // it must be TRIGGER_SPAWN + if (!(self->spawnflags & 1)) + { +// gi.dprintf("func_wall missing TRIGGER_SPAWN\n"); + self->spawnflags |= 1; + } + + // yell if the spawnflags are odd + if (self->spawnflags & 4) + { + if (!(self->spawnflags & 2)) + { + gi.dprintf("func_wall START_ON without TOGGLE\n"); + self->spawnflags |= 2; + } + } + + self->use = func_wall_use; + if (self->spawnflags & 4) + { + self->solid = SOLID_BSP; + } + else + { + self->solid = SOLID_NOT; + self->svflags |= SVF_NOCLIENT; + } + gi.linkentity (self); +} + + +/*QUAKED func_object (0 .5 .8) ? TRIGGER_SPAWN ANIMATED ANIMATED_FAST +This is solid bmodel that will fall if it's support it removed. +*/ + +void func_object_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + // only squash thing we fall on top of + if (!plane) + return; + if (plane->normal[2] < 1.0) + return; + if (other->takedamage == DAMAGE_NO) + return; + T_Damage (other, self, self, vec3_origin, self->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH); +} + +void func_object_release (edict_t *self) +{ + self->movetype = MOVETYPE_TOSS; + self->touch = func_object_touch; +} + +void func_object_use (edict_t *self, edict_t *other, edict_t *activator) +{ + self->solid = SOLID_BSP; + self->svflags &= ~SVF_NOCLIENT; + self->use = NULL; + KillBox (self); + func_object_release (self); +} + +void SP_func_object (edict_t *self) +{ + gi.setmodel (self, self->model); + + self->mins[0] += 1; + self->mins[1] += 1; + self->mins[2] += 1; + self->maxs[0] -= 1; + self->maxs[1] -= 1; + self->maxs[2] -= 1; + + if (!self->dmg) + self->dmg = 100; + + if (self->spawnflags == 0) + { + self->solid = SOLID_BSP; + self->movetype = MOVETYPE_PUSH; + self->think = func_object_release; + self->nextthink = level.time + 2 * FRAMETIME; + } + else + { + self->solid = SOLID_NOT; + self->movetype = MOVETYPE_PUSH; + self->use = func_object_use; + self->svflags |= SVF_NOCLIENT; + } + + if (self->spawnflags & 2) + self->s.effects |= EF_ANIM_ALL; + if (self->spawnflags & 4) + self->s.effects |= EF_ANIM_ALLFAST; + + self->clipmask = MASK_MONSTERSOLID; + + gi.linkentity (self); +} + + +/*QUAKED func_explosive (0 .5 .8) ? Trigger_Spawn ANIMATED ANIMATED_FAST +Any brush that you want to explode or break apart. If you want an +ex0plosion, set dmg and it will do a radius explosion of that amount +at the center of the bursh. + +If targeted it will not be shootable. + +health defaults to 100. + +mass defaults to 75. This determines how much debris is emitted when +it explodes. You get one large chunk per 100 of mass (up to 8) and +one small chunk per 25 of mass (up to 16). So 800 gives the most. +*/ +void func_explosive_explode (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + vec3_t origin; + vec3_t chunkorigin; + vec3_t size; + int count; + int mass; + + // bmodel origins are (0 0 0), we need to adjust that here + VectorScale (self->size, 0.5, size); + VectorAdd (self->absmin, size, origin); + VectorCopy (origin, self->s.origin); + + self->takedamage = DAMAGE_NO; + + if (self->dmg) + T_RadiusDamage (self, attacker, self->dmg, NULL, self->dmg+40, MOD_EXPLOSIVE); + + VectorSubtract (self->s.origin, inflictor->s.origin, self->velocity); + VectorNormalize (self->velocity); + VectorScale (self->velocity, 150, self->velocity); + + // start chunks towards the center + VectorScale (size, 0.5, size); + + mass = self->mass; + if (!mass) + mass = 75; + + // big chunks + if (mass >= 100) + { + count = mass / 100; + if (count > 8) + count = 8; + while(count--) + { + chunkorigin[0] = origin[0] + crandom() * size[0]; + chunkorigin[1] = origin[1] + crandom() * size[1]; + chunkorigin[2] = origin[2] + crandom() * size[2]; + ThrowDebris (self, "models/objects/debris1/tris.md2", 1, chunkorigin); + } + } + + // small chunks + count = mass / 25; + if (count > 16) + count = 16; + while(count--) + { + chunkorigin[0] = origin[0] + crandom() * size[0]; + chunkorigin[1] = origin[1] + crandom() * size[1]; + chunkorigin[2] = origin[2] + crandom() * size[2]; + ThrowDebris (self, "models/objects/debris2/tris.md2", 2, chunkorigin); + } + + G_UseTargets (self, attacker); + + if (self->dmg) + BecomeExplosion1 (self); + else + G_FreeEdict (self); +} + +void func_explosive_use(edict_t *self, edict_t *other, edict_t *activator) +{ + func_explosive_explode (self, self, other, self->health, vec3_origin); +} + +void func_explosive_spawn (edict_t *self, edict_t *other, edict_t *activator) +{ + self->solid = SOLID_BSP; + self->svflags &= ~SVF_NOCLIENT; + self->use = NULL; + KillBox (self); + gi.linkentity (self); +} + +void SP_func_explosive (edict_t *self) +{ + if (deathmatch->value) + { // auto-remove for deathmatch + G_FreeEdict (self); + return; + } + + self->movetype = MOVETYPE_PUSH; + + gi.modelindex ("models/objects/debris1/tris.md2"); + gi.modelindex ("models/objects/debris2/tris.md2"); + + gi.setmodel (self, self->model); + + if (self->spawnflags & 1) + { + self->svflags |= SVF_NOCLIENT; + self->solid = SOLID_NOT; + self->use = func_explosive_spawn; + } + else + { + self->solid = SOLID_BSP; + if (self->targetname) + self->use = func_explosive_use; + } + + if (self->spawnflags & 2) + self->s.effects |= EF_ANIM_ALL; + if (self->spawnflags & 4) + self->s.effects |= EF_ANIM_ALLFAST; + + if (self->use != func_explosive_use) + { + if (!self->health) + self->health = 100; + self->die = func_explosive_explode; + self->takedamage = DAMAGE_YES; + } + + gi.linkentity (self); +} + + +/*QUAKED misc_explobox (0 .5 .8) (-16 -16 0) (16 16 40) +Large exploding box. You can override its mass (100), +health (80), and dmg (150). +*/ + +void barrel_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) + +{ + float ratio; + vec3_t v; + + if ((!other->groundentity) || (other->groundentity == self)) + return; + + ratio = (float)other->mass / (float)self->mass; + VectorSubtract (self->s.origin, other->s.origin, v); + M_walkmove (self, vectoyaw(v), 20 * ratio * FRAMETIME); +} + +void barrel_explode (edict_t *self) +{ + vec3_t org; + float spd; + vec3_t save; + + T_RadiusDamage (self, self->activator, self->dmg, NULL, self->dmg+40, MOD_BARREL); + + VectorCopy (self->s.origin, save); + VectorMA (self->absmin, 0.5, self->size, self->s.origin); + + // a few big chunks + spd = 1.5 * (float)self->dmg / 200.0; + org[0] = self->s.origin[0] + crandom() * self->size[0]; + org[1] = self->s.origin[1] + crandom() * self->size[1]; + org[2] = self->s.origin[2] + crandom() * self->size[2]; + ThrowDebris (self, "models/objects/debris1/tris.md2", spd, org); + org[0] = self->s.origin[0] + crandom() * self->size[0]; + org[1] = self->s.origin[1] + crandom() * self->size[1]; + org[2] = self->s.origin[2] + crandom() * self->size[2]; + ThrowDebris (self, "models/objects/debris1/tris.md2", spd, org); + + // bottom corners + spd = 1.75 * (float)self->dmg / 200.0; + VectorCopy (self->absmin, org); + ThrowDebris (self, "models/objects/debris3/tris.md2", spd, org); + VectorCopy (self->absmin, org); + org[0] += self->size[0]; + ThrowDebris (self, "models/objects/debris3/tris.md2", spd, org); + VectorCopy (self->absmin, org); + org[1] += self->size[1]; + ThrowDebris (self, "models/objects/debris3/tris.md2", spd, org); + VectorCopy (self->absmin, org); + org[0] += self->size[0]; + org[1] += self->size[1]; + ThrowDebris (self, "models/objects/debris3/tris.md2", spd, org); + + // a bunch of little chunks + spd = 2 * self->dmg / 200; + org[0] = self->s.origin[0] + crandom() * self->size[0]; + org[1] = self->s.origin[1] + crandom() * self->size[1]; + org[2] = self->s.origin[2] + crandom() * self->size[2]; + ThrowDebris (self, "models/objects/debris2/tris.md2", spd, org); + org[0] = self->s.origin[0] + crandom() * self->size[0]; + org[1] = self->s.origin[1] + crandom() * self->size[1]; + org[2] = self->s.origin[2] + crandom() * self->size[2]; + ThrowDebris (self, "models/objects/debris2/tris.md2", spd, org); + org[0] = self->s.origin[0] + crandom() * self->size[0]; + org[1] = self->s.origin[1] + crandom() * self->size[1]; + org[2] = self->s.origin[2] + crandom() * self->size[2]; + ThrowDebris (self, "models/objects/debris2/tris.md2", spd, org); + org[0] = self->s.origin[0] + crandom() * self->size[0]; + org[1] = self->s.origin[1] + crandom() * self->size[1]; + org[2] = self->s.origin[2] + crandom() * self->size[2]; + ThrowDebris (self, "models/objects/debris2/tris.md2", spd, org); + org[0] = self->s.origin[0] + crandom() * self->size[0]; + org[1] = self->s.origin[1] + crandom() * self->size[1]; + org[2] = self->s.origin[2] + crandom() * self->size[2]; + ThrowDebris (self, "models/objects/debris2/tris.md2", spd, org); + org[0] = self->s.origin[0] + crandom() * self->size[0]; + org[1] = self->s.origin[1] + crandom() * self->size[1]; + org[2] = self->s.origin[2] + crandom() * self->size[2]; + ThrowDebris (self, "models/objects/debris2/tris.md2", spd, org); + org[0] = self->s.origin[0] + crandom() * self->size[0]; + org[1] = self->s.origin[1] + crandom() * self->size[1]; + org[2] = self->s.origin[2] + crandom() * self->size[2]; + ThrowDebris (self, "models/objects/debris2/tris.md2", spd, org); + org[0] = self->s.origin[0] + crandom() * self->size[0]; + org[1] = self->s.origin[1] + crandom() * self->size[1]; + org[2] = self->s.origin[2] + crandom() * self->size[2]; + ThrowDebris (self, "models/objects/debris2/tris.md2", spd, org); + + VectorCopy (save, self->s.origin); + if (self->groundentity) + BecomeExplosion2 (self); + else + BecomeExplosion1 (self); +} + +void barrel_delay (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + self->takedamage = DAMAGE_NO; + self->nextthink = level.time + 2 * FRAMETIME; + self->think = barrel_explode; + self->activator = attacker; +} + +void SP_misc_explobox (edict_t *self) +{ + if (deathmatch->value) + { // auto-remove for deathmatch + G_FreeEdict (self); + return; + } + + gi.modelindex ("models/objects/debris1/tris.md2"); + gi.modelindex ("models/objects/debris2/tris.md2"); + gi.modelindex ("models/objects/debris3/tris.md2"); + + self->solid = SOLID_BBOX; + self->movetype = MOVETYPE_STEP; + + self->model = "models/objects/barrels/tris.md2"; + self->s.modelindex = gi.modelindex (self->model); + VectorSet (self->mins, -16, -16, 0); + VectorSet (self->maxs, 16, 16, 40); + + if (!self->mass) + self->mass = 400; + if (!self->health) + self->health = 10; + if (!self->dmg) + self->dmg = 150; + + self->die = barrel_delay; + self->takedamage = DAMAGE_YES; + self->monsterinfo.aiflags = AI_NOSTEP; + + self->touch = barrel_touch; + + self->think = M_droptofloor; + self->nextthink = level.time + 2 * FRAMETIME; + + gi.linkentity (self); +} + + +// +// miscellaneous specialty items +// + +/*QUAKED misc_blackhole (1 .5 0) (-8 -8 -8) (8 8 8) +*/ + +void misc_blackhole_use (edict_t *ent, edict_t *other, edict_t *activator) +{ + /* + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_BOSSTPORT); + gi.WritePosition (ent->s.origin); + gi.multicast (ent->s.origin, MULTICAST_PVS); + */ + G_FreeEdict (ent); +} + +void misc_blackhole_think (edict_t *self) +{ + if (++self->s.frame < 19) + self->nextthink = level.time + FRAMETIME; + else + { + self->s.frame = 0; + self->nextthink = level.time + FRAMETIME; + } +} + +void SP_misc_blackhole (edict_t *ent) +{ + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_NOT; + VectorSet (ent->mins, -64, -64, 0); + VectorSet (ent->maxs, 64, 64, 8); + ent->s.modelindex = gi.modelindex ("models/objects/black/tris.md2"); + ent->s.renderfx = RF_TRANSLUCENT; + ent->use = misc_blackhole_use; + ent->think = misc_blackhole_think; + ent->nextthink = level.time + 2 * FRAMETIME; + gi.linkentity (ent); +} + +/*QUAKED misc_eastertank (1 .5 0) (-32 -32 -16) (32 32 32) +*/ + +void misc_eastertank_think (edict_t *self) +{ + if (++self->s.frame < 293) + self->nextthink = level.time + FRAMETIME; + else + { + self->s.frame = 254; + self->nextthink = level.time + FRAMETIME; + } +} + +void SP_misc_eastertank (edict_t *ent) +{ + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_BBOX; + VectorSet (ent->mins, -32, -32, -16); + VectorSet (ent->maxs, 32, 32, 32); + ent->s.modelindex = gi.modelindex ("models/monsters/tank/tris.md2"); + ent->s.frame = 254; + ent->think = misc_eastertank_think; + ent->nextthink = level.time + 2 * FRAMETIME; + gi.linkentity (ent); +} + +/*QUAKED misc_easterchick (1 .5 0) (-32 -32 0) (32 32 32) +*/ + + +void misc_easterchick_think (edict_t *self) +{ + if (++self->s.frame < 247) + self->nextthink = level.time + FRAMETIME; + else + { + self->s.frame = 208; + self->nextthink = level.time + FRAMETIME; + } +} + +void SP_misc_easterchick (edict_t *ent) +{ + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_BBOX; + VectorSet (ent->mins, -32, -32, 0); + VectorSet (ent->maxs, 32, 32, 32); + ent->s.modelindex = gi.modelindex ("models/monsters/bitch/tris.md2"); + ent->s.frame = 208; + ent->think = misc_easterchick_think; + ent->nextthink = level.time + 2 * FRAMETIME; + gi.linkentity (ent); +} + +/*QUAKED misc_easterchick2 (1 .5 0) (-32 -32 0) (32 32 32) +*/ + + +void misc_easterchick2_think (edict_t *self) +{ + if (++self->s.frame < 287) + self->nextthink = level.time + FRAMETIME; + else + { + self->s.frame = 248; + self->nextthink = level.time + FRAMETIME; + } +} + +void SP_misc_easterchick2 (edict_t *ent) +{ + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_BBOX; + VectorSet (ent->mins, -32, -32, 0); + VectorSet (ent->maxs, 32, 32, 32); + ent->s.modelindex = gi.modelindex ("models/monsters/bitch/tris.md2"); + ent->s.frame = 248; + ent->think = misc_easterchick2_think; + ent->nextthink = level.time + 2 * FRAMETIME; + gi.linkentity (ent); +} + + +/*QUAKED monster_commander_body (1 .5 0) (-32 -32 0) (32 32 48) +Not really a monster, this is the Tank Commander's decapitated body. +There should be a item_commander_head that has this as it's target. +*/ + +void commander_body_think (edict_t *self) +{ + if (++self->s.frame < 24) + self->nextthink = level.time + FRAMETIME; + else + self->nextthink = 0; + + if (self->s.frame == 22) + gi.sound (self, CHAN_BODY, gi.soundindex ("tank/thud.wav"), 1, ATTN_NORM, 0); +} + +void commander_body_use (edict_t *self, edict_t *other, edict_t *activator) +{ + self->think = commander_body_think; + self->nextthink = level.time + FRAMETIME; + gi.sound (self, CHAN_BODY, gi.soundindex ("tank/pain.wav"), 1, ATTN_NORM, 0); +} + +void commander_body_drop (edict_t *self) +{ + self->movetype = MOVETYPE_TOSS; + self->s.origin[2] += 2; +} + +void SP_monster_commander_body (edict_t *self) +{ + self->movetype = MOVETYPE_NONE; + self->solid = SOLID_BBOX; + self->model = "models/monsters/commandr/tris.md2"; + self->s.modelindex = gi.modelindex (self->model); + VectorSet (self->mins, -32, -32, 0); + VectorSet (self->maxs, 32, 32, 48); + self->use = commander_body_use; + self->takedamage = DAMAGE_YES; + self->flags = FL_GODMODE; + self->s.renderfx |= RF_FRAMELERP; + gi.linkentity (self); + + gi.soundindex ("tank/thud.wav"); + gi.soundindex ("tank/pain.wav"); + + self->think = commander_body_drop; + self->nextthink = level.time + 5 * FRAMETIME; +} + + +/*QUAKED misc_banner (1 .5 0) (-4 -4 -4) (4 4 4) +The origin is the bottom of the banner. +The banner is 128 tall. +*/ +void misc_banner_think (edict_t *ent) +{ + ent->s.frame = (ent->s.frame + 1) % 16; + ent->nextthink = level.time + FRAMETIME; +} + +void SP_misc_banner (edict_t *ent) +{ + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_NOT; + ent->s.modelindex = gi.modelindex ("models/objects/banner/tris.md2"); + ent->s.frame = rand() % 16; + gi.linkentity (ent); + + ent->think = misc_banner_think; + ent->nextthink = level.time + FRAMETIME; +} + +/*QUAKED misc_deadsoldier (1 .5 0) (-16 -16 0) (16 16 16) ON_BACK ON_STOMACH BACK_DECAP FETAL_POS SIT_DECAP IMPALED +This is the dead player model. Comes in 6 exciting different poses! +*/ +void misc_deadsoldier_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + int n; + + if (self->health > -80) + return; + + gi.sound (self, CHAN_BODY, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0); + for (n= 0; n < 4; n++) + ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); + ThrowHead (self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC); +} + +void SP_misc_deadsoldier (edict_t *ent) +{ + if (deathmatch->value) + { // auto-remove for deathmatch + G_FreeEdict (ent); + return; + } + + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_BBOX; + ent->s.modelindex=gi.modelindex ("models/deadbods/dude/tris.md2"); + + // Defaults to frame 0 + if (ent->spawnflags & 2) + ent->s.frame = 1; + else if (ent->spawnflags & 4) + ent->s.frame = 2; + else if (ent->spawnflags & 8) + ent->s.frame = 3; + else if (ent->spawnflags & 16) + ent->s.frame = 4; + else if (ent->spawnflags & 32) + ent->s.frame = 5; + else + ent->s.frame = 0; + + VectorSet (ent->mins, -16, -16, 0); + VectorSet (ent->maxs, 16, 16, 16); + ent->deadflag = DEAD_DEAD; + ent->takedamage = DAMAGE_YES; + ent->svflags |= SVF_MONSTER|SVF_DEADMONSTER; + ent->die = misc_deadsoldier_die; + ent->monsterinfo.aiflags |= AI_GOOD_GUY; + + gi.linkentity (ent); +} + +/*QUAKED misc_viper (1 .5 0) (-16 -16 0) (16 16 32) +This is the Viper for the flyby bombing. +It is trigger_spawned, so you must have something use it for it to show up. +There must be a path for it to follow once it is activated. + +"speed" How fast the Viper should fly +*/ + +extern void train_use (edict_t *self, edict_t *other, edict_t *activator); +extern void func_train_find (edict_t *self); + +void misc_viper_use (edict_t *self, edict_t *other, edict_t *activator) +{ + self->svflags &= ~SVF_NOCLIENT; + self->use = train_use; + train_use (self, other, activator); +} + +void SP_misc_viper (edict_t *ent) +{ + if (!ent->target) + { + gi.dprintf ("misc_viper without a target at %s\n", vtos(ent->absmin)); + G_FreeEdict (ent); + return; + } + + if (!ent->speed) + ent->speed = 300; + + ent->movetype = MOVETYPE_PUSH; + ent->solid = SOLID_NOT; + ent->s.modelindex = gi.modelindex ("models/ships/viper/tris.md2"); + VectorSet (ent->mins, -16, -16, 0); + VectorSet (ent->maxs, 16, 16, 32); + + ent->think = func_train_find; + ent->nextthink = level.time + FRAMETIME; + ent->use = misc_viper_use; + ent->svflags |= SVF_NOCLIENT; + ent->moveinfo.accel = ent->moveinfo.decel = ent->moveinfo.speed = ent->speed; + + gi.linkentity (ent); +} + + +/*QUAKED misc_bigviper (1 .5 0) (-176 -120 -24) (176 120 72) +This is a large stationary viper as seen in Paul's intro +*/ +void SP_misc_bigviper (edict_t *ent) +{ + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_BBOX; + VectorSet (ent->mins, -176, -120, -24); + VectorSet (ent->maxs, 176, 120, 72); + ent->s.modelindex = gi.modelindex ("models/ships/bigviper/tris.md2"); + gi.linkentity (ent); +} + + +/*QUAKED misc_viper_bomb (1 0 0) (-8 -8 -8) (8 8 8) +"dmg" how much boom should the bomb make? +*/ +void misc_viper_bomb_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + G_UseTargets (self, self->activator); + + self->s.origin[2] = self->absmin[2] + 1; + T_RadiusDamage (self, self, self->dmg, NULL, self->dmg+40, MOD_BOMB); + BecomeExplosion2 (self); +} + +void misc_viper_bomb_prethink (edict_t *self) +{ + vec3_t v; + float diff; + + self->groundentity = NULL; + + diff = self->timestamp - level.time; + if (diff < -1.0) + diff = -1.0; + + VectorScale (self->moveinfo.dir, 1.0 + diff, v); + v[2] = diff; + + diff = self->s.angles[2]; + vectoangles (v, self->s.angles); + self->s.angles[2] = diff + 10; +} + +void misc_viper_bomb_use (edict_t *self, edict_t *other, edict_t *activator) +{ + edict_t *viper; + + self->solid = SOLID_BBOX; + self->svflags &= ~SVF_NOCLIENT; + self->s.effects |= EF_ROCKET; + self->use = NULL; + self->movetype = MOVETYPE_TOSS; + self->prethink = misc_viper_bomb_prethink; + self->touch = misc_viper_bomb_touch; + self->activator = activator; + + viper = G_Find (NULL, FOFS(classname), "misc_viper"); + VectorScale (viper->moveinfo.dir, viper->moveinfo.speed, self->velocity); + + self->timestamp = level.time; + VectorCopy (viper->moveinfo.dir, self->moveinfo.dir); +} + +void SP_misc_viper_bomb (edict_t *self) +{ + self->movetype = MOVETYPE_NONE; + self->solid = SOLID_NOT; + VectorSet (self->mins, -8, -8, -8); + VectorSet (self->maxs, 8, 8, 8); + + self->s.modelindex = gi.modelindex ("models/objects/bomb/tris.md2"); + + if (!self->dmg) + self->dmg = 1000; + + self->use = misc_viper_bomb_use; + self->svflags |= SVF_NOCLIENT; + + gi.linkentity (self); +} + + +/*QUAKED misc_strogg_ship (1 .5 0) (-16 -16 0) (16 16 32) +This is a Storgg ship for the flybys. +It is trigger_spawned, so you must have something use it for it to show up. +There must be a path for it to follow once it is activated. + +"speed" How fast it should fly +*/ + +extern void train_use (edict_t *self, edict_t *other, edict_t *activator); +extern void func_train_find (edict_t *self); + +void misc_strogg_ship_use (edict_t *self, edict_t *other, edict_t *activator) +{ + self->svflags &= ~SVF_NOCLIENT; + self->use = train_use; + train_use (self, other, activator); +} + +void SP_misc_strogg_ship (edict_t *ent) +{ + if (!ent->target) + { + gi.dprintf ("%s without a target at %s\n", ent->classname, vtos(ent->absmin)); + G_FreeEdict (ent); + return; + } + + if (!ent->speed) + ent->speed = 300; + + ent->movetype = MOVETYPE_PUSH; + ent->solid = SOLID_NOT; + ent->s.modelindex = gi.modelindex ("models/ships/strogg1/tris.md2"); + VectorSet (ent->mins, -16, -16, 0); + VectorSet (ent->maxs, 16, 16, 32); + + ent->think = func_train_find; + ent->nextthink = level.time + FRAMETIME; + ent->use = misc_strogg_ship_use; + ent->svflags |= SVF_NOCLIENT; + ent->moveinfo.accel = ent->moveinfo.decel = ent->moveinfo.speed = ent->speed; + + gi.linkentity (ent); +} + + +/*QUAKED misc_satellite_dish (1 .5 0) (-64 -64 0) (64 64 128) +*/ +void misc_satellite_dish_think (edict_t *self) +{ + self->s.frame++; + if (self->s.frame < 38) + self->nextthink = level.time + FRAMETIME; +} + +void misc_satellite_dish_use (edict_t *self, edict_t *other, edict_t *activator) +{ + self->s.frame = 0; + self->think = misc_satellite_dish_think; + self->nextthink = level.time + FRAMETIME; +} + +void SP_misc_satellite_dish (edict_t *ent) +{ + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_BBOX; + VectorSet (ent->mins, -64, -64, 0); + VectorSet (ent->maxs, 64, 64, 128); + ent->s.modelindex = gi.modelindex ("models/objects/satellite/tris.md2"); + ent->use = misc_satellite_dish_use; + gi.linkentity (ent); +} + + +/*QUAKED light_mine1 (0 1 0) (-2 -2 -12) (2 2 12) +*/ +void SP_light_mine1 (edict_t *ent) +{ + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_BBOX; + ent->s.modelindex = gi.modelindex ("models/objects/minelite/light1/tris.md2"); + gi.linkentity (ent); +} + + +/*QUAKED light_mine2 (0 1 0) (-2 -2 -12) (2 2 12) +*/ +void SP_light_mine2 (edict_t *ent) +{ + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_BBOX; + ent->s.modelindex = gi.modelindex ("models/objects/minelite/light2/tris.md2"); + gi.linkentity (ent); +} + + +/*QUAKED misc_gib_arm (1 0 0) (-8 -8 -8) (8 8 8) +Intended for use with the target_spawner +*/ +void SP_misc_gib_arm (edict_t *ent) +{ + gi.setmodel (ent, "models/objects/gibs/arm/tris.md2"); + ent->solid = SOLID_NOT; + ent->s.effects |= EF_GIB; + ent->takedamage = DAMAGE_YES; + ent->die = gib_die; + ent->movetype = MOVETYPE_TOSS; + ent->svflags |= SVF_MONSTER; + ent->deadflag = DEAD_DEAD; + ent->avelocity[0] = random()*200; + ent->avelocity[1] = random()*200; + ent->avelocity[2] = random()*200; + ent->think = G_FreeEdict; + ent->nextthink = level.time + 30; + gi.linkentity (ent); +} + +/*QUAKED misc_gib_leg (1 0 0) (-8 -8 -8) (8 8 8) +Intended for use with the target_spawner +*/ +void SP_misc_gib_leg (edict_t *ent) +{ + gi.setmodel (ent, "models/objects/gibs/leg/tris.md2"); + ent->solid = SOLID_NOT; + ent->s.effects |= EF_GIB; + ent->takedamage = DAMAGE_YES; + ent->die = gib_die; + ent->movetype = MOVETYPE_TOSS; + ent->svflags |= SVF_MONSTER; + ent->deadflag = DEAD_DEAD; + ent->avelocity[0] = random()*200; + ent->avelocity[1] = random()*200; + ent->avelocity[2] = random()*200; + ent->think = G_FreeEdict; + ent->nextthink = level.time + 30; + gi.linkentity (ent); +} + +/*QUAKED misc_gib_head (1 0 0) (-8 -8 -8) (8 8 8) +Intended for use with the target_spawner +*/ +void SP_misc_gib_head (edict_t *ent) +{ + gi.setmodel (ent, "models/objects/gibs/head/tris.md2"); + ent->solid = SOLID_NOT; + ent->s.effects |= EF_GIB; + ent->takedamage = DAMAGE_YES; + ent->die = gib_die; + ent->movetype = MOVETYPE_TOSS; + ent->svflags |= SVF_MONSTER; + ent->deadflag = DEAD_DEAD; + ent->avelocity[0] = random()*200; + ent->avelocity[1] = random()*200; + ent->avelocity[2] = random()*200; + ent->think = G_FreeEdict; + ent->nextthink = level.time + 30; + gi.linkentity (ent); +} + +//===================================================== + +/*QUAKED target_character (0 0 1) ? +used with target_string (must be on same "team") +"count" is position in the string (starts at 1) +*/ + +void SP_target_character (edict_t *self) +{ + self->movetype = MOVETYPE_PUSH; + gi.setmodel (self, self->model); + self->solid = SOLID_BSP; + self->s.frame = 12; + gi.linkentity (self); + return; +} + + +/*QUAKED target_string (0 0 1) (-8 -8 -8) (8 8 8) +*/ + +void target_string_use (edict_t *self, edict_t *other, edict_t *activator) +{ + edict_t *e; + int n, l; + char c; + + l = strlen(self->message); + for (e = self->teammaster; e; e = e->teamchain) + { + if (!e->count) + continue; + n = e->count - 1; + if (n > l) + { + e->s.frame = 12; + continue; + } + + c = self->message[n]; + if (c >= '0' && c <= '9') + e->s.frame = c - '0'; + else if (c == '-') + e->s.frame = 10; + else if (c == ':') + e->s.frame = 11; + else + e->s.frame = 12; + } +} + +void SP_target_string (edict_t *self) +{ + if (!self->message) + self->message = ""; + self->use = target_string_use; +} + + +/*QUAKED func_clock (0 0 1) (-8 -8 -8) (8 8 8) TIMER_UP TIMER_DOWN START_OFF MULTI_USE +target a target_string with this + +The default is to be a time of day clock + +TIMER_UP and TIMER_DOWN run for "count" seconds and the fire "pathtarget" +If START_OFF, this entity must be used before it starts + +"style" 0 "xx" + 1 "xx:xx" + 2 "xx:xx:xx" +*/ + +#define CLOCK_MESSAGE_SIZE 16 + +// don't let field width of any clock messages change, or it +// could cause an overwrite after a game load + +static void func_clock_reset (edict_t *self) +{ + self->activator = NULL; + if (self->spawnflags & 1) + { + self->health = 0; + self->wait = self->count; + } + else if (self->spawnflags & 2) + { + self->health = self->count; + self->wait = 0; + } +} + +static void func_clock_format_countdown (edict_t *self) +{ + if (self->style == 0) + { + Com_sprintf (self->message, CLOCK_MESSAGE_SIZE, "%2i", self->health); + return; + } + + if (self->style == 1) + { + Com_sprintf(self->message, CLOCK_MESSAGE_SIZE, "%2i:%2i", self->health / 60, self->health % 60); + if (self->message[3] == ' ') + self->message[3] = '0'; + return; + } + + if (self->style == 2) + { + Com_sprintf(self->message, CLOCK_MESSAGE_SIZE, "%2i:%2i:%2i", self->health / 3600, (self->health - (self->health / 3600) * 3600) / 60, self->health % 60); + if (self->message[3] == ' ') + self->message[3] = '0'; + if (self->message[6] == ' ') + self->message[6] = '0'; + return; + } +} + +void func_clock_think (edict_t *self) +{ + if (!self->enemy) + { + self->enemy = G_Find (NULL, FOFS(targetname), self->target); + if (!self->enemy) + return; + } + + if (self->spawnflags & 1) + { + func_clock_format_countdown (self); + self->health++; + } + else if (self->spawnflags & 2) + { + func_clock_format_countdown (self); + self->health--; + } + else + { + struct tm *ltime; + time_t gmtime; + + time(&gmtime); + ltime = localtime(&gmtime); + Com_sprintf (self->message, CLOCK_MESSAGE_SIZE, "%2i:%2i:%2i", ltime->tm_hour, ltime->tm_min, ltime->tm_sec); + if (self->message[3] == ' ') + self->message[3] = '0'; + if (self->message[6] == ' ') + self->message[6] = '0'; + } + + self->enemy->message = self->message; + self->enemy->use (self->enemy, self, self); + + if (((self->spawnflags & 1) && (self->health > self->wait)) || + ((self->spawnflags & 2) && (self->health < self->wait))) + { + if (self->pathtarget) + { + char *savetarget; + char *savemessage; + + savetarget = self->target; + savemessage = self->message; + self->target = self->pathtarget; + self->message = NULL; + G_UseTargets (self, self->activator); + self->target = savetarget; + self->message = savemessage; + } + + if (!(self->spawnflags & 8)) + return; + + func_clock_reset (self); + + if (self->spawnflags & 4) + return; + } + + self->nextthink = level.time + 1; +} + +void func_clock_use (edict_t *self, edict_t *other, edict_t *activator) +{ + if (!(self->spawnflags & 8)) + self->use = NULL; + if (self->activator) + return; + self->activator = activator; + self->think (self); +} + +void SP_func_clock (edict_t *self) +{ + if (!self->target) + { + gi.dprintf("%s with no target at %s\n", self->classname, vtos(self->s.origin)); + G_FreeEdict (self); + return; + } + + if ((self->spawnflags & 2) && (!self->count)) + { + gi.dprintf("%s with no count at %s\n", self->classname, vtos(self->s.origin)); + G_FreeEdict (self); + return; + } + + if ((self->spawnflags & 1) && (!self->count)) + self->count = 60*60;; + + func_clock_reset (self); + + self->message = gi.TagMalloc (CLOCK_MESSAGE_SIZE, TAG_LEVEL); + + self->think = func_clock_think; + + if (self->spawnflags & 4) + self->use = func_clock_use; + else + self->nextthink = level.time + 1; +} + +//================================================================================= + +void teleporter_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + edict_t *dest; + int i; + + if (!other->client) + return; + dest = G_Find (NULL, FOFS(targetname), self->target); + if (!dest) + { + gi.dprintf ("Couldn't find destination\n"); + return; + } + + // unlink to make sure it can't possibly interfere with KillBox + gi.unlinkentity (other); + + VectorCopy (dest->s.origin, other->s.origin); + VectorCopy (dest->s.origin, other->s.old_origin); + other->s.origin[2] += 10; + + // clear the velocity and hold them in place briefly + VectorClear (other->velocity); + other->client->ps.pmove.pm_time = 160>>3; // hold time + other->client->ps.pmove.pm_flags |= PMF_TIME_TELEPORT; + + // draw the teleport splash at source and on the player + self->owner->s.event = EV_PLAYER_TELEPORT; + other->s.event = EV_PLAYER_TELEPORT; + + // set angles + for (i=0 ; i<3 ; i++) + { + other->client->ps.pmove.delta_angles[i] = ANGLE2SHORT(dest->s.angles[i] - other->client->resp.cmd_angles[i]); + } + + VectorClear (other->s.angles); + VectorClear (other->client->ps.viewangles); + VectorClear (other->client->v_angle); + + // kill anything at the destination + KillBox (other); + + gi.linkentity (other); +} + +/*QUAKED misc_teleporter (1 0 0) (-32 -32 -24) (32 32 -16) +Stepping onto this disc will teleport players to the targeted misc_teleporter_dest object. +*/ +void SP_misc_teleporter (edict_t *ent) +{ + edict_t *trig; + + if (!ent->target) + { + gi.dprintf ("teleporter without a target.\n"); + G_FreeEdict (ent); + return; + } + + gi.setmodel (ent, "models/objects/dmspot/tris.md2"); + ent->s.skinnum = 1; + ent->s.effects = EF_TELEPORTER; + ent->s.sound = gi.soundindex ("world/amb10.wav"); + ent->solid = SOLID_BBOX; + + VectorSet (ent->mins, -32, -32, -24); + VectorSet (ent->maxs, 32, 32, -16); + gi.linkentity (ent); + + trig = G_Spawn (); + trig->touch = teleporter_touch; + trig->solid = SOLID_TRIGGER; + trig->target = ent->target; + trig->owner = ent; + VectorCopy (ent->s.origin, trig->s.origin); + VectorSet (trig->mins, -8, -8, 8); + VectorSet (trig->maxs, 8, 8, 24); + gi.linkentity (trig); + +} + +/*QUAKED misc_teleporter_dest (1 0 0) (-32 -32 -24) (32 32 -16) +Point teleporters at these. +*/ +void SP_misc_teleporter_dest (edict_t *ent) +{ + gi.setmodel (ent, "models/objects/dmspot/tris.md2"); + ent->s.skinnum = 0; + ent->solid = SOLID_BBOX; +// ent->s.effects |= EF_FLIES; + VectorSet (ent->mins, -32, -32, -24); + VectorSet (ent->maxs, 32, 32, -16); + gi.linkentity (ent); +} + diff --git a/original/baseq2/g_monster.c b/original/baseq2/g_monster.c new file mode 100644 index 0000000..c7b954b --- /dev/null +++ b/original/baseq2/g_monster.c @@ -0,0 +1,740 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +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. + +*/ +#include "g_local.h" + + +// +// monster weapons +// + +//FIXME mosnters should call these with a totally accurate direction +// and we can mess it up based on skill. Spread should be for normal +// and we can tighten or loosen based on skill. We could muck with +// the damages too, but I'm not sure that's such a good idea. +void monster_fire_bullet (edict_t *self, vec3_t start, vec3_t dir, int damage, int kick, int hspread, int vspread, int flashtype) +{ + fire_bullet (self, start, dir, damage, kick, hspread, vspread, MOD_UNKNOWN); + + gi.WriteByte (svc_muzzleflash2); + gi.WriteShort (self - g_edicts); + gi.WriteByte (flashtype); + gi.multicast (start, MULTICAST_PVS); +} + +void monster_fire_shotgun (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int hspread, int vspread, int count, int flashtype) +{ + fire_shotgun (self, start, aimdir, damage, kick, hspread, vspread, count, MOD_UNKNOWN); + + gi.WriteByte (svc_muzzleflash2); + gi.WriteShort (self - g_edicts); + gi.WriteByte (flashtype); + gi.multicast (start, MULTICAST_PVS); +} + +void monster_fire_blaster (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int flashtype, int effect) +{ + fire_blaster (self, start, dir, damage, speed, effect, false); + + gi.WriteByte (svc_muzzleflash2); + gi.WriteShort (self - g_edicts); + gi.WriteByte (flashtype); + gi.multicast (start, MULTICAST_PVS); +} + +void monster_fire_grenade (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, int flashtype) +{ + fire_grenade (self, start, aimdir, damage, speed, 2.5, damage+40); + + gi.WriteByte (svc_muzzleflash2); + gi.WriteShort (self - g_edicts); + gi.WriteByte (flashtype); + gi.multicast (start, MULTICAST_PVS); +} + +void monster_fire_rocket (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int flashtype) +{ + fire_rocket (self, start, dir, damage, speed, damage+20, damage); + + gi.WriteByte (svc_muzzleflash2); + gi.WriteShort (self - g_edicts); + gi.WriteByte (flashtype); + gi.multicast (start, MULTICAST_PVS); +} + +void monster_fire_railgun (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int flashtype) +{ + fire_rail (self, start, aimdir, damage, kick); + + gi.WriteByte (svc_muzzleflash2); + gi.WriteShort (self - g_edicts); + gi.WriteByte (flashtype); + gi.multicast (start, MULTICAST_PVS); +} + +void monster_fire_bfg (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, int kick, float damage_radius, int flashtype) +{ + fire_bfg (self, start, aimdir, damage, speed, damage_radius); + + gi.WriteByte (svc_muzzleflash2); + gi.WriteShort (self - g_edicts); + gi.WriteByte (flashtype); + gi.multicast (start, MULTICAST_PVS); +} + + + +// +// Monster utility functions +// + +static void M_FliesOff (edict_t *self) +{ + self->s.effects &= ~EF_FLIES; + self->s.sound = 0; +} + +static void M_FliesOn (edict_t *self) +{ + if (self->waterlevel) + return; + self->s.effects |= EF_FLIES; + self->s.sound = gi.soundindex ("infantry/inflies1.wav"); + self->think = M_FliesOff; + self->nextthink = level.time + 60; +} + +void M_FlyCheck (edict_t *self) +{ + if (self->waterlevel) + return; + + if (random() > 0.5) + return; + + self->think = M_FliesOn; + self->nextthink = level.time + 5 + 10 * random(); +} + +void AttackFinished (edict_t *self, float time) +{ + self->monsterinfo.attack_finished = level.time + time; +} + + +void M_CheckGround (edict_t *ent) +{ + vec3_t point; + trace_t trace; + + if (ent->flags & (FL_SWIM|FL_FLY)) + return; + + if (ent->velocity[2] > 100) + { + ent->groundentity = NULL; + return; + } + +// if the hull point one-quarter unit down is solid the entity is on ground + point[0] = ent->s.origin[0]; + point[1] = ent->s.origin[1]; + point[2] = ent->s.origin[2] - 0.25; + + trace = gi.trace (ent->s.origin, ent->mins, ent->maxs, point, ent, MASK_MONSTERSOLID); + + // check steepness + if ( trace.plane.normal[2] < 0.7 && !trace.startsolid) + { + ent->groundentity = NULL; + return; + } + +// ent->groundentity = trace.ent; +// ent->groundentity_linkcount = trace.ent->linkcount; +// if (!trace.startsolid && !trace.allsolid) +// VectorCopy (trace.endpos, ent->s.origin); + if (!trace.startsolid && !trace.allsolid) + { + VectorCopy (trace.endpos, ent->s.origin); + ent->groundentity = trace.ent; + ent->groundentity_linkcount = trace.ent->linkcount; + ent->velocity[2] = 0; + } +} + + +void M_CatagorizePosition (edict_t *ent) +{ + vec3_t point; + int cont; + +// +// get waterlevel +// + point[0] = ent->s.origin[0]; + point[1] = ent->s.origin[1]; + point[2] = ent->s.origin[2] + ent->mins[2] + 1; + cont = gi.pointcontents (point); + + if (!(cont & MASK_WATER)) + { + ent->waterlevel = 0; + ent->watertype = 0; + return; + } + + ent->watertype = cont; + ent->waterlevel = 1; + point[2] += 26; + cont = gi.pointcontents (point); + if (!(cont & MASK_WATER)) + return; + + ent->waterlevel = 2; + point[2] += 22; + cont = gi.pointcontents (point); + if (cont & MASK_WATER) + ent->waterlevel = 3; +} + + +void M_WorldEffects (edict_t *ent) +{ + int dmg; + + if (ent->health > 0) + { + if (!(ent->flags & FL_SWIM)) + { + if (ent->waterlevel < 3) + { + ent->air_finished = level.time + 12; + } + else if (ent->air_finished < level.time) + { // drown! + if (ent->pain_debounce_time < level.time) + { + dmg = 2 + 2 * floor(level.time - ent->air_finished); + if (dmg > 15) + dmg = 15; + T_Damage (ent, world, world, vec3_origin, ent->s.origin, vec3_origin, dmg, 0, DAMAGE_NO_ARMOR, MOD_WATER); + ent->pain_debounce_time = level.time + 1; + } + } + } + else + { + if (ent->waterlevel > 0) + { + ent->air_finished = level.time + 9; + } + else if (ent->air_finished < level.time) + { // suffocate! + if (ent->pain_debounce_time < level.time) + { + dmg = 2 + 2 * floor(level.time - ent->air_finished); + if (dmg > 15) + dmg = 15; + T_Damage (ent, world, world, vec3_origin, ent->s.origin, vec3_origin, dmg, 0, DAMAGE_NO_ARMOR, MOD_WATER); + ent->pain_debounce_time = level.time + 1; + } + } + } + } + + if (ent->waterlevel == 0) + { + if (ent->flags & FL_INWATER) + { + gi.sound (ent, CHAN_BODY, gi.soundindex("player/watr_out.wav"), 1, ATTN_NORM, 0); + ent->flags &= ~FL_INWATER; + } + return; + } + + if ((ent->watertype & CONTENTS_LAVA) && !(ent->flags & FL_IMMUNE_LAVA)) + { + if (ent->damage_debounce_time < level.time) + { + ent->damage_debounce_time = level.time + 0.2; + T_Damage (ent, world, world, vec3_origin, ent->s.origin, vec3_origin, 10*ent->waterlevel, 0, 0, MOD_LAVA); + } + } + if ((ent->watertype & CONTENTS_SLIME) && !(ent->flags & FL_IMMUNE_SLIME)) + { + if (ent->damage_debounce_time < level.time) + { + ent->damage_debounce_time = level.time + 1; + T_Damage (ent, world, world, vec3_origin, ent->s.origin, vec3_origin, 4*ent->waterlevel, 0, 0, MOD_SLIME); + } + } + + if ( !(ent->flags & FL_INWATER) ) + { + if (!(ent->svflags & SVF_DEADMONSTER)) + { + if (ent->watertype & CONTENTS_LAVA) + if (random() <= 0.5) + gi.sound (ent, CHAN_BODY, gi.soundindex("player/lava1.wav"), 1, ATTN_NORM, 0); + else + gi.sound (ent, CHAN_BODY, gi.soundindex("player/lava2.wav"), 1, ATTN_NORM, 0); + else if (ent->watertype & CONTENTS_SLIME) + gi.sound (ent, CHAN_BODY, gi.soundindex("player/watr_in.wav"), 1, ATTN_NORM, 0); + else if (ent->watertype & CONTENTS_WATER) + gi.sound (ent, CHAN_BODY, gi.soundindex("player/watr_in.wav"), 1, ATTN_NORM, 0); + } + + ent->flags |= FL_INWATER; + ent->damage_debounce_time = 0; + } +} + + +void M_droptofloor (edict_t *ent) +{ + vec3_t end; + trace_t trace; + + ent->s.origin[2] += 1; + VectorCopy (ent->s.origin, end); + end[2] -= 256; + + trace = gi.trace (ent->s.origin, ent->mins, ent->maxs, end, ent, MASK_MONSTERSOLID); + + if (trace.fraction == 1 || trace.allsolid) + return; + + VectorCopy (trace.endpos, ent->s.origin); + + gi.linkentity (ent); + M_CheckGround (ent); + M_CatagorizePosition (ent); +} + + +void M_SetEffects (edict_t *ent) +{ + ent->s.effects &= ~(EF_COLOR_SHELL|EF_POWERSCREEN); + ent->s.renderfx &= ~(RF_SHELL_RED|RF_SHELL_GREEN|RF_SHELL_BLUE); + + if (ent->monsterinfo.aiflags & AI_RESURRECTING) + { + ent->s.effects |= EF_COLOR_SHELL; + ent->s.renderfx |= RF_SHELL_RED; + } + + if (ent->health <= 0) + return; + + if (ent->powerarmor_time > level.time) + { + if (ent->monsterinfo.power_armor_type == POWER_ARMOR_SCREEN) + { + ent->s.effects |= EF_POWERSCREEN; + } + else if (ent->monsterinfo.power_armor_type == POWER_ARMOR_SHIELD) + { + ent->s.effects |= EF_COLOR_SHELL; + ent->s.renderfx |= RF_SHELL_GREEN; + } + } +} + + +void M_MoveFrame (edict_t *self) +{ + mmove_t *move; + int index; + + move = self->monsterinfo.currentmove; + self->nextthink = level.time + FRAMETIME; + + if ((self->monsterinfo.nextframe) && (self->monsterinfo.nextframe >= move->firstframe) && (self->monsterinfo.nextframe <= move->lastframe)) + { + self->s.frame = self->monsterinfo.nextframe; + self->monsterinfo.nextframe = 0; + } + else + { + if (self->s.frame == move->lastframe) + { + if (move->endfunc) + { + move->endfunc (self); + + // regrab move, endfunc is very likely to change it + move = self->monsterinfo.currentmove; + + // check for death + if (self->svflags & SVF_DEADMONSTER) + return; + } + } + + if (self->s.frame < move->firstframe || self->s.frame > move->lastframe) + { + self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; + self->s.frame = move->firstframe; + } + else + { + if (!(self->monsterinfo.aiflags & AI_HOLD_FRAME)) + { + self->s.frame++; + if (self->s.frame > move->lastframe) + self->s.frame = move->firstframe; + } + } + } + + index = self->s.frame - move->firstframe; + if (move->frame[index].aifunc) + if (!(self->monsterinfo.aiflags & AI_HOLD_FRAME)) + move->frame[index].aifunc (self, move->frame[index].dist * self->monsterinfo.scale); + else + move->frame[index].aifunc (self, 0); + + if (move->frame[index].thinkfunc) + move->frame[index].thinkfunc (self); +} + + +void monster_think (edict_t *self) +{ + M_MoveFrame (self); + if (self->linkcount != self->monsterinfo.linkcount) + { + self->monsterinfo.linkcount = self->linkcount; + M_CheckGround (self); + } + M_CatagorizePosition (self); + M_WorldEffects (self); + M_SetEffects (self); +} + + +/* +================ +monster_use + +Using a monster makes it angry at the current activator +================ +*/ +void monster_use (edict_t *self, edict_t *other, edict_t *activator) +{ + if (self->enemy) + return; + if (self->health <= 0) + return; + if (activator->flags & FL_NOTARGET) + return; + if (!(activator->client) && !(activator->monsterinfo.aiflags & AI_GOOD_GUY)) + return; + +// delay reaction so if the monster is teleported, its sound is still heard + self->enemy = activator; + FoundTarget (self); +} + + +void monster_start_go (edict_t *self); + + +void monster_triggered_spawn (edict_t *self) +{ + self->s.origin[2] += 1; + KillBox (self); + + self->solid = SOLID_BBOX; + self->movetype = MOVETYPE_STEP; + self->svflags &= ~SVF_NOCLIENT; + self->air_finished = level.time + 12; + gi.linkentity (self); + + monster_start_go (self); + + if (self->enemy && !(self->spawnflags & 1) && !(self->enemy->flags & FL_NOTARGET)) + { + FoundTarget (self); + } + else + { + self->enemy = NULL; + } +} + +void monster_triggered_spawn_use (edict_t *self, edict_t *other, edict_t *activator) +{ + // we have a one frame delay here so we don't telefrag the guy who activated us + self->think = monster_triggered_spawn; + self->nextthink = level.time + FRAMETIME; + if (activator->client) + self->enemy = activator; + self->use = monster_use; +} + +void monster_triggered_start (edict_t *self) +{ + self->solid = SOLID_NOT; + self->movetype = MOVETYPE_NONE; + self->svflags |= SVF_NOCLIENT; + self->nextthink = 0; + self->use = monster_triggered_spawn_use; +} + + +/* +================ +monster_death_use + +When a monster dies, it fires all of its targets with the current +enemy as activator. +================ +*/ +void monster_death_use (edict_t *self) +{ + self->flags &= ~(FL_FLY|FL_SWIM); + self->monsterinfo.aiflags &= AI_GOOD_GUY; + + if (self->item) + { + Drop_Item (self, self->item); + self->item = NULL; + } + + if (self->deathtarget) + self->target = self->deathtarget; + + if (!self->target) + return; + + G_UseTargets (self, self->enemy); +} + + +//============================================================================ + +qboolean monster_start (edict_t *self) +{ + if (deathmatch->value) + { + G_FreeEdict (self); + return false; + } + + if ((self->spawnflags & 4) && !(self->monsterinfo.aiflags & AI_GOOD_GUY)) + { + self->spawnflags &= ~4; + self->spawnflags |= 1; +// gi.dprintf("fixed spawnflags on %s at %s\n", self->classname, vtos(self->s.origin)); + } + + if (!(self->monsterinfo.aiflags & AI_GOOD_GUY)) + level.total_monsters++; + + self->nextthink = level.time + FRAMETIME; + self->svflags |= SVF_MONSTER; + self->s.renderfx |= RF_FRAMELERP; + self->takedamage = DAMAGE_AIM; + self->air_finished = level.time + 12; + self->use = monster_use; + self->max_health = self->health; + self->clipmask = MASK_MONSTERSOLID; + + self->s.skinnum = 0; + self->deadflag = DEAD_NO; + self->svflags &= ~SVF_DEADMONSTER; + + if (!self->monsterinfo.checkattack) + self->monsterinfo.checkattack = M_CheckAttack; + VectorCopy (self->s.origin, self->s.old_origin); + + if (st.item) + { + self->item = FindItemByClassname (st.item); + if (!self->item) + gi.dprintf("%s at %s has bad item: %s\n", self->classname, vtos(self->s.origin), st.item); + } + + // randomize what frame they start on + if (self->monsterinfo.currentmove) + self->s.frame = self->monsterinfo.currentmove->firstframe + (rand() % (self->monsterinfo.currentmove->lastframe - self->monsterinfo.currentmove->firstframe + 1)); + + return true; +} + +void monster_start_go (edict_t *self) +{ + vec3_t v; + + if (self->health <= 0) + return; + + // check for target to combat_point and change to combattarget + if (self->target) + { + qboolean notcombat; + qboolean fixup; + edict_t *target; + + target = NULL; + notcombat = false; + fixup = false; + while ((target = G_Find (target, FOFS(targetname), self->target)) != NULL) + { + if (strcmp(target->classname, "point_combat") == 0) + { + self->combattarget = self->target; + fixup = true; + } + else + { + notcombat = true; + } + } + if (notcombat && self->combattarget) + gi.dprintf("%s at %s has target with mixed types\n", self->classname, vtos(self->s.origin)); + if (fixup) + self->target = NULL; + } + + // validate combattarget + if (self->combattarget) + { + edict_t *target; + + target = NULL; + while ((target = G_Find (target, FOFS(targetname), self->combattarget)) != NULL) + { + if (strcmp(target->classname, "point_combat") != 0) + { + gi.dprintf("%s at (%i %i %i) has a bad combattarget %s : %s at (%i %i %i)\n", + self->classname, (int)self->s.origin[0], (int)self->s.origin[1], (int)self->s.origin[2], + self->combattarget, target->classname, (int)target->s.origin[0], (int)target->s.origin[1], + (int)target->s.origin[2]); + } + } + } + + if (self->target) + { + self->goalentity = self->movetarget = G_PickTarget(self->target); + if (!self->movetarget) + { + gi.dprintf ("%s can't find target %s at %s\n", self->classname, self->target, vtos(self->s.origin)); + self->target = NULL; + self->monsterinfo.pausetime = 100000000; + self->monsterinfo.stand (self); + } + else if (strcmp (self->movetarget->classname, "path_corner") == 0) + { + VectorSubtract (self->goalentity->s.origin, self->s.origin, v); + self->ideal_yaw = self->s.angles[YAW] = vectoyaw(v); + self->monsterinfo.walk (self); + self->target = NULL; + } + else + { + self->goalentity = self->movetarget = NULL; + self->monsterinfo.pausetime = 100000000; + self->monsterinfo.stand (self); + } + } + else + { + self->monsterinfo.pausetime = 100000000; + self->monsterinfo.stand (self); + } + + self->think = monster_think; + self->nextthink = level.time + FRAMETIME; +} + + +void walkmonster_start_go (edict_t *self) +{ + if (!(self->spawnflags & 2) && level.time < 1) + { + M_droptofloor (self); + + if (self->groundentity) + if (!M_walkmove (self, 0, 0)) + gi.dprintf ("%s in solid at %s\n", self->classname, vtos(self->s.origin)); + } + + if (!self->yaw_speed) + self->yaw_speed = 20; + self->viewheight = 25; + + monster_start_go (self); + + if (self->spawnflags & 2) + monster_triggered_start (self); +} + +void walkmonster_start (edict_t *self) +{ + self->think = walkmonster_start_go; + monster_start (self); +} + + +void flymonster_start_go (edict_t *self) +{ + if (!M_walkmove (self, 0, 0)) + gi.dprintf ("%s in solid at %s\n", self->classname, vtos(self->s.origin)); + + if (!self->yaw_speed) + self->yaw_speed = 10; + self->viewheight = 25; + + monster_start_go (self); + + if (self->spawnflags & 2) + monster_triggered_start (self); +} + + +void flymonster_start (edict_t *self) +{ + self->flags |= FL_FLY; + self->think = flymonster_start_go; + monster_start (self); +} + + +void swimmonster_start_go (edict_t *self) +{ + if (!self->yaw_speed) + self->yaw_speed = 10; + self->viewheight = 10; + + monster_start_go (self); + + if (self->spawnflags & 2) + monster_triggered_start (self); +} + +void swimmonster_start (edict_t *self) +{ + self->flags |= FL_SWIM; + self->think = swimmonster_start_go; + monster_start (self); +} diff --git a/original/baseq2/g_phys.c b/original/baseq2/g_phys.c new file mode 100644 index 0000000..8cbd1bc --- /dev/null +++ b/original/baseq2/g_phys.c @@ -0,0 +1,961 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +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. + +*/ +// g_phys.c + +#include "g_local.h" + +/* + + +pushmove objects do not obey gravity, and do not interact with each other or trigger fields, but block normal movement and push normal objects when they move. + +onground is set for toss objects when they come to a complete rest. it is set for steping or walking objects + +doors, plats, etc are SOLID_BSP, and MOVETYPE_PUSH +bonus items are SOLID_TRIGGER touch, and MOVETYPE_TOSS +corpses are SOLID_NOT and MOVETYPE_TOSS +crates are SOLID_BBOX and MOVETYPE_TOSS +walking monsters are SOLID_SLIDEBOX and MOVETYPE_STEP +flying/floating monsters are SOLID_SLIDEBOX and MOVETYPE_FLY + +solid_edge items only clip against bsp models. + +*/ + + +/* +============ +SV_TestEntityPosition + +============ +*/ +edict_t *SV_TestEntityPosition (edict_t *ent) +{ + trace_t trace; + int mask; + + if (ent->clipmask) + mask = ent->clipmask; + else + mask = MASK_SOLID; + trace = gi.trace (ent->s.origin, ent->mins, ent->maxs, ent->s.origin, ent, mask); + + if (trace.startsolid) + return g_edicts; + + return NULL; +} + + +/* +================ +SV_CheckVelocity +================ +*/ +void SV_CheckVelocity (edict_t *ent) +{ + int i; + +// +// bound velocity +// + for (i=0 ; i<3 ; i++) + { + if (ent->velocity[i] > sv_maxvelocity->value) + ent->velocity[i] = sv_maxvelocity->value; + else if (ent->velocity[i] < -sv_maxvelocity->value) + ent->velocity[i] = -sv_maxvelocity->value; + } +} + +/* +============= +SV_RunThink + +Runs thinking code for this frame if necessary +============= +*/ +qboolean SV_RunThink (edict_t *ent) +{ + float thinktime; + + thinktime = ent->nextthink; + if (thinktime <= 0) + return true; + if (thinktime > level.time+0.001) + return true; + + ent->nextthink = 0; + if (!ent->think) + gi.error ("NULL ent->think"); + ent->think (ent); + + return false; +} + +/* +================== +SV_Impact + +Two entities have touched, so run their touch functions +================== +*/ +void SV_Impact (edict_t *e1, trace_t *trace) +{ + edict_t *e2; +// cplane_t backplane; + + e2 = trace->ent; + + if (e1->touch && e1->solid != SOLID_NOT) + e1->touch (e1, e2, &trace->plane, trace->surface); + + if (e2->touch && e2->solid != SOLID_NOT) + e2->touch (e2, e1, NULL, NULL); +} + + +/* +================== +ClipVelocity + +Slide off of the impacting object +returns the blocked flags (1 = floor, 2 = step / wall) +================== +*/ +#define STOP_EPSILON 0.1 + +int ClipVelocity (vec3_t in, vec3_t normal, vec3_t out, float overbounce) +{ + float backoff; + float change; + int i, blocked; + + blocked = 0; + if (normal[2] > 0) + blocked |= 1; // floor + if (!normal[2]) + blocked |= 2; // step + + backoff = DotProduct (in, normal) * overbounce; + + for (i=0 ; i<3 ; i++) + { + change = normal[i]*backoff; + out[i] = in[i] - change; + if (out[i] > -STOP_EPSILON && out[i] < STOP_EPSILON) + out[i] = 0; + } + + return blocked; +} + + +/* +============ +SV_FlyMove + +The basic solid body movement clip that slides along multiple planes +Returns the clipflags if the velocity was modified (hit something solid) +1 = floor +2 = wall / step +4 = dead stop +============ +*/ +#define MAX_CLIP_PLANES 5 +int SV_FlyMove (edict_t *ent, float time, int mask) +{ + edict_t *hit; + int bumpcount, numbumps; + vec3_t dir; + float d; + int numplanes; + vec3_t planes[MAX_CLIP_PLANES]; + vec3_t primal_velocity, original_velocity, new_velocity; + int i, j; + trace_t trace; + vec3_t end; + float time_left; + int blocked; + + numbumps = 4; + + blocked = 0; + VectorCopy (ent->velocity, original_velocity); + VectorCopy (ent->velocity, primal_velocity); + numplanes = 0; + + time_left = time; + + ent->groundentity = NULL; + for (bumpcount=0 ; bumpcounts.origin[i] + time_left * ent->velocity[i]; + + trace = gi.trace (ent->s.origin, ent->mins, ent->maxs, end, ent, mask); + + if (trace.allsolid) + { // entity is trapped in another solid + VectorCopy (vec3_origin, ent->velocity); + return 3; + } + + if (trace.fraction > 0) + { // actually covered some distance + VectorCopy (trace.endpos, ent->s.origin); + VectorCopy (ent->velocity, original_velocity); + numplanes = 0; + } + + if (trace.fraction == 1) + break; // moved the entire distance + + hit = trace.ent; + + if (trace.plane.normal[2] > 0.7) + { + blocked |= 1; // floor + if ( hit->solid == SOLID_BSP) + { + ent->groundentity = hit; + ent->groundentity_linkcount = hit->linkcount; + } + } + if (!trace.plane.normal[2]) + { + blocked |= 2; // step + } + +// +// run the impact function +// + SV_Impact (ent, &trace); + if (!ent->inuse) + break; // removed by the impact function + + + time_left -= time_left * trace.fraction; + + // cliped to another plane + if (numplanes >= MAX_CLIP_PLANES) + { // this shouldn't really happen + VectorCopy (vec3_origin, ent->velocity); + return 3; + } + + VectorCopy (trace.plane.normal, planes[numplanes]); + numplanes++; + +// +// modify original_velocity so it parallels all of the clip planes +// + for (i=0 ; ivelocity); + } + else + { // go along the crease + if (numplanes != 2) + { +// gi.dprintf ("clip velocity, numplanes == %i\n",numplanes); + VectorCopy (vec3_origin, ent->velocity); + return 7; + } + CrossProduct (planes[0], planes[1], dir); + d = DotProduct (dir, ent->velocity); + VectorScale (dir, d, ent->velocity); + } + +// +// if original velocity is against the original velocity, stop dead +// to avoid tiny occilations in sloping corners +// + if (DotProduct (ent->velocity, primal_velocity) <= 0) + { + VectorCopy (vec3_origin, ent->velocity); + return blocked; + } + } + + return blocked; +} + + +/* +============ +SV_AddGravity + +============ +*/ +void SV_AddGravity (edict_t *ent) +{ + ent->velocity[2] -= ent->gravity * sv_gravity->value * FRAMETIME; +} + +/* +=============================================================================== + +PUSHMOVE + +=============================================================================== +*/ + +/* +============ +SV_PushEntity + +Does not change the entities velocity at all +============ +*/ +trace_t SV_PushEntity (edict_t *ent, vec3_t push) +{ + trace_t trace; + vec3_t start; + vec3_t end; + int mask; + + VectorCopy (ent->s.origin, start); + VectorAdd (start, push, end); + +retry: + if (ent->clipmask) + mask = ent->clipmask; + else + mask = MASK_SOLID; + + trace = gi.trace (start, ent->mins, ent->maxs, end, ent, mask); + + VectorCopy (trace.endpos, ent->s.origin); + gi.linkentity (ent); + + if (trace.fraction != 1.0) + { + SV_Impact (ent, &trace); + + // if the pushed entity went away and the pusher is still there + if (!trace.ent->inuse && ent->inuse) + { + // move the pusher back and try again + VectorCopy (start, ent->s.origin); + gi.linkentity (ent); + goto retry; + } + } + + if (ent->inuse) + G_TouchTriggers (ent); + + return trace; +} + + +typedef struct +{ + edict_t *ent; + vec3_t origin; + vec3_t angles; + float deltayaw; +} pushed_t; +pushed_t pushed[MAX_EDICTS], *pushed_p; + +edict_t *obstacle; + +/* +============ +SV_Push + +Objects need to be moved back on a failed push, +otherwise riders would continue to slide. +============ +*/ +qboolean SV_Push (edict_t *pusher, vec3_t move, vec3_t amove) +{ + int i, e; + edict_t *check, *block; + vec3_t mins, maxs; + pushed_t *p; + vec3_t org, org2, move2, forward, right, up; + + // clamp the move to 1/8 units, so the position will + // be accurate for client side prediction + for (i=0 ; i<3 ; i++) + { + float temp; + temp = move[i]*8.0; + if (temp > 0.0) + temp += 0.5; + else + temp -= 0.5; + move[i] = 0.125 * (int)temp; + } + + // find the bounding box + for (i=0 ; i<3 ; i++) + { + mins[i] = pusher->absmin[i] + move[i]; + maxs[i] = pusher->absmax[i] + move[i]; + } + +// we need this for pushing things later + VectorSubtract (vec3_origin, amove, org); + AngleVectors (org, forward, right, up); + +// save the pusher's original position + pushed_p->ent = pusher; + VectorCopy (pusher->s.origin, pushed_p->origin); + VectorCopy (pusher->s.angles, pushed_p->angles); + if (pusher->client) + pushed_p->deltayaw = pusher->client->ps.pmove.delta_angles[YAW]; + pushed_p++; + +// move the pusher to it's final position + VectorAdd (pusher->s.origin, move, pusher->s.origin); + VectorAdd (pusher->s.angles, amove, pusher->s.angles); + gi.linkentity (pusher); + +// see if any solid entities are inside the final position + check = g_edicts+1; + for (e = 1; e < globals.num_edicts; e++, check++) + { + if (!check->inuse) + continue; + if (check->movetype == MOVETYPE_PUSH + || check->movetype == MOVETYPE_STOP + || check->movetype == MOVETYPE_NONE + || check->movetype == MOVETYPE_NOCLIP) + continue; + + if (!check->area.prev) + continue; // not linked in anywhere + + // if the entity is standing on the pusher, it will definitely be moved + if (check->groundentity != pusher) + { + // see if the ent needs to be tested + if ( check->absmin[0] >= maxs[0] + || check->absmin[1] >= maxs[1] + || check->absmin[2] >= maxs[2] + || check->absmax[0] <= mins[0] + || check->absmax[1] <= mins[1] + || check->absmax[2] <= mins[2] ) + continue; + + // see if the ent's bbox is inside the pusher's final position + if (!SV_TestEntityPosition (check)) + continue; + } + + if ((pusher->movetype == MOVETYPE_PUSH) || (check->groundentity == pusher)) + { + // move this entity + pushed_p->ent = check; + VectorCopy (check->s.origin, pushed_p->origin); + VectorCopy (check->s.angles, pushed_p->angles); + pushed_p++; + + // try moving the contacted entity + VectorAdd (check->s.origin, move, check->s.origin); + if (check->client) + { // FIXME: doesn't rotate monsters? + check->client->ps.pmove.delta_angles[YAW] += amove[YAW]; + } + + // figure movement due to the pusher's amove + VectorSubtract (check->s.origin, pusher->s.origin, org); + org2[0] = DotProduct (org, forward); + org2[1] = -DotProduct (org, right); + org2[2] = DotProduct (org, up); + VectorSubtract (org2, org, move2); + VectorAdd (check->s.origin, move2, check->s.origin); + + // may have pushed them off an edge + if (check->groundentity != pusher) + check->groundentity = NULL; + + block = SV_TestEntityPosition (check); + if (!block) + { // pushed ok + gi.linkentity (check); + // impact? + continue; + } + + // if it is ok to leave in the old position, do it + // this is only relevent for riding entities, not pushed + // FIXME: this doesn't acount for rotation + VectorSubtract (check->s.origin, move, check->s.origin); + block = SV_TestEntityPosition (check); + if (!block) + { + pushed_p--; + continue; + } + } + + // save off the obstacle so we can call the block function + obstacle = check; + + // move back any entities we already moved + // go backwards, so if the same entity was pushed + // twice, it goes back to the original position + for (p=pushed_p-1 ; p>=pushed ; p--) + { + VectorCopy (p->origin, p->ent->s.origin); + VectorCopy (p->angles, p->ent->s.angles); + if (p->ent->client) + { + p->ent->client->ps.pmove.delta_angles[YAW] = p->deltayaw; + } + gi.linkentity (p->ent); + } + return false; + } + +//FIXME: is there a better way to handle this? + // see if anything we moved has touched a trigger + for (p=pushed_p-1 ; p>=pushed ; p--) + G_TouchTriggers (p->ent); + + return true; +} + +/* +================ +SV_Physics_Pusher + +Bmodel objects don't interact with each other, but +push all box objects +================ +*/ +void SV_Physics_Pusher (edict_t *ent) +{ + vec3_t move, amove; + edict_t *part, *mv; + + // if not a team captain, so movement will be handled elsewhere + if ( ent->flags & FL_TEAMSLAVE) + return; + + // make sure all team slaves can move before commiting + // any moves or calling any think functions + // if the move is blocked, all moved objects will be backed out +//retry: + pushed_p = pushed; + for (part = ent ; part ; part=part->teamchain) + { + if (part->velocity[0] || part->velocity[1] || part->velocity[2] || + part->avelocity[0] || part->avelocity[1] || part->avelocity[2] + ) + { // object is moving + VectorScale (part->velocity, FRAMETIME, move); + VectorScale (part->avelocity, FRAMETIME, amove); + + if (!SV_Push (part, move, amove)) + break; // move was blocked + } + } + if (pushed_p > &pushed[MAX_EDICTS]) + gi.error (ERR_FATAL, "pushed_p > &pushed[MAX_EDICTS], memory corrupted"); + + if (part) + { + // the move failed, bump all nextthink times and back out moves + for (mv = ent ; mv ; mv=mv->teamchain) + { + if (mv->nextthink > 0) + mv->nextthink += FRAMETIME; + } + + // if the pusher has a "blocked" function, call it + // otherwise, just stay in place until the obstacle is gone + if (part->blocked) + part->blocked (part, obstacle); +#if 0 + // if the pushed entity went away and the pusher is still there + if (!obstacle->inuse && part->inuse) + goto retry; +#endif + } + else + { + // the move succeeded, so call all think functions + for (part = ent ; part ; part=part->teamchain) + { + SV_RunThink (part); + } + } +} + +//================================================================== + +/* +============= +SV_Physics_None + +Non moving objects can only think +============= +*/ +void SV_Physics_None (edict_t *ent) +{ +// regular thinking + SV_RunThink (ent); +} + +/* +============= +SV_Physics_Noclip + +A moving object that doesn't obey physics +============= +*/ +void SV_Physics_Noclip (edict_t *ent) +{ +// regular thinking + if (!SV_RunThink (ent)) + return; + + VectorMA (ent->s.angles, FRAMETIME, ent->avelocity, ent->s.angles); + VectorMA (ent->s.origin, FRAMETIME, ent->velocity, ent->s.origin); + + gi.linkentity (ent); +} + +/* +============================================================================== + +TOSS / BOUNCE + +============================================================================== +*/ + +/* +============= +SV_Physics_Toss + +Toss, bounce, and fly movement. When onground, do nothing. +============= +*/ +void SV_Physics_Toss (edict_t *ent) +{ + trace_t trace; + vec3_t move; + float backoff; + edict_t *slave; + qboolean wasinwater; + qboolean isinwater; + vec3_t old_origin; + +// regular thinking + SV_RunThink (ent); + + // if not a team captain, so movement will be handled elsewhere + if ( ent->flags & FL_TEAMSLAVE) + return; + + if (ent->velocity[2] > 0) + ent->groundentity = NULL; + +// check for the groundentity going away + if (ent->groundentity) + if (!ent->groundentity->inuse) + ent->groundentity = NULL; + +// if onground, return without moving + if ( ent->groundentity ) + return; + + VectorCopy (ent->s.origin, old_origin); + + SV_CheckVelocity (ent); + +// add gravity + if (ent->movetype != MOVETYPE_FLY + && ent->movetype != MOVETYPE_FLYMISSILE) + SV_AddGravity (ent); + +// move angles + VectorMA (ent->s.angles, FRAMETIME, ent->avelocity, ent->s.angles); + +// move origin + VectorScale (ent->velocity, FRAMETIME, move); + trace = SV_PushEntity (ent, move); + if (!ent->inuse) + return; + + if (trace.fraction < 1) + { + if (ent->movetype == MOVETYPE_BOUNCE) + backoff = 1.5; + else + backoff = 1; + + ClipVelocity (ent->velocity, trace.plane.normal, ent->velocity, backoff); + + // stop if on ground + if (trace.plane.normal[2] > 0.7) + { + if (ent->velocity[2] < 60 || ent->movetype != MOVETYPE_BOUNCE ) + { + ent->groundentity = trace.ent; + ent->groundentity_linkcount = trace.ent->linkcount; + VectorCopy (vec3_origin, ent->velocity); + VectorCopy (vec3_origin, ent->avelocity); + } + } + +// if (ent->touch) +// ent->touch (ent, trace.ent, &trace.plane, trace.surface); + } + +// check for water transition + wasinwater = (ent->watertype & MASK_WATER); + ent->watertype = gi.pointcontents (ent->s.origin); + isinwater = ent->watertype & MASK_WATER; + + if (isinwater) + ent->waterlevel = 1; + else + ent->waterlevel = 0; + + if (!wasinwater && isinwater) + gi.positioned_sound (old_origin, g_edicts, CHAN_AUTO, gi.soundindex("misc/h2ohit1.wav"), 1, 1, 0); + else if (wasinwater && !isinwater) + gi.positioned_sound (ent->s.origin, g_edicts, CHAN_AUTO, gi.soundindex("misc/h2ohit1.wav"), 1, 1, 0); + +// move teamslaves + for (slave = ent->teamchain; slave; slave = slave->teamchain) + { + VectorCopy (ent->s.origin, slave->s.origin); + gi.linkentity (slave); + } +} + +/* +=============================================================================== + +STEPPING MOVEMENT + +=============================================================================== +*/ + +/* +============= +SV_Physics_Step + +Monsters freefall when they don't have a ground entity, otherwise +all movement is done with discrete steps. + +This is also used for objects that have become still on the ground, but +will fall if the floor is pulled out from under them. +FIXME: is this true? +============= +*/ + +//FIXME: hacked in for E3 demo +#define sv_stopspeed 100 +#define sv_friction 6 +#define sv_waterfriction 1 + +void SV_AddRotationalFriction (edict_t *ent) +{ + int n; + float adjustment; + + VectorMA (ent->s.angles, FRAMETIME, ent->avelocity, ent->s.angles); + adjustment = FRAMETIME * sv_stopspeed * sv_friction; + for (n = 0; n < 3; n++) + { + if (ent->avelocity[n] > 0) + { + ent->avelocity[n] -= adjustment; + if (ent->avelocity[n] < 0) + ent->avelocity[n] = 0; + } + else + { + ent->avelocity[n] += adjustment; + if (ent->avelocity[n] > 0) + ent->avelocity[n] = 0; + } + } +} + +void SV_Physics_Step (edict_t *ent) +{ + qboolean wasonground; + qboolean hitsound = false; + float *vel; + float speed, newspeed, control; + float friction; + edict_t *groundentity; + int mask; + + // airborn monsters should always check for ground + if (!ent->groundentity) + M_CheckGround (ent); + + groundentity = ent->groundentity; + + SV_CheckVelocity (ent); + + if (groundentity) + wasonground = true; + else + wasonground = false; + + if (ent->avelocity[0] || ent->avelocity[1] || ent->avelocity[2]) + SV_AddRotationalFriction (ent); + + // add gravity except: + // flying monsters + // swimming monsters who are in the water + if (! wasonground) + if (!(ent->flags & FL_FLY)) + if (!((ent->flags & FL_SWIM) && (ent->waterlevel > 2))) + { + if (ent->velocity[2] < sv_gravity->value*-0.1) + hitsound = true; + if (ent->waterlevel == 0) + SV_AddGravity (ent); + } + + // friction for flying monsters that have been given vertical velocity + if ((ent->flags & FL_FLY) && (ent->velocity[2] != 0)) + { + speed = fabs(ent->velocity[2]); + control = speed < sv_stopspeed ? sv_stopspeed : speed; + friction = sv_friction/3; + newspeed = speed - (FRAMETIME * control * friction); + if (newspeed < 0) + newspeed = 0; + newspeed /= speed; + ent->velocity[2] *= newspeed; + } + + // friction for flying monsters that have been given vertical velocity + if ((ent->flags & FL_SWIM) && (ent->velocity[2] != 0)) + { + speed = fabs(ent->velocity[2]); + control = speed < sv_stopspeed ? sv_stopspeed : speed; + newspeed = speed - (FRAMETIME * control * sv_waterfriction * ent->waterlevel); + if (newspeed < 0) + newspeed = 0; + newspeed /= speed; + ent->velocity[2] *= newspeed; + } + + if (ent->velocity[2] || ent->velocity[1] || ent->velocity[0]) + { + // apply friction + // let dead monsters who aren't completely onground slide + if ((wasonground) || (ent->flags & (FL_SWIM|FL_FLY))) + if (!(ent->health <= 0.0 && !M_CheckBottom(ent))) + { + vel = ent->velocity; + speed = sqrt(vel[0]*vel[0] +vel[1]*vel[1]); + if (speed) + { + friction = sv_friction; + + control = speed < sv_stopspeed ? sv_stopspeed : speed; + newspeed = speed - FRAMETIME*control*friction; + + if (newspeed < 0) + newspeed = 0; + newspeed /= speed; + + vel[0] *= newspeed; + vel[1] *= newspeed; + } + } + + if (ent->svflags & SVF_MONSTER) + mask = MASK_MONSTERSOLID; + else + mask = MASK_SOLID; + SV_FlyMove (ent, FRAMETIME, mask); + + gi.linkentity (ent); + G_TouchTriggers (ent); + if (!ent->inuse) + return; + + if (ent->groundentity) + if (!wasonground) + if (hitsound) + gi.sound (ent, 0, gi.soundindex("world/land.wav"), 1, 1, 0); + } + +// regular thinking + SV_RunThink (ent); +} + +//============================================================================ +/* +================ +G_RunEntity + +================ +*/ +void G_RunEntity (edict_t *ent) +{ + if (ent->prethink) + ent->prethink (ent); + + switch ( (int)ent->movetype) + { + case MOVETYPE_PUSH: + case MOVETYPE_STOP: + SV_Physics_Pusher (ent); + break; + case MOVETYPE_NONE: + SV_Physics_None (ent); + break; + case MOVETYPE_NOCLIP: + SV_Physics_Noclip (ent); + break; + case MOVETYPE_STEP: + SV_Physics_Step (ent); + break; + case MOVETYPE_TOSS: + case MOVETYPE_BOUNCE: + case MOVETYPE_FLY: + case MOVETYPE_FLYMISSILE: + SV_Physics_Toss (ent); + break; + default: + gi.error ("SV_Physics: bad movetype %i", (int)ent->movetype); + } +} diff --git a/original/baseq2/g_save.c b/original/baseq2/g_save.c new file mode 100644 index 0000000..957862d --- /dev/null +++ b/original/baseq2/g_save.c @@ -0,0 +1,770 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +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. + +*/ + +#include "g_local.h" + +#define Function(f) {#f, f} + +mmove_t mmove_reloc; + +field_t fields[] = { + {"classname", FOFS(classname), F_LSTRING}, + {"model", FOFS(model), F_LSTRING}, + {"spawnflags", FOFS(spawnflags), F_INT}, + {"speed", FOFS(speed), F_FLOAT}, + {"accel", FOFS(accel), F_FLOAT}, + {"decel", FOFS(decel), F_FLOAT}, + {"target", FOFS(target), F_LSTRING}, + {"targetname", FOFS(targetname), F_LSTRING}, + {"pathtarget", FOFS(pathtarget), F_LSTRING}, + {"deathtarget", FOFS(deathtarget), F_LSTRING}, + {"killtarget", FOFS(killtarget), F_LSTRING}, + {"combattarget", FOFS(combattarget), F_LSTRING}, + {"message", FOFS(message), F_LSTRING}, + {"team", FOFS(team), F_LSTRING}, + {"wait", FOFS(wait), F_FLOAT}, + {"delay", FOFS(delay), F_FLOAT}, + {"random", FOFS(random), F_FLOAT}, + {"move_origin", FOFS(move_origin), F_VECTOR}, + {"move_angles", FOFS(move_angles), F_VECTOR}, + {"style", FOFS(style), F_INT}, + {"count", FOFS(count), F_INT}, + {"health", FOFS(health), F_INT}, + {"sounds", FOFS(sounds), F_INT}, + {"light", 0, F_IGNORE}, + {"dmg", FOFS(dmg), F_INT}, + {"mass", FOFS(mass), F_INT}, + {"volume", FOFS(volume), F_FLOAT}, + {"attenuation", FOFS(attenuation), F_FLOAT}, + {"map", FOFS(map), F_LSTRING}, + {"origin", FOFS(s.origin), F_VECTOR}, + {"angles", FOFS(s.angles), F_VECTOR}, + {"angle", FOFS(s.angles), F_ANGLEHACK}, + + {"goalentity", FOFS(goalentity), F_EDICT, FFL_NOSPAWN}, + {"movetarget", FOFS(movetarget), F_EDICT, FFL_NOSPAWN}, + {"enemy", FOFS(enemy), F_EDICT, FFL_NOSPAWN}, + {"oldenemy", FOFS(oldenemy), F_EDICT, FFL_NOSPAWN}, + {"activator", FOFS(activator), F_EDICT, FFL_NOSPAWN}, + {"groundentity", FOFS(groundentity), F_EDICT, FFL_NOSPAWN}, + {"teamchain", FOFS(teamchain), F_EDICT, FFL_NOSPAWN}, + {"teammaster", FOFS(teammaster), F_EDICT, FFL_NOSPAWN}, + {"owner", FOFS(owner), F_EDICT, FFL_NOSPAWN}, + {"mynoise", FOFS(mynoise), F_EDICT, FFL_NOSPAWN}, + {"mynoise2", FOFS(mynoise2), F_EDICT, FFL_NOSPAWN}, + {"target_ent", FOFS(target_ent), F_EDICT, FFL_NOSPAWN}, + {"chain", FOFS(chain), F_EDICT, FFL_NOSPAWN}, + + {"prethink", FOFS(prethink), F_FUNCTION, FFL_NOSPAWN}, + {"think", FOFS(think), F_FUNCTION, FFL_NOSPAWN}, + {"blocked", FOFS(blocked), F_FUNCTION, FFL_NOSPAWN}, + {"touch", FOFS(touch), F_FUNCTION, FFL_NOSPAWN}, + {"use", FOFS(use), F_FUNCTION, FFL_NOSPAWN}, + {"pain", FOFS(pain), F_FUNCTION, FFL_NOSPAWN}, + {"die", FOFS(die), F_FUNCTION, FFL_NOSPAWN}, + + {"stand", FOFS(monsterinfo.stand), F_FUNCTION, FFL_NOSPAWN}, + {"idle", FOFS(monsterinfo.idle), F_FUNCTION, FFL_NOSPAWN}, + {"search", FOFS(monsterinfo.search), F_FUNCTION, FFL_NOSPAWN}, + {"walk", FOFS(monsterinfo.walk), F_FUNCTION, FFL_NOSPAWN}, + {"run", FOFS(monsterinfo.run), F_FUNCTION, FFL_NOSPAWN}, + {"dodge", FOFS(monsterinfo.dodge), F_FUNCTION, FFL_NOSPAWN}, + {"attack", FOFS(monsterinfo.attack), F_FUNCTION, FFL_NOSPAWN}, + {"melee", FOFS(monsterinfo.melee), F_FUNCTION, FFL_NOSPAWN}, + {"sight", FOFS(monsterinfo.sight), F_FUNCTION, FFL_NOSPAWN}, + {"checkattack", FOFS(monsterinfo.checkattack), F_FUNCTION, FFL_NOSPAWN}, + {"currentmove", FOFS(monsterinfo.currentmove), F_MMOVE, FFL_NOSPAWN}, + + {"endfunc", FOFS(moveinfo.endfunc), F_FUNCTION, FFL_NOSPAWN}, + + // temp spawn vars -- only valid when the spawn function is called + {"lip", STOFS(lip), F_INT, FFL_SPAWNTEMP}, + {"distance", STOFS(distance), F_INT, FFL_SPAWNTEMP}, + {"height", STOFS(height), F_INT, FFL_SPAWNTEMP}, + {"noise", STOFS(noise), F_LSTRING, FFL_SPAWNTEMP}, + {"pausetime", STOFS(pausetime), F_FLOAT, FFL_SPAWNTEMP}, + {"item", STOFS(item), F_LSTRING, FFL_SPAWNTEMP}, + +//need for item field in edict struct, FFL_SPAWNTEMP item will be skipped on saves + {"item", FOFS(item), F_ITEM}, + + {"gravity", STOFS(gravity), F_LSTRING, FFL_SPAWNTEMP}, + {"sky", STOFS(sky), F_LSTRING, FFL_SPAWNTEMP}, + {"skyrotate", STOFS(skyrotate), F_FLOAT, FFL_SPAWNTEMP}, + {"skyaxis", STOFS(skyaxis), F_VECTOR, FFL_SPAWNTEMP}, + {"minyaw", STOFS(minyaw), F_FLOAT, FFL_SPAWNTEMP}, + {"maxyaw", STOFS(maxyaw), F_FLOAT, FFL_SPAWNTEMP}, + {"minpitch", STOFS(minpitch), F_FLOAT, FFL_SPAWNTEMP}, + {"maxpitch", STOFS(maxpitch), F_FLOAT, FFL_SPAWNTEMP}, + {"nextmap", STOFS(nextmap), F_LSTRING, FFL_SPAWNTEMP}, + + {0, 0, 0, 0} + +}; + +field_t levelfields[] = +{ + {"changemap", LLOFS(changemap), F_LSTRING}, + + {"sight_client", LLOFS(sight_client), F_EDICT}, + {"sight_entity", LLOFS(sight_entity), F_EDICT}, + {"sound_entity", LLOFS(sound_entity), F_EDICT}, + {"sound2_entity", LLOFS(sound2_entity), F_EDICT}, + + {NULL, 0, F_INT} +}; + +field_t clientfields[] = +{ + {"pers.weapon", CLOFS(pers.weapon), F_ITEM}, + {"pers.lastweapon", CLOFS(pers.lastweapon), F_ITEM}, + {"newweapon", CLOFS(newweapon), F_ITEM}, + + {NULL, 0, F_INT} +}; + +/* +============ +InitGame + +This will be called when the dll is first loaded, which +only happens when a new game is started or a save game +is loaded. +============ +*/ +void InitGame (void) +{ + gi.dprintf ("==== InitGame ====\n"); + + gun_x = gi.cvar ("gun_x", "0", 0); + gun_y = gi.cvar ("gun_y", "0", 0); + gun_z = gi.cvar ("gun_z", "0", 0); + + //FIXME: sv_ prefix is wrong for these + sv_rollspeed = gi.cvar ("sv_rollspeed", "200", 0); + sv_rollangle = gi.cvar ("sv_rollangle", "2", 0); + sv_maxvelocity = gi.cvar ("sv_maxvelocity", "2000", 0); + sv_gravity = gi.cvar ("sv_gravity", "800", 0); + + // noset vars + dedicated = gi.cvar ("dedicated", "0", CVAR_NOSET); + + // latched vars + sv_cheats = gi.cvar ("cheats", "0", CVAR_SERVERINFO|CVAR_LATCH); + gi.cvar ("gamename", GAMEVERSION , CVAR_SERVERINFO | CVAR_LATCH); + gi.cvar ("gamedate", __DATE__ , CVAR_SERVERINFO | CVAR_LATCH); + + maxclients = gi.cvar ("maxclients", "4", CVAR_SERVERINFO | CVAR_LATCH); + maxspectators = gi.cvar ("maxspectators", "4", CVAR_SERVERINFO); + deathmatch = gi.cvar ("deathmatch", "0", CVAR_LATCH); + coop = gi.cvar ("coop", "0", CVAR_LATCH); + skill = gi.cvar ("skill", "1", CVAR_LATCH); + maxentities = gi.cvar ("maxentities", "1024", CVAR_LATCH); + + // change anytime vars + dmflags = gi.cvar ("dmflags", "0", CVAR_SERVERINFO); + fraglimit = gi.cvar ("fraglimit", "0", CVAR_SERVERINFO); + timelimit = gi.cvar ("timelimit", "0", CVAR_SERVERINFO); + password = gi.cvar ("password", "", CVAR_USERINFO); + spectator_password = gi.cvar ("spectator_password", "", CVAR_USERINFO); + needpass = gi.cvar ("needpass", "0", CVAR_SERVERINFO); + filterban = gi.cvar ("filterban", "1", 0); + + g_select_empty = gi.cvar ("g_select_empty", "0", CVAR_ARCHIVE); + + run_pitch = gi.cvar ("run_pitch", "0.002", 0); + run_roll = gi.cvar ("run_roll", "0.005", 0); + bob_up = gi.cvar ("bob_up", "0.005", 0); + bob_pitch = gi.cvar ("bob_pitch", "0.002", 0); + bob_roll = gi.cvar ("bob_roll", "0.002", 0); + + // flood control + flood_msgs = gi.cvar ("flood_msgs", "4", 0); + flood_persecond = gi.cvar ("flood_persecond", "4", 0); + flood_waitdelay = gi.cvar ("flood_waitdelay", "10", 0); + + // dm map list + sv_maplist = gi.cvar ("sv_maplist", "", 0); + + // items + InitItems (); + + Com_sprintf (game.helpmessage1, sizeof(game.helpmessage1), ""); + + Com_sprintf (game.helpmessage2, sizeof(game.helpmessage2), ""); + + // initialize all entities for this game + game.maxentities = maxentities->value; + g_edicts = gi.TagMalloc (game.maxentities * sizeof(g_edicts[0]), TAG_GAME); + globals.edicts = g_edicts; + globals.max_edicts = game.maxentities; + + // initialize all clients for this game + game.maxclients = maxclients->value; + game.clients = gi.TagMalloc (game.maxclients * sizeof(game.clients[0]), TAG_GAME); + globals.num_edicts = game.maxclients+1; +} + +//========================================================= + +void WriteField1 (FILE *f, field_t *field, byte *base) +{ + void *p; + int len; + int index; + + if (field->flags & FFL_SPAWNTEMP) + return; + + p = (void *)(base + field->ofs); + switch (field->type) + { + case F_INT: + case F_FLOAT: + case F_ANGLEHACK: + case F_VECTOR: + case F_IGNORE: + break; + + case F_LSTRING: + case F_GSTRING: + if ( *(char **)p ) + len = strlen(*(char **)p) + 1; + else + len = 0; + *(int *)p = len; + break; + case F_EDICT: + if ( *(edict_t **)p == NULL) + index = -1; + else + index = *(edict_t **)p - g_edicts; + *(int *)p = index; + break; + case F_CLIENT: + if ( *(gclient_t **)p == NULL) + index = -1; + else + index = *(gclient_t **)p - game.clients; + *(int *)p = index; + break; + case F_ITEM: + if ( *(edict_t **)p == NULL) + index = -1; + else + index = *(gitem_t **)p - itemlist; + *(int *)p = index; + break; + + //relative to code segment + case F_FUNCTION: + if (*(byte **)p == NULL) + index = 0; + else + index = *(byte **)p - ((byte *)InitGame); + *(int *)p = index; + break; + + //relative to data segment + case F_MMOVE: + if (*(byte **)p == NULL) + index = 0; + else + index = *(byte **)p - (byte *)&mmove_reloc; + *(int *)p = index; + break; + + default: + gi.error ("WriteEdict: unknown field type"); + } +} + + +void WriteField2 (FILE *f, field_t *field, byte *base) +{ + int len; + void *p; + + if (field->flags & FFL_SPAWNTEMP) + return; + + p = (void *)(base + field->ofs); + switch (field->type) + { + case F_LSTRING: + if ( *(char **)p ) + { + len = strlen(*(char **)p) + 1; + fwrite (*(char **)p, len, 1, f); + } + break; + } +} + +void ReadField (FILE *f, field_t *field, byte *base) +{ + void *p; + int len; + int index; + + if (field->flags & FFL_SPAWNTEMP) + return; + + p = (void *)(base + field->ofs); + switch (field->type) + { + case F_INT: + case F_FLOAT: + case F_ANGLEHACK: + case F_VECTOR: + case F_IGNORE: + break; + + case F_LSTRING: + len = *(int *)p; + if (!len) + *(char **)p = NULL; + else + { + *(char **)p = gi.TagMalloc (len, TAG_LEVEL); + fread (*(char **)p, len, 1, f); + } + break; + case F_EDICT: + index = *(int *)p; + if ( index == -1 ) + *(edict_t **)p = NULL; + else + *(edict_t **)p = &g_edicts[index]; + break; + case F_CLIENT: + index = *(int *)p; + if ( index == -1 ) + *(gclient_t **)p = NULL; + else + *(gclient_t **)p = &game.clients[index]; + break; + case F_ITEM: + index = *(int *)p; + if ( index == -1 ) + *(gitem_t **)p = NULL; + else + *(gitem_t **)p = &itemlist[index]; + break; + + //relative to code segment + case F_FUNCTION: + index = *(int *)p; + if ( index == 0 ) + *(byte **)p = NULL; + else + *(byte **)p = ((byte *)InitGame) + index; + break; + + //relative to data segment + case F_MMOVE: + index = *(int *)p; + if (index == 0) + *(byte **)p = NULL; + else + *(byte **)p = (byte *)&mmove_reloc + index; + break; + + default: + gi.error ("ReadEdict: unknown field type"); + } +} + +//========================================================= + +/* +============== +WriteClient + +All pointer variables (except function pointers) must be handled specially. +============== +*/ +void WriteClient (FILE *f, gclient_t *client) +{ + field_t *field; + gclient_t temp; + + // all of the ints, floats, and vectors stay as they are + temp = *client; + + // change the pointers to lengths or indexes + for (field=clientfields ; field->name ; field++) + { + WriteField1 (f, field, (byte *)&temp); + } + + // write the block + fwrite (&temp, sizeof(temp), 1, f); + + // now write any allocated data following the edict + for (field=clientfields ; field->name ; field++) + { + WriteField2 (f, field, (byte *)client); + } +} + +/* +============== +ReadClient + +All pointer variables (except function pointers) must be handled specially. +============== +*/ +void ReadClient (FILE *f, gclient_t *client) +{ + field_t *field; + + fread (client, sizeof(*client), 1, f); + + for (field=clientfields ; field->name ; field++) + { + ReadField (f, field, (byte *)client); + } +} + +/* +============ +WriteGame + +This will be called whenever the game goes to a new level, +and when the user explicitly saves the game. + +Game information include cross level data, like multi level +triggers, help computer info, and all client states. + +A single player death will automatically restore from the +last save position. +============ +*/ +void WriteGame (char *filename, qboolean autosave) +{ + FILE *f; + int i; + char str[16]; + + if (!autosave) + SaveClientData (); + + f = fopen (filename, "wb"); + if (!f) + gi.error ("Couldn't open %s", filename); + + memset (str, 0, sizeof(str)); + strcpy (str, __DATE__); + fwrite (str, sizeof(str), 1, f); + + game.autosaved = autosave; + fwrite (&game, sizeof(game), 1, f); + game.autosaved = false; + + for (i=0 ; iname ; field++) + { + WriteField1 (f, field, (byte *)&temp); + } + + // write the block + fwrite (&temp, sizeof(temp), 1, f); + + // now write any allocated data following the edict + for (field=fields ; field->name ; field++) + { + WriteField2 (f, field, (byte *)ent); + } + +} + +/* +============== +WriteLevelLocals + +All pointer variables (except function pointers) must be handled specially. +============== +*/ +void WriteLevelLocals (FILE *f) +{ + field_t *field; + level_locals_t temp; + + // all of the ints, floats, and vectors stay as they are + temp = level; + + // change the pointers to lengths or indexes + for (field=levelfields ; field->name ; field++) + { + WriteField1 (f, field, (byte *)&temp); + } + + // write the block + fwrite (&temp, sizeof(temp), 1, f); + + // now write any allocated data following the edict + for (field=levelfields ; field->name ; field++) + { + WriteField2 (f, field, (byte *)&level); + } +} + + +/* +============== +ReadEdict + +All pointer variables (except function pointers) must be handled specially. +============== +*/ +void ReadEdict (FILE *f, edict_t *ent) +{ + field_t *field; + + fread (ent, sizeof(*ent), 1, f); + + for (field=fields ; field->name ; field++) + { + ReadField (f, field, (byte *)ent); + } +} + +/* +============== +ReadLevelLocals + +All pointer variables (except function pointers) must be handled specially. +============== +*/ +void ReadLevelLocals (FILE *f) +{ + field_t *field; + + fread (&level, sizeof(level), 1, f); + + for (field=levelfields ; field->name ; field++) + { + ReadField (f, field, (byte *)&level); + } +} + +/* +================= +WriteLevel + +================= +*/ +void WriteLevel (char *filename) +{ + int i; + edict_t *ent; + FILE *f; + void *base; + + f = fopen (filename, "wb"); + if (!f) + gi.error ("Couldn't open %s", filename); + + // write out edict size for checking + i = sizeof(edict_t); + fwrite (&i, sizeof(i), 1, f); + + // write out a function pointer for checking + base = (void *)InitGame; + fwrite (&base, sizeof(base), 1, f); + + // write out level_locals_t + WriteLevelLocals (f); + + // write out all the entities + for (i=0 ; iinuse) + continue; + fwrite (&i, sizeof(i), 1, f); + WriteEdict (f, ent); + } + i = -1; + fwrite (&i, sizeof(i), 1, f); + + fclose (f); +} + + +/* +================= +ReadLevel + +SpawnEntities will allready have been called on the +level the same way it was when the level was saved. + +That is necessary to get the baselines +set up identically. + +The server will have cleared all of the world links before +calling ReadLevel. + +No clients are connected yet. +================= +*/ +void ReadLevel (char *filename) +{ + int entnum; + FILE *f; + int i; + void *base; + edict_t *ent; + + f = fopen (filename, "rb"); + if (!f) + gi.error ("Couldn't open %s", filename); + + // free any dynamic memory allocated by loading the level + // base state + gi.FreeTags (TAG_LEVEL); + + // wipe all the entities + memset (g_edicts, 0, game.maxentities*sizeof(g_edicts[0])); + globals.num_edicts = maxclients->value+1; + + // check edict size + fread (&i, sizeof(i), 1, f); + if (i != sizeof(edict_t)) + { + fclose (f); + gi.error ("ReadLevel: mismatched edict size"); + } + + // check function pointer base address + fread (&base, sizeof(base), 1, f); +#ifdef _WIN32 + if (base != (void *)InitGame) + { + fclose (f); + gi.error ("ReadLevel: function pointers have moved"); + } +#else + gi.dprintf("Function offsets %d\n", ((byte *)base) - ((byte *)InitGame)); +#endif + + // load the level locals + ReadLevelLocals (f); + + // load all the entities + while (1) + { + if (fread (&entnum, sizeof(entnum), 1, f) != 1) + { + fclose (f); + gi.error ("ReadLevel: failed to read entnum"); + } + if (entnum == -1) + break; + if (entnum >= globals.num_edicts) + globals.num_edicts = entnum+1; + + ent = &g_edicts[entnum]; + ReadEdict (f, ent); + + // let the server rebuild world links for this ent + memset (&ent->area, 0, sizeof(ent->area)); + gi.linkentity (ent); + } + + fclose (f); + + // mark all clients as unconnected + for (i=0 ; ivalue ; i++) + { + ent = &g_edicts[i+1]; + ent->client = game.clients + i; + ent->client->pers.connected = false; + } + + // do any load time things at this point + for (i=0 ; iinuse) + continue; + + // fire any cross-level triggers + if (ent->classname) + if (strcmp(ent->classname, "target_crosslevel_target") == 0) + ent->nextthink = level.time + ent->delay; + } +} diff --git a/original/baseq2/g_spawn.c b/original/baseq2/g_spawn.c new file mode 100644 index 0000000..c6084e9 --- /dev/null +++ b/original/baseq2/g_spawn.c @@ -0,0 +1,984 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +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. + +*/ + +#include "g_local.h" + +typedef struct +{ + char *name; + void (*spawn)(edict_t *ent); +} spawn_t; + + +void SP_item_health (edict_t *self); +void SP_item_health_small (edict_t *self); +void SP_item_health_large (edict_t *self); +void SP_item_health_mega (edict_t *self); + +void SP_info_player_start (edict_t *ent); +void SP_info_player_deathmatch (edict_t *ent); +void SP_info_player_coop (edict_t *ent); +void SP_info_player_intermission (edict_t *ent); + +void SP_func_plat (edict_t *ent); +void SP_func_rotating (edict_t *ent); +void SP_func_button (edict_t *ent); +void SP_func_door (edict_t *ent); +void SP_func_door_secret (edict_t *ent); +void SP_func_door_rotating (edict_t *ent); +void SP_func_water (edict_t *ent); +void SP_func_train (edict_t *ent); +void SP_func_conveyor (edict_t *self); +void SP_func_wall (edict_t *self); +void SP_func_object (edict_t *self); +void SP_func_explosive (edict_t *self); +void SP_func_timer (edict_t *self); +void SP_func_areaportal (edict_t *ent); +void SP_func_clock (edict_t *ent); +void SP_func_killbox (edict_t *ent); + +void SP_trigger_always (edict_t *ent); +void SP_trigger_once (edict_t *ent); +void SP_trigger_multiple (edict_t *ent); +void SP_trigger_relay (edict_t *ent); +void SP_trigger_push (edict_t *ent); +void SP_trigger_hurt (edict_t *ent); +void SP_trigger_key (edict_t *ent); +void SP_trigger_counter (edict_t *ent); +void SP_trigger_elevator (edict_t *ent); +void SP_trigger_gravity (edict_t *ent); +void SP_trigger_monsterjump (edict_t *ent); + +void SP_target_temp_entity (edict_t *ent); +void SP_target_speaker (edict_t *ent); +void SP_target_explosion (edict_t *ent); +void SP_target_changelevel (edict_t *ent); +void SP_target_secret (edict_t *ent); +void SP_target_goal (edict_t *ent); +void SP_target_splash (edict_t *ent); +void SP_target_spawner (edict_t *ent); +void SP_target_blaster (edict_t *ent); +void SP_target_crosslevel_trigger (edict_t *ent); +void SP_target_crosslevel_target (edict_t *ent); +void SP_target_laser (edict_t *self); +void SP_target_help (edict_t *ent); +void SP_target_actor (edict_t *ent); +void SP_target_lightramp (edict_t *self); +void SP_target_earthquake (edict_t *ent); +void SP_target_character (edict_t *ent); +void SP_target_string (edict_t *ent); + +void SP_worldspawn (edict_t *ent); +void SP_viewthing (edict_t *ent); + +void SP_light (edict_t *self); +void SP_light_mine1 (edict_t *ent); +void SP_light_mine2 (edict_t *ent); +void SP_info_null (edict_t *self); +void SP_info_notnull (edict_t *self); +void SP_path_corner (edict_t *self); +void SP_point_combat (edict_t *self); + +void SP_misc_explobox (edict_t *self); +void SP_misc_banner (edict_t *self); +void SP_misc_satellite_dish (edict_t *self); +void SP_misc_actor (edict_t *self); +void SP_misc_gib_arm (edict_t *self); +void SP_misc_gib_leg (edict_t *self); +void SP_misc_gib_head (edict_t *self); +void SP_misc_insane (edict_t *self); +void SP_misc_deadsoldier (edict_t *self); +void SP_misc_viper (edict_t *self); +void SP_misc_viper_bomb (edict_t *self); +void SP_misc_bigviper (edict_t *self); +void SP_misc_strogg_ship (edict_t *self); +void SP_misc_teleporter (edict_t *self); +void SP_misc_teleporter_dest (edict_t *self); +void SP_misc_blackhole (edict_t *self); +void SP_misc_eastertank (edict_t *self); +void SP_misc_easterchick (edict_t *self); +void SP_misc_easterchick2 (edict_t *self); + +void SP_monster_berserk (edict_t *self); +void SP_monster_gladiator (edict_t *self); +void SP_monster_gunner (edict_t *self); +void SP_monster_infantry (edict_t *self); +void SP_monster_soldier_light (edict_t *self); +void SP_monster_soldier (edict_t *self); +void SP_monster_soldier_ss (edict_t *self); +void SP_monster_tank (edict_t *self); +void SP_monster_medic (edict_t *self); +void SP_monster_flipper (edict_t *self); +void SP_monster_chick (edict_t *self); +void SP_monster_parasite (edict_t *self); +void SP_monster_flyer (edict_t *self); +void SP_monster_brain (edict_t *self); +void SP_monster_floater (edict_t *self); +void SP_monster_hover (edict_t *self); +void SP_monster_mutant (edict_t *self); +void SP_monster_supertank (edict_t *self); +void SP_monster_boss2 (edict_t *self); +void SP_monster_jorg (edict_t *self); +void SP_monster_boss3_stand (edict_t *self); + +void SP_monster_commander_body (edict_t *self); + +void SP_turret_breach (edict_t *self); +void SP_turret_base (edict_t *self); +void SP_turret_driver (edict_t *self); + + +spawn_t spawns[] = { + {"item_health", SP_item_health}, + {"item_health_small", SP_item_health_small}, + {"item_health_large", SP_item_health_large}, + {"item_health_mega", SP_item_health_mega}, + + {"info_player_start", SP_info_player_start}, + {"info_player_deathmatch", SP_info_player_deathmatch}, + {"info_player_coop", SP_info_player_coop}, + {"info_player_intermission", SP_info_player_intermission}, + + {"func_plat", SP_func_plat}, + {"func_button", SP_func_button}, + {"func_door", SP_func_door}, + {"func_door_secret", SP_func_door_secret}, + {"func_door_rotating", SP_func_door_rotating}, + {"func_rotating", SP_func_rotating}, + {"func_train", SP_func_train}, + {"func_water", SP_func_water}, + {"func_conveyor", SP_func_conveyor}, + {"func_areaportal", SP_func_areaportal}, + {"func_clock", SP_func_clock}, + {"func_wall", SP_func_wall}, + {"func_object", SP_func_object}, + {"func_timer", SP_func_timer}, + {"func_explosive", SP_func_explosive}, + {"func_killbox", SP_func_killbox}, + + {"trigger_always", SP_trigger_always}, + {"trigger_once", SP_trigger_once}, + {"trigger_multiple", SP_trigger_multiple}, + {"trigger_relay", SP_trigger_relay}, + {"trigger_push", SP_trigger_push}, + {"trigger_hurt", SP_trigger_hurt}, + {"trigger_key", SP_trigger_key}, + {"trigger_counter", SP_trigger_counter}, + {"trigger_elevator", SP_trigger_elevator}, + {"trigger_gravity", SP_trigger_gravity}, + {"trigger_monsterjump", SP_trigger_monsterjump}, + + {"target_temp_entity", SP_target_temp_entity}, + {"target_speaker", SP_target_speaker}, + {"target_explosion", SP_target_explosion}, + {"target_changelevel", SP_target_changelevel}, + {"target_secret", SP_target_secret}, + {"target_goal", SP_target_goal}, + {"target_splash", SP_target_splash}, + {"target_spawner", SP_target_spawner}, + {"target_blaster", SP_target_blaster}, + {"target_crosslevel_trigger", SP_target_crosslevel_trigger}, + {"target_crosslevel_target", SP_target_crosslevel_target}, + {"target_laser", SP_target_laser}, + {"target_help", SP_target_help}, + {"target_actor", SP_target_actor}, + {"target_lightramp", SP_target_lightramp}, + {"target_earthquake", SP_target_earthquake}, + {"target_character", SP_target_character}, + {"target_string", SP_target_string}, + + {"worldspawn", SP_worldspawn}, + {"viewthing", SP_viewthing}, + + {"light", SP_light}, + {"light_mine1", SP_light_mine1}, + {"light_mine2", SP_light_mine2}, + {"info_null", SP_info_null}, + {"func_group", SP_info_null}, + {"info_notnull", SP_info_notnull}, + {"path_corner", SP_path_corner}, + {"point_combat", SP_point_combat}, + + {"misc_explobox", SP_misc_explobox}, + {"misc_banner", SP_misc_banner}, + {"misc_satellite_dish", SP_misc_satellite_dish}, + {"misc_actor", SP_misc_actor}, + {"misc_gib_arm", SP_misc_gib_arm}, + {"misc_gib_leg", SP_misc_gib_leg}, + {"misc_gib_head", SP_misc_gib_head}, + {"misc_insane", SP_misc_insane}, + {"misc_deadsoldier", SP_misc_deadsoldier}, + {"misc_viper", SP_misc_viper}, + {"misc_viper_bomb", SP_misc_viper_bomb}, + {"misc_bigviper", SP_misc_bigviper}, + {"misc_strogg_ship", SP_misc_strogg_ship}, + {"misc_teleporter", SP_misc_teleporter}, + {"misc_teleporter_dest", SP_misc_teleporter_dest}, + {"misc_blackhole", SP_misc_blackhole}, + {"misc_eastertank", SP_misc_eastertank}, + {"misc_easterchick", SP_misc_easterchick}, + {"misc_easterchick2", SP_misc_easterchick2}, + + {"monster_berserk", SP_monster_berserk}, + {"monster_gladiator", SP_monster_gladiator}, + {"monster_gunner", SP_monster_gunner}, + {"monster_infantry", SP_monster_infantry}, + {"monster_soldier_light", SP_monster_soldier_light}, + {"monster_soldier", SP_monster_soldier}, + {"monster_soldier_ss", SP_monster_soldier_ss}, + {"monster_tank", SP_monster_tank}, + {"monster_tank_commander", SP_monster_tank}, + {"monster_medic", SP_monster_medic}, + {"monster_flipper", SP_monster_flipper}, + {"monster_chick", SP_monster_chick}, + {"monster_parasite", SP_monster_parasite}, + {"monster_flyer", SP_monster_flyer}, + {"monster_brain", SP_monster_brain}, + {"monster_floater", SP_monster_floater}, + {"monster_hover", SP_monster_hover}, + {"monster_mutant", SP_monster_mutant}, + {"monster_supertank", SP_monster_supertank}, + {"monster_boss2", SP_monster_boss2}, + {"monster_boss3_stand", SP_monster_boss3_stand}, + {"monster_jorg", SP_monster_jorg}, + + {"monster_commander_body", SP_monster_commander_body}, + + {"turret_breach", SP_turret_breach}, + {"turret_base", SP_turret_base}, + {"turret_driver", SP_turret_driver}, + + {NULL, NULL} +}; + +/* +=============== +ED_CallSpawn + +Finds the spawn function for the entity and calls it +=============== +*/ +void ED_CallSpawn (edict_t *ent) +{ + spawn_t *s; + gitem_t *item; + int i; + + if (!ent->classname) + { + gi.dprintf ("ED_CallSpawn: NULL classname\n"); + return; + } + + // check item spawn functions + for (i=0,item=itemlist ; iclassname) + continue; + if (!strcmp(item->classname, ent->classname)) + { // found it + SpawnItem (ent, item); + return; + } + } + + // check normal spawn functions + for (s=spawns ; s->name ; s++) + { + if (!strcmp(s->name, ent->classname)) + { // found it + s->spawn (ent); + return; + } + } + gi.dprintf ("%s doesn't have a spawn function\n", ent->classname); +} + +/* +============= +ED_NewString +============= +*/ +char *ED_NewString (char *string) +{ + char *newb, *new_p; + int i,l; + + l = strlen(string) + 1; + + newb = gi.TagMalloc (l, TAG_LEVEL); + + new_p = newb; + + for (i=0 ; i< l ; i++) + { + if (string[i] == '\\' && i < l-1) + { + i++; + if (string[i] == 'n') + *new_p++ = '\n'; + else + *new_p++ = '\\'; + } + else + *new_p++ = string[i]; + } + + return newb; +} + + + + +/* +=============== +ED_ParseField + +Takes a key/value pair and sets the binary values +in an edict +=============== +*/ +void ED_ParseField (char *key, char *value, edict_t *ent) +{ + field_t *f; + byte *b; + float v; + vec3_t vec; + + for (f=fields ; f->name ; f++) + { + if (!(f->flags & FFL_NOSPAWN) && !Q_stricmp(f->name, key)) + { // found it + if (f->flags & FFL_SPAWNTEMP) + b = (byte *)&st; + else + b = (byte *)ent; + + switch (f->type) + { + case F_LSTRING: + *(char **)(b+f->ofs) = ED_NewString (value); + break; + case F_VECTOR: + sscanf (value, "%f %f %f", &vec[0], &vec[1], &vec[2]); + ((float *)(b+f->ofs))[0] = vec[0]; + ((float *)(b+f->ofs))[1] = vec[1]; + ((float *)(b+f->ofs))[2] = vec[2]; + break; + case F_INT: + *(int *)(b+f->ofs) = atoi(value); + break; + case F_FLOAT: + *(float *)(b+f->ofs) = atof(value); + break; + case F_ANGLEHACK: + v = atof(value); + ((float *)(b+f->ofs))[0] = 0; + ((float *)(b+f->ofs))[1] = v; + ((float *)(b+f->ofs))[2] = 0; + break; + case F_IGNORE: + break; + } + return; + } + } + gi.dprintf ("%s is not a field\n", key); +} + +/* +==================== +ED_ParseEdict + +Parses an edict out of the given string, returning the new position +ed should be a properly initialized empty edict. +==================== +*/ +char *ED_ParseEdict (char *data, edict_t *ent) +{ + qboolean init; + char keyname[256]; + char *com_token; + + init = false; + memset (&st, 0, sizeof(st)); + +// go through all the dictionary pairs + while (1) + { + // parse key + com_token = COM_Parse (&data); + if (com_token[0] == '}') + break; + if (!data) + gi.error ("ED_ParseEntity: EOF without closing brace"); + + strncpy (keyname, com_token, sizeof(keyname)-1); + + // parse value + com_token = COM_Parse (&data); + if (!data) + gi.error ("ED_ParseEntity: EOF without closing brace"); + + if (com_token[0] == '}') + gi.error ("ED_ParseEntity: closing brace without data"); + + init = true; + + // keynames with a leading underscore are used for utility comments, + // and are immediately discarded by quake + if (keyname[0] == '_') + continue; + + ED_ParseField (keyname, com_token, ent); + } + + if (!init) + memset (ent, 0, sizeof(*ent)); + + return data; +} + + +/* +================ +G_FindTeams + +Chain together all entities with a matching team field. + +All but the first will have the FL_TEAMSLAVE flag set. +All but the last will have the teamchain field set to the next one +================ +*/ +void G_FindTeams (void) +{ + edict_t *e, *e2, *chain; + int i, j; + int c, c2; + + c = 0; + c2 = 0; + for (i=1, e=g_edicts+i ; i < globals.num_edicts ; i++,e++) + { + if (!e->inuse) + continue; + if (!e->team) + continue; + if (e->flags & FL_TEAMSLAVE) + continue; + chain = e; + e->teammaster = e; + c++; + c2++; + for (j=i+1, e2=e+1 ; j < globals.num_edicts ; j++,e2++) + { + if (!e2->inuse) + continue; + if (!e2->team) + continue; + if (e2->flags & FL_TEAMSLAVE) + continue; + if (!strcmp(e->team, e2->team)) + { + c2++; + chain->teamchain = e2; + e2->teammaster = e; + chain = e2; + e2->flags |= FL_TEAMSLAVE; + } + } + } + + gi.dprintf ("%i teams with %i entities\n", c, c2); +} + +/* +============== +SpawnEntities + +Creates a server's entity / program execution context by +parsing textual entity definitions out of an ent file. +============== +*/ +void SpawnEntities (char *mapname, char *entities, char *spawnpoint) +{ + edict_t *ent; + int inhibit; + char *com_token; + int i; + float skill_level; + + skill_level = floor (skill->value); + if (skill_level < 0) + skill_level = 0; + if (skill_level > 3) + skill_level = 3; + if (skill->value != skill_level) + gi.cvar_forceset("skill", va("%f", skill_level)); + + SaveClientData (); + + gi.FreeTags (TAG_LEVEL); + + memset (&level, 0, sizeof(level)); + memset (g_edicts, 0, game.maxentities * sizeof (g_edicts[0])); + + strncpy (level.mapname, mapname, sizeof(level.mapname)-1); + strncpy (game.spawnpoint, spawnpoint, sizeof(game.spawnpoint)-1); + + // set client fields on player ents + for (i=0 ; iclassname, "trigger_once") && !Q_stricmp(ent->model, "*27")) + ent->spawnflags &= ~SPAWNFLAG_NOT_HARD; + + // remove things (except the world) from different skill levels or deathmatch + if (ent != g_edicts) + { + if (deathmatch->value) + { + if ( ent->spawnflags & SPAWNFLAG_NOT_DEATHMATCH ) + { + G_FreeEdict (ent); + inhibit++; + continue; + } + } + else + { + if ( /* ((coop->value) && (ent->spawnflags & SPAWNFLAG_NOT_COOP)) || */ + ((skill->value == 0) && (ent->spawnflags & SPAWNFLAG_NOT_EASY)) || + ((skill->value == 1) && (ent->spawnflags & SPAWNFLAG_NOT_MEDIUM)) || + (((skill->value == 2) || (skill->value == 3)) && (ent->spawnflags & SPAWNFLAG_NOT_HARD)) + ) + { + G_FreeEdict (ent); + inhibit++; + continue; + } + } + + ent->spawnflags &= ~(SPAWNFLAG_NOT_EASY|SPAWNFLAG_NOT_MEDIUM|SPAWNFLAG_NOT_HARD|SPAWNFLAG_NOT_COOP|SPAWNFLAG_NOT_DEATHMATCH); + } + + ED_CallSpawn (ent); + } + + gi.dprintf ("%i entities inhibited\n", inhibit); + +#ifdef DEBUG + i = 1; + ent = EDICT_NUM(i); + while (i < globals.num_edicts) { + if (ent->inuse != 0 || ent->inuse != 1) + Com_DPrintf("Invalid entity %d\n", i); + i++, ent++; + } +#endif + + G_FindTeams (); + + PlayerTrail_Init (); +} + + +//=================================================================== + +#if 0 + // cursor positioning + xl + xr + yb + yt + xv + yv + + // drawing + statpic + pic + num + string + + // control + if + ifeq + ifbit + endif + +#endif + +char *single_statusbar = +"yb -24 " + +// health +"xv 0 " +"hnum " +"xv 50 " +"pic 0 " + +// ammo +"if 2 " +" xv 100 " +" anum " +" xv 150 " +" pic 2 " +"endif " + +// armor +"if 4 " +" xv 200 " +" rnum " +" xv 250 " +" pic 4 " +"endif " + +// selected item +"if 6 " +" xv 296 " +" pic 6 " +"endif " + +"yb -50 " + +// picked up item +"if 7 " +" xv 0 " +" pic 7 " +" xv 26 " +" yb -42 " +" stat_string 8 " +" yb -50 " +"endif " + +// timer +"if 9 " +" xv 262 " +" num 2 10 " +" xv 296 " +" pic 9 " +"endif " + +// help / weapon icon +"if 11 " +" xv 148 " +" pic 11 " +"endif " +; + +char *dm_statusbar = +"yb -24 " + +// health +"xv 0 " +"hnum " +"xv 50 " +"pic 0 " + +// ammo +"if 2 " +" xv 100 " +" anum " +" xv 150 " +" pic 2 " +"endif " + +// armor +"if 4 " +" xv 200 " +" rnum " +" xv 250 " +" pic 4 " +"endif " + +// selected item +"if 6 " +" xv 296 " +" pic 6 " +"endif " + +"yb -50 " + +// picked up item +"if 7 " +" xv 0 " +" pic 7 " +" xv 26 " +" yb -42 " +" stat_string 8 " +" yb -50 " +"endif " + +// timer +"if 9 " +" xv 246 " +" num 2 10 " +" xv 296 " +" pic 9 " +"endif " + +// help / weapon icon +"if 11 " +" xv 148 " +" pic 11 " +"endif " + +// frags +"xr -50 " +"yt 2 " +"num 3 14 " + +// spectator +"if 17 " + "xv 0 " + "yb -58 " + "string2 \"SPECTATOR MODE\" " +"endif " + +// chase camera +"if 16 " + "xv 0 " + "yb -68 " + "string \"Chasing\" " + "xv 64 " + "stat_string 16 " +"endif " +; + + +/*QUAKED worldspawn (0 0 0) ? + +Only used for the world. +"sky" environment map name +"skyaxis" vector axis for rotating sky +"skyrotate" speed of rotation in degrees/second +"sounds" music cd track number +"gravity" 800 is default gravity +"message" text to print at user logon +*/ +void SP_worldspawn (edict_t *ent) +{ + ent->movetype = MOVETYPE_PUSH; + ent->solid = SOLID_BSP; + ent->inuse = true; // since the world doesn't use G_Spawn() + ent->s.modelindex = 1; // world model is always index 1 + + //--------------- + + // reserve some spots for dead player bodies for coop / deathmatch + InitBodyQue (); + + // set configstrings for items + SetItemNames (); + + if (st.nextmap) + strcpy (level.nextmap, st.nextmap); + + // make some data visible to the server + + if (ent->message && ent->message[0]) + { + gi.configstring (CS_NAME, ent->message); + strncpy (level.level_name, ent->message, sizeof(level.level_name)); + } + else + strncpy (level.level_name, level.mapname, sizeof(level.level_name)); + + if (st.sky && st.sky[0]) + gi.configstring (CS_SKY, st.sky); + else + gi.configstring (CS_SKY, "unit1_"); + + gi.configstring (CS_SKYROTATE, va("%f", st.skyrotate) ); + + gi.configstring (CS_SKYAXIS, va("%f %f %f", + st.skyaxis[0], st.skyaxis[1], st.skyaxis[2]) ); + + gi.configstring (CS_CDTRACK, va("%i", ent->sounds) ); + + gi.configstring (CS_MAXCLIENTS, va("%i", (int)(maxclients->value) ) ); + + // status bar program + if (deathmatch->value) + gi.configstring (CS_STATUSBAR, dm_statusbar); + else + gi.configstring (CS_STATUSBAR, single_statusbar); + + //--------------- + + + // help icon for statusbar + gi.imageindex ("i_help"); + level.pic_health = gi.imageindex ("i_health"); + gi.imageindex ("help"); + gi.imageindex ("field_3"); + + if (!st.gravity) + gi.cvar_set("sv_gravity", "800"); + else + gi.cvar_set("sv_gravity", st.gravity); + + snd_fry = gi.soundindex ("player/fry.wav"); // standing in lava / slime + + PrecacheItem (FindItem ("Blaster")); + + gi.soundindex ("player/lava1.wav"); + gi.soundindex ("player/lava2.wav"); + + gi.soundindex ("misc/pc_up.wav"); + gi.soundindex ("misc/talk1.wav"); + + gi.soundindex ("misc/udeath.wav"); + + // gibs + gi.soundindex ("items/respawn1.wav"); + + // sexed sounds + gi.soundindex ("*death1.wav"); + gi.soundindex ("*death2.wav"); + gi.soundindex ("*death3.wav"); + gi.soundindex ("*death4.wav"); + gi.soundindex ("*fall1.wav"); + gi.soundindex ("*fall2.wav"); + gi.soundindex ("*gurp1.wav"); // drowning damage + gi.soundindex ("*gurp2.wav"); + gi.soundindex ("*jump1.wav"); // player jump + gi.soundindex ("*pain25_1.wav"); + gi.soundindex ("*pain25_2.wav"); + gi.soundindex ("*pain50_1.wav"); + gi.soundindex ("*pain50_2.wav"); + gi.soundindex ("*pain75_1.wav"); + gi.soundindex ("*pain75_2.wav"); + gi.soundindex ("*pain100_1.wav"); + gi.soundindex ("*pain100_2.wav"); + + // sexed models + // THIS ORDER MUST MATCH THE DEFINES IN g_local.h + // you can add more, max 15 + gi.modelindex ("#w_blaster.md2"); + gi.modelindex ("#w_shotgun.md2"); + gi.modelindex ("#w_sshotgun.md2"); + gi.modelindex ("#w_machinegun.md2"); + gi.modelindex ("#w_chaingun.md2"); + gi.modelindex ("#a_grenades.md2"); + gi.modelindex ("#w_glauncher.md2"); + gi.modelindex ("#w_rlauncher.md2"); + gi.modelindex ("#w_hyperblaster.md2"); + gi.modelindex ("#w_railgun.md2"); + gi.modelindex ("#w_bfg.md2"); + + //------------------- + + gi.soundindex ("player/gasp1.wav"); // gasping for air + gi.soundindex ("player/gasp2.wav"); // head breaking surface, not gasping + + gi.soundindex ("player/watr_in.wav"); // feet hitting water + gi.soundindex ("player/watr_out.wav"); // feet leaving water + + gi.soundindex ("player/watr_un.wav"); // head going underwater + + gi.soundindex ("player/u_breath1.wav"); + gi.soundindex ("player/u_breath2.wav"); + + gi.soundindex ("items/pkup.wav"); // bonus item pickup + gi.soundindex ("world/land.wav"); // landing thud + gi.soundindex ("misc/h2ohit1.wav"); // landing splash + + gi.soundindex ("items/damage.wav"); + gi.soundindex ("items/protect.wav"); + gi.soundindex ("items/protect4.wav"); + gi.soundindex ("weapons/noammo.wav"); + + gi.soundindex ("infantry/inflies1.wav"); + + sm_meat_index = gi.modelindex ("models/objects/gibs/sm_meat/tris.md2"); + gi.modelindex ("models/objects/gibs/arm/tris.md2"); + gi.modelindex ("models/objects/gibs/bone/tris.md2"); + gi.modelindex ("models/objects/gibs/bone2/tris.md2"); + gi.modelindex ("models/objects/gibs/chest/tris.md2"); + gi.modelindex ("models/objects/gibs/skull/tris.md2"); + gi.modelindex ("models/objects/gibs/head2/tris.md2"); + +// +// Setup light animation tables. 'a' is total darkness, 'z' is doublebright. +// + + // 0 normal + gi.configstring(CS_LIGHTS+0, "m"); + + // 1 FLICKER (first variety) + gi.configstring(CS_LIGHTS+1, "mmnmmommommnonmmonqnmmo"); + + // 2 SLOW STRONG PULSE + gi.configstring(CS_LIGHTS+2, "abcdefghijklmnopqrstuvwxyzyxwvutsrqponmlkjihgfedcba"); + + // 3 CANDLE (first variety) + gi.configstring(CS_LIGHTS+3, "mmmmmaaaaammmmmaaaaaabcdefgabcdefg"); + + // 4 FAST STROBE + gi.configstring(CS_LIGHTS+4, "mamamamamama"); + + // 5 GENTLE PULSE 1 + gi.configstring(CS_LIGHTS+5,"jklmnopqrstuvwxyzyxwvutsrqponmlkj"); + + // 6 FLICKER (second variety) + gi.configstring(CS_LIGHTS+6, "nmonqnmomnmomomno"); + + // 7 CANDLE (second variety) + gi.configstring(CS_LIGHTS+7, "mmmaaaabcdefgmmmmaaaammmaamm"); + + // 8 CANDLE (third variety) + gi.configstring(CS_LIGHTS+8, "mmmaaammmaaammmabcdefaaaammmmabcdefmmmaaaa"); + + // 9 SLOW STROBE (fourth variety) + gi.configstring(CS_LIGHTS+9, "aaaaaaaazzzzzzzz"); + + // 10 FLUORESCENT FLICKER + gi.configstring(CS_LIGHTS+10, "mmamammmmammamamaaamammma"); + + // 11 SLOW PULSE NOT FADE TO BLACK + gi.configstring(CS_LIGHTS+11, "abcdefghijklmnopqrrqponmlkjihgfedcba"); + + // styles 32-62 are assigned by the light program for switchable lights + + // 63 testing + gi.configstring(CS_LIGHTS+63, "a"); +} + diff --git a/original/baseq2/g_svcmds.c b/original/baseq2/g_svcmds.c new file mode 100644 index 0000000..6868633 --- /dev/null +++ b/original/baseq2/g_svcmds.c @@ -0,0 +1,300 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +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. + +*/ + +#include "g_local.h" + + +void Svcmd_Test_f (void) +{ + gi.cprintf (NULL, PRINT_HIGH, "Svcmd_Test_f()\n"); +} + +/* +============================================================================== + +PACKET FILTERING + + +You can add or remove addresses from the filter list with: + +addip +removeip + +The ip address is specified in dot format, and any unspecified digits will match any value, so you can specify an entire class C network with "addip 192.246.40". + +Removeip will only remove an address specified exactly the same way. You cannot addip a subnet, then removeip a single host. + +listip +Prints the current list of filters. + +writeip +Dumps "addip " commands to listip.cfg so it can be execed at a later date. The filter lists are not saved and restored by default, because I beleive it would cause too much confusion. + +filterban <0 or 1> + +If 1 (the default), then ip addresses matching the current list will be prohibited from entering the game. This is the default setting. + +If 0, then only addresses matching the list will be allowed. This lets you easily set up a private game, or a game that only allows players from your local network. + + +============================================================================== +*/ + +typedef struct +{ + unsigned mask; + unsigned compare; +} ipfilter_t; + +#define MAX_IPFILTERS 1024 + +ipfilter_t ipfilters[MAX_IPFILTERS]; +int numipfilters; + +/* +================= +StringToFilter +================= +*/ +static qboolean StringToFilter (char *s, ipfilter_t *f) +{ + char num[128]; + int i, j; + byte b[4]; + byte m[4]; + + for (i=0 ; i<4 ; i++) + { + b[i] = 0; + m[i] = 0; + } + + for (i=0 ; i<4 ; i++) + { + if (*s < '0' || *s > '9') + { + gi.cprintf(NULL, PRINT_HIGH, "Bad filter address: %s\n", s); + return false; + } + + j = 0; + while (*s >= '0' && *s <= '9') + { + num[j++] = *s++; + } + num[j] = 0; + b[i] = atoi(num); + if (b[i] != 0) + m[i] = 255; + + if (!*s) + break; + s++; + } + + f->mask = *(unsigned *)m; + f->compare = *(unsigned *)b; + + return true; +} + +/* +================= +SV_FilterPacket +================= +*/ +qboolean SV_FilterPacket (char *from) +{ + int i; + unsigned in; + byte m[4]; + char *p; + + i = 0; + p = from; + while (*p && i < 4) { + m[i] = 0; + while (*p >= '0' && *p <= '9') { + m[i] = m[i]*10 + (*p - '0'); + p++; + } + if (!*p || *p == ':') + break; + i++, p++; + } + + in = *(unsigned *)m; + + for (i=0 ; ivalue; + + return (int)!filterban->value; +} + + +/* +================= +SV_AddIP_f +================= +*/ +void SVCmd_AddIP_f (void) +{ + int i; + + if (gi.argc() < 3) { + gi.cprintf(NULL, PRINT_HIGH, "Usage: addip \n"); + return; + } + + for (i=0 ; i\n"); + return; + } + + if (!StringToFilter (gi.argv(2), &f)) + return; + + for (i=0 ; istring) + sprintf (name, "%s/listip.cfg", GAMEVERSION); + else + sprintf (name, "%s/listip.cfg", game->string); + + gi.cprintf (NULL, PRINT_HIGH, "Writing %s.\n", name); + + f = fopen (name, "wb"); + if (!f) + { + gi.cprintf (NULL, PRINT_HIGH, "Couldn't open %s\n", name); + return; + } + + fprintf(f, "set filterban %d\n", (int)filterban->value); + + for (i=0 ; istyle); + gi.WritePosition (ent->s.origin); + gi.multicast (ent->s.origin, MULTICAST_PVS); +} + +void SP_target_temp_entity (edict_t *ent) +{ + ent->use = Use_Target_Tent; +} + + +//========================================================== + +//========================================================== + +/*QUAKED target_speaker (1 0 0) (-8 -8 -8) (8 8 8) looped-on looped-off reliable +"noise" wav file to play +"attenuation" +-1 = none, send to whole level +1 = normal fighting sounds +2 = idle sound level +3 = ambient sound level +"volume" 0.0 to 1.0 + +Normal sounds play each time the target is used. The reliable flag can be set for crucial voiceovers. + +Looped sounds are always atten 3 / vol 1, and the use function toggles it on/off. +Multiple identical looping sounds will just increase volume without any speed cost. +*/ +void Use_Target_Speaker (edict_t *ent, edict_t *other, edict_t *activator) +{ + int chan; + + if (ent->spawnflags & 3) + { // looping sound toggles + if (ent->s.sound) + ent->s.sound = 0; // turn it off + else + ent->s.sound = ent->noise_index; // start it + } + else + { // normal sound + if (ent->spawnflags & 4) + chan = CHAN_VOICE|CHAN_RELIABLE; + else + chan = CHAN_VOICE; + // use a positioned_sound, because this entity won't normally be + // sent to any clients because it is invisible + gi.positioned_sound (ent->s.origin, ent, chan, ent->noise_index, ent->volume, ent->attenuation, 0); + } +} + +void SP_target_speaker (edict_t *ent) +{ + char buffer[MAX_QPATH]; + + if(!st.noise) + { + gi.dprintf("target_speaker with no noise set at %s\n", vtos(ent->s.origin)); + return; + } + if (!strstr (st.noise, ".wav")) + Com_sprintf (buffer, sizeof(buffer), "%s.wav", st.noise); + else + strncpy (buffer, st.noise, sizeof(buffer)); + ent->noise_index = gi.soundindex (buffer); + + if (!ent->volume) + ent->volume = 1.0; + + if (!ent->attenuation) + ent->attenuation = 1.0; + else if (ent->attenuation == -1) // use -1 so 0 defaults to 1 + ent->attenuation = 0; + + // check for prestarted looping sound + if (ent->spawnflags & 1) + ent->s.sound = ent->noise_index; + + ent->use = Use_Target_Speaker; + + // must link the entity so we get areas and clusters so + // the server can determine who to send updates to + gi.linkentity (ent); +} + + +//========================================================== + +void Use_Target_Help (edict_t *ent, edict_t *other, edict_t *activator) +{ + if (ent->spawnflags & 1) + strncpy (game.helpmessage1, ent->message, sizeof(game.helpmessage2)-1); + else + strncpy (game.helpmessage2, ent->message, sizeof(game.helpmessage1)-1); + + game.helpchanged++; +} + +/*QUAKED target_help (1 0 1) (-16 -16 -24) (16 16 24) help1 +When fired, the "message" key becomes the current personal computer string, and the message light will be set on all clients status bars. +*/ +void SP_target_help(edict_t *ent) +{ + if (deathmatch->value) + { // auto-remove for deathmatch + G_FreeEdict (ent); + return; + } + + if (!ent->message) + { + gi.dprintf ("%s with no message at %s\n", ent->classname, vtos(ent->s.origin)); + G_FreeEdict (ent); + return; + } + ent->use = Use_Target_Help; +} + +//========================================================== + +/*QUAKED target_secret (1 0 1) (-8 -8 -8) (8 8 8) +Counts a secret found. +These are single use targets. +*/ +void use_target_secret (edict_t *ent, edict_t *other, edict_t *activator) +{ + gi.sound (ent, CHAN_VOICE, ent->noise_index, 1, ATTN_NORM, 0); + + level.found_secrets++; + + G_UseTargets (ent, activator); + G_FreeEdict (ent); +} + +void SP_target_secret (edict_t *ent) +{ + if (deathmatch->value) + { // auto-remove for deathmatch + G_FreeEdict (ent); + return; + } + + ent->use = use_target_secret; + if (!st.noise) + st.noise = "misc/secret.wav"; + ent->noise_index = gi.soundindex (st.noise); + ent->svflags = SVF_NOCLIENT; + level.total_secrets++; + // map bug hack + if (!Q_stricmp(level.mapname, "mine3") && ent->s.origin[0] == 280 && ent->s.origin[1] == -2048 && ent->s.origin[2] == -624) + ent->message = "You have found a secret area."; +} + +//========================================================== + +/*QUAKED target_goal (1 0 1) (-8 -8 -8) (8 8 8) +Counts a goal completed. +These are single use targets. +*/ +void use_target_goal (edict_t *ent, edict_t *other, edict_t *activator) +{ + gi.sound (ent, CHAN_VOICE, ent->noise_index, 1, ATTN_NORM, 0); + + level.found_goals++; + + if (level.found_goals == level.total_goals) + gi.configstring (CS_CDTRACK, "0"); + + G_UseTargets (ent, activator); + G_FreeEdict (ent); +} + +void SP_target_goal (edict_t *ent) +{ + if (deathmatch->value) + { // auto-remove for deathmatch + G_FreeEdict (ent); + return; + } + + ent->use = use_target_goal; + if (!st.noise) + st.noise = "misc/secret.wav"; + ent->noise_index = gi.soundindex (st.noise); + ent->svflags = SVF_NOCLIENT; + level.total_goals++; +} + +//========================================================== + + +/*QUAKED target_explosion (1 0 0) (-8 -8 -8) (8 8 8) +Spawns an explosion temporary entity when used. + +"delay" wait this long before going off +"dmg" how much radius damage should be done, defaults to 0 +*/ +void target_explosion_explode (edict_t *self) +{ + float save; + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_EXPLOSION1); + gi.WritePosition (self->s.origin); + gi.multicast (self->s.origin, MULTICAST_PHS); + + T_RadiusDamage (self, self->activator, self->dmg, NULL, self->dmg+40, MOD_EXPLOSIVE); + + save = self->delay; + self->delay = 0; + G_UseTargets (self, self->activator); + self->delay = save; +} + +void use_target_explosion (edict_t *self, edict_t *other, edict_t *activator) +{ + self->activator = activator; + + if (!self->delay) + { + target_explosion_explode (self); + return; + } + + self->think = target_explosion_explode; + self->nextthink = level.time + self->delay; +} + +void SP_target_explosion (edict_t *ent) +{ + ent->use = use_target_explosion; + ent->svflags = SVF_NOCLIENT; +} + + +//========================================================== + +/*QUAKED target_changelevel (1 0 0) (-8 -8 -8) (8 8 8) +Changes level to "map" when fired +*/ +void use_target_changelevel (edict_t *self, edict_t *other, edict_t *activator) +{ + if (level.intermissiontime) + return; // already activated + + if (!deathmatch->value && !coop->value) + { + if (g_edicts[1].health <= 0) + return; + } + + // if noexit, do a ton of damage to other + if (deathmatch->value && !( (int)dmflags->value & DF_ALLOW_EXIT) && other != world) + { + T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, 10 * other->max_health, 1000, 0, MOD_EXIT); + return; + } + + // if multiplayer, let everyone know who hit the exit + if (deathmatch->value) + { + if (activator && activator->client) + gi.bprintf (PRINT_HIGH, "%s exited the level.\n", activator->client->pers.netname); + } + + // if going to a new unit, clear cross triggers + if (strstr(self->map, "*")) + game.serverflags &= ~(SFL_CROSS_TRIGGER_MASK); + + BeginIntermission (self); +} + +void SP_target_changelevel (edict_t *ent) +{ + if (!ent->map) + { + gi.dprintf("target_changelevel with no map at %s\n", vtos(ent->s.origin)); + G_FreeEdict (ent); + return; + } + + // ugly hack because *SOMEBODY* screwed up their map + if((Q_stricmp(level.mapname, "fact1") == 0) && (Q_stricmp(ent->map, "fact3") == 0)) + ent->map = "fact3$secret1"; + + ent->use = use_target_changelevel; + ent->svflags = SVF_NOCLIENT; +} + + +//========================================================== + +/*QUAKED target_splash (1 0 0) (-8 -8 -8) (8 8 8) +Creates a particle splash effect when used. + +Set "sounds" to one of the following: + 1) sparks + 2) blue water + 3) brown water + 4) slime + 5) lava + 6) blood + +"count" how many pixels in the splash +"dmg" if set, does a radius damage at this location when it splashes + useful for lava/sparks +*/ + +void use_target_splash (edict_t *self, edict_t *other, edict_t *activator) +{ + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_SPLASH); + gi.WriteByte (self->count); + gi.WritePosition (self->s.origin); + gi.WriteDir (self->movedir); + gi.WriteByte (self->sounds); + gi.multicast (self->s.origin, MULTICAST_PVS); + + if (self->dmg) + T_RadiusDamage (self, activator, self->dmg, NULL, self->dmg+40, MOD_SPLASH); +} + +void SP_target_splash (edict_t *self) +{ + self->use = use_target_splash; + G_SetMovedir (self->s.angles, self->movedir); + + if (!self->count) + self->count = 32; + + self->svflags = SVF_NOCLIENT; +} + + +//========================================================== + +/*QUAKED target_spawner (1 0 0) (-8 -8 -8) (8 8 8) +Set target to the type of entity you want spawned. +Useful for spawning monsters and gibs in the factory levels. + +For monsters: + Set direction to the facing you want it to have. + +For gibs: + Set direction if you want it moving and + speed how fast it should be moving otherwise it + will just be dropped +*/ +void ED_CallSpawn (edict_t *ent); + +void use_target_spawner (edict_t *self, edict_t *other, edict_t *activator) +{ + edict_t *ent; + + ent = G_Spawn(); + ent->classname = self->target; + VectorCopy (self->s.origin, ent->s.origin); + VectorCopy (self->s.angles, ent->s.angles); + ED_CallSpawn (ent); + gi.unlinkentity (ent); + KillBox (ent); + gi.linkentity (ent); + if (self->speed) + VectorCopy (self->movedir, ent->velocity); +} + +void SP_target_spawner (edict_t *self) +{ + self->use = use_target_spawner; + self->svflags = SVF_NOCLIENT; + if (self->speed) + { + G_SetMovedir (self->s.angles, self->movedir); + VectorScale (self->movedir, self->speed, self->movedir); + } +} + +//========================================================== + +/*QUAKED target_blaster (1 0 0) (-8 -8 -8) (8 8 8) NOTRAIL NOEFFECTS +Fires a blaster bolt in the set direction when triggered. + +dmg default is 15 +speed default is 1000 +*/ + +void use_target_blaster (edict_t *self, edict_t *other, edict_t *activator) +{ + int effect; + + if (self->spawnflags & 2) + effect = 0; + else if (self->spawnflags & 1) + effect = EF_HYPERBLASTER; + else + effect = EF_BLASTER; + + fire_blaster (self, self->s.origin, self->movedir, self->dmg, self->speed, EF_BLASTER, MOD_TARGET_BLASTER); + gi.sound (self, CHAN_VOICE, self->noise_index, 1, ATTN_NORM, 0); +} + +void SP_target_blaster (edict_t *self) +{ + self->use = use_target_blaster; + G_SetMovedir (self->s.angles, self->movedir); + self->noise_index = gi.soundindex ("weapons/laser2.wav"); + + if (!self->dmg) + self->dmg = 15; + if (!self->speed) + self->speed = 1000; + + self->svflags = SVF_NOCLIENT; +} + + +//========================================================== + +/*QUAKED target_crosslevel_trigger (.5 .5 .5) (-8 -8 -8) (8 8 8) trigger1 trigger2 trigger3 trigger4 trigger5 trigger6 trigger7 trigger8 +Once this trigger is touched/used, any trigger_crosslevel_target with the same trigger number is automatically used when a level is started within the same unit. It is OK to check multiple triggers. Message, delay, target, and killtarget also work. +*/ +void trigger_crosslevel_trigger_use (edict_t *self, edict_t *other, edict_t *activator) +{ + game.serverflags |= self->spawnflags; + G_FreeEdict (self); +} + +void SP_target_crosslevel_trigger (edict_t *self) +{ + self->svflags = SVF_NOCLIENT; + self->use = trigger_crosslevel_trigger_use; +} + +/*QUAKED target_crosslevel_target (.5 .5 .5) (-8 -8 -8) (8 8 8) trigger1 trigger2 trigger3 trigger4 trigger5 trigger6 trigger7 trigger8 +Triggered by a trigger_crosslevel elsewhere within a unit. If multiple triggers are checked, all must be true. Delay, target and +killtarget also work. + +"delay" delay before using targets if the trigger has been activated (default 1) +*/ +void target_crosslevel_target_think (edict_t *self) +{ + if (self->spawnflags == (game.serverflags & SFL_CROSS_TRIGGER_MASK & self->spawnflags)) + { + G_UseTargets (self, self); + G_FreeEdict (self); + } +} + +void SP_target_crosslevel_target (edict_t *self) +{ + if (! self->delay) + self->delay = 1; + self->svflags = SVF_NOCLIENT; + + self->think = target_crosslevel_target_think; + self->nextthink = level.time + self->delay; +} + +//========================================================== + +/*QUAKED target_laser (0 .5 .8) (-8 -8 -8) (8 8 8) START_ON RED GREEN BLUE YELLOW ORANGE FAT +When triggered, fires a laser. You can either set a target +or a direction. +*/ + +void target_laser_think (edict_t *self) +{ + edict_t *ignore; + vec3_t start; + vec3_t end; + trace_t tr; + vec3_t point; + vec3_t last_movedir; + int count; + + if (self->spawnflags & 0x80000000) + count = 8; + else + count = 4; + + if (self->enemy) + { + VectorCopy (self->movedir, last_movedir); + VectorMA (self->enemy->absmin, 0.5, self->enemy->size, point); + VectorSubtract (point, self->s.origin, self->movedir); + VectorNormalize (self->movedir); + if (!VectorCompare(self->movedir, last_movedir)) + self->spawnflags |= 0x80000000; + } + + ignore = self; + VectorCopy (self->s.origin, start); + VectorMA (start, 2048, self->movedir, end); + while(1) + { + tr = gi.trace (start, NULL, NULL, end, ignore, CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_DEADMONSTER); + + if (!tr.ent) + break; + + // hurt it if we can + if ((tr.ent->takedamage) && !(tr.ent->flags & FL_IMMUNE_LASER)) + T_Damage (tr.ent, self, self->activator, self->movedir, tr.endpos, vec3_origin, self->dmg, 1, DAMAGE_ENERGY, MOD_TARGET_LASER); + + // if we hit something that's not a monster or player or is immune to lasers, we're done + if (!(tr.ent->svflags & SVF_MONSTER) && (!tr.ent->client)) + { + if (self->spawnflags & 0x80000000) + { + self->spawnflags &= ~0x80000000; + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_LASER_SPARKS); + gi.WriteByte (count); + gi.WritePosition (tr.endpos); + gi.WriteDir (tr.plane.normal); + gi.WriteByte (self->s.skinnum); + gi.multicast (tr.endpos, MULTICAST_PVS); + } + break; + } + + ignore = tr.ent; + VectorCopy (tr.endpos, start); + } + + VectorCopy (tr.endpos, self->s.old_origin); + + self->nextthink = level.time + FRAMETIME; +} + +void target_laser_on (edict_t *self) +{ + if (!self->activator) + self->activator = self; + self->spawnflags |= 0x80000001; + self->svflags &= ~SVF_NOCLIENT; + target_laser_think (self); +} + +void target_laser_off (edict_t *self) +{ + self->spawnflags &= ~1; + self->svflags |= SVF_NOCLIENT; + self->nextthink = 0; +} + +void target_laser_use (edict_t *self, edict_t *other, edict_t *activator) +{ + self->activator = activator; + if (self->spawnflags & 1) + target_laser_off (self); + else + target_laser_on (self); +} + +void target_laser_start (edict_t *self) +{ + edict_t *ent; + + self->movetype = MOVETYPE_NONE; + self->solid = SOLID_NOT; + self->s.renderfx |= RF_BEAM|RF_TRANSLUCENT; + self->s.modelindex = 1; // must be non-zero + + // set the beam diameter + if (self->spawnflags & 64) + self->s.frame = 16; + else + self->s.frame = 4; + + // set the color + if (self->spawnflags & 2) + self->s.skinnum = 0xf2f2f0f0; + else if (self->spawnflags & 4) + self->s.skinnum = 0xd0d1d2d3; + else if (self->spawnflags & 8) + self->s.skinnum = 0xf3f3f1f1; + else if (self->spawnflags & 16) + self->s.skinnum = 0xdcdddedf; + else if (self->spawnflags & 32) + self->s.skinnum = 0xe0e1e2e3; + + if (!self->enemy) + { + if (self->target) + { + ent = G_Find (NULL, FOFS(targetname), self->target); + if (!ent) + gi.dprintf ("%s at %s: %s is a bad target\n", self->classname, vtos(self->s.origin), self->target); + self->enemy = ent; + } + else + { + G_SetMovedir (self->s.angles, self->movedir); + } + } + self->use = target_laser_use; + self->think = target_laser_think; + + if (!self->dmg) + self->dmg = 1; + + VectorSet (self->mins, -8, -8, -8); + VectorSet (self->maxs, 8, 8, 8); + gi.linkentity (self); + + if (self->spawnflags & 1) + target_laser_on (self); + else + target_laser_off (self); +} + +void SP_target_laser (edict_t *self) +{ + // let everything else get spawned before we start firing + self->think = target_laser_start; + self->nextthink = level.time + 1; +} + +//========================================================== + +/*QUAKED target_lightramp (0 .5 .8) (-8 -8 -8) (8 8 8) TOGGLE +speed How many seconds the ramping will take +message two letters; starting lightlevel and ending lightlevel +*/ + +void target_lightramp_think (edict_t *self) +{ + char style[2]; + + style[0] = 'a' + self->movedir[0] + (level.time - self->timestamp) / FRAMETIME * self->movedir[2]; + style[1] = 0; + gi.configstring (CS_LIGHTS+self->enemy->style, style); + + if ((level.time - self->timestamp) < self->speed) + { + self->nextthink = level.time + FRAMETIME; + } + else if (self->spawnflags & 1) + { + char temp; + + temp = self->movedir[0]; + self->movedir[0] = self->movedir[1]; + self->movedir[1] = temp; + self->movedir[2] *= -1; + } +} + +void target_lightramp_use (edict_t *self, edict_t *other, edict_t *activator) +{ + if (!self->enemy) + { + edict_t *e; + + // check all the targets + e = NULL; + while (1) + { + e = G_Find (e, FOFS(targetname), self->target); + if (!e) + break; + if (strcmp(e->classname, "light") != 0) + { + gi.dprintf("%s at %s ", self->classname, vtos(self->s.origin)); + gi.dprintf("target %s (%s at %s) is not a light\n", self->target, e->classname, vtos(e->s.origin)); + } + else + { + self->enemy = e; + } + } + + if (!self->enemy) + { + gi.dprintf("%s target %s not found at %s\n", self->classname, self->target, vtos(self->s.origin)); + G_FreeEdict (self); + return; + } + } + + self->timestamp = level.time; + target_lightramp_think (self); +} + +void SP_target_lightramp (edict_t *self) +{ + if (!self->message || strlen(self->message) != 2 || self->message[0] < 'a' || self->message[0] > 'z' || self->message[1] < 'a' || self->message[1] > 'z' || self->message[0] == self->message[1]) + { + gi.dprintf("target_lightramp has bad ramp (%s) at %s\n", self->message, vtos(self->s.origin)); + G_FreeEdict (self); + return; + } + + if (deathmatch->value) + { + G_FreeEdict (self); + return; + } + + if (!self->target) + { + gi.dprintf("%s with no target at %s\n", self->classname, vtos(self->s.origin)); + G_FreeEdict (self); + return; + } + + self->svflags |= SVF_NOCLIENT; + self->use = target_lightramp_use; + self->think = target_lightramp_think; + + self->movedir[0] = self->message[0] - 'a'; + self->movedir[1] = self->message[1] - 'a'; + self->movedir[2] = (self->movedir[1] - self->movedir[0]) / (self->speed / FRAMETIME); +} + +//========================================================== + +/*QUAKED target_earthquake (1 0 0) (-8 -8 -8) (8 8 8) +When triggered, this initiates a level-wide earthquake. +All players and monsters are affected. +"speed" severity of the quake (default:200) +"count" duration of the quake (default:5) +*/ + +void target_earthquake_think (edict_t *self) +{ + int i; + edict_t *e; + + if (self->last_move_time < level.time) + { + gi.positioned_sound (self->s.origin, self, CHAN_AUTO, self->noise_index, 1.0, ATTN_NONE, 0); + self->last_move_time = level.time + 0.5; + } + + for (i=1, e=g_edicts+i; i < globals.num_edicts; i++,e++) + { + if (!e->inuse) + continue; + if (!e->client) + continue; + if (!e->groundentity) + continue; + + e->groundentity = NULL; + e->velocity[0] += crandom()* 150; + e->velocity[1] += crandom()* 150; + e->velocity[2] = self->speed * (100.0 / e->mass); + } + + if (level.time < self->timestamp) + self->nextthink = level.time + FRAMETIME; +} + +void target_earthquake_use (edict_t *self, edict_t *other, edict_t *activator) +{ + self->timestamp = level.time + self->count; + self->nextthink = level.time + FRAMETIME; + self->activator = activator; + self->last_move_time = 0; +} + +void SP_target_earthquake (edict_t *self) +{ + if (!self->targetname) + gi.dprintf("untargeted %s at %s\n", self->classname, vtos(self->s.origin)); + + if (!self->count) + self->count = 5; + + if (!self->speed) + self->speed = 200; + + self->svflags |= SVF_NOCLIENT; + self->think = target_earthquake_think; + self->use = target_earthquake_use; + + self->noise_index = gi.soundindex ("world/quake.wav"); +} diff --git a/original/baseq2/g_trigger.c b/original/baseq2/g_trigger.c new file mode 100644 index 0000000..53a1b6c --- /dev/null +++ b/original/baseq2/g_trigger.c @@ -0,0 +1,598 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +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. + +*/ +#include "g_local.h" + + +void InitTrigger (edict_t *self) +{ + if (!VectorCompare (self->s.angles, vec3_origin)) + G_SetMovedir (self->s.angles, self->movedir); + + self->solid = SOLID_TRIGGER; + self->movetype = MOVETYPE_NONE; + gi.setmodel (self, self->model); + self->svflags = SVF_NOCLIENT; +} + + +// the wait time has passed, so set back up for another activation +void multi_wait (edict_t *ent) +{ + ent->nextthink = 0; +} + + +// the trigger was just activated +// ent->activator should be set to the activator so it can be held through a delay +// so wait for the delay time before firing +void multi_trigger (edict_t *ent) +{ + if (ent->nextthink) + return; // already been triggered + + G_UseTargets (ent, ent->activator); + + if (ent->wait > 0) + { + ent->think = multi_wait; + ent->nextthink = level.time + ent->wait; + } + else + { // we can't just remove (self) here, because this is a touch function + // called while looping through area links... + ent->touch = NULL; + ent->nextthink = level.time + FRAMETIME; + ent->think = G_FreeEdict; + } +} + +void Use_Multi (edict_t *ent, edict_t *other, edict_t *activator) +{ + ent->activator = activator; + multi_trigger (ent); +} + +void Touch_Multi (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if(other->client) + { + if (self->spawnflags & 2) + return; + } + else if (other->svflags & SVF_MONSTER) + { + if (!(self->spawnflags & 1)) + return; + } + else + return; + + if (!VectorCompare(self->movedir, vec3_origin)) + { + vec3_t forward; + + AngleVectors(other->s.angles, forward, NULL, NULL); + if (_DotProduct(forward, self->movedir) < 0) + return; + } + + self->activator = other; + multi_trigger (self); +} + +/*QUAKED trigger_multiple (.5 .5 .5) ? MONSTER NOT_PLAYER TRIGGERED +Variable sized repeatable trigger. Must be targeted at one or more entities. +If "delay" is set, the trigger waits some time after activating before firing. +"wait" : Seconds between triggerings. (.2 default) +sounds +1) secret +2) beep beep +3) large switch +4) +set "message" to text string +*/ +void trigger_enable (edict_t *self, edict_t *other, edict_t *activator) +{ + self->solid = SOLID_TRIGGER; + self->use = Use_Multi; + gi.linkentity (self); +} + +void SP_trigger_multiple (edict_t *ent) +{ + if (ent->sounds == 1) + ent->noise_index = gi.soundindex ("misc/secret.wav"); + else if (ent->sounds == 2) + ent->noise_index = gi.soundindex ("misc/talk.wav"); + else if (ent->sounds == 3) + ent->noise_index = gi.soundindex ("misc/trigger1.wav"); + + if (!ent->wait) + ent->wait = 0.2; + ent->touch = Touch_Multi; + ent->movetype = MOVETYPE_NONE; + ent->svflags |= SVF_NOCLIENT; + + + if (ent->spawnflags & 4) + { + ent->solid = SOLID_NOT; + ent->use = trigger_enable; + } + else + { + ent->solid = SOLID_TRIGGER; + ent->use = Use_Multi; + } + + if (!VectorCompare(ent->s.angles, vec3_origin)) + G_SetMovedir (ent->s.angles, ent->movedir); + + gi.setmodel (ent, ent->model); + gi.linkentity (ent); +} + + +/*QUAKED trigger_once (.5 .5 .5) ? x x TRIGGERED +Triggers once, then removes itself. +You must set the key "target" to the name of another object in the level that has a matching "targetname". + +If TRIGGERED, this trigger must be triggered before it is live. + +sounds + 1) secret + 2) beep beep + 3) large switch + 4) + +"message" string to be displayed when triggered +*/ + +void SP_trigger_once(edict_t *ent) +{ + // make old maps work because I messed up on flag assignments here + // triggered was on bit 1 when it should have been on bit 4 + if (ent->spawnflags & 1) + { + vec3_t v; + + VectorMA (ent->mins, 0.5, ent->size, v); + ent->spawnflags &= ~1; + ent->spawnflags |= 4; + gi.dprintf("fixed TRIGGERED flag on %s at %s\n", ent->classname, vtos(v)); + } + + ent->wait = -1; + SP_trigger_multiple (ent); +} + +/*QUAKED trigger_relay (.5 .5 .5) (-8 -8 -8) (8 8 8) +This fixed size trigger cannot be touched, it can only be fired by other events. +*/ +void trigger_relay_use (edict_t *self, edict_t *other, edict_t *activator) +{ + G_UseTargets (self, activator); +} + +void SP_trigger_relay (edict_t *self) +{ + self->use = trigger_relay_use; +} + + +/* +============================================================================== + +trigger_key + +============================================================================== +*/ + +/*QUAKED trigger_key (.5 .5 .5) (-8 -8 -8) (8 8 8) +A relay trigger that only fires it's targets if player has the proper key. +Use "item" to specify the required key, for example "key_data_cd" +*/ +void trigger_key_use (edict_t *self, edict_t *other, edict_t *activator) +{ + int index; + + if (!self->item) + return; + if (!activator->client) + return; + + index = ITEM_INDEX(self->item); + if (!activator->client->pers.inventory[index]) + { + if (level.time < self->touch_debounce_time) + return; + self->touch_debounce_time = level.time + 5.0; + gi.centerprintf (activator, "You need the %s", self->item->pickup_name); + gi.sound (activator, CHAN_AUTO, gi.soundindex ("misc/keytry.wav"), 1, ATTN_NORM, 0); + return; + } + + gi.sound (activator, CHAN_AUTO, gi.soundindex ("misc/keyuse.wav"), 1, ATTN_NORM, 0); + if (coop->value) + { + int player; + edict_t *ent; + + if (strcmp(self->item->classname, "key_power_cube") == 0) + { + int cube; + + for (cube = 0; cube < 8; cube++) + if (activator->client->pers.power_cubes & (1 << cube)) + break; + for (player = 1; player <= game.maxclients; player++) + { + ent = &g_edicts[player]; + if (!ent->inuse) + continue; + if (!ent->client) + continue; + if (ent->client->pers.power_cubes & (1 << cube)) + { + ent->client->pers.inventory[index]--; + ent->client->pers.power_cubes &= ~(1 << cube); + } + } + } + else + { + for (player = 1; player <= game.maxclients; player++) + { + ent = &g_edicts[player]; + if (!ent->inuse) + continue; + if (!ent->client) + continue; + ent->client->pers.inventory[index] = 0; + } + } + } + else + { + activator->client->pers.inventory[index]--; + } + + G_UseTargets (self, activator); + + self->use = NULL; +} + +void SP_trigger_key (edict_t *self) +{ + if (!st.item) + { + gi.dprintf("no key item for trigger_key at %s\n", vtos(self->s.origin)); + return; + } + self->item = FindItemByClassname (st.item); + + if (!self->item) + { + gi.dprintf("item %s not found for trigger_key at %s\n", st.item, vtos(self->s.origin)); + return; + } + + if (!self->target) + { + gi.dprintf("%s at %s has no target\n", self->classname, vtos(self->s.origin)); + return; + } + + gi.soundindex ("misc/keytry.wav"); + gi.soundindex ("misc/keyuse.wav"); + + self->use = trigger_key_use; +} + + +/* +============================================================================== + +trigger_counter + +============================================================================== +*/ + +/*QUAKED trigger_counter (.5 .5 .5) ? nomessage +Acts as an intermediary for an action that takes multiple inputs. + +If nomessage is not set, t will print "1 more.. " etc when triggered and "sequence complete" when finished. + +After the counter has been triggered "count" times (default 2), it will fire all of it's targets and remove itself. +*/ + +void trigger_counter_use(edict_t *self, edict_t *other, edict_t *activator) +{ + if (self->count == 0) + return; + + self->count--; + + if (self->count) + { + if (! (self->spawnflags & 1)) + { + gi.centerprintf(activator, "%i more to go...", self->count); + gi.sound (activator, CHAN_AUTO, gi.soundindex ("misc/talk1.wav"), 1, ATTN_NORM, 0); + } + return; + } + + if (! (self->spawnflags & 1)) + { + gi.centerprintf(activator, "Sequence completed!"); + gi.sound (activator, CHAN_AUTO, gi.soundindex ("misc/talk1.wav"), 1, ATTN_NORM, 0); + } + self->activator = activator; + multi_trigger (self); +} + +void SP_trigger_counter (edict_t *self) +{ + self->wait = -1; + if (!self->count) + self->count = 2; + + self->use = trigger_counter_use; +} + + +/* +============================================================================== + +trigger_always + +============================================================================== +*/ + +/*QUAKED trigger_always (.5 .5 .5) (-8 -8 -8) (8 8 8) +This trigger will always fire. It is activated by the world. +*/ +void SP_trigger_always (edict_t *ent) +{ + // we must have some delay to make sure our use targets are present + if (ent->delay < 0.2) + ent->delay = 0.2; + G_UseTargets(ent, ent); +} + + +/* +============================================================================== + +trigger_push + +============================================================================== +*/ + +#define PUSH_ONCE 1 + +static int windsound; + +void trigger_push_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (strcmp(other->classname, "grenade") == 0) + { + VectorScale (self->movedir, self->speed * 10, other->velocity); + } + else if (other->health > 0) + { + VectorScale (self->movedir, self->speed * 10, other->velocity); + + if (other->client) + { + // don't take falling damage immediately from this + VectorCopy (other->velocity, other->client->oldvelocity); + if (other->fly_sound_debounce_time < level.time) + { + other->fly_sound_debounce_time = level.time + 1.5; + gi.sound (other, CHAN_AUTO, windsound, 1, ATTN_NORM, 0); + } + } + } + if (self->spawnflags & PUSH_ONCE) + G_FreeEdict (self); +} + + +/*QUAKED trigger_push (.5 .5 .5) ? PUSH_ONCE +Pushes the player +"speed" defaults to 1000 +*/ +void SP_trigger_push (edict_t *self) +{ + InitTrigger (self); + windsound = gi.soundindex ("misc/windfly.wav"); + self->touch = trigger_push_touch; + if (!self->speed) + self->speed = 1000; + gi.linkentity (self); +} + + +/* +============================================================================== + +trigger_hurt + +============================================================================== +*/ + +/*QUAKED trigger_hurt (.5 .5 .5) ? START_OFF TOGGLE SILENT NO_PROTECTION SLOW +Any entity that touches this will be hurt. + +It does dmg points of damage each server frame + +SILENT supresses playing the sound +SLOW changes the damage rate to once per second +NO_PROTECTION *nothing* stops the damage + +"dmg" default 5 (whole numbers only) + +*/ +void hurt_use (edict_t *self, edict_t *other, edict_t *activator) +{ + if (self->solid == SOLID_NOT) + self->solid = SOLID_TRIGGER; + else + self->solid = SOLID_NOT; + gi.linkentity (self); + + if (!(self->spawnflags & 2)) + self->use = NULL; +} + + +void hurt_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + int dflags; + + if (!other->takedamage) + return; + + if (self->timestamp > level.time) + return; + + if (self->spawnflags & 16) + self->timestamp = level.time + 1; + else + self->timestamp = level.time + FRAMETIME; + + if (!(self->spawnflags & 4)) + { + if ((level.framenum % 10) == 0) + gi.sound (other, CHAN_AUTO, self->noise_index, 1, ATTN_NORM, 0); + } + + if (self->spawnflags & 8) + dflags = DAMAGE_NO_PROTECTION; + else + dflags = 0; + T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, self->dmg, dflags, MOD_TRIGGER_HURT); +} + +void SP_trigger_hurt (edict_t *self) +{ + InitTrigger (self); + + self->noise_index = gi.soundindex ("world/electro.wav"); + self->touch = hurt_touch; + + if (!self->dmg) + self->dmg = 5; + + if (self->spawnflags & 1) + self->solid = SOLID_NOT; + else + self->solid = SOLID_TRIGGER; + + if (self->spawnflags & 2) + self->use = hurt_use; + + gi.linkentity (self); +} + + +/* +============================================================================== + +trigger_gravity + +============================================================================== +*/ + +/*QUAKED trigger_gravity (.5 .5 .5) ? +Changes the touching entites gravity to +the value of "gravity". 1.0 is standard +gravity for the level. +*/ + +void trigger_gravity_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + other->gravity = self->gravity; +} + +void SP_trigger_gravity (edict_t *self) +{ + if (st.gravity == 0) + { + gi.dprintf("trigger_gravity without gravity set at %s\n", vtos(self->s.origin)); + G_FreeEdict (self); + return; + } + + InitTrigger (self); + self->gravity = atoi(st.gravity); + self->touch = trigger_gravity_touch; +} + + +/* +============================================================================== + +trigger_monsterjump + +============================================================================== +*/ + +/*QUAKED trigger_monsterjump (.5 .5 .5) ? +Walking monsters that touch this will jump in the direction of the trigger's angle +"speed" default to 200, the speed thrown forward +"height" default to 200, the speed thrown upwards +*/ + +void trigger_monsterjump_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (other->flags & (FL_FLY | FL_SWIM) ) + return; + if (other->svflags & SVF_DEADMONSTER) + return; + if ( !(other->svflags & SVF_MONSTER)) + return; + +// set XY even if not on ground, so the jump will clear lips + other->velocity[0] = self->movedir[0] * self->speed; + other->velocity[1] = self->movedir[1] * self->speed; + + if (!other->groundentity) + return; + + other->groundentity = NULL; + other->velocity[2] = self->movedir[2]; +} + +void SP_trigger_monsterjump (edict_t *self) +{ + if (!self->speed) + self->speed = 200; + if (!st.height) + st.height = 200; + if (self->s.angles[YAW] == 0) + self->s.angles[YAW] = 360; + InitTrigger (self); + self->touch = trigger_monsterjump_touch; + self->movedir[2] = st.height; +} + diff --git a/original/baseq2/g_turret.c b/original/baseq2/g_turret.c new file mode 100644 index 0000000..0153fd9 --- /dev/null +++ b/original/baseq2/g_turret.c @@ -0,0 +1,432 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +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. + +*/ +// g_turret.c + +#include "g_local.h" + + +void AnglesNormalize(vec3_t vec) +{ + while(vec[0] > 360) + vec[0] -= 360; + while(vec[0] < 0) + vec[0] += 360; + while(vec[1] > 360) + vec[1] -= 360; + while(vec[1] < 0) + vec[1] += 360; +} + +float SnapToEights(float x) +{ + x *= 8.0; + if (x > 0.0) + x += 0.5; + else + x -= 0.5; + return 0.125 * (int)x; +} + + +void turret_blocked(edict_t *self, edict_t *other) +{ + edict_t *attacker; + + if (other->takedamage) + { + if (self->teammaster->owner) + attacker = self->teammaster->owner; + else + attacker = self->teammaster; + T_Damage (other, self, attacker, vec3_origin, other->s.origin, vec3_origin, self->teammaster->dmg, 10, 0, MOD_CRUSH); + } +} + +/*QUAKED turret_breach (0 0 0) ? +This portion of the turret can change both pitch and yaw. +The model should be made with a flat pitch. +It (and the associated base) need to be oriented towards 0. +Use "angle" to set the starting angle. + +"speed" default 50 +"dmg" default 10 +"angle" point this forward +"target" point this at an info_notnull at the muzzle tip +"minpitch" min acceptable pitch angle : default -30 +"maxpitch" max acceptable pitch angle : default 30 +"minyaw" min acceptable yaw angle : default 0 +"maxyaw" max acceptable yaw angle : default 360 +*/ + +void turret_breach_fire (edict_t *self) +{ + vec3_t f, r, u; + vec3_t start; + int damage; + int speed; + + AngleVectors (self->s.angles, f, r, u); + VectorMA (self->s.origin, self->move_origin[0], f, start); + VectorMA (start, self->move_origin[1], r, start); + VectorMA (start, self->move_origin[2], u, start); + + damage = 100 + random() * 50; + speed = 550 + 50 * skill->value; + fire_rocket (self->teammaster->owner, start, f, damage, speed, 150, damage); + gi.positioned_sound (start, self, CHAN_WEAPON, gi.soundindex("weapons/rocklf1a.wav"), 1, ATTN_NORM, 0); +} + +void turret_breach_think (edict_t *self) +{ + edict_t *ent; + vec3_t current_angles; + vec3_t delta; + + VectorCopy (self->s.angles, current_angles); + AnglesNormalize(current_angles); + + AnglesNormalize(self->move_angles); + if (self->move_angles[PITCH] > 180) + self->move_angles[PITCH] -= 360; + + // clamp angles to mins & maxs + if (self->move_angles[PITCH] > self->pos1[PITCH]) + self->move_angles[PITCH] = self->pos1[PITCH]; + else if (self->move_angles[PITCH] < self->pos2[PITCH]) + self->move_angles[PITCH] = self->pos2[PITCH]; + + if ((self->move_angles[YAW] < self->pos1[YAW]) || (self->move_angles[YAW] > self->pos2[YAW])) + { + float dmin, dmax; + + dmin = fabs(self->pos1[YAW] - self->move_angles[YAW]); + if (dmin < -180) + dmin += 360; + else if (dmin > 180) + dmin -= 360; + dmax = fabs(self->pos2[YAW] - self->move_angles[YAW]); + if (dmax < -180) + dmax += 360; + else if (dmax > 180) + dmax -= 360; + if (fabs(dmin) < fabs(dmax)) + self->move_angles[YAW] = self->pos1[YAW]; + else + self->move_angles[YAW] = self->pos2[YAW]; + } + + VectorSubtract (self->move_angles, current_angles, delta); + if (delta[0] < -180) + delta[0] += 360; + else if (delta[0] > 180) + delta[0] -= 360; + if (delta[1] < -180) + delta[1] += 360; + else if (delta[1] > 180) + delta[1] -= 360; + delta[2] = 0; + + if (delta[0] > self->speed * FRAMETIME) + delta[0] = self->speed * FRAMETIME; + if (delta[0] < -1 * self->speed * FRAMETIME) + delta[0] = -1 * self->speed * FRAMETIME; + if (delta[1] > self->speed * FRAMETIME) + delta[1] = self->speed * FRAMETIME; + if (delta[1] < -1 * self->speed * FRAMETIME) + delta[1] = -1 * self->speed * FRAMETIME; + + VectorScale (delta, 1.0/FRAMETIME, self->avelocity); + + self->nextthink = level.time + FRAMETIME; + + for (ent = self->teammaster; ent; ent = ent->teamchain) + ent->avelocity[1] = self->avelocity[1]; + + // if we have adriver, adjust his velocities + if (self->owner) + { + float angle; + float target_z; + float diff; + vec3_t target; + vec3_t dir; + + // angular is easy, just copy ours + self->owner->avelocity[0] = self->avelocity[0]; + self->owner->avelocity[1] = self->avelocity[1]; + + // x & y + angle = self->s.angles[1] + self->owner->move_origin[1]; + angle *= (M_PI*2 / 360); + target[0] = SnapToEights(self->s.origin[0] + cos(angle) * self->owner->move_origin[0]); + target[1] = SnapToEights(self->s.origin[1] + sin(angle) * self->owner->move_origin[0]); + target[2] = self->owner->s.origin[2]; + + VectorSubtract (target, self->owner->s.origin, dir); + self->owner->velocity[0] = dir[0] * 1.0 / FRAMETIME; + self->owner->velocity[1] = dir[1] * 1.0 / FRAMETIME; + + // z + angle = self->s.angles[PITCH] * (M_PI*2 / 360); + target_z = SnapToEights(self->s.origin[2] + self->owner->move_origin[0] * tan(angle) + self->owner->move_origin[2]); + + diff = target_z - self->owner->s.origin[2]; + self->owner->velocity[2] = diff * 1.0 / FRAMETIME; + + if (self->spawnflags & 65536) + { + turret_breach_fire (self); + self->spawnflags &= ~65536; + } + } +} + +void turret_breach_finish_init (edict_t *self) +{ + // get and save info for muzzle location + if (!self->target) + { + gi.dprintf("%s at %s needs a target\n", self->classname, vtos(self->s.origin)); + } + else + { + self->target_ent = G_PickTarget (self->target); + VectorSubtract (self->target_ent->s.origin, self->s.origin, self->move_origin); + G_FreeEdict(self->target_ent); + } + + self->teammaster->dmg = self->dmg; + self->think = turret_breach_think; + self->think (self); +} + +void SP_turret_breach (edict_t *self) +{ + self->solid = SOLID_BSP; + self->movetype = MOVETYPE_PUSH; + gi.setmodel (self, self->model); + + if (!self->speed) + self->speed = 50; + if (!self->dmg) + self->dmg = 10; + + if (!st.minpitch) + st.minpitch = -30; + if (!st.maxpitch) + st.maxpitch = 30; + if (!st.maxyaw) + st.maxyaw = 360; + + self->pos1[PITCH] = -1 * st.minpitch; + self->pos1[YAW] = st.minyaw; + self->pos2[PITCH] = -1 * st.maxpitch; + self->pos2[YAW] = st.maxyaw; + + self->ideal_yaw = self->s.angles[YAW]; + self->move_angles[YAW] = self->ideal_yaw; + + self->blocked = turret_blocked; + + self->think = turret_breach_finish_init; + self->nextthink = level.time + FRAMETIME; + gi.linkentity (self); +} + + +/*QUAKED turret_base (0 0 0) ? +This portion of the turret changes yaw only. +MUST be teamed with a turret_breach. +*/ + +void SP_turret_base (edict_t *self) +{ + self->solid = SOLID_BSP; + self->movetype = MOVETYPE_PUSH; + gi.setmodel (self, self->model); + self->blocked = turret_blocked; + gi.linkentity (self); +} + + +/*QUAKED turret_driver (1 .5 0) (-16 -16 -24) (16 16 32) +Must NOT be on the team with the rest of the turret parts. +Instead it must target the turret_breach. +*/ + +void infantry_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage); +void infantry_stand (edict_t *self); +void monster_use (edict_t *self, edict_t *other, edict_t *activator); + +void turret_driver_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + edict_t *ent; + + // level the gun + self->target_ent->move_angles[0] = 0; + + // remove the driver from the end of them team chain + for (ent = self->target_ent->teammaster; ent->teamchain != self; ent = ent->teamchain) + ; + ent->teamchain = NULL; + self->teammaster = NULL; + self->flags &= ~FL_TEAMSLAVE; + + self->target_ent->owner = NULL; + self->target_ent->teammaster->owner = NULL; + + infantry_die (self, inflictor, attacker, damage); +} + +qboolean FindTarget (edict_t *self); + +void turret_driver_think (edict_t *self) +{ + vec3_t target; + vec3_t dir; + float reaction_time; + + self->nextthink = level.time + FRAMETIME; + + if (self->enemy && (!self->enemy->inuse || self->enemy->health <= 0)) + self->enemy = NULL; + + if (!self->enemy) + { + if (!FindTarget (self)) + return; + self->monsterinfo.trail_time = level.time; + self->monsterinfo.aiflags &= ~AI_LOST_SIGHT; + } + else + { + if (visible (self, self->enemy)) + { + if (self->monsterinfo.aiflags & AI_LOST_SIGHT) + { + self->monsterinfo.trail_time = level.time; + self->monsterinfo.aiflags &= ~AI_LOST_SIGHT; + } + } + else + { + self->monsterinfo.aiflags |= AI_LOST_SIGHT; + return; + } + } + + // let the turret know where we want it to aim + VectorCopy (self->enemy->s.origin, target); + target[2] += self->enemy->viewheight; + VectorSubtract (target, self->target_ent->s.origin, dir); + vectoangles (dir, self->target_ent->move_angles); + + // decide if we should shoot + if (level.time < self->monsterinfo.attack_finished) + return; + + reaction_time = (3 - skill->value) * 1.0; + if ((level.time - self->monsterinfo.trail_time) < reaction_time) + return; + + self->monsterinfo.attack_finished = level.time + reaction_time + 1.0; + //FIXME how do we really want to pass this along? + self->target_ent->spawnflags |= 65536; +} + +void turret_driver_link (edict_t *self) +{ + vec3_t vec; + edict_t *ent; + + self->think = turret_driver_think; + self->nextthink = level.time + FRAMETIME; + + self->target_ent = G_PickTarget (self->target); + self->target_ent->owner = self; + self->target_ent->teammaster->owner = self; + VectorCopy (self->target_ent->s.angles, self->s.angles); + + vec[0] = self->target_ent->s.origin[0] - self->s.origin[0]; + vec[1] = self->target_ent->s.origin[1] - self->s.origin[1]; + vec[2] = 0; + self->move_origin[0] = VectorLength(vec); + + VectorSubtract (self->s.origin, self->target_ent->s.origin, vec); + vectoangles (vec, vec); + AnglesNormalize(vec); + self->move_origin[1] = vec[1]; + + self->move_origin[2] = self->s.origin[2] - self->target_ent->s.origin[2]; + + // add the driver to the end of them team chain + for (ent = self->target_ent->teammaster; ent->teamchain; ent = ent->teamchain) + ; + ent->teamchain = self; + self->teammaster = self->target_ent->teammaster; + self->flags |= FL_TEAMSLAVE; +} + +void SP_turret_driver (edict_t *self) +{ + if (deathmatch->value) + { + G_FreeEdict (self); + return; + } + + self->movetype = MOVETYPE_PUSH; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex("models/monsters/infantry/tris.md2"); + VectorSet (self->mins, -16, -16, -24); + VectorSet (self->maxs, 16, 16, 32); + + self->health = 100; + self->gib_health = 0; + self->mass = 200; + self->viewheight = 24; + + self->die = turret_driver_die; + self->monsterinfo.stand = infantry_stand; + + self->flags |= FL_NO_KNOCKBACK; + + level.total_monsters++; + + self->svflags |= SVF_MONSTER; + self->s.renderfx |= RF_FRAMELERP; + self->takedamage = DAMAGE_AIM; + self->use = monster_use; + self->clipmask = MASK_MONSTERSOLID; + VectorCopy (self->s.origin, self->s.old_origin); + self->monsterinfo.aiflags |= AI_STAND_GROUND|AI_DUCKED; + + if (st.item) + { + self->item = FindItemByClassname (st.item); + if (!self->item) + gi.dprintf("%s at %s has bad item: %s\n", self->classname, vtos(self->s.origin), st.item); + } + + self->think = turret_driver_link; + self->nextthink = level.time + FRAMETIME; + + gi.linkentity (self); +} diff --git a/original/baseq2/g_utils.c b/original/baseq2/g_utils.c new file mode 100644 index 0000000..af2e645 --- /dev/null +++ b/original/baseq2/g_utils.c @@ -0,0 +1,568 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +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. + +*/ +// g_utils.c -- misc utility functions for game module + +#include "g_local.h" + + +void G_ProjectSource (vec3_t point, vec3_t distance, vec3_t forward, vec3_t right, vec3_t result) +{ + result[0] = point[0] + forward[0] * distance[0] + right[0] * distance[1]; + result[1] = point[1] + forward[1] * distance[0] + right[1] * distance[1]; + result[2] = point[2] + forward[2] * distance[0] + right[2] * distance[1] + distance[2]; +} + + +/* +============= +G_Find + +Searches all active entities for the next one that holds +the matching string at fieldofs (use the FOFS() macro) in the structure. + +Searches beginning at the edict after from, or the beginning if NULL +NULL will be returned if the end of the list is reached. + +============= +*/ +edict_t *G_Find (edict_t *from, int fieldofs, char *match) +{ + char *s; + + if (!from) + from = g_edicts; + else + from++; + + for ( ; from < &g_edicts[globals.num_edicts] ; from++) + { + if (!from->inuse) + continue; + s = *(char **) ((byte *)from + fieldofs); + if (!s) + continue; + if (!Q_stricmp (s, match)) + return from; + } + + return NULL; +} + + +/* +================= +findradius + +Returns entities that have origins within a spherical area + +findradius (origin, radius) +================= +*/ +edict_t *findradius (edict_t *from, vec3_t org, float rad) +{ + vec3_t eorg; + int j; + + if (!from) + from = g_edicts; + else + from++; + for ( ; from < &g_edicts[globals.num_edicts]; from++) + { + if (!from->inuse) + continue; + if (from->solid == SOLID_NOT) + continue; + for (j=0 ; j<3 ; j++) + eorg[j] = org[j] - (from->s.origin[j] + (from->mins[j] + from->maxs[j])*0.5); + if (VectorLength(eorg) > rad) + continue; + return from; + } + + return NULL; +} + + +/* +============= +G_PickTarget + +Searches all active entities for the next one that holds +the matching string at fieldofs (use the FOFS() macro) in the structure. + +Searches beginning at the edict after from, or the beginning if NULL +NULL will be returned if the end of the list is reached. + +============= +*/ +#define MAXCHOICES 8 + +edict_t *G_PickTarget (char *targetname) +{ + edict_t *ent = NULL; + int num_choices = 0; + edict_t *choice[MAXCHOICES]; + + if (!targetname) + { + gi.dprintf("G_PickTarget called with NULL targetname\n"); + return NULL; + } + + while(1) + { + ent = G_Find (ent, FOFS(targetname), targetname); + if (!ent) + break; + choice[num_choices++] = ent; + if (num_choices == MAXCHOICES) + break; + } + + if (!num_choices) + { + gi.dprintf("G_PickTarget: target %s not found\n", targetname); + return NULL; + } + + return choice[rand() % num_choices]; +} + + + +void Think_Delay (edict_t *ent) +{ + G_UseTargets (ent, ent->activator); + G_FreeEdict (ent); +} + +/* +============================== +G_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. + +Search for (string)targetname in all entities that +match (string)self.target and call their .use function + +============================== +*/ +void G_UseTargets (edict_t *ent, edict_t *activator) +{ + edict_t *t; + +// +// check for a delay +// + if (ent->delay) + { + // create a temp object to fire at a later time + t = G_Spawn(); + t->classname = "DelayedUse"; + t->nextthink = level.time + ent->delay; + t->think = Think_Delay; + t->activator = activator; + if (!activator) + gi.dprintf ("Think_Delay with no activator\n"); + t->message = ent->message; + t->target = ent->target; + t->killtarget = ent->killtarget; + return; + } + + +// +// print the message +// + if ((ent->message) && !(activator->svflags & SVF_MONSTER)) + { + gi.centerprintf (activator, "%s", ent->message); + if (ent->noise_index) + gi.sound (activator, CHAN_AUTO, ent->noise_index, 1, ATTN_NORM, 0); + else + gi.sound (activator, CHAN_AUTO, gi.soundindex ("misc/talk1.wav"), 1, ATTN_NORM, 0); + } + +// +// kill killtargets +// + if (ent->killtarget) + { + t = NULL; + while ((t = G_Find (t, FOFS(targetname), ent->killtarget))) + { + G_FreeEdict (t); + if (!ent->inuse) + { + gi.dprintf("entity was removed while using killtargets\n"); + return; + } + } + } + +// +// fire targets +// + if (ent->target) + { + t = NULL; + while ((t = G_Find (t, FOFS(targetname), ent->target))) + { + // doors fire area portals in a specific way + if (!Q_stricmp(t->classname, "func_areaportal") && + (!Q_stricmp(ent->classname, "func_door") || !Q_stricmp(ent->classname, "func_door_rotating"))) + continue; + + if (t == ent) + { + gi.dprintf ("WARNING: Entity used itself.\n"); + } + else + { + if (t->use) + t->use (t, ent, activator); + } + if (!ent->inuse) + { + gi.dprintf("entity was removed while using targets\n"); + return; + } + } + } +} + + +/* +============= +TempVector + +This is just a convenience function +for making temporary vectors for function calls +============= +*/ +float *tv (float x, float y, float z) +{ + static int index; + static vec3_t vecs[8]; + float *v; + + // use an array so that multiple tempvectors won't collide + // for a while + v = vecs[index]; + index = (index + 1)&7; + + v[0] = x; + v[1] = y; + v[2] = z; + + return v; +} + + +/* +============= +VectorToString + +This is just a convenience function +for printing vectors +============= +*/ +char *vtos (vec3_t v) +{ + static int index; + static char str[8][32]; + char *s; + + // use an array so that multiple vtos won't collide + s = str[index]; + index = (index + 1)&7; + + Com_sprintf (s, 32, "(%i %i %i)", (int)v[0], (int)v[1], (int)v[2]); + + return s; +} + + +vec3_t VEC_UP = {0, -1, 0}; +vec3_t MOVEDIR_UP = {0, 0, 1}; +vec3_t VEC_DOWN = {0, -2, 0}; +vec3_t MOVEDIR_DOWN = {0, 0, -1}; + +void G_SetMovedir (vec3_t angles, vec3_t movedir) +{ + if (VectorCompare (angles, VEC_UP)) + { + VectorCopy (MOVEDIR_UP, movedir); + } + else if (VectorCompare (angles, VEC_DOWN)) + { + VectorCopy (MOVEDIR_DOWN, movedir); + } + else + { + AngleVectors (angles, movedir, NULL, NULL); + } + + VectorClear (angles); +} + + +float vectoyaw (vec3_t vec) +{ + float yaw; + + if (/*vec[YAW] == 0 &&*/ vec[PITCH] == 0) + { + yaw = 0; + if (vec[YAW] > 0) + yaw = 90; + else if (vec[YAW] < 0) + yaw = -90; + } + else + { + yaw = (int) (atan2(vec[YAW], vec[PITCH]) * 180 / M_PI); + if (yaw < 0) + yaw += 360; + } + + return yaw; +} + + +void vectoangles (vec3_t value1, vec3_t angles) +{ + float forward; + float yaw, pitch; + + if (value1[1] == 0 && value1[0] == 0) + { + yaw = 0; + if (value1[2] > 0) + pitch = 90; + else + pitch = 270; + } + else + { + if (value1[0]) + yaw = (int) (atan2(value1[1], value1[0]) * 180 / M_PI); + else if (value1[1] > 0) + yaw = 90; + else + yaw = -90; + if (yaw < 0) + yaw += 360; + + forward = sqrt (value1[0]*value1[0] + value1[1]*value1[1]); + pitch = (int) (atan2(value1[2], forward) * 180 / M_PI); + if (pitch < 0) + pitch += 360; + } + + angles[PITCH] = -pitch; + angles[YAW] = yaw; + angles[ROLL] = 0; +} + +char *G_CopyString (char *in) +{ + char *out; + + out = gi.TagMalloc (strlen(in)+1, TAG_LEVEL); + strcpy (out, in); + return out; +} + + +void G_InitEdict (edict_t *e) +{ + e->inuse = true; + e->classname = "noclass"; + e->gravity = 1.0; + e->s.number = e - g_edicts; +} + +/* +================= +G_Spawn + +Either finds a free edict, or allocates a new one. +Try to avoid reusing an entity that was recently freed, because it +can cause the client to think the entity morphed into something else +instead of being removed and recreated, which can cause interpolated +angles and bad trails. +================= +*/ +edict_t *G_Spawn (void) +{ + int i; + edict_t *e; + + e = &g_edicts[(int)maxclients->value+1]; + for ( i=maxclients->value+1 ; iinuse && ( e->freetime < 2 || level.time - e->freetime > 0.5 ) ) + { + G_InitEdict (e); + return e; + } + } + + if (i == game.maxentities) + gi.error ("ED_Alloc: no free edicts"); + + globals.num_edicts++; + G_InitEdict (e); + return e; +} + +/* +================= +G_FreeEdict + +Marks the edict as free +================= +*/ +void G_FreeEdict (edict_t *ed) +{ + gi.unlinkentity (ed); // unlink from world + + if ((ed - g_edicts) <= (maxclients->value + BODY_QUEUE_SIZE)) + { +// gi.dprintf("tried to free special edict\n"); + return; + } + + memset (ed, 0, sizeof(*ed)); + ed->classname = "freed"; + ed->freetime = level.time; + ed->inuse = false; +} + + +/* +============ +G_TouchTriggers + +============ +*/ +void G_TouchTriggers (edict_t *ent) +{ + int i, num; + edict_t *touch[MAX_EDICTS], *hit; + + // dead things don't activate triggers! + if ((ent->client || (ent->svflags & SVF_MONSTER)) && (ent->health <= 0)) + return; + + num = gi.BoxEdicts (ent->absmin, ent->absmax, touch + , MAX_EDICTS, AREA_TRIGGERS); + + // be careful, it is possible to have an entity in this + // list removed before we get to it (killtriggered) + for (i=0 ; iinuse) + continue; + if (!hit->touch) + continue; + hit->touch (hit, ent, NULL, NULL); + } +} + +/* +============ +G_TouchSolids + +Call after linking a new trigger in during gameplay +to force all entities it covers to immediately touch it +============ +*/ +void G_TouchSolids (edict_t *ent) +{ + int i, num; + edict_t *touch[MAX_EDICTS], *hit; + + num = gi.BoxEdicts (ent->absmin, ent->absmax, touch + , MAX_EDICTS, AREA_SOLID); + + // be careful, it is possible to have an entity in this + // list removed before we get to it (killtriggered) + for (i=0 ; iinuse) + continue; + if (ent->touch) + ent->touch (hit, ent, NULL, NULL); + if (!ent->inuse) + break; + } +} + + + + +/* +============================================================================== + +Kill box + +============================================================================== +*/ + +/* +================= +KillBox + +Kills all entities that would touch the proposed new positioning +of ent. Ent should be unlinked before calling this! +================= +*/ +qboolean KillBox (edict_t *ent) +{ + trace_t tr; + + while (1) + { + tr = gi.trace (ent->s.origin, ent->mins, ent->maxs, ent->s.origin, NULL, MASK_PLAYERSOLID); + if (!tr.ent) + break; + + // nail it + T_Damage (tr.ent, ent, ent, vec3_origin, ent->s.origin, vec3_origin, 100000, 0, DAMAGE_NO_PROTECTION, MOD_TELEFRAG); + + // if we didn't kill it, fail + if (tr.ent->solid) + return false; + } + + return true; // all clear +} diff --git a/original/baseq2/g_weapon.c b/original/baseq2/g_weapon.c new file mode 100644 index 0000000..53f4bee --- /dev/null +++ b/original/baseq2/g_weapon.c @@ -0,0 +1,918 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +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. + +*/ +#include "g_local.h" + + +/* +================= +check_dodge + +This is a support routine used when a client is firing +a non-instant attack weapon. It checks to see if a +monster's dodge function should be called. +================= +*/ +static void check_dodge (edict_t *self, vec3_t start, vec3_t dir, int speed) +{ + vec3_t end; + vec3_t v; + trace_t tr; + float eta; + + // easy mode only ducks one quarter the time + if (skill->value == 0) + { + if (random() > 0.25) + return; + } + VectorMA (start, 8192, dir, end); + tr = gi.trace (start, NULL, NULL, end, self, MASK_SHOT); + if ((tr.ent) && (tr.ent->svflags & SVF_MONSTER) && (tr.ent->health > 0) && (tr.ent->monsterinfo.dodge) && infront(tr.ent, self)) + { + VectorSubtract (tr.endpos, start, v); + eta = (VectorLength(v) - tr.ent->maxs[0]) / speed; + tr.ent->monsterinfo.dodge (tr.ent, self, eta); + } +} + + +/* +================= +fire_hit + +Used for all impact (hit/punch/slash) attacks +================= +*/ +qboolean fire_hit (edict_t *self, vec3_t aim, int damage, int kick) +{ + trace_t tr; + vec3_t forward, right, up; + vec3_t v; + vec3_t point; + float range; + vec3_t dir; + + //see if enemy is in range + VectorSubtract (self->enemy->s.origin, self->s.origin, dir); + range = VectorLength(dir); + if (range > aim[0]) + return false; + + if (aim[1] > self->mins[0] && aim[1] < self->maxs[0]) + { + // the hit is straight on so back the range up to the edge of their bbox + range -= self->enemy->maxs[0]; + } + else + { + // this is a side hit so adjust the "right" value out to the edge of their bbox + if (aim[1] < 0) + aim[1] = self->enemy->mins[0]; + else + aim[1] = self->enemy->maxs[0]; + } + + VectorMA (self->s.origin, range, dir, point); + + tr = gi.trace (self->s.origin, NULL, NULL, point, self, MASK_SHOT); + if (tr.fraction < 1) + { + if (!tr.ent->takedamage) + return false; + // if it will hit any client/monster then hit the one we wanted to hit + if ((tr.ent->svflags & SVF_MONSTER) || (tr.ent->client)) + tr.ent = self->enemy; + } + + AngleVectors(self->s.angles, forward, right, up); + VectorMA (self->s.origin, range, forward, point); + VectorMA (point, aim[1], right, point); + VectorMA (point, aim[2], up, point); + VectorSubtract (point, self->enemy->s.origin, dir); + + // do the damage + T_Damage (tr.ent, self, self, dir, point, vec3_origin, damage, kick/2, DAMAGE_NO_KNOCKBACK, MOD_HIT); + + if (!(tr.ent->svflags & SVF_MONSTER) && (!tr.ent->client)) + return false; + + // do our special form of knockback here + VectorMA (self->enemy->absmin, 0.5, self->enemy->size, v); + VectorSubtract (v, point, v); + VectorNormalize (v); + VectorMA (self->enemy->velocity, kick, v, self->enemy->velocity); + if (self->enemy->velocity[2] > 0) + self->enemy->groundentity = NULL; + return true; +} + + +/* +================= +fire_lead + +This is an internal support routine used for bullet/pellet based weapons. +================= +*/ +static void fire_lead (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int te_impact, int hspread, int vspread, int mod) +{ + trace_t tr; + vec3_t dir; + vec3_t forward, right, up; + vec3_t end; + float r; + float u; + vec3_t water_start; + qboolean water = false; + int content_mask = MASK_SHOT | MASK_WATER; + + tr = gi.trace (self->s.origin, NULL, NULL, start, self, MASK_SHOT); + if (!(tr.fraction < 1.0)) + { + vectoangles (aimdir, dir); + AngleVectors (dir, forward, right, up); + + r = crandom()*hspread; + u = crandom()*vspread; + VectorMA (start, 8192, forward, end); + VectorMA (end, r, right, end); + VectorMA (end, u, up, end); + + if (gi.pointcontents (start) & MASK_WATER) + { + water = true; + VectorCopy (start, water_start); + content_mask &= ~MASK_WATER; + } + + tr = gi.trace (start, NULL, NULL, end, self, content_mask); + + // see if we hit water + if (tr.contents & MASK_WATER) + { + int color; + + water = true; + VectorCopy (tr.endpos, water_start); + + if (!VectorCompare (start, tr.endpos)) + { + if (tr.contents & CONTENTS_WATER) + { + if (strcmp(tr.surface->name, "*brwater") == 0) + color = SPLASH_BROWN_WATER; + else + color = SPLASH_BLUE_WATER; + } + else if (tr.contents & CONTENTS_SLIME) + color = SPLASH_SLIME; + else if (tr.contents & CONTENTS_LAVA) + color = SPLASH_LAVA; + else + color = SPLASH_UNKNOWN; + + if (color != SPLASH_UNKNOWN) + { + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_SPLASH); + gi.WriteByte (8); + gi.WritePosition (tr.endpos); + gi.WriteDir (tr.plane.normal); + gi.WriteByte (color); + gi.multicast (tr.endpos, MULTICAST_PVS); + } + + // change bullet's course when it enters water + VectorSubtract (end, start, dir); + vectoangles (dir, dir); + AngleVectors (dir, forward, right, up); + r = crandom()*hspread*2; + u = crandom()*vspread*2; + VectorMA (water_start, 8192, forward, end); + VectorMA (end, r, right, end); + VectorMA (end, u, up, end); + } + + // re-trace ignoring water this time + tr = gi.trace (water_start, NULL, NULL, end, self, MASK_SHOT); + } + } + + // send gun puff / flash + if (!((tr.surface) && (tr.surface->flags & SURF_SKY))) + { + if (tr.fraction < 1.0) + { + if (tr.ent->takedamage) + { + T_Damage (tr.ent, self, self, aimdir, tr.endpos, tr.plane.normal, damage, kick, DAMAGE_BULLET, mod); + } + else + { + if (strncmp (tr.surface->name, "sky", 3) != 0) + { + gi.WriteByte (svc_temp_entity); + gi.WriteByte (te_impact); + gi.WritePosition (tr.endpos); + gi.WriteDir (tr.plane.normal); + gi.multicast (tr.endpos, MULTICAST_PVS); + + if (self->client) + PlayerNoise(self, tr.endpos, PNOISE_IMPACT); + } + } + } + } + + // if went through water, determine where the end and make a bubble trail + if (water) + { + vec3_t pos; + + VectorSubtract (tr.endpos, water_start, dir); + VectorNormalize (dir); + VectorMA (tr.endpos, -2, dir, pos); + if (gi.pointcontents (pos) & MASK_WATER) + VectorCopy (pos, tr.endpos); + else + tr = gi.trace (pos, NULL, NULL, water_start, tr.ent, MASK_WATER); + + VectorAdd (water_start, tr.endpos, pos); + VectorScale (pos, 0.5, pos); + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_BUBBLETRAIL); + gi.WritePosition (water_start); + gi.WritePosition (tr.endpos); + gi.multicast (pos, MULTICAST_PVS); + } +} + + +/* +================= +fire_bullet + +Fires a single round. Used for machinegun and chaingun. Would be fine for +pistols, rifles, etc.... +================= +*/ +void fire_bullet (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int hspread, int vspread, int mod) +{ + fire_lead (self, start, aimdir, damage, kick, TE_GUNSHOT, hspread, vspread, mod); +} + + +/* +================= +fire_shotgun + +Shoots shotgun pellets. Used by shotgun and super shotgun. +================= +*/ +void fire_shotgun (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int hspread, int vspread, int count, int mod) +{ + int i; + + for (i = 0; i < count; i++) + fire_lead (self, start, aimdir, damage, kick, TE_SHOTGUN, hspread, vspread, mod); +} + + +/* +================= +fire_blaster + +Fires a single blaster bolt. Used by the blaster and hyper blaster. +================= +*/ +void blaster_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + int mod; + + if (other == self->owner) + return; + + if (surf && (surf->flags & SURF_SKY)) + { + G_FreeEdict (self); + return; + } + + if (self->owner->client) + PlayerNoise(self->owner, self->s.origin, PNOISE_IMPACT); + + if (other->takedamage) + { + if (self->spawnflags & 1) + mod = MOD_HYPERBLASTER; + else + mod = MOD_BLASTER; + T_Damage (other, self, self->owner, self->velocity, self->s.origin, plane->normal, self->dmg, 1, DAMAGE_ENERGY, mod); + } + else + { + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_BLASTER); + gi.WritePosition (self->s.origin); + if (!plane) + gi.WriteDir (vec3_origin); + else + gi.WriteDir (plane->normal); + gi.multicast (self->s.origin, MULTICAST_PVS); + } + + G_FreeEdict (self); +} + +void fire_blaster (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int effect, qboolean hyper) +{ + edict_t *bolt; + trace_t tr; + + VectorNormalize (dir); + + bolt = G_Spawn(); + bolt->svflags = SVF_DEADMONSTER; + // yes, I know it looks weird that projectiles are deadmonsters + // what this means is that when prediction is used against the object + // (blaster/hyperblaster shots), the player won't be solid clipped against + // the object. Right now trying to run into a firing hyperblaster + // is very jerky since you are predicted 'against' the shots. + VectorCopy (start, bolt->s.origin); + VectorCopy (start, bolt->s.old_origin); + vectoangles (dir, bolt->s.angles); + VectorScale (dir, speed, bolt->velocity); + bolt->movetype = MOVETYPE_FLYMISSILE; + bolt->clipmask = MASK_SHOT; + bolt->solid = SOLID_BBOX; + bolt->s.effects |= effect; + VectorClear (bolt->mins); + VectorClear (bolt->maxs); + bolt->s.modelindex = gi.modelindex ("models/objects/laser/tris.md2"); + bolt->s.sound = gi.soundindex ("misc/lasfly.wav"); + bolt->owner = self; + bolt->touch = blaster_touch; + bolt->nextthink = level.time + 2; + bolt->think = G_FreeEdict; + bolt->dmg = damage; + bolt->classname = "bolt"; + if (hyper) + bolt->spawnflags = 1; + gi.linkentity (bolt); + + if (self->client) + check_dodge (self, bolt->s.origin, dir, speed); + + tr = gi.trace (self->s.origin, NULL, NULL, bolt->s.origin, bolt, MASK_SHOT); + if (tr.fraction < 1.0) + { + VectorMA (bolt->s.origin, -10, dir, bolt->s.origin); + bolt->touch (bolt, tr.ent, NULL, NULL); + } +} + + +/* +================= +fire_grenade +================= +*/ +static void Grenade_Explode (edict_t *ent) +{ + vec3_t origin; + int mod; + + if (ent->owner->client) + PlayerNoise(ent->owner, ent->s.origin, PNOISE_IMPACT); + + //FIXME: if we are onground then raise our Z just a bit since we are a point? + if (ent->enemy) + { + float points; + vec3_t v; + vec3_t dir; + + VectorAdd (ent->enemy->mins, ent->enemy->maxs, v); + VectorMA (ent->enemy->s.origin, 0.5, v, v); + VectorSubtract (ent->s.origin, v, v); + points = ent->dmg - 0.5 * VectorLength (v); + VectorSubtract (ent->enemy->s.origin, ent->s.origin, dir); + if (ent->spawnflags & 1) + mod = MOD_HANDGRENADE; + else + mod = MOD_GRENADE; + T_Damage (ent->enemy, ent, ent->owner, dir, ent->s.origin, vec3_origin, (int)points, (int)points, DAMAGE_RADIUS, mod); + } + + if (ent->spawnflags & 2) + mod = MOD_HELD_GRENADE; + else if (ent->spawnflags & 1) + mod = MOD_HG_SPLASH; + else + mod = MOD_G_SPLASH; + T_RadiusDamage(ent, ent->owner, ent->dmg, ent->enemy, ent->dmg_radius, mod); + + VectorMA (ent->s.origin, -0.02, ent->velocity, origin); + gi.WriteByte (svc_temp_entity); + if (ent->waterlevel) + { + if (ent->groundentity) + gi.WriteByte (TE_GRENADE_EXPLOSION_WATER); + else + gi.WriteByte (TE_ROCKET_EXPLOSION_WATER); + } + else + { + if (ent->groundentity) + gi.WriteByte (TE_GRENADE_EXPLOSION); + else + gi.WriteByte (TE_ROCKET_EXPLOSION); + } + gi.WritePosition (origin); + gi.multicast (ent->s.origin, MULTICAST_PHS); + + G_FreeEdict (ent); +} + +static void Grenade_Touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (other == ent->owner) + return; + + if (surf && (surf->flags & SURF_SKY)) + { + G_FreeEdict (ent); + return; + } + + if (!other->takedamage) + { + if (ent->spawnflags & 1) + { + if (random() > 0.5) + gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/hgrenb1a.wav"), 1, ATTN_NORM, 0); + else + gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/hgrenb2a.wav"), 1, ATTN_NORM, 0); + } + else + { + gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/grenlb1b.wav"), 1, ATTN_NORM, 0); + } + return; + } + + ent->enemy = other; + Grenade_Explode (ent); +} + +void fire_grenade (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, float timer, float damage_radius) +{ + edict_t *grenade; + vec3_t dir; + vec3_t forward, right, up; + + vectoangles (aimdir, dir); + AngleVectors (dir, forward, right, up); + + grenade = G_Spawn(); + VectorCopy (start, grenade->s.origin); + VectorScale (aimdir, speed, grenade->velocity); + VectorMA (grenade->velocity, 200 + crandom() * 10.0, up, grenade->velocity); + VectorMA (grenade->velocity, crandom() * 10.0, right, grenade->velocity); + VectorSet (grenade->avelocity, 300, 300, 300); + grenade->movetype = MOVETYPE_BOUNCE; + grenade->clipmask = MASK_SHOT; + grenade->solid = SOLID_BBOX; + grenade->s.effects |= EF_GRENADE; + VectorClear (grenade->mins); + VectorClear (grenade->maxs); + grenade->s.modelindex = gi.modelindex ("models/objects/grenade/tris.md2"); + grenade->owner = self; + grenade->touch = Grenade_Touch; + grenade->nextthink = level.time + timer; + grenade->think = Grenade_Explode; + grenade->dmg = damage; + grenade->dmg_radius = damage_radius; + grenade->classname = "grenade"; + + gi.linkentity (grenade); +} + +void fire_grenade2 (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, float timer, float damage_radius, qboolean held) +{ + edict_t *grenade; + vec3_t dir; + vec3_t forward, right, up; + + vectoangles (aimdir, dir); + AngleVectors (dir, forward, right, up); + + grenade = G_Spawn(); + VectorCopy (start, grenade->s.origin); + VectorScale (aimdir, speed, grenade->velocity); + VectorMA (grenade->velocity, 200 + crandom() * 10.0, up, grenade->velocity); + VectorMA (grenade->velocity, crandom() * 10.0, right, grenade->velocity); + VectorSet (grenade->avelocity, 300, 300, 300); + grenade->movetype = MOVETYPE_BOUNCE; + grenade->clipmask = MASK_SHOT; + grenade->solid = SOLID_BBOX; + grenade->s.effects |= EF_GRENADE; + VectorClear (grenade->mins); + VectorClear (grenade->maxs); + grenade->s.modelindex = gi.modelindex ("models/objects/grenade2/tris.md2"); + grenade->owner = self; + grenade->touch = Grenade_Touch; + grenade->nextthink = level.time + timer; + grenade->think = Grenade_Explode; + grenade->dmg = damage; + grenade->dmg_radius = damage_radius; + grenade->classname = "hgrenade"; + if (held) + grenade->spawnflags = 3; + else + grenade->spawnflags = 1; + grenade->s.sound = gi.soundindex("weapons/hgrenc1b.wav"); + + if (timer <= 0.0) + Grenade_Explode (grenade); + else + { + gi.sound (self, CHAN_WEAPON, gi.soundindex ("weapons/hgrent1a.wav"), 1, ATTN_NORM, 0); + gi.linkentity (grenade); + } +} + + +/* +================= +fire_rocket +================= +*/ +void rocket_touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + vec3_t origin; + int n; + + if (other == ent->owner) + return; + + if (surf && (surf->flags & SURF_SKY)) + { + G_FreeEdict (ent); + return; + } + + if (ent->owner->client) + PlayerNoise(ent->owner, ent->s.origin, PNOISE_IMPACT); + + // calculate position for the explosion entity + VectorMA (ent->s.origin, -0.02, ent->velocity, origin); + + if (other->takedamage) + { + T_Damage (other, ent, ent->owner, ent->velocity, ent->s.origin, plane->normal, ent->dmg, 0, 0, MOD_ROCKET); + } + else + { + // don't throw any debris in net games + if (!deathmatch->value && !coop->value) + { + if ((surf) && !(surf->flags & (SURF_WARP|SURF_TRANS33|SURF_TRANS66|SURF_FLOWING))) + { + n = rand() % 5; + while(n--) + ThrowDebris (ent, "models/objects/debris2/tris.md2", 2, ent->s.origin); + } + } + } + + T_RadiusDamage(ent, ent->owner, ent->radius_dmg, other, ent->dmg_radius, MOD_R_SPLASH); + + gi.WriteByte (svc_temp_entity); + if (ent->waterlevel) + gi.WriteByte (TE_ROCKET_EXPLOSION_WATER); + else + gi.WriteByte (TE_ROCKET_EXPLOSION); + gi.WritePosition (origin); + gi.multicast (ent->s.origin, MULTICAST_PHS); + + G_FreeEdict (ent); +} + +void fire_rocket (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, float damage_radius, int radius_damage) +{ + edict_t *rocket; + + rocket = G_Spawn(); + VectorCopy (start, rocket->s.origin); + VectorCopy (dir, rocket->movedir); + vectoangles (dir, rocket->s.angles); + VectorScale (dir, speed, rocket->velocity); + rocket->movetype = MOVETYPE_FLYMISSILE; + rocket->clipmask = MASK_SHOT; + rocket->solid = SOLID_BBOX; + rocket->s.effects |= EF_ROCKET; + VectorClear (rocket->mins); + VectorClear (rocket->maxs); + rocket->s.modelindex = gi.modelindex ("models/objects/rocket/tris.md2"); + rocket->owner = self; + rocket->touch = rocket_touch; + rocket->nextthink = level.time + 8000/speed; + rocket->think = G_FreeEdict; + rocket->dmg = damage; + rocket->radius_dmg = radius_damage; + rocket->dmg_radius = damage_radius; + rocket->s.sound = gi.soundindex ("weapons/rockfly.wav"); + rocket->classname = "rocket"; + + if (self->client) + check_dodge (self, rocket->s.origin, dir, speed); + + gi.linkentity (rocket); +} + + +/* +================= +fire_rail +================= +*/ +void fire_rail (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick) +{ + vec3_t from; + vec3_t end; + trace_t tr; + edict_t *ignore; + int mask; + qboolean water; + + VectorMA (start, 8192, aimdir, end); + VectorCopy (start, from); + ignore = self; + water = false; + mask = MASK_SHOT|CONTENTS_SLIME|CONTENTS_LAVA; + while (ignore) + { + tr = gi.trace (from, NULL, NULL, end, ignore, mask); + + if (tr.contents & (CONTENTS_SLIME|CONTENTS_LAVA)) + { + mask &= ~(CONTENTS_SLIME|CONTENTS_LAVA); + water = true; + } + else + { + //ZOID--added so rail goes through SOLID_BBOX entities (gibs, etc) + if ((tr.ent->svflags & SVF_MONSTER) || (tr.ent->client) || + (tr.ent->solid == SOLID_BBOX)) + ignore = tr.ent; + else + ignore = NULL; + + if ((tr.ent != self) && (tr.ent->takedamage)) + T_Damage (tr.ent, self, self, aimdir, tr.endpos, tr.plane.normal, damage, kick, 0, MOD_RAILGUN); + } + + VectorCopy (tr.endpos, from); + } + + // send gun puff / flash + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_RAILTRAIL); + gi.WritePosition (start); + gi.WritePosition (tr.endpos); + gi.multicast (self->s.origin, MULTICAST_PHS); +// gi.multicast (start, MULTICAST_PHS); + if (water) + { + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_RAILTRAIL); + gi.WritePosition (start); + gi.WritePosition (tr.endpos); + gi.multicast (tr.endpos, MULTICAST_PHS); + } + + if (self->client) + PlayerNoise(self, tr.endpos, PNOISE_IMPACT); +} + + +/* +================= +fire_bfg +================= +*/ +void bfg_explode (edict_t *self) +{ + edict_t *ent; + float points; + vec3_t v; + float dist; + + if (self->s.frame == 0) + { + // the BFG effect + ent = NULL; + while ((ent = findradius(ent, self->s.origin, self->dmg_radius)) != NULL) + { + if (!ent->takedamage) + continue; + if (ent == self->owner) + continue; + if (!CanDamage (ent, self)) + continue; + if (!CanDamage (ent, self->owner)) + continue; + + VectorAdd (ent->mins, ent->maxs, v); + VectorMA (ent->s.origin, 0.5, v, v); + VectorSubtract (self->s.origin, v, v); + dist = VectorLength(v); + points = self->radius_dmg * (1.0 - sqrt(dist/self->dmg_radius)); + if (ent == self->owner) + points = points * 0.5; + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_BFG_EXPLOSION); + gi.WritePosition (ent->s.origin); + gi.multicast (ent->s.origin, MULTICAST_PHS); + T_Damage (ent, self, self->owner, self->velocity, ent->s.origin, vec3_origin, (int)points, 0, DAMAGE_ENERGY, MOD_BFG_EFFECT); + } + } + + self->nextthink = level.time + FRAMETIME; + self->s.frame++; + if (self->s.frame == 5) + self->think = G_FreeEdict; +} + +void bfg_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (other == self->owner) + return; + + if (surf && (surf->flags & SURF_SKY)) + { + G_FreeEdict (self); + return; + } + + if (self->owner->client) + PlayerNoise(self->owner, self->s.origin, PNOISE_IMPACT); + + // core explosion - prevents firing it into the wall/floor + if (other->takedamage) + T_Damage (other, self, self->owner, self->velocity, self->s.origin, plane->normal, 200, 0, 0, MOD_BFG_BLAST); + T_RadiusDamage(self, self->owner, 200, other, 100, MOD_BFG_BLAST); + + gi.sound (self, CHAN_VOICE, gi.soundindex ("weapons/bfg__x1b.wav"), 1, ATTN_NORM, 0); + self->solid = SOLID_NOT; + self->touch = NULL; + VectorMA (self->s.origin, -1 * FRAMETIME, self->velocity, self->s.origin); + VectorClear (self->velocity); + self->s.modelindex = gi.modelindex ("sprites/s_bfg3.sp2"); + self->s.frame = 0; + self->s.sound = 0; + self->s.effects &= ~EF_ANIM_ALLFAST; + self->think = bfg_explode; + self->nextthink = level.time + FRAMETIME; + self->enemy = other; + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_BFG_BIGEXPLOSION); + gi.WritePosition (self->s.origin); + gi.multicast (self->s.origin, MULTICAST_PVS); +} + + +void bfg_think (edict_t *self) +{ + edict_t *ent; + edict_t *ignore; + vec3_t point; + vec3_t dir; + vec3_t start; + vec3_t end; + int dmg; + trace_t tr; + + if (deathmatch->value) + dmg = 5; + else + dmg = 10; + + ent = NULL; + while ((ent = findradius(ent, self->s.origin, 256)) != NULL) + { + if (ent == self) + continue; + + if (ent == self->owner) + continue; + + if (!ent->takedamage) + continue; + + if (!(ent->svflags & SVF_MONSTER) && (!ent->client) && (strcmp(ent->classname, "misc_explobox") != 0)) + continue; + + VectorMA (ent->absmin, 0.5, ent->size, point); + + VectorSubtract (point, self->s.origin, dir); + VectorNormalize (dir); + + ignore = self; + VectorCopy (self->s.origin, start); + VectorMA (start, 2048, dir, end); + while(1) + { + tr = gi.trace (start, NULL, NULL, end, ignore, CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_DEADMONSTER); + + if (!tr.ent) + break; + + // hurt it if we can + if ((tr.ent->takedamage) && !(tr.ent->flags & FL_IMMUNE_LASER) && (tr.ent != self->owner)) + T_Damage (tr.ent, self, self->owner, dir, tr.endpos, vec3_origin, dmg, 1, DAMAGE_ENERGY, MOD_BFG_LASER); + + // if we hit something that's not a monster or player we're done + if (!(tr.ent->svflags & SVF_MONSTER) && (!tr.ent->client)) + { + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_LASER_SPARKS); + gi.WriteByte (4); + gi.WritePosition (tr.endpos); + gi.WriteDir (tr.plane.normal); + gi.WriteByte (self->s.skinnum); + gi.multicast (tr.endpos, MULTICAST_PVS); + break; + } + + ignore = tr.ent; + VectorCopy (tr.endpos, start); + } + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_BFG_LASER); + gi.WritePosition (self->s.origin); + gi.WritePosition (tr.endpos); + gi.multicast (self->s.origin, MULTICAST_PHS); + } + + self->nextthink = level.time + FRAMETIME; +} + + +void fire_bfg (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, float damage_radius) +{ + edict_t *bfg; + + bfg = G_Spawn(); + VectorCopy (start, bfg->s.origin); + VectorCopy (dir, bfg->movedir); + vectoangles (dir, bfg->s.angles); + VectorScale (dir, speed, bfg->velocity); + bfg->movetype = MOVETYPE_FLYMISSILE; + bfg->clipmask = MASK_SHOT; + bfg->solid = SOLID_BBOX; + bfg->s.effects |= EF_BFG | EF_ANIM_ALLFAST; + VectorClear (bfg->mins); + VectorClear (bfg->maxs); + bfg->s.modelindex = gi.modelindex ("sprites/s_bfg1.sp2"); + bfg->owner = self; + bfg->touch = bfg_touch; + bfg->nextthink = level.time + 8000/speed; + bfg->think = G_FreeEdict; + bfg->radius_dmg = damage; + bfg->dmg_radius = damage_radius; + bfg->classname = "bfg blast"; + bfg->s.sound = gi.soundindex ("weapons/bfg__l1a.wav"); + + bfg->think = bfg_think; + bfg->nextthink = level.time + FRAMETIME; + bfg->teammaster = bfg; + bfg->teamchain = NULL; + + if (self->client) + check_dodge (self, bfg->s.origin, dir, speed); + + gi.linkentity (bfg); +} diff --git a/original/baseq2/game.h b/original/baseq2/game.h new file mode 100644 index 0000000..659e0f8 --- /dev/null +++ b/original/baseq2/game.h @@ -0,0 +1,235 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +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. + +*/ + +// game.h -- game dll information visible to server + +#define GAME_API_VERSION 3 + +// edict->svflags + +#define SVF_NOCLIENT 0x00000001 // don't send entity to clients, even if it has effects +#define SVF_DEADMONSTER 0x00000002 // treat as CONTENTS_DEADMONSTER for collision +#define SVF_MONSTER 0x00000004 // treat as CONTENTS_MONSTER for collision + +// edict->solid values + +typedef enum +{ +SOLID_NOT, // no interaction with other objects +SOLID_TRIGGER, // only touch when inside, after moving +SOLID_BBOX, // touch on edge +SOLID_BSP // bsp clip, touch on edge +} solid_t; + +//=============================================================== + +// link_t is only used for entity area links now +typedef struct link_s +{ + struct link_s *prev, *next; +} link_t; + +#define MAX_ENT_CLUSTERS 16 + + +typedef struct edict_s edict_t; +typedef struct gclient_s gclient_t; + + +#ifndef GAME_INCLUDE + +struct gclient_s +{ + player_state_t ps; // communicated by server to clients + int ping; + // the game dll can add anything it wants after + // this point in the structure +}; + + +struct edict_s +{ + entity_state_t s; + struct gclient_s *client; + qboolean inuse; + int linkcount; + + // FIXME: move these fields to a server private sv_entity_t + link_t area; // linked to a division node or leaf + + int num_clusters; // if -1, use headnode instead + int clusternums[MAX_ENT_CLUSTERS]; + int headnode; // unused if num_clusters != -1 + int areanum, areanum2; + + //================================ + + int svflags; // SVF_NOCLIENT, SVF_DEADMONSTER, SVF_MONSTER, etc + vec3_t mins, maxs; + vec3_t absmin, absmax, size; + solid_t solid; + int clipmask; + edict_t *owner; + + // the game dll can add anything it wants after + // this point in the structure +}; + +#endif // GAME_INCLUDE + +//=============================================================== + +// +// functions provided by the main engine +// +typedef struct +{ + // special messages + void (*bprintf) (int printlevel, char *fmt, ...); + void (*dprintf) (char *fmt, ...); + void (*cprintf) (edict_t *ent, int printlevel, char *fmt, ...); + void (*centerprintf) (edict_t *ent, char *fmt, ...); + void (*sound) (edict_t *ent, int channel, int soundindex, float volume, float attenuation, float timeofs); + void (*positioned_sound) (vec3_t origin, edict_t *ent, int channel, int soundinedex, float volume, float attenuation, float timeofs); + + // config strings hold all the index strings, the lightstyles, + // and misc data like the sky definition and cdtrack. + // All of the current configstrings are sent to clients when + // they connect, and changes are sent to all connected clients. + void (*configstring) (int num, char *string); + + void (*error) (char *fmt, ...); + + // the *index functions create configstrings and some internal server state + int (*modelindex) (char *name); + int (*soundindex) (char *name); + int (*imageindex) (char *name); + + void (*setmodel) (edict_t *ent, char *name); + + // collision detection + trace_t (*trace) (vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, edict_t *passent, int contentmask); + int (*pointcontents) (vec3_t point); + qboolean (*inPVS) (vec3_t p1, vec3_t p2); + qboolean (*inPHS) (vec3_t p1, vec3_t p2); + void (*SetAreaPortalState) (int portalnum, qboolean open); + qboolean (*AreasConnected) (int area1, int area2); + + // an entity will never be sent to a client or used for collision + // if it is not passed to linkentity. If the size, position, or + // solidity changes, it must be relinked. + void (*linkentity) (edict_t *ent); + void (*unlinkentity) (edict_t *ent); // call before removing an interactive edict + int (*BoxEdicts) (vec3_t mins, vec3_t maxs, edict_t **list, int maxcount, int areatype); + void (*Pmove) (pmove_t *pmove); // player movement code common with client prediction + + // network messaging + void (*multicast) (vec3_t origin, multicast_t to); + void (*unicast) (edict_t *ent, qboolean reliable); + void (*WriteChar) (int c); + void (*WriteByte) (int c); + void (*WriteShort) (int c); + void (*WriteLong) (int c); + void (*WriteFloat) (float f); + void (*WriteString) (char *s); + void (*WritePosition) (vec3_t pos); // some fractional bits + void (*WriteDir) (vec3_t pos); // single byte encoded, very coarse + void (*WriteAngle) (float f); + + // managed memory allocation + void *(*TagMalloc) (int size, int tag); + void (*TagFree) (void *block); + void (*FreeTags) (int tag); + + // console variable interaction + cvar_t *(*cvar) (char *var_name, char *value, int flags); + cvar_t *(*cvar_set) (char *var_name, char *value); + cvar_t *(*cvar_forceset) (char *var_name, char *value); + + // ClientCommand and ServerCommand parameter access + int (*argc) (void); + char *(*argv) (int n); + char *(*args) (void); // concatenation of all argv >= 1 + + // add commands to the server console as if they were typed in + // for map changing, etc + void (*AddCommandString) (char *text); + + void (*DebugGraph) (float value, int color); +} game_import_t; + +// +// functions exported by the game subsystem +// +typedef struct +{ + int apiversion; + + // the init function will only be called when a game starts, + // not each time a level is loaded. Persistant data for clients + // and the server can be allocated in init + void (*Init) (void); + void (*Shutdown) (void); + + // each new level entered will cause a call to SpawnEntities + void (*SpawnEntities) (char *mapname, char *entstring, char *spawnpoint); + + // Read/Write Game is for storing persistant cross level information + // about the world state and the clients. + // WriteGame is called every time a level is exited. + // ReadGame is called on a loadgame. + void (*WriteGame) (char *filename, qboolean autosave); + void (*ReadGame) (char *filename); + + // ReadLevel is called after the default map information has been + // loaded with SpawnEntities + void (*WriteLevel) (char *filename); + void (*ReadLevel) (char *filename); + + qboolean (*ClientConnect) (edict_t *ent, char *userinfo); + void (*ClientBegin) (edict_t *ent); + void (*ClientUserinfoChanged) (edict_t *ent, char *userinfo); + void (*ClientDisconnect) (edict_t *ent); + void (*ClientCommand) (edict_t *ent); + void (*ClientThink) (edict_t *ent, usercmd_t *cmd); + + void (*RunFrame) (void); + + // ServerCommand will be called when an "sv " command is issued on the + // server console. + // The game can issue gi.argc() / gi.argv() commands to get the rest + // of the parameters + void (*ServerCommand) (void); + + // + // global variables shared between game and server + // + + // The edict array is allocated in the game dll so it + // can vary in size from one game to another. + // + // The size will be fixed when ge->Init() is called + struct edict_s *edicts; + int edict_size; + int num_edicts; // current number, <= max_edicts + int max_edicts; +} game_export_t; + +game_export_t *GetGameApi (game_import_t *import); diff --git a/original/baseq2/m_actor.c b/original/baseq2/m_actor.c new file mode 100644 index 0000000..d7fc15d --- /dev/null +++ b/original/baseq2/m_actor.c @@ -0,0 +1,609 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +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. + +*/ +// g_actor.c + +#include "g_local.h" +#include "m_actor.h" + +#define MAX_ACTOR_NAMES 8 +char *actor_names[MAX_ACTOR_NAMES] = +{ + "Hellrot", + "Tokay", + "Killme", + "Disruptor", + "Adrianator", + "Rambear", + "Titus", + "Bitterman" +}; + + +mframe_t actor_frames_stand [] = +{ + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL +}; +mmove_t actor_move_stand = {FRAME_stand101, FRAME_stand140, actor_frames_stand, NULL}; + +void actor_stand (edict_t *self) +{ + self->monsterinfo.currentmove = &actor_move_stand; + + // randomize on startup + if (level.time < 1.0) + self->s.frame = self->monsterinfo.currentmove->firstframe + (rand() % (self->monsterinfo.currentmove->lastframe - self->monsterinfo.currentmove->firstframe + 1)); +} + + +mframe_t actor_frames_walk [] = +{ + ai_walk, 0, NULL, + ai_walk, 6, NULL, + ai_walk, 10, NULL, + ai_walk, 3, NULL, + ai_walk, 2, NULL, + ai_walk, 7, NULL, + ai_walk, 10, NULL, + ai_walk, 1, NULL, + ai_walk, 4, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL +}; +mmove_t actor_move_walk = {FRAME_walk01, FRAME_walk08, actor_frames_walk, NULL}; + +void actor_walk (edict_t *self) +{ + self->monsterinfo.currentmove = &actor_move_walk; +} + + +mframe_t actor_frames_run [] = +{ + ai_run, 4, NULL, + ai_run, 15, NULL, + ai_run, 15, NULL, + ai_run, 8, NULL, + ai_run, 20, NULL, + ai_run, 15, NULL, + ai_run, 8, NULL, + ai_run, 17, NULL, + ai_run, 12, NULL, + ai_run, -2, NULL, + ai_run, -2, NULL, + ai_run, -1, NULL +}; +mmove_t actor_move_run = {FRAME_run02, FRAME_run07, actor_frames_run, NULL}; + +void actor_run (edict_t *self) +{ + if ((level.time < self->pain_debounce_time) && (!self->enemy)) + { + if (self->movetarget) + actor_walk(self); + else + actor_stand(self); + return; + } + + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + { + actor_stand(self); + return; + } + + self->monsterinfo.currentmove = &actor_move_run; +} + + +mframe_t actor_frames_pain1 [] = +{ + ai_move, -5, NULL, + ai_move, 4, NULL, + ai_move, 1, NULL +}; +mmove_t actor_move_pain1 = {FRAME_pain101, FRAME_pain103, actor_frames_pain1, actor_run}; + +mframe_t actor_frames_pain2 [] = +{ + ai_move, -4, NULL, + ai_move, 4, NULL, + ai_move, 0, NULL +}; +mmove_t actor_move_pain2 = {FRAME_pain201, FRAME_pain203, actor_frames_pain2, actor_run}; + +mframe_t actor_frames_pain3 [] = +{ + ai_move, -1, NULL, + ai_move, 1, NULL, + ai_move, 0, NULL +}; +mmove_t actor_move_pain3 = {FRAME_pain301, FRAME_pain303, actor_frames_pain3, actor_run}; + +mframe_t actor_frames_flipoff [] = +{ + ai_turn, 0, NULL, + ai_turn, 0, NULL, + ai_turn, 0, NULL, + ai_turn, 0, NULL, + ai_turn, 0, NULL, + ai_turn, 0, NULL, + ai_turn, 0, NULL, + ai_turn, 0, NULL, + ai_turn, 0, NULL, + ai_turn, 0, NULL, + ai_turn, 0, NULL, + ai_turn, 0, NULL, + ai_turn, 0, NULL, + ai_turn, 0, NULL +}; +mmove_t actor_move_flipoff = {FRAME_flip01, FRAME_flip14, actor_frames_flipoff, actor_run}; + +mframe_t actor_frames_taunt [] = +{ + ai_turn, 0, NULL, + ai_turn, 0, NULL, + ai_turn, 0, NULL, + ai_turn, 0, NULL, + ai_turn, 0, NULL, + ai_turn, 0, NULL, + ai_turn, 0, NULL, + ai_turn, 0, NULL, + ai_turn, 0, NULL, + ai_turn, 0, NULL, + ai_turn, 0, NULL, + ai_turn, 0, NULL, + ai_turn, 0, NULL, + ai_turn, 0, NULL, + ai_turn, 0, NULL, + ai_turn, 0, NULL, + ai_turn, 0, NULL +}; +mmove_t actor_move_taunt = {FRAME_taunt01, FRAME_taunt17, actor_frames_taunt, actor_run}; + +char *messages[] = +{ + "Watch it", + "#$@*&", + "Idiot", + "Check your targets" +}; + +void actor_pain (edict_t *self, edict_t *other, float kick, int damage) +{ + int n; + + if (self->health < (self->max_health / 2)) + self->s.skinnum = 1; + + if (level.time < self->pain_debounce_time) + return; + + self->pain_debounce_time = level.time + 3; +// gi.sound (self, CHAN_VOICE, actor.sound_pain, 1, ATTN_NORM, 0); + + if ((other->client) && (random() < 0.4)) + { + vec3_t v; + char *name; + + VectorSubtract (other->s.origin, self->s.origin, v); + self->ideal_yaw = vectoyaw (v); + if (random() < 0.5) + self->monsterinfo.currentmove = &actor_move_flipoff; + else + self->monsterinfo.currentmove = &actor_move_taunt; + name = actor_names[(self - g_edicts)%MAX_ACTOR_NAMES]; + gi.cprintf (other, PRINT_CHAT, "%s: %s!\n", name, messages[rand()%3]); + return; + } + + n = rand() % 3; + if (n == 0) + self->monsterinfo.currentmove = &actor_move_pain1; + else if (n == 1) + self->monsterinfo.currentmove = &actor_move_pain2; + else + self->monsterinfo.currentmove = &actor_move_pain3; +} + + +void actorMachineGun (edict_t *self) +{ + vec3_t start, target; + vec3_t forward, right; + + AngleVectors (self->s.angles, forward, right, NULL); + G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_ACTOR_MACHINEGUN_1], forward, right, start); + if (self->enemy) + { + if (self->enemy->health > 0) + { + VectorMA (self->enemy->s.origin, -0.2, self->enemy->velocity, target); + target[2] += self->enemy->viewheight; + } + else + { + VectorCopy (self->enemy->absmin, target); + target[2] += (self->enemy->size[2] / 2); + } + VectorSubtract (target, start, forward); + VectorNormalize (forward); + } + else + { + AngleVectors (self->s.angles, forward, NULL, NULL); + } + monster_fire_bullet (self, start, forward, 3, 4, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, MZ2_ACTOR_MACHINEGUN_1); +} + + +void actor_dead (edict_t *self) +{ + VectorSet (self->mins, -16, -16, -24); + VectorSet (self->maxs, 16, 16, -8); + self->movetype = MOVETYPE_TOSS; + self->svflags |= SVF_DEADMONSTER; + self->nextthink = 0; + gi.linkentity (self); +} + +mframe_t actor_frames_death1 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, -13, NULL, + ai_move, 14, NULL, + ai_move, 3, NULL, + ai_move, -2, NULL, + ai_move, 1, NULL +}; +mmove_t actor_move_death1 = {FRAME_death101, FRAME_death107, actor_frames_death1, actor_dead}; + +mframe_t actor_frames_death2 [] = +{ + ai_move, 0, NULL, + ai_move, 7, NULL, + ai_move, -6, NULL, + ai_move, -5, NULL, + ai_move, 1, NULL, + ai_move, 0, NULL, + ai_move, -1, NULL, + ai_move, -2, NULL, + ai_move, -1, NULL, + ai_move, -9, NULL, + ai_move, -13, NULL, + ai_move, -13, NULL, + ai_move, 0, NULL +}; +mmove_t actor_move_death2 = {FRAME_death201, FRAME_death213, actor_frames_death2, actor_dead}; + +void actor_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + int n; + +// check for gib + if (self->health <= -80) + { +// gi.sound (self, CHAN_VOICE, actor.sound_gib, 1, ATTN_NORM, 0); + for (n= 0; n < 2; n++) + ThrowGib (self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC); + for (n= 0; n < 4; n++) + ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); + ThrowHead (self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC); + self->deadflag = DEAD_DEAD; + return; + } + + if (self->deadflag == DEAD_DEAD) + return; + +// regular death +// gi.sound (self, CHAN_VOICE, actor.sound_die, 1, ATTN_NORM, 0); + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_YES; + + n = rand() % 2; + if (n == 0) + self->monsterinfo.currentmove = &actor_move_death1; + else + self->monsterinfo.currentmove = &actor_move_death2; +} + + +void actor_fire (edict_t *self) +{ + actorMachineGun (self); + + if (level.time >= self->monsterinfo.pausetime) + self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; + else + self->monsterinfo.aiflags |= AI_HOLD_FRAME; +} + +mframe_t actor_frames_attack [] = +{ + ai_charge, -2, actor_fire, + ai_charge, -2, NULL, + ai_charge, 3, NULL, + ai_charge, 2, NULL +}; +mmove_t actor_move_attack = {FRAME_attak01, FRAME_attak04, actor_frames_attack, actor_run}; + +void actor_attack(edict_t *self) +{ + int n; + + self->monsterinfo.currentmove = &actor_move_attack; + n = (rand() & 15) + 3 + 7; + self->monsterinfo.pausetime = level.time + n * FRAMETIME; +} + + +void actor_use (edict_t *self, edict_t *other, edict_t *activator) +{ + vec3_t v; + + self->goalentity = self->movetarget = G_PickTarget(self->target); + if ((!self->movetarget) || (strcmp(self->movetarget->classname, "target_actor") != 0)) + { + gi.dprintf ("%s has bad target %s at %s\n", self->classname, self->target, vtos(self->s.origin)); + self->target = NULL; + self->monsterinfo.pausetime = 100000000; + self->monsterinfo.stand (self); + return; + } + + VectorSubtract (self->goalentity->s.origin, self->s.origin, v); + self->ideal_yaw = self->s.angles[YAW] = vectoyaw(v); + self->monsterinfo.walk (self); + self->target = NULL; +} + + +/*QUAKED misc_actor (1 .5 0) (-16 -16 -24) (16 16 32) +*/ + +void SP_misc_actor (edict_t *self) +{ + if (deathmatch->value) + { + G_FreeEdict (self); + return; + } + + if (!self->targetname) + { + gi.dprintf("untargeted %s at %s\n", self->classname, vtos(self->s.origin)); + G_FreeEdict (self); + return; + } + + if (!self->target) + { + gi.dprintf("%s with no target at %s\n", self->classname, vtos(self->s.origin)); + G_FreeEdict (self); + return; + } + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex("players/male/tris.md2"); + VectorSet (self->mins, -16, -16, -24); + VectorSet (self->maxs, 16, 16, 32); + + if (!self->health) + self->health = 100; + self->mass = 200; + + self->pain = actor_pain; + self->die = actor_die; + + self->monsterinfo.stand = actor_stand; + self->monsterinfo.walk = actor_walk; + self->monsterinfo.run = actor_run; + self->monsterinfo.attack = actor_attack; + self->monsterinfo.melee = NULL; + self->monsterinfo.sight = NULL; + + self->monsterinfo.aiflags |= AI_GOOD_GUY; + + gi.linkentity (self); + + self->monsterinfo.currentmove = &actor_move_stand; + self->monsterinfo.scale = MODEL_SCALE; + + walkmonster_start (self); + + // actors always start in a dormant state, they *must* be used to get going + self->use = actor_use; +} + + +/*QUAKED target_actor (.5 .3 0) (-8 -8 -8) (8 8 8) JUMP SHOOT ATTACK x HOLD BRUTAL +JUMP jump in set direction upon reaching this target +SHOOT take a single shot at the pathtarget +ATTACK attack pathtarget until it or actor is dead + +"target" next target_actor +"pathtarget" target of any action to be taken at this point +"wait" amount of time actor should pause at this point +"message" actor will "say" this to the player + +for JUMP only: +"speed" speed thrown forward (default 200) +"height" speed thrown upwards (default 200) +*/ + +void target_actor_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + vec3_t v; + + if (other->movetarget != self) + return; + + if (other->enemy) + return; + + other->goalentity = other->movetarget = NULL; + + if (self->message) + { + int n; + edict_t *ent; + + for (n = 1; n <= game.maxclients; n++) + { + ent = &g_edicts[n]; + if (!ent->inuse) + continue; + gi.cprintf (ent, PRINT_CHAT, "%s: %s\n", actor_names[(other - g_edicts)%MAX_ACTOR_NAMES], self->message); + } + } + + if (self->spawnflags & 1) //jump + { + other->velocity[0] = self->movedir[0] * self->speed; + other->velocity[1] = self->movedir[1] * self->speed; + + if (other->groundentity) + { + other->groundentity = NULL; + other->velocity[2] = self->movedir[2]; + gi.sound(other, CHAN_VOICE, gi.soundindex("player/male/jump1.wav"), 1, ATTN_NORM, 0); + } + } + + if (self->spawnflags & 2) //shoot + { + } + else if (self->spawnflags & 4) //attack + { + other->enemy = G_PickTarget(self->pathtarget); + if (other->enemy) + { + other->goalentity = other->enemy; + if (self->spawnflags & 32) + other->monsterinfo.aiflags |= AI_BRUTAL; + if (self->spawnflags & 16) + { + other->monsterinfo.aiflags |= AI_STAND_GROUND; + actor_stand (other); + } + else + { + actor_run (other); + } + } + } + + if (!(self->spawnflags & 6) && (self->pathtarget)) + { + char *savetarget; + + savetarget = self->target; + self->target = self->pathtarget; + G_UseTargets (self, other); + self->target = savetarget; + } + + other->movetarget = G_PickTarget(self->target); + + if (!other->goalentity) + other->goalentity = other->movetarget; + + if (!other->movetarget && !other->enemy) + { + other->monsterinfo.pausetime = level.time + 100000000; + other->monsterinfo.stand (other); + } + else if (other->movetarget == other->goalentity) + { + VectorSubtract (other->movetarget->s.origin, other->s.origin, v); + other->ideal_yaw = vectoyaw (v); + } +} + +void SP_target_actor (edict_t *self) +{ + if (!self->targetname) + gi.dprintf ("%s with no targetname at %s\n", self->classname, vtos(self->s.origin)); + + self->solid = SOLID_TRIGGER; + self->touch = target_actor_touch; + VectorSet (self->mins, -8, -8, -8); + VectorSet (self->maxs, 8, 8, 8); + self->svflags = SVF_NOCLIENT; + + if (self->spawnflags & 1) + { + if (!self->speed) + self->speed = 200; + if (!st.height) + st.height = 200; + if (self->s.angles[YAW] == 0) + self->s.angles[YAW] = 360; + G_SetMovedir (self->s.angles, self->movedir); + self->movedir[2] = st.height; + } + + gi.linkentity (self); +} diff --git a/original/baseq2/m_actor.h b/original/baseq2/m_actor.h new file mode 100644 index 0000000..5fc0d8d --- /dev/null +++ b/original/baseq2/m_actor.h @@ -0,0 +1,506 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +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. + +*/ +// G:\quake2\baseq2\models/player_y + +// This file generated by ModelGen - Do NOT Modify + +#define FRAME_attak01 0 +#define FRAME_attak02 1 +#define FRAME_attak03 2 +#define FRAME_attak04 3 +#define FRAME_death101 4 +#define FRAME_death102 5 +#define FRAME_death103 6 +#define FRAME_death104 7 +#define FRAME_death105 8 +#define FRAME_death106 9 +#define FRAME_death107 10 +#define FRAME_death201 11 +#define FRAME_death202 12 +#define FRAME_death203 13 +#define FRAME_death204 14 +#define FRAME_death205 15 +#define FRAME_death206 16 +#define FRAME_death207 17 +#define FRAME_death208 18 +#define FRAME_death209 19 +#define FRAME_death210 20 +#define FRAME_death211 21 +#define FRAME_death212 22 +#define FRAME_death213 23 +#define FRAME_death301 24 +#define FRAME_death302 25 +#define FRAME_death303 26 +#define FRAME_death304 27 +#define FRAME_death305 28 +#define FRAME_death306 29 +#define FRAME_death307 30 +#define FRAME_death308 31 +#define FRAME_death309 32 +#define FRAME_death310 33 +#define FRAME_death311 34 +#define FRAME_death312 35 +#define FRAME_death313 36 +#define FRAME_death314 37 +#define FRAME_death315 38 +#define FRAME_flip01 39 +#define FRAME_flip02 40 +#define FRAME_flip03 41 +#define FRAME_flip04 42 +#define FRAME_flip05 43 +#define FRAME_flip06 44 +#define FRAME_flip07 45 +#define FRAME_flip08 46 +#define FRAME_flip09 47 +#define FRAME_flip10 48 +#define FRAME_flip11 49 +#define FRAME_flip12 50 +#define FRAME_flip13 51 +#define FRAME_flip14 52 +#define FRAME_grenad01 53 +#define FRAME_grenad02 54 +#define FRAME_grenad03 55 +#define FRAME_grenad04 56 +#define FRAME_grenad05 57 +#define FRAME_grenad06 58 +#define FRAME_grenad07 59 +#define FRAME_grenad08 60 +#define FRAME_grenad09 61 +#define FRAME_grenad10 62 +#define FRAME_grenad11 63 +#define FRAME_grenad12 64 +#define FRAME_grenad13 65 +#define FRAME_grenad14 66 +#define FRAME_grenad15 67 +#define FRAME_jump01 68 +#define FRAME_jump02 69 +#define FRAME_jump03 70 +#define FRAME_jump04 71 +#define FRAME_jump05 72 +#define FRAME_jump06 73 +#define FRAME_pain101 74 +#define FRAME_pain102 75 +#define FRAME_pain103 76 +#define FRAME_pain201 77 +#define FRAME_pain202 78 +#define FRAME_pain203 79 +#define FRAME_pain301 80 +#define FRAME_pain302 81 +#define FRAME_pain303 82 +#define FRAME_push01 83 +#define FRAME_push02 84 +#define FRAME_push03 85 +#define FRAME_push04 86 +#define FRAME_push05 87 +#define FRAME_push06 88 +#define FRAME_push07 89 +#define FRAME_push08 90 +#define FRAME_push09 91 +#define FRAME_run01 92 +#define FRAME_run02 93 +#define FRAME_run03 94 +#define FRAME_run04 95 +#define FRAME_run05 96 +#define FRAME_run06 97 +#define FRAME_run07 98 +#define FRAME_run08 99 +#define FRAME_run09 100 +#define FRAME_run10 101 +#define FRAME_run11 102 +#define FRAME_run12 103 +#define FRAME_runs01 104 +#define FRAME_runs02 105 +#define FRAME_runs03 106 +#define FRAME_runs04 107 +#define FRAME_runs05 108 +#define FRAME_runs06 109 +#define FRAME_runs07 110 +#define FRAME_runs08 111 +#define FRAME_runs09 112 +#define FRAME_runs10 113 +#define FRAME_runs11 114 +#define FRAME_runs12 115 +#define FRAME_salute01 116 +#define FRAME_salute02 117 +#define FRAME_salute03 118 +#define FRAME_salute04 119 +#define FRAME_salute05 120 +#define FRAME_salute06 121 +#define FRAME_salute07 122 +#define FRAME_salute08 123 +#define FRAME_salute09 124 +#define FRAME_salute10 125 +#define FRAME_salute11 126 +#define FRAME_salute12 127 +#define FRAME_stand101 128 +#define FRAME_stand102 129 +#define FRAME_stand103 130 +#define FRAME_stand104 131 +#define FRAME_stand105 132 +#define FRAME_stand106 133 +#define FRAME_stand107 134 +#define FRAME_stand108 135 +#define FRAME_stand109 136 +#define FRAME_stand110 137 +#define FRAME_stand111 138 +#define FRAME_stand112 139 +#define FRAME_stand113 140 +#define FRAME_stand114 141 +#define FRAME_stand115 142 +#define FRAME_stand116 143 +#define FRAME_stand117 144 +#define FRAME_stand118 145 +#define FRAME_stand119 146 +#define FRAME_stand120 147 +#define FRAME_stand121 148 +#define FRAME_stand122 149 +#define FRAME_stand123 150 +#define FRAME_stand124 151 +#define FRAME_stand125 152 +#define FRAME_stand126 153 +#define FRAME_stand127 154 +#define FRAME_stand128 155 +#define FRAME_stand129 156 +#define FRAME_stand130 157 +#define FRAME_stand131 158 +#define FRAME_stand132 159 +#define FRAME_stand133 160 +#define FRAME_stand134 161 +#define FRAME_stand135 162 +#define FRAME_stand136 163 +#define FRAME_stand137 164 +#define FRAME_stand138 165 +#define FRAME_stand139 166 +#define FRAME_stand140 167 +#define FRAME_stand201 168 +#define FRAME_stand202 169 +#define FRAME_stand203 170 +#define FRAME_stand204 171 +#define FRAME_stand205 172 +#define FRAME_stand206 173 +#define FRAME_stand207 174 +#define FRAME_stand208 175 +#define FRAME_stand209 176 +#define FRAME_stand210 177 +#define FRAME_stand211 178 +#define FRAME_stand212 179 +#define FRAME_stand213 180 +#define FRAME_stand214 181 +#define FRAME_stand215 182 +#define FRAME_stand216 183 +#define FRAME_stand217 184 +#define FRAME_stand218 185 +#define FRAME_stand219 186 +#define FRAME_stand220 187 +#define FRAME_stand221 188 +#define FRAME_stand222 189 +#define FRAME_stand223 190 +#define FRAME_swim01 191 +#define FRAME_swim02 192 +#define FRAME_swim03 193 +#define FRAME_swim04 194 +#define FRAME_swim05 195 +#define FRAME_swim06 196 +#define FRAME_swim07 197 +#define FRAME_swim08 198 +#define FRAME_swim09 199 +#define FRAME_swim10 200 +#define FRAME_swim11 201 +#define FRAME_swim12 202 +#define FRAME_sw_atk01 203 +#define FRAME_sw_atk02 204 +#define FRAME_sw_atk03 205 +#define FRAME_sw_atk04 206 +#define FRAME_sw_atk05 207 +#define FRAME_sw_atk06 208 +#define FRAME_sw_pan01 209 +#define FRAME_sw_pan02 210 +#define FRAME_sw_pan03 211 +#define FRAME_sw_pan04 212 +#define FRAME_sw_pan05 213 +#define FRAME_sw_std01 214 +#define FRAME_sw_std02 215 +#define FRAME_sw_std03 216 +#define FRAME_sw_std04 217 +#define FRAME_sw_std05 218 +#define FRAME_sw_std06 219 +#define FRAME_sw_std07 220 +#define FRAME_sw_std08 221 +#define FRAME_sw_std09 222 +#define FRAME_sw_std10 223 +#define FRAME_sw_std11 224 +#define FRAME_sw_std12 225 +#define FRAME_sw_std13 226 +#define FRAME_sw_std14 227 +#define FRAME_sw_std15 228 +#define FRAME_sw_std16 229 +#define FRAME_sw_std17 230 +#define FRAME_sw_std18 231 +#define FRAME_sw_std19 232 +#define FRAME_sw_std20 233 +#define FRAME_taunt01 234 +#define FRAME_taunt02 235 +#define FRAME_taunt03 236 +#define FRAME_taunt04 237 +#define FRAME_taunt05 238 +#define FRAME_taunt06 239 +#define FRAME_taunt07 240 +#define FRAME_taunt08 241 +#define FRAME_taunt09 242 +#define FRAME_taunt10 243 +#define FRAME_taunt11 244 +#define FRAME_taunt12 245 +#define FRAME_taunt13 246 +#define FRAME_taunt14 247 +#define FRAME_taunt15 248 +#define FRAME_taunt16 249 +#define FRAME_taunt17 250 +#define FRAME_walk01 251 +#define FRAME_walk02 252 +#define FRAME_walk03 253 +#define FRAME_walk04 254 +#define FRAME_walk05 255 +#define FRAME_walk06 256 +#define FRAME_walk07 257 +#define FRAME_walk08 258 +#define FRAME_walk09 259 +#define FRAME_walk10 260 +#define FRAME_walk11 261 +#define FRAME_wave01 262 +#define FRAME_wave02 263 +#define FRAME_wave03 264 +#define FRAME_wave04 265 +#define FRAME_wave05 266 +#define FRAME_wave06 267 +#define FRAME_wave07 268 +#define FRAME_wave08 269 +#define FRAME_wave09 270 +#define FRAME_wave10 271 +#define FRAME_wave11 272 +#define FRAME_wave12 273 +#define FRAME_wave13 274 +#define FRAME_wave14 275 +#define FRAME_wave15 276 +#define FRAME_wave16 277 +#define FRAME_wave17 278 +#define FRAME_wave18 279 +#define FRAME_wave19 280 +#define FRAME_wave20 281 +#define FRAME_wave21 282 +#define FRAME_bl_atk01 283 +#define FRAME_bl_atk02 284 +#define FRAME_bl_atk03 285 +#define FRAME_bl_atk04 286 +#define FRAME_bl_atk05 287 +#define FRAME_bl_atk06 288 +#define FRAME_bl_flp01 289 +#define FRAME_bl_flp02 290 +#define FRAME_bl_flp13 291 +#define FRAME_bl_flp14 292 +#define FRAME_bl_flp15 293 +#define FRAME_bl_jmp01 294 +#define FRAME_bl_jmp02 295 +#define FRAME_bl_jmp03 296 +#define FRAME_bl_jmp04 297 +#define FRAME_bl_jmp05 298 +#define FRAME_bl_jmp06 299 +#define FRAME_bl_pn101 300 +#define FRAME_bl_pn102 301 +#define FRAME_bl_pn103 302 +#define FRAME_bl_pn201 303 +#define FRAME_bl_pn202 304 +#define FRAME_bl_pn203 305 +#define FRAME_bl_pn301 306 +#define FRAME_bl_pn302 307 +#define FRAME_bl_pn303 308 +#define FRAME_bl_psh08 309 +#define FRAME_bl_psh09 310 +#define FRAME_bl_run01 311 +#define FRAME_bl_run02 312 +#define FRAME_bl_run03 313 +#define FRAME_bl_run04 314 +#define FRAME_bl_run05 315 +#define FRAME_bl_run06 316 +#define FRAME_bl_run07 317 +#define FRAME_bl_run08 318 +#define FRAME_bl_run09 319 +#define FRAME_bl_run10 320 +#define FRAME_bl_run11 321 +#define FRAME_bl_run12 322 +#define FRAME_bl_rns03 323 +#define FRAME_bl_rns04 324 +#define FRAME_bl_rns05 325 +#define FRAME_bl_rns06 326 +#define FRAME_bl_rns07 327 +#define FRAME_bl_rns08 328 +#define FRAME_bl_rns09 329 +#define FRAME_bl_sal10 330 +#define FRAME_bl_sal11 331 +#define FRAME_bl_sal12 332 +#define FRAME_bl_std01 333 +#define FRAME_bl_std02 334 +#define FRAME_bl_std03 335 +#define FRAME_bl_std04 336 +#define FRAME_bl_std05 337 +#define FRAME_bl_std06 338 +#define FRAME_bl_std07 339 +#define FRAME_bl_std08 340 +#define FRAME_bl_std09 341 +#define FRAME_bl_std10 342 +#define FRAME_bl_std11 343 +#define FRAME_bl_std12 344 +#define FRAME_bl_std13 345 +#define FRAME_bl_std14 346 +#define FRAME_bl_std15 347 +#define FRAME_bl_std16 348 +#define FRAME_bl_std17 349 +#define FRAME_bl_std18 350 +#define FRAME_bl_std19 351 +#define FRAME_bl_std20 352 +#define FRAME_bl_std21 353 +#define FRAME_bl_std22 354 +#define FRAME_bl_std23 355 +#define FRAME_bl_std24 356 +#define FRAME_bl_std25 357 +#define FRAME_bl_std26 358 +#define FRAME_bl_std27 359 +#define FRAME_bl_std28 360 +#define FRAME_bl_std29 361 +#define FRAME_bl_std30 362 +#define FRAME_bl_std31 363 +#define FRAME_bl_std32 364 +#define FRAME_bl_std33 365 +#define FRAME_bl_std34 366 +#define FRAME_bl_std35 367 +#define FRAME_bl_std36 368 +#define FRAME_bl_std37 369 +#define FRAME_bl_std38 370 +#define FRAME_bl_std39 371 +#define FRAME_bl_std40 372 +#define FRAME_bl_swm01 373 +#define FRAME_bl_swm02 374 +#define FRAME_bl_swm03 375 +#define FRAME_bl_swm04 376 +#define FRAME_bl_swm05 377 +#define FRAME_bl_swm06 378 +#define FRAME_bl_swm07 379 +#define FRAME_bl_swm08 380 +#define FRAME_bl_swm09 381 +#define FRAME_bl_swm10 382 +#define FRAME_bl_swm11 383 +#define FRAME_bl_swm12 384 +#define FRAME_bl_swk01 385 +#define FRAME_bl_swk02 386 +#define FRAME_bl_swk03 387 +#define FRAME_bl_swk04 388 +#define FRAME_bl_swk05 389 +#define FRAME_bl_swk06 390 +#define FRAME_bl_swp01 391 +#define FRAME_bl_swp02 392 +#define FRAME_bl_swp03 393 +#define FRAME_bl_swp04 394 +#define FRAME_bl_swp05 395 +#define FRAME_bl_sws01 396 +#define FRAME_bl_sws02 397 +#define FRAME_bl_sws03 398 +#define FRAME_bl_sws04 399 +#define FRAME_bl_sws05 400 +#define FRAME_bl_sws06 401 +#define FRAME_bl_sws07 402 +#define FRAME_bl_sws08 403 +#define FRAME_bl_sws09 404 +#define FRAME_bl_sws10 405 +#define FRAME_bl_sws11 406 +#define FRAME_bl_sws12 407 +#define FRAME_bl_sws13 408 +#define FRAME_bl_sws14 409 +#define FRAME_bl_tau14 410 +#define FRAME_bl_tau15 411 +#define FRAME_bl_tau16 412 +#define FRAME_bl_tau17 413 +#define FRAME_bl_wlk01 414 +#define FRAME_bl_wlk02 415 +#define FRAME_bl_wlk03 416 +#define FRAME_bl_wlk04 417 +#define FRAME_bl_wlk05 418 +#define FRAME_bl_wlk06 419 +#define FRAME_bl_wlk07 420 +#define FRAME_bl_wlk08 421 +#define FRAME_bl_wlk09 422 +#define FRAME_bl_wlk10 423 +#define FRAME_bl_wlk11 424 +#define FRAME_bl_wav19 425 +#define FRAME_bl_wav20 426 +#define FRAME_bl_wav21 427 +#define FRAME_cr_atk01 428 +#define FRAME_cr_atk02 429 +#define FRAME_cr_atk03 430 +#define FRAME_cr_atk04 431 +#define FRAME_cr_atk05 432 +#define FRAME_cr_atk06 433 +#define FRAME_cr_atk07 434 +#define FRAME_cr_atk08 435 +#define FRAME_cr_pan01 436 +#define FRAME_cr_pan02 437 +#define FRAME_cr_pan03 438 +#define FRAME_cr_pan04 439 +#define FRAME_cr_std01 440 +#define FRAME_cr_std02 441 +#define FRAME_cr_std03 442 +#define FRAME_cr_std04 443 +#define FRAME_cr_std05 444 +#define FRAME_cr_std06 445 +#define FRAME_cr_std07 446 +#define FRAME_cr_std08 447 +#define FRAME_cr_wlk01 448 +#define FRAME_cr_wlk02 449 +#define FRAME_cr_wlk03 450 +#define FRAME_cr_wlk04 451 +#define FRAME_cr_wlk05 452 +#define FRAME_cr_wlk06 453 +#define FRAME_cr_wlk07 454 +#define FRAME_crbl_a01 455 +#define FRAME_crbl_a02 456 +#define FRAME_crbl_a03 457 +#define FRAME_crbl_a04 458 +#define FRAME_crbl_a05 459 +#define FRAME_crbl_a06 460 +#define FRAME_crbl_a07 461 +#define FRAME_crbl_p01 462 +#define FRAME_crbl_p02 463 +#define FRAME_crbl_p03 464 +#define FRAME_crbl_p04 465 +#define FRAME_crbl_s01 466 +#define FRAME_crbl_s02 467 +#define FRAME_crbl_s03 468 +#define FRAME_crbl_s04 469 +#define FRAME_crbl_s05 470 +#define FRAME_crbl_s06 471 +#define FRAME_crbl_s07 472 +#define FRAME_crbl_s08 473 +#define FRAME_crbl_w01 474 +#define FRAME_crbl_w02 475 +#define FRAME_crbl_w03 476 +#define FRAME_crbl_w04 477 +#define FRAME_crbl_w05 478 +#define FRAME_crbl_w06 479 +#define FRAME_crbl_w07 480 + +#define MODEL_SCALE 1.000000 diff --git a/original/baseq2/m_berserk.c b/original/baseq2/m_berserk.c new file mode 100644 index 0000000..c5d0bd0 --- /dev/null +++ b/original/baseq2/m_berserk.c @@ -0,0 +1,457 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +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. + +*/ +/* +============================================================================== + +BERSERK + +============================================================================== +*/ + +#include "g_local.h" +#include "m_berserk.h" + + +static int sound_pain; +static int sound_die; +static int sound_idle; +static int sound_punch; +static int sound_sight; +static int sound_search; + +void berserk_sight (edict_t *self, edict_t *other) +{ + gi.sound (self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); +} + +void berserk_search (edict_t *self) +{ + gi.sound (self, CHAN_VOICE, sound_search, 1, ATTN_NORM, 0); +} + + +void berserk_fidget (edict_t *self); +mframe_t berserk_frames_stand [] = +{ + ai_stand, 0, berserk_fidget, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL +}; +mmove_t berserk_move_stand = {FRAME_stand1, FRAME_stand5, berserk_frames_stand, NULL}; + +void berserk_stand (edict_t *self) +{ + self->monsterinfo.currentmove = &berserk_move_stand; +} + +mframe_t berserk_frames_stand_fidget [] = +{ + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL +}; +mmove_t berserk_move_stand_fidget = {FRAME_standb1, FRAME_standb20, berserk_frames_stand_fidget, berserk_stand}; + +void berserk_fidget (edict_t *self) +{ + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + return; + if (random() > 0.15) + return; + + self->monsterinfo.currentmove = &berserk_move_stand_fidget; + gi.sound (self, CHAN_WEAPON, sound_idle, 1, ATTN_IDLE, 0); +} + + +mframe_t berserk_frames_walk [] = +{ + ai_walk, 9.1, NULL, + ai_walk, 6.3, NULL, + ai_walk, 4.9, NULL, + ai_walk, 6.7, NULL, + ai_walk, 6.0, NULL, + ai_walk, 8.2, NULL, + ai_walk, 7.2, NULL, + ai_walk, 6.1, NULL, + ai_walk, 4.9, NULL, + ai_walk, 4.7, NULL, + ai_walk, 4.7, NULL, + ai_walk, 4.8, NULL +}; +mmove_t berserk_move_walk = {FRAME_walkc1, FRAME_walkc11, berserk_frames_walk, NULL}; + +void berserk_walk (edict_t *self) +{ + self->monsterinfo.currentmove = &berserk_move_walk; +} + +/* + + ***************************** + SKIPPED THIS FOR NOW! + ***************************** + + Running -> Arm raised in air + +void() berserk_runb1 =[ $r_att1 , berserk_runb2 ] {ai_run(21);}; +void() berserk_runb2 =[ $r_att2 , berserk_runb3 ] {ai_run(11);}; +void() berserk_runb3 =[ $r_att3 , berserk_runb4 ] {ai_run(21);}; +void() berserk_runb4 =[ $r_att4 , berserk_runb5 ] {ai_run(25);}; +void() berserk_runb5 =[ $r_att5 , berserk_runb6 ] {ai_run(18);}; +void() berserk_runb6 =[ $r_att6 , berserk_runb7 ] {ai_run(19);}; +// running with arm in air : start loop +void() berserk_runb7 =[ $r_att7 , berserk_runb8 ] {ai_run(21);}; +void() berserk_runb8 =[ $r_att8 , berserk_runb9 ] {ai_run(11);}; +void() berserk_runb9 =[ $r_att9 , berserk_runb10 ] {ai_run(21);}; +void() berserk_runb10 =[ $r_att10 , berserk_runb11 ] {ai_run(25);}; +void() berserk_runb11 =[ $r_att11 , berserk_runb12 ] {ai_run(18);}; +void() berserk_runb12 =[ $r_att12 , berserk_runb7 ] {ai_run(19);}; +// running with arm in air : end loop +*/ + + +mframe_t berserk_frames_run1 [] = +{ + ai_run, 21, NULL, + ai_run, 11, NULL, + ai_run, 21, NULL, + ai_run, 25, NULL, + ai_run, 18, NULL, + ai_run, 19, NULL +}; +mmove_t berserk_move_run1 = {FRAME_run1, FRAME_run6, berserk_frames_run1, NULL}; + +void berserk_run (edict_t *self) +{ + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + self->monsterinfo.currentmove = &berserk_move_stand; + else + self->monsterinfo.currentmove = &berserk_move_run1; +} + + +void berserk_attack_spike (edict_t *self) +{ + static vec3_t aim = {MELEE_DISTANCE, 0, -24}; + fire_hit (self, aim, (15 + (rand() % 6)), 400); // Faster attack -- upwards and backwards +} + + +void berserk_swing (edict_t *self) +{ + gi.sound (self, CHAN_WEAPON, sound_punch, 1, ATTN_NORM, 0); +} + +mframe_t berserk_frames_attack_spike [] = +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, berserk_swing, + ai_charge, 0, berserk_attack_spike, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL +}; +mmove_t berserk_move_attack_spike = {FRAME_att_c1, FRAME_att_c8, berserk_frames_attack_spike, berserk_run}; + + +void berserk_attack_club (edict_t *self) +{ + vec3_t aim; + + VectorSet (aim, MELEE_DISTANCE, self->mins[0], -4); + fire_hit (self, aim, (5 + (rand() % 6)), 400); // Slower attack +} + +mframe_t berserk_frames_attack_club [] = +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, berserk_swing, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, berserk_attack_club, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL +}; +mmove_t berserk_move_attack_club = {FRAME_att_c9, FRAME_att_c20, berserk_frames_attack_club, berserk_run}; + + +void berserk_strike (edict_t *self) +{ + //FIXME play impact sound +} + + +mframe_t berserk_frames_attack_strike [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, berserk_swing, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, berserk_strike, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 9.7, NULL, + ai_move, 13.6, NULL +}; + +mmove_t berserk_move_attack_strike = {FRAME_att_c21, FRAME_att_c34, berserk_frames_attack_strike, berserk_run}; + + +void berserk_melee (edict_t *self) +{ + if ((rand() % 2) == 0) + self->monsterinfo.currentmove = &berserk_move_attack_spike; + else + self->monsterinfo.currentmove = &berserk_move_attack_club; +} + + +/* +void() berserk_atke1 =[ $r_attb1, berserk_atke2 ] {ai_run(9);}; +void() berserk_atke2 =[ $r_attb2, berserk_atke3 ] {ai_run(6);}; +void() berserk_atke3 =[ $r_attb3, berserk_atke4 ] {ai_run(18.4);}; +void() berserk_atke4 =[ $r_attb4, berserk_atke5 ] {ai_run(25);}; +void() berserk_atke5 =[ $r_attb5, berserk_atke6 ] {ai_run(14);}; +void() berserk_atke6 =[ $r_attb6, berserk_atke7 ] {ai_run(20);}; +void() berserk_atke7 =[ $r_attb7, berserk_atke8 ] {ai_run(8.5);}; +void() berserk_atke8 =[ $r_attb8, berserk_atke9 ] {ai_run(3);}; +void() berserk_atke9 =[ $r_attb9, berserk_atke10 ] {ai_run(17.5);}; +void() berserk_atke10 =[ $r_attb10, berserk_atke11 ] {ai_run(17);}; +void() berserk_atke11 =[ $r_attb11, berserk_atke12 ] {ai_run(9);}; +void() berserk_atke12 =[ $r_attb12, berserk_atke13 ] {ai_run(25);}; +void() berserk_atke13 =[ $r_attb13, berserk_atke14 ] {ai_run(3.7);}; +void() berserk_atke14 =[ $r_attb14, berserk_atke15 ] {ai_run(2.6);}; +void() berserk_atke15 =[ $r_attb15, berserk_atke16 ] {ai_run(19);}; +void() berserk_atke16 =[ $r_attb16, berserk_atke17 ] {ai_run(25);}; +void() berserk_atke17 =[ $r_attb17, berserk_atke18 ] {ai_run(19.6);}; +void() berserk_atke18 =[ $r_attb18, berserk_run1 ] {ai_run(7.8);}; +*/ + + +mframe_t berserk_frames_pain1 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t berserk_move_pain1 = {FRAME_painc1, FRAME_painc4, berserk_frames_pain1, berserk_run}; + + +mframe_t berserk_frames_pain2 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t berserk_move_pain2 = {FRAME_painb1, FRAME_painb20, berserk_frames_pain2, berserk_run}; + +void berserk_pain (edict_t *self, edict_t *other, float kick, int damage) +{ + if (self->health < (self->max_health / 2)) + self->s.skinnum = 1; + + if (level.time < self->pain_debounce_time) + return; + + self->pain_debounce_time = level.time + 3; + gi.sound (self, CHAN_VOICE, sound_pain, 1, ATTN_NORM, 0); + + if (skill->value == 3) + return; // no pain anims in nightmare + + if ((damage < 20) || (random() < 0.5)) + self->monsterinfo.currentmove = &berserk_move_pain1; + else + self->monsterinfo.currentmove = &berserk_move_pain2; +} + + +void berserk_dead (edict_t *self) +{ + VectorSet (self->mins, -16, -16, -24); + VectorSet (self->maxs, 16, 16, -8); + self->movetype = MOVETYPE_TOSS; + self->svflags |= SVF_DEADMONSTER; + self->nextthink = 0; + gi.linkentity (self); +} + + +mframe_t berserk_frames_death1 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL + +}; +mmove_t berserk_move_death1 = {FRAME_death1, FRAME_death13, berserk_frames_death1, berserk_dead}; + + +mframe_t berserk_frames_death2 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t berserk_move_death2 = {FRAME_deathc1, FRAME_deathc8, berserk_frames_death2, berserk_dead}; + + +void berserk_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + int n; + + if (self->health <= self->gib_health) + { + gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0); + for (n= 0; n < 2; n++) + ThrowGib (self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC); + for (n= 0; n < 4; n++) + ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); + ThrowHead (self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC); + self->deadflag = DEAD_DEAD; + return; + } + + if (self->deadflag == DEAD_DEAD) + return; + + gi.sound (self, CHAN_VOICE, sound_die, 1, ATTN_NORM, 0); + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_YES; + + if (damage >= 50) + self->monsterinfo.currentmove = &berserk_move_death1; + else + self->monsterinfo.currentmove = &berserk_move_death2; +} + + +/*QUAKED monster_berserk (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight +*/ +void SP_monster_berserk (edict_t *self) +{ + if (deathmatch->value) + { + G_FreeEdict (self); + return; + } + + // pre-caches + sound_pain = gi.soundindex ("berserk/berpain2.wav"); + sound_die = gi.soundindex ("berserk/berdeth2.wav"); + sound_idle = gi.soundindex ("berserk/beridle1.wav"); + sound_punch = gi.soundindex ("berserk/attack.wav"); + sound_search = gi.soundindex ("berserk/bersrch1.wav"); + sound_sight = gi.soundindex ("berserk/sight.wav"); + + self->s.modelindex = gi.modelindex("models/monsters/berserk/tris.md2"); + VectorSet (self->mins, -16, -16, -24); + VectorSet (self->maxs, 16, 16, 32); + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + + self->health = 240; + self->gib_health = -60; + self->mass = 250; + + self->pain = berserk_pain; + self->die = berserk_die; + + self->monsterinfo.stand = berserk_stand; + self->monsterinfo.walk = berserk_walk; + self->monsterinfo.run = berserk_run; + self->monsterinfo.dodge = NULL; + self->monsterinfo.attack = NULL; + self->monsterinfo.melee = berserk_melee; + self->monsterinfo.sight = berserk_sight; + self->monsterinfo.search = berserk_search; + + self->monsterinfo.currentmove = &berserk_move_stand; + self->monsterinfo.scale = MODEL_SCALE; + + gi.linkentity (self); + + walkmonster_start (self); +} diff --git a/original/baseq2/m_berserk.h b/original/baseq2/m_berserk.h new file mode 100644 index 0000000..6cce9e8 --- /dev/null +++ b/original/baseq2/m_berserk.h @@ -0,0 +1,269 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +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. + +*/ +// G:\quake2\baseq2\models/monsters/berserk + +// This file generated by ModelGen - Do NOT Modify + +#define FRAME_stand1 0 +#define FRAME_stand2 1 +#define FRAME_stand3 2 +#define FRAME_stand4 3 +#define FRAME_stand5 4 +#define FRAME_standb1 5 +#define FRAME_standb2 6 +#define FRAME_standb3 7 +#define FRAME_standb4 8 +#define FRAME_standb5 9 +#define FRAME_standb6 10 +#define FRAME_standb7 11 +#define FRAME_standb8 12 +#define FRAME_standb9 13 +#define FRAME_standb10 14 +#define FRAME_standb11 15 +#define FRAME_standb12 16 +#define FRAME_standb13 17 +#define FRAME_standb14 18 +#define FRAME_standb15 19 +#define FRAME_standb16 20 +#define FRAME_standb17 21 +#define FRAME_standb18 22 +#define FRAME_standb19 23 +#define FRAME_standb20 24 +#define FRAME_walkc1 25 +#define FRAME_walkc2 26 +#define FRAME_walkc3 27 +#define FRAME_walkc4 28 +#define FRAME_walkc5 29 +#define FRAME_walkc6 30 +#define FRAME_walkc7 31 +#define FRAME_walkc8 32 +#define FRAME_walkc9 33 +#define FRAME_walkc10 34 +#define FRAME_walkc11 35 +#define FRAME_run1 36 +#define FRAME_run2 37 +#define FRAME_run3 38 +#define FRAME_run4 39 +#define FRAME_run5 40 +#define FRAME_run6 41 +#define FRAME_att_a1 42 +#define FRAME_att_a2 43 +#define FRAME_att_a3 44 +#define FRAME_att_a4 45 +#define FRAME_att_a5 46 +#define FRAME_att_a6 47 +#define FRAME_att_a7 48 +#define FRAME_att_a8 49 +#define FRAME_att_a9 50 +#define FRAME_att_a10 51 +#define FRAME_att_a11 52 +#define FRAME_att_a12 53 +#define FRAME_att_a13 54 +#define FRAME_att_b1 55 +#define FRAME_att_b2 56 +#define FRAME_att_b3 57 +#define FRAME_att_b4 58 +#define FRAME_att_b5 59 +#define FRAME_att_b6 60 +#define FRAME_att_b7 61 +#define FRAME_att_b8 62 +#define FRAME_att_b9 63 +#define FRAME_att_b10 64 +#define FRAME_att_b11 65 +#define FRAME_att_b12 66 +#define FRAME_att_b13 67 +#define FRAME_att_b14 68 +#define FRAME_att_b15 69 +#define FRAME_att_b16 70 +#define FRAME_att_b17 71 +#define FRAME_att_b18 72 +#define FRAME_att_b19 73 +#define FRAME_att_b20 74 +#define FRAME_att_b21 75 +#define FRAME_att_c1 76 +#define FRAME_att_c2 77 +#define FRAME_att_c3 78 +#define FRAME_att_c4 79 +#define FRAME_att_c5 80 +#define FRAME_att_c6 81 +#define FRAME_att_c7 82 +#define FRAME_att_c8 83 +#define FRAME_att_c9 84 +#define FRAME_att_c10 85 +#define FRAME_att_c11 86 +#define FRAME_att_c12 87 +#define FRAME_att_c13 88 +#define FRAME_att_c14 89 +#define FRAME_att_c15 90 +#define FRAME_att_c16 91 +#define FRAME_att_c17 92 +#define FRAME_att_c18 93 +#define FRAME_att_c19 94 +#define FRAME_att_c20 95 +#define FRAME_att_c21 96 +#define FRAME_att_c22 97 +#define FRAME_att_c23 98 +#define FRAME_att_c24 99 +#define FRAME_att_c25 100 +#define FRAME_att_c26 101 +#define FRAME_att_c27 102 +#define FRAME_att_c28 103 +#define FRAME_att_c29 104 +#define FRAME_att_c30 105 +#define FRAME_att_c31 106 +#define FRAME_att_c32 107 +#define FRAME_att_c33 108 +#define FRAME_att_c34 109 +#define FRAME_r_att1 110 +#define FRAME_r_att2 111 +#define FRAME_r_att3 112 +#define FRAME_r_att4 113 +#define FRAME_r_att5 114 +#define FRAME_r_att6 115 +#define FRAME_r_att7 116 +#define FRAME_r_att8 117 +#define FRAME_r_att9 118 +#define FRAME_r_att10 119 +#define FRAME_r_att11 120 +#define FRAME_r_att12 121 +#define FRAME_r_att13 122 +#define FRAME_r_att14 123 +#define FRAME_r_att15 124 +#define FRAME_r_att16 125 +#define FRAME_r_att17 126 +#define FRAME_r_att18 127 +#define FRAME_r_attb1 128 +#define FRAME_r_attb2 129 +#define FRAME_r_attb3 130 +#define FRAME_r_attb4 131 +#define FRAME_r_attb5 132 +#define FRAME_r_attb6 133 +#define FRAME_r_attb7 134 +#define FRAME_r_attb8 135 +#define FRAME_r_attb9 136 +#define FRAME_r_attb10 137 +#define FRAME_r_attb11 138 +#define FRAME_r_attb12 139 +#define FRAME_r_attb13 140 +#define FRAME_r_attb14 141 +#define FRAME_r_attb15 142 +#define FRAME_r_attb16 143 +#define FRAME_r_attb17 144 +#define FRAME_r_attb18 145 +#define FRAME_slam1 146 +#define FRAME_slam2 147 +#define FRAME_slam3 148 +#define FRAME_slam4 149 +#define FRAME_slam5 150 +#define FRAME_slam6 151 +#define FRAME_slam7 152 +#define FRAME_slam8 153 +#define FRAME_slam9 154 +#define FRAME_slam10 155 +#define FRAME_slam11 156 +#define FRAME_slam12 157 +#define FRAME_slam13 158 +#define FRAME_slam14 159 +#define FRAME_slam15 160 +#define FRAME_slam16 161 +#define FRAME_slam17 162 +#define FRAME_slam18 163 +#define FRAME_slam19 164 +#define FRAME_slam20 165 +#define FRAME_slam21 166 +#define FRAME_slam22 167 +#define FRAME_slam23 168 +#define FRAME_duck1 169 +#define FRAME_duck2 170 +#define FRAME_duck3 171 +#define FRAME_duck4 172 +#define FRAME_duck5 173 +#define FRAME_duck6 174 +#define FRAME_duck7 175 +#define FRAME_duck8 176 +#define FRAME_duck9 177 +#define FRAME_duck10 178 +#define FRAME_fall1 179 +#define FRAME_fall2 180 +#define FRAME_fall3 181 +#define FRAME_fall4 182 +#define FRAME_fall5 183 +#define FRAME_fall6 184 +#define FRAME_fall7 185 +#define FRAME_fall8 186 +#define FRAME_fall9 187 +#define FRAME_fall10 188 +#define FRAME_fall11 189 +#define FRAME_fall12 190 +#define FRAME_fall13 191 +#define FRAME_fall14 192 +#define FRAME_fall15 193 +#define FRAME_fall16 194 +#define FRAME_fall17 195 +#define FRAME_fall18 196 +#define FRAME_fall19 197 +#define FRAME_fall20 198 +#define FRAME_painc1 199 +#define FRAME_painc2 200 +#define FRAME_painc3 201 +#define FRAME_painc4 202 +#define FRAME_painb1 203 +#define FRAME_painb2 204 +#define FRAME_painb3 205 +#define FRAME_painb4 206 +#define FRAME_painb5 207 +#define FRAME_painb6 208 +#define FRAME_painb7 209 +#define FRAME_painb8 210 +#define FRAME_painb9 211 +#define FRAME_painb10 212 +#define FRAME_painb11 213 +#define FRAME_painb12 214 +#define FRAME_painb13 215 +#define FRAME_painb14 216 +#define FRAME_painb15 217 +#define FRAME_painb16 218 +#define FRAME_painb17 219 +#define FRAME_painb18 220 +#define FRAME_painb19 221 +#define FRAME_painb20 222 +#define FRAME_death1 223 +#define FRAME_death2 224 +#define FRAME_death3 225 +#define FRAME_death4 226 +#define FRAME_death5 227 +#define FRAME_death6 228 +#define FRAME_death7 229 +#define FRAME_death8 230 +#define FRAME_death9 231 +#define FRAME_death10 232 +#define FRAME_death11 233 +#define FRAME_death12 234 +#define FRAME_death13 235 +#define FRAME_deathc1 236 +#define FRAME_deathc2 237 +#define FRAME_deathc3 238 +#define FRAME_deathc4 239 +#define FRAME_deathc5 240 +#define FRAME_deathc6 241 +#define FRAME_deathc7 242 +#define FRAME_deathc8 243 + +#define MODEL_SCALE 1.000000 diff --git a/original/baseq2/m_boss2.c b/original/baseq2/m_boss2.c new file mode 100644 index 0000000..3b64884 --- /dev/null +++ b/original/baseq2/m_boss2.c @@ -0,0 +1,679 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +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. + +*/ +/* +============================================================================== + +boss2 + +============================================================================== +*/ + +#include "g_local.h" +#include "m_boss2.h" + +void BossExplode (edict_t *self); + +qboolean infront (edict_t *self, edict_t *other); + +static int sound_pain1; +static int sound_pain2; +static int sound_pain3; +static int sound_death; +static int sound_search1; + +void boss2_search (edict_t *self) +{ + if (random() < 0.5) + gi.sound (self, CHAN_VOICE, sound_search1, 1, ATTN_NONE, 0); +} + +void boss2_run (edict_t *self); +void boss2_stand (edict_t *self); +void boss2_dead (edict_t *self); +void boss2_attack (edict_t *self); +void boss2_attack_mg (edict_t *self); +void boss2_reattack_mg (edict_t *self); +void boss2_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point); + +void Boss2Rocket (edict_t *self) +{ + vec3_t forward, right; + vec3_t start; + vec3_t dir; + vec3_t vec; + + AngleVectors (self->s.angles, forward, right, NULL); + +//1 + G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_BOSS2_ROCKET_1], forward, right, start); + VectorCopy (self->enemy->s.origin, vec); + vec[2] += self->enemy->viewheight; + VectorSubtract (vec, start, dir); + VectorNormalize (dir); + monster_fire_rocket (self, start, dir, 50, 500, MZ2_BOSS2_ROCKET_1); + +//2 + G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_BOSS2_ROCKET_2], forward, right, start); + VectorCopy (self->enemy->s.origin, vec); + vec[2] += self->enemy->viewheight; + VectorSubtract (vec, start, dir); + VectorNormalize (dir); + monster_fire_rocket (self, start, dir, 50, 500, MZ2_BOSS2_ROCKET_2); + +//3 + G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_BOSS2_ROCKET_3], forward, right, start); + VectorCopy (self->enemy->s.origin, vec); + vec[2] += self->enemy->viewheight; + VectorSubtract (vec, start, dir); + VectorNormalize (dir); + monster_fire_rocket (self, start, dir, 50, 500, MZ2_BOSS2_ROCKET_3); + +//4 + G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_BOSS2_ROCKET_4], forward, right, start); + VectorCopy (self->enemy->s.origin, vec); + vec[2] += self->enemy->viewheight; + VectorSubtract (vec, start, dir); + VectorNormalize (dir); + monster_fire_rocket (self, start, dir, 50, 500, MZ2_BOSS2_ROCKET_4); +} + +void boss2_firebullet_right (edict_t *self) +{ + vec3_t forward, right, target; + vec3_t start; + + AngleVectors (self->s.angles, forward, right, NULL); + G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_BOSS2_MACHINEGUN_R1], forward, right, start); + + VectorMA (self->enemy->s.origin, -0.2, self->enemy->velocity, target); + target[2] += self->enemy->viewheight; + VectorSubtract (target, start, forward); + VectorNormalize (forward); + + monster_fire_bullet (self, start, forward, 6, 4, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, MZ2_BOSS2_MACHINEGUN_R1); +} + +void boss2_firebullet_left (edict_t *self) +{ + vec3_t forward, right, target; + vec3_t start; + + AngleVectors (self->s.angles, forward, right, NULL); + G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_BOSS2_MACHINEGUN_L1], forward, right, start); + + VectorMA (self->enemy->s.origin, -0.2, self->enemy->velocity, target); + + target[2] += self->enemy->viewheight; + VectorSubtract (target, start, forward); + VectorNormalize (forward); + + monster_fire_bullet (self, start, forward, 6, 4, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, MZ2_BOSS2_MACHINEGUN_L1); +} + +void Boss2MachineGun (edict_t *self) +{ +/* vec3_t forward, right; + vec3_t start; + vec3_t dir; + vec3_t vec; + int flash_number; + + AngleVectors (self->s.angles, forward, right, NULL); + + flash_number = MZ2_BOSS2_MACHINEGUN_1 + (self->s.frame - FRAME_attack10); + G_ProjectSource (self->s.origin, monster_flash_offset[flash_number], forward, right, start); + + VectorCopy (self->enemy->s.origin, vec); + vec[2] += self->enemy->viewheight; + VectorSubtract (vec, start, dir); + VectorNormalize (dir); + monster_fire_bullet (self, start, dir, 3, 4, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, flash_number); +*/ + boss2_firebullet_left(self); + boss2_firebullet_right(self); +} + + +mframe_t boss2_frames_stand [] = +{ + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL +}; +mmove_t boss2_move_stand = {FRAME_stand30, FRAME_stand50, boss2_frames_stand, NULL}; + +mframe_t boss2_frames_fidget [] = +{ + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL +}; +mmove_t boss2_move_fidget = {FRAME_stand1, FRAME_stand30, boss2_frames_fidget, NULL}; + +mframe_t boss2_frames_walk [] = +{ + ai_walk, 8, NULL, + ai_walk, 8, NULL, + ai_walk, 8, NULL, + ai_walk, 8, NULL, + ai_walk, 8, NULL, + ai_walk, 8, NULL, + ai_walk, 8, NULL, + ai_walk, 8, NULL, + ai_walk, 8, NULL, + ai_walk, 8, NULL, + ai_walk, 8, NULL, + ai_walk, 8, NULL, + ai_walk, 8, NULL, + ai_walk, 8, NULL, + ai_walk, 8, NULL, + ai_walk, 8, NULL, + ai_walk, 8, NULL, + ai_walk, 8, NULL, + ai_walk, 8, NULL, + ai_walk, 8, NULL +}; +mmove_t boss2_move_walk = {FRAME_walk1, FRAME_walk20, boss2_frames_walk, NULL}; + + +mframe_t boss2_frames_run [] = +{ + ai_run, 8, NULL, + ai_run, 8, NULL, + ai_run, 8, NULL, + ai_run, 8, NULL, + ai_run, 8, NULL, + ai_run, 8, NULL, + ai_run, 8, NULL, + ai_run, 8, NULL, + ai_run, 8, NULL, + ai_run, 8, NULL, + ai_run, 8, NULL, + ai_run, 8, NULL, + ai_run, 8, NULL, + ai_run, 8, NULL, + ai_run, 8, NULL, + ai_run, 8, NULL, + ai_run, 8, NULL, + ai_run, 8, NULL, + ai_run, 8, NULL, + ai_run, 8, NULL +}; +mmove_t boss2_move_run = {FRAME_walk1, FRAME_walk20, boss2_frames_run, NULL}; + +mframe_t boss2_frames_attack_pre_mg [] = +{ + ai_charge, 1, NULL, + ai_charge, 1, NULL, + ai_charge, 1, NULL, + ai_charge, 1, NULL, + ai_charge, 1, NULL, + ai_charge, 1, NULL, + ai_charge, 1, NULL, + ai_charge, 1, NULL, + ai_charge, 1, boss2_attack_mg +}; +mmove_t boss2_move_attack_pre_mg = {FRAME_attack1, FRAME_attack9, boss2_frames_attack_pre_mg, NULL}; + + +// Loop this +mframe_t boss2_frames_attack_mg [] = +{ + ai_charge, 1, Boss2MachineGun, + ai_charge, 1, Boss2MachineGun, + ai_charge, 1, Boss2MachineGun, + ai_charge, 1, Boss2MachineGun, + ai_charge, 1, Boss2MachineGun, + ai_charge, 1, boss2_reattack_mg +}; +mmove_t boss2_move_attack_mg = {FRAME_attack10, FRAME_attack15, boss2_frames_attack_mg, NULL}; + +mframe_t boss2_frames_attack_post_mg [] = +{ + ai_charge, 1, NULL, + ai_charge, 1, NULL, + ai_charge, 1, NULL, + ai_charge, 1, NULL +}; +mmove_t boss2_move_attack_post_mg = {FRAME_attack16, FRAME_attack19, boss2_frames_attack_post_mg, boss2_run}; + +mframe_t boss2_frames_attack_rocket [] = +{ + ai_charge, 1, NULL, + ai_charge, 1, NULL, + ai_charge, 1, NULL, + ai_charge, 1, NULL, + ai_charge, 1, NULL, + ai_charge, 1, NULL, + ai_charge, 1, NULL, + ai_charge, 1, NULL, + ai_charge, 1, NULL, + ai_charge, 1, NULL, + ai_charge, 1, NULL, + ai_charge, 1, NULL, + ai_move, -20, Boss2Rocket, + ai_charge, 1, NULL, + ai_charge, 1, NULL, + ai_charge, 1, NULL, + ai_charge, 1, NULL, + ai_charge, 1, NULL, + ai_charge, 1, NULL, + ai_charge, 1, NULL, + ai_charge, 1, NULL +}; +mmove_t boss2_move_attack_rocket = {FRAME_attack20, FRAME_attack40, boss2_frames_attack_rocket, boss2_run}; + +mframe_t boss2_frames_pain_heavy [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t boss2_move_pain_heavy = {FRAME_pain2, FRAME_pain19, boss2_frames_pain_heavy, boss2_run}; + +mframe_t boss2_frames_pain_light [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t boss2_move_pain_light = {FRAME_pain20, FRAME_pain23, boss2_frames_pain_light, boss2_run}; + +mframe_t boss2_frames_death [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, BossExplode +}; +mmove_t boss2_move_death = {FRAME_death2, FRAME_death50, boss2_frames_death, boss2_dead}; + +void boss2_stand (edict_t *self) +{ + self->monsterinfo.currentmove = &boss2_move_stand; +} + +void boss2_run (edict_t *self) +{ + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + self->monsterinfo.currentmove = &boss2_move_stand; + else + self->monsterinfo.currentmove = &boss2_move_run; +} + +void boss2_walk (edict_t *self) +{ + self->monsterinfo.currentmove = &boss2_move_walk; +} + +void boss2_attack (edict_t *self) +{ + vec3_t vec; + float range; + + VectorSubtract (self->enemy->s.origin, self->s.origin, vec); + range = VectorLength (vec); + + if (range <= 125) + { + self->monsterinfo.currentmove = &boss2_move_attack_pre_mg; + } + else + { + if (random() <= 0.6) + self->monsterinfo.currentmove = &boss2_move_attack_pre_mg; + else + self->monsterinfo.currentmove = &boss2_move_attack_rocket; + } +} + +void boss2_attack_mg (edict_t *self) +{ + self->monsterinfo.currentmove = &boss2_move_attack_mg; +} + +void boss2_reattack_mg (edict_t *self) +{ + if ( infront(self, self->enemy) ) + if (random() <= 0.7) + self->monsterinfo.currentmove = &boss2_move_attack_mg; + else + self->monsterinfo.currentmove = &boss2_move_attack_post_mg; + else + self->monsterinfo.currentmove = &boss2_move_attack_post_mg; +} + + +void boss2_pain (edict_t *self, edict_t *other, float kick, int damage) +{ + if (self->health < (self->max_health / 2)) + self->s.skinnum = 1; + + if (level.time < self->pain_debounce_time) + return; + + self->pain_debounce_time = level.time + 3; +// American wanted these at no attenuation + if (damage < 10) + { + gi.sound (self, CHAN_VOICE, sound_pain3, 1, ATTN_NONE, 0); + self->monsterinfo.currentmove = &boss2_move_pain_light; + } + else if (damage < 30) + { + gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NONE, 0); + self->monsterinfo.currentmove = &boss2_move_pain_light; + } + else + { + gi.sound (self, CHAN_VOICE, sound_pain2, 1, ATTN_NONE, 0); + self->monsterinfo.currentmove = &boss2_move_pain_heavy; + } +} + +void boss2_dead (edict_t *self) +{ + VectorSet (self->mins, -56, -56, 0); + VectorSet (self->maxs, 56, 56, 80); + self->movetype = MOVETYPE_TOSS; + self->svflags |= SVF_DEADMONSTER; + self->nextthink = 0; + gi.linkentity (self); +} + +void boss2_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + gi.sound (self, CHAN_VOICE, sound_death, 1, ATTN_NONE, 0); + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_NO; + self->count = 0; + self->monsterinfo.currentmove = &boss2_move_death; +#if 0 + int n; + + self->s.sound = 0; + // check for gib + if (self->health <= self->gib_health) + { + gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0); + for (n= 0; n < 2; n++) + ThrowGib (self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC); + for (n= 0; n < 4; n++) + ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); + ThrowHead (self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC); + self->deadflag = DEAD_DEAD; + return; + } + + if (self->deadflag == DEAD_DEAD) + return; + + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_YES; + self->monsterinfo.currentmove = &boss2_move_death; +#endif +} + +qboolean Boss2_CheckAttack (edict_t *self) +{ + vec3_t spot1, spot2; + vec3_t temp; + float chance; + trace_t tr; + qboolean enemy_infront; + int enemy_range; + float enemy_yaw; + + if (self->enemy->health > 0) + { + // see if any entities are in the way of the shot + VectorCopy (self->s.origin, spot1); + spot1[2] += self->viewheight; + VectorCopy (self->enemy->s.origin, spot2); + spot2[2] += self->enemy->viewheight; + + tr = gi.trace (spot1, NULL, NULL, spot2, self, CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_SLIME|CONTENTS_LAVA); + + // do we have a clear shot? + if (tr.ent != self->enemy) + return false; + } + + enemy_infront = infront(self, self->enemy); + enemy_range = range(self, self->enemy); + VectorSubtract (self->enemy->s.origin, self->s.origin, temp); + enemy_yaw = vectoyaw(temp); + + self->ideal_yaw = enemy_yaw; + + + // melee attack + if (enemy_range == RANGE_MELEE) + { + if (self->monsterinfo.melee) + self->monsterinfo.attack_state = AS_MELEE; + else + self->monsterinfo.attack_state = AS_MISSILE; + return true; + } + +// missile attack + if (!self->monsterinfo.attack) + return false; + + if (level.time < self->monsterinfo.attack_finished) + return false; + + if (enemy_range == RANGE_FAR) + return false; + + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + { + chance = 0.4; + } + else if (enemy_range == RANGE_MELEE) + { + chance = 0.8; + } + else if (enemy_range == RANGE_NEAR) + { + chance = 0.8; + } + else if (enemy_range == RANGE_MID) + { + chance = 0.8; + } + else + { + return false; + } + + if (random () < chance) + { + self->monsterinfo.attack_state = AS_MISSILE; + self->monsterinfo.attack_finished = level.time + 2*random(); + return true; + } + + if (self->flags & FL_FLY) + { + if (random() < 0.3) + self->monsterinfo.attack_state = AS_SLIDING; + else + self->monsterinfo.attack_state = AS_STRAIGHT; + } + + return false; +} + + + +/*QUAKED monster_boss2 (1 .5 0) (-56 -56 0) (56 56 80) Ambush Trigger_Spawn Sight +*/ +void SP_monster_boss2 (edict_t *self) +{ + if (deathmatch->value) + { + G_FreeEdict (self); + return; + } + + sound_pain1 = gi.soundindex ("bosshovr/bhvpain1.wav"); + sound_pain2 = gi.soundindex ("bosshovr/bhvpain2.wav"); + sound_pain3 = gi.soundindex ("bosshovr/bhvpain3.wav"); + sound_death = gi.soundindex ("bosshovr/bhvdeth1.wav"); + sound_search1 = gi.soundindex ("bosshovr/bhvunqv1.wav"); + + self->s.sound = gi.soundindex ("bosshovr/bhvengn1.wav"); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex ("models/monsters/boss2/tris.md2"); + VectorSet (self->mins, -56, -56, 0); + VectorSet (self->maxs, 56, 56, 80); + + self->health = 2000; + self->gib_health = -200; + self->mass = 1000; + + self->flags |= FL_IMMUNE_LASER; + + self->pain = boss2_pain; + self->die = boss2_die; + + self->monsterinfo.stand = boss2_stand; + self->monsterinfo.walk = boss2_walk; + self->monsterinfo.run = boss2_run; + self->monsterinfo.attack = boss2_attack; + self->monsterinfo.search = boss2_search; + self->monsterinfo.checkattack = Boss2_CheckAttack; + gi.linkentity (self); + + self->monsterinfo.currentmove = &boss2_move_stand; + self->monsterinfo.scale = MODEL_SCALE; + + flymonster_start (self); +} diff --git a/original/baseq2/m_boss2.h b/original/baseq2/m_boss2.h new file mode 100644 index 0000000..05b0a08 --- /dev/null +++ b/original/baseq2/m_boss2.h @@ -0,0 +1,206 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +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. + +*/ +// G:\quake2\baseq2\models/monsters/boss2 + +// This file generated by ModelGen - Do NOT Modify + +#define FRAME_stand30 0 +#define FRAME_stand31 1 +#define FRAME_stand32 2 +#define FRAME_stand33 3 +#define FRAME_stand34 4 +#define FRAME_stand35 5 +#define FRAME_stand36 6 +#define FRAME_stand37 7 +#define FRAME_stand38 8 +#define FRAME_stand39 9 +#define FRAME_stand40 10 +#define FRAME_stand41 11 +#define FRAME_stand42 12 +#define FRAME_stand43 13 +#define FRAME_stand44 14 +#define FRAME_stand45 15 +#define FRAME_stand46 16 +#define FRAME_stand47 17 +#define FRAME_stand48 18 +#define FRAME_stand49 19 +#define FRAME_stand50 20 +#define FRAME_stand1 21 +#define FRAME_stand2 22 +#define FRAME_stand3 23 +#define FRAME_stand4 24 +#define FRAME_stand5 25 +#define FRAME_stand6 26 +#define FRAME_stand7 27 +#define FRAME_stand8 28 +#define FRAME_stand9 29 +#define FRAME_stand10 30 +#define FRAME_stand11 31 +#define FRAME_stand12 32 +#define FRAME_stand13 33 +#define FRAME_stand14 34 +#define FRAME_stand15 35 +#define FRAME_stand16 36 +#define FRAME_stand17 37 +#define FRAME_stand18 38 +#define FRAME_stand19 39 +#define FRAME_stand20 40 +#define FRAME_stand21 41 +#define FRAME_stand22 42 +#define FRAME_stand23 43 +#define FRAME_stand24 44 +#define FRAME_stand25 45 +#define FRAME_stand26 46 +#define FRAME_stand27 47 +#define FRAME_stand28 48 +#define FRAME_stand29 49 +#define FRAME_walk1 50 +#define FRAME_walk2 51 +#define FRAME_walk3 52 +#define FRAME_walk4 53 +#define FRAME_walk5 54 +#define FRAME_walk6 55 +#define FRAME_walk7 56 +#define FRAME_walk8 57 +#define FRAME_walk9 58 +#define FRAME_walk10 59 +#define FRAME_walk11 60 +#define FRAME_walk12 61 +#define FRAME_walk13 62 +#define FRAME_walk14 63 +#define FRAME_walk15 64 +#define FRAME_walk16 65 +#define FRAME_walk17 66 +#define FRAME_walk18 67 +#define FRAME_walk19 68 +#define FRAME_walk20 69 +#define FRAME_attack1 70 +#define FRAME_attack2 71 +#define FRAME_attack3 72 +#define FRAME_attack4 73 +#define FRAME_attack5 74 +#define FRAME_attack6 75 +#define FRAME_attack7 76 +#define FRAME_attack8 77 +#define FRAME_attack9 78 +#define FRAME_attack10 79 +#define FRAME_attack11 80 +#define FRAME_attack12 81 +#define FRAME_attack13 82 +#define FRAME_attack14 83 +#define FRAME_attack15 84 +#define FRAME_attack16 85 +#define FRAME_attack17 86 +#define FRAME_attack18 87 +#define FRAME_attack19 88 +#define FRAME_attack20 89 +#define FRAME_attack21 90 +#define FRAME_attack22 91 +#define FRAME_attack23 92 +#define FRAME_attack24 93 +#define FRAME_attack25 94 +#define FRAME_attack26 95 +#define FRAME_attack27 96 +#define FRAME_attack28 97 +#define FRAME_attack29 98 +#define FRAME_attack30 99 +#define FRAME_attack31 100 +#define FRAME_attack32 101 +#define FRAME_attack33 102 +#define FRAME_attack34 103 +#define FRAME_attack35 104 +#define FRAME_attack36 105 +#define FRAME_attack37 106 +#define FRAME_attack38 107 +#define FRAME_attack39 108 +#define FRAME_attack40 109 +#define FRAME_pain2 110 +#define FRAME_pain3 111 +#define FRAME_pain4 112 +#define FRAME_pain5 113 +#define FRAME_pain6 114 +#define FRAME_pain7 115 +#define FRAME_pain8 116 +#define FRAME_pain9 117 +#define FRAME_pain10 118 +#define FRAME_pain11 119 +#define FRAME_pain12 120 +#define FRAME_pain13 121 +#define FRAME_pain14 122 +#define FRAME_pain15 123 +#define FRAME_pain16 124 +#define FRAME_pain17 125 +#define FRAME_pain18 126 +#define FRAME_pain19 127 +#define FRAME_pain20 128 +#define FRAME_pain21 129 +#define FRAME_pain22 130 +#define FRAME_pain23 131 +#define FRAME_death2 132 +#define FRAME_death3 133 +#define FRAME_death4 134 +#define FRAME_death5 135 +#define FRAME_death6 136 +#define FRAME_death7 137 +#define FRAME_death8 138 +#define FRAME_death9 139 +#define FRAME_death10 140 +#define FRAME_death11 141 +#define FRAME_death12 142 +#define FRAME_death13 143 +#define FRAME_death14 144 +#define FRAME_death15 145 +#define FRAME_death16 146 +#define FRAME_death17 147 +#define FRAME_death18 148 +#define FRAME_death19 149 +#define FRAME_death20 150 +#define FRAME_death21 151 +#define FRAME_death22 152 +#define FRAME_death23 153 +#define FRAME_death24 154 +#define FRAME_death25 155 +#define FRAME_death26 156 +#define FRAME_death27 157 +#define FRAME_death28 158 +#define FRAME_death29 159 +#define FRAME_death30 160 +#define FRAME_death31 161 +#define FRAME_death32 162 +#define FRAME_death33 163 +#define FRAME_death34 164 +#define FRAME_death35 165 +#define FRAME_death36 166 +#define FRAME_death37 167 +#define FRAME_death38 168 +#define FRAME_death39 169 +#define FRAME_death40 170 +#define FRAME_death41 171 +#define FRAME_death42 172 +#define FRAME_death43 173 +#define FRAME_death44 174 +#define FRAME_death45 175 +#define FRAME_death46 176 +#define FRAME_death47 177 +#define FRAME_death48 178 +#define FRAME_death49 179 +#define FRAME_death50 180 + +#define MODEL_SCALE 1.000000 diff --git a/original/baseq2/m_boss3.c b/original/baseq2/m_boss3.c new file mode 100644 index 0000000..d1f11df --- /dev/null +++ b/original/baseq2/m_boss3.c @@ -0,0 +1,76 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +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. + +*/ +/* +============================================================================== + +boss3 + +============================================================================== +*/ + +#include "g_local.h" +#include "m_boss32.h" + +void Use_Boss3 (edict_t *ent, edict_t *other, edict_t *activator) +{ + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_BOSSTPORT); + gi.WritePosition (ent->s.origin); + gi.multicast (ent->s.origin, MULTICAST_PVS); + G_FreeEdict (ent); +} + +void Think_Boss3Stand (edict_t *ent) +{ + if (ent->s.frame == FRAME_stand260) + ent->s.frame = FRAME_stand201; + else + ent->s.frame++; + ent->nextthink = level.time + FRAMETIME; +} + +/*QUAKED monster_boss3_stand (1 .5 0) (-32 -32 0) (32 32 90) + +Just stands and cycles in one place until targeted, then teleports away. +*/ +void SP_monster_boss3_stand (edict_t *self) +{ + if (deathmatch->value) + { + G_FreeEdict (self); + return; + } + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->model = "models/monsters/boss3/rider/tris.md2"; + self->s.modelindex = gi.modelindex (self->model); + self->s.frame = FRAME_stand201; + + gi.soundindex ("misc/bigtele.wav"); + + VectorSet (self->mins, -32, -32, 0); + VectorSet (self->maxs, 32, 32, 90); + + self->use = Use_Boss3; + self->think = Think_Boss3Stand; + self->nextthink = level.time + FRAMETIME; + gi.linkentity (self); +} diff --git a/original/baseq2/m_boss31.c b/original/baseq2/m_boss31.c new file mode 100644 index 0000000..7c91d99 --- /dev/null +++ b/original/baseq2/m_boss31.c @@ -0,0 +1,749 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +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. + +*/ +/* +============================================================================== + +jorg + +============================================================================== +*/ + +#include "g_local.h" +#include "m_boss31.h" + +extern SP_monster_makron (edict_t *self); +qboolean visible (edict_t *self, edict_t *other); + +static int sound_pain1; +static int sound_pain2; +static int sound_pain3; +static int sound_idle; +static int sound_death; +static int sound_search1; +static int sound_search2; +static int sound_search3; +static int sound_attack1; +static int sound_attack2; +static int sound_firegun; +static int sound_step_left; +static int sound_step_right; +static int sound_death_hit; + +void BossExplode (edict_t *self); +void MakronToss (edict_t *self); + + +void jorg_search (edict_t *self) +{ + float r; + + r = random(); + + if (r <= 0.3) + gi.sound (self, CHAN_VOICE, sound_search1, 1, ATTN_NORM, 0); + else if (r <= 0.6) + gi.sound (self, CHAN_VOICE, sound_search2, 1, ATTN_NORM, 0); + else + gi.sound (self, CHAN_VOICE, sound_search3, 1, ATTN_NORM, 0); +} + + +void jorg_dead (edict_t *self); +void jorgBFG (edict_t *self); +void jorgMachineGun (edict_t *self); +void jorg_firebullet (edict_t *self); +void jorg_reattack1(edict_t *self); +void jorg_attack1(edict_t *self); +void jorg_idle(edict_t *self); +void jorg_step_left(edict_t *self); +void jorg_step_right(edict_t *self); +void jorg_death_hit(edict_t *self); + +// +// stand +// + +mframe_t jorg_frames_stand []= +{ + ai_stand, 0, jorg_idle, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, // 10 + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, // 20 + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, // 30 + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 19, NULL, + ai_stand, 11, jorg_step_left, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 6, NULL, + ai_stand, 9, jorg_step_right, + ai_stand, 0, NULL, // 40 + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, -2, NULL, + ai_stand, -17, jorg_step_left, + ai_stand, 0, NULL, + ai_stand, -12, NULL, // 50 + ai_stand, -14, jorg_step_right // 51 +}; +mmove_t jorg_move_stand = {FRAME_stand01, FRAME_stand51, jorg_frames_stand, NULL}; + +void jorg_idle (edict_t *self) +{ + gi.sound (self, CHAN_VOICE, sound_idle, 1, ATTN_NORM,0); +} + +void jorg_death_hit (edict_t *self) +{ + gi.sound (self, CHAN_BODY, sound_death_hit, 1, ATTN_NORM,0); +} + + +void jorg_step_left (edict_t *self) +{ + gi.sound (self, CHAN_BODY, sound_step_left, 1, ATTN_NORM,0); +} + +void jorg_step_right (edict_t *self) +{ + gi.sound (self, CHAN_BODY, sound_step_right, 1, ATTN_NORM,0); +} + + +void jorg_stand (edict_t *self) +{ + self->monsterinfo.currentmove = &jorg_move_stand; +} + +mframe_t jorg_frames_run [] = +{ + ai_run, 17, jorg_step_left, + ai_run, 0, NULL, + ai_run, 0, NULL, + ai_run, 0, NULL, + ai_run, 12, NULL, + ai_run, 8, NULL, + ai_run, 10, NULL, + ai_run, 33, jorg_step_right, + ai_run, 0, NULL, + ai_run, 0, NULL, + ai_run, 0, NULL, + ai_run, 9, NULL, + ai_run, 9, NULL, + ai_run, 9, NULL +}; +mmove_t jorg_move_run = {FRAME_walk06, FRAME_walk19, jorg_frames_run, NULL}; + +// +// walk +// + +mframe_t jorg_frames_start_walk [] = +{ + ai_walk, 5, NULL, + ai_walk, 6, NULL, + ai_walk, 7, NULL, + ai_walk, 9, NULL, + ai_walk, 15, NULL +}; +mmove_t jorg_move_start_walk = {FRAME_walk01, FRAME_walk05, jorg_frames_start_walk, NULL}; + +mframe_t jorg_frames_walk [] = +{ + ai_walk, 17, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 12, NULL, + ai_walk, 8, NULL, + ai_walk, 10, NULL, + ai_walk, 33, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 9, NULL, + ai_walk, 9, NULL, + ai_walk, 9, NULL +}; +mmove_t jorg_move_walk = {FRAME_walk06, FRAME_walk19, jorg_frames_walk, NULL}; + +mframe_t jorg_frames_end_walk [] = +{ + ai_walk, 11, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 8, NULL, + ai_walk, -8, NULL +}; +mmove_t jorg_move_end_walk = {FRAME_walk20, FRAME_walk25, jorg_frames_end_walk, NULL}; + +void jorg_walk (edict_t *self) +{ + self->monsterinfo.currentmove = &jorg_move_walk; +} + +void jorg_run (edict_t *self) +{ + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + self->monsterinfo.currentmove = &jorg_move_stand; + else + self->monsterinfo.currentmove = &jorg_move_run; +} + +mframe_t jorg_frames_pain3 [] = +{ + ai_move, -28, NULL, + ai_move, -6, NULL, + ai_move, -3, jorg_step_left, + ai_move, -9, NULL, + ai_move, 0, jorg_step_right, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, -7, NULL, + ai_move, 1, NULL, + ai_move, -11, NULL, + ai_move, -4, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 10, NULL, + ai_move, 11, NULL, + ai_move, 0, NULL, + ai_move, 10, NULL, + ai_move, 3, NULL, + ai_move, 10, NULL, + ai_move, 7, jorg_step_left, + ai_move, 17, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, jorg_step_right +}; +mmove_t jorg_move_pain3 = {FRAME_pain301, FRAME_pain325, jorg_frames_pain3, jorg_run}; + +mframe_t jorg_frames_pain2 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t jorg_move_pain2 = {FRAME_pain201, FRAME_pain203, jorg_frames_pain2, jorg_run}; + +mframe_t jorg_frames_pain1 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t jorg_move_pain1 = {FRAME_pain101, FRAME_pain103, jorg_frames_pain1, jorg_run}; + +mframe_t jorg_frames_death1 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, // 10 + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, // 20 + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, // 30 + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, // 40 + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, MakronToss, + ai_move, 0, BossExplode // 50 +}; +mmove_t jorg_move_death = {FRAME_death01, FRAME_death50, jorg_frames_death1, jorg_dead}; + +mframe_t jorg_frames_attack2 []= +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, jorgBFG, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t jorg_move_attack2 = {FRAME_attak201, FRAME_attak213, jorg_frames_attack2, jorg_run}; + +mframe_t jorg_frames_start_attack1 [] = +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL +}; +mmove_t jorg_move_start_attack1 = {FRAME_attak101, FRAME_attak108, jorg_frames_start_attack1, jorg_attack1}; + +mframe_t jorg_frames_attack1[]= +{ + ai_charge, 0, jorg_firebullet, + ai_charge, 0, jorg_firebullet, + ai_charge, 0, jorg_firebullet, + ai_charge, 0, jorg_firebullet, + ai_charge, 0, jorg_firebullet, + ai_charge, 0, jorg_firebullet +}; +mmove_t jorg_move_attack1 = {FRAME_attak109, FRAME_attak114, jorg_frames_attack1, jorg_reattack1}; + +mframe_t jorg_frames_end_attack1[]= +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t jorg_move_end_attack1 = {FRAME_attak115, FRAME_attak118, jorg_frames_end_attack1, jorg_run}; + +void jorg_reattack1(edict_t *self) +{ + if (visible(self, self->enemy)) + if (random() < 0.9) + self->monsterinfo.currentmove = &jorg_move_attack1; + else + { + self->s.sound = 0; + self->monsterinfo.currentmove = &jorg_move_end_attack1; + } + else + { + self->s.sound = 0; + self->monsterinfo.currentmove = &jorg_move_end_attack1; + } +} + +void jorg_attack1(edict_t *self) +{ + self->monsterinfo.currentmove = &jorg_move_attack1; +} + +void jorg_pain (edict_t *self, edict_t *other, float kick, int damage) +{ + + if (self->health < (self->max_health / 2)) + self->s.skinnum = 1; + + self->s.sound = 0; + + if (level.time < self->pain_debounce_time) + return; + + // Lessen the chance of him going into his pain frames if he takes little damage + if (damage <= 40) + if (random()<=0.6) + return; + + /* + If he's entering his attack1 or using attack1, lessen the chance of him + going into pain + */ + + if ( (self->s.frame >= FRAME_attak101) && (self->s.frame <= FRAME_attak108) ) + if (random() <= 0.005) + return; + + if ( (self->s.frame >= FRAME_attak109) && (self->s.frame <= FRAME_attak114) ) + if (random() <= 0.00005) + return; + + + if ( (self->s.frame >= FRAME_attak201) && (self->s.frame <= FRAME_attak208) ) + if (random() <= 0.005) + return; + + + self->pain_debounce_time = level.time + 3; + if (skill->value == 3) + return; // no pain anims in nightmare + + if (damage <= 50) + { + gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM,0); + self->monsterinfo.currentmove = &jorg_move_pain1; + } + else if (damage <= 100) + { + gi.sound (self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM,0); + self->monsterinfo.currentmove = &jorg_move_pain2; + } + else + { + if (random() <= 0.3) + { + gi.sound (self, CHAN_VOICE, sound_pain3, 1, ATTN_NORM,0); + self->monsterinfo.currentmove = &jorg_move_pain3; + } + } +}; + +void jorgBFG (edict_t *self) +{ + vec3_t forward, right; + vec3_t start; + vec3_t dir; + vec3_t vec; + + AngleVectors (self->s.angles, forward, right, NULL); + G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_JORG_BFG_1], forward, right, start); + + VectorCopy (self->enemy->s.origin, vec); + vec[2] += self->enemy->viewheight; + VectorSubtract (vec, start, dir); + VectorNormalize (dir); + gi.sound (self, CHAN_VOICE, sound_attack2, 1, ATTN_NORM, 0); + /*void monster_fire_bfg (edict_t *self, + vec3_t start, + vec3_t aimdir, + int damage, + int speed, + int kick, + float damage_radius, + int flashtype)*/ + monster_fire_bfg (self, start, dir, 50, 300, 100, 200, MZ2_JORG_BFG_1); +} + +void jorg_firebullet_right (edict_t *self) +{ + vec3_t forward, right, target; + vec3_t start; + + AngleVectors (self->s.angles, forward, right, NULL); + G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_JORG_MACHINEGUN_R1], forward, right, start); + + VectorMA (self->enemy->s.origin, -0.2, self->enemy->velocity, target); + target[2] += self->enemy->viewheight; + VectorSubtract (target, start, forward); + VectorNormalize (forward); + + monster_fire_bullet (self, start, forward, 6, 4, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, MZ2_JORG_MACHINEGUN_R1); +} + +void jorg_firebullet_left (edict_t *self) +{ + vec3_t forward, right, target; + vec3_t start; + + AngleVectors (self->s.angles, forward, right, NULL); + G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_JORG_MACHINEGUN_L1], forward, right, start); + + VectorMA (self->enemy->s.origin, -0.2, self->enemy->velocity, target); + target[2] += self->enemy->viewheight; + VectorSubtract (target, start, forward); + VectorNormalize (forward); + + monster_fire_bullet (self, start, forward, 6, 4, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, MZ2_JORG_MACHINEGUN_L1); +} + +void jorg_firebullet (edict_t *self) +{ + jorg_firebullet_left(self); + jorg_firebullet_right(self); +}; + +void jorg_attack(edict_t *self) +{ + vec3_t vec; + float range; + + VectorSubtract (self->enemy->s.origin, self->s.origin, vec); + range = VectorLength (vec); + + if (random() <= 0.75) + { + gi.sound (self, CHAN_VOICE, sound_attack1, 1, ATTN_NORM,0); + self->s.sound = gi.soundindex ("boss3/w_loop.wav"); + self->monsterinfo.currentmove = &jorg_move_start_attack1; + } + else + { + gi.sound (self, CHAN_VOICE, sound_attack2, 1, ATTN_NORM,0); + self->monsterinfo.currentmove = &jorg_move_attack2; + } +} + +void jorg_dead (edict_t *self) +{ +#if 0 + edict_t *tempent; + /* + VectorSet (self->mins, -16, -16, -24); + VectorSet (self->maxs, 16, 16, -8); + */ + + // Jorg is on modelindex2. Do not clear him. + VectorSet (self->mins, -60, -60, 0); + VectorSet (self->maxs, 60, 60, 72); + self->movetype = MOVETYPE_TOSS; + self->nextthink = 0; + gi.linkentity (self); + + tempent = G_Spawn(); + VectorCopy (self->s.origin, tempent->s.origin); + VectorCopy (self->s.angles, tempent->s.angles); + tempent->killtarget = self->killtarget; + tempent->target = self->target; + tempent->activator = self->enemy; + self->killtarget = 0; + self->target = 0; + SP_monster_makron (tempent); +#endif +} + + +void jorg_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + gi.sound (self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0); + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_NO; + self->s.sound = 0; + self->count = 0; + self->monsterinfo.currentmove = &jorg_move_death; +} + +qboolean Jorg_CheckAttack (edict_t *self) +{ + vec3_t spot1, spot2; + vec3_t temp; + float chance; + trace_t tr; + qboolean enemy_infront; + int enemy_range; + float enemy_yaw; + + if (self->enemy->health > 0) + { + // see if any entities are in the way of the shot + VectorCopy (self->s.origin, spot1); + spot1[2] += self->viewheight; + VectorCopy (self->enemy->s.origin, spot2); + spot2[2] += self->enemy->viewheight; + + tr = gi.trace (spot1, NULL, NULL, spot2, self, CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_SLIME|CONTENTS_LAVA); + + // do we have a clear shot? + if (tr.ent != self->enemy) + return false; + } + + enemy_infront = infront(self, self->enemy); + enemy_range = range(self, self->enemy); + VectorSubtract (self->enemy->s.origin, self->s.origin, temp); + enemy_yaw = vectoyaw(temp); + + self->ideal_yaw = enemy_yaw; + + + // melee attack + if (enemy_range == RANGE_MELEE) + { + if (self->monsterinfo.melee) + self->monsterinfo.attack_state = AS_MELEE; + else + self->monsterinfo.attack_state = AS_MISSILE; + return true; + } + +// missile attack + if (!self->monsterinfo.attack) + return false; + + if (level.time < self->monsterinfo.attack_finished) + return false; + + if (enemy_range == RANGE_FAR) + return false; + + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + { + chance = 0.4; + } + else if (enemy_range == RANGE_MELEE) + { + chance = 0.8; + } + else if (enemy_range == RANGE_NEAR) + { + chance = 0.4; + } + else if (enemy_range == RANGE_MID) + { + chance = 0.2; + } + else + { + return false; + } + + if (random () < chance) + { + self->monsterinfo.attack_state = AS_MISSILE; + self->monsterinfo.attack_finished = level.time + 2*random(); + return true; + } + + if (self->flags & FL_FLY) + { + if (random() < 0.3) + self->monsterinfo.attack_state = AS_SLIDING; + else + self->monsterinfo.attack_state = AS_STRAIGHT; + } + + return false; +} + + +void MakronPrecache (void); + +/*QUAKED monster_jorg (1 .5 0) (-80 -80 0) (90 90 140) Ambush Trigger_Spawn Sight +*/ +void SP_monster_jorg (edict_t *self) +{ + if (deathmatch->value) + { + G_FreeEdict (self); + return; + } + + sound_pain1 = gi.soundindex ("boss3/bs3pain1.wav"); + sound_pain2 = gi.soundindex ("boss3/bs3pain2.wav"); + sound_pain3 = gi.soundindex ("boss3/bs3pain3.wav"); + sound_death = gi.soundindex ("boss3/bs3deth1.wav"); + sound_attack1 = gi.soundindex ("boss3/bs3atck1.wav"); + sound_attack2 = gi.soundindex ("boss3/bs3atck2.wav"); + sound_search1 = gi.soundindex ("boss3/bs3srch1.wav"); + sound_search2 = gi.soundindex ("boss3/bs3srch2.wav"); + sound_search3 = gi.soundindex ("boss3/bs3srch3.wav"); + sound_idle = gi.soundindex ("boss3/bs3idle1.wav"); + sound_step_left = gi.soundindex ("boss3/step1.wav"); + sound_step_right = gi.soundindex ("boss3/step2.wav"); + sound_firegun = gi.soundindex ("boss3/xfire.wav"); + sound_death_hit = gi.soundindex ("boss3/d_hit.wav"); + + MakronPrecache (); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex ("models/monsters/boss3/rider/tris.md2"); + self->s.modelindex2 = gi.modelindex ("models/monsters/boss3/jorg/tris.md2"); + VectorSet (self->mins, -80, -80, 0); + VectorSet (self->maxs, 80, 80, 140); + + self->health = 3000; + self->gib_health = -2000; + self->mass = 1000; + + self->pain = jorg_pain; + self->die = jorg_die; + self->monsterinfo.stand = jorg_stand; + self->monsterinfo.walk = jorg_walk; + self->monsterinfo.run = jorg_run; + self->monsterinfo.dodge = NULL; + self->monsterinfo.attack = jorg_attack; + self->monsterinfo.search = jorg_search; + self->monsterinfo.melee = NULL; + self->monsterinfo.sight = NULL; + self->monsterinfo.checkattack = Jorg_CheckAttack; + gi.linkentity (self); + + self->monsterinfo.currentmove = &jorg_move_stand; + self->monsterinfo.scale = MODEL_SCALE; + + walkmonster_start(self); +} diff --git a/original/baseq2/m_boss31.h b/original/baseq2/m_boss31.h new file mode 100644 index 0000000..39f3af5 --- /dev/null +++ b/original/baseq2/m_boss31.h @@ -0,0 +1,213 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +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. + +*/ +// G:\quake2\baseq2\models/monsters/boss3/jorg + +// This file generated by ModelGen - Do NOT Modify + +#define FRAME_attak101 0 +#define FRAME_attak102 1 +#define FRAME_attak103 2 +#define FRAME_attak104 3 +#define FRAME_attak105 4 +#define FRAME_attak106 5 +#define FRAME_attak107 6 +#define FRAME_attak108 7 +#define FRAME_attak109 8 +#define FRAME_attak110 9 +#define FRAME_attak111 10 +#define FRAME_attak112 11 +#define FRAME_attak113 12 +#define FRAME_attak114 13 +#define FRAME_attak115 14 +#define FRAME_attak116 15 +#define FRAME_attak117 16 +#define FRAME_attak118 17 +#define FRAME_attak201 18 +#define FRAME_attak202 19 +#define FRAME_attak203 20 +#define FRAME_attak204 21 +#define FRAME_attak205 22 +#define FRAME_attak206 23 +#define FRAME_attak207 24 +#define FRAME_attak208 25 +#define FRAME_attak209 26 +#define FRAME_attak210 27 +#define FRAME_attak211 28 +#define FRAME_attak212 29 +#define FRAME_attak213 30 +#define FRAME_death01 31 +#define FRAME_death02 32 +#define FRAME_death03 33 +#define FRAME_death04 34 +#define FRAME_death05 35 +#define FRAME_death06 36 +#define FRAME_death07 37 +#define FRAME_death08 38 +#define FRAME_death09 39 +#define FRAME_death10 40 +#define FRAME_death11 41 +#define FRAME_death12 42 +#define FRAME_death13 43 +#define FRAME_death14 44 +#define FRAME_death15 45 +#define FRAME_death16 46 +#define FRAME_death17 47 +#define FRAME_death18 48 +#define FRAME_death19 49 +#define FRAME_death20 50 +#define FRAME_death21 51 +#define FRAME_death22 52 +#define FRAME_death23 53 +#define FRAME_death24 54 +#define FRAME_death25 55 +#define FRAME_death26 56 +#define FRAME_death27 57 +#define FRAME_death28 58 +#define FRAME_death29 59 +#define FRAME_death30 60 +#define FRAME_death31 61 +#define FRAME_death32 62 +#define FRAME_death33 63 +#define FRAME_death34 64 +#define FRAME_death35 65 +#define FRAME_death36 66 +#define FRAME_death37 67 +#define FRAME_death38 68 +#define FRAME_death39 69 +#define FRAME_death40 70 +#define FRAME_death41 71 +#define FRAME_death42 72 +#define FRAME_death43 73 +#define FRAME_death44 74 +#define FRAME_death45 75 +#define FRAME_death46 76 +#define FRAME_death47 77 +#define FRAME_death48 78 +#define FRAME_death49 79 +#define FRAME_death50 80 +#define FRAME_pain101 81 +#define FRAME_pain102 82 +#define FRAME_pain103 83 +#define FRAME_pain201 84 +#define FRAME_pain202 85 +#define FRAME_pain203 86 +#define FRAME_pain301 87 +#define FRAME_pain302 88 +#define FRAME_pain303 89 +#define FRAME_pain304 90 +#define FRAME_pain305 91 +#define FRAME_pain306 92 +#define FRAME_pain307 93 +#define FRAME_pain308 94 +#define FRAME_pain309 95 +#define FRAME_pain310 96 +#define FRAME_pain311 97 +#define FRAME_pain312 98 +#define FRAME_pain313 99 +#define FRAME_pain314 100 +#define FRAME_pain315 101 +#define FRAME_pain316 102 +#define FRAME_pain317 103 +#define FRAME_pain318 104 +#define FRAME_pain319 105 +#define FRAME_pain320 106 +#define FRAME_pain321 107 +#define FRAME_pain322 108 +#define FRAME_pain323 109 +#define FRAME_pain324 110 +#define FRAME_pain325 111 +#define FRAME_stand01 112 +#define FRAME_stand02 113 +#define FRAME_stand03 114 +#define FRAME_stand04 115 +#define FRAME_stand05 116 +#define FRAME_stand06 117 +#define FRAME_stand07 118 +#define FRAME_stand08 119 +#define FRAME_stand09 120 +#define FRAME_stand10 121 +#define FRAME_stand11 122 +#define FRAME_stand12 123 +#define FRAME_stand13 124 +#define FRAME_stand14 125 +#define FRAME_stand15 126 +#define FRAME_stand16 127 +#define FRAME_stand17 128 +#define FRAME_stand18 129 +#define FRAME_stand19 130 +#define FRAME_stand20 131 +#define FRAME_stand21 132 +#define FRAME_stand22 133 +#define FRAME_stand23 134 +#define FRAME_stand24 135 +#define FRAME_stand25 136 +#define FRAME_stand26 137 +#define FRAME_stand27 138 +#define FRAME_stand28 139 +#define FRAME_stand29 140 +#define FRAME_stand30 141 +#define FRAME_stand31 142 +#define FRAME_stand32 143 +#define FRAME_stand33 144 +#define FRAME_stand34 145 +#define FRAME_stand35 146 +#define FRAME_stand36 147 +#define FRAME_stand37 148 +#define FRAME_stand38 149 +#define FRAME_stand39 150 +#define FRAME_stand40 151 +#define FRAME_stand41 152 +#define FRAME_stand42 153 +#define FRAME_stand43 154 +#define FRAME_stand44 155 +#define FRAME_stand45 156 +#define FRAME_stand46 157 +#define FRAME_stand47 158 +#define FRAME_stand48 159 +#define FRAME_stand49 160 +#define FRAME_stand50 161 +#define FRAME_stand51 162 +#define FRAME_walk01 163 +#define FRAME_walk02 164 +#define FRAME_walk03 165 +#define FRAME_walk04 166 +#define FRAME_walk05 167 +#define FRAME_walk06 168 +#define FRAME_walk07 169 +#define FRAME_walk08 170 +#define FRAME_walk09 171 +#define FRAME_walk10 172 +#define FRAME_walk11 173 +#define FRAME_walk12 174 +#define FRAME_walk13 175 +#define FRAME_walk14 176 +#define FRAME_walk15 177 +#define FRAME_walk16 178 +#define FRAME_walk17 179 +#define FRAME_walk18 180 +#define FRAME_walk19 181 +#define FRAME_walk20 182 +#define FRAME_walk21 183 +#define FRAME_walk22 184 +#define FRAME_walk23 185 +#define FRAME_walk24 186 +#define FRAME_walk25 187 + +#define MODEL_SCALE 1.000000 diff --git a/original/baseq2/m_boss32.c b/original/baseq2/m_boss32.c new file mode 100644 index 0000000..c4c19de --- /dev/null +++ b/original/baseq2/m_boss32.c @@ -0,0 +1,913 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +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. + +*/ +/* +============================================================================== + +Makron -- Final Boss + +============================================================================== +*/ + +#include "g_local.h" +#include "m_boss32.h" + +qboolean visible (edict_t *self, edict_t *other); + +void MakronRailgun (edict_t *self); +void MakronSaveloc (edict_t *self); +void MakronHyperblaster (edict_t *self); +void makron_step_left (edict_t *self); +void makron_step_right (edict_t *self); +void makronBFG (edict_t *self); +void makron_dead (edict_t *self); + +static int sound_pain4; +static int sound_pain5; +static int sound_pain6; +static int sound_death; +static int sound_step_left; +static int sound_step_right; +static int sound_attack_bfg; +static int sound_brainsplorch; +static int sound_prerailgun; +static int sound_popup; +static int sound_taunt1; +static int sound_taunt2; +static int sound_taunt3; +static int sound_hit; + +void makron_taunt (edict_t *self) +{ + float r; + + r=random(); + if (r <= 0.3) + gi.sound (self, CHAN_AUTO, sound_taunt1, 1, ATTN_NONE, 0); + else if (r <= 0.6) + gi.sound (self, CHAN_AUTO, sound_taunt2, 1, ATTN_NONE, 0); + else + gi.sound (self, CHAN_AUTO, sound_taunt3, 1, ATTN_NONE, 0); +} + +// +// stand +// + +mframe_t makron_frames_stand []= +{ + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, // 10 + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, // 20 + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, // 30 + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, // 40 + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, // 50 + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL // 60 +}; +mmove_t makron_move_stand = {FRAME_stand201, FRAME_stand260, makron_frames_stand, NULL}; + +void makron_stand (edict_t *self) +{ + self->monsterinfo.currentmove = &makron_move_stand; +} + +mframe_t makron_frames_run [] = +{ + ai_run, 3, makron_step_left, + ai_run, 12, NULL, + ai_run, 8, NULL, + ai_run, 8, NULL, + ai_run, 8, makron_step_right, + ai_run, 6, NULL, + ai_run, 12, NULL, + ai_run, 9, NULL, + ai_run, 6, NULL, + ai_run, 12, NULL +}; +mmove_t makron_move_run = {FRAME_walk204, FRAME_walk213, makron_frames_run, NULL}; + +void makron_hit (edict_t *self) +{ + gi.sound (self, CHAN_AUTO, sound_hit, 1, ATTN_NONE,0); +} + +void makron_popup (edict_t *self) +{ + gi.sound (self, CHAN_BODY, sound_popup, 1, ATTN_NONE,0); +} + +void makron_step_left (edict_t *self) +{ + gi.sound (self, CHAN_BODY, sound_step_left, 1, ATTN_NORM,0); +} + +void makron_step_right (edict_t *self) +{ + gi.sound (self, CHAN_BODY, sound_step_right, 1, ATTN_NORM,0); +} + +void makron_brainsplorch (edict_t *self) +{ + gi.sound (self, CHAN_VOICE, sound_brainsplorch, 1, ATTN_NORM,0); +} + +void makron_prerailgun (edict_t *self) +{ + gi.sound (self, CHAN_WEAPON, sound_prerailgun, 1, ATTN_NORM,0); +} + + +mframe_t makron_frames_walk [] = +{ + ai_walk, 3, makron_step_left, + ai_walk, 12, NULL, + ai_walk, 8, NULL, + ai_walk, 8, NULL, + ai_walk, 8, makron_step_right, + ai_walk, 6, NULL, + ai_walk, 12, NULL, + ai_walk, 9, NULL, + ai_walk, 6, NULL, + ai_walk, 12, NULL +}; +mmove_t makron_move_walk = {FRAME_walk204, FRAME_walk213, makron_frames_run, NULL}; + +void makron_walk (edict_t *self) +{ + self->monsterinfo.currentmove = &makron_move_walk; +} + +void makron_run (edict_t *self) +{ + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + self->monsterinfo.currentmove = &makron_move_stand; + else + self->monsterinfo.currentmove = &makron_move_run; +} + +mframe_t makron_frames_pain6 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, // 10 + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, makron_popup, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, // 20 + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, makron_taunt, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t makron_move_pain6 = {FRAME_pain601, FRAME_pain627, makron_frames_pain6, makron_run}; + +mframe_t makron_frames_pain5 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t makron_move_pain5 = {FRAME_pain501, FRAME_pain504, makron_frames_pain5, makron_run}; + +mframe_t makron_frames_pain4 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t makron_move_pain4 = {FRAME_pain401, FRAME_pain404, makron_frames_pain4, makron_run}; + +mframe_t makron_frames_death2 [] = +{ + ai_move, -15, NULL, + ai_move, 3, NULL, + ai_move, -12, NULL, + ai_move, 0, makron_step_left, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, // 10 + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 11, NULL, + ai_move, 12, NULL, + ai_move, 11, makron_step_right, + ai_move, 0, NULL, + ai_move, 0, NULL, // 20 + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, // 30 + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 5, NULL, + ai_move, 7, NULL, + ai_move, 6, makron_step_left, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, -1, NULL, + ai_move, 2, NULL, // 40 + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, // 50 + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, -6, NULL, + ai_move, -4, NULL, + ai_move, -6, makron_step_right, + ai_move, -4, NULL, + ai_move, -4, makron_step_left, + ai_move, 0, NULL, + ai_move, 0, NULL, // 60 + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, -2, NULL, + ai_move, -5, NULL, + ai_move, -3, makron_step_right, + ai_move, -8, NULL, + ai_move, -3, makron_step_left, + ai_move, -7, NULL, + ai_move, -4, NULL, + ai_move, -4, makron_step_right, // 70 + ai_move, -6, NULL, + ai_move, -7, NULL, + ai_move, 0, makron_step_left, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, // 80 + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, -2, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 2, NULL, + ai_move, 0, NULL, // 90 + ai_move, 27, makron_hit, + ai_move, 26, NULL, + ai_move, 0, makron_brainsplorch, + ai_move, 0, NULL, + ai_move, 0, NULL // 95 +}; +mmove_t makron_move_death2 = {FRAME_death201, FRAME_death295, makron_frames_death2, makron_dead}; + +mframe_t makron_frames_death3 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t makron_move_death3 = {FRAME_death301, FRAME_death320, makron_frames_death3, NULL}; + +mframe_t makron_frames_sight [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t makron_move_sight= {FRAME_active01, FRAME_active13, makron_frames_sight, makron_run}; + +void makronBFG (edict_t *self) +{ + vec3_t forward, right; + vec3_t start; + vec3_t dir; + vec3_t vec; + + AngleVectors (self->s.angles, forward, right, NULL); + G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_MAKRON_BFG], forward, right, start); + + VectorCopy (self->enemy->s.origin, vec); + vec[2] += self->enemy->viewheight; + VectorSubtract (vec, start, dir); + VectorNormalize (dir); + gi.sound (self, CHAN_VOICE, sound_attack_bfg, 1, ATTN_NORM, 0); + monster_fire_bfg (self, start, dir, 50, 300, 100, 300, MZ2_MAKRON_BFG); +} + + +mframe_t makron_frames_attack3 []= +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, makronBFG, // FIXME: BFG Attack here + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t makron_move_attack3 = {FRAME_attak301, FRAME_attak308, makron_frames_attack3, makron_run}; + +mframe_t makron_frames_attack4[]= +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_move, 0, MakronHyperblaster, // fire + ai_move, 0, MakronHyperblaster, // fire + ai_move, 0, MakronHyperblaster, // fire + ai_move, 0, MakronHyperblaster, // fire + ai_move, 0, MakronHyperblaster, // fire + ai_move, 0, MakronHyperblaster, // fire + ai_move, 0, MakronHyperblaster, // fire + ai_move, 0, MakronHyperblaster, // fire + ai_move, 0, MakronHyperblaster, // fire + ai_move, 0, MakronHyperblaster, // fire + ai_move, 0, MakronHyperblaster, // fire + ai_move, 0, MakronHyperblaster, // fire + ai_move, 0, MakronHyperblaster, // fire + ai_move, 0, MakronHyperblaster, // fire + ai_move, 0, MakronHyperblaster, // fire + ai_move, 0, MakronHyperblaster, // fire + ai_move, 0, MakronHyperblaster, // fire + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t makron_move_attack4 = {FRAME_attak401, FRAME_attak426, makron_frames_attack4, makron_run}; + +mframe_t makron_frames_attack5[]= +{ + ai_charge, 0, makron_prerailgun, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, MakronSaveloc, + ai_move, 0, MakronRailgun, // Fire railgun + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t makron_move_attack5 = {FRAME_attak501, FRAME_attak516, makron_frames_attack5, makron_run}; + +void MakronSaveloc (edict_t *self) +{ + VectorCopy (self->enemy->s.origin, self->pos1); //save for aiming the shot + self->pos1[2] += self->enemy->viewheight; +}; + +// FIXME: He's not firing from the proper Z +void MakronRailgun (edict_t *self) +{ + vec3_t start; + vec3_t dir; + vec3_t forward, right; + + AngleVectors (self->s.angles, forward, right, NULL); + G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_MAKRON_RAILGUN_1], forward, right, start); + + // calc direction to where we targted + VectorSubtract (self->pos1, start, dir); + VectorNormalize (dir); + + monster_fire_railgun (self, start, dir, 50, 100, MZ2_MAKRON_RAILGUN_1); +} + +// FIXME: This is all wrong. He's not firing at the proper angles. +void MakronHyperblaster (edict_t *self) +{ + vec3_t dir; + vec3_t vec; + vec3_t start; + vec3_t forward, right; + int flash_number; + + flash_number = MZ2_MAKRON_BLASTER_1 + (self->s.frame - FRAME_attak405); + + AngleVectors (self->s.angles, forward, right, NULL); + G_ProjectSource (self->s.origin, monster_flash_offset[flash_number], forward, right, start); + + if (self->enemy) + { + VectorCopy (self->enemy->s.origin, vec); + vec[2] += self->enemy->viewheight; + VectorSubtract (vec, start, vec); + vectoangles (vec, vec); + dir[0] = vec[0]; + } + else + { + dir[0] = 0; + } + if (self->s.frame <= FRAME_attak413) + dir[1] = self->s.angles[1] - 10 * (self->s.frame - FRAME_attak413); + else + dir[1] = self->s.angles[1] + 10 * (self->s.frame - FRAME_attak421); + dir[2] = 0; + + AngleVectors (dir, forward, NULL, NULL); + + monster_fire_blaster (self, start, forward, 15, 1000, MZ2_MAKRON_BLASTER_1, EF_BLASTER); +} + + +void makron_pain (edict_t *self, edict_t *other, float kick, int damage) +{ + + if (self->health < (self->max_health / 2)) + self->s.skinnum = 1; + + if (level.time < self->pain_debounce_time) + return; + + // Lessen the chance of him going into his pain frames + if (damage <=25) + if (random()<0.2) + return; + + self->pain_debounce_time = level.time + 3; + if (skill->value == 3) + return; // no pain anims in nightmare + + + if (damage <= 40) + { + gi.sound (self, CHAN_VOICE, sound_pain4, 1, ATTN_NONE,0); + self->monsterinfo.currentmove = &makron_move_pain4; + } + else if (damage <= 110) + { + gi.sound (self, CHAN_VOICE, sound_pain5, 1, ATTN_NONE,0); + self->monsterinfo.currentmove = &makron_move_pain5; + } + else + { + if (damage <= 150) + if (random() <= 0.45) + { + gi.sound (self, CHAN_VOICE, sound_pain6, 1, ATTN_NONE,0); + self->monsterinfo.currentmove = &makron_move_pain6; + } + else + if (random() <= 0.35) + { + gi.sound (self, CHAN_VOICE, sound_pain6, 1, ATTN_NONE,0); + self->monsterinfo.currentmove = &makron_move_pain6; + } + } +}; + +void makron_sight(edict_t *self, edict_t *other) +{ + self->monsterinfo.currentmove = &makron_move_sight; +}; + +void makron_attack(edict_t *self) +{ + vec3_t vec; + float range; + float r; + + r = random(); + + VectorSubtract (self->enemy->s.origin, self->s.origin, vec); + range = VectorLength (vec); + + + if (r <= 0.3) + self->monsterinfo.currentmove = &makron_move_attack3; + else if (r <= 0.6) + self->monsterinfo.currentmove = &makron_move_attack4; + else + self->monsterinfo.currentmove = &makron_move_attack5; +} + +/* +--- +Makron Torso. This needs to be spawned in +--- +*/ + +void makron_torso_think (edict_t *self) +{ + if (++self->s.frame < 365) + self->nextthink = level.time + FRAMETIME; + else + { + self->s.frame = 346; + self->nextthink = level.time + FRAMETIME; + } +} + +void makron_torso (edict_t *ent) +{ + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_NOT; + VectorSet (ent->mins, -8, -8, 0); + VectorSet (ent->maxs, 8, 8, 8); + ent->s.frame = 346; + ent->s.modelindex = gi.modelindex ("models/monsters/boss3/rider/tris.md2"); + ent->think = makron_torso_think; + ent->nextthink = level.time + 2 * FRAMETIME; + ent->s.sound = gi.soundindex ("makron/spine.wav"); + gi.linkentity (ent); +} + + +// +// death +// + +void makron_dead (edict_t *self) +{ + VectorSet (self->mins, -60, -60, 0); + VectorSet (self->maxs, 60, 60, 72); + self->movetype = MOVETYPE_TOSS; + self->svflags |= SVF_DEADMONSTER; + self->nextthink = 0; + gi.linkentity (self); +} + + +void makron_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + edict_t *tempent; + + int n; + + self->s.sound = 0; + // check for gib + if (self->health <= self->gib_health) + { + gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0); + for (n= 0; n < 1 /*4*/; n++) + ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); + for (n= 0; n < 4; n++) + ThrowGib (self, "models/objects/gibs/sm_metal/tris.md2", damage, GIB_METALLIC); + ThrowHead (self, "models/objects/gibs/gear/tris.md2", damage, GIB_METALLIC); + self->deadflag = DEAD_DEAD; + return; + } + + if (self->deadflag == DEAD_DEAD) + return; + +// regular death + gi.sound (self, CHAN_VOICE, sound_death, 1, ATTN_NONE, 0); + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_YES; + + tempent = G_Spawn(); + VectorCopy (self->s.origin, tempent->s.origin); + VectorCopy (self->s.angles, tempent->s.angles); + tempent->s.origin[1] -= 84; + makron_torso (tempent); + + self->monsterinfo.currentmove = &makron_move_death2; + +} + +qboolean Makron_CheckAttack (edict_t *self) +{ + vec3_t spot1, spot2; + vec3_t temp; + float chance; + trace_t tr; + qboolean enemy_infront; + int enemy_range; + float enemy_yaw; + + if (self->enemy->health > 0) + { + // see if any entities are in the way of the shot + VectorCopy (self->s.origin, spot1); + spot1[2] += self->viewheight; + VectorCopy (self->enemy->s.origin, spot2); + spot2[2] += self->enemy->viewheight; + + tr = gi.trace (spot1, NULL, NULL, spot2, self, CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_SLIME|CONTENTS_LAVA); + + // do we have a clear shot? + if (tr.ent != self->enemy) + return false; + } + + enemy_infront = infront(self, self->enemy); + enemy_range = range(self, self->enemy); + VectorSubtract (self->enemy->s.origin, self->s.origin, temp); + enemy_yaw = vectoyaw(temp); + + self->ideal_yaw = enemy_yaw; + + + // melee attack + if (enemy_range == RANGE_MELEE) + { + if (self->monsterinfo.melee) + self->monsterinfo.attack_state = AS_MELEE; + else + self->monsterinfo.attack_state = AS_MISSILE; + return true; + } + +// missile attack + if (!self->monsterinfo.attack) + return false; + + if (level.time < self->monsterinfo.attack_finished) + return false; + + if (enemy_range == RANGE_FAR) + return false; + + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + { + chance = 0.4; + } + else if (enemy_range == RANGE_MELEE) + { + chance = 0.8; + } + else if (enemy_range == RANGE_NEAR) + { + chance = 0.4; + } + else if (enemy_range == RANGE_MID) + { + chance = 0.2; + } + else + { + return false; + } + + if (random () < chance) + { + self->monsterinfo.attack_state = AS_MISSILE; + self->monsterinfo.attack_finished = level.time + 2*random(); + return true; + } + + if (self->flags & FL_FLY) + { + if (random() < 0.3) + self->monsterinfo.attack_state = AS_SLIDING; + else + self->monsterinfo.attack_state = AS_STRAIGHT; + } + + return false; +} + + +// +// monster_makron +// + +void MakronPrecache (void) +{ + sound_pain4 = gi.soundindex ("makron/pain3.wav"); + sound_pain5 = gi.soundindex ("makron/pain2.wav"); + sound_pain6 = gi.soundindex ("makron/pain1.wav"); + sound_death = gi.soundindex ("makron/death.wav"); + sound_step_left = gi.soundindex ("makron/step1.wav"); + sound_step_right = gi.soundindex ("makron/step2.wav"); + sound_attack_bfg = gi.soundindex ("makron/bfg_fire.wav"); + sound_brainsplorch = gi.soundindex ("makron/brain1.wav"); + sound_prerailgun = gi.soundindex ("makron/rail_up.wav"); + sound_popup = gi.soundindex ("makron/popup.wav"); + sound_taunt1 = gi.soundindex ("makron/voice4.wav"); + sound_taunt2 = gi.soundindex ("makron/voice3.wav"); + sound_taunt3 = gi.soundindex ("makron/voice.wav"); + sound_hit = gi.soundindex ("makron/bhit.wav"); + + gi.modelindex ("models/monsters/boss3/rider/tris.md2"); +} + +/*QUAKED monster_makron (1 .5 0) (-30 -30 0) (30 30 90) Ambush Trigger_Spawn Sight +*/ +void SP_monster_makron (edict_t *self) +{ + if (deathmatch->value) + { + G_FreeEdict (self); + return; + } + + MakronPrecache (); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex ("models/monsters/boss3/rider/tris.md2"); + VectorSet (self->mins, -30, -30, 0); + VectorSet (self->maxs, 30, 30, 90); + + self->health = 3000; + self->gib_health = -2000; + self->mass = 500; + + self->pain = makron_pain; + self->die = makron_die; + self->monsterinfo.stand = makron_stand; + self->monsterinfo.walk = makron_walk; + self->monsterinfo.run = makron_run; + self->monsterinfo.dodge = NULL; + self->monsterinfo.attack = makron_attack; + self->monsterinfo.melee = NULL; + self->monsterinfo.sight = makron_sight; + self->monsterinfo.checkattack = Makron_CheckAttack; + + gi.linkentity (self); + +// self->monsterinfo.currentmove = &makron_move_stand; + self->monsterinfo.currentmove = &makron_move_sight; + self->monsterinfo.scale = MODEL_SCALE; + + walkmonster_start(self); +} + + +/* +================= +MakronSpawn + +================= +*/ +void MakronSpawn (edict_t *self) +{ + vec3_t vec; + edict_t *player; + + SP_monster_makron (self); + + // jump at player + player = level.sight_client; + if (!player) + return; + + VectorSubtract (player->s.origin, self->s.origin, vec); + self->s.angles[YAW] = vectoyaw(vec); + VectorNormalize (vec); + VectorMA (vec3_origin, 400, vec, self->velocity); + self->velocity[2] = 200; + self->groundentity = NULL; +} + +/* +================= +MakronToss + +Jorg is just about dead, so set up to launch Makron out +================= +*/ +void MakronToss (edict_t *self) +{ + edict_t *ent; + + ent = G_Spawn (); + ent->nextthink = level.time + 0.8; + ent->think = MakronSpawn; + ent->target = self->target; + VectorCopy (self->s.origin, ent->s.origin); +} diff --git a/original/baseq2/m_boss32.h b/original/baseq2/m_boss32.h new file mode 100644 index 0000000..b01861e --- /dev/null +++ b/original/baseq2/m_boss32.h @@ -0,0 +1,516 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +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. + +*/ +// G:\quake2\baseq2\models/monsters/boss3/rider + +// This file generated by ModelGen - Do NOT Modify + +#define FRAME_attak101 0 +#define FRAME_attak102 1 +#define FRAME_attak103 2 +#define FRAME_attak104 3 +#define FRAME_attak105 4 +#define FRAME_attak106 5 +#define FRAME_attak107 6 +#define FRAME_attak108 7 +#define FRAME_attak109 8 +#define FRAME_attak110 9 +#define FRAME_attak111 10 +#define FRAME_attak112 11 +#define FRAME_attak113 12 +#define FRAME_attak114 13 +#define FRAME_attak115 14 +#define FRAME_attak116 15 +#define FRAME_attak117 16 +#define FRAME_attak118 17 +#define FRAME_attak201 18 +#define FRAME_attak202 19 +#define FRAME_attak203 20 +#define FRAME_attak204 21 +#define FRAME_attak205 22 +#define FRAME_attak206 23 +#define FRAME_attak207 24 +#define FRAME_attak208 25 +#define FRAME_attak209 26 +#define FRAME_attak210 27 +#define FRAME_attak211 28 +#define FRAME_attak212 29 +#define FRAME_attak213 30 +#define FRAME_death01 31 +#define FRAME_death02 32 +#define FRAME_death03 33 +#define FRAME_death04 34 +#define FRAME_death05 35 +#define FRAME_death06 36 +#define FRAME_death07 37 +#define FRAME_death08 38 +#define FRAME_death09 39 +#define FRAME_death10 40 +#define FRAME_death11 41 +#define FRAME_death12 42 +#define FRAME_death13 43 +#define FRAME_death14 44 +#define FRAME_death15 45 +#define FRAME_death16 46 +#define FRAME_death17 47 +#define FRAME_death18 48 +#define FRAME_death19 49 +#define FRAME_death20 50 +#define FRAME_death21 51 +#define FRAME_death22 52 +#define FRAME_death23 53 +#define FRAME_death24 54 +#define FRAME_death25 55 +#define FRAME_death26 56 +#define FRAME_death27 57 +#define FRAME_death28 58 +#define FRAME_death29 59 +#define FRAME_death30 60 +#define FRAME_death31 61 +#define FRAME_death32 62 +#define FRAME_death33 63 +#define FRAME_death34 64 +#define FRAME_death35 65 +#define FRAME_death36 66 +#define FRAME_death37 67 +#define FRAME_death38 68 +#define FRAME_death39 69 +#define FRAME_death40 70 +#define FRAME_death41 71 +#define FRAME_death42 72 +#define FRAME_death43 73 +#define FRAME_death44 74 +#define FRAME_death45 75 +#define FRAME_death46 76 +#define FRAME_death47 77 +#define FRAME_death48 78 +#define FRAME_death49 79 +#define FRAME_death50 80 +#define FRAME_pain101 81 +#define FRAME_pain102 82 +#define FRAME_pain103 83 +#define FRAME_pain201 84 +#define FRAME_pain202 85 +#define FRAME_pain203 86 +#define FRAME_pain301 87 +#define FRAME_pain302 88 +#define FRAME_pain303 89 +#define FRAME_pain304 90 +#define FRAME_pain305 91 +#define FRAME_pain306 92 +#define FRAME_pain307 93 +#define FRAME_pain308 94 +#define FRAME_pain309 95 +#define FRAME_pain310 96 +#define FRAME_pain311 97 +#define FRAME_pain312 98 +#define FRAME_pain313 99 +#define FRAME_pain314 100 +#define FRAME_pain315 101 +#define FRAME_pain316 102 +#define FRAME_pain317 103 +#define FRAME_pain318 104 +#define FRAME_pain319 105 +#define FRAME_pain320 106 +#define FRAME_pain321 107 +#define FRAME_pain322 108 +#define FRAME_pain323 109 +#define FRAME_pain324 110 +#define FRAME_pain325 111 +#define FRAME_stand01 112 +#define FRAME_stand02 113 +#define FRAME_stand03 114 +#define FRAME_stand04 115 +#define FRAME_stand05 116 +#define FRAME_stand06 117 +#define FRAME_stand07 118 +#define FRAME_stand08 119 +#define FRAME_stand09 120 +#define FRAME_stand10 121 +#define FRAME_stand11 122 +#define FRAME_stand12 123 +#define FRAME_stand13 124 +#define FRAME_stand14 125 +#define FRAME_stand15 126 +#define FRAME_stand16 127 +#define FRAME_stand17 128 +#define FRAME_stand18 129 +#define FRAME_stand19 130 +#define FRAME_stand20 131 +#define FRAME_stand21 132 +#define FRAME_stand22 133 +#define FRAME_stand23 134 +#define FRAME_stand24 135 +#define FRAME_stand25 136 +#define FRAME_stand26 137 +#define FRAME_stand27 138 +#define FRAME_stand28 139 +#define FRAME_stand29 140 +#define FRAME_stand30 141 +#define FRAME_stand31 142 +#define FRAME_stand32 143 +#define FRAME_stand33 144 +#define FRAME_stand34 145 +#define FRAME_stand35 146 +#define FRAME_stand36 147 +#define FRAME_stand37 148 +#define FRAME_stand38 149 +#define FRAME_stand39 150 +#define FRAME_stand40 151 +#define FRAME_stand41 152 +#define FRAME_stand42 153 +#define FRAME_stand43 154 +#define FRAME_stand44 155 +#define FRAME_stand45 156 +#define FRAME_stand46 157 +#define FRAME_stand47 158 +#define FRAME_stand48 159 +#define FRAME_stand49 160 +#define FRAME_stand50 161 +#define FRAME_stand51 162 +#define FRAME_walk01 163 +#define FRAME_walk02 164 +#define FRAME_walk03 165 +#define FRAME_walk04 166 +#define FRAME_walk05 167 +#define FRAME_walk06 168 +#define FRAME_walk07 169 +#define FRAME_walk08 170 +#define FRAME_walk09 171 +#define FRAME_walk10 172 +#define FRAME_walk11 173 +#define FRAME_walk12 174 +#define FRAME_walk13 175 +#define FRAME_walk14 176 +#define FRAME_walk15 177 +#define FRAME_walk16 178 +#define FRAME_walk17 179 +#define FRAME_walk18 180 +#define FRAME_walk19 181 +#define FRAME_walk20 182 +#define FRAME_walk21 183 +#define FRAME_walk22 184 +#define FRAME_walk23 185 +#define FRAME_walk24 186 +#define FRAME_walk25 187 +#define FRAME_active01 188 +#define FRAME_active02 189 +#define FRAME_active03 190 +#define FRAME_active04 191 +#define FRAME_active05 192 +#define FRAME_active06 193 +#define FRAME_active07 194 +#define FRAME_active08 195 +#define FRAME_active09 196 +#define FRAME_active10 197 +#define FRAME_active11 198 +#define FRAME_active12 199 +#define FRAME_active13 200 +#define FRAME_attak301 201 +#define FRAME_attak302 202 +#define FRAME_attak303 203 +#define FRAME_attak304 204 +#define FRAME_attak305 205 +#define FRAME_attak306 206 +#define FRAME_attak307 207 +#define FRAME_attak308 208 +#define FRAME_attak401 209 +#define FRAME_attak402 210 +#define FRAME_attak403 211 +#define FRAME_attak404 212 +#define FRAME_attak405 213 +#define FRAME_attak406 214 +#define FRAME_attak407 215 +#define FRAME_attak408 216 +#define FRAME_attak409 217 +#define FRAME_attak410 218 +#define FRAME_attak411 219 +#define FRAME_attak412 220 +#define FRAME_attak413 221 +#define FRAME_attak414 222 +#define FRAME_attak415 223 +#define FRAME_attak416 224 +#define FRAME_attak417 225 +#define FRAME_attak418 226 +#define FRAME_attak419 227 +#define FRAME_attak420 228 +#define FRAME_attak421 229 +#define FRAME_attak422 230 +#define FRAME_attak423 231 +#define FRAME_attak424 232 +#define FRAME_attak425 233 +#define FRAME_attak426 234 +#define FRAME_attak501 235 +#define FRAME_attak502 236 +#define FRAME_attak503 237 +#define FRAME_attak504 238 +#define FRAME_attak505 239 +#define FRAME_attak506 240 +#define FRAME_attak507 241 +#define FRAME_attak508 242 +#define FRAME_attak509 243 +#define FRAME_attak510 244 +#define FRAME_attak511 245 +#define FRAME_attak512 246 +#define FRAME_attak513 247 +#define FRAME_attak514 248 +#define FRAME_attak515 249 +#define FRAME_attak516 250 +#define FRAME_death201 251 +#define FRAME_death202 252 +#define FRAME_death203 253 +#define FRAME_death204 254 +#define FRAME_death205 255 +#define FRAME_death206 256 +#define FRAME_death207 257 +#define FRAME_death208 258 +#define FRAME_death209 259 +#define FRAME_death210 260 +#define FRAME_death211 261 +#define FRAME_death212 262 +#define FRAME_death213 263 +#define FRAME_death214 264 +#define FRAME_death215 265 +#define FRAME_death216 266 +#define FRAME_death217 267 +#define FRAME_death218 268 +#define FRAME_death219 269 +#define FRAME_death220 270 +#define FRAME_death221 271 +#define FRAME_death222 272 +#define FRAME_death223 273 +#define FRAME_death224 274 +#define FRAME_death225 275 +#define FRAME_death226 276 +#define FRAME_death227 277 +#define FRAME_death228 278 +#define FRAME_death229 279 +#define FRAME_death230 280 +#define FRAME_death231 281 +#define FRAME_death232 282 +#define FRAME_death233 283 +#define FRAME_death234 284 +#define FRAME_death235 285 +#define FRAME_death236 286 +#define FRAME_death237 287 +#define FRAME_death238 288 +#define FRAME_death239 289 +#define FRAME_death240 290 +#define FRAME_death241 291 +#define FRAME_death242 292 +#define FRAME_death243 293 +#define FRAME_death244 294 +#define FRAME_death245 295 +#define FRAME_death246 296 +#define FRAME_death247 297 +#define FRAME_death248 298 +#define FRAME_death249 299 +#define FRAME_death250 300 +#define FRAME_death251 301 +#define FRAME_death252 302 +#define FRAME_death253 303 +#define FRAME_death254 304 +#define FRAME_death255 305 +#define FRAME_death256 306 +#define FRAME_death257 307 +#define FRAME_death258 308 +#define FRAME_death259 309 +#define FRAME_death260 310 +#define FRAME_death261 311 +#define FRAME_death262 312 +#define FRAME_death263 313 +#define FRAME_death264 314 +#define FRAME_death265 315 +#define FRAME_death266 316 +#define FRAME_death267 317 +#define FRAME_death268 318 +#define FRAME_death269 319 +#define FRAME_death270 320 +#define FRAME_death271 321 +#define FRAME_death272 322 +#define FRAME_death273 323 +#define FRAME_death274 324 +#define FRAME_death275 325 +#define FRAME_death276 326 +#define FRAME_death277 327 +#define FRAME_death278 328 +#define FRAME_death279 329 +#define FRAME_death280 330 +#define FRAME_death281 331 +#define FRAME_death282 332 +#define FRAME_death283 333 +#define FRAME_death284 334 +#define FRAME_death285 335 +#define FRAME_death286 336 +#define FRAME_death287 337 +#define FRAME_death288 338 +#define FRAME_death289 339 +#define FRAME_death290 340 +#define FRAME_death291 341 +#define FRAME_death292 342 +#define FRAME_death293 343 +#define FRAME_death294 344 +#define FRAME_death295 345 +#define FRAME_death301 346 +#define FRAME_death302 347 +#define FRAME_death303 348 +#define FRAME_death304 349 +#define FRAME_death305 350 +#define FRAME_death306 351 +#define FRAME_death307 352 +#define FRAME_death308 353 +#define FRAME_death309 354 +#define FRAME_death310 355 +#define FRAME_death311 356 +#define FRAME_death312 357 +#define FRAME_death313 358 +#define FRAME_death314 359 +#define FRAME_death315 360 +#define FRAME_death316 361 +#define FRAME_death317 362 +#define FRAME_death318 363 +#define FRAME_death319 364 +#define FRAME_death320 365 +#define FRAME_jump01 366 +#define FRAME_jump02 367 +#define FRAME_jump03 368 +#define FRAME_jump04 369 +#define FRAME_jump05 370 +#define FRAME_jump06 371 +#define FRAME_jump07 372 +#define FRAME_jump08 373 +#define FRAME_jump09 374 +#define FRAME_jump10 375 +#define FRAME_jump11 376 +#define FRAME_jump12 377 +#define FRAME_jump13 378 +#define FRAME_pain401 379 +#define FRAME_pain402 380 +#define FRAME_pain403 381 +#define FRAME_pain404 382 +#define FRAME_pain501 383 +#define FRAME_pain502 384 +#define FRAME_pain503 385 +#define FRAME_pain504 386 +#define FRAME_pain601 387 +#define FRAME_pain602 388 +#define FRAME_pain603 389 +#define FRAME_pain604 390 +#define FRAME_pain605 391 +#define FRAME_pain606 392 +#define FRAME_pain607 393 +#define FRAME_pain608 394 +#define FRAME_pain609 395 +#define FRAME_pain610 396 +#define FRAME_pain611 397 +#define FRAME_pain612 398 +#define FRAME_pain613 399 +#define FRAME_pain614 400 +#define FRAME_pain615 401 +#define FRAME_pain616 402 +#define FRAME_pain617 403 +#define FRAME_pain618 404 +#define FRAME_pain619 405 +#define FRAME_pain620 406 +#define FRAME_pain621 407 +#define FRAME_pain622 408 +#define FRAME_pain623 409 +#define FRAME_pain624 410 +#define FRAME_pain625 411 +#define FRAME_pain626 412 +#define FRAME_pain627 413 +#define FRAME_stand201 414 +#define FRAME_stand202 415 +#define FRAME_stand203 416 +#define FRAME_stand204 417 +#define FRAME_stand205 418 +#define FRAME_stand206 419 +#define FRAME_stand207 420 +#define FRAME_stand208 421 +#define FRAME_stand209 422 +#define FRAME_stand210 423 +#define FRAME_stand211 424 +#define FRAME_stand212 425 +#define FRAME_stand213 426 +#define FRAME_stand214 427 +#define FRAME_stand215 428 +#define FRAME_stand216 429 +#define FRAME_stand217 430 +#define FRAME_stand218 431 +#define FRAME_stand219 432 +#define FRAME_stand220 433 +#define FRAME_stand221 434 +#define FRAME_stand222 435 +#define FRAME_stand223 436 +#define FRAME_stand224 437 +#define FRAME_stand225 438 +#define FRAME_stand226 439 +#define FRAME_stand227 440 +#define FRAME_stand228 441 +#define FRAME_stand229 442 +#define FRAME_stand230 443 +#define FRAME_stand231 444 +#define FRAME_stand232 445 +#define FRAME_stand233 446 +#define FRAME_stand234 447 +#define FRAME_stand235 448 +#define FRAME_stand236 449 +#define FRAME_stand237 450 +#define FRAME_stand238 451 +#define FRAME_stand239 452 +#define FRAME_stand240 453 +#define FRAME_stand241 454 +#define FRAME_stand242 455 +#define FRAME_stand243 456 +#define FRAME_stand244 457 +#define FRAME_stand245 458 +#define FRAME_stand246 459 +#define FRAME_stand247 460 +#define FRAME_stand248 461 +#define FRAME_stand249 462 +#define FRAME_stand250 463 +#define FRAME_stand251 464 +#define FRAME_stand252 465 +#define FRAME_stand253 466 +#define FRAME_stand254 467 +#define FRAME_stand255 468 +#define FRAME_stand256 469 +#define FRAME_stand257 470 +#define FRAME_stand258 471 +#define FRAME_stand259 472 +#define FRAME_stand260 473 +#define FRAME_walk201 474 +#define FRAME_walk202 475 +#define FRAME_walk203 476 +#define FRAME_walk204 477 +#define FRAME_walk205 478 +#define FRAME_walk206 479 +#define FRAME_walk207 480 +#define FRAME_walk208 481 +#define FRAME_walk209 482 +#define FRAME_walk210 483 +#define FRAME_walk211 484 +#define FRAME_walk212 485 +#define FRAME_walk213 486 +#define FRAME_walk214 487 +#define FRAME_walk215 488 +#define FRAME_walk216 489 +#define FRAME_walk217 490 + +#define MODEL_SCALE 1.000000 diff --git a/original/baseq2/m_brain.c b/original/baseq2/m_brain.c new file mode 100644 index 0000000..87c5047 --- /dev/null +++ b/original/baseq2/m_brain.c @@ -0,0 +1,676 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +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. + +*/ +/* +============================================================================== + +brain + +============================================================================== +*/ + +#include "g_local.h" +#include "m_brain.h" + + +static int sound_chest_open; +static int sound_tentacles_extend; +static int sound_tentacles_retract; +static int sound_death; +static int sound_idle1; +static int sound_idle2; +static int sound_idle3; +static int sound_pain1; +static int sound_pain2; +static int sound_sight; +static int sound_search; +static int sound_melee1; +static int sound_melee2; +static int sound_melee3; + + +void brain_sight (edict_t *self, edict_t *other) +{ + gi.sound (self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); +} + +void brain_search (edict_t *self) +{ + gi.sound (self, CHAN_VOICE, sound_search, 1, ATTN_NORM, 0); +} + + +void brain_run (edict_t *self); +void brain_dead (edict_t *self); + + +// +// STAND +// + +mframe_t brain_frames_stand [] = +{ + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL +}; +mmove_t brain_move_stand = {FRAME_stand01, FRAME_stand30, brain_frames_stand, NULL}; + +void brain_stand (edict_t *self) +{ + self->monsterinfo.currentmove = &brain_move_stand; +} + + +// +// IDLE +// + +mframe_t brain_frames_idle [] = +{ + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL +}; +mmove_t brain_move_idle = {FRAME_stand31, FRAME_stand60, brain_frames_idle, brain_stand}; + +void brain_idle (edict_t *self) +{ + gi.sound (self, CHAN_AUTO, sound_idle3, 1, ATTN_IDLE, 0); + self->monsterinfo.currentmove = &brain_move_idle; +} + + +// +// WALK +// +mframe_t brain_frames_walk1 [] = +{ + ai_walk, 7, NULL, + ai_walk, 2, NULL, + ai_walk, 3, NULL, + ai_walk, 3, NULL, + ai_walk, 1, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 9, NULL, + ai_walk, -4, NULL, + ai_walk, -1, NULL, + ai_walk, 2, NULL +}; +mmove_t brain_move_walk1 = {FRAME_walk101, FRAME_walk111, brain_frames_walk1, NULL}; + +// walk2 is FUBAR, do not use +#if 0 +void brain_walk2_cycle (edict_t *self) +{ + if (random() > 0.1) + self->monsterinfo.nextframe = FRAME_walk220; +} + +mframe_t brain_frames_walk2 [] = +{ + ai_walk, 3, NULL, + ai_walk, -2, NULL, + ai_walk, -4, NULL, + ai_walk, -3, NULL, + ai_walk, 0, NULL, + ai_walk, 1, NULL, + ai_walk, 12, NULL, + ai_walk, 0, NULL, + ai_walk, -3, NULL, + ai_walk, 0, NULL, + + ai_walk, -2, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 1, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 10, NULL, // Cycle Start + + ai_walk, -1, NULL, + ai_walk, 7, NULL, + ai_walk, 0, NULL, + ai_walk, 3, NULL, + ai_walk, -3, NULL, + ai_walk, 2, NULL, + ai_walk, 4, NULL, + ai_walk, -3, NULL, + ai_walk, 2, NULL, + ai_walk, 0, NULL, + + ai_walk, 4, brain_walk2_cycle, + ai_walk, -1, NULL, + ai_walk, -1, NULL, + ai_walk, -8, NULL, + ai_walk, 0, NULL, + ai_walk, 1, NULL, + ai_walk, 5, NULL, + ai_walk, 2, NULL, + ai_walk, -1, NULL, + ai_walk, -5, NULL +}; +mmove_t brain_move_walk2 = {FRAME_walk201, FRAME_walk240, brain_frames_walk2, NULL}; +#endif + +void brain_walk (edict_t *self) +{ +// if (random() <= 0.5) + self->monsterinfo.currentmove = &brain_move_walk1; +// else +// self->monsterinfo.currentmove = &brain_move_walk2; +} + + + +mframe_t brain_frames_defense [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t brain_move_defense = {FRAME_defens01, FRAME_defens08, brain_frames_defense, NULL}; + +mframe_t brain_frames_pain3 [] = +{ + ai_move, -2, NULL, + ai_move, 2, NULL, + ai_move, 1, NULL, + ai_move, 3, NULL, + ai_move, 0, NULL, + ai_move, -4, NULL +}; +mmove_t brain_move_pain3 = {FRAME_pain301, FRAME_pain306, brain_frames_pain3, brain_run}; + +mframe_t brain_frames_pain2 [] = +{ + ai_move, -2, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 3, NULL, + ai_move, 1, NULL, + ai_move, -2, NULL +}; +mmove_t brain_move_pain2 = {FRAME_pain201, FRAME_pain208, brain_frames_pain2, brain_run}; + +mframe_t brain_frames_pain1 [] = +{ + ai_move, -6, NULL, + ai_move, -2, NULL, + ai_move, -6, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 2, NULL, + ai_move, 0, NULL, + ai_move, 2, NULL, + ai_move, 1, NULL, + ai_move, 7, NULL, + ai_move, 0, NULL, + ai_move, 3, NULL, + ai_move, -1, NULL +}; +mmove_t brain_move_pain1 = {FRAME_pain101, FRAME_pain121, brain_frames_pain1, brain_run}; + + +// +// DUCK +// + +void brain_duck_down (edict_t *self) +{ + if (self->monsterinfo.aiflags & AI_DUCKED) + return; + self->monsterinfo.aiflags |= AI_DUCKED; + self->maxs[2] -= 32; + self->takedamage = DAMAGE_YES; + gi.linkentity (self); +} + +void brain_duck_hold (edict_t *self) +{ + if (level.time >= self->monsterinfo.pausetime) + self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; + else + self->monsterinfo.aiflags |= AI_HOLD_FRAME; +} + +void brain_duck_up (edict_t *self) +{ + self->monsterinfo.aiflags &= ~AI_DUCKED; + self->maxs[2] += 32; + self->takedamage = DAMAGE_AIM; + gi.linkentity (self); +} + +mframe_t brain_frames_duck [] = +{ + ai_move, 0, NULL, + ai_move, -2, brain_duck_down, + ai_move, 17, brain_duck_hold, + ai_move, -3, NULL, + ai_move, -1, brain_duck_up, + ai_move, -5, NULL, + ai_move, -6, NULL, + ai_move, -6, NULL +}; +mmove_t brain_move_duck = {FRAME_duck01, FRAME_duck08, brain_frames_duck, brain_run}; + +void brain_dodge (edict_t *self, edict_t *attacker, float eta) +{ + if (random() > 0.25) + return; + + if (!self->enemy) + self->enemy = attacker; + + self->monsterinfo.pausetime = level.time + eta + 0.5; + self->monsterinfo.currentmove = &brain_move_duck; +} + + +mframe_t brain_frames_death2 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 9, NULL, + ai_move, 0, NULL +}; +mmove_t brain_move_death2 = {FRAME_death201, FRAME_death205, brain_frames_death2, brain_dead}; + +mframe_t brain_frames_death1 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, -2, NULL, + ai_move, 9, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t brain_move_death1 = {FRAME_death101, FRAME_death118, brain_frames_death1, brain_dead}; + + +// +// MELEE +// + +void brain_swing_right (edict_t *self) +{ + gi.sound (self, CHAN_BODY, sound_melee1, 1, ATTN_NORM, 0); +} + +void brain_hit_right (edict_t *self) +{ + vec3_t aim; + + VectorSet (aim, MELEE_DISTANCE, self->maxs[0], 8); + if (fire_hit (self, aim, (15 + (rand() %5)), 40)) + gi.sound (self, CHAN_WEAPON, sound_melee3, 1, ATTN_NORM, 0); +} + +void brain_swing_left (edict_t *self) +{ + gi.sound (self, CHAN_BODY, sound_melee2, 1, ATTN_NORM, 0); +} + +void brain_hit_left (edict_t *self) +{ + vec3_t aim; + + VectorSet (aim, MELEE_DISTANCE, self->mins[0], 8); + if (fire_hit (self, aim, (15 + (rand() %5)), 40)) + gi.sound (self, CHAN_WEAPON, sound_melee3, 1, ATTN_NORM, 0); +} + +mframe_t brain_frames_attack1 [] = +{ + ai_charge, 8, NULL, + ai_charge, 3, NULL, + ai_charge, 5, NULL, + ai_charge, 0, NULL, + ai_charge, -3, brain_swing_right, + ai_charge, 0, NULL, + ai_charge, -5, NULL, + ai_charge, -7, brain_hit_right, + ai_charge, 0, NULL, + ai_charge, 6, brain_swing_left, + ai_charge, 1, NULL, + ai_charge, 2, brain_hit_left, + ai_charge, -3, NULL, + ai_charge, 6, NULL, + ai_charge, -1, NULL, + ai_charge, -3, NULL, + ai_charge, 2, NULL, + ai_charge, -11,NULL +}; +mmove_t brain_move_attack1 = {FRAME_attak101, FRAME_attak118, brain_frames_attack1, brain_run}; + +void brain_chest_open (edict_t *self) +{ + self->spawnflags &= ~65536; + self->monsterinfo.power_armor_type = POWER_ARMOR_NONE; + gi.sound (self, CHAN_BODY, sound_chest_open, 1, ATTN_NORM, 0); +} + +void brain_tentacle_attack (edict_t *self) +{ + vec3_t aim; + + VectorSet (aim, MELEE_DISTANCE, 0, 8); + if (fire_hit (self, aim, (10 + (rand() %5)), -600) && skill->value > 0) + self->spawnflags |= 65536; + gi.sound (self, CHAN_WEAPON, sound_tentacles_retract, 1, ATTN_NORM, 0); +} + +void brain_chest_closed (edict_t *self) +{ + self->monsterinfo.power_armor_type = POWER_ARMOR_SCREEN; + if (self->spawnflags & 65536) + { + self->spawnflags &= ~65536; + self->monsterinfo.currentmove = &brain_move_attack1; + } +} + +mframe_t brain_frames_attack2 [] = +{ + ai_charge, 5, NULL, + ai_charge, -4, NULL, + ai_charge, -4, NULL, + ai_charge, -3, NULL, + ai_charge, 0, brain_chest_open, + ai_charge, 0, NULL, + ai_charge, 13, brain_tentacle_attack, + ai_charge, 0, NULL, + ai_charge, 2, NULL, + ai_charge, 0, NULL, + ai_charge, -9, brain_chest_closed, + ai_charge, 0, NULL, + ai_charge, 4, NULL, + ai_charge, 3, NULL, + ai_charge, 2, NULL, + ai_charge, -3, NULL, + ai_charge, -6, NULL +}; +mmove_t brain_move_attack2 = {FRAME_attak201, FRAME_attak217, brain_frames_attack2, brain_run}; + +void brain_melee(edict_t *self) +{ + if (random() <= 0.5) + self->monsterinfo.currentmove = &brain_move_attack1; + else + self->monsterinfo.currentmove = &brain_move_attack2; +} + + +// +// RUN +// + +mframe_t brain_frames_run [] = +{ + ai_run, 9, NULL, + ai_run, 2, NULL, + ai_run, 3, NULL, + ai_run, 3, NULL, + ai_run, 1, NULL, + ai_run, 0, NULL, + ai_run, 0, NULL, + ai_run, 10, NULL, + ai_run, -4, NULL, + ai_run, -1, NULL, + ai_run, 2, NULL +}; +mmove_t brain_move_run = {FRAME_walk101, FRAME_walk111, brain_frames_run, NULL}; + +void brain_run (edict_t *self) +{ + self->monsterinfo.power_armor_type = POWER_ARMOR_SCREEN; + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + self->monsterinfo.currentmove = &brain_move_stand; + else + self->monsterinfo.currentmove = &brain_move_run; +} + + +void brain_pain (edict_t *self, edict_t *other, float kick, int damage) +{ + float r; + + if (self->health < (self->max_health / 2)) + self->s.skinnum = 1; + + if (level.time < self->pain_debounce_time) + return; + + self->pain_debounce_time = level.time + 3; + if (skill->value == 3) + return; // no pain anims in nightmare + + r = random(); + if (r < 0.33) + { + gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &brain_move_pain1; + } + else if (r < 0.66) + { + gi.sound (self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &brain_move_pain2; + } + else + { + gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &brain_move_pain3; + } +} + +void brain_dead (edict_t *self) +{ + VectorSet (self->mins, -16, -16, -24); + VectorSet (self->maxs, 16, 16, -8); + self->movetype = MOVETYPE_TOSS; + self->svflags |= SVF_DEADMONSTER; + self->nextthink = 0; + gi.linkentity (self); +} + + + +void brain_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + int n; + + self->s.effects = 0; + self->monsterinfo.power_armor_type = POWER_ARMOR_NONE; + +// check for gib + if (self->health <= self->gib_health) + { + gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0); + for (n= 0; n < 2; n++) + ThrowGib (self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC); + for (n= 0; n < 4; n++) + ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); + ThrowHead (self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC); + self->deadflag = DEAD_DEAD; + return; + } + + if (self->deadflag == DEAD_DEAD) + return; + +// regular death + gi.sound (self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0); + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_YES; + if (random() <= 0.5) + self->monsterinfo.currentmove = &brain_move_death1; + else + self->monsterinfo.currentmove = &brain_move_death2; +} + +/*QUAKED monster_brain (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight +*/ +void SP_monster_brain (edict_t *self) +{ + if (deathmatch->value) + { + G_FreeEdict (self); + return; + } + + sound_chest_open = gi.soundindex ("brain/brnatck1.wav"); + sound_tentacles_extend = gi.soundindex ("brain/brnatck2.wav"); + sound_tentacles_retract = gi.soundindex ("brain/brnatck3.wav"); + sound_death = gi.soundindex ("brain/brndeth1.wav"); + sound_idle1 = gi.soundindex ("brain/brnidle1.wav"); + sound_idle2 = gi.soundindex ("brain/brnidle2.wav"); + sound_idle3 = gi.soundindex ("brain/brnlens1.wav"); + sound_pain1 = gi.soundindex ("brain/brnpain1.wav"); + sound_pain2 = gi.soundindex ("brain/brnpain2.wav"); + sound_sight = gi.soundindex ("brain/brnsght1.wav"); + sound_search = gi.soundindex ("brain/brnsrch1.wav"); + sound_melee1 = gi.soundindex ("brain/melee1.wav"); + sound_melee2 = gi.soundindex ("brain/melee2.wav"); + sound_melee3 = gi.soundindex ("brain/melee3.wav"); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex ("models/monsters/brain/tris.md2"); + VectorSet (self->mins, -16, -16, -24); + VectorSet (self->maxs, 16, 16, 32); + + self->health = 300; + self->gib_health = -150; + self->mass = 400; + + self->pain = brain_pain; + self->die = brain_die; + + self->monsterinfo.stand = brain_stand; + self->monsterinfo.walk = brain_walk; + self->monsterinfo.run = brain_run; + self->monsterinfo.dodge = brain_dodge; +// self->monsterinfo.attack = brain_attack; + self->monsterinfo.melee = brain_melee; + self->monsterinfo.sight = brain_sight; + self->monsterinfo.search = brain_search; + self->monsterinfo.idle = brain_idle; + + self->monsterinfo.power_armor_type = POWER_ARMOR_SCREEN; + self->monsterinfo.power_armor_power = 100; + + gi.linkentity (self); + + self->monsterinfo.currentmove = &brain_move_stand; + self->monsterinfo.scale = MODEL_SCALE; + + walkmonster_start (self); +} diff --git a/original/baseq2/m_brain.h b/original/baseq2/m_brain.h new file mode 100644 index 0000000..99a9ad3 --- /dev/null +++ b/original/baseq2/m_brain.h @@ -0,0 +1,247 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +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. + +*/ +// G:\quake2\baseq2\models/monsters/brain + +// This file generated by ModelGen - Do NOT Modify + +#define FRAME_walk101 0 +#define FRAME_walk102 1 +#define FRAME_walk103 2 +#define FRAME_walk104 3 +#define FRAME_walk105 4 +#define FRAME_walk106 5 +#define FRAME_walk107 6 +#define FRAME_walk108 7 +#define FRAME_walk109 8 +#define FRAME_walk110 9 +#define FRAME_walk111 10 +#define FRAME_walk112 11 +#define FRAME_walk113 12 +#define FRAME_walk201 13 +#define FRAME_walk202 14 +#define FRAME_walk203 15 +#define FRAME_walk204 16 +#define FRAME_walk205 17 +#define FRAME_walk206 18 +#define FRAME_walk207 19 +#define FRAME_walk208 20 +#define FRAME_walk209 21 +#define FRAME_walk210 22 +#define FRAME_walk211 23 +#define FRAME_walk212 24 +#define FRAME_walk213 25 +#define FRAME_walk214 26 +#define FRAME_walk215 27 +#define FRAME_walk216 28 +#define FRAME_walk217 29 +#define FRAME_walk218 30 +#define FRAME_walk219 31 +#define FRAME_walk220 32 +#define FRAME_walk221 33 +#define FRAME_walk222 34 +#define FRAME_walk223 35 +#define FRAME_walk224 36 +#define FRAME_walk225 37 +#define FRAME_walk226 38 +#define FRAME_walk227 39 +#define FRAME_walk228 40 +#define FRAME_walk229 41 +#define FRAME_walk230 42 +#define FRAME_walk231 43 +#define FRAME_walk232 44 +#define FRAME_walk233 45 +#define FRAME_walk234 46 +#define FRAME_walk235 47 +#define FRAME_walk236 48 +#define FRAME_walk237 49 +#define FRAME_walk238 50 +#define FRAME_walk239 51 +#define FRAME_walk240 52 +#define FRAME_attak101 53 +#define FRAME_attak102 54 +#define FRAME_attak103 55 +#define FRAME_attak104 56 +#define FRAME_attak105 57 +#define FRAME_attak106 58 +#define FRAME_attak107 59 +#define FRAME_attak108 60 +#define FRAME_attak109 61 +#define FRAME_attak110 62 +#define FRAME_attak111 63 +#define FRAME_attak112 64 +#define FRAME_attak113 65 +#define FRAME_attak114 66 +#define FRAME_attak115 67 +#define FRAME_attak116 68 +#define FRAME_attak117 69 +#define FRAME_attak118 70 +#define FRAME_attak201 71 +#define FRAME_attak202 72 +#define FRAME_attak203 73 +#define FRAME_attak204 74 +#define FRAME_attak205 75 +#define FRAME_attak206 76 +#define FRAME_attak207 77 +#define FRAME_attak208 78 +#define FRAME_attak209 79 +#define FRAME_attak210 80 +#define FRAME_attak211 81 +#define FRAME_attak212 82 +#define FRAME_attak213 83 +#define FRAME_attak214 84 +#define FRAME_attak215 85 +#define FRAME_attak216 86 +#define FRAME_attak217 87 +#define FRAME_pain101 88 +#define FRAME_pain102 89 +#define FRAME_pain103 90 +#define FRAME_pain104 91 +#define FRAME_pain105 92 +#define FRAME_pain106 93 +#define FRAME_pain107 94 +#define FRAME_pain108 95 +#define FRAME_pain109 96 +#define FRAME_pain110 97 +#define FRAME_pain111 98 +#define FRAME_pain112 99 +#define FRAME_pain113 100 +#define FRAME_pain114 101 +#define FRAME_pain115 102 +#define FRAME_pain116 103 +#define FRAME_pain117 104 +#define FRAME_pain118 105 +#define FRAME_pain119 106 +#define FRAME_pain120 107 +#define FRAME_pain121 108 +#define FRAME_pain201 109 +#define FRAME_pain202 110 +#define FRAME_pain203 111 +#define FRAME_pain204 112 +#define FRAME_pain205 113 +#define FRAME_pain206 114 +#define FRAME_pain207 115 +#define FRAME_pain208 116 +#define FRAME_pain301 117 +#define FRAME_pain302 118 +#define FRAME_pain303 119 +#define FRAME_pain304 120 +#define FRAME_pain305 121 +#define FRAME_pain306 122 +#define FRAME_death101 123 +#define FRAME_death102 124 +#define FRAME_death103 125 +#define FRAME_death104 126 +#define FRAME_death105 127 +#define FRAME_death106 128 +#define FRAME_death107 129 +#define FRAME_death108 130 +#define FRAME_death109 131 +#define FRAME_death110 132 +#define FRAME_death111 133 +#define FRAME_death112 134 +#define FRAME_death113 135 +#define FRAME_death114 136 +#define FRAME_death115 137 +#define FRAME_death116 138 +#define FRAME_death117 139 +#define FRAME_death118 140 +#define FRAME_death201 141 +#define FRAME_death202 142 +#define FRAME_death203 143 +#define FRAME_death204 144 +#define FRAME_death205 145 +#define FRAME_duck01 146 +#define FRAME_duck02 147 +#define FRAME_duck03 148 +#define FRAME_duck04 149 +#define FRAME_duck05 150 +#define FRAME_duck06 151 +#define FRAME_duck07 152 +#define FRAME_duck08 153 +#define FRAME_defens01 154 +#define FRAME_defens02 155 +#define FRAME_defens03 156 +#define FRAME_defens04 157 +#define FRAME_defens05 158 +#define FRAME_defens06 159 +#define FRAME_defens07 160 +#define FRAME_defens08 161 +#define FRAME_stand01 162 +#define FRAME_stand02 163 +#define FRAME_stand03 164 +#define FRAME_stand04 165 +#define FRAME_stand05 166 +#define FRAME_stand06 167 +#define FRAME_stand07 168 +#define FRAME_stand08 169 +#define FRAME_stand09 170 +#define FRAME_stand10 171 +#define FRAME_stand11 172 +#define FRAME_stand12 173 +#define FRAME_stand13 174 +#define FRAME_stand14 175 +#define FRAME_stand15 176 +#define FRAME_stand16 177 +#define FRAME_stand17 178 +#define FRAME_stand18 179 +#define FRAME_stand19 180 +#define FRAME_stand20 181 +#define FRAME_stand21 182 +#define FRAME_stand22 183 +#define FRAME_stand23 184 +#define FRAME_stand24 185 +#define FRAME_stand25 186 +#define FRAME_stand26 187 +#define FRAME_stand27 188 +#define FRAME_stand28 189 +#define FRAME_stand29 190 +#define FRAME_stand30 191 +#define FRAME_stand31 192 +#define FRAME_stand32 193 +#define FRAME_stand33 194 +#define FRAME_stand34 195 +#define FRAME_stand35 196 +#define FRAME_stand36 197 +#define FRAME_stand37 198 +#define FRAME_stand38 199 +#define FRAME_stand39 200 +#define FRAME_stand40 201 +#define FRAME_stand41 202 +#define FRAME_stand42 203 +#define FRAME_stand43 204 +#define FRAME_stand44 205 +#define FRAME_stand45 206 +#define FRAME_stand46 207 +#define FRAME_stand47 208 +#define FRAME_stand48 209 +#define FRAME_stand49 210 +#define FRAME_stand50 211 +#define FRAME_stand51 212 +#define FRAME_stand52 213 +#define FRAME_stand53 214 +#define FRAME_stand54 215 +#define FRAME_stand55 216 +#define FRAME_stand56 217 +#define FRAME_stand57 218 +#define FRAME_stand58 219 +#define FRAME_stand59 220 +#define FRAME_stand60 221 + +#define MODEL_SCALE 1.000000 diff --git a/original/baseq2/m_chick.c b/original/baseq2/m_chick.c new file mode 100644 index 0000000..cde1431 --- /dev/null +++ b/original/baseq2/m_chick.c @@ -0,0 +1,677 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +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. + +*/ +/* +============================================================================== + +chick + +============================================================================== +*/ + +#include "g_local.h" +#include "m_chick.h" + +qboolean visible (edict_t *self, edict_t *other); + +void chick_stand (edict_t *self); +void chick_run (edict_t *self); +void chick_reslash(edict_t *self); +void chick_rerocket(edict_t *self); +void chick_attack1(edict_t *self); + +static int sound_missile_prelaunch; +static int sound_missile_launch; +static int sound_melee_swing; +static int sound_melee_hit; +static int sound_missile_reload; +static int sound_death1; +static int sound_death2; +static int sound_fall_down; +static int sound_idle1; +static int sound_idle2; +static int sound_pain1; +static int sound_pain2; +static int sound_pain3; +static int sound_sight; +static int sound_search; + + +void ChickMoan (edict_t *self) +{ + if (random() < 0.5) + gi.sound (self, CHAN_VOICE, sound_idle1, 1, ATTN_IDLE, 0); + else + gi.sound (self, CHAN_VOICE, sound_idle2, 1, ATTN_IDLE, 0); +} + +mframe_t chick_frames_fidget [] = +{ + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, ChickMoan, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL +}; +mmove_t chick_move_fidget = {FRAME_stand201, FRAME_stand230, chick_frames_fidget, chick_stand}; + +void chick_fidget (edict_t *self) +{ + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + return; + if (random() <= 0.3) + self->monsterinfo.currentmove = &chick_move_fidget; +} + +mframe_t chick_frames_stand [] = +{ + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, chick_fidget, + +}; +mmove_t chick_move_stand = {FRAME_stand101, FRAME_stand130, chick_frames_stand, NULL}; + +void chick_stand (edict_t *self) +{ + self->monsterinfo.currentmove = &chick_move_stand; +} + +mframe_t chick_frames_start_run [] = +{ + ai_run, 1, NULL, + ai_run, 0, NULL, + ai_run, 0, NULL, + ai_run, -1, NULL, + ai_run, -1, NULL, + ai_run, 0, NULL, + ai_run, 1, NULL, + ai_run, 3, NULL, + ai_run, 6, NULL, + ai_run, 3, NULL +}; +mmove_t chick_move_start_run = {FRAME_walk01, FRAME_walk10, chick_frames_start_run, chick_run}; + +mframe_t chick_frames_run [] = +{ + ai_run, 6, NULL, + ai_run, 8, NULL, + ai_run, 13, NULL, + ai_run, 5, NULL, + ai_run, 7, NULL, + ai_run, 4, NULL, + ai_run, 11, NULL, + ai_run, 5, NULL, + ai_run, 9, NULL, + ai_run, 7, NULL + +}; + +mmove_t chick_move_run = {FRAME_walk11, FRAME_walk20, chick_frames_run, NULL}; + +mframe_t chick_frames_walk [] = +{ + ai_walk, 6, NULL, + ai_walk, 8, NULL, + ai_walk, 13, NULL, + ai_walk, 5, NULL, + ai_walk, 7, NULL, + ai_walk, 4, NULL, + ai_walk, 11, NULL, + ai_walk, 5, NULL, + ai_walk, 9, NULL, + ai_walk, 7, NULL +}; + +mmove_t chick_move_walk = {FRAME_walk11, FRAME_walk20, chick_frames_walk, NULL}; + +void chick_walk (edict_t *self) +{ + self->monsterinfo.currentmove = &chick_move_walk; +} + +void chick_run (edict_t *self) +{ + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + { + self->monsterinfo.currentmove = &chick_move_stand; + return; + } + + if (self->monsterinfo.currentmove == &chick_move_walk || + self->monsterinfo.currentmove == &chick_move_start_run) + { + self->monsterinfo.currentmove = &chick_move_run; + } + else + { + self->monsterinfo.currentmove = &chick_move_start_run; + } +} + +mframe_t chick_frames_pain1 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t chick_move_pain1 = {FRAME_pain101, FRAME_pain105, chick_frames_pain1, chick_run}; + +mframe_t chick_frames_pain2 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t chick_move_pain2 = {FRAME_pain201, FRAME_pain205, chick_frames_pain2, chick_run}; + +mframe_t chick_frames_pain3 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, -6, NULL, + ai_move, 3, NULL, + ai_move, 11, NULL, + ai_move, 3, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 4, NULL, + ai_move, 1, NULL, + ai_move, 0, NULL, + ai_move, -3, NULL, + ai_move, -4, NULL, + ai_move, 5, NULL, + ai_move, 7, NULL, + ai_move, -2, NULL, + ai_move, 3, NULL, + ai_move, -5, NULL, + ai_move, -2, NULL, + ai_move, -8, NULL, + ai_move, 2, NULL +}; +mmove_t chick_move_pain3 = {FRAME_pain301, FRAME_pain321, chick_frames_pain3, chick_run}; + +void chick_pain (edict_t *self, edict_t *other, float kick, int damage) +{ + float r; + + if (self->health < (self->max_health / 2)) + self->s.skinnum = 1; + + if (level.time < self->pain_debounce_time) + return; + + self->pain_debounce_time = level.time + 3; + + r = random(); + if (r < 0.33) + gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); + else if (r < 0.66) + gi.sound (self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0); + else + gi.sound (self, CHAN_VOICE, sound_pain3, 1, ATTN_NORM, 0); + + if (skill->value == 3) + return; // no pain anims in nightmare + + if (damage <= 10) + self->monsterinfo.currentmove = &chick_move_pain1; + else if (damage <= 25) + self->monsterinfo.currentmove = &chick_move_pain2; + else + self->monsterinfo.currentmove = &chick_move_pain3; +} + +void chick_dead (edict_t *self) +{ + VectorSet (self->mins, -16, -16, 0); + VectorSet (self->maxs, 16, 16, 16); + self->movetype = MOVETYPE_TOSS; + self->svflags |= SVF_DEADMONSTER; + self->nextthink = 0; + gi.linkentity (self); +} + +mframe_t chick_frames_death2 [] = +{ + ai_move, -6, NULL, + ai_move, 0, NULL, + ai_move, -1, NULL, + ai_move, -5, NULL, + ai_move, 0, NULL, + ai_move, -1, NULL, + ai_move, -2, NULL, + ai_move, 1, NULL, + ai_move, 10, NULL, + ai_move, 2, NULL, + ai_move, 3, NULL, + ai_move, 1, NULL, + ai_move, 2, NULL, + ai_move, 0, NULL, + ai_move, 3, NULL, + ai_move, 3, NULL, + ai_move, 1, NULL, + ai_move, -3, NULL, + ai_move, -5, NULL, + ai_move, 4, NULL, + ai_move, 15, NULL, + ai_move, 14, NULL, + ai_move, 1, NULL +}; +mmove_t chick_move_death2 = {FRAME_death201, FRAME_death223, chick_frames_death2, chick_dead}; + +mframe_t chick_frames_death1 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, -7, NULL, + ai_move, 4, NULL, + ai_move, 11, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL + +}; +mmove_t chick_move_death1 = {FRAME_death101, FRAME_death112, chick_frames_death1, chick_dead}; + +void chick_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + int n; + +// check for gib + if (self->health <= self->gib_health) + { + gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0); + for (n= 0; n < 2; n++) + ThrowGib (self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC); + for (n= 0; n < 4; n++) + ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); + ThrowHead (self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC); + self->deadflag = DEAD_DEAD; + return; + } + + if (self->deadflag == DEAD_DEAD) + return; + +// regular death + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_YES; + + n = rand() % 2; + if (n == 0) + { + self->monsterinfo.currentmove = &chick_move_death1; + gi.sound (self, CHAN_VOICE, sound_death1, 1, ATTN_NORM, 0); + } + else + { + self->monsterinfo.currentmove = &chick_move_death2; + gi.sound (self, CHAN_VOICE, sound_death2, 1, ATTN_NORM, 0); + } +} + + +void chick_duck_down (edict_t *self) +{ + if (self->monsterinfo.aiflags & AI_DUCKED) + return; + self->monsterinfo.aiflags |= AI_DUCKED; + self->maxs[2] -= 32; + self->takedamage = DAMAGE_YES; + self->monsterinfo.pausetime = level.time + 1; + gi.linkentity (self); +} + +void chick_duck_hold (edict_t *self) +{ + if (level.time >= self->monsterinfo.pausetime) + self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; + else + self->monsterinfo.aiflags |= AI_HOLD_FRAME; +} + +void chick_duck_up (edict_t *self) +{ + self->monsterinfo.aiflags &= ~AI_DUCKED; + self->maxs[2] += 32; + self->takedamage = DAMAGE_AIM; + gi.linkentity (self); +} + +mframe_t chick_frames_duck [] = +{ + ai_move, 0, chick_duck_down, + ai_move, 1, NULL, + ai_move, 4, chick_duck_hold, + ai_move, -4, NULL, + ai_move, -5, chick_duck_up, + ai_move, 3, NULL, + ai_move, 1, NULL +}; +mmove_t chick_move_duck = {FRAME_duck01, FRAME_duck07, chick_frames_duck, chick_run}; + +void chick_dodge (edict_t *self, edict_t *attacker, float eta) +{ + if (random() > 0.25) + return; + + if (!self->enemy) + self->enemy = attacker; + + self->monsterinfo.currentmove = &chick_move_duck; +} + +void ChickSlash (edict_t *self) +{ + vec3_t aim; + + VectorSet (aim, MELEE_DISTANCE, self->mins[0], 10); + gi.sound (self, CHAN_WEAPON, sound_melee_swing, 1, ATTN_NORM, 0); + fire_hit (self, aim, (10 + (rand() %6)), 100); +} + + +void ChickRocket (edict_t *self) +{ + vec3_t forward, right; + vec3_t start; + vec3_t dir; + vec3_t vec; + + AngleVectors (self->s.angles, forward, right, NULL); + G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_CHICK_ROCKET_1], forward, right, start); + + VectorCopy (self->enemy->s.origin, vec); + vec[2] += self->enemy->viewheight; + VectorSubtract (vec, start, dir); + VectorNormalize (dir); + + monster_fire_rocket (self, start, dir, 50, 500, MZ2_CHICK_ROCKET_1); +} + +void Chick_PreAttack1 (edict_t *self) +{ + gi.sound (self, CHAN_VOICE, sound_missile_prelaunch, 1, ATTN_NORM, 0); +} + +void ChickReload (edict_t *self) +{ + gi.sound (self, CHAN_VOICE, sound_missile_reload, 1, ATTN_NORM, 0); +} + + +mframe_t chick_frames_start_attack1 [] = +{ + ai_charge, 0, Chick_PreAttack1, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 4, NULL, + ai_charge, 0, NULL, + ai_charge, -3, NULL, + ai_charge, 3, NULL, + ai_charge, 5, NULL, + ai_charge, 7, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, chick_attack1 +}; +mmove_t chick_move_start_attack1 = {FRAME_attak101, FRAME_attak113, chick_frames_start_attack1, NULL}; + + +mframe_t chick_frames_attack1 [] = +{ + ai_charge, 19, ChickRocket, + ai_charge, -6, NULL, + ai_charge, -5, NULL, + ai_charge, -2, NULL, + ai_charge, -7, NULL, + ai_charge, 0, NULL, + ai_charge, 1, NULL, + ai_charge, 10, ChickReload, + ai_charge, 4, NULL, + ai_charge, 5, NULL, + ai_charge, 6, NULL, + ai_charge, 6, NULL, + ai_charge, 4, NULL, + ai_charge, 3, chick_rerocket + +}; +mmove_t chick_move_attack1 = {FRAME_attak114, FRAME_attak127, chick_frames_attack1, NULL}; + +mframe_t chick_frames_end_attack1 [] = +{ + ai_charge, -3, NULL, + ai_charge, 0, NULL, + ai_charge, -6, NULL, + ai_charge, -4, NULL, + ai_charge, -2, NULL +}; +mmove_t chick_move_end_attack1 = {FRAME_attak128, FRAME_attak132, chick_frames_end_attack1, chick_run}; + +void chick_rerocket(edict_t *self) +{ + if (self->enemy->health > 0) + { + if (range (self, self->enemy) > RANGE_MELEE) + if ( visible (self, self->enemy) ) + if (random() <= 0.6) + { + self->monsterinfo.currentmove = &chick_move_attack1; + return; + } + } + self->monsterinfo.currentmove = &chick_move_end_attack1; +} + +void chick_attack1(edict_t *self) +{ + self->monsterinfo.currentmove = &chick_move_attack1; +} + +mframe_t chick_frames_slash [] = +{ + ai_charge, 1, NULL, + ai_charge, 7, ChickSlash, + ai_charge, -7, NULL, + ai_charge, 1, NULL, + ai_charge, -1, NULL, + ai_charge, 1, NULL, + ai_charge, 0, NULL, + ai_charge, 1, NULL, + ai_charge, -2, chick_reslash +}; +mmove_t chick_move_slash = {FRAME_attak204, FRAME_attak212, chick_frames_slash, NULL}; + +mframe_t chick_frames_end_slash [] = +{ + ai_charge, -6, NULL, + ai_charge, -1, NULL, + ai_charge, -6, NULL, + ai_charge, 0, NULL +}; +mmove_t chick_move_end_slash = {FRAME_attak213, FRAME_attak216, chick_frames_end_slash, chick_run}; + + +void chick_reslash(edict_t *self) +{ + if (self->enemy->health > 0) + { + if (range (self, self->enemy) == RANGE_MELEE) + if (random() <= 0.9) + { + self->monsterinfo.currentmove = &chick_move_slash; + return; + } + else + { + self->monsterinfo.currentmove = &chick_move_end_slash; + return; + } + } + self->monsterinfo.currentmove = &chick_move_end_slash; +} + +void chick_slash(edict_t *self) +{ + self->monsterinfo.currentmove = &chick_move_slash; +} + + +mframe_t chick_frames_start_slash [] = +{ + ai_charge, 1, NULL, + ai_charge, 8, NULL, + ai_charge, 3, NULL +}; +mmove_t chick_move_start_slash = {FRAME_attak201, FRAME_attak203, chick_frames_start_slash, chick_slash}; + + + +void chick_melee(edict_t *self) +{ + self->monsterinfo.currentmove = &chick_move_start_slash; +} + + +void chick_attack(edict_t *self) +{ + self->monsterinfo.currentmove = &chick_move_start_attack1; +} + +void chick_sight(edict_t *self, edict_t *other) +{ + gi.sound (self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); +} + +/*QUAKED monster_chick (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight +*/ +void SP_monster_chick (edict_t *self) +{ + if (deathmatch->value) + { + G_FreeEdict (self); + return; + } + + sound_missile_prelaunch = gi.soundindex ("chick/chkatck1.wav"); + sound_missile_launch = gi.soundindex ("chick/chkatck2.wav"); + sound_melee_swing = gi.soundindex ("chick/chkatck3.wav"); + sound_melee_hit = gi.soundindex ("chick/chkatck4.wav"); + sound_missile_reload = gi.soundindex ("chick/chkatck5.wav"); + sound_death1 = gi.soundindex ("chick/chkdeth1.wav"); + sound_death2 = gi.soundindex ("chick/chkdeth2.wav"); + sound_fall_down = gi.soundindex ("chick/chkfall1.wav"); + sound_idle1 = gi.soundindex ("chick/chkidle1.wav"); + sound_idle2 = gi.soundindex ("chick/chkidle2.wav"); + sound_pain1 = gi.soundindex ("chick/chkpain1.wav"); + sound_pain2 = gi.soundindex ("chick/chkpain2.wav"); + sound_pain3 = gi.soundindex ("chick/chkpain3.wav"); + sound_sight = gi.soundindex ("chick/chksght1.wav"); + sound_search = gi.soundindex ("chick/chksrch1.wav"); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex ("models/monsters/bitch/tris.md2"); + VectorSet (self->mins, -16, -16, 0); + VectorSet (self->maxs, 16, 16, 56); + + self->health = 175; + self->gib_health = -70; + self->mass = 200; + + self->pain = chick_pain; + self->die = chick_die; + + self->monsterinfo.stand = chick_stand; + self->monsterinfo.walk = chick_walk; + self->monsterinfo.run = chick_run; + self->monsterinfo.dodge = chick_dodge; + self->monsterinfo.attack = chick_attack; + self->monsterinfo.melee = chick_melee; + self->monsterinfo.sight = chick_sight; + + gi.linkentity (self); + + self->monsterinfo.currentmove = &chick_move_stand; + self->monsterinfo.scale = MODEL_SCALE; + + walkmonster_start (self); +} diff --git a/original/baseq2/m_chick.h b/original/baseq2/m_chick.h new file mode 100644 index 0000000..a7b5c9f --- /dev/null +++ b/original/baseq2/m_chick.h @@ -0,0 +1,313 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +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. + +*/ +// G:\quake2\baseq2\models/monsters/bitch + +// This file generated by qdata - Do NOT Modify + +#define FRAME_attak101 0 +#define FRAME_attak102 1 +#define FRAME_attak103 2 +#define FRAME_attak104 3 +#define FRAME_attak105 4 +#define FRAME_attak106 5 +#define FRAME_attak107 6 +#define FRAME_attak108 7 +#define FRAME_attak109 8 +#define FRAME_attak110 9 +#define FRAME_attak111 10 +#define FRAME_attak112 11 +#define FRAME_attak113 12 +#define FRAME_attak114 13 +#define FRAME_attak115 14 +#define FRAME_attak116 15 +#define FRAME_attak117 16 +#define FRAME_attak118 17 +#define FRAME_attak119 18 +#define FRAME_attak120 19 +#define FRAME_attak121 20 +#define FRAME_attak122 21 +#define FRAME_attak123 22 +#define FRAME_attak124 23 +#define FRAME_attak125 24 +#define FRAME_attak126 25 +#define FRAME_attak127 26 +#define FRAME_attak128 27 +#define FRAME_attak129 28 +#define FRAME_attak130 29 +#define FRAME_attak131 30 +#define FRAME_attak132 31 +#define FRAME_attak201 32 +#define FRAME_attak202 33 +#define FRAME_attak203 34 +#define FRAME_attak204 35 +#define FRAME_attak205 36 +#define FRAME_attak206 37 +#define FRAME_attak207 38 +#define FRAME_attak208 39 +#define FRAME_attak209 40 +#define FRAME_attak210 41 +#define FRAME_attak211 42 +#define FRAME_attak212 43 +#define FRAME_attak213 44 +#define FRAME_attak214 45 +#define FRAME_attak215 46 +#define FRAME_attak216 47 +#define FRAME_death101 48 +#define FRAME_death102 49 +#define FRAME_death103 50 +#define FRAME_death104 51 +#define FRAME_death105 52 +#define FRAME_death106 53 +#define FRAME_death107 54 +#define FRAME_death108 55 +#define FRAME_death109 56 +#define FRAME_death110 57 +#define FRAME_death111 58 +#define FRAME_death112 59 +#define FRAME_death201 60 +#define FRAME_death202 61 +#define FRAME_death203 62 +#define FRAME_death204 63 +#define FRAME_death205 64 +#define FRAME_death206 65 +#define FRAME_death207 66 +#define FRAME_death208 67 +#define FRAME_death209 68 +#define FRAME_death210 69 +#define FRAME_death211 70 +#define FRAME_death212 71 +#define FRAME_death213 72 +#define FRAME_death214 73 +#define FRAME_death215 74 +#define FRAME_death216 75 +#define FRAME_death217 76 +#define FRAME_death218 77 +#define FRAME_death219 78 +#define FRAME_death220 79 +#define FRAME_death221 80 +#define FRAME_death222 81 +#define FRAME_death223 82 +#define FRAME_duck01 83 +#define FRAME_duck02 84 +#define FRAME_duck03 85 +#define FRAME_duck04 86 +#define FRAME_duck05 87 +#define FRAME_duck06 88 +#define FRAME_duck07 89 +#define FRAME_pain101 90 +#define FRAME_pain102 91 +#define FRAME_pain103 92 +#define FRAME_pain104 93 +#define FRAME_pain105 94 +#define FRAME_pain201 95 +#define FRAME_pain202 96 +#define FRAME_pain203 97 +#define FRAME_pain204 98 +#define FRAME_pain205 99 +#define FRAME_pain301 100 +#define FRAME_pain302 101 +#define FRAME_pain303 102 +#define FRAME_pain304 103 +#define FRAME_pain305 104 +#define FRAME_pain306 105 +#define FRAME_pain307 106 +#define FRAME_pain308 107 +#define FRAME_pain309 108 +#define FRAME_pain310 109 +#define FRAME_pain311 110 +#define FRAME_pain312 111 +#define FRAME_pain313 112 +#define FRAME_pain314 113 +#define FRAME_pain315 114 +#define FRAME_pain316 115 +#define FRAME_pain317 116 +#define FRAME_pain318 117 +#define FRAME_pain319 118 +#define FRAME_pain320 119 +#define FRAME_pain321 120 +#define FRAME_stand101 121 +#define FRAME_stand102 122 +#define FRAME_stand103 123 +#define FRAME_stand104 124 +#define FRAME_stand105 125 +#define FRAME_stand106 126 +#define FRAME_stand107 127 +#define FRAME_stand108 128 +#define FRAME_stand109 129 +#define FRAME_stand110 130 +#define FRAME_stand111 131 +#define FRAME_stand112 132 +#define FRAME_stand113 133 +#define FRAME_stand114 134 +#define FRAME_stand115 135 +#define FRAME_stand116 136 +#define FRAME_stand117 137 +#define FRAME_stand118 138 +#define FRAME_stand119 139 +#define FRAME_stand120 140 +#define FRAME_stand121 141 +#define FRAME_stand122 142 +#define FRAME_stand123 143 +#define FRAME_stand124 144 +#define FRAME_stand125 145 +#define FRAME_stand126 146 +#define FRAME_stand127 147 +#define FRAME_stand128 148 +#define FRAME_stand129 149 +#define FRAME_stand130 150 +#define FRAME_stand201 151 +#define FRAME_stand202 152 +#define FRAME_stand203 153 +#define FRAME_stand204 154 +#define FRAME_stand205 155 +#define FRAME_stand206 156 +#define FRAME_stand207 157 +#define FRAME_stand208 158 +#define FRAME_stand209 159 +#define FRAME_stand210 160 +#define FRAME_stand211 161 +#define FRAME_stand212 162 +#define FRAME_stand213 163 +#define FRAME_stand214 164 +#define FRAME_stand215 165 +#define FRAME_stand216 166 +#define FRAME_stand217 167 +#define FRAME_stand218 168 +#define FRAME_stand219 169 +#define FRAME_stand220 170 +#define FRAME_stand221 171 +#define FRAME_stand222 172 +#define FRAME_stand223 173 +#define FRAME_stand224 174 +#define FRAME_stand225 175 +#define FRAME_stand226 176 +#define FRAME_stand227 177 +#define FRAME_stand228 178 +#define FRAME_stand229 179 +#define FRAME_stand230 180 +#define FRAME_walk01 181 +#define FRAME_walk02 182 +#define FRAME_walk03 183 +#define FRAME_walk04 184 +#define FRAME_walk05 185 +#define FRAME_walk06 186 +#define FRAME_walk07 187 +#define FRAME_walk08 188 +#define FRAME_walk09 189 +#define FRAME_walk10 190 +#define FRAME_walk11 191 +#define FRAME_walk12 192 +#define FRAME_walk13 193 +#define FRAME_walk14 194 +#define FRAME_walk15 195 +#define FRAME_walk16 196 +#define FRAME_walk17 197 +#define FRAME_walk18 198 +#define FRAME_walk19 199 +#define FRAME_walk20 200 +#define FRAME_walk21 201 +#define FRAME_walk22 202 +#define FRAME_walk23 203 +#define FRAME_walk24 204 +#define FRAME_walk25 205 +#define FRAME_walk26 206 +#define FRAME_walk27 207 +#define FRAME_recln201 208 +#define FRAME_recln202 209 +#define FRAME_recln203 210 +#define FRAME_recln204 211 +#define FRAME_recln205 212 +#define FRAME_recln206 213 +#define FRAME_recln207 214 +#define FRAME_recln208 215 +#define FRAME_recln209 216 +#define FRAME_recln210 217 +#define FRAME_recln211 218 +#define FRAME_recln212 219 +#define FRAME_recln213 220 +#define FRAME_recln214 221 +#define FRAME_recln215 222 +#define FRAME_recln216 223 +#define FRAME_recln217 224 +#define FRAME_recln218 225 +#define FRAME_recln219 226 +#define FRAME_recln220 227 +#define FRAME_recln221 228 +#define FRAME_recln222 229 +#define FRAME_recln223 230 +#define FRAME_recln224 231 +#define FRAME_recln225 232 +#define FRAME_recln226 233 +#define FRAME_recln227 234 +#define FRAME_recln228 235 +#define FRAME_recln229 236 +#define FRAME_recln230 237 +#define FRAME_recln231 238 +#define FRAME_recln232 239 +#define FRAME_recln233 240 +#define FRAME_recln234 241 +#define FRAME_recln235 242 +#define FRAME_recln236 243 +#define FRAME_recln237 244 +#define FRAME_recln238 245 +#define FRAME_recln239 246 +#define FRAME_recln240 247 +#define FRAME_recln101 248 +#define FRAME_recln102 249 +#define FRAME_recln103 250 +#define FRAME_recln104 251 +#define FRAME_recln105 252 +#define FRAME_recln106 253 +#define FRAME_recln107 254 +#define FRAME_recln108 255 +#define FRAME_recln109 256 +#define FRAME_recln110 257 +#define FRAME_recln111 258 +#define FRAME_recln112 259 +#define FRAME_recln113 260 +#define FRAME_recln114 261 +#define FRAME_recln115 262 +#define FRAME_recln116 263 +#define FRAME_recln117 264 +#define FRAME_recln118 265 +#define FRAME_recln119 266 +#define FRAME_recln120 267 +#define FRAME_recln121 268 +#define FRAME_recln122 269 +#define FRAME_recln123 270 +#define FRAME_recln124 271 +#define FRAME_recln125 272 +#define FRAME_recln126 273 +#define FRAME_recln127 274 +#define FRAME_recln128 275 +#define FRAME_recln129 276 +#define FRAME_recln130 277 +#define FRAME_recln131 278 +#define FRAME_recln132 279 +#define FRAME_recln133 280 +#define FRAME_recln134 281 +#define FRAME_recln135 282 +#define FRAME_recln136 283 +#define FRAME_recln137 284 +#define FRAME_recln138 285 +#define FRAME_recln139 286 +#define FRAME_recln140 287 + +#define MODEL_SCALE 1.000000 diff --git a/original/baseq2/m_flash.c b/original/baseq2/m_flash.c new file mode 100644 index 0000000..f6b458c --- /dev/null +++ b/original/baseq2/m_flash.c @@ -0,0 +1,488 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +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. + +*/ +// m_flash.c + +#include "q_shared.h" + +// this file is included in both the game dll and quake2, +// the game needs it to source shot locations, the client +// needs it to position muzzle flashes +vec3_t monster_flash_offset [] = +{ +// flash 0 is not used + 0.0, 0.0, 0.0, + +// MZ2_TANK_BLASTER_1 1 + 20.7, -18.5, 28.7, +// MZ2_TANK_BLASTER_2 2 + 16.6, -21.5, 30.1, +// MZ2_TANK_BLASTER_3 3 + 11.8, -23.9, 32.1, +// MZ2_TANK_MACHINEGUN_1 4 + 22.9, -0.7, 25.3, +// MZ2_TANK_MACHINEGUN_2 5 + 22.2, 6.2, 22.3, +// MZ2_TANK_MACHINEGUN_3 6 + 19.4, 13.1, 18.6, +// MZ2_TANK_MACHINEGUN_4 7 + 19.4, 18.8, 18.6, +// MZ2_TANK_MACHINEGUN_5 8 + 17.9, 25.0, 18.6, +// MZ2_TANK_MACHINEGUN_6 9 + 14.1, 30.5, 20.6, +// MZ2_TANK_MACHINEGUN_7 10 + 9.3, 35.3, 22.1, +// MZ2_TANK_MACHINEGUN_8 11 + 4.7, 38.4, 22.1, +// MZ2_TANK_MACHINEGUN_9 12 + -1.1, 40.4, 24.1, +// MZ2_TANK_MACHINEGUN_10 13 + -6.5, 41.2, 24.1, +// MZ2_TANK_MACHINEGUN_11 14 + 3.2, 40.1, 24.7, +// MZ2_TANK_MACHINEGUN_12 15 + 11.7, 36.7, 26.0, +// MZ2_TANK_MACHINEGUN_13 16 + 18.9, 31.3, 26.0, +// MZ2_TANK_MACHINEGUN_14 17 + 24.4, 24.4, 26.4, +// MZ2_TANK_MACHINEGUN_15 18 + 27.1, 17.1, 27.2, +// MZ2_TANK_MACHINEGUN_16 19 + 28.5, 9.1, 28.0, +// MZ2_TANK_MACHINEGUN_17 20 + 27.1, 2.2, 28.0, +// MZ2_TANK_MACHINEGUN_18 21 + 24.9, -2.8, 28.0, +// MZ2_TANK_MACHINEGUN_19 22 + 21.6, -7.0, 26.4, +// MZ2_TANK_ROCKET_1 23 + 6.2, 29.1, 49.1, +// MZ2_TANK_ROCKET_2 24 + 6.9, 23.8, 49.1, +// MZ2_TANK_ROCKET_3 25 + 8.3, 17.8, 49.5, + +// MZ2_INFANTRY_MACHINEGUN_1 26 + 26.6, 7.1, 13.1, +// MZ2_INFANTRY_MACHINEGUN_2 27 + 18.2, 7.5, 15.4, +// MZ2_INFANTRY_MACHINEGUN_3 28 + 17.2, 10.3, 17.9, +// MZ2_INFANTRY_MACHINEGUN_4 29 + 17.0, 12.8, 20.1, +// MZ2_INFANTRY_MACHINEGUN_5 30 + 15.1, 14.1, 21.8, +// MZ2_INFANTRY_MACHINEGUN_6 31 + 11.8, 17.2, 23.1, +// MZ2_INFANTRY_MACHINEGUN_7 32 + 11.4, 20.2, 21.0, +// MZ2_INFANTRY_MACHINEGUN_8 33 + 9.0, 23.0, 18.9, +// MZ2_INFANTRY_MACHINEGUN_9 34 + 13.9, 18.6, 17.7, +// MZ2_INFANTRY_MACHINEGUN_10 35 + 15.4, 15.6, 15.8, +// MZ2_INFANTRY_MACHINEGUN_11 36 + 10.2, 15.2, 25.1, +// MZ2_INFANTRY_MACHINEGUN_12 37 + -1.9, 15.1, 28.2, +// MZ2_INFANTRY_MACHINEGUN_13 38 + -12.4, 13.0, 20.2, + +// MZ2_SOLDIER_BLASTER_1 39 + 10.6 * 1.2, 7.7 * 1.2, 7.8 * 1.2, +// MZ2_SOLDIER_BLASTER_2 40 + 21.1 * 1.2, 3.6 * 1.2, 19.0 * 1.2, +// MZ2_SOLDIER_SHOTGUN_1 41 + 10.6 * 1.2, 7.7 * 1.2, 7.8 * 1.2, +// MZ2_SOLDIER_SHOTGUN_2 42 + 21.1 * 1.2, 3.6 * 1.2, 19.0 * 1.2, +// MZ2_SOLDIER_MACHINEGUN_1 43 + 10.6 * 1.2, 7.7 * 1.2, 7.8 * 1.2, +// MZ2_SOLDIER_MACHINEGUN_2 44 + 21.1 * 1.2, 3.6 * 1.2, 19.0 * 1.2, + +// MZ2_GUNNER_MACHINEGUN_1 45 + 30.1 * 1.15, 3.9 * 1.15, 19.6 * 1.15, +// MZ2_GUNNER_MACHINEGUN_2 46 + 29.1 * 1.15, 2.5 * 1.15, 20.7 * 1.15, +// MZ2_GUNNER_MACHINEGUN_3 47 + 28.2 * 1.15, 2.5 * 1.15, 22.2 * 1.15, +// MZ2_GUNNER_MACHINEGUN_4 48 + 28.2 * 1.15, 3.6 * 1.15, 22.0 * 1.15, +// MZ2_GUNNER_MACHINEGUN_5 49 + 26.9 * 1.15, 2.0 * 1.15, 23.4 * 1.15, +// MZ2_GUNNER_MACHINEGUN_6 50 + 26.5 * 1.15, 0.6 * 1.15, 20.8 * 1.15, +// MZ2_GUNNER_MACHINEGUN_7 51 + 26.9 * 1.15, 0.5 * 1.15, 21.5 * 1.15, +// MZ2_GUNNER_MACHINEGUN_8 52 + 29.0 * 1.15, 2.4 * 1.15, 19.5 * 1.15, +// MZ2_GUNNER_GRENADE_1 53 + 4.6 * 1.15, -16.8 * 1.15, 7.3 * 1.15, +// MZ2_GUNNER_GRENADE_2 54 + 4.6 * 1.15, -16.8 * 1.15, 7.3 * 1.15, +// MZ2_GUNNER_GRENADE_3 55 + 4.6 * 1.15, -16.8 * 1.15, 7.3 * 1.15, +// MZ2_GUNNER_GRENADE_4 56 + 4.6 * 1.15, -16.8 * 1.15, 7.3 * 1.15, + +// MZ2_CHICK_ROCKET_1 57 +// -24.8, -9.0, 39.0, + 24.8, -9.0, 39.0, // PGM - this was incorrect in Q2 + +// MZ2_FLYER_BLASTER_1 58 + 12.1, 13.4, -14.5, +// MZ2_FLYER_BLASTER_2 59 + 12.1, -7.4, -14.5, + +// MZ2_MEDIC_BLASTER_1 60 + 12.1, 5.4, 16.5, + +// MZ2_GLADIATOR_RAILGUN_1 61 + 30.0, 18.0, 28.0, + +// MZ2_HOVER_BLASTER_1 62 + 32.5, -0.8, 10.0, + +// MZ2_ACTOR_MACHINEGUN_1 63 + 18.4, 7.4, 9.6, + +// MZ2_SUPERTANK_MACHINEGUN_1 64 + 30.0, 30.0, 88.5, +// MZ2_SUPERTANK_MACHINEGUN_2 65 + 30.0, 30.0, 88.5, +// MZ2_SUPERTANK_MACHINEGUN_3 66 + 30.0, 30.0, 88.5, +// MZ2_SUPERTANK_MACHINEGUN_4 67 + 30.0, 30.0, 88.5, +// MZ2_SUPERTANK_MACHINEGUN_5 68 + 30.0, 30.0, 88.5, +// MZ2_SUPERTANK_MACHINEGUN_6 69 + 30.0, 30.0, 88.5, +// MZ2_SUPERTANK_ROCKET_1 70 + 16.0, -22.5, 91.2, +// MZ2_SUPERTANK_ROCKET_2 71 + 16.0, -33.4, 86.7, +// MZ2_SUPERTANK_ROCKET_3 72 + 16.0, -42.8, 83.3, + +// --- Start Xian Stuff --- +// MZ2_BOSS2_MACHINEGUN_L1 73 + 32, -40, 70, +// MZ2_BOSS2_MACHINEGUN_L2 74 + 32, -40, 70, +// MZ2_BOSS2_MACHINEGUN_L3 75 + 32, -40, 70, +// MZ2_BOSS2_MACHINEGUN_L4 76 + 32, -40, 70, +// MZ2_BOSS2_MACHINEGUN_L5 77 + 32, -40, 70, +// --- End Xian Stuff + +// MZ2_BOSS2_ROCKET_1 78 + 22.0, 16.0, 10.0, +// MZ2_BOSS2_ROCKET_2 79 + 22.0, 8.0, 10.0, +// MZ2_BOSS2_ROCKET_3 80 + 22.0, -8.0, 10.0, +// MZ2_BOSS2_ROCKET_4 81 + 22.0, -16.0, 10.0, + +// MZ2_FLOAT_BLASTER_1 82 + 32.5, -0.8, 10, + +// MZ2_SOLDIER_BLASTER_3 83 + 20.8 * 1.2, 10.1 * 1.2, -2.7 * 1.2, +// MZ2_SOLDIER_SHOTGUN_3 84 + 20.8 * 1.2, 10.1 * 1.2, -2.7 * 1.2, +// MZ2_SOLDIER_MACHINEGUN_3 85 + 20.8 * 1.2, 10.1 * 1.2, -2.7 * 1.2, +// MZ2_SOLDIER_BLASTER_4 86 + 7.6 * 1.2, 9.3 * 1.2, 0.8 * 1.2, +// MZ2_SOLDIER_SHOTGUN_4 87 + 7.6 * 1.2, 9.3 * 1.2, 0.8 * 1.2, +// MZ2_SOLDIER_MACHINEGUN_4 88 + 7.6 * 1.2, 9.3 * 1.2, 0.8 * 1.2, +// MZ2_SOLDIER_BLASTER_5 89 + 30.5 * 1.2, 9.9 * 1.2, -18.7 * 1.2, +// MZ2_SOLDIER_SHOTGUN_5 90 + 30.5 * 1.2, 9.9 * 1.2, -18.7 * 1.2, +// MZ2_SOLDIER_MACHINEGUN_5 91 + 30.5 * 1.2, 9.9 * 1.2, -18.7 * 1.2, +// MZ2_SOLDIER_BLASTER_6 92 + 27.6 * 1.2, 3.4 * 1.2, -10.4 * 1.2, +// MZ2_SOLDIER_SHOTGUN_6 93 + 27.6 * 1.2, 3.4 * 1.2, -10.4 * 1.2, +// MZ2_SOLDIER_MACHINEGUN_6 94 + 27.6 * 1.2, 3.4 * 1.2, -10.4 * 1.2, +// MZ2_SOLDIER_BLASTER_7 95 + 28.9 * 1.2, 4.6 * 1.2, -8.1 * 1.2, +// MZ2_SOLDIER_SHOTGUN_7 96 + 28.9 * 1.2, 4.6 * 1.2, -8.1 * 1.2, +// MZ2_SOLDIER_MACHINEGUN_7 97 + 28.9 * 1.2, 4.6 * 1.2, -8.1 * 1.2, +// MZ2_SOLDIER_BLASTER_8 98 +// 34.5 * 1.2, 9.6 * 1.2, 6.1 * 1.2, + 31.5 * 1.2, 9.6 * 1.2, 10.1 * 1.2, +// MZ2_SOLDIER_SHOTGUN_8 99 + 34.5 * 1.2, 9.6 * 1.2, 6.1 * 1.2, +// MZ2_SOLDIER_MACHINEGUN_8 100 + 34.5 * 1.2, 9.6 * 1.2, 6.1 * 1.2, + +// --- Xian shit below --- +// MZ2_MAKRON_BFG 101 + 17, -19.5, 62.9, +// MZ2_MAKRON_BLASTER_1 102 + -3.6, -24.1, 59.5, +// MZ2_MAKRON_BLASTER_2 103 + -1.6, -19.3, 59.5, +// MZ2_MAKRON_BLASTER_3 104 + -0.1, -14.4, 59.5, +// MZ2_MAKRON_BLASTER_4 105 + 2.0, -7.6, 59.5, +// MZ2_MAKRON_BLASTER_5 106 + 3.4, 1.3, 59.5, +// MZ2_MAKRON_BLASTER_6 107 + 3.7, 11.1, 59.5, +// MZ2_MAKRON_BLASTER_7 108 + -0.3, 22.3, 59.5, +// MZ2_MAKRON_BLASTER_8 109 + -6, 33, 59.5, +// MZ2_MAKRON_BLASTER_9 110 + -9.3, 36.4, 59.5, +// MZ2_MAKRON_BLASTER_10 111 + -7, 35, 59.5, +// MZ2_MAKRON_BLASTER_11 112 + -2.1, 29, 59.5, +// MZ2_MAKRON_BLASTER_12 113 + 3.9, 17.3, 59.5, +// MZ2_MAKRON_BLASTER_13 114 + 6.1, 5.8, 59.5, +// MZ2_MAKRON_BLASTER_14 115 + 5.9, -4.4, 59.5, +// MZ2_MAKRON_BLASTER_15 116 + 4.2, -14.1, 59.5, +// MZ2_MAKRON_BLASTER_16 117 + 2.4, -18.8, 59.5, +// MZ2_MAKRON_BLASTER_17 118 + -1.8, -25.5, 59.5, +// MZ2_MAKRON_RAILGUN_1 119 + -17.3, 7.8, 72.4, + +// MZ2_JORG_MACHINEGUN_L1 120 + 78.5, -47.1, 96, +// MZ2_JORG_MACHINEGUN_L2 121 + 78.5, -47.1, 96, +// MZ2_JORG_MACHINEGUN_L3 122 + 78.5, -47.1, 96, +// MZ2_JORG_MACHINEGUN_L4 123 + 78.5, -47.1, 96, +// MZ2_JORG_MACHINEGUN_L5 124 + 78.5, -47.1, 96, +// MZ2_JORG_MACHINEGUN_L6 125 + 78.5, -47.1, 96, +// MZ2_JORG_MACHINEGUN_R1 126 + 78.5, 46.7, 96, +// MZ2_JORG_MACHINEGUN_R2 127 + 78.5, 46.7, 96, +// MZ2_JORG_MACHINEGUN_R3 128 + 78.5, 46.7, 96, +// MZ2_JORG_MACHINEGUN_R4 129 + 78.5, 46.7, 96, +// MZ2_JORG_MACHINEGUN_R5 130 + 78.5, 46.7, 96, +// MZ2_JORG_MACHINEGUN_R6 131 + 78.5, 46.7, 96, +// MZ2_JORG_BFG_1 132 + 6.3, -9, 111.2, + +// MZ2_BOSS2_MACHINEGUN_R1 73 + 32, 40, 70, +// MZ2_BOSS2_MACHINEGUN_R2 74 + 32, 40, 70, +// MZ2_BOSS2_MACHINEGUN_R3 75 + 32, 40, 70, +// MZ2_BOSS2_MACHINEGUN_R4 76 + 32, 40, 70, +// MZ2_BOSS2_MACHINEGUN_R5 77 + 32, 40, 70, + +// --- End Xian Shit --- + +// ROGUE +// note that the above really ends at 137 +// carrier machineguns +// MZ2_CARRIER_MACHINEGUN_L1 + 56, -32, 32, +// MZ2_CARRIER_MACHINEGUN_R1 + 56, 32, 32, +// MZ2_CARRIER_GRENADE + 42, 24, 50, +// MZ2_TURRET_MACHINEGUN 141 + 16, 0, 0, +// MZ2_TURRET_ROCKET 142 + 16, 0, 0, +// MZ2_TURRET_BLASTER 143 + 16, 0, 0, +// MZ2_STALKER_BLASTER 144 + 24, 0, 6, +// MZ2_DAEDALUS_BLASTER 145 + 32.5, -0.8, 10.0, +// MZ2_MEDIC_BLASTER_2 146 + 12.1, 5.4, 16.5, +// MZ2_CARRIER_RAILGUN 147 + 32, 0, 6, +// MZ2_WIDOW_DISRUPTOR 148 + 57.72, 14.50, 88.81, +// MZ2_WIDOW_BLASTER 149 + 56, 32, 32, +// MZ2_WIDOW_RAIL 150 + 62, -20, 84, +// MZ2_WIDOW_PLASMABEAM 151 // PMM - not used! + 32, 0, 6, +// MZ2_CARRIER_MACHINEGUN_L2 152 + 61, -32, 12, +// MZ2_CARRIER_MACHINEGUN_R2 153 + 61, 32, 12, +// MZ2_WIDOW_RAIL_LEFT 154 + 17, -62, 91, +// MZ2_WIDOW_RAIL_RIGHT 155 + 68, 12, 86, +// MZ2_WIDOW_BLASTER_SWEEP1 156 pmm - the sweeps need to be in sequential order + 47.5, 56, 89, +// MZ2_WIDOW_BLASTER_SWEEP2 157 + 54, 52, 91, +// MZ2_WIDOW_BLASTER_SWEEP3 158 + 58, 40, 91, +// MZ2_WIDOW_BLASTER_SWEEP4 159 + 68, 30, 88, +// MZ2_WIDOW_BLASTER_SWEEP5 160 + 74, 20, 88, +// MZ2_WIDOW_BLASTER_SWEEP6 161 + 73, 11, 87, +// MZ2_WIDOW_BLASTER_SWEEP7 162 + 73, 3, 87, +// MZ2_WIDOW_BLASTER_SWEEP8 163 + 70, -12, 87, +// MZ2_WIDOW_BLASTER_SWEEP9 164 + 67, -20, 90, +// MZ2_WIDOW_BLASTER_100 165 + -20, 76, 90, +// MZ2_WIDOW_BLASTER_90 166 + -8, 74, 90, +// MZ2_WIDOW_BLASTER_80 167 + 0, 72, 90, +// MZ2_WIDOW_BLASTER_70 168 d06 + 10, 71, 89, +// MZ2_WIDOW_BLASTER_60 169 d07 + 23, 70, 87, +// MZ2_WIDOW_BLASTER_50 170 d08 + 32, 64, 85, +// MZ2_WIDOW_BLASTER_40 171 + 40, 58, 84, +// MZ2_WIDOW_BLASTER_30 172 d10 + 48, 50, 83, +// MZ2_WIDOW_BLASTER_20 173 + 54, 42, 82, +// MZ2_WIDOW_BLASTER_10 174 d12 + 56, 34, 82, +// MZ2_WIDOW_BLASTER_0 175 + 58, 26, 82, +// MZ2_WIDOW_BLASTER_10L 176 d14 + 60, 16, 82, +// MZ2_WIDOW_BLASTER_20L 177 + 59, 6, 81, +// MZ2_WIDOW_BLASTER_30L 178 d16 + 58, -2, 80, +// MZ2_WIDOW_BLASTER_40L 179 + 57, -10, 79, +// MZ2_WIDOW_BLASTER_50L 180 d18 + 54, -18, 78, +// MZ2_WIDOW_BLASTER_60L 181 + 42, -32, 80, +// MZ2_WIDOW_BLASTER_70L 182 d20 + 36, -40, 78, +// MZ2_WIDOW_RUN_1 183 + 68.4, 10.88, 82.08, +// MZ2_WIDOW_RUN_2 184 + 68.51, 8.64, 85.14, +// MZ2_WIDOW_RUN_3 185 + 68.66, 6.38, 88.78, +// MZ2_WIDOW_RUN_4 186 + 68.73, 5.1, 84.47, +// MZ2_WIDOW_RUN_5 187 + 68.82, 4.79, 80.52, +// MZ2_WIDOW_RUN_6 188 + 68.77, 6.11, 85.37, +// MZ2_WIDOW_RUN_7 189 + 68.67, 7.99, 90.24, +// MZ2_WIDOW_RUN_8 190 + 68.55, 9.54, 87.36, +// MZ2_CARRIER_ROCKET_1 191 + 0, 0, -5, +// MZ2_CARRIER_ROCKET_2 192 + 0, 0, -5, +// MZ2_CARRIER_ROCKET_3 193 + 0, 0, -5, +// MZ2_CARRIER_ROCKET_4 194 + 0, 0, -5, +// MZ2_WIDOW2_BEAMER_1 195 +// 72.13, -17.63, 93.77, + 69.00, -17.63, 93.77, +// MZ2_WIDOW2_BEAMER_2 196 +// 71.46, -17.08, 89.82, + 69.00, -17.08, 89.82, +// MZ2_WIDOW2_BEAMER_3 197 +// 71.47, -18.40, 90.70, + 69.00, -18.40, 90.70, +// MZ2_WIDOW2_BEAMER_4 198 +// 71.96, -18.34, 94.32, + 69.00, -18.34, 94.32, +// MZ2_WIDOW2_BEAMER_5 199 +// 72.25, -18.30, 97.98, + 69.00, -18.30, 97.98, +// MZ2_WIDOW2_BEAM_SWEEP_1 200 + 45.04, -59.02, 92.24, +// MZ2_WIDOW2_BEAM_SWEEP_2 201 + 50.68, -54.70, 91.96, +// MZ2_WIDOW2_BEAM_SWEEP_3 202 + 56.57, -47.72, 91.65, +// MZ2_WIDOW2_BEAM_SWEEP_4 203 + 61.75, -38.75, 91.38, +// MZ2_WIDOW2_BEAM_SWEEP_5 204 + 65.55, -28.76, 91.24, +// MZ2_WIDOW2_BEAM_SWEEP_6 205 + 67.79, -18.90, 91.22, +// MZ2_WIDOW2_BEAM_SWEEP_7 206 + 68.60, -9.52, 91.23, +// MZ2_WIDOW2_BEAM_SWEEP_8 207 + 68.08, 0.18, 91.32, +// MZ2_WIDOW2_BEAM_SWEEP_9 208 + 66.14, 9.79, 91.44, +// MZ2_WIDOW2_BEAM_SWEEP_10 209 + 62.77, 18.91, 91.65, +// MZ2_WIDOW2_BEAM_SWEEP_11 210 + 58.29, 27.11, 92.00, + +// end of table + 0.0, 0.0, 0.0 +}; diff --git a/original/baseq2/m_flipper.c b/original/baseq2/m_flipper.c new file mode 100644 index 0000000..6c62329 --- /dev/null +++ b/original/baseq2/m_flipper.c @@ -0,0 +1,403 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +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. + +*/ +/* +============================================================================== + +FLIPPER + +============================================================================== +*/ + +#include "g_local.h" +#include "m_flipper.h" + + +static int sound_chomp; +static int sound_attack; +static int sound_pain1; +static int sound_pain2; +static int sound_death; +static int sound_idle; +static int sound_search; +static int sound_sight; + + +void flipper_stand (edict_t *self); + +mframe_t flipper_frames_stand [] = +{ + ai_stand, 0, NULL +}; + +mmove_t flipper_move_stand = {FRAME_flphor01, FRAME_flphor01, flipper_frames_stand, NULL}; + +void flipper_stand (edict_t *self) +{ + self->monsterinfo.currentmove = &flipper_move_stand; +} + +#define FLIPPER_RUN_SPEED 24 + +mframe_t flipper_frames_run [] = +{ + ai_run, FLIPPER_RUN_SPEED, NULL, // 6 + ai_run, FLIPPER_RUN_SPEED, NULL, + ai_run, FLIPPER_RUN_SPEED, NULL, + ai_run, FLIPPER_RUN_SPEED, NULL, + ai_run, FLIPPER_RUN_SPEED, NULL, // 10 + + ai_run, FLIPPER_RUN_SPEED, NULL, + ai_run, FLIPPER_RUN_SPEED, NULL, + ai_run, FLIPPER_RUN_SPEED, NULL, + ai_run, FLIPPER_RUN_SPEED, NULL, + ai_run, FLIPPER_RUN_SPEED, NULL, + ai_run, FLIPPER_RUN_SPEED, NULL, + ai_run, FLIPPER_RUN_SPEED, NULL, + ai_run, FLIPPER_RUN_SPEED, NULL, + ai_run, FLIPPER_RUN_SPEED, NULL, + ai_run, FLIPPER_RUN_SPEED, NULL, // 20 + + ai_run, FLIPPER_RUN_SPEED, NULL, + ai_run, FLIPPER_RUN_SPEED, NULL, + ai_run, FLIPPER_RUN_SPEED, NULL, + ai_run, FLIPPER_RUN_SPEED, NULL, + ai_run, FLIPPER_RUN_SPEED, NULL, + ai_run, FLIPPER_RUN_SPEED, NULL, + ai_run, FLIPPER_RUN_SPEED, NULL, + ai_run, FLIPPER_RUN_SPEED, NULL, + ai_run, FLIPPER_RUN_SPEED, NULL // 29 +}; +mmove_t flipper_move_run_loop = {FRAME_flpver06, FRAME_flpver29, flipper_frames_run, NULL}; + +void flipper_run_loop (edict_t *self) +{ + self->monsterinfo.currentmove = &flipper_move_run_loop; +} + +mframe_t flipper_frames_run_start [] = +{ + ai_run, 8, NULL, + ai_run, 8, NULL, + ai_run, 8, NULL, + ai_run, 8, NULL, + ai_run, 8, NULL, + ai_run, 8, NULL +}; +mmove_t flipper_move_run_start = {FRAME_flpver01, FRAME_flpver06, flipper_frames_run_start, flipper_run_loop}; + +void flipper_run (edict_t *self) +{ + self->monsterinfo.currentmove = &flipper_move_run_start; +} + +/* Standard Swimming */ +mframe_t flipper_frames_walk [] = +{ + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL +}; +mmove_t flipper_move_walk = {FRAME_flphor01, FRAME_flphor24, flipper_frames_walk, NULL}; + +void flipper_walk (edict_t *self) +{ + self->monsterinfo.currentmove = &flipper_move_walk; +} + +mframe_t flipper_frames_start_run [] = +{ + ai_run, 8, NULL, + ai_run, 8, NULL, + ai_run, 8, NULL, + ai_run, 8, NULL, + ai_run, 8, flipper_run +}; +mmove_t flipper_move_start_run = {FRAME_flphor01, FRAME_flphor05, flipper_frames_start_run, NULL}; + +void flipper_start_run (edict_t *self) +{ + self->monsterinfo.currentmove = &flipper_move_start_run; +} + +mframe_t flipper_frames_pain2 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t flipper_move_pain2 = {FRAME_flppn101, FRAME_flppn105, flipper_frames_pain2, flipper_run}; + +mframe_t flipper_frames_pain1 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t flipper_move_pain1 = {FRAME_flppn201, FRAME_flppn205, flipper_frames_pain1, flipper_run}; + +void flipper_bite (edict_t *self) +{ + vec3_t aim; + + VectorSet (aim, MELEE_DISTANCE, 0, 0); + fire_hit (self, aim, 5, 0); +} + +void flipper_preattack (edict_t *self) +{ + gi.sound (self, CHAN_WEAPON, sound_chomp, 1, ATTN_NORM, 0); +} + +mframe_t flipper_frames_attack [] = +{ + ai_charge, 0, flipper_preattack, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, flipper_bite, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, flipper_bite, + ai_charge, 0, NULL +}; +mmove_t flipper_move_attack = {FRAME_flpbit01, FRAME_flpbit20, flipper_frames_attack, flipper_run}; + +void flipper_melee(edict_t *self) +{ + self->monsterinfo.currentmove = &flipper_move_attack; +} + +void flipper_pain (edict_t *self, edict_t *other, float kick, int damage) +{ + int n; + + if (self->health < (self->max_health / 2)) + self->s.skinnum = 1; + + if (level.time < self->pain_debounce_time) + return; + + self->pain_debounce_time = level.time + 3; + + if (skill->value == 3) + return; // no pain anims in nightmare + + n = (rand() + 1) % 2; + if (n == 0) + { + gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &flipper_move_pain1; + } + else + { + gi.sound (self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &flipper_move_pain2; + } +} + +void flipper_dead (edict_t *self) +{ + VectorSet (self->mins, -16, -16, -24); + VectorSet (self->maxs, 16, 16, -8); + self->movetype = MOVETYPE_TOSS; + self->svflags |= SVF_DEADMONSTER; + self->nextthink = 0; + gi.linkentity (self); +} + +mframe_t flipper_frames_death [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t flipper_move_death = {FRAME_flpdth01, FRAME_flpdth56, flipper_frames_death, flipper_dead}; + +void flipper_sight (edict_t *self, edict_t *other) +{ + gi.sound (self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); +} + +void flipper_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + int n; + +// check for gib + if (self->health <= self->gib_health) + { + gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0); + for (n= 0; n < 2; n++) + ThrowGib (self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC); + for (n= 0; n < 2; n++) + ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); + ThrowHead (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); + self->deadflag = DEAD_DEAD; + return; + } + + if (self->deadflag == DEAD_DEAD) + return; + +// regular death + gi.sound (self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0); + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_YES; + self->monsterinfo.currentmove = &flipper_move_death; +} + +/*QUAKED monster_flipper (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight +*/ +void SP_monster_flipper (edict_t *self) +{ + if (deathmatch->value) + { + G_FreeEdict (self); + return; + } + + sound_pain1 = gi.soundindex ("flipper/flppain1.wav"); + sound_pain2 = gi.soundindex ("flipper/flppain2.wav"); + sound_death = gi.soundindex ("flipper/flpdeth1.wav"); + sound_chomp = gi.soundindex ("flipper/flpatck1.wav"); + sound_attack = gi.soundindex ("flipper/flpatck2.wav"); + sound_idle = gi.soundindex ("flipper/flpidle1.wav"); + sound_search = gi.soundindex ("flipper/flpsrch1.wav"); + sound_sight = gi.soundindex ("flipper/flpsght1.wav"); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex ("models/monsters/flipper/tris.md2"); + VectorSet (self->mins, -16, -16, 0); + VectorSet (self->maxs, 16, 16, 32); + + self->health = 50; + self->gib_health = -30; + self->mass = 100; + + self->pain = flipper_pain; + self->die = flipper_die; + + self->monsterinfo.stand = flipper_stand; + self->monsterinfo.walk = flipper_walk; + self->monsterinfo.run = flipper_start_run; + self->monsterinfo.melee = flipper_melee; + self->monsterinfo.sight = flipper_sight; + + gi.linkentity (self); + + self->monsterinfo.currentmove = &flipper_move_stand; + self->monsterinfo.scale = MODEL_SCALE; + + swimmonster_start (self); +} diff --git a/original/baseq2/m_flipper.h b/original/baseq2/m_flipper.h new file mode 100644 index 0000000..24522c0 --- /dev/null +++ b/original/baseq2/m_flipper.h @@ -0,0 +1,185 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +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. + +*/ +// G:\quake2\baseq2\models/monsters/flipper + +// This file generated by ModelGen - Do NOT Modify + +#define FRAME_flpbit01 0 +#define FRAME_flpbit02 1 +#define FRAME_flpbit03 2 +#define FRAME_flpbit04 3 +#define FRAME_flpbit05 4 +#define FRAME_flpbit06 5 +#define FRAME_flpbit07 6 +#define FRAME_flpbit08 7 +#define FRAME_flpbit09 8 +#define FRAME_flpbit10 9 +#define FRAME_flpbit11 10 +#define FRAME_flpbit12 11 +#define FRAME_flpbit13 12 +#define FRAME_flpbit14 13 +#define FRAME_flpbit15 14 +#define FRAME_flpbit16 15 +#define FRAME_flpbit17 16 +#define FRAME_flpbit18 17 +#define FRAME_flpbit19 18 +#define FRAME_flpbit20 19 +#define FRAME_flptal01 20 +#define FRAME_flptal02 21 +#define FRAME_flptal03 22 +#define FRAME_flptal04 23 +#define FRAME_flptal05 24 +#define FRAME_flptal06 25 +#define FRAME_flptal07 26 +#define FRAME_flptal08 27 +#define FRAME_flptal09 28 +#define FRAME_flptal10 29 +#define FRAME_flptal11 30 +#define FRAME_flptal12 31 +#define FRAME_flptal13 32 +#define FRAME_flptal14 33 +#define FRAME_flptal15 34 +#define FRAME_flptal16 35 +#define FRAME_flptal17 36 +#define FRAME_flptal18 37 +#define FRAME_flptal19 38 +#define FRAME_flptal20 39 +#define FRAME_flptal21 40 +#define FRAME_flphor01 41 +#define FRAME_flphor02 42 +#define FRAME_flphor03 43 +#define FRAME_flphor04 44 +#define FRAME_flphor05 45 +#define FRAME_flphor06 46 +#define FRAME_flphor07 47 +#define FRAME_flphor08 48 +#define FRAME_flphor09 49 +#define FRAME_flphor10 50 +#define FRAME_flphor11 51 +#define FRAME_flphor12 52 +#define FRAME_flphor13 53 +#define FRAME_flphor14 54 +#define FRAME_flphor15 55 +#define FRAME_flphor16 56 +#define FRAME_flphor17 57 +#define FRAME_flphor18 58 +#define FRAME_flphor19 59 +#define FRAME_flphor20 60 +#define FRAME_flphor21 61 +#define FRAME_flphor22 62 +#define FRAME_flphor23 63 +#define FRAME_flphor24 64 +#define FRAME_flpver01 65 +#define FRAME_flpver02 66 +#define FRAME_flpver03 67 +#define FRAME_flpver04 68 +#define FRAME_flpver05 69 +#define FRAME_flpver06 70 +#define FRAME_flpver07 71 +#define FRAME_flpver08 72 +#define FRAME_flpver09 73 +#define FRAME_flpver10 74 +#define FRAME_flpver11 75 +#define FRAME_flpver12 76 +#define FRAME_flpver13 77 +#define FRAME_flpver14 78 +#define FRAME_flpver15 79 +#define FRAME_flpver16 80 +#define FRAME_flpver17 81 +#define FRAME_flpver18 82 +#define FRAME_flpver19 83 +#define FRAME_flpver20 84 +#define FRAME_flpver21 85 +#define FRAME_flpver22 86 +#define FRAME_flpver23 87 +#define FRAME_flpver24 88 +#define FRAME_flpver25 89 +#define FRAME_flpver26 90 +#define FRAME_flpver27 91 +#define FRAME_flpver28 92 +#define FRAME_flpver29 93 +#define FRAME_flppn101 94 +#define FRAME_flppn102 95 +#define FRAME_flppn103 96 +#define FRAME_flppn104 97 +#define FRAME_flppn105 98 +#define FRAME_flppn201 99 +#define FRAME_flppn202 100 +#define FRAME_flppn203 101 +#define FRAME_flppn204 102 +#define FRAME_flppn205 103 +#define FRAME_flpdth01 104 +#define FRAME_flpdth02 105 +#define FRAME_flpdth03 106 +#define FRAME_flpdth04 107 +#define FRAME_flpdth05 108 +#define FRAME_flpdth06 109 +#define FRAME_flpdth07 110 +#define FRAME_flpdth08 111 +#define FRAME_flpdth09 112 +#define FRAME_flpdth10 113 +#define FRAME_flpdth11 114 +#define FRAME_flpdth12 115 +#define FRAME_flpdth13 116 +#define FRAME_flpdth14 117 +#define FRAME_flpdth15 118 +#define FRAME_flpdth16 119 +#define FRAME_flpdth17 120 +#define FRAME_flpdth18 121 +#define FRAME_flpdth19 122 +#define FRAME_flpdth20 123 +#define FRAME_flpdth21 124 +#define FRAME_flpdth22 125 +#define FRAME_flpdth23 126 +#define FRAME_flpdth24 127 +#define FRAME_flpdth25 128 +#define FRAME_flpdth26 129 +#define FRAME_flpdth27 130 +#define FRAME_flpdth28 131 +#define FRAME_flpdth29 132 +#define FRAME_flpdth30 133 +#define FRAME_flpdth31 134 +#define FRAME_flpdth32 135 +#define FRAME_flpdth33 136 +#define FRAME_flpdth34 137 +#define FRAME_flpdth35 138 +#define FRAME_flpdth36 139 +#define FRAME_flpdth37 140 +#define FRAME_flpdth38 141 +#define FRAME_flpdth39 142 +#define FRAME_flpdth40 143 +#define FRAME_flpdth41 144 +#define FRAME_flpdth42 145 +#define FRAME_flpdth43 146 +#define FRAME_flpdth44 147 +#define FRAME_flpdth45 148 +#define FRAME_flpdth46 149 +#define FRAME_flpdth47 150 +#define FRAME_flpdth48 151 +#define FRAME_flpdth49 152 +#define FRAME_flpdth50 153 +#define FRAME_flpdth51 154 +#define FRAME_flpdth52 155 +#define FRAME_flpdth53 156 +#define FRAME_flpdth54 157 +#define FRAME_flpdth55 158 +#define FRAME_flpdth56 159 + +#define MODEL_SCALE 1.000000 diff --git a/original/baseq2/m_float.c b/original/baseq2/m_float.c new file mode 100644 index 0000000..57f4750 --- /dev/null +++ b/original/baseq2/m_float.c @@ -0,0 +1,663 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +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. + +*/ +/* +============================================================================== + +floater + +============================================================================== +*/ + +#include "g_local.h" +#include "m_float.h" + + +static int sound_attack2; +static int sound_attack3; +static int sound_death1; +static int sound_idle; +static int sound_pain1; +static int sound_pain2; +static int sound_sight; + + +void floater_sight (edict_t *self, edict_t *other) +{ + gi.sound (self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); +} + +void floater_idle (edict_t *self) +{ + gi.sound (self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0); +} + + +//void floater_stand1 (edict_t *self); +void floater_dead (edict_t *self); +void floater_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point); +void floater_run (edict_t *self); +void floater_wham (edict_t *self); +void floater_zap (edict_t *self); + + +void floater_fire_blaster (edict_t *self) +{ + vec3_t start; + vec3_t forward, right; + vec3_t end; + vec3_t dir; + int effect; + + if ((self->s.frame == FRAME_attak104) || (self->s.frame == FRAME_attak107)) + effect = EF_HYPERBLASTER; + else + effect = 0; + AngleVectors (self->s.angles, forward, right, NULL); + G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_FLOAT_BLASTER_1], forward, right, start); + + VectorCopy (self->enemy->s.origin, end); + end[2] += self->enemy->viewheight; + VectorSubtract (end, start, dir); + + monster_fire_blaster (self, start, dir, 1, 1000, MZ2_FLOAT_BLASTER_1, effect); +} + + +mframe_t floater_frames_stand1 [] = +{ + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL +}; +mmove_t floater_move_stand1 = {FRAME_stand101, FRAME_stand152, floater_frames_stand1, NULL}; + +mframe_t floater_frames_stand2 [] = +{ + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL +}; +mmove_t floater_move_stand2 = {FRAME_stand201, FRAME_stand252, floater_frames_stand2, NULL}; + +void floater_stand (edict_t *self) +{ + if (random() <= 0.5) + self->monsterinfo.currentmove = &floater_move_stand1; + else + self->monsterinfo.currentmove = &floater_move_stand2; +} + +mframe_t floater_frames_activate [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t floater_move_activate = {FRAME_actvat01, FRAME_actvat31, floater_frames_activate, NULL}; + +mframe_t floater_frames_attack1 [] = +{ + ai_charge, 0, NULL, // Blaster attack + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, floater_fire_blaster, // BOOM (0, -25.8, 32.5) -- LOOP Starts + ai_charge, 0, floater_fire_blaster, + ai_charge, 0, floater_fire_blaster, + ai_charge, 0, floater_fire_blaster, + ai_charge, 0, floater_fire_blaster, + ai_charge, 0, floater_fire_blaster, + ai_charge, 0, floater_fire_blaster, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL // -- LOOP Ends +}; +mmove_t floater_move_attack1 = {FRAME_attak101, FRAME_attak114, floater_frames_attack1, floater_run}; + +mframe_t floater_frames_attack2 [] = +{ + ai_charge, 0, NULL, // Claws + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, floater_wham, // WHAM (0, -45, 29.6) -- LOOP Starts + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, // -- LOOP Ends + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL +}; +mmove_t floater_move_attack2 = {FRAME_attak201, FRAME_attak225, floater_frames_attack2, floater_run}; + +mframe_t floater_frames_attack3 [] = +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, floater_zap, // -- LOOP Starts + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, // -- LOOP Ends + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL +}; +mmove_t floater_move_attack3 = {FRAME_attak301, FRAME_attak334, floater_frames_attack3, floater_run}; + +mframe_t floater_frames_death [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t floater_move_death = {FRAME_death01, FRAME_death13, floater_frames_death, floater_dead}; + +mframe_t floater_frames_pain1 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t floater_move_pain1 = {FRAME_pain101, FRAME_pain107, floater_frames_pain1, floater_run}; + +mframe_t floater_frames_pain2 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t floater_move_pain2 = {FRAME_pain201, FRAME_pain208, floater_frames_pain2, floater_run}; + +mframe_t floater_frames_pain3 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t floater_move_pain3 = {FRAME_pain301, FRAME_pain312, floater_frames_pain3, floater_run}; + +mframe_t floater_frames_walk [] = +{ + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL +}; +mmove_t floater_move_walk = {FRAME_stand101, FRAME_stand152, floater_frames_walk, NULL}; + +mframe_t floater_frames_run [] = +{ + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL +}; +mmove_t floater_move_run = {FRAME_stand101, FRAME_stand152, floater_frames_run, NULL}; + +void floater_run (edict_t *self) +{ + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + self->monsterinfo.currentmove = &floater_move_stand1; + else + self->monsterinfo.currentmove = &floater_move_run; +} + +void floater_walk (edict_t *self) +{ + self->monsterinfo.currentmove = &floater_move_walk; +} + +void floater_wham (edict_t *self) +{ + static vec3_t aim = {MELEE_DISTANCE, 0, 0}; + gi.sound (self, CHAN_WEAPON, sound_attack3, 1, ATTN_NORM, 0); + fire_hit (self, aim, 5 + rand() % 6, -50); +} + +void floater_zap (edict_t *self) +{ + vec3_t forward, right; + vec3_t origin; + vec3_t dir; + vec3_t offset; + + VectorSubtract (self->enemy->s.origin, self->s.origin, dir); + + AngleVectors (self->s.angles, forward, right, NULL); + //FIXME use a flash and replace these two lines with the commented one + VectorSet (offset, 18.5, -0.9, 10); + G_ProjectSource (self->s.origin, offset, forward, right, origin); +// G_ProjectSource (self->s.origin, monster_flash_offset[flash_number], forward, right, origin); + + gi.sound (self, CHAN_WEAPON, sound_attack2, 1, ATTN_NORM, 0); + + //FIXME use the flash, Luke + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_SPLASH); + gi.WriteByte (32); + gi.WritePosition (origin); + gi.WriteDir (dir); + gi.WriteByte (1); //sparks + gi.multicast (origin, MULTICAST_PVS); + + T_Damage (self->enemy, self, self, dir, self->enemy->s.origin, vec3_origin, 5 + rand() % 6, -10, DAMAGE_ENERGY, MOD_UNKNOWN); +} + +void floater_attack(edict_t *self) +{ + self->monsterinfo.currentmove = &floater_move_attack1; +} + + +void floater_melee(edict_t *self) +{ + if (random() < 0.5) + self->monsterinfo.currentmove = &floater_move_attack3; + else + self->monsterinfo.currentmove = &floater_move_attack2; +} + + +void floater_pain (edict_t *self, edict_t *other, float kick, int damage) +{ + int n; + + if (self->health < (self->max_health / 2)) + self->s.skinnum = 1; + + if (level.time < self->pain_debounce_time) + return; + + self->pain_debounce_time = level.time + 3; + if (skill->value == 3) + return; // no pain anims in nightmare + + n = (rand() + 1) % 3; + if (n == 0) + { + gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &floater_move_pain1; + } + else + { + gi.sound (self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &floater_move_pain2; + } +} + +void floater_dead (edict_t *self) +{ + VectorSet (self->mins, -16, -16, -24); + VectorSet (self->maxs, 16, 16, -8); + self->movetype = MOVETYPE_TOSS; + self->svflags |= SVF_DEADMONSTER; + self->nextthink = 0; + gi.linkentity (self); +} + +void floater_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + gi.sound (self, CHAN_VOICE, sound_death1, 1, ATTN_NORM, 0); + BecomeExplosion1(self); +} + +/*QUAKED monster_floater (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight +*/ +void SP_monster_floater (edict_t *self) +{ + if (deathmatch->value) + { + G_FreeEdict (self); + return; + } + + sound_attack2 = gi.soundindex ("floater/fltatck2.wav"); + sound_attack3 = gi.soundindex ("floater/fltatck3.wav"); + sound_death1 = gi.soundindex ("floater/fltdeth1.wav"); + sound_idle = gi.soundindex ("floater/fltidle1.wav"); + sound_pain1 = gi.soundindex ("floater/fltpain1.wav"); + sound_pain2 = gi.soundindex ("floater/fltpain2.wav"); + sound_sight = gi.soundindex ("floater/fltsght1.wav"); + + gi.soundindex ("floater/fltatck1.wav"); + + self->s.sound = gi.soundindex ("floater/fltsrch1.wav"); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex ("models/monsters/float/tris.md2"); + VectorSet (self->mins, -24, -24, -24); + VectorSet (self->maxs, 24, 24, 32); + + self->health = 200; + self->gib_health = -80; + self->mass = 300; + + self->pain = floater_pain; + self->die = floater_die; + + self->monsterinfo.stand = floater_stand; + self->monsterinfo.walk = floater_walk; + self->monsterinfo.run = floater_run; +// self->monsterinfo.dodge = floater_dodge; + self->monsterinfo.attack = floater_attack; + self->monsterinfo.melee = floater_melee; + self->monsterinfo.sight = floater_sight; + self->monsterinfo.idle = floater_idle; + + gi.linkentity (self); + + if (random() <= 0.5) + self->monsterinfo.currentmove = &floater_move_stand1; + else + self->monsterinfo.currentmove = &floater_move_stand2; + + self->monsterinfo.scale = MODEL_SCALE; + + flymonster_start (self); +} diff --git a/original/baseq2/m_float.h b/original/baseq2/m_float.h new file mode 100644 index 0000000..453e5dc --- /dev/null +++ b/original/baseq2/m_float.h @@ -0,0 +1,273 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +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. + +*/ +// G:\quake2\baseq2\models/monsters/float + +// This file generated by ModelGen - Do NOT Modify + +#define FRAME_actvat01 0 +#define FRAME_actvat02 1 +#define FRAME_actvat03 2 +#define FRAME_actvat04 3 +#define FRAME_actvat05 4 +#define FRAME_actvat06 5 +#define FRAME_actvat07 6 +#define FRAME_actvat08 7 +#define FRAME_actvat09 8 +#define FRAME_actvat10 9 +#define FRAME_actvat11 10 +#define FRAME_actvat12 11 +#define FRAME_actvat13 12 +#define FRAME_actvat14 13 +#define FRAME_actvat15 14 +#define FRAME_actvat16 15 +#define FRAME_actvat17 16 +#define FRAME_actvat18 17 +#define FRAME_actvat19 18 +#define FRAME_actvat20 19 +#define FRAME_actvat21 20 +#define FRAME_actvat22 21 +#define FRAME_actvat23 22 +#define FRAME_actvat24 23 +#define FRAME_actvat25 24 +#define FRAME_actvat26 25 +#define FRAME_actvat27 26 +#define FRAME_actvat28 27 +#define FRAME_actvat29 28 +#define FRAME_actvat30 29 +#define FRAME_actvat31 30 +#define FRAME_attak101 31 +#define FRAME_attak102 32 +#define FRAME_attak103 33 +#define FRAME_attak104 34 +#define FRAME_attak105 35 +#define FRAME_attak106 36 +#define FRAME_attak107 37 +#define FRAME_attak108 38 +#define FRAME_attak109 39 +#define FRAME_attak110 40 +#define FRAME_attak111 41 +#define FRAME_attak112 42 +#define FRAME_attak113 43 +#define FRAME_attak114 44 +#define FRAME_attak201 45 +#define FRAME_attak202 46 +#define FRAME_attak203 47 +#define FRAME_attak204 48 +#define FRAME_attak205 49 +#define FRAME_attak206 50 +#define FRAME_attak207 51 +#define FRAME_attak208 52 +#define FRAME_attak209 53 +#define FRAME_attak210 54 +#define FRAME_attak211 55 +#define FRAME_attak212 56 +#define FRAME_attak213 57 +#define FRAME_attak214 58 +#define FRAME_attak215 59 +#define FRAME_attak216 60 +#define FRAME_attak217 61 +#define FRAME_attak218 62 +#define FRAME_attak219 63 +#define FRAME_attak220 64 +#define FRAME_attak221 65 +#define FRAME_attak222 66 +#define FRAME_attak223 67 +#define FRAME_attak224 68 +#define FRAME_attak225 69 +#define FRAME_attak301 70 +#define FRAME_attak302 71 +#define FRAME_attak303 72 +#define FRAME_attak304 73 +#define FRAME_attak305 74 +#define FRAME_attak306 75 +#define FRAME_attak307 76 +#define FRAME_attak308 77 +#define FRAME_attak309 78 +#define FRAME_attak310 79 +#define FRAME_attak311 80 +#define FRAME_attak312 81 +#define FRAME_attak313 82 +#define FRAME_attak314 83 +#define FRAME_attak315 84 +#define FRAME_attak316 85 +#define FRAME_attak317 86 +#define FRAME_attak318 87 +#define FRAME_attak319 88 +#define FRAME_attak320 89 +#define FRAME_attak321 90 +#define FRAME_attak322 91 +#define FRAME_attak323 92 +#define FRAME_attak324 93 +#define FRAME_attak325 94 +#define FRAME_attak326 95 +#define FRAME_attak327 96 +#define FRAME_attak328 97 +#define FRAME_attak329 98 +#define FRAME_attak330 99 +#define FRAME_attak331 100 +#define FRAME_attak332 101 +#define FRAME_attak333 102 +#define FRAME_attak334 103 +#define FRAME_death01 104 +#define FRAME_death02 105 +#define FRAME_death03 106 +#define FRAME_death04 107 +#define FRAME_death05 108 +#define FRAME_death06 109 +#define FRAME_death07 110 +#define FRAME_death08 111 +#define FRAME_death09 112 +#define FRAME_death10 113 +#define FRAME_death11 114 +#define FRAME_death12 115 +#define FRAME_death13 116 +#define FRAME_pain101 117 +#define FRAME_pain102 118 +#define FRAME_pain103 119 +#define FRAME_pain104 120 +#define FRAME_pain105 121 +#define FRAME_pain106 122 +#define FRAME_pain107 123 +#define FRAME_pain201 124 +#define FRAME_pain202 125 +#define FRAME_pain203 126 +#define FRAME_pain204 127 +#define FRAME_pain205 128 +#define FRAME_pain206 129 +#define FRAME_pain207 130 +#define FRAME_pain208 131 +#define FRAME_pain301 132 +#define FRAME_pain302 133 +#define FRAME_pain303 134 +#define FRAME_pain304 135 +#define FRAME_pain305 136 +#define FRAME_pain306 137 +#define FRAME_pain307 138 +#define FRAME_pain308 139 +#define FRAME_pain309 140 +#define FRAME_pain310 141 +#define FRAME_pain311 142 +#define FRAME_pain312 143 +#define FRAME_stand101 144 +#define FRAME_stand102 145 +#define FRAME_stand103 146 +#define FRAME_stand104 147 +#define FRAME_stand105 148 +#define FRAME_stand106 149 +#define FRAME_stand107 150 +#define FRAME_stand108 151 +#define FRAME_stand109 152 +#define FRAME_stand110 153 +#define FRAME_stand111 154 +#define FRAME_stand112 155 +#define FRAME_stand113 156 +#define FRAME_stand114 157 +#define FRAME_stand115 158 +#define FRAME_stand116 159 +#define FRAME_stand117 160 +#define FRAME_stand118 161 +#define FRAME_stand119 162 +#define FRAME_stand120 163 +#define FRAME_stand121 164 +#define FRAME_stand122 165 +#define FRAME_stand123 166 +#define FRAME_stand124 167 +#define FRAME_stand125 168 +#define FRAME_stand126 169 +#define FRAME_stand127 170 +#define FRAME_stand128 171 +#define FRAME_stand129 172 +#define FRAME_stand130 173 +#define FRAME_stand131 174 +#define FRAME_stand132 175 +#define FRAME_stand133 176 +#define FRAME_stand134 177 +#define FRAME_stand135 178 +#define FRAME_stand136 179 +#define FRAME_stand137 180 +#define FRAME_stand138 181 +#define FRAME_stand139 182 +#define FRAME_stand140 183 +#define FRAME_stand141 184 +#define FRAME_stand142 185 +#define FRAME_stand143 186 +#define FRAME_stand144 187 +#define FRAME_stand145 188 +#define FRAME_stand146 189 +#define FRAME_stand147 190 +#define FRAME_stand148 191 +#define FRAME_stand149 192 +#define FRAME_stand150 193 +#define FRAME_stand151 194 +#define FRAME_stand152 195 +#define FRAME_stand201 196 +#define FRAME_stand202 197 +#define FRAME_stand203 198 +#define FRAME_stand204 199 +#define FRAME_stand205 200 +#define FRAME_stand206 201 +#define FRAME_stand207 202 +#define FRAME_stand208 203 +#define FRAME_stand209 204 +#define FRAME_stand210 205 +#define FRAME_stand211 206 +#define FRAME_stand212 207 +#define FRAME_stand213 208 +#define FRAME_stand214 209 +#define FRAME_stand215 210 +#define FRAME_stand216 211 +#define FRAME_stand217 212 +#define FRAME_stand218 213 +#define FRAME_stand219 214 +#define FRAME_stand220 215 +#define FRAME_stand221 216 +#define FRAME_stand222 217 +#define FRAME_stand223 218 +#define FRAME_stand224 219 +#define FRAME_stand225 220 +#define FRAME_stand226 221 +#define FRAME_stand227 222 +#define FRAME_stand228 223 +#define FRAME_stand229 224 +#define FRAME_stand230 225 +#define FRAME_stand231 226 +#define FRAME_stand232 227 +#define FRAME_stand233 228 +#define FRAME_stand234 229 +#define FRAME_stand235 230 +#define FRAME_stand236 231 +#define FRAME_stand237 232 +#define FRAME_stand238 233 +#define FRAME_stand239 234 +#define FRAME_stand240 235 +#define FRAME_stand241 236 +#define FRAME_stand242 237 +#define FRAME_stand243 238 +#define FRAME_stand244 239 +#define FRAME_stand245 240 +#define FRAME_stand246 241 +#define FRAME_stand247 242 +#define FRAME_stand248 243 +#define FRAME_stand249 244 +#define FRAME_stand250 245 +#define FRAME_stand251 246 +#define FRAME_stand252 247 + +#define MODEL_SCALE 1.000000 diff --git a/original/baseq2/m_flyer.c b/original/baseq2/m_flyer.c new file mode 100644 index 0000000..86409a6 --- /dev/null +++ b/original/baseq2/m_flyer.c @@ -0,0 +1,626 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +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. + +*/ +/* +============================================================================== + +flyer + +============================================================================== +*/ + +#include "g_local.h" +#include "m_flyer.h" + +qboolean visible (edict_t *self, edict_t *other); + +static int nextmove; // Used for start/stop frames + +static int sound_sight; +static int sound_idle; +static int sound_pain1; +static int sound_pain2; +static int sound_slash; +static int sound_sproing; +static int sound_die; + + +void flyer_check_melee(edict_t *self); +void flyer_loop_melee (edict_t *self); +void flyer_melee (edict_t *self); +void flyer_setstart (edict_t *self); +void flyer_stand (edict_t *self); +void flyer_nextmove (edict_t *self); + + +void flyer_sight (edict_t *self, edict_t *other) +{ + gi.sound (self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); +} + +void flyer_idle (edict_t *self) +{ + gi.sound (self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0); +} + +void flyer_pop_blades (edict_t *self) +{ + gi.sound (self, CHAN_VOICE, sound_sproing, 1, ATTN_NORM, 0); +} + + +mframe_t flyer_frames_stand [] = +{ + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL +}; +mmove_t flyer_move_stand = {FRAME_stand01, FRAME_stand45, flyer_frames_stand, NULL}; + + +mframe_t flyer_frames_walk [] = +{ + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL +}; +mmove_t flyer_move_walk = {FRAME_stand01, FRAME_stand45, flyer_frames_walk, NULL}; + +mframe_t flyer_frames_run [] = +{ + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL +}; +mmove_t flyer_move_run = {FRAME_stand01, FRAME_stand45, flyer_frames_run, NULL}; + +void flyer_run (edict_t *self) +{ + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + self->monsterinfo.currentmove = &flyer_move_stand; + else + self->monsterinfo.currentmove = &flyer_move_run; +} + +void flyer_walk (edict_t *self) +{ + self->monsterinfo.currentmove = &flyer_move_walk; +} + +void flyer_stand (edict_t *self) +{ + self->monsterinfo.currentmove = &flyer_move_stand; +} + +mframe_t flyer_frames_start [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, flyer_nextmove +}; +mmove_t flyer_move_start = {FRAME_start01, FRAME_start06, flyer_frames_start, NULL}; + +mframe_t flyer_frames_stop [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, flyer_nextmove +}; +mmove_t flyer_move_stop = {FRAME_stop01, FRAME_stop07, flyer_frames_stop, NULL}; + +void flyer_stop (edict_t *self) +{ + self->monsterinfo.currentmove = &flyer_move_stop; +} + +void flyer_start (edict_t *self) +{ + self->monsterinfo.currentmove = &flyer_move_start; +} + + +mframe_t flyer_frames_rollright [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t flyer_move_rollright = {FRAME_rollr01, FRAME_rollr09, flyer_frames_rollright, NULL}; + +mframe_t flyer_frames_rollleft [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t flyer_move_rollleft = {FRAME_rollf01, FRAME_rollf09, flyer_frames_rollleft, NULL}; + +mframe_t flyer_frames_pain3 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t flyer_move_pain3 = {FRAME_pain301, FRAME_pain304, flyer_frames_pain3, flyer_run}; + +mframe_t flyer_frames_pain2 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t flyer_move_pain2 = {FRAME_pain201, FRAME_pain204, flyer_frames_pain2, flyer_run}; + +mframe_t flyer_frames_pain1 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t flyer_move_pain1 = {FRAME_pain101, FRAME_pain109, flyer_frames_pain1, flyer_run}; + +mframe_t flyer_frames_defense [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, // Hold this frame + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t flyer_move_defense = {FRAME_defens01, FRAME_defens06, flyer_frames_defense, NULL}; + +mframe_t flyer_frames_bankright [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t flyer_move_bankright = {FRAME_bankr01, FRAME_bankr07, flyer_frames_bankright, NULL}; + +mframe_t flyer_frames_bankleft [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t flyer_move_bankleft = {FRAME_bankl01, FRAME_bankl07, flyer_frames_bankleft, NULL}; + + +void flyer_fire (edict_t *self, int flash_number) +{ + vec3_t start; + vec3_t forward, right; + vec3_t end; + vec3_t dir; + int effect; + + if ((self->s.frame == FRAME_attak204) || (self->s.frame == FRAME_attak207) || (self->s.frame == FRAME_attak210)) + effect = EF_HYPERBLASTER; + else + effect = 0; + AngleVectors (self->s.angles, forward, right, NULL); + G_ProjectSource (self->s.origin, monster_flash_offset[flash_number], forward, right, start); + + VectorCopy (self->enemy->s.origin, end); + end[2] += self->enemy->viewheight; + VectorSubtract (end, start, dir); + + monster_fire_blaster (self, start, dir, 1, 1000, flash_number, effect); +} + +void flyer_fireleft (edict_t *self) +{ + flyer_fire (self, MZ2_FLYER_BLASTER_1); +} + +void flyer_fireright (edict_t *self) +{ + flyer_fire (self, MZ2_FLYER_BLASTER_2); +} + + +mframe_t flyer_frames_attack2 [] = +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, -10, flyer_fireleft, // left gun + ai_charge, -10, flyer_fireright, // right gun + ai_charge, -10, flyer_fireleft, // left gun + ai_charge, -10, flyer_fireright, // right gun + ai_charge, -10, flyer_fireleft, // left gun + ai_charge, -10, flyer_fireright, // right gun + ai_charge, -10, flyer_fireleft, // left gun + ai_charge, -10, flyer_fireright, // right gun + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL +}; +mmove_t flyer_move_attack2 = {FRAME_attak201, FRAME_attak217, flyer_frames_attack2, flyer_run}; + + +void flyer_slash_left (edict_t *self) +{ + vec3_t aim; + + VectorSet (aim, MELEE_DISTANCE, self->mins[0], 0); + fire_hit (self, aim, 5, 0); + gi.sound (self, CHAN_WEAPON, sound_slash, 1, ATTN_NORM, 0); +} + +void flyer_slash_right (edict_t *self) +{ + vec3_t aim; + + VectorSet (aim, MELEE_DISTANCE, self->maxs[0], 0); + fire_hit (self, aim, 5, 0); + gi.sound (self, CHAN_WEAPON, sound_slash, 1, ATTN_NORM, 0); +} + +mframe_t flyer_frames_start_melee [] = +{ + ai_charge, 0, flyer_pop_blades, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL +}; +mmove_t flyer_move_start_melee = {FRAME_attak101, FRAME_attak106, flyer_frames_start_melee, flyer_loop_melee}; + +mframe_t flyer_frames_end_melee [] = +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL +}; +mmove_t flyer_move_end_melee = {FRAME_attak119, FRAME_attak121, flyer_frames_end_melee, flyer_run}; + + +mframe_t flyer_frames_loop_melee [] = +{ + ai_charge, 0, NULL, // Loop Start + ai_charge, 0, NULL, + ai_charge, 0, flyer_slash_left, // Left Wing Strike + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, flyer_slash_right, // Right Wing Strike + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL // Loop Ends + +}; +mmove_t flyer_move_loop_melee = {FRAME_attak107, FRAME_attak118, flyer_frames_loop_melee, flyer_check_melee}; + +void flyer_loop_melee (edict_t *self) +{ +/* if (random() <= 0.5) + self->monsterinfo.currentmove = &flyer_move_attack1; + else */ + self->monsterinfo.currentmove = &flyer_move_loop_melee; +} + + + +void flyer_attack (edict_t *self) +{ +/* if (random() <= 0.5) + self->monsterinfo.currentmove = &flyer_move_attack1; + else */ + self->monsterinfo.currentmove = &flyer_move_attack2; +} + +void flyer_setstart (edict_t *self) +{ + nextmove = ACTION_run; + self->monsterinfo.currentmove = &flyer_move_start; +} + +void flyer_nextmove (edict_t *self) +{ + if (nextmove == ACTION_attack1) + self->monsterinfo.currentmove = &flyer_move_start_melee; + else if (nextmove == ACTION_attack2) + self->monsterinfo.currentmove = &flyer_move_attack2; + else if (nextmove == ACTION_run) + self->monsterinfo.currentmove = &flyer_move_run; +} + +void flyer_melee (edict_t *self) +{ +// flyer.nextmove = ACTION_attack1; +// self->monsterinfo.currentmove = &flyer_move_stop; + self->monsterinfo.currentmove = &flyer_move_start_melee; +} + +void flyer_check_melee(edict_t *self) +{ + if (range (self, self->enemy) == RANGE_MELEE) + if (random() <= 0.8) + self->monsterinfo.currentmove = &flyer_move_loop_melee; + else + self->monsterinfo.currentmove = &flyer_move_end_melee; + else + self->monsterinfo.currentmove = &flyer_move_end_melee; +} + +void flyer_pain (edict_t *self, edict_t *other, float kick, int damage) +{ + int n; + + if (self->health < (self->max_health / 2)) + self->s.skinnum = 1; + + if (level.time < self->pain_debounce_time) + return; + + self->pain_debounce_time = level.time + 3; + if (skill->value == 3) + return; // no pain anims in nightmare + + n = rand() % 3; + if (n == 0) + { + gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &flyer_move_pain1; + } + else if (n == 1) + { + gi.sound (self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &flyer_move_pain2; + } + else + { + gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &flyer_move_pain3; + } +} + + +void flyer_die(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + gi.sound (self, CHAN_VOICE, sound_die, 1, ATTN_NORM, 0); + BecomeExplosion1(self); +} + + +/*QUAKED monster_flyer (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight +*/ +void SP_monster_flyer (edict_t *self) +{ + if (deathmatch->value) + { + G_FreeEdict (self); + return; + } + + // fix a map bug in jail5.bsp + if (!Q_stricmp(level.mapname, "jail5") && (self->s.origin[2] == -104)) + { + self->targetname = self->target; + self->target = NULL; + } + + sound_sight = gi.soundindex ("flyer/flysght1.wav"); + sound_idle = gi.soundindex ("flyer/flysrch1.wav"); + sound_pain1 = gi.soundindex ("flyer/flypain1.wav"); + sound_pain2 = gi.soundindex ("flyer/flypain2.wav"); + sound_slash = gi.soundindex ("flyer/flyatck2.wav"); + sound_sproing = gi.soundindex ("flyer/flyatck1.wav"); + sound_die = gi.soundindex ("flyer/flydeth1.wav"); + + gi.soundindex ("flyer/flyatck3.wav"); + + self->s.modelindex = gi.modelindex ("models/monsters/flyer/tris.md2"); + VectorSet (self->mins, -16, -16, -24); + VectorSet (self->maxs, 16, 16, 32); + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + + self->s.sound = gi.soundindex ("flyer/flyidle1.wav"); + + self->health = 50; + self->mass = 50; + + self->pain = flyer_pain; + self->die = flyer_die; + + self->monsterinfo.stand = flyer_stand; + self->monsterinfo.walk = flyer_walk; + self->monsterinfo.run = flyer_run; + self->monsterinfo.attack = flyer_attack; + self->monsterinfo.melee = flyer_melee; + self->monsterinfo.sight = flyer_sight; + self->monsterinfo.idle = flyer_idle; + + gi.linkentity (self); + + self->monsterinfo.currentmove = &flyer_move_stand; + self->monsterinfo.scale = MODEL_SCALE; + + flymonster_start (self); +} diff --git a/original/baseq2/m_flyer.h b/original/baseq2/m_flyer.h new file mode 100644 index 0000000..ebe0806 --- /dev/null +++ b/original/baseq2/m_flyer.h @@ -0,0 +1,182 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +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. + +*/ +// G:\quake2\baseq2\models/monsters/flyer + +// This file generated by ModelGen - Do NOT Modify + +#define ACTION_nothing 0 +#define ACTION_attack1 1 +#define ACTION_attack2 2 +#define ACTION_run 3 +#define ACTION_walk 4 + +#define FRAME_start01 0 +#define FRAME_start02 1 +#define FRAME_start03 2 +#define FRAME_start04 3 +#define FRAME_start05 4 +#define FRAME_start06 5 +#define FRAME_stop01 6 +#define FRAME_stop02 7 +#define FRAME_stop03 8 +#define FRAME_stop04 9 +#define FRAME_stop05 10 +#define FRAME_stop06 11 +#define FRAME_stop07 12 +#define FRAME_stand01 13 +#define FRAME_stand02 14 +#define FRAME_stand03 15 +#define FRAME_stand04 16 +#define FRAME_stand05 17 +#define FRAME_stand06 18 +#define FRAME_stand07 19 +#define FRAME_stand08 20 +#define FRAME_stand09 21 +#define FRAME_stand10 22 +#define FRAME_stand11 23 +#define FRAME_stand12 24 +#define FRAME_stand13 25 +#define FRAME_stand14 26 +#define FRAME_stand15 27 +#define FRAME_stand16 28 +#define FRAME_stand17 29 +#define FRAME_stand18 30 +#define FRAME_stand19 31 +#define FRAME_stand20 32 +#define FRAME_stand21 33 +#define FRAME_stand22 34 +#define FRAME_stand23 35 +#define FRAME_stand24 36 +#define FRAME_stand25 37 +#define FRAME_stand26 38 +#define FRAME_stand27 39 +#define FRAME_stand28 40 +#define FRAME_stand29 41 +#define FRAME_stand30 42 +#define FRAME_stand31 43 +#define FRAME_stand32 44 +#define FRAME_stand33 45 +#define FRAME_stand34 46 +#define FRAME_stand35 47 +#define FRAME_stand36 48 +#define FRAME_stand37 49 +#define FRAME_stand38 50 +#define FRAME_stand39 51 +#define FRAME_stand40 52 +#define FRAME_stand41 53 +#define FRAME_stand42 54 +#define FRAME_stand43 55 +#define FRAME_stand44 56 +#define FRAME_stand45 57 +#define FRAME_attak101 58 +#define FRAME_attak102 59 +#define FRAME_attak103 60 +#define FRAME_attak104 61 +#define FRAME_attak105 62 +#define FRAME_attak106 63 +#define FRAME_attak107 64 +#define FRAME_attak108 65 +#define FRAME_attak109 66 +#define FRAME_attak110 67 +#define FRAME_attak111 68 +#define FRAME_attak112 69 +#define FRAME_attak113 70 +#define FRAME_attak114 71 +#define FRAME_attak115 72 +#define FRAME_attak116 73 +#define FRAME_attak117 74 +#define FRAME_attak118 75 +#define FRAME_attak119 76 +#define FRAME_attak120 77 +#define FRAME_attak121 78 +#define FRAME_attak201 79 +#define FRAME_attak202 80 +#define FRAME_attak203 81 +#define FRAME_attak204 82 +#define FRAME_attak205 83 +#define FRAME_attak206 84 +#define FRAME_attak207 85 +#define FRAME_attak208 86 +#define FRAME_attak209 87 +#define FRAME_attak210 88 +#define FRAME_attak211 89 +#define FRAME_attak212 90 +#define FRAME_attak213 91 +#define FRAME_attak214 92 +#define FRAME_attak215 93 +#define FRAME_attak216 94 +#define FRAME_attak217 95 +#define FRAME_bankl01 96 +#define FRAME_bankl02 97 +#define FRAME_bankl03 98 +#define FRAME_bankl04 99 +#define FRAME_bankl05 100 +#define FRAME_bankl06 101 +#define FRAME_bankl07 102 +#define FRAME_bankr01 103 +#define FRAME_bankr02 104 +#define FRAME_bankr03 105 +#define FRAME_bankr04 106 +#define FRAME_bankr05 107 +#define FRAME_bankr06 108 +#define FRAME_bankr07 109 +#define FRAME_rollf01 110 +#define FRAME_rollf02 111 +#define FRAME_rollf03 112 +#define FRAME_rollf04 113 +#define FRAME_rollf05 114 +#define FRAME_rollf06 115 +#define FRAME_rollf07 116 +#define FRAME_rollf08 117 +#define FRAME_rollf09 118 +#define FRAME_rollr01 119 +#define FRAME_rollr02 120 +#define FRAME_rollr03 121 +#define FRAME_rollr04 122 +#define FRAME_rollr05 123 +#define FRAME_rollr06 124 +#define FRAME_rollr07 125 +#define FRAME_rollr08 126 +#define FRAME_rollr09 127 +#define FRAME_defens01 128 +#define FRAME_defens02 129 +#define FRAME_defens03 130 +#define FRAME_defens04 131 +#define FRAME_defens05 132 +#define FRAME_defens06 133 +#define FRAME_pain101 134 +#define FRAME_pain102 135 +#define FRAME_pain103 136 +#define FRAME_pain104 137 +#define FRAME_pain105 138 +#define FRAME_pain106 139 +#define FRAME_pain107 140 +#define FRAME_pain108 141 +#define FRAME_pain109 142 +#define FRAME_pain201 143 +#define FRAME_pain202 144 +#define FRAME_pain203 145 +#define FRAME_pain204 146 +#define FRAME_pain301 147 +#define FRAME_pain302 148 +#define FRAME_pain303 149 +#define FRAME_pain304 150 + +#define MODEL_SCALE 1.000000 diff --git a/original/baseq2/m_gladiator.c b/original/baseq2/m_gladiator.c new file mode 100644 index 0000000..69556e8 --- /dev/null +++ b/original/baseq2/m_gladiator.c @@ -0,0 +1,387 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +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. + +*/ +/* +============================================================================== + +GLADIATOR + +============================================================================== +*/ + +#include "g_local.h" +#include "m_gladiator.h" + + +static int sound_pain1; +static int sound_pain2; +static int sound_die; +static int sound_gun; +static int sound_cleaver_swing; +static int sound_cleaver_hit; +static int sound_cleaver_miss; +static int sound_idle; +static int sound_search; +static int sound_sight; + + +void gladiator_idle (edict_t *self) +{ + gi.sound (self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0); +} + +void gladiator_sight (edict_t *self, edict_t *other) +{ + gi.sound (self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); +} + +void gladiator_search (edict_t *self) +{ + gi.sound (self, CHAN_VOICE, sound_search, 1, ATTN_NORM, 0); +} + +void gladiator_cleaver_swing (edict_t *self) +{ + gi.sound (self, CHAN_WEAPON, sound_cleaver_swing, 1, ATTN_NORM, 0); +} + +mframe_t gladiator_frames_stand [] = +{ + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL +}; +mmove_t gladiator_move_stand = {FRAME_stand1, FRAME_stand7, gladiator_frames_stand, NULL}; + +void gladiator_stand (edict_t *self) +{ + self->monsterinfo.currentmove = &gladiator_move_stand; +} + + +mframe_t gladiator_frames_walk [] = +{ + ai_walk, 15, NULL, + ai_walk, 7, NULL, + ai_walk, 6, NULL, + ai_walk, 5, NULL, + ai_walk, 2, NULL, + ai_walk, 0, NULL, + ai_walk, 2, NULL, + ai_walk, 8, NULL, + ai_walk, 12, NULL, + ai_walk, 8, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 2, NULL, + ai_walk, 2, NULL, + ai_walk, 1, NULL, + ai_walk, 8, NULL +}; +mmove_t gladiator_move_walk = {FRAME_walk1, FRAME_walk16, gladiator_frames_walk, NULL}; + +void gladiator_walk (edict_t *self) +{ + self->monsterinfo.currentmove = &gladiator_move_walk; +} + + +mframe_t gladiator_frames_run [] = +{ + ai_run, 23, NULL, + ai_run, 14, NULL, + ai_run, 14, NULL, + ai_run, 21, NULL, + ai_run, 12, NULL, + ai_run, 13, NULL +}; +mmove_t gladiator_move_run = {FRAME_run1, FRAME_run6, gladiator_frames_run, NULL}; + +void gladiator_run (edict_t *self) +{ + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + self->monsterinfo.currentmove = &gladiator_move_stand; + else + self->monsterinfo.currentmove = &gladiator_move_run; +} + + +void GaldiatorMelee (edict_t *self) +{ + vec3_t aim; + + VectorSet (aim, MELEE_DISTANCE, self->mins[0], -4); + if (fire_hit (self, aim, (20 + (rand() %5)), 300)) + gi.sound (self, CHAN_AUTO, sound_cleaver_hit, 1, ATTN_NORM, 0); + else + gi.sound (self, CHAN_AUTO, sound_cleaver_miss, 1, ATTN_NORM, 0); +} + +mframe_t gladiator_frames_attack_melee [] = +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, gladiator_cleaver_swing, + ai_charge, 0, NULL, + ai_charge, 0, GaldiatorMelee, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, gladiator_cleaver_swing, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, GaldiatorMelee, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL +}; +mmove_t gladiator_move_attack_melee = {FRAME_melee1, FRAME_melee17, gladiator_frames_attack_melee, gladiator_run}; + +void gladiator_melee(edict_t *self) +{ + self->monsterinfo.currentmove = &gladiator_move_attack_melee; +} + + +void GladiatorGun (edict_t *self) +{ + vec3_t start; + vec3_t dir; + vec3_t forward, right; + + AngleVectors (self->s.angles, forward, right, NULL); + G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_GLADIATOR_RAILGUN_1], forward, right, start); + + // calc direction to where we targted + VectorSubtract (self->pos1, start, dir); + VectorNormalize (dir); + + monster_fire_railgun (self, start, dir, 50, 100, MZ2_GLADIATOR_RAILGUN_1); +} + +mframe_t gladiator_frames_attack_gun [] = +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, GladiatorGun, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL +}; +mmove_t gladiator_move_attack_gun = {FRAME_attack1, FRAME_attack9, gladiator_frames_attack_gun, gladiator_run}; + +void gladiator_attack(edict_t *self) +{ + float range; + vec3_t v; + + // a small safe zone + VectorSubtract (self->s.origin, self->enemy->s.origin, v); + range = VectorLength(v); + if (range <= (MELEE_DISTANCE + 32)) + return; + + // charge up the railgun + gi.sound (self, CHAN_WEAPON, sound_gun, 1, ATTN_NORM, 0); + VectorCopy (self->enemy->s.origin, self->pos1); //save for aiming the shot + self->pos1[2] += self->enemy->viewheight; + self->monsterinfo.currentmove = &gladiator_move_attack_gun; +} + + +mframe_t gladiator_frames_pain [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t gladiator_move_pain = {FRAME_pain1, FRAME_pain6, gladiator_frames_pain, gladiator_run}; + +mframe_t gladiator_frames_pain_air [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t gladiator_move_pain_air = {FRAME_painup1, FRAME_painup7, gladiator_frames_pain_air, gladiator_run}; + +void gladiator_pain (edict_t *self, edict_t *other, float kick, int damage) +{ + + if (self->health < (self->max_health / 2)) + self->s.skinnum = 1; + + if (level.time < self->pain_debounce_time) + { + if ((self->velocity[2] > 100) && (self->monsterinfo.currentmove == &gladiator_move_pain)) + self->monsterinfo.currentmove = &gladiator_move_pain_air; + return; + } + + self->pain_debounce_time = level.time + 3; + + if (random() < 0.5) + gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); + else + gi.sound (self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0); + + if (skill->value == 3) + return; // no pain anims in nightmare + + if (self->velocity[2] > 100) + self->monsterinfo.currentmove = &gladiator_move_pain_air; + else + self->monsterinfo.currentmove = &gladiator_move_pain; + +} + + +void gladiator_dead (edict_t *self) +{ + VectorSet (self->mins, -16, -16, -24); + VectorSet (self->maxs, 16, 16, -8); + self->movetype = MOVETYPE_TOSS; + self->svflags |= SVF_DEADMONSTER; + self->nextthink = 0; + gi.linkentity (self); +} + +mframe_t gladiator_frames_death [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t gladiator_move_death = {FRAME_death1, FRAME_death22, gladiator_frames_death, gladiator_dead}; + +void gladiator_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + int n; + +// check for gib + if (self->health <= self->gib_health) + { + gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0); + for (n= 0; n < 2; n++) + ThrowGib (self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC); + for (n= 0; n < 4; n++) + ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); + ThrowHead (self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC); + self->deadflag = DEAD_DEAD; + return; + } + + if (self->deadflag == DEAD_DEAD) + return; + +// regular death + gi.sound (self, CHAN_VOICE, sound_die, 1, ATTN_NORM, 0); + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_YES; + + self->monsterinfo.currentmove = &gladiator_move_death; +} + + +/*QUAKED monster_gladiator (1 .5 0) (-32 -32 -24) (32 32 64) Ambush Trigger_Spawn Sight +*/ +void SP_monster_gladiator (edict_t *self) +{ + if (deathmatch->value) + { + G_FreeEdict (self); + return; + } + + + sound_pain1 = gi.soundindex ("gladiator/pain.wav"); + sound_pain2 = gi.soundindex ("gladiator/gldpain2.wav"); + sound_die = gi.soundindex ("gladiator/glddeth2.wav"); + sound_gun = gi.soundindex ("gladiator/railgun.wav"); + sound_cleaver_swing = gi.soundindex ("gladiator/melee1.wav"); + sound_cleaver_hit = gi.soundindex ("gladiator/melee2.wav"); + sound_cleaver_miss = gi.soundindex ("gladiator/melee3.wav"); + sound_idle = gi.soundindex ("gladiator/gldidle1.wav"); + sound_search = gi.soundindex ("gladiator/gldsrch1.wav"); + sound_sight = gi.soundindex ("gladiator/sight.wav"); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex ("models/monsters/gladiatr/tris.md2"); + VectorSet (self->mins, -32, -32, -24); + VectorSet (self->maxs, 32, 32, 64); + + self->health = 400; + self->gib_health = -175; + self->mass = 400; + + self->pain = gladiator_pain; + self->die = gladiator_die; + + self->monsterinfo.stand = gladiator_stand; + self->monsterinfo.walk = gladiator_walk; + self->monsterinfo.run = gladiator_run; + self->monsterinfo.dodge = NULL; + self->monsterinfo.attack = gladiator_attack; + self->monsterinfo.melee = gladiator_melee; + self->monsterinfo.sight = gladiator_sight; + self->monsterinfo.idle = gladiator_idle; + self->monsterinfo.search = gladiator_search; + + gi.linkentity (self); + self->monsterinfo.currentmove = &gladiator_move_stand; + self->monsterinfo.scale = MODEL_SCALE; + + walkmonster_start (self); +} diff --git a/original/baseq2/m_gladiator.h b/original/baseq2/m_gladiator.h new file mode 100644 index 0000000..898c7b1 --- /dev/null +++ b/original/baseq2/m_gladiator.h @@ -0,0 +1,115 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +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. + +*/ +// G:\quake2\baseq2\models/monsters/gladiatr + +// This file generated by ModelGen - Do NOT Modify + +#define FRAME_stand1 0 +#define FRAME_stand2 1 +#define FRAME_stand3 2 +#define FRAME_stand4 3 +#define FRAME_stand5 4 +#define FRAME_stand6 5 +#define FRAME_stand7 6 +#define FRAME_walk1 7 +#define FRAME_walk2 8 +#define FRAME_walk3 9 +#define FRAME_walk4 10 +#define FRAME_walk5 11 +#define FRAME_walk6 12 +#define FRAME_walk7 13 +#define FRAME_walk8 14 +#define FRAME_walk9 15 +#define FRAME_walk10 16 +#define FRAME_walk11 17 +#define FRAME_walk12 18 +#define FRAME_walk13 19 +#define FRAME_walk14 20 +#define FRAME_walk15 21 +#define FRAME_walk16 22 +#define FRAME_run1 23 +#define FRAME_run2 24 +#define FRAME_run3 25 +#define FRAME_run4 26 +#define FRAME_run5 27 +#define FRAME_run6 28 +#define FRAME_melee1 29 +#define FRAME_melee2 30 +#define FRAME_melee3 31 +#define FRAME_melee4 32 +#define FRAME_melee5 33 +#define FRAME_melee6 34 +#define FRAME_melee7 35 +#define FRAME_melee8 36 +#define FRAME_melee9 37 +#define FRAME_melee10 38 +#define FRAME_melee11 39 +#define FRAME_melee12 40 +#define FRAME_melee13 41 +#define FRAME_melee14 42 +#define FRAME_melee15 43 +#define FRAME_melee16 44 +#define FRAME_melee17 45 +#define FRAME_attack1 46 +#define FRAME_attack2 47 +#define FRAME_attack3 48 +#define FRAME_attack4 49 +#define FRAME_attack5 50 +#define FRAME_attack6 51 +#define FRAME_attack7 52 +#define FRAME_attack8 53 +#define FRAME_attack9 54 +#define FRAME_pain1 55 +#define FRAME_pain2 56 +#define FRAME_pain3 57 +#define FRAME_pain4 58 +#define FRAME_pain5 59 +#define FRAME_pain6 60 +#define FRAME_death1 61 +#define FRAME_death2 62 +#define FRAME_death3 63 +#define FRAME_death4 64 +#define FRAME_death5 65 +#define FRAME_death6 66 +#define FRAME_death7 67 +#define FRAME_death8 68 +#define FRAME_death9 69 +#define FRAME_death10 70 +#define FRAME_death11 71 +#define FRAME_death12 72 +#define FRAME_death13 73 +#define FRAME_death14 74 +#define FRAME_death15 75 +#define FRAME_death16 76 +#define FRAME_death17 77 +#define FRAME_death18 78 +#define FRAME_death19 79 +#define FRAME_death20 80 +#define FRAME_death21 81 +#define FRAME_death22 82 +#define FRAME_painup1 83 +#define FRAME_painup2 84 +#define FRAME_painup3 85 +#define FRAME_painup4 86 +#define FRAME_painup5 87 +#define FRAME_painup6 88 +#define FRAME_painup7 89 + +#define MODEL_SCALE 1.000000 diff --git a/original/baseq2/m_gunner.c b/original/baseq2/m_gunner.c new file mode 100644 index 0000000..96bea13 --- /dev/null +++ b/original/baseq2/m_gunner.c @@ -0,0 +1,628 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +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. + +*/ +/* +============================================================================== + +GUNNER + +============================================================================== +*/ + +#include "g_local.h" +#include "m_gunner.h" + + +static int sound_pain; +static int sound_pain2; +static int sound_death; +static int sound_idle; +static int sound_open; +static int sound_search; +static int sound_sight; + + +void gunner_idlesound (edict_t *self) +{ + gi.sound (self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0); +} + +void gunner_sight (edict_t *self, edict_t *other) +{ + gi.sound (self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); +} + +void gunner_search (edict_t *self) +{ + gi.sound (self, CHAN_VOICE, sound_search, 1, ATTN_NORM, 0); +} + + +qboolean visible (edict_t *self, edict_t *other); +void GunnerGrenade (edict_t *self); +void GunnerFire (edict_t *self); +void gunner_fire_chain(edict_t *self); +void gunner_refire_chain(edict_t *self); + + +void gunner_stand (edict_t *self); + +mframe_t gunner_frames_fidget [] = +{ + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, gunner_idlesound, + ai_stand, 0, NULL, + + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL +}; +mmove_t gunner_move_fidget = {FRAME_stand31, FRAME_stand70, gunner_frames_fidget, gunner_stand}; + +void gunner_fidget (edict_t *self) +{ + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + return; + if (random() <= 0.05) + self->monsterinfo.currentmove = &gunner_move_fidget; +} + +mframe_t gunner_frames_stand [] = +{ + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, gunner_fidget, + + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, gunner_fidget, + + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, gunner_fidget +}; +mmove_t gunner_move_stand = {FRAME_stand01, FRAME_stand30, gunner_frames_stand, NULL}; + +void gunner_stand (edict_t *self) +{ + self->monsterinfo.currentmove = &gunner_move_stand; +} + + +mframe_t gunner_frames_walk [] = +{ + ai_walk, 0, NULL, + ai_walk, 3, NULL, + ai_walk, 4, NULL, + ai_walk, 5, NULL, + ai_walk, 7, NULL, + ai_walk, 2, NULL, + ai_walk, 6, NULL, + ai_walk, 4, NULL, + ai_walk, 2, NULL, + ai_walk, 7, NULL, + ai_walk, 5, NULL, + ai_walk, 7, NULL, + ai_walk, 4, NULL +}; +mmove_t gunner_move_walk = {FRAME_walk07, FRAME_walk19, gunner_frames_walk, NULL}; + +void gunner_walk (edict_t *self) +{ + self->monsterinfo.currentmove = &gunner_move_walk; +} + +mframe_t gunner_frames_run [] = +{ + ai_run, 26, NULL, + ai_run, 9, NULL, + ai_run, 9, NULL, + ai_run, 9, NULL, + ai_run, 15, NULL, + ai_run, 10, NULL, + ai_run, 13, NULL, + ai_run, 6, NULL +}; + +mmove_t gunner_move_run = {FRAME_run01, FRAME_run08, gunner_frames_run, NULL}; + +void gunner_run (edict_t *self) +{ + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + self->monsterinfo.currentmove = &gunner_move_stand; + else + self->monsterinfo.currentmove = &gunner_move_run; +} + +mframe_t gunner_frames_runandshoot [] = +{ + ai_run, 32, NULL, + ai_run, 15, NULL, + ai_run, 10, NULL, + ai_run, 18, NULL, + ai_run, 8, NULL, + ai_run, 20, NULL +}; + +mmove_t gunner_move_runandshoot = {FRAME_runs01, FRAME_runs06, gunner_frames_runandshoot, NULL}; + +void gunner_runandshoot (edict_t *self) +{ + self->monsterinfo.currentmove = &gunner_move_runandshoot; +} + +mframe_t gunner_frames_pain3 [] = +{ + ai_move, -3, NULL, + ai_move, 1, NULL, + ai_move, 1, NULL, + ai_move, 0, NULL, + ai_move, 1, NULL +}; +mmove_t gunner_move_pain3 = {FRAME_pain301, FRAME_pain305, gunner_frames_pain3, gunner_run}; + +mframe_t gunner_frames_pain2 [] = +{ + ai_move, -2, NULL, + ai_move, 11, NULL, + ai_move, 6, NULL, + ai_move, 2, NULL, + ai_move, -1, NULL, + ai_move, -7, NULL, + ai_move, -2, NULL, + ai_move, -7, NULL +}; +mmove_t gunner_move_pain2 = {FRAME_pain201, FRAME_pain208, gunner_frames_pain2, gunner_run}; + +mframe_t gunner_frames_pain1 [] = +{ + ai_move, 2, NULL, + ai_move, 0, NULL, + ai_move, -5, NULL, + ai_move, 3, NULL, + ai_move, -1, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 1, NULL, + ai_move, 1, NULL, + ai_move, 2, NULL, + ai_move, 1, NULL, + ai_move, 0, NULL, + ai_move, -2, NULL, + ai_move, -2, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t gunner_move_pain1 = {FRAME_pain101, FRAME_pain118, gunner_frames_pain1, gunner_run}; + +void gunner_pain (edict_t *self, edict_t *other, float kick, int damage) +{ + if (self->health < (self->max_health / 2)) + self->s.skinnum = 1; + + if (level.time < self->pain_debounce_time) + return; + + self->pain_debounce_time = level.time + 3; + + if (rand()&1) + gi.sound (self, CHAN_VOICE, sound_pain, 1, ATTN_NORM, 0); + else + gi.sound (self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0); + + if (skill->value == 3) + return; // no pain anims in nightmare + + if (damage <= 10) + self->monsterinfo.currentmove = &gunner_move_pain3; + else if (damage <= 25) + self->monsterinfo.currentmove = &gunner_move_pain2; + else + self->monsterinfo.currentmove = &gunner_move_pain1; +} + +void gunner_dead (edict_t *self) +{ + VectorSet (self->mins, -16, -16, -24); + VectorSet (self->maxs, 16, 16, -8); + self->movetype = MOVETYPE_TOSS; + self->svflags |= SVF_DEADMONSTER; + self->nextthink = 0; + gi.linkentity (self); +} + +mframe_t gunner_frames_death [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, -7, NULL, + ai_move, -3, NULL, + ai_move, -5, NULL, + ai_move, 8, NULL, + ai_move, 6, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t gunner_move_death = {FRAME_death01, FRAME_death11, gunner_frames_death, gunner_dead}; + +void gunner_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + int n; + +// check for gib + if (self->health <= self->gib_health) + { + gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0); + for (n= 0; n < 2; n++) + ThrowGib (self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC); + for (n= 0; n < 4; n++) + ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); + ThrowHead (self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC); + self->deadflag = DEAD_DEAD; + return; + } + + if (self->deadflag == DEAD_DEAD) + return; + +// regular death + gi.sound (self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0); + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_YES; + self->monsterinfo.currentmove = &gunner_move_death; +} + + +void gunner_duck_down (edict_t *self) +{ + if (self->monsterinfo.aiflags & AI_DUCKED) + return; + self->monsterinfo.aiflags |= AI_DUCKED; + if (skill->value >= 2) + { + if (random() > 0.5) + GunnerGrenade (self); + } + + self->maxs[2] -= 32; + self->takedamage = DAMAGE_YES; + self->monsterinfo.pausetime = level.time + 1; + gi.linkentity (self); +} + +void gunner_duck_hold (edict_t *self) +{ + if (level.time >= self->monsterinfo.pausetime) + self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; + else + self->monsterinfo.aiflags |= AI_HOLD_FRAME; +} + +void gunner_duck_up (edict_t *self) +{ + self->monsterinfo.aiflags &= ~AI_DUCKED; + self->maxs[2] += 32; + self->takedamage = DAMAGE_AIM; + gi.linkentity (self); +} + +mframe_t gunner_frames_duck [] = +{ + ai_move, 1, gunner_duck_down, + ai_move, 1, NULL, + ai_move, 1, gunner_duck_hold, + ai_move, 0, NULL, + ai_move, -1, NULL, + ai_move, -1, NULL, + ai_move, 0, gunner_duck_up, + ai_move, -1, NULL +}; +mmove_t gunner_move_duck = {FRAME_duck01, FRAME_duck08, gunner_frames_duck, gunner_run}; + +void gunner_dodge (edict_t *self, edict_t *attacker, float eta) +{ + if (random() > 0.25) + return; + + if (!self->enemy) + self->enemy = attacker; + + self->monsterinfo.currentmove = &gunner_move_duck; +} + + +void gunner_opengun (edict_t *self) +{ + gi.sound (self, CHAN_VOICE, sound_open, 1, ATTN_IDLE, 0); +} + +void GunnerFire (edict_t *self) +{ + vec3_t start; + vec3_t forward, right; + vec3_t target; + vec3_t aim; + int flash_number; + + flash_number = MZ2_GUNNER_MACHINEGUN_1 + (self->s.frame - FRAME_attak216); + + AngleVectors (self->s.angles, forward, right, NULL); + G_ProjectSource (self->s.origin, monster_flash_offset[flash_number], forward, right, start); + + // project enemy back a bit and target there + VectorCopy (self->enemy->s.origin, target); + VectorMA (target, -0.2, self->enemy->velocity, target); + target[2] += self->enemy->viewheight; + + VectorSubtract (target, start, aim); + VectorNormalize (aim); + monster_fire_bullet (self, start, aim, 3, 4, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, flash_number); +} + +void GunnerGrenade (edict_t *self) +{ + vec3_t start; + vec3_t forward, right; + vec3_t aim; + int flash_number; + + if (self->s.frame == FRAME_attak105) + flash_number = MZ2_GUNNER_GRENADE_1; + else if (self->s.frame == FRAME_attak108) + flash_number = MZ2_GUNNER_GRENADE_2; + else if (self->s.frame == FRAME_attak111) + flash_number = MZ2_GUNNER_GRENADE_3; + else // (self->s.frame == FRAME_attak114) + flash_number = MZ2_GUNNER_GRENADE_4; + + AngleVectors (self->s.angles, forward, right, NULL); + G_ProjectSource (self->s.origin, monster_flash_offset[flash_number], forward, right, start); + + //FIXME : do a spread -225 -75 75 225 degrees around forward + VectorCopy (forward, aim); + + monster_fire_grenade (self, start, aim, 50, 600, flash_number); +} + +mframe_t gunner_frames_attack_chain [] = +{ + /* + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + */ + ai_charge, 0, gunner_opengun, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL +}; +mmove_t gunner_move_attack_chain = {FRAME_attak209, FRAME_attak215, gunner_frames_attack_chain, gunner_fire_chain}; + +mframe_t gunner_frames_fire_chain [] = +{ + ai_charge, 0, GunnerFire, + ai_charge, 0, GunnerFire, + ai_charge, 0, GunnerFire, + ai_charge, 0, GunnerFire, + ai_charge, 0, GunnerFire, + ai_charge, 0, GunnerFire, + ai_charge, 0, GunnerFire, + ai_charge, 0, GunnerFire +}; +mmove_t gunner_move_fire_chain = {FRAME_attak216, FRAME_attak223, gunner_frames_fire_chain, gunner_refire_chain}; + +mframe_t gunner_frames_endfire_chain [] = +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL +}; +mmove_t gunner_move_endfire_chain = {FRAME_attak224, FRAME_attak230, gunner_frames_endfire_chain, gunner_run}; + +mframe_t gunner_frames_attack_grenade [] = +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, GunnerGrenade, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, GunnerGrenade, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, GunnerGrenade, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, GunnerGrenade, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL +}; +mmove_t gunner_move_attack_grenade = {FRAME_attak101, FRAME_attak121, gunner_frames_attack_grenade, gunner_run}; + +void gunner_attack(edict_t *self) +{ + if (range (self, self->enemy) == RANGE_MELEE) + { + self->monsterinfo.currentmove = &gunner_move_attack_chain; + } + else + { + if (random() <= 0.5) + self->monsterinfo.currentmove = &gunner_move_attack_grenade; + else + self->monsterinfo.currentmove = &gunner_move_attack_chain; + } +} + +void gunner_fire_chain(edict_t *self) +{ + self->monsterinfo.currentmove = &gunner_move_fire_chain; +} + +void gunner_refire_chain(edict_t *self) +{ + if (self->enemy->health > 0) + if ( visible (self, self->enemy) ) + if (random() <= 0.5) + { + self->monsterinfo.currentmove = &gunner_move_fire_chain; + return; + } + self->monsterinfo.currentmove = &gunner_move_endfire_chain; +} + +/*QUAKED monster_gunner (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight +*/ +void SP_monster_gunner (edict_t *self) +{ + if (deathmatch->value) + { + G_FreeEdict (self); + return; + } + + sound_death = gi.soundindex ("gunner/death1.wav"); + sound_pain = gi.soundindex ("gunner/gunpain2.wav"); + sound_pain2 = gi.soundindex ("gunner/gunpain1.wav"); + sound_idle = gi.soundindex ("gunner/gunidle1.wav"); + sound_open = gi.soundindex ("gunner/gunatck1.wav"); + sound_search = gi.soundindex ("gunner/gunsrch1.wav"); + sound_sight = gi.soundindex ("gunner/sight1.wav"); + + gi.soundindex ("gunner/gunatck2.wav"); + gi.soundindex ("gunner/gunatck3.wav"); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex ("models/monsters/gunner/tris.md2"); + VectorSet (self->mins, -16, -16, -24); + VectorSet (self->maxs, 16, 16, 32); + + self->health = 175; + self->gib_health = -70; + self->mass = 200; + + self->pain = gunner_pain; + self->die = gunner_die; + + self->monsterinfo.stand = gunner_stand; + self->monsterinfo.walk = gunner_walk; + self->monsterinfo.run = gunner_run; + self->monsterinfo.dodge = gunner_dodge; + self->monsterinfo.attack = gunner_attack; + self->monsterinfo.melee = NULL; + self->monsterinfo.sight = gunner_sight; + self->monsterinfo.search = gunner_search; + + gi.linkentity (self); + + self->monsterinfo.currentmove = &gunner_move_stand; + self->monsterinfo.scale = MODEL_SCALE; + + walkmonster_start (self); +} diff --git a/original/baseq2/m_gunner.h b/original/baseq2/m_gunner.h new file mode 100644 index 0000000..718832c --- /dev/null +++ b/original/baseq2/m_gunner.h @@ -0,0 +1,234 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +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. + +*/ +// G:\quake2\baseq2\models/gunner + +// This file generated by ModelGen - Do NOT Modify + +#define FRAME_stand01 0 +#define FRAME_stand02 1 +#define FRAME_stand03 2 +#define FRAME_stand04 3 +#define FRAME_stand05 4 +#define FRAME_stand06 5 +#define FRAME_stand07 6 +#define FRAME_stand08 7 +#define FRAME_stand09 8 +#define FRAME_stand10 9 +#define FRAME_stand11 10 +#define FRAME_stand12 11 +#define FRAME_stand13 12 +#define FRAME_stand14 13 +#define FRAME_stand15 14 +#define FRAME_stand16 15 +#define FRAME_stand17 16 +#define FRAME_stand18 17 +#define FRAME_stand19 18 +#define FRAME_stand20 19 +#define FRAME_stand21 20 +#define FRAME_stand22 21 +#define FRAME_stand23 22 +#define FRAME_stand24 23 +#define FRAME_stand25 24 +#define FRAME_stand26 25 +#define FRAME_stand27 26 +#define FRAME_stand28 27 +#define FRAME_stand29 28 +#define FRAME_stand30 29 +#define FRAME_stand31 30 +#define FRAME_stand32 31 +#define FRAME_stand33 32 +#define FRAME_stand34 33 +#define FRAME_stand35 34 +#define FRAME_stand36 35 +#define FRAME_stand37 36 +#define FRAME_stand38 37 +#define FRAME_stand39 38 +#define FRAME_stand40 39 +#define FRAME_stand41 40 +#define FRAME_stand42 41 +#define FRAME_stand43 42 +#define FRAME_stand44 43 +#define FRAME_stand45 44 +#define FRAME_stand46 45 +#define FRAME_stand47 46 +#define FRAME_stand48 47 +#define FRAME_stand49 48 +#define FRAME_stand50 49 +#define FRAME_stand51 50 +#define FRAME_stand52 51 +#define FRAME_stand53 52 +#define FRAME_stand54 53 +#define FRAME_stand55 54 +#define FRAME_stand56 55 +#define FRAME_stand57 56 +#define FRAME_stand58 57 +#define FRAME_stand59 58 +#define FRAME_stand60 59 +#define FRAME_stand61 60 +#define FRAME_stand62 61 +#define FRAME_stand63 62 +#define FRAME_stand64 63 +#define FRAME_stand65 64 +#define FRAME_stand66 65 +#define FRAME_stand67 66 +#define FRAME_stand68 67 +#define FRAME_stand69 68 +#define FRAME_stand70 69 +#define FRAME_walk01 70 +#define FRAME_walk02 71 +#define FRAME_walk03 72 +#define FRAME_walk04 73 +#define FRAME_walk05 74 +#define FRAME_walk06 75 +#define FRAME_walk07 76 +#define FRAME_walk08 77 +#define FRAME_walk09 78 +#define FRAME_walk10 79 +#define FRAME_walk11 80 +#define FRAME_walk12 81 +#define FRAME_walk13 82 +#define FRAME_walk14 83 +#define FRAME_walk15 84 +#define FRAME_walk16 85 +#define FRAME_walk17 86 +#define FRAME_walk18 87 +#define FRAME_walk19 88 +#define FRAME_walk20 89 +#define FRAME_walk21 90 +#define FRAME_walk22 91 +#define FRAME_walk23 92 +#define FRAME_walk24 93 +#define FRAME_run01 94 +#define FRAME_run02 95 +#define FRAME_run03 96 +#define FRAME_run04 97 +#define FRAME_run05 98 +#define FRAME_run06 99 +#define FRAME_run07 100 +#define FRAME_run08 101 +#define FRAME_runs01 102 +#define FRAME_runs02 103 +#define FRAME_runs03 104 +#define FRAME_runs04 105 +#define FRAME_runs05 106 +#define FRAME_runs06 107 +#define FRAME_attak101 108 +#define FRAME_attak102 109 +#define FRAME_attak103 110 +#define FRAME_attak104 111 +#define FRAME_attak105 112 +#define FRAME_attak106 113 +#define FRAME_attak107 114 +#define FRAME_attak108 115 +#define FRAME_attak109 116 +#define FRAME_attak110 117 +#define FRAME_attak111 118 +#define FRAME_attak112 119 +#define FRAME_attak113 120 +#define FRAME_attak114 121 +#define FRAME_attak115 122 +#define FRAME_attak116 123 +#define FRAME_attak117 124 +#define FRAME_attak118 125 +#define FRAME_attak119 126 +#define FRAME_attak120 127 +#define FRAME_attak121 128 +#define FRAME_attak201 129 +#define FRAME_attak202 130 +#define FRAME_attak203 131 +#define FRAME_attak204 132 +#define FRAME_attak205 133 +#define FRAME_attak206 134 +#define FRAME_attak207 135 +#define FRAME_attak208 136 +#define FRAME_attak209 137 +#define FRAME_attak210 138 +#define FRAME_attak211 139 +#define FRAME_attak212 140 +#define FRAME_attak213 141 +#define FRAME_attak214 142 +#define FRAME_attak215 143 +#define FRAME_attak216 144 +#define FRAME_attak217 145 +#define FRAME_attak218 146 +#define FRAME_attak219 147 +#define FRAME_attak220 148 +#define FRAME_attak221 149 +#define FRAME_attak222 150 +#define FRAME_attak223 151 +#define FRAME_attak224 152 +#define FRAME_attak225 153 +#define FRAME_attak226 154 +#define FRAME_attak227 155 +#define FRAME_attak228 156 +#define FRAME_attak229 157 +#define FRAME_attak230 158 +#define FRAME_pain101 159 +#define FRAME_pain102 160 +#define FRAME_pain103 161 +#define FRAME_pain104 162 +#define FRAME_pain105 163 +#define FRAME_pain106 164 +#define FRAME_pain107 165 +#define FRAME_pain108 166 +#define FRAME_pain109 167 +#define FRAME_pain110 168 +#define FRAME_pain111 169 +#define FRAME_pain112 170 +#define FRAME_pain113 171 +#define FRAME_pain114 172 +#define FRAME_pain115 173 +#define FRAME_pain116 174 +#define FRAME_pain117 175 +#define FRAME_pain118 176 +#define FRAME_pain201 177 +#define FRAME_pain202 178 +#define FRAME_pain203 179 +#define FRAME_pain204 180 +#define FRAME_pain205 181 +#define FRAME_pain206 182 +#define FRAME_pain207 183 +#define FRAME_pain208 184 +#define FRAME_pain301 185 +#define FRAME_pain302 186 +#define FRAME_pain303 187 +#define FRAME_pain304 188 +#define FRAME_pain305 189 +#define FRAME_death01 190 +#define FRAME_death02 191 +#define FRAME_death03 192 +#define FRAME_death04 193 +#define FRAME_death05 194 +#define FRAME_death06 195 +#define FRAME_death07 196 +#define FRAME_death08 197 +#define FRAME_death09 198 +#define FRAME_death10 199 +#define FRAME_death11 200 +#define FRAME_duck01 201 +#define FRAME_duck02 202 +#define FRAME_duck03 203 +#define FRAME_duck04 204 +#define FRAME_duck05 205 +#define FRAME_duck06 206 +#define FRAME_duck07 207 +#define FRAME_duck08 208 + +#define MODEL_SCALE 1.150000 diff --git a/original/baseq2/m_hover.c b/original/baseq2/m_hover.c new file mode 100644 index 0000000..0851d3d --- /dev/null +++ b/original/baseq2/m_hover.c @@ -0,0 +1,620 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +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. + +*/ +/* +============================================================================== + +hover + +============================================================================== +*/ + +#include "g_local.h" +#include "m_hover.h" + +qboolean visible (edict_t *self, edict_t *other); + + +static int sound_pain1; +static int sound_pain2; +static int sound_death1; +static int sound_death2; +static int sound_sight; +static int sound_search1; +static int sound_search2; + + +void hover_sight (edict_t *self, edict_t *other) +{ + gi.sound (self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); +} + +void hover_search (edict_t *self) +{ + if (random() < 0.5) + gi.sound (self, CHAN_VOICE, sound_search1, 1, ATTN_NORM, 0); + else + gi.sound (self, CHAN_VOICE, sound_search2, 1, ATTN_NORM, 0); +} + + +void hover_run (edict_t *self); +void hover_stand (edict_t *self); +void hover_dead (edict_t *self); +void hover_attack (edict_t *self); +void hover_reattack (edict_t *self); +void hover_fire_blaster (edict_t *self); +void hover_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point); + +mframe_t hover_frames_stand [] = +{ + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL +}; +mmove_t hover_move_stand = {FRAME_stand01, FRAME_stand30, hover_frames_stand, NULL}; + +mframe_t hover_frames_stop1 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t hover_move_stop1 = {FRAME_stop101, FRAME_stop109, hover_frames_stop1, NULL}; + +mframe_t hover_frames_stop2 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t hover_move_stop2 = {FRAME_stop201, FRAME_stop208, hover_frames_stop2, NULL}; + +mframe_t hover_frames_takeoff [] = +{ + ai_move, 0, NULL, + ai_move, -2, NULL, + ai_move, 5, NULL, + ai_move, -1, NULL, + ai_move, 1, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, -1, NULL, + ai_move, -1, NULL, + ai_move, -1, NULL, + ai_move, 0, NULL, + ai_move, 2, NULL, + ai_move, 2, NULL, + ai_move, 1, NULL, + ai_move, 1, NULL, + ai_move, -6, NULL, + ai_move, -9, NULL, + ai_move, 1, NULL, + ai_move, 0, NULL, + ai_move, 2, NULL, + ai_move, 2, NULL, + ai_move, 1, NULL, + ai_move, 1, NULL, + ai_move, 1, NULL, + ai_move, 2, NULL, + ai_move, 0, NULL, + ai_move, 2, NULL, + ai_move, 3, NULL, + ai_move, 2, NULL, + ai_move, 0, NULL +}; +mmove_t hover_move_takeoff = {FRAME_takeof01, FRAME_takeof30, hover_frames_takeoff, NULL}; + +mframe_t hover_frames_pain3 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t hover_move_pain3 = {FRAME_pain301, FRAME_pain309, hover_frames_pain3, hover_run}; + +mframe_t hover_frames_pain2 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t hover_move_pain2 = {FRAME_pain201, FRAME_pain212, hover_frames_pain2, hover_run}; + +mframe_t hover_frames_pain1 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 2, NULL, + ai_move, -8, NULL, + ai_move, -4, NULL, + ai_move, -6, NULL, + ai_move, -4, NULL, + ai_move, -3, NULL, + ai_move, 1, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 3, NULL, + ai_move, 1, NULL, + ai_move, 0, NULL, + ai_move, 2, NULL, + ai_move, 3, NULL, + ai_move, 2, NULL, + ai_move, 7, NULL, + ai_move, 1, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 2, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 5, NULL, + ai_move, 3, NULL, + ai_move, 4, NULL +}; +mmove_t hover_move_pain1 = {FRAME_pain101, FRAME_pain128, hover_frames_pain1, hover_run}; + +mframe_t hover_frames_land [] = +{ + ai_move, 0, NULL +}; +mmove_t hover_move_land = {FRAME_land01, FRAME_land01, hover_frames_land, NULL}; + +mframe_t hover_frames_forward [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t hover_move_forward = {FRAME_forwrd01, FRAME_forwrd35, hover_frames_forward, NULL}; + +mframe_t hover_frames_walk [] = +{ + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL +}; +mmove_t hover_move_walk = {FRAME_forwrd01, FRAME_forwrd35, hover_frames_walk, NULL}; + +mframe_t hover_frames_run [] = +{ + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL +}; +mmove_t hover_move_run = {FRAME_forwrd01, FRAME_forwrd35, hover_frames_run, NULL}; + +mframe_t hover_frames_death1 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, -10,NULL, + ai_move, 3, NULL, + ai_move, 5, NULL, + ai_move, 4, NULL, + ai_move, 7, NULL +}; +mmove_t hover_move_death1 = {FRAME_death101, FRAME_death111, hover_frames_death1, hover_dead}; + +mframe_t hover_frames_backward [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t hover_move_backward = {FRAME_backwd01, FRAME_backwd24, hover_frames_backward, NULL}; + +mframe_t hover_frames_start_attack [] = +{ + ai_charge, 1, NULL, + ai_charge, 1, NULL, + ai_charge, 1, NULL +}; +mmove_t hover_move_start_attack = {FRAME_attak101, FRAME_attak103, hover_frames_start_attack, hover_attack}; + +mframe_t hover_frames_attack1 [] = +{ + ai_charge, -10, hover_fire_blaster, + ai_charge, -10, hover_fire_blaster, + ai_charge, 0, hover_reattack, +}; +mmove_t hover_move_attack1 = {FRAME_attak104, FRAME_attak106, hover_frames_attack1, NULL}; + + +mframe_t hover_frames_end_attack [] = +{ + ai_charge, 1, NULL, + ai_charge, 1, NULL +}; +mmove_t hover_move_end_attack = {FRAME_attak107, FRAME_attak108, hover_frames_end_attack, hover_run}; + +void hover_reattack (edict_t *self) +{ + if (self->enemy->health > 0 ) + if (visible (self, self->enemy) ) + if (random() <= 0.6) + { + self->monsterinfo.currentmove = &hover_move_attack1; + return; + } + self->monsterinfo.currentmove = &hover_move_end_attack; +} + + +void hover_fire_blaster (edict_t *self) +{ + vec3_t start; + vec3_t forward, right; + vec3_t end; + vec3_t dir; + int effect; + + if (self->s.frame == FRAME_attak104) + effect = EF_HYPERBLASTER; + else + effect = 0; + + AngleVectors (self->s.angles, forward, right, NULL); + G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_HOVER_BLASTER_1], forward, right, start); + + VectorCopy (self->enemy->s.origin, end); + end[2] += self->enemy->viewheight; + VectorSubtract (end, start, dir); + + monster_fire_blaster (self, start, dir, 1, 1000, MZ2_HOVER_BLASTER_1, effect); +} + + +void hover_stand (edict_t *self) +{ + self->monsterinfo.currentmove = &hover_move_stand; +} + +void hover_run (edict_t *self) +{ + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + self->monsterinfo.currentmove = &hover_move_stand; + else + self->monsterinfo.currentmove = &hover_move_run; +} + +void hover_walk (edict_t *self) +{ + self->monsterinfo.currentmove = &hover_move_walk; +} + +void hover_start_attack (edict_t *self) +{ + self->monsterinfo.currentmove = &hover_move_start_attack; +} + +void hover_attack(edict_t *self) +{ + self->monsterinfo.currentmove = &hover_move_attack1; +} + + +void hover_pain (edict_t *self, edict_t *other, float kick, int damage) +{ + if (self->health < (self->max_health / 2)) + self->s.skinnum = 1; + + if (level.time < self->pain_debounce_time) + return; + + self->pain_debounce_time = level.time + 3; + + if (skill->value == 3) + return; // no pain anims in nightmare + + if (damage <= 25) + { + if (random() < 0.5) + { + gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &hover_move_pain3; + } + else + { + gi.sound (self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &hover_move_pain2; + } + } + else + { + gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &hover_move_pain1; + } +} + +void hover_deadthink (edict_t *self) +{ + if (!self->groundentity && level.time < self->timestamp) + { + self->nextthink = level.time + FRAMETIME; + return; + } + BecomeExplosion1(self); +} + +void hover_dead (edict_t *self) +{ + VectorSet (self->mins, -16, -16, -24); + VectorSet (self->maxs, 16, 16, -8); + self->movetype = MOVETYPE_TOSS; + self->think = hover_deadthink; + self->nextthink = level.time + FRAMETIME; + self->timestamp = level.time + 15; + gi.linkentity (self); +} + +void hover_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + int n; + +// check for gib + if (self->health <= self->gib_health) + { + gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0); + for (n= 0; n < 2; n++) + ThrowGib (self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC); + for (n= 0; n < 2; n++) + ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); + ThrowHead (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); + self->deadflag = DEAD_DEAD; + return; + } + + if (self->deadflag == DEAD_DEAD) + return; + +// regular death + if (random() < 0.5) + gi.sound (self, CHAN_VOICE, sound_death1, 1, ATTN_NORM, 0); + else + gi.sound (self, CHAN_VOICE, sound_death2, 1, ATTN_NORM, 0); + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_YES; + self->monsterinfo.currentmove = &hover_move_death1; +} + +/*QUAKED monster_hover (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight +*/ +void SP_monster_hover (edict_t *self) +{ + if (deathmatch->value) + { + G_FreeEdict (self); + return; + } + + sound_pain1 = gi.soundindex ("hover/hovpain1.wav"); + sound_pain2 = gi.soundindex ("hover/hovpain2.wav"); + sound_death1 = gi.soundindex ("hover/hovdeth1.wav"); + sound_death2 = gi.soundindex ("hover/hovdeth2.wav"); + sound_sight = gi.soundindex ("hover/hovsght1.wav"); + sound_search1 = gi.soundindex ("hover/hovsrch1.wav"); + sound_search2 = gi.soundindex ("hover/hovsrch2.wav"); + + gi.soundindex ("hover/hovatck1.wav"); + + self->s.sound = gi.soundindex ("hover/hovidle1.wav"); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex("models/monsters/hover/tris.md2"); + VectorSet (self->mins, -24, -24, -24); + VectorSet (self->maxs, 24, 24, 32); + + self->health = 240; + self->gib_health = -100; + self->mass = 150; + + self->pain = hover_pain; + self->die = hover_die; + + self->monsterinfo.stand = hover_stand; + self->monsterinfo.walk = hover_walk; + self->monsterinfo.run = hover_run; +// self->monsterinfo.dodge = hover_dodge; + self->monsterinfo.attack = hover_start_attack; + self->monsterinfo.sight = hover_sight; + self->monsterinfo.search = hover_search; + + gi.linkentity (self); + + self->monsterinfo.currentmove = &hover_move_stand; + self->monsterinfo.scale = MODEL_SCALE; + + flymonster_start (self); +} diff --git a/original/baseq2/m_hover.h b/original/baseq2/m_hover.h new file mode 100644 index 0000000..c236eaa --- /dev/null +++ b/original/baseq2/m_hover.h @@ -0,0 +1,230 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +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. + +*/ +// G:\quake2\baseq2\models/monsters/hover + +// This file generated by ModelGen - Do NOT Modify + +#define FRAME_stand01 0 +#define FRAME_stand02 1 +#define FRAME_stand03 2 +#define FRAME_stand04 3 +#define FRAME_stand05 4 +#define FRAME_stand06 5 +#define FRAME_stand07 6 +#define FRAME_stand08 7 +#define FRAME_stand09 8 +#define FRAME_stand10 9 +#define FRAME_stand11 10 +#define FRAME_stand12 11 +#define FRAME_stand13 12 +#define FRAME_stand14 13 +#define FRAME_stand15 14 +#define FRAME_stand16 15 +#define FRAME_stand17 16 +#define FRAME_stand18 17 +#define FRAME_stand19 18 +#define FRAME_stand20 19 +#define FRAME_stand21 20 +#define FRAME_stand22 21 +#define FRAME_stand23 22 +#define FRAME_stand24 23 +#define FRAME_stand25 24 +#define FRAME_stand26 25 +#define FRAME_stand27 26 +#define FRAME_stand28 27 +#define FRAME_stand29 28 +#define FRAME_stand30 29 +#define FRAME_forwrd01 30 +#define FRAME_forwrd02 31 +#define FRAME_forwrd03 32 +#define FRAME_forwrd04 33 +#define FRAME_forwrd05 34 +#define FRAME_forwrd06 35 +#define FRAME_forwrd07 36 +#define FRAME_forwrd08 37 +#define FRAME_forwrd09 38 +#define FRAME_forwrd10 39 +#define FRAME_forwrd11 40 +#define FRAME_forwrd12 41 +#define FRAME_forwrd13 42 +#define FRAME_forwrd14 43 +#define FRAME_forwrd15 44 +#define FRAME_forwrd16 45 +#define FRAME_forwrd17 46 +#define FRAME_forwrd18 47 +#define FRAME_forwrd19 48 +#define FRAME_forwrd20 49 +#define FRAME_forwrd21 50 +#define FRAME_forwrd22 51 +#define FRAME_forwrd23 52 +#define FRAME_forwrd24 53 +#define FRAME_forwrd25 54 +#define FRAME_forwrd26 55 +#define FRAME_forwrd27 56 +#define FRAME_forwrd28 57 +#define FRAME_forwrd29 58 +#define FRAME_forwrd30 59 +#define FRAME_forwrd31 60 +#define FRAME_forwrd32 61 +#define FRAME_forwrd33 62 +#define FRAME_forwrd34 63 +#define FRAME_forwrd35 64 +#define FRAME_stop101 65 +#define FRAME_stop102 66 +#define FRAME_stop103 67 +#define FRAME_stop104 68 +#define FRAME_stop105 69 +#define FRAME_stop106 70 +#define FRAME_stop107 71 +#define FRAME_stop108 72 +#define FRAME_stop109 73 +#define FRAME_stop201 74 +#define FRAME_stop202 75 +#define FRAME_stop203 76 +#define FRAME_stop204 77 +#define FRAME_stop205 78 +#define FRAME_stop206 79 +#define FRAME_stop207 80 +#define FRAME_stop208 81 +#define FRAME_takeof01 82 +#define FRAME_takeof02 83 +#define FRAME_takeof03 84 +#define FRAME_takeof04 85 +#define FRAME_takeof05 86 +#define FRAME_takeof06 87 +#define FRAME_takeof07 88 +#define FRAME_takeof08 89 +#define FRAME_takeof09 90 +#define FRAME_takeof10 91 +#define FRAME_takeof11 92 +#define FRAME_takeof12 93 +#define FRAME_takeof13 94 +#define FRAME_takeof14 95 +#define FRAME_takeof15 96 +#define FRAME_takeof16 97 +#define FRAME_takeof17 98 +#define FRAME_takeof18 99 +#define FRAME_takeof19 100 +#define FRAME_takeof20 101 +#define FRAME_takeof21 102 +#define FRAME_takeof22 103 +#define FRAME_takeof23 104 +#define FRAME_takeof24 105 +#define FRAME_takeof25 106 +#define FRAME_takeof26 107 +#define FRAME_takeof27 108 +#define FRAME_takeof28 109 +#define FRAME_takeof29 110 +#define FRAME_takeof30 111 +#define FRAME_land01 112 +#define FRAME_pain101 113 +#define FRAME_pain102 114 +#define FRAME_pain103 115 +#define FRAME_pain104 116 +#define FRAME_pain105 117 +#define FRAME_pain106 118 +#define FRAME_pain107 119 +#define FRAME_pain108 120 +#define FRAME_pain109 121 +#define FRAME_pain110 122 +#define FRAME_pain111 123 +#define FRAME_pain112 124 +#define FRAME_pain113 125 +#define FRAME_pain114 126 +#define FRAME_pain115 127 +#define FRAME_pain116 128 +#define FRAME_pain117 129 +#define FRAME_pain118 130 +#define FRAME_pain119 131 +#define FRAME_pain120 132 +#define FRAME_pain121 133 +#define FRAME_pain122 134 +#define FRAME_pain123 135 +#define FRAME_pain124 136 +#define FRAME_pain125 137 +#define FRAME_pain126 138 +#define FRAME_pain127 139 +#define FRAME_pain128 140 +#define FRAME_pain201 141 +#define FRAME_pain202 142 +#define FRAME_pain203 143 +#define FRAME_pain204 144 +#define FRAME_pain205 145 +#define FRAME_pain206 146 +#define FRAME_pain207 147 +#define FRAME_pain208 148 +#define FRAME_pain209 149 +#define FRAME_pain210 150 +#define FRAME_pain211 151 +#define FRAME_pain212 152 +#define FRAME_pain301 153 +#define FRAME_pain302 154 +#define FRAME_pain303 155 +#define FRAME_pain304 156 +#define FRAME_pain305 157 +#define FRAME_pain306 158 +#define FRAME_pain307 159 +#define FRAME_pain308 160 +#define FRAME_pain309 161 +#define FRAME_death101 162 +#define FRAME_death102 163 +#define FRAME_death103 164 +#define FRAME_death104 165 +#define FRAME_death105 166 +#define FRAME_death106 167 +#define FRAME_death107 168 +#define FRAME_death108 169 +#define FRAME_death109 170 +#define FRAME_death110 171 +#define FRAME_death111 172 +#define FRAME_backwd01 173 +#define FRAME_backwd02 174 +#define FRAME_backwd03 175 +#define FRAME_backwd04 176 +#define FRAME_backwd05 177 +#define FRAME_backwd06 178 +#define FRAME_backwd07 179 +#define FRAME_backwd08 180 +#define FRAME_backwd09 181 +#define FRAME_backwd10 182 +#define FRAME_backwd11 183 +#define FRAME_backwd12 184 +#define FRAME_backwd13 185 +#define FRAME_backwd14 186 +#define FRAME_backwd15 187 +#define FRAME_backwd16 188 +#define FRAME_backwd17 189 +#define FRAME_backwd18 190 +#define FRAME_backwd19 191 +#define FRAME_backwd20 192 +#define FRAME_backwd21 193 +#define FRAME_backwd22 194 +#define FRAME_backwd23 195 +#define FRAME_backwd24 196 +#define FRAME_attak101 197 +#define FRAME_attak102 198 +#define FRAME_attak103 199 +#define FRAME_attak104 200 +#define FRAME_attak105 201 +#define FRAME_attak106 202 +#define FRAME_attak107 203 +#define FRAME_attak108 204 + +#define MODEL_SCALE 1.000000 diff --git a/original/baseq2/m_infantry.c b/original/baseq2/m_infantry.c new file mode 100644 index 0000000..9ce7f21 --- /dev/null +++ b/original/baseq2/m_infantry.c @@ -0,0 +1,607 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +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. + +*/ +/* +============================================================================== + +INFANTRY + +============================================================================== +*/ + +#include "g_local.h" +#include "m_infantry.h" + +void InfantryMachineGun (edict_t *self); + + +static int sound_pain1; +static int sound_pain2; +static int sound_die1; +static int sound_die2; + +static int sound_gunshot; +static int sound_weapon_cock; +static int sound_punch_swing; +static int sound_punch_hit; +static int sound_sight; +static int sound_search; +static int sound_idle; + + +mframe_t infantry_frames_stand [] = +{ + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL +}; +mmove_t infantry_move_stand = {FRAME_stand50, FRAME_stand71, infantry_frames_stand, NULL}; + +void infantry_stand (edict_t *self) +{ + self->monsterinfo.currentmove = &infantry_move_stand; +} + + +mframe_t infantry_frames_fidget [] = +{ + ai_stand, 1, NULL, + ai_stand, 0, NULL, + ai_stand, 1, NULL, + ai_stand, 3, NULL, + ai_stand, 6, NULL, + ai_stand, 3, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 1, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 1, NULL, + ai_stand, 0, NULL, + ai_stand, -1, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 1, NULL, + ai_stand, 0, NULL, + ai_stand, -2, NULL, + ai_stand, 1, NULL, + ai_stand, 1, NULL, + ai_stand, 1, NULL, + ai_stand, -1, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, -1, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, -1, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 1, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, -1, NULL, + ai_stand, -1, NULL, + ai_stand, 0, NULL, + ai_stand, -3, NULL, + ai_stand, -2, NULL, + ai_stand, -3, NULL, + ai_stand, -3, NULL, + ai_stand, -2, NULL +}; +mmove_t infantry_move_fidget = {FRAME_stand01, FRAME_stand49, infantry_frames_fidget, infantry_stand}; + +void infantry_fidget (edict_t *self) +{ + self->monsterinfo.currentmove = &infantry_move_fidget; + gi.sound (self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0); +} + +mframe_t infantry_frames_walk [] = +{ + ai_walk, 5, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 5, NULL, + ai_walk, 4, NULL, + ai_walk, 5, NULL, + ai_walk, 6, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 5, NULL +}; +mmove_t infantry_move_walk = {FRAME_walk03, FRAME_walk14, infantry_frames_walk, NULL}; + +void infantry_walk (edict_t *self) +{ + self->monsterinfo.currentmove = &infantry_move_walk; +} + +mframe_t infantry_frames_run [] = +{ + ai_run, 10, NULL, + ai_run, 20, NULL, + ai_run, 5, NULL, + ai_run, 7, NULL, + ai_run, 30, NULL, + ai_run, 35, NULL, + ai_run, 2, NULL, + ai_run, 6, NULL +}; +mmove_t infantry_move_run = {FRAME_run01, FRAME_run08, infantry_frames_run, NULL}; + +void infantry_run (edict_t *self) +{ + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + self->monsterinfo.currentmove = &infantry_move_stand; + else + self->monsterinfo.currentmove = &infantry_move_run; +} + + +mframe_t infantry_frames_pain1 [] = +{ + ai_move, -3, NULL, + ai_move, -2, NULL, + ai_move, -1, NULL, + ai_move, -2, NULL, + ai_move, -1, NULL, + ai_move, 1, NULL, + ai_move, -1, NULL, + ai_move, 1, NULL, + ai_move, 6, NULL, + ai_move, 2, NULL +}; +mmove_t infantry_move_pain1 = {FRAME_pain101, FRAME_pain110, infantry_frames_pain1, infantry_run}; + +mframe_t infantry_frames_pain2 [] = +{ + ai_move, -3, NULL, + ai_move, -3, NULL, + ai_move, 0, NULL, + ai_move, -1, NULL, + ai_move, -2, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 2, NULL, + ai_move, 5, NULL, + ai_move, 2, NULL +}; +mmove_t infantry_move_pain2 = {FRAME_pain201, FRAME_pain210, infantry_frames_pain2, infantry_run}; + +void infantry_pain (edict_t *self, edict_t *other, float kick, int damage) +{ + int n; + + if (self->health < (self->max_health / 2)) + self->s.skinnum = 1; + + if (level.time < self->pain_debounce_time) + return; + + self->pain_debounce_time = level.time + 3; + + if (skill->value == 3) + return; // no pain anims in nightmare + + n = rand() % 2; + if (n == 0) + { + self->monsterinfo.currentmove = &infantry_move_pain1; + gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); + } + else + { + self->monsterinfo.currentmove = &infantry_move_pain2; + gi.sound (self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0); + } +} + + +vec3_t aimangles[] = +{ + 0.0, 5.0, 0.0, + 10.0, 15.0, 0.0, + 20.0, 25.0, 0.0, + 25.0, 35.0, 0.0, + 30.0, 40.0, 0.0, + 30.0, 45.0, 0.0, + 25.0, 50.0, 0.0, + 20.0, 40.0, 0.0, + 15.0, 35.0, 0.0, + 40.0, 35.0, 0.0, + 70.0, 35.0, 0.0, + 90.0, 35.0, 0.0 +}; + +void InfantryMachineGun (edict_t *self) +{ + vec3_t start, target; + vec3_t forward, right; + vec3_t vec; + int flash_number; + + if (self->s.frame == FRAME_attak111) + { + flash_number = MZ2_INFANTRY_MACHINEGUN_1; + AngleVectors (self->s.angles, forward, right, NULL); + G_ProjectSource (self->s.origin, monster_flash_offset[flash_number], forward, right, start); + + if (self->enemy) + { + VectorMA (self->enemy->s.origin, -0.2, self->enemy->velocity, target); + target[2] += self->enemy->viewheight; + VectorSubtract (target, start, forward); + VectorNormalize (forward); + } + else + { + AngleVectors (self->s.angles, forward, right, NULL); + } + } + else + { + flash_number = MZ2_INFANTRY_MACHINEGUN_2 + (self->s.frame - FRAME_death211); + + AngleVectors (self->s.angles, forward, right, NULL); + G_ProjectSource (self->s.origin, monster_flash_offset[flash_number], forward, right, start); + + VectorSubtract (self->s.angles, aimangles[flash_number-MZ2_INFANTRY_MACHINEGUN_2], vec); + AngleVectors (vec, forward, NULL, NULL); + } + + monster_fire_bullet (self, start, forward, 3, 4, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, flash_number); +} + +void infantry_sight (edict_t *self, edict_t *other) +{ + gi.sound (self, CHAN_BODY, sound_sight, 1, ATTN_NORM, 0); +} + +void infantry_dead (edict_t *self) +{ + VectorSet (self->mins, -16, -16, -24); + VectorSet (self->maxs, 16, 16, -8); + self->movetype = MOVETYPE_TOSS; + self->svflags |= SVF_DEADMONSTER; + gi.linkentity (self); + + M_FlyCheck (self); +} + +mframe_t infantry_frames_death1 [] = +{ + ai_move, -4, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, -1, NULL, + ai_move, -4, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, -1, NULL, + ai_move, 3, NULL, + ai_move, 1, NULL, + ai_move, 1, NULL, + ai_move, -2, NULL, + ai_move, 2, NULL, + ai_move, 2, NULL, + ai_move, 9, NULL, + ai_move, 9, NULL, + ai_move, 5, NULL, + ai_move, -3, NULL, + ai_move, -3, NULL +}; +mmove_t infantry_move_death1 = {FRAME_death101, FRAME_death120, infantry_frames_death1, infantry_dead}; + +// Off with his head +mframe_t infantry_frames_death2 [] = +{ + ai_move, 0, NULL, + ai_move, 1, NULL, + ai_move, 5, NULL, + ai_move, -1, NULL, + ai_move, 0, NULL, + ai_move, 1, NULL, + ai_move, 1, NULL, + ai_move, 4, NULL, + ai_move, 3, NULL, + ai_move, 0, NULL, + ai_move, -2, InfantryMachineGun, + ai_move, -2, InfantryMachineGun, + ai_move, -3, InfantryMachineGun, + ai_move, -1, InfantryMachineGun, + ai_move, -2, InfantryMachineGun, + ai_move, 0, InfantryMachineGun, + ai_move, 2, InfantryMachineGun, + ai_move, 2, InfantryMachineGun, + ai_move, 3, InfantryMachineGun, + ai_move, -10, InfantryMachineGun, + ai_move, -7, InfantryMachineGun, + ai_move, -8, InfantryMachineGun, + ai_move, -6, NULL, + ai_move, 4, NULL, + ai_move, 0, NULL +}; +mmove_t infantry_move_death2 = {FRAME_death201, FRAME_death225, infantry_frames_death2, infantry_dead}; + +mframe_t infantry_frames_death3 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, -6, NULL, + ai_move, -11, NULL, + ai_move, -3, NULL, + ai_move, -11, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t infantry_move_death3 = {FRAME_death301, FRAME_death309, infantry_frames_death3, infantry_dead}; + + +void infantry_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + int n; + +// check for gib + if (self->health <= self->gib_health) + { + gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0); + for (n= 0; n < 2; n++) + ThrowGib (self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC); + for (n= 0; n < 4; n++) + ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); + ThrowHead (self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC); + self->deadflag = DEAD_DEAD; + return; + } + + if (self->deadflag == DEAD_DEAD) + return; + +// regular death + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_YES; + + n = rand() % 3; + if (n == 0) + { + self->monsterinfo.currentmove = &infantry_move_death1; + gi.sound (self, CHAN_VOICE, sound_die2, 1, ATTN_NORM, 0); + } + else if (n == 1) + { + self->monsterinfo.currentmove = &infantry_move_death2; + gi.sound (self, CHAN_VOICE, sound_die1, 1, ATTN_NORM, 0); + } + else + { + self->monsterinfo.currentmove = &infantry_move_death3; + gi.sound (self, CHAN_VOICE, sound_die2, 1, ATTN_NORM, 0); + } +} + + +void infantry_duck_down (edict_t *self) +{ + if (self->monsterinfo.aiflags & AI_DUCKED) + return; + self->monsterinfo.aiflags |= AI_DUCKED; + self->maxs[2] -= 32; + self->takedamage = DAMAGE_YES; + self->monsterinfo.pausetime = level.time + 1; + gi.linkentity (self); +} + +void infantry_duck_hold (edict_t *self) +{ + if (level.time >= self->monsterinfo.pausetime) + self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; + else + self->monsterinfo.aiflags |= AI_HOLD_FRAME; +} + +void infantry_duck_up (edict_t *self) +{ + self->monsterinfo.aiflags &= ~AI_DUCKED; + self->maxs[2] += 32; + self->takedamage = DAMAGE_AIM; + gi.linkentity (self); +} + +mframe_t infantry_frames_duck [] = +{ + ai_move, -2, infantry_duck_down, + ai_move, -5, infantry_duck_hold, + ai_move, 3, NULL, + ai_move, 4, infantry_duck_up, + ai_move, 0, NULL +}; +mmove_t infantry_move_duck = {FRAME_duck01, FRAME_duck05, infantry_frames_duck, infantry_run}; + +void infantry_dodge (edict_t *self, edict_t *attacker, float eta) +{ + if (random() > 0.25) + return; + + if (!self->enemy) + self->enemy = attacker; + + self->monsterinfo.currentmove = &infantry_move_duck; +} + + +void infantry_cock_gun (edict_t *self) +{ + int n; + + gi.sound (self, CHAN_WEAPON, sound_weapon_cock, 1, ATTN_NORM, 0); + n = (rand() & 15) + 3 + 7; + self->monsterinfo.pausetime = level.time + n * FRAMETIME; +} + +void infantry_fire (edict_t *self) +{ + InfantryMachineGun (self); + + if (level.time >= self->monsterinfo.pausetime) + self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; + else + self->monsterinfo.aiflags |= AI_HOLD_FRAME; +} + +mframe_t infantry_frames_attack1 [] = +{ + ai_charge, 4, NULL, + ai_charge, -1, NULL, + ai_charge, -1, NULL, + ai_charge, 0, infantry_cock_gun, + ai_charge, -1, NULL, + ai_charge, 1, NULL, + ai_charge, 1, NULL, + ai_charge, 2, NULL, + ai_charge, -2, NULL, + ai_charge, -3, NULL, + ai_charge, 1, infantry_fire, + ai_charge, 5, NULL, + ai_charge, -1, NULL, + ai_charge, -2, NULL, + ai_charge, -3, NULL +}; +mmove_t infantry_move_attack1 = {FRAME_attak101, FRAME_attak115, infantry_frames_attack1, infantry_run}; + + +void infantry_swing (edict_t *self) +{ + gi.sound (self, CHAN_WEAPON, sound_punch_swing, 1, ATTN_NORM, 0); +} + +void infantry_smack (edict_t *self) +{ + vec3_t aim; + + VectorSet (aim, MELEE_DISTANCE, 0, 0); + if (fire_hit (self, aim, (5 + (rand() % 5)), 50)) + gi.sound (self, CHAN_WEAPON, sound_punch_hit, 1, ATTN_NORM, 0); +} + +mframe_t infantry_frames_attack2 [] = +{ + ai_charge, 3, NULL, + ai_charge, 6, NULL, + ai_charge, 0, infantry_swing, + ai_charge, 8, NULL, + ai_charge, 5, NULL, + ai_charge, 8, infantry_smack, + ai_charge, 6, NULL, + ai_charge, 3, NULL, +}; +mmove_t infantry_move_attack2 = {FRAME_attak201, FRAME_attak208, infantry_frames_attack2, infantry_run}; + +void infantry_attack(edict_t *self) +{ + if (range (self, self->enemy) == RANGE_MELEE) + self->monsterinfo.currentmove = &infantry_move_attack2; + else + self->monsterinfo.currentmove = &infantry_move_attack1; +} + + +/*QUAKED monster_infantry (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight +*/ +void SP_monster_infantry (edict_t *self) +{ + if (deathmatch->value) + { + G_FreeEdict (self); + return; + } + + sound_pain1 = gi.soundindex ("infantry/infpain1.wav"); + sound_pain2 = gi.soundindex ("infantry/infpain2.wav"); + sound_die1 = gi.soundindex ("infantry/infdeth1.wav"); + sound_die2 = gi.soundindex ("infantry/infdeth2.wav"); + + sound_gunshot = gi.soundindex ("infantry/infatck1.wav"); + sound_weapon_cock = gi.soundindex ("infantry/infatck3.wav"); + sound_punch_swing = gi.soundindex ("infantry/infatck2.wav"); + sound_punch_hit = gi.soundindex ("infantry/melee2.wav"); + + sound_sight = gi.soundindex ("infantry/infsght1.wav"); + sound_search = gi.soundindex ("infantry/infsrch1.wav"); + sound_idle = gi.soundindex ("infantry/infidle1.wav"); + + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex("models/monsters/infantry/tris.md2"); + VectorSet (self->mins, -16, -16, -24); + VectorSet (self->maxs, 16, 16, 32); + + self->health = 100; + self->gib_health = -40; + self->mass = 200; + + self->pain = infantry_pain; + self->die = infantry_die; + + self->monsterinfo.stand = infantry_stand; + self->monsterinfo.walk = infantry_walk; + self->monsterinfo.run = infantry_run; + self->monsterinfo.dodge = infantry_dodge; + self->monsterinfo.attack = infantry_attack; + self->monsterinfo.melee = NULL; + self->monsterinfo.sight = infantry_sight; + self->monsterinfo.idle = infantry_fidget; + + gi.linkentity (self); + + self->monsterinfo.currentmove = &infantry_move_stand; + self->monsterinfo.scale = MODEL_SCALE; + + walkmonster_start (self); +} diff --git a/original/baseq2/m_infantry.h b/original/baseq2/m_infantry.h new file mode 100644 index 0000000..be5e325 --- /dev/null +++ b/original/baseq2/m_infantry.h @@ -0,0 +1,232 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +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. + +*/ +// G:\quake2\baseq2\models/monsters/infantry + +// This file generated by ModelGen - Do NOT Modify + +#define FRAME_gun02 0 +#define FRAME_stand01 1 +#define FRAME_stand02 2 +#define FRAME_stand03 3 +#define FRAME_stand04 4 +#define FRAME_stand05 5 +#define FRAME_stand06 6 +#define FRAME_stand07 7 +#define FRAME_stand08 8 +#define FRAME_stand09 9 +#define FRAME_stand10 10 +#define FRAME_stand11 11 +#define FRAME_stand12 12 +#define FRAME_stand13 13 +#define FRAME_stand14 14 +#define FRAME_stand15 15 +#define FRAME_stand16 16 +#define FRAME_stand17 17 +#define FRAME_stand18 18 +#define FRAME_stand19 19 +#define FRAME_stand20 20 +#define FRAME_stand21 21 +#define FRAME_stand22 22 +#define FRAME_stand23 23 +#define FRAME_stand24 24 +#define FRAME_stand25 25 +#define FRAME_stand26 26 +#define FRAME_stand27 27 +#define FRAME_stand28 28 +#define FRAME_stand29 29 +#define FRAME_stand30 30 +#define FRAME_stand31 31 +#define FRAME_stand32 32 +#define FRAME_stand33 33 +#define FRAME_stand34 34 +#define FRAME_stand35 35 +#define FRAME_stand36 36 +#define FRAME_stand37 37 +#define FRAME_stand38 38 +#define FRAME_stand39 39 +#define FRAME_stand40 40 +#define FRAME_stand41 41 +#define FRAME_stand42 42 +#define FRAME_stand43 43 +#define FRAME_stand44 44 +#define FRAME_stand45 45 +#define FRAME_stand46 46 +#define FRAME_stand47 47 +#define FRAME_stand48 48 +#define FRAME_stand49 49 +#define FRAME_stand50 50 +#define FRAME_stand51 51 +#define FRAME_stand52 52 +#define FRAME_stand53 53 +#define FRAME_stand54 54 +#define FRAME_stand55 55 +#define FRAME_stand56 56 +#define FRAME_stand57 57 +#define FRAME_stand58 58 +#define FRAME_stand59 59 +#define FRAME_stand60 60 +#define FRAME_stand61 61 +#define FRAME_stand62 62 +#define FRAME_stand63 63 +#define FRAME_stand64 64 +#define FRAME_stand65 65 +#define FRAME_stand66 66 +#define FRAME_stand67 67 +#define FRAME_stand68 68 +#define FRAME_stand69 69 +#define FRAME_stand70 70 +#define FRAME_stand71 71 +#define FRAME_walk01 72 +#define FRAME_walk02 73 +#define FRAME_walk03 74 +#define FRAME_walk04 75 +#define FRAME_walk05 76 +#define FRAME_walk06 77 +#define FRAME_walk07 78 +#define FRAME_walk08 79 +#define FRAME_walk09 80 +#define FRAME_walk10 81 +#define FRAME_walk11 82 +#define FRAME_walk12 83 +#define FRAME_walk13 84 +#define FRAME_walk14 85 +#define FRAME_walk15 86 +#define FRAME_walk16 87 +#define FRAME_walk17 88 +#define FRAME_walk18 89 +#define FRAME_walk19 90 +#define FRAME_walk20 91 +#define FRAME_run01 92 +#define FRAME_run02 93 +#define FRAME_run03 94 +#define FRAME_run04 95 +#define FRAME_run05 96 +#define FRAME_run06 97 +#define FRAME_run07 98 +#define FRAME_run08 99 +#define FRAME_pain101 100 +#define FRAME_pain102 101 +#define FRAME_pain103 102 +#define FRAME_pain104 103 +#define FRAME_pain105 104 +#define FRAME_pain106 105 +#define FRAME_pain107 106 +#define FRAME_pain108 107 +#define FRAME_pain109 108 +#define FRAME_pain110 109 +#define FRAME_pain201 110 +#define FRAME_pain202 111 +#define FRAME_pain203 112 +#define FRAME_pain204 113 +#define FRAME_pain205 114 +#define FRAME_pain206 115 +#define FRAME_pain207 116 +#define FRAME_pain208 117 +#define FRAME_pain209 118 +#define FRAME_pain210 119 +#define FRAME_duck01 120 +#define FRAME_duck02 121 +#define FRAME_duck03 122 +#define FRAME_duck04 123 +#define FRAME_duck05 124 +#define FRAME_death101 125 +#define FRAME_death102 126 +#define FRAME_death103 127 +#define FRAME_death104 128 +#define FRAME_death105 129 +#define FRAME_death106 130 +#define FRAME_death107 131 +#define FRAME_death108 132 +#define FRAME_death109 133 +#define FRAME_death110 134 +#define FRAME_death111 135 +#define FRAME_death112 136 +#define FRAME_death113 137 +#define FRAME_death114 138 +#define FRAME_death115 139 +#define FRAME_death116 140 +#define FRAME_death117 141 +#define FRAME_death118 142 +#define FRAME_death119 143 +#define FRAME_death120 144 +#define FRAME_death201 145 +#define FRAME_death202 146 +#define FRAME_death203 147 +#define FRAME_death204 148 +#define FRAME_death205 149 +#define FRAME_death206 150 +#define FRAME_death207 151 +#define FRAME_death208 152 +#define FRAME_death209 153 +#define FRAME_death210 154 +#define FRAME_death211 155 +#define FRAME_death212 156 +#define FRAME_death213 157 +#define FRAME_death214 158 +#define FRAME_death215 159 +#define FRAME_death216 160 +#define FRAME_death217 161 +#define FRAME_death218 162 +#define FRAME_death219 163 +#define FRAME_death220 164 +#define FRAME_death221 165 +#define FRAME_death222 166 +#define FRAME_death223 167 +#define FRAME_death224 168 +#define FRAME_death225 169 +#define FRAME_death301 170 +#define FRAME_death302 171 +#define FRAME_death303 172 +#define FRAME_death304 173 +#define FRAME_death305 174 +#define FRAME_death306 175 +#define FRAME_death307 176 +#define FRAME_death308 177 +#define FRAME_death309 178 +#define FRAME_block01 179 +#define FRAME_block02 180 +#define FRAME_block03 181 +#define FRAME_block04 182 +#define FRAME_block05 183 +#define FRAME_attak101 184 +#define FRAME_attak102 185 +#define FRAME_attak103 186 +#define FRAME_attak104 187 +#define FRAME_attak105 188 +#define FRAME_attak106 189 +#define FRAME_attak107 190 +#define FRAME_attak108 191 +#define FRAME_attak109 192 +#define FRAME_attak110 193 +#define FRAME_attak111 194 +#define FRAME_attak112 195 +#define FRAME_attak113 196 +#define FRAME_attak114 197 +#define FRAME_attak115 198 +#define FRAME_attak201 199 +#define FRAME_attak202 200 +#define FRAME_attak203 201 +#define FRAME_attak204 202 +#define FRAME_attak205 203 +#define FRAME_attak206 204 +#define FRAME_attak207 205 +#define FRAME_attak208 206 + +#define MODEL_SCALE 1.000000 diff --git a/original/baseq2/m_insane.c b/original/baseq2/m_insane.c new file mode 100644 index 0000000..7f7cfd3 --- /dev/null +++ b/original/baseq2/m_insane.c @@ -0,0 +1,693 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +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. + +*/ +/* +============================================================================== + +insane + +============================================================================== +*/ + +#include "g_local.h" +#include "m_insane.h" + + +static int sound_fist; +static int sound_shake; +static int sound_moan; +static int sound_scream[8]; + +void insane_fist (edict_t *self) +{ + gi.sound (self, CHAN_VOICE, sound_fist, 1, ATTN_IDLE, 0); +} + +void insane_shake (edict_t *self) +{ + gi.sound (self, CHAN_VOICE, sound_shake, 1, ATTN_IDLE, 0); +} + +void insane_moan (edict_t *self) +{ + gi.sound (self, CHAN_VOICE, sound_moan, 1, ATTN_IDLE, 0); +} + +void insane_scream (edict_t *self) +{ + gi.sound (self, CHAN_VOICE, sound_scream[rand()%8], 1, ATTN_IDLE, 0); +} + + +void insane_stand (edict_t *self); +void insane_dead (edict_t *self); +void insane_cross (edict_t *self); +void insane_walk (edict_t *self); +void insane_run (edict_t *self); +void insane_checkdown (edict_t *self); +void insane_checkup (edict_t *self); +void insane_onground (edict_t *self); + + +mframe_t insane_frames_stand_normal [] = +{ + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, insane_checkdown +}; +mmove_t insane_move_stand_normal = {FRAME_stand60, FRAME_stand65, insane_frames_stand_normal, insane_stand}; + +mframe_t insane_frames_stand_insane [] = +{ + ai_stand, 0, insane_shake, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, insane_checkdown +}; +mmove_t insane_move_stand_insane = {FRAME_stand65, FRAME_stand94, insane_frames_stand_insane, insane_stand}; + +mframe_t insane_frames_uptodown [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, insane_moan, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 2.7, NULL, + ai_move, 4.1, NULL, + ai_move, 6, NULL, + ai_move, 7.6, NULL, + ai_move, 3.6, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, insane_fist, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, insane_fist, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t insane_move_uptodown = {FRAME_stand1, FRAME_stand40, insane_frames_uptodown, insane_onground}; + + +mframe_t insane_frames_downtoup [] = +{ + ai_move, -0.7, NULL, // 41 + ai_move, -1.2, NULL, // 42 + ai_move, -1.5, NULL, // 43 + ai_move, -4.5, NULL, // 44 + ai_move, -3.5, NULL, // 45 + ai_move, -0.2, NULL, // 46 + ai_move, 0, NULL, // 47 + ai_move, -1.3, NULL, // 48 + ai_move, -3, NULL, // 49 + ai_move, -2, NULL, // 50 + ai_move, 0, NULL, // 51 + ai_move, 0, NULL, // 52 + ai_move, 0, NULL, // 53 + ai_move, -3.3, NULL, // 54 + ai_move, -1.6, NULL, // 55 + ai_move, -0.3, NULL, // 56 + ai_move, 0, NULL, // 57 + ai_move, 0, NULL, // 58 + ai_move, 0, NULL // 59 +}; +mmove_t insane_move_downtoup = {FRAME_stand41, FRAME_stand59, insane_frames_downtoup, insane_stand}; + +mframe_t insane_frames_jumpdown [] = +{ + ai_move, 0.2, NULL, + ai_move, 11.5, NULL, + ai_move, 5.1, NULL, + ai_move, 7.1, NULL, + ai_move, 0, NULL +}; +mmove_t insane_move_jumpdown = {FRAME_stand96, FRAME_stand100, insane_frames_jumpdown, insane_onground}; + + +mframe_t insane_frames_down [] = +{ + ai_move, 0, NULL, // 100 + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, // 110 + ai_move, -1.7, NULL, + ai_move, -1.6, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, insane_fist, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, // 120 + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, // 130 + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, insane_moan, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, // 140 + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, // 150 + ai_move, 0.5, NULL, + ai_move, 0, NULL, + ai_move, -0.2, insane_scream, + ai_move, 0, NULL, + ai_move, 0.2, NULL, + ai_move, 0.4, NULL, + ai_move, 0.6, NULL, + ai_move, 0.8, NULL, + ai_move, 0.7, NULL, + ai_move, 0, insane_checkup // 160 +}; +mmove_t insane_move_down = {FRAME_stand100, FRAME_stand160, insane_frames_down, insane_onground}; + +mframe_t insane_frames_walk_normal [] = +{ + ai_walk, 0, insane_scream, + ai_walk, 2.5, NULL, + ai_walk, 3.5, NULL, + ai_walk, 1.7, NULL, + ai_walk, 2.3, NULL, + ai_walk, 2.4, NULL, + ai_walk, 2.2, NULL, + ai_walk, 4.2, NULL, + ai_walk, 5.6, NULL, + ai_walk, 3.3, NULL, + ai_walk, 2.4, NULL, + ai_walk, 0.9, NULL, + ai_walk, 0, NULL +}; +mmove_t insane_move_walk_normal = {FRAME_walk27, FRAME_walk39, insane_frames_walk_normal, insane_walk}; +mmove_t insane_move_run_normal = {FRAME_walk27, FRAME_walk39, insane_frames_walk_normal, insane_run}; + +mframe_t insane_frames_walk_insane [] = +{ + ai_walk, 0, insane_scream, // walk 1 + ai_walk, 3.4, NULL, // walk 2 + ai_walk, 3.6, NULL, // 3 + ai_walk, 2.9, NULL, // 4 + ai_walk, 2.2, NULL, // 5 + ai_walk, 2.6, NULL, // 6 + ai_walk, 0, NULL, // 7 + ai_walk, 0.7, NULL, // 8 + ai_walk, 4.8, NULL, // 9 + ai_walk, 5.3, NULL, // 10 + ai_walk, 1.1, NULL, // 11 + ai_walk, 2, NULL, // 12 + ai_walk, 0.5, NULL, // 13 + ai_walk, 0, NULL, // 14 + ai_walk, 0, NULL, // 15 + ai_walk, 4.9, NULL, // 16 + ai_walk, 6.7, NULL, // 17 + ai_walk, 3.8, NULL, // 18 + ai_walk, 2, NULL, // 19 + ai_walk, 0.2, NULL, // 20 + ai_walk, 0, NULL, // 21 + ai_walk, 3.4, NULL, // 22 + ai_walk, 6.4, NULL, // 23 + ai_walk, 5, NULL, // 24 + ai_walk, 1.8, NULL, // 25 + ai_walk, 0, NULL // 26 +}; +mmove_t insane_move_walk_insane = {FRAME_walk1, FRAME_walk26, insane_frames_walk_insane, insane_walk}; +mmove_t insane_move_run_insane = {FRAME_walk1, FRAME_walk26, insane_frames_walk_insane, insane_run}; + +mframe_t insane_frames_stand_pain [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t insane_move_stand_pain = {FRAME_st_pain2, FRAME_st_pain12, insane_frames_stand_pain, insane_run}; + +mframe_t insane_frames_stand_death [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t insane_move_stand_death = {FRAME_st_death2, FRAME_st_death18, insane_frames_stand_death, insane_dead}; + +mframe_t insane_frames_crawl [] = +{ + ai_walk, 0, insane_scream, + ai_walk, 1.5, NULL, + ai_walk, 2.1, NULL, + ai_walk, 3.6, NULL, + ai_walk, 2, NULL, + ai_walk, 0.9, NULL, + ai_walk, 3, NULL, + ai_walk, 3.4, NULL, + ai_walk, 2.4, NULL +}; +mmove_t insane_move_crawl = {FRAME_crawl1, FRAME_crawl9, insane_frames_crawl, NULL}; +mmove_t insane_move_runcrawl = {FRAME_crawl1, FRAME_crawl9, insane_frames_crawl, NULL}; + +mframe_t insane_frames_crawl_pain [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t insane_move_crawl_pain = {FRAME_cr_pain2, FRAME_cr_pain10, insane_frames_crawl_pain, insane_run}; + +mframe_t insane_frames_crawl_death [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t insane_move_crawl_death = {FRAME_cr_death10, FRAME_cr_death16, insane_frames_crawl_death, insane_dead}; + +mframe_t insane_frames_cross [] = +{ + ai_move, 0, insane_moan, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t insane_move_cross = {FRAME_cross1, FRAME_cross15, insane_frames_cross, insane_cross}; + +mframe_t insane_frames_struggle_cross [] = +{ + ai_move, 0, insane_scream, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t insane_move_struggle_cross = {FRAME_cross16, FRAME_cross30, insane_frames_struggle_cross, insane_cross}; + +void insane_cross (edict_t *self) +{ + if (random() < 0.8) + self->monsterinfo.currentmove = &insane_move_cross; + else + self->monsterinfo.currentmove = &insane_move_struggle_cross; +} + +void insane_walk (edict_t *self) +{ + if ( self->spawnflags & 16 ) // Hold Ground? + if (self->s.frame == FRAME_cr_pain10) + { + self->monsterinfo.currentmove = &insane_move_down; + return; + } + if (self->spawnflags & 4) + self->monsterinfo.currentmove = &insane_move_crawl; + else + if (random() <= 0.5) + self->monsterinfo.currentmove = &insane_move_walk_normal; + else + self->monsterinfo.currentmove = &insane_move_walk_insane; +} + +void insane_run (edict_t *self) +{ + if ( self->spawnflags & 16 ) // Hold Ground? + if (self->s.frame == FRAME_cr_pain10) + { + self->monsterinfo.currentmove = &insane_move_down; + return; + } + if (self->spawnflags & 4) // Crawling? + self->monsterinfo.currentmove = &insane_move_runcrawl; + else + if (random() <= 0.5) // Else, mix it up + self->monsterinfo.currentmove = &insane_move_run_normal; + else + self->monsterinfo.currentmove = &insane_move_run_insane; +} + + +void insane_pain (edict_t *self, edict_t *other, float kick, int damage) +{ + int l,r; + +// if (self->health < (self->max_health / 2)) +// self->s.skinnum = 1; + + if (level.time < self->pain_debounce_time) + return; + + self->pain_debounce_time = level.time + 3; + + r = 1 + (rand()&1); + if (self->health < 25) + l = 25; + else if (self->health < 50) + l = 50; + else if (self->health < 75) + l = 75; + else + l = 100; + gi.sound (self, CHAN_VOICE, gi.soundindex (va("player/male/pain%i_%i.wav", l, r)), 1, ATTN_IDLE, 0); + + if (skill->value == 3) + return; // no pain anims in nightmare + + // Don't go into pain frames if crucified. + if (self->spawnflags & 8) + { + self->monsterinfo.currentmove = &insane_move_struggle_cross; + return; + } + + if ( ((self->s.frame >= FRAME_crawl1) && (self->s.frame <= FRAME_crawl9)) || ((self->s.frame >= FRAME_stand99) && (self->s.frame <= FRAME_stand160)) ) + { + self->monsterinfo.currentmove = &insane_move_crawl_pain; + } + else + self->monsterinfo.currentmove = &insane_move_stand_pain; + +} + +void insane_onground (edict_t *self) +{ + self->monsterinfo.currentmove = &insane_move_down; +} + +void insane_checkdown (edict_t *self) +{ +// if ( (self->s.frame == FRAME_stand94) || (self->s.frame == FRAME_stand65) ) + if (self->spawnflags & 32) // Always stand + return; + if (random() < 0.3) + if (random() < 0.5) + self->monsterinfo.currentmove = &insane_move_uptodown; + else + self->monsterinfo.currentmove = &insane_move_jumpdown; +} + +void insane_checkup (edict_t *self) +{ + // If Hold_Ground and Crawl are set + if ( (self->spawnflags & 4) && (self->spawnflags & 16) ) + return; + if (random() < 0.5) + self->monsterinfo.currentmove = &insane_move_downtoup; + +} + +void insane_stand (edict_t *self) +{ + if (self->spawnflags & 8) // If crucified + { + self->monsterinfo.currentmove = &insane_move_cross; + self->monsterinfo.aiflags |= AI_STAND_GROUND; + } + // If Hold_Ground and Crawl are set + else if ( (self->spawnflags & 4) && (self->spawnflags & 16) ) + self->monsterinfo.currentmove = &insane_move_down; + else + if (random() < 0.5) + self->monsterinfo.currentmove = &insane_move_stand_normal; + else + self->monsterinfo.currentmove = &insane_move_stand_insane; +} + +void insane_dead (edict_t *self) +{ + if (self->spawnflags & 8) + { + self->flags |= FL_FLY; + } + else + { + VectorSet (self->mins, -16, -16, -24); + VectorSet (self->maxs, 16, 16, -8); + self->movetype = MOVETYPE_TOSS; + } + self->svflags |= SVF_DEADMONSTER; + self->nextthink = 0; + gi.linkentity (self); +} + + +void insane_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + int n; + + if (self->health <= self->gib_health) + { + gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_IDLE, 0); + for (n= 0; n < 2; n++) + ThrowGib (self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC); + for (n= 0; n < 4; n++) + ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); + ThrowHead (self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC); + self->deadflag = DEAD_DEAD; + return; + } + + if (self->deadflag == DEAD_DEAD) + return; + + gi.sound (self, CHAN_VOICE, gi.soundindex(va("player/male/death%i.wav", (rand()%4)+1)), 1, ATTN_IDLE, 0); + + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_YES; + + if (self->spawnflags & 8) + { + insane_dead (self); + } + else + { + if ( ((self->s.frame >= FRAME_crawl1) && (self->s.frame <= FRAME_crawl9)) || ((self->s.frame >= FRAME_stand99) && (self->s.frame <= FRAME_stand160)) ) + self->monsterinfo.currentmove = &insane_move_crawl_death; + else + self->monsterinfo.currentmove = &insane_move_stand_death; + } +} + + +/*QUAKED misc_insane (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn CRAWL CRUCIFIED STAND_GROUND ALWAYS_STAND +*/ +void SP_misc_insane (edict_t *self) +{ +// static int skin = 0; //@@ + + if (deathmatch->value) + { + G_FreeEdict (self); + return; + } + + sound_fist = gi.soundindex ("insane/insane11.wav"); + sound_shake = gi.soundindex ("insane/insane5.wav"); + sound_moan = gi.soundindex ("insane/insane7.wav"); + sound_scream[0] = gi.soundindex ("insane/insane1.wav"); + sound_scream[1] = gi.soundindex ("insane/insane2.wav"); + sound_scream[2] = gi.soundindex ("insane/insane3.wav"); + sound_scream[3] = gi.soundindex ("insane/insane4.wav"); + sound_scream[4] = gi.soundindex ("insane/insane6.wav"); + sound_scream[5] = gi.soundindex ("insane/insane8.wav"); + sound_scream[6] = gi.soundindex ("insane/insane9.wav"); + sound_scream[7] = gi.soundindex ("insane/insane10.wav"); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex("models/monsters/insane/tris.md2"); + + VectorSet (self->mins, -16, -16, -24); + VectorSet (self->maxs, 16, 16, 32); + + self->health = 100; + self->gib_health = -50; + self->mass = 300; + + self->pain = insane_pain; + self->die = insane_die; + + self->monsterinfo.stand = insane_stand; + self->monsterinfo.walk = insane_walk; + self->monsterinfo.run = insane_run; + self->monsterinfo.dodge = NULL; + self->monsterinfo.attack = NULL; + self->monsterinfo.melee = NULL; + self->monsterinfo.sight = NULL; + self->monsterinfo.aiflags |= AI_GOOD_GUY; + +//@@ +// self->s.skinnum = skin; +// skin++; +// if (skin > 12) +// skin = 0; + + gi.linkentity (self); + + if (self->spawnflags & 16) // Stand Ground + self->monsterinfo.aiflags |= AI_STAND_GROUND; + + self->monsterinfo.currentmove = &insane_move_stand_normal; + + self->monsterinfo.scale = MODEL_SCALE; + + if (self->spawnflags & 8) // Crucified ? + { + VectorSet (self->mins, -16, 0, 0); + VectorSet (self->maxs, 16, 8, 32); + self->flags |= FL_NO_KNOCKBACK; + flymonster_start (self); + } + else + { + walkmonster_start (self); + self->s.skinnum = rand()%3; + } +} diff --git a/original/baseq2/m_insane.h b/original/baseq2/m_insane.h new file mode 100644 index 0000000..cd5ad9a --- /dev/null +++ b/original/baseq2/m_insane.h @@ -0,0 +1,307 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +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. + +*/ +// G:\quake2\baseq2\models/monsters/insane + +// This file generated by ModelGen - Do NOT Modify + +#define FRAME_stand1 0 +#define FRAME_stand2 1 +#define FRAME_stand3 2 +#define FRAME_stand4 3 +#define FRAME_stand5 4 +#define FRAME_stand6 5 +#define FRAME_stand7 6 +#define FRAME_stand8 7 +#define FRAME_stand9 8 +#define FRAME_stand10 9 +#define FRAME_stand11 10 +#define FRAME_stand12 11 +#define FRAME_stand13 12 +#define FRAME_stand14 13 +#define FRAME_stand15 14 +#define FRAME_stand16 15 +#define FRAME_stand17 16 +#define FRAME_stand18 17 +#define FRAME_stand19 18 +#define FRAME_stand20 19 +#define FRAME_stand21 20 +#define FRAME_stand22 21 +#define FRAME_stand23 22 +#define FRAME_stand24 23 +#define FRAME_stand25 24 +#define FRAME_stand26 25 +#define FRAME_stand27 26 +#define FRAME_stand28 27 +#define FRAME_stand29 28 +#define FRAME_stand30 29 +#define FRAME_stand31 30 +#define FRAME_stand32 31 +#define FRAME_stand33 32 +#define FRAME_stand34 33 +#define FRAME_stand35 34 +#define FRAME_stand36 35 +#define FRAME_stand37 36 +#define FRAME_stand38 37 +#define FRAME_stand39 38 +#define FRAME_stand40 39 +#define FRAME_stand41 40 +#define FRAME_stand42 41 +#define FRAME_stand43 42 +#define FRAME_stand44 43 +#define FRAME_stand45 44 +#define FRAME_stand46 45 +#define FRAME_stand47 46 +#define FRAME_stand48 47 +#define FRAME_stand49 48 +#define FRAME_stand50 49 +#define FRAME_stand51 50 +#define FRAME_stand52 51 +#define FRAME_stand53 52 +#define FRAME_stand54 53 +#define FRAME_stand55 54 +#define FRAME_stand56 55 +#define FRAME_stand57 56 +#define FRAME_stand58 57 +#define FRAME_stand59 58 +#define FRAME_stand60 59 +#define FRAME_stand61 60 +#define FRAME_stand62 61 +#define FRAME_stand63 62 +#define FRAME_stand64 63 +#define FRAME_stand65 64 +#define FRAME_stand66 65 +#define FRAME_stand67 66 +#define FRAME_stand68 67 +#define FRAME_stand69 68 +#define FRAME_stand70 69 +#define FRAME_stand71 70 +#define FRAME_stand72 71 +#define FRAME_stand73 72 +#define FRAME_stand74 73 +#define FRAME_stand75 74 +#define FRAME_stand76 75 +#define FRAME_stand77 76 +#define FRAME_stand78 77 +#define FRAME_stand79 78 +#define FRAME_stand80 79 +#define FRAME_stand81 80 +#define FRAME_stand82 81 +#define FRAME_stand83 82 +#define FRAME_stand84 83 +#define FRAME_stand85 84 +#define FRAME_stand86 85 +#define FRAME_stand87 86 +#define FRAME_stand88 87 +#define FRAME_stand89 88 +#define FRAME_stand90 89 +#define FRAME_stand91 90 +#define FRAME_stand92 91 +#define FRAME_stand93 92 +#define FRAME_stand94 93 +#define FRAME_stand95 94 +#define FRAME_stand96 95 +#define FRAME_stand97 96 +#define FRAME_stand98 97 +#define FRAME_stand99 98 +#define FRAME_stand100 99 +#define FRAME_stand101 100 +#define FRAME_stand102 101 +#define FRAME_stand103 102 +#define FRAME_stand104 103 +#define FRAME_stand105 104 +#define FRAME_stand106 105 +#define FRAME_stand107 106 +#define FRAME_stand108 107 +#define FRAME_stand109 108 +#define FRAME_stand110 109 +#define FRAME_stand111 110 +#define FRAME_stand112 111 +#define FRAME_stand113 112 +#define FRAME_stand114 113 +#define FRAME_stand115 114 +#define FRAME_stand116 115 +#define FRAME_stand117 116 +#define FRAME_stand118 117 +#define FRAME_stand119 118 +#define FRAME_stand120 119 +#define FRAME_stand121 120 +#define FRAME_stand122 121 +#define FRAME_stand123 122 +#define FRAME_stand124 123 +#define FRAME_stand125 124 +#define FRAME_stand126 125 +#define FRAME_stand127 126 +#define FRAME_stand128 127 +#define FRAME_stand129 128 +#define FRAME_stand130 129 +#define FRAME_stand131 130 +#define FRAME_stand132 131 +#define FRAME_stand133 132 +#define FRAME_stand134 133 +#define FRAME_stand135 134 +#define FRAME_stand136 135 +#define FRAME_stand137 136 +#define FRAME_stand138 137 +#define FRAME_stand139 138 +#define FRAME_stand140 139 +#define FRAME_stand141 140 +#define FRAME_stand142 141 +#define FRAME_stand143 142 +#define FRAME_stand144 143 +#define FRAME_stand145 144 +#define FRAME_stand146 145 +#define FRAME_stand147 146 +#define FRAME_stand148 147 +#define FRAME_stand149 148 +#define FRAME_stand150 149 +#define FRAME_stand151 150 +#define FRAME_stand152 151 +#define FRAME_stand153 152 +#define FRAME_stand154 153 +#define FRAME_stand155 154 +#define FRAME_stand156 155 +#define FRAME_stand157 156 +#define FRAME_stand158 157 +#define FRAME_stand159 158 +#define FRAME_stand160 159 +#define FRAME_walk27 160 +#define FRAME_walk28 161 +#define FRAME_walk29 162 +#define FRAME_walk30 163 +#define FRAME_walk31 164 +#define FRAME_walk32 165 +#define FRAME_walk33 166 +#define FRAME_walk34 167 +#define FRAME_walk35 168 +#define FRAME_walk36 169 +#define FRAME_walk37 170 +#define FRAME_walk38 171 +#define FRAME_walk39 172 +#define FRAME_walk1 173 +#define FRAME_walk2 174 +#define FRAME_walk3 175 +#define FRAME_walk4 176 +#define FRAME_walk5 177 +#define FRAME_walk6 178 +#define FRAME_walk7 179 +#define FRAME_walk8 180 +#define FRAME_walk9 181 +#define FRAME_walk10 182 +#define FRAME_walk11 183 +#define FRAME_walk12 184 +#define FRAME_walk13 185 +#define FRAME_walk14 186 +#define FRAME_walk15 187 +#define FRAME_walk16 188 +#define FRAME_walk17 189 +#define FRAME_walk18 190 +#define FRAME_walk19 191 +#define FRAME_walk20 192 +#define FRAME_walk21 193 +#define FRAME_walk22 194 +#define FRAME_walk23 195 +#define FRAME_walk24 196 +#define FRAME_walk25 197 +#define FRAME_walk26 198 +#define FRAME_st_pain2 199 +#define FRAME_st_pain3 200 +#define FRAME_st_pain4 201 +#define FRAME_st_pain5 202 +#define FRAME_st_pain6 203 +#define FRAME_st_pain7 204 +#define FRAME_st_pain8 205 +#define FRAME_st_pain9 206 +#define FRAME_st_pain10 207 +#define FRAME_st_pain11 208 +#define FRAME_st_pain12 209 +#define FRAME_st_death2 210 +#define FRAME_st_death3 211 +#define FRAME_st_death4 212 +#define FRAME_st_death5 213 +#define FRAME_st_death6 214 +#define FRAME_st_death7 215 +#define FRAME_st_death8 216 +#define FRAME_st_death9 217 +#define FRAME_st_death10 218 +#define FRAME_st_death11 219 +#define FRAME_st_death12 220 +#define FRAME_st_death13 221 +#define FRAME_st_death14 222 +#define FRAME_st_death15 223 +#define FRAME_st_death16 224 +#define FRAME_st_death17 225 +#define FRAME_st_death18 226 +#define FRAME_crawl1 227 +#define FRAME_crawl2 228 +#define FRAME_crawl3 229 +#define FRAME_crawl4 230 +#define FRAME_crawl5 231 +#define FRAME_crawl6 232 +#define FRAME_crawl7 233 +#define FRAME_crawl8 234 +#define FRAME_crawl9 235 +#define FRAME_cr_pain2 236 +#define FRAME_cr_pain3 237 +#define FRAME_cr_pain4 238 +#define FRAME_cr_pain5 239 +#define FRAME_cr_pain6 240 +#define FRAME_cr_pain7 241 +#define FRAME_cr_pain8 242 +#define FRAME_cr_pain9 243 +#define FRAME_cr_pain10 244 +#define FRAME_cr_death10 245 +#define FRAME_cr_death11 246 +#define FRAME_cr_death12 247 +#define FRAME_cr_death13 248 +#define FRAME_cr_death14 249 +#define FRAME_cr_death15 250 +#define FRAME_cr_death16 251 +#define FRAME_cross1 252 +#define FRAME_cross2 253 +#define FRAME_cross3 254 +#define FRAME_cross4 255 +#define FRAME_cross5 256 +#define FRAME_cross6 257 +#define FRAME_cross7 258 +#define FRAME_cross8 259 +#define FRAME_cross9 260 +#define FRAME_cross10 261 +#define FRAME_cross11 262 +#define FRAME_cross12 263 +#define FRAME_cross13 264 +#define FRAME_cross14 265 +#define FRAME_cross15 266 +#define FRAME_cross16 267 +#define FRAME_cross17 268 +#define FRAME_cross18 269 +#define FRAME_cross19 270 +#define FRAME_cross20 271 +#define FRAME_cross21 272 +#define FRAME_cross22 273 +#define FRAME_cross23 274 +#define FRAME_cross24 275 +#define FRAME_cross25 276 +#define FRAME_cross26 277 +#define FRAME_cross27 278 +#define FRAME_cross28 279 +#define FRAME_cross29 280 +#define FRAME_cross30 281 + +#define MODEL_SCALE 1.000000 diff --git a/original/baseq2/m_medic.c b/original/baseq2/m_medic.c new file mode 100644 index 0000000..7607f82 --- /dev/null +++ b/original/baseq2/m_medic.c @@ -0,0 +1,769 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +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. + +*/ +/* +============================================================================== + +MEDIC + +============================================================================== +*/ + +#include "g_local.h" +#include "m_medic.h" + +qboolean visible (edict_t *self, edict_t *other); + + +static int sound_idle1; +static int sound_pain1; +static int sound_pain2; +static int sound_die; +static int sound_sight; +static int sound_search; +static int sound_hook_launch; +static int sound_hook_hit; +static int sound_hook_heal; +static int sound_hook_retract; + + +edict_t *medic_FindDeadMonster (edict_t *self) +{ + edict_t *ent = NULL; + edict_t *best = NULL; + + while ((ent = findradius(ent, self->s.origin, 1024)) != NULL) + { + if (ent == self) + continue; + if (!(ent->svflags & SVF_MONSTER)) + continue; + if (ent->monsterinfo.aiflags & AI_GOOD_GUY) + continue; + if (ent->owner) + continue; + if (ent->health > 0) + continue; + if (ent->nextthink) + continue; + if (!visible(self, ent)) + continue; + if (!best) + { + best = ent; + continue; + } + if (ent->max_health <= best->max_health) + continue; + best = ent; + } + + return best; +} + +void medic_idle (edict_t *self) +{ + edict_t *ent; + + gi.sound (self, CHAN_VOICE, sound_idle1, 1, ATTN_IDLE, 0); + + ent = medic_FindDeadMonster(self); + if (ent) + { + self->enemy = ent; + self->enemy->owner = self; + self->monsterinfo.aiflags |= AI_MEDIC; + FoundTarget (self); + } +} + +void medic_search (edict_t *self) +{ + edict_t *ent; + + gi.sound (self, CHAN_VOICE, sound_search, 1, ATTN_IDLE, 0); + + if (!self->oldenemy) + { + ent = medic_FindDeadMonster(self); + if (ent) + { + self->oldenemy = self->enemy; + self->enemy = ent; + self->enemy->owner = self; + self->monsterinfo.aiflags |= AI_MEDIC; + FoundTarget (self); + } + } +} + +void medic_sight (edict_t *self, edict_t *other) +{ + gi.sound (self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); +} + + +mframe_t medic_frames_stand [] = +{ + ai_stand, 0, medic_idle, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + +}; +mmove_t medic_move_stand = {FRAME_wait1, FRAME_wait90, medic_frames_stand, NULL}; + +void medic_stand (edict_t *self) +{ + self->monsterinfo.currentmove = &medic_move_stand; +} + + +mframe_t medic_frames_walk [] = +{ + ai_walk, 6.2, NULL, + ai_walk, 18.1, NULL, + ai_walk, 1, NULL, + ai_walk, 9, NULL, + ai_walk, 10, NULL, + ai_walk, 9, NULL, + ai_walk, 11, NULL, + ai_walk, 11.6, NULL, + ai_walk, 2, NULL, + ai_walk, 9.9, NULL, + ai_walk, 14, NULL, + ai_walk, 9.3, NULL +}; +mmove_t medic_move_walk = {FRAME_walk1, FRAME_walk12, medic_frames_walk, NULL}; + +void medic_walk (edict_t *self) +{ + self->monsterinfo.currentmove = &medic_move_walk; +} + + +mframe_t medic_frames_run [] = +{ + ai_run, 18, NULL, + ai_run, 22.5, NULL, + ai_run, 25.4, NULL, + ai_run, 23.4, NULL, + ai_run, 24, NULL, + ai_run, 35.6, NULL + +}; +mmove_t medic_move_run = {FRAME_run1, FRAME_run6, medic_frames_run, NULL}; + +void medic_run (edict_t *self) +{ + if (!(self->monsterinfo.aiflags & AI_MEDIC)) + { + edict_t *ent; + + ent = medic_FindDeadMonster(self); + if (ent) + { + self->oldenemy = self->enemy; + self->enemy = ent; + self->enemy->owner = self; + self->monsterinfo.aiflags |= AI_MEDIC; + FoundTarget (self); + return; + } + } + + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + self->monsterinfo.currentmove = &medic_move_stand; + else + self->monsterinfo.currentmove = &medic_move_run; +} + + +mframe_t medic_frames_pain1 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t medic_move_pain1 = {FRAME_paina1, FRAME_paina8, medic_frames_pain1, medic_run}; + +mframe_t medic_frames_pain2 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t medic_move_pain2 = {FRAME_painb1, FRAME_painb15, medic_frames_pain2, medic_run}; + +void medic_pain (edict_t *self, edict_t *other, float kick, int damage) +{ + if (self->health < (self->max_health / 2)) + self->s.skinnum = 1; + + if (level.time < self->pain_debounce_time) + return; + + self->pain_debounce_time = level.time + 3; + + if (skill->value == 3) + return; // no pain anims in nightmare + + if (random() < 0.5) + { + self->monsterinfo.currentmove = &medic_move_pain1; + gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); + } + else + { + self->monsterinfo.currentmove = &medic_move_pain2; + gi.sound (self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0); + } +} + +void medic_fire_blaster (edict_t *self) +{ + vec3_t start; + vec3_t forward, right; + vec3_t end; + vec3_t dir; + int effect; + + if ((self->s.frame == FRAME_attack9) || (self->s.frame == FRAME_attack12)) + effect = EF_BLASTER; + else if ((self->s.frame == FRAME_attack19) || (self->s.frame == FRAME_attack22) || (self->s.frame == FRAME_attack25) || (self->s.frame == FRAME_attack28)) + effect = EF_HYPERBLASTER; + else + effect = 0; + + AngleVectors (self->s.angles, forward, right, NULL); + G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_MEDIC_BLASTER_1], forward, right, start); + + VectorCopy (self->enemy->s.origin, end); + end[2] += self->enemy->viewheight; + VectorSubtract (end, start, dir); + + monster_fire_blaster (self, start, dir, 2, 1000, MZ2_MEDIC_BLASTER_1, effect); +} + + +void medic_dead (edict_t *self) +{ + VectorSet (self->mins, -16, -16, -24); + VectorSet (self->maxs, 16, 16, -8); + self->movetype = MOVETYPE_TOSS; + self->svflags |= SVF_DEADMONSTER; + self->nextthink = 0; + gi.linkentity (self); +} + +mframe_t medic_frames_death [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t medic_move_death = {FRAME_death1, FRAME_death30, medic_frames_death, medic_dead}; + +void medic_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + int n; + + // if we had a pending patient, free him up for another medic + if ((self->enemy) && (self->enemy->owner == self)) + self->enemy->owner = NULL; + +// check for gib + if (self->health <= self->gib_health) + { + gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0); + for (n= 0; n < 2; n++) + ThrowGib (self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC); + for (n= 0; n < 4; n++) + ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); + ThrowHead (self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC); + self->deadflag = DEAD_DEAD; + return; + } + + if (self->deadflag == DEAD_DEAD) + return; + +// regular death + gi.sound (self, CHAN_VOICE, sound_die, 1, ATTN_NORM, 0); + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_YES; + + self->monsterinfo.currentmove = &medic_move_death; +} + + +void medic_duck_down (edict_t *self) +{ + if (self->monsterinfo.aiflags & AI_DUCKED) + return; + self->monsterinfo.aiflags |= AI_DUCKED; + self->maxs[2] -= 32; + self->takedamage = DAMAGE_YES; + self->monsterinfo.pausetime = level.time + 1; + gi.linkentity (self); +} + +void medic_duck_hold (edict_t *self) +{ + if (level.time >= self->monsterinfo.pausetime) + self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; + else + self->monsterinfo.aiflags |= AI_HOLD_FRAME; +} + +void medic_duck_up (edict_t *self) +{ + self->monsterinfo.aiflags &= ~AI_DUCKED; + self->maxs[2] += 32; + self->takedamage = DAMAGE_AIM; + gi.linkentity (self); +} + +mframe_t medic_frames_duck [] = +{ + ai_move, -1, NULL, + ai_move, -1, NULL, + ai_move, -1, medic_duck_down, + ai_move, -1, medic_duck_hold, + ai_move, -1, NULL, + ai_move, -1, NULL, + ai_move, -1, medic_duck_up, + ai_move, -1, NULL, + ai_move, -1, NULL, + ai_move, -1, NULL, + ai_move, -1, NULL, + ai_move, -1, NULL, + ai_move, -1, NULL, + ai_move, -1, NULL, + ai_move, -1, NULL, + ai_move, -1, NULL +}; +mmove_t medic_move_duck = {FRAME_duck1, FRAME_duck16, medic_frames_duck, medic_run}; + +void medic_dodge (edict_t *self, edict_t *attacker, float eta) +{ + if (random() > 0.25) + return; + + if (!self->enemy) + self->enemy = attacker; + + self->monsterinfo.currentmove = &medic_move_duck; +} + +mframe_t medic_frames_attackHyperBlaster [] = +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, medic_fire_blaster, + ai_charge, 0, medic_fire_blaster, + ai_charge, 0, medic_fire_blaster, + ai_charge, 0, medic_fire_blaster, + ai_charge, 0, medic_fire_blaster, + ai_charge, 0, medic_fire_blaster, + ai_charge, 0, medic_fire_blaster, + ai_charge, 0, medic_fire_blaster, + ai_charge, 0, medic_fire_blaster, + ai_charge, 0, medic_fire_blaster, + ai_charge, 0, medic_fire_blaster, + ai_charge, 0, medic_fire_blaster +}; +mmove_t medic_move_attackHyperBlaster = {FRAME_attack15, FRAME_attack30, medic_frames_attackHyperBlaster, medic_run}; + + +void medic_continue (edict_t *self) +{ + if (visible (self, self->enemy) ) + if (random() <= 0.95) + self->monsterinfo.currentmove = &medic_move_attackHyperBlaster; +} + + +mframe_t medic_frames_attackBlaster [] = +{ + ai_charge, 0, NULL, + ai_charge, 5, NULL, + ai_charge, 5, NULL, + ai_charge, 3, NULL, + ai_charge, 2, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, medic_fire_blaster, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, medic_fire_blaster, + ai_charge, 0, NULL, + ai_charge, 0, medic_continue // Change to medic_continue... Else, go to frame 32 +}; +mmove_t medic_move_attackBlaster = {FRAME_attack1, FRAME_attack14, medic_frames_attackBlaster, medic_run}; + + +void medic_hook_launch (edict_t *self) +{ + gi.sound (self, CHAN_WEAPON, sound_hook_launch, 1, ATTN_NORM, 0); +} + +void ED_CallSpawn (edict_t *ent); + +static vec3_t medic_cable_offsets[] = +{ + 45.0, -9.2, 15.5, + 48.4, -9.7, 15.2, + 47.8, -9.8, 15.8, + 47.3, -9.3, 14.3, + 45.4, -10.1, 13.1, + 41.9, -12.7, 12.0, + 37.8, -15.8, 11.2, + 34.3, -18.4, 10.7, + 32.7, -19.7, 10.4, + 32.7, -19.7, 10.4 +}; + +void medic_cable_attack (edict_t *self) +{ + vec3_t offset, start, end, f, r; + trace_t tr; + vec3_t dir, angles; + float distance; + + if (!self->enemy->inuse) + return; + + AngleVectors (self->s.angles, f, r, NULL); + VectorCopy (medic_cable_offsets[self->s.frame - FRAME_attack42], offset); + G_ProjectSource (self->s.origin, offset, f, r, start); + + // check for max distance + VectorSubtract (start, self->enemy->s.origin, dir); + distance = VectorLength(dir); + if (distance > 256) + return; + + // check for min/max pitch + vectoangles (dir, angles); + if (angles[0] < -180) + angles[0] += 360; + if (fabs(angles[0]) > 45) + return; + + tr = gi.trace (start, NULL, NULL, self->enemy->s.origin, self, MASK_SHOT); + if (tr.fraction != 1.0 && tr.ent != self->enemy) + return; + + if (self->s.frame == FRAME_attack43) + { + gi.sound (self->enemy, CHAN_AUTO, sound_hook_hit, 1, ATTN_NORM, 0); + self->enemy->monsterinfo.aiflags |= AI_RESURRECTING; + } + else if (self->s.frame == FRAME_attack50) + { + self->enemy->spawnflags = 0; + self->enemy->monsterinfo.aiflags = 0; + self->enemy->target = NULL; + self->enemy->targetname = NULL; + self->enemy->combattarget = NULL; + self->enemy->deathtarget = NULL; + self->enemy->owner = self; + ED_CallSpawn (self->enemy); + self->enemy->owner = NULL; + if (self->enemy->think) + { + self->enemy->nextthink = level.time; + self->enemy->think (self->enemy); + } + self->enemy->monsterinfo.aiflags |= AI_RESURRECTING; + if (self->oldenemy && self->oldenemy->client) + { + self->enemy->enemy = self->oldenemy; + FoundTarget (self->enemy); + } + } + else + { + if (self->s.frame == FRAME_attack44) + gi.sound (self, CHAN_WEAPON, sound_hook_heal, 1, ATTN_NORM, 0); + } + + // adjust start for beam origin being in middle of a segment + VectorMA (start, 8, f, start); + + // adjust end z for end spot since the monster is currently dead + VectorCopy (self->enemy->s.origin, end); + end[2] = self->enemy->absmin[2] + self->enemy->size[2] / 2; + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_MEDIC_CABLE_ATTACK); + gi.WriteShort (self - g_edicts); + gi.WritePosition (start); + gi.WritePosition (end); + gi.multicast (self->s.origin, MULTICAST_PVS); +} + +void medic_hook_retract (edict_t *self) +{ + gi.sound (self, CHAN_WEAPON, sound_hook_retract, 1, ATTN_NORM, 0); + self->enemy->monsterinfo.aiflags &= ~AI_RESURRECTING; +} + +mframe_t medic_frames_attackCable [] = +{ + ai_move, 2, NULL, + ai_move, 3, NULL, + ai_move, 5, NULL, + ai_move, 4.4, NULL, + ai_charge, 4.7, NULL, + ai_charge, 5, NULL, + ai_charge, 6, NULL, + ai_charge, 4, NULL, + ai_charge, 0, NULL, + ai_move, 0, medic_hook_launch, + ai_move, 0, medic_cable_attack, + ai_move, 0, medic_cable_attack, + ai_move, 0, medic_cable_attack, + ai_move, 0, medic_cable_attack, + ai_move, 0, medic_cable_attack, + ai_move, 0, medic_cable_attack, + ai_move, 0, medic_cable_attack, + ai_move, 0, medic_cable_attack, + ai_move, 0, medic_cable_attack, + ai_move, -15, medic_hook_retract, + ai_move, -1.5, NULL, + ai_move, -1.2, NULL, + ai_move, -3, NULL, + ai_move, -2, NULL, + ai_move, 0.3, NULL, + ai_move, 0.7, NULL, + ai_move, 1.2, NULL, + ai_move, 1.3, NULL +}; +mmove_t medic_move_attackCable = {FRAME_attack33, FRAME_attack60, medic_frames_attackCable, medic_run}; + + +void medic_attack(edict_t *self) +{ + if (self->monsterinfo.aiflags & AI_MEDIC) + self->monsterinfo.currentmove = &medic_move_attackCable; + else + self->monsterinfo.currentmove = &medic_move_attackBlaster; +} + +qboolean medic_checkattack (edict_t *self) +{ + if (self->monsterinfo.aiflags & AI_MEDIC) + { + medic_attack(self); + return true; + } + + return M_CheckAttack (self); +} + + +/*QUAKED monster_medic (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight +*/ +void SP_monster_medic (edict_t *self) +{ + if (deathmatch->value) + { + G_FreeEdict (self); + return; + } + + sound_idle1 = gi.soundindex ("medic/idle.wav"); + sound_pain1 = gi.soundindex ("medic/medpain1.wav"); + sound_pain2 = gi.soundindex ("medic/medpain2.wav"); + sound_die = gi.soundindex ("medic/meddeth1.wav"); + sound_sight = gi.soundindex ("medic/medsght1.wav"); + sound_search = gi.soundindex ("medic/medsrch1.wav"); + sound_hook_launch = gi.soundindex ("medic/medatck2.wav"); + sound_hook_hit = gi.soundindex ("medic/medatck3.wav"); + sound_hook_heal = gi.soundindex ("medic/medatck4.wav"); + sound_hook_retract = gi.soundindex ("medic/medatck5.wav"); + + gi.soundindex ("medic/medatck1.wav"); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex ("models/monsters/medic/tris.md2"); + VectorSet (self->mins, -24, -24, -24); + VectorSet (self->maxs, 24, 24, 32); + + self->health = 300; + self->gib_health = -130; + self->mass = 400; + + self->pain = medic_pain; + self->die = medic_die; + + self->monsterinfo.stand = medic_stand; + self->monsterinfo.walk = medic_walk; + self->monsterinfo.run = medic_run; + self->monsterinfo.dodge = medic_dodge; + self->monsterinfo.attack = medic_attack; + self->monsterinfo.melee = NULL; + self->monsterinfo.sight = medic_sight; + self->monsterinfo.idle = medic_idle; + self->monsterinfo.search = medic_search; + self->monsterinfo.checkattack = medic_checkattack; + + gi.linkentity (self); + + self->monsterinfo.currentmove = &medic_move_stand; + self->monsterinfo.scale = MODEL_SCALE; + + walkmonster_start (self); +} diff --git a/original/baseq2/m_medic.h b/original/baseq2/m_medic.h new file mode 100644 index 0000000..88675c9 --- /dev/null +++ b/original/baseq2/m_medic.h @@ -0,0 +1,262 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +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. + +*/ +// G:\quake2\baseq2\models/monsters/medic + +// This file generated by ModelGen - Do NOT Modify + +#define FRAME_walk1 0 +#define FRAME_walk2 1 +#define FRAME_walk3 2 +#define FRAME_walk4 3 +#define FRAME_walk5 4 +#define FRAME_walk6 5 +#define FRAME_walk7 6 +#define FRAME_walk8 7 +#define FRAME_walk9 8 +#define FRAME_walk10 9 +#define FRAME_walk11 10 +#define FRAME_walk12 11 +#define FRAME_wait1 12 +#define FRAME_wait2 13 +#define FRAME_wait3 14 +#define FRAME_wait4 15 +#define FRAME_wait5 16 +#define FRAME_wait6 17 +#define FRAME_wait7 18 +#define FRAME_wait8 19 +#define FRAME_wait9 20 +#define FRAME_wait10 21 +#define FRAME_wait11 22 +#define FRAME_wait12 23 +#define FRAME_wait13 24 +#define FRAME_wait14 25 +#define FRAME_wait15 26 +#define FRAME_wait16 27 +#define FRAME_wait17 28 +#define FRAME_wait18 29 +#define FRAME_wait19 30 +#define FRAME_wait20 31 +#define FRAME_wait21 32 +#define FRAME_wait22 33 +#define FRAME_wait23 34 +#define FRAME_wait24 35 +#define FRAME_wait25 36 +#define FRAME_wait26 37 +#define FRAME_wait27 38 +#define FRAME_wait28 39 +#define FRAME_wait29 40 +#define FRAME_wait30 41 +#define FRAME_wait31 42 +#define FRAME_wait32 43 +#define FRAME_wait33 44 +#define FRAME_wait34 45 +#define FRAME_wait35 46 +#define FRAME_wait36 47 +#define FRAME_wait37 48 +#define FRAME_wait38 49 +#define FRAME_wait39 50 +#define FRAME_wait40 51 +#define FRAME_wait41 52 +#define FRAME_wait42 53 +#define FRAME_wait43 54 +#define FRAME_wait44 55 +#define FRAME_wait45 56 +#define FRAME_wait46 57 +#define FRAME_wait47 58 +#define FRAME_wait48 59 +#define FRAME_wait49 60 +#define FRAME_wait50 61 +#define FRAME_wait51 62 +#define FRAME_wait52 63 +#define FRAME_wait53 64 +#define FRAME_wait54 65 +#define FRAME_wait55 66 +#define FRAME_wait56 67 +#define FRAME_wait57 68 +#define FRAME_wait58 69 +#define FRAME_wait59 70 +#define FRAME_wait60 71 +#define FRAME_wait61 72 +#define FRAME_wait62 73 +#define FRAME_wait63 74 +#define FRAME_wait64 75 +#define FRAME_wait65 76 +#define FRAME_wait66 77 +#define FRAME_wait67 78 +#define FRAME_wait68 79 +#define FRAME_wait69 80 +#define FRAME_wait70 81 +#define FRAME_wait71 82 +#define FRAME_wait72 83 +#define FRAME_wait73 84 +#define FRAME_wait74 85 +#define FRAME_wait75 86 +#define FRAME_wait76 87 +#define FRAME_wait77 88 +#define FRAME_wait78 89 +#define FRAME_wait79 90 +#define FRAME_wait80 91 +#define FRAME_wait81 92 +#define FRAME_wait82 93 +#define FRAME_wait83 94 +#define FRAME_wait84 95 +#define FRAME_wait85 96 +#define FRAME_wait86 97 +#define FRAME_wait87 98 +#define FRAME_wait88 99 +#define FRAME_wait89 100 +#define FRAME_wait90 101 +#define FRAME_run1 102 +#define FRAME_run2 103 +#define FRAME_run3 104 +#define FRAME_run4 105 +#define FRAME_run5 106 +#define FRAME_run6 107 +#define FRAME_paina1 108 +#define FRAME_paina2 109 +#define FRAME_paina3 110 +#define FRAME_paina4 111 +#define FRAME_paina5 112 +#define FRAME_paina6 113 +#define FRAME_paina7 114 +#define FRAME_paina8 115 +#define FRAME_painb1 116 +#define FRAME_painb2 117 +#define FRAME_painb3 118 +#define FRAME_painb4 119 +#define FRAME_painb5 120 +#define FRAME_painb6 121 +#define FRAME_painb7 122 +#define FRAME_painb8 123 +#define FRAME_painb9 124 +#define FRAME_painb10 125 +#define FRAME_painb11 126 +#define FRAME_painb12 127 +#define FRAME_painb13 128 +#define FRAME_painb14 129 +#define FRAME_painb15 130 +#define FRAME_duck1 131 +#define FRAME_duck2 132 +#define FRAME_duck3 133 +#define FRAME_duck4 134 +#define FRAME_duck5 135 +#define FRAME_duck6 136 +#define FRAME_duck7 137 +#define FRAME_duck8 138 +#define FRAME_duck9 139 +#define FRAME_duck10 140 +#define FRAME_duck11 141 +#define FRAME_duck12 142 +#define FRAME_duck13 143 +#define FRAME_duck14 144 +#define FRAME_duck15 145 +#define FRAME_duck16 146 +#define FRAME_death1 147 +#define FRAME_death2 148 +#define FRAME_death3 149 +#define FRAME_death4 150 +#define FRAME_death5 151 +#define FRAME_death6 152 +#define FRAME_death7 153 +#define FRAME_death8 154 +#define FRAME_death9 155 +#define FRAME_death10 156 +#define FRAME_death11 157 +#define FRAME_death12 158 +#define FRAME_death13 159 +#define FRAME_death14 160 +#define FRAME_death15 161 +#define FRAME_death16 162 +#define FRAME_death17 163 +#define FRAME_death18 164 +#define FRAME_death19 165 +#define FRAME_death20 166 +#define FRAME_death21 167 +#define FRAME_death22 168 +#define FRAME_death23 169 +#define FRAME_death24 170 +#define FRAME_death25 171 +#define FRAME_death26 172 +#define FRAME_death27 173 +#define FRAME_death28 174 +#define FRAME_death29 175 +#define FRAME_death30 176 +#define FRAME_attack1 177 +#define FRAME_attack2 178 +#define FRAME_attack3 179 +#define FRAME_attack4 180 +#define FRAME_attack5 181 +#define FRAME_attack6 182 +#define FRAME_attack7 183 +#define FRAME_attack8 184 +#define FRAME_attack9 185 +#define FRAME_attack10 186 +#define FRAME_attack11 187 +#define FRAME_attack12 188 +#define FRAME_attack13 189 +#define FRAME_attack14 190 +#define FRAME_attack15 191 +#define FRAME_attack16 192 +#define FRAME_attack17 193 +#define FRAME_attack18 194 +#define FRAME_attack19 195 +#define FRAME_attack20 196 +#define FRAME_attack21 197 +#define FRAME_attack22 198 +#define FRAME_attack23 199 +#define FRAME_attack24 200 +#define FRAME_attack25 201 +#define FRAME_attack26 202 +#define FRAME_attack27 203 +#define FRAME_attack28 204 +#define FRAME_attack29 205 +#define FRAME_attack30 206 +#define FRAME_attack31 207 +#define FRAME_attack32 208 +#define FRAME_attack33 209 +#define FRAME_attack34 210 +#define FRAME_attack35 211 +#define FRAME_attack36 212 +#define FRAME_attack37 213 +#define FRAME_attack38 214 +#define FRAME_attack39 215 +#define FRAME_attack40 216 +#define FRAME_attack41 217 +#define FRAME_attack42 218 +#define FRAME_attack43 219 +#define FRAME_attack44 220 +#define FRAME_attack45 221 +#define FRAME_attack46 222 +#define FRAME_attack47 223 +#define FRAME_attack48 224 +#define FRAME_attack49 225 +#define FRAME_attack50 226 +#define FRAME_attack51 227 +#define FRAME_attack52 228 +#define FRAME_attack53 229 +#define FRAME_attack54 230 +#define FRAME_attack55 231 +#define FRAME_attack56 232 +#define FRAME_attack57 233 +#define FRAME_attack58 234 +#define FRAME_attack59 235 +#define FRAME_attack60 236 + +#define MODEL_SCALE 1.000000 diff --git a/original/baseq2/m_move.c b/original/baseq2/m_move.c new file mode 100644 index 0000000..229cfcd --- /dev/null +++ b/original/baseq2/m_move.c @@ -0,0 +1,556 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +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. + +*/ +// m_move.c -- monster movement + +#include "g_local.h" + +#define STEPSIZE 18 + +/* +============= +M_CheckBottom + +Returns false if any part of the bottom of the entity is off an edge that +is not a staircase. + +============= +*/ +int c_yes, c_no; + +qboolean M_CheckBottom (edict_t *ent) +{ + vec3_t mins, maxs, start, stop; + trace_t trace; + int x, y; + float mid, bottom; + + VectorAdd (ent->s.origin, ent->mins, mins); + VectorAdd (ent->s.origin, ent->maxs, maxs); + +// if all of the points under the corners are solid world, don't bother +// with the tougher checks +// the corners must be within 16 of the midpoint + start[2] = mins[2] - 1; + for (x=0 ; x<=1 ; x++) + for (y=0 ; y<=1 ; y++) + { + start[0] = x ? maxs[0] : mins[0]; + start[1] = y ? maxs[1] : mins[1]; + if (gi.pointcontents (start) != CONTENTS_SOLID) + goto realcheck; + } + + c_yes++; + return true; // we got out easy + +realcheck: + c_no++; +// +// check it for real... +// + start[2] = mins[2]; + +// the midpoint must be within 16 of the bottom + start[0] = stop[0] = (mins[0] + maxs[0])*0.5; + start[1] = stop[1] = (mins[1] + maxs[1])*0.5; + stop[2] = start[2] - 2*STEPSIZE; + trace = gi.trace (start, vec3_origin, vec3_origin, stop, ent, MASK_MONSTERSOLID); + + if (trace.fraction == 1.0) + return false; + mid = bottom = trace.endpos[2]; + +// the corners must be within 16 of the midpoint + for (x=0 ; x<=1 ; x++) + for (y=0 ; y<=1 ; y++) + { + start[0] = stop[0] = x ? maxs[0] : mins[0]; + start[1] = stop[1] = y ? maxs[1] : mins[1]; + + trace = gi.trace (start, vec3_origin, vec3_origin, stop, ent, MASK_MONSTERSOLID); + + if (trace.fraction != 1.0 && trace.endpos[2] > bottom) + bottom = trace.endpos[2]; + if (trace.fraction == 1.0 || mid - trace.endpos[2] > STEPSIZE) + return false; + } + + c_yes++; + return true; +} + + +/* +============= +SV_movestep + +Called by monster program code. +The move will be adjusted for slopes and stairs, but if the move isn't +possible, no move is done, false is returned, and +pr_global_struct->trace_normal is set to the normal of the blocking wall +============= +*/ +//FIXME since we need to test end position contents here, can we avoid doing +//it again later in catagorize position? +qboolean SV_movestep (edict_t *ent, vec3_t move, qboolean relink) +{ + float dz; + vec3_t oldorg, neworg, end; + trace_t trace; + int i; + float stepsize; + vec3_t test; + int contents; + +// try the move + VectorCopy (ent->s.origin, oldorg); + VectorAdd (ent->s.origin, move, neworg); + +// flying monsters don't step up + if ( ent->flags & (FL_SWIM | FL_FLY) ) + { + // try one move with vertical motion, then one without + for (i=0 ; i<2 ; i++) + { + VectorAdd (ent->s.origin, move, neworg); + if (i == 0 && ent->enemy) + { + if (!ent->goalentity) + ent->goalentity = ent->enemy; + dz = ent->s.origin[2] - ent->goalentity->s.origin[2]; + if (ent->goalentity->client) + { + if (dz > 40) + neworg[2] -= 8; + if (!((ent->flags & FL_SWIM) && (ent->waterlevel < 2))) + if (dz < 30) + neworg[2] += 8; + } + else + { + if (dz > 8) + neworg[2] -= 8; + else if (dz > 0) + neworg[2] -= dz; + else if (dz < -8) + neworg[2] += 8; + else + neworg[2] += dz; + } + } + trace = gi.trace (ent->s.origin, ent->mins, ent->maxs, neworg, ent, MASK_MONSTERSOLID); + + // fly monsters don't enter water voluntarily + if (ent->flags & FL_FLY) + { + if (!ent->waterlevel) + { + test[0] = trace.endpos[0]; + test[1] = trace.endpos[1]; + test[2] = trace.endpos[2] + ent->mins[2] + 1; + contents = gi.pointcontents(test); + if (contents & MASK_WATER) + return false; + } + } + + // swim monsters don't exit water voluntarily + if (ent->flags & FL_SWIM) + { + if (ent->waterlevel < 2) + { + test[0] = trace.endpos[0]; + test[1] = trace.endpos[1]; + test[2] = trace.endpos[2] + ent->mins[2] + 1; + contents = gi.pointcontents(test); + if (!(contents & MASK_WATER)) + return false; + } + } + + if (trace.fraction == 1) + { + VectorCopy (trace.endpos, ent->s.origin); + if (relink) + { + gi.linkentity (ent); + G_TouchTriggers (ent); + } + return true; + } + + if (!ent->enemy) + break; + } + + return false; + } + +// push down from a step height above the wished position + if (!(ent->monsterinfo.aiflags & AI_NOSTEP)) + stepsize = STEPSIZE; + else + stepsize = 1; + + neworg[2] += stepsize; + VectorCopy (neworg, end); + end[2] -= stepsize*2; + + trace = gi.trace (neworg, ent->mins, ent->maxs, end, ent, MASK_MONSTERSOLID); + + if (trace.allsolid) + return false; + + if (trace.startsolid) + { + neworg[2] -= stepsize; + trace = gi.trace (neworg, ent->mins, ent->maxs, end, ent, MASK_MONSTERSOLID); + if (trace.allsolid || trace.startsolid) + return false; + } + + + // don't go in to water + if (ent->waterlevel == 0) + { + test[0] = trace.endpos[0]; + test[1] = trace.endpos[1]; + test[2] = trace.endpos[2] + ent->mins[2] + 1; + contents = gi.pointcontents(test); + + if (contents & MASK_WATER) + return false; + } + + if (trace.fraction == 1) + { + // if monster had the ground pulled out, go ahead and fall + if ( ent->flags & FL_PARTIALGROUND ) + { + VectorAdd (ent->s.origin, move, ent->s.origin); + if (relink) + { + gi.linkentity (ent); + G_TouchTriggers (ent); + } + ent->groundentity = NULL; + return true; + } + + return false; // walked off an edge + } + +// check point traces down for dangling corners + VectorCopy (trace.endpos, ent->s.origin); + + if (!M_CheckBottom (ent)) + { + if ( ent->flags & FL_PARTIALGROUND ) + { // entity had floor mostly pulled out from underneath it + // and is trying to correct + if (relink) + { + gi.linkentity (ent); + G_TouchTriggers (ent); + } + return true; + } + VectorCopy (oldorg, ent->s.origin); + return false; + } + + if ( ent->flags & FL_PARTIALGROUND ) + { + ent->flags &= ~FL_PARTIALGROUND; + } + ent->groundentity = trace.ent; + ent->groundentity_linkcount = trace.ent->linkcount; + +// the move is ok + if (relink) + { + gi.linkentity (ent); + G_TouchTriggers (ent); + } + return true; +} + + +//============================================================================ + +/* +=============== +M_ChangeYaw + +=============== +*/ +void M_ChangeYaw (edict_t *ent) +{ + float ideal; + float current; + float move; + float speed; + + current = anglemod(ent->s.angles[YAW]); + ideal = ent->ideal_yaw; + + if (current == ideal) + return; + + move = ideal - current; + speed = ent->yaw_speed; + if (ideal > current) + { + if (move >= 180) + move = move - 360; + } + else + { + if (move <= -180) + move = move + 360; + } + if (move > 0) + { + if (move > speed) + move = speed; + } + else + { + if (move < -speed) + move = -speed; + } + + ent->s.angles[YAW] = anglemod (current + move); +} + + +/* +====================== +SV_StepDirection + +Turns to the movement direction, and walks the current distance if +facing it. + +====================== +*/ +qboolean SV_StepDirection (edict_t *ent, float yaw, float dist) +{ + vec3_t move, oldorigin; + float delta; + + ent->ideal_yaw = yaw; + M_ChangeYaw (ent); + + yaw = yaw*M_PI*2 / 360; + move[0] = cos(yaw)*dist; + move[1] = sin(yaw)*dist; + move[2] = 0; + + VectorCopy (ent->s.origin, oldorigin); + if (SV_movestep (ent, move, false)) + { + delta = ent->s.angles[YAW] - ent->ideal_yaw; + if (delta > 45 && delta < 315) + { // not turned far enough, so don't take the step + VectorCopy (oldorigin, ent->s.origin); + } + gi.linkentity (ent); + G_TouchTriggers (ent); + return true; + } + gi.linkentity (ent); + G_TouchTriggers (ent); + return false; +} + +/* +====================== +SV_FixCheckBottom + +====================== +*/ +void SV_FixCheckBottom (edict_t *ent) +{ + ent->flags |= FL_PARTIALGROUND; +} + + + +/* +================ +SV_NewChaseDir + +================ +*/ +#define DI_NODIR -1 +void SV_NewChaseDir (edict_t *actor, edict_t *enemy, float dist) +{ + float deltax,deltay; + float d[3]; + float tdir, olddir, turnaround; + + //FIXME: how did we get here with no enemy + if (!enemy) + return; + + olddir = anglemod( (int)(actor->ideal_yaw/45)*45 ); + turnaround = anglemod(olddir - 180); + + deltax = enemy->s.origin[0] - actor->s.origin[0]; + deltay = enemy->s.origin[1] - actor->s.origin[1]; + if (deltax>10) + d[1]= 0; + else if (deltax<-10) + d[1]= 180; + else + d[1]= DI_NODIR; + if (deltay<-10) + d[2]= 270; + else if (deltay>10) + d[2]= 90; + else + d[2]= DI_NODIR; + +// try direct route + if (d[1] != DI_NODIR && d[2] != DI_NODIR) + { + if (d[1] == 0) + tdir = d[2] == 90 ? 45 : 315; + else + tdir = d[2] == 90 ? 135 : 215; + + if (tdir != turnaround && SV_StepDirection(actor, tdir, dist)) + return; + } + +// try other directions + if ( ((rand()&3) & 1) || abs(deltay)>abs(deltax)) + { + tdir=d[1]; + d[1]=d[2]; + d[2]=tdir; + } + + if (d[1]!=DI_NODIR && d[1]!=turnaround + && SV_StepDirection(actor, d[1], dist)) + return; + + if (d[2]!=DI_NODIR && d[2]!=turnaround + && SV_StepDirection(actor, d[2], dist)) + return; + +/* there is no direct path to the player, so pick another direction */ + + if (olddir!=DI_NODIR && SV_StepDirection(actor, olddir, dist)) + return; + + if (rand()&1) /*randomly determine direction of search*/ + { + for (tdir=0 ; tdir<=315 ; tdir += 45) + if (tdir!=turnaround && SV_StepDirection(actor, tdir, dist) ) + return; + } + else + { + for (tdir=315 ; tdir >=0 ; tdir -= 45) + if (tdir!=turnaround && SV_StepDirection(actor, tdir, dist) ) + return; + } + + if (turnaround != DI_NODIR && SV_StepDirection(actor, turnaround, dist) ) + return; + + actor->ideal_yaw = olddir; // can't move + +// if a bridge was pulled out from underneath a monster, it may not have +// a valid standing position at all + + if (!M_CheckBottom (actor)) + SV_FixCheckBottom (actor); +} + +/* +====================== +SV_CloseEnough + +====================== +*/ +qboolean SV_CloseEnough (edict_t *ent, edict_t *goal, float dist) +{ + int i; + + for (i=0 ; i<3 ; i++) + { + if (goal->absmin[i] > ent->absmax[i] + dist) + return false; + if (goal->absmax[i] < ent->absmin[i] - dist) + return false; + } + return true; +} + + +/* +====================== +M_MoveToGoal +====================== +*/ +void M_MoveToGoal (edict_t *ent, float dist) +{ + edict_t *goal; + + goal = ent->goalentity; + + if (!ent->groundentity && !(ent->flags & (FL_FLY|FL_SWIM))) + return; + +// if the next step hits the enemy, return immediately + if (ent->enemy && SV_CloseEnough (ent, ent->enemy, dist) ) + return; + +// bump around... + if ( (rand()&3)==1 || !SV_StepDirection (ent, ent->ideal_yaw, dist)) + { + if (ent->inuse) + SV_NewChaseDir (ent, goal, dist); + } +} + + +/* +=============== +M_walkmove +=============== +*/ +qboolean M_walkmove (edict_t *ent, float yaw, float dist) +{ + vec3_t move; + + if (!ent->groundentity && !(ent->flags & (FL_FLY|FL_SWIM))) + return false; + + yaw = yaw*M_PI*2 / 360; + + move[0] = cos(yaw)*dist; + move[1] = sin(yaw)*dist; + move[2] = 0; + + return SV_movestep(ent, move, true); +} diff --git a/original/baseq2/m_mutant.c b/original/baseq2/m_mutant.c new file mode 100644 index 0000000..f61acbf --- /dev/null +++ b/original/baseq2/m_mutant.c @@ -0,0 +1,663 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +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. + +*/ +/* +============================================================================== + +mutant + +============================================================================== +*/ + +#include "g_local.h" +#include "m_mutant.h" + + +static int sound_swing; +static int sound_hit; +static int sound_hit2; +static int sound_death; +static int sound_idle; +static int sound_pain1; +static int sound_pain2; +static int sound_sight; +static int sound_search; +static int sound_step1; +static int sound_step2; +static int sound_step3; +static int sound_thud; + +// +// SOUNDS +// + +void mutant_step (edict_t *self) +{ + int n; + n = (rand() + 1) % 3; + if (n == 0) + gi.sound (self, CHAN_VOICE, sound_step1, 1, ATTN_NORM, 0); + else if (n == 1) + gi.sound (self, CHAN_VOICE, sound_step2, 1, ATTN_NORM, 0); + else + gi.sound (self, CHAN_VOICE, sound_step3, 1, ATTN_NORM, 0); +} + +void mutant_sight (edict_t *self, edict_t *other) +{ + gi.sound (self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); +} + +void mutant_search (edict_t *self) +{ + gi.sound (self, CHAN_VOICE, sound_search, 1, ATTN_NORM, 0); +} + +void mutant_swing (edict_t *self) +{ + gi.sound (self, CHAN_VOICE, sound_swing, 1, ATTN_NORM, 0); +} + + +// +// STAND +// + +mframe_t mutant_frames_stand [] = +{ + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, // 10 + + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, // 20 + + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, // 30 + + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, // 40 + + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, // 50 + + ai_stand, 0, NULL +}; +mmove_t mutant_move_stand = {FRAME_stand101, FRAME_stand151, mutant_frames_stand, NULL}; + +void mutant_stand (edict_t *self) +{ + self->monsterinfo.currentmove = &mutant_move_stand; +} + + +// +// IDLE +// + +void mutant_idle_loop (edict_t *self) +{ + if (random() < 0.75) + self->monsterinfo.nextframe = FRAME_stand155; +} + +mframe_t mutant_frames_idle [] = +{ + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, // scratch loop start + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, mutant_idle_loop, // scratch loop end + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL +}; +mmove_t mutant_move_idle = {FRAME_stand152, FRAME_stand164, mutant_frames_idle, mutant_stand}; + +void mutant_idle (edict_t *self) +{ + self->monsterinfo.currentmove = &mutant_move_idle; + gi.sound (self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0); +} + + +// +// WALK +// + +void mutant_walk (edict_t *self); + +mframe_t mutant_frames_walk [] = +{ + ai_walk, 3, NULL, + ai_walk, 1, NULL, + ai_walk, 5, NULL, + ai_walk, 10, NULL, + ai_walk, 13, NULL, + ai_walk, 10, NULL, + ai_walk, 0, NULL, + ai_walk, 5, NULL, + ai_walk, 6, NULL, + ai_walk, 16, NULL, + ai_walk, 15, NULL, + ai_walk, 6, NULL +}; +mmove_t mutant_move_walk = {FRAME_walk05, FRAME_walk16, mutant_frames_walk, NULL}; + +void mutant_walk_loop (edict_t *self) +{ + self->monsterinfo.currentmove = &mutant_move_walk; +} + +mframe_t mutant_frames_start_walk [] = +{ + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, -2, NULL, + ai_walk, 1, NULL +}; +mmove_t mutant_move_start_walk = {FRAME_walk01, FRAME_walk04, mutant_frames_start_walk, mutant_walk_loop}; + +void mutant_walk (edict_t *self) +{ + self->monsterinfo.currentmove = &mutant_move_start_walk; +} + + +// +// RUN +// + +mframe_t mutant_frames_run [] = +{ + ai_run, 40, NULL, + ai_run, 40, mutant_step, + ai_run, 24, NULL, + ai_run, 5, mutant_step, + ai_run, 17, NULL, + ai_run, 10, NULL +}; +mmove_t mutant_move_run = {FRAME_run03, FRAME_run08, mutant_frames_run, NULL}; + +void mutant_run (edict_t *self) +{ + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + self->monsterinfo.currentmove = &mutant_move_stand; + else + self->monsterinfo.currentmove = &mutant_move_run; +} + + +// +// MELEE +// + +void mutant_hit_left (edict_t *self) +{ + vec3_t aim; + + VectorSet (aim, MELEE_DISTANCE, self->mins[0], 8); + if (fire_hit (self, aim, (10 + (rand() %5)), 100)) + gi.sound (self, CHAN_WEAPON, sound_hit, 1, ATTN_NORM, 0); + else + gi.sound (self, CHAN_WEAPON, sound_swing, 1, ATTN_NORM, 0); +} + +void mutant_hit_right (edict_t *self) +{ + vec3_t aim; + + VectorSet (aim, MELEE_DISTANCE, self->maxs[0], 8); + if (fire_hit (self, aim, (10 + (rand() %5)), 100)) + gi.sound (self, CHAN_WEAPON, sound_hit2, 1, ATTN_NORM, 0); + else + gi.sound (self, CHAN_WEAPON, sound_swing, 1, ATTN_NORM, 0); +} + +void mutant_check_refire (edict_t *self) +{ + if (!self->enemy || !self->enemy->inuse || self->enemy->health <= 0) + return; + + if ( ((skill->value == 3) && (random() < 0.5)) || (range(self, self->enemy) == RANGE_MELEE) ) + self->monsterinfo.nextframe = FRAME_attack09; +} + +mframe_t mutant_frames_attack [] = +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, mutant_hit_left, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, mutant_hit_right, + ai_charge, 0, mutant_check_refire +}; +mmove_t mutant_move_attack = {FRAME_attack09, FRAME_attack15, mutant_frames_attack, mutant_run}; + +void mutant_melee (edict_t *self) +{ + self->monsterinfo.currentmove = &mutant_move_attack; +} + + +// +// ATTACK +// + +void mutant_jump_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (self->health <= 0) + { + self->touch = NULL; + return; + } + + if (other->takedamage) + { + if (VectorLength(self->velocity) > 400) + { + vec3_t point; + vec3_t normal; + int damage; + + VectorCopy (self->velocity, normal); + VectorNormalize(normal); + VectorMA (self->s.origin, self->maxs[0], normal, point); + damage = 40 + 10 * random(); + T_Damage (other, self, self, self->velocity, point, normal, damage, damage, 0, MOD_UNKNOWN); + } + } + + if (!M_CheckBottom (self)) + { + if (self->groundentity) + { + self->monsterinfo.nextframe = FRAME_attack02; + self->touch = NULL; + } + return; + } + + self->touch = NULL; +} + +void mutant_jump_takeoff (edict_t *self) +{ + vec3_t forward; + + gi.sound (self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); + AngleVectors (self->s.angles, forward, NULL, NULL); + self->s.origin[2] += 1; + VectorScale (forward, 600, self->velocity); + self->velocity[2] = 250; + self->groundentity = NULL; + self->monsterinfo.aiflags |= AI_DUCKED; + self->monsterinfo.attack_finished = level.time + 3; + self->touch = mutant_jump_touch; +} + +void mutant_check_landing (edict_t *self) +{ + if (self->groundentity) + { + gi.sound (self, CHAN_WEAPON, sound_thud, 1, ATTN_NORM, 0); + self->monsterinfo.attack_finished = 0; + self->monsterinfo.aiflags &= ~AI_DUCKED; + return; + } + + if (level.time > self->monsterinfo.attack_finished) + self->monsterinfo.nextframe = FRAME_attack02; + else + self->monsterinfo.nextframe = FRAME_attack05; +} + +mframe_t mutant_frames_jump [] = +{ + ai_charge, 0, NULL, + ai_charge, 17, NULL, + ai_charge, 15, mutant_jump_takeoff, + ai_charge, 15, NULL, + ai_charge, 15, mutant_check_landing, + ai_charge, 0, NULL, + ai_charge, 3, NULL, + ai_charge, 0, NULL +}; +mmove_t mutant_move_jump = {FRAME_attack01, FRAME_attack08, mutant_frames_jump, mutant_run}; + +void mutant_jump (edict_t *self) +{ + self->monsterinfo.currentmove = &mutant_move_jump; +} + + +// +// CHECKATTACK +// + +qboolean mutant_check_melee (edict_t *self) +{ + if (range (self, self->enemy) == RANGE_MELEE) + return true; + return false; +} + +qboolean mutant_check_jump (edict_t *self) +{ + vec3_t v; + float distance; + + if (self->absmin[2] > (self->enemy->absmin[2] + 0.75 * self->enemy->size[2])) + return false; + + if (self->absmax[2] < (self->enemy->absmin[2] + 0.25 * self->enemy->size[2])) + return false; + + v[0] = self->s.origin[0] - self->enemy->s.origin[0]; + v[1] = self->s.origin[1] - self->enemy->s.origin[1]; + v[2] = 0; + distance = VectorLength(v); + + if (distance < 100) + return false; + if (distance > 100) + { + if (random() < 0.9) + return false; + } + + return true; +} + +qboolean mutant_checkattack (edict_t *self) +{ + if (!self->enemy || self->enemy->health <= 0) + return false; + + if (mutant_check_melee(self)) + { + self->monsterinfo.attack_state = AS_MELEE; + return true; + } + + if (mutant_check_jump(self)) + { + self->monsterinfo.attack_state = AS_MISSILE; + // FIXME play a jump sound here + return true; + } + + return false; +} + + +// +// PAIN +// + +mframe_t mutant_frames_pain1 [] = +{ + ai_move, 4, NULL, + ai_move, -3, NULL, + ai_move, -8, NULL, + ai_move, 2, NULL, + ai_move, 5, NULL +}; +mmove_t mutant_move_pain1 = {FRAME_pain101, FRAME_pain105, mutant_frames_pain1, mutant_run}; + +mframe_t mutant_frames_pain2 [] = +{ + ai_move, -24,NULL, + ai_move, 11, NULL, + ai_move, 5, NULL, + ai_move, -2, NULL, + ai_move, 6, NULL, + ai_move, 4, NULL +}; +mmove_t mutant_move_pain2 = {FRAME_pain201, FRAME_pain206, mutant_frames_pain2, mutant_run}; + +mframe_t mutant_frames_pain3 [] = +{ + ai_move, -22,NULL, + ai_move, 3, NULL, + ai_move, 3, NULL, + ai_move, 2, NULL, + ai_move, 1, NULL, + ai_move, 1, NULL, + ai_move, 6, NULL, + ai_move, 3, NULL, + ai_move, 2, NULL, + ai_move, 0, NULL, + ai_move, 1, NULL +}; +mmove_t mutant_move_pain3 = {FRAME_pain301, FRAME_pain311, mutant_frames_pain3, mutant_run}; + +void mutant_pain (edict_t *self, edict_t *other, float kick, int damage) +{ + float r; + + if (self->health < (self->max_health / 2)) + self->s.skinnum = 1; + + if (level.time < self->pain_debounce_time) + return; + + self->pain_debounce_time = level.time + 3; + + if (skill->value == 3) + return; // no pain anims in nightmare + + r = random(); + if (r < 0.33) + { + gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &mutant_move_pain1; + } + else if (r < 0.66) + { + gi.sound (self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &mutant_move_pain2; + } + else + { + gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &mutant_move_pain3; + } +} + + +// +// DEATH +// + +void mutant_dead (edict_t *self) +{ + VectorSet (self->mins, -16, -16, -24); + VectorSet (self->maxs, 16, 16, -8); + self->movetype = MOVETYPE_TOSS; + self->svflags |= SVF_DEADMONSTER; + gi.linkentity (self); + + M_FlyCheck (self); +} + +mframe_t mutant_frames_death1 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t mutant_move_death1 = {FRAME_death101, FRAME_death109, mutant_frames_death1, mutant_dead}; + +mframe_t mutant_frames_death2 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t mutant_move_death2 = {FRAME_death201, FRAME_death210, mutant_frames_death2, mutant_dead}; + +void mutant_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + int n; + + if (self->health <= self->gib_health) + { + gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0); + for (n= 0; n < 2; n++) + ThrowGib (self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC); + for (n= 0; n < 4; n++) + ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); + ThrowHead (self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC); + self->deadflag = DEAD_DEAD; + return; + } + + if (self->deadflag == DEAD_DEAD) + return; + + gi.sound (self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0); + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_YES; + self->s.skinnum = 1; + + if (random() < 0.5) + self->monsterinfo.currentmove = &mutant_move_death1; + else + self->monsterinfo.currentmove = &mutant_move_death2; +} + + +// +// SPAWN +// + +/*QUAKED monster_mutant (1 .5 0) (-32 -32 -24) (32 32 32) Ambush Trigger_Spawn Sight +*/ +void SP_monster_mutant (edict_t *self) +{ + if (deathmatch->value) + { + G_FreeEdict (self); + return; + } + + sound_swing = gi.soundindex ("mutant/mutatck1.wav"); + sound_hit = gi.soundindex ("mutant/mutatck2.wav"); + sound_hit2 = gi.soundindex ("mutant/mutatck3.wav"); + sound_death = gi.soundindex ("mutant/mutdeth1.wav"); + sound_idle = gi.soundindex ("mutant/mutidle1.wav"); + sound_pain1 = gi.soundindex ("mutant/mutpain1.wav"); + sound_pain2 = gi.soundindex ("mutant/mutpain2.wav"); + sound_sight = gi.soundindex ("mutant/mutsght1.wav"); + sound_search = gi.soundindex ("mutant/mutsrch1.wav"); + sound_step1 = gi.soundindex ("mutant/step1.wav"); + sound_step2 = gi.soundindex ("mutant/step2.wav"); + sound_step3 = gi.soundindex ("mutant/step3.wav"); + sound_thud = gi.soundindex ("mutant/thud1.wav"); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex ("models/monsters/mutant/tris.md2"); + VectorSet (self->mins, -32, -32, -24); + VectorSet (self->maxs, 32, 32, 48); + + self->health = 300; + self->gib_health = -120; + self->mass = 300; + + self->pain = mutant_pain; + self->die = mutant_die; + + self->monsterinfo.stand = mutant_stand; + self->monsterinfo.walk = mutant_walk; + self->monsterinfo.run = mutant_run; + self->monsterinfo.dodge = NULL; + self->monsterinfo.attack = mutant_jump; + self->monsterinfo.melee = mutant_melee; + self->monsterinfo.sight = mutant_sight; + self->monsterinfo.search = mutant_search; + self->monsterinfo.idle = mutant_idle; + self->monsterinfo.checkattack = mutant_checkattack; + + gi.linkentity (self); + + self->monsterinfo.currentmove = &mutant_move_stand; + + self->monsterinfo.scale = MODEL_SCALE; + walkmonster_start (self); +} diff --git a/original/baseq2/m_mutant.h b/original/baseq2/m_mutant.h new file mode 100644 index 0000000..8c6f3fe --- /dev/null +++ b/original/baseq2/m_mutant.h @@ -0,0 +1,174 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +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. + +*/ +// G:\quake2\baseq2\models/monsters/mutant + +// This file generated by ModelGen - Do NOT Modify + +#define FRAME_attack01 0 +#define FRAME_attack02 1 +#define FRAME_attack03 2 +#define FRAME_attack04 3 +#define FRAME_attack05 4 +#define FRAME_attack06 5 +#define FRAME_attack07 6 +#define FRAME_attack08 7 +#define FRAME_attack09 8 +#define FRAME_attack10 9 +#define FRAME_attack11 10 +#define FRAME_attack12 11 +#define FRAME_attack13 12 +#define FRAME_attack14 13 +#define FRAME_attack15 14 +#define FRAME_death101 15 +#define FRAME_death102 16 +#define FRAME_death103 17 +#define FRAME_death104 18 +#define FRAME_death105 19 +#define FRAME_death106 20 +#define FRAME_death107 21 +#define FRAME_death108 22 +#define FRAME_death109 23 +#define FRAME_death201 24 +#define FRAME_death202 25 +#define FRAME_death203 26 +#define FRAME_death204 27 +#define FRAME_death205 28 +#define FRAME_death206 29 +#define FRAME_death207 30 +#define FRAME_death208 31 +#define FRAME_death209 32 +#define FRAME_death210 33 +#define FRAME_pain101 34 +#define FRAME_pain102 35 +#define FRAME_pain103 36 +#define FRAME_pain104 37 +#define FRAME_pain105 38 +#define FRAME_pain201 39 +#define FRAME_pain202 40 +#define FRAME_pain203 41 +#define FRAME_pain204 42 +#define FRAME_pain205 43 +#define FRAME_pain206 44 +#define FRAME_pain301 45 +#define FRAME_pain302 46 +#define FRAME_pain303 47 +#define FRAME_pain304 48 +#define FRAME_pain305 49 +#define FRAME_pain306 50 +#define FRAME_pain307 51 +#define FRAME_pain308 52 +#define FRAME_pain309 53 +#define FRAME_pain310 54 +#define FRAME_pain311 55 +#define FRAME_run03 56 +#define FRAME_run04 57 +#define FRAME_run05 58 +#define FRAME_run06 59 +#define FRAME_run07 60 +#define FRAME_run08 61 +#define FRAME_stand101 62 +#define FRAME_stand102 63 +#define FRAME_stand103 64 +#define FRAME_stand104 65 +#define FRAME_stand105 66 +#define FRAME_stand106 67 +#define FRAME_stand107 68 +#define FRAME_stand108 69 +#define FRAME_stand109 70 +#define FRAME_stand110 71 +#define FRAME_stand111 72 +#define FRAME_stand112 73 +#define FRAME_stand113 74 +#define FRAME_stand114 75 +#define FRAME_stand115 76 +#define FRAME_stand116 77 +#define FRAME_stand117 78 +#define FRAME_stand118 79 +#define FRAME_stand119 80 +#define FRAME_stand120 81 +#define FRAME_stand121 82 +#define FRAME_stand122 83 +#define FRAME_stand123 84 +#define FRAME_stand124 85 +#define FRAME_stand125 86 +#define FRAME_stand126 87 +#define FRAME_stand127 88 +#define FRAME_stand128 89 +#define FRAME_stand129 90 +#define FRAME_stand130 91 +#define FRAME_stand131 92 +#define FRAME_stand132 93 +#define FRAME_stand133 94 +#define FRAME_stand134 95 +#define FRAME_stand135 96 +#define FRAME_stand136 97 +#define FRAME_stand137 98 +#define FRAME_stand138 99 +#define FRAME_stand139 100 +#define FRAME_stand140 101 +#define FRAME_stand141 102 +#define FRAME_stand142 103 +#define FRAME_stand143 104 +#define FRAME_stand144 105 +#define FRAME_stand145 106 +#define FRAME_stand146 107 +#define FRAME_stand147 108 +#define FRAME_stand148 109 +#define FRAME_stand149 110 +#define FRAME_stand150 111 +#define FRAME_stand151 112 +#define FRAME_stand152 113 +#define FRAME_stand153 114 +#define FRAME_stand154 115 +#define FRAME_stand155 116 +#define FRAME_stand156 117 +#define FRAME_stand157 118 +#define FRAME_stand158 119 +#define FRAME_stand159 120 +#define FRAME_stand160 121 +#define FRAME_stand161 122 +#define FRAME_stand162 123 +#define FRAME_stand163 124 +#define FRAME_stand164 125 +#define FRAME_walk01 126 +#define FRAME_walk02 127 +#define FRAME_walk03 128 +#define FRAME_walk04 129 +#define FRAME_walk05 130 +#define FRAME_walk06 131 +#define FRAME_walk07 132 +#define FRAME_walk08 133 +#define FRAME_walk09 134 +#define FRAME_walk10 135 +#define FRAME_walk11 136 +#define FRAME_walk12 137 +#define FRAME_walk13 138 +#define FRAME_walk14 139 +#define FRAME_walk15 140 +#define FRAME_walk16 141 +#define FRAME_walk17 142 +#define FRAME_walk18 143 +#define FRAME_walk19 144 +#define FRAME_walk20 145 +#define FRAME_walk21 146 +#define FRAME_walk22 147 +#define FRAME_walk23 148 + +#define MODEL_SCALE 1.000000 diff --git a/original/baseq2/m_parasite.c b/original/baseq2/m_parasite.c new file mode 100644 index 0000000..3478750 --- /dev/null +++ b/original/baseq2/m_parasite.c @@ -0,0 +1,552 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +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. + +*/ +/* +============================================================================== + +parasite + +============================================================================== +*/ + +#include "g_local.h" +#include "m_parasite.h" + + +static int sound_pain1; +static int sound_pain2; +static int sound_die; +static int sound_launch; +static int sound_impact; +static int sound_suck; +static int sound_reelin; +static int sound_sight; +static int sound_tap; +static int sound_scratch; +static int sound_search; + + +void parasite_stand (edict_t *self); +void parasite_start_run (edict_t *self); +void parasite_run (edict_t *self); +void parasite_walk (edict_t *self); +void parasite_start_walk (edict_t *self); +void parasite_end_fidget (edict_t *self); +void parasite_do_fidget (edict_t *self); +void parasite_refidget (edict_t *self); + + +void parasite_launch (edict_t *self) +{ + gi.sound (self, CHAN_WEAPON, sound_launch, 1, ATTN_NORM, 0); +} + +void parasite_reel_in (edict_t *self) +{ + gi.sound (self, CHAN_WEAPON, sound_reelin, 1, ATTN_NORM, 0); +} + +void parasite_sight (edict_t *self, edict_t *other) +{ + gi.sound (self, CHAN_WEAPON, sound_sight, 1, ATTN_NORM, 0); +} + +void parasite_tap (edict_t *self) +{ + gi.sound (self, CHAN_WEAPON, sound_tap, 1, ATTN_IDLE, 0); +} + +void parasite_scratch (edict_t *self) +{ + gi.sound (self, CHAN_WEAPON, sound_scratch, 1, ATTN_IDLE, 0); +} + +void parasite_search (edict_t *self) +{ + gi.sound (self, CHAN_WEAPON, sound_search, 1, ATTN_IDLE, 0); +} + + +mframe_t parasite_frames_start_fidget [] = +{ + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL +}; +mmove_t parasite_move_start_fidget = {FRAME_stand18, FRAME_stand21, parasite_frames_start_fidget, parasite_do_fidget}; + +mframe_t parasite_frames_fidget [] = +{ + ai_stand, 0, parasite_scratch, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, parasite_scratch, + ai_stand, 0, NULL, + ai_stand, 0, NULL +}; +mmove_t parasite_move_fidget = {FRAME_stand22, FRAME_stand27, parasite_frames_fidget, parasite_refidget}; + +mframe_t parasite_frames_end_fidget [] = +{ + ai_stand, 0, parasite_scratch, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL +}; +mmove_t parasite_move_end_fidget = {FRAME_stand28, FRAME_stand35, parasite_frames_end_fidget, parasite_stand}; + +void parasite_end_fidget (edict_t *self) +{ + self->monsterinfo.currentmove = ¶site_move_end_fidget; +} + +void parasite_do_fidget (edict_t *self) +{ + self->monsterinfo.currentmove = ¶site_move_fidget; +} + +void parasite_refidget (edict_t *self) +{ + if (random() <= 0.8) + self->monsterinfo.currentmove = ¶site_move_fidget; + else + self->monsterinfo.currentmove = ¶site_move_end_fidget; +} + +void parasite_idle (edict_t *self) +{ + self->monsterinfo.currentmove = ¶site_move_start_fidget; +} + + +mframe_t parasite_frames_stand [] = +{ + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, parasite_tap, + ai_stand, 0, NULL, + ai_stand, 0, parasite_tap, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, parasite_tap, + ai_stand, 0, NULL, + ai_stand, 0, parasite_tap, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, parasite_tap, + ai_stand, 0, NULL, + ai_stand, 0, parasite_tap +}; +mmove_t parasite_move_stand = {FRAME_stand01, FRAME_stand17, parasite_frames_stand, parasite_stand}; + +void parasite_stand (edict_t *self) +{ + self->monsterinfo.currentmove = ¶site_move_stand; +} + + +mframe_t parasite_frames_run [] = +{ + ai_run, 30, NULL, + ai_run, 30, NULL, + ai_run, 22, NULL, + ai_run, 19, NULL, + ai_run, 24, NULL, + ai_run, 28, NULL, + ai_run, 25, NULL +}; +mmove_t parasite_move_run = {FRAME_run03, FRAME_run09, parasite_frames_run, NULL}; + +mframe_t parasite_frames_start_run [] = +{ + ai_run, 0, NULL, + ai_run, 30, NULL, +}; +mmove_t parasite_move_start_run = {FRAME_run01, FRAME_run02, parasite_frames_start_run, parasite_run}; + +mframe_t parasite_frames_stop_run [] = +{ + ai_run, 20, NULL, + ai_run, 20, NULL, + ai_run, 12, NULL, + ai_run, 10, NULL, + ai_run, 0, NULL, + ai_run, 0, NULL +}; +mmove_t parasite_move_stop_run = {FRAME_run10, FRAME_run15, parasite_frames_stop_run, NULL}; + +void parasite_start_run (edict_t *self) +{ + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + self->monsterinfo.currentmove = ¶site_move_stand; + else + self->monsterinfo.currentmove = ¶site_move_start_run; +} + +void parasite_run (edict_t *self) +{ + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + self->monsterinfo.currentmove = ¶site_move_stand; + else + self->monsterinfo.currentmove = ¶site_move_run; +} + + +mframe_t parasite_frames_walk [] = +{ + ai_walk, 30, NULL, + ai_walk, 30, NULL, + ai_walk, 22, NULL, + ai_walk, 19, NULL, + ai_walk, 24, NULL, + ai_walk, 28, NULL, + ai_walk, 25, NULL +}; +mmove_t parasite_move_walk = {FRAME_run03, FRAME_run09, parasite_frames_walk, parasite_walk}; + +mframe_t parasite_frames_start_walk [] = +{ + ai_walk, 0, NULL, + ai_walk, 30, parasite_walk +}; +mmove_t parasite_move_start_walk = {FRAME_run01, FRAME_run02, parasite_frames_start_walk, NULL}; + +mframe_t parasite_frames_stop_walk [] = +{ + ai_walk, 20, NULL, + ai_walk, 20, NULL, + ai_walk, 12, NULL, + ai_walk, 10, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL +}; +mmove_t parasite_move_stop_walk = {FRAME_run10, FRAME_run15, parasite_frames_stop_walk, NULL}; + +void parasite_start_walk (edict_t *self) +{ + self->monsterinfo.currentmove = ¶site_move_start_walk; +} + +void parasite_walk (edict_t *self) +{ + self->monsterinfo.currentmove = ¶site_move_walk; +} + + +mframe_t parasite_frames_pain1 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 6, NULL, + ai_move, 16, NULL, + ai_move, -6, NULL, + ai_move, -7, NULL, + ai_move, 0, NULL +}; +mmove_t parasite_move_pain1 = {FRAME_pain101, FRAME_pain111, parasite_frames_pain1, parasite_start_run}; + +void parasite_pain (edict_t *self, edict_t *other, float kick, int damage) +{ + if (self->health < (self->max_health / 2)) + self->s.skinnum = 1; + + if (level.time < self->pain_debounce_time) + return; + + self->pain_debounce_time = level.time + 3; + + if (skill->value == 3) + return; // no pain anims in nightmare + + if (random() < 0.5) + gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); + else + gi.sound (self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0); + + self->monsterinfo.currentmove = ¶site_move_pain1; +} + + +static qboolean parasite_drain_attack_ok (vec3_t start, vec3_t end) +{ + vec3_t dir, angles; + + // check for max distance + VectorSubtract (start, end, dir); + if (VectorLength(dir) > 256) + return false; + + // check for min/max pitch + vectoangles (dir, angles); + if (angles[0] < -180) + angles[0] += 360; + if (fabs(angles[0]) > 30) + return false; + + return true; +} + +void parasite_drain_attack (edict_t *self) +{ + vec3_t offset, start, f, r, end, dir; + trace_t tr; + int damage; + + AngleVectors (self->s.angles, f, r, NULL); + VectorSet (offset, 24, 0, 6); + G_ProjectSource (self->s.origin, offset, f, r, start); + + VectorCopy (self->enemy->s.origin, end); + if (!parasite_drain_attack_ok(start, end)) + { + end[2] = self->enemy->s.origin[2] + self->enemy->maxs[2] - 8; + if (!parasite_drain_attack_ok(start, end)) + { + end[2] = self->enemy->s.origin[2] + self->enemy->mins[2] + 8; + if (!parasite_drain_attack_ok(start, end)) + return; + } + } + VectorCopy (self->enemy->s.origin, end); + + tr = gi.trace (start, NULL, NULL, end, self, MASK_SHOT); + if (tr.ent != self->enemy) + return; + + if (self->s.frame == FRAME_drain03) + { + damage = 5; + gi.sound (self->enemy, CHAN_AUTO, sound_impact, 1, ATTN_NORM, 0); + } + else + { + if (self->s.frame == FRAME_drain04) + gi.sound (self, CHAN_WEAPON, sound_suck, 1, ATTN_NORM, 0); + damage = 2; + } + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_PARASITE_ATTACK); + gi.WriteShort (self - g_edicts); + gi.WritePosition (start); + gi.WritePosition (end); + gi.multicast (self->s.origin, MULTICAST_PVS); + + VectorSubtract (start, end, dir); + T_Damage (self->enemy, self, self, dir, self->enemy->s.origin, vec3_origin, damage, 0, DAMAGE_NO_KNOCKBACK, MOD_UNKNOWN); +} + +mframe_t parasite_frames_drain [] = +{ + ai_charge, 0, parasite_launch, + ai_charge, 0, NULL, + ai_charge, 15, parasite_drain_attack, // Target hits + ai_charge, 0, parasite_drain_attack, // drain + ai_charge, 0, parasite_drain_attack, // drain + ai_charge, 0, parasite_drain_attack, // drain + ai_charge, 0, parasite_drain_attack, // drain + ai_charge, -2, parasite_drain_attack, // drain + ai_charge, -2, parasite_drain_attack, // drain + ai_charge, -3, parasite_drain_attack, // drain + ai_charge, -2, parasite_drain_attack, // drain + ai_charge, 0, parasite_drain_attack, // drain + ai_charge, -1, parasite_drain_attack, // drain + ai_charge, 0, parasite_reel_in, // let go + ai_charge, -2, NULL, + ai_charge, -2, NULL, + ai_charge, -3, NULL, + ai_charge, 0, NULL +}; +mmove_t parasite_move_drain = {FRAME_drain01, FRAME_drain18, parasite_frames_drain, parasite_start_run}; + + +mframe_t parasite_frames_break [] = +{ + ai_charge, 0, NULL, + ai_charge, -3, NULL, + ai_charge, 1, NULL, + ai_charge, 2, NULL, + ai_charge, -3, NULL, + ai_charge, 1, NULL, + ai_charge, 1, NULL, + ai_charge, 3, NULL, + ai_charge, 0, NULL, + ai_charge, -18, NULL, + ai_charge, 3, NULL, + ai_charge, 9, NULL, + ai_charge, 6, NULL, + ai_charge, 0, NULL, + ai_charge, -18, NULL, + ai_charge, 0, NULL, + ai_charge, 8, NULL, + ai_charge, 9, NULL, + ai_charge, 0, NULL, + ai_charge, -18, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, // airborne + ai_charge, 0, NULL, // airborne + ai_charge, 0, NULL, // slides + ai_charge, 0, NULL, // slides + ai_charge, 0, NULL, // slides + ai_charge, 0, NULL, // slides + ai_charge, 4, NULL, + ai_charge, 11, NULL, + ai_charge, -2, NULL, + ai_charge, -5, NULL, + ai_charge, 1, NULL +}; +mmove_t parasite_move_break = {FRAME_break01, FRAME_break32, parasite_frames_break, parasite_start_run}; + +/* +=== +Break Stuff Ends +=== +*/ + +void parasite_attack (edict_t *self) +{ +// if (random() <= 0.2) +// self->monsterinfo.currentmove = ¶site_move_break; +// else + self->monsterinfo.currentmove = ¶site_move_drain; +} + + + +/* +=== +Death Stuff Starts +=== +*/ + +void parasite_dead (edict_t *self) +{ + VectorSet (self->mins, -16, -16, -24); + VectorSet (self->maxs, 16, 16, -8); + self->movetype = MOVETYPE_TOSS; + self->svflags |= SVF_DEADMONSTER; + self->nextthink = 0; + gi.linkentity (self); +} + +mframe_t parasite_frames_death [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t parasite_move_death = {FRAME_death101, FRAME_death107, parasite_frames_death, parasite_dead}; + +void parasite_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + int n; + +// check for gib + if (self->health <= self->gib_health) + { + gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0); + for (n= 0; n < 2; n++) + ThrowGib (self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC); + for (n= 0; n < 4; n++) + ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); + ThrowHead (self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC); + self->deadflag = DEAD_DEAD; + return; + } + + if (self->deadflag == DEAD_DEAD) + return; + +// regular death + gi.sound (self, CHAN_VOICE, sound_die, 1, ATTN_NORM, 0); + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_YES; + self->monsterinfo.currentmove = ¶site_move_death; +} + +/* +=== +End Death Stuff +=== +*/ + +/*QUAKED monster_parasite (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight +*/ +void SP_monster_parasite (edict_t *self) +{ + if (deathmatch->value) + { + G_FreeEdict (self); + return; + } + + sound_pain1 = gi.soundindex ("parasite/parpain1.wav"); + sound_pain2 = gi.soundindex ("parasite/parpain2.wav"); + sound_die = gi.soundindex ("parasite/pardeth1.wav"); + sound_launch = gi.soundindex("parasite/paratck1.wav"); + sound_impact = gi.soundindex("parasite/paratck2.wav"); + sound_suck = gi.soundindex("parasite/paratck3.wav"); + sound_reelin = gi.soundindex("parasite/paratck4.wav"); + sound_sight = gi.soundindex("parasite/parsght1.wav"); + sound_tap = gi.soundindex("parasite/paridle1.wav"); + sound_scratch = gi.soundindex("parasite/paridle2.wav"); + sound_search = gi.soundindex("parasite/parsrch1.wav"); + + self->s.modelindex = gi.modelindex ("models/monsters/parasite/tris.md2"); + VectorSet (self->mins, -16, -16, -24); + VectorSet (self->maxs, 16, 16, 24); + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + + self->health = 175; + self->gib_health = -50; + self->mass = 250; + + self->pain = parasite_pain; + self->die = parasite_die; + + self->monsterinfo.stand = parasite_stand; + self->monsterinfo.walk = parasite_start_walk; + self->monsterinfo.run = parasite_start_run; + self->monsterinfo.attack = parasite_attack; + self->monsterinfo.sight = parasite_sight; + self->monsterinfo.idle = parasite_idle; + + gi.linkentity (self); + + self->monsterinfo.currentmove = ¶site_move_stand; + self->monsterinfo.scale = MODEL_SCALE; + + walkmonster_start (self); +} diff --git a/original/baseq2/m_parasite.h b/original/baseq2/m_parasite.h new file mode 100644 index 0000000..2410e79 --- /dev/null +++ b/original/baseq2/m_parasite.h @@ -0,0 +1,143 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +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. + +*/ +// G:\quake2\baseq2\models/monsters/parasite + +// This file generated by ModelGen - Do NOT Modify + +#define FRAME_break01 0 +#define FRAME_break02 1 +#define FRAME_break03 2 +#define FRAME_break04 3 +#define FRAME_break05 4 +#define FRAME_break06 5 +#define FRAME_break07 6 +#define FRAME_break08 7 +#define FRAME_break09 8 +#define FRAME_break10 9 +#define FRAME_break11 10 +#define FRAME_break12 11 +#define FRAME_break13 12 +#define FRAME_break14 13 +#define FRAME_break15 14 +#define FRAME_break16 15 +#define FRAME_break17 16 +#define FRAME_break18 17 +#define FRAME_break19 18 +#define FRAME_break20 19 +#define FRAME_break21 20 +#define FRAME_break22 21 +#define FRAME_break23 22 +#define FRAME_break24 23 +#define FRAME_break25 24 +#define FRAME_break26 25 +#define FRAME_break27 26 +#define FRAME_break28 27 +#define FRAME_break29 28 +#define FRAME_break30 29 +#define FRAME_break31 30 +#define FRAME_break32 31 +#define FRAME_death101 32 +#define FRAME_death102 33 +#define FRAME_death103 34 +#define FRAME_death104 35 +#define FRAME_death105 36 +#define FRAME_death106 37 +#define FRAME_death107 38 +#define FRAME_drain01 39 +#define FRAME_drain02 40 +#define FRAME_drain03 41 +#define FRAME_drain04 42 +#define FRAME_drain05 43 +#define FRAME_drain06 44 +#define FRAME_drain07 45 +#define FRAME_drain08 46 +#define FRAME_drain09 47 +#define FRAME_drain10 48 +#define FRAME_drain11 49 +#define FRAME_drain12 50 +#define FRAME_drain13 51 +#define FRAME_drain14 52 +#define FRAME_drain15 53 +#define FRAME_drain16 54 +#define FRAME_drain17 55 +#define FRAME_drain18 56 +#define FRAME_pain101 57 +#define FRAME_pain102 58 +#define FRAME_pain103 59 +#define FRAME_pain104 60 +#define FRAME_pain105 61 +#define FRAME_pain106 62 +#define FRAME_pain107 63 +#define FRAME_pain108 64 +#define FRAME_pain109 65 +#define FRAME_pain110 66 +#define FRAME_pain111 67 +#define FRAME_run01 68 +#define FRAME_run02 69 +#define FRAME_run03 70 +#define FRAME_run04 71 +#define FRAME_run05 72 +#define FRAME_run06 73 +#define FRAME_run07 74 +#define FRAME_run08 75 +#define FRAME_run09 76 +#define FRAME_run10 77 +#define FRAME_run11 78 +#define FRAME_run12 79 +#define FRAME_run13 80 +#define FRAME_run14 81 +#define FRAME_run15 82 +#define FRAME_stand01 83 +#define FRAME_stand02 84 +#define FRAME_stand03 85 +#define FRAME_stand04 86 +#define FRAME_stand05 87 +#define FRAME_stand06 88 +#define FRAME_stand07 89 +#define FRAME_stand08 90 +#define FRAME_stand09 91 +#define FRAME_stand10 92 +#define FRAME_stand11 93 +#define FRAME_stand12 94 +#define FRAME_stand13 95 +#define FRAME_stand14 96 +#define FRAME_stand15 97 +#define FRAME_stand16 98 +#define FRAME_stand17 99 +#define FRAME_stand18 100 +#define FRAME_stand19 101 +#define FRAME_stand20 102 +#define FRAME_stand21 103 +#define FRAME_stand22 104 +#define FRAME_stand23 105 +#define FRAME_stand24 106 +#define FRAME_stand25 107 +#define FRAME_stand26 108 +#define FRAME_stand27 109 +#define FRAME_stand28 110 +#define FRAME_stand29 111 +#define FRAME_stand30 112 +#define FRAME_stand31 113 +#define FRAME_stand32 114 +#define FRAME_stand33 115 +#define FRAME_stand34 116 +#define FRAME_stand35 117 + +#define MODEL_SCALE 1.000000 diff --git a/original/baseq2/m_player.h b/original/baseq2/m_player.h new file mode 100644 index 0000000..15f5a62 --- /dev/null +++ b/original/baseq2/m_player.h @@ -0,0 +1,224 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +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. + +*/ +// G:\quake2\baseq2\models/player_x/frames + +// This file generated by qdata - Do NOT Modify + +#define FRAME_stand01 0 +#define FRAME_stand02 1 +#define FRAME_stand03 2 +#define FRAME_stand04 3 +#define FRAME_stand05 4 +#define FRAME_stand06 5 +#define FRAME_stand07 6 +#define FRAME_stand08 7 +#define FRAME_stand09 8 +#define FRAME_stand10 9 +#define FRAME_stand11 10 +#define FRAME_stand12 11 +#define FRAME_stand13 12 +#define FRAME_stand14 13 +#define FRAME_stand15 14 +#define FRAME_stand16 15 +#define FRAME_stand17 16 +#define FRAME_stand18 17 +#define FRAME_stand19 18 +#define FRAME_stand20 19 +#define FRAME_stand21 20 +#define FRAME_stand22 21 +#define FRAME_stand23 22 +#define FRAME_stand24 23 +#define FRAME_stand25 24 +#define FRAME_stand26 25 +#define FRAME_stand27 26 +#define FRAME_stand28 27 +#define FRAME_stand29 28 +#define FRAME_stand30 29 +#define FRAME_stand31 30 +#define FRAME_stand32 31 +#define FRAME_stand33 32 +#define FRAME_stand34 33 +#define FRAME_stand35 34 +#define FRAME_stand36 35 +#define FRAME_stand37 36 +#define FRAME_stand38 37 +#define FRAME_stand39 38 +#define FRAME_stand40 39 +#define FRAME_run1 40 +#define FRAME_run2 41 +#define FRAME_run3 42 +#define FRAME_run4 43 +#define FRAME_run5 44 +#define FRAME_run6 45 +#define FRAME_attack1 46 +#define FRAME_attack2 47 +#define FRAME_attack3 48 +#define FRAME_attack4 49 +#define FRAME_attack5 50 +#define FRAME_attack6 51 +#define FRAME_attack7 52 +#define FRAME_attack8 53 +#define FRAME_pain101 54 +#define FRAME_pain102 55 +#define FRAME_pain103 56 +#define FRAME_pain104 57 +#define FRAME_pain201 58 +#define FRAME_pain202 59 +#define FRAME_pain203 60 +#define FRAME_pain204 61 +#define FRAME_pain301 62 +#define FRAME_pain302 63 +#define FRAME_pain303 64 +#define FRAME_pain304 65 +#define FRAME_jump1 66 +#define FRAME_jump2 67 +#define FRAME_jump3 68 +#define FRAME_jump4 69 +#define FRAME_jump5 70 +#define FRAME_jump6 71 +#define FRAME_flip01 72 +#define FRAME_flip02 73 +#define FRAME_flip03 74 +#define FRAME_flip04 75 +#define FRAME_flip05 76 +#define FRAME_flip06 77 +#define FRAME_flip07 78 +#define FRAME_flip08 79 +#define FRAME_flip09 80 +#define FRAME_flip10 81 +#define FRAME_flip11 82 +#define FRAME_flip12 83 +#define FRAME_salute01 84 +#define FRAME_salute02 85 +#define FRAME_salute03 86 +#define FRAME_salute04 87 +#define FRAME_salute05 88 +#define FRAME_salute06 89 +#define FRAME_salute07 90 +#define FRAME_salute08 91 +#define FRAME_salute09 92 +#define FRAME_salute10 93 +#define FRAME_salute11 94 +#define FRAME_taunt01 95 +#define FRAME_taunt02 96 +#define FRAME_taunt03 97 +#define FRAME_taunt04 98 +#define FRAME_taunt05 99 +#define FRAME_taunt06 100 +#define FRAME_taunt07 101 +#define FRAME_taunt08 102 +#define FRAME_taunt09 103 +#define FRAME_taunt10 104 +#define FRAME_taunt11 105 +#define FRAME_taunt12 106 +#define FRAME_taunt13 107 +#define FRAME_taunt14 108 +#define FRAME_taunt15 109 +#define FRAME_taunt16 110 +#define FRAME_taunt17 111 +#define FRAME_wave01 112 +#define FRAME_wave02 113 +#define FRAME_wave03 114 +#define FRAME_wave04 115 +#define FRAME_wave05 116 +#define FRAME_wave06 117 +#define FRAME_wave07 118 +#define FRAME_wave08 119 +#define FRAME_wave09 120 +#define FRAME_wave10 121 +#define FRAME_wave11 122 +#define FRAME_point01 123 +#define FRAME_point02 124 +#define FRAME_point03 125 +#define FRAME_point04 126 +#define FRAME_point05 127 +#define FRAME_point06 128 +#define FRAME_point07 129 +#define FRAME_point08 130 +#define FRAME_point09 131 +#define FRAME_point10 132 +#define FRAME_point11 133 +#define FRAME_point12 134 +#define FRAME_crstnd01 135 +#define FRAME_crstnd02 136 +#define FRAME_crstnd03 137 +#define FRAME_crstnd04 138 +#define FRAME_crstnd05 139 +#define FRAME_crstnd06 140 +#define FRAME_crstnd07 141 +#define FRAME_crstnd08 142 +#define FRAME_crstnd09 143 +#define FRAME_crstnd10 144 +#define FRAME_crstnd11 145 +#define FRAME_crstnd12 146 +#define FRAME_crstnd13 147 +#define FRAME_crstnd14 148 +#define FRAME_crstnd15 149 +#define FRAME_crstnd16 150 +#define FRAME_crstnd17 151 +#define FRAME_crstnd18 152 +#define FRAME_crstnd19 153 +#define FRAME_crwalk1 154 +#define FRAME_crwalk2 155 +#define FRAME_crwalk3 156 +#define FRAME_crwalk4 157 +#define FRAME_crwalk5 158 +#define FRAME_crwalk6 159 +#define FRAME_crattak1 160 +#define FRAME_crattak2 161 +#define FRAME_crattak3 162 +#define FRAME_crattak4 163 +#define FRAME_crattak5 164 +#define FRAME_crattak6 165 +#define FRAME_crattak7 166 +#define FRAME_crattak8 167 +#define FRAME_crattak9 168 +#define FRAME_crpain1 169 +#define FRAME_crpain2 170 +#define FRAME_crpain3 171 +#define FRAME_crpain4 172 +#define FRAME_crdeath1 173 +#define FRAME_crdeath2 174 +#define FRAME_crdeath3 175 +#define FRAME_crdeath4 176 +#define FRAME_crdeath5 177 +#define FRAME_death101 178 +#define FRAME_death102 179 +#define FRAME_death103 180 +#define FRAME_death104 181 +#define FRAME_death105 182 +#define FRAME_death106 183 +#define FRAME_death201 184 +#define FRAME_death202 185 +#define FRAME_death203 186 +#define FRAME_death204 187 +#define FRAME_death205 188 +#define FRAME_death206 189 +#define FRAME_death301 190 +#define FRAME_death302 191 +#define FRAME_death303 192 +#define FRAME_death304 193 +#define FRAME_death305 194 +#define FRAME_death306 195 +#define FRAME_death307 196 +#define FRAME_death308 197 + +#define MODEL_SCALE 1.000000 + diff --git a/original/baseq2/m_rider.h b/original/baseq2/m_rider.h new file mode 100644 index 0000000..f47af79 --- /dev/null +++ b/original/baseq2/m_rider.h @@ -0,0 +1,66 @@ +// G:\quake2\baseq2\models/monsters/boss3/rider + +// This file generated by ModelGen - Do NOT Modify + +#define FRAME_stand201 0 +#define FRAME_stand202 1 +#define FRAME_stand203 2 +#define FRAME_stand204 3 +#define FRAME_stand205 4 +#define FRAME_stand206 5 +#define FRAME_stand207 6 +#define FRAME_stand208 7 +#define FRAME_stand209 8 +#define FRAME_stand210 9 +#define FRAME_stand211 10 +#define FRAME_stand212 11 +#define FRAME_stand213 12 +#define FRAME_stand214 13 +#define FRAME_stand215 14 +#define FRAME_stand216 15 +#define FRAME_stand217 16 +#define FRAME_stand218 17 +#define FRAME_stand219 18 +#define FRAME_stand220 19 +#define FRAME_stand221 20 +#define FRAME_stand222 21 +#define FRAME_stand223 22 +#define FRAME_stand224 23 +#define FRAME_stand225 24 +#define FRAME_stand226 25 +#define FRAME_stand227 26 +#define FRAME_stand228 27 +#define FRAME_stand229 28 +#define FRAME_stand230 29 +#define FRAME_stand231 30 +#define FRAME_stand232 31 +#define FRAME_stand233 32 +#define FRAME_stand234 33 +#define FRAME_stand235 34 +#define FRAME_stand236 35 +#define FRAME_stand237 36 +#define FRAME_stand238 37 +#define FRAME_stand239 38 +#define FRAME_stand240 39 +#define FRAME_stand241 40 +#define FRAME_stand242 41 +#define FRAME_stand243 42 +#define FRAME_stand244 43 +#define FRAME_stand245 44 +#define FRAME_stand246 45 +#define FRAME_stand247 46 +#define FRAME_stand248 47 +#define FRAME_stand249 48 +#define FRAME_stand250 49 +#define FRAME_stand251 50 +#define FRAME_stand252 51 +#define FRAME_stand253 52 +#define FRAME_stand254 53 +#define FRAME_stand255 54 +#define FRAME_stand256 55 +#define FRAME_stand257 56 +#define FRAME_stand258 57 +#define FRAME_stand259 58 +#define FRAME_stand260 59 + +#define MODEL_SCALE 1.000000 diff --git a/original/baseq2/m_soldier.c b/original/baseq2/m_soldier.c new file mode 100644 index 0000000..dae6d61 --- /dev/null +++ b/original/baseq2/m_soldier.c @@ -0,0 +1,1299 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +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. + +*/ +/* +============================================================================== + +SOLDIER + +============================================================================== +*/ + +#include "g_local.h" +#include "m_soldier.h" + + +static int sound_idle; +static int sound_sight1; +static int sound_sight2; +static int sound_pain_light; +static int sound_pain; +static int sound_pain_ss; +static int sound_death_light; +static int sound_death; +static int sound_death_ss; +static int sound_cock; + + +void soldier_idle (edict_t *self) +{ + if (random() > 0.8) + gi.sound (self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0); +} + +void soldier_cock (edict_t *self) +{ + if (self->s.frame == FRAME_stand322) + gi.sound (self, CHAN_WEAPON, sound_cock, 1, ATTN_IDLE, 0); + else + gi.sound (self, CHAN_WEAPON, sound_cock, 1, ATTN_NORM, 0); +} + + +// STAND + +void soldier_stand (edict_t *self); + +mframe_t soldier_frames_stand1 [] = +{ + ai_stand, 0, soldier_idle, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL +}; +mmove_t soldier_move_stand1 = {FRAME_stand101, FRAME_stand130, soldier_frames_stand1, soldier_stand}; + +mframe_t soldier_frames_stand3 [] = +{ + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + + ai_stand, 0, NULL, + ai_stand, 0, soldier_cock, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL +}; +mmove_t soldier_move_stand3 = {FRAME_stand301, FRAME_stand339, soldier_frames_stand3, soldier_stand}; + +#if 0 +mframe_t soldier_frames_stand4 [] = +{ + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 4, NULL, + ai_stand, 1, NULL, + ai_stand, -1, NULL, + ai_stand, -2, NULL, + + ai_stand, 0, NULL, + ai_stand, 0, NULL +}; +mmove_t soldier_move_stand4 = {FRAME_stand401, FRAME_stand452, soldier_frames_stand4, NULL}; +#endif + +void soldier_stand (edict_t *self) +{ + if ((self->monsterinfo.currentmove == &soldier_move_stand3) || (random() < 0.8)) + self->monsterinfo.currentmove = &soldier_move_stand1; + else + self->monsterinfo.currentmove = &soldier_move_stand3; +} + + +// +// WALK +// + +void soldier_walk1_random (edict_t *self) +{ + if (random() > 0.1) + self->monsterinfo.nextframe = FRAME_walk101; +} + +mframe_t soldier_frames_walk1 [] = +{ + ai_walk, 3, NULL, + ai_walk, 6, NULL, + ai_walk, 2, NULL, + ai_walk, 2, NULL, + ai_walk, 2, NULL, + ai_walk, 1, NULL, + ai_walk, 6, NULL, + ai_walk, 5, NULL, + ai_walk, 3, NULL, + ai_walk, -1, soldier_walk1_random, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL +}; +mmove_t soldier_move_walk1 = {FRAME_walk101, FRAME_walk133, soldier_frames_walk1, NULL}; + +mframe_t soldier_frames_walk2 [] = +{ + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 9, NULL, + ai_walk, 8, NULL, + ai_walk, 5, NULL, + ai_walk, 1, NULL, + ai_walk, 3, NULL, + ai_walk, 7, NULL, + ai_walk, 6, NULL, + ai_walk, 7, NULL +}; +mmove_t soldier_move_walk2 = {FRAME_walk209, FRAME_walk218, soldier_frames_walk2, NULL}; + +void soldier_walk (edict_t *self) +{ + if (random() < 0.5) + self->monsterinfo.currentmove = &soldier_move_walk1; + else + self->monsterinfo.currentmove = &soldier_move_walk2; +} + + +// +// RUN +// + +void soldier_run (edict_t *self); + +mframe_t soldier_frames_start_run [] = +{ + ai_run, 7, NULL, + ai_run, 5, NULL +}; +mmove_t soldier_move_start_run = {FRAME_run01, FRAME_run02, soldier_frames_start_run, soldier_run}; + +mframe_t soldier_frames_run [] = +{ + ai_run, 10, NULL, + ai_run, 11, NULL, + ai_run, 11, NULL, + ai_run, 16, NULL, + ai_run, 10, NULL, + ai_run, 15, NULL +}; +mmove_t soldier_move_run = {FRAME_run03, FRAME_run08, soldier_frames_run, NULL}; + +void soldier_run (edict_t *self) +{ + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + { + self->monsterinfo.currentmove = &soldier_move_stand1; + return; + } + + if (self->monsterinfo.currentmove == &soldier_move_walk1 || + self->monsterinfo.currentmove == &soldier_move_walk2 || + self->monsterinfo.currentmove == &soldier_move_start_run) + { + self->monsterinfo.currentmove = &soldier_move_run; + } + else + { + self->monsterinfo.currentmove = &soldier_move_start_run; + } +} + + +// +// PAIN +// + +mframe_t soldier_frames_pain1 [] = +{ + ai_move, -3, NULL, + ai_move, 4, NULL, + ai_move, 1, NULL, + ai_move, 1, NULL, + ai_move, 0, NULL +}; +mmove_t soldier_move_pain1 = {FRAME_pain101, FRAME_pain105, soldier_frames_pain1, soldier_run}; + +mframe_t soldier_frames_pain2 [] = +{ + ai_move, -13, NULL, + ai_move, -1, NULL, + ai_move, 2, NULL, + ai_move, 4, NULL, + ai_move, 2, NULL, + ai_move, 3, NULL, + ai_move, 2, NULL +}; +mmove_t soldier_move_pain2 = {FRAME_pain201, FRAME_pain207, soldier_frames_pain2, soldier_run}; + +mframe_t soldier_frames_pain3 [] = +{ + ai_move, -8, NULL, + ai_move, 10, NULL, + ai_move, -4, NULL, + ai_move, -1, NULL, + ai_move, -3, NULL, + ai_move, 0, NULL, + ai_move, 3, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 1, NULL, + ai_move, 0, NULL, + ai_move, 1, NULL, + ai_move, 2, NULL, + ai_move, 4, NULL, + ai_move, 3, NULL, + ai_move, 2, NULL +}; +mmove_t soldier_move_pain3 = {FRAME_pain301, FRAME_pain318, soldier_frames_pain3, soldier_run}; + +mframe_t soldier_frames_pain4 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, -10, NULL, + ai_move, -6, NULL, + ai_move, 8, NULL, + ai_move, 4, NULL, + ai_move, 1, NULL, + ai_move, 0, NULL, + ai_move, 2, NULL, + ai_move, 5, NULL, + ai_move, 2, NULL, + ai_move, -1, NULL, + ai_move, -1, NULL, + ai_move, 3, NULL, + ai_move, 2, NULL, + ai_move, 0, NULL +}; +mmove_t soldier_move_pain4 = {FRAME_pain401, FRAME_pain417, soldier_frames_pain4, soldier_run}; + + +void soldier_pain (edict_t *self, edict_t *other, float kick, int damage) +{ + float r; + int n; + + if (self->health < (self->max_health / 2)) + self->s.skinnum |= 1; + + if (level.time < self->pain_debounce_time) + { + if ((self->velocity[2] > 100) && ( (self->monsterinfo.currentmove == &soldier_move_pain1) || (self->monsterinfo.currentmove == &soldier_move_pain2) || (self->monsterinfo.currentmove == &soldier_move_pain3))) + self->monsterinfo.currentmove = &soldier_move_pain4; + return; + } + + self->pain_debounce_time = level.time + 3; + + n = self->s.skinnum | 1; + if (n == 1) + gi.sound (self, CHAN_VOICE, sound_pain_light, 1, ATTN_NORM, 0); + else if (n == 3) + gi.sound (self, CHAN_VOICE, sound_pain, 1, ATTN_NORM, 0); + else + gi.sound (self, CHAN_VOICE, sound_pain_ss, 1, ATTN_NORM, 0); + + if (self->velocity[2] > 100) + { + self->monsterinfo.currentmove = &soldier_move_pain4; + return; + } + + if (skill->value == 3) + return; // no pain anims in nightmare + + r = random(); + + if (r < 0.33) + self->monsterinfo.currentmove = &soldier_move_pain1; + else if (r < 0.66) + self->monsterinfo.currentmove = &soldier_move_pain2; + else + self->monsterinfo.currentmove = &soldier_move_pain3; +} + + +// +// ATTACK +// + +static int blaster_flash [] = {MZ2_SOLDIER_BLASTER_1, MZ2_SOLDIER_BLASTER_2, MZ2_SOLDIER_BLASTER_3, MZ2_SOLDIER_BLASTER_4, MZ2_SOLDIER_BLASTER_5, MZ2_SOLDIER_BLASTER_6, MZ2_SOLDIER_BLASTER_7, MZ2_SOLDIER_BLASTER_8}; +static int shotgun_flash [] = {MZ2_SOLDIER_SHOTGUN_1, MZ2_SOLDIER_SHOTGUN_2, MZ2_SOLDIER_SHOTGUN_3, MZ2_SOLDIER_SHOTGUN_4, MZ2_SOLDIER_SHOTGUN_5, MZ2_SOLDIER_SHOTGUN_6, MZ2_SOLDIER_SHOTGUN_7, MZ2_SOLDIER_SHOTGUN_8}; +static int machinegun_flash [] = {MZ2_SOLDIER_MACHINEGUN_1, MZ2_SOLDIER_MACHINEGUN_2, MZ2_SOLDIER_MACHINEGUN_3, MZ2_SOLDIER_MACHINEGUN_4, MZ2_SOLDIER_MACHINEGUN_5, MZ2_SOLDIER_MACHINEGUN_6, MZ2_SOLDIER_MACHINEGUN_7, MZ2_SOLDIER_MACHINEGUN_8}; + +void soldier_fire (edict_t *self, int flash_number) +{ + vec3_t start; + vec3_t forward, right, up; + vec3_t aim; + vec3_t dir; + vec3_t end; + float r, u; + int flash_index; + + if (self->s.skinnum < 2) + flash_index = blaster_flash[flash_number]; + else if (self->s.skinnum < 4) + flash_index = shotgun_flash[flash_number]; + else + flash_index = machinegun_flash[flash_number]; + + AngleVectors (self->s.angles, forward, right, NULL); + G_ProjectSource (self->s.origin, monster_flash_offset[flash_index], forward, right, start); + + if (flash_number == 5 || flash_number == 6) + { + VectorCopy (forward, aim); + } + else + { + VectorCopy (self->enemy->s.origin, end); + end[2] += self->enemy->viewheight; + VectorSubtract (end, start, aim); + vectoangles (aim, dir); + AngleVectors (dir, forward, right, up); + + r = crandom()*1000; + u = crandom()*500; + VectorMA (start, 8192, forward, end); + VectorMA (end, r, right, end); + VectorMA (end, u, up, end); + + VectorSubtract (end, start, aim); + VectorNormalize (aim); + } + + if (self->s.skinnum <= 1) + { + monster_fire_blaster (self, start, aim, 5, 600, flash_index, EF_BLASTER); + } + else if (self->s.skinnum <= 3) + { + monster_fire_shotgun (self, start, aim, 2, 1, DEFAULT_SHOTGUN_HSPREAD, DEFAULT_SHOTGUN_VSPREAD, DEFAULT_SHOTGUN_COUNT, flash_index); + } + else + { + if (!(self->monsterinfo.aiflags & AI_HOLD_FRAME)) + self->monsterinfo.pausetime = level.time + (3 + rand() % 8) * FRAMETIME; + + monster_fire_bullet (self, start, aim, 2, 4, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, flash_index); + + if (level.time >= self->monsterinfo.pausetime) + self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; + else + self->monsterinfo.aiflags |= AI_HOLD_FRAME; + } +} + +// ATTACK1 (blaster/shotgun) + +void soldier_fire1 (edict_t *self) +{ + soldier_fire (self, 0); +} + +void soldier_attack1_refire1 (edict_t *self) +{ + if (self->s.skinnum > 1) + return; + + if (self->enemy->health <= 0) + return; + + if ( ((skill->value == 3) && (random() < 0.5)) || (range(self, self->enemy) == RANGE_MELEE) ) + self->monsterinfo.nextframe = FRAME_attak102; + else + self->monsterinfo.nextframe = FRAME_attak110; +} + +void soldier_attack1_refire2 (edict_t *self) +{ + if (self->s.skinnum < 2) + return; + + if (self->enemy->health <= 0) + return; + + if ( ((skill->value == 3) && (random() < 0.5)) || (range(self, self->enemy) == RANGE_MELEE) ) + self->monsterinfo.nextframe = FRAME_attak102; +} + +mframe_t soldier_frames_attack1 [] = +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, soldier_fire1, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, soldier_attack1_refire1, + ai_charge, 0, NULL, + ai_charge, 0, soldier_cock, + ai_charge, 0, soldier_attack1_refire2, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL +}; +mmove_t soldier_move_attack1 = {FRAME_attak101, FRAME_attak112, soldier_frames_attack1, soldier_run}; + +// ATTACK2 (blaster/shotgun) + +void soldier_fire2 (edict_t *self) +{ + soldier_fire (self, 1); +} + +void soldier_attack2_refire1 (edict_t *self) +{ + if (self->s.skinnum > 1) + return; + + if (self->enemy->health <= 0) + return; + + if ( ((skill->value == 3) && (random() < 0.5)) || (range(self, self->enemy) == RANGE_MELEE) ) + self->monsterinfo.nextframe = FRAME_attak204; + else + self->monsterinfo.nextframe = FRAME_attak216; +} + +void soldier_attack2_refire2 (edict_t *self) +{ + if (self->s.skinnum < 2) + return; + + if (self->enemy->health <= 0) + return; + + if ( ((skill->value == 3) && (random() < 0.5)) || (range(self, self->enemy) == RANGE_MELEE) ) + self->monsterinfo.nextframe = FRAME_attak204; +} + +mframe_t soldier_frames_attack2 [] = +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, soldier_fire2, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, soldier_attack2_refire1, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, soldier_cock, + ai_charge, 0, NULL, + ai_charge, 0, soldier_attack2_refire2, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL +}; +mmove_t soldier_move_attack2 = {FRAME_attak201, FRAME_attak218, soldier_frames_attack2, soldier_run}; + +// ATTACK3 (duck and shoot) + +void soldier_duck_down (edict_t *self) +{ + if (self->monsterinfo.aiflags & AI_DUCKED) + return; + self->monsterinfo.aiflags |= AI_DUCKED; + self->maxs[2] -= 32; + self->takedamage = DAMAGE_YES; + self->monsterinfo.pausetime = level.time + 1; + gi.linkentity (self); +} + +void soldier_duck_up (edict_t *self) +{ + self->monsterinfo.aiflags &= ~AI_DUCKED; + self->maxs[2] += 32; + self->takedamage = DAMAGE_AIM; + gi.linkentity (self); +} + +void soldier_fire3 (edict_t *self) +{ + soldier_duck_down (self); + soldier_fire (self, 2); +} + +void soldier_attack3_refire (edict_t *self) +{ + if ((level.time + 0.4) < self->monsterinfo.pausetime) + self->monsterinfo.nextframe = FRAME_attak303; +} + +mframe_t soldier_frames_attack3 [] = +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, soldier_fire3, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, soldier_attack3_refire, + ai_charge, 0, soldier_duck_up, + ai_charge, 0, NULL, + ai_charge, 0, NULL +}; +mmove_t soldier_move_attack3 = {FRAME_attak301, FRAME_attak309, soldier_frames_attack3, soldier_run}; + +// ATTACK4 (machinegun) + +void soldier_fire4 (edict_t *self) +{ + soldier_fire (self, 3); +// +// if (self->enemy->health <= 0) +// return; +// +// if ( ((skill->value == 3) && (random() < 0.5)) || (range(self, self->enemy) == RANGE_MELEE) ) +// self->monsterinfo.nextframe = FRAME_attak402; +} + +mframe_t soldier_frames_attack4 [] = +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, soldier_fire4, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL +}; +mmove_t soldier_move_attack4 = {FRAME_attak401, FRAME_attak406, soldier_frames_attack4, soldier_run}; + +#if 0 +// ATTACK5 (prone) + +void soldier_fire5 (edict_t *self) +{ + soldier_fire (self, 4); +} + +void soldier_attack5_refire (edict_t *self) +{ + if (self->enemy->health <= 0) + return; + + if ( ((skill->value == 3) && (random() < 0.5)) || (range(self, self->enemy) == RANGE_MELEE) ) + self->monsterinfo.nextframe = FRAME_attak505; +} + +mframe_t soldier_frames_attack5 [] = +{ + ai_charge, 8, NULL, + ai_charge, 8, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, soldier_fire5, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, soldier_attack5_refire +}; +mmove_t soldier_move_attack5 = {FRAME_attak501, FRAME_attak508, soldier_frames_attack5, soldier_run}; +#endif + +// ATTACK6 (run & shoot) + +void soldier_fire8 (edict_t *self) +{ + soldier_fire (self, 7); +} + +void soldier_attack6_refire (edict_t *self) +{ + if (self->enemy->health <= 0) + return; + + if (range(self, self->enemy) < RANGE_MID) + return; + + if (skill->value == 3) + self->monsterinfo.nextframe = FRAME_runs03; +} + +mframe_t soldier_frames_attack6 [] = +{ + ai_charge, 10, NULL, + ai_charge, 4, NULL, + ai_charge, 12, NULL, + ai_charge, 11, soldier_fire8, + ai_charge, 13, NULL, + ai_charge, 18, NULL, + ai_charge, 15, NULL, + ai_charge, 14, NULL, + ai_charge, 11, NULL, + ai_charge, 8, NULL, + ai_charge, 11, NULL, + ai_charge, 12, NULL, + ai_charge, 12, NULL, + ai_charge, 17, soldier_attack6_refire +}; +mmove_t soldier_move_attack6 = {FRAME_runs01, FRAME_runs14, soldier_frames_attack6, soldier_run}; + +void soldier_attack(edict_t *self) +{ + if (self->s.skinnum < 4) + { + if (random() < 0.5) + self->monsterinfo.currentmove = &soldier_move_attack1; + else + self->monsterinfo.currentmove = &soldier_move_attack2; + } + else + { + self->monsterinfo.currentmove = &soldier_move_attack4; + } +} + + +// +// SIGHT +// + +void soldier_sight(edict_t *self, edict_t *other) +{ + if (random() < 0.5) + gi.sound (self, CHAN_VOICE, sound_sight1, 1, ATTN_NORM, 0); + else + gi.sound (self, CHAN_VOICE, sound_sight2, 1, ATTN_NORM, 0); + + if ((skill->value > 0) && (range(self, self->enemy) >= RANGE_MID)) + { + if (random() > 0.5) + self->monsterinfo.currentmove = &soldier_move_attack6; + } +} + +// +// DUCK +// + +void soldier_duck_hold (edict_t *self) +{ + if (level.time >= self->monsterinfo.pausetime) + self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; + else + self->monsterinfo.aiflags |= AI_HOLD_FRAME; +} + +mframe_t soldier_frames_duck [] = +{ + ai_move, 5, soldier_duck_down, + ai_move, -1, soldier_duck_hold, + ai_move, 1, NULL, + ai_move, 0, soldier_duck_up, + ai_move, 5, NULL +}; +mmove_t soldier_move_duck = {FRAME_duck01, FRAME_duck05, soldier_frames_duck, soldier_run}; + +void soldier_dodge (edict_t *self, edict_t *attacker, float eta) +{ + float r; + + r = random(); + if (r > 0.25) + return; + + if (!self->enemy) + self->enemy = attacker; + + if (skill->value == 0) + { + self->monsterinfo.currentmove = &soldier_move_duck; + return; + } + + self->monsterinfo.pausetime = level.time + eta + 0.3; + r = random(); + + if (skill->value == 1) + { + if (r > 0.33) + self->monsterinfo.currentmove = &soldier_move_duck; + else + self->monsterinfo.currentmove = &soldier_move_attack3; + return; + } + + if (skill->value >= 2) + { + if (r > 0.66) + self->monsterinfo.currentmove = &soldier_move_duck; + else + self->monsterinfo.currentmove = &soldier_move_attack3; + return; + } + + self->monsterinfo.currentmove = &soldier_move_attack3; +} + + +// +// DEATH +// + +void soldier_fire6 (edict_t *self) +{ + soldier_fire (self, 5); +} + +void soldier_fire7 (edict_t *self) +{ + soldier_fire (self, 6); +} + +void soldier_dead (edict_t *self) +{ + VectorSet (self->mins, -16, -16, -24); + VectorSet (self->maxs, 16, 16, -8); + self->movetype = MOVETYPE_TOSS; + self->svflags |= SVF_DEADMONSTER; + self->nextthink = 0; + gi.linkentity (self); +} + +mframe_t soldier_frames_death1 [] = +{ + ai_move, 0, NULL, + ai_move, -10, NULL, + ai_move, -10, NULL, + ai_move, -10, NULL, + ai_move, -5, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 0, NULL, + ai_move, 0, soldier_fire6, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, soldier_fire7, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t soldier_move_death1 = {FRAME_death101, FRAME_death136, soldier_frames_death1, soldier_dead}; + +mframe_t soldier_frames_death2 [] = +{ + ai_move, -5, NULL, + ai_move, -5, NULL, + ai_move, -5, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t soldier_move_death2 = {FRAME_death201, FRAME_death235, soldier_frames_death2, soldier_dead}; + +mframe_t soldier_frames_death3 [] = +{ + ai_move, -5, NULL, + ai_move, -5, NULL, + ai_move, -5, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, +}; +mmove_t soldier_move_death3 = {FRAME_death301, FRAME_death345, soldier_frames_death3, soldier_dead}; + +mframe_t soldier_frames_death4 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t soldier_move_death4 = {FRAME_death401, FRAME_death453, soldier_frames_death4, soldier_dead}; + +mframe_t soldier_frames_death5 [] = +{ + ai_move, -5, NULL, + ai_move, -5, NULL, + ai_move, -5, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t soldier_move_death5 = {FRAME_death501, FRAME_death524, soldier_frames_death5, soldier_dead}; + +mframe_t soldier_frames_death6 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t soldier_move_death6 = {FRAME_death601, FRAME_death610, soldier_frames_death6, soldier_dead}; + +void soldier_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + int n; + +// check for gib + if (self->health <= self->gib_health) + { + gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0); + for (n= 0; n < 3; n++) + ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); + ThrowGib (self, "models/objects/gibs/chest/tris.md2", damage, GIB_ORGANIC); + ThrowHead (self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC); + self->deadflag = DEAD_DEAD; + return; + } + + if (self->deadflag == DEAD_DEAD) + return; + +// regular death + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_YES; + self->s.skinnum |= 1; + + if (self->s.skinnum == 1) + gi.sound (self, CHAN_VOICE, sound_death_light, 1, ATTN_NORM, 0); + else if (self->s.skinnum == 3) + gi.sound (self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0); + else // (self->s.skinnum == 5) + gi.sound (self, CHAN_VOICE, sound_death_ss, 1, ATTN_NORM, 0); + + if (fabs((self->s.origin[2] + self->viewheight) - point[2]) <= 4) + { + // head shot + self->monsterinfo.currentmove = &soldier_move_death3; + return; + } + + n = rand() % 5; + if (n == 0) + self->monsterinfo.currentmove = &soldier_move_death1; + else if (n == 1) + self->monsterinfo.currentmove = &soldier_move_death2; + else if (n == 2) + self->monsterinfo.currentmove = &soldier_move_death4; + else if (n == 3) + self->monsterinfo.currentmove = &soldier_move_death5; + else + self->monsterinfo.currentmove = &soldier_move_death6; +} + + +// +// SPAWN +// + +void SP_monster_soldier_x (edict_t *self) +{ + + self->s.modelindex = gi.modelindex ("models/monsters/soldier/tris.md2"); + self->monsterinfo.scale = MODEL_SCALE; + VectorSet (self->mins, -16, -16, -24); + VectorSet (self->maxs, 16, 16, 32); + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + + sound_idle = gi.soundindex ("soldier/solidle1.wav"); + sound_sight1 = gi.soundindex ("soldier/solsght1.wav"); + sound_sight2 = gi.soundindex ("soldier/solsrch1.wav"); + sound_cock = gi.soundindex ("infantry/infatck3.wav"); + + self->mass = 100; + + self->pain = soldier_pain; + self->die = soldier_die; + + self->monsterinfo.stand = soldier_stand; + self->monsterinfo.walk = soldier_walk; + self->monsterinfo.run = soldier_run; + self->monsterinfo.dodge = soldier_dodge; + self->monsterinfo.attack = soldier_attack; + self->monsterinfo.melee = NULL; + self->monsterinfo.sight = soldier_sight; + + gi.linkentity (self); + + self->monsterinfo.stand (self); + + walkmonster_start (self); +} + + +/*QUAKED monster_soldier_light (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight +*/ +void SP_monster_soldier_light (edict_t *self) +{ + if (deathmatch->value) + { + G_FreeEdict (self); + return; + } + + SP_monster_soldier_x (self); + + sound_pain_light = gi.soundindex ("soldier/solpain2.wav"); + sound_death_light = gi.soundindex ("soldier/soldeth2.wav"); + gi.modelindex ("models/objects/laser/tris.md2"); + gi.soundindex ("misc/lasfly.wav"); + gi.soundindex ("soldier/solatck2.wav"); + + self->s.skinnum = 0; + self->health = 20; + self->gib_health = -30; +} + +/*QUAKED monster_soldier (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight +*/ +void SP_monster_soldier (edict_t *self) +{ + if (deathmatch->value) + { + G_FreeEdict (self); + return; + } + + SP_monster_soldier_x (self); + + sound_pain = gi.soundindex ("soldier/solpain1.wav"); + sound_death = gi.soundindex ("soldier/soldeth1.wav"); + gi.soundindex ("soldier/solatck1.wav"); + + self->s.skinnum = 2; + self->health = 30; + self->gib_health = -30; +} + +/*QUAKED monster_soldier_ss (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight +*/ +void SP_monster_soldier_ss (edict_t *self) +{ + if (deathmatch->value) + { + G_FreeEdict (self); + return; + } + + SP_monster_soldier_x (self); + + sound_pain_ss = gi.soundindex ("soldier/solpain3.wav"); + sound_death_ss = gi.soundindex ("soldier/soldeth3.wav"); + gi.soundindex ("soldier/solatck3.wav"); + + self->s.skinnum = 4; + self->health = 40; + self->gib_health = -30; +} diff --git a/original/baseq2/m_soldier.h b/original/baseq2/m_soldier.h new file mode 100644 index 0000000..91167be --- /dev/null +++ b/original/baseq2/m_soldier.h @@ -0,0 +1,500 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +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. + +*/ +// G:\quake2\baseq2\models/monsters/soldier + +// This file generated by ModelGen - Do NOT Modify + +#define FRAME_attak101 0 +#define FRAME_attak102 1 +#define FRAME_attak103 2 +#define FRAME_attak104 3 +#define FRAME_attak105 4 +#define FRAME_attak106 5 +#define FRAME_attak107 6 +#define FRAME_attak108 7 +#define FRAME_attak109 8 +#define FRAME_attak110 9 +#define FRAME_attak111 10 +#define FRAME_attak112 11 +#define FRAME_attak201 12 +#define FRAME_attak202 13 +#define FRAME_attak203 14 +#define FRAME_attak204 15 +#define FRAME_attak205 16 +#define FRAME_attak206 17 +#define FRAME_attak207 18 +#define FRAME_attak208 19 +#define FRAME_attak209 20 +#define FRAME_attak210 21 +#define FRAME_attak211 22 +#define FRAME_attak212 23 +#define FRAME_attak213 24 +#define FRAME_attak214 25 +#define FRAME_attak215 26 +#define FRAME_attak216 27 +#define FRAME_attak217 28 +#define FRAME_attak218 29 +#define FRAME_attak301 30 +#define FRAME_attak302 31 +#define FRAME_attak303 32 +#define FRAME_attak304 33 +#define FRAME_attak305 34 +#define FRAME_attak306 35 +#define FRAME_attak307 36 +#define FRAME_attak308 37 +#define FRAME_attak309 38 +#define FRAME_attak401 39 +#define FRAME_attak402 40 +#define FRAME_attak403 41 +#define FRAME_attak404 42 +#define FRAME_attak405 43 +#define FRAME_attak406 44 +#define FRAME_duck01 45 +#define FRAME_duck02 46 +#define FRAME_duck03 47 +#define FRAME_duck04 48 +#define FRAME_duck05 49 +#define FRAME_pain101 50 +#define FRAME_pain102 51 +#define FRAME_pain103 52 +#define FRAME_pain104 53 +#define FRAME_pain105 54 +#define FRAME_pain201 55 +#define FRAME_pain202 56 +#define FRAME_pain203 57 +#define FRAME_pain204 58 +#define FRAME_pain205 59 +#define FRAME_pain206 60 +#define FRAME_pain207 61 +#define FRAME_pain301 62 +#define FRAME_pain302 63 +#define FRAME_pain303 64 +#define FRAME_pain304 65 +#define FRAME_pain305 66 +#define FRAME_pain306 67 +#define FRAME_pain307 68 +#define FRAME_pain308 69 +#define FRAME_pain309 70 +#define FRAME_pain310 71 +#define FRAME_pain311 72 +#define FRAME_pain312 73 +#define FRAME_pain313 74 +#define FRAME_pain314 75 +#define FRAME_pain315 76 +#define FRAME_pain316 77 +#define FRAME_pain317 78 +#define FRAME_pain318 79 +#define FRAME_pain401 80 +#define FRAME_pain402 81 +#define FRAME_pain403 82 +#define FRAME_pain404 83 +#define FRAME_pain405 84 +#define FRAME_pain406 85 +#define FRAME_pain407 86 +#define FRAME_pain408 87 +#define FRAME_pain409 88 +#define FRAME_pain410 89 +#define FRAME_pain411 90 +#define FRAME_pain412 91 +#define FRAME_pain413 92 +#define FRAME_pain414 93 +#define FRAME_pain415 94 +#define FRAME_pain416 95 +#define FRAME_pain417 96 +#define FRAME_run01 97 +#define FRAME_run02 98 +#define FRAME_run03 99 +#define FRAME_run04 100 +#define FRAME_run05 101 +#define FRAME_run06 102 +#define FRAME_run07 103 +#define FRAME_run08 104 +#define FRAME_run09 105 +#define FRAME_run10 106 +#define FRAME_run11 107 +#define FRAME_run12 108 +#define FRAME_runs01 109 +#define FRAME_runs02 110 +#define FRAME_runs03 111 +#define FRAME_runs04 112 +#define FRAME_runs05 113 +#define FRAME_runs06 114 +#define FRAME_runs07 115 +#define FRAME_runs08 116 +#define FRAME_runs09 117 +#define FRAME_runs10 118 +#define FRAME_runs11 119 +#define FRAME_runs12 120 +#define FRAME_runs13 121 +#define FRAME_runs14 122 +#define FRAME_runs15 123 +#define FRAME_runs16 124 +#define FRAME_runs17 125 +#define FRAME_runs18 126 +#define FRAME_runt01 127 +#define FRAME_runt02 128 +#define FRAME_runt03 129 +#define FRAME_runt04 130 +#define FRAME_runt05 131 +#define FRAME_runt06 132 +#define FRAME_runt07 133 +#define FRAME_runt08 134 +#define FRAME_runt09 135 +#define FRAME_runt10 136 +#define FRAME_runt11 137 +#define FRAME_runt12 138 +#define FRAME_runt13 139 +#define FRAME_runt14 140 +#define FRAME_runt15 141 +#define FRAME_runt16 142 +#define FRAME_runt17 143 +#define FRAME_runt18 144 +#define FRAME_runt19 145 +#define FRAME_stand101 146 +#define FRAME_stand102 147 +#define FRAME_stand103 148 +#define FRAME_stand104 149 +#define FRAME_stand105 150 +#define FRAME_stand106 151 +#define FRAME_stand107 152 +#define FRAME_stand108 153 +#define FRAME_stand109 154 +#define FRAME_stand110 155 +#define FRAME_stand111 156 +#define FRAME_stand112 157 +#define FRAME_stand113 158 +#define FRAME_stand114 159 +#define FRAME_stand115 160 +#define FRAME_stand116 161 +#define FRAME_stand117 162 +#define FRAME_stand118 163 +#define FRAME_stand119 164 +#define FRAME_stand120 165 +#define FRAME_stand121 166 +#define FRAME_stand122 167 +#define FRAME_stand123 168 +#define FRAME_stand124 169 +#define FRAME_stand125 170 +#define FRAME_stand126 171 +#define FRAME_stand127 172 +#define FRAME_stand128 173 +#define FRAME_stand129 174 +#define FRAME_stand130 175 +#define FRAME_stand301 176 +#define FRAME_stand302 177 +#define FRAME_stand303 178 +#define FRAME_stand304 179 +#define FRAME_stand305 180 +#define FRAME_stand306 181 +#define FRAME_stand307 182 +#define FRAME_stand308 183 +#define FRAME_stand309 184 +#define FRAME_stand310 185 +#define FRAME_stand311 186 +#define FRAME_stand312 187 +#define FRAME_stand313 188 +#define FRAME_stand314 189 +#define FRAME_stand315 190 +#define FRAME_stand316 191 +#define FRAME_stand317 192 +#define FRAME_stand318 193 +#define FRAME_stand319 194 +#define FRAME_stand320 195 +#define FRAME_stand321 196 +#define FRAME_stand322 197 +#define FRAME_stand323 198 +#define FRAME_stand324 199 +#define FRAME_stand325 200 +#define FRAME_stand326 201 +#define FRAME_stand327 202 +#define FRAME_stand328 203 +#define FRAME_stand329 204 +#define FRAME_stand330 205 +#define FRAME_stand331 206 +#define FRAME_stand332 207 +#define FRAME_stand333 208 +#define FRAME_stand334 209 +#define FRAME_stand335 210 +#define FRAME_stand336 211 +#define FRAME_stand337 212 +#define FRAME_stand338 213 +#define FRAME_stand339 214 +#define FRAME_walk101 215 +#define FRAME_walk102 216 +#define FRAME_walk103 217 +#define FRAME_walk104 218 +#define FRAME_walk105 219 +#define FRAME_walk106 220 +#define FRAME_walk107 221 +#define FRAME_walk108 222 +#define FRAME_walk109 223 +#define FRAME_walk110 224 +#define FRAME_walk111 225 +#define FRAME_walk112 226 +#define FRAME_walk113 227 +#define FRAME_walk114 228 +#define FRAME_walk115 229 +#define FRAME_walk116 230 +#define FRAME_walk117 231 +#define FRAME_walk118 232 +#define FRAME_walk119 233 +#define FRAME_walk120 234 +#define FRAME_walk121 235 +#define FRAME_walk122 236 +#define FRAME_walk123 237 +#define FRAME_walk124 238 +#define FRAME_walk125 239 +#define FRAME_walk126 240 +#define FRAME_walk127 241 +#define FRAME_walk128 242 +#define FRAME_walk129 243 +#define FRAME_walk130 244 +#define FRAME_walk131 245 +#define FRAME_walk132 246 +#define FRAME_walk133 247 +#define FRAME_walk201 248 +#define FRAME_walk202 249 +#define FRAME_walk203 250 +#define FRAME_walk204 251 +#define FRAME_walk205 252 +#define FRAME_walk206 253 +#define FRAME_walk207 254 +#define FRAME_walk208 255 +#define FRAME_walk209 256 +#define FRAME_walk210 257 +#define FRAME_walk211 258 +#define FRAME_walk212 259 +#define FRAME_walk213 260 +#define FRAME_walk214 261 +#define FRAME_walk215 262 +#define FRAME_walk216 263 +#define FRAME_walk217 264 +#define FRAME_walk218 265 +#define FRAME_walk219 266 +#define FRAME_walk220 267 +#define FRAME_walk221 268 +#define FRAME_walk222 269 +#define FRAME_walk223 270 +#define FRAME_walk224 271 +#define FRAME_death101 272 +#define FRAME_death102 273 +#define FRAME_death103 274 +#define FRAME_death104 275 +#define FRAME_death105 276 +#define FRAME_death106 277 +#define FRAME_death107 278 +#define FRAME_death108 279 +#define FRAME_death109 280 +#define FRAME_death110 281 +#define FRAME_death111 282 +#define FRAME_death112 283 +#define FRAME_death113 284 +#define FRAME_death114 285 +#define FRAME_death115 286 +#define FRAME_death116 287 +#define FRAME_death117 288 +#define FRAME_death118 289 +#define FRAME_death119 290 +#define FRAME_death120 291 +#define FRAME_death121 292 +#define FRAME_death122 293 +#define FRAME_death123 294 +#define FRAME_death124 295 +#define FRAME_death125 296 +#define FRAME_death126 297 +#define FRAME_death127 298 +#define FRAME_death128 299 +#define FRAME_death129 300 +#define FRAME_death130 301 +#define FRAME_death131 302 +#define FRAME_death132 303 +#define FRAME_death133 304 +#define FRAME_death134 305 +#define FRAME_death135 306 +#define FRAME_death136 307 +#define FRAME_death201 308 +#define FRAME_death202 309 +#define FRAME_death203 310 +#define FRAME_death204 311 +#define FRAME_death205 312 +#define FRAME_death206 313 +#define FRAME_death207 314 +#define FRAME_death208 315 +#define FRAME_death209 316 +#define FRAME_death210 317 +#define FRAME_death211 318 +#define FRAME_death212 319 +#define FRAME_death213 320 +#define FRAME_death214 321 +#define FRAME_death215 322 +#define FRAME_death216 323 +#define FRAME_death217 324 +#define FRAME_death218 325 +#define FRAME_death219 326 +#define FRAME_death220 327 +#define FRAME_death221 328 +#define FRAME_death222 329 +#define FRAME_death223 330 +#define FRAME_death224 331 +#define FRAME_death225 332 +#define FRAME_death226 333 +#define FRAME_death227 334 +#define FRAME_death228 335 +#define FRAME_death229 336 +#define FRAME_death230 337 +#define FRAME_death231 338 +#define FRAME_death232 339 +#define FRAME_death233 340 +#define FRAME_death234 341 +#define FRAME_death235 342 +#define FRAME_death301 343 +#define FRAME_death302 344 +#define FRAME_death303 345 +#define FRAME_death304 346 +#define FRAME_death305 347 +#define FRAME_death306 348 +#define FRAME_death307 349 +#define FRAME_death308 350 +#define FRAME_death309 351 +#define FRAME_death310 352 +#define FRAME_death311 353 +#define FRAME_death312 354 +#define FRAME_death313 355 +#define FRAME_death314 356 +#define FRAME_death315 357 +#define FRAME_death316 358 +#define FRAME_death317 359 +#define FRAME_death318 360 +#define FRAME_death319 361 +#define FRAME_death320 362 +#define FRAME_death321 363 +#define FRAME_death322 364 +#define FRAME_death323 365 +#define FRAME_death324 366 +#define FRAME_death325 367 +#define FRAME_death326 368 +#define FRAME_death327 369 +#define FRAME_death328 370 +#define FRAME_death329 371 +#define FRAME_death330 372 +#define FRAME_death331 373 +#define FRAME_death332 374 +#define FRAME_death333 375 +#define FRAME_death334 376 +#define FRAME_death335 377 +#define FRAME_death336 378 +#define FRAME_death337 379 +#define FRAME_death338 380 +#define FRAME_death339 381 +#define FRAME_death340 382 +#define FRAME_death341 383 +#define FRAME_death342 384 +#define FRAME_death343 385 +#define FRAME_death344 386 +#define FRAME_death345 387 +#define FRAME_death401 388 +#define FRAME_death402 389 +#define FRAME_death403 390 +#define FRAME_death404 391 +#define FRAME_death405 392 +#define FRAME_death406 393 +#define FRAME_death407 394 +#define FRAME_death408 395 +#define FRAME_death409 396 +#define FRAME_death410 397 +#define FRAME_death411 398 +#define FRAME_death412 399 +#define FRAME_death413 400 +#define FRAME_death414 401 +#define FRAME_death415 402 +#define FRAME_death416 403 +#define FRAME_death417 404 +#define FRAME_death418 405 +#define FRAME_death419 406 +#define FRAME_death420 407 +#define FRAME_death421 408 +#define FRAME_death422 409 +#define FRAME_death423 410 +#define FRAME_death424 411 +#define FRAME_death425 412 +#define FRAME_death426 413 +#define FRAME_death427 414 +#define FRAME_death428 415 +#define FRAME_death429 416 +#define FRAME_death430 417 +#define FRAME_death431 418 +#define FRAME_death432 419 +#define FRAME_death433 420 +#define FRAME_death434 421 +#define FRAME_death435 422 +#define FRAME_death436 423 +#define FRAME_death437 424 +#define FRAME_death438 425 +#define FRAME_death439 426 +#define FRAME_death440 427 +#define FRAME_death441 428 +#define FRAME_death442 429 +#define FRAME_death443 430 +#define FRAME_death444 431 +#define FRAME_death445 432 +#define FRAME_death446 433 +#define FRAME_death447 434 +#define FRAME_death448 435 +#define FRAME_death449 436 +#define FRAME_death450 437 +#define FRAME_death451 438 +#define FRAME_death452 439 +#define FRAME_death453 440 +#define FRAME_death501 441 +#define FRAME_death502 442 +#define FRAME_death503 443 +#define FRAME_death504 444 +#define FRAME_death505 445 +#define FRAME_death506 446 +#define FRAME_death507 447 +#define FRAME_death508 448 +#define FRAME_death509 449 +#define FRAME_death510 450 +#define FRAME_death511 451 +#define FRAME_death512 452 +#define FRAME_death513 453 +#define FRAME_death514 454 +#define FRAME_death515 455 +#define FRAME_death516 456 +#define FRAME_death517 457 +#define FRAME_death518 458 +#define FRAME_death519 459 +#define FRAME_death520 460 +#define FRAME_death521 461 +#define FRAME_death522 462 +#define FRAME_death523 463 +#define FRAME_death524 464 +#define FRAME_death601 465 +#define FRAME_death602 466 +#define FRAME_death603 467 +#define FRAME_death604 468 +#define FRAME_death605 469 +#define FRAME_death606 470 +#define FRAME_death607 471 +#define FRAME_death608 472 +#define FRAME_death609 473 +#define FRAME_death610 474 + +#define MODEL_SCALE 1.200000 diff --git a/original/baseq2/m_supertank.c b/original/baseq2/m_supertank.c new file mode 100644 index 0000000..c1de0e8 --- /dev/null +++ b/original/baseq2/m_supertank.c @@ -0,0 +1,717 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +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. + +*/ +/* +============================================================================== + +SUPERTANK + +============================================================================== +*/ + +#include "g_local.h" +#include "m_supertank.h" + +qboolean visible (edict_t *self, edict_t *other); + +static int sound_pain1; +static int sound_pain2; +static int sound_pain3; +static int sound_death; +static int sound_search1; +static int sound_search2; + +static int tread_sound; + +void BossExplode (edict_t *self); + +void TreadSound (edict_t *self) +{ + gi.sound (self, CHAN_VOICE, tread_sound, 1, ATTN_NORM, 0); +} + +void supertank_search (edict_t *self) +{ + if (random() < 0.5) + gi.sound (self, CHAN_VOICE, sound_search1, 1, ATTN_NORM, 0); + else + gi.sound (self, CHAN_VOICE, sound_search2, 1, ATTN_NORM, 0); +} + + +void supertank_dead (edict_t *self); +void supertankRocket (edict_t *self); +void supertankMachineGun (edict_t *self); +void supertank_reattack1(edict_t *self); + + +// +// stand +// + +mframe_t supertank_frames_stand []= +{ + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL +}; +mmove_t supertank_move_stand = {FRAME_stand_1, FRAME_stand_60, supertank_frames_stand, NULL}; + +void supertank_stand (edict_t *self) +{ + self->monsterinfo.currentmove = &supertank_move_stand; +} + + +mframe_t supertank_frames_run [] = +{ + ai_run, 12, TreadSound, + ai_run, 12, NULL, + ai_run, 12, NULL, + ai_run, 12, NULL, + ai_run, 12, NULL, + ai_run, 12, NULL, + ai_run, 12, NULL, + ai_run, 12, NULL, + ai_run, 12, NULL, + ai_run, 12, NULL, + ai_run, 12, NULL, + ai_run, 12, NULL, + ai_run, 12, NULL, + ai_run, 12, NULL, + ai_run, 12, NULL, + ai_run, 12, NULL, + ai_run, 12, NULL, + ai_run, 12, NULL +}; +mmove_t supertank_move_run = {FRAME_forwrd_1, FRAME_forwrd_18, supertank_frames_run, NULL}; + +// +// walk +// + + +mframe_t supertank_frames_forward [] = +{ + ai_walk, 4, TreadSound, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL +}; +mmove_t supertank_move_forward = {FRAME_forwrd_1, FRAME_forwrd_18, supertank_frames_forward, NULL}; + +void supertank_forward (edict_t *self) +{ + self->monsterinfo.currentmove = &supertank_move_forward; +} + +void supertank_walk (edict_t *self) +{ + self->monsterinfo.currentmove = &supertank_move_forward; +} + +void supertank_run (edict_t *self) +{ + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + self->monsterinfo.currentmove = &supertank_move_stand; + else + self->monsterinfo.currentmove = &supertank_move_run; +} + +mframe_t supertank_frames_turn_right [] = +{ + ai_move, 0, TreadSound, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t supertank_move_turn_right = {FRAME_right_1, FRAME_right_18, supertank_frames_turn_right, supertank_run}; + +mframe_t supertank_frames_turn_left [] = +{ + ai_move, 0, TreadSound, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t supertank_move_turn_left = {FRAME_left_1, FRAME_left_18, supertank_frames_turn_left, supertank_run}; + + +mframe_t supertank_frames_pain3 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t supertank_move_pain3 = {FRAME_pain3_9, FRAME_pain3_12, supertank_frames_pain3, supertank_run}; + +mframe_t supertank_frames_pain2 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t supertank_move_pain2 = {FRAME_pain2_5, FRAME_pain2_8, supertank_frames_pain2, supertank_run}; + +mframe_t supertank_frames_pain1 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t supertank_move_pain1 = {FRAME_pain1_1, FRAME_pain1_4, supertank_frames_pain1, supertank_run}; + +mframe_t supertank_frames_death1 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, BossExplode +}; +mmove_t supertank_move_death = {FRAME_death_1, FRAME_death_24, supertank_frames_death1, supertank_dead}; + +mframe_t supertank_frames_backward[] = +{ + ai_walk, 0, TreadSound, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL +}; +mmove_t supertank_move_backward = {FRAME_backwd_1, FRAME_backwd_18, supertank_frames_backward, NULL}; + +mframe_t supertank_frames_attack4[]= +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t supertank_move_attack4 = {FRAME_attak4_1, FRAME_attak4_6, supertank_frames_attack4, supertank_run}; + +mframe_t supertank_frames_attack3[]= +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t supertank_move_attack3 = {FRAME_attak3_1, FRAME_attak3_27, supertank_frames_attack3, supertank_run}; + +mframe_t supertank_frames_attack2[]= +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, supertankRocket, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, supertankRocket, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, supertankRocket, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t supertank_move_attack2 = {FRAME_attak2_1, FRAME_attak2_27, supertank_frames_attack2, supertank_run}; + +mframe_t supertank_frames_attack1[]= +{ + ai_charge, 0, supertankMachineGun, + ai_charge, 0, supertankMachineGun, + ai_charge, 0, supertankMachineGun, + ai_charge, 0, supertankMachineGun, + ai_charge, 0, supertankMachineGun, + ai_charge, 0, supertankMachineGun, + +}; +mmove_t supertank_move_attack1 = {FRAME_attak1_1, FRAME_attak1_6, supertank_frames_attack1, supertank_reattack1}; + +mframe_t supertank_frames_end_attack1[]= +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t supertank_move_end_attack1 = {FRAME_attak1_7, FRAME_attak1_20, supertank_frames_end_attack1, supertank_run}; + + +void supertank_reattack1(edict_t *self) +{ + if (visible(self, self->enemy)) + if (random() < 0.9) + self->monsterinfo.currentmove = &supertank_move_attack1; + else + self->monsterinfo.currentmove = &supertank_move_end_attack1; + else + self->monsterinfo.currentmove = &supertank_move_end_attack1; +} + +void supertank_pain (edict_t *self, edict_t *other, float kick, int damage) +{ + + if (self->health < (self->max_health / 2)) + self->s.skinnum = 1; + + if (level.time < self->pain_debounce_time) + return; + + // Lessen the chance of him going into his pain frames + if (damage <=25) + if (random()<0.2) + return; + + // Don't go into pain if he's firing his rockets + if (skill->value >= 2) + if ( (self->s.frame >= FRAME_attak2_1) && (self->s.frame <= FRAME_attak2_14) ) + return; + + self->pain_debounce_time = level.time + 3; + + if (skill->value == 3) + return; // no pain anims in nightmare + + if (damage <= 10) + { + gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM,0); + self->monsterinfo.currentmove = &supertank_move_pain1; + } + else if (damage <= 25) + { + gi.sound (self, CHAN_VOICE, sound_pain3, 1, ATTN_NORM,0); + self->monsterinfo.currentmove = &supertank_move_pain2; + } + else + { + gi.sound (self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM,0); + self->monsterinfo.currentmove = &supertank_move_pain3; + } +}; + + +void supertankRocket (edict_t *self) +{ + vec3_t forward, right; + vec3_t start; + vec3_t dir; + vec3_t vec; + int flash_number; + + if (self->s.frame == FRAME_attak2_8) + flash_number = MZ2_SUPERTANK_ROCKET_1; + else if (self->s.frame == FRAME_attak2_11) + flash_number = MZ2_SUPERTANK_ROCKET_2; + else // (self->s.frame == FRAME_attak2_14) + flash_number = MZ2_SUPERTANK_ROCKET_3; + + AngleVectors (self->s.angles, forward, right, NULL); + G_ProjectSource (self->s.origin, monster_flash_offset[flash_number], forward, right, start); + + VectorCopy (self->enemy->s.origin, vec); + vec[2] += self->enemy->viewheight; + VectorSubtract (vec, start, dir); + VectorNormalize (dir); + + monster_fire_rocket (self, start, dir, 50, 500, flash_number); +} + +void supertankMachineGun (edict_t *self) +{ + vec3_t dir; + vec3_t vec; + vec3_t start; + vec3_t forward, right; + int flash_number; + + flash_number = MZ2_SUPERTANK_MACHINEGUN_1 + (self->s.frame - FRAME_attak1_1); + + //FIXME!!! + dir[0] = 0; + dir[1] = self->s.angles[1]; + dir[2] = 0; + + AngleVectors (dir, forward, right, NULL); + G_ProjectSource (self->s.origin, monster_flash_offset[flash_number], forward, right, start); + + if (self->enemy) + { + VectorCopy (self->enemy->s.origin, vec); + VectorMA (vec, 0, self->enemy->velocity, vec); + vec[2] += self->enemy->viewheight; + VectorSubtract (vec, start, forward); + VectorNormalize (forward); + } + + monster_fire_bullet (self, start, forward, 6, 4, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, flash_number); +} + + +void supertank_attack(edict_t *self) +{ + vec3_t vec; + float range; + //float r; + + VectorSubtract (self->enemy->s.origin, self->s.origin, vec); + range = VectorLength (vec); + + //r = random(); + + // Attack 1 == Chaingun + // Attack 2 == Rocket Launcher + + if (range <= 160) + { + self->monsterinfo.currentmove = &supertank_move_attack1; + } + else + { // fire rockets more often at distance + if (random() < 0.3) + self->monsterinfo.currentmove = &supertank_move_attack1; + else + self->monsterinfo.currentmove = &supertank_move_attack2; + } +} + + +// +// death +// + +void supertank_dead (edict_t *self) +{ + VectorSet (self->mins, -60, -60, 0); + VectorSet (self->maxs, 60, 60, 72); + self->movetype = MOVETYPE_TOSS; + self->svflags |= SVF_DEADMONSTER; + self->nextthink = 0; + gi.linkentity (self); +} + + +void BossExplode (edict_t *self) +{ + vec3_t org; + int n; + + self->think = BossExplode; + VectorCopy (self->s.origin, org); + org[2] += 24 + (rand()&15); + switch (self->count++) + { + case 0: + org[0] -= 24; + org[1] -= 24; + break; + case 1: + org[0] += 24; + org[1] += 24; + break; + case 2: + org[0] += 24; + org[1] -= 24; + break; + case 3: + org[0] -= 24; + org[1] += 24; + break; + case 4: + org[0] -= 48; + org[1] -= 48; + break; + case 5: + org[0] += 48; + org[1] += 48; + break; + case 6: + org[0] -= 48; + org[1] += 48; + break; + case 7: + org[0] += 48; + org[1] -= 48; + break; + case 8: + self->s.sound = 0; + for (n= 0; n < 4; n++) + ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", 500, GIB_ORGANIC); + for (n= 0; n < 8; n++) + ThrowGib (self, "models/objects/gibs/sm_metal/tris.md2", 500, GIB_METALLIC); + ThrowGib (self, "models/objects/gibs/chest/tris.md2", 500, GIB_ORGANIC); + ThrowHead (self, "models/objects/gibs/gear/tris.md2", 500, GIB_METALLIC); + self->deadflag = DEAD_DEAD; + return; + } + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_EXPLOSION1); + gi.WritePosition (org); + gi.multicast (self->s.origin, MULTICAST_PVS); + + self->nextthink = level.time + 0.1; +} + + +void supertank_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + gi.sound (self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0); + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_NO; + self->count = 0; + self->monsterinfo.currentmove = &supertank_move_death; +} + +// +// monster_supertank +// + +/*QUAKED monster_supertank (1 .5 0) (-64 -64 0) (64 64 72) Ambush Trigger_Spawn Sight +*/ +void SP_monster_supertank (edict_t *self) +{ + if (deathmatch->value) + { + G_FreeEdict (self); + return; + } + + sound_pain1 = gi.soundindex ("bosstank/btkpain1.wav"); + sound_pain2 = gi.soundindex ("bosstank/btkpain2.wav"); + sound_pain3 = gi.soundindex ("bosstank/btkpain3.wav"); + sound_death = gi.soundindex ("bosstank/btkdeth1.wav"); + sound_search1 = gi.soundindex ("bosstank/btkunqv1.wav"); + sound_search2 = gi.soundindex ("bosstank/btkunqv2.wav"); + +// self->s.sound = gi.soundindex ("bosstank/btkengn1.wav"); + tread_sound = gi.soundindex ("bosstank/btkengn1.wav"); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex ("models/monsters/boss1/tris.md2"); + VectorSet (self->mins, -64, -64, 0); + VectorSet (self->maxs, 64, 64, 112); + + self->health = 1500; + self->gib_health = -500; + self->mass = 800; + + self->pain = supertank_pain; + self->die = supertank_die; + self->monsterinfo.stand = supertank_stand; + self->monsterinfo.walk = supertank_walk; + self->monsterinfo.run = supertank_run; + self->monsterinfo.dodge = NULL; + self->monsterinfo.attack = supertank_attack; + self->monsterinfo.search = supertank_search; + self->monsterinfo.melee = NULL; + self->monsterinfo.sight = NULL; + + gi.linkentity (self); + + self->monsterinfo.currentmove = &supertank_move_stand; + self->monsterinfo.scale = MODEL_SCALE; + + walkmonster_start(self); +} diff --git a/original/baseq2/m_supertank.h b/original/baseq2/m_supertank.h new file mode 100644 index 0000000..53f4a66 --- /dev/null +++ b/original/baseq2/m_supertank.h @@ -0,0 +1,279 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +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. + +*/ +// G:\quake2\baseq2\models/monsters/boss1/backup + +// This file generated by ModelGen - Do NOT Modify + +#define FRAME_attak1_1 0 +#define FRAME_attak1_2 1 +#define FRAME_attak1_3 2 +#define FRAME_attak1_4 3 +#define FRAME_attak1_5 4 +#define FRAME_attak1_6 5 +#define FRAME_attak1_7 6 +#define FRAME_attak1_8 7 +#define FRAME_attak1_9 8 +#define FRAME_attak1_10 9 +#define FRAME_attak1_11 10 +#define FRAME_attak1_12 11 +#define FRAME_attak1_13 12 +#define FRAME_attak1_14 13 +#define FRAME_attak1_15 14 +#define FRAME_attak1_16 15 +#define FRAME_attak1_17 16 +#define FRAME_attak1_18 17 +#define FRAME_attak1_19 18 +#define FRAME_attak1_20 19 +#define FRAME_attak2_1 20 +#define FRAME_attak2_2 21 +#define FRAME_attak2_3 22 +#define FRAME_attak2_4 23 +#define FRAME_attak2_5 24 +#define FRAME_attak2_6 25 +#define FRAME_attak2_7 26 +#define FRAME_attak2_8 27 +#define FRAME_attak2_9 28 +#define FRAME_attak2_10 29 +#define FRAME_attak2_11 30 +#define FRAME_attak2_12 31 +#define FRAME_attak2_13 32 +#define FRAME_attak2_14 33 +#define FRAME_attak2_15 34 +#define FRAME_attak2_16 35 +#define FRAME_attak2_17 36 +#define FRAME_attak2_18 37 +#define FRAME_attak2_19 38 +#define FRAME_attak2_20 39 +#define FRAME_attak2_21 40 +#define FRAME_attak2_22 41 +#define FRAME_attak2_23 42 +#define FRAME_attak2_24 43 +#define FRAME_attak2_25 44 +#define FRAME_attak2_26 45 +#define FRAME_attak2_27 46 +#define FRAME_attak3_1 47 +#define FRAME_attak3_2 48 +#define FRAME_attak3_3 49 +#define FRAME_attak3_4 50 +#define FRAME_attak3_5 51 +#define FRAME_attak3_6 52 +#define FRAME_attak3_7 53 +#define FRAME_attak3_8 54 +#define FRAME_attak3_9 55 +#define FRAME_attak3_10 56 +#define FRAME_attak3_11 57 +#define FRAME_attak3_12 58 +#define FRAME_attak3_13 59 +#define FRAME_attak3_14 60 +#define FRAME_attak3_15 61 +#define FRAME_attak3_16 62 +#define FRAME_attak3_17 63 +#define FRAME_attak3_18 64 +#define FRAME_attak3_19 65 +#define FRAME_attak3_20 66 +#define FRAME_attak3_21 67 +#define FRAME_attak3_22 68 +#define FRAME_attak3_23 69 +#define FRAME_attak3_24 70 +#define FRAME_attak3_25 71 +#define FRAME_attak3_26 72 +#define FRAME_attak3_27 73 +#define FRAME_attak4_1 74 +#define FRAME_attak4_2 75 +#define FRAME_attak4_3 76 +#define FRAME_attak4_4 77 +#define FRAME_attak4_5 78 +#define FRAME_attak4_6 79 +#define FRAME_backwd_1 80 +#define FRAME_backwd_2 81 +#define FRAME_backwd_3 82 +#define FRAME_backwd_4 83 +#define FRAME_backwd_5 84 +#define FRAME_backwd_6 85 +#define FRAME_backwd_7 86 +#define FRAME_backwd_8 87 +#define FRAME_backwd_9 88 +#define FRAME_backwd_10 89 +#define FRAME_backwd_11 90 +#define FRAME_backwd_12 91 +#define FRAME_backwd_13 92 +#define FRAME_backwd_14 93 +#define FRAME_backwd_15 94 +#define FRAME_backwd_16 95 +#define FRAME_backwd_17 96 +#define FRAME_backwd_18 97 +#define FRAME_death_1 98 +#define FRAME_death_2 99 +#define FRAME_death_3 100 +#define FRAME_death_4 101 +#define FRAME_death_5 102 +#define FRAME_death_6 103 +#define FRAME_death_7 104 +#define FRAME_death_8 105 +#define FRAME_death_9 106 +#define FRAME_death_10 107 +#define FRAME_death_11 108 +#define FRAME_death_12 109 +#define FRAME_death_13 110 +#define FRAME_death_14 111 +#define FRAME_death_15 112 +#define FRAME_death_16 113 +#define FRAME_death_17 114 +#define FRAME_death_18 115 +#define FRAME_death_19 116 +#define FRAME_death_20 117 +#define FRAME_death_21 118 +#define FRAME_death_22 119 +#define FRAME_death_23 120 +#define FRAME_death_24 121 +#define FRAME_death_31 122 +#define FRAME_death_32 123 +#define FRAME_death_33 124 +#define FRAME_death_45 125 +#define FRAME_death_46 126 +#define FRAME_death_47 127 +#define FRAME_forwrd_1 128 +#define FRAME_forwrd_2 129 +#define FRAME_forwrd_3 130 +#define FRAME_forwrd_4 131 +#define FRAME_forwrd_5 132 +#define FRAME_forwrd_6 133 +#define FRAME_forwrd_7 134 +#define FRAME_forwrd_8 135 +#define FRAME_forwrd_9 136 +#define FRAME_forwrd_10 137 +#define FRAME_forwrd_11 138 +#define FRAME_forwrd_12 139 +#define FRAME_forwrd_13 140 +#define FRAME_forwrd_14 141 +#define FRAME_forwrd_15 142 +#define FRAME_forwrd_16 143 +#define FRAME_forwrd_17 144 +#define FRAME_forwrd_18 145 +#define FRAME_left_1 146 +#define FRAME_left_2 147 +#define FRAME_left_3 148 +#define FRAME_left_4 149 +#define FRAME_left_5 150 +#define FRAME_left_6 151 +#define FRAME_left_7 152 +#define FRAME_left_8 153 +#define FRAME_left_9 154 +#define FRAME_left_10 155 +#define FRAME_left_11 156 +#define FRAME_left_12 157 +#define FRAME_left_13 158 +#define FRAME_left_14 159 +#define FRAME_left_15 160 +#define FRAME_left_16 161 +#define FRAME_left_17 162 +#define FRAME_left_18 163 +#define FRAME_pain1_1 164 +#define FRAME_pain1_2 165 +#define FRAME_pain1_3 166 +#define FRAME_pain1_4 167 +#define FRAME_pain2_5 168 +#define FRAME_pain2_6 169 +#define FRAME_pain2_7 170 +#define FRAME_pain2_8 171 +#define FRAME_pain3_9 172 +#define FRAME_pain3_10 173 +#define FRAME_pain3_11 174 +#define FRAME_pain3_12 175 +#define FRAME_right_1 176 +#define FRAME_right_2 177 +#define FRAME_right_3 178 +#define FRAME_right_4 179 +#define FRAME_right_5 180 +#define FRAME_right_6 181 +#define FRAME_right_7 182 +#define FRAME_right_8 183 +#define FRAME_right_9 184 +#define FRAME_right_10 185 +#define FRAME_right_11 186 +#define FRAME_right_12 187 +#define FRAME_right_13 188 +#define FRAME_right_14 189 +#define FRAME_right_15 190 +#define FRAME_right_16 191 +#define FRAME_right_17 192 +#define FRAME_right_18 193 +#define FRAME_stand_1 194 +#define FRAME_stand_2 195 +#define FRAME_stand_3 196 +#define FRAME_stand_4 197 +#define FRAME_stand_5 198 +#define FRAME_stand_6 199 +#define FRAME_stand_7 200 +#define FRAME_stand_8 201 +#define FRAME_stand_9 202 +#define FRAME_stand_10 203 +#define FRAME_stand_11 204 +#define FRAME_stand_12 205 +#define FRAME_stand_13 206 +#define FRAME_stand_14 207 +#define FRAME_stand_15 208 +#define FRAME_stand_16 209 +#define FRAME_stand_17 210 +#define FRAME_stand_18 211 +#define FRAME_stand_19 212 +#define FRAME_stand_20 213 +#define FRAME_stand_21 214 +#define FRAME_stand_22 215 +#define FRAME_stand_23 216 +#define FRAME_stand_24 217 +#define FRAME_stand_25 218 +#define FRAME_stand_26 219 +#define FRAME_stand_27 220 +#define FRAME_stand_28 221 +#define FRAME_stand_29 222 +#define FRAME_stand_30 223 +#define FRAME_stand_31 224 +#define FRAME_stand_32 225 +#define FRAME_stand_33 226 +#define FRAME_stand_34 227 +#define FRAME_stand_35 228 +#define FRAME_stand_36 229 +#define FRAME_stand_37 230 +#define FRAME_stand_38 231 +#define FRAME_stand_39 232 +#define FRAME_stand_40 233 +#define FRAME_stand_41 234 +#define FRAME_stand_42 235 +#define FRAME_stand_43 236 +#define FRAME_stand_44 237 +#define FRAME_stand_45 238 +#define FRAME_stand_46 239 +#define FRAME_stand_47 240 +#define FRAME_stand_48 241 +#define FRAME_stand_49 242 +#define FRAME_stand_50 243 +#define FRAME_stand_51 244 +#define FRAME_stand_52 245 +#define FRAME_stand_53 246 +#define FRAME_stand_54 247 +#define FRAME_stand_55 248 +#define FRAME_stand_56 249 +#define FRAME_stand_57 250 +#define FRAME_stand_58 251 +#define FRAME_stand_59 252 +#define FRAME_stand_60 253 + +#define MODEL_SCALE 1.000000 diff --git a/original/baseq2/m_tank.c b/original/baseq2/m_tank.c new file mode 100644 index 0000000..f1bccba --- /dev/null +++ b/original/baseq2/m_tank.c @@ -0,0 +1,856 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +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. + +*/ +/* +============================================================================== + +TANK + +============================================================================== +*/ + +#include "g_local.h" +#include "m_tank.h" + + +void tank_refire_rocket (edict_t *self); +void tank_doattack_rocket (edict_t *self); +void tank_reattack_blaster (edict_t *self); + +static int sound_thud; +static int sound_pain; +static int sound_idle; +static int sound_die; +static int sound_step; +static int sound_sight; +static int sound_windup; +static int sound_strike; + +// +// misc +// + +void tank_sight (edict_t *self, edict_t *other) +{ + gi.sound (self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); +} + + +void tank_footstep (edict_t *self) +{ + gi.sound (self, CHAN_BODY, sound_step, 1, ATTN_NORM, 0); +} + +void tank_thud (edict_t *self) +{ + gi.sound (self, CHAN_BODY, sound_thud, 1, ATTN_NORM, 0); +} + +void tank_windup (edict_t *self) +{ + gi.sound (self, CHAN_WEAPON, sound_windup, 1, ATTN_NORM, 0); +} + +void tank_idle (edict_t *self) +{ + gi.sound (self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0); +} + + +// +// stand +// + +mframe_t tank_frames_stand []= +{ + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL +}; +mmove_t tank_move_stand = {FRAME_stand01, FRAME_stand30, tank_frames_stand, NULL}; + +void tank_stand (edict_t *self) +{ + self->monsterinfo.currentmove = &tank_move_stand; +} + + +// +// walk +// + +void tank_walk (edict_t *self); + +mframe_t tank_frames_start_walk [] = +{ + ai_walk, 0, NULL, + ai_walk, 6, NULL, + ai_walk, 6, NULL, + ai_walk, 11, tank_footstep +}; +mmove_t tank_move_start_walk = {FRAME_walk01, FRAME_walk04, tank_frames_start_walk, tank_walk}; + +mframe_t tank_frames_walk [] = +{ + ai_walk, 4, NULL, + ai_walk, 5, NULL, + ai_walk, 3, NULL, + ai_walk, 2, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 4, NULL, + ai_walk, 4, tank_footstep, + ai_walk, 3, NULL, + ai_walk, 5, NULL, + ai_walk, 4, NULL, + ai_walk, 5, NULL, + ai_walk, 7, NULL, + ai_walk, 7, NULL, + ai_walk, 6, NULL, + ai_walk, 6, tank_footstep +}; +mmove_t tank_move_walk = {FRAME_walk05, FRAME_walk20, tank_frames_walk, NULL}; + +mframe_t tank_frames_stop_walk [] = +{ + ai_walk, 3, NULL, + ai_walk, 3, NULL, + ai_walk, 2, NULL, + ai_walk, 2, NULL, + ai_walk, 4, tank_footstep +}; +mmove_t tank_move_stop_walk = {FRAME_walk21, FRAME_walk25, tank_frames_stop_walk, tank_stand}; + +void tank_walk (edict_t *self) +{ + self->monsterinfo.currentmove = &tank_move_walk; +} + + +// +// run +// + +void tank_run (edict_t *self); + +mframe_t tank_frames_start_run [] = +{ + ai_run, 0, NULL, + ai_run, 6, NULL, + ai_run, 6, NULL, + ai_run, 11, tank_footstep +}; +mmove_t tank_move_start_run = {FRAME_walk01, FRAME_walk04, tank_frames_start_run, tank_run}; + +mframe_t tank_frames_run [] = +{ + ai_run, 4, NULL, + ai_run, 5, NULL, + ai_run, 3, NULL, + ai_run, 2, NULL, + ai_run, 5, NULL, + ai_run, 5, NULL, + ai_run, 4, NULL, + ai_run, 4, tank_footstep, + ai_run, 3, NULL, + ai_run, 5, NULL, + ai_run, 4, NULL, + ai_run, 5, NULL, + ai_run, 7, NULL, + ai_run, 7, NULL, + ai_run, 6, NULL, + ai_run, 6, tank_footstep +}; +mmove_t tank_move_run = {FRAME_walk05, FRAME_walk20, tank_frames_run, NULL}; + +mframe_t tank_frames_stop_run [] = +{ + ai_run, 3, NULL, + ai_run, 3, NULL, + ai_run, 2, NULL, + ai_run, 2, NULL, + ai_run, 4, tank_footstep +}; +mmove_t tank_move_stop_run = {FRAME_walk21, FRAME_walk25, tank_frames_stop_run, tank_walk}; + +void tank_run (edict_t *self) +{ + if (self->enemy && self->enemy->client) + self->monsterinfo.aiflags |= AI_BRUTAL; + else + self->monsterinfo.aiflags &= ~AI_BRUTAL; + + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + { + self->monsterinfo.currentmove = &tank_move_stand; + return; + } + + if (self->monsterinfo.currentmove == &tank_move_walk || + self->monsterinfo.currentmove == &tank_move_start_run) + { + self->monsterinfo.currentmove = &tank_move_run; + } + else + { + self->monsterinfo.currentmove = &tank_move_start_run; + } +} + +// +// pain +// + +mframe_t tank_frames_pain1 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t tank_move_pain1 = {FRAME_pain101, FRAME_pain104, tank_frames_pain1, tank_run}; + +mframe_t tank_frames_pain2 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t tank_move_pain2 = {FRAME_pain201, FRAME_pain205, tank_frames_pain2, tank_run}; + +mframe_t tank_frames_pain3 [] = +{ + ai_move, -7, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 2, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 3, NULL, + ai_move, 0, NULL, + ai_move, 2, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, tank_footstep +}; +mmove_t tank_move_pain3 = {FRAME_pain301, FRAME_pain316, tank_frames_pain3, tank_run}; + + +void tank_pain (edict_t *self, edict_t *other, float kick, int damage) +{ + if (self->health < (self->max_health / 2)) + self->s.skinnum |= 1; + + if (damage <= 10) + return; + + if (level.time < self->pain_debounce_time) + return; + + if (damage <= 30) + if (random() > 0.2) + return; + + // If hard or nightmare, don't go into pain while attacking + if ( skill->value >= 2) + { + if ( (self->s.frame >= FRAME_attak301) && (self->s.frame <= FRAME_attak330) ) + return; + if ( (self->s.frame >= FRAME_attak101) && (self->s.frame <= FRAME_attak116) ) + return; + } + + self->pain_debounce_time = level.time + 3; + gi.sound (self, CHAN_VOICE, sound_pain, 1, ATTN_NORM, 0); + + if (skill->value == 3) + return; // no pain anims in nightmare + + if (damage <= 30) + self->monsterinfo.currentmove = &tank_move_pain1; + else if (damage <= 60) + self->monsterinfo.currentmove = &tank_move_pain2; + else + self->monsterinfo.currentmove = &tank_move_pain3; +}; + + +// +// attacks +// + +void TankBlaster (edict_t *self) +{ + vec3_t forward, right; + vec3_t start; + vec3_t end; + vec3_t dir; + int flash_number; + + if (self->s.frame == FRAME_attak110) + flash_number = MZ2_TANK_BLASTER_1; + else if (self->s.frame == FRAME_attak113) + flash_number = MZ2_TANK_BLASTER_2; + else // (self->s.frame == FRAME_attak116) + flash_number = MZ2_TANK_BLASTER_3; + + AngleVectors (self->s.angles, forward, right, NULL); + G_ProjectSource (self->s.origin, monster_flash_offset[flash_number], forward, right, start); + + VectorCopy (self->enemy->s.origin, end); + end[2] += self->enemy->viewheight; + VectorSubtract (end, start, dir); + + monster_fire_blaster (self, start, dir, 30, 800, flash_number, EF_BLASTER); +} + +void TankStrike (edict_t *self) +{ + gi.sound (self, CHAN_WEAPON, sound_strike, 1, ATTN_NORM, 0); +} + +void TankRocket (edict_t *self) +{ + vec3_t forward, right; + vec3_t start; + vec3_t dir; + vec3_t vec; + int flash_number; + + if (self->s.frame == FRAME_attak324) + flash_number = MZ2_TANK_ROCKET_1; + else if (self->s.frame == FRAME_attak327) + flash_number = MZ2_TANK_ROCKET_2; + else // (self->s.frame == FRAME_attak330) + flash_number = MZ2_TANK_ROCKET_3; + + AngleVectors (self->s.angles, forward, right, NULL); + G_ProjectSource (self->s.origin, monster_flash_offset[flash_number], forward, right, start); + + VectorCopy (self->enemy->s.origin, vec); + vec[2] += self->enemy->viewheight; + VectorSubtract (vec, start, dir); + VectorNormalize (dir); + + monster_fire_rocket (self, start, dir, 50, 550, flash_number); +} + +void TankMachineGun (edict_t *self) +{ + vec3_t dir; + vec3_t vec; + vec3_t start; + vec3_t forward, right; + int flash_number; + + flash_number = MZ2_TANK_MACHINEGUN_1 + (self->s.frame - FRAME_attak406); + + AngleVectors (self->s.angles, forward, right, NULL); + G_ProjectSource (self->s.origin, monster_flash_offset[flash_number], forward, right, start); + + if (self->enemy) + { + VectorCopy (self->enemy->s.origin, vec); + vec[2] += self->enemy->viewheight; + VectorSubtract (vec, start, vec); + vectoangles (vec, vec); + dir[0] = vec[0]; + } + else + { + dir[0] = 0; + } + if (self->s.frame <= FRAME_attak415) + dir[1] = self->s.angles[1] - 8 * (self->s.frame - FRAME_attak411); + else + dir[1] = self->s.angles[1] + 8 * (self->s.frame - FRAME_attak419); + dir[2] = 0; + + AngleVectors (dir, forward, NULL, NULL); + + monster_fire_bullet (self, start, forward, 20, 4, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, flash_number); +} + + +mframe_t tank_frames_attack_blast [] = +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, -1, NULL, + ai_charge, -2, NULL, + ai_charge, -1, NULL, + ai_charge, -1, NULL, + ai_charge, 0, NULL, + ai_charge, 0, TankBlaster, // 10 + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, TankBlaster, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, TankBlaster // 16 +}; +mmove_t tank_move_attack_blast = {FRAME_attak101, FRAME_attak116, tank_frames_attack_blast, tank_reattack_blaster}; + +mframe_t tank_frames_reattack_blast [] = +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, TankBlaster, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, TankBlaster // 16 +}; +mmove_t tank_move_reattack_blast = {FRAME_attak111, FRAME_attak116, tank_frames_reattack_blast, tank_reattack_blaster}; + +mframe_t tank_frames_attack_post_blast [] = +{ + ai_move, 0, NULL, // 17 + ai_move, 0, NULL, + ai_move, 2, NULL, + ai_move, 3, NULL, + ai_move, 2, NULL, + ai_move, -2, tank_footstep // 22 +}; +mmove_t tank_move_attack_post_blast = {FRAME_attak117, FRAME_attak122, tank_frames_attack_post_blast, tank_run}; + +void tank_reattack_blaster (edict_t *self) +{ + if (skill->value >= 2) + if (visible (self, self->enemy)) + if (self->enemy->health > 0) + if (random() <= 0.6) + { + self->monsterinfo.currentmove = &tank_move_reattack_blast; + return; + } + self->monsterinfo.currentmove = &tank_move_attack_post_blast; +} + + +void tank_poststrike (edict_t *self) +{ + self->enemy = NULL; + tank_run (self); +} + +mframe_t tank_frames_attack_strike [] = +{ + ai_move, 3, NULL, + ai_move, 2, NULL, + ai_move, 2, NULL, + ai_move, 1, NULL, + ai_move, 6, NULL, + ai_move, 7, NULL, + ai_move, 9, tank_footstep, + ai_move, 2, NULL, + ai_move, 1, NULL, + ai_move, 2, NULL, + ai_move, 2, tank_footstep, + ai_move, 2, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, -2, NULL, + ai_move, -2, NULL, + ai_move, 0, tank_windup, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, TankStrike, + ai_move, 0, NULL, + ai_move, -1, NULL, + ai_move, -1, NULL, + ai_move, -1, NULL, + ai_move, -1, NULL, + ai_move, -1, NULL, + ai_move, -3, NULL, + ai_move, -10, NULL, + ai_move, -10, NULL, + ai_move, -2, NULL, + ai_move, -3, NULL, + ai_move, -2, tank_footstep +}; +mmove_t tank_move_attack_strike = {FRAME_attak201, FRAME_attak238, tank_frames_attack_strike, tank_poststrike}; + +mframe_t tank_frames_attack_pre_rocket [] = +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, // 10 + + ai_charge, 0, NULL, + ai_charge, 1, NULL, + ai_charge, 2, NULL, + ai_charge, 7, NULL, + ai_charge, 7, NULL, + ai_charge, 7, tank_footstep, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, // 20 + + ai_charge, -3, NULL +}; +mmove_t tank_move_attack_pre_rocket = {FRAME_attak301, FRAME_attak321, tank_frames_attack_pre_rocket, tank_doattack_rocket}; + +mframe_t tank_frames_attack_fire_rocket [] = +{ + ai_charge, -3, NULL, // Loop Start 22 + ai_charge, 0, NULL, + ai_charge, 0, TankRocket, // 24 + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, TankRocket, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, -1, TankRocket // 30 Loop End +}; +mmove_t tank_move_attack_fire_rocket = {FRAME_attak322, FRAME_attak330, tank_frames_attack_fire_rocket, tank_refire_rocket}; + +mframe_t tank_frames_attack_post_rocket [] = +{ + ai_charge, 0, NULL, // 31 + ai_charge, -1, NULL, + ai_charge, -1, NULL, + ai_charge, 0, NULL, + ai_charge, 2, NULL, + ai_charge, 3, NULL, + ai_charge, 4, NULL, + ai_charge, 2, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, // 40 + + ai_charge, 0, NULL, + ai_charge, -9, NULL, + ai_charge, -8, NULL, + ai_charge, -7, NULL, + ai_charge, -1, NULL, + ai_charge, -1, tank_footstep, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, // 50 + + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL +}; +mmove_t tank_move_attack_post_rocket = {FRAME_attak331, FRAME_attak353, tank_frames_attack_post_rocket, tank_run}; + +mframe_t tank_frames_attack_chain [] = +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + NULL, 0, TankMachineGun, + NULL, 0, TankMachineGun, + NULL, 0, TankMachineGun, + NULL, 0, TankMachineGun, + NULL, 0, TankMachineGun, + NULL, 0, TankMachineGun, + NULL, 0, TankMachineGun, + NULL, 0, TankMachineGun, + NULL, 0, TankMachineGun, + NULL, 0, TankMachineGun, + NULL, 0, TankMachineGun, + NULL, 0, TankMachineGun, + NULL, 0, TankMachineGun, + NULL, 0, TankMachineGun, + NULL, 0, TankMachineGun, + NULL, 0, TankMachineGun, + NULL, 0, TankMachineGun, + NULL, 0, TankMachineGun, + NULL, 0, TankMachineGun, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL +}; +mmove_t tank_move_attack_chain = {FRAME_attak401, FRAME_attak429, tank_frames_attack_chain, tank_run}; + +void tank_refire_rocket (edict_t *self) +{ + // Only on hard or nightmare + if ( skill->value >= 2 ) + if (self->enemy->health > 0) + if (visible(self, self->enemy) ) + if (random() <= 0.4) + { + self->monsterinfo.currentmove = &tank_move_attack_fire_rocket; + return; + } + self->monsterinfo.currentmove = &tank_move_attack_post_rocket; +} + +void tank_doattack_rocket (edict_t *self) +{ + self->monsterinfo.currentmove = &tank_move_attack_fire_rocket; +} + +void tank_attack(edict_t *self) +{ + vec3_t vec; + float range; + float r; + + if (self->enemy->health < 0) + { + self->monsterinfo.currentmove = &tank_move_attack_strike; + self->monsterinfo.aiflags &= ~AI_BRUTAL; + return; + } + + VectorSubtract (self->enemy->s.origin, self->s.origin, vec); + range = VectorLength (vec); + + r = random(); + + if (range <= 125) + { + if (r < 0.4) + self->monsterinfo.currentmove = &tank_move_attack_chain; + else + self->monsterinfo.currentmove = &tank_move_attack_blast; + } + else if (range <= 250) + { + if (r < 0.5) + self->monsterinfo.currentmove = &tank_move_attack_chain; + else + self->monsterinfo.currentmove = &tank_move_attack_blast; + } + else + { + if (r < 0.33) + self->monsterinfo.currentmove = &tank_move_attack_chain; + else if (r < 0.66) + { + self->monsterinfo.currentmove = &tank_move_attack_pre_rocket; + self->pain_debounce_time = level.time + 5.0; // no pain for a while + } + else + self->monsterinfo.currentmove = &tank_move_attack_blast; + } +} + + +// +// death +// + +void tank_dead (edict_t *self) +{ + VectorSet (self->mins, -16, -16, -16); + VectorSet (self->maxs, 16, 16, -0); + self->movetype = MOVETYPE_TOSS; + self->svflags |= SVF_DEADMONSTER; + self->nextthink = 0; + gi.linkentity (self); +} + +mframe_t tank_frames_death1 [] = +{ + ai_move, -7, NULL, + ai_move, -2, NULL, + ai_move, -2, NULL, + ai_move, 1, NULL, + ai_move, 3, NULL, + ai_move, 6, NULL, + ai_move, 1, NULL, + ai_move, 1, NULL, + ai_move, 2, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, -2, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, -3, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, -4, NULL, + ai_move, -6, NULL, + ai_move, -4, NULL, + ai_move, -5, NULL, + ai_move, -7, NULL, + ai_move, -15, tank_thud, + ai_move, -5, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t tank_move_death = {FRAME_death101, FRAME_death132, tank_frames_death1, tank_dead}; + +void tank_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + int n; + +// check for gib + if (self->health <= self->gib_health) + { + gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0); + for (n= 0; n < 1 /*4*/; n++) + ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); + for (n= 0; n < 4; n++) + ThrowGib (self, "models/objects/gibs/sm_metal/tris.md2", damage, GIB_METALLIC); + ThrowGib (self, "models/objects/gibs/chest/tris.md2", damage, GIB_ORGANIC); + ThrowHead (self, "models/objects/gibs/gear/tris.md2", damage, GIB_METALLIC); + self->deadflag = DEAD_DEAD; + return; + } + + if (self->deadflag == DEAD_DEAD) + return; + +// regular death + gi.sound (self, CHAN_VOICE, sound_die, 1, ATTN_NORM, 0); + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_YES; + + self->monsterinfo.currentmove = &tank_move_death; + +} + + +// +// monster_tank +// + +/*QUAKED monster_tank (1 .5 0) (-32 -32 -16) (32 32 72) Ambush Trigger_Spawn Sight +*/ +/*QUAKED monster_tank_commander (1 .5 0) (-32 -32 -16) (32 32 72) Ambush Trigger_Spawn Sight +*/ +void SP_monster_tank (edict_t *self) +{ + if (deathmatch->value) + { + G_FreeEdict (self); + return; + } + + self->s.modelindex = gi.modelindex ("models/monsters/tank/tris.md2"); + VectorSet (self->mins, -32, -32, -16); + VectorSet (self->maxs, 32, 32, 72); + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + + sound_pain = gi.soundindex ("tank/tnkpain2.wav"); + sound_thud = gi.soundindex ("tank/tnkdeth2.wav"); + sound_idle = gi.soundindex ("tank/tnkidle1.wav"); + sound_die = gi.soundindex ("tank/death.wav"); + sound_step = gi.soundindex ("tank/step.wav"); + sound_windup = gi.soundindex ("tank/tnkatck4.wav"); + sound_strike = gi.soundindex ("tank/tnkatck5.wav"); + sound_sight = gi.soundindex ("tank/sight1.wav"); + + gi.soundindex ("tank/tnkatck1.wav"); + gi.soundindex ("tank/tnkatk2a.wav"); + gi.soundindex ("tank/tnkatk2b.wav"); + gi.soundindex ("tank/tnkatk2c.wav"); + gi.soundindex ("tank/tnkatk2d.wav"); + gi.soundindex ("tank/tnkatk2e.wav"); + gi.soundindex ("tank/tnkatck3.wav"); + + if (strcmp(self->classname, "monster_tank_commander") == 0) + { + self->health = 1000; + self->gib_health = -225; + } + else + { + self->health = 750; + self->gib_health = -200; + } + + self->mass = 500; + + self->pain = tank_pain; + self->die = tank_die; + self->monsterinfo.stand = tank_stand; + self->monsterinfo.walk = tank_walk; + self->monsterinfo.run = tank_run; + self->monsterinfo.dodge = NULL; + self->monsterinfo.attack = tank_attack; + self->monsterinfo.melee = NULL; + self->monsterinfo.sight = tank_sight; + self->monsterinfo.idle = tank_idle; + + gi.linkentity (self); + + self->monsterinfo.currentmove = &tank_move_stand; + self->monsterinfo.scale = MODEL_SCALE; + + walkmonster_start(self); + + if (strcmp(self->classname, "monster_tank_commander") == 0) + self->s.skinnum = 2; +} diff --git a/original/baseq2/m_tank.h b/original/baseq2/m_tank.h new file mode 100644 index 0000000..284a5be --- /dev/null +++ b/original/baseq2/m_tank.h @@ -0,0 +1,319 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +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. + +*/ +// G:\quake2\baseq2\models/monsters/tank + +// This file generated by qdata - Do NOT Modify + +#define FRAME_stand01 0 +#define FRAME_stand02 1 +#define FRAME_stand03 2 +#define FRAME_stand04 3 +#define FRAME_stand05 4 +#define FRAME_stand06 5 +#define FRAME_stand07 6 +#define FRAME_stand08 7 +#define FRAME_stand09 8 +#define FRAME_stand10 9 +#define FRAME_stand11 10 +#define FRAME_stand12 11 +#define FRAME_stand13 12 +#define FRAME_stand14 13 +#define FRAME_stand15 14 +#define FRAME_stand16 15 +#define FRAME_stand17 16 +#define FRAME_stand18 17 +#define FRAME_stand19 18 +#define FRAME_stand20 19 +#define FRAME_stand21 20 +#define FRAME_stand22 21 +#define FRAME_stand23 22 +#define FRAME_stand24 23 +#define FRAME_stand25 24 +#define FRAME_stand26 25 +#define FRAME_stand27 26 +#define FRAME_stand28 27 +#define FRAME_stand29 28 +#define FRAME_stand30 29 +#define FRAME_walk01 30 +#define FRAME_walk02 31 +#define FRAME_walk03 32 +#define FRAME_walk04 33 +#define FRAME_walk05 34 +#define FRAME_walk06 35 +#define FRAME_walk07 36 +#define FRAME_walk08 37 +#define FRAME_walk09 38 +#define FRAME_walk10 39 +#define FRAME_walk11 40 +#define FRAME_walk12 41 +#define FRAME_walk13 42 +#define FRAME_walk14 43 +#define FRAME_walk15 44 +#define FRAME_walk16 45 +#define FRAME_walk17 46 +#define FRAME_walk18 47 +#define FRAME_walk19 48 +#define FRAME_walk20 49 +#define FRAME_walk21 50 +#define FRAME_walk22 51 +#define FRAME_walk23 52 +#define FRAME_walk24 53 +#define FRAME_walk25 54 +#define FRAME_attak101 55 +#define FRAME_attak102 56 +#define FRAME_attak103 57 +#define FRAME_attak104 58 +#define FRAME_attak105 59 +#define FRAME_attak106 60 +#define FRAME_attak107 61 +#define FRAME_attak108 62 +#define FRAME_attak109 63 +#define FRAME_attak110 64 +#define FRAME_attak111 65 +#define FRAME_attak112 66 +#define FRAME_attak113 67 +#define FRAME_attak114 68 +#define FRAME_attak115 69 +#define FRAME_attak116 70 +#define FRAME_attak117 71 +#define FRAME_attak118 72 +#define FRAME_attak119 73 +#define FRAME_attak120 74 +#define FRAME_attak121 75 +#define FRAME_attak122 76 +#define FRAME_attak201 77 +#define FRAME_attak202 78 +#define FRAME_attak203 79 +#define FRAME_attak204 80 +#define FRAME_attak205 81 +#define FRAME_attak206 82 +#define FRAME_attak207 83 +#define FRAME_attak208 84 +#define FRAME_attak209 85 +#define FRAME_attak210 86 +#define FRAME_attak211 87 +#define FRAME_attak212 88 +#define FRAME_attak213 89 +#define FRAME_attak214 90 +#define FRAME_attak215 91 +#define FRAME_attak216 92 +#define FRAME_attak217 93 +#define FRAME_attak218 94 +#define FRAME_attak219 95 +#define FRAME_attak220 96 +#define FRAME_attak221 97 +#define FRAME_attak222 98 +#define FRAME_attak223 99 +#define FRAME_attak224 100 +#define FRAME_attak225 101 +#define FRAME_attak226 102 +#define FRAME_attak227 103 +#define FRAME_attak228 104 +#define FRAME_attak229 105 +#define FRAME_attak230 106 +#define FRAME_attak231 107 +#define FRAME_attak232 108 +#define FRAME_attak233 109 +#define FRAME_attak234 110 +#define FRAME_attak235 111 +#define FRAME_attak236 112 +#define FRAME_attak237 113 +#define FRAME_attak238 114 +#define FRAME_attak301 115 +#define FRAME_attak302 116 +#define FRAME_attak303 117 +#define FRAME_attak304 118 +#define FRAME_attak305 119 +#define FRAME_attak306 120 +#define FRAME_attak307 121 +#define FRAME_attak308 122 +#define FRAME_attak309 123 +#define FRAME_attak310 124 +#define FRAME_attak311 125 +#define FRAME_attak312 126 +#define FRAME_attak313 127 +#define FRAME_attak314 128 +#define FRAME_attak315 129 +#define FRAME_attak316 130 +#define FRAME_attak317 131 +#define FRAME_attak318 132 +#define FRAME_attak319 133 +#define FRAME_attak320 134 +#define FRAME_attak321 135 +#define FRAME_attak322 136 +#define FRAME_attak323 137 +#define FRAME_attak324 138 +#define FRAME_attak325 139 +#define FRAME_attak326 140 +#define FRAME_attak327 141 +#define FRAME_attak328 142 +#define FRAME_attak329 143 +#define FRAME_attak330 144 +#define FRAME_attak331 145 +#define FRAME_attak332 146 +#define FRAME_attak333 147 +#define FRAME_attak334 148 +#define FRAME_attak335 149 +#define FRAME_attak336 150 +#define FRAME_attak337 151 +#define FRAME_attak338 152 +#define FRAME_attak339 153 +#define FRAME_attak340 154 +#define FRAME_attak341 155 +#define FRAME_attak342 156 +#define FRAME_attak343 157 +#define FRAME_attak344 158 +#define FRAME_attak345 159 +#define FRAME_attak346 160 +#define FRAME_attak347 161 +#define FRAME_attak348 162 +#define FRAME_attak349 163 +#define FRAME_attak350 164 +#define FRAME_attak351 165 +#define FRAME_attak352 166 +#define FRAME_attak353 167 +#define FRAME_attak401 168 +#define FRAME_attak402 169 +#define FRAME_attak403 170 +#define FRAME_attak404 171 +#define FRAME_attak405 172 +#define FRAME_attak406 173 +#define FRAME_attak407 174 +#define FRAME_attak408 175 +#define FRAME_attak409 176 +#define FRAME_attak410 177 +#define FRAME_attak411 178 +#define FRAME_attak412 179 +#define FRAME_attak413 180 +#define FRAME_attak414 181 +#define FRAME_attak415 182 +#define FRAME_attak416 183 +#define FRAME_attak417 184 +#define FRAME_attak418 185 +#define FRAME_attak419 186 +#define FRAME_attak420 187 +#define FRAME_attak421 188 +#define FRAME_attak422 189 +#define FRAME_attak423 190 +#define FRAME_attak424 191 +#define FRAME_attak425 192 +#define FRAME_attak426 193 +#define FRAME_attak427 194 +#define FRAME_attak428 195 +#define FRAME_attak429 196 +#define FRAME_pain101 197 +#define FRAME_pain102 198 +#define FRAME_pain103 199 +#define FRAME_pain104 200 +#define FRAME_pain201 201 +#define FRAME_pain202 202 +#define FRAME_pain203 203 +#define FRAME_pain204 204 +#define FRAME_pain205 205 +#define FRAME_pain301 206 +#define FRAME_pain302 207 +#define FRAME_pain303 208 +#define FRAME_pain304 209 +#define FRAME_pain305 210 +#define FRAME_pain306 211 +#define FRAME_pain307 212 +#define FRAME_pain308 213 +#define FRAME_pain309 214 +#define FRAME_pain310 215 +#define FRAME_pain311 216 +#define FRAME_pain312 217 +#define FRAME_pain313 218 +#define FRAME_pain314 219 +#define FRAME_pain315 220 +#define FRAME_pain316 221 +#define FRAME_death101 222 +#define FRAME_death102 223 +#define FRAME_death103 224 +#define FRAME_death104 225 +#define FRAME_death105 226 +#define FRAME_death106 227 +#define FRAME_death107 228 +#define FRAME_death108 229 +#define FRAME_death109 230 +#define FRAME_death110 231 +#define FRAME_death111 232 +#define FRAME_death112 233 +#define FRAME_death113 234 +#define FRAME_death114 235 +#define FRAME_death115 236 +#define FRAME_death116 237 +#define FRAME_death117 238 +#define FRAME_death118 239 +#define FRAME_death119 240 +#define FRAME_death120 241 +#define FRAME_death121 242 +#define FRAME_death122 243 +#define FRAME_death123 244 +#define FRAME_death124 245 +#define FRAME_death125 246 +#define FRAME_death126 247 +#define FRAME_death127 248 +#define FRAME_death128 249 +#define FRAME_death129 250 +#define FRAME_death130 251 +#define FRAME_death131 252 +#define FRAME_death132 253 +#define FRAME_recln101 254 +#define FRAME_recln102 255 +#define FRAME_recln103 256 +#define FRAME_recln104 257 +#define FRAME_recln105 258 +#define FRAME_recln106 259 +#define FRAME_recln107 260 +#define FRAME_recln108 261 +#define FRAME_recln109 262 +#define FRAME_recln110 263 +#define FRAME_recln111 264 +#define FRAME_recln112 265 +#define FRAME_recln113 266 +#define FRAME_recln114 267 +#define FRAME_recln115 268 +#define FRAME_recln116 269 +#define FRAME_recln117 270 +#define FRAME_recln118 271 +#define FRAME_recln119 272 +#define FRAME_recln120 273 +#define FRAME_recln121 274 +#define FRAME_recln122 275 +#define FRAME_recln123 276 +#define FRAME_recln124 277 +#define FRAME_recln125 278 +#define FRAME_recln126 279 +#define FRAME_recln127 280 +#define FRAME_recln128 281 +#define FRAME_recln129 282 +#define FRAME_recln130 283 +#define FRAME_recln131 284 +#define FRAME_recln132 285 +#define FRAME_recln133 286 +#define FRAME_recln134 287 +#define FRAME_recln135 288 +#define FRAME_recln136 289 +#define FRAME_recln137 290 +#define FRAME_recln138 291 +#define FRAME_recln139 292 +#define FRAME_recln140 293 + +#define MODEL_SCALE 1.000000 diff --git a/original/baseq2/p_client.c b/original/baseq2/p_client.c new file mode 100644 index 0000000..32b8a1c --- /dev/null +++ b/original/baseq2/p_client.c @@ -0,0 +1,1815 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +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. + +*/ +#include "g_local.h" +#include "m_player.h" + +void ClientUserinfoChanged (edict_t *ent, char *userinfo); + +void SP_misc_teleporter_dest (edict_t *ent); + +// +// Gross, ugly, disgustuing hack section +// + +// this function is an ugly as hell hack to fix some map flaws +// +// the coop spawn spots on some maps are SNAFU. There are coop spots +// with the wrong targetname as well as spots with no name at all +// +// we use carnal knowledge of the maps to fix the coop spot targetnames to match +// that of the nearest named single player spot + +static void SP_FixCoopSpots (edict_t *self) +{ + edict_t *spot; + vec3_t d; + + spot = NULL; + + while(1) + { + spot = G_Find(spot, FOFS(classname), "info_player_start"); + if (!spot) + return; + if (!spot->targetname) + continue; + VectorSubtract(self->s.origin, spot->s.origin, d); + if (VectorLength(d) < 384) + { + if ((!self->targetname) || Q_stricmp(self->targetname, spot->targetname) != 0) + { +// gi.dprintf("FixCoopSpots changed %s at %s targetname from %s to %s\n", self->classname, vtos(self->s.origin), self->targetname, spot->targetname); + self->targetname = spot->targetname; + } + return; + } + } +} + +// now if that one wasn't ugly enough for you then try this one on for size +// some maps don't have any coop spots at all, so we need to create them +// where they should have been + +static void SP_CreateCoopSpots (edict_t *self) +{ + edict_t *spot; + + if(Q_stricmp(level.mapname, "security") == 0) + { + spot = G_Spawn(); + spot->classname = "info_player_coop"; + spot->s.origin[0] = 188 - 64; + spot->s.origin[1] = -164; + spot->s.origin[2] = 80; + spot->targetname = "jail3"; + spot->s.angles[1] = 90; + + spot = G_Spawn(); + spot->classname = "info_player_coop"; + spot->s.origin[0] = 188 + 64; + spot->s.origin[1] = -164; + spot->s.origin[2] = 80; + spot->targetname = "jail3"; + spot->s.angles[1] = 90; + + spot = G_Spawn(); + spot->classname = "info_player_coop"; + spot->s.origin[0] = 188 + 128; + spot->s.origin[1] = -164; + spot->s.origin[2] = 80; + spot->targetname = "jail3"; + spot->s.angles[1] = 90; + + return; + } +} + + +/*QUAKED info_player_start (1 0 0) (-16 -16 -24) (16 16 32) +The normal starting point for a level. +*/ +void SP_info_player_start(edict_t *self) +{ + if (!coop->value) + return; + if(Q_stricmp(level.mapname, "security") == 0) + { + // invoke one of our gross, ugly, disgusting hacks + self->think = SP_CreateCoopSpots; + self->nextthink = level.time + FRAMETIME; + } +} + +/*QUAKED info_player_deathmatch (1 0 1) (-16 -16 -24) (16 16 32) +potential spawning position for deathmatch games +*/ +void SP_info_player_deathmatch(edict_t *self) +{ + if (!deathmatch->value) + { + G_FreeEdict (self); + return; + } + SP_misc_teleporter_dest (self); +} + +/*QUAKED info_player_coop (1 0 1) (-16 -16 -24) (16 16 32) +potential spawning position for coop games +*/ + +void SP_info_player_coop(edict_t *self) +{ + if (!coop->value) + { + G_FreeEdict (self); + return; + } + + if((Q_stricmp(level.mapname, "jail2") == 0) || + (Q_stricmp(level.mapname, "jail4") == 0) || + (Q_stricmp(level.mapname, "mine1") == 0) || + (Q_stricmp(level.mapname, "mine2") == 0) || + (Q_stricmp(level.mapname, "mine3") == 0) || + (Q_stricmp(level.mapname, "mine4") == 0) || + (Q_stricmp(level.mapname, "lab") == 0) || + (Q_stricmp(level.mapname, "boss1") == 0) || + (Q_stricmp(level.mapname, "fact3") == 0) || + (Q_stricmp(level.mapname, "biggun") == 0) || + (Q_stricmp(level.mapname, "space") == 0) || + (Q_stricmp(level.mapname, "command") == 0) || + (Q_stricmp(level.mapname, "power2") == 0) || + (Q_stricmp(level.mapname, "strike") == 0)) + { + // invoke one of our gross, ugly, disgusting hacks + self->think = SP_FixCoopSpots; + self->nextthink = level.time + FRAMETIME; + } +} + + +/*QUAKED info_player_intermission (1 0 1) (-16 -16 -24) (16 16 32) +The deathmatch intermission point will be at one of these +Use 'angles' instead of 'angle', so you can set pitch or roll as well as yaw. 'pitch yaw roll' +*/ +void SP_info_player_intermission(void) +{ +} + + +//======================================================================= + + +void player_pain (edict_t *self, edict_t *other, float kick, int damage) +{ + // player pain is handled at the end of the frame in P_DamageFeedback +} + + +qboolean IsFemale (edict_t *ent) +{ + char *info; + + if (!ent->client) + return false; + + info = Info_ValueForKey (ent->client->pers.userinfo, "gender"); + if (info[0] == 'f' || info[0] == 'F') + return true; + return false; +} + +qboolean IsNeutral (edict_t *ent) +{ + char *info; + + if (!ent->client) + return false; + + info = Info_ValueForKey (ent->client->pers.userinfo, "gender"); + if (info[0] != 'f' && info[0] != 'F' && info[0] != 'm' && info[0] != 'M') + return true; + return false; +} + +void ClientObituary (edict_t *self, edict_t *inflictor, edict_t *attacker) +{ + int mod; + char *message; + char *message2; + qboolean ff; + + if (coop->value && attacker->client) + meansOfDeath |= MOD_FRIENDLY_FIRE; + + if (deathmatch->value || coop->value) + { + ff = meansOfDeath & MOD_FRIENDLY_FIRE; + mod = meansOfDeath & ~MOD_FRIENDLY_FIRE; + message = NULL; + message2 = ""; + + switch (mod) + { + case MOD_SUICIDE: + message = "suicides"; + break; + case MOD_FALLING: + message = "cratered"; + break; + case MOD_CRUSH: + message = "was squished"; + break; + case MOD_WATER: + message = "sank like a rock"; + break; + case MOD_SLIME: + message = "melted"; + break; + case MOD_LAVA: + message = "does a back flip into the lava"; + break; + case MOD_EXPLOSIVE: + case MOD_BARREL: + message = "blew up"; + break; + case MOD_EXIT: + message = "found a way out"; + break; + case MOD_TARGET_LASER: + message = "saw the light"; + break; + case MOD_TARGET_BLASTER: + message = "got blasted"; + break; + case MOD_BOMB: + case MOD_SPLASH: + case MOD_TRIGGER_HURT: + message = "was in the wrong place"; + break; + } + if (attacker == self) + { + switch (mod) + { + case MOD_HELD_GRENADE: + message = "tried to put the pin back in"; + break; + case MOD_HG_SPLASH: + case MOD_G_SPLASH: + if (IsNeutral(self)) + message = "tripped on its own grenade"; + else if (IsFemale(self)) + message = "tripped on her own grenade"; + else + message = "tripped on his own grenade"; + break; + case MOD_R_SPLASH: + if (IsNeutral(self)) + message = "blew itself up"; + else if (IsFemale(self)) + message = "blew herself up"; + else + message = "blew himself up"; + break; + case MOD_BFG_BLAST: + message = "should have used a smaller gun"; + break; + default: + if (IsNeutral(self)) + message = "killed itself"; + else if (IsFemale(self)) + message = "killed herself"; + else + message = "killed himself"; + break; + } + } + if (message) + { + gi.bprintf (PRINT_MEDIUM, "%s %s.\n", self->client->pers.netname, message); + if (deathmatch->value) + self->client->resp.score--; + self->enemy = NULL; + return; + } + + self->enemy = attacker; + if (attacker && attacker->client) + { + switch (mod) + { + case MOD_BLASTER: + message = "was blasted by"; + break; + case MOD_SHOTGUN: + message = "was gunned down by"; + break; + case MOD_SSHOTGUN: + message = "was blown away by"; + message2 = "'s super shotgun"; + break; + case MOD_MACHINEGUN: + message = "was machinegunned by"; + break; + case MOD_CHAINGUN: + message = "was cut in half by"; + message2 = "'s chaingun"; + break; + case MOD_GRENADE: + message = "was popped by"; + message2 = "'s grenade"; + break; + case MOD_G_SPLASH: + message = "was shredded by"; + message2 = "'s shrapnel"; + break; + case MOD_ROCKET: + message = "ate"; + message2 = "'s rocket"; + break; + case MOD_R_SPLASH: + message = "almost dodged"; + message2 = "'s rocket"; + break; + case MOD_HYPERBLASTER: + message = "was melted by"; + message2 = "'s hyperblaster"; + break; + case MOD_RAILGUN: + message = "was railed by"; + break; + case MOD_BFG_LASER: + message = "saw the pretty lights from"; + message2 = "'s BFG"; + break; + case MOD_BFG_BLAST: + message = "was disintegrated by"; + message2 = "'s BFG blast"; + break; + case MOD_BFG_EFFECT: + message = "couldn't hide from"; + message2 = "'s BFG"; + break; + case MOD_HANDGRENADE: + message = "caught"; + message2 = "'s handgrenade"; + break; + case MOD_HG_SPLASH: + message = "didn't see"; + message2 = "'s handgrenade"; + break; + case MOD_HELD_GRENADE: + message = "feels"; + message2 = "'s pain"; + break; + case MOD_TELEFRAG: + message = "tried to invade"; + message2 = "'s personal space"; + break; + } + if (message) + { + gi.bprintf (PRINT_MEDIUM,"%s %s %s%s\n", self->client->pers.netname, message, attacker->client->pers.netname, message2); + if (deathmatch->value) + { + if (ff) + attacker->client->resp.score--; + else + attacker->client->resp.score++; + } + return; + } + } + } + + gi.bprintf (PRINT_MEDIUM,"%s died.\n", self->client->pers.netname); + if (deathmatch->value) + self->client->resp.score--; +} + + +void Touch_Item (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf); + +void TossClientWeapon (edict_t *self) +{ + gitem_t *item; + edict_t *drop; + qboolean quad; + float spread; + + if (!deathmatch->value) + return; + + item = self->client->pers.weapon; + if (! self->client->pers.inventory[self->client->ammo_index] ) + item = NULL; + if (item && (strcmp (item->pickup_name, "Blaster") == 0)) + item = NULL; + + if (!((int)(dmflags->value) & DF_QUAD_DROP)) + quad = false; + else + quad = (self->client->quad_framenum > (level.framenum + 10)); + + if (item && quad) + spread = 22.5; + else + spread = 0.0; + + if (item) + { + self->client->v_angle[YAW] -= spread; + drop = Drop_Item (self, item); + self->client->v_angle[YAW] += spread; + drop->spawnflags = DROPPED_PLAYER_ITEM; + } + + if (quad) + { + self->client->v_angle[YAW] += spread; + drop = Drop_Item (self, FindItemByClassname ("item_quad")); + self->client->v_angle[YAW] -= spread; + drop->spawnflags |= DROPPED_PLAYER_ITEM; + + drop->touch = Touch_Item; + drop->nextthink = level.time + (self->client->quad_framenum - level.framenum) * FRAMETIME; + drop->think = G_FreeEdict; + } +} + + +/* +================== +LookAtKiller +================== +*/ +void LookAtKiller (edict_t *self, edict_t *inflictor, edict_t *attacker) +{ + vec3_t dir; + + if (attacker && attacker != world && attacker != self) + { + VectorSubtract (attacker->s.origin, self->s.origin, dir); + } + else if (inflictor && inflictor != world && inflictor != self) + { + VectorSubtract (inflictor->s.origin, self->s.origin, dir); + } + else + { + self->client->killer_yaw = self->s.angles[YAW]; + return; + } + + if (dir[0]) + self->client->killer_yaw = 180/M_PI*atan2(dir[1], dir[0]); + else { + self->client->killer_yaw = 0; + if (dir[1] > 0) + self->client->killer_yaw = 90; + else if (dir[1] < 0) + self->client->killer_yaw = -90; + } + if (self->client->killer_yaw < 0) + self->client->killer_yaw += 360; + + +} + +/* +================== +player_die +================== +*/ +void player_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + int n; + + VectorClear (self->avelocity); + + self->takedamage = DAMAGE_YES; + self->movetype = MOVETYPE_TOSS; + + self->s.modelindex2 = 0; // remove linked weapon model + + self->s.angles[0] = 0; + self->s.angles[2] = 0; + + self->s.sound = 0; + self->client->weapon_sound = 0; + + self->maxs[2] = -8; + +// self->solid = SOLID_NOT; + self->svflags |= SVF_DEADMONSTER; + + if (!self->deadflag) + { + self->client->respawn_time = level.time + 1.0; + LookAtKiller (self, inflictor, attacker); + self->client->ps.pmove.pm_type = PM_DEAD; + ClientObituary (self, inflictor, attacker); + TossClientWeapon (self); + if (deathmatch->value) + Cmd_Help_f (self); // show scores + + // clear inventory + // this is kind of ugly, but it's how we want to handle keys in coop + for (n = 0; n < game.num_items; n++) + { + if (coop->value && itemlist[n].flags & IT_KEY) + self->client->resp.coop_respawn.inventory[n] = self->client->pers.inventory[n]; + self->client->pers.inventory[n] = 0; + } + } + + // remove powerups + self->client->quad_framenum = 0; + self->client->invincible_framenum = 0; + self->client->breather_framenum = 0; + self->client->enviro_framenum = 0; + self->flags &= ~FL_POWER_ARMOR; + + if (self->health < -40) + { // gib + gi.sound (self, CHAN_BODY, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0); + for (n= 0; n < 4; n++) + ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); + ThrowClientHead (self, damage); + + self->takedamage = DAMAGE_NO; + } + else + { // normal death + if (!self->deadflag) + { + static int i; + + i = (i+1)%3; + // start a death animation + self->client->anim_priority = ANIM_DEATH; + if (self->client->ps.pmove.pm_flags & PMF_DUCKED) + { + self->s.frame = FRAME_crdeath1-1; + self->client->anim_end = FRAME_crdeath5; + } + else switch (i) + { + case 0: + self->s.frame = FRAME_death101-1; + self->client->anim_end = FRAME_death106; + break; + case 1: + self->s.frame = FRAME_death201-1; + self->client->anim_end = FRAME_death206; + break; + case 2: + self->s.frame = FRAME_death301-1; + self->client->anim_end = FRAME_death308; + break; + } + gi.sound (self, CHAN_VOICE, gi.soundindex(va("*death%i.wav", (rand()%4)+1)), 1, ATTN_NORM, 0); + } + } + + self->deadflag = DEAD_DEAD; + + gi.linkentity (self); +} + +//======================================================================= + +/* +============== +InitClientPersistant + +This is only called when the game first initializes in single player, +but is called after each death and level change in deathmatch +============== +*/ +void InitClientPersistant (gclient_t *client) +{ + gitem_t *item; + + memset (&client->pers, 0, sizeof(client->pers)); + + item = FindItem("Blaster"); + client->pers.selected_item = ITEM_INDEX(item); + client->pers.inventory[client->pers.selected_item] = 1; + + client->pers.weapon = item; + + client->pers.health = 100; + client->pers.max_health = 100; + + client->pers.max_bullets = 200; + client->pers.max_shells = 100; + client->pers.max_rockets = 50; + client->pers.max_grenades = 50; + client->pers.max_cells = 200; + client->pers.max_slugs = 50; + + client->pers.connected = true; +} + + +void InitClientResp (gclient_t *client) +{ + memset (&client->resp, 0, sizeof(client->resp)); + client->resp.enterframe = level.framenum; + client->resp.coop_respawn = client->pers; +} + +/* +================== +SaveClientData + +Some information that should be persistant, like health, +is still stored in the edict structure, so it needs to +be mirrored out to the client structure before all the +edicts are wiped. +================== +*/ +void SaveClientData (void) +{ + int i; + edict_t *ent; + + for (i=0 ; iinuse) + continue; + game.clients[i].pers.health = ent->health; + game.clients[i].pers.max_health = ent->max_health; + game.clients[i].pers.savedFlags = (ent->flags & (FL_GODMODE|FL_NOTARGET|FL_POWER_ARMOR)); + if (coop->value) + game.clients[i].pers.score = ent->client->resp.score; + } +} + +void FetchClientEntData (edict_t *ent) +{ + ent->health = ent->client->pers.health; + ent->max_health = ent->client->pers.max_health; + ent->flags |= ent->client->pers.savedFlags; + if (coop->value) + ent->client->resp.score = ent->client->pers.score; +} + + + +/* +======================================================================= + + SelectSpawnPoint + +======================================================================= +*/ + +/* +================ +PlayersRangeFromSpot + +Returns the distance to the nearest player from the given spot +================ +*/ +float PlayersRangeFromSpot (edict_t *spot) +{ + edict_t *player; + float bestplayerdistance; + vec3_t v; + int n; + float playerdistance; + + + bestplayerdistance = 9999999; + + for (n = 1; n <= maxclients->value; n++) + { + player = &g_edicts[n]; + + if (!player->inuse) + continue; + + if (player->health <= 0) + continue; + + VectorSubtract (spot->s.origin, player->s.origin, v); + playerdistance = VectorLength (v); + + if (playerdistance < bestplayerdistance) + bestplayerdistance = playerdistance; + } + + return bestplayerdistance; +} + +/* +================ +SelectRandomDeathmatchSpawnPoint + +go to a random point, but NOT the two points closest +to other players +================ +*/ +edict_t *SelectRandomDeathmatchSpawnPoint (void) +{ + edict_t *spot, *spot1, *spot2; + int count = 0; + int selection; + float range, range1, range2; + + spot = NULL; + range1 = range2 = 99999; + spot1 = spot2 = NULL; + + while ((spot = G_Find (spot, FOFS(classname), "info_player_deathmatch")) != NULL) + { + count++; + range = PlayersRangeFromSpot(spot); + if (range < range1) + { + range1 = range; + spot1 = spot; + } + else if (range < range2) + { + range2 = range; + spot2 = spot; + } + } + + if (!count) + return NULL; + + if (count <= 2) + { + spot1 = spot2 = NULL; + } + else + count -= 2; + + selection = rand() % count; + + spot = NULL; + do + { + spot = G_Find (spot, FOFS(classname), "info_player_deathmatch"); + if (spot == spot1 || spot == spot2) + selection++; + } while(selection--); + + return spot; +} + +/* +================ +SelectFarthestDeathmatchSpawnPoint + +================ +*/ +edict_t *SelectFarthestDeathmatchSpawnPoint (void) +{ + edict_t *bestspot; + float bestdistance, bestplayerdistance; + edict_t *spot; + + + spot = NULL; + bestspot = NULL; + bestdistance = 0; + while ((spot = G_Find (spot, FOFS(classname), "info_player_deathmatch")) != NULL) + { + bestplayerdistance = PlayersRangeFromSpot (spot); + + if (bestplayerdistance > bestdistance) + { + bestspot = spot; + bestdistance = bestplayerdistance; + } + } + + if (bestspot) + { + return bestspot; + } + + // if there is a player just spawned on each and every start spot + // we have no choice to turn one into a telefrag meltdown + spot = G_Find (NULL, FOFS(classname), "info_player_deathmatch"); + + return spot; +} + +edict_t *SelectDeathmatchSpawnPoint (void) +{ + if ( (int)(dmflags->value) & DF_SPAWN_FARTHEST) + return SelectFarthestDeathmatchSpawnPoint (); + else + return SelectRandomDeathmatchSpawnPoint (); +} + + +edict_t *SelectCoopSpawnPoint (edict_t *ent) +{ + int index; + edict_t *spot = NULL; + char *target; + + index = ent->client - game.clients; + + // player 0 starts in normal player spawn point + if (!index) + return NULL; + + spot = NULL; + + // assume there are four coop spots at each spawnpoint + while (1) + { + spot = G_Find (spot, FOFS(classname), "info_player_coop"); + if (!spot) + return NULL; // we didn't have enough... + + target = spot->targetname; + if (!target) + target = ""; + if ( Q_stricmp(game.spawnpoint, target) == 0 ) + { // this is a coop spawn point for one of the clients here + index--; + if (!index) + return spot; // this is it + } + } + + + return spot; +} + + +/* +=========== +SelectSpawnPoint + +Chooses a player start, deathmatch start, coop start, etc +============ +*/ +void SelectSpawnPoint (edict_t *ent, vec3_t origin, vec3_t angles) +{ + edict_t *spot = NULL; + + if (deathmatch->value) + spot = SelectDeathmatchSpawnPoint (); + else if (coop->value) + spot = SelectCoopSpawnPoint (ent); + + // find a single player start spot + if (!spot) + { + while ((spot = G_Find (spot, FOFS(classname), "info_player_start")) != NULL) + { + if (!game.spawnpoint[0] && !spot->targetname) + break; + + if (!game.spawnpoint[0] || !spot->targetname) + continue; + + if (Q_stricmp(game.spawnpoint, spot->targetname) == 0) + break; + } + + if (!spot) + { + if (!game.spawnpoint[0]) + { // there wasn't a spawnpoint without a target, so use any + spot = G_Find (spot, FOFS(classname), "info_player_start"); + } + if (!spot) + gi.error ("Couldn't find spawn point %s\n", game.spawnpoint); + } + } + + VectorCopy (spot->s.origin, origin); + origin[2] += 9; + VectorCopy (spot->s.angles, angles); +} + +//====================================================================== + + +void InitBodyQue (void) +{ + int i; + edict_t *ent; + + level.body_que = 0; + for (i=0; iclassname = "bodyque"; + } +} + +void body_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + int n; + + if (self->health < -40) + { + gi.sound (self, CHAN_BODY, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0); + for (n= 0; n < 4; n++) + ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); + self->s.origin[2] -= 48; + ThrowClientHead (self, damage); + self->takedamage = DAMAGE_NO; + } +} + +void CopyToBodyQue (edict_t *ent) +{ + edict_t *body; + + // grab a body que and cycle to the next one + body = &g_edicts[(int)maxclients->value + level.body_que + 1]; + level.body_que = (level.body_que + 1) % BODY_QUEUE_SIZE; + + // FIXME: send an effect on the removed body + + gi.unlinkentity (ent); + + gi.unlinkentity (body); + body->s = ent->s; + body->s.number = body - g_edicts; + + body->svflags = ent->svflags; + VectorCopy (ent->mins, body->mins); + VectorCopy (ent->maxs, body->maxs); + VectorCopy (ent->absmin, body->absmin); + VectorCopy (ent->absmax, body->absmax); + VectorCopy (ent->size, body->size); + body->solid = ent->solid; + body->clipmask = ent->clipmask; + body->owner = ent->owner; + body->movetype = ent->movetype; + + body->die = body_die; + body->takedamage = DAMAGE_YES; + + gi.linkentity (body); +} + + +void respawn (edict_t *self) +{ + if (deathmatch->value || coop->value) + { + // spectator's don't leave bodies + if (self->movetype != MOVETYPE_NOCLIP) + CopyToBodyQue (self); + self->svflags &= ~SVF_NOCLIENT; + PutClientInServer (self); + + // add a teleportation effect + self->s.event = EV_PLAYER_TELEPORT; + + // hold in place briefly + self->client->ps.pmove.pm_flags = PMF_TIME_TELEPORT; + self->client->ps.pmove.pm_time = 14; + + self->client->respawn_time = level.time; + + return; + } + + // restart the entire server + gi.AddCommandString ("menu_loadgame\n"); +} + +/* + * only called when pers.spectator changes + * note that resp.spectator should be the opposite of pers.spectator here + */ +void spectator_respawn (edict_t *ent) +{ + int i, numspec; + + // if the user wants to become a spectator, make sure he doesn't + // exceed max_spectators + + if (ent->client->pers.spectator) { + char *value = Info_ValueForKey (ent->client->pers.userinfo, "spectator"); + if (*spectator_password->string && + strcmp(spectator_password->string, "none") && + strcmp(spectator_password->string, value)) { + gi.cprintf(ent, PRINT_HIGH, "Spectator password incorrect.\n"); + ent->client->pers.spectator = false; + gi.WriteByte (svc_stufftext); + gi.WriteString ("spectator 0\n"); + gi.unicast(ent, true); + return; + } + + // count spectators + for (i = 1, numspec = 0; i <= maxclients->value; i++) + if (g_edicts[i].inuse && g_edicts[i].client->pers.spectator) + numspec++; + + if (numspec >= maxspectators->value) { + gi.cprintf(ent, PRINT_HIGH, "Server spectator limit is full."); + ent->client->pers.spectator = false; + // reset his spectator var + gi.WriteByte (svc_stufftext); + gi.WriteString ("spectator 0\n"); + gi.unicast(ent, true); + return; + } + } else { + // he was a spectator and wants to join the game + // he must have the right password + char *value = Info_ValueForKey (ent->client->pers.userinfo, "password"); + if (*password->string && strcmp(password->string, "none") && + strcmp(password->string, value)) { + gi.cprintf(ent, PRINT_HIGH, "Password incorrect.\n"); + ent->client->pers.spectator = true; + gi.WriteByte (svc_stufftext); + gi.WriteString ("spectator 1\n"); + gi.unicast(ent, true); + return; + } + } + + // clear client on respawn + ent->client->resp.score = ent->client->pers.score = 0; + + ent->svflags &= ~SVF_NOCLIENT; + PutClientInServer (ent); + + // add a teleportation effect + if (!ent->client->pers.spectator) { + // send effect + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + gi.WriteByte (MZ_LOGIN); + gi.multicast (ent->s.origin, MULTICAST_PVS); + + // hold in place briefly + ent->client->ps.pmove.pm_flags = PMF_TIME_TELEPORT; + ent->client->ps.pmove.pm_time = 14; + } + + ent->client->respawn_time = level.time; + + if (ent->client->pers.spectator) + gi.bprintf (PRINT_HIGH, "%s has moved to the sidelines\n", ent->client->pers.netname); + else + gi.bprintf (PRINT_HIGH, "%s joined the game\n", ent->client->pers.netname); +} + +//============================================================== + + +/* +=========== +PutClientInServer + +Called when a player connects to a server or respawns in +a deathmatch. +============ +*/ +void PutClientInServer (edict_t *ent) +{ + vec3_t mins = {-16, -16, -24}; + vec3_t maxs = {16, 16, 32}; + int index; + vec3_t spawn_origin, spawn_angles; + gclient_t *client; + int i; + client_persistant_t saved; + client_respawn_t resp; + + // find a spawn point + // do it before setting health back up, so farthest + // ranging doesn't count this client + SelectSpawnPoint (ent, spawn_origin, spawn_angles); + + index = ent-g_edicts-1; + client = ent->client; + + // deathmatch wipes most client data every spawn + if (deathmatch->value) + { + char userinfo[MAX_INFO_STRING]; + + resp = client->resp; + memcpy (userinfo, client->pers.userinfo, sizeof(userinfo)); + InitClientPersistant (client); + ClientUserinfoChanged (ent, userinfo); + } + else if (coop->value) + { +// int n; + char userinfo[MAX_INFO_STRING]; + + resp = client->resp; + memcpy (userinfo, client->pers.userinfo, sizeof(userinfo)); + // this is kind of ugly, but it's how we want to handle keys in coop +// for (n = 0; n < game.num_items; n++) +// { +// if (itemlist[n].flags & IT_KEY) +// resp.coop_respawn.inventory[n] = client->pers.inventory[n]; +// } + resp.coop_respawn.game_helpchanged = client->pers.game_helpchanged; + resp.coop_respawn.helpchanged = client->pers.helpchanged; + client->pers = resp.coop_respawn; + ClientUserinfoChanged (ent, userinfo); + if (resp.score > client->pers.score) + client->pers.score = resp.score; + } + else + { + memset (&resp, 0, sizeof(resp)); + } + + // clear everything but the persistant data + saved = client->pers; + memset (client, 0, sizeof(*client)); + client->pers = saved; + if (client->pers.health <= 0) + InitClientPersistant(client); + client->resp = resp; + + // copy some data from the client to the entity + FetchClientEntData (ent); + + // clear entity values + ent->groundentity = NULL; + ent->client = &game.clients[index]; + ent->takedamage = DAMAGE_AIM; + ent->movetype = MOVETYPE_WALK; + ent->viewheight = 22; + ent->inuse = true; + ent->classname = "player"; + ent->mass = 200; + ent->solid = SOLID_BBOX; + ent->deadflag = DEAD_NO; + ent->air_finished = level.time + 12; + ent->clipmask = MASK_PLAYERSOLID; + ent->model = "players/male/tris.md2"; + ent->pain = player_pain; + ent->die = player_die; + ent->waterlevel = 0; + ent->watertype = 0; + ent->flags &= ~FL_NO_KNOCKBACK; + ent->svflags &= ~SVF_DEADMONSTER; + + VectorCopy (mins, ent->mins); + VectorCopy (maxs, ent->maxs); + VectorClear (ent->velocity); + + // clear playerstate values + memset (&ent->client->ps, 0, sizeof(client->ps)); + + client->ps.pmove.origin[0] = spawn_origin[0]*8; + client->ps.pmove.origin[1] = spawn_origin[1]*8; + client->ps.pmove.origin[2] = spawn_origin[2]*8; + + if (deathmatch->value && ((int)dmflags->value & DF_FIXED_FOV)) + { + client->ps.fov = 90; + } + else + { + client->ps.fov = atoi(Info_ValueForKey(client->pers.userinfo, "fov")); + if (client->ps.fov < 1) + client->ps.fov = 90; + else if (client->ps.fov > 160) + client->ps.fov = 160; + } + + client->ps.gunindex = gi.modelindex(client->pers.weapon->view_model); + + // clear entity state values + ent->s.effects = 0; + ent->s.modelindex = 255; // will use the skin specified model + ent->s.modelindex2 = 255; // custom gun model + // sknum is player num and weapon number + // weapon number will be added in changeweapon + ent->s.skinnum = ent - g_edicts - 1; + + ent->s.frame = 0; + VectorCopy (spawn_origin, ent->s.origin); + ent->s.origin[2] += 1; // make sure off ground + VectorCopy (ent->s.origin, ent->s.old_origin); + + // set the delta angle + for (i=0 ; i<3 ; i++) + { + client->ps.pmove.delta_angles[i] = ANGLE2SHORT(spawn_angles[i] - client->resp.cmd_angles[i]); + } + + ent->s.angles[PITCH] = 0; + ent->s.angles[YAW] = spawn_angles[YAW]; + ent->s.angles[ROLL] = 0; + VectorCopy (ent->s.angles, client->ps.viewangles); + VectorCopy (ent->s.angles, client->v_angle); + + // spawn a spectator + if (client->pers.spectator) { + client->chase_target = NULL; + + client->resp.spectator = true; + + ent->movetype = MOVETYPE_NOCLIP; + ent->solid = SOLID_NOT; + ent->svflags |= SVF_NOCLIENT; + ent->client->ps.gunindex = 0; + gi.linkentity (ent); + return; + } else + client->resp.spectator = false; + + if (!KillBox (ent)) + { // could't spawn in? + } + + gi.linkentity (ent); + + // force the current weapon up + client->newweapon = client->pers.weapon; + ChangeWeapon (ent); +} + +/* +===================== +ClientBeginDeathmatch + +A client has just connected to the server in +deathmatch mode, so clear everything out before starting them. +===================== +*/ +void ClientBeginDeathmatch (edict_t *ent) +{ + G_InitEdict (ent); + + InitClientResp (ent->client); + + // locate ent at a spawn point + PutClientInServer (ent); + + if (level.intermissiontime) + { + MoveClientToIntermission (ent); + } + else + { + // send effect + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + gi.WriteByte (MZ_LOGIN); + gi.multicast (ent->s.origin, MULTICAST_PVS); + } + + gi.bprintf (PRINT_HIGH, "%s entered the game\n", ent->client->pers.netname); + + // make sure all view stuff is valid + ClientEndServerFrame (ent); +} + + +/* +=========== +ClientBegin + +called when a client has finished connecting, and is ready +to be placed into the game. This will happen every level load. +============ +*/ +void ClientBegin (edict_t *ent) +{ + int i; + + ent->client = game.clients + (ent - g_edicts - 1); + + if (deathmatch->value) + { + ClientBeginDeathmatch (ent); + return; + } + + // if there is already a body waiting for us (a loadgame), just + // take it, otherwise spawn one from scratch + if (ent->inuse == true) + { + // the client has cleared the client side viewangles upon + // connecting to the server, which is different than the + // state when the game is saved, so we need to compensate + // with deltaangles + for (i=0 ; i<3 ; i++) + ent->client->ps.pmove.delta_angles[i] = ANGLE2SHORT(ent->client->ps.viewangles[i]); + } + else + { + // a spawn point will completely reinitialize the entity + // except for the persistant data that was initialized at + // ClientConnect() time + G_InitEdict (ent); + ent->classname = "player"; + InitClientResp (ent->client); + PutClientInServer (ent); + } + + if (level.intermissiontime) + { + MoveClientToIntermission (ent); + } + else + { + // send effect if in a multiplayer game + if (game.maxclients > 1) + { + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + gi.WriteByte (MZ_LOGIN); + gi.multicast (ent->s.origin, MULTICAST_PVS); + + gi.bprintf (PRINT_HIGH, "%s entered the game\n", ent->client->pers.netname); + } + } + + // make sure all view stuff is valid + ClientEndServerFrame (ent); +} + +/* +=========== +ClientUserInfoChanged + +called whenever the player updates a userinfo variable. + +The game can override any of the settings in place +(forcing skins or names, etc) before copying it off. +============ +*/ +void ClientUserinfoChanged (edict_t *ent, char *userinfo) +{ + char *s; + int playernum; + + // check for malformed or illegal info strings + if (!Info_Validate(userinfo)) + { + strcpy (userinfo, "\\name\\badinfo\\skin\\male/grunt"); + } + + // set name + s = Info_ValueForKey (userinfo, "name"); + strncpy (ent->client->pers.netname, s, sizeof(ent->client->pers.netname)-1); + + // set spectator + s = Info_ValueForKey (userinfo, "spectator"); + // spectators are only supported in deathmatch + if (deathmatch->value && *s && strcmp(s, "0")) + ent->client->pers.spectator = true; + else + ent->client->pers.spectator = false; + + // set skin + s = Info_ValueForKey (userinfo, "skin"); + + playernum = ent-g_edicts-1; + + // combine name and skin into a configstring + gi.configstring (CS_PLAYERSKINS+playernum, va("%s\\%s", ent->client->pers.netname, s) ); + + // fov + if (deathmatch->value && ((int)dmflags->value & DF_FIXED_FOV)) + { + ent->client->ps.fov = 90; + } + else + { + ent->client->ps.fov = atoi(Info_ValueForKey(userinfo, "fov")); + if (ent->client->ps.fov < 1) + ent->client->ps.fov = 90; + else if (ent->client->ps.fov > 160) + ent->client->ps.fov = 160; + } + + // handedness + s = Info_ValueForKey (userinfo, "hand"); + if (strlen(s)) + { + ent->client->pers.hand = atoi(s); + } + + // save off the userinfo in case we want to check something later + strncpy (ent->client->pers.userinfo, userinfo, sizeof(ent->client->pers.userinfo)-1); +} + + +/* +=========== +ClientConnect + +Called when a player begins connecting to the server. +The game can refuse entrance to a client by returning false. +If the client is allowed, the connection process will continue +and eventually get to ClientBegin() +Changing levels will NOT cause this to be called again, but +loadgames will. +============ +*/ +qboolean ClientConnect (edict_t *ent, char *userinfo) +{ + char *value; + + // check to see if they are on the banned IP list + value = Info_ValueForKey (userinfo, "ip"); + if (SV_FilterPacket(value)) { + Info_SetValueForKey(userinfo, "rejmsg", "Banned."); + return false; + } + + // check for a spectator + value = Info_ValueForKey (userinfo, "spectator"); + if (deathmatch->value && *value && strcmp(value, "0")) { + int i, numspec; + + if (*spectator_password->string && + strcmp(spectator_password->string, "none") && + strcmp(spectator_password->string, value)) { + Info_SetValueForKey(userinfo, "rejmsg", "Spectator password required or incorrect."); + return false; + } + + // count spectators + for (i = numspec = 0; i < maxclients->value; i++) + if (g_edicts[i+1].inuse && g_edicts[i+1].client->pers.spectator) + numspec++; + + if (numspec >= maxspectators->value) { + Info_SetValueForKey(userinfo, "rejmsg", "Server spectator limit is full."); + return false; + } + } else { + // check for a password + value = Info_ValueForKey (userinfo, "password"); + if (*password->string && strcmp(password->string, "none") && + strcmp(password->string, value)) { + Info_SetValueForKey(userinfo, "rejmsg", "Password required or incorrect."); + return false; + } + } + + + // they can connect + ent->client = game.clients + (ent - g_edicts - 1); + + // if there is already a body waiting for us (a loadgame), just + // take it, otherwise spawn one from scratch + if (ent->inuse == false) + { + // clear the respawning variables + InitClientResp (ent->client); + if (!game.autosaved || !ent->client->pers.weapon) + InitClientPersistant (ent->client); + } + + ClientUserinfoChanged (ent, userinfo); + + if (game.maxclients > 1) + gi.dprintf ("%s connected\n", ent->client->pers.netname); + + ent->svflags = 0; // make sure we start with known default + ent->client->pers.connected = true; + return true; +} + +/* +=========== +ClientDisconnect + +Called when a player drops from the server. +Will not be called between levels. +============ +*/ +void ClientDisconnect (edict_t *ent) +{ + int playernum; + + if (!ent->client) + return; + + gi.bprintf (PRINT_HIGH, "%s disconnected\n", ent->client->pers.netname); + + // send effect + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + gi.WriteByte (MZ_LOGOUT); + gi.multicast (ent->s.origin, MULTICAST_PVS); + + gi.unlinkentity (ent); + ent->s.modelindex = 0; + ent->solid = SOLID_NOT; + ent->inuse = false; + ent->classname = "disconnected"; + ent->client->pers.connected = false; + + playernum = ent-g_edicts-1; + gi.configstring (CS_PLAYERSKINS+playernum, ""); +} + + +//============================================================== + + +edict_t *pm_passent; + +// pmove doesn't need to know about passent and contentmask +trace_t PM_trace (vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end) +{ + if (pm_passent->health > 0) + return gi.trace (start, mins, maxs, end, pm_passent, MASK_PLAYERSOLID); + else + return gi.trace (start, mins, maxs, end, pm_passent, MASK_DEADSOLID); +} + +unsigned CheckBlock (void *b, int c) +{ + int v,i; + v = 0; + for (i=0 ; is, sizeof(pm->s)); + c2 = CheckBlock (&pm->cmd, sizeof(pm->cmd)); + Com_Printf ("sv %3i:%i %i\n", pm->cmd.impulse, c1, c2); +} + +/* +============== +ClientThink + +This will be called once for each client frame, which will +usually be a couple times for each server frame. +============== +*/ +void ClientThink (edict_t *ent, usercmd_t *ucmd) +{ + gclient_t *client; + edict_t *other; + int i, j; + pmove_t pm; + + level.current_entity = ent; + client = ent->client; + + if (level.intermissiontime) + { + client->ps.pmove.pm_type = PM_FREEZE; + // can exit intermission after five seconds + if (level.time > level.intermissiontime + 5.0 + && (ucmd->buttons & BUTTON_ANY) ) + level.exitintermission = true; + return; + } + + pm_passent = ent; + + if (ent->client->chase_target) { + + client->resp.cmd_angles[0] = SHORT2ANGLE(ucmd->angles[0]); + client->resp.cmd_angles[1] = SHORT2ANGLE(ucmd->angles[1]); + client->resp.cmd_angles[2] = SHORT2ANGLE(ucmd->angles[2]); + + } else { + + // set up for pmove + memset (&pm, 0, sizeof(pm)); + + if (ent->movetype == MOVETYPE_NOCLIP) + client->ps.pmove.pm_type = PM_SPECTATOR; + else if (ent->s.modelindex != 255) + client->ps.pmove.pm_type = PM_GIB; + else if (ent->deadflag) + client->ps.pmove.pm_type = PM_DEAD; + else + client->ps.pmove.pm_type = PM_NORMAL; + + client->ps.pmove.gravity = sv_gravity->value; + pm.s = client->ps.pmove; + + for (i=0 ; i<3 ; i++) + { + pm.s.origin[i] = ent->s.origin[i]*8; + pm.s.velocity[i] = ent->velocity[i]*8; + } + + if (memcmp(&client->old_pmove, &pm.s, sizeof(pm.s))) + { + pm.snapinitial = true; + // gi.dprintf ("pmove changed!\n"); + } + + pm.cmd = *ucmd; + + pm.trace = PM_trace; // adds default parms + pm.pointcontents = gi.pointcontents; + + // perform a pmove + gi.Pmove (&pm); + + // save results of pmove + client->ps.pmove = pm.s; + client->old_pmove = pm.s; + + for (i=0 ; i<3 ; i++) + { + ent->s.origin[i] = pm.s.origin[i]*0.125; + ent->velocity[i] = pm.s.velocity[i]*0.125; + } + + VectorCopy (pm.mins, ent->mins); + VectorCopy (pm.maxs, ent->maxs); + + client->resp.cmd_angles[0] = SHORT2ANGLE(ucmd->angles[0]); + client->resp.cmd_angles[1] = SHORT2ANGLE(ucmd->angles[1]); + client->resp.cmd_angles[2] = SHORT2ANGLE(ucmd->angles[2]); + + if (ent->groundentity && !pm.groundentity && (pm.cmd.upmove >= 10) && (pm.waterlevel == 0)) + { + gi.sound(ent, CHAN_VOICE, gi.soundindex("*jump1.wav"), 1, ATTN_NORM, 0); + PlayerNoise(ent, ent->s.origin, PNOISE_SELF); + } + + ent->viewheight = pm.viewheight; + ent->waterlevel = pm.waterlevel; + ent->watertype = pm.watertype; + ent->groundentity = pm.groundentity; + if (pm.groundentity) + ent->groundentity_linkcount = pm.groundentity->linkcount; + + if (ent->deadflag) + { + client->ps.viewangles[ROLL] = 40; + client->ps.viewangles[PITCH] = -15; + client->ps.viewangles[YAW] = client->killer_yaw; + } + else + { + VectorCopy (pm.viewangles, client->v_angle); + VectorCopy (pm.viewangles, client->ps.viewangles); + } + + gi.linkentity (ent); + + if (ent->movetype != MOVETYPE_NOCLIP) + G_TouchTriggers (ent); + + // touch other objects + for (i=0 ; itouch) + continue; + other->touch (other, ent, NULL, NULL); + } + + } + + client->oldbuttons = client->buttons; + client->buttons = ucmd->buttons; + client->latched_buttons |= client->buttons & ~client->oldbuttons; + + // save light level the player is standing on for + // monster sighting AI + ent->light_level = ucmd->lightlevel; + + // fire weapon from final position if needed + if (client->latched_buttons & BUTTON_ATTACK) + { + if (client->resp.spectator) { + + client->latched_buttons = 0; + + if (client->chase_target) { + client->chase_target = NULL; + client->ps.pmove.pm_flags &= ~PMF_NO_PREDICTION; + } else + GetChaseTarget(ent); + + } else if (!client->weapon_thunk) { + client->weapon_thunk = true; + Think_Weapon (ent); + } + } + + if (client->resp.spectator) { + if (ucmd->upmove >= 10) { + if (!(client->ps.pmove.pm_flags & PMF_JUMP_HELD)) { + client->ps.pmove.pm_flags |= PMF_JUMP_HELD; + if (client->chase_target) + ChaseNext(ent); + else + GetChaseTarget(ent); + } + } else + client->ps.pmove.pm_flags &= ~PMF_JUMP_HELD; + } + + // update chase cam if being followed + for (i = 1; i <= maxclients->value; i++) { + other = g_edicts + i; + if (other->inuse && other->client->chase_target == ent) + UpdateChaseCam(other); + } +} + + +/* +============== +ClientBeginServerFrame + +This will be called once for each server frame, before running +any other entities in the world. +============== +*/ +void ClientBeginServerFrame (edict_t *ent) +{ + gclient_t *client; + int buttonMask; + + if (level.intermissiontime) + return; + + client = ent->client; + + if (deathmatch->value && + client->pers.spectator != client->resp.spectator && + (level.time - client->respawn_time) >= 5) { + spectator_respawn(ent); + return; + } + + // run weapon animations if it hasn't been done by a ucmd_t + if (!client->weapon_thunk && !client->resp.spectator) + Think_Weapon (ent); + else + client->weapon_thunk = false; + + if (ent->deadflag) + { + // wait for any button just going down + if ( level.time > client->respawn_time) + { + // in deathmatch, only wait for attack button + if (deathmatch->value) + buttonMask = BUTTON_ATTACK; + else + buttonMask = -1; + + if ( ( client->latched_buttons & buttonMask ) || + (deathmatch->value && ((int)dmflags->value & DF_FORCE_RESPAWN) ) ) + { + respawn(ent); + client->latched_buttons = 0; + } + } + return; + } + + // add player trail so monsters can follow + if (!deathmatch->value) + if (!visible (ent, PlayerTrail_LastSpot() ) ) + PlayerTrail_Add (ent->s.old_origin); + + client->latched_buttons = 0; +} diff --git a/original/baseq2/p_hud.c b/original/baseq2/p_hud.c new file mode 100644 index 0000000..d5249a0 --- /dev/null +++ b/original/baseq2/p_hud.c @@ -0,0 +1,571 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +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. + +*/ +#include "g_local.h" + + + +/* +====================================================================== + +INTERMISSION + +====================================================================== +*/ + +void MoveClientToIntermission (edict_t *ent) +{ + if (deathmatch->value || coop->value) + ent->client->showscores = true; + VectorCopy (level.intermission_origin, ent->s.origin); + ent->client->ps.pmove.origin[0] = level.intermission_origin[0]*8; + ent->client->ps.pmove.origin[1] = level.intermission_origin[1]*8; + ent->client->ps.pmove.origin[2] = level.intermission_origin[2]*8; + VectorCopy (level.intermission_angle, ent->client->ps.viewangles); + ent->client->ps.pmove.pm_type = PM_FREEZE; + ent->client->ps.gunindex = 0; + ent->client->ps.blend[3] = 0; + ent->client->ps.rdflags &= ~RDF_UNDERWATER; + + // clean up powerup info + ent->client->quad_framenum = 0; + ent->client->invincible_framenum = 0; + ent->client->breather_framenum = 0; + ent->client->enviro_framenum = 0; + ent->client->grenade_blew_up = false; + ent->client->grenade_time = 0; + + ent->viewheight = 0; + ent->s.modelindex = 0; + ent->s.modelindex2 = 0; + ent->s.modelindex3 = 0; + ent->s.modelindex = 0; + ent->s.effects = 0; + ent->s.sound = 0; + ent->solid = SOLID_NOT; + + // add the layout + + if (deathmatch->value || coop->value) + { + DeathmatchScoreboardMessage (ent, NULL); + gi.unicast (ent, true); + } + +} + +void BeginIntermission (edict_t *targ) +{ + int i, n; + edict_t *ent, *client; + + if (level.intermissiontime) + return; // already activated + + game.autosaved = false; + + // respawn any dead clients + for (i=0 ; ivalue ; i++) + { + client = g_edicts + 1 + i; + if (!client->inuse) + continue; + if (client->health <= 0) + respawn(client); + } + + level.intermissiontime = level.time; + level.changemap = targ->map; + + if (strstr(level.changemap, "*")) + { + if (coop->value) + { + for (i=0 ; ivalue ; i++) + { + client = g_edicts + 1 + i; + if (!client->inuse) + continue; + // strip players of all keys between units + for (n = 0; n < MAX_ITEMS; n++) + { + if (itemlist[n].flags & IT_KEY) + client->client->pers.inventory[n] = 0; + } + } + } + } + else + { + if (!deathmatch->value) + { + level.exitintermission = 1; // go immediately to the next level + return; + } + } + + level.exitintermission = 0; + + // find an intermission spot + ent = G_Find (NULL, FOFS(classname), "info_player_intermission"); + if (!ent) + { // the map creator forgot to put in an intermission point... + ent = G_Find (NULL, FOFS(classname), "info_player_start"); + if (!ent) + ent = G_Find (NULL, FOFS(classname), "info_player_deathmatch"); + } + else + { // chose one of four spots + i = rand() & 3; + while (i--) + { + ent = G_Find (ent, FOFS(classname), "info_player_intermission"); + if (!ent) // wrap around the list + ent = G_Find (ent, FOFS(classname), "info_player_intermission"); + } + } + + VectorCopy (ent->s.origin, level.intermission_origin); + VectorCopy (ent->s.angles, level.intermission_angle); + + // move all clients to the intermission point + for (i=0 ; ivalue ; i++) + { + client = g_edicts + 1 + i; + if (!client->inuse) + continue; + MoveClientToIntermission (client); + } +} + + +/* +================== +DeathmatchScoreboardMessage + +================== +*/ +void DeathmatchScoreboardMessage (edict_t *ent, edict_t *killer) +{ + char entry[1024]; + char string[1400]; + int stringlength; + int i, j, k; + int sorted[MAX_CLIENTS]; + int sortedscores[MAX_CLIENTS]; + int score, total; + int picnum; + int x, y; + gclient_t *cl; + edict_t *cl_ent; + char *tag; + + // sort the clients by score + total = 0; + for (i=0 ; iinuse || game.clients[i].resp.spectator) + continue; + score = game.clients[i].resp.score; + for (j=0 ; j sortedscores[j]) + break; + } + for (k=total ; k>j ; k--) + { + sorted[k] = sorted[k-1]; + sortedscores[k] = sortedscores[k-1]; + } + sorted[j] = i; + sortedscores[j] = score; + total++; + } + + // print level name and exit rules + string[0] = 0; + + stringlength = strlen(string); + + // add the clients in sorted order + if (total > 12) + total = 12; + + for (i=0 ; i=6) ? 160 : 0; + y = 32 + 32 * (i%6); + + // add a dogtag + if (cl_ent == ent) + tag = "tag1"; + else if (cl_ent == killer) + tag = "tag2"; + else + tag = NULL; + if (tag) + { + Com_sprintf (entry, sizeof(entry), + "xv %i yv %i picn %s ",x+32, y, tag); + j = strlen(entry); + if (stringlength + j > 1024) + break; + strcpy (string + stringlength, entry); + stringlength += j; + } + + // send the layout + Com_sprintf (entry, sizeof(entry), + "client %i %i %i %i %i %i ", + x, y, sorted[i], cl->resp.score, cl->ping, (level.framenum - cl->resp.enterframe)/600); + j = strlen(entry); + if (stringlength + j > 1024) + break; + strcpy (string + stringlength, entry); + stringlength += j; + } + + gi.WriteByte (svc_layout); + gi.WriteString (string); +} + + +/* +================== +DeathmatchScoreboard + +Draw instead of help message. +Note that it isn't that hard to overflow the 1400 byte message limit! +================== +*/ +void DeathmatchScoreboard (edict_t *ent) +{ + DeathmatchScoreboardMessage (ent, ent->enemy); + gi.unicast (ent, true); +} + + +/* +================== +Cmd_Score_f + +Display the scoreboard +================== +*/ +void Cmd_Score_f (edict_t *ent) +{ + ent->client->showinventory = false; + ent->client->showhelp = false; + + if (!deathmatch->value && !coop->value) + return; + + if (ent->client->showscores) + { + ent->client->showscores = false; + return; + } + + ent->client->showscores = true; + DeathmatchScoreboard (ent); +} + + +/* +================== +HelpComputer + +Draw help computer. +================== +*/ +void HelpComputer (edict_t *ent) +{ + char string[1024]; + char *sk; + + if (skill->value == 0) + sk = "easy"; + else if (skill->value == 1) + sk = "medium"; + else if (skill->value == 2) + sk = "hard"; + else + sk = "hard+"; + + // send the layout + Com_sprintf (string, sizeof(string), + "xv 32 yv 8 picn help " // background + "xv 202 yv 12 string2 \"%s\" " // skill + "xv 0 yv 24 cstring2 \"%s\" " // level name + "xv 0 yv 54 cstring2 \"%s\" " // help 1 + "xv 0 yv 110 cstring2 \"%s\" " // help 2 + "xv 50 yv 164 string2 \" kills goals secrets\" " + "xv 50 yv 172 string2 \"%3i/%3i %i/%i %i/%i\" ", + sk, + level.level_name, + game.helpmessage1, + game.helpmessage2, + level.killed_monsters, level.total_monsters, + level.found_goals, level.total_goals, + level.found_secrets, level.total_secrets); + + gi.WriteByte (svc_layout); + gi.WriteString (string); + gi.unicast (ent, true); +} + + +/* +================== +Cmd_Help_f + +Display the current help message +================== +*/ +void Cmd_Help_f (edict_t *ent) +{ + // this is for backwards compatability + if (deathmatch->value) + { + Cmd_Score_f (ent); + return; + } + + ent->client->showinventory = false; + ent->client->showscores = false; + + if (ent->client->showhelp && (ent->client->pers.game_helpchanged == game.helpchanged)) + { + ent->client->showhelp = false; + return; + } + + ent->client->showhelp = true; + ent->client->pers.helpchanged = 0; + HelpComputer (ent); +} + + +//======================================================================= + +/* +=============== +G_SetStats +=============== +*/ +void G_SetStats (edict_t *ent) +{ + gitem_t *item; + int index, cells; + int power_armor_type; + + // + // health + // + ent->client->ps.stats[STAT_HEALTH_ICON] = level.pic_health; + ent->client->ps.stats[STAT_HEALTH] = ent->health; + + // + // ammo + // + if (!ent->client->ammo_index /* || !ent->client->pers.inventory[ent->client->ammo_index] */) + { + ent->client->ps.stats[STAT_AMMO_ICON] = 0; + ent->client->ps.stats[STAT_AMMO] = 0; + } + else + { + item = &itemlist[ent->client->ammo_index]; + ent->client->ps.stats[STAT_AMMO_ICON] = gi.imageindex (item->icon); + ent->client->ps.stats[STAT_AMMO] = ent->client->pers.inventory[ent->client->ammo_index]; + } + + // + // armor + // + power_armor_type = PowerArmorType (ent); + if (power_armor_type) + { + cells = ent->client->pers.inventory[ITEM_INDEX(FindItem ("cells"))]; + if (cells == 0) + { // ran out of cells for power armor + ent->flags &= ~FL_POWER_ARMOR; + gi.sound(ent, CHAN_ITEM, gi.soundindex("misc/power2.wav"), 1, ATTN_NORM, 0); + power_armor_type = 0;; + } + } + + index = ArmorIndex (ent); + if (power_armor_type && (!index || (level.framenum & 8) ) ) + { // flash between power armor and other armor icon + ent->client->ps.stats[STAT_ARMOR_ICON] = gi.imageindex ("i_powershield"); + ent->client->ps.stats[STAT_ARMOR] = cells; + } + else if (index) + { + item = GetItemByIndex (index); + ent->client->ps.stats[STAT_ARMOR_ICON] = gi.imageindex (item->icon); + ent->client->ps.stats[STAT_ARMOR] = ent->client->pers.inventory[index]; + } + else + { + ent->client->ps.stats[STAT_ARMOR_ICON] = 0; + ent->client->ps.stats[STAT_ARMOR] = 0; + } + + // + // pickup message + // + if (level.time > ent->client->pickup_msg_time) + { + ent->client->ps.stats[STAT_PICKUP_ICON] = 0; + ent->client->ps.stats[STAT_PICKUP_STRING] = 0; + } + + // + // timers + // + if (ent->client->quad_framenum > level.framenum) + { + ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex ("p_quad"); + ent->client->ps.stats[STAT_TIMER] = (ent->client->quad_framenum - level.framenum)/10; + } + else if (ent->client->invincible_framenum > level.framenum) + { + ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex ("p_invulnerability"); + ent->client->ps.stats[STAT_TIMER] = (ent->client->invincible_framenum - level.framenum)/10; + } + else if (ent->client->enviro_framenum > level.framenum) + { + ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex ("p_envirosuit"); + ent->client->ps.stats[STAT_TIMER] = (ent->client->enviro_framenum - level.framenum)/10; + } + else if (ent->client->breather_framenum > level.framenum) + { + ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex ("p_rebreather"); + ent->client->ps.stats[STAT_TIMER] = (ent->client->breather_framenum - level.framenum)/10; + } + else + { + ent->client->ps.stats[STAT_TIMER_ICON] = 0; + ent->client->ps.stats[STAT_TIMER] = 0; + } + + // + // selected item + // + if (ent->client->pers.selected_item == -1) + ent->client->ps.stats[STAT_SELECTED_ICON] = 0; + else + ent->client->ps.stats[STAT_SELECTED_ICON] = gi.imageindex (itemlist[ent->client->pers.selected_item].icon); + + ent->client->ps.stats[STAT_SELECTED_ITEM] = ent->client->pers.selected_item; + + // + // layouts + // + ent->client->ps.stats[STAT_LAYOUTS] = 0; + + if (deathmatch->value) + { + if (ent->client->pers.health <= 0 || level.intermissiontime + || ent->client->showscores) + ent->client->ps.stats[STAT_LAYOUTS] |= 1; + if (ent->client->showinventory && ent->client->pers.health > 0) + ent->client->ps.stats[STAT_LAYOUTS] |= 2; + } + else + { + if (ent->client->showscores || ent->client->showhelp) + ent->client->ps.stats[STAT_LAYOUTS] |= 1; + if (ent->client->showinventory && ent->client->pers.health > 0) + ent->client->ps.stats[STAT_LAYOUTS] |= 2; + } + + // + // frags + // + ent->client->ps.stats[STAT_FRAGS] = ent->client->resp.score; + + // + // help icon / current weapon if not shown + // + if (ent->client->pers.helpchanged && (level.framenum&8) ) + ent->client->ps.stats[STAT_HELPICON] = gi.imageindex ("i_help"); + else if ( (ent->client->pers.hand == CENTER_HANDED || ent->client->ps.fov > 91) + && ent->client->pers.weapon) + ent->client->ps.stats[STAT_HELPICON] = gi.imageindex (ent->client->pers.weapon->icon); + else + ent->client->ps.stats[STAT_HELPICON] = 0; + + ent->client->ps.stats[STAT_SPECTATOR] = 0; +} + +/* +=============== +G_CheckChaseStats +=============== +*/ +void G_CheckChaseStats (edict_t *ent) +{ + int i; + gclient_t *cl; + + for (i = 1; i <= maxclients->value; i++) { + cl = g_edicts[i].client; + if (!g_edicts[i].inuse || cl->chase_target != ent) + continue; + memcpy(cl->ps.stats, ent->client->ps.stats, sizeof(cl->ps.stats)); + G_SetSpectatorStats(g_edicts + i); + } +} + +/* +=============== +G_SetSpectatorStats +=============== +*/ +void G_SetSpectatorStats (edict_t *ent) +{ + gclient_t *cl = ent->client; + + if (!cl->chase_target) + G_SetStats (ent); + + cl->ps.stats[STAT_SPECTATOR] = 1; + + // layouts are independant in spectator + cl->ps.stats[STAT_LAYOUTS] = 0; + if (cl->pers.health <= 0 || level.intermissiontime || cl->showscores) + cl->ps.stats[STAT_LAYOUTS] |= 1; + if (cl->showinventory && cl->pers.health > 0) + cl->ps.stats[STAT_LAYOUTS] |= 2; + + if (cl->chase_target && cl->chase_target->inuse) + cl->ps.stats[STAT_CHASE] = CS_PLAYERSKINS + + (cl->chase_target - g_edicts) - 1; + else + cl->ps.stats[STAT_CHASE] = 0; +} + diff --git a/original/baseq2/p_trail.c b/original/baseq2/p_trail.c new file mode 100644 index 0000000..d968682 --- /dev/null +++ b/original/baseq2/p_trail.c @@ -0,0 +1,146 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +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. + +*/ +#include "g_local.h" + + +/* +============================================================================== + +PLAYER TRAIL + +============================================================================== + +This is a circular list containing the a list of points of where +the player has been recently. It is used by monsters for pursuit. + +.origin the spot +.owner forward link +.aiment backward link +*/ + + +#define TRAIL_LENGTH 8 + +edict_t *trail[TRAIL_LENGTH]; +int trail_head; +qboolean trail_active = false; + +#define NEXT(n) (((n) + 1) & (TRAIL_LENGTH - 1)) +#define PREV(n) (((n) - 1) & (TRAIL_LENGTH - 1)) + + +void PlayerTrail_Init (void) +{ + int n; + + if (deathmatch->value /* FIXME || coop */) + return; + + for (n = 0; n < TRAIL_LENGTH; n++) + { + trail[n] = G_Spawn(); + trail[n]->classname = "player_trail"; + } + + trail_head = 0; + trail_active = true; +} + + +void PlayerTrail_Add (vec3_t spot) +{ + vec3_t temp; + + if (!trail_active) + return; + + VectorCopy (spot, trail[trail_head]->s.origin); + + trail[trail_head]->timestamp = level.time; + + VectorSubtract (spot, trail[PREV(trail_head)]->s.origin, temp); + trail[trail_head]->s.angles[1] = vectoyaw (temp); + + trail_head = NEXT(trail_head); +} + + +void PlayerTrail_New (vec3_t spot) +{ + if (!trail_active) + return; + + PlayerTrail_Init (); + PlayerTrail_Add (spot); +} + + +edict_t *PlayerTrail_PickFirst (edict_t *self) +{ + int marker; + int n; + + if (!trail_active) + return NULL; + + for (marker = trail_head, n = TRAIL_LENGTH; n; n--) + { + if(trail[marker]->timestamp <= self->monsterinfo.trail_time) + marker = NEXT(marker); + else + break; + } + + if (visible(self, trail[marker])) + { + return trail[marker]; + } + + if (visible(self, trail[PREV(marker)])) + { + return trail[PREV(marker)]; + } + + return trail[marker]; +} + +edict_t *PlayerTrail_PickNext (edict_t *self) +{ + int marker; + int n; + + if (!trail_active) + return NULL; + + for (marker = trail_head, n = TRAIL_LENGTH; n; n--) + { + if(trail[marker]->timestamp <= self->monsterinfo.trail_time) + marker = NEXT(marker); + else + break; + } + + return trail[marker]; +} + +edict_t *PlayerTrail_LastSpot (void) +{ + return trail[PREV(trail_head)]; +} diff --git a/original/baseq2/p_view.c b/original/baseq2/p_view.c new file mode 100644 index 0000000..04ba7c6 --- /dev/null +++ b/original/baseq2/p_view.c @@ -0,0 +1,1087 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +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. + +*/ + +#include "g_local.h" +#include "m_player.h" + + + +static edict_t *current_player; +static gclient_t *current_client; + +static vec3_t forward, right, up; +float xyspeed; + +float bobmove; +int bobcycle; // odd cycles are right foot going forward +float bobfracsin; // sin(bobfrac*M_PI) + +/* +=============== +SV_CalcRoll + +=============== +*/ +float SV_CalcRoll (vec3_t angles, vec3_t velocity) +{ + float sign; + float side; + float value; + + side = DotProduct (velocity, right); + sign = side < 0 ? -1 : 1; + side = fabs(side); + + value = sv_rollangle->value; + + if (side < sv_rollspeed->value) + side = side * value / sv_rollspeed->value; + else + side = value; + + return side*sign; + +} + + +/* +=============== +P_DamageFeedback + +Handles color blends and view kicks +=============== +*/ +void P_DamageFeedback (edict_t *player) +{ + gclient_t *client; + float side; + float realcount, count, kick; + vec3_t v; + int r, l; + static vec3_t power_color = {0.0, 1.0, 0.0}; + static vec3_t acolor = {1.0, 1.0, 1.0}; + static vec3_t bcolor = {1.0, 0.0, 0.0}; + + client = player->client; + + // flash the backgrounds behind the status numbers + client->ps.stats[STAT_FLASHES] = 0; + if (client->damage_blood) + client->ps.stats[STAT_FLASHES] |= 1; + if (client->damage_armor && !(player->flags & FL_GODMODE) && (client->invincible_framenum <= level.framenum)) + client->ps.stats[STAT_FLASHES] |= 2; + + // total points of damage shot at the player this frame + count = (client->damage_blood + client->damage_armor + client->damage_parmor); + if (count == 0) + return; // didn't take any damage + + // start a pain animation if still in the player model + if (client->anim_priority < ANIM_PAIN && player->s.modelindex == 255) + { + static int i; + + client->anim_priority = ANIM_PAIN; + if (client->ps.pmove.pm_flags & PMF_DUCKED) + { + player->s.frame = FRAME_crpain1-1; + client->anim_end = FRAME_crpain4; + } + else + { + i = (i+1)%3; + switch (i) + { + case 0: + player->s.frame = FRAME_pain101-1; + client->anim_end = FRAME_pain104; + break; + case 1: + player->s.frame = FRAME_pain201-1; + client->anim_end = FRAME_pain204; + break; + case 2: + player->s.frame = FRAME_pain301-1; + client->anim_end = FRAME_pain304; + break; + } + } + } + + realcount = count; + if (count < 10) + count = 10; // always make a visible effect + + // play an apropriate pain sound + if ((level.time > player->pain_debounce_time) && !(player->flags & FL_GODMODE) && (client->invincible_framenum <= level.framenum)) + { + r = 1 + (rand()&1); + player->pain_debounce_time = level.time + 0.7; + if (player->health < 25) + l = 25; + else if (player->health < 50) + l = 50; + else if (player->health < 75) + l = 75; + else + l = 100; + gi.sound (player, CHAN_VOICE, gi.soundindex(va("*pain%i_%i.wav", l, r)), 1, ATTN_NORM, 0); + } + + // the total alpha of the blend is always proportional to count + if (client->damage_alpha < 0) + client->damage_alpha = 0; + client->damage_alpha += count*0.01; + if (client->damage_alpha < 0.2) + client->damage_alpha = 0.2; + if (client->damage_alpha > 0.6) + client->damage_alpha = 0.6; // don't go too saturated + + // the color of the blend will vary based on how much was absorbed + // by different armors + VectorClear (v); + if (client->damage_parmor) + VectorMA (v, (float)client->damage_parmor/realcount, power_color, v); + if (client->damage_armor) + VectorMA (v, (float)client->damage_armor/realcount, acolor, v); + if (client->damage_blood) + VectorMA (v, (float)client->damage_blood/realcount, bcolor, v); + VectorCopy (v, client->damage_blend); + + + // + // calculate view angle kicks + // + kick = abs(client->damage_knockback); + if (kick && player->health > 0) // kick of 0 means no view adjust at all + { + kick = kick * 100 / player->health; + + if (kick < count*0.5) + kick = count*0.5; + if (kick > 50) + kick = 50; + + VectorSubtract (client->damage_from, player->s.origin, v); + VectorNormalize (v); + + side = DotProduct (v, right); + client->v_dmg_roll = kick*side*0.3; + + side = -DotProduct (v, forward); + client->v_dmg_pitch = kick*side*0.3; + + client->v_dmg_time = level.time + DAMAGE_TIME; + } + + // + // clear totals + // + client->damage_blood = 0; + client->damage_armor = 0; + client->damage_parmor = 0; + client->damage_knockback = 0; +} + + + + +/* +=============== +SV_CalcViewOffset + +Auto pitching on slopes? + + fall from 128: 400 = 160000 + fall from 256: 580 = 336400 + fall from 384: 720 = 518400 + fall from 512: 800 = 640000 + fall from 640: 960 = + + damage = deltavelocity*deltavelocity * 0.0001 + +=============== +*/ +void SV_CalcViewOffset (edict_t *ent) +{ + float *angles; + float bob; + float ratio; + float delta; + vec3_t v; + + +//=================================== + + // base angles + angles = ent->client->ps.kick_angles; + + // if dead, fix the angle and don't add any kick + if (ent->deadflag) + { + VectorClear (angles); + + ent->client->ps.viewangles[ROLL] = 40; + ent->client->ps.viewangles[PITCH] = -15; + ent->client->ps.viewangles[YAW] = ent->client->killer_yaw; + } + else + { + // add angles based on weapon kick + + VectorCopy (ent->client->kick_angles, angles); + + // add angles based on damage kick + + ratio = (ent->client->v_dmg_time - level.time) / DAMAGE_TIME; + if (ratio < 0) + { + ratio = 0; + ent->client->v_dmg_pitch = 0; + ent->client->v_dmg_roll = 0; + } + angles[PITCH] += ratio * ent->client->v_dmg_pitch; + angles[ROLL] += ratio * ent->client->v_dmg_roll; + + // add pitch based on fall kick + + ratio = (ent->client->fall_time - level.time) / FALL_TIME; + if (ratio < 0) + ratio = 0; + angles[PITCH] += ratio * ent->client->fall_value; + + // add angles based on velocity + + delta = DotProduct (ent->velocity, forward); + angles[PITCH] += delta*run_pitch->value; + + delta = DotProduct (ent->velocity, right); + angles[ROLL] += delta*run_roll->value; + + // add angles based on bob + + delta = bobfracsin * bob_pitch->value * xyspeed; + if (ent->client->ps.pmove.pm_flags & PMF_DUCKED) + delta *= 6; // crouching + angles[PITCH] += delta; + delta = bobfracsin * bob_roll->value * xyspeed; + if (ent->client->ps.pmove.pm_flags & PMF_DUCKED) + delta *= 6; // crouching + if (bobcycle & 1) + delta = -delta; + angles[ROLL] += delta; + } + +//=================================== + + // base origin + + VectorClear (v); + + // add view height + + v[2] += ent->viewheight; + + // add fall height + + ratio = (ent->client->fall_time - level.time) / FALL_TIME; + if (ratio < 0) + ratio = 0; + v[2] -= ratio * ent->client->fall_value * 0.4; + + // add bob height + + bob = bobfracsin * xyspeed * bob_up->value; + if (bob > 6) + bob = 6; + //gi.DebugGraph (bob *2, 255); + v[2] += bob; + + // add kick offset + + VectorAdd (v, ent->client->kick_origin, v); + + // absolutely bound offsets + // so the view can never be outside the player box + + if (v[0] < -14) + v[0] = -14; + else if (v[0] > 14) + v[0] = 14; + if (v[1] < -14) + v[1] = -14; + else if (v[1] > 14) + v[1] = 14; + if (v[2] < -22) + v[2] = -22; + else if (v[2] > 30) + v[2] = 30; + + VectorCopy (v, ent->client->ps.viewoffset); +} + +/* +============== +SV_CalcGunOffset +============== +*/ +void SV_CalcGunOffset (edict_t *ent) +{ + int i; + float delta; + + // gun angles from bobbing + ent->client->ps.gunangles[ROLL] = xyspeed * bobfracsin * 0.005; + ent->client->ps.gunangles[YAW] = xyspeed * bobfracsin * 0.01; + if (bobcycle & 1) + { + ent->client->ps.gunangles[ROLL] = -ent->client->ps.gunangles[ROLL]; + ent->client->ps.gunangles[YAW] = -ent->client->ps.gunangles[YAW]; + } + + ent->client->ps.gunangles[PITCH] = xyspeed * bobfracsin * 0.005; + + // gun angles from delta movement + for (i=0 ; i<3 ; i++) + { + delta = ent->client->oldviewangles[i] - ent->client->ps.viewangles[i]; + if (delta > 180) + delta -= 360; + if (delta < -180) + delta += 360; + if (delta > 45) + delta = 45; + if (delta < -45) + delta = -45; + if (i == YAW) + ent->client->ps.gunangles[ROLL] += 0.1*delta; + ent->client->ps.gunangles[i] += 0.2 * delta; + } + + // gun height + VectorClear (ent->client->ps.gunoffset); +// ent->ps->gunorigin[2] += bob; + + // gun_x / gun_y / gun_z are development tools + for (i=0 ; i<3 ; i++) + { + ent->client->ps.gunoffset[i] += forward[i]*(gun_y->value); + ent->client->ps.gunoffset[i] += right[i]*gun_x->value; + ent->client->ps.gunoffset[i] += up[i]* (-gun_z->value); + } +} + + +/* +============= +SV_AddBlend +============= +*/ +void SV_AddBlend (float r, float g, float b, float a, float *v_blend) +{ + float a2, a3; + + if (a <= 0) + return; + a2 = v_blend[3] + (1-v_blend[3])*a; // new total alpha + a3 = v_blend[3]/a2; // fraction of color from old + + v_blend[0] = v_blend[0]*a3 + r*(1-a3); + v_blend[1] = v_blend[1]*a3 + g*(1-a3); + v_blend[2] = v_blend[2]*a3 + b*(1-a3); + v_blend[3] = a2; +} + + +/* +============= +SV_CalcBlend +============= +*/ +void SV_CalcBlend (edict_t *ent) +{ + int contents; + vec3_t vieworg; + int remaining; + + ent->client->ps.blend[0] = ent->client->ps.blend[1] = + ent->client->ps.blend[2] = ent->client->ps.blend[3] = 0; + + // add for contents + VectorAdd (ent->s.origin, ent->client->ps.viewoffset, vieworg); + contents = gi.pointcontents (vieworg); + if (contents & (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER) ) + ent->client->ps.rdflags |= RDF_UNDERWATER; + else + ent->client->ps.rdflags &= ~RDF_UNDERWATER; + + if (contents & (CONTENTS_SOLID|CONTENTS_LAVA)) + SV_AddBlend (1.0, 0.3, 0.0, 0.6, ent->client->ps.blend); + else if (contents & CONTENTS_SLIME) + SV_AddBlend (0.0, 0.1, 0.05, 0.6, ent->client->ps.blend); + else if (contents & CONTENTS_WATER) + SV_AddBlend (0.5, 0.3, 0.2, 0.4, ent->client->ps.blend); + + // add for powerups + if (ent->client->quad_framenum > level.framenum) + { + remaining = ent->client->quad_framenum - level.framenum; + if (remaining == 30) // beginning to fade + gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage2.wav"), 1, ATTN_NORM, 0); + if (remaining > 30 || (remaining & 4) ) + SV_AddBlend (0, 0, 1, 0.08, ent->client->ps.blend); + } + else if (ent->client->invincible_framenum > level.framenum) + { + remaining = ent->client->invincible_framenum - level.framenum; + if (remaining == 30) // beginning to fade + gi.sound(ent, CHAN_ITEM, gi.soundindex("items/protect2.wav"), 1, ATTN_NORM, 0); + if (remaining > 30 || (remaining & 4) ) + SV_AddBlend (1, 1, 0, 0.08, ent->client->ps.blend); + } + else if (ent->client->enviro_framenum > level.framenum) + { + remaining = ent->client->enviro_framenum - level.framenum; + if (remaining == 30) // beginning to fade + gi.sound(ent, CHAN_ITEM, gi.soundindex("items/airout.wav"), 1, ATTN_NORM, 0); + if (remaining > 30 || (remaining & 4) ) + SV_AddBlend (0, 1, 0, 0.08, ent->client->ps.blend); + } + else if (ent->client->breather_framenum > level.framenum) + { + remaining = ent->client->breather_framenum - level.framenum; + if (remaining == 30) // beginning to fade + gi.sound(ent, CHAN_ITEM, gi.soundindex("items/airout.wav"), 1, ATTN_NORM, 0); + if (remaining > 30 || (remaining & 4) ) + SV_AddBlend (0.4, 1, 0.4, 0.04, ent->client->ps.blend); + } + + // add for damage + if (ent->client->damage_alpha > 0) + SV_AddBlend (ent->client->damage_blend[0],ent->client->damage_blend[1] + ,ent->client->damage_blend[2], ent->client->damage_alpha, ent->client->ps.blend); + + if (ent->client->bonus_alpha > 0) + SV_AddBlend (0.85, 0.7, 0.3, ent->client->bonus_alpha, ent->client->ps.blend); + + // drop the damage value + ent->client->damage_alpha -= 0.06; + if (ent->client->damage_alpha < 0) + ent->client->damage_alpha = 0; + + // drop the bonus value + ent->client->bonus_alpha -= 0.1; + if (ent->client->bonus_alpha < 0) + ent->client->bonus_alpha = 0; +} + + +/* +================= +P_FallingDamage +================= +*/ +void P_FallingDamage (edict_t *ent) +{ + float delta; + int damage; + vec3_t dir; + + if (ent->s.modelindex != 255) + return; // not in the player model + + if (ent->movetype == MOVETYPE_NOCLIP) + return; + + if ((ent->client->oldvelocity[2] < 0) && (ent->velocity[2] > ent->client->oldvelocity[2]) && (!ent->groundentity)) + { + delta = ent->client->oldvelocity[2]; + } + else + { + if (!ent->groundentity) + return; + delta = ent->velocity[2] - ent->client->oldvelocity[2]; + } + delta = delta*delta * 0.0001; + + // never take falling damage if completely underwater + if (ent->waterlevel == 3) + return; + if (ent->waterlevel == 2) + delta *= 0.25; + if (ent->waterlevel == 1) + delta *= 0.5; + + if (delta < 1) + return; + + if (delta < 15) + { + ent->s.event = EV_FOOTSTEP; + return; + } + + ent->client->fall_value = delta*0.5; + if (ent->client->fall_value > 40) + ent->client->fall_value = 40; + ent->client->fall_time = level.time + FALL_TIME; + + if (delta > 30) + { + if (ent->health > 0) + { + if (delta >= 55) + ent->s.event = EV_FALLFAR; + else + ent->s.event = EV_FALL; + } + ent->pain_debounce_time = level.time; // no normal pain sound + damage = (delta-30)/2; + if (damage < 1) + damage = 1; + VectorSet (dir, 0, 0, 1); + + if (!deathmatch->value || !((int)dmflags->value & DF_NO_FALLING) ) + T_Damage (ent, world, world, dir, ent->s.origin, vec3_origin, damage, 0, 0, MOD_FALLING); + } + else + { + ent->s.event = EV_FALLSHORT; + return; + } +} + + + +/* +============= +P_WorldEffects +============= +*/ +void P_WorldEffects (void) +{ + qboolean breather; + qboolean envirosuit; + int waterlevel, old_waterlevel; + + if (current_player->movetype == MOVETYPE_NOCLIP) + { + current_player->air_finished = level.time + 12; // don't need air + return; + } + + waterlevel = current_player->waterlevel; + old_waterlevel = current_client->old_waterlevel; + current_client->old_waterlevel = waterlevel; + + breather = current_client->breather_framenum > level.framenum; + envirosuit = current_client->enviro_framenum > level.framenum; + + // + // if just entered a water volume, play a sound + // + if (!old_waterlevel && waterlevel) + { + PlayerNoise(current_player, current_player->s.origin, PNOISE_SELF); + if (current_player->watertype & CONTENTS_LAVA) + gi.sound (current_player, CHAN_BODY, gi.soundindex("player/lava_in.wav"), 1, ATTN_NORM, 0); + else if (current_player->watertype & CONTENTS_SLIME) + gi.sound (current_player, CHAN_BODY, gi.soundindex("player/watr_in.wav"), 1, ATTN_NORM, 0); + else if (current_player->watertype & CONTENTS_WATER) + gi.sound (current_player, CHAN_BODY, gi.soundindex("player/watr_in.wav"), 1, ATTN_NORM, 0); + current_player->flags |= FL_INWATER; + + // clear damage_debounce, so the pain sound will play immediately + current_player->damage_debounce_time = level.time - 1; + } + + // + // if just completely exited a water volume, play a sound + // + if (old_waterlevel && ! waterlevel) + { + PlayerNoise(current_player, current_player->s.origin, PNOISE_SELF); + gi.sound (current_player, CHAN_BODY, gi.soundindex("player/watr_out.wav"), 1, ATTN_NORM, 0); + current_player->flags &= ~FL_INWATER; + } + + // + // check for head just going under water + // + if (old_waterlevel != 3 && waterlevel == 3) + { + gi.sound (current_player, CHAN_BODY, gi.soundindex("player/watr_un.wav"), 1, ATTN_NORM, 0); + } + + // + // check for head just coming out of water + // + if (old_waterlevel == 3 && waterlevel != 3) + { + if (current_player->air_finished < level.time) + { // gasp for air + gi.sound (current_player, CHAN_VOICE, gi.soundindex("player/gasp1.wav"), 1, ATTN_NORM, 0); + PlayerNoise(current_player, current_player->s.origin, PNOISE_SELF); + } + else if (current_player->air_finished < level.time + 11) + { // just break surface + gi.sound (current_player, CHAN_VOICE, gi.soundindex("player/gasp2.wav"), 1, ATTN_NORM, 0); + } + } + + // + // check for drowning + // + if (waterlevel == 3) + { + // breather or envirosuit give air + if (breather || envirosuit) + { + current_player->air_finished = level.time + 10; + + if (((int)(current_client->breather_framenum - level.framenum) % 25) == 0) + { + if (!current_client->breather_sound) + gi.sound (current_player, CHAN_AUTO, gi.soundindex("player/u_breath1.wav"), 1, ATTN_NORM, 0); + else + gi.sound (current_player, CHAN_AUTO, gi.soundindex("player/u_breath2.wav"), 1, ATTN_NORM, 0); + current_client->breather_sound ^= 1; + PlayerNoise(current_player, current_player->s.origin, PNOISE_SELF); + //FIXME: release a bubble? + } + } + + // if out of air, start drowning + if (current_player->air_finished < level.time) + { // drown! + if (current_player->client->next_drown_time < level.time + && current_player->health > 0) + { + current_player->client->next_drown_time = level.time + 1; + + // take more damage the longer underwater + current_player->dmg += 2; + if (current_player->dmg > 15) + current_player->dmg = 15; + + // play a gurp sound instead of a normal pain sound + if (current_player->health <= current_player->dmg) + gi.sound (current_player, CHAN_VOICE, gi.soundindex("player/drown1.wav"), 1, ATTN_NORM, 0); + else if (rand()&1) + gi.sound (current_player, CHAN_VOICE, gi.soundindex("*gurp1.wav"), 1, ATTN_NORM, 0); + else + gi.sound (current_player, CHAN_VOICE, gi.soundindex("*gurp2.wav"), 1, ATTN_NORM, 0); + + current_player->pain_debounce_time = level.time; + + T_Damage (current_player, world, world, vec3_origin, current_player->s.origin, vec3_origin, current_player->dmg, 0, DAMAGE_NO_ARMOR, MOD_WATER); + } + } + } + else + { + current_player->air_finished = level.time + 12; + current_player->dmg = 2; + } + + // + // check for sizzle damage + // + if (waterlevel && (current_player->watertype&(CONTENTS_LAVA|CONTENTS_SLIME)) ) + { + if (current_player->watertype & CONTENTS_LAVA) + { + if (current_player->health > 0 + && current_player->pain_debounce_time <= level.time + && current_client->invincible_framenum < level.framenum) + { + if (rand()&1) + gi.sound (current_player, CHAN_VOICE, gi.soundindex("player/burn1.wav"), 1, ATTN_NORM, 0); + else + gi.sound (current_player, CHAN_VOICE, gi.soundindex("player/burn2.wav"), 1, ATTN_NORM, 0); + current_player->pain_debounce_time = level.time + 1; + } + + if (envirosuit) // take 1/3 damage with envirosuit + T_Damage (current_player, world, world, vec3_origin, current_player->s.origin, vec3_origin, 1*waterlevel, 0, 0, MOD_LAVA); + else + T_Damage (current_player, world, world, vec3_origin, current_player->s.origin, vec3_origin, 3*waterlevel, 0, 0, MOD_LAVA); + } + + if (current_player->watertype & CONTENTS_SLIME) + { + if (!envirosuit) + { // no damage from slime with envirosuit + T_Damage (current_player, world, world, vec3_origin, current_player->s.origin, vec3_origin, 1*waterlevel, 0, 0, MOD_SLIME); + } + } + } +} + + +/* +=============== +G_SetClientEffects +=============== +*/ +void G_SetClientEffects (edict_t *ent) +{ + int pa_type; + int remaining; + + ent->s.effects = 0; + ent->s.renderfx = 0; + + if (ent->health <= 0 || level.intermissiontime) + return; + + if (ent->powerarmor_time > level.time) + { + pa_type = PowerArmorType (ent); + if (pa_type == POWER_ARMOR_SCREEN) + { + ent->s.effects |= EF_POWERSCREEN; + } + else if (pa_type == POWER_ARMOR_SHIELD) + { + ent->s.effects |= EF_COLOR_SHELL; + ent->s.renderfx |= RF_SHELL_GREEN; + } + } + + if (ent->client->quad_framenum > level.framenum) + { + remaining = ent->client->quad_framenum - level.framenum; + if (remaining > 30 || (remaining & 4) ) + ent->s.effects |= EF_QUAD; + } + + if (ent->client->invincible_framenum > level.framenum) + { + remaining = ent->client->invincible_framenum - level.framenum; + if (remaining > 30 || (remaining & 4) ) + ent->s.effects |= EF_PENT; + } + + // show cheaters!!! + if (ent->flags & FL_GODMODE) + { + ent->s.effects |= EF_COLOR_SHELL; + ent->s.renderfx |= (RF_SHELL_RED|RF_SHELL_GREEN|RF_SHELL_BLUE); + } +} + + +/* +=============== +G_SetClientEvent +=============== +*/ +void G_SetClientEvent (edict_t *ent) +{ + if (ent->s.event) + return; + + if ( ent->groundentity && xyspeed > 225) + { + if ( (int)(current_client->bobtime+bobmove) != bobcycle ) + ent->s.event = EV_FOOTSTEP; + } +} + +/* +=============== +G_SetClientSound +=============== +*/ +void G_SetClientSound (edict_t *ent) +{ + char *weap; + + if (ent->client->pers.game_helpchanged != game.helpchanged) + { + ent->client->pers.game_helpchanged = game.helpchanged; + ent->client->pers.helpchanged = 1; + } + + // help beep (no more than three times) + if (ent->client->pers.helpchanged && ent->client->pers.helpchanged <= 3 && !(level.framenum&63) ) + { + ent->client->pers.helpchanged++; + gi.sound (ent, CHAN_VOICE, gi.soundindex ("misc/pc_up.wav"), 1, ATTN_STATIC, 0); + } + + + if (ent->client->pers.weapon) + weap = ent->client->pers.weapon->classname; + else + weap = ""; + + if (ent->waterlevel && (ent->watertype&(CONTENTS_LAVA|CONTENTS_SLIME)) ) + ent->s.sound = snd_fry; + else if (strcmp(weap, "weapon_railgun") == 0) + ent->s.sound = gi.soundindex("weapons/rg_hum.wav"); + else if (strcmp(weap, "weapon_bfg") == 0) + ent->s.sound = gi.soundindex("weapons/bfg_hum.wav"); + else if (ent->client->weapon_sound) + ent->s.sound = ent->client->weapon_sound; + else + ent->s.sound = 0; +} + +/* +=============== +G_SetClientFrame +=============== +*/ +void G_SetClientFrame (edict_t *ent) +{ + gclient_t *client; + qboolean duck, run; + + if (ent->s.modelindex != 255) + return; // not in the player model + + client = ent->client; + + if (client->ps.pmove.pm_flags & PMF_DUCKED) + duck = true; + else + duck = false; + if (xyspeed) + run = true; + else + run = false; + + // check for stand/duck and stop/go transitions + if (duck != client->anim_duck && client->anim_priority < ANIM_DEATH) + goto newanim; + if (run != client->anim_run && client->anim_priority == ANIM_BASIC) + goto newanim; + if (!ent->groundentity && client->anim_priority <= ANIM_WAVE) + goto newanim; + + if(client->anim_priority == ANIM_REVERSE) + { + if(ent->s.frame > client->anim_end) + { + ent->s.frame--; + return; + } + } + else if (ent->s.frame < client->anim_end) + { // continue an animation + ent->s.frame++; + return; + } + + if (client->anim_priority == ANIM_DEATH) + return; // stay there + if (client->anim_priority == ANIM_JUMP) + { + if (!ent->groundentity) + return; // stay there + ent->client->anim_priority = ANIM_WAVE; + ent->s.frame = FRAME_jump3; + ent->client->anim_end = FRAME_jump6; + return; + } + +newanim: + // return to either a running or standing frame + client->anim_priority = ANIM_BASIC; + client->anim_duck = duck; + client->anim_run = run; + + if (!ent->groundentity) + { + client->anim_priority = ANIM_JUMP; + if (ent->s.frame != FRAME_jump2) + ent->s.frame = FRAME_jump1; + client->anim_end = FRAME_jump2; + } + else if (run) + { // running + if (duck) + { + ent->s.frame = FRAME_crwalk1; + client->anim_end = FRAME_crwalk6; + } + else + { + ent->s.frame = FRAME_run1; + client->anim_end = FRAME_run6; + } + } + else + { // standing + if (duck) + { + ent->s.frame = FRAME_crstnd01; + client->anim_end = FRAME_crstnd19; + } + else + { + ent->s.frame = FRAME_stand01; + client->anim_end = FRAME_stand40; + } + } +} + + +/* +================= +ClientEndServerFrame + +Called for each player at the end of the server frame +and right after spawning +================= +*/ +void ClientEndServerFrame (edict_t *ent) +{ + float bobtime; + int i; + + current_player = ent; + current_client = ent->client; + + // + // If the origin or velocity have changed since ClientThink(), + // update the pmove values. This will happen when the client + // is pushed by a bmodel or kicked by an explosion. + // + // If it wasn't updated here, the view position would lag a frame + // behind the body position when pushed -- "sinking into plats" + // + for (i=0 ; i<3 ; i++) + { + current_client->ps.pmove.origin[i] = ent->s.origin[i]*8.0; + current_client->ps.pmove.velocity[i] = ent->velocity[i]*8.0; + } + + // + // If the end of unit layout is displayed, don't give + // the player any normal movement attributes + // + if (level.intermissiontime) + { + // FIXME: add view drifting here? + current_client->ps.blend[3] = 0; + current_client->ps.fov = 90; + G_SetStats (ent); + return; + } + + AngleVectors (ent->client->v_angle, forward, right, up); + + // burn from lava, etc + P_WorldEffects (); + + // + // set model angles from view angles so other things in + // the world can tell which direction you are looking + // + if (ent->client->v_angle[PITCH] > 180) + ent->s.angles[PITCH] = (-360 + ent->client->v_angle[PITCH])/3; + else + ent->s.angles[PITCH] = ent->client->v_angle[PITCH]/3; + ent->s.angles[YAW] = ent->client->v_angle[YAW]; + ent->s.angles[ROLL] = 0; + ent->s.angles[ROLL] = SV_CalcRoll (ent->s.angles, ent->velocity)*4; + + // + // calculate speed and cycle to be used for + // all cyclic walking effects + // + xyspeed = sqrt(ent->velocity[0]*ent->velocity[0] + ent->velocity[1]*ent->velocity[1]); + + if (xyspeed < 5) + { + bobmove = 0; + current_client->bobtime = 0; // start at beginning of cycle again + } + else if (ent->groundentity) + { // so bobbing only cycles when on ground + if (xyspeed > 210) + bobmove = 0.25; + else if (xyspeed > 100) + bobmove = 0.125; + else + bobmove = 0.0625; + } + + bobtime = (current_client->bobtime += bobmove); + + if (current_client->ps.pmove.pm_flags & PMF_DUCKED) + bobtime *= 4; + + bobcycle = (int)bobtime; + bobfracsin = fabs(sin(bobtime*M_PI)); + + // detect hitting the floor + P_FallingDamage (ent); + + // apply all the damage taken this frame + P_DamageFeedback (ent); + + // determine the view offsets + SV_CalcViewOffset (ent); + + // determine the gun offsets + SV_CalcGunOffset (ent); + + // determine the full screen color blend + // must be after viewoffset, so eye contents can be + // accurately determined + // FIXME: with client prediction, the contents + // should be determined by the client + SV_CalcBlend (ent); + + // chase cam stuff + if (ent->client->resp.spectator) + G_SetSpectatorStats(ent); + else + G_SetStats (ent); + G_CheckChaseStats(ent); + + G_SetClientEvent (ent); + + G_SetClientEffects (ent); + + G_SetClientSound (ent); + + G_SetClientFrame (ent); + + VectorCopy (ent->velocity, ent->client->oldvelocity); + VectorCopy (ent->client->ps.viewangles, ent->client->oldviewangles); + + // clear weapon kicks + VectorClear (ent->client->kick_origin); + VectorClear (ent->client->kick_angles); + + // if the scoreboard is up, update it + if (ent->client->showscores && !(level.framenum & 31) ) + { + DeathmatchScoreboardMessage (ent, ent->enemy); + gi.unicast (ent, false); + } +} + diff --git a/original/baseq2/p_weapon.c b/original/baseq2/p_weapon.c new file mode 100644 index 0000000..79daba1 --- /dev/null +++ b/original/baseq2/p_weapon.c @@ -0,0 +1,1434 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +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. + +*/ +// g_weapon.c + +#include "g_local.h" +#include "m_player.h" + + +static qboolean is_quad; +static byte is_silenced; + + +void weapon_grenade_fire (edict_t *ent, qboolean held); + + +static void P_ProjectSource (gclient_t *client, vec3_t point, vec3_t distance, vec3_t forward, vec3_t right, vec3_t result) +{ + vec3_t _distance; + + VectorCopy (distance, _distance); + if (client->pers.hand == LEFT_HANDED) + _distance[1] *= -1; + else if (client->pers.hand == CENTER_HANDED) + _distance[1] = 0; + G_ProjectSource (point, _distance, forward, right, result); +} + + +/* +=============== +PlayerNoise + +Each player can have two noise objects associated with it: +a personal noise (jumping, pain, weapon firing), and a weapon +target noise (bullet wall impacts) + +Monsters that don't directly see the player can move +to a noise in hopes of seeing the player from there. +=============== +*/ +void PlayerNoise(edict_t *who, vec3_t where, int type) +{ + edict_t *noise; + + if (type == PNOISE_WEAPON) + { + if (who->client->silencer_shots) + { + who->client->silencer_shots--; + return; + } + } + + if (deathmatch->value) + return; + + if (who->flags & FL_NOTARGET) + return; + + + if (!who->mynoise) + { + noise = G_Spawn(); + noise->classname = "player_noise"; + VectorSet (noise->mins, -8, -8, -8); + VectorSet (noise->maxs, 8, 8, 8); + noise->owner = who; + noise->svflags = SVF_NOCLIENT; + who->mynoise = noise; + + noise = G_Spawn(); + noise->classname = "player_noise"; + VectorSet (noise->mins, -8, -8, -8); + VectorSet (noise->maxs, 8, 8, 8); + noise->owner = who; + noise->svflags = SVF_NOCLIENT; + who->mynoise2 = noise; + } + + if (type == PNOISE_SELF || type == PNOISE_WEAPON) + { + noise = who->mynoise; + level.sound_entity = noise; + level.sound_entity_framenum = level.framenum; + } + else // type == PNOISE_IMPACT + { + noise = who->mynoise2; + level.sound2_entity = noise; + level.sound2_entity_framenum = level.framenum; + } + + VectorCopy (where, noise->s.origin); + VectorSubtract (where, noise->maxs, noise->absmin); + VectorAdd (where, noise->maxs, noise->absmax); + noise->teleport_time = level.time; + gi.linkentity (noise); +} + + +qboolean Pickup_Weapon (edict_t *ent, edict_t *other) +{ + int index; + gitem_t *ammo; + + index = ITEM_INDEX(ent->item); + + if ( ( ((int)(dmflags->value) & DF_WEAPONS_STAY) || coop->value) + && other->client->pers.inventory[index]) + { + if (!(ent->spawnflags & (DROPPED_ITEM | DROPPED_PLAYER_ITEM) ) ) + return false; // leave the weapon for others to pickup + } + + other->client->pers.inventory[index]++; + + if (!(ent->spawnflags & DROPPED_ITEM) ) + { + // give them some ammo with it + ammo = FindItem (ent->item->ammo); + if ( (int)dmflags->value & DF_INFINITE_AMMO ) + Add_Ammo (other, ammo, 1000); + else + Add_Ammo (other, ammo, ammo->quantity); + + if (! (ent->spawnflags & DROPPED_PLAYER_ITEM) ) + { + if (deathmatch->value) + { + if ((int)(dmflags->value) & DF_WEAPONS_STAY) + ent->flags |= FL_RESPAWN; + else + SetRespawn (ent, 30); + } + if (coop->value) + ent->flags |= FL_RESPAWN; + } + } + + if (other->client->pers.weapon != ent->item && + (other->client->pers.inventory[index] == 1) && + ( !deathmatch->value || other->client->pers.weapon == FindItem("blaster") ) ) + other->client->newweapon = ent->item; + + return true; +} + + +/* +=============== +ChangeWeapon + +The old weapon has been dropped all the way, so make the new one +current +=============== +*/ +void ChangeWeapon (edict_t *ent) +{ + int i; + + if (ent->client->grenade_time) + { + ent->client->grenade_time = level.time; + ent->client->weapon_sound = 0; + weapon_grenade_fire (ent, false); + ent->client->grenade_time = 0; + } + + ent->client->pers.lastweapon = ent->client->pers.weapon; + ent->client->pers.weapon = ent->client->newweapon; + ent->client->newweapon = NULL; + ent->client->machinegun_shots = 0; + + // set visible model + if (ent->s.modelindex == 255) { + if (ent->client->pers.weapon) + i = ((ent->client->pers.weapon->weapmodel & 0xff) << 8); + else + i = 0; + ent->s.skinnum = (ent - g_edicts - 1) | i; + } + + if (ent->client->pers.weapon && ent->client->pers.weapon->ammo) + ent->client->ammo_index = ITEM_INDEX(FindItem(ent->client->pers.weapon->ammo)); + else + ent->client->ammo_index = 0; + + if (!ent->client->pers.weapon) + { // dead + ent->client->ps.gunindex = 0; + return; + } + + ent->client->weaponstate = WEAPON_ACTIVATING; + ent->client->ps.gunframe = 0; + ent->client->ps.gunindex = gi.modelindex(ent->client->pers.weapon->view_model); + + ent->client->anim_priority = ANIM_PAIN; + if(ent->client->ps.pmove.pm_flags & PMF_DUCKED) + { + ent->s.frame = FRAME_crpain1; + ent->client->anim_end = FRAME_crpain4; + } + else + { + ent->s.frame = FRAME_pain301; + ent->client->anim_end = FRAME_pain304; + + } +} + +/* +================= +NoAmmoWeaponChange +================= +*/ +void NoAmmoWeaponChange (edict_t *ent) +{ + if ( ent->client->pers.inventory[ITEM_INDEX(FindItem("slugs"))] + && ent->client->pers.inventory[ITEM_INDEX(FindItem("railgun"))] ) + { + ent->client->newweapon = FindItem ("railgun"); + return; + } + if ( ent->client->pers.inventory[ITEM_INDEX(FindItem("cells"))] + && ent->client->pers.inventory[ITEM_INDEX(FindItem("hyperblaster"))] ) + { + ent->client->newweapon = FindItem ("hyperblaster"); + return; + } + if ( ent->client->pers.inventory[ITEM_INDEX(FindItem("bullets"))] + && ent->client->pers.inventory[ITEM_INDEX(FindItem("chaingun"))] ) + { + ent->client->newweapon = FindItem ("chaingun"); + return; + } + if ( ent->client->pers.inventory[ITEM_INDEX(FindItem("bullets"))] + && ent->client->pers.inventory[ITEM_INDEX(FindItem("machinegun"))] ) + { + ent->client->newweapon = FindItem ("machinegun"); + return; + } + if ( ent->client->pers.inventory[ITEM_INDEX(FindItem("shells"))] > 1 + && ent->client->pers.inventory[ITEM_INDEX(FindItem("super shotgun"))] ) + { + ent->client->newweapon = FindItem ("super shotgun"); + return; + } + if ( ent->client->pers.inventory[ITEM_INDEX(FindItem("shells"))] + && ent->client->pers.inventory[ITEM_INDEX(FindItem("shotgun"))] ) + { + ent->client->newweapon = FindItem ("shotgun"); + return; + } + ent->client->newweapon = FindItem ("blaster"); +} + +/* +================= +Think_Weapon + +Called by ClientBeginServerFrame and ClientThink +================= +*/ +void Think_Weapon (edict_t *ent) +{ + // if just died, put the weapon away + if (ent->health < 1) + { + ent->client->newweapon = NULL; + ChangeWeapon (ent); + } + + // call active weapon think routine + if (ent->client->pers.weapon && ent->client->pers.weapon->weaponthink) + { + is_quad = (ent->client->quad_framenum > level.framenum); + if (ent->client->silencer_shots) + is_silenced = MZ_SILENCED; + else + is_silenced = 0; + ent->client->pers.weapon->weaponthink (ent); + } +} + + +/* +================ +Use_Weapon + +Make the weapon ready if there is ammo +================ +*/ +void Use_Weapon (edict_t *ent, gitem_t *item) +{ + int ammo_index; + gitem_t *ammo_item; + + // see if we're already using it + if (item == ent->client->pers.weapon) + return; + + if (item->ammo && !g_select_empty->value && !(item->flags & IT_AMMO)) + { + ammo_item = FindItem(item->ammo); + ammo_index = ITEM_INDEX(ammo_item); + + if (!ent->client->pers.inventory[ammo_index]) + { + gi.cprintf (ent, PRINT_HIGH, "No %s for %s.\n", ammo_item->pickup_name, item->pickup_name); + return; + } + + if (ent->client->pers.inventory[ammo_index] < item->quantity) + { + gi.cprintf (ent, PRINT_HIGH, "Not enough %s for %s.\n", ammo_item->pickup_name, item->pickup_name); + return; + } + } + + // change to this weapon when down + ent->client->newweapon = item; +} + + + +/* +================ +Drop_Weapon +================ +*/ +void Drop_Weapon (edict_t *ent, gitem_t *item) +{ + int index; + + if ((int)(dmflags->value) & DF_WEAPONS_STAY) + return; + + index = ITEM_INDEX(item); + // see if we're already using it + if ( ((item == ent->client->pers.weapon) || (item == ent->client->newweapon))&& (ent->client->pers.inventory[index] == 1) ) + { + gi.cprintf (ent, PRINT_HIGH, "Can't drop current weapon\n"); + return; + } + + Drop_Item (ent, item); + ent->client->pers.inventory[index]--; +} + + +/* +================ +Weapon_Generic + +A generic function to handle the basics of weapon thinking +================ +*/ +#define FRAME_FIRE_FIRST (FRAME_ACTIVATE_LAST + 1) +#define FRAME_IDLE_FIRST (FRAME_FIRE_LAST + 1) +#define FRAME_DEACTIVATE_FIRST (FRAME_IDLE_LAST + 1) + +void Weapon_Generic (edict_t *ent, int FRAME_ACTIVATE_LAST, int FRAME_FIRE_LAST, int FRAME_IDLE_LAST, int FRAME_DEACTIVATE_LAST, int *pause_frames, int *fire_frames, void (*fire)(edict_t *ent)) +{ + int n; + + if(ent->deadflag || ent->s.modelindex != 255) // VWep animations screw up corpses + { + return; + } + + if (ent->client->weaponstate == WEAPON_DROPPING) + { + if (ent->client->ps.gunframe == FRAME_DEACTIVATE_LAST) + { + ChangeWeapon (ent); + return; + } + else if ((FRAME_DEACTIVATE_LAST - ent->client->ps.gunframe) == 4) + { + ent->client->anim_priority = ANIM_REVERSE; + if(ent->client->ps.pmove.pm_flags & PMF_DUCKED) + { + ent->s.frame = FRAME_crpain4+1; + ent->client->anim_end = FRAME_crpain1; + } + else + { + ent->s.frame = FRAME_pain304+1; + ent->client->anim_end = FRAME_pain301; + + } + } + + ent->client->ps.gunframe++; + return; + } + + if (ent->client->weaponstate == WEAPON_ACTIVATING) + { + if (ent->client->ps.gunframe == FRAME_ACTIVATE_LAST) + { + ent->client->weaponstate = WEAPON_READY; + ent->client->ps.gunframe = FRAME_IDLE_FIRST; + return; + } + + ent->client->ps.gunframe++; + return; + } + + if ((ent->client->newweapon) && (ent->client->weaponstate != WEAPON_FIRING)) + { + ent->client->weaponstate = WEAPON_DROPPING; + ent->client->ps.gunframe = FRAME_DEACTIVATE_FIRST; + + if ((FRAME_DEACTIVATE_LAST - FRAME_DEACTIVATE_FIRST) < 4) + { + ent->client->anim_priority = ANIM_REVERSE; + if(ent->client->ps.pmove.pm_flags & PMF_DUCKED) + { + ent->s.frame = FRAME_crpain4+1; + ent->client->anim_end = FRAME_crpain1; + } + else + { + ent->s.frame = FRAME_pain304+1; + ent->client->anim_end = FRAME_pain301; + + } + } + return; + } + + if (ent->client->weaponstate == WEAPON_READY) + { + if ( ((ent->client->latched_buttons|ent->client->buttons) & BUTTON_ATTACK) ) + { + ent->client->latched_buttons &= ~BUTTON_ATTACK; + if ((!ent->client->ammo_index) || + ( ent->client->pers.inventory[ent->client->ammo_index] >= ent->client->pers.weapon->quantity)) + { + ent->client->ps.gunframe = FRAME_FIRE_FIRST; + ent->client->weaponstate = WEAPON_FIRING; + + // start the animation + ent->client->anim_priority = ANIM_ATTACK; + if (ent->client->ps.pmove.pm_flags & PMF_DUCKED) + { + ent->s.frame = FRAME_crattak1-1; + ent->client->anim_end = FRAME_crattak9; + } + else + { + ent->s.frame = FRAME_attack1-1; + ent->client->anim_end = FRAME_attack8; + } + } + else + { + if (level.time >= ent->pain_debounce_time) + { + gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/noammo.wav"), 1, ATTN_NORM, 0); + ent->pain_debounce_time = level.time + 1; + } + NoAmmoWeaponChange (ent); + } + } + else + { + if (ent->client->ps.gunframe == FRAME_IDLE_LAST) + { + ent->client->ps.gunframe = FRAME_IDLE_FIRST; + return; + } + + if (pause_frames) + { + for (n = 0; pause_frames[n]; n++) + { + if (ent->client->ps.gunframe == pause_frames[n]) + { + if (rand()&15) + return; + } + } + } + + ent->client->ps.gunframe++; + return; + } + } + + if (ent->client->weaponstate == WEAPON_FIRING) + { + for (n = 0; fire_frames[n]; n++) + { + if (ent->client->ps.gunframe == fire_frames[n]) + { + if (ent->client->quad_framenum > level.framenum) + gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage3.wav"), 1, ATTN_NORM, 0); + + fire (ent); + break; + } + } + + if (!fire_frames[n]) + ent->client->ps.gunframe++; + + if (ent->client->ps.gunframe == FRAME_IDLE_FIRST+1) + ent->client->weaponstate = WEAPON_READY; + } +} + + +/* +====================================================================== + +GRENADE + +====================================================================== +*/ + +#define GRENADE_TIMER 3.0 +#define GRENADE_MINSPEED 400 +#define GRENADE_MAXSPEED 800 + +void weapon_grenade_fire (edict_t *ent, qboolean held) +{ + vec3_t offset; + vec3_t forward, right; + vec3_t start; + int damage = 125; + float timer; + int speed; + float radius; + + radius = damage+40; + if (is_quad) + damage *= 4; + + VectorSet(offset, 8, 8, ent->viewheight-8); + AngleVectors (ent->client->v_angle, forward, right, NULL); + P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start); + + timer = ent->client->grenade_time - level.time; + speed = GRENADE_MINSPEED + (GRENADE_TIMER - timer) * ((GRENADE_MAXSPEED - GRENADE_MINSPEED) / GRENADE_TIMER); + fire_grenade2 (ent, start, forward, damage, speed, timer, radius, held); + + if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) ) + ent->client->pers.inventory[ent->client->ammo_index]--; + + ent->client->grenade_time = level.time + 1.0; + + if(ent->deadflag || ent->s.modelindex != 255) // VWep animations screw up corpses + { + return; + } + + if (ent->health <= 0) + return; + + if (ent->client->ps.pmove.pm_flags & PMF_DUCKED) + { + ent->client->anim_priority = ANIM_ATTACK; + ent->s.frame = FRAME_crattak1-1; + ent->client->anim_end = FRAME_crattak3; + } + else + { + ent->client->anim_priority = ANIM_REVERSE; + ent->s.frame = FRAME_wave08; + ent->client->anim_end = FRAME_wave01; + } +} + +void Weapon_Grenade (edict_t *ent) +{ + if ((ent->client->newweapon) && (ent->client->weaponstate == WEAPON_READY)) + { + ChangeWeapon (ent); + return; + } + + if (ent->client->weaponstate == WEAPON_ACTIVATING) + { + ent->client->weaponstate = WEAPON_READY; + ent->client->ps.gunframe = 16; + return; + } + + if (ent->client->weaponstate == WEAPON_READY) + { + if ( ((ent->client->latched_buttons|ent->client->buttons) & BUTTON_ATTACK) ) + { + ent->client->latched_buttons &= ~BUTTON_ATTACK; + if (ent->client->pers.inventory[ent->client->ammo_index]) + { + ent->client->ps.gunframe = 1; + ent->client->weaponstate = WEAPON_FIRING; + ent->client->grenade_time = 0; + } + else + { + if (level.time >= ent->pain_debounce_time) + { + gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/noammo.wav"), 1, ATTN_NORM, 0); + ent->pain_debounce_time = level.time + 1; + } + NoAmmoWeaponChange (ent); + } + return; + } + + if ((ent->client->ps.gunframe == 29) || (ent->client->ps.gunframe == 34) || (ent->client->ps.gunframe == 39) || (ent->client->ps.gunframe == 48)) + { + if (rand()&15) + return; + } + + if (++ent->client->ps.gunframe > 48) + ent->client->ps.gunframe = 16; + return; + } + + if (ent->client->weaponstate == WEAPON_FIRING) + { + if (ent->client->ps.gunframe == 5) + gi.sound(ent, CHAN_WEAPON, gi.soundindex("weapons/hgrena1b.wav"), 1, ATTN_NORM, 0); + + if (ent->client->ps.gunframe == 11) + { + if (!ent->client->grenade_time) + { + ent->client->grenade_time = level.time + GRENADE_TIMER + 0.2; + ent->client->weapon_sound = gi.soundindex("weapons/hgrenc1b.wav"); + } + + // they waited too long, detonate it in their hand + if (!ent->client->grenade_blew_up && level.time >= ent->client->grenade_time) + { + ent->client->weapon_sound = 0; + weapon_grenade_fire (ent, true); + ent->client->grenade_blew_up = true; + } + + if (ent->client->buttons & BUTTON_ATTACK) + return; + + if (ent->client->grenade_blew_up) + { + if (level.time >= ent->client->grenade_time) + { + ent->client->ps.gunframe = 15; + ent->client->grenade_blew_up = false; + } + else + { + return; + } + } + } + + if (ent->client->ps.gunframe == 12) + { + ent->client->weapon_sound = 0; + weapon_grenade_fire (ent, false); + } + + if ((ent->client->ps.gunframe == 15) && (level.time < ent->client->grenade_time)) + return; + + ent->client->ps.gunframe++; + + if (ent->client->ps.gunframe == 16) + { + ent->client->grenade_time = 0; + ent->client->weaponstate = WEAPON_READY; + } + } +} + +/* +====================================================================== + +GRENADE LAUNCHER + +====================================================================== +*/ + +void weapon_grenadelauncher_fire (edict_t *ent) +{ + vec3_t offset; + vec3_t forward, right; + vec3_t start; + int damage = 120; + float radius; + + radius = damage+40; + if (is_quad) + damage *= 4; + + VectorSet(offset, 8, 8, ent->viewheight-8); + AngleVectors (ent->client->v_angle, forward, right, NULL); + P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start); + + VectorScale (forward, -2, ent->client->kick_origin); + ent->client->kick_angles[0] = -1; + + fire_grenade (ent, start, forward, damage, 600, 2.5, radius); + + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + gi.WriteByte (MZ_GRENADE | is_silenced); + gi.multicast (ent->s.origin, MULTICAST_PVS); + + ent->client->ps.gunframe++; + + PlayerNoise(ent, start, PNOISE_WEAPON); + + if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) ) + ent->client->pers.inventory[ent->client->ammo_index]--; +} + +void Weapon_GrenadeLauncher (edict_t *ent) +{ + static int pause_frames[] = {34, 51, 59, 0}; + static int fire_frames[] = {6, 0}; + + Weapon_Generic (ent, 5, 16, 59, 64, pause_frames, fire_frames, weapon_grenadelauncher_fire); +} + +/* +====================================================================== + +ROCKET + +====================================================================== +*/ + +void Weapon_RocketLauncher_Fire (edict_t *ent) +{ + vec3_t offset, start; + vec3_t forward, right; + int damage; + float damage_radius; + int radius_damage; + + damage = 100 + (int)(random() * 20.0); + radius_damage = 120; + damage_radius = 120; + if (is_quad) + { + damage *= 4; + radius_damage *= 4; + } + + AngleVectors (ent->client->v_angle, forward, right, NULL); + + VectorScale (forward, -2, ent->client->kick_origin); + ent->client->kick_angles[0] = -1; + + VectorSet(offset, 8, 8, ent->viewheight-8); + P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start); + fire_rocket (ent, start, forward, damage, 650, damage_radius, radius_damage); + + // send muzzle flash + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + gi.WriteByte (MZ_ROCKET | is_silenced); + gi.multicast (ent->s.origin, MULTICAST_PVS); + + ent->client->ps.gunframe++; + + PlayerNoise(ent, start, PNOISE_WEAPON); + + if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) ) + ent->client->pers.inventory[ent->client->ammo_index]--; +} + +void Weapon_RocketLauncher (edict_t *ent) +{ + static int pause_frames[] = {25, 33, 42, 50, 0}; + static int fire_frames[] = {5, 0}; + + Weapon_Generic (ent, 4, 12, 50, 54, pause_frames, fire_frames, Weapon_RocketLauncher_Fire); +} + + +/* +====================================================================== + +BLASTER / HYPERBLASTER + +====================================================================== +*/ + +void Blaster_Fire (edict_t *ent, vec3_t g_offset, int damage, qboolean hyper, int effect) +{ + vec3_t forward, right; + vec3_t start; + vec3_t offset; + + if (is_quad) + damage *= 4; + AngleVectors (ent->client->v_angle, forward, right, NULL); + VectorSet(offset, 24, 8, ent->viewheight-8); + VectorAdd (offset, g_offset, offset); + P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start); + + VectorScale (forward, -2, ent->client->kick_origin); + ent->client->kick_angles[0] = -1; + + fire_blaster (ent, start, forward, damage, 1000, effect, hyper); + + // send muzzle flash + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + if (hyper) + gi.WriteByte (MZ_HYPERBLASTER | is_silenced); + else + gi.WriteByte (MZ_BLASTER | is_silenced); + gi.multicast (ent->s.origin, MULTICAST_PVS); + + PlayerNoise(ent, start, PNOISE_WEAPON); +} + + +void Weapon_Blaster_Fire (edict_t *ent) +{ + int damage; + + if (deathmatch->value) + damage = 15; + else + damage = 10; + Blaster_Fire (ent, vec3_origin, damage, false, EF_BLASTER); + ent->client->ps.gunframe++; +} + +void Weapon_Blaster (edict_t *ent) +{ + static int pause_frames[] = {19, 32, 0}; + static int fire_frames[] = {5, 0}; + + Weapon_Generic (ent, 4, 8, 52, 55, pause_frames, fire_frames, Weapon_Blaster_Fire); +} + + +void Weapon_HyperBlaster_Fire (edict_t *ent) +{ + float rotation; + vec3_t offset; + int effect; + int damage; + + ent->client->weapon_sound = gi.soundindex("weapons/hyprbl1a.wav"); + + if (!(ent->client->buttons & BUTTON_ATTACK)) + { + ent->client->ps.gunframe++; + } + else + { + if (! ent->client->pers.inventory[ent->client->ammo_index] ) + { + if (level.time >= ent->pain_debounce_time) + { + gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/noammo.wav"), 1, ATTN_NORM, 0); + ent->pain_debounce_time = level.time + 1; + } + NoAmmoWeaponChange (ent); + } + else + { + rotation = (ent->client->ps.gunframe - 5) * 2*M_PI/6; + offset[0] = -4 * sin(rotation); + offset[1] = 0; + offset[2] = 4 * cos(rotation); + + if ((ent->client->ps.gunframe == 6) || (ent->client->ps.gunframe == 9)) + effect = EF_HYPERBLASTER; + else + effect = 0; + if (deathmatch->value) + damage = 15; + else + damage = 20; + Blaster_Fire (ent, offset, damage, true, effect); + if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) ) + ent->client->pers.inventory[ent->client->ammo_index]--; + + ent->client->anim_priority = ANIM_ATTACK; + if (ent->client->ps.pmove.pm_flags & PMF_DUCKED) + { + ent->s.frame = FRAME_crattak1 - 1; + ent->client->anim_end = FRAME_crattak9; + } + else + { + ent->s.frame = FRAME_attack1 - 1; + ent->client->anim_end = FRAME_attack8; + } + } + + ent->client->ps.gunframe++; + if (ent->client->ps.gunframe == 12 && ent->client->pers.inventory[ent->client->ammo_index]) + ent->client->ps.gunframe = 6; + } + + if (ent->client->ps.gunframe == 12) + { + gi.sound(ent, CHAN_AUTO, gi.soundindex("weapons/hyprbd1a.wav"), 1, ATTN_NORM, 0); + ent->client->weapon_sound = 0; + } + +} + +void Weapon_HyperBlaster (edict_t *ent) +{ + static int pause_frames[] = {0}; + static int fire_frames[] = {6, 7, 8, 9, 10, 11, 0}; + + Weapon_Generic (ent, 5, 20, 49, 53, pause_frames, fire_frames, Weapon_HyperBlaster_Fire); +} + +/* +====================================================================== + +MACHINEGUN / CHAINGUN + +====================================================================== +*/ + +void Machinegun_Fire (edict_t *ent) +{ + int i; + vec3_t start; + vec3_t forward, right; + vec3_t angles; + int damage = 8; + int kick = 2; + vec3_t offset; + + if (!(ent->client->buttons & BUTTON_ATTACK)) + { + ent->client->machinegun_shots = 0; + ent->client->ps.gunframe++; + return; + } + + if (ent->client->ps.gunframe == 5) + ent->client->ps.gunframe = 4; + else + ent->client->ps.gunframe = 5; + + if (ent->client->pers.inventory[ent->client->ammo_index] < 1) + { + ent->client->ps.gunframe = 6; + if (level.time >= ent->pain_debounce_time) + { + gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/noammo.wav"), 1, ATTN_NORM, 0); + ent->pain_debounce_time = level.time + 1; + } + NoAmmoWeaponChange (ent); + return; + } + + if (is_quad) + { + damage *= 4; + kick *= 4; + } + + for (i=1 ; i<3 ; i++) + { + ent->client->kick_origin[i] = crandom() * 0.35; + ent->client->kick_angles[i] = crandom() * 0.7; + } + ent->client->kick_origin[0] = crandom() * 0.35; + ent->client->kick_angles[0] = ent->client->machinegun_shots * -1.5; + + // raise the gun as it is firing + if (!deathmatch->value) + { + ent->client->machinegun_shots++; + if (ent->client->machinegun_shots > 9) + ent->client->machinegun_shots = 9; + } + + // get start / end positions + VectorAdd (ent->client->v_angle, ent->client->kick_angles, angles); + AngleVectors (angles, forward, right, NULL); + VectorSet(offset, 0, 8, ent->viewheight-8); + P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start); + fire_bullet (ent, start, forward, damage, kick, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, MOD_MACHINEGUN); + + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + gi.WriteByte (MZ_MACHINEGUN | is_silenced); + gi.multicast (ent->s.origin, MULTICAST_PVS); + + PlayerNoise(ent, start, PNOISE_WEAPON); + + if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) ) + ent->client->pers.inventory[ent->client->ammo_index]--; + + ent->client->anim_priority = ANIM_ATTACK; + if (ent->client->ps.pmove.pm_flags & PMF_DUCKED) + { + ent->s.frame = FRAME_crattak1 - (int) (random()+0.25); + ent->client->anim_end = FRAME_crattak9; + } + else + { + ent->s.frame = FRAME_attack1 - (int) (random()+0.25); + ent->client->anim_end = FRAME_attack8; + } +} + +void Weapon_Machinegun (edict_t *ent) +{ + static int pause_frames[] = {23, 45, 0}; + static int fire_frames[] = {4, 5, 0}; + + Weapon_Generic (ent, 3, 5, 45, 49, pause_frames, fire_frames, Machinegun_Fire); +} + +void Chaingun_Fire (edict_t *ent) +{ + int i; + int shots; + vec3_t start; + vec3_t forward, right, up; + float r, u; + vec3_t offset; + int damage; + int kick = 2; + + if (deathmatch->value) + damage = 6; + else + damage = 8; + + if (ent->client->ps.gunframe == 5) + gi.sound(ent, CHAN_AUTO, gi.soundindex("weapons/chngnu1a.wav"), 1, ATTN_IDLE, 0); + + if ((ent->client->ps.gunframe == 14) && !(ent->client->buttons & BUTTON_ATTACK)) + { + ent->client->ps.gunframe = 32; + ent->client->weapon_sound = 0; + return; + } + else if ((ent->client->ps.gunframe == 21) && (ent->client->buttons & BUTTON_ATTACK) + && ent->client->pers.inventory[ent->client->ammo_index]) + { + ent->client->ps.gunframe = 15; + } + else + { + ent->client->ps.gunframe++; + } + + if (ent->client->ps.gunframe == 22) + { + ent->client->weapon_sound = 0; + gi.sound(ent, CHAN_AUTO, gi.soundindex("weapons/chngnd1a.wav"), 1, ATTN_IDLE, 0); + } + else + { + ent->client->weapon_sound = gi.soundindex("weapons/chngnl1a.wav"); + } + + ent->client->anim_priority = ANIM_ATTACK; + if (ent->client->ps.pmove.pm_flags & PMF_DUCKED) + { + ent->s.frame = FRAME_crattak1 - (ent->client->ps.gunframe & 1); + ent->client->anim_end = FRAME_crattak9; + } + else + { + ent->s.frame = FRAME_attack1 - (ent->client->ps.gunframe & 1); + ent->client->anim_end = FRAME_attack8; + } + + if (ent->client->ps.gunframe <= 9) + shots = 1; + else if (ent->client->ps.gunframe <= 14) + { + if (ent->client->buttons & BUTTON_ATTACK) + shots = 2; + else + shots = 1; + } + else + shots = 3; + + if (ent->client->pers.inventory[ent->client->ammo_index] < shots) + shots = ent->client->pers.inventory[ent->client->ammo_index]; + + if (!shots) + { + if (level.time >= ent->pain_debounce_time) + { + gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/noammo.wav"), 1, ATTN_NORM, 0); + ent->pain_debounce_time = level.time + 1; + } + NoAmmoWeaponChange (ent); + return; + } + + if (is_quad) + { + damage *= 4; + kick *= 4; + } + + for (i=0 ; i<3 ; i++) + { + ent->client->kick_origin[i] = crandom() * 0.35; + ent->client->kick_angles[i] = crandom() * 0.7; + } + + for (i=0 ; iclient->v_angle, forward, right, up); + r = 7 + crandom()*4; + u = crandom()*4; + VectorSet(offset, 0, r, u + ent->viewheight-8); + P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start); + + fire_bullet (ent, start, forward, damage, kick, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, MOD_CHAINGUN); + } + + // send muzzle flash + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + gi.WriteByte ((MZ_CHAINGUN1 + shots - 1) | is_silenced); + gi.multicast (ent->s.origin, MULTICAST_PVS); + + PlayerNoise(ent, start, PNOISE_WEAPON); + + if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) ) + ent->client->pers.inventory[ent->client->ammo_index] -= shots; +} + + +void Weapon_Chaingun (edict_t *ent) +{ + static int pause_frames[] = {38, 43, 51, 61, 0}; + static int fire_frames[] = {5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 0}; + + Weapon_Generic (ent, 4, 31, 61, 64, pause_frames, fire_frames, Chaingun_Fire); +} + + +/* +====================================================================== + +SHOTGUN / SUPERSHOTGUN + +====================================================================== +*/ + +void weapon_shotgun_fire (edict_t *ent) +{ + vec3_t start; + vec3_t forward, right; + vec3_t offset; + int damage = 4; + int kick = 8; + + if (ent->client->ps.gunframe == 9) + { + ent->client->ps.gunframe++; + return; + } + + AngleVectors (ent->client->v_angle, forward, right, NULL); + + VectorScale (forward, -2, ent->client->kick_origin); + ent->client->kick_angles[0] = -2; + + VectorSet(offset, 0, 8, ent->viewheight-8); + P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start); + + if (is_quad) + { + damage *= 4; + kick *= 4; + } + + if (deathmatch->value) + fire_shotgun (ent, start, forward, damage, kick, 500, 500, DEFAULT_DEATHMATCH_SHOTGUN_COUNT, MOD_SHOTGUN); + else + fire_shotgun (ent, start, forward, damage, kick, 500, 500, DEFAULT_SHOTGUN_COUNT, MOD_SHOTGUN); + + // send muzzle flash + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + gi.WriteByte (MZ_SHOTGUN | is_silenced); + gi.multicast (ent->s.origin, MULTICAST_PVS); + + ent->client->ps.gunframe++; + PlayerNoise(ent, start, PNOISE_WEAPON); + + if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) ) + ent->client->pers.inventory[ent->client->ammo_index]--; +} + +void Weapon_Shotgun (edict_t *ent) +{ + static int pause_frames[] = {22, 28, 34, 0}; + static int fire_frames[] = {8, 9, 0}; + + Weapon_Generic (ent, 7, 18, 36, 39, pause_frames, fire_frames, weapon_shotgun_fire); +} + + +void weapon_supershotgun_fire (edict_t *ent) +{ + vec3_t start; + vec3_t forward, right; + vec3_t offset; + vec3_t v; + int damage = 6; + int kick = 12; + + AngleVectors (ent->client->v_angle, forward, right, NULL); + + VectorScale (forward, -2, ent->client->kick_origin); + ent->client->kick_angles[0] = -2; + + VectorSet(offset, 0, 8, ent->viewheight-8); + P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start); + + if (is_quad) + { + damage *= 4; + kick *= 4; + } + + v[PITCH] = ent->client->v_angle[PITCH]; + v[YAW] = ent->client->v_angle[YAW] - 5; + v[ROLL] = ent->client->v_angle[ROLL]; + AngleVectors (v, forward, NULL, NULL); + fire_shotgun (ent, start, forward, damage, kick, DEFAULT_SHOTGUN_HSPREAD, DEFAULT_SHOTGUN_VSPREAD, DEFAULT_SSHOTGUN_COUNT/2, MOD_SSHOTGUN); + v[YAW] = ent->client->v_angle[YAW] + 5; + AngleVectors (v, forward, NULL, NULL); + fire_shotgun (ent, start, forward, damage, kick, DEFAULT_SHOTGUN_HSPREAD, DEFAULT_SHOTGUN_VSPREAD, DEFAULT_SSHOTGUN_COUNT/2, MOD_SSHOTGUN); + + // send muzzle flash + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + gi.WriteByte (MZ_SSHOTGUN | is_silenced); + gi.multicast (ent->s.origin, MULTICAST_PVS); + + ent->client->ps.gunframe++; + PlayerNoise(ent, start, PNOISE_WEAPON); + + if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) ) + ent->client->pers.inventory[ent->client->ammo_index] -= 2; +} + +void Weapon_SuperShotgun (edict_t *ent) +{ + static int pause_frames[] = {29, 42, 57, 0}; + static int fire_frames[] = {7, 0}; + + Weapon_Generic (ent, 6, 17, 57, 61, pause_frames, fire_frames, weapon_supershotgun_fire); +} + + + +/* +====================================================================== + +RAILGUN + +====================================================================== +*/ + +void weapon_railgun_fire (edict_t *ent) +{ + vec3_t start; + vec3_t forward, right; + vec3_t offset; + int damage; + int kick; + + if (deathmatch->value) + { // normal damage is too extreme in dm + damage = 100; + kick = 200; + } + else + { + damage = 150; + kick = 250; + } + + if (is_quad) + { + damage *= 4; + kick *= 4; + } + + AngleVectors (ent->client->v_angle, forward, right, NULL); + + VectorScale (forward, -3, ent->client->kick_origin); + ent->client->kick_angles[0] = -3; + + VectorSet(offset, 0, 7, ent->viewheight-8); + P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start); + fire_rail (ent, start, forward, damage, kick); + + // send muzzle flash + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + gi.WriteByte (MZ_RAILGUN | is_silenced); + gi.multicast (ent->s.origin, MULTICAST_PVS); + + ent->client->ps.gunframe++; + PlayerNoise(ent, start, PNOISE_WEAPON); + + if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) ) + ent->client->pers.inventory[ent->client->ammo_index]--; +} + + +void Weapon_Railgun (edict_t *ent) +{ + static int pause_frames[] = {56, 0}; + static int fire_frames[] = {4, 0}; + + Weapon_Generic (ent, 3, 18, 56, 61, pause_frames, fire_frames, weapon_railgun_fire); +} + + +/* +====================================================================== + +BFG10K + +====================================================================== +*/ + +void weapon_bfg_fire (edict_t *ent) +{ + vec3_t offset, start; + vec3_t forward, right; + int damage; + float damage_radius = 1000; + + if (deathmatch->value) + damage = 200; + else + damage = 500; + + if (ent->client->ps.gunframe == 9) + { + // send muzzle flash + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + gi.WriteByte (MZ_BFG | is_silenced); + gi.multicast (ent->s.origin, MULTICAST_PVS); + + ent->client->ps.gunframe++; + + PlayerNoise(ent, start, PNOISE_WEAPON); + return; + } + + // cells can go down during windup (from power armor hits), so + // check again and abort firing if we don't have enough now + if (ent->client->pers.inventory[ent->client->ammo_index] < 50) + { + ent->client->ps.gunframe++; + return; + } + + if (is_quad) + damage *= 4; + + AngleVectors (ent->client->v_angle, forward, right, NULL); + + VectorScale (forward, -2, ent->client->kick_origin); + + // make a big pitch kick with an inverse fall + ent->client->v_dmg_pitch = -40; + ent->client->v_dmg_roll = crandom()*8; + ent->client->v_dmg_time = level.time + DAMAGE_TIME; + + VectorSet(offset, 8, 8, ent->viewheight-8); + P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start); + fire_bfg (ent, start, forward, damage, 400, damage_radius); + + ent->client->ps.gunframe++; + + PlayerNoise(ent, start, PNOISE_WEAPON); + + if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) ) + ent->client->pers.inventory[ent->client->ammo_index] -= 50; +} + +void Weapon_BFG (edict_t *ent) +{ + static int pause_frames[] = {39, 45, 50, 55, 0}; + static int fire_frames[] = {9, 17, 0}; + + Weapon_Generic (ent, 8, 32, 55, 58, pause_frames, fire_frames, weapon_bfg_fire); +} + + +//====================================================================== diff --git a/original/baseq2/q_shared.c b/original/baseq2/q_shared.c new file mode 100644 index 0000000..9f640a3 --- /dev/null +++ b/original/baseq2/q_shared.c @@ -0,0 +1,1418 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +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. + +*/ +#include "q_shared.h" + +#define DEG2RAD( a ) ( a * M_PI ) / 180.0F + +vec3_t vec3_origin = {0,0,0}; + +//============================================================================ + +#ifdef _WIN32 +#pragma optimize( "", off ) +#endif + +void RotatePointAroundVector( vec3_t dst, const vec3_t dir, const vec3_t point, float degrees ) +{ + float m[3][3]; + float im[3][3]; + float zrot[3][3]; + float tmpmat[3][3]; + float rot[3][3]; + int i; + vec3_t vr, vup, vf; + + vf[0] = dir[0]; + vf[1] = dir[1]; + vf[2] = dir[2]; + + PerpendicularVector( vr, dir ); + CrossProduct( vr, vf, vup ); + + m[0][0] = vr[0]; + m[1][0] = vr[1]; + m[2][0] = vr[2]; + + m[0][1] = vup[0]; + m[1][1] = vup[1]; + m[2][1] = vup[2]; + + m[0][2] = vf[0]; + m[1][2] = vf[1]; + m[2][2] = vf[2]; + + memcpy( im, m, sizeof( im ) ); + + im[0][1] = m[1][0]; + im[0][2] = m[2][0]; + im[1][0] = m[0][1]; + im[1][2] = m[2][1]; + im[2][0] = m[0][2]; + im[2][1] = m[1][2]; + + memset( zrot, 0, sizeof( zrot ) ); + zrot[0][0] = zrot[1][1] = zrot[2][2] = 1.0F; + + zrot[0][0] = cos( DEG2RAD( degrees ) ); + zrot[0][1] = sin( DEG2RAD( degrees ) ); + zrot[1][0] = -sin( DEG2RAD( degrees ) ); + zrot[1][1] = cos( DEG2RAD( degrees ) ); + + R_ConcatRotations( m, zrot, tmpmat ); + R_ConcatRotations( tmpmat, im, rot ); + + for ( i = 0; i < 3; i++ ) + { + dst[i] = rot[i][0] * point[0] + rot[i][1] * point[1] + rot[i][2] * point[2]; + } +} + +#ifdef _WIN32 +#pragma optimize( "", on ) +#endif + + + +void AngleVectors (vec3_t angles, vec3_t forward, vec3_t right, vec3_t up) +{ + float angle; + static float sr, sp, sy, cr, cp, cy; + // static to help MS compiler fp bugs + + angle = angles[YAW] * (M_PI*2 / 360); + sy = sin(angle); + cy = cos(angle); + angle = angles[PITCH] * (M_PI*2 / 360); + sp = sin(angle); + cp = cos(angle); + angle = angles[ROLL] * (M_PI*2 / 360); + sr = sin(angle); + cr = cos(angle); + + if (forward) + { + forward[0] = cp*cy; + forward[1] = cp*sy; + forward[2] = -sp; + } + if (right) + { + right[0] = (-1*sr*sp*cy+-1*cr*-sy); + right[1] = (-1*sr*sp*sy+-1*cr*cy); + right[2] = -1*sr*cp; + } + if (up) + { + up[0] = (cr*sp*cy+-sr*-sy); + up[1] = (cr*sp*sy+-sr*cy); + up[2] = cr*cp; + } +} + + +void ProjectPointOnPlane( vec3_t dst, const vec3_t p, const vec3_t normal ) +{ + float d; + vec3_t n; + float inv_denom; + + inv_denom = 1.0F / DotProduct( normal, normal ); + + d = DotProduct( normal, p ) * inv_denom; + + n[0] = normal[0] * inv_denom; + n[1] = normal[1] * inv_denom; + n[2] = normal[2] * inv_denom; + + dst[0] = p[0] - d * n[0]; + dst[1] = p[1] - d * n[1]; + dst[2] = p[2] - d * n[2]; +} + +/* +** assumes "src" is normalized +*/ +void PerpendicularVector( vec3_t dst, const vec3_t src ) +{ + int pos; + int i; + float minelem = 1.0F; + vec3_t tempvec; + + /* + ** find the smallest magnitude axially aligned vector + */ + for ( pos = 0, i = 0; i < 3; i++ ) + { + if ( fabs( src[i] ) < minelem ) + { + pos = i; + minelem = fabs( src[i] ); + } + } + tempvec[0] = tempvec[1] = tempvec[2] = 0.0F; + tempvec[pos] = 1.0F; + + /* + ** project the point onto the plane defined by src + */ + ProjectPointOnPlane( dst, tempvec, src ); + + /* + ** normalize the result + */ + VectorNormalize( dst ); +} + + + +/* +================ +R_ConcatRotations +================ +*/ +void R_ConcatRotations (float in1[3][3], float in2[3][3], float out[3][3]) +{ + out[0][0] = in1[0][0] * in2[0][0] + in1[0][1] * in2[1][0] + + in1[0][2] * in2[2][0]; + out[0][1] = in1[0][0] * in2[0][1] + in1[0][1] * in2[1][1] + + in1[0][2] * in2[2][1]; + out[0][2] = in1[0][0] * in2[0][2] + in1[0][1] * in2[1][2] + + in1[0][2] * in2[2][2]; + out[1][0] = in1[1][0] * in2[0][0] + in1[1][1] * in2[1][0] + + in1[1][2] * in2[2][0]; + out[1][1] = in1[1][0] * in2[0][1] + in1[1][1] * in2[1][1] + + in1[1][2] * in2[2][1]; + out[1][2] = in1[1][0] * in2[0][2] + in1[1][1] * in2[1][2] + + in1[1][2] * in2[2][2]; + out[2][0] = in1[2][0] * in2[0][0] + in1[2][1] * in2[1][0] + + in1[2][2] * in2[2][0]; + out[2][1] = in1[2][0] * in2[0][1] + in1[2][1] * in2[1][1] + + in1[2][2] * in2[2][1]; + out[2][2] = in1[2][0] * in2[0][2] + in1[2][1] * in2[1][2] + + in1[2][2] * in2[2][2]; +} + + +/* +================ +R_ConcatTransforms +================ +*/ +void R_ConcatTransforms (float in1[3][4], float in2[3][4], float out[3][4]) +{ + out[0][0] = in1[0][0] * in2[0][0] + in1[0][1] * in2[1][0] + + in1[0][2] * in2[2][0]; + out[0][1] = in1[0][0] * in2[0][1] + in1[0][1] * in2[1][1] + + in1[0][2] * in2[2][1]; + out[0][2] = in1[0][0] * in2[0][2] + in1[0][1] * in2[1][2] + + in1[0][2] * in2[2][2]; + out[0][3] = in1[0][0] * in2[0][3] + in1[0][1] * in2[1][3] + + in1[0][2] * in2[2][3] + in1[0][3]; + out[1][0] = in1[1][0] * in2[0][0] + in1[1][1] * in2[1][0] + + in1[1][2] * in2[2][0]; + out[1][1] = in1[1][0] * in2[0][1] + in1[1][1] * in2[1][1] + + in1[1][2] * in2[2][1]; + out[1][2] = in1[1][0] * in2[0][2] + in1[1][1] * in2[1][2] + + in1[1][2] * in2[2][2]; + out[1][3] = in1[1][0] * in2[0][3] + in1[1][1] * in2[1][3] + + in1[1][2] * in2[2][3] + in1[1][3]; + out[2][0] = in1[2][0] * in2[0][0] + in1[2][1] * in2[1][0] + + in1[2][2] * in2[2][0]; + out[2][1] = in1[2][0] * in2[0][1] + in1[2][1] * in2[1][1] + + in1[2][2] * in2[2][1]; + out[2][2] = in1[2][0] * in2[0][2] + in1[2][1] * in2[1][2] + + in1[2][2] * in2[2][2]; + out[2][3] = in1[2][0] * in2[0][3] + in1[2][1] * in2[1][3] + + in1[2][2] * in2[2][3] + in1[2][3]; +} + + +//============================================================================ + + +float Q_fabs (float f) +{ +#if 0 + if (f >= 0) + return f; + return -f; +#else + int tmp = * ( int * ) &f; + tmp &= 0x7FFFFFFF; + return * ( float * ) &tmp; +#endif +} + +#if defined _M_IX86 && !defined C_ONLY +#pragma warning (disable:4035) +__declspec( naked ) long Q_ftol( float f ) +{ + static int tmp; + __asm fld dword ptr [esp+4] + __asm fistp tmp + __asm mov eax, tmp + __asm ret +} +#pragma warning (default:4035) +#endif + +/* +=============== +LerpAngle + +=============== +*/ +float LerpAngle (float a2, float a1, float frac) +{ + if (a1 - a2 > 180) + a1 -= 360; + if (a1 - a2 < -180) + a1 += 360; + return a2 + frac * (a1 - a2); +} + + +float anglemod(float a) +{ +#if 0 + if (a >= 0) + a -= 360*(int)(a/360); + else + a += 360*( 1 + (int)(-a/360) ); +#endif + a = (360.0/65536) * ((int)(a*(65536/360.0)) & 65535); + return a; +} + + int i; + vec3_t corners[2]; + + +// this is the slow, general version +int BoxOnPlaneSide2 (vec3_t emins, vec3_t emaxs, struct cplane_s *p) +{ + int i; + float dist1, dist2; + int sides; + vec3_t corners[2]; + + for (i=0 ; i<3 ; i++) + { + if (p->normal[i] < 0) + { + corners[0][i] = emins[i]; + corners[1][i] = emaxs[i]; + } + else + { + corners[1][i] = emins[i]; + corners[0][i] = emaxs[i]; + } + } + dist1 = DotProduct (p->normal, corners[0]) - p->dist; + dist2 = DotProduct (p->normal, corners[1]) - p->dist; + sides = 0; + if (dist1 >= 0) + sides = 1; + if (dist2 < 0) + sides |= 2; + + return sides; +} + +/* +================== +BoxOnPlaneSide + +Returns 1, 2, or 1 + 2 +================== +*/ +#if !id386 || defined __linux__ +int BoxOnPlaneSide (vec3_t emins, vec3_t emaxs, struct cplane_s *p) +{ + float dist1, dist2; + int sides; + +// fast axial cases + if (p->type < 3) + { + if (p->dist <= emins[p->type]) + return 1; + if (p->dist >= emaxs[p->type]) + return 2; + return 3; + } + +// general case + switch (p->signbits) + { + case 0: +dist1 = p->normal[0]*emaxs[0] + p->normal[1]*emaxs[1] + p->normal[2]*emaxs[2]; +dist2 = p->normal[0]*emins[0] + p->normal[1]*emins[1] + p->normal[2]*emins[2]; + break; + case 1: +dist1 = p->normal[0]*emins[0] + p->normal[1]*emaxs[1] + p->normal[2]*emaxs[2]; +dist2 = p->normal[0]*emaxs[0] + p->normal[1]*emins[1] + p->normal[2]*emins[2]; + break; + case 2: +dist1 = p->normal[0]*emaxs[0] + p->normal[1]*emins[1] + p->normal[2]*emaxs[2]; +dist2 = p->normal[0]*emins[0] + p->normal[1]*emaxs[1] + p->normal[2]*emins[2]; + break; + case 3: +dist1 = p->normal[0]*emins[0] + p->normal[1]*emins[1] + p->normal[2]*emaxs[2]; +dist2 = p->normal[0]*emaxs[0] + p->normal[1]*emaxs[1] + p->normal[2]*emins[2]; + break; + case 4: +dist1 = p->normal[0]*emaxs[0] + p->normal[1]*emaxs[1] + p->normal[2]*emins[2]; +dist2 = p->normal[0]*emins[0] + p->normal[1]*emins[1] + p->normal[2]*emaxs[2]; + break; + case 5: +dist1 = p->normal[0]*emins[0] + p->normal[1]*emaxs[1] + p->normal[2]*emins[2]; +dist2 = p->normal[0]*emaxs[0] + p->normal[1]*emins[1] + p->normal[2]*emaxs[2]; + break; + case 6: +dist1 = p->normal[0]*emaxs[0] + p->normal[1]*emins[1] + p->normal[2]*emins[2]; +dist2 = p->normal[0]*emins[0] + p->normal[1]*emaxs[1] + p->normal[2]*emaxs[2]; + break; + case 7: +dist1 = p->normal[0]*emins[0] + p->normal[1]*emins[1] + p->normal[2]*emins[2]; +dist2 = p->normal[0]*emaxs[0] + p->normal[1]*emaxs[1] + p->normal[2]*emaxs[2]; + break; + default: + dist1 = dist2 = 0; // shut up compiler + assert( 0 ); + break; + } + + sides = 0; + if (dist1 >= p->dist) + sides = 1; + if (dist2 < p->dist) + sides |= 2; + + assert( sides != 0 ); + + return sides; +} +#else +#pragma warning( disable: 4035 ) + +__declspec( naked ) int BoxOnPlaneSide (vec3_t emins, vec3_t emaxs, struct cplane_s *p) +{ + static int bops_initialized; + static int Ljmptab[8]; + + __asm { + + push ebx + + cmp bops_initialized, 1 + je initialized + mov bops_initialized, 1 + + mov Ljmptab[0*4], offset Lcase0 + mov Ljmptab[1*4], offset Lcase1 + mov Ljmptab[2*4], offset Lcase2 + mov Ljmptab[3*4], offset Lcase3 + mov Ljmptab[4*4], offset Lcase4 + mov Ljmptab[5*4], offset Lcase5 + mov Ljmptab[6*4], offset Lcase6 + mov Ljmptab[7*4], offset Lcase7 + +initialized: + + mov edx,ds:dword ptr[4+12+esp] + mov ecx,ds:dword ptr[4+4+esp] + xor eax,eax + mov ebx,ds:dword ptr[4+8+esp] + mov al,ds:byte ptr[17+edx] + cmp al,8 + jge Lerror + fld ds:dword ptr[0+edx] + fld st(0) + jmp dword ptr[Ljmptab+eax*4] +Lcase0: + fmul ds:dword ptr[ebx] + fld ds:dword ptr[0+4+edx] + fxch st(2) + fmul ds:dword ptr[ecx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[4+ebx] + fld ds:dword ptr[0+8+edx] + fxch st(2) + fmul ds:dword ptr[4+ecx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[8+ebx] + fxch st(5) + faddp st(3),st(0) + fmul ds:dword ptr[8+ecx] + fxch st(1) + faddp st(3),st(0) + fxch st(3) + faddp st(2),st(0) + jmp LSetSides +Lcase1: + fmul ds:dword ptr[ecx] + fld ds:dword ptr[0+4+edx] + fxch st(2) + fmul ds:dword ptr[ebx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[4+ebx] + fld ds:dword ptr[0+8+edx] + fxch st(2) + fmul ds:dword ptr[4+ecx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[8+ebx] + fxch st(5) + faddp st(3),st(0) + fmul ds:dword ptr[8+ecx] + fxch st(1) + faddp st(3),st(0) + fxch st(3) + faddp st(2),st(0) + jmp LSetSides +Lcase2: + fmul ds:dword ptr[ebx] + fld ds:dword ptr[0+4+edx] + fxch st(2) + fmul ds:dword ptr[ecx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[4+ecx] + fld ds:dword ptr[0+8+edx] + fxch st(2) + fmul ds:dword ptr[4+ebx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[8+ebx] + fxch st(5) + faddp st(3),st(0) + fmul ds:dword ptr[8+ecx] + fxch st(1) + faddp st(3),st(0) + fxch st(3) + faddp st(2),st(0) + jmp LSetSides +Lcase3: + fmul ds:dword ptr[ecx] + fld ds:dword ptr[0+4+edx] + fxch st(2) + fmul ds:dword ptr[ebx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[4+ecx] + fld ds:dword ptr[0+8+edx] + fxch st(2) + fmul ds:dword ptr[4+ebx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[8+ebx] + fxch st(5) + faddp st(3),st(0) + fmul ds:dword ptr[8+ecx] + fxch st(1) + faddp st(3),st(0) + fxch st(3) + faddp st(2),st(0) + jmp LSetSides +Lcase4: + fmul ds:dword ptr[ebx] + fld ds:dword ptr[0+4+edx] + fxch st(2) + fmul ds:dword ptr[ecx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[4+ebx] + fld ds:dword ptr[0+8+edx] + fxch st(2) + fmul ds:dword ptr[4+ecx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[8+ecx] + fxch st(5) + faddp st(3),st(0) + fmul ds:dword ptr[8+ebx] + fxch st(1) + faddp st(3),st(0) + fxch st(3) + faddp st(2),st(0) + jmp LSetSides +Lcase5: + fmul ds:dword ptr[ecx] + fld ds:dword ptr[0+4+edx] + fxch st(2) + fmul ds:dword ptr[ebx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[4+ebx] + fld ds:dword ptr[0+8+edx] + fxch st(2) + fmul ds:dword ptr[4+ecx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[8+ecx] + fxch st(5) + faddp st(3),st(0) + fmul ds:dword ptr[8+ebx] + fxch st(1) + faddp st(3),st(0) + fxch st(3) + faddp st(2),st(0) + jmp LSetSides +Lcase6: + fmul ds:dword ptr[ebx] + fld ds:dword ptr[0+4+edx] + fxch st(2) + fmul ds:dword ptr[ecx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[4+ecx] + fld ds:dword ptr[0+8+edx] + fxch st(2) + fmul ds:dword ptr[4+ebx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[8+ecx] + fxch st(5) + faddp st(3),st(0) + fmul ds:dword ptr[8+ebx] + fxch st(1) + faddp st(3),st(0) + fxch st(3) + faddp st(2),st(0) + jmp LSetSides +Lcase7: + fmul ds:dword ptr[ecx] + fld ds:dword ptr[0+4+edx] + fxch st(2) + fmul ds:dword ptr[ebx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[4+ecx] + fld ds:dword ptr[0+8+edx] + fxch st(2) + fmul ds:dword ptr[4+ebx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[8+ecx] + fxch st(5) + faddp st(3),st(0) + fmul ds:dword ptr[8+ebx] + fxch st(1) + faddp st(3),st(0) + fxch st(3) + faddp st(2),st(0) +LSetSides: + faddp st(2),st(0) + fcomp ds:dword ptr[12+edx] + xor ecx,ecx + fnstsw ax + fcomp ds:dword ptr[12+edx] + and ah,1 + xor ah,1 + add cl,ah + fnstsw ax + and ah,1 + add ah,ah + add cl,ah + pop ebx + mov eax,ecx + ret +Lerror: + int 3 + } +} +#pragma warning( default: 4035 ) +#endif + +void ClearBounds (vec3_t mins, vec3_t maxs) +{ + mins[0] = mins[1] = mins[2] = 99999; + maxs[0] = maxs[1] = maxs[2] = -99999; +} + +void AddPointToBounds (vec3_t v, vec3_t mins, vec3_t maxs) +{ + int i; + vec_t val; + + for (i=0 ; i<3 ; i++) + { + val = v[i]; + if (val < mins[i]) + mins[i] = val; + if (val > maxs[i]) + maxs[i] = val; + } +} + + +int VectorCompare (vec3_t v1, vec3_t v2) +{ + if (v1[0] != v2[0] || v1[1] != v2[1] || v1[2] != v2[2]) + return 0; + + return 1; +} + + +vec_t VectorNormalize (vec3_t v) +{ + float length, ilength; + + length = v[0]*v[0] + v[1]*v[1] + v[2]*v[2]; + length = sqrt (length); // FIXME + + if (length) + { + ilength = 1/length; + v[0] *= ilength; + v[1] *= ilength; + v[2] *= ilength; + } + + return length; + +} + +vec_t VectorNormalize2 (vec3_t v, vec3_t out) +{ + float length, ilength; + + length = v[0]*v[0] + v[1]*v[1] + v[2]*v[2]; + length = sqrt (length); // FIXME + + if (length) + { + ilength = 1/length; + out[0] = v[0]*ilength; + out[1] = v[1]*ilength; + out[2] = v[2]*ilength; + } + + return length; + +} + +void VectorMA (vec3_t veca, float scale, vec3_t vecb, vec3_t vecc) +{ + vecc[0] = veca[0] + scale*vecb[0]; + vecc[1] = veca[1] + scale*vecb[1]; + vecc[2] = veca[2] + scale*vecb[2]; +} + + +vec_t _DotProduct (vec3_t v1, vec3_t v2) +{ + return v1[0]*v2[0] + v1[1]*v2[1] + v1[2]*v2[2]; +} + +void _VectorSubtract (vec3_t veca, vec3_t vecb, vec3_t out) +{ + out[0] = veca[0]-vecb[0]; + out[1] = veca[1]-vecb[1]; + out[2] = veca[2]-vecb[2]; +} + +void _VectorAdd (vec3_t veca, vec3_t vecb, vec3_t out) +{ + out[0] = veca[0]+vecb[0]; + out[1] = veca[1]+vecb[1]; + out[2] = veca[2]+vecb[2]; +} + +void _VectorCopy (vec3_t in, vec3_t out) +{ + out[0] = in[0]; + out[1] = in[1]; + out[2] = in[2]; +} + +void CrossProduct (vec3_t v1, vec3_t v2, vec3_t cross) +{ + cross[0] = v1[1]*v2[2] - v1[2]*v2[1]; + cross[1] = v1[2]*v2[0] - v1[0]*v2[2]; + cross[2] = v1[0]*v2[1] - v1[1]*v2[0]; +} + +double sqrt(double x); + +vec_t VectorLength(vec3_t v) +{ + int i; + float length; + + length = 0; + for (i=0 ; i< 3 ; i++) + length += v[i]*v[i]; + length = sqrt (length); // FIXME + + return length; +} + +void VectorInverse (vec3_t v) +{ + v[0] = -v[0]; + v[1] = -v[1]; + v[2] = -v[2]; +} + +void VectorScale (vec3_t in, vec_t scale, vec3_t out) +{ + out[0] = in[0]*scale; + out[1] = in[1]*scale; + out[2] = in[2]*scale; +} + + +int Q_log2(int val) +{ + int answer=0; + while (val>>=1) + answer++; + return answer; +} + + + +//==================================================================================== + +/* +============ +COM_SkipPath +============ +*/ +char *COM_SkipPath (char *pathname) +{ + char *last; + + last = pathname; + while (*pathname) + { + if (*pathname=='/') + last = pathname+1; + pathname++; + } + return last; +} + +/* +============ +COM_StripExtension +============ +*/ +void COM_StripExtension (char *in, char *out) +{ + while (*in && *in != '.') + *out++ = *in++; + *out = 0; +} + +/* +============ +COM_FileExtension +============ +*/ +char *COM_FileExtension (char *in) +{ + static char exten[8]; + int i; + + while (*in && *in != '.') + in++; + if (!*in) + return ""; + in++; + for (i=0 ; i<7 && *in ; i++,in++) + exten[i] = *in; + exten[i] = 0; + return exten; +} + +/* +============ +COM_FileBase +============ +*/ +void COM_FileBase (char *in, char *out) +{ + char *s, *s2; + + s = in + strlen(in) - 1; + + while (s != in && *s != '.') + s--; + + for (s2 = s ; s2 != in && *s2 != '/' ; s2--) + ; + + if (s-s2 < 2) + out[0] = 0; + else + { + s--; + strncpy (out,s2+1, s-s2); + out[s-s2] = 0; + } +} + +/* +============ +COM_FilePath + +Returns the path up to, but not including the last / +============ +*/ +void COM_FilePath (char *in, char *out) +{ + char *s; + + s = in + strlen(in) - 1; + + while (s != in && *s != '/') + s--; + + strncpy (out,in, s-in); + out[s-in] = 0; +} + + +/* +================== +COM_DefaultExtension +================== +*/ +void COM_DefaultExtension (char *path, char *extension) +{ + char *src; +// +// if path doesn't have a .EXT, append extension +// (extension should include the .) +// + src = path + strlen(path) - 1; + + while (*src != '/' && src != path) + { + if (*src == '.') + return; // it has an extension + src--; + } + + strcat (path, extension); +} + +/* +============================================================================ + + BYTE ORDER FUNCTIONS + +============================================================================ +*/ + +qboolean bigendien; + +// can't just use function pointers, or dll linkage can +// mess up when qcommon is included in multiple places +short (*_BigShort) (short l); +short (*_LittleShort) (short l); +int (*_BigLong) (int l); +int (*_LittleLong) (int l); +float (*_BigFloat) (float l); +float (*_LittleFloat) (float l); + +short BigShort(short l){return _BigShort(l);} +short LittleShort(short l) {return _LittleShort(l);} +int BigLong (int l) {return _BigLong(l);} +int LittleLong (int l) {return _LittleLong(l);} +float BigFloat (float l) {return _BigFloat(l);} +float LittleFloat (float l) {return _LittleFloat(l);} + +short ShortSwap (short l) +{ + byte b1,b2; + + b1 = l&255; + b2 = (l>>8)&255; + + return (b1<<8) + b2; +} + +short ShortNoSwap (short l) +{ + return l; +} + +int LongSwap (int l) +{ + byte b1,b2,b3,b4; + + b1 = l&255; + b2 = (l>>8)&255; + b3 = (l>>16)&255; + b4 = (l>>24)&255; + + return ((int)b1<<24) + ((int)b2<<16) + ((int)b3<<8) + b4; +} + +int LongNoSwap (int l) +{ + return l; +} + +float FloatSwap (float f) +{ + union + { + float f; + byte b[4]; + } dat1, dat2; + + + dat1.f = f; + dat2.b[0] = dat1.b[3]; + dat2.b[1] = dat1.b[2]; + dat2.b[2] = dat1.b[1]; + dat2.b[3] = dat1.b[0]; + return dat2.f; +} + +float FloatNoSwap (float f) +{ + return f; +} + +/* +================ +Swap_Init +================ +*/ +void Swap_Init (void) +{ + byte swaptest[2] = {1,0}; + +// set the byte swapping variables in a portable manner + if ( *(short *)swaptest == 1) + { + bigendien = false; + _BigShort = ShortSwap; + _LittleShort = ShortNoSwap; + _BigLong = LongSwap; + _LittleLong = LongNoSwap; + _BigFloat = FloatSwap; + _LittleFloat = FloatNoSwap; + } + else + { + bigendien = true; + _BigShort = ShortNoSwap; + _LittleShort = ShortSwap; + _BigLong = LongNoSwap; + _LittleLong = LongSwap; + _BigFloat = FloatNoSwap; + _LittleFloat = FloatSwap; + } + +} + + + +/* +============ +va + +does a varargs printf into a temp buffer, so I don't need to have +varargs versions of all text functions. +FIXME: make this buffer size safe someday +============ +*/ +char *va(char *format, ...) +{ + va_list argptr; + static char string[1024]; + + va_start (argptr, format); + vsprintf (string, format,argptr); + va_end (argptr); + + return string; +} + + +char com_token[MAX_TOKEN_CHARS]; + +/* +============== +COM_Parse + +Parse a token out of a string +============== +*/ +char *COM_Parse (char **data_p) +{ + int c; + int len; + char *data; + + data = *data_p; + len = 0; + com_token[0] = 0; + + if (!data) + { + *data_p = NULL; + return ""; + } + +// skip whitespace +skipwhite: + while ( (c = *data) <= ' ') + { + if (c == 0) + { + *data_p = NULL; + return ""; + } + data++; + } + +// skip // comments + if (c=='/' && data[1] == '/') + { + while (*data && *data != '\n') + data++; + goto skipwhite; + } + +// handle quoted strings specially + if (c == '\"') + { + data++; + while (1) + { + c = *data++; + if (c=='\"' || !c) + { + com_token[len] = 0; + *data_p = data; + return com_token; + } + if (len < MAX_TOKEN_CHARS) + { + com_token[len] = c; + len++; + } + } + } + +// parse a regular word + do + { + if (len < MAX_TOKEN_CHARS) + { + com_token[len] = c; + len++; + } + data++; + c = *data; + } while (c>32); + + if (len == MAX_TOKEN_CHARS) + { +// Com_Printf ("Token exceeded %i chars, discarded.\n", MAX_TOKEN_CHARS); + len = 0; + } + com_token[len] = 0; + + *data_p = data; + return com_token; +} + + +/* +=============== +Com_PageInMemory + +=============== +*/ +int paged_total; + +void Com_PageInMemory (byte *buffer, int size) +{ + int i; + + for (i=size-1 ; i>0 ; i-=4096) + paged_total += buffer[i]; +} + + + +/* +============================================================================ + + LIBRARY REPLACEMENT FUNCTIONS + +============================================================================ +*/ + +// FIXME: replace all Q_stricmp with Q_strcasecmp +int Q_stricmp (char *s1, char *s2) +{ +#if defined(WIN32) + return _stricmp (s1, s2); +#else + return strcasecmp (s1, s2); +#endif +} + + +int Q_strncasecmp (char *s1, char *s2, int n) +{ + int c1, c2; + + do + { + c1 = *s1++; + c2 = *s2++; + + if (!n--) + return 0; // strings are equal until end point + + if (c1 != c2) + { + if (c1 >= 'a' && c1 <= 'z') + c1 -= ('a' - 'A'); + if (c2 >= 'a' && c2 <= 'z') + c2 -= ('a' - 'A'); + if (c1 != c2) + return -1; // strings not equal + } + } while (c1); + + return 0; // strings are equal +} + +int Q_strcasecmp (char *s1, char *s2) +{ + return Q_strncasecmp (s1, s2, 99999); +} + + + +void Com_sprintf (char *dest, int size, char *fmt, ...) +{ + int len; + va_list argptr; + char bigbuffer[0x10000]; + + va_start (argptr,fmt); + len = vsprintf (bigbuffer,fmt,argptr); + va_end (argptr); + if (len >= size) + Com_Printf ("Com_sprintf: overflow of %i in %i\n", len, size); + strncpy (dest, bigbuffer, size-1); +} + +/* +===================================================================== + + INFO STRINGS + +===================================================================== +*/ + +/* +=============== +Info_ValueForKey + +Searches the string for the given +key and returns the associated value, or an empty string. +=============== +*/ +char *Info_ValueForKey (char *s, char *key) +{ + char pkey[512]; + static char value[2][512]; // use two buffers so compares + // work without stomping on each other + static int valueindex; + char *o; + + valueindex ^= 1; + if (*s == '\\') + s++; + while (1) + { + o = pkey; + while (*s != '\\') + { + if (!*s) + return ""; + *o++ = *s++; + } + *o = 0; + s++; + + o = value[valueindex]; + + while (*s != '\\' && *s) + { + if (!*s) + return ""; + *o++ = *s++; + } + *o = 0; + + if (!strcmp (key, pkey) ) + return value[valueindex]; + + if (!*s) + return ""; + s++; + } +} + +void Info_RemoveKey (char *s, char *key) +{ + char *start; + char pkey[512]; + char value[512]; + char *o; + + if (strstr (key, "\\")) + { +// Com_Printf ("Can't use a key with a \\\n"); + return; + } + + while (1) + { + start = s; + if (*s == '\\') + s++; + o = pkey; + while (*s != '\\') + { + if (!*s) + return; + *o++ = *s++; + } + *o = 0; + s++; + + o = value; + while (*s != '\\' && *s) + { + if (!*s) + return; + *o++ = *s++; + } + *o = 0; + + if (!strcmp (key, pkey) ) + { + strcpy (start, s); // remove this part + return; + } + + if (!*s) + return; + } + +} + + +/* +================== +Info_Validate + +Some characters are illegal in info strings because they +can mess up the server's parsing +================== +*/ +qboolean Info_Validate (char *s) +{ + if (strstr (s, "\"")) + return false; + if (strstr (s, ";")) + return false; + return true; +} + +void Info_SetValueForKey (char *s, char *key, char *value) +{ + char newi[MAX_INFO_STRING], *v; + int c; + int maxsize = MAX_INFO_STRING; + + if (strstr (key, "\\") || strstr (value, "\\") ) + { + Com_Printf ("Can't use keys or values with a \\\n"); + return; + } + + if (strstr (key, ";") ) + { + Com_Printf ("Can't use keys or values with a semicolon\n"); + return; + } + + if (strstr (key, "\"") || strstr (value, "\"") ) + { + Com_Printf ("Can't use keys or values with a \"\n"); + return; + } + + if (strlen(key) > MAX_INFO_KEY-1 || strlen(value) > MAX_INFO_KEY-1) + { + Com_Printf ("Keys and values must be < 64 characters.\n"); + return; + } + Info_RemoveKey (s, key); + if (!value || !strlen(value)) + return; + + Com_sprintf (newi, sizeof(newi), "\\%s\\%s", key, value); + + if (strlen(newi) + strlen(s) > maxsize) + { + Com_Printf ("Info string length exceeded\n"); + return; + } + + // only copy ascii values + s += strlen(s); + v = newi; + while (*v) + { + c = *v++; + c &= 127; // strip high bits + if (c >= 32 && c < 127) + *s++ = c; + } + *s = 0; +} + +//==================================================================== + + diff --git a/original/baseq2/q_shared.h b/original/baseq2/q_shared.h new file mode 100644 index 0000000..b10b741 --- /dev/null +++ b/original/baseq2/q_shared.h @@ -0,0 +1,1200 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +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. + +*/ + +// q_shared.h -- included first by ALL program modules + +#ifdef _WIN32 +// unknown pragmas are SUPPOSED to be ignored, but.... +#pragma warning(disable : 4244) // MIPS +#pragma warning(disable : 4136) // X86 +#pragma warning(disable : 4051) // ALPHA + +#pragma warning(disable : 4018) // signed/unsigned mismatch +#pragma warning(disable : 4305) // truncation from const double to float + +#endif + +#include +#include +#include +#include +#include +#include +#include + +#if (defined _M_IX86 || defined __i386__) && !defined C_ONLY && !defined __sun__ +#define id386 1 +#else +#define id386 0 +#endif + +#if defined _M_ALPHA && !defined C_ONLY +#define idaxp 1 +#else +#define idaxp 0 +#endif + +typedef unsigned char byte; +typedef enum {false, true} qboolean; + + +#ifndef NULL +#define NULL ((void *)0) +#endif + + +// angle indexes +#define PITCH 0 // up / down +#define YAW 1 // left / right +#define ROLL 2 // fall over + +#define MAX_STRING_CHARS 1024 // max length of a string passed to Cmd_TokenizeString +#define MAX_STRING_TOKENS 80 // max tokens resulting from Cmd_TokenizeString +#define MAX_TOKEN_CHARS 128 // max length of an individual token + +#define MAX_QPATH 64 // max length of a quake game pathname +#define MAX_OSPATH 128 // max length of a filesystem pathname + +// +// per-level limits +// +#define MAX_CLIENTS 256 // absolute limit +#define MAX_EDICTS 1024 // must change protocol to increase more +#define MAX_LIGHTSTYLES 256 +#define MAX_MODELS 256 // these are sent over the net as bytes +#define MAX_SOUNDS 256 // so they cannot be blindly increased +#define MAX_IMAGES 256 +#define MAX_ITEMS 256 +#define MAX_GENERAL (MAX_CLIENTS*2) // general config strings + + +// game print flags +#define PRINT_LOW 0 // pickup messages +#define PRINT_MEDIUM 1 // death messages +#define PRINT_HIGH 2 // critical messages +#define PRINT_CHAT 3 // chat messages + + + +#define ERR_FATAL 0 // exit the entire game with a popup window +#define ERR_DROP 1 // print to console and disconnect from game +#define ERR_DISCONNECT 2 // don't kill server + +#define PRINT_ALL 0 +#define PRINT_DEVELOPER 1 // only print when "developer 1" +#define PRINT_ALERT 2 + + +// destination class for gi.multicast() +typedef enum +{ +MULTICAST_ALL, +MULTICAST_PHS, +MULTICAST_PVS, +MULTICAST_ALL_R, +MULTICAST_PHS_R, +MULTICAST_PVS_R +} multicast_t; + + +/* +============================================================== + +MATHLIB + +============================================================== +*/ + +typedef float vec_t; +typedef vec_t vec3_t[3]; +typedef vec_t vec5_t[5]; + +typedef int fixed4_t; +typedef int fixed8_t; +typedef int fixed16_t; + +#ifndef M_PI +#define M_PI 3.14159265358979323846 // matches value in gcc v2 math.h +#endif + +struct cplane_s; + +extern vec3_t vec3_origin; + +#define nanmask (255<<23) + +#define IS_NAN(x) (((*(int *)&x)&nanmask)==nanmask) + +// microsoft's fabs seems to be ungodly slow... +//float Q_fabs (float f); +//#define fabs(f) Q_fabs(f) +#if !defined C_ONLY && !defined __linux__ && !defined __sgi +extern long Q_ftol( float f ); +#else +#define Q_ftol( f ) ( long ) (f) +#endif + +#define DotProduct(x,y) (x[0]*y[0]+x[1]*y[1]+x[2]*y[2]) +#define VectorSubtract(a,b,c) (c[0]=a[0]-b[0],c[1]=a[1]-b[1],c[2]=a[2]-b[2]) +#define VectorAdd(a,b,c) (c[0]=a[0]+b[0],c[1]=a[1]+b[1],c[2]=a[2]+b[2]) +#define VectorCopy(a,b) (b[0]=a[0],b[1]=a[1],b[2]=a[2]) +#define VectorClear(a) (a[0]=a[1]=a[2]=0) +#define VectorNegate(a,b) (b[0]=-a[0],b[1]=-a[1],b[2]=-a[2]) +#define VectorSet(v, x, y, z) (v[0]=(x), v[1]=(y), v[2]=(z)) + +void VectorMA (vec3_t veca, float scale, vec3_t vecb, vec3_t vecc); + +// just in case you do't want to use the macros +vec_t _DotProduct (vec3_t v1, vec3_t v2); +void _VectorSubtract (vec3_t veca, vec3_t vecb, vec3_t out); +void _VectorAdd (vec3_t veca, vec3_t vecb, vec3_t out); +void _VectorCopy (vec3_t in, vec3_t out); + +void ClearBounds (vec3_t mins, vec3_t maxs); +void AddPointToBounds (vec3_t v, vec3_t mins, vec3_t maxs); +int VectorCompare (vec3_t v1, vec3_t v2); +vec_t VectorLength (vec3_t v); +void CrossProduct (vec3_t v1, vec3_t v2, vec3_t cross); +vec_t VectorNormalize (vec3_t v); // returns vector length +vec_t VectorNormalize2 (vec3_t v, vec3_t out); +void VectorInverse (vec3_t v); +void VectorScale (vec3_t in, vec_t scale, vec3_t out); +int Q_log2(int val); + +void R_ConcatRotations (float in1[3][3], float in2[3][3], float out[3][3]); +void R_ConcatTransforms (float in1[3][4], float in2[3][4], float out[3][4]); + +void AngleVectors (vec3_t angles, vec3_t forward, vec3_t right, vec3_t up); +int BoxOnPlaneSide (vec3_t emins, vec3_t emaxs, struct cplane_s *plane); +float anglemod(float a); +float LerpAngle (float a1, float a2, float frac); + +#define BOX_ON_PLANE_SIDE(emins, emaxs, p) \ + (((p)->type < 3)? \ + ( \ + ((p)->dist <= (emins)[(p)->type])? \ + 1 \ + : \ + ( \ + ((p)->dist >= (emaxs)[(p)->type])?\ + 2 \ + : \ + 3 \ + ) \ + ) \ + : \ + BoxOnPlaneSide( (emins), (emaxs), (p))) + +void ProjectPointOnPlane( vec3_t dst, const vec3_t p, const vec3_t normal ); +void PerpendicularVector( vec3_t dst, const vec3_t src ); +void RotatePointAroundVector( vec3_t dst, const vec3_t dir, const vec3_t point, float degrees ); + + +//============================================= + +char *COM_SkipPath (char *pathname); +void COM_StripExtension (char *in, char *out); +void COM_FileBase (char *in, char *out); +void COM_FilePath (char *in, char *out); +void COM_DefaultExtension (char *path, char *extension); + +char *COM_Parse (char **data_p); +// data is an in/out parm, returns a parsed out token + +void Com_sprintf (char *dest, int size, char *fmt, ...); + +void Com_PageInMemory (byte *buffer, int size); + +//============================================= + +// portable case insensitive compare +int Q_stricmp (char *s1, char *s2); +int Q_strcasecmp (char *s1, char *s2); +int Q_strncasecmp (char *s1, char *s2, int n); + +//============================================= + +short BigShort(short l); +short LittleShort(short l); +int BigLong (int l); +int LittleLong (int l); +float BigFloat (float l); +float LittleFloat (float l); + +void Swap_Init (void); +char *va(char *format, ...); + +//============================================= + +// +// key / value info strings +// +#define MAX_INFO_KEY 64 +#define MAX_INFO_VALUE 64 +#define MAX_INFO_STRING 512 + +char *Info_ValueForKey (char *s, char *key); +void Info_RemoveKey (char *s, char *key); +void Info_SetValueForKey (char *s, char *key, char *value); +qboolean Info_Validate (char *s); + +/* +============================================================== + +SYSTEM SPECIFIC + +============================================================== +*/ + +extern int curtime; // time returned by last Sys_Milliseconds + +int Sys_Milliseconds (void); +void Sys_Mkdir (char *path); + +// large block stack allocation routines +void *Hunk_Begin (int maxsize); +void *Hunk_Alloc (int size); +void Hunk_Free (void *buf); +int Hunk_End (void); + +// directory searching +#define SFF_ARCH 0x01 +#define SFF_HIDDEN 0x02 +#define SFF_RDONLY 0x04 +#define SFF_SUBDIR 0x08 +#define SFF_SYSTEM 0x10 + +/* +** pass in an attribute mask of things you wish to REJECT +*/ +char *Sys_FindFirst (char *path, unsigned musthave, unsigned canthave ); +char *Sys_FindNext ( unsigned musthave, unsigned canthave ); +void Sys_FindClose (void); + + +// this is only here so the functions in q_shared.c and q_shwin.c can link +void Sys_Error (char *error, ...); +void Com_Printf (char *msg, ...); + + +/* +========================================================== + +CVARS (console variables) + +========================================================== +*/ + +#ifndef CVAR +#define CVAR + +#define CVAR_ARCHIVE 1 // set to cause it to be saved to vars.rc +#define CVAR_USERINFO 2 // added to userinfo when changed +#define CVAR_SERVERINFO 4 // added to serverinfo when changed +#define CVAR_NOSET 8 // don't allow change from console at all, + // but can be set from the command line +#define CVAR_LATCH 16 // save changes until server restart + +// nothing outside the Cvar_*() functions should modify these fields! +typedef struct cvar_s +{ + char *name; + char *string; + char *latched_string; // for CVAR_LATCH vars + int flags; + qboolean modified; // set each time the cvar is changed + float value; + struct cvar_s *next; +} cvar_t; + +#endif // CVAR + +/* +============================================================== + +COLLISION DETECTION + +============================================================== +*/ + +// lower bits are stronger, and will eat weaker brushes completely +#define CONTENTS_SOLID 1 // an eye is never valid in a solid +#define CONTENTS_WINDOW 2 // translucent, but not watery +#define CONTENTS_AUX 4 +#define CONTENTS_LAVA 8 +#define CONTENTS_SLIME 16 +#define CONTENTS_WATER 32 +#define CONTENTS_MIST 64 +#define LAST_VISIBLE_CONTENTS 64 + +// remaining contents are non-visible, and don't eat brushes + +#define CONTENTS_AREAPORTAL 0x8000 + +#define CONTENTS_PLAYERCLIP 0x10000 +#define CONTENTS_MONSTERCLIP 0x20000 + +// currents can be added to any other contents, and may be mixed +#define CONTENTS_CURRENT_0 0x40000 +#define CONTENTS_CURRENT_90 0x80000 +#define CONTENTS_CURRENT_180 0x100000 +#define CONTENTS_CURRENT_270 0x200000 +#define CONTENTS_CURRENT_UP 0x400000 +#define CONTENTS_CURRENT_DOWN 0x800000 + +#define CONTENTS_ORIGIN 0x1000000 // removed before bsping an entity + +#define CONTENTS_MONSTER 0x2000000 // should never be on a brush, only in game +#define CONTENTS_DEADMONSTER 0x4000000 +#define CONTENTS_DETAIL 0x8000000 // brushes to be added after vis leafs +#define CONTENTS_TRANSLUCENT 0x10000000 // auto set if any surface has trans +#define CONTENTS_LADDER 0x20000000 + + + +#define SURF_LIGHT 0x1 // value will hold the light strength + +#define SURF_SLICK 0x2 // effects game physics + +#define SURF_SKY 0x4 // don't draw, but add to skybox +#define SURF_WARP 0x8 // turbulent water warp +#define SURF_TRANS33 0x10 +#define SURF_TRANS66 0x20 +#define SURF_FLOWING 0x40 // scroll towards angle +#define SURF_NODRAW 0x80 // don't bother referencing the texture + + + +// content masks +#define MASK_ALL (-1) +#define MASK_SOLID (CONTENTS_SOLID|CONTENTS_WINDOW) +#define MASK_PLAYERSOLID (CONTENTS_SOLID|CONTENTS_PLAYERCLIP|CONTENTS_WINDOW|CONTENTS_MONSTER) +#define MASK_DEADSOLID (CONTENTS_SOLID|CONTENTS_PLAYERCLIP|CONTENTS_WINDOW) +#define MASK_MONSTERSOLID (CONTENTS_SOLID|CONTENTS_MONSTERCLIP|CONTENTS_WINDOW|CONTENTS_MONSTER) +#define MASK_WATER (CONTENTS_WATER|CONTENTS_LAVA|CONTENTS_SLIME) +#define MASK_OPAQUE (CONTENTS_SOLID|CONTENTS_SLIME|CONTENTS_LAVA) +#define MASK_SHOT (CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_WINDOW|CONTENTS_DEADMONSTER) +#define MASK_CURRENT (CONTENTS_CURRENT_0|CONTENTS_CURRENT_90|CONTENTS_CURRENT_180|CONTENTS_CURRENT_270|CONTENTS_CURRENT_UP|CONTENTS_CURRENT_DOWN) + + +// gi.BoxEdicts() can return a list of either solid or trigger entities +// FIXME: eliminate AREA_ distinction? +#define AREA_SOLID 1 +#define AREA_TRIGGERS 2 + + +// plane_t structure +// !!! if this is changed, it must be changed in asm code too !!! +typedef struct cplane_s +{ + vec3_t normal; + float dist; + byte type; // for fast side tests + byte signbits; // signx + (signy<<1) + (signz<<1) + byte pad[2]; +} cplane_t; + +// structure offset for asm code +#define CPLANE_NORMAL_X 0 +#define CPLANE_NORMAL_Y 4 +#define CPLANE_NORMAL_Z 8 +#define CPLANE_DIST 12 +#define CPLANE_TYPE 16 +#define CPLANE_SIGNBITS 17 +#define CPLANE_PAD0 18 +#define CPLANE_PAD1 19 + +typedef struct cmodel_s +{ + vec3_t mins, maxs; + vec3_t origin; // for sounds or lights + int headnode; +} cmodel_t; + +typedef struct csurface_s +{ + char name[16]; + int flags; + int value; +} csurface_t; + +typedef struct mapsurface_s // used internally due to name len probs //ZOID +{ + csurface_t c; + char rname[32]; +} mapsurface_t; + +// a trace is returned when a box is swept through the world +typedef struct +{ + qboolean allsolid; // if true, plane is not valid + qboolean startsolid; // if true, the initial point was in a solid area + float fraction; // time completed, 1.0 = didn't hit anything + vec3_t endpos; // final position + cplane_t plane; // surface normal at impact + csurface_t *surface; // surface hit + int contents; // contents on other side of surface hit + struct edict_s *ent; // not set by CM_*() functions +} trace_t; + + + +// pmove_state_t is the information necessary for client side movement +// prediction +typedef enum +{ + // can accelerate and turn + PM_NORMAL, + PM_SPECTATOR, + // no acceleration or turning + PM_DEAD, + PM_GIB, // different bounding box + PM_FREEZE +} pmtype_t; + +// pmove->pm_flags +#define PMF_DUCKED 1 +#define PMF_JUMP_HELD 2 +#define PMF_ON_GROUND 4 +#define PMF_TIME_WATERJUMP 8 // pm_time is waterjump +#define PMF_TIME_LAND 16 // pm_time is time before rejump +#define PMF_TIME_TELEPORT 32 // pm_time is non-moving time +#define PMF_NO_PREDICTION 64 // temporarily disables prediction (used for grappling hook) + +// this structure needs to be communicated bit-accurate +// from the server to the client to guarantee that +// prediction stays in sync, so no floats are used. +// if any part of the game code modifies this struct, it +// will result in a prediction error of some degree. +typedef struct +{ + pmtype_t pm_type; + + short origin[3]; // 12.3 + short velocity[3]; // 12.3 + byte pm_flags; // ducked, jump_held, etc + byte pm_time; // each unit = 8 ms + short gravity; + short delta_angles[3]; // add to command angles to get view direction + // changed by spawns, rotating objects, and teleporters +} pmove_state_t; + + +// +// button bits +// +#define BUTTON_ATTACK 1 +#define BUTTON_USE 2 +#define BUTTON_ANY 128 // any key whatsoever + + +// usercmd_t is sent to the server each client frame +typedef struct usercmd_s +{ + byte msec; + byte buttons; + short angles[3]; + short forwardmove, sidemove, upmove; + byte impulse; // remove? + byte lightlevel; // light level the player is standing on +} usercmd_t; + + +#define MAXTOUCH 32 +typedef struct +{ + // state (in / out) + pmove_state_t s; + + // command (in) + usercmd_t cmd; + qboolean snapinitial; // if s has been changed outside pmove + + // results (out) + int numtouch; + struct edict_s *touchents[MAXTOUCH]; + + vec3_t viewangles; // clamped + float viewheight; + + vec3_t mins, maxs; // bounding box size + + struct edict_s *groundentity; + int watertype; + int waterlevel; + + // callbacks to test the world + trace_t (*trace) (vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end); + int (*pointcontents) (vec3_t point); +} pmove_t; + + +// entity_state_t->effects +// Effects are things handled on the client side (lights, particles, frame animations) +// that happen constantly on the given entity. +// An entity that has effects will be sent to the client +// even if it has a zero index model. +#define EF_ROTATE 0x00000001 // rotate (bonus items) +#define EF_GIB 0x00000002 // leave a trail +#define EF_BLASTER 0x00000008 // redlight + trail +#define EF_ROCKET 0x00000010 // redlight + trail +#define EF_GRENADE 0x00000020 +#define EF_HYPERBLASTER 0x00000040 +#define EF_BFG 0x00000080 +#define EF_COLOR_SHELL 0x00000100 +#define EF_POWERSCREEN 0x00000200 +#define EF_ANIM01 0x00000400 // automatically cycle between frames 0 and 1 at 2 hz +#define EF_ANIM23 0x00000800 // automatically cycle between frames 2 and 3 at 2 hz +#define EF_ANIM_ALL 0x00001000 // automatically cycle through all frames at 2hz +#define EF_ANIM_ALLFAST 0x00002000 // automatically cycle through all frames at 10hz +#define EF_FLIES 0x00004000 +#define EF_QUAD 0x00008000 +#define EF_PENT 0x00010000 +#define EF_TELEPORTER 0x00020000 // particle fountain +#define EF_FLAG1 0x00040000 +#define EF_FLAG2 0x00080000 +// RAFAEL +#define EF_IONRIPPER 0x00100000 +#define EF_GREENGIB 0x00200000 +#define EF_BLUEHYPERBLASTER 0x00400000 +#define EF_SPINNINGLIGHTS 0x00800000 +#define EF_PLASMA 0x01000000 +#define EF_TRAP 0x02000000 + +//ROGUE +#define EF_TRACKER 0x04000000 +#define EF_DOUBLE 0x08000000 +#define EF_SPHERETRANS 0x10000000 +#define EF_TAGTRAIL 0x20000000 +#define EF_HALF_DAMAGE 0x40000000 +#define EF_TRACKERTRAIL 0x80000000 +//ROGUE + +// entity_state_t->renderfx flags +#define RF_MINLIGHT 1 // allways have some light (viewmodel) +#define RF_VIEWERMODEL 2 // don't draw through eyes, only mirrors +#define RF_WEAPONMODEL 4 // only draw through eyes +#define RF_FULLBRIGHT 8 // allways draw full intensity +#define RF_DEPTHHACK 16 // for view weapon Z crunching +#define RF_TRANSLUCENT 32 +#define RF_FRAMELERP 64 +#define RF_BEAM 128 +#define RF_CUSTOMSKIN 256 // skin is an index in image_precache +#define RF_GLOW 512 // pulse lighting for bonus items +#define RF_SHELL_RED 1024 +#define RF_SHELL_GREEN 2048 +#define RF_SHELL_BLUE 4096 + +//ROGUE +#define RF_IR_VISIBLE 0x00008000 // 32768 +#define RF_SHELL_DOUBLE 0x00010000 // 65536 +#define RF_SHELL_HALF_DAM 0x00020000 +#define RF_USE_DISGUISE 0x00040000 +//ROGUE + +// player_state_t->refdef flags +#define RDF_UNDERWATER 1 // warp the screen as apropriate +#define RDF_NOWORLDMODEL 2 // used for player configuration screen + +//ROGUE +#define RDF_IRGOGGLES 4 +#define RDF_UVGOGGLES 8 +//ROGUE + +// +// muzzle flashes / player effects +// +#define MZ_BLASTER 0 +#define MZ_MACHINEGUN 1 +#define MZ_SHOTGUN 2 +#define MZ_CHAINGUN1 3 +#define MZ_CHAINGUN2 4 +#define MZ_CHAINGUN3 5 +#define MZ_RAILGUN 6 +#define MZ_ROCKET 7 +#define MZ_GRENADE 8 +#define MZ_LOGIN 9 +#define MZ_LOGOUT 10 +#define MZ_RESPAWN 11 +#define MZ_BFG 12 +#define MZ_SSHOTGUN 13 +#define MZ_HYPERBLASTER 14 +#define MZ_ITEMRESPAWN 15 +// RAFAEL +#define MZ_IONRIPPER 16 +#define MZ_BLUEHYPERBLASTER 17 +#define MZ_PHALANX 18 +#define MZ_SILENCED 128 // bit flag ORed with one of the above numbers + +//ROGUE +#define MZ_ETF_RIFLE 30 +#define MZ_UNUSED 31 +#define MZ_SHOTGUN2 32 +#define MZ_HEATBEAM 33 +#define MZ_BLASTER2 34 +#define MZ_TRACKER 35 +#define MZ_NUKE1 36 +#define MZ_NUKE2 37 +#define MZ_NUKE4 38 +#define MZ_NUKE8 39 +//ROGUE + +// +// monster muzzle flashes +// +#define MZ2_TANK_BLASTER_1 1 +#define MZ2_TANK_BLASTER_2 2 +#define MZ2_TANK_BLASTER_3 3 +#define MZ2_TANK_MACHINEGUN_1 4 +#define MZ2_TANK_MACHINEGUN_2 5 +#define MZ2_TANK_MACHINEGUN_3 6 +#define MZ2_TANK_MACHINEGUN_4 7 +#define MZ2_TANK_MACHINEGUN_5 8 +#define MZ2_TANK_MACHINEGUN_6 9 +#define MZ2_TANK_MACHINEGUN_7 10 +#define MZ2_TANK_MACHINEGUN_8 11 +#define MZ2_TANK_MACHINEGUN_9 12 +#define MZ2_TANK_MACHINEGUN_10 13 +#define MZ2_TANK_MACHINEGUN_11 14 +#define MZ2_TANK_MACHINEGUN_12 15 +#define MZ2_TANK_MACHINEGUN_13 16 +#define MZ2_TANK_MACHINEGUN_14 17 +#define MZ2_TANK_MACHINEGUN_15 18 +#define MZ2_TANK_MACHINEGUN_16 19 +#define MZ2_TANK_MACHINEGUN_17 20 +#define MZ2_TANK_MACHINEGUN_18 21 +#define MZ2_TANK_MACHINEGUN_19 22 +#define MZ2_TANK_ROCKET_1 23 +#define MZ2_TANK_ROCKET_2 24 +#define MZ2_TANK_ROCKET_3 25 + +#define MZ2_INFANTRY_MACHINEGUN_1 26 +#define MZ2_INFANTRY_MACHINEGUN_2 27 +#define MZ2_INFANTRY_MACHINEGUN_3 28 +#define MZ2_INFANTRY_MACHINEGUN_4 29 +#define MZ2_INFANTRY_MACHINEGUN_5 30 +#define MZ2_INFANTRY_MACHINEGUN_6 31 +#define MZ2_INFANTRY_MACHINEGUN_7 32 +#define MZ2_INFANTRY_MACHINEGUN_8 33 +#define MZ2_INFANTRY_MACHINEGUN_9 34 +#define MZ2_INFANTRY_MACHINEGUN_10 35 +#define MZ2_INFANTRY_MACHINEGUN_11 36 +#define MZ2_INFANTRY_MACHINEGUN_12 37 +#define MZ2_INFANTRY_MACHINEGUN_13 38 + +#define MZ2_SOLDIER_BLASTER_1 39 +#define MZ2_SOLDIER_BLASTER_2 40 +#define MZ2_SOLDIER_SHOTGUN_1 41 +#define MZ2_SOLDIER_SHOTGUN_2 42 +#define MZ2_SOLDIER_MACHINEGUN_1 43 +#define MZ2_SOLDIER_MACHINEGUN_2 44 + +#define MZ2_GUNNER_MACHINEGUN_1 45 +#define MZ2_GUNNER_MACHINEGUN_2 46 +#define MZ2_GUNNER_MACHINEGUN_3 47 +#define MZ2_GUNNER_MACHINEGUN_4 48 +#define MZ2_GUNNER_MACHINEGUN_5 49 +#define MZ2_GUNNER_MACHINEGUN_6 50 +#define MZ2_GUNNER_MACHINEGUN_7 51 +#define MZ2_GUNNER_MACHINEGUN_8 52 +#define MZ2_GUNNER_GRENADE_1 53 +#define MZ2_GUNNER_GRENADE_2 54 +#define MZ2_GUNNER_GRENADE_3 55 +#define MZ2_GUNNER_GRENADE_4 56 + +#define MZ2_CHICK_ROCKET_1 57 + +#define MZ2_FLYER_BLASTER_1 58 +#define MZ2_FLYER_BLASTER_2 59 + +#define MZ2_MEDIC_BLASTER_1 60 + +#define MZ2_GLADIATOR_RAILGUN_1 61 + +#define MZ2_HOVER_BLASTER_1 62 + +#define MZ2_ACTOR_MACHINEGUN_1 63 + +#define MZ2_SUPERTANK_MACHINEGUN_1 64 +#define MZ2_SUPERTANK_MACHINEGUN_2 65 +#define MZ2_SUPERTANK_MACHINEGUN_3 66 +#define MZ2_SUPERTANK_MACHINEGUN_4 67 +#define MZ2_SUPERTANK_MACHINEGUN_5 68 +#define MZ2_SUPERTANK_MACHINEGUN_6 69 +#define MZ2_SUPERTANK_ROCKET_1 70 +#define MZ2_SUPERTANK_ROCKET_2 71 +#define MZ2_SUPERTANK_ROCKET_3 72 + +#define MZ2_BOSS2_MACHINEGUN_L1 73 +#define MZ2_BOSS2_MACHINEGUN_L2 74 +#define MZ2_BOSS2_MACHINEGUN_L3 75 +#define MZ2_BOSS2_MACHINEGUN_L4 76 +#define MZ2_BOSS2_MACHINEGUN_L5 77 +#define MZ2_BOSS2_ROCKET_1 78 +#define MZ2_BOSS2_ROCKET_2 79 +#define MZ2_BOSS2_ROCKET_3 80 +#define MZ2_BOSS2_ROCKET_4 81 + +#define MZ2_FLOAT_BLASTER_1 82 + +#define MZ2_SOLDIER_BLASTER_3 83 +#define MZ2_SOLDIER_SHOTGUN_3 84 +#define MZ2_SOLDIER_MACHINEGUN_3 85 +#define MZ2_SOLDIER_BLASTER_4 86 +#define MZ2_SOLDIER_SHOTGUN_4 87 +#define MZ2_SOLDIER_MACHINEGUN_4 88 +#define MZ2_SOLDIER_BLASTER_5 89 +#define MZ2_SOLDIER_SHOTGUN_5 90 +#define MZ2_SOLDIER_MACHINEGUN_5 91 +#define MZ2_SOLDIER_BLASTER_6 92 +#define MZ2_SOLDIER_SHOTGUN_6 93 +#define MZ2_SOLDIER_MACHINEGUN_6 94 +#define MZ2_SOLDIER_BLASTER_7 95 +#define MZ2_SOLDIER_SHOTGUN_7 96 +#define MZ2_SOLDIER_MACHINEGUN_7 97 +#define MZ2_SOLDIER_BLASTER_8 98 +#define MZ2_SOLDIER_SHOTGUN_8 99 +#define MZ2_SOLDIER_MACHINEGUN_8 100 + +// --- Xian shit below --- +#define MZ2_MAKRON_BFG 101 +#define MZ2_MAKRON_BLASTER_1 102 +#define MZ2_MAKRON_BLASTER_2 103 +#define MZ2_MAKRON_BLASTER_3 104 +#define MZ2_MAKRON_BLASTER_4 105 +#define MZ2_MAKRON_BLASTER_5 106 +#define MZ2_MAKRON_BLASTER_6 107 +#define MZ2_MAKRON_BLASTER_7 108 +#define MZ2_MAKRON_BLASTER_8 109 +#define MZ2_MAKRON_BLASTER_9 110 +#define MZ2_MAKRON_BLASTER_10 111 +#define MZ2_MAKRON_BLASTER_11 112 +#define MZ2_MAKRON_BLASTER_12 113 +#define MZ2_MAKRON_BLASTER_13 114 +#define MZ2_MAKRON_BLASTER_14 115 +#define MZ2_MAKRON_BLASTER_15 116 +#define MZ2_MAKRON_BLASTER_16 117 +#define MZ2_MAKRON_BLASTER_17 118 +#define MZ2_MAKRON_RAILGUN_1 119 +#define MZ2_JORG_MACHINEGUN_L1 120 +#define MZ2_JORG_MACHINEGUN_L2 121 +#define MZ2_JORG_MACHINEGUN_L3 122 +#define MZ2_JORG_MACHINEGUN_L4 123 +#define MZ2_JORG_MACHINEGUN_L5 124 +#define MZ2_JORG_MACHINEGUN_L6 125 +#define MZ2_JORG_MACHINEGUN_R1 126 +#define MZ2_JORG_MACHINEGUN_R2 127 +#define MZ2_JORG_MACHINEGUN_R3 128 +#define MZ2_JORG_MACHINEGUN_R4 129 +#define MZ2_JORG_MACHINEGUN_R5 130 +#define MZ2_JORG_MACHINEGUN_R6 131 +#define MZ2_JORG_BFG_1 132 +#define MZ2_BOSS2_MACHINEGUN_R1 133 +#define MZ2_BOSS2_MACHINEGUN_R2 134 +#define MZ2_BOSS2_MACHINEGUN_R3 135 +#define MZ2_BOSS2_MACHINEGUN_R4 136 +#define MZ2_BOSS2_MACHINEGUN_R5 137 + +//ROGUE +#define MZ2_CARRIER_MACHINEGUN_L1 138 +#define MZ2_CARRIER_MACHINEGUN_R1 139 +#define MZ2_CARRIER_GRENADE 140 +#define MZ2_TURRET_MACHINEGUN 141 +#define MZ2_TURRET_ROCKET 142 +#define MZ2_TURRET_BLASTER 143 +#define MZ2_STALKER_BLASTER 144 +#define MZ2_DAEDALUS_BLASTER 145 +#define MZ2_MEDIC_BLASTER_2 146 +#define MZ2_CARRIER_RAILGUN 147 +#define MZ2_WIDOW_DISRUPTOR 148 +#define MZ2_WIDOW_BLASTER 149 +#define MZ2_WIDOW_RAIL 150 +#define MZ2_WIDOW_PLASMABEAM 151 // PMM - not used +#define MZ2_CARRIER_MACHINEGUN_L2 152 +#define MZ2_CARRIER_MACHINEGUN_R2 153 +#define MZ2_WIDOW_RAIL_LEFT 154 +#define MZ2_WIDOW_RAIL_RIGHT 155 +#define MZ2_WIDOW_BLASTER_SWEEP1 156 +#define MZ2_WIDOW_BLASTER_SWEEP2 157 +#define MZ2_WIDOW_BLASTER_SWEEP3 158 +#define MZ2_WIDOW_BLASTER_SWEEP4 159 +#define MZ2_WIDOW_BLASTER_SWEEP5 160 +#define MZ2_WIDOW_BLASTER_SWEEP6 161 +#define MZ2_WIDOW_BLASTER_SWEEP7 162 +#define MZ2_WIDOW_BLASTER_SWEEP8 163 +#define MZ2_WIDOW_BLASTER_SWEEP9 164 +#define MZ2_WIDOW_BLASTER_100 165 +#define MZ2_WIDOW_BLASTER_90 166 +#define MZ2_WIDOW_BLASTER_80 167 +#define MZ2_WIDOW_BLASTER_70 168 +#define MZ2_WIDOW_BLASTER_60 169 +#define MZ2_WIDOW_BLASTER_50 170 +#define MZ2_WIDOW_BLASTER_40 171 +#define MZ2_WIDOW_BLASTER_30 172 +#define MZ2_WIDOW_BLASTER_20 173 +#define MZ2_WIDOW_BLASTER_10 174 +#define MZ2_WIDOW_BLASTER_0 175 +#define MZ2_WIDOW_BLASTER_10L 176 +#define MZ2_WIDOW_BLASTER_20L 177 +#define MZ2_WIDOW_BLASTER_30L 178 +#define MZ2_WIDOW_BLASTER_40L 179 +#define MZ2_WIDOW_BLASTER_50L 180 +#define MZ2_WIDOW_BLASTER_60L 181 +#define MZ2_WIDOW_BLASTER_70L 182 +#define MZ2_WIDOW_RUN_1 183 +#define MZ2_WIDOW_RUN_2 184 +#define MZ2_WIDOW_RUN_3 185 +#define MZ2_WIDOW_RUN_4 186 +#define MZ2_WIDOW_RUN_5 187 +#define MZ2_WIDOW_RUN_6 188 +#define MZ2_WIDOW_RUN_7 189 +#define MZ2_WIDOW_RUN_8 190 +#define MZ2_CARRIER_ROCKET_1 191 +#define MZ2_CARRIER_ROCKET_2 192 +#define MZ2_CARRIER_ROCKET_3 193 +#define MZ2_CARRIER_ROCKET_4 194 +#define MZ2_WIDOW2_BEAMER_1 195 +#define MZ2_WIDOW2_BEAMER_2 196 +#define MZ2_WIDOW2_BEAMER_3 197 +#define MZ2_WIDOW2_BEAMER_4 198 +#define MZ2_WIDOW2_BEAMER_5 199 +#define MZ2_WIDOW2_BEAM_SWEEP_1 200 +#define MZ2_WIDOW2_BEAM_SWEEP_2 201 +#define MZ2_WIDOW2_BEAM_SWEEP_3 202 +#define MZ2_WIDOW2_BEAM_SWEEP_4 203 +#define MZ2_WIDOW2_BEAM_SWEEP_5 204 +#define MZ2_WIDOW2_BEAM_SWEEP_6 205 +#define MZ2_WIDOW2_BEAM_SWEEP_7 206 +#define MZ2_WIDOW2_BEAM_SWEEP_8 207 +#define MZ2_WIDOW2_BEAM_SWEEP_9 208 +#define MZ2_WIDOW2_BEAM_SWEEP_10 209 +#define MZ2_WIDOW2_BEAM_SWEEP_11 210 + +// ROGUE + +extern vec3_t monster_flash_offset []; + + +// temp entity events +// +// Temp entity events are for things that happen +// at a location seperate from any existing entity. +// Temporary entity messages are explicitly constructed +// and broadcast. +typedef enum +{ + TE_GUNSHOT, + TE_BLOOD, + TE_BLASTER, + TE_RAILTRAIL, + TE_SHOTGUN, + TE_EXPLOSION1, + TE_EXPLOSION2, + TE_ROCKET_EXPLOSION, + TE_GRENADE_EXPLOSION, + TE_SPARKS, + TE_SPLASH, + TE_BUBBLETRAIL, + TE_SCREEN_SPARKS, + TE_SHIELD_SPARKS, + TE_BULLET_SPARKS, + TE_LASER_SPARKS, + TE_PARASITE_ATTACK, + TE_ROCKET_EXPLOSION_WATER, + TE_GRENADE_EXPLOSION_WATER, + TE_MEDIC_CABLE_ATTACK, + TE_BFG_EXPLOSION, + TE_BFG_BIGEXPLOSION, + TE_BOSSTPORT, // used as '22' in a map, so DON'T RENUMBER!!! + TE_BFG_LASER, + TE_GRAPPLE_CABLE, + TE_WELDING_SPARKS, + TE_GREENBLOOD, + TE_BLUEHYPERBLASTER, + TE_PLASMA_EXPLOSION, + TE_TUNNEL_SPARKS, +//ROGUE + TE_BLASTER2, + TE_RAILTRAIL2, + TE_FLAME, + TE_LIGHTNING, + TE_DEBUGTRAIL, + TE_PLAIN_EXPLOSION, + TE_FLASHLIGHT, + TE_FORCEWALL, + TE_HEATBEAM, + TE_MONSTER_HEATBEAM, + TE_STEAM, + TE_BUBBLETRAIL2, + TE_MOREBLOOD, + TE_HEATBEAM_SPARKS, + TE_HEATBEAM_STEAM, + TE_CHAINFIST_SMOKE, + TE_ELECTRIC_SPARKS, + TE_TRACKER_EXPLOSION, + TE_TELEPORT_EFFECT, + TE_DBALL_GOAL, + TE_WIDOWBEAMOUT, + TE_NUKEBLAST, + TE_WIDOWSPLASH, + TE_EXPLOSION1_BIG, + TE_EXPLOSION1_NP, + TE_FLECHETTE +//ROGUE +} temp_event_t; + +#define SPLASH_UNKNOWN 0 +#define SPLASH_SPARKS 1 +#define SPLASH_BLUE_WATER 2 +#define SPLASH_BROWN_WATER 3 +#define SPLASH_SLIME 4 +#define SPLASH_LAVA 5 +#define SPLASH_BLOOD 6 + + +// sound channels +// channel 0 never willingly overrides +// other channels (1-7) allways override a playing sound on that channel +#define CHAN_AUTO 0 +#define CHAN_WEAPON 1 +#define CHAN_VOICE 2 +#define CHAN_ITEM 3 +#define CHAN_BODY 4 +// modifier flags +#define CHAN_NO_PHS_ADD 8 // send to all clients, not just ones in PHS (ATTN 0 will also do this) +#define CHAN_RELIABLE 16 // send by reliable message, not datagram + + +// sound attenuation values +#define ATTN_NONE 0 // full volume the entire level +#define ATTN_NORM 1 +#define ATTN_IDLE 2 +#define ATTN_STATIC 3 // diminish very rapidly with distance + + +// player_state->stats[] indexes +#define STAT_HEALTH_ICON 0 +#define STAT_HEALTH 1 +#define STAT_AMMO_ICON 2 +#define STAT_AMMO 3 +#define STAT_ARMOR_ICON 4 +#define STAT_ARMOR 5 +#define STAT_SELECTED_ICON 6 +#define STAT_PICKUP_ICON 7 +#define STAT_PICKUP_STRING 8 +#define STAT_TIMER_ICON 9 +#define STAT_TIMER 10 +#define STAT_HELPICON 11 +#define STAT_SELECTED_ITEM 12 +#define STAT_LAYOUTS 13 +#define STAT_FRAGS 14 +#define STAT_FLASHES 15 // cleared each frame, 1 = health, 2 = armor +#define STAT_CHASE 16 +#define STAT_SPECTATOR 17 + +#define MAX_STATS 32 + + +// dmflags->value flags +#define DF_NO_HEALTH 0x00000001 // 1 +#define DF_NO_ITEMS 0x00000002 // 2 +#define DF_WEAPONS_STAY 0x00000004 // 4 +#define DF_NO_FALLING 0x00000008 // 8 +#define DF_INSTANT_ITEMS 0x00000010 // 16 +#define DF_SAME_LEVEL 0x00000020 // 32 +#define DF_SKINTEAMS 0x00000040 // 64 +#define DF_MODELTEAMS 0x00000080 // 128 +#define DF_NO_FRIENDLY_FIRE 0x00000100 // 256 +#define DF_SPAWN_FARTHEST 0x00000200 // 512 +#define DF_FORCE_RESPAWN 0x00000400 // 1024 +#define DF_NO_ARMOR 0x00000800 // 2048 +#define DF_ALLOW_EXIT 0x00001000 // 4096 +#define DF_INFINITE_AMMO 0x00002000 // 8192 +#define DF_QUAD_DROP 0x00004000 // 16384 +#define DF_FIXED_FOV 0x00008000 // 32768 + +// RAFAEL +#define DF_QUADFIRE_DROP 0x00010000 // 65536 + +//ROGUE +#define DF_NO_MINES 0x00020000 +#define DF_NO_STACK_DOUBLE 0x00040000 +#define DF_NO_NUKES 0x00080000 +#define DF_NO_SPHERES 0x00100000 +//ROGUE + +/* +ROGUE - VERSIONS +1234 08/13/1998 Activision +1235 08/14/1998 Id Software +1236 08/15/1998 Steve Tietze +1237 08/15/1998 Phil Dobranski +1238 08/15/1998 John Sheley +1239 08/17/1998 Barrett Alexander +1230 08/17/1998 Brandon Fish +1245 08/17/1998 Don MacAskill +1246 08/17/1998 David "Zoid" Kirsch +1247 08/17/1998 Manu Smith +1248 08/17/1998 Geoff Scully +1249 08/17/1998 Andy Van Fossen +1240 08/20/1998 Activision Build 2 +1256 08/20/1998 Ranger Clan +1257 08/20/1998 Ensemble Studios +1258 08/21/1998 Robert Duffy +1259 08/21/1998 Stephen Seachord +1250 08/21/1998 Stephen Heaslip +1267 08/21/1998 Samir Sandesara +1268 08/21/1998 Oliver Wyman +1269 08/21/1998 Steven Marchegiano +1260 08/21/1998 Build #2 for Nihilistic +1278 08/21/1998 Build #2 for Ensemble + +9999 08/20/1998 Internal Use +*/ +#define ROGUE_VERSION_ID 1278 + +#define ROGUE_VERSION_STRING "08/21/1998 Beta 2 for Ensemble" + +// ROGUE +/* +========================================================== + + ELEMENTS COMMUNICATED ACROSS THE NET + +========================================================== +*/ + +#define ANGLE2SHORT(x) ((int)((x)*65536/360) & 65535) +#define SHORT2ANGLE(x) ((x)*(360.0/65536)) + + +// +// config strings are a general means of communication from +// the server to all connected clients. +// Each config string can be at most MAX_QPATH characters. +// +#define CS_NAME 0 +#define CS_CDTRACK 1 +#define CS_SKY 2 +#define CS_SKYAXIS 3 // %f %f %f format +#define CS_SKYROTATE 4 +#define CS_STATUSBAR 5 // display program string + +#define CS_AIRACCEL 29 // air acceleration control +#define CS_MAXCLIENTS 30 +#define CS_MAPCHECKSUM 31 // for catching cheater maps + +#define CS_MODELS 32 +#define CS_SOUNDS (CS_MODELS+MAX_MODELS) +#define CS_IMAGES (CS_SOUNDS+MAX_SOUNDS) +#define CS_LIGHTS (CS_IMAGES+MAX_IMAGES) +#define CS_ITEMS (CS_LIGHTS+MAX_LIGHTSTYLES) +#define CS_PLAYERSKINS (CS_ITEMS+MAX_ITEMS) +#define CS_GENERAL (CS_PLAYERSKINS+MAX_CLIENTS) +#define MAX_CONFIGSTRINGS (CS_GENERAL+MAX_GENERAL) + + +//============================================== + + +// entity_state_t->event values +// ertity events are for effects that take place reletive +// to an existing entities origin. Very network efficient. +// All muzzle flashes really should be converted to events... +typedef enum +{ + EV_NONE, + EV_ITEM_RESPAWN, + EV_FOOTSTEP, + EV_FALLSHORT, + EV_FALL, + EV_FALLFAR, + EV_PLAYER_TELEPORT, + EV_OTHER_TELEPORT +} entity_event_t; + + +// entity_state_t is the information conveyed from the server +// in an update message about entities that the client will +// need to render in some way +typedef struct entity_state_s +{ + int number; // edict index + + vec3_t origin; + vec3_t angles; + vec3_t old_origin; // for lerping + int modelindex; + int modelindex2, modelindex3, modelindex4; // weapons, CTF flags, etc + int frame; + int skinnum; + unsigned int effects; // PGM - we're filling it, so it needs to be unsigned + int renderfx; + int solid; // for client side prediction, 8*(bits 0-4) is x/y radius + // 8*(bits 5-9) is z down distance, 8(bits10-15) is z up + // gi.linkentity sets this properly + int sound; // for looping sounds, to guarantee shutoff + int event; // impulse events -- muzzle flashes, footsteps, etc + // events only go out for a single frame, they + // are automatically cleared each frame +} entity_state_t; + +//============================================== + + +// player_state_t is the information needed in addition to pmove_state_t +// to rendered a view. There will only be 10 player_state_t sent each second, +// but the number of pmove_state_t changes will be reletive to client +// frame rates +typedef struct +{ + pmove_state_t pmove; // for prediction + + // these fields do not need to be communicated bit-precise + + vec3_t viewangles; // for fixed views + vec3_t viewoffset; // add to pmovestate->origin + vec3_t kick_angles; // add to view direction to get render angles + // set by weapon kicks, pain effects, etc + + vec3_t gunangles; + vec3_t gunoffset; + int gunindex; + int gunframe; + + float blend[4]; // rgba full screen effect + + float fov; // horizontal field of view + + int rdflags; // refdef flags + + short stats[MAX_STATS]; // fast status bar updates +} player_state_t; + + +// ================== +// PGM +#define VIDREF_GL 1 +#define VIDREF_SOFT 2 +#define VIDREF_OTHER 3 + +extern int vidref_val; +// PGM +// ================== diff --git a/original/ctf/g_ai.c b/original/ctf/g_ai.c new file mode 100644 index 0000000..c5ea318 --- /dev/null +++ b/original/ctf/g_ai.c @@ -0,0 +1,1117 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +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. + +*/ +// g_ai.c + +#include "g_local.h" + +qboolean FindTarget (edict_t *self); +extern cvar_t *maxclients; + +qboolean ai_checkattack (edict_t *self, float dist); + +qboolean enemy_vis; +qboolean enemy_infront; +int enemy_range; +float enemy_yaw; + +//============================================================================ + + +/* +================= +AI_SetSightClient + +Called once each frame to set level.sight_client to the +player to be checked for in findtarget. + +If all clients are either dead or in notarget, sight_client +will be null. + +In coop games, sight_client will cycle between the clients. +================= +*/ +void AI_SetSightClient (void) +{ + edict_t *ent; + int start, check; + + if (level.sight_client == NULL) + start = 1; + else + start = level.sight_client - g_edicts; + + check = start; + while (1) + { + check++; + if (check > game.maxclients) + check = 1; + ent = &g_edicts[check]; + if (ent->inuse + && ent->health > 0 + && !(ent->flags & FL_NOTARGET) ) + { + level.sight_client = ent; + return; // got one + } + if (check == start) + { + level.sight_client = NULL; + return; // nobody to see + } + } +} + +//============================================================================ + +/* +============= +ai_move + +Move the specified distance at current facing. +This replaces the QC functions: ai_forward, ai_back, ai_pain, and ai_painforward +============== +*/ +void ai_move (edict_t *self, float dist) +{ + M_walkmove (self, self->s.angles[YAW], dist); +} + + +/* +============= +ai_stand + +Used for standing around and looking for players +Distance is for slight position adjustments needed by the animations +============== +*/ +void ai_stand (edict_t *self, float dist) +{ + vec3_t v; + + if (dist) + M_walkmove (self, self->s.angles[YAW], dist); + + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + { + if (self->enemy) + { + VectorSubtract (self->enemy->s.origin, self->s.origin, v); + self->ideal_yaw = vectoyaw(v); + if (self->s.angles[YAW] != self->ideal_yaw && self->monsterinfo.aiflags & AI_TEMP_STAND_GROUND) + { + self->monsterinfo.aiflags &= ~(AI_STAND_GROUND | AI_TEMP_STAND_GROUND); + self->monsterinfo.run (self); + } + M_ChangeYaw (self); + ai_checkattack (self, 0); + } + else + FindTarget (self); + return; + } + + if (FindTarget (self)) + return; + + if (level.time > self->monsterinfo.pausetime) + { + self->monsterinfo.walk (self); + return; + } + + if (!(self->spawnflags & 1) && (self->monsterinfo.idle) && (level.time > self->monsterinfo.idle_time)) + { + if (self->monsterinfo.idle_time) + { + self->monsterinfo.idle (self); + self->monsterinfo.idle_time = level.time + 15 + random() * 15; + } + else + { + self->monsterinfo.idle_time = level.time + random() * 15; + } + } +} + + +/* +============= +ai_walk + +The monster is walking it's beat +============= +*/ +void ai_walk (edict_t *self, float dist) +{ + M_MoveToGoal (self, dist); + + // check for noticing a player + if (FindTarget (self)) + return; + + if ((self->monsterinfo.search) && (level.time > self->monsterinfo.idle_time)) + { + if (self->monsterinfo.idle_time) + { + self->monsterinfo.search (self); + self->monsterinfo.idle_time = level.time + 15 + random() * 15; + } + else + { + self->monsterinfo.idle_time = level.time + random() * 15; + } + } +} + + +/* +============= +ai_charge + +Turns towards target and advances +Use this call with a distnace of 0 to replace ai_face +============== +*/ +void ai_charge (edict_t *self, float dist) +{ + vec3_t v; + + VectorSubtract (self->enemy->s.origin, self->s.origin, v); + self->ideal_yaw = vectoyaw(v); + M_ChangeYaw (self); + + if (dist) + M_walkmove (self, self->s.angles[YAW], dist); +} + + +/* +============= +ai_turn + +don't move, but turn towards ideal_yaw +Distance is for slight position adjustments needed by the animations +============= +*/ +void ai_turn (edict_t *self, float dist) +{ + if (dist) + M_walkmove (self, self->s.angles[YAW], dist); + + if (FindTarget (self)) + return; + + M_ChangeYaw (self); +} + + +/* + +.enemy +Will be world if not currently angry at anyone. + +.movetarget +The next path spot to walk toward. If .enemy, ignore .movetarget. +When an enemy is killed, the monster will try to return to it's path. + +.hunt_time +Set to time + something when the player is in sight, but movement straight for +him is blocked. This causes the monster to use wall following code for +movement direction instead of sighting on the player. + +.ideal_yaw +A yaw angle of the intended direction, which will be turned towards at up +to 45 deg / state. If the enemy is in view and hunt_time is not active, +this will be the exact line towards the enemy. + +.pausetime +A monster will leave it's stand state and head towards it's .movetarget when +time > .pausetime. + +walkmove(angle, speed) primitive is all or nothing +*/ + +/* +============= +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 +============= +*/ +int range (edict_t *self, edict_t *other) +{ + vec3_t v; + float len; + + VectorSubtract (self->s.origin, other->s.origin, v); + len = VectorLength (v); + if (len < MELEE_DISTANCE) + return RANGE_MELEE; + if (len < 500) + return RANGE_NEAR; + if (len < 1000) + return RANGE_MID; + return RANGE_FAR; +} + +/* +============= +visible + +returns 1 if the entity is visible to self, even if not infront () +============= +*/ +qboolean visible (edict_t *self, edict_t *other) +{ + vec3_t spot1; + vec3_t spot2; + trace_t trace; + + VectorCopy (self->s.origin, spot1); + spot1[2] += self->viewheight; + VectorCopy (other->s.origin, spot2); + spot2[2] += other->viewheight; + trace = gi.trace (spot1, vec3_origin, vec3_origin, spot2, self, MASK_OPAQUE); + + if (trace.fraction == 1.0) + return true; + return false; +} + + +/* +============= +infront + +returns 1 if the entity is in front (in sight) of self +============= +*/ +qboolean infront (edict_t *self, edict_t *other) +{ + vec3_t vec; + float dot; + vec3_t forward; + + AngleVectors (self->s.angles, forward, NULL, NULL); + VectorSubtract (other->s.origin, self->s.origin, vec); + VectorNormalize (vec); + dot = DotProduct (vec, forward); + + if (dot > 0.3) + return true; + return false; +} + + +//============================================================================ + +void HuntTarget (edict_t *self) +{ + vec3_t vec; + + self->goalentity = self->enemy; + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + self->monsterinfo.stand (self); + else + self->monsterinfo.run (self); + VectorSubtract (self->enemy->s.origin, self->s.origin, vec); + self->ideal_yaw = vectoyaw(vec); + // wait a while before first attack + if (!(self->monsterinfo.aiflags & AI_STAND_GROUND)) + AttackFinished (self, 1); +} + +void FoundTarget (edict_t *self) +{ + // let other monsters see this monster for a while + if (self->enemy->client) + { + level.sight_entity = self; + level.sight_entity_framenum = level.framenum; + level.sight_entity->light_level = 128; + } + + self->show_hostile = level.time + 1; // wake up other monsters + + VectorCopy(self->enemy->s.origin, self->monsterinfo.last_sighting); + self->monsterinfo.trail_time = level.time; + + if (!self->combattarget) + { + HuntTarget (self); + return; + } + + self->goalentity = self->movetarget = G_PickTarget(self->combattarget); + if (!self->movetarget) + { + self->goalentity = self->movetarget = self->enemy; + HuntTarget (self); + gi.dprintf("%s at %s, combattarget %s not found\n", self->classname, vtos(self->s.origin), self->combattarget); + return; + } + + // clear out our combattarget, these are a one shot deal + self->combattarget = NULL; + self->monsterinfo.aiflags |= AI_COMBAT_POINT; + + // clear the targetname, that point is ours! + self->movetarget->targetname = NULL; + self->monsterinfo.pausetime = 0; + + // run for it + self->monsterinfo.run (self); +} + + +/* +=========== +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. +============ +*/ +qboolean FindTarget (edict_t *self) +{ + edict_t *client; + qboolean heardit; + int r; + + if (self->monsterinfo.aiflags & AI_GOOD_GUY) + { + if (self->goalentity && self->goalentity->inuse && self->goalentity->classname) + { + if (strcmp(self->goalentity->classname, "target_actor") == 0) + return false; + } + + //FIXME look for monsters? + return false; + } + + // if we're going to a combat point, just proceed + if (self->monsterinfo.aiflags & AI_COMBAT_POINT) + return false; + +// if the first spawnflag bit is set, the monster will only wake up on +// really seeing the player, not another monster getting angry or hearing +// something + +// revised behavior so they will wake up if they "see" a player make a noise +// but not weapon impact/explosion noises + + heardit = false; + if ((level.sight_entity_framenum >= (level.framenum - 1)) && !(self->spawnflags & 1) ) + { + client = level.sight_entity; + if (client->enemy == self->enemy) + { + return false; + } + } + else if (level.sound_entity_framenum >= (level.framenum - 1)) + { + client = level.sound_entity; + heardit = true; + } + else if (!(self->enemy) && (level.sound2_entity_framenum >= (level.framenum - 1)) && !(self->spawnflags & 1) ) + { + client = level.sound2_entity; + heardit = true; + } + else + { + client = level.sight_client; + if (!client) + return false; // no clients to get mad at + } + + // if the entity went away, forget it + if (!client->inuse) + return false; + + if (client == self->enemy) + return true; // JDC false; + + if (client->client) + { + if (client->flags & FL_NOTARGET) + return false; + } + else if (client->svflags & SVF_MONSTER) + { + if (!client->enemy) + return false; + if (client->enemy->flags & FL_NOTARGET) + return false; + } + else if (heardit) + { + if (client->owner->flags & FL_NOTARGET) + return false; + } + else + return false; + + if (!heardit) + { + r = range (self, client); + + if (r == RANGE_FAR) + return false; + +// this is where we would check invisibility + + // is client in an spot too dark to be seen? + if (client->light_level <= 5) + return false; + + if (!visible (self, client)) + { + return false; + } + + if (r == RANGE_NEAR) + { + if (client->show_hostile < level.time && !infront (self, client)) + { + return false; + } + } + else if (r == RANGE_MID) + { + if (!infront (self, client)) + { + return false; + } + } + + self->enemy = client; + + if (strcmp(self->enemy->classname, "player_noise") != 0) + { + self->monsterinfo.aiflags &= ~AI_SOUND_TARGET; + + if (!self->enemy->client) + { + self->enemy = self->enemy->enemy; + if (!self->enemy->client) + { + self->enemy = NULL; + return false; + } + } + } + } + else // heardit + { + vec3_t temp; + + if (self->spawnflags & 1) + { + if (!visible (self, client)) + return false; + } + else + { + if (!gi.inPHS(self->s.origin, client->s.origin)) + return false; + } + + VectorSubtract (client->s.origin, self->s.origin, temp); + + if (VectorLength(temp) > 1000) // too far to hear + { + return false; + } + + // check area portals - if they are different and not connected then we can't hear it + if (client->areanum != self->areanum) + if (!gi.AreasConnected(self->areanum, client->areanum)) + return false; + + self->ideal_yaw = vectoyaw(temp); + M_ChangeYaw (self); + + // hunt the sound for a bit; hopefully find the real player + self->monsterinfo.aiflags |= AI_SOUND_TARGET; + self->enemy = client; + } + +// +// got one +// + FoundTarget (self); + + if (!(self->monsterinfo.aiflags & AI_SOUND_TARGET) && (self->monsterinfo.sight)) + self->monsterinfo.sight (self, self->enemy); + + return true; +} + + +//============================================================================= + +/* +============ +FacingIdeal + +============ +*/ +qboolean FacingIdeal(edict_t *self) +{ + float delta; + + delta = anglemod(self->s.angles[YAW] - self->ideal_yaw); + if (delta > 45 && delta < 315) + return false; + return true; +} + + +//============================================================================= + +qboolean M_CheckAttack (edict_t *self) +{ + vec3_t spot1, spot2; + float chance; + trace_t tr; + + if (self->enemy->health > 0) + { + // see if any entities are in the way of the shot + VectorCopy (self->s.origin, spot1); + spot1[2] += self->viewheight; + VectorCopy (self->enemy->s.origin, spot2); + spot2[2] += self->enemy->viewheight; + + tr = gi.trace (spot1, NULL, NULL, spot2, self, CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_SLIME|CONTENTS_LAVA|CONTENTS_WINDOW); + + // do we have a clear shot? + if (tr.ent != self->enemy) + return false; + } + + // melee attack + if (enemy_range == RANGE_MELEE) + { + // don't always melee in easy mode + if (skill->value == 0 && (rand()&3) ) + return false; + if (self->monsterinfo.melee) + self->monsterinfo.attack_state = AS_MELEE; + else + self->monsterinfo.attack_state = AS_MISSILE; + return true; + } + +// missile attack + if (!self->monsterinfo.attack) + return false; + + if (level.time < self->monsterinfo.attack_finished) + return false; + + if (enemy_range == RANGE_FAR) + return false; + + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + { + chance = 0.4; + } + else if (enemy_range == RANGE_MELEE) + { + chance = 0.2; + } + else if (enemy_range == RANGE_NEAR) + { + chance = 0.1; + } + else if (enemy_range == RANGE_MID) + { + chance = 0.02; + } + else + { + return false; + } + + if (skill->value == 0) + chance *= 0.5; + else if (skill->value >= 2) + chance *= 2; + + if (random () < chance) + { + self->monsterinfo.attack_state = AS_MISSILE; + self->monsterinfo.attack_finished = level.time + 2*random(); + return true; + } + + if (self->flags & FL_FLY) + { + if (random() < 0.3) + self->monsterinfo.attack_state = AS_SLIDING; + else + self->monsterinfo.attack_state = AS_STRAIGHT; + } + + return false; +} + + +/* +============= +ai_run_melee + +Turn and close until within an angle to launch a melee attack +============= +*/ +void ai_run_melee(edict_t *self) +{ + self->ideal_yaw = enemy_yaw; + M_ChangeYaw (self); + + if (FacingIdeal(self)) + { + self->monsterinfo.melee (self); + self->monsterinfo.attack_state = AS_STRAIGHT; + } +} + + +/* +============= +ai_run_missile + +Turn in place until within an angle to launch a missile attack +============= +*/ +void ai_run_missile(edict_t *self) +{ + self->ideal_yaw = enemy_yaw; + M_ChangeYaw (self); + + if (FacingIdeal(self)) + { + self->monsterinfo.attack (self); + self->monsterinfo.attack_state = AS_STRAIGHT; + } +}; + + +/* +============= +ai_run_slide + +Strafe sideways, but stay at aproximately the same range +============= +*/ +void ai_run_slide(edict_t *self, float distance) +{ + float ofs; + + self->ideal_yaw = enemy_yaw; + M_ChangeYaw (self); + + if (self->monsterinfo.lefty) + ofs = 90; + else + ofs = -90; + + if (M_walkmove (self, self->ideal_yaw + ofs, distance)) + return; + + self->monsterinfo.lefty = 1 - self->monsterinfo.lefty; + M_walkmove (self, self->ideal_yaw - ofs, distance); +} + + +/* +============= +ai_checkattack + +Decides if we're going to attack or do something else +used by ai_run and ai_stand +============= +*/ +qboolean ai_checkattack (edict_t *self, float dist) +{ + vec3_t temp; + qboolean hesDeadJim; + +// this causes monsters to run blindly to the combat point w/o firing + if (self->goalentity) + { + if (self->monsterinfo.aiflags & AI_COMBAT_POINT) + return false; + + if (self->monsterinfo.aiflags & AI_SOUND_TARGET) + { + if ((level.time - self->enemy->teleport_time) > 5.0) + { + if (self->goalentity == self->enemy) + if (self->movetarget) + self->goalentity = self->movetarget; + else + self->goalentity = NULL; + self->monsterinfo.aiflags &= ~AI_SOUND_TARGET; + if (self->monsterinfo.aiflags & AI_TEMP_STAND_GROUND) + self->monsterinfo.aiflags &= ~(AI_STAND_GROUND | AI_TEMP_STAND_GROUND); + } + else + { + self->show_hostile = level.time + 1; + return false; + } + } + } + + enemy_vis = false; + +// see if the enemy is dead + hesDeadJim = false; + if ((!self->enemy) || (!self->enemy->inuse)) + { + hesDeadJim = true; + } + else if (self->monsterinfo.aiflags & AI_MEDIC) + { + if (self->enemy->health > 0) + { + hesDeadJim = true; + self->monsterinfo.aiflags &= ~AI_MEDIC; + } + } + else + { + if (self->monsterinfo.aiflags & AI_BRUTAL) + { + if (self->enemy->health <= -80) + hesDeadJim = true; + } + else + { + if (self->enemy->health <= 0) + hesDeadJim = true; + } + } + + if (hesDeadJim) + { + self->enemy = NULL; + // FIXME: look all around for other targets + if (self->oldenemy && self->oldenemy->health > 0) + { + self->enemy = self->oldenemy; + self->oldenemy = NULL; + HuntTarget (self); + } + else + { + if (self->movetarget) + { + self->goalentity = self->movetarget; + self->monsterinfo.walk (self); + } + else + { + // we need the pausetime otherwise the stand code + // will just revert to walking with no target and + // the monsters will wonder around aimlessly trying + // to hunt the world entity + self->monsterinfo.pausetime = level.time + 100000000; + self->monsterinfo.stand (self); + } + return true; + } + } + + self->show_hostile = level.time + 1; // wake up other monsters + +// check knowledge of enemy + enemy_vis = visible(self, self->enemy); + if (enemy_vis) + { + self->monsterinfo.search_time = level.time + 5; + VectorCopy (self->enemy->s.origin, self->monsterinfo.last_sighting); + } + +// look for other coop players here +// if (coop && self->monsterinfo.search_time < level.time) +// { +// if (FindTarget (self)) +// return true; +// } + + enemy_infront = infront(self, self->enemy); + enemy_range = range(self, self->enemy); + VectorSubtract (self->enemy->s.origin, self->s.origin, temp); + enemy_yaw = vectoyaw(temp); + + + // JDC self->ideal_yaw = enemy_yaw; + + if (self->monsterinfo.attack_state == AS_MISSILE) + { + ai_run_missile (self); + return true; + } + if (self->monsterinfo.attack_state == AS_MELEE) + { + ai_run_melee (self); + return true; + } + + // if enemy is not currently visible, we will never attack + if (!enemy_vis) + return false; + + return self->monsterinfo.checkattack (self); +} + + +/* +============= +ai_run + +The monster has an enemy it is trying to kill +============= +*/ +void ai_run (edict_t *self, float dist) +{ + vec3_t v; + edict_t *tempgoal; + edict_t *save; + qboolean new; + edict_t *marker; + float d1, d2; + trace_t tr; + vec3_t v_forward, v_right; + float left, center, right; + vec3_t left_target, right_target; + + // if we're going to a combat point, just proceed + if (self->monsterinfo.aiflags & AI_COMBAT_POINT) + { + M_MoveToGoal (self, dist); + return; + } + + if (self->monsterinfo.aiflags & AI_SOUND_TARGET) + { + VectorSubtract (self->s.origin, self->enemy->s.origin, v); + if (VectorLength(v) < 64) + { + self->monsterinfo.aiflags |= (AI_STAND_GROUND | AI_TEMP_STAND_GROUND); + self->monsterinfo.stand (self); + return; + } + + M_MoveToGoal (self, dist); + + if (!FindTarget (self)) + return; + } + + if (ai_checkattack (self, dist)) + return; + + if (self->monsterinfo.attack_state == AS_SLIDING) + { + ai_run_slide (self, dist); + return; + } + + if (enemy_vis) + { +// if (self.aiflags & AI_LOST_SIGHT) +// dprint("regained sight\n"); + M_MoveToGoal (self, dist); + self->monsterinfo.aiflags &= ~AI_LOST_SIGHT; + VectorCopy (self->enemy->s.origin, self->monsterinfo.last_sighting); + self->monsterinfo.trail_time = level.time; + return; + } + + // coop will change to another enemy if visible + if (coop->value) + { // FIXME: insane guys get mad with this, which causes crashes! + if (FindTarget (self)) + return; + } + + if ((self->monsterinfo.search_time) && (level.time > (self->monsterinfo.search_time + 20))) + { + M_MoveToGoal (self, dist); + self->monsterinfo.search_time = 0; +// dprint("search timeout\n"); + return; + } + + save = self->goalentity; + tempgoal = G_Spawn(); + self->goalentity = tempgoal; + + new = false; + + if (!(self->monsterinfo.aiflags & AI_LOST_SIGHT)) + { + // just lost sight of the player, decide where to go first +// dprint("lost sight of player, last seen at "); dprint(vtos(self.last_sighting)); dprint("\n"); + self->monsterinfo.aiflags |= (AI_LOST_SIGHT | AI_PURSUIT_LAST_SEEN); + self->monsterinfo.aiflags &= ~(AI_PURSUE_NEXT | AI_PURSUE_TEMP); + new = true; + } + + if (self->monsterinfo.aiflags & AI_PURSUE_NEXT) + { + self->monsterinfo.aiflags &= ~AI_PURSUE_NEXT; +// dprint("reached current goal: "); dprint(vtos(self.origin)); dprint(" "); dprint(vtos(self.last_sighting)); dprint(" "); dprint(ftos(vlen(self.origin - self.last_sighting))); dprint("\n"); + + // give ourself more time since we got this far + self->monsterinfo.search_time = level.time + 5; + + if (self->monsterinfo.aiflags & AI_PURSUE_TEMP) + { +// dprint("was temp goal; retrying original\n"); + self->monsterinfo.aiflags &= ~AI_PURSUE_TEMP; + marker = NULL; + VectorCopy (self->monsterinfo.saved_goal, self->monsterinfo.last_sighting); + new = true; + } + else if (self->monsterinfo.aiflags & AI_PURSUIT_LAST_SEEN) + { + self->monsterinfo.aiflags &= ~AI_PURSUIT_LAST_SEEN; + marker = PlayerTrail_PickFirst (self); + } + else + { + marker = PlayerTrail_PickNext (self); + } + + if (marker) + { + VectorCopy (marker->s.origin, self->monsterinfo.last_sighting); + self->monsterinfo.trail_time = marker->timestamp; + self->s.angles[YAW] = self->ideal_yaw = marker->s.angles[YAW]; +// dprint("heading is "); dprint(ftos(self.ideal_yaw)); dprint("\n"); + +// debug_drawline(self.origin, self.last_sighting, 52); + new = true; + } + } + + VectorSubtract (self->s.origin, self->monsterinfo.last_sighting, v); + d1 = VectorLength(v); + if (d1 <= dist) + { + self->monsterinfo.aiflags |= AI_PURSUE_NEXT; + dist = d1; + } + + VectorCopy (self->monsterinfo.last_sighting, self->goalentity->s.origin); + + if (new) + { +// gi.dprintf("checking for course correction\n"); + + tr = gi.trace(self->s.origin, self->mins, self->maxs, self->monsterinfo.last_sighting, self, MASK_PLAYERSOLID); + if (tr.fraction < 1) + { + VectorSubtract (self->goalentity->s.origin, self->s.origin, v); + d1 = VectorLength(v); + center = tr.fraction; + d2 = d1 * ((center+1)/2); + self->s.angles[YAW] = self->ideal_yaw = vectoyaw(v); + AngleVectors(self->s.angles, v_forward, v_right, NULL); + + VectorSet(v, d2, -16, 0); + G_ProjectSource (self->s.origin, v, v_forward, v_right, left_target); + tr = gi.trace(self->s.origin, self->mins, self->maxs, left_target, self, MASK_PLAYERSOLID); + left = tr.fraction; + + VectorSet(v, d2, 16, 0); + G_ProjectSource (self->s.origin, v, v_forward, v_right, right_target); + tr = gi.trace(self->s.origin, self->mins, self->maxs, right_target, self, MASK_PLAYERSOLID); + right = tr.fraction; + + center = (d1*center)/d2; + if (left >= center && left > right) + { + if (left < 1) + { + VectorSet(v, d2 * left * 0.5, -16, 0); + G_ProjectSource (self->s.origin, v, v_forward, v_right, left_target); +// gi.dprintf("incomplete path, go part way and adjust again\n"); + } + VectorCopy (self->monsterinfo.last_sighting, self->monsterinfo.saved_goal); + self->monsterinfo.aiflags |= AI_PURSUE_TEMP; + VectorCopy (left_target, self->goalentity->s.origin); + VectorCopy (left_target, self->monsterinfo.last_sighting); + VectorSubtract (self->goalentity->s.origin, self->s.origin, v); + self->s.angles[YAW] = self->ideal_yaw = vectoyaw(v); +// gi.dprintf("adjusted left\n"); +// debug_drawline(self.origin, self.last_sighting, 152); + } + else if (right >= center && right > left) + { + if (right < 1) + { + VectorSet(v, d2 * right * 0.5, 16, 0); + G_ProjectSource (self->s.origin, v, v_forward, v_right, right_target); +// gi.dprintf("incomplete path, go part way and adjust again\n"); + } + VectorCopy (self->monsterinfo.last_sighting, self->monsterinfo.saved_goal); + self->monsterinfo.aiflags |= AI_PURSUE_TEMP; + VectorCopy (right_target, self->goalentity->s.origin); + VectorCopy (right_target, self->monsterinfo.last_sighting); + VectorSubtract (self->goalentity->s.origin, self->s.origin, v); + self->s.angles[YAW] = self->ideal_yaw = vectoyaw(v); +// gi.dprintf("adjusted right\n"); +// debug_drawline(self.origin, self.last_sighting, 152); + } + } +// else gi.dprintf("course was fine\n"); + } + + M_MoveToGoal (self, dist); + + G_FreeEdict(tempgoal); + + if (self) + self->goalentity = save; +} diff --git a/original/ctf/g_chase.c b/original/ctf/g_chase.c new file mode 100644 index 0000000..b57648e --- /dev/null +++ b/original/ctf/g_chase.c @@ -0,0 +1,157 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +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. + +*/ +#include "g_local.h" + + +void UpdateChaseCam(edict_t *ent) +{ + vec3_t o, ownerv, goal; + edict_t *targ; + vec3_t forward, right; + trace_t trace; + int i; + vec3_t oldgoal; + vec3_t angles; + + // is our chase target gone? + if (!ent->client->chase_target->inuse) { + ent->client->chase_target = NULL; + return; + } + + targ = ent->client->chase_target; + + VectorCopy(targ->s.origin, ownerv); + VectorCopy(ent->s.origin, oldgoal); + + ownerv[2] += targ->viewheight; + + VectorCopy(targ->client->v_angle, angles); + if (angles[PITCH] > 56) + angles[PITCH] = 56; + AngleVectors (angles, forward, right, NULL); + VectorNormalize(forward); + VectorMA(ownerv, -30, forward, o); + + if (o[2] < targ->s.origin[2] + 20) + o[2] = targ->s.origin[2] + 20; + + // jump animation lifts + if (!targ->groundentity) + o[2] += 16; + + trace = gi.trace(ownerv, vec3_origin, vec3_origin, o, targ, MASK_SOLID); + + VectorCopy(trace.endpos, goal); + + VectorMA(goal, 2, forward, goal); + + // pad for floors and ceilings + VectorCopy(goal, o); + o[2] += 6; + trace = gi.trace(goal, vec3_origin, vec3_origin, o, targ, MASK_SOLID); + if (trace.fraction < 1) { + VectorCopy(trace.endpos, goal); + goal[2] -= 6; + } + + VectorCopy(goal, o); + o[2] -= 6; + trace = gi.trace(goal, vec3_origin, vec3_origin, o, targ, MASK_SOLID); + if (trace.fraction < 1) { + VectorCopy(trace.endpos, goal); + goal[2] += 6; + } + + ent->client->ps.pmove.pm_type = PM_FREEZE; + + VectorCopy(goal, ent->s.origin); + for (i=0 ; i<3 ; i++) + ent->client->ps.pmove.delta_angles[i] = ANGLE2SHORT(targ->client->v_angle[i] - ent->client->resp.cmd_angles[i]); + + VectorCopy(targ->client->v_angle, ent->client->ps.viewangles); + VectorCopy(targ->client->v_angle, ent->client->v_angle); + + ent->viewheight = 0; + ent->client->ps.pmove.pm_flags |= PMF_NO_PREDICTION; + gi.linkentity(ent); + + if ((!ent->client->showscores && !ent->client->menu && + !ent->client->showinventory && !ent->client->showhelp && + !(level.framenum & 31)) || ent->client->update_chase) { + char s[1024]; + + ent->client->update_chase = false; + sprintf(s, "xv 0 yb -68 string2 \"Chasing %s\"", + targ->client->pers.netname); + gi.WriteByte (svc_layout); + gi.WriteString (s); + gi.unicast(ent, false); + } + +} + +void ChaseNext(edict_t *ent) +{ + int i; + edict_t *e; + + if (!ent->client->chase_target) + return; + + i = ent->client->chase_target - g_edicts; + do { + i++; + if (i > maxclients->value) + i = 1; + e = g_edicts + i; + if (!e->inuse) + continue; + if (e->solid != SOLID_NOT) + break; + } while (e != ent->client->chase_target); + + ent->client->chase_target = e; + ent->client->update_chase = true; +} + +void ChasePrev(edict_t *ent) +{ + int i; + edict_t *e; + + if (!ent->client->chase_target) + return; + + i = ent->client->chase_target - g_edicts; + do { + i--; + if (i < 1) + i = maxclients->value; + e = g_edicts + i; + if (!e->inuse) + continue; + if (e->solid != SOLID_NOT) + break; + } while (e != ent->client->chase_target); + + ent->client->chase_target = e; + ent->client->update_chase = true; +} diff --git a/original/ctf/g_cmds.c b/original/ctf/g_cmds.c new file mode 100644 index 0000000..2b9aaed --- /dev/null +++ b/original/ctf/g_cmds.c @@ -0,0 +1,1066 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +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. + +*/ +#include "g_local.h" +#include "m_player.h" + + +char *ClientTeam (edict_t *ent) +{ + char *p; + static char value[512]; + + value[0] = 0; + + if (!ent->client) + return value; + + strcpy(value, Info_ValueForKey (ent->client->pers.userinfo, "skin")); + p = strchr(value, '/'); + if (!p) + return value; + + if ((int)(dmflags->value) & DF_MODELTEAMS) + { + *p = 0; + return value; + } + + // if ((int)(dmflags->value) & DF_SKINTEAMS) + return ++p; +} + +qboolean OnSameTeam (edict_t *ent1, edict_t *ent2) +{ + char ent1Team [512]; + char ent2Team [512]; + + if (!((int)(dmflags->value) & (DF_MODELTEAMS | DF_SKINTEAMS))) + return false; + + strcpy (ent1Team, ClientTeam (ent1)); + strcpy (ent2Team, ClientTeam (ent2)); + + if (strcmp(ent1Team, ent2Team) == 0) + return true; + return false; +} + + +void SelectNextItem (edict_t *ent, int itflags) +{ + gclient_t *cl; + int i, index; + gitem_t *it; + + cl = ent->client; + +//ZOID + if (cl->menu) { + PMenu_Next(ent); + return; + } else if (cl->chase_target) { + ChaseNext(ent); + return; + } +//ZOID + + // scan for the next valid one + for (i=1 ; i<=MAX_ITEMS ; i++) + { + index = (cl->pers.selected_item + i)%MAX_ITEMS; + if (!cl->pers.inventory[index]) + continue; + it = &itemlist[index]; + if (!it->use) + continue; + if (!(it->flags & itflags)) + continue; + + cl->pers.selected_item = index; + return; + } + + cl->pers.selected_item = -1; +} + +void SelectPrevItem (edict_t *ent, int itflags) +{ + gclient_t *cl; + int i, index; + gitem_t *it; + + cl = ent->client; + +//ZOID + if (cl->menu) { + PMenu_Prev(ent); + return; + } else if (cl->chase_target) { + ChasePrev(ent); + return; + } +//ZOID + + // scan for the next valid one + for (i=1 ; i<=MAX_ITEMS ; i++) + { + index = (cl->pers.selected_item + MAX_ITEMS - i)%MAX_ITEMS; + if (!cl->pers.inventory[index]) + continue; + it = &itemlist[index]; + if (!it->use) + continue; + if (!(it->flags & itflags)) + continue; + + cl->pers.selected_item = index; + return; + } + + cl->pers.selected_item = -1; +} + +void ValidateSelectedItem (edict_t *ent) +{ + gclient_t *cl; + + cl = ent->client; + + if (cl->pers.inventory[cl->pers.selected_item]) + return; // valid + + SelectNextItem (ent, -1); +} + + +//================================================================================= + +/* +================== +Cmd_Give_f + +Give items to a client +================== +*/ +void Cmd_Give_f (edict_t *ent) +{ + char *name; + gitem_t *it; + int index; + int i; + qboolean give_all; + edict_t *it_ent; + + if (deathmatch->value && !sv_cheats->value) + { + gi.cprintf (ent, PRINT_HIGH, "You must run the server with '+set cheats 1' to enable this command.\n"); + return; + } + + name = gi.args(); + + if (Q_stricmp(name, "all") == 0) + give_all = true; + else + give_all = false; + + if (give_all || Q_stricmp(gi.argv(1), "health") == 0) + { + if (gi.argc() == 3) + ent->health = atoi(gi.argv(2)); + else + ent->health = ent->max_health; + if (!give_all) + return; + } + + if (give_all || Q_stricmp(name, "weapons") == 0) + { + for (i=0 ; ipickup) + continue; + if (!(it->flags & IT_WEAPON)) + continue; + ent->client->pers.inventory[i] += 1; + } + if (!give_all) + return; + } + + if (give_all || Q_stricmp(name, "ammo") == 0) + { + for (i=0 ; ipickup) + continue; + if (!(it->flags & IT_AMMO)) + continue; + Add_Ammo (ent, it, 1000); + } + if (!give_all) + return; + } + + if (give_all || Q_stricmp(name, "armor") == 0) + { + gitem_armor_t *info; + + it = FindItem("Jacket Armor"); + ent->client->pers.inventory[ITEM_INDEX(it)] = 0; + + it = FindItem("Combat Armor"); + ent->client->pers.inventory[ITEM_INDEX(it)] = 0; + + it = FindItem("Body Armor"); + info = (gitem_armor_t *)it->info; + ent->client->pers.inventory[ITEM_INDEX(it)] = info->max_count; + + if (!give_all) + return; + } + + if (give_all || Q_stricmp(name, "Power Shield") == 0) + { + it = FindItem("Power Shield"); + it_ent = G_Spawn(); + it_ent->classname = it->classname; + SpawnItem (it_ent, it); + Touch_Item (it_ent, ent, NULL, NULL); + if (it_ent->inuse) + G_FreeEdict(it_ent); + + if (!give_all) + return; + } + + if (give_all) + { + for (i=0 ; ipickup) + continue; + if (it->flags & (IT_ARMOR|IT_WEAPON|IT_AMMO)) + continue; + ent->client->pers.inventory[i] = 1; + } + return; + } + + it = FindItem (name); + if (!it) + { + name = gi.argv(1); + it = FindItem (name); + if (!it) + { + gi.cprintf (ent, PRINT_HIGH, "unknown item\n"); + return; + } + } + + if (!it->pickup) + { + gi.cprintf (ent, PRINT_HIGH, "non-pickup item\n"); + return; + } + + index = ITEM_INDEX(it); + + if (it->flags & IT_AMMO) + { + if (gi.argc() == 3) + ent->client->pers.inventory[index] = atoi(gi.argv(2)); + else + ent->client->pers.inventory[index] += it->quantity; + } + else + { + it_ent = G_Spawn(); + it_ent->classname = it->classname; + SpawnItem (it_ent, it); + Touch_Item (it_ent, ent, NULL, NULL); + if (it_ent->inuse) + G_FreeEdict(it_ent); + } +} + + +/* +================== +Cmd_God_f + +Sets client to godmode + +argv(0) god +================== +*/ +void Cmd_God_f (edict_t *ent) +{ + char *msg; + + if (deathmatch->value && !sv_cheats->value) + { + gi.cprintf (ent, PRINT_HIGH, "You must run the server with '+set cheats 1' to enable this command.\n"); + return; + } + + ent->flags ^= FL_GODMODE; + if (!(ent->flags & FL_GODMODE) ) + msg = "godmode OFF\n"; + else + msg = "godmode ON\n"; + + gi.cprintf (ent, PRINT_HIGH, msg); +} + + +/* +================== +Cmd_Notarget_f + +Sets client to notarget + +argv(0) notarget +================== +*/ +void Cmd_Notarget_f (edict_t *ent) +{ + char *msg; + + if (deathmatch->value && !sv_cheats->value) + { + gi.cprintf (ent, PRINT_HIGH, "You must run the server with '+set cheats 1' to enable this command.\n"); + return; + } + + ent->flags ^= FL_NOTARGET; + if (!(ent->flags & FL_NOTARGET) ) + msg = "notarget OFF\n"; + else + msg = "notarget ON\n"; + + gi.cprintf (ent, PRINT_HIGH, msg); +} + + +/* +================== +Cmd_Noclip_f + +argv(0) noclip +================== +*/ +void Cmd_Noclip_f (edict_t *ent) +{ + char *msg; + + if (deathmatch->value && !sv_cheats->value) + { + gi.cprintf (ent, PRINT_HIGH, "You must run the server with '+set cheats 1' to enable this command.\n"); + return; + } + + if (ent->movetype == MOVETYPE_NOCLIP) + { + ent->movetype = MOVETYPE_WALK; + msg = "noclip OFF\n"; + } + else + { + ent->movetype = MOVETYPE_NOCLIP; + msg = "noclip ON\n"; + } + + gi.cprintf (ent, PRINT_HIGH, msg); +} + + +/* +================== +Cmd_Use_f + +Use an inventory item +================== +*/ +void Cmd_Use_f (edict_t *ent) +{ + int index; + gitem_t *it; + char *s; + + s = gi.args(); + it = FindItem (s); + if (!it) + { + gi.cprintf (ent, PRINT_HIGH, "unknown item: %s\n", s); + return; + } + if (!it->use) + { + gi.cprintf (ent, PRINT_HIGH, "Item is not usable.\n"); + return; + } + index = ITEM_INDEX(it); + if (!ent->client->pers.inventory[index]) + { + gi.cprintf (ent, PRINT_HIGH, "Out of item: %s\n", s); + return; + } + + it->use (ent, it); +} + + +/* +================== +Cmd_Drop_f + +Drop an inventory item +================== +*/ +void Cmd_Drop_f (edict_t *ent) +{ + int index; + gitem_t *it; + char *s; + +//ZOID--special case for tech powerups + if (Q_stricmp(gi.args(), "tech") == 0 && (it = CTFWhat_Tech(ent)) != NULL) { + it->drop (ent, it); + return; + } +//ZOID + + s = gi.args(); + it = FindItem (s); + if (!it) + { + gi.cprintf (ent, PRINT_HIGH, "unknown item: %s\n", s); + return; + } + if (!it->drop) + { + gi.cprintf (ent, PRINT_HIGH, "Item is not dropable.\n"); + return; + } + index = ITEM_INDEX(it); + if (!ent->client->pers.inventory[index]) + { + gi.cprintf (ent, PRINT_HIGH, "Out of item: %s\n", s); + return; + } + + it->drop (ent, it); +} + + +/* +================= +Cmd_Inven_f +================= +*/ +void Cmd_Inven_f (edict_t *ent) +{ + int i; + gclient_t *cl; + + cl = ent->client; + + cl->showscores = false; + cl->showhelp = false; + +//ZOID + if (ent->client->menu) { + PMenu_Close(ent); + ent->client->update_chase = true; + return; + } +//ZOID + + if (cl->showinventory) + { + cl->showinventory = false; + return; + } + +//ZOID + if (ctf->value && cl->resp.ctf_team == CTF_NOTEAM) { + CTFOpenJoinMenu(ent); + return; + } +//ZOID + + cl->showinventory = true; + + gi.WriteByte (svc_inventory); + for (i=0 ; ipers.inventory[i]); + } + gi.unicast (ent, true); +} + +/* +================= +Cmd_InvUse_f +================= +*/ +void Cmd_InvUse_f (edict_t *ent) +{ + gitem_t *it; + +//ZOID + if (ent->client->menu) { + PMenu_Select(ent); + return; + } +//ZOID + + ValidateSelectedItem (ent); + + if (ent->client->pers.selected_item == -1) + { + gi.cprintf (ent, PRINT_HIGH, "No item to use.\n"); + return; + } + + it = &itemlist[ent->client->pers.selected_item]; + if (!it->use) + { + gi.cprintf (ent, PRINT_HIGH, "Item is not usable.\n"); + return; + } + it->use (ent, it); +} + +//ZOID +/* +================= +Cmd_LastWeap_f +================= +*/ +void Cmd_LastWeap_f (edict_t *ent) +{ + gclient_t *cl; + + cl = ent->client; + + if (!cl->pers.weapon || !cl->pers.lastweapon) + return; + + cl->pers.lastweapon->use (ent, cl->pers.lastweapon); +} +//ZOID + +/* +================= +Cmd_WeapPrev_f +================= +*/ +void Cmd_WeapPrev_f (edict_t *ent) +{ + gclient_t *cl; + int i, index; + gitem_t *it; + int selected_weapon; + + cl = ent->client; + + if (!cl->pers.weapon) + return; + + selected_weapon = ITEM_INDEX(cl->pers.weapon); + + // scan for the next valid one + for (i=1 ; i<=MAX_ITEMS ; i++) + { + index = (selected_weapon + i)%MAX_ITEMS; + if (!cl->pers.inventory[index]) + continue; + it = &itemlist[index]; + if (!it->use) + continue; + if (! (it->flags & IT_WEAPON) ) + continue; + it->use (ent, it); + if (cl->pers.weapon == it) + return; // successful + } +} + +/* +================= +Cmd_WeapNext_f +================= +*/ +void Cmd_WeapNext_f (edict_t *ent) +{ + gclient_t *cl; + int i, index; + gitem_t *it; + int selected_weapon; + + cl = ent->client; + + if (!cl->pers.weapon) + return; + + selected_weapon = ITEM_INDEX(cl->pers.weapon); + + // scan for the next valid one + for (i=1 ; i<=MAX_ITEMS ; i++) + { + index = (selected_weapon + MAX_ITEMS - i)%MAX_ITEMS; + if (!cl->pers.inventory[index]) + continue; + it = &itemlist[index]; + if (!it->use) + continue; + if (! (it->flags & IT_WEAPON) ) + continue; + it->use (ent, it); + if (cl->pers.weapon == it) + return; // successful + } +} + +/* +================= +Cmd_WeapLast_f +================= +*/ +void Cmd_WeapLast_f (edict_t *ent) +{ + gclient_t *cl; + int index; + gitem_t *it; + + cl = ent->client; + + if (!cl->pers.weapon || !cl->pers.lastweapon) + return; + + index = ITEM_INDEX(cl->pers.lastweapon); + if (!cl->pers.inventory[index]) + return; + it = &itemlist[index]; + if (!it->use) + return; + if (! (it->flags & IT_WEAPON) ) + return; + it->use (ent, it); +} + +/* +================= +Cmd_InvDrop_f +================= +*/ +void Cmd_InvDrop_f (edict_t *ent) +{ + gitem_t *it; + + ValidateSelectedItem (ent); + + if (ent->client->pers.selected_item == -1) + { + gi.cprintf (ent, PRINT_HIGH, "No item to drop.\n"); + return; + } + + it = &itemlist[ent->client->pers.selected_item]; + if (!it->drop) + { + gi.cprintf (ent, PRINT_HIGH, "Item is not dropable.\n"); + return; + } + it->drop (ent, it); +} + +/* +================= +Cmd_Kill_f +================= +*/ +void Cmd_Kill_f (edict_t *ent) +{ +//ZOID + if (ent->solid == SOLID_NOT) + return; +//ZOID + + if((level.time - ent->client->respawn_time) < 5) + return; + ent->flags &= ~FL_GODMODE; + ent->health = 0; + meansOfDeath = MOD_SUICIDE; + player_die (ent, ent, ent, 100000, vec3_origin); +} + +/* +================= +Cmd_PutAway_f +================= +*/ +void Cmd_PutAway_f (edict_t *ent) +{ + ent->client->showscores = false; + ent->client->showhelp = false; + ent->client->showinventory = false; +//ZOID + if (ent->client->menu) + PMenu_Close(ent); + ent->client->update_chase = true; +//ZOID +} + + +int PlayerSort (void const *a, void const *b) +{ + int anum, bnum; + + anum = *(int *)a; + bnum = *(int *)b; + + anum = game.clients[anum].ps.stats[STAT_FRAGS]; + bnum = game.clients[bnum].ps.stats[STAT_FRAGS]; + + if (anum < bnum) + return -1; + if (anum > bnum) + return 1; + return 0; +} + +/* +================= +Cmd_Players_f +================= +*/ +void Cmd_Players_f (edict_t *ent) +{ + int i; + int count; + char small[64]; + char large[1280]; + int index[256]; + + count = 0; + for (i = 0 ; i < maxclients->value ; i++) + if (game.clients[i].pers.connected) + { + index[count] = i; + count++; + } + + // sort by frags + qsort (index, count, sizeof(index[0]), PlayerSort); + + // print information + large[0] = 0; + + for (i = 0 ; i < count ; i++) + { + Com_sprintf (small, sizeof(small), "%3i %s\n", + game.clients[index[i]].ps.stats[STAT_FRAGS], + game.clients[index[i]].pers.netname); + if (strlen (small) + strlen(large) > sizeof(large) - 100 ) + { // can't print all of them in one packet + strcat (large, "...\n"); + break; + } + strcat (large, small); + } + + gi.cprintf (ent, PRINT_HIGH, "%s\n%i players\n", large, count); +} + +/* +================= +Cmd_Wave_f +================= +*/ +void Cmd_Wave_f (edict_t *ent) +{ + int i; + + i = atoi (gi.argv(1)); + + // can't wave when ducked + if (ent->client->ps.pmove.pm_flags & PMF_DUCKED) + return; + + if (ent->client->anim_priority > ANIM_WAVE) + return; + + ent->client->anim_priority = ANIM_WAVE; + + switch (i) + { + case 0: + gi.cprintf (ent, PRINT_HIGH, "flipoff\n"); + ent->s.frame = FRAME_flip01-1; + ent->client->anim_end = FRAME_flip12; + break; + case 1: + gi.cprintf (ent, PRINT_HIGH, "salute\n"); + ent->s.frame = FRAME_salute01-1; + ent->client->anim_end = FRAME_salute11; + break; + case 2: + gi.cprintf (ent, PRINT_HIGH, "taunt\n"); + ent->s.frame = FRAME_taunt01-1; + ent->client->anim_end = FRAME_taunt17; + break; + case 3: + gi.cprintf (ent, PRINT_HIGH, "wave\n"); + ent->s.frame = FRAME_wave01-1; + ent->client->anim_end = FRAME_wave11; + break; + case 4: + default: + gi.cprintf (ent, PRINT_HIGH, "point\n"); + ent->s.frame = FRAME_point01-1; + ent->client->anim_end = FRAME_point12; + break; + } +} + +qboolean CheckFlood(edict_t *ent) +{ + int i; + gclient_t *cl; + + if (flood_msgs->value) { + cl = ent->client; + + if (level.time < cl->flood_locktill) { + gi.cprintf(ent, PRINT_HIGH, "You can't talk for %d more seconds\n", + (int)(cl->flood_locktill - level.time)); + return true; + } + i = cl->flood_whenhead - flood_msgs->value + 1; + if (i < 0) + i = (sizeof(cl->flood_when)/sizeof(cl->flood_when[0])) + i; + if (cl->flood_when[i] && + level.time - cl->flood_when[i] < flood_persecond->value) { + cl->flood_locktill = level.time + flood_waitdelay->value; + gi.cprintf(ent, PRINT_CHAT, "Flood protection: You can't talk for %d seconds.\n", + (int)flood_waitdelay->value); + return true; + } + cl->flood_whenhead = (cl->flood_whenhead + 1) % + (sizeof(cl->flood_when)/sizeof(cl->flood_when[0])); + cl->flood_when[cl->flood_whenhead] = level.time; + } + return false; +} + +/* +================== +Cmd_Say_f +================== +*/ +void Cmd_Say_f (edict_t *ent, qboolean team, qboolean arg0) +{ + int j; + edict_t *other; + char *p; + char text[2048]; + + if (gi.argc () < 2 && !arg0) + return; + + if (!((int)(dmflags->value) & (DF_MODELTEAMS | DF_SKINTEAMS))) + team = false; + + if (team) + Com_sprintf (text, sizeof(text), "(%s): ", ent->client->pers.netname); + else + Com_sprintf (text, sizeof(text), "%s: ", ent->client->pers.netname); + + if (arg0) + { + strcat (text, gi.argv(0)); + strcat (text, " "); + strcat (text, gi.args()); + } + else + { + p = gi.args(); + + if (*p == '"') + { + p++; + p[strlen(p)-1] = 0; + } + strcat(text, p); + } + + // don't let text be too long for malicious reasons + if (strlen(text) > 150) + text[150] = 0; + + strcat(text, "\n"); + + if (CheckFlood(ent)) + return; + + if (dedicated->value) + gi.cprintf(NULL, PRINT_CHAT, "%s", text); + + for (j = 1; j <= game.maxclients; j++) + { + other = &g_edicts[j]; + if (!other->inuse) + continue; + if (!other->client) + continue; + if (team) + { + if (!OnSameTeam(ent, other)) + continue; + } + gi.cprintf(other, PRINT_CHAT, "%s", text); + } +} + +/* +================= +ClientCommand +================= +*/ +void ClientCommand (edict_t *ent) +{ + char *cmd; + + if (!ent->client) + return; // not fully in game yet + + cmd = gi.argv(0); + + if (Q_stricmp (cmd, "players") == 0) + { + Cmd_Players_f (ent); + return; + } + if (Q_stricmp (cmd, "say") == 0) + { + Cmd_Say_f (ent, false, false); + return; + } + if (Q_stricmp (cmd, "say_team") == 0 || Q_stricmp (cmd, "steam") == 0) + { + CTFSay_Team(ent, gi.args()); + return; + } + if (Q_stricmp (cmd, "score") == 0) + { + Cmd_Score_f (ent); + return; + } + if (Q_stricmp (cmd, "help") == 0) + { + Cmd_Help_f (ent); + return; + } + + if (level.intermissiontime) + return; + + if (Q_stricmp (cmd, "use") == 0) + Cmd_Use_f (ent); + else if (Q_stricmp (cmd, "drop") == 0) + Cmd_Drop_f (ent); + else if (Q_stricmp (cmd, "give") == 0) + Cmd_Give_f (ent); + else if (Q_stricmp (cmd, "god") == 0) + Cmd_God_f (ent); + else if (Q_stricmp (cmd, "notarget") == 0) + Cmd_Notarget_f (ent); + else if (Q_stricmp (cmd, "noclip") == 0) + Cmd_Noclip_f (ent); + else if (Q_stricmp (cmd, "inven") == 0) + Cmd_Inven_f (ent); + else if (Q_stricmp (cmd, "invnext") == 0) + SelectNextItem (ent, -1); + else if (Q_stricmp (cmd, "invprev") == 0) + SelectPrevItem (ent, -1); + else if (Q_stricmp (cmd, "invnextw") == 0) + SelectNextItem (ent, IT_WEAPON); + else if (Q_stricmp (cmd, "invprevw") == 0) + SelectPrevItem (ent, IT_WEAPON); + else if (Q_stricmp (cmd, "invnextp") == 0) + SelectNextItem (ent, IT_POWERUP); + else if (Q_stricmp (cmd, "invprevp") == 0) + SelectPrevItem (ent, IT_POWERUP); + else if (Q_stricmp (cmd, "invuse") == 0) + Cmd_InvUse_f (ent); + else if (Q_stricmp (cmd, "invdrop") == 0) + Cmd_InvDrop_f (ent); + else if (Q_stricmp (cmd, "weapprev") == 0) + Cmd_WeapPrev_f (ent); + else if (Q_stricmp (cmd, "weapnext") == 0) + Cmd_WeapNext_f (ent); + else if (Q_stricmp (cmd, "weaplast") == 0) + Cmd_WeapLast_f (ent); + else if (Q_stricmp (cmd, "kill") == 0) + Cmd_Kill_f (ent); + else if (Q_stricmp (cmd, "putaway") == 0) + Cmd_PutAway_f (ent); + else if (Q_stricmp (cmd, "wave") == 0) + Cmd_Wave_f (ent); +//ZOID + else if (Q_stricmp (cmd, "team") == 0) + { + CTFTeam_f (ent); + } else if (Q_stricmp(cmd, "id") == 0) { + CTFID_f (ent); + } else if (Q_stricmp(cmd, "yes") == 0) { + CTFVoteYes(ent); + } else if (Q_stricmp(cmd, "no") == 0) { + CTFVoteNo(ent); + } else if (Q_stricmp(cmd, "ready") == 0) { + CTFReady(ent); + } else if (Q_stricmp(cmd, "notready") == 0) { + CTFNotReady(ent); + } else if (Q_stricmp(cmd, "ghost") == 0) { + CTFGhost(ent); + } else if (Q_stricmp(cmd, "admin") == 0) { + CTFAdmin(ent); + } else if (Q_stricmp(cmd, "stats") == 0) { + CTFStats(ent); + } else if (Q_stricmp(cmd, "warp") == 0) { + CTFWarp(ent); + } else if (Q_stricmp(cmd, "boot") == 0) { + CTFBoot(ent); + } else if (Q_stricmp(cmd, "playerlist") == 0) { + CTFPlayerList(ent); + } else if (Q_stricmp(cmd, "observer") == 0) { + CTFObserver(ent); + } +//ZOID + else // anything that doesn't match a command will be a chat + Cmd_Say_f (ent, false, true); +} diff --git a/original/ctf/g_combat.c b/original/ctf/g_combat.c new file mode 100644 index 0000000..d0f8308 --- /dev/null +++ b/original/ctf/g_combat.c @@ -0,0 +1,596 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +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. + +*/ +// g_combat.c + +#include "g_local.h" + +/* +============ +CanDamage + +Returns true if the inflictor can directly damage the target. Used for +explosions and melee attacks. +============ +*/ +qboolean CanDamage (edict_t *targ, edict_t *inflictor) +{ + vec3_t dest; + trace_t trace; + +// bmodels need special checking because their origin is 0,0,0 + if (targ->movetype == MOVETYPE_PUSH) + { + VectorAdd (targ->absmin, targ->absmax, dest); + VectorScale (dest, 0.5, dest); + trace = gi.trace (inflictor->s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID); + if (trace.fraction == 1.0) + return true; + if (trace.ent == targ) + return true; + return false; + } + + trace = gi.trace (inflictor->s.origin, vec3_origin, vec3_origin, targ->s.origin, inflictor, MASK_SOLID); + if (trace.fraction == 1.0) + return true; + + VectorCopy (targ->s.origin, dest); + dest[0] += 15.0; + dest[1] += 15.0; + trace = gi.trace (inflictor->s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID); + if (trace.fraction == 1.0) + return true; + + VectorCopy (targ->s.origin, dest); + dest[0] += 15.0; + dest[1] -= 15.0; + trace = gi.trace (inflictor->s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID); + if (trace.fraction == 1.0) + return true; + + VectorCopy (targ->s.origin, dest); + dest[0] -= 15.0; + dest[1] += 15.0; + trace = gi.trace (inflictor->s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID); + if (trace.fraction == 1.0) + return true; + + VectorCopy (targ->s.origin, dest); + dest[0] -= 15.0; + dest[1] -= 15.0; + trace = gi.trace (inflictor->s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID); + if (trace.fraction == 1.0) + return true; + + + return false; +} + + +/* +============ +Killed +============ +*/ +void Killed (edict_t *targ, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + if (targ->health < -999) + targ->health = -999; + + targ->enemy = attacker; + + if ((targ->svflags & SVF_MONSTER) && (targ->deadflag != DEAD_DEAD)) + { +// targ->svflags |= SVF_DEADMONSTER; // now treat as a different content type + if (!(targ->monsterinfo.aiflags & AI_GOOD_GUY)) + { + level.killed_monsters++; + if (coop->value && attacker->client) + attacker->client->resp.score++; + // medics won't heal monsters that they kill themselves + if (strcmp(attacker->classname, "monster_medic") == 0) + targ->owner = attacker; + } + } + + if (targ->movetype == MOVETYPE_PUSH || targ->movetype == MOVETYPE_STOP || targ->movetype == MOVETYPE_NONE) + { // doors, triggers, etc + targ->die (targ, inflictor, attacker, damage, point); + return; + } + + if ((targ->svflags & SVF_MONSTER) && (targ->deadflag != DEAD_DEAD)) + { + targ->touch = NULL; + monster_death_use (targ); + } + + targ->die (targ, inflictor, attacker, damage, point); +} + + +/* +================ +SpawnDamage +================ +*/ +void SpawnDamage (int type, vec3_t origin, vec3_t normal, int damage) +{ + if (damage > 255) + damage = 255; + gi.WriteByte (svc_temp_entity); + gi.WriteByte (type); +// gi.WriteByte (damage); + gi.WritePosition (origin); + gi.WriteDir (normal); + gi.multicast (origin, MULTICAST_PVS); +} + + +/* +============ +T_Damage + +targ entity that is being damaged +inflictor entity that is causing the damage +attacker entity that caused the inflictor to damage targ + example: targ=monster, inflictor=rocket, attacker=player + +dir direction of the attack +point point at which the damage is being inflicted +normal normal vector from that point +damage amount of damage being inflicted +knockback force to be applied against targ as a result of the damage + +dflags these flags are used to control how T_Damage works + DAMAGE_RADIUS damage was indirect (from a nearby explosion) + DAMAGE_NO_ARMOR armor does not protect from this damage + DAMAGE_ENERGY damage is from an energy based weapon + DAMAGE_NO_KNOCKBACK do not affect velocity, just view angles + DAMAGE_BULLET damage is from a bullet (used for ricochets) + DAMAGE_NO_PROTECTION kills godmode, armor, everything +============ +*/ +static int CheckPowerArmor (edict_t *ent, vec3_t point, vec3_t normal, int damage, int dflags) +{ + gclient_t *client; + int save; + int power_armor_type; + int index; + int damagePerCell; + int pa_te_type; + int power; + int power_used; + + if (!damage) + return 0; + + client = ent->client; + + if (dflags & DAMAGE_NO_ARMOR) + return 0; + + if (client) + { + power_armor_type = PowerArmorType (ent); + if (power_armor_type != POWER_ARMOR_NONE) + { + index = ITEM_INDEX(FindItem("Cells")); + power = client->pers.inventory[index]; + } + } + else if (ent->svflags & SVF_MONSTER) + { + power_armor_type = ent->monsterinfo.power_armor_type; + power = ent->monsterinfo.power_armor_power; + } + else + return 0; + + if (power_armor_type == POWER_ARMOR_NONE) + return 0; + if (!power) + return 0; + + if (power_armor_type == POWER_ARMOR_SCREEN) + { + vec3_t vec; + float dot; + vec3_t forward; + + // only works if damage point is in front + AngleVectors (ent->s.angles, forward, NULL, NULL); + VectorSubtract (point, ent->s.origin, vec); + VectorNormalize (vec); + dot = DotProduct (vec, forward); + if (dot <= 0.3) + return 0; + + damagePerCell = 1; + pa_te_type = TE_SCREEN_SPARKS; + damage = damage / 3; + } + else + { + damagePerCell = 1; // power armor is weaker in CTF + pa_te_type = TE_SHIELD_SPARKS; + damage = (2 * damage) / 3; + } + + save = power * damagePerCell; + if (!save) + return 0; + if (save > damage) + save = damage; + + SpawnDamage (pa_te_type, point, normal, save); + ent->powerarmor_time = level.time + 0.2; + + power_used = save / damagePerCell; + + if (client) + client->pers.inventory[index] -= power_used; + else + ent->monsterinfo.power_armor_power -= power_used; + return save; +} + +static int CheckArmor (edict_t *ent, vec3_t point, vec3_t normal, int damage, int te_sparks, int dflags) +{ + gclient_t *client; + int save; + int index; + gitem_t *armor; + + if (!damage) + return 0; + + client = ent->client; + + if (!client) + return 0; + + if (dflags & DAMAGE_NO_ARMOR) + return 0; + + index = ArmorIndex (ent); + if (!index) + return 0; + + armor = GetItemByIndex (index); + + if (dflags & DAMAGE_ENERGY) + save = ceil(((gitem_armor_t *)armor->info)->energy_protection*damage); + else + save = ceil(((gitem_armor_t *)armor->info)->normal_protection*damage); + if (save >= client->pers.inventory[index]) + save = client->pers.inventory[index]; + + if (!save) + return 0; + + client->pers.inventory[index] -= save; + SpawnDamage (te_sparks, point, normal, save); + + return save; +} + +void M_ReactToDamage (edict_t *targ, edict_t *attacker) +{ + if (!(attacker->client) && !(attacker->svflags & SVF_MONSTER)) + return; + + if (attacker == targ || attacker == targ->enemy) + return; + + // if we are a good guy monster and our attacker is a player + // or another good guy, do not get mad at them + if (targ->monsterinfo.aiflags & AI_GOOD_GUY) + { + if (attacker->client || (attacker->monsterinfo.aiflags & AI_GOOD_GUY)) + return; + } + + // we now know that we are not both good guys + + // if attacker is a client, get mad at them because he's good and we're not + if (attacker->client) + { + // this can only happen in coop (both new and old enemies are clients) + // only switch if can't see the current enemy + if (targ->enemy && targ->enemy->client) + { + if (visible(targ, targ->enemy)) + { + targ->oldenemy = attacker; + return; + } + targ->oldenemy = targ->enemy; + } + targ->enemy = attacker; + if (!(targ->monsterinfo.aiflags & AI_DUCKED)) + FoundTarget (targ); + return; + } + + // it's the same base (walk/swim/fly) type and a different classname and it's not a tank + // (they spray too much), get mad at them + if (((targ->flags & (FL_FLY|FL_SWIM)) == (attacker->flags & (FL_FLY|FL_SWIM))) && + (strcmp (targ->classname, attacker->classname) != 0) && + (strcmp(attacker->classname, "monster_tank") != 0) && + (strcmp(attacker->classname, "monster_supertank") != 0) && + (strcmp(attacker->classname, "monster_makron") != 0) && + (strcmp(attacker->classname, "monster_jorg") != 0) ) + { + if (targ->enemy) + if (targ->enemy->client) + targ->oldenemy = targ->enemy; + targ->enemy = attacker; + if (!(targ->monsterinfo.aiflags & AI_DUCKED)) + FoundTarget (targ); + } + else + // otherwise get mad at whoever they are mad at (help our buddy) + { + if (targ->enemy) + if (targ->enemy->client) + targ->oldenemy = targ->enemy; + targ->enemy = attacker->enemy; + FoundTarget (targ); + } +} + +qboolean CheckTeamDamage (edict_t *targ, edict_t *attacker) +{ +//ZOID + if (ctf->value && targ->client && attacker->client) + if (targ->client->resp.ctf_team == attacker->client->resp.ctf_team && + targ != attacker) + return true; +//ZOID + + //FIXME make the next line real and uncomment this block + // if ((ability to damage a teammate == OFF) && (targ's team == attacker's team)) + return false; +} + +void T_Damage (edict_t *targ, edict_t *inflictor, edict_t *attacker, vec3_t dir, vec3_t point, vec3_t normal, int damage, int knockback, int dflags, int mod) +{ + gclient_t *client; + int take; + int save; + int asave; + int psave; + int te_sparks; + + if (!targ->takedamage) + return; + + // friendly fire avoidance + // if enabled you can't hurt teammates (but you can hurt yourself) + // knockback still occurs + if ((targ != attacker) && ((deathmatch->value && ((int)(dmflags->value) & (DF_MODELTEAMS | DF_SKINTEAMS))) || coop->value)) + { + if (OnSameTeam (targ, attacker)) + { + if ((int)(dmflags->value) & DF_NO_FRIENDLY_FIRE) + damage = 0; + else + mod |= MOD_FRIENDLY_FIRE; + } + } + meansOfDeath = mod; + + // easy mode takes half damage + if (skill->value == 0 && deathmatch->value == 0 && targ->client) + { + damage *= 0.5; + if (!damage) + damage = 1; + } + + client = targ->client; + + if (dflags & DAMAGE_BULLET) + te_sparks = TE_BULLET_SPARKS; + else + te_sparks = TE_SPARKS; + + VectorNormalize(dir); + +// bonus damage for suprising a monster + if (!(dflags & DAMAGE_RADIUS) && (targ->svflags & SVF_MONSTER) && (attacker->client) && (!targ->enemy) && (targ->health > 0)) + damage *= 2; + +//ZOID +//strength tech + damage = CTFApplyStrength(attacker, damage); +//ZOID + + if (targ->flags & FL_NO_KNOCKBACK) + knockback = 0; + +// figure momentum add + if (!(dflags & DAMAGE_NO_KNOCKBACK)) + { + if ((knockback) && (targ->movetype != MOVETYPE_NONE) && (targ->movetype != MOVETYPE_BOUNCE) && (targ->movetype != MOVETYPE_PUSH) && (targ->movetype != MOVETYPE_STOP)) + { + vec3_t kvel; + float mass; + + if (targ->mass < 50) + mass = 50; + else + mass = targ->mass; + + if (targ->client && attacker == targ) + VectorScale (dir, 1600.0 * (float)knockback / mass, kvel); // the rocket jump hack... + else + VectorScale (dir, 500.0 * (float)knockback / mass, kvel); + + VectorAdd (targ->velocity, kvel, targ->velocity); + } + } + + take = damage; + save = 0; + + // check for godmode + if ( (targ->flags & FL_GODMODE) && !(dflags & DAMAGE_NO_PROTECTION) ) + { + take = 0; + save = damage; + SpawnDamage (te_sparks, point, normal, save); + } + + // check for invincibility + if ((client && client->invincible_framenum > level.framenum ) && !(dflags & DAMAGE_NO_PROTECTION)) + { + if (targ->pain_debounce_time < level.time) + { + gi.sound(targ, CHAN_ITEM, gi.soundindex("items/protect4.wav"), 1, ATTN_NORM, 0); + targ->pain_debounce_time = level.time + 2; + } + take = 0; + save = damage; + } + +//ZOID +//team armor protect + if (ctf->value && targ->client && attacker->client && + targ->client->resp.ctf_team == attacker->client->resp.ctf_team && + targ != attacker && ((int)dmflags->value & DF_ARMOR_PROTECT)) { + psave = asave = 0; + } else { +//ZOID + psave = CheckPowerArmor (targ, point, normal, take, dflags); + take -= psave; + + asave = CheckArmor (targ, point, normal, take, te_sparks, dflags); + take -= asave; + } + + //treat cheat/powerup savings the same as armor + asave += save; + +//ZOID +//resistance tech + take = CTFApplyResistance(targ, take); +//ZOID + + // team damage avoidance + if (!(dflags & DAMAGE_NO_PROTECTION) && CheckTeamDamage (targ, attacker)) + return; + +//ZOID + CTFCheckHurtCarrier(targ, attacker); +//ZOID + +// do the damage + if (take) + { + if ((targ->svflags & SVF_MONSTER) || (client)) + SpawnDamage (TE_BLOOD, point, normal, take); + else + SpawnDamage (te_sparks, point, normal, take); + + if (!CTFMatchSetup()) + targ->health = targ->health - take; + + if (targ->health <= 0) + { + if ((targ->svflags & SVF_MONSTER) || (client)) + targ->flags |= FL_NO_KNOCKBACK; + Killed (targ, inflictor, attacker, take, point); + return; + } + } + + if (targ->svflags & SVF_MONSTER) + { + M_ReactToDamage (targ, attacker); + if (!(targ->monsterinfo.aiflags & AI_DUCKED) && (take)) + { + targ->pain (targ, attacker, knockback, take); + // nightmare mode monsters don't go into pain frames often + if (skill->value == 3) + targ->pain_debounce_time = level.time + 5; + } + } + else if (client) + { + if (!(targ->flags & FL_GODMODE) && (take) && !CTFMatchSetup()) + targ->pain (targ, attacker, knockback, take); + } + else if (take) + { + if (targ->pain) + targ->pain (targ, attacker, knockback, take); + } + + // add to the damage inflicted on a player this frame + // the total will be turned into screen blends and view angle kicks + // at the end of the frame + if (client) + { + client->damage_parmor += psave; + client->damage_armor += asave; + client->damage_blood += take; + client->damage_knockback += knockback; + VectorCopy (point, client->damage_from); + } +} + + +/* +============ +T_RadiusDamage +============ +*/ +void T_RadiusDamage (edict_t *inflictor, edict_t *attacker, float damage, edict_t *ignore, float radius, int mod) +{ + float points; + edict_t *ent = NULL; + vec3_t v; + vec3_t dir; + + while ((ent = findradius(ent, inflictor->s.origin, radius)) != NULL) + { + if (ent == ignore) + continue; + if (!ent->takedamage) + continue; + + VectorAdd (ent->mins, ent->maxs, v); + VectorMA (ent->s.origin, 0.5, v, v); + VectorSubtract (inflictor->s.origin, v, v); + points = damage - 0.5 * VectorLength (v); + if (ent == attacker) + points = points * 0.5; + if (points > 0) + { + if (CanDamage (ent, inflictor)) + { + VectorSubtract (ent->s.origin, inflictor->s.origin, dir); + T_Damage (ent, inflictor, attacker, dir, inflictor->s.origin, vec3_origin, (int)points, (int)points, DAMAGE_RADIUS, mod); + } + } + } +} diff --git a/original/ctf/g_ctf.c b/original/ctf/g_ctf.c new file mode 100644 index 0000000..e4e54fa --- /dev/null +++ b/original/ctf/g_ctf.c @@ -0,0 +1,4197 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +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. + +*/ +#include "g_local.h" +#include "m_player.h" + +typedef enum match_s { + MATCH_NONE, + MATCH_SETUP, + MATCH_PREGAME, + MATCH_GAME, + MATCH_POST +} match_t; + +typedef enum { + ELECT_NONE, + ELECT_MATCH, + ELECT_ADMIN, + ELECT_MAP +} elect_t; + +typedef struct ctfgame_s +{ + int team1, team2; + int total1, total2; // these are only set when going into intermission! + float last_flag_capture; + int last_capture_team; + + match_t match; // match state + float matchtime; // time for match start/end (depends on state) + int lasttime; // last time update + qboolean countdown; // has audio countdown started? + + elect_t election; // election type + edict_t *etarget; // for admin election, who's being elected + char elevel[32]; // for map election, target level + int evotes; // votes so far + int needvotes; // votes needed + float electtime; // remaining time until election times out + char emsg[256]; // election name + int warnactive; // true if stat string 30 is active + + + ghost_t ghosts[MAX_CLIENTS]; // ghost codes +} ctfgame_t; + +ctfgame_t ctfgame; + +cvar_t *ctf; +cvar_t *ctf_forcejoin; + +cvar_t *competition; +cvar_t *matchlock; +cvar_t *electpercentage; +cvar_t *matchtime; +cvar_t *matchsetuptime; +cvar_t *matchstarttime; +cvar_t *admin_password; +cvar_t *allow_admin; +cvar_t *warp_list; +cvar_t *warn_unbalanced; + +// Index for various CTF pics, this saves us from calling gi.imageindex +// all the time and saves a few CPU cycles since we don't have to do +// a bunch of string compares all the time. +// These are set in CTFPrecache() called from worldspawn +int imageindex_i_ctf1; +int imageindex_i_ctf2; +int imageindex_i_ctf1d; +int imageindex_i_ctf2d; +int imageindex_i_ctf1t; +int imageindex_i_ctf2t; +int imageindex_i_ctfj; +int imageindex_sbfctf1; +int imageindex_sbfctf2; +int imageindex_ctfsb1; +int imageindex_ctfsb2; + +char *ctf_statusbar = +"yb -24 " + +// health +"xv 0 " +"hnum " +"xv 50 " +"pic 0 " + +// ammo +"if 2 " +" xv 100 " +" anum " +" xv 150 " +" pic 2 " +"endif " + +// armor +"if 4 " +" xv 200 " +" rnum " +" xv 250 " +" pic 4 " +"endif " + +// selected item +"if 6 " +" xv 296 " +" pic 6 " +"endif " + +"yb -50 " + +// picked up item +"if 7 " +" xv 0 " +" pic 7 " +" xv 26 " +" yb -42 " +" stat_string 8 " +" yb -50 " +"endif " + +// timer +"if 9 " + "xv 246 " + "num 2 10 " + "xv 296 " + "pic 9 " +"endif " + +// help / weapon icon +"if 11 " + "xv 148 " + "pic 11 " +"endif " + +// frags +"xr -50 " +"yt 2 " +"num 3 14 " + +//tech +"yb -129 " +"if 26 " + "xr -26 " + "pic 26 " +"endif " + +// red team +"yb -102 " +"if 17 " + "xr -26 " + "pic 17 " +"endif " +"xr -62 " +"num 2 18 " +//joined overlay +"if 22 " + "yb -104 " + "xr -28 " + "pic 22 " +"endif " + +// blue team +"yb -75 " +"if 19 " + "xr -26 " + "pic 19 " +"endif " +"xr -62 " +"num 2 20 " +"if 23 " + "yb -77 " + "xr -28 " + "pic 23 " +"endif " + +// have flag graph +"if 21 " + "yt 26 " + "xr -24 " + "pic 21 " +"endif " + +// id view state +"if 27 " + "xv 112 " + "yb -58 " + "stat_string 27 " +"endif " + +"if 29 " + "xv 96 " + "yb -58 " + "pic 29 " +"endif " + +"if 28 " + "xl 0 " + "yb -78 " + "stat_string 28 " +"endif " + +"if 30 " + "xl 0 " + "yb -88 " + "stat_string 30 " +"endif " +; + +static char *tnames[] = { + "item_tech1", "item_tech2", "item_tech3", "item_tech4", + NULL +}; + +void stuffcmd(edict_t *ent, char *s) +{ + gi.WriteByte (11); + gi.WriteString (s); + gi.unicast (ent, true); +} + +/*--------------------------------------------------------------------------*/ + +/* +================= +findradius + +Returns entities that have origins within a spherical area + +findradius (origin, radius) +================= +*/ +static edict_t *loc_findradius (edict_t *from, vec3_t org, float rad) +{ + vec3_t eorg; + int j; + + if (!from) + from = g_edicts; + else + from++; + for ( ; from < &g_edicts[globals.num_edicts]; from++) + { + if (!from->inuse) + continue; +#if 0 + if (from->solid == SOLID_NOT) + continue; +#endif + for (j=0 ; j<3 ; j++) + eorg[j] = org[j] - (from->s.origin[j] + (from->mins[j] + from->maxs[j])*0.5); + if (VectorLength(eorg) > rad) + continue; + return from; + } + + return NULL; +} + +static void loc_buildboxpoints(vec3_t p[8], vec3_t org, vec3_t mins, vec3_t maxs) +{ + VectorAdd(org, mins, p[0]); + VectorCopy(p[0], p[1]); + p[1][0] -= mins[0]; + VectorCopy(p[0], p[2]); + p[2][1] -= mins[1]; + VectorCopy(p[0], p[3]); + p[3][0] -= mins[0]; + p[3][1] -= mins[1]; + VectorAdd(org, maxs, p[4]); + VectorCopy(p[4], p[5]); + p[5][0] -= maxs[0]; + VectorCopy(p[0], p[6]); + p[6][1] -= maxs[1]; + VectorCopy(p[0], p[7]); + p[7][0] -= maxs[0]; + p[7][1] -= maxs[1]; +} + +static qboolean loc_CanSee (edict_t *targ, edict_t *inflictor) +{ + trace_t trace; + vec3_t targpoints[8]; + int i; + vec3_t viewpoint; + +// bmodels need special checking because their origin is 0,0,0 + if (targ->movetype == MOVETYPE_PUSH) + return false; // bmodels not supported + + loc_buildboxpoints(targpoints, targ->s.origin, targ->mins, targ->maxs); + + VectorCopy(inflictor->s.origin, viewpoint); + viewpoint[2] += inflictor->viewheight; + + for (i = 0; i < 8; i++) { + trace = gi.trace (viewpoint, vec3_origin, vec3_origin, targpoints[i], inflictor, MASK_SOLID); + if (trace.fraction == 1.0) + return true; + } + + return false; +} + +/*--------------------------------------------------------------------------*/ + +static gitem_t *flag1_item; +static gitem_t *flag2_item; + +void CTFSpawn(void) +{ + if (!flag1_item) + flag1_item = FindItemByClassname("item_flag_team1"); + if (!flag2_item) + flag2_item = FindItemByClassname("item_flag_team2"); + memset(&ctfgame, 0, sizeof(ctfgame)); + CTFSetupTechSpawn(); + + if (competition->value > 1) { + ctfgame.match = MATCH_SETUP; + ctfgame.matchtime = level.time + matchsetuptime->value * 60; + } +} + +void CTFInit(void) +{ + ctf = gi.cvar("ctf", "1", CVAR_SERVERINFO); + ctf_forcejoin = gi.cvar("ctf_forcejoin", "", 0); + competition = gi.cvar("competition", "0", CVAR_SERVERINFO); + matchlock = gi.cvar("matchlock", "1", CVAR_SERVERINFO); + electpercentage = gi.cvar("electpercentage", "66", 0); + matchtime = gi.cvar("matchtime", "20", CVAR_SERVERINFO); + matchsetuptime = gi.cvar("matchsetuptime", "10", 0); + matchstarttime = gi.cvar("matchstarttime", "20", 0); + admin_password = gi.cvar("admin_password", "", 0); + allow_admin = gi.cvar("allow_admin", "1", 0); + warp_list = gi.cvar("warp_list", "q2ctf1 q2ctf2 q2ctf3 q2ctf4 q2ctf5", 0); + warn_unbalanced = gi.cvar("warn_unbalanced", "1", 0); +} + +/* + * Precache CTF items + */ + +void CTFPrecache(void) +{ + imageindex_i_ctf1 = gi.imageindex("i_ctf1"); + imageindex_i_ctf2 = gi.imageindex("i_ctf2"); + imageindex_i_ctf1d = gi.imageindex("i_ctf1d"); + imageindex_i_ctf2d = gi.imageindex("i_ctf2d"); + imageindex_i_ctf1t = gi.imageindex("i_ctf1t"); + imageindex_i_ctf2t = gi.imageindex("i_ctf2t"); + imageindex_i_ctfj = gi.imageindex("i_ctfj"); + imageindex_sbfctf1 = gi.imageindex("sbfctf1"); + imageindex_sbfctf2 = gi.imageindex("sbfctf2"); + imageindex_ctfsb1 = gi.imageindex("ctfsb1"); + imageindex_ctfsb2 = gi.imageindex("ctfsb2"); +} + +/*--------------------------------------------------------------------------*/ + +char *CTFTeamName(int team) +{ + switch (team) { + case CTF_TEAM1: + return "RED"; + case CTF_TEAM2: + return "BLUE"; + } + return "UNKNOWN"; // Hanzo pointed out this was spelled wrong as "UKNOWN" +} + +char *CTFOtherTeamName(int team) +{ + switch (team) { + case CTF_TEAM1: + return "BLUE"; + case CTF_TEAM2: + return "RED"; + } + return "UNKNOWN"; // Hanzo pointed out this was spelled wrong as "UKNOWN" +} + +int CTFOtherTeam(int team) +{ + switch (team) { + case CTF_TEAM1: + return CTF_TEAM2; + case CTF_TEAM2: + return CTF_TEAM1; + } + return -1; // invalid value +} + +/*--------------------------------------------------------------------------*/ + +edict_t *SelectRandomDeathmatchSpawnPoint (void); +edict_t *SelectFarthestDeathmatchSpawnPoint (void); +float PlayersRangeFromSpot (edict_t *spot); + +void CTFAssignSkin(edict_t *ent, char *s) +{ + int playernum = ent-g_edicts-1; + char *p; + char t[64]; + + Com_sprintf(t, sizeof(t), "%s", s); + + if ((p = strchr(t, '/')) != NULL) + p[1] = 0; + else + strcpy(t, "male/"); + + switch (ent->client->resp.ctf_team) { + case CTF_TEAM1: + gi.configstring (CS_PLAYERSKINS+playernum, va("%s\\%s%s", + ent->client->pers.netname, t, CTF_TEAM1_SKIN) ); + break; + case CTF_TEAM2: + gi.configstring (CS_PLAYERSKINS+playernum, + va("%s\\%s%s", ent->client->pers.netname, t, CTF_TEAM2_SKIN) ); + break; + default: + gi.configstring (CS_PLAYERSKINS+playernum, + va("%s\\%s", ent->client->pers.netname, s) ); + break; + } +// gi.cprintf(ent, PRINT_HIGH, "You have been assigned to %s team.\n", ent->client->pers.netname); +} + +void CTFAssignTeam(gclient_t *who) +{ + edict_t *player; + int i; + int team1count = 0, team2count = 0; + + who->resp.ctf_state = 0; + + if (!((int)dmflags->value & DF_CTF_FORCEJOIN)) { + who->resp.ctf_team = CTF_NOTEAM; + return; + } + + for (i = 1; i <= maxclients->value; i++) { + player = &g_edicts[i]; + + if (!player->inuse || player->client == who) + continue; + + switch (player->client->resp.ctf_team) { + case CTF_TEAM1: + team1count++; + break; + case CTF_TEAM2: + team2count++; + } + } + if (team1count < team2count) + who->resp.ctf_team = CTF_TEAM1; + else if (team2count < team1count) + who->resp.ctf_team = CTF_TEAM2; + else if (rand() & 1) + who->resp.ctf_team = CTF_TEAM1; + else + who->resp.ctf_team = CTF_TEAM2; +} + +/* +================ +SelectCTFSpawnPoint + +go to a ctf point, but NOT the two points closest +to other players +================ +*/ +edict_t *SelectCTFSpawnPoint (edict_t *ent) +{ + edict_t *spot, *spot1, *spot2; + int count = 0; + int selection; + float range, range1, range2; + char *cname; + + if (ent->client->resp.ctf_state) + if ( (int)(dmflags->value) & DF_SPAWN_FARTHEST) + return SelectFarthestDeathmatchSpawnPoint (); + else + return SelectRandomDeathmatchSpawnPoint (); + + ent->client->resp.ctf_state++; + + switch (ent->client->resp.ctf_team) { + case CTF_TEAM1: + cname = "info_player_team1"; + break; + case CTF_TEAM2: + cname = "info_player_team2"; + break; + default: + return SelectRandomDeathmatchSpawnPoint(); + } + + spot = NULL; + range1 = range2 = 99999; + spot1 = spot2 = NULL; + + while ((spot = G_Find (spot, FOFS(classname), cname)) != NULL) + { + count++; + range = PlayersRangeFromSpot(spot); + if (range < range1) + { + range1 = range; + spot1 = spot; + } + else if (range < range2) + { + range2 = range; + spot2 = spot; + } + } + + if (!count) + return SelectRandomDeathmatchSpawnPoint(); + + if (count <= 2) + { + spot1 = spot2 = NULL; + } + else + count -= 2; + + selection = rand() % count; + + spot = NULL; + do + { + spot = G_Find (spot, FOFS(classname), cname); + if (spot == spot1 || spot == spot2) + selection++; + } while(selection--); + + return spot; +} + +/*------------------------------------------------------------------------*/ +/* +CTFFragBonuses + +Calculate the bonuses for flag defense, flag carrier defense, etc. +Note that bonuses are not cumaltive. You get one, they are in importance +order. +*/ +void CTFFragBonuses(edict_t *targ, edict_t *inflictor, edict_t *attacker) +{ + int i; + edict_t *ent; + gitem_t *flag_item, *enemy_flag_item; + int otherteam; + edict_t *flag, *carrier; + char *c; + vec3_t v1, v2; + + if (targ->client && attacker->client) { + if (attacker->client->resp.ghost) + if (attacker != targ) + attacker->client->resp.ghost->kills++; + if (targ->client->resp.ghost) + targ->client->resp.ghost->deaths++; + } + + // no bonus for fragging yourself + if (!targ->client || !attacker->client || targ == attacker) + return; + + otherteam = CTFOtherTeam(targ->client->resp.ctf_team); + if (otherteam < 0) + return; // whoever died isn't on a team + + // same team, if the flag at base, check to he has the enemy flag + if (targ->client->resp.ctf_team == CTF_TEAM1) { + flag_item = flag1_item; + enemy_flag_item = flag2_item; + } else { + flag_item = flag2_item; + enemy_flag_item = flag1_item; + } + + // did the attacker frag the flag carrier? + if (targ->client->pers.inventory[ITEM_INDEX(enemy_flag_item)]) { + attacker->client->resp.ctf_lastfraggedcarrier = level.time; + attacker->client->resp.score += CTF_FRAG_CARRIER_BONUS; + gi.cprintf(attacker, PRINT_MEDIUM, "BONUS: %d points for fragging enemy flag carrier.\n", + CTF_FRAG_CARRIER_BONUS); + + // the target had the flag, clear the hurt carrier + // field on the other team + for (i = 1; i <= maxclients->value; i++) { + ent = g_edicts + i; + if (ent->inuse && ent->client->resp.ctf_team == otherteam) + ent->client->resp.ctf_lasthurtcarrier = 0; + } + return; + } + + if (targ->client->resp.ctf_lasthurtcarrier && + level.time - targ->client->resp.ctf_lasthurtcarrier < CTF_CARRIER_DANGER_PROTECT_TIMEOUT && + !attacker->client->pers.inventory[ITEM_INDEX(flag_item)]) { + // attacker is on the same team as the flag carrier and + // fragged a guy who hurt our flag carrier + attacker->client->resp.score += CTF_CARRIER_DANGER_PROTECT_BONUS; + gi.bprintf(PRINT_MEDIUM, "%s defends %s's flag carrier against an agressive enemy\n", + attacker->client->pers.netname, + CTFTeamName(attacker->client->resp.ctf_team)); + if (attacker->client->resp.ghost) + attacker->client->resp.ghost->carrierdef++; + return; + } + + // flag and flag carrier area defense bonuses + + // we have to find the flag and carrier entities + + // find the flag + switch (attacker->client->resp.ctf_team) { + case CTF_TEAM1: + c = "item_flag_team1"; + break; + case CTF_TEAM2: + c = "item_flag_team2"; + break; + default: + return; + } + + flag = NULL; + while ((flag = G_Find (flag, FOFS(classname), c)) != NULL) { + if (!(flag->spawnflags & DROPPED_ITEM)) + break; + } + + if (!flag) + return; // can't find attacker's flag + + // find attacker's team's flag carrier + for (i = 1; i <= maxclients->value; i++) { + carrier = g_edicts + i; + if (carrier->inuse && + carrier->client->pers.inventory[ITEM_INDEX(flag_item)]) + break; + carrier = NULL; + } + + // ok we have the attackers flag and a pointer to the carrier + + // check to see if we are defending the base's flag + VectorSubtract(targ->s.origin, flag->s.origin, v1); + VectorSubtract(attacker->s.origin, flag->s.origin, v2); + + if ((VectorLength(v1) < CTF_TARGET_PROTECT_RADIUS || + VectorLength(v2) < CTF_TARGET_PROTECT_RADIUS || + loc_CanSee(flag, targ) || loc_CanSee(flag, attacker)) && + attacker->client->resp.ctf_team != targ->client->resp.ctf_team) { + // we defended the base flag + attacker->client->resp.score += CTF_FLAG_DEFENSE_BONUS; + if (flag->solid == SOLID_NOT) + gi.bprintf(PRINT_MEDIUM, "%s defends the %s base.\n", + attacker->client->pers.netname, + CTFTeamName(attacker->client->resp.ctf_team)); + else + gi.bprintf(PRINT_MEDIUM, "%s defends the %s flag.\n", + attacker->client->pers.netname, + CTFTeamName(attacker->client->resp.ctf_team)); + if (attacker->client->resp.ghost) + attacker->client->resp.ghost->basedef++; + return; + } + + if (carrier && carrier != attacker) { + VectorSubtract(targ->s.origin, carrier->s.origin, v1); + VectorSubtract(attacker->s.origin, carrier->s.origin, v1); + + if (VectorLength(v1) < CTF_ATTACKER_PROTECT_RADIUS || + VectorLength(v2) < CTF_ATTACKER_PROTECT_RADIUS || + loc_CanSee(carrier, targ) || loc_CanSee(carrier, attacker)) { + attacker->client->resp.score += CTF_CARRIER_PROTECT_BONUS; + gi.bprintf(PRINT_MEDIUM, "%s defends the %s's flag carrier.\n", + attacker->client->pers.netname, + CTFTeamName(attacker->client->resp.ctf_team)); + if (attacker->client->resp.ghost) + attacker->client->resp.ghost->carrierdef++; + return; + } + } +} + +void CTFCheckHurtCarrier(edict_t *targ, edict_t *attacker) +{ + gitem_t *flag_item; + + if (!targ->client || !attacker->client) + return; + + if (targ->client->resp.ctf_team == CTF_TEAM1) + flag_item = flag2_item; + else + flag_item = flag1_item; + + if (targ->client->pers.inventory[ITEM_INDEX(flag_item)] && + targ->client->resp.ctf_team != attacker->client->resp.ctf_team) + attacker->client->resp.ctf_lasthurtcarrier = level.time; +} + + +/*------------------------------------------------------------------------*/ + +void CTFResetFlag(int ctf_team) +{ + char *c; + edict_t *ent; + + switch (ctf_team) { + case CTF_TEAM1: + c = "item_flag_team1"; + break; + case CTF_TEAM2: + c = "item_flag_team2"; + break; + default: + return; + } + + ent = NULL; + while ((ent = G_Find (ent, FOFS(classname), c)) != NULL) { + if (ent->spawnflags & DROPPED_ITEM) + G_FreeEdict(ent); + else { + ent->svflags &= ~SVF_NOCLIENT; + ent->solid = SOLID_TRIGGER; + gi.linkentity(ent); + ent->s.event = EV_ITEM_RESPAWN; + } + } +} + +void CTFResetFlags(void) +{ + CTFResetFlag(CTF_TEAM1); + CTFResetFlag(CTF_TEAM2); +} + +qboolean CTFPickup_Flag(edict_t *ent, edict_t *other) +{ + int ctf_team; + int i; + edict_t *player; + gitem_t *flag_item, *enemy_flag_item; + + // figure out what team this flag is + if (strcmp(ent->classname, "item_flag_team1") == 0) + ctf_team = CTF_TEAM1; + else if (strcmp(ent->classname, "item_flag_team2") == 0) + ctf_team = CTF_TEAM2; + else { + gi.cprintf(ent, PRINT_HIGH, "Don't know what team the flag is on.\n"); + return false; + } + + // same team, if the flag at base, check to he has the enemy flag + if (ctf_team == CTF_TEAM1) { + flag_item = flag1_item; + enemy_flag_item = flag2_item; + } else { + flag_item = flag2_item; + enemy_flag_item = flag1_item; + } + + if (ctf_team == other->client->resp.ctf_team) { + + if (!(ent->spawnflags & DROPPED_ITEM)) { + // the flag is at home base. if the player has the enemy + // flag, he's just won! + + if (other->client->pers.inventory[ITEM_INDEX(enemy_flag_item)]) { + gi.bprintf(PRINT_HIGH, "%s captured the %s flag!\n", + other->client->pers.netname, CTFOtherTeamName(ctf_team)); + other->client->pers.inventory[ITEM_INDEX(enemy_flag_item)] = 0; + + ctfgame.last_flag_capture = level.time; + ctfgame.last_capture_team = ctf_team; + if (ctf_team == CTF_TEAM1) + ctfgame.team1++; + else + ctfgame.team2++; + + gi.sound (ent, CHAN_RELIABLE+CHAN_NO_PHS_ADD+CHAN_VOICE, gi.soundindex("ctf/flagcap.wav"), 1, ATTN_NONE, 0); + + // other gets another 10 frag bonus + other->client->resp.score += CTF_CAPTURE_BONUS; + if (other->client->resp.ghost) + other->client->resp.ghost->caps++; + + // Ok, let's do the player loop, hand out the bonuses + for (i = 1; i <= maxclients->value; i++) { + player = &g_edicts[i]; + if (!player->inuse) + continue; + + if (player->client->resp.ctf_team != other->client->resp.ctf_team) + player->client->resp.ctf_lasthurtcarrier = -5; + else if (player->client->resp.ctf_team == other->client->resp.ctf_team) { + if (player != other) + player->client->resp.score += CTF_TEAM_BONUS; + // award extra points for capture assists + if (player->client->resp.ctf_lastreturnedflag + CTF_RETURN_FLAG_ASSIST_TIMEOUT > level.time) { + gi.bprintf(PRINT_HIGH, "%s gets an assist for returning the flag!\n", player->client->pers.netname); + player->client->resp.score += CTF_RETURN_FLAG_ASSIST_BONUS; + } + if (player->client->resp.ctf_lastfraggedcarrier + CTF_FRAG_CARRIER_ASSIST_TIMEOUT > level.time) { + gi.bprintf(PRINT_HIGH, "%s gets an assist for fragging the flag carrier!\n", player->client->pers.netname); + player->client->resp.score += CTF_FRAG_CARRIER_ASSIST_BONUS; + } + } + } + + CTFResetFlags(); + return false; + } + return false; // its at home base already + } + // hey, its not home. return it by teleporting it back + gi.bprintf(PRINT_HIGH, "%s returned the %s flag!\n", + other->client->pers.netname, CTFTeamName(ctf_team)); + other->client->resp.score += CTF_RECOVERY_BONUS; + other->client->resp.ctf_lastreturnedflag = level.time; + gi.sound (ent, CHAN_RELIABLE+CHAN_NO_PHS_ADD+CHAN_VOICE, gi.soundindex("ctf/flagret.wav"), 1, ATTN_NONE, 0); + //CTFResetFlag will remove this entity! We must return false + CTFResetFlag(ctf_team); + return false; + } + + // hey, its not our flag, pick it up + gi.bprintf(PRINT_HIGH, "%s got the %s flag!\n", + other->client->pers.netname, CTFTeamName(ctf_team)); + other->client->resp.score += CTF_FLAG_BONUS; + + other->client->pers.inventory[ITEM_INDEX(flag_item)] = 1; + other->client->resp.ctf_flagsince = level.time; + + // pick up the flag + // if it's not a dropped flag, we just make is disappear + // if it's dropped, it will be removed by the pickup caller + if (!(ent->spawnflags & DROPPED_ITEM)) { + ent->flags |= FL_RESPAWN; + ent->svflags |= SVF_NOCLIENT; + ent->solid = SOLID_NOT; + } + return true; +} + +static void CTFDropFlagTouch(edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + //owner (who dropped us) can't touch for two secs + if (other == ent->owner && + ent->nextthink - level.time > CTF_AUTO_FLAG_RETURN_TIMEOUT-2) + return; + + Touch_Item (ent, other, plane, surf); +} + +static void CTFDropFlagThink(edict_t *ent) +{ + // auto return the flag + // reset flag will remove ourselves + if (strcmp(ent->classname, "item_flag_team1") == 0) { + CTFResetFlag(CTF_TEAM1); + gi.bprintf(PRINT_HIGH, "The %s flag has returned!\n", + CTFTeamName(CTF_TEAM1)); + } else if (strcmp(ent->classname, "item_flag_team2") == 0) { + CTFResetFlag(CTF_TEAM2); + gi.bprintf(PRINT_HIGH, "The %s flag has returned!\n", + CTFTeamName(CTF_TEAM2)); + } +} + +// Called from PlayerDie, to drop the flag from a dying player +void CTFDeadDropFlag(edict_t *self) +{ + edict_t *dropped = NULL; + + if (self->client->pers.inventory[ITEM_INDEX(flag1_item)]) { + dropped = Drop_Item(self, flag1_item); + self->client->pers.inventory[ITEM_INDEX(flag1_item)] = 0; + gi.bprintf(PRINT_HIGH, "%s lost the %s flag!\n", + self->client->pers.netname, CTFTeamName(CTF_TEAM1)); + } else if (self->client->pers.inventory[ITEM_INDEX(flag2_item)]) { + dropped = Drop_Item(self, flag2_item); + self->client->pers.inventory[ITEM_INDEX(flag2_item)] = 0; + gi.bprintf(PRINT_HIGH, "%s lost the %s flag!\n", + self->client->pers.netname, CTFTeamName(CTF_TEAM2)); + } + + if (dropped) { + dropped->think = CTFDropFlagThink; + dropped->nextthink = level.time + CTF_AUTO_FLAG_RETURN_TIMEOUT; + dropped->touch = CTFDropFlagTouch; + } +} + +qboolean CTFDrop_Flag(edict_t *ent, gitem_t *item) +{ + if (rand() & 1) + gi.cprintf(ent, PRINT_HIGH, "Only lusers drop flags.\n"); + else + gi.cprintf(ent, PRINT_HIGH, "Winners don't drop flags.\n"); + return false; +} + +static void CTFFlagThink(edict_t *ent) +{ + if (ent->solid != SOLID_NOT) + ent->s.frame = 173 + (((ent->s.frame - 173) + 1) % 16); + ent->nextthink = level.time + FRAMETIME; +} + + +void CTFFlagSetup (edict_t *ent) +{ + trace_t tr; + vec3_t dest; + float *v; + + v = tv(-15,-15,-15); + VectorCopy (v, ent->mins); + v = tv(15,15,15); + VectorCopy (v, ent->maxs); + + if (ent->model) + gi.setmodel (ent, ent->model); + else + gi.setmodel (ent, ent->item->world_model); + ent->solid = SOLID_TRIGGER; + ent->movetype = MOVETYPE_TOSS; + ent->touch = Touch_Item; + + v = tv(0,0,-128); + VectorAdd (ent->s.origin, v, dest); + + tr = gi.trace (ent->s.origin, ent->mins, ent->maxs, dest, ent, MASK_SOLID); + if (tr.startsolid) + { + gi.dprintf ("CTFFlagSetup: %s startsolid at %s\n", ent->classname, vtos(ent->s.origin)); + G_FreeEdict (ent); + return; + } + + VectorCopy (tr.endpos, ent->s.origin); + + gi.linkentity (ent); + + ent->nextthink = level.time + FRAMETIME; + ent->think = CTFFlagThink; +} + +void CTFEffects(edict_t *player) +{ + player->s.effects &= ~(EF_FLAG1 | EF_FLAG2); + if (player->health > 0) { + if (player->client->pers.inventory[ITEM_INDEX(flag1_item)]) { + player->s.effects |= EF_FLAG1; + } + if (player->client->pers.inventory[ITEM_INDEX(flag2_item)]) { + player->s.effects |= EF_FLAG2; + } + } + + if (player->client->pers.inventory[ITEM_INDEX(flag1_item)]) + player->s.modelindex3 = gi.modelindex("players/male/flag1.md2"); + else if (player->client->pers.inventory[ITEM_INDEX(flag2_item)]) + player->s.modelindex3 = gi.modelindex("players/male/flag2.md2"); + else + player->s.modelindex3 = 0; +} + +// called when we enter the intermission +void CTFCalcScores(void) +{ + int i; + + ctfgame.total1 = ctfgame.total2 = 0; + for (i = 0; i < maxclients->value; i++) { + if (!g_edicts[i+1].inuse) + continue; + if (game.clients[i].resp.ctf_team == CTF_TEAM1) + ctfgame.total1 += game.clients[i].resp.score; + else if (game.clients[i].resp.ctf_team == CTF_TEAM2) + ctfgame.total2 += game.clients[i].resp.score; + } +} + +void CTFID_f (edict_t *ent) +{ + if (ent->client->resp.id_state) { + gi.cprintf(ent, PRINT_HIGH, "Disabling player identication display.\n"); + ent->client->resp.id_state = false; + } else { + gi.cprintf(ent, PRINT_HIGH, "Activating player identication display.\n"); + ent->client->resp.id_state = true; + } +} + +static void CTFSetIDView(edict_t *ent) +{ + vec3_t forward, dir; + trace_t tr; + edict_t *who, *best; + float bd = 0, d; + int i; + + // only check every few frames + if (level.time - ent->client->resp.lastidtime < 0.25) + return; + ent->client->resp.lastidtime = level.time; + + ent->client->ps.stats[STAT_CTF_ID_VIEW] = 0; + ent->client->ps.stats[STAT_CTF_ID_VIEW_COLOR] = 0; + + AngleVectors(ent->client->v_angle, forward, NULL, NULL); + VectorScale(forward, 1024, forward); + VectorAdd(ent->s.origin, forward, forward); + tr = gi.trace(ent->s.origin, NULL, NULL, forward, ent, MASK_SOLID); + if (tr.fraction < 1 && tr.ent && tr.ent->client) { + ent->client->ps.stats[STAT_CTF_ID_VIEW] = + CS_GENERAL + (tr.ent - g_edicts - 1); + if (tr.ent->client->resp.ctf_team == CTF_TEAM1) + ent->client->ps.stats[STAT_CTF_ID_VIEW_COLOR] = imageindex_sbfctf1; + else if (tr.ent->client->resp.ctf_team == CTF_TEAM2) + ent->client->ps.stats[STAT_CTF_ID_VIEW_COLOR] = imageindex_sbfctf2; + return; + } + + AngleVectors(ent->client->v_angle, forward, NULL, NULL); + best = NULL; + for (i = 1; i <= maxclients->value; i++) { + who = g_edicts + i; + if (!who->inuse || who->solid == SOLID_NOT) + continue; + VectorSubtract(who->s.origin, ent->s.origin, dir); + VectorNormalize(dir); + d = DotProduct(forward, dir); + if (d > bd && loc_CanSee(ent, who)) { + bd = d; + best = who; + } + } + if (bd > 0.90) { + ent->client->ps.stats[STAT_CTF_ID_VIEW] = + CS_GENERAL + (best - g_edicts - 1); + if (best->client->resp.ctf_team == CTF_TEAM1) + ent->client->ps.stats[STAT_CTF_ID_VIEW_COLOR] = imageindex_sbfctf1; + else if (best->client->resp.ctf_team == CTF_TEAM2) + ent->client->ps.stats[STAT_CTF_ID_VIEW_COLOR] = imageindex_sbfctf2; + } +} + +void SetCTFStats(edict_t *ent) +{ + gitem_t *tech; + int i; + int p1, p2; + edict_t *e; + + if (ctfgame.match > MATCH_NONE) + ent->client->ps.stats[STAT_CTF_MATCH] = CONFIG_CTF_MATCH; + else + ent->client->ps.stats[STAT_CTF_MATCH] = 0; + + if (ctfgame.warnactive) + ent->client->ps.stats[STAT_CTF_TEAMINFO] = CONFIG_CTF_TEAMINFO; + else + ent->client->ps.stats[STAT_CTF_TEAMINFO] = 0; + + //ghosting + if (ent->client->resp.ghost) { + ent->client->resp.ghost->score = ent->client->resp.score; + strcpy(ent->client->resp.ghost->netname, ent->client->pers.netname); + ent->client->resp.ghost->number = ent->s.number; + } + + // logo headers for the frag display + ent->client->ps.stats[STAT_CTF_TEAM1_HEADER] = imageindex_ctfsb1; + ent->client->ps.stats[STAT_CTF_TEAM2_HEADER] = imageindex_ctfsb2; + + // if during intermission, we must blink the team header of the winning team + if (level.intermissiontime && (level.framenum & 8)) { // blink 1/8th second + // note that ctfgame.total[12] is set when we go to intermission + if (ctfgame.team1 > ctfgame.team2) + ent->client->ps.stats[STAT_CTF_TEAM1_HEADER] = 0; + else if (ctfgame.team2 > ctfgame.team1) + ent->client->ps.stats[STAT_CTF_TEAM2_HEADER] = 0; + else if (ctfgame.total1 > ctfgame.total2) // frag tie breaker + ent->client->ps.stats[STAT_CTF_TEAM1_HEADER] = 0; + else if (ctfgame.total2 > ctfgame.total1) + ent->client->ps.stats[STAT_CTF_TEAM2_HEADER] = 0; + else { // tie game! + ent->client->ps.stats[STAT_CTF_TEAM1_HEADER] = 0; + ent->client->ps.stats[STAT_CTF_TEAM2_HEADER] = 0; + } + } + + // tech icon + i = 0; + ent->client->ps.stats[STAT_CTF_TECH] = 0; + while (tnames[i]) { + if ((tech = FindItemByClassname(tnames[i])) != NULL && + ent->client->pers.inventory[ITEM_INDEX(tech)]) { + ent->client->ps.stats[STAT_CTF_TECH] = gi.imageindex(tech->icon); + break; + } + i++; + } + + // figure out what icon to display for team logos + // three states: + // flag at base + // flag taken + // flag dropped + p1 = imageindex_i_ctf1; + e = G_Find(NULL, FOFS(classname), "item_flag_team1"); + if (e != NULL) { + if (e->solid == SOLID_NOT) { + int i; + + // not at base + // check if on player + p1 = imageindex_i_ctf1d; // default to dropped + for (i = 1; i <= maxclients->value; i++) + if (g_edicts[i].inuse && + g_edicts[i].client->pers.inventory[ITEM_INDEX(flag1_item)]) { + // enemy has it + p1 = imageindex_i_ctf1t; + break; + } + } else if (e->spawnflags & DROPPED_ITEM) + p1 = imageindex_i_ctf1d; // must be dropped + } + p2 = imageindex_i_ctf2; + e = G_Find(NULL, FOFS(classname), "item_flag_team2"); + if (e != NULL) { + if (e->solid == SOLID_NOT) { + int i; + + // not at base + // check if on player + p2 = imageindex_i_ctf2d; // default to dropped + for (i = 1; i <= maxclients->value; i++) + if (g_edicts[i].inuse && + g_edicts[i].client->pers.inventory[ITEM_INDEX(flag2_item)]) { + // enemy has it + p2 = imageindex_i_ctf2t; + break; + } + } else if (e->spawnflags & DROPPED_ITEM) + p2 = imageindex_i_ctf2d; // must be dropped + } + + + ent->client->ps.stats[STAT_CTF_TEAM1_PIC] = p1; + ent->client->ps.stats[STAT_CTF_TEAM2_PIC] = p2; + + if (ctfgame.last_flag_capture && level.time - ctfgame.last_flag_capture < 5) { + if (ctfgame.last_capture_team == CTF_TEAM1) + if (level.framenum & 8) + ent->client->ps.stats[STAT_CTF_TEAM1_PIC] = p1; + else + ent->client->ps.stats[STAT_CTF_TEAM1_PIC] = 0; + else + if (level.framenum & 8) + ent->client->ps.stats[STAT_CTF_TEAM2_PIC] = p2; + else + ent->client->ps.stats[STAT_CTF_TEAM2_PIC] = 0; + } + + ent->client->ps.stats[STAT_CTF_TEAM1_CAPS] = ctfgame.team1; + ent->client->ps.stats[STAT_CTF_TEAM2_CAPS] = ctfgame.team2; + + ent->client->ps.stats[STAT_CTF_FLAG_PIC] = 0; + if (ent->client->resp.ctf_team == CTF_TEAM1 && + ent->client->pers.inventory[ITEM_INDEX(flag2_item)] && + (level.framenum & 8)) + ent->client->ps.stats[STAT_CTF_FLAG_PIC] = imageindex_i_ctf2; + + else if (ent->client->resp.ctf_team == CTF_TEAM2 && + ent->client->pers.inventory[ITEM_INDEX(flag1_item)] && + (level.framenum & 8)) + ent->client->ps.stats[STAT_CTF_FLAG_PIC] = imageindex_i_ctf1; + + ent->client->ps.stats[STAT_CTF_JOINED_TEAM1_PIC] = 0; + ent->client->ps.stats[STAT_CTF_JOINED_TEAM2_PIC] = 0; + if (ent->client->resp.ctf_team == CTF_TEAM1) + ent->client->ps.stats[STAT_CTF_JOINED_TEAM1_PIC] = imageindex_i_ctfj; + else if (ent->client->resp.ctf_team == CTF_TEAM2) + ent->client->ps.stats[STAT_CTF_JOINED_TEAM2_PIC] = imageindex_i_ctfj; + + if (ent->client->resp.id_state) + CTFSetIDView(ent); + else { + ent->client->ps.stats[STAT_CTF_ID_VIEW] = 0; + ent->client->ps.stats[STAT_CTF_ID_VIEW_COLOR] = 0; + } +} + +/*------------------------------------------------------------------------*/ + +/*QUAKED info_player_team1 (1 0 0) (-16 -16 -24) (16 16 32) +potential team1 spawning position for ctf games +*/ +void SP_info_player_team1(edict_t *self) +{ +} + +/*QUAKED info_player_team2 (0 0 1) (-16 -16 -24) (16 16 32) +potential team2 spawning position for ctf games +*/ +void SP_info_player_team2(edict_t *self) +{ +} + + +/*------------------------------------------------------------------------*/ +/* GRAPPLE */ +/*------------------------------------------------------------------------*/ + +// ent is player +void CTFPlayerResetGrapple(edict_t *ent) +{ + if (ent->client && ent->client->ctf_grapple) + CTFResetGrapple(ent->client->ctf_grapple); +} + +// self is grapple, not player +void CTFResetGrapple(edict_t *self) +{ + if (self->owner->client->ctf_grapple) { + float volume = 1.0; + gclient_t *cl; + + if (self->owner->client->silencer_shots) + volume = 0.2; + + gi.sound (self->owner, CHAN_RELIABLE+CHAN_WEAPON, gi.soundindex("weapons/grapple/grreset.wav"), volume, ATTN_NORM, 0); + cl = self->owner->client; + cl->ctf_grapple = NULL; + cl->ctf_grapplereleasetime = level.time; + cl->ctf_grapplestate = CTF_GRAPPLE_STATE_FLY; // we're firing, not on hook + cl->ps.pmove.pm_flags &= ~PMF_NO_PREDICTION; + G_FreeEdict(self); + } +} + +void CTFGrappleTouch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + float volume = 1.0; + + if (other == self->owner) + return; + + if (self->owner->client->ctf_grapplestate != CTF_GRAPPLE_STATE_FLY) + return; + + if (surf && (surf->flags & SURF_SKY)) + { + CTFResetGrapple(self); + return; + } + + VectorCopy(vec3_origin, self->velocity); + + PlayerNoise(self->owner, self->s.origin, PNOISE_IMPACT); + + if (other->takedamage) { + T_Damage (other, self, self->owner, self->velocity, self->s.origin, plane->normal, self->dmg, 1, 0, MOD_GRAPPLE); + CTFResetGrapple(self); + return; + } + + self->owner->client->ctf_grapplestate = CTF_GRAPPLE_STATE_PULL; // we're on hook + self->enemy = other; + + self->solid = SOLID_NOT; + + if (self->owner->client->silencer_shots) + volume = 0.2; + + gi.sound (self->owner, CHAN_RELIABLE+CHAN_WEAPON, gi.soundindex("weapons/grapple/grpull.wav"), volume, ATTN_NORM, 0); + gi.sound (self, CHAN_WEAPON, gi.soundindex("weapons/grapple/grhit.wav"), volume, ATTN_NORM, 0); + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_SPARKS); + gi.WritePosition (self->s.origin); + if (!plane) + gi.WriteDir (vec3_origin); + else + gi.WriteDir (plane->normal); + gi.multicast (self->s.origin, MULTICAST_PVS); +} + +// draw beam between grapple and self +void CTFGrappleDrawCable(edict_t *self) +{ + vec3_t offset, start, end, f, r; + vec3_t dir; + float distance; + + AngleVectors (self->owner->client->v_angle, f, r, NULL); + VectorSet(offset, 16, 16, self->owner->viewheight-8); + P_ProjectSource (self->owner->client, self->owner->s.origin, offset, f, r, start); + + VectorSubtract(start, self->owner->s.origin, offset); + + VectorSubtract (start, self->s.origin, dir); + distance = VectorLength(dir); + // don't draw cable if close + if (distance < 64) + return; + +#if 0 + if (distance > 256) + return; + + // check for min/max pitch + vectoangles (dir, angles); + if (angles[0] < -180) + angles[0] += 360; + if (fabs(angles[0]) > 45) + return; + + trace_t tr; //!! + + tr = gi.trace (start, NULL, NULL, self->s.origin, self, MASK_SHOT); + if (tr.ent != self) { + CTFResetGrapple(self); + return; + } +#endif + + // adjust start for beam origin being in middle of a segment +// VectorMA (start, 8, f, start); + + VectorCopy (self->s.origin, end); + // adjust end z for end spot since the monster is currently dead +// end[2] = self->absmin[2] + self->size[2] / 2; + + gi.WriteByte (svc_temp_entity); +#if 1 //def USE_GRAPPLE_CABLE + gi.WriteByte (TE_GRAPPLE_CABLE); + gi.WriteShort (self->owner - g_edicts); + gi.WritePosition (self->owner->s.origin); + gi.WritePosition (end); + gi.WritePosition (offset); +#else + gi.WriteByte (TE_MEDIC_CABLE_ATTACK); + gi.WriteShort (self - g_edicts); + gi.WritePosition (end); + gi.WritePosition (start); +#endif + gi.multicast (self->s.origin, MULTICAST_PVS); +} + +void SV_AddGravity (edict_t *ent); + +// pull the player toward the grapple +void CTFGrapplePull(edict_t *self) +{ + vec3_t hookdir, v; + float vlen; + + if (strcmp(self->owner->client->pers.weapon->classname, "weapon_grapple") == 0 && + !self->owner->client->newweapon && + self->owner->client->weaponstate != WEAPON_FIRING && + self->owner->client->weaponstate != WEAPON_ACTIVATING) { + CTFResetGrapple(self); + return; + } + + if (self->enemy) { + if (self->enemy->solid == SOLID_NOT) { + CTFResetGrapple(self); + return; + } + if (self->enemy->solid == SOLID_BBOX) { + VectorScale(self->enemy->size, 0.5, v); + VectorAdd(v, self->enemy->s.origin, v); + VectorAdd(v, self->enemy->mins, self->s.origin); + gi.linkentity (self); + } else + VectorCopy(self->enemy->velocity, self->velocity); + if (self->enemy->takedamage && + !CheckTeamDamage (self->enemy, self->owner)) { + float volume = 1.0; + + if (self->owner->client->silencer_shots) + volume = 0.2; + + T_Damage (self->enemy, self, self->owner, self->velocity, self->s.origin, vec3_origin, 1, 1, 0, MOD_GRAPPLE); + gi.sound (self, CHAN_WEAPON, gi.soundindex("weapons/grapple/grhurt.wav"), volume, ATTN_NORM, 0); + } + if (self->enemy->deadflag) { // he died + CTFResetGrapple(self); + return; + } + } + + CTFGrappleDrawCable(self); + + if (self->owner->client->ctf_grapplestate > CTF_GRAPPLE_STATE_FLY) { + // pull player toward grapple + // this causes icky stuff with prediction, we need to extend + // the prediction layer to include two new fields in the player + // move stuff: a point and a velocity. The client should add + // that velociy in the direction of the point + vec3_t forward, up; + + AngleVectors (self->owner->client->v_angle, forward, NULL, up); + VectorCopy(self->owner->s.origin, v); + v[2] += self->owner->viewheight; + VectorSubtract (self->s.origin, v, hookdir); + + vlen = VectorLength(hookdir); + + if (self->owner->client->ctf_grapplestate == CTF_GRAPPLE_STATE_PULL && + vlen < 64) { + float volume = 1.0; + + if (self->owner->client->silencer_shots) + volume = 0.2; + + self->owner->client->ps.pmove.pm_flags |= PMF_NO_PREDICTION; + gi.sound (self->owner, CHAN_RELIABLE+CHAN_WEAPON, gi.soundindex("weapons/grapple/grhang.wav"), volume, ATTN_NORM, 0); + self->owner->client->ctf_grapplestate = CTF_GRAPPLE_STATE_HANG; + } + + VectorNormalize (hookdir); + VectorScale(hookdir, CTF_GRAPPLE_PULL_SPEED, hookdir); + VectorCopy(hookdir, self->owner->velocity); + SV_AddGravity(self->owner); + } +} + +void CTFFireGrapple (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int effect) +{ + edict_t *grapple; + trace_t tr; + + VectorNormalize (dir); + + grapple = G_Spawn(); + VectorCopy (start, grapple->s.origin); + VectorCopy (start, grapple->s.old_origin); + vectoangles (dir, grapple->s.angles); + VectorScale (dir, speed, grapple->velocity); + grapple->movetype = MOVETYPE_FLYMISSILE; + grapple->clipmask = MASK_SHOT; + grapple->solid = SOLID_BBOX; + grapple->s.effects |= effect; + VectorClear (grapple->mins); + VectorClear (grapple->maxs); + grapple->s.modelindex = gi.modelindex ("models/weapons/grapple/hook/tris.md2"); +// grapple->s.sound = gi.soundindex ("misc/lasfly.wav"); + grapple->owner = self; + grapple->touch = CTFGrappleTouch; +// grapple->nextthink = level.time + FRAMETIME; +// grapple->think = CTFGrappleThink; + grapple->dmg = damage; + self->client->ctf_grapple = grapple; + self->client->ctf_grapplestate = CTF_GRAPPLE_STATE_FLY; // we're firing, not on hook + gi.linkentity (grapple); + + tr = gi.trace (self->s.origin, NULL, NULL, grapple->s.origin, grapple, MASK_SHOT); + if (tr.fraction < 1.0) + { + VectorMA (grapple->s.origin, -10, dir, grapple->s.origin); + grapple->touch (grapple, tr.ent, NULL, NULL); + } +} + +void CTFGrappleFire (edict_t *ent, vec3_t g_offset, int damage, int effect) +{ + vec3_t forward, right; + vec3_t start; + vec3_t offset; + float volume = 1.0; + + if (ent->client->ctf_grapplestate > CTF_GRAPPLE_STATE_FLY) + return; // it's already out + + AngleVectors (ent->client->v_angle, forward, right, NULL); +// VectorSet(offset, 24, 16, ent->viewheight-8+2); + VectorSet(offset, 24, 8, ent->viewheight-8+2); + VectorAdd (offset, g_offset, offset); + P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start); + + VectorScale (forward, -2, ent->client->kick_origin); + ent->client->kick_angles[0] = -1; + + if (ent->client->silencer_shots) + volume = 0.2; + + gi.sound (ent, CHAN_RELIABLE+CHAN_WEAPON, gi.soundindex("weapons/grapple/grfire.wav"), volume, ATTN_NORM, 0); + CTFFireGrapple (ent, start, forward, damage, CTF_GRAPPLE_SPEED, effect); + +#if 0 + // send muzzle flash + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + gi.WriteByte (MZ_BLASTER); + gi.multicast (ent->s.origin, MULTICAST_PVS); +#endif + + PlayerNoise(ent, start, PNOISE_WEAPON); +} + + +void CTFWeapon_Grapple_Fire (edict_t *ent) +{ + int damage; + + damage = 10; + CTFGrappleFire (ent, vec3_origin, damage, 0); + ent->client->ps.gunframe++; +} + +void CTFWeapon_Grapple (edict_t *ent) +{ + static int pause_frames[] = {10, 18, 27, 0}; + static int fire_frames[] = {6, 0}; + int prevstate; + + // if the the attack button is still down, stay in the firing frame + if ((ent->client->buttons & BUTTON_ATTACK) && + ent->client->weaponstate == WEAPON_FIRING && + ent->client->ctf_grapple) + ent->client->ps.gunframe = 9; + + if (!(ent->client->buttons & BUTTON_ATTACK) && + ent->client->ctf_grapple) { + CTFResetGrapple(ent->client->ctf_grapple); + if (ent->client->weaponstate == WEAPON_FIRING) + ent->client->weaponstate = WEAPON_READY; + } + + + if (ent->client->newweapon && + ent->client->ctf_grapplestate > CTF_GRAPPLE_STATE_FLY && + ent->client->weaponstate == WEAPON_FIRING) { + // he wants to change weapons while grappled + ent->client->weaponstate = WEAPON_DROPPING; + ent->client->ps.gunframe = 32; + } + + prevstate = ent->client->weaponstate; + Weapon_Generic (ent, 5, 9, 31, 36, pause_frames, fire_frames, + CTFWeapon_Grapple_Fire); + + // if we just switched back to grapple, immediately go to fire frame + if (prevstate == WEAPON_ACTIVATING && + ent->client->weaponstate == WEAPON_READY && + ent->client->ctf_grapplestate > CTF_GRAPPLE_STATE_FLY) { + if (!(ent->client->buttons & BUTTON_ATTACK)) + ent->client->ps.gunframe = 9; + else + ent->client->ps.gunframe = 5; + ent->client->weaponstate = WEAPON_FIRING; + } +} + +void CTFTeam_f (edict_t *ent) +{ + char *t, *s; + int desired_team; + + t = gi.args(); + if (!*t) { + gi.cprintf(ent, PRINT_HIGH, "You are on the %s team.\n", + CTFTeamName(ent->client->resp.ctf_team)); + return; + } + + if (ctfgame.match > MATCH_SETUP) { + gi.cprintf(ent, PRINT_HIGH, "Can't change teams in a match.\n"); + return; + } + + if (Q_stricmp(t, "red") == 0) + desired_team = CTF_TEAM1; + else if (Q_stricmp(t, "blue") == 0) + desired_team = CTF_TEAM2; + else { + gi.cprintf(ent, PRINT_HIGH, "Unknown team %s.\n", t); + return; + } + + if (ent->client->resp.ctf_team == desired_team) { + gi.cprintf(ent, PRINT_HIGH, "You are already on the %s team.\n", + CTFTeamName(ent->client->resp.ctf_team)); + return; + } + +//// + ent->svflags = 0; + ent->flags &= ~FL_GODMODE; + ent->client->resp.ctf_team = desired_team; + ent->client->resp.ctf_state = 0; + s = Info_ValueForKey (ent->client->pers.userinfo, "skin"); + CTFAssignSkin(ent, s); + + if (ent->solid == SOLID_NOT) { // spectator + PutClientInServer (ent); + // add a teleportation effect + ent->s.event = EV_PLAYER_TELEPORT; + // hold in place briefly + ent->client->ps.pmove.pm_flags = PMF_TIME_TELEPORT; + ent->client->ps.pmove.pm_time = 14; + gi.bprintf(PRINT_HIGH, "%s joined the %s team.\n", + ent->client->pers.netname, CTFTeamName(desired_team)); + return; + } + + ent->health = 0; + player_die (ent, ent, ent, 100000, vec3_origin); + // don't even bother waiting for death frames + ent->deadflag = DEAD_DEAD; + respawn (ent); + + ent->client->resp.score = 0; + + gi.bprintf(PRINT_HIGH, "%s changed to the %s team.\n", + ent->client->pers.netname, CTFTeamName(desired_team)); +} + +/* +================== +CTFScoreboardMessage +================== +*/ +void CTFScoreboardMessage (edict_t *ent, edict_t *killer) +{ + char entry[1024]; + char string[1400]; + int len; + int i, j, k, n; + int sorted[2][MAX_CLIENTS]; + int sortedscores[2][MAX_CLIENTS]; + int score, total[2], totalscore[2]; + int last[2]; + gclient_t *cl; + edict_t *cl_ent; + int team; + int maxsize = 1000; + + // sort the clients by team and score + total[0] = total[1] = 0; + last[0] = last[1] = 0; + totalscore[0] = totalscore[1] = 0; + for (i=0 ; iinuse) + continue; + if (game.clients[i].resp.ctf_team == CTF_TEAM1) + team = 0; + else if (game.clients[i].resp.ctf_team == CTF_TEAM2) + team = 1; + else + continue; // unknown team? + + score = game.clients[i].resp.score; + for (j=0 ; j sortedscores[team][j]) + break; + } + for (k=total[team] ; k>j ; k--) + { + sorted[team][k] = sorted[team][k-1]; + sortedscores[team][k] = sortedscores[team][k-1]; + } + sorted[team][j] = i; + sortedscores[team][j] = score; + totalscore[team] += score; + total[team]++; + } + + // print level name and exit rules + // add the clients in sorted order + *string = 0; + len = 0; + + // team one + sprintf(string, "if 24 xv 8 yv 8 pic 24 endif " + "xv 40 yv 28 string \"%4d/%-3d\" " + "xv 98 yv 12 num 2 18 " + "if 25 xv 168 yv 8 pic 25 endif " + "xv 200 yv 28 string \"%4d/%-3d\" " + "xv 256 yv 12 num 2 20 ", + totalscore[0], total[0], + totalscore[1], total[1]); + len = strlen(string); + + for (i=0 ; i<16 ; i++) + { + if (i >= total[0] && i >= total[1]) + break; // we're done + +#if 0 //ndef NEW_SCORE + // set up y + sprintf(entry, "yv %d ", 42 + i * 8); + if (maxsize - len > strlen(entry)) { + strcat(string, entry); + len = strlen(string); + } +#else + *entry = 0; +#endif + + // left side + if (i < total[0]) { + cl = &game.clients[sorted[0][i]]; + cl_ent = g_edicts + 1 + sorted[0][i]; + +#if 0 //ndef NEW_SCORE + sprintf(entry+strlen(entry), + "xv 0 %s \"%3d %3d %-12.12s\" ", + (cl_ent == ent) ? "string2" : "string", + cl->resp.score, + (cl->ping > 999) ? 999 : cl->ping, + cl->pers.netname); + + if (cl_ent->client->pers.inventory[ITEM_INDEX(flag2_item)]) + strcat(entry, "xv 56 picn sbfctf2 "); +#else + sprintf(entry+strlen(entry), + "ctf 0 %d %d %d %d ", + 42 + i * 8, + sorted[0][i], + cl->resp.score, + cl->ping > 999 ? 999 : cl->ping); + + if (cl_ent->client->pers.inventory[ITEM_INDEX(flag2_item)]) + sprintf(entry + strlen(entry), "xv 56 yv %d picn sbfctf2 ", + 42 + i * 8); +#endif + + if (maxsize - len > strlen(entry)) { + strcat(string, entry); + len = strlen(string); + last[0] = i; + } + } + + // right side + if (i < total[1]) { + cl = &game.clients[sorted[1][i]]; + cl_ent = g_edicts + 1 + sorted[1][i]; + +#if 0 //ndef NEW_SCORE + sprintf(entry+strlen(entry), + "xv 160 %s \"%3d %3d %-12.12s\" ", + (cl_ent == ent) ? "string2" : "string", + cl->resp.score, + (cl->ping > 999) ? 999 : cl->ping, + cl->pers.netname); + + if (cl_ent->client->pers.inventory[ITEM_INDEX(flag1_item)]) + strcat(entry, "xv 216 picn sbfctf1 "); + +#else + + sprintf(entry+strlen(entry), + "ctf 160 %d %d %d %d ", + 42 + i * 8, + sorted[1][i], + cl->resp.score, + cl->ping > 999 ? 999 : cl->ping); + + if (cl_ent->client->pers.inventory[ITEM_INDEX(flag1_item)]) + sprintf(entry + strlen(entry), "xv 216 yv %d picn sbfctf1 ", + 42 + i * 8); +#endif + if (maxsize - len > strlen(entry)) { + strcat(string, entry); + len = strlen(string); + last[1] = i; + } + } + } + + // put in spectators if we have enough room + if (last[0] > last[1]) + j = last[0]; + else + j = last[1]; + j = (j + 2) * 8 + 42; + + k = n = 0; + if (maxsize - len > 50) { + for (i = 0; i < maxclients->value; i++) { + cl_ent = g_edicts + 1 + i; + cl = &game.clients[i]; + if (!cl_ent->inuse || + cl_ent->solid != SOLID_NOT || + cl_ent->client->resp.ctf_team != CTF_NOTEAM) + continue; + + if (!k) { + k = 1; + sprintf(entry, "xv 0 yv %d string2 \"Spectators\" ", j); + strcat(string, entry); + len = strlen(string); + j += 8; + } + + sprintf(entry+strlen(entry), + "ctf %d %d %d %d %d ", + (n & 1) ? 160 : 0, // x + j, // y + i, // playernum + cl->resp.score, + cl->ping > 999 ? 999 : cl->ping); + if (maxsize - len > strlen(entry)) { + strcat(string, entry); + len = strlen(string); + } + + if (n & 1) + j += 8; + n++; + } + } + + if (total[0] - last[0] > 1) // couldn't fit everyone + sprintf(string + strlen(string), "xv 8 yv %d string \"..and %d more\" ", + 42 + (last[0]+1)*8, total[0] - last[0] - 1); + if (total[1] - last[1] > 1) // couldn't fit everyone + sprintf(string + strlen(string), "xv 168 yv %d string \"..and %d more\" ", + 42 + (last[1]+1)*8, total[1] - last[1] - 1); + + gi.WriteByte (svc_layout); + gi.WriteString (string); +} + +/*------------------------------------------------------------------------*/ +/* TECH */ +/*------------------------------------------------------------------------*/ + +void CTFHasTech(edict_t *who) +{ + if (level.time - who->client->ctf_lasttechmsg > 2) { + gi.centerprintf(who, "You already have a TECH powerup."); + who->client->ctf_lasttechmsg = level.time; + } +} + +gitem_t *CTFWhat_Tech(edict_t *ent) +{ + gitem_t *tech; + int i; + + i = 0; + while (tnames[i]) { + if ((tech = FindItemByClassname(tnames[i])) != NULL && + ent->client->pers.inventory[ITEM_INDEX(tech)]) { + return tech; + } + i++; + } + return NULL; +} + +qboolean CTFPickup_Tech (edict_t *ent, edict_t *other) +{ + gitem_t *tech; + int i; + + i = 0; + while (tnames[i]) { + if ((tech = FindItemByClassname(tnames[i])) != NULL && + other->client->pers.inventory[ITEM_INDEX(tech)]) { + CTFHasTech(other); + return false; // has this one + } + i++; + } + + // client only gets one tech + other->client->pers.inventory[ITEM_INDEX(ent->item)]++; + other->client->ctf_regentime = level.time; + return true; +} + +static void SpawnTech(gitem_t *item, edict_t *spot); + +static edict_t *FindTechSpawn(void) +{ + edict_t *spot = NULL; + int i = rand() % 16; + + while (i--) + spot = G_Find (spot, FOFS(classname), "info_player_deathmatch"); + if (!spot) + spot = G_Find (spot, FOFS(classname), "info_player_deathmatch"); + return spot; +} + +static void TechThink(edict_t *tech) +{ + edict_t *spot; + + if ((spot = FindTechSpawn()) != NULL) { + SpawnTech(tech->item, spot); + G_FreeEdict(tech); + } else { + tech->nextthink = level.time + CTF_TECH_TIMEOUT; + tech->think = TechThink; + } +} + +void CTFDrop_Tech(edict_t *ent, gitem_t *item) +{ + edict_t *tech; + + tech = Drop_Item(ent, item); + tech->nextthink = level.time + CTF_TECH_TIMEOUT; + tech->think = TechThink; + ent->client->pers.inventory[ITEM_INDEX(item)] = 0; +} + +void CTFDeadDropTech(edict_t *ent) +{ + gitem_t *tech; + edict_t *dropped; + int i; + + i = 0; + while (tnames[i]) { + if ((tech = FindItemByClassname(tnames[i])) != NULL && + ent->client->pers.inventory[ITEM_INDEX(tech)]) { + dropped = Drop_Item(ent, tech); + // hack the velocity to make it bounce random + dropped->velocity[0] = (rand() % 600) - 300; + dropped->velocity[1] = (rand() % 600) - 300; + dropped->nextthink = level.time + CTF_TECH_TIMEOUT; + dropped->think = TechThink; + dropped->owner = NULL; + ent->client->pers.inventory[ITEM_INDEX(tech)] = 0; + } + i++; + } +} + +static void SpawnTech(gitem_t *item, edict_t *spot) +{ + edict_t *ent; + vec3_t forward, right; + vec3_t angles; + + ent = G_Spawn(); + + ent->classname = item->classname; + ent->item = item; + ent->spawnflags = DROPPED_ITEM; + ent->s.effects = item->world_model_flags; + ent->s.renderfx = RF_GLOW; + VectorSet (ent->mins, -15, -15, -15); + VectorSet (ent->maxs, 15, 15, 15); + gi.setmodel (ent, ent->item->world_model); + ent->solid = SOLID_TRIGGER; + ent->movetype = MOVETYPE_TOSS; + ent->touch = Touch_Item; + ent->owner = ent; + + angles[0] = 0; + angles[1] = rand() % 360; + angles[2] = 0; + + AngleVectors (angles, forward, right, NULL); + VectorCopy (spot->s.origin, ent->s.origin); + ent->s.origin[2] += 16; + VectorScale (forward, 100, ent->velocity); + ent->velocity[2] = 300; + + ent->nextthink = level.time + CTF_TECH_TIMEOUT; + ent->think = TechThink; + + gi.linkentity (ent); +} + +static void SpawnTechs(edict_t *ent) +{ + gitem_t *tech; + edict_t *spot; + int i; + + i = 0; + while (tnames[i]) { + if ((tech = FindItemByClassname(tnames[i])) != NULL && + (spot = FindTechSpawn()) != NULL) + SpawnTech(tech, spot); + i++; + } + if (ent) + G_FreeEdict(ent); +} + +// frees the passed edict! +void CTFRespawnTech(edict_t *ent) +{ + edict_t *spot; + + if ((spot = FindTechSpawn()) != NULL) + SpawnTech(ent->item, spot); + G_FreeEdict(ent); +} + +void CTFSetupTechSpawn(void) +{ + edict_t *ent; + + if (((int)dmflags->value & DF_CTF_NO_TECH)) + return; + + ent = G_Spawn(); + ent->nextthink = level.time + 2; + ent->think = SpawnTechs; +} + +void CTFResetTech(void) +{ + edict_t *ent; + int i; + + for (ent = g_edicts + 1, i = 1; i < globals.num_edicts; i++, ent++) { + if (ent->inuse) + if (ent->item && (ent->item->flags & IT_TECH)) + G_FreeEdict(ent); + } + SpawnTechs(NULL); +} + +int CTFApplyResistance(edict_t *ent, int dmg) +{ + static gitem_t *tech = NULL; + float volume = 1.0; + + if (ent->client && ent->client->silencer_shots) + volume = 0.2; + + if (!tech) + tech = FindItemByClassname("item_tech1"); + if (dmg && tech && ent->client && ent->client->pers.inventory[ITEM_INDEX(tech)]) { + // make noise + gi.sound(ent, CHAN_VOICE, gi.soundindex("ctf/tech1.wav"), volume, ATTN_NORM, 0); + return dmg / 2; + } + return dmg; +} + +int CTFApplyStrength(edict_t *ent, int dmg) +{ + static gitem_t *tech = NULL; + + if (!tech) + tech = FindItemByClassname("item_tech2"); + if (dmg && tech && ent->client && ent->client->pers.inventory[ITEM_INDEX(tech)]) { + return dmg * 2; + } + return dmg; +} + +qboolean CTFApplyStrengthSound(edict_t *ent) +{ + static gitem_t *tech = NULL; + float volume = 1.0; + + if (ent->client && ent->client->silencer_shots) + volume = 0.2; + + if (!tech) + tech = FindItemByClassname("item_tech2"); + if (tech && ent->client && + ent->client->pers.inventory[ITEM_INDEX(tech)]) { + if (ent->client->ctf_techsndtime < level.time) { + ent->client->ctf_techsndtime = level.time + 1; + if (ent->client->quad_framenum > level.framenum) + gi.sound(ent, CHAN_VOICE, gi.soundindex("ctf/tech2x.wav"), volume, ATTN_NORM, 0); + else + gi.sound(ent, CHAN_VOICE, gi.soundindex("ctf/tech2.wav"), volume, ATTN_NORM, 0); + } + return true; + } + return false; +} + + +qboolean CTFApplyHaste(edict_t *ent) +{ + static gitem_t *tech = NULL; + + if (!tech) + tech = FindItemByClassname("item_tech3"); + if (tech && ent->client && + ent->client->pers.inventory[ITEM_INDEX(tech)]) + return true; + return false; +} + +void CTFApplyHasteSound(edict_t *ent) +{ + static gitem_t *tech = NULL; + float volume = 1.0; + + if (ent->client && ent->client->silencer_shots) + volume = 0.2; + + if (!tech) + tech = FindItemByClassname("item_tech3"); + if (tech && ent->client && + ent->client->pers.inventory[ITEM_INDEX(tech)] && + ent->client->ctf_techsndtime < level.time) { + ent->client->ctf_techsndtime = level.time + 1; + gi.sound(ent, CHAN_VOICE, gi.soundindex("ctf/tech3.wav"), volume, ATTN_NORM, 0); + } +} + +void CTFApplyRegeneration(edict_t *ent) +{ + static gitem_t *tech = NULL; + qboolean noise = false; + gclient_t *client; + int index; + float volume = 1.0; + + client = ent->client; + if (!client) + return; + + if (ent->client->silencer_shots) + volume = 0.2; + + if (!tech) + tech = FindItemByClassname("item_tech4"); + if (tech && client->pers.inventory[ITEM_INDEX(tech)]) { + if (client->ctf_regentime < level.time) { + client->ctf_regentime = level.time; + if (ent->health < 150) { + ent->health += 5; + if (ent->health > 150) + ent->health = 150; + client->ctf_regentime += 0.5; + noise = true; + } + index = ArmorIndex (ent); + if (index && client->pers.inventory[index] < 150) { + client->pers.inventory[index] += 5; + if (client->pers.inventory[index] > 150) + client->pers.inventory[index] = 150; + client->ctf_regentime += 0.5; + noise = true; + } + } + if (noise && ent->client->ctf_techsndtime < level.time) { + ent->client->ctf_techsndtime = level.time + 1; + gi.sound(ent, CHAN_VOICE, gi.soundindex("ctf/tech4.wav"), volume, ATTN_NORM, 0); + } + } +} + +qboolean CTFHasRegeneration(edict_t *ent) +{ + static gitem_t *tech = NULL; + + if (!tech) + tech = FindItemByClassname("item_tech4"); + if (tech && ent->client && + ent->client->pers.inventory[ITEM_INDEX(tech)]) + return true; + return false; +} + +/* +====================================================================== + +SAY_TEAM + +====================================================================== +*/ + +// This array is in 'importance order', it indicates what items are +// more important when reporting their names. +struct { + char *classname; + int priority; +} loc_names[] = +{ + { "item_flag_team1", 1 }, + { "item_flag_team2", 1 }, + { "item_quad", 2 }, + { "item_invulnerability", 2 }, + { "weapon_bfg", 3 }, + { "weapon_railgun", 4 }, + { "weapon_rocketlauncher", 4 }, + { "weapon_hyperblaster", 4 }, + { "weapon_chaingun", 4 }, + { "weapon_grenadelauncher", 4 }, + { "weapon_machinegun", 4 }, + { "weapon_supershotgun", 4 }, + { "weapon_shotgun", 4 }, + { "item_power_screen", 5 }, + { "item_power_shield", 5 }, + { "item_armor_body", 6 }, + { "item_armor_combat", 6 }, + { "item_armor_jacket", 6 }, + { "item_silencer", 7 }, + { "item_breather", 7 }, + { "item_enviro", 7 }, + { "item_adrenaline", 7 }, + { "item_bandolier", 8 }, + { "item_pack", 8 }, + { NULL, 0 } +}; + + +static void CTFSay_Team_Location(edict_t *who, char *buf) +{ + edict_t *what = NULL; + edict_t *hot = NULL; + float hotdist = 999999, newdist; + vec3_t v; + int hotindex = 999; + int i; + gitem_t *item; + int nearteam = -1; + edict_t *flag1, *flag2; + qboolean hotsee = false; + qboolean cansee; + + while ((what = loc_findradius(what, who->s.origin, 1024)) != NULL) { + // find what in loc_classnames + for (i = 0; loc_names[i].classname; i++) + if (strcmp(what->classname, loc_names[i].classname) == 0) + break; + if (!loc_names[i].classname) + continue; + // something we can see get priority over something we can't + cansee = loc_CanSee(what, who); + if (cansee && !hotsee) { + hotsee = true; + hotindex = loc_names[i].priority; + hot = what; + VectorSubtract(what->s.origin, who->s.origin, v); + hotdist = VectorLength(v); + continue; + } + // if we can't see this, but we have something we can see, skip it + if (hotsee && !cansee) + continue; + if (hotsee && hotindex < loc_names[i].priority) + continue; + VectorSubtract(what->s.origin, who->s.origin, v); + newdist = VectorLength(v); + if (newdist < hotdist || + (cansee && loc_names[i].priority < hotindex)) { + hot = what; + hotdist = newdist; + hotindex = i; + hotsee = loc_CanSee(hot, who); + } + } + + if (!hot) { + strcpy(buf, "nowhere"); + return; + } + + // we now have the closest item + // see if there's more than one in the map, if so + // we need to determine what team is closest + what = NULL; + while ((what = G_Find(what, FOFS(classname), hot->classname)) != NULL) { + if (what == hot) + continue; + // if we are here, there is more than one, find out if hot + // is closer to red flag or blue flag + if ((flag1 = G_Find(NULL, FOFS(classname), "item_flag_team1")) != NULL && + (flag2 = G_Find(NULL, FOFS(classname), "item_flag_team2")) != NULL) { + VectorSubtract(hot->s.origin, flag1->s.origin, v); + hotdist = VectorLength(v); + VectorSubtract(hot->s.origin, flag2->s.origin, v); + newdist = VectorLength(v); + if (hotdist < newdist) + nearteam = CTF_TEAM1; + else if (hotdist > newdist) + nearteam = CTF_TEAM2; + } + break; + } + + if ((item = FindItemByClassname(hot->classname)) == NULL) { + strcpy(buf, "nowhere"); + return; + } + + // in water? + if (who->waterlevel) + strcpy(buf, "in the water "); + else + *buf = 0; + + // near or above + VectorSubtract(who->s.origin, hot->s.origin, v); + if (fabs(v[2]) > fabs(v[0]) && fabs(v[2]) > fabs(v[1])) + if (v[2] > 0) + strcat(buf, "above "); + else + strcat(buf, "below "); + else + strcat(buf, "near "); + + if (nearteam == CTF_TEAM1) + strcat(buf, "the red "); + else if (nearteam == CTF_TEAM2) + strcat(buf, "the blue "); + else + strcat(buf, "the "); + + strcat(buf, item->pickup_name); +} + +static void CTFSay_Team_Armor(edict_t *who, char *buf) +{ + gitem_t *item; + int index, cells; + int power_armor_type; + + *buf = 0; + + power_armor_type = PowerArmorType (who); + if (power_armor_type) + { + cells = who->client->pers.inventory[ITEM_INDEX(FindItem ("cells"))]; + if (cells) + sprintf(buf+strlen(buf), "%s with %i cells ", + (power_armor_type == POWER_ARMOR_SCREEN) ? + "Power Screen" : "Power Shield", cells); + } + + index = ArmorIndex (who); + if (index) + { + item = GetItemByIndex (index); + if (item) { + if (*buf) + strcat(buf, "and "); + sprintf(buf+strlen(buf), "%i units of %s", + who->client->pers.inventory[index], item->pickup_name); + } + } + + if (!*buf) + strcpy(buf, "no armor"); +} + +static void CTFSay_Team_Health(edict_t *who, char *buf) +{ + if (who->health <= 0) + strcpy(buf, "dead"); + else + sprintf(buf, "%i health", who->health); +} + +static void CTFSay_Team_Tech(edict_t *who, char *buf) +{ + gitem_t *tech; + int i; + + // see if the player has a tech powerup + i = 0; + while (tnames[i]) { + if ((tech = FindItemByClassname(tnames[i])) != NULL && + who->client->pers.inventory[ITEM_INDEX(tech)]) { + sprintf(buf, "the %s", tech->pickup_name); + return; + } + i++; + } + strcpy(buf, "no powerup"); +} + +static void CTFSay_Team_Weapon(edict_t *who, char *buf) +{ + if (who->client->pers.weapon) + strcpy(buf, who->client->pers.weapon->pickup_name); + else + strcpy(buf, "none"); +} + +static void CTFSay_Team_Sight(edict_t *who, char *buf) +{ + int i; + edict_t *targ; + int n = 0; + char s[1024]; + char s2[1024]; + + *s = *s2 = 0; + for (i = 1; i <= maxclients->value; i++) { + targ = g_edicts + i; + if (!targ->inuse || + targ == who || + !loc_CanSee(targ, who)) + continue; + if (*s2) { + if (strlen(s) + strlen(s2) + 3 < sizeof(s)) { + if (n) + strcat(s, ", "); + strcat(s, s2); + *s2 = 0; + } + n++; + } + strcpy(s2, targ->client->pers.netname); + } + if (*s2) { + if (strlen(s) + strlen(s2) + 6 < sizeof(s)) { + if (n) + strcat(s, " and "); + strcat(s, s2); + } + strcpy(buf, s); + } else + strcpy(buf, "no one"); +} + +void CTFSay_Team(edict_t *who, char *msg) +{ + char outmsg[256]; + char buf[256]; + int i; + char *p; + edict_t *cl_ent; + + if (CheckFlood(who)) + return; + + outmsg[0] = 0; + + if (*msg == '\"') { + msg[strlen(msg) - 1] = 0; + msg++; + } + + for (p = outmsg; *msg && (p - outmsg) < sizeof(outmsg) - 2; msg++) { + if (*msg == '%') { + switch (*++msg) { + case 'l' : + case 'L' : + CTFSay_Team_Location(who, buf); + if (strlen(buf) + (p - outmsg) < sizeof(outmsg) - 2) { + strcpy(p, buf); + p += strlen(buf); + } + break; + case 'a' : + case 'A' : + CTFSay_Team_Armor(who, buf); + if (strlen(buf) + (p - outmsg) < sizeof(outmsg) - 2) { + strcpy(p, buf); + p += strlen(buf); + } + break; + case 'h' : + case 'H' : + CTFSay_Team_Health(who, buf); + if (strlen(buf) + (p - outmsg) < sizeof(outmsg) - 2) { + strcpy(p, buf); + p += strlen(buf); + } + break; + case 't' : + case 'T' : + CTFSay_Team_Tech(who, buf); + if (strlen(buf) + (p - outmsg) < sizeof(outmsg) - 2) { + strcpy(p, buf); + p += strlen(buf); + } + break; + case 'w' : + case 'W' : + CTFSay_Team_Weapon(who, buf); + if (strlen(buf) + (p - outmsg) < sizeof(outmsg) - 2) { + strcpy(p, buf); + p += strlen(buf); + } + break; + + case 'n' : + case 'N' : + CTFSay_Team_Sight(who, buf); + if (strlen(buf) + (p - outmsg) < sizeof(outmsg) - 2) { + strcpy(p, buf); + p += strlen(buf); + } + break; + + default : + *p++ = *msg; + } + } else + *p++ = *msg; + } + *p = 0; + + for (i = 0; i < maxclients->value; i++) { + cl_ent = g_edicts + 1 + i; + if (!cl_ent->inuse) + continue; + if (cl_ent->client->resp.ctf_team == who->client->resp.ctf_team) + gi.cprintf(cl_ent, PRINT_CHAT, "(%s): %s\n", + who->client->pers.netname, outmsg); + } +} + +/*-----------------------------------------------------------------------*/ +/*QUAKED misc_ctf_banner (1 .5 0) (-4 -64 0) (4 64 248) TEAM2 +The origin is the bottom of the banner. +The banner is 248 tall. +*/ +static void misc_ctf_banner_think (edict_t *ent) +{ + ent->s.frame = (ent->s.frame + 1) % 16; + ent->nextthink = level.time + FRAMETIME; +} + +void SP_misc_ctf_banner (edict_t *ent) +{ + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_NOT; + ent->s.modelindex = gi.modelindex ("models/ctf/banner/tris.md2"); + if (ent->spawnflags & 1) // team2 + ent->s.skinnum = 1; + + ent->s.frame = rand() % 16; + gi.linkentity (ent); + + ent->think = misc_ctf_banner_think; + ent->nextthink = level.time + FRAMETIME; +} + +/*QUAKED misc_ctf_small_banner (1 .5 0) (-4 -32 0) (4 32 124) TEAM2 +The origin is the bottom of the banner. +The banner is 124 tall. +*/ +void SP_misc_ctf_small_banner (edict_t *ent) +{ + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_NOT; + ent->s.modelindex = gi.modelindex ("models/ctf/banner/small.md2"); + if (ent->spawnflags & 1) // team2 + ent->s.skinnum = 1; + + ent->s.frame = rand() % 16; + gi.linkentity (ent); + + ent->think = misc_ctf_banner_think; + ent->nextthink = level.time + FRAMETIME; +} + +/*-----------------------------------------------------------------------*/ + +static void SetLevelName(pmenu_t *p) +{ + static char levelname[33]; + + levelname[0] = '*'; + if (g_edicts[0].message) + strncpy(levelname+1, g_edicts[0].message, sizeof(levelname) - 2); + else + strncpy(levelname+1, level.mapname, sizeof(levelname) - 2); + levelname[sizeof(levelname) - 1] = 0; + p->text = levelname; +} + + +/*-----------------------------------------------------------------------*/ + + +/* ELECTIONS */ + +qboolean CTFBeginElection(edict_t *ent, elect_t type, char *msg) +{ + int i; + int count; + edict_t *e; + + if (electpercentage->value == 0) { + gi.cprintf(ent, PRINT_HIGH, "Elections are disabled, only an admin can process this action.\n"); + return false; + } + + + if (ctfgame.election != ELECT_NONE) { + gi.cprintf(ent, PRINT_HIGH, "Election already in progress.\n"); + return false; + } + + // clear votes + count = 0; + for (i = 1; i <= maxclients->value; i++) { + e = g_edicts + i; + e->client->resp.voted = false; + if (e->inuse) + count++; + } + + if (count < 2) { + gi.cprintf(ent, PRINT_HIGH, "Not enough players for election.\n"); + return false; + } + + ctfgame.etarget = ent; + ctfgame.election = type; + ctfgame.evotes = 0; + ctfgame.needvotes = (count * electpercentage->value) / 100; + ctfgame.electtime = level.time + 20; // twenty seconds for election + strncpy(ctfgame.emsg, msg, sizeof(ctfgame.emsg) - 1); + + // tell everyone + gi.bprintf(PRINT_CHAT, "%s\n", ctfgame.emsg); + gi.bprintf(PRINT_HIGH, "Type YES or NO to vote on this request.\n"); + gi.bprintf(PRINT_HIGH, "Votes: %d Needed: %d Time left: %ds\n", ctfgame.evotes, ctfgame.needvotes, + (int)(ctfgame.electtime - level.time)); + + return true; +} + +void DoRespawn (edict_t *ent); + +void CTFResetAllPlayers(void) +{ + int i; + edict_t *ent; + + for (i = 1; i <= maxclients->value; i++) { + ent = g_edicts + i; + if (!ent->inuse) + continue; + + if (ent->client->menu) + PMenu_Close(ent); + + CTFPlayerResetGrapple(ent); + CTFDeadDropFlag(ent); + CTFDeadDropTech(ent); + + ent->client->resp.ctf_team = CTF_NOTEAM; + ent->client->resp.ready = false; + + ent->svflags = 0; + ent->flags &= ~FL_GODMODE; + PutClientInServer(ent); + } + + // reset the level + CTFResetTech(); + CTFResetFlags(); + + for (ent = g_edicts + 1, i = 1; i < globals.num_edicts; i++, ent++) { + if (ent->inuse && !ent->client) { + if (ent->solid == SOLID_NOT && ent->think == DoRespawn && + ent->nextthink >= level.time) { + ent->nextthink = 0; + DoRespawn(ent); + } + } + } + if (ctfgame.match == MATCH_SETUP) + ctfgame.matchtime = level.time + matchsetuptime->value * 60; +} + +void CTFAssignGhost(edict_t *ent) +{ + int ghost, i; + + for (ghost = 0; ghost < MAX_CLIENTS; ghost++) + if (!ctfgame.ghosts[ghost].code) + break; + if (ghost == MAX_CLIENTS) + return; + ctfgame.ghosts[ghost].team = ent->client->resp.ctf_team; + ctfgame.ghosts[ghost].score = 0; + for (;;) { + ctfgame.ghosts[ghost].code = 10000 + (rand() % 90000); + for (i = 0; i < MAX_CLIENTS; i++) + if (i != ghost && ctfgame.ghosts[i].code == ctfgame.ghosts[ghost].code) + break; + if (i == MAX_CLIENTS) + break; + } + ctfgame.ghosts[ghost].ent = ent; + strcpy(ctfgame.ghosts[ghost].netname, ent->client->pers.netname); + ent->client->resp.ghost = ctfgame.ghosts + ghost; + gi.cprintf(ent, PRINT_CHAT, "Your ghost code is **** %d ****\n", ctfgame.ghosts[ghost].code); + gi.cprintf(ent, PRINT_HIGH, "If you lose connection, you can rejoin with your score " + "intact by typing \"ghost %d\".\n", ctfgame.ghosts[ghost].code); +} + +// start a match +void CTFStartMatch(void) +{ + int i; + edict_t *ent; + int ghost = 0; + + ctfgame.match = MATCH_GAME; + ctfgame.matchtime = level.time + matchtime->value * 60; + ctfgame.countdown = false; + + ctfgame.team1 = ctfgame.team2 = 0; + + memset(ctfgame.ghosts, 0, sizeof(ctfgame.ghosts)); + + for (i = 1; i <= maxclients->value; i++) { + ent = g_edicts + i; + if (!ent->inuse) + continue; + + ent->client->resp.score = 0; + ent->client->resp.ctf_state = 0; + ent->client->resp.ghost = NULL; + + gi.centerprintf(ent, "******************\n\nMATCH HAS STARTED!\n\n******************"); + + if (ent->client->resp.ctf_team != CTF_NOTEAM) { + // make up a ghost code + CTFAssignGhost(ent); + CTFPlayerResetGrapple(ent); + ent->svflags = SVF_NOCLIENT; + ent->flags &= ~FL_GODMODE; + + ent->client->respawn_time = level.time + 1.0 + ((rand()%30)/10.0); + ent->client->ps.pmove.pm_type = PM_DEAD; + ent->client->anim_priority = ANIM_DEATH; + ent->s.frame = FRAME_death308-1; + ent->client->anim_end = FRAME_death308; + ent->deadflag = DEAD_DEAD; + ent->movetype = MOVETYPE_NOCLIP; + ent->client->ps.gunindex = 0; + gi.linkentity (ent); + } + } +} + +void CTFEndMatch(void) +{ + ctfgame.match = MATCH_POST; + gi.bprintf(PRINT_CHAT, "MATCH COMPLETED!\n"); + + CTFCalcScores(); + + gi.bprintf(PRINT_HIGH, "RED TEAM: %d captures, %d points\n", + ctfgame.team1, ctfgame.total1); + gi.bprintf(PRINT_HIGH, "BLUE TEAM: %d captures, %d points\n", + ctfgame.team2, ctfgame.total2); + + if (ctfgame.team1 > ctfgame.team2) + gi.bprintf(PRINT_CHAT, "RED team won over the BLUE team by %d CAPTURES!\n", + ctfgame.team1 - ctfgame.team2); + else if (ctfgame.team2 > ctfgame.team1) + gi.bprintf(PRINT_CHAT, "BLUE team won over the RED team by %d CAPTURES!\n", + ctfgame.team2 - ctfgame.team1); + else if (ctfgame.total1 > ctfgame.total2) // frag tie breaker + gi.bprintf(PRINT_CHAT, "RED team won over the BLUE team by %d POINTS!\n", + ctfgame.total1 - ctfgame.total2); + else if (ctfgame.total2 > ctfgame.total1) + gi.bprintf(PRINT_CHAT, "BLUE team won over the RED team by %d POINTS!\n", + ctfgame.total2 - ctfgame.total1); + else + gi.bprintf(PRINT_CHAT, "TIE GAME!\n"); + + EndDMLevel(); +} + +qboolean CTFNextMap(void) +{ + if (ctfgame.match == MATCH_POST) { + ctfgame.match = MATCH_SETUP; + CTFResetAllPlayers(); + return true; + } + return false; +} + +void CTFWinElection(void) +{ + switch (ctfgame.election) { + case ELECT_MATCH : + // reset into match mode + if (competition->value < 3) + gi.cvar_set("competition", "2"); + ctfgame.match = MATCH_SETUP; + CTFResetAllPlayers(); + break; + + case ELECT_ADMIN : + ctfgame.etarget->client->resp.admin = true; + gi.bprintf(PRINT_HIGH, "%s has become an admin.\n", ctfgame.etarget->client->pers.netname); + gi.cprintf(ctfgame.etarget, PRINT_HIGH, "Type 'admin' to access the adminstration menu.\n"); + break; + + case ELECT_MAP : + gi.bprintf(PRINT_HIGH, "%s is warping to level %s.\n", + ctfgame.etarget->client->pers.netname, ctfgame.elevel); + strncpy(level.forcemap, ctfgame.elevel, sizeof(level.forcemap) - 1); + EndDMLevel(); + break; + } + ctfgame.election = ELECT_NONE; +} + +void CTFVoteYes(edict_t *ent) +{ + if (ctfgame.election == ELECT_NONE) { + gi.cprintf(ent, PRINT_HIGH, "No election is in progress.\n"); + return; + } + if (ent->client->resp.voted) { + gi.cprintf(ent, PRINT_HIGH, "You already voted.\n"); + return; + } + if (ctfgame.etarget == ent) { + gi.cprintf(ent, PRINT_HIGH, "You can't vote for yourself.\n"); + return; + } + + ent->client->resp.voted = true; + + ctfgame.evotes++; + if (ctfgame.evotes == ctfgame.needvotes) { + // the election has been won + CTFWinElection(); + return; + } + gi.bprintf(PRINT_HIGH, "%s\n", ctfgame.emsg); + gi.bprintf(PRINT_CHAT, "Votes: %d Needed: %d Time left: %ds\n", ctfgame.evotes, ctfgame.needvotes, + (int)(ctfgame.electtime - level.time)); +} + +void CTFVoteNo(edict_t *ent) +{ + if (ctfgame.election == ELECT_NONE) { + gi.cprintf(ent, PRINT_HIGH, "No election is in progress.\n"); + return; + } + if (ent->client->resp.voted) { + gi.cprintf(ent, PRINT_HIGH, "You already voted.\n"); + return; + } + if (ctfgame.etarget == ent) { + gi.cprintf(ent, PRINT_HIGH, "You can't vote for yourself.\n"); + return; + } + + ent->client->resp.voted = true; + + gi.bprintf(PRINT_HIGH, "%s\n", ctfgame.emsg); + gi.bprintf(PRINT_CHAT, "Votes: %d Needed: %d Time left: %ds\n", ctfgame.evotes, ctfgame.needvotes, + (int)(ctfgame.electtime - level.time)); +} + +void CTFReady(edict_t *ent) +{ + int i, j; + edict_t *e; + int t1, t2; + + if (ent->client->resp.ctf_team == CTF_NOTEAM) { + gi.cprintf(ent, PRINT_HIGH, "Pick a team first (hit for menu)\n"); + return; + } + + if (ctfgame.match != MATCH_SETUP) { + gi.cprintf(ent, PRINT_HIGH, "A match is not being setup.\n"); + return; + } + + if (ent->client->resp.ready) { + gi.cprintf(ent, PRINT_HIGH, "You have already commited.\n"); + return; + } + + ent->client->resp.ready = true; + gi.bprintf(PRINT_HIGH, "%s is ready.\n", ent->client->pers.netname); + + t1 = t2 = 0; + for (j = 0, i = 1; i <= maxclients->value; i++) { + e = g_edicts + i; + if (!e->inuse) + continue; + if (e->client->resp.ctf_team != CTF_NOTEAM && !e->client->resp.ready) + j++; + if (e->client->resp.ctf_team == CTF_TEAM1) + t1++; + else if (e->client->resp.ctf_team == CTF_TEAM2) + t2++; + } + if (!j && t1 && t2) { + // everyone has commited + gi.bprintf(PRINT_CHAT, "All players have commited. Match starting\n"); + ctfgame.match = MATCH_PREGAME; + ctfgame.matchtime = level.time + matchstarttime->value; + ctfgame.countdown = false; + gi.positioned_sound (world->s.origin, world, CHAN_AUTO | CHAN_RELIABLE, gi.soundindex("misc/talk1.wav"), 1, ATTN_NONE, 0); + } +} + +void CTFNotReady(edict_t *ent) +{ + if (ent->client->resp.ctf_team == CTF_NOTEAM) { + gi.cprintf(ent, PRINT_HIGH, "Pick a team first (hit for menu)\n"); + return; + } + + if (ctfgame.match != MATCH_SETUP && ctfgame.match != MATCH_PREGAME) { + gi.cprintf(ent, PRINT_HIGH, "A match is not being setup.\n"); + return; + } + + if (!ent->client->resp.ready) { + gi.cprintf(ent, PRINT_HIGH, "You haven't commited.\n"); + return; + } + + ent->client->resp.ready = false; + gi.bprintf(PRINT_HIGH, "%s is no longer ready.\n", ent->client->pers.netname); + + if (ctfgame.match == MATCH_PREGAME) { + gi.bprintf(PRINT_CHAT, "Match halted.\n"); + ctfgame.match = MATCH_SETUP; + ctfgame.matchtime = level.time + matchsetuptime->value * 60; + } +} + +void CTFGhost(edict_t *ent) +{ + int i; + int n; + + if (gi.argc() < 2) { + gi.cprintf(ent, PRINT_HIGH, "Usage: ghost \n"); + return; + } + + if (ent->client->resp.ctf_team != CTF_NOTEAM) { + gi.cprintf(ent, PRINT_HIGH, "You are already in the game.\n"); + return; + } + if (ctfgame.match != MATCH_GAME) { + gi.cprintf(ent, PRINT_HIGH, "No match is in progress.\n"); + return; + } + + n = atoi(gi.argv(1)); + + for (i = 0; i < MAX_CLIENTS; i++) { + if (ctfgame.ghosts[i].code && ctfgame.ghosts[i].code == n) { + gi.cprintf(ent, PRINT_HIGH, "Ghost code accepted, your position has been reinstated.\n"); + ctfgame.ghosts[i].ent->client->resp.ghost = NULL; + ent->client->resp.ctf_team = ctfgame.ghosts[i].team; + ent->client->resp.ghost = ctfgame.ghosts + i; + ent->client->resp.score = ctfgame.ghosts[i].score; + ent->client->resp.ctf_state = 0; + ctfgame.ghosts[i].ent = ent; + ent->svflags = 0; + ent->flags &= ~FL_GODMODE; + PutClientInServer(ent); + gi.bprintf(PRINT_HIGH, "%s has been reinstated to %s team.\n", + ent->client->pers.netname, CTFTeamName(ent->client->resp.ctf_team)); + return; + } + } + gi.cprintf(ent, PRINT_HIGH, "Invalid ghost code.\n"); +} + +qboolean CTFMatchSetup(void) +{ + if (ctfgame.match == MATCH_SETUP || ctfgame.match == MATCH_PREGAME) + return true; + return false; +} + +qboolean CTFMatchOn(void) +{ + if (ctfgame.match == MATCH_GAME) + return true; + return false; +} + + +/*-----------------------------------------------------------------------*/ + +void CTFJoinTeam1(edict_t *ent, pmenuhnd_t *p); +void CTFJoinTeam2(edict_t *ent, pmenuhnd_t *p); +void CTFCredits(edict_t *ent, pmenuhnd_t *p); +void CTFReturnToMain(edict_t *ent, pmenuhnd_t *p); +void CTFChaseCam(edict_t *ent, pmenuhnd_t *p); + +pmenu_t creditsmenu[] = { + { "*Quake II", PMENU_ALIGN_CENTER, NULL }, + { "*ThreeWave Capture the Flag", PMENU_ALIGN_CENTER, NULL }, + { NULL, PMENU_ALIGN_CENTER, NULL }, + { "*Programming", PMENU_ALIGN_CENTER, NULL }, + { "Dave 'Zoid' Kirsch", PMENU_ALIGN_CENTER, NULL }, + { "*Level Design", PMENU_ALIGN_CENTER, NULL }, + { "Christian Antkow", PMENU_ALIGN_CENTER, NULL }, + { "Tim Willits", PMENU_ALIGN_CENTER, NULL }, + { "Dave 'Zoid' Kirsch", PMENU_ALIGN_CENTER, NULL }, + { "*Art", PMENU_ALIGN_CENTER, NULL }, + { "Adrian Carmack Paul Steed", PMENU_ALIGN_CENTER, NULL }, + { "Kevin Cloud", PMENU_ALIGN_CENTER, NULL }, + { "*Sound", PMENU_ALIGN_CENTER, NULL }, + { "Tom 'Bjorn' Klok", PMENU_ALIGN_CENTER, NULL }, + { "*Original CTF Art Design", PMENU_ALIGN_CENTER, NULL }, + { "Brian 'Whaleboy' Cozzens", PMENU_ALIGN_CENTER, NULL }, + { NULL, PMENU_ALIGN_CENTER, NULL }, + { "Return to Main Menu", PMENU_ALIGN_LEFT, CTFReturnToMain } +}; + +static const int jmenu_level = 2; +static const int jmenu_match = 3; +static const int jmenu_red = 5; +static const int jmenu_blue = 7; +static const int jmenu_chase = 9; +static const int jmenu_reqmatch = 11; + +pmenu_t joinmenu[] = { + { "*Quake II", PMENU_ALIGN_CENTER, NULL }, + { "*ThreeWave Capture the Flag", PMENU_ALIGN_CENTER, NULL }, + { NULL, PMENU_ALIGN_CENTER, NULL }, + { NULL, PMENU_ALIGN_CENTER, NULL }, + { NULL, PMENU_ALIGN_CENTER, NULL }, + { "Join Red Team", PMENU_ALIGN_LEFT, CTFJoinTeam1 }, + { NULL, PMENU_ALIGN_LEFT, NULL }, + { "Join Blue Team", PMENU_ALIGN_LEFT, CTFJoinTeam2 }, + { NULL, PMENU_ALIGN_LEFT, NULL }, + { "Chase Camera", PMENU_ALIGN_LEFT, CTFChaseCam }, + { "Credits", PMENU_ALIGN_LEFT, CTFCredits }, + { NULL, PMENU_ALIGN_LEFT, NULL }, + { NULL, PMENU_ALIGN_LEFT, NULL }, + { "Use [ and ] to move cursor", PMENU_ALIGN_LEFT, NULL }, + { "ENTER to select", PMENU_ALIGN_LEFT, NULL }, + { "ESC to Exit Menu", PMENU_ALIGN_LEFT, NULL }, + { "(TAB to Return)", PMENU_ALIGN_LEFT, NULL }, + { "v" CTF_STRING_VERSION, PMENU_ALIGN_RIGHT, NULL }, +}; + +pmenu_t nochasemenu[] = { + { "*Quake II", PMENU_ALIGN_CENTER, NULL }, + { "*ThreeWave Capture the Flag", PMENU_ALIGN_CENTER, NULL }, + { NULL, PMENU_ALIGN_CENTER, NULL }, + { NULL, PMENU_ALIGN_CENTER, NULL }, + { "No one to chase", PMENU_ALIGN_LEFT, NULL }, + { NULL, PMENU_ALIGN_CENTER, NULL }, + { "Return to Main Menu", PMENU_ALIGN_LEFT, CTFReturnToMain } +}; + +void CTFJoinTeam(edict_t *ent, int desired_team) +{ + char *s; + + PMenu_Close(ent); + + ent->svflags &= ~SVF_NOCLIENT; + ent->client->resp.ctf_team = desired_team; + ent->client->resp.ctf_state = 0; + s = Info_ValueForKey (ent->client->pers.userinfo, "skin"); + CTFAssignSkin(ent, s); + + // assign a ghost if we are in match mode + if (ctfgame.match == MATCH_GAME) { + if (ent->client->resp.ghost) + ent->client->resp.ghost->code = 0; + ent->client->resp.ghost = NULL; + CTFAssignGhost(ent); + } + + PutClientInServer (ent); + // add a teleportation effect + ent->s.event = EV_PLAYER_TELEPORT; + // hold in place briefly + ent->client->ps.pmove.pm_flags = PMF_TIME_TELEPORT; + ent->client->ps.pmove.pm_time = 14; + gi.bprintf(PRINT_HIGH, "%s joined the %s team.\n", + ent->client->pers.netname, CTFTeamName(desired_team)); + + if (ctfgame.match == MATCH_SETUP) { + gi.centerprintf(ent, "***********************\n" + "Type \"ready\" in console\n" + "to ready up.\n" + "***********************"); + } +} + +void CTFJoinTeam1(edict_t *ent, pmenuhnd_t *p) +{ + CTFJoinTeam(ent, CTF_TEAM1); +} + +void CTFJoinTeam2(edict_t *ent, pmenuhnd_t *p) +{ + CTFJoinTeam(ent, CTF_TEAM2); +} + +void CTFChaseCam(edict_t *ent, pmenuhnd_t *p) +{ + int i; + edict_t *e; + + if (ent->client->chase_target) { + ent->client->chase_target = NULL; + ent->client->ps.pmove.pm_flags &= ~PMF_NO_PREDICTION; + PMenu_Close(ent); + return; + } + + for (i = 1; i <= maxclients->value; i++) { + e = g_edicts + i; + if (e->inuse && e->solid != SOLID_NOT) { + ent->client->chase_target = e; + PMenu_Close(ent); + ent->client->update_chase = true; + return; + } + } + + SetLevelName(nochasemenu + jmenu_level); + + PMenu_Close(ent); + PMenu_Open(ent, nochasemenu, -1, sizeof(nochasemenu) / sizeof(pmenu_t), NULL); +} + +void CTFReturnToMain(edict_t *ent, pmenuhnd_t *p) +{ + PMenu_Close(ent); + CTFOpenJoinMenu(ent); +} + +void CTFRequestMatch(edict_t *ent, pmenuhnd_t *p) +{ + char text[1024]; + + PMenu_Close(ent); + + sprintf(text, "%s has requested to switch to competition mode.", + ent->client->pers.netname); + CTFBeginElection(ent, ELECT_MATCH, text); +} + +void DeathmatchScoreboard (edict_t *ent); + +void CTFShowScores(edict_t *ent, pmenu_t *p) +{ + PMenu_Close(ent); + + ent->client->showscores = true; + ent->client->showinventory = false; + DeathmatchScoreboard (ent); +} + +int CTFUpdateJoinMenu(edict_t *ent) +{ + static char team1players[32]; + static char team2players[32]; + int num1, num2, i; + + if (ctfgame.match >= MATCH_PREGAME && matchlock->value) { + joinmenu[jmenu_red].text = "MATCH IS LOCKED"; + joinmenu[jmenu_red].SelectFunc = NULL; + joinmenu[jmenu_blue].text = " (entry is not permitted)"; + joinmenu[jmenu_blue].SelectFunc = NULL; + } else { + if (ctfgame.match >= MATCH_PREGAME) { + joinmenu[jmenu_red].text = "Join Red MATCH Team"; + joinmenu[jmenu_blue].text = "Join Blue MATCH Team"; + } else { + joinmenu[jmenu_red].text = "Join Red Team"; + joinmenu[jmenu_blue].text = "Join Blue Team"; + } + joinmenu[jmenu_red].SelectFunc = CTFJoinTeam1; + joinmenu[jmenu_blue].SelectFunc = CTFJoinTeam2; + } + + if (ctf_forcejoin->string && *ctf_forcejoin->string) { + if (stricmp(ctf_forcejoin->string, "red") == 0) { + joinmenu[jmenu_blue].text = NULL; + joinmenu[jmenu_blue].SelectFunc = NULL; + } else if (stricmp(ctf_forcejoin->string, "blue") == 0) { + joinmenu[jmenu_red].text = NULL; + joinmenu[jmenu_red].SelectFunc = NULL; + } + } + + if (ent->client->chase_target) + joinmenu[jmenu_chase].text = "Leave Chase Camera"; + else + joinmenu[jmenu_chase].text = "Chase Camera"; + + SetLevelName(joinmenu + jmenu_level); + + num1 = num2 = 0; + for (i = 0; i < maxclients->value; i++) { + if (!g_edicts[i+1].inuse) + continue; + if (game.clients[i].resp.ctf_team == CTF_TEAM1) + num1++; + else if (game.clients[i].resp.ctf_team == CTF_TEAM2) + num2++; + } + + sprintf(team1players, " (%d players)", num1); + sprintf(team2players, " (%d players)", num2); + + switch (ctfgame.match) { + case MATCH_NONE : + joinmenu[jmenu_match].text = NULL; + break; + + case MATCH_SETUP : + joinmenu[jmenu_match].text = "*MATCH SETUP IN PROGRESS"; + break; + + case MATCH_PREGAME : + joinmenu[jmenu_match].text = "*MATCH STARTING"; + break; + + case MATCH_GAME : + joinmenu[jmenu_match].text = "*MATCH IN PROGRESS"; + break; + } + + if (joinmenu[jmenu_red].text) + joinmenu[jmenu_red+1].text = team1players; + else + joinmenu[jmenu_red+1].text = NULL; + if (joinmenu[jmenu_blue].text) + joinmenu[jmenu_blue+1].text = team2players; + else + joinmenu[jmenu_blue+1].text = NULL; + + joinmenu[jmenu_reqmatch].text = NULL; + joinmenu[jmenu_reqmatch].SelectFunc = NULL; + if (competition->value && ctfgame.match < MATCH_SETUP) { + joinmenu[jmenu_reqmatch].text = "Request Match"; + joinmenu[jmenu_reqmatch].SelectFunc = CTFRequestMatch; + } + + if (num1 > num2) + return CTF_TEAM1; + else if (num2 > num1) + return CTF_TEAM2; + return (rand() & 1) ? CTF_TEAM1 : CTF_TEAM2; +} + +void CTFOpenJoinMenu(edict_t *ent) +{ + int team; + + team = CTFUpdateJoinMenu(ent); + if (ent->client->chase_target) + team = 8; + else if (team == CTF_TEAM1) + team = 4; + else + team = 6; + PMenu_Open(ent, joinmenu, team, sizeof(joinmenu) / sizeof(pmenu_t), NULL); +} + +void CTFCredits(edict_t *ent, pmenuhnd_t *p) +{ + PMenu_Close(ent); + PMenu_Open(ent, creditsmenu, -1, sizeof(creditsmenu) / sizeof(pmenu_t), NULL); +} + +qboolean CTFStartClient(edict_t *ent) +{ + if (ent->client->resp.ctf_team != CTF_NOTEAM) + return false; + + if (!((int)dmflags->value & DF_CTF_FORCEJOIN) || ctfgame.match >= MATCH_SETUP) { + // start as 'observer' + ent->movetype = MOVETYPE_NOCLIP; + ent->solid = SOLID_NOT; + ent->svflags |= SVF_NOCLIENT; + ent->client->resp.ctf_team = CTF_NOTEAM; + ent->client->ps.gunindex = 0; + gi.linkentity (ent); + + CTFOpenJoinMenu(ent); + return true; + } + return false; +} + +void CTFObserver(edict_t *ent) +{ + char userinfo[MAX_INFO_STRING]; + + // start as 'observer' + if (ent->movetype == MOVETYPE_NOCLIP) + + CTFPlayerResetGrapple(ent); + CTFDeadDropFlag(ent); + CTFDeadDropTech(ent); + + ent->deadflag = DEAD_NO; + ent->movetype = MOVETYPE_NOCLIP; + ent->solid = SOLID_NOT; + ent->svflags |= SVF_NOCLIENT; + ent->client->resp.ctf_team = CTF_NOTEAM; + ent->client->ps.gunindex = 0; + ent->client->resp.score = 0; + memcpy (userinfo, ent->client->pers.userinfo, sizeof(userinfo)); + InitClientPersistant(ent->client); + ClientUserinfoChanged (ent, userinfo); + gi.linkentity (ent); + CTFOpenJoinMenu(ent); +} + +qboolean CTFInMatch(void) +{ + if (ctfgame.match > MATCH_NONE) + return true; + return false; +} + +qboolean CTFCheckRules(void) +{ + int t; + int i, j; + char text[64]; + edict_t *ent; + + if (ctfgame.election != ELECT_NONE && ctfgame.electtime <= level.time) { + gi.bprintf(PRINT_CHAT, "Election timed out and has been cancelled.\n"); + ctfgame.election = ELECT_NONE; + } + + if (ctfgame.match != MATCH_NONE) { + t = ctfgame.matchtime - level.time; + + // no team warnings in match mode + ctfgame.warnactive = 0; + + if (t <= 0) { // time ended on something + switch (ctfgame.match) { + case MATCH_SETUP : + // go back to normal mode + if (competition->value < 3) { + ctfgame.match = MATCH_NONE; + gi.cvar_set("competition", "1"); + CTFResetAllPlayers(); + } else { + // reset the time + ctfgame.matchtime = level.time + matchsetuptime->value * 60; + } + return false; + + case MATCH_PREGAME : + // match started! + CTFStartMatch(); + gi.positioned_sound (world->s.origin, world, CHAN_AUTO | CHAN_RELIABLE, gi.soundindex("misc/tele_up.wav"), 1, ATTN_NONE, 0); + return false; + + case MATCH_GAME : + // match ended! + CTFEndMatch(); + gi.positioned_sound (world->s.origin, world, CHAN_AUTO | CHAN_RELIABLE, gi.soundindex("misc/bigtele.wav"), 1, ATTN_NONE, 0); + return false; + } + } + + if (t == ctfgame.lasttime) + return false; + + ctfgame.lasttime = t; + + switch (ctfgame.match) { + case MATCH_SETUP : + for (j = 0, i = 1; i <= maxclients->value; i++) { + ent = g_edicts + i; + if (!ent->inuse) + continue; + if (ent->client->resp.ctf_team != CTF_NOTEAM && + !ent->client->resp.ready) + j++; + } + + if (competition->value < 3) + sprintf(text, "%02d:%02d SETUP: %d not ready", + t / 60, t % 60, j); + else + sprintf(text, "SETUP: %d not ready", j); + + gi.configstring (CONFIG_CTF_MATCH, text); + break; + + + case MATCH_PREGAME : + sprintf(text, "%02d:%02d UNTIL START", + t / 60, t % 60); + gi.configstring (CONFIG_CTF_MATCH, text); + + if (t <= 10 && !ctfgame.countdown) { + ctfgame.countdown = true; + gi.positioned_sound (world->s.origin, world, CHAN_AUTO | CHAN_RELIABLE, gi.soundindex("world/10_0.wav"), 1, ATTN_NONE, 0); + } + break; + + case MATCH_GAME: + sprintf(text, "%02d:%02d MATCH", + t / 60, t % 60); + gi.configstring (CONFIG_CTF_MATCH, text); + if (t <= 10 && !ctfgame.countdown) { + ctfgame.countdown = true; + gi.positioned_sound (world->s.origin, world, CHAN_AUTO | CHAN_RELIABLE, gi.soundindex("world/10_0.wav"), 1, ATTN_NONE, 0); + } + break; + } + return false; + + } else { + int team1 = 0, team2 = 0; + + if (level.time == ctfgame.lasttime) + return false; + ctfgame.lasttime = level.time; + // this is only done in non-match (public) mode + + if (warn_unbalanced->value) { + // count up the team totals + for (i = 1; i <= maxclients->value; i++) { + ent = g_edicts + i; + if (!ent->inuse) + continue; + if (ent->client->resp.ctf_team == CTF_TEAM1) + team1++; + else if (ent->client->resp.ctf_team == CTF_TEAM2) + team2++; + } + + if (team1 - team2 >= 2 && team2 >= 2) { + if (ctfgame.warnactive != CTF_TEAM1) { + ctfgame.warnactive = CTF_TEAM1; + gi.configstring (CONFIG_CTF_TEAMINFO, "WARNING: Red has too many players"); + } + } else if (team2 - team1 >= 2 && team1 >= 2) { + if (ctfgame.warnactive != CTF_TEAM2) { + ctfgame.warnactive = CTF_TEAM2; + gi.configstring (CONFIG_CTF_TEAMINFO, "WARNING: Blue has too many players"); + } + } else + ctfgame.warnactive = 0; + } else + ctfgame.warnactive = 0; + + } + + + + if (capturelimit->value && + (ctfgame.team1 >= capturelimit->value || + ctfgame.team2 >= capturelimit->value)) { + gi.bprintf (PRINT_HIGH, "Capturelimit hit.\n"); + return true; + } + return false; +} + +/*-------------------------------------------------------------------------- + * just here to help old map conversions + *--------------------------------------------------------------------------*/ + +static void old_teleporter_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + edict_t *dest; + int i; + vec3_t forward; + + if (!other->client) + return; + dest = G_Find (NULL, FOFS(targetname), self->target); + if (!dest) + { + gi.dprintf ("Couldn't find destination\n"); + return; + } + +//ZOID + CTFPlayerResetGrapple(other); +//ZOID + + // unlink to make sure it can't possibly interfere with KillBox + gi.unlinkentity (other); + + VectorCopy (dest->s.origin, other->s.origin); + VectorCopy (dest->s.origin, other->s.old_origin); +// other->s.origin[2] += 10; + + // clear the velocity and hold them in place briefly + VectorClear (other->velocity); + other->client->ps.pmove.pm_time = 160>>3; // hold time + other->client->ps.pmove.pm_flags |= PMF_TIME_TELEPORT; + + // draw the teleport splash at source and on the player + self->enemy->s.event = EV_PLAYER_TELEPORT; + other->s.event = EV_PLAYER_TELEPORT; + + // set angles + for (i=0 ; i<3 ; i++) + other->client->ps.pmove.delta_angles[i] = ANGLE2SHORT(dest->s.angles[i] - other->client->resp.cmd_angles[i]); + + other->s.angles[PITCH] = 0; + other->s.angles[YAW] = dest->s.angles[YAW]; + other->s.angles[ROLL] = 0; + VectorCopy (dest->s.angles, other->client->ps.viewangles); + VectorCopy (dest->s.angles, other->client->v_angle); + + // give a little forward velocity + AngleVectors (other->client->v_angle, forward, NULL, NULL); + VectorScale(forward, 200, other->velocity); + + // kill anything at the destination + if (!KillBox (other)) + { + } + + gi.linkentity (other); +} + +/*QUAKED trigger_teleport (0.5 0.5 0.5) ? +Players touching this will be teleported +*/ +void SP_trigger_teleport (edict_t *ent) +{ + edict_t *s; + int i; + + if (!ent->target) + { + gi.dprintf ("teleporter without a target.\n"); + G_FreeEdict (ent); + return; + } + + ent->svflags |= SVF_NOCLIENT; + ent->solid = SOLID_TRIGGER; + ent->touch = old_teleporter_touch; + gi.setmodel (ent, ent->model); + gi.linkentity (ent); + + // noise maker and splash effect dude + s = G_Spawn(); + ent->enemy = s; + for (i = 0; i < 3; i++) + s->s.origin[i] = ent->mins[i] + (ent->maxs[i] - ent->mins[i])/2; + s->s.sound = gi.soundindex ("world/hum1.wav"); + gi.linkentity(s); + +} + +/*QUAKED info_teleport_destination (0.5 0.5 0.5) (-16 -16 -24) (16 16 32) +Point trigger_teleports at these. +*/ +void SP_info_teleport_destination (edict_t *ent) +{ + ent->s.origin[2] += 16; +} + +/*----------------------------------------------------------------------------------*/ +/* ADMIN */ + +typedef struct admin_settings_s { + int matchlen; + int matchsetuplen; + int matchstartlen; + qboolean weaponsstay; + qboolean instantitems; + qboolean quaddrop; + qboolean instantweap; + qboolean matchlock; +} admin_settings_t; + +#define SETMENU_SIZE (7 + 5) + +void CTFAdmin_UpdateSettings(edict_t *ent, pmenuhnd_t *setmenu); +void CTFOpenAdminMenu(edict_t *ent); + +void CTFAdmin_SettingsApply(edict_t *ent, pmenuhnd_t *p) +{ + admin_settings_t *settings = p->arg; + char st[80]; + int i; + + if (settings->matchlen != matchtime->value) { + gi.bprintf(PRINT_HIGH, "%s changed the match length to %d minutes.\n", + ent->client->pers.netname, settings->matchlen); + if (ctfgame.match == MATCH_GAME) { + // in the middle of a match, change it on the fly + ctfgame.matchtime = (ctfgame.matchtime - matchtime->value*60) + settings->matchlen*60; + } + sprintf(st, "%d", settings->matchlen); + gi.cvar_set("matchtime", st); + } + + if (settings->matchsetuplen != matchsetuptime->value) { + gi.bprintf(PRINT_HIGH, "%s changed the match setup time to %d minutes.\n", + ent->client->pers.netname, settings->matchsetuplen); + if (ctfgame.match == MATCH_SETUP) { + // in the middle of a match, change it on the fly + ctfgame.matchtime = (ctfgame.matchtime - matchsetuptime->value*60) + settings->matchsetuplen*60; + } + sprintf(st, "%d", settings->matchsetuplen); + gi.cvar_set("matchsetuptime", st); + } + + if (settings->matchstartlen != matchstarttime->value) { + gi.bprintf(PRINT_HIGH, "%s changed the match start time to %d seconds.\n", + ent->client->pers.netname, settings->matchstartlen); + if (ctfgame.match == MATCH_PREGAME) { + // in the middle of a match, change it on the fly + ctfgame.matchtime = (ctfgame.matchtime - matchstarttime->value) + settings->matchstartlen; + } + sprintf(st, "%d", settings->matchstartlen); + gi.cvar_set("matchstarttime", st); + } + + if (settings->weaponsstay != !!((int)dmflags->value & DF_WEAPONS_STAY)) { + gi.bprintf(PRINT_HIGH, "%s turned %s weapons stay.\n", + ent->client->pers.netname, settings->weaponsstay ? "on" : "off"); + i = (int)dmflags->value; + if (settings->weaponsstay) + i |= DF_WEAPONS_STAY; + else + i &= ~DF_WEAPONS_STAY; + sprintf(st, "%d", i); + gi.cvar_set("dmflags", st); + } + + if (settings->instantitems != !!((int)dmflags->value & DF_INSTANT_ITEMS)) { + gi.bprintf(PRINT_HIGH, "%s turned %s instant items.\n", + ent->client->pers.netname, settings->instantitems ? "on" : "off"); + i = (int)dmflags->value; + if (settings->instantitems) + i |= DF_INSTANT_ITEMS; + else + i &= ~DF_INSTANT_ITEMS; + sprintf(st, "%d", i); + gi.cvar_set("dmflags", st); + } + + if (settings->quaddrop != !!((int)dmflags->value & DF_QUAD_DROP)) { + gi.bprintf(PRINT_HIGH, "%s turned %s quad drop.\n", + ent->client->pers.netname, settings->quaddrop ? "on" : "off"); + i = (int)dmflags->value; + if (settings->quaddrop) + i |= DF_QUAD_DROP; + else + i &= ~DF_QUAD_DROP; + sprintf(st, "%d", i); + gi.cvar_set("dmflags", st); + } + + if (settings->instantweap != !!((int)instantweap->value)) { + gi.bprintf(PRINT_HIGH, "%s turned %s instant weapons.\n", + ent->client->pers.netname, settings->instantweap ? "on" : "off"); + sprintf(st, "%d", (int)settings->instantweap); + gi.cvar_set("instantweap", st); + } + + if (settings->matchlock != !!((int)matchlock->value)) { + gi.bprintf(PRINT_HIGH, "%s turned %s match lock.\n", + ent->client->pers.netname, settings->matchlock ? "on" : "off"); + sprintf(st, "%d", (int)settings->matchlock); + gi.cvar_set("matchlock", st); + } + + PMenu_Close(ent); + CTFOpenAdminMenu(ent); +} + +void CTFAdmin_SettingsCancel(edict_t *ent, pmenuhnd_t *p) +{ + admin_settings_t *settings = p->arg; + + PMenu_Close(ent); + CTFOpenAdminMenu(ent); +} + +void CTFAdmin_ChangeMatchLen(edict_t *ent, pmenuhnd_t *p) +{ + admin_settings_t *settings = p->arg; + + settings->matchlen = (settings->matchlen % 60) + 5; + if (settings->matchlen < 5) + settings->matchlen = 5; + + CTFAdmin_UpdateSettings(ent, p); +} + +void CTFAdmin_ChangeMatchSetupLen(edict_t *ent, pmenuhnd_t *p) +{ + admin_settings_t *settings = p->arg; + + settings->matchsetuplen = (settings->matchsetuplen % 60) + 5; + if (settings->matchsetuplen < 5) + settings->matchsetuplen = 5; + + CTFAdmin_UpdateSettings(ent, p); +} + +void CTFAdmin_ChangeMatchStartLen(edict_t *ent, pmenuhnd_t *p) +{ + admin_settings_t *settings = p->arg; + + settings->matchstartlen = (settings->matchstartlen % 600) + 10; + if (settings->matchstartlen < 20) + settings->matchstartlen = 20; + + CTFAdmin_UpdateSettings(ent, p); +} + +void CTFAdmin_ChangeWeapStay(edict_t *ent, pmenuhnd_t *p) +{ + admin_settings_t *settings = p->arg; + + settings->weaponsstay = !settings->weaponsstay; + CTFAdmin_UpdateSettings(ent, p); +} + +void CTFAdmin_ChangeInstantItems(edict_t *ent, pmenuhnd_t *p) +{ + admin_settings_t *settings = p->arg; + + settings->instantitems = !settings->instantitems; + CTFAdmin_UpdateSettings(ent, p); +} + +void CTFAdmin_ChangeQuadDrop(edict_t *ent, pmenuhnd_t *p) +{ + admin_settings_t *settings = p->arg; + + settings->quaddrop = !settings->quaddrop; + CTFAdmin_UpdateSettings(ent, p); +} + +void CTFAdmin_ChangeInstantWeap(edict_t *ent, pmenuhnd_t *p) +{ + admin_settings_t *settings = p->arg; + + settings->instantweap = !settings->instantweap; + CTFAdmin_UpdateSettings(ent, p); +} + +void CTFAdmin_ChangeMatchLock(edict_t *ent, pmenuhnd_t *p) +{ + admin_settings_t *settings = p->arg; + + settings->matchlock = !settings->matchlock; + CTFAdmin_UpdateSettings(ent, p); +} + +void CTFAdmin_UpdateSettings(edict_t *ent, pmenuhnd_t *setmenu) +{ + int i = 2; + char text[64]; + admin_settings_t *settings = setmenu->arg; + + sprintf(text, "Match Len: %2d mins", settings->matchlen); + PMenu_UpdateEntry(setmenu->entries + i, text, PMENU_ALIGN_LEFT, CTFAdmin_ChangeMatchLen); + i++; + + sprintf(text, "Match Setup Len: %2d mins", settings->matchsetuplen); + PMenu_UpdateEntry(setmenu->entries + i, text, PMENU_ALIGN_LEFT, CTFAdmin_ChangeMatchSetupLen); + i++; + + sprintf(text, "Match Start Len: %2d secs", settings->matchstartlen); + PMenu_UpdateEntry(setmenu->entries + i, text, PMENU_ALIGN_LEFT, CTFAdmin_ChangeMatchStartLen); + i++; + + sprintf(text, "Weapons Stay: %s", settings->weaponsstay ? "Yes" : "No"); + PMenu_UpdateEntry(setmenu->entries + i, text, PMENU_ALIGN_LEFT, CTFAdmin_ChangeWeapStay); + i++; + + sprintf(text, "Instant Items: %s", settings->instantitems ? "Yes" : "No"); + PMenu_UpdateEntry(setmenu->entries + i, text, PMENU_ALIGN_LEFT, CTFAdmin_ChangeInstantItems); + i++; + + sprintf(text, "Quad Drop: %s", settings->quaddrop ? "Yes" : "No"); + PMenu_UpdateEntry(setmenu->entries + i, text, PMENU_ALIGN_LEFT, CTFAdmin_ChangeQuadDrop); + i++; + + sprintf(text, "Instant Weapons: %s", settings->instantweap ? "Yes" : "No"); + PMenu_UpdateEntry(setmenu->entries + i, text, PMENU_ALIGN_LEFT, CTFAdmin_ChangeInstantWeap); + i++; + + sprintf(text, "Match Lock: %s", settings->matchlock ? "Yes" : "No"); + PMenu_UpdateEntry(setmenu->entries + i, text, PMENU_ALIGN_LEFT, CTFAdmin_ChangeMatchLock); + i++; + + PMenu_Update(ent); +} + +pmenu_t def_setmenu[] = { + { "*Settings Menu", PMENU_ALIGN_CENTER, NULL }, + { NULL, PMENU_ALIGN_CENTER, NULL }, + { NULL, PMENU_ALIGN_LEFT, NULL }, //int matchlen; + { NULL, PMENU_ALIGN_LEFT, NULL }, //int matchsetuplen; + { NULL, PMENU_ALIGN_LEFT, NULL }, //int matchstartlen; + { NULL, PMENU_ALIGN_LEFT, NULL }, //qboolean weaponsstay; + { NULL, PMENU_ALIGN_LEFT, NULL }, //qboolean instantitems; + { NULL, PMENU_ALIGN_LEFT, NULL }, //qboolean quaddrop; + { NULL, PMENU_ALIGN_LEFT, NULL }, //qboolean instantweap; + { NULL, PMENU_ALIGN_LEFT, NULL }, //qboolean matchlock; + { NULL, PMENU_ALIGN_LEFT, NULL }, + { "Apply", PMENU_ALIGN_LEFT, CTFAdmin_SettingsApply }, + { "Cancel", PMENU_ALIGN_LEFT, CTFAdmin_SettingsCancel } +}; + +void CTFAdmin_Settings(edict_t *ent, pmenuhnd_t *p) +{ + admin_settings_t *settings; + pmenuhnd_t *menu; + + PMenu_Close(ent); + + settings = malloc(sizeof(*settings)); + + settings->matchlen = matchtime->value; + settings->matchsetuplen = matchsetuptime->value; + settings->matchstartlen = matchstarttime->value; + settings->weaponsstay = !!((int)dmflags->value & DF_WEAPONS_STAY); + settings->instantitems = !!((int)dmflags->value & DF_INSTANT_ITEMS); + settings->quaddrop = !!((int)dmflags->value & DF_QUAD_DROP); + settings->instantweap = instantweap->value != 0; + settings->matchlock = matchlock->value != 0; + + menu = PMenu_Open(ent, def_setmenu, -1, sizeof(def_setmenu) / sizeof(pmenu_t), settings); + CTFAdmin_UpdateSettings(ent, menu); +} + +void CTFAdmin_MatchSet(edict_t *ent, pmenuhnd_t *p) +{ + PMenu_Close(ent); + + if (ctfgame.match == MATCH_SETUP) { + gi.bprintf(PRINT_CHAT, "Match has been forced to start.\n"); + ctfgame.match = MATCH_PREGAME; + ctfgame.matchtime = level.time + matchstarttime->value; + gi.positioned_sound (world->s.origin, world, CHAN_AUTO | CHAN_RELIABLE, gi.soundindex("misc/talk1.wav"), 1, ATTN_NONE, 0); + ctfgame.countdown = false; + } else if (ctfgame.match == MATCH_GAME) { + gi.bprintf(PRINT_CHAT, "Match has been forced to terminate.\n"); + ctfgame.match = MATCH_SETUP; + ctfgame.matchtime = level.time + matchsetuptime->value * 60; + CTFResetAllPlayers(); + } +} + +void CTFAdmin_MatchMode(edict_t *ent, pmenuhnd_t *p) +{ + PMenu_Close(ent); + + if (ctfgame.match != MATCH_SETUP) { + if (competition->value < 3) + gi.cvar_set("competition", "2"); + ctfgame.match = MATCH_SETUP; + CTFResetAllPlayers(); + } +} + +void CTFAdmin_Reset(edict_t *ent, pmenuhnd_t *p) +{ + PMenu_Close(ent); + + // go back to normal mode + gi.bprintf(PRINT_CHAT, "Match mode has been terminated, reseting to normal game.\n"); + ctfgame.match = MATCH_NONE; + gi.cvar_set("competition", "1"); + CTFResetAllPlayers(); +} + +void CTFAdmin_Cancel(edict_t *ent, pmenuhnd_t *p) +{ + PMenu_Close(ent); +} + + +pmenu_t adminmenu[] = { + { "*Administration Menu", PMENU_ALIGN_CENTER, NULL }, + { NULL, PMENU_ALIGN_CENTER, NULL }, // blank + { "Settings", PMENU_ALIGN_LEFT, CTFAdmin_Settings }, + { NULL, PMENU_ALIGN_LEFT, NULL }, + { NULL, PMENU_ALIGN_LEFT, NULL }, + { "Cancel", PMENU_ALIGN_LEFT, CTFAdmin_Cancel }, + { NULL, PMENU_ALIGN_CENTER, NULL }, +}; + +void CTFOpenAdminMenu(edict_t *ent) +{ + adminmenu[3].text = NULL; + adminmenu[3].SelectFunc = NULL; + adminmenu[4].text = NULL; + adminmenu[4].SelectFunc = NULL; + if (ctfgame.match == MATCH_SETUP) { + adminmenu[3].text = "Force start match"; + adminmenu[3].SelectFunc = CTFAdmin_MatchSet; + adminmenu[4].text = "Reset to pickup mode"; + adminmenu[4].SelectFunc = CTFAdmin_Reset; + } else if (ctfgame.match == MATCH_GAME || ctfgame.match == MATCH_PREGAME) { + adminmenu[3].text = "Cancel match"; + adminmenu[3].SelectFunc = CTFAdmin_MatchSet; + } else if (ctfgame.match == MATCH_NONE && competition->value) { + adminmenu[3].text = "Switch to match mode"; + adminmenu[3].SelectFunc = CTFAdmin_MatchMode; + } + + +// if (ent->client->menu) +// PMenu_Close(ent->client->menu); + + PMenu_Open(ent, adminmenu, -1, sizeof(adminmenu) / sizeof(pmenu_t), NULL); +} + +void CTFAdmin(edict_t *ent) +{ + char text[1024]; + + if (!allow_admin->value) { + gi.cprintf(ent, PRINT_HIGH, "Administration is disabled\n"); + return; + } + + if (gi.argc() > 1 && admin_password->string && *admin_password->string && + !ent->client->resp.admin && strcmp(admin_password->string, gi.argv(1)) == 0) { + ent->client->resp.admin = true; + gi.bprintf(PRINT_HIGH, "%s has become an admin.\n", ent->client->pers.netname); + gi.cprintf(ent, PRINT_HIGH, "Type 'admin' to access the adminstration menu.\n"); + } + + if (!ent->client->resp.admin) { + sprintf(text, "%s has requested admin rights.", + ent->client->pers.netname); + CTFBeginElection(ent, ELECT_ADMIN, text); + return; + } + + if (ent->client->menu) + PMenu_Close(ent); + + CTFOpenAdminMenu(ent); +} + +/*----------------------------------------------------------------*/ + +void CTFStats(edict_t *ent) +{ + int i, e; + ghost_t *g; + char st[80]; + char text[1024]; + edict_t *e2; + + *text = 0; + if (ctfgame.match == MATCH_SETUP) { + for (i = 1; i <= maxclients->value; i++) { + e2 = g_edicts + i; + if (!e2->inuse) + continue; + if (!e2->client->resp.ready && e2->client->resp.ctf_team != CTF_NOTEAM) { + sprintf(st, "%s is not ready.\n", e2->client->pers.netname); + if (strlen(text) + strlen(st) < sizeof(text) - 50) + strcat(text, st); + } + } + } + + for (i = 0, g = ctfgame.ghosts; i < MAX_CLIENTS; i++, g++) + if (g->ent) + break; + + if (i == MAX_CLIENTS) { + if (*text) + gi.cprintf(ent, PRINT_HIGH, "%s", text); + gi.cprintf(ent, PRINT_HIGH, "No statistics available.\n"); + return; + } + + strcat(text, " #|Name |Score|Kills|Death|BasDf|CarDf|Effcy|\n"); + + for (i = 0, g = ctfgame.ghosts; i < MAX_CLIENTS; i++, g++) { + if (!*g->netname) + continue; + + if (g->deaths + g->kills == 0) + e = 50; + else + e = g->kills * 100 / (g->kills + g->deaths); + sprintf(st, "%3d|%-16.16s|%5d|%5d|%5d|%5d|%5d|%4d%%|\n", + g->number, + g->netname, + g->score, + g->kills, + g->deaths, + g->basedef, + g->carrierdef, + e); + if (strlen(text) + strlen(st) > sizeof(text) - 50) { + sprintf(text+strlen(text), "And more...\n"); + gi.cprintf(ent, PRINT_HIGH, "%s", text); + return; + } + strcat(text, st); + } + gi.cprintf(ent, PRINT_HIGH, "%s", text); +} + +void CTFPlayerList(edict_t *ent) +{ + int i; + char st[80]; + char text[1400]; + edict_t *e2; + +#if 0 + *text = 0; + if (ctfgame.match == MATCH_SETUP) { + for (i = 1; i <= maxclients->value; i++) { + e2 = g_edicts + i; + if (!e2->inuse) + continue; + if (!e2->client->resp.ready && e2->client->resp.ctf_team != CTF_NOTEAM) { + sprintf(st, "%s is not ready.\n", e2->client->pers.netname); + if (strlen(text) + strlen(st) < sizeof(text) - 50) + strcat(text, st); + } + } + } +#endif + + // number, name, connect time, ping, score, admin + + *text = 0; + for (i = 1; i <= maxclients->value; i++) { + e2 = g_edicts + i; + if (!e2->inuse) + continue; + + Com_sprintf(st, sizeof(st), "%3d %-16.16s %02d:%02d %4d %3d%s%s\n", + i, + e2->client->pers.netname, + (level.framenum - e2->client->resp.enterframe) / 600, + ((level.framenum - e2->client->resp.enterframe) % 600)/10, + e2->client->ping, + e2->client->resp.score, + (ctfgame.match == MATCH_SETUP || ctfgame.match == MATCH_PREGAME) ? + (e2->client->resp.ready ? " (ready)" : " (notready)") : "", + e2->client->resp.admin ? " (admin)" : ""); + + if (strlen(text) + strlen(st) > sizeof(text) - 50) { + sprintf(text+strlen(text), "And more...\n"); + gi.cprintf(ent, PRINT_HIGH, "%s", text); + return; + } + strcat(text, st); + } + gi.cprintf(ent, PRINT_HIGH, "%s", text); +} + + +void CTFWarp(edict_t *ent) +{ + char text[1024]; + char *mlist, *token; + static const char *seps = " \t\n\r"; + + if (gi.argc() < 2) { + gi.cprintf(ent, PRINT_HIGH, "Where do you want to warp to?\n"); + gi.cprintf(ent, PRINT_HIGH, "Available levels are: %s\n", warp_list->string); + return; + } + + mlist = strdup(warp_list->string); + + token = strtok(mlist, seps); + while (token != NULL) { + if (Q_stricmp(token, gi.argv(1)) == 0) + break; + token = strtok(NULL, seps); + } + + if (token == NULL) { + gi.cprintf(ent, PRINT_HIGH, "Unknown CTF level.\n"); + gi.cprintf(ent, PRINT_HIGH, "Available levels are: %s\n", warp_list->string); + free(mlist); + return; + } + + free(mlist); + + + if (ent->client->resp.admin) { + gi.bprintf(PRINT_HIGH, "%s is warping to level %s.\n", + ent->client->pers.netname, gi.argv(1)); + strncpy(level.forcemap, gi.argv(1), sizeof(level.forcemap) - 1); + EndDMLevel(); + return; + } + + sprintf(text, "%s has requested warping to level %s.", + ent->client->pers.netname, gi.argv(1)); + if (CTFBeginElection(ent, ELECT_MAP, text)) + strncpy(ctfgame.elevel, gi.argv(1), sizeof(ctfgame.elevel) - 1); +} + +void CTFBoot(edict_t *ent) +{ + int i; + edict_t *targ; + char text[80]; + + if (!ent->client->resp.admin) { + gi.cprintf(ent, PRINT_HIGH, "You are not an admin.\n"); + return; + } + + if (gi.argc() < 2) { + gi.cprintf(ent, PRINT_HIGH, "Who do you want to kick?\n"); + return; + } + + if (*gi.argv(1) < '0' && *gi.argv(1) > '9') { + gi.cprintf(ent, PRINT_HIGH, "Specify the player number to kick.\n"); + return; + } + + i = atoi(gi.argv(1)); + if (i < 1 || i > maxclients->value) { + gi.cprintf(ent, PRINT_HIGH, "Invalid player number.\n"); + return; + } + + targ = g_edicts + i; + if (!targ->inuse) { + gi.cprintf(ent, PRINT_HIGH, "That player number is not connected.\n"); + return; + } + + sprintf(text, "kick %d\n", i - 1); + gi.AddCommandString(text); +} + + +void CTFSetPowerUpEffect(edict_t *ent, int def) +{ + if (ent->client->resp.ctf_team == CTF_TEAM1) + ent->s.effects |= EF_PENT; // red + else if (ent->client->resp.ctf_team == CTF_TEAM2) + ent->s.effects |= EF_QUAD; // red + else + ent->s.effects |= def; +} + diff --git a/original/ctf/g_ctf.h b/original/ctf/g_ctf.h new file mode 100644 index 0000000..8de4513 --- /dev/null +++ b/original/ctf/g_ctf.h @@ -0,0 +1,192 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +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. + +*/ + +#define CTF_VERSION 1.52 +#define CTF_VSTRING2(x) #x +#define CTF_VSTRING(x) CTF_VSTRING2(x) +#define CTF_STRING_VERSION CTF_VSTRING(CTF_VERSION) + +#define STAT_CTF_TEAM1_PIC 17 +#define STAT_CTF_TEAM1_CAPS 18 +#define STAT_CTF_TEAM2_PIC 19 +#define STAT_CTF_TEAM2_CAPS 20 +#define STAT_CTF_FLAG_PIC 21 +#define STAT_CTF_JOINED_TEAM1_PIC 22 +#define STAT_CTF_JOINED_TEAM2_PIC 23 +#define STAT_CTF_TEAM1_HEADER 24 +#define STAT_CTF_TEAM2_HEADER 25 +#define STAT_CTF_TECH 26 +#define STAT_CTF_ID_VIEW 27 +#define STAT_CTF_MATCH 28 +#define STAT_CTF_ID_VIEW_COLOR 29 +#define STAT_CTF_TEAMINFO 30 + +#define CONFIG_CTF_MATCH (CS_AIRACCEL-1) +#define CONFIG_CTF_TEAMINFO (CS_AIRACCEL-2) + +typedef enum { + CTF_NOTEAM, + CTF_TEAM1, + CTF_TEAM2 +} ctfteam_t; + +typedef enum { + CTF_GRAPPLE_STATE_FLY, + CTF_GRAPPLE_STATE_PULL, + CTF_GRAPPLE_STATE_HANG +} ctfgrapplestate_t; + +typedef struct ghost_s { + char netname[16]; + int number; + + // stats + int deaths; + int kills; + int caps; + int basedef; + int carrierdef; + + int code; // ghost code + int team; // team + int score; // frags at time of disconnect + edict_t *ent; +} ghost_t; + +extern cvar_t *ctf; + +#define CTF_TEAM1_SKIN "ctf_r" +#define CTF_TEAM2_SKIN "ctf_b" + +#define DF_CTF_FORCEJOIN 131072 +#define DF_ARMOR_PROTECT 262144 +#define DF_CTF_NO_TECH 524288 + +#define CTF_CAPTURE_BONUS 15 // what you get for capture +#define CTF_TEAM_BONUS 10 // what your team gets for capture +#define CTF_RECOVERY_BONUS 1 // what you get for recovery +#define CTF_FLAG_BONUS 0 // what you get for picking up enemy flag +#define CTF_FRAG_CARRIER_BONUS 2 // what you get for fragging enemy flag carrier +#define CTF_FLAG_RETURN_TIME 40 // seconds until auto return + +#define CTF_CARRIER_DANGER_PROTECT_BONUS 2 // bonus for fraggin someone who has recently hurt your flag carrier +#define CTF_CARRIER_PROTECT_BONUS 1 // bonus for fraggin someone while either you or your target are near your flag carrier +#define CTF_FLAG_DEFENSE_BONUS 1 // bonus for fraggin someone while either you or your target are near your flag +#define CTF_RETURN_FLAG_ASSIST_BONUS 1 // awarded for returning a flag that causes a capture to happen almost immediately +#define CTF_FRAG_CARRIER_ASSIST_BONUS 2 // award for fragging a flag carrier if a capture happens almost immediately + +#define CTF_TARGET_PROTECT_RADIUS 400 // the radius around an object being defended where a target will be worth extra frags +#define CTF_ATTACKER_PROTECT_RADIUS 400 // the radius around an object being defended where an attacker will get extra frags when making kills + +#define CTF_CARRIER_DANGER_PROTECT_TIMEOUT 8 +#define CTF_FRAG_CARRIER_ASSIST_TIMEOUT 10 +#define CTF_RETURN_FLAG_ASSIST_TIMEOUT 10 + +#define CTF_AUTO_FLAG_RETURN_TIMEOUT 30 // number of seconds before dropped flag auto-returns + +#define CTF_TECH_TIMEOUT 60 // seconds before techs spawn again + +#define CTF_GRAPPLE_SPEED 650 // speed of grapple in flight +#define CTF_GRAPPLE_PULL_SPEED 650 // speed player is pulled at + +void CTFInit(void); +void CTFSpawn(void); +void CTFPrecache(void); + +void SP_info_player_team1(edict_t *self); +void SP_info_player_team2(edict_t *self); + +char *CTFTeamName(int team); +char *CTFOtherTeamName(int team); +void CTFAssignSkin(edict_t *ent, char *s); +void CTFAssignTeam(gclient_t *who); +edict_t *SelectCTFSpawnPoint (edict_t *ent); +qboolean CTFPickup_Flag(edict_t *ent, edict_t *other); +qboolean CTFDrop_Flag(edict_t *ent, gitem_t *item); +void CTFEffects(edict_t *player); +void CTFCalcScores(void); +void SetCTFStats(edict_t *ent); +void CTFDeadDropFlag(edict_t *self); +void CTFScoreboardMessage (edict_t *ent, edict_t *killer); +void CTFTeam_f (edict_t *ent); +void CTFID_f (edict_t *ent); +void CTFSay_Team(edict_t *who, char *msg); +void CTFFlagSetup (edict_t *ent); +void CTFResetFlag(int ctf_team); +void CTFFragBonuses(edict_t *targ, edict_t *inflictor, edict_t *attacker); +void CTFCheckHurtCarrier(edict_t *targ, edict_t *attacker); + +// GRAPPLE +void CTFWeapon_Grapple (edict_t *ent); +void CTFPlayerResetGrapple(edict_t *ent); +void CTFGrapplePull(edict_t *self); +void CTFResetGrapple(edict_t *self); + +//TECH +gitem_t *CTFWhat_Tech(edict_t *ent); +qboolean CTFPickup_Tech (edict_t *ent, edict_t *other); +void CTFDrop_Tech(edict_t *ent, gitem_t *item); +void CTFDeadDropTech(edict_t *ent); +void CTFSetupTechSpawn(void); +int CTFApplyResistance(edict_t *ent, int dmg); +int CTFApplyStrength(edict_t *ent, int dmg); +qboolean CTFApplyStrengthSound(edict_t *ent); +qboolean CTFApplyHaste(edict_t *ent); +void CTFApplyHasteSound(edict_t *ent); +void CTFApplyRegeneration(edict_t *ent); +qboolean CTFHasRegeneration(edict_t *ent); +void CTFRespawnTech(edict_t *ent); +void CTFResetTech(void); + +void CTFOpenJoinMenu(edict_t *ent); +qboolean CTFStartClient(edict_t *ent); +void CTFVoteYes(edict_t *ent); +void CTFVoteNo(edict_t *ent); +void CTFReady(edict_t *ent); +void CTFNotReady(edict_t *ent); +qboolean CTFNextMap(void); +qboolean CTFMatchSetup(void); +qboolean CTFMatchOn(void); +void CTFGhost(edict_t *ent); +void CTFAdmin(edict_t *ent); +qboolean CTFInMatch(void); +void CTFStats(edict_t *ent); +void CTFWarp(edict_t *ent); +void CTFBoot(edict_t *ent); +void CTFPlayerList(edict_t *ent); + +qboolean CTFCheckRules(void); + +void SP_misc_ctf_banner (edict_t *ent); +void SP_misc_ctf_small_banner (edict_t *ent); + +extern char *ctf_statusbar; + +void UpdateChaseCam(edict_t *ent); +void ChaseNext(edict_t *ent); +void ChasePrev(edict_t *ent); + +void CTFObserver(edict_t *ent); + +void SP_trigger_teleport (edict_t *ent); +void SP_info_teleport_destination (edict_t *ent); + +void CTFSetPowerUpEffect(edict_t *ent, int def); + diff --git a/original/ctf/g_func.c b/original/ctf/g_func.c new file mode 100644 index 0000000..70365a0 --- /dev/null +++ b/original/ctf/g_func.c @@ -0,0 +1,2047 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +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. + +*/ +#include "g_local.h" + +/* +========================================================= + + PLATS + + movement options: + + linear + smooth start, hard stop + smooth start, smooth stop + + start + end + acceleration + speed + deceleration + begin sound + end sound + target fired when reaching end + wait at end + + object characteristics that use move segments + --------------------------------------------- + movetype_push, or movetype_stop + action when touched + action when blocked + action when used + disabled? + auto trigger spawning + + +========================================================= +*/ + +#define PLAT_LOW_TRIGGER 1 + +#define STATE_TOP 0 +#define STATE_BOTTOM 1 +#define STATE_UP 2 +#define STATE_DOWN 3 + +#define DOOR_START_OPEN 1 +#define DOOR_REVERSE 2 +#define DOOR_CRUSHER 4 +#define DOOR_NOMONSTER 8 +#define DOOR_TOGGLE 32 +#define DOOR_X_AXIS 64 +#define DOOR_Y_AXIS 128 + + +// +// Support routines for movement (changes in origin using velocity) +// + +void Move_Done (edict_t *ent) +{ + VectorClear (ent->velocity); + ent->moveinfo.endfunc (ent); +} + +void Move_Final (edict_t *ent) +{ + if (ent->moveinfo.remaining_distance == 0) + { + Move_Done (ent); + return; + } + + VectorScale (ent->moveinfo.dir, ent->moveinfo.remaining_distance / FRAMETIME, ent->velocity); + + ent->think = Move_Done; + ent->nextthink = level.time + FRAMETIME; +} + +void Move_Begin (edict_t *ent) +{ + float frames; + + if ((ent->moveinfo.speed * FRAMETIME) >= ent->moveinfo.remaining_distance) + { + Move_Final (ent); + return; + } + VectorScale (ent->moveinfo.dir, ent->moveinfo.speed, ent->velocity); + frames = floor((ent->moveinfo.remaining_distance / ent->moveinfo.speed) / FRAMETIME); + ent->moveinfo.remaining_distance -= frames * ent->moveinfo.speed * FRAMETIME; + ent->nextthink = level.time + (frames * FRAMETIME); + ent->think = Move_Final; +} + +void Think_AccelMove (edict_t *ent); + +void Move_Calc (edict_t *ent, vec3_t dest, void(*func)(edict_t*)) +{ + VectorClear (ent->velocity); + VectorSubtract (dest, ent->s.origin, ent->moveinfo.dir); + ent->moveinfo.remaining_distance = VectorNormalize (ent->moveinfo.dir); + ent->moveinfo.endfunc = func; + + if (ent->moveinfo.speed == ent->moveinfo.accel && ent->moveinfo.speed == ent->moveinfo.decel) + { + if (level.current_entity == ((ent->flags & FL_TEAMSLAVE) ? ent->teammaster : ent)) + { + Move_Begin (ent); + } + else + { + ent->nextthink = level.time + FRAMETIME; + ent->think = Move_Begin; + } + } + else + { + // accelerative + ent->moveinfo.current_speed = 0; + ent->think = Think_AccelMove; + ent->nextthink = level.time + FRAMETIME; + } +} + + +// +// Support routines for angular movement (changes in angle using avelocity) +// + +void AngleMove_Done (edict_t *ent) +{ + VectorClear (ent->avelocity); + ent->moveinfo.endfunc (ent); +} + +void AngleMove_Final (edict_t *ent) +{ + vec3_t move; + + if (ent->moveinfo.state == STATE_UP) + VectorSubtract (ent->moveinfo.end_angles, ent->s.angles, move); + else + VectorSubtract (ent->moveinfo.start_angles, ent->s.angles, move); + + if (VectorCompare (move, vec3_origin)) + { + AngleMove_Done (ent); + return; + } + + VectorScale (move, 1.0/FRAMETIME, ent->avelocity); + + ent->think = AngleMove_Done; + ent->nextthink = level.time + FRAMETIME; +} + +void AngleMove_Begin (edict_t *ent) +{ + vec3_t destdelta; + float len; + float traveltime; + float frames; + + // set destdelta to the vector needed to move + if (ent->moveinfo.state == STATE_UP) + VectorSubtract (ent->moveinfo.end_angles, ent->s.angles, destdelta); + else + VectorSubtract (ent->moveinfo.start_angles, ent->s.angles, destdelta); + + // calculate length of vector + len = VectorLength (destdelta); + + // divide by speed to get time to reach dest + traveltime = len / ent->moveinfo.speed; + + if (traveltime < FRAMETIME) + { + AngleMove_Final (ent); + return; + } + + frames = floor(traveltime / FRAMETIME); + + // scale the destdelta vector by the time spent traveling to get velocity + VectorScale (destdelta, 1.0 / traveltime, ent->avelocity); + + // set nextthink to trigger a think when dest is reached + ent->nextthink = level.time + frames * FRAMETIME; + ent->think = AngleMove_Final; +} + +void AngleMove_Calc (edict_t *ent, void(*func)(edict_t*)) +{ + VectorClear (ent->avelocity); + ent->moveinfo.endfunc = func; + if (level.current_entity == ((ent->flags & FL_TEAMSLAVE) ? ent->teammaster : ent)) + { + AngleMove_Begin (ent); + } + else + { + ent->nextthink = level.time + FRAMETIME; + ent->think = AngleMove_Begin; + } +} + + +/* +============== +Think_AccelMove + +The team has completed a frame of movement, so +change the speed for the next frame +============== +*/ +#define AccelerationDistance(target, rate) (target * ((target / rate) + 1) / 2) + +void plat_CalcAcceleratedMove(moveinfo_t *moveinfo) +{ + float accel_dist; + float decel_dist; + + moveinfo->move_speed = moveinfo->speed; + + if (moveinfo->remaining_distance < moveinfo->accel) + { + moveinfo->current_speed = moveinfo->remaining_distance; + return; + } + + accel_dist = AccelerationDistance (moveinfo->speed, moveinfo->accel); + decel_dist = AccelerationDistance (moveinfo->speed, moveinfo->decel); + + if ((moveinfo->remaining_distance - accel_dist - decel_dist) < 0) + { + float f; + + f = (moveinfo->accel + moveinfo->decel) / (moveinfo->accel * moveinfo->decel); + moveinfo->move_speed = (-2 + sqrt(4 - 4 * f * (-2 * moveinfo->remaining_distance))) / (2 * f); + decel_dist = AccelerationDistance (moveinfo->move_speed, moveinfo->decel); + } + + moveinfo->decel_distance = decel_dist; +}; + +void plat_Accelerate (moveinfo_t *moveinfo) +{ + // are we decelerating? + if (moveinfo->remaining_distance <= moveinfo->decel_distance) + { + if (moveinfo->remaining_distance < moveinfo->decel_distance) + { + if (moveinfo->next_speed) + { + moveinfo->current_speed = moveinfo->next_speed; + moveinfo->next_speed = 0; + return; + } + if (moveinfo->current_speed > moveinfo->decel) + moveinfo->current_speed -= moveinfo->decel; + } + return; + } + + // are we at full speed and need to start decelerating during this move? + if (moveinfo->current_speed == moveinfo->move_speed) + if ((moveinfo->remaining_distance - moveinfo->current_speed) < moveinfo->decel_distance) + { + float p1_distance; + float p2_distance; + float distance; + + p1_distance = moveinfo->remaining_distance - moveinfo->decel_distance; + p2_distance = moveinfo->move_speed * (1.0 - (p1_distance / moveinfo->move_speed)); + distance = p1_distance + p2_distance; + moveinfo->current_speed = moveinfo->move_speed; + moveinfo->next_speed = moveinfo->move_speed - moveinfo->decel * (p2_distance / distance); + return; + } + + // are we accelerating? + if (moveinfo->current_speed < moveinfo->speed) + { + float old_speed; + float p1_distance; + float p1_speed; + float p2_distance; + float distance; + + old_speed = moveinfo->current_speed; + + // figure simple acceleration up to move_speed + moveinfo->current_speed += moveinfo->accel; + if (moveinfo->current_speed > moveinfo->speed) + moveinfo->current_speed = moveinfo->speed; + + // are we accelerating throughout this entire move? + if ((moveinfo->remaining_distance - moveinfo->current_speed) >= moveinfo->decel_distance) + return; + + // during this move we will accelrate from current_speed to move_speed + // and cross over the decel_distance; figure the average speed for the + // entire move + p1_distance = moveinfo->remaining_distance - moveinfo->decel_distance; + p1_speed = (old_speed + moveinfo->move_speed) / 2.0; + p2_distance = moveinfo->move_speed * (1.0 - (p1_distance / p1_speed)); + distance = p1_distance + p2_distance; + moveinfo->current_speed = (p1_speed * (p1_distance / distance)) + (moveinfo->move_speed * (p2_distance / distance)); + moveinfo->next_speed = moveinfo->move_speed - moveinfo->decel * (p2_distance / distance); + return; + } + + // we are at constant velocity (move_speed) + return; +}; + +void Think_AccelMove (edict_t *ent) +{ + ent->moveinfo.remaining_distance -= ent->moveinfo.current_speed; + + if (ent->moveinfo.current_speed == 0) // starting or blocked + plat_CalcAcceleratedMove(&ent->moveinfo); + + plat_Accelerate (&ent->moveinfo); + + // will the entire move complete on next frame? + if (ent->moveinfo.remaining_distance <= ent->moveinfo.current_speed) + { + Move_Final (ent); + return; + } + + VectorScale (ent->moveinfo.dir, ent->moveinfo.current_speed*10, ent->velocity); + ent->nextthink = level.time + FRAMETIME; + ent->think = Think_AccelMove; +} + + +void plat_go_down (edict_t *ent); + +void plat_hit_top (edict_t *ent) +{ + if (!(ent->flags & FL_TEAMSLAVE)) + { + if (ent->moveinfo.sound_end) + gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, ent->moveinfo.sound_end, 1, ATTN_STATIC, 0); + ent->s.sound = 0; + } + ent->moveinfo.state = STATE_TOP; + + ent->think = plat_go_down; + ent->nextthink = level.time + 3; +} + +void plat_hit_bottom (edict_t *ent) +{ + if (!(ent->flags & FL_TEAMSLAVE)) + { + if (ent->moveinfo.sound_end) + gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, ent->moveinfo.sound_end, 1, ATTN_STATIC, 0); + ent->s.sound = 0; + } + ent->moveinfo.state = STATE_BOTTOM; +} + +void plat_go_down (edict_t *ent) +{ + if (!(ent->flags & FL_TEAMSLAVE)) + { + if (ent->moveinfo.sound_start) + gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, ent->moveinfo.sound_start, 1, ATTN_STATIC, 0); + ent->s.sound = ent->moveinfo.sound_middle; + } + ent->moveinfo.state = STATE_DOWN; + Move_Calc (ent, ent->moveinfo.end_origin, plat_hit_bottom); +} + +void plat_go_up (edict_t *ent) +{ + if (!(ent->flags & FL_TEAMSLAVE)) + { + if (ent->moveinfo.sound_start) + gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, ent->moveinfo.sound_start, 1, ATTN_STATIC, 0); + ent->s.sound = ent->moveinfo.sound_middle; + } + ent->moveinfo.state = STATE_UP; + Move_Calc (ent, ent->moveinfo.start_origin, plat_hit_top); +} + +void plat_blocked (edict_t *self, edict_t *other) +{ + if (!(other->svflags & SVF_MONSTER) && (!other->client) ) + { + // give it a chance to go away on it's own terms (like gibs) + T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, 100000, 1, 0, MOD_CRUSH); + // if it's still there, nuke it + if (other) + BecomeExplosion1 (other); + return; + } + + T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH); + + if (self->moveinfo.state == STATE_UP) + plat_go_down (self); + else if (self->moveinfo.state == STATE_DOWN) + plat_go_up (self); +} + + +void Use_Plat (edict_t *ent, edict_t *other, edict_t *activator) +{ + if (ent->think) + return; // already down + plat_go_down (ent); +} + + +void Touch_Plat_Center (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (!other->client) + return; + + if (other->health <= 0) + return; + + ent = ent->enemy; // now point at the plat, not the trigger + if (ent->moveinfo.state == STATE_BOTTOM) + plat_go_up (ent); + else if (ent->moveinfo.state == STATE_TOP) + ent->nextthink = level.time + 1; // the player is still on the plat, so delay going down +} + +void plat_spawn_inside_trigger (edict_t *ent) +{ + edict_t *trigger; + vec3_t tmin, tmax; + +// +// middle trigger +// + trigger = G_Spawn(); + trigger->touch = Touch_Plat_Center; + trigger->movetype = MOVETYPE_NONE; + trigger->solid = SOLID_TRIGGER; + trigger->enemy = ent; + + tmin[0] = ent->mins[0] + 25; + tmin[1] = ent->mins[1] + 25; + tmin[2] = ent->mins[2]; + + tmax[0] = ent->maxs[0] - 25; + tmax[1] = ent->maxs[1] - 25; + tmax[2] = ent->maxs[2] + 8; + + tmin[2] = tmax[2] - (ent->pos1[2] - ent->pos2[2] + st.lip); + + if (ent->spawnflags & PLAT_LOW_TRIGGER) + tmax[2] = tmin[2] + 8; + + if (tmax[0] - tmin[0] <= 0) + { + tmin[0] = (ent->mins[0] + ent->maxs[0]) *0.5; + tmax[0] = tmin[0] + 1; + } + if (tmax[1] - tmin[1] <= 0) + { + tmin[1] = (ent->mins[1] + ent->maxs[1]) *0.5; + tmax[1] = tmin[1] + 1; + } + + VectorCopy (tmin, trigger->mins); + VectorCopy (tmax, trigger->maxs); + + gi.linkentity (trigger); +} + + +/*QUAKED func_plat (0 .5 .8) ? PLAT_LOW_TRIGGER +speed default 150 + +Plats are always drawn in the extended position, so they will light correctly. + +If the plat is the target of another trigger or button, it will start out disabled in the extended position until it is trigger, when it will lower and become a normal plat. + +"speed" overrides default 200. +"accel" overrides default 500 +"lip" overrides default 8 pixel lip + +If the "height" key is set, that will determine the amount the plat moves, instead of being implicitly determoveinfoned by the model's height. + +Set "sounds" to one of the following: +1) base fast +2) chain slow +*/ +void SP_func_plat (edict_t *ent) +{ + VectorClear (ent->s.angles); + ent->solid = SOLID_BSP; + ent->movetype = MOVETYPE_PUSH; + + gi.setmodel (ent, ent->model); + + ent->blocked = plat_blocked; + + if (!ent->speed) + ent->speed = 20; + else + ent->speed *= 0.1; + + if (!ent->accel) + ent->accel = 5; + else + ent->accel *= 0.1; + + if (!ent->decel) + ent->decel = 5; + else + ent->decel *= 0.1; + + if (!ent->dmg) + ent->dmg = 2; + + if (!st.lip) + st.lip = 8; + + // pos1 is the top position, pos2 is the bottom + VectorCopy (ent->s.origin, ent->pos1); + VectorCopy (ent->s.origin, ent->pos2); + if (st.height) + ent->pos2[2] -= st.height; + else + ent->pos2[2] -= (ent->maxs[2] - ent->mins[2]) - st.lip; + + ent->use = Use_Plat; + + plat_spawn_inside_trigger (ent); // the "start moving" trigger + + if (ent->targetname) + { + ent->moveinfo.state = STATE_UP; + } + else + { + VectorCopy (ent->pos2, ent->s.origin); + gi.linkentity (ent); + ent->moveinfo.state = STATE_BOTTOM; + } + + ent->moveinfo.speed = ent->speed; + ent->moveinfo.accel = ent->accel; + ent->moveinfo.decel = ent->decel; + ent->moveinfo.wait = ent->wait; + VectorCopy (ent->pos1, ent->moveinfo.start_origin); + VectorCopy (ent->s.angles, ent->moveinfo.start_angles); + VectorCopy (ent->pos2, ent->moveinfo.end_origin); + VectorCopy (ent->s.angles, ent->moveinfo.end_angles); + + ent->moveinfo.sound_start = gi.soundindex ("plats/pt1_strt.wav"); + ent->moveinfo.sound_middle = gi.soundindex ("plats/pt1_mid.wav"); + ent->moveinfo.sound_end = gi.soundindex ("plats/pt1_end.wav"); +} + +//==================================================================== + +/*QUAKED func_rotating (0 .5 .8) ? START_ON REVERSE X_AXIS Y_AXIS TOUCH_PAIN STOP ANIMATED ANIMATED_FAST +You need to have an origin brush as part of this entity. The center of that brush will be +the point around which it is rotated. It will rotate around the Z axis by default. You can +check either the X_AXIS or Y_AXIS box to change that. + +"speed" determines how fast it moves; default value is 100. +"dmg" damage to inflict when blocked (2 default) + +REVERSE will cause the it to rotate in the opposite direction. +STOP mean it will stop moving instead of pushing entities +*/ + +void rotating_blocked (edict_t *self, edict_t *other) +{ + T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH); +} + +void rotating_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (self->avelocity[0] || self->avelocity[1] || self->avelocity[2]) + T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH); +} + +void rotating_use (edict_t *self, edict_t *other, edict_t *activator) +{ + if (!VectorCompare (self->avelocity, vec3_origin)) + { + self->s.sound = 0; + VectorClear (self->avelocity); + self->touch = NULL; + } + else + { + self->s.sound = self->moveinfo.sound_middle; + VectorScale (self->movedir, self->speed, self->avelocity); + if (self->spawnflags & 16) + self->touch = rotating_touch; + } +} + +void SP_func_rotating (edict_t *ent) +{ + ent->solid = SOLID_BSP; + if (ent->spawnflags & 32) + ent->movetype = MOVETYPE_STOP; + else + ent->movetype = MOVETYPE_PUSH; + + // set the axis of rotation + VectorClear(ent->movedir); + if (ent->spawnflags & 4) + ent->movedir[2] = 1.0; + else if (ent->spawnflags & 8) + ent->movedir[0] = 1.0; + else // Z_AXIS + ent->movedir[1] = 1.0; + + // check for reverse rotation + if (ent->spawnflags & 2) + VectorNegate (ent->movedir, ent->movedir); + + if (!ent->speed) + ent->speed = 100; + if (!ent->dmg) + ent->dmg = 2; + +// ent->moveinfo.sound_middle = "doors/hydro1.wav"; + + ent->use = rotating_use; + if (ent->dmg) + ent->blocked = rotating_blocked; + + if (ent->spawnflags & 1) + ent->use (ent, NULL, NULL); + + if (ent->spawnflags & 64) + ent->s.effects |= EF_ANIM_ALL; + if (ent->spawnflags & 128) + ent->s.effects |= EF_ANIM_ALLFAST; + + gi.setmodel (ent, ent->model); + gi.linkentity (ent); +} + +/* +====================================================================== + +BUTTONS + +====================================================================== +*/ + +/*QUAKED func_button (0 .5 .8) ? +When a button is touched, it moves some distance in the direction of it's angle, triggers all of it's targets, waits some time, then returns to it's original position where it can be triggered again. + +"angle" determines the opening direction +"target" all entities with a matching targetname will be used +"speed" override the default 40 speed +"wait" override the default 1 second wait (-1 = never return) +"lip" override the default 4 pixel lip remaining at end of move +"health" if set, the button must be killed instead of touched +"sounds" +1) silent +2) steam metal +3) wooden clunk +4) metallic click +5) in-out +*/ + +void button_done (edict_t *self) +{ + self->moveinfo.state = STATE_BOTTOM; + self->s.effects &= ~EF_ANIM23; + self->s.effects |= EF_ANIM01; +} + +void button_return (edict_t *self) +{ + self->moveinfo.state = STATE_DOWN; + + Move_Calc (self, self->moveinfo.start_origin, button_done); + + self->s.frame = 0; + + if (self->health) + self->takedamage = DAMAGE_YES; +} + +void button_wait (edict_t *self) +{ + self->moveinfo.state = STATE_TOP; + self->s.effects &= ~EF_ANIM01; + self->s.effects |= EF_ANIM23; + + G_UseTargets (self, self->activator); + self->s.frame = 1; + if (self->moveinfo.wait >= 0) + { + self->nextthink = level.time + self->moveinfo.wait; + self->think = button_return; + } +} + +void button_fire (edict_t *self) +{ + if (self->moveinfo.state == STATE_UP || self->moveinfo.state == STATE_TOP) + return; + + self->moveinfo.state = STATE_UP; + if (self->moveinfo.sound_start && !(self->flags & FL_TEAMSLAVE)) + gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_start, 1, ATTN_STATIC, 0); + Move_Calc (self, self->moveinfo.end_origin, button_wait); +} + +void button_use (edict_t *self, edict_t *other, edict_t *activator) +{ + self->activator = activator; + button_fire (self); +} + +void button_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (!other->client) + return; + + if (other->health <= 0) + return; + + self->activator = other; + button_fire (self); +} + +void button_killed (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + self->activator = attacker; + self->health = self->max_health; + self->takedamage = DAMAGE_NO; + button_fire (self); +} + +void SP_func_button (edict_t *ent) +{ + vec3_t abs_movedir; + float dist; + + G_SetMovedir (ent->s.angles, ent->movedir); + ent->movetype = MOVETYPE_STOP; + ent->solid = SOLID_BSP; + gi.setmodel (ent, ent->model); + + if (ent->sounds != 1) + ent->moveinfo.sound_start = gi.soundindex ("switches/butn2.wav"); + + if (!ent->speed) + ent->speed = 40; + if (!ent->accel) + ent->accel = ent->speed; + if (!ent->decel) + ent->decel = ent->speed; + + if (!ent->wait) + ent->wait = 3; + if (!st.lip) + st.lip = 4; + + VectorCopy (ent->s.origin, ent->pos1); + abs_movedir[0] = fabs(ent->movedir[0]); + abs_movedir[1] = fabs(ent->movedir[1]); + abs_movedir[2] = fabs(ent->movedir[2]); + dist = abs_movedir[0] * ent->size[0] + abs_movedir[1] * ent->size[1] + abs_movedir[2] * ent->size[2] - st.lip; + VectorMA (ent->pos1, dist, ent->movedir, ent->pos2); + + ent->use = button_use; + ent->s.effects |= EF_ANIM01; + + if (ent->health) + { + ent->max_health = ent->health; + ent->die = button_killed; + ent->takedamage = DAMAGE_YES; + } + else if (! ent->targetname) + ent->touch = button_touch; + + ent->moveinfo.state = STATE_BOTTOM; + + ent->moveinfo.speed = ent->speed; + ent->moveinfo.accel = ent->accel; + ent->moveinfo.decel = ent->decel; + ent->moveinfo.wait = ent->wait; + VectorCopy (ent->pos1, ent->moveinfo.start_origin); + VectorCopy (ent->s.angles, ent->moveinfo.start_angles); + VectorCopy (ent->pos2, ent->moveinfo.end_origin); + VectorCopy (ent->s.angles, ent->moveinfo.end_angles); + + gi.linkentity (ent); +} + +/* +====================================================================== + +DOORS + + spawn a trigger surrounding the entire team unless it is + allready targeted by another + +====================================================================== +*/ + +/*QUAKED func_door (0 .5 .8) ? START_OPEN x CRUSHER NOMONSTER ANIMATED TOGGLE ANIMATED_FAST +TOGGLE wait in both the start and end states for a trigger event. +START_OPEN the door to moves to its destination when spawned, and operate in reverse. It is used to temporarily or permanently close off an area when triggered (not useful for touch or takedamage doors). +NOMONSTER monsters will not trigger this door + +"message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet +"angle" determines the opening direction +"targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door. +"health" if set, door must be shot open +"speed" movement speed (100 default) +"wait" wait before returning (3 default, -1 = never return) +"lip" lip remaining at end of move (8 default) +"dmg" damage to inflict when blocked (2 default) +"sounds" +1) silent +2) light +3) medium +4) heavy +*/ + +void door_use_areaportals (edict_t *self, qboolean open) +{ + edict_t *t = NULL; + + if (!self->target) + return; + + while ((t = G_Find (t, FOFS(targetname), self->target))) + { + if (Q_stricmp(t->classname, "func_areaportal") == 0) + { + gi.SetAreaPortalState (t->style, open); + } + } +} + +void door_go_down (edict_t *self); + +void door_hit_top (edict_t *self) +{ + if (!(self->flags & FL_TEAMSLAVE)) + { + if (self->moveinfo.sound_end) + gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_end, 1, ATTN_STATIC, 0); + self->s.sound = 0; + } + self->moveinfo.state = STATE_TOP; + if (self->spawnflags & DOOR_TOGGLE) + return; + if (self->moveinfo.wait >= 0) + { + self->think = door_go_down; + self->nextthink = level.time + self->moveinfo.wait; + } +} + +void door_hit_bottom (edict_t *self) +{ + if (!(self->flags & FL_TEAMSLAVE)) + { + if (self->moveinfo.sound_end) + gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_end, 1, ATTN_STATIC, 0); + self->s.sound = 0; + } + self->moveinfo.state = STATE_BOTTOM; + door_use_areaportals (self, false); +} + +void door_go_down (edict_t *self) +{ + if (!(self->flags & FL_TEAMSLAVE)) + { + if (self->moveinfo.sound_start) + gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_start, 1, ATTN_STATIC, 0); + self->s.sound = self->moveinfo.sound_middle; + } + if (self->max_health) + { + self->takedamage = DAMAGE_YES; + self->health = self->max_health; + } + + self->moveinfo.state = STATE_DOWN; + if (strcmp(self->classname, "func_door") == 0) + Move_Calc (self, self->moveinfo.start_origin, door_hit_bottom); + else if (strcmp(self->classname, "func_door_rotating") == 0) + AngleMove_Calc (self, door_hit_bottom); +} + +void door_go_up (edict_t *self, edict_t *activator) +{ + if (self->moveinfo.state == STATE_UP) + return; // already going up + + if (self->moveinfo.state == STATE_TOP) + { // reset top wait time + if (self->moveinfo.wait >= 0) + self->nextthink = level.time + self->moveinfo.wait; + return; + } + + if (!(self->flags & FL_TEAMSLAVE)) + { + if (self->moveinfo.sound_start) + gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_start, 1, ATTN_STATIC, 0); + self->s.sound = self->moveinfo.sound_middle; + } + self->moveinfo.state = STATE_UP; + if (strcmp(self->classname, "func_door") == 0) + Move_Calc (self, self->moveinfo.end_origin, door_hit_top); + else if (strcmp(self->classname, "func_door_rotating") == 0) + AngleMove_Calc (self, door_hit_top); + + G_UseTargets (self, activator); + door_use_areaportals (self, true); +} + +void door_use (edict_t *self, edict_t *other, edict_t *activator) +{ + edict_t *ent; + + if (self->flags & FL_TEAMSLAVE) + return; + + if (self->spawnflags & DOOR_TOGGLE) + { + if (self->moveinfo.state == STATE_UP || self->moveinfo.state == STATE_TOP) + { + // trigger all paired doors + for (ent = self ; ent ; ent = ent->teamchain) + { + ent->message = NULL; + ent->touch = NULL; + door_go_down (ent); + } + return; + } + } + + // trigger all paired doors + for (ent = self ; ent ; ent = ent->teamchain) + { + ent->message = NULL; + ent->touch = NULL; + door_go_up (ent, activator); + } +}; + +void Touch_DoorTrigger (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (other->health <= 0) + return; + + if (!(other->svflags & SVF_MONSTER) && (!other->client)) + return; + + if ((self->owner->spawnflags & DOOR_NOMONSTER) && (other->svflags & SVF_MONSTER)) + return; + + if (level.time < self->touch_debounce_time) + return; + self->touch_debounce_time = level.time + 1.0; + + door_use (self->owner, other, other); +} + +void Think_CalcMoveSpeed (edict_t *self) +{ + edict_t *ent; + float min; + float time; + float newspeed; + float ratio; + float dist; + + if (self->flags & FL_TEAMSLAVE) + return; // only the team master does this + + // find the smallest distance any member of the team will be moving + min = fabs(self->moveinfo.distance); + for (ent = self->teamchain; ent; ent = ent->teamchain) + { + dist = fabs(ent->moveinfo.distance); + if (dist < min) + min = dist; + } + + time = min / self->moveinfo.speed; + + // adjust speeds so they will all complete at the same time + for (ent = self; ent; ent = ent->teamchain) + { + newspeed = fabs(ent->moveinfo.distance) / time; + ratio = newspeed / ent->moveinfo.speed; + if (ent->moveinfo.accel == ent->moveinfo.speed) + ent->moveinfo.accel = newspeed; + else + ent->moveinfo.accel *= ratio; + if (ent->moveinfo.decel == ent->moveinfo.speed) + ent->moveinfo.decel = newspeed; + else + ent->moveinfo.decel *= ratio; + ent->moveinfo.speed = newspeed; + } +} + +void Think_SpawnDoorTrigger (edict_t *ent) +{ + edict_t *other; + vec3_t mins, maxs; + + if (ent->flags & FL_TEAMSLAVE) + return; // only the team leader spawns a trigger + + VectorCopy (ent->absmin, mins); + VectorCopy (ent->absmax, maxs); + + for (other = ent->teamchain ; other ; other=other->teamchain) + { + AddPointToBounds (other->absmin, mins, maxs); + AddPointToBounds (other->absmax, mins, maxs); + } + + // expand + mins[0] -= 60; + mins[1] -= 60; + maxs[0] += 60; + maxs[1] += 60; + + other = G_Spawn (); + VectorCopy (mins, other->mins); + VectorCopy (maxs, other->maxs); + other->owner = ent; + other->solid = SOLID_TRIGGER; + other->movetype = MOVETYPE_NONE; + other->touch = Touch_DoorTrigger; + gi.linkentity (other); + + if (ent->spawnflags & DOOR_START_OPEN) + door_use_areaportals (ent, true); + + Think_CalcMoveSpeed (ent); +} + +void door_blocked (edict_t *self, edict_t *other) +{ + edict_t *ent; + + if (!(other->svflags & SVF_MONSTER) && (!other->client) ) + { + // give it a chance to go away on it's own terms (like gibs) + T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, 100000, 1, 0, MOD_CRUSH); + // if it's still there, nuke it + if (other) + BecomeExplosion1 (other); + return; + } + + T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH); + + if (self->spawnflags & DOOR_CRUSHER) + return; + + +// if a door has a negative wait, it would never come back if blocked, +// so let it just squash the object to death real fast + if (self->moveinfo.wait >= 0) + { + if (self->moveinfo.state == STATE_DOWN) + { + for (ent = self->teammaster ; ent ; ent = ent->teamchain) + door_go_up (ent, ent->activator); + } + else + { + for (ent = self->teammaster ; ent ; ent = ent->teamchain) + door_go_down (ent); + } + } +} + +void door_killed (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + edict_t *ent; + + for (ent = self->teammaster ; ent ; ent = ent->teamchain) + { + ent->health = ent->max_health; + ent->takedamage = DAMAGE_NO; + } + door_use (self->teammaster, attacker, attacker); +} + +void door_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (!other->client) + return; + + if (level.time < self->touch_debounce_time) + return; + self->touch_debounce_time = level.time + 5.0; + + gi.centerprintf (other, "%s", self->message); + gi.sound (other, CHAN_AUTO, gi.soundindex ("misc/talk1.wav"), 1, ATTN_NORM, 0); +} + +void SP_func_door (edict_t *ent) +{ + vec3_t abs_movedir; + + if (ent->sounds != 1) + { + ent->moveinfo.sound_start = gi.soundindex ("doors/dr1_strt.wav"); + ent->moveinfo.sound_middle = gi.soundindex ("doors/dr1_mid.wav"); + ent->moveinfo.sound_end = gi.soundindex ("doors/dr1_end.wav"); + } + + G_SetMovedir (ent->s.angles, ent->movedir); + ent->movetype = MOVETYPE_PUSH; + ent->solid = SOLID_BSP; + gi.setmodel (ent, ent->model); + + ent->blocked = door_blocked; + ent->use = door_use; + + if (!ent->speed) + ent->speed = 100; + if (deathmatch->value) + ent->speed *= 2; + + if (!ent->accel) + ent->accel = ent->speed; + if (!ent->decel) + ent->decel = ent->speed; + + if (!ent->wait) + ent->wait = 3; + if (!st.lip) + st.lip = 8; + if (!ent->dmg) + ent->dmg = 2; + + // calculate second position + VectorCopy (ent->s.origin, ent->pos1); + abs_movedir[0] = fabs(ent->movedir[0]); + abs_movedir[1] = fabs(ent->movedir[1]); + abs_movedir[2] = fabs(ent->movedir[2]); + ent->moveinfo.distance = abs_movedir[0] * ent->size[0] + abs_movedir[1] * ent->size[1] + abs_movedir[2] * ent->size[2] - st.lip; + VectorMA (ent->pos1, ent->moveinfo.distance, ent->movedir, ent->pos2); + + // if it starts open, switch the positions + if (ent->spawnflags & DOOR_START_OPEN) + { + VectorCopy (ent->pos2, ent->s.origin); + VectorCopy (ent->pos1, ent->pos2); + VectorCopy (ent->s.origin, ent->pos1); + } + + ent->moveinfo.state = STATE_BOTTOM; + + if (ent->health) + { + ent->takedamage = DAMAGE_YES; + ent->die = door_killed; + ent->max_health = ent->health; + } + else if (ent->targetname && ent->message) + { + gi.soundindex ("misc/talk.wav"); + ent->touch = door_touch; + } + + ent->moveinfo.speed = ent->speed; + ent->moveinfo.accel = ent->accel; + ent->moveinfo.decel = ent->decel; + ent->moveinfo.wait = ent->wait; + VectorCopy (ent->pos1, ent->moveinfo.start_origin); + VectorCopy (ent->s.angles, ent->moveinfo.start_angles); + VectorCopy (ent->pos2, ent->moveinfo.end_origin); + VectorCopy (ent->s.angles, ent->moveinfo.end_angles); + + if (ent->spawnflags & 16) + ent->s.effects |= EF_ANIM_ALL; + if (ent->spawnflags & 64) + ent->s.effects |= EF_ANIM_ALLFAST; + + // to simplify logic elsewhere, make non-teamed doors into a team of one + if (!ent->team) + ent->teammaster = ent; + + gi.linkentity (ent); + + ent->nextthink = level.time + FRAMETIME; + if (ent->health || ent->targetname) + ent->think = Think_CalcMoveSpeed; + else + ent->think = Think_SpawnDoorTrigger; +} + + +/*QUAKED func_door_rotating (0 .5 .8) ? START_OPEN REVERSE CRUSHER NOMONSTER ANIMATED TOGGLE X_AXIS Y_AXIS +TOGGLE causes the door to wait in both the start and end states for a trigger event. + +START_OPEN the door to moves to its destination when spawned, and operate in reverse. It is used to temporarily or permanently close off an area when triggered (not useful for touch or takedamage doors). +NOMONSTER monsters will not trigger this door + +You need to have an origin brush as part of this entity. The center of that brush will be +the point around which it is rotated. It will rotate around the Z axis by default. You can +check either the X_AXIS or Y_AXIS box to change that. + +"distance" is how many degrees the door will be rotated. +"speed" determines how fast the door moves; default value is 100. + +REVERSE will cause the door to rotate in the opposite direction. + +"message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet +"angle" determines the opening direction +"targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door. +"health" if set, door must be shot open +"speed" movement speed (100 default) +"wait" wait before returning (3 default, -1 = never return) +"dmg" damage to inflict when blocked (2 default) +"sounds" +1) silent +2) light +3) medium +4) heavy +*/ + +void SP_func_door_rotating (edict_t *ent) +{ + VectorClear (ent->s.angles); + + // set the axis of rotation + VectorClear(ent->movedir); + if (ent->spawnflags & DOOR_X_AXIS) + ent->movedir[2] = 1.0; + else if (ent->spawnflags & DOOR_Y_AXIS) + ent->movedir[0] = 1.0; + else // Z_AXIS + ent->movedir[1] = 1.0; + + // check for reverse rotation + if (ent->spawnflags & DOOR_REVERSE) + VectorNegate (ent->movedir, ent->movedir); + + if (!st.distance) + { + gi.dprintf("%s at %s with no distance set\n", ent->classname, vtos(ent->s.origin)); + st.distance = 90; + } + + VectorCopy (ent->s.angles, ent->pos1); + VectorMA (ent->s.angles, st.distance, ent->movedir, ent->pos2); + ent->moveinfo.distance = st.distance; + + ent->movetype = MOVETYPE_PUSH; + ent->solid = SOLID_BSP; + gi.setmodel (ent, ent->model); + + ent->blocked = door_blocked; + ent->use = door_use; + + if (!ent->speed) + ent->speed = 100; + if (!ent->accel) + ent->accel = ent->speed; + if (!ent->decel) + ent->decel = ent->speed; + + if (!ent->wait) + ent->wait = 3; + if (!ent->dmg) + ent->dmg = 2; + + if (ent->sounds != 1) + { + ent->moveinfo.sound_start = gi.soundindex ("doors/dr1_strt.wav"); + ent->moveinfo.sound_middle = gi.soundindex ("doors/dr1_mid.wav"); + ent->moveinfo.sound_end = gi.soundindex ("doors/dr1_end.wav"); + } + + // if it starts open, switch the positions + if (ent->spawnflags & DOOR_START_OPEN) + { + VectorCopy (ent->pos2, ent->s.angles); + VectorCopy (ent->pos1, ent->pos2); + VectorCopy (ent->s.angles, ent->pos1); + VectorNegate (ent->movedir, ent->movedir); + } + + if (ent->health) + { + ent->takedamage = DAMAGE_YES; + ent->die = door_killed; + ent->max_health = ent->health; + } + + if (ent->targetname && ent->message) + { + gi.soundindex ("misc/talk.wav"); + ent->touch = door_touch; + } + + ent->moveinfo.state = STATE_BOTTOM; + ent->moveinfo.speed = ent->speed; + ent->moveinfo.accel = ent->accel; + ent->moveinfo.decel = ent->decel; + ent->moveinfo.wait = ent->wait; + VectorCopy (ent->s.origin, ent->moveinfo.start_origin); + VectorCopy (ent->pos1, ent->moveinfo.start_angles); + VectorCopy (ent->s.origin, ent->moveinfo.end_origin); + VectorCopy (ent->pos2, ent->moveinfo.end_angles); + + if (ent->spawnflags & 16) + ent->s.effects |= EF_ANIM_ALL; + + // to simplify logic elsewhere, make non-teamed doors into a team of one + if (!ent->team) + ent->teammaster = ent; + + gi.linkentity (ent); + + ent->nextthink = level.time + FRAMETIME; + if (ent->health || ent->targetname) + ent->think = Think_CalcMoveSpeed; + else + ent->think = Think_SpawnDoorTrigger; +} + + +/*QUAKED func_water (0 .5 .8) ? START_OPEN +func_water is a moveable water brush. It must be targeted to operate. Use a non-water texture at your own risk. + +START_OPEN causes the water to move to its destination when spawned and operate in reverse. + +"angle" determines the opening direction (up or down only) +"speed" movement speed (25 default) +"wait" wait before returning (-1 default, -1 = TOGGLE) +"lip" lip remaining at end of move (0 default) +"sounds" (yes, these need to be changed) +0) no sound +1) water +2) lava +*/ + +void SP_func_water (edict_t *self) +{ + vec3_t abs_movedir; + + G_SetMovedir (self->s.angles, self->movedir); + self->movetype = MOVETYPE_PUSH; + self->solid = SOLID_BSP; + gi.setmodel (self, self->model); + + switch (self->sounds) + { + default: + break; + + case 1: // water + self->moveinfo.sound_start = gi.soundindex ("world/mov_watr.wav"); + self->moveinfo.sound_end = gi.soundindex ("world/stp_watr.wav"); + break; + + case 2: // lava + self->moveinfo.sound_start = gi.soundindex ("world/mov_watr.wav"); + self->moveinfo.sound_end = gi.soundindex ("world/stp_watr.wav"); + break; + } + + // calculate second position + VectorCopy (self->s.origin, self->pos1); + abs_movedir[0] = fabs(self->movedir[0]); + abs_movedir[1] = fabs(self->movedir[1]); + abs_movedir[2] = fabs(self->movedir[2]); + self->moveinfo.distance = abs_movedir[0] * self->size[0] + abs_movedir[1] * self->size[1] + abs_movedir[2] * self->size[2] - st.lip; + VectorMA (self->pos1, self->moveinfo.distance, self->movedir, self->pos2); + + // if it starts open, switch the positions + if (self->spawnflags & DOOR_START_OPEN) + { + VectorCopy (self->pos2, self->s.origin); + VectorCopy (self->pos1, self->pos2); + VectorCopy (self->s.origin, self->pos1); + } + + VectorCopy (self->pos1, self->moveinfo.start_origin); + VectorCopy (self->s.angles, self->moveinfo.start_angles); + VectorCopy (self->pos2, self->moveinfo.end_origin); + VectorCopy (self->s.angles, self->moveinfo.end_angles); + + self->moveinfo.state = STATE_BOTTOM; + + if (!self->speed) + self->speed = 25; + self->moveinfo.accel = self->moveinfo.decel = self->moveinfo.speed = self->speed; + + if (!self->wait) + self->wait = -1; + self->moveinfo.wait = self->wait; + + self->use = door_use; + + if (self->wait == -1) + self->spawnflags |= DOOR_TOGGLE; + + self->classname = "func_door"; + + gi.linkentity (self); +} + + +#define TRAIN_START_ON 1 +#define TRAIN_TOGGLE 2 +#define TRAIN_BLOCK_STOPS 4 + +/*QUAKED func_train (0 .5 .8) ? START_ON TOGGLE BLOCK_STOPS +Trains are moving platforms that players can ride. +The targets origin specifies the min point of the train at each corner. +The train spawns at the first target it is pointing at. +If the train is the target of a button or trigger, it will not begin moving until activated. +speed default 100 +dmg default 2 +noise looping sound to play when the train is in motion + +*/ +void train_next (edict_t *self); + +void train_blocked (edict_t *self, edict_t *other) +{ + if (!(other->svflags & SVF_MONSTER) && (!other->client) ) + { + // give it a chance to go away on it's own terms (like gibs) + T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, 100000, 1, 0, MOD_CRUSH); + // if it's still there, nuke it + if (other) + BecomeExplosion1 (other); + return; + } + + if (level.time < self->touch_debounce_time) + return; + + if (!self->dmg) + return; + self->touch_debounce_time = level.time + 0.5; + T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH); +} + +void train_wait (edict_t *self) +{ + if (self->target_ent->pathtarget) + { + char *savetarget; + edict_t *ent; + + ent = self->target_ent; + savetarget = ent->target; + ent->target = ent->pathtarget; + G_UseTargets (ent, self->activator); + ent->target = savetarget; + + // make sure we didn't get killed by a killtarget + if (!self->inuse) + return; + } + + if (self->moveinfo.wait) + { + if (self->moveinfo.wait > 0) + { + self->nextthink = level.time + self->moveinfo.wait; + self->think = train_next; + } + else if (self->spawnflags & TRAIN_TOGGLE) // && wait < 0 + { + train_next (self); + self->spawnflags &= ~TRAIN_START_ON; + VectorClear (self->velocity); + self->nextthink = 0; + } + + if (!(self->flags & FL_TEAMSLAVE)) + { + if (self->moveinfo.sound_end) + gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_end, 1, ATTN_STATIC, 0); + self->s.sound = 0; + } + } + else + { + train_next (self); + } + +} + +void train_next (edict_t *self) +{ + edict_t *ent; + vec3_t dest; + qboolean first; + + first = true; +again: + if (!self->target) + { +// gi.dprintf ("train_next: no next target\n"); + return; + } + + ent = G_PickTarget (self->target); + if (!ent) + { + gi.dprintf ("train_next: bad target %s\n", self->target); + return; + } + + self->target = ent->target; + + // check for a teleport path_corner + if (ent->spawnflags & 1) + { + if (!first) + { + gi.dprintf ("connected teleport path_corners, see %s at %s\n", ent->classname, vtos(ent->s.origin)); + return; + } + first = false; + VectorSubtract (ent->s.origin, self->mins, self->s.origin); + VectorCopy (self->s.origin, self->s.old_origin); + gi.linkentity (self); + goto again; + } + + self->moveinfo.wait = ent->wait; + self->target_ent = ent; + + if (!(self->flags & FL_TEAMSLAVE)) + { + if (self->moveinfo.sound_start) + gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_start, 1, ATTN_STATIC, 0); + self->s.sound = self->moveinfo.sound_middle; + } + + VectorSubtract (ent->s.origin, self->mins, dest); + self->moveinfo.state = STATE_TOP; + VectorCopy (self->s.origin, self->moveinfo.start_origin); + VectorCopy (dest, self->moveinfo.end_origin); + Move_Calc (self, dest, train_wait); + self->spawnflags |= TRAIN_START_ON; +} + +void train_resume (edict_t *self) +{ + edict_t *ent; + vec3_t dest; + + ent = self->target_ent; + + VectorSubtract (ent->s.origin, self->mins, dest); + self->moveinfo.state = STATE_TOP; + VectorCopy (self->s.origin, self->moveinfo.start_origin); + VectorCopy (dest, self->moveinfo.end_origin); + Move_Calc (self, dest, train_wait); + self->spawnflags |= TRAIN_START_ON; +} + +void func_train_find (edict_t *self) +{ + edict_t *ent; + + if (!self->target) + { + gi.dprintf ("train_find: no target\n"); + return; + } + ent = G_PickTarget (self->target); + if (!ent) + { + gi.dprintf ("train_find: target %s not found\n", self->target); + return; + } + self->target = ent->target; + + VectorSubtract (ent->s.origin, self->mins, self->s.origin); + gi.linkentity (self); + + // if not triggered, start immediately + if (!self->targetname) + self->spawnflags |= TRAIN_START_ON; + + if (self->spawnflags & TRAIN_START_ON) + { + self->nextthink = level.time + FRAMETIME; + self->think = train_next; + self->activator = self; + } +} + +void train_use (edict_t *self, edict_t *other, edict_t *activator) +{ + self->activator = activator; + + if (self->spawnflags & TRAIN_START_ON) + { + if (!(self->spawnflags & TRAIN_TOGGLE)) + return; + self->spawnflags &= ~TRAIN_START_ON; + VectorClear (self->velocity); + self->nextthink = 0; + } + else + { + if (self->target_ent) + train_resume(self); + else + train_next(self); + } +} + +void SP_func_train (edict_t *self) +{ + self->movetype = MOVETYPE_PUSH; + + VectorClear (self->s.angles); + self->blocked = train_blocked; + if (self->spawnflags & TRAIN_BLOCK_STOPS) + self->dmg = 0; + else + { + if (!self->dmg) + self->dmg = 100; + } + self->solid = SOLID_BSP; + gi.setmodel (self, self->model); + + if (st.noise) + self->moveinfo.sound_middle = gi.soundindex (st.noise); + + if (!self->speed) + self->speed = 100; + + self->moveinfo.speed = self->speed; + self->moveinfo.accel = self->moveinfo.decel = self->moveinfo.speed; + + self->use = train_use; + + gi.linkentity (self); + + if (self->target) + { + // start trains on the second frame, to make sure their targets have had + // a chance to spawn + self->nextthink = level.time + FRAMETIME; + self->think = func_train_find; + } + else + { + gi.dprintf ("func_train without a target at %s\n", vtos(self->absmin)); + } +} + + +/*QUAKED trigger_elevator (0.3 0.1 0.6) (-8 -8 -8) (8 8 8) +*/ +void trigger_elevator_use (edict_t *self, edict_t *other, edict_t *activator) +{ + edict_t *target; + + if (self->movetarget->nextthink) + { +// gi.dprintf("elevator busy\n"); + return; + } + + if (!other->pathtarget) + { + gi.dprintf("elevator used with no pathtarget\n"); + return; + } + + target = G_PickTarget (other->pathtarget); + if (!target) + { + gi.dprintf("elevator used with bad pathtarget: %s\n", other->pathtarget); + return; + } + + self->movetarget->target_ent = target; + train_resume (self->movetarget); +} + +void trigger_elevator_init (edict_t *self) +{ + if (!self->target) + { + gi.dprintf("trigger_elevator has no target\n"); + return; + } + self->movetarget = G_PickTarget (self->target); + if (!self->movetarget) + { + gi.dprintf("trigger_elevator unable to find target %s\n", self->target); + return; + } + if (strcmp(self->movetarget->classname, "func_train") != 0) + { + gi.dprintf("trigger_elevator target %s is not a train\n", self->target); + return; + } + + self->use = trigger_elevator_use; + self->svflags = SVF_NOCLIENT; + +} + +void SP_trigger_elevator (edict_t *self) +{ + self->think = trigger_elevator_init; + self->nextthink = level.time + FRAMETIME; +} + + +/*QUAKED func_timer (0.3 0.1 0.6) (-8 -8 -8) (8 8 8) START_ON +"wait" base time between triggering all targets, default is 1 +"random" wait variance, default is 0 + +so, the basic time between firing is a random time between +(wait - random) and (wait + random) + +"delay" delay before first firing when turned on, default is 0 + +"pausetime" additional delay used only the very first time + and only if spawned with START_ON + +These can used but not touched. +*/ +void func_timer_think (edict_t *self) +{ + G_UseTargets (self, self->activator); + self->nextthink = level.time + self->wait + crandom() * self->random; +} + +void func_timer_use (edict_t *self, edict_t *other, edict_t *activator) +{ + self->activator = activator; + + // if on, turn it off + if (self->nextthink) + { + self->nextthink = 0; + return; + } + + // turn it on + if (self->delay) + self->nextthink = level.time + self->delay; + else + func_timer_think (self); +} + +void SP_func_timer (edict_t *self) +{ + if (!self->wait) + self->wait = 1.0; + + self->use = func_timer_use; + self->think = func_timer_think; + + if (self->random >= self->wait) + { + self->random = self->wait - FRAMETIME; + gi.dprintf("func_timer at %s has random >= wait\n", vtos(self->s.origin)); + } + + if (self->spawnflags & 1) + { + self->nextthink = level.time + 1.0 + st.pausetime + self->delay + self->wait + crandom() * self->random; + self->activator = self; + } + + self->svflags = SVF_NOCLIENT; +} + + +/*QUAKED func_conveyor (0 .5 .8) ? START_ON TOGGLE +Conveyors are stationary brushes that move what's on them. +The brush should be have a surface with at least one current content enabled. +speed default 100 +*/ + +void func_conveyor_use (edict_t *self, edict_t *other, edict_t *activator) +{ + if (self->spawnflags & 1) + { + self->speed = 0; + self->spawnflags &= ~1; + } + else + { + self->speed = self->count; + self->spawnflags |= 1; + } + + if (!(self->spawnflags & 2)) + self->count = 0; +} + +void SP_func_conveyor (edict_t *self) +{ + if (!self->speed) + self->speed = 100; + + if (!(self->spawnflags & 1)) + { + self->count = self->speed; + self->speed = 0; + } + + self->use = func_conveyor_use; + + gi.setmodel (self, self->model); + self->solid = SOLID_BSP; + gi.linkentity (self); +} + + +/*QUAKED func_door_secret (0 .5 .8) ? always_shoot 1st_left 1st_down +A secret door. Slide back and then to the side. + +open_once doors never closes +1st_left 1st move is left of arrow +1st_down 1st move is down from arrow +always_shoot door is shootebale even if targeted + +"angle" determines the direction +"dmg" damage to inflic when blocked (default 2) +"wait" how long to hold in the open position (default 5, -1 means hold) +*/ + +#define SECRET_ALWAYS_SHOOT 1 +#define SECRET_1ST_LEFT 2 +#define SECRET_1ST_DOWN 4 + +void door_secret_move1 (edict_t *self); +void door_secret_move2 (edict_t *self); +void door_secret_move3 (edict_t *self); +void door_secret_move4 (edict_t *self); +void door_secret_move5 (edict_t *self); +void door_secret_move6 (edict_t *self); +void door_secret_done (edict_t *self); + +void door_secret_use (edict_t *self, edict_t *other, edict_t *activator) +{ + // make sure we're not already moving + if (!VectorCompare(self->s.origin, vec3_origin)) + return; + + Move_Calc (self, self->pos1, door_secret_move1); + door_use_areaportals (self, true); +} + +void door_secret_move1 (edict_t *self) +{ + self->nextthink = level.time + 1.0; + self->think = door_secret_move2; +} + +void door_secret_move2 (edict_t *self) +{ + Move_Calc (self, self->pos2, door_secret_move3); +} + +void door_secret_move3 (edict_t *self) +{ + if (self->wait == -1) + return; + self->nextthink = level.time + self->wait; + self->think = door_secret_move4; +} + +void door_secret_move4 (edict_t *self) +{ + Move_Calc (self, self->pos1, door_secret_move5); +} + +void door_secret_move5 (edict_t *self) +{ + self->nextthink = level.time + 1.0; + self->think = door_secret_move6; +} + +void door_secret_move6 (edict_t *self) +{ + Move_Calc (self, vec3_origin, door_secret_done); +} + +void door_secret_done (edict_t *self) +{ + if (!(self->targetname) || (self->spawnflags & SECRET_ALWAYS_SHOOT)) + { + self->health = 0; + self->takedamage = DAMAGE_YES; + } + door_use_areaportals (self, false); +} + +void door_secret_blocked (edict_t *self, edict_t *other) +{ + if (!(other->svflags & SVF_MONSTER) && (!other->client) ) + { + // give it a chance to go away on it's own terms (like gibs) + T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, 100000, 1, 0, MOD_CRUSH); + // if it's still there, nuke it + if (other) + BecomeExplosion1 (other); + return; + } + + if (level.time < self->touch_debounce_time) + return; + self->touch_debounce_time = level.time + 0.5; + + T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH); +} + +void door_secret_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + self->takedamage = DAMAGE_NO; + door_secret_use (self, attacker, attacker); +} + +void SP_func_door_secret (edict_t *ent) +{ + vec3_t forward, right, up; + float side; + float width; + float length; + + ent->moveinfo.sound_start = gi.soundindex ("doors/dr1_strt.wav"); + ent->moveinfo.sound_middle = gi.soundindex ("doors/dr1_mid.wav"); + ent->moveinfo.sound_end = gi.soundindex ("doors/dr1_end.wav"); + + ent->movetype = MOVETYPE_PUSH; + ent->solid = SOLID_BSP; + gi.setmodel (ent, ent->model); + + ent->blocked = door_secret_blocked; + ent->use = door_secret_use; + + if (!(ent->targetname) || (ent->spawnflags & SECRET_ALWAYS_SHOOT)) + { + ent->health = 0; + ent->takedamage = DAMAGE_YES; + ent->die = door_secret_die; + } + + if (!ent->dmg) + ent->dmg = 2; + + if (!ent->wait) + ent->wait = 5; + + ent->moveinfo.accel = + ent->moveinfo.decel = + ent->moveinfo.speed = 50; + + // calculate positions + AngleVectors (ent->s.angles, forward, right, up); + VectorClear (ent->s.angles); + side = 1.0 - (ent->spawnflags & SECRET_1ST_LEFT); + if (ent->spawnflags & SECRET_1ST_DOWN) + width = fabs(DotProduct(up, ent->size)); + else + width = fabs(DotProduct(right, ent->size)); + length = fabs(DotProduct(forward, ent->size)); + if (ent->spawnflags & SECRET_1ST_DOWN) + VectorMA (ent->s.origin, -1 * width, up, ent->pos1); + else + VectorMA (ent->s.origin, side * width, right, ent->pos1); + VectorMA (ent->pos1, length, forward, ent->pos2); + + if (ent->health) + { + ent->takedamage = DAMAGE_YES; + ent->die = door_killed; + ent->max_health = ent->health; + } + else if (ent->targetname && ent->message) + { + gi.soundindex ("misc/talk.wav"); + ent->touch = door_touch; + } + + ent->classname = "func_door"; + + gi.linkentity (ent); +} + + +/*QUAKED func_killbox (1 0 0) ? +Kills everything inside when fired, irrespective of protection. +*/ +void use_killbox (edict_t *self, edict_t *other, edict_t *activator) +{ + KillBox (self); +} + +void SP_func_killbox (edict_t *ent) +{ + gi.setmodel (ent, ent->model); + ent->use = use_killbox; + ent->svflags = SVF_NOCLIENT; +} + diff --git a/original/ctf/g_items.c b/original/ctf/g_items.c new file mode 100644 index 0000000..56fe2e6 --- /dev/null +++ b/original/ctf/g_items.c @@ -0,0 +1,2446 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +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. + +*/ +#include "g_local.h" + + +qboolean Pickup_Weapon (edict_t *ent, edict_t *other); +void Use_Weapon (edict_t *ent, gitem_t *inv); +void Drop_Weapon (edict_t *ent, gitem_t *inv); + +void Weapon_Blaster (edict_t *ent); +void Weapon_Shotgun (edict_t *ent); +void Weapon_SuperShotgun (edict_t *ent); +void Weapon_Machinegun (edict_t *ent); +void Weapon_Chaingun (edict_t *ent); +void Weapon_HyperBlaster (edict_t *ent); +void Weapon_RocketLauncher (edict_t *ent); +void Weapon_Grenade (edict_t *ent); +void Weapon_GrenadeLauncher (edict_t *ent); +void Weapon_Railgun (edict_t *ent); +void Weapon_BFG (edict_t *ent); + +gitem_armor_t jacketarmor_info = { 25, 50, .30, .00, ARMOR_JACKET}; +gitem_armor_t combatarmor_info = { 50, 100, .60, .30, ARMOR_COMBAT}; +gitem_armor_t bodyarmor_info = {100, 200, .80, .60, ARMOR_BODY}; + +static int jacket_armor_index; +static int combat_armor_index; +static int body_armor_index; +static int power_screen_index; +static int power_shield_index; + +#define HEALTH_IGNORE_MAX 1 +#define HEALTH_TIMED 2 + +void Use_Quad (edict_t *ent, gitem_t *item); +static int quad_drop_timeout_hack; + +//====================================================================== + +/* +=============== +GetItemByIndex +=============== +*/ +gitem_t *GetItemByIndex (int index) +{ + if (index == 0 || index >= game.num_items) + return NULL; + + return &itemlist[index]; +} + + +/* +=============== +FindItemByClassname + +=============== +*/ +gitem_t *FindItemByClassname (char *classname) +{ + int i; + gitem_t *it; + + it = itemlist; + for (i=0 ; iclassname) + continue; + if (!Q_stricmp(it->classname, classname)) + return it; + } + + return NULL; +} + +/* +=============== +FindItem + +=============== +*/ +gitem_t *FindItem (char *pickup_name) +{ + int i; + gitem_t *it; + + it = itemlist; + for (i=0 ; ipickup_name) + continue; + if (!Q_stricmp(it->pickup_name, pickup_name)) + return it; + } + + return NULL; +} + +//====================================================================== + +void DoRespawn (edict_t *ent) +{ + if (ent->team) + { + edict_t *master; + int count; + int choice; + + master = ent->teammaster; + +//ZOID +//in ctf, when we are weapons stay, only the master of a team of weapons +//is spawned + if (ctf->value && + ((int)dmflags->value & DF_WEAPONS_STAY) && + master->item && (master->item->flags & IT_WEAPON)) + ent = master; + else { +//ZOID + + for (count = 0, ent = master; ent; ent = ent->chain, count++) + ; + + choice = rand() % count; + + for (count = 0, ent = master; count < choice; ent = ent->chain, count++) + ; + } + } + + ent->svflags &= ~SVF_NOCLIENT; + ent->solid = SOLID_TRIGGER; + gi.linkentity (ent); + + // send an effect + ent->s.event = EV_ITEM_RESPAWN; +} + +void SetRespawn (edict_t *ent, float delay) +{ + ent->flags |= FL_RESPAWN; + ent->svflags |= SVF_NOCLIENT; + ent->solid = SOLID_NOT; + ent->nextthink = level.time + delay; + ent->think = DoRespawn; + gi.linkentity (ent); +} + + +//====================================================================== + +qboolean Pickup_Powerup (edict_t *ent, edict_t *other) +{ + int quantity; + + quantity = other->client->pers.inventory[ITEM_INDEX(ent->item)]; + if ((skill->value == 1 && quantity >= 2) || (skill->value >= 2 && quantity >= 1)) + return false; + + if ((coop->value) && (ent->item->flags & IT_STAY_COOP) && (quantity > 0)) + return false; + + other->client->pers.inventory[ITEM_INDEX(ent->item)]++; + + if (deathmatch->value) + { + if (!(ent->spawnflags & DROPPED_ITEM) ) + SetRespawn (ent, ent->item->quantity); + if (((int)dmflags->value & DF_INSTANT_ITEMS) || ((ent->item->use == Use_Quad) && (ent->spawnflags & DROPPED_PLAYER_ITEM))) + { + if ((ent->item->use == Use_Quad) && (ent->spawnflags & DROPPED_PLAYER_ITEM)) + quad_drop_timeout_hack = (ent->nextthink - level.time) / FRAMETIME; + ent->item->use (other, ent->item); + } + } + + return true; +} + +void Drop_General (edict_t *ent, gitem_t *item) +{ + Drop_Item (ent, item); + ent->client->pers.inventory[ITEM_INDEX(item)]--; + ValidateSelectedItem (ent); +} + + +//====================================================================== + +qboolean Pickup_Adrenaline (edict_t *ent, edict_t *other) +{ + if (!deathmatch->value) + other->max_health += 1; + + if (other->health < other->max_health) + other->health = other->max_health; + + if (!(ent->spawnflags & DROPPED_ITEM) && (deathmatch->value)) + SetRespawn (ent, ent->item->quantity); + + return true; +} + +qboolean Pickup_AncientHead (edict_t *ent, edict_t *other) +{ + other->max_health += 2; + + if (!(ent->spawnflags & DROPPED_ITEM) && (deathmatch->value)) + SetRespawn (ent, ent->item->quantity); + + return true; +} + +qboolean Pickup_Bandolier (edict_t *ent, edict_t *other) +{ + gitem_t *item; + int index; + + if (other->client->pers.max_bullets < 250) + other->client->pers.max_bullets = 250; + if (other->client->pers.max_shells < 150) + other->client->pers.max_shells = 150; + if (other->client->pers.max_cells < 250) + other->client->pers.max_cells = 250; + if (other->client->pers.max_slugs < 75) + other->client->pers.max_slugs = 75; + + item = FindItem("Bullets"); + if (item) + { + index = ITEM_INDEX(item); + other->client->pers.inventory[index] += item->quantity; + if (other->client->pers.inventory[index] > other->client->pers.max_bullets) + other->client->pers.inventory[index] = other->client->pers.max_bullets; + } + + item = FindItem("Shells"); + if (item) + { + index = ITEM_INDEX(item); + other->client->pers.inventory[index] += item->quantity; + if (other->client->pers.inventory[index] > other->client->pers.max_shells) + other->client->pers.inventory[index] = other->client->pers.max_shells; + } + + if (!(ent->spawnflags & DROPPED_ITEM) && (deathmatch->value)) + SetRespawn (ent, ent->item->quantity); + + return true; +} + +qboolean Pickup_Pack (edict_t *ent, edict_t *other) +{ + gitem_t *item; + int index; + + if (other->client->pers.max_bullets < 300) + other->client->pers.max_bullets = 300; + if (other->client->pers.max_shells < 200) + other->client->pers.max_shells = 200; + if (other->client->pers.max_rockets < 100) + other->client->pers.max_rockets = 100; + if (other->client->pers.max_grenades < 100) + other->client->pers.max_grenades = 100; + if (other->client->pers.max_cells < 300) + other->client->pers.max_cells = 300; + if (other->client->pers.max_slugs < 100) + other->client->pers.max_slugs = 100; + + item = FindItem("Bullets"); + if (item) + { + index = ITEM_INDEX(item); + other->client->pers.inventory[index] += item->quantity; + if (other->client->pers.inventory[index] > other->client->pers.max_bullets) + other->client->pers.inventory[index] = other->client->pers.max_bullets; + } + + item = FindItem("Shells"); + if (item) + { + index = ITEM_INDEX(item); + other->client->pers.inventory[index] += item->quantity; + if (other->client->pers.inventory[index] > other->client->pers.max_shells) + other->client->pers.inventory[index] = other->client->pers.max_shells; + } + + item = FindItem("Cells"); + if (item) + { + index = ITEM_INDEX(item); + other->client->pers.inventory[index] += item->quantity; + if (other->client->pers.inventory[index] > other->client->pers.max_cells) + other->client->pers.inventory[index] = other->client->pers.max_cells; + } + + item = FindItem("Grenades"); + if (item) + { + index = ITEM_INDEX(item); + other->client->pers.inventory[index] += item->quantity; + if (other->client->pers.inventory[index] > other->client->pers.max_grenades) + other->client->pers.inventory[index] = other->client->pers.max_grenades; + } + + item = FindItem("Rockets"); + if (item) + { + index = ITEM_INDEX(item); + other->client->pers.inventory[index] += item->quantity; + if (other->client->pers.inventory[index] > other->client->pers.max_rockets) + other->client->pers.inventory[index] = other->client->pers.max_rockets; + } + + item = FindItem("Slugs"); + if (item) + { + index = ITEM_INDEX(item); + other->client->pers.inventory[index] += item->quantity; + if (other->client->pers.inventory[index] > other->client->pers.max_slugs) + other->client->pers.inventory[index] = other->client->pers.max_slugs; + } + + if (!(ent->spawnflags & DROPPED_ITEM) && (deathmatch->value)) + SetRespawn (ent, ent->item->quantity); + + return true; +} + +//====================================================================== + +void Use_Quad (edict_t *ent, gitem_t *item) +{ + int timeout; + + ent->client->pers.inventory[ITEM_INDEX(item)]--; + ValidateSelectedItem (ent); + + if (quad_drop_timeout_hack) + { + timeout = quad_drop_timeout_hack; + quad_drop_timeout_hack = 0; + } + else + { + timeout = 300; + } + + if (ent->client->quad_framenum > level.framenum) + ent->client->quad_framenum += timeout; + else + ent->client->quad_framenum = level.framenum + timeout; + + gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage.wav"), 1, ATTN_NORM, 0); +} + +//====================================================================== + +void Use_Breather (edict_t *ent, gitem_t *item) +{ + ent->client->pers.inventory[ITEM_INDEX(item)]--; + ValidateSelectedItem (ent); + + if (ent->client->breather_framenum > level.framenum) + ent->client->breather_framenum += 300; + else + ent->client->breather_framenum = level.framenum + 300; + +// gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage.wav"), 1, ATTN_NORM, 0); +} + +//====================================================================== + +void Use_Envirosuit (edict_t *ent, gitem_t *item) +{ + ent->client->pers.inventory[ITEM_INDEX(item)]--; + ValidateSelectedItem (ent); + + if (ent->client->enviro_framenum > level.framenum) + ent->client->enviro_framenum += 300; + else + ent->client->enviro_framenum = level.framenum + 300; + +// gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage.wav"), 1, ATTN_NORM, 0); +} + +//====================================================================== + +void Use_Invulnerability (edict_t *ent, gitem_t *item) +{ + ent->client->pers.inventory[ITEM_INDEX(item)]--; + ValidateSelectedItem (ent); + + if (ent->client->invincible_framenum > level.framenum) + ent->client->invincible_framenum += 300; + else + ent->client->invincible_framenum = level.framenum + 300; + + gi.sound(ent, CHAN_ITEM, gi.soundindex("items/protect.wav"), 1, ATTN_NORM, 0); +} + +//====================================================================== + +void Use_Silencer (edict_t *ent, gitem_t *item) +{ + ent->client->pers.inventory[ITEM_INDEX(item)]--; + ValidateSelectedItem (ent); + ent->client->silencer_shots += 30; + +// gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage.wav"), 1, ATTN_NORM, 0); +} + +//====================================================================== + +qboolean Pickup_Key (edict_t *ent, edict_t *other) +{ + if (coop->value) + { + if (strcmp(ent->classname, "key_power_cube") == 0) + { + if (other->client->pers.power_cubes & ((ent->spawnflags & 0x0000ff00)>> 8)) + return false; + other->client->pers.inventory[ITEM_INDEX(ent->item)]++; + other->client->pers.power_cubes |= ((ent->spawnflags & 0x0000ff00) >> 8); + } + else + { + if (other->client->pers.inventory[ITEM_INDEX(ent->item)]) + return false; + other->client->pers.inventory[ITEM_INDEX(ent->item)] = 1; + } + return true; + } + other->client->pers.inventory[ITEM_INDEX(ent->item)]++; + return true; +} + +//====================================================================== + +qboolean Add_Ammo (edict_t *ent, gitem_t *item, int count) +{ + int index; + int max; + + if (!ent->client) + return false; + + if (item->tag == AMMO_BULLETS) + max = ent->client->pers.max_bullets; + else if (item->tag == AMMO_SHELLS) + max = ent->client->pers.max_shells; + else if (item->tag == AMMO_ROCKETS) + max = ent->client->pers.max_rockets; + else if (item->tag == AMMO_GRENADES) + max = ent->client->pers.max_grenades; + else if (item->tag == AMMO_CELLS) + max = ent->client->pers.max_cells; + else if (item->tag == AMMO_SLUGS) + max = ent->client->pers.max_slugs; + else + return false; + + index = ITEM_INDEX(item); + + if (ent->client->pers.inventory[index] == max) + return false; + + ent->client->pers.inventory[index] += count; + + if (ent->client->pers.inventory[index] > max) + ent->client->pers.inventory[index] = max; + + return true; +} + +qboolean Pickup_Ammo (edict_t *ent, edict_t *other) +{ + int oldcount; + int count; + qboolean weapon; + + weapon = (ent->item->flags & IT_WEAPON); + if ( (weapon) && ( (int)dmflags->value & DF_INFINITE_AMMO ) ) + count = 1000; + else if (ent->count) + count = ent->count; + else + count = ent->item->quantity; + + oldcount = other->client->pers.inventory[ITEM_INDEX(ent->item)]; + + if (!Add_Ammo (other, ent->item, count)) + return false; + + if (weapon && !oldcount) + { + if (other->client->pers.weapon != ent->item && ( !deathmatch->value || other->client->pers.weapon == FindItem("blaster") ) ) + other->client->newweapon = ent->item; + } + + if (!(ent->spawnflags & (DROPPED_ITEM | DROPPED_PLAYER_ITEM)) && (deathmatch->value)) + SetRespawn (ent, 30); + return true; +} + +void Drop_Ammo (edict_t *ent, gitem_t *item) +{ + edict_t *dropped; + int index; + + index = ITEM_INDEX(item); + dropped = Drop_Item (ent, item); + if (ent->client->pers.inventory[index] >= item->quantity) + dropped->count = item->quantity; + else + dropped->count = ent->client->pers.inventory[index]; + ent->client->pers.inventory[index] -= dropped->count; + ValidateSelectedItem (ent); +} + + +//====================================================================== + +void MegaHealth_think (edict_t *self) +{ + if (self->owner->health > self->owner->max_health +//ZOID + && !CTFHasRegeneration(self->owner) +//ZOID + ) + { + self->nextthink = level.time + 1; + self->owner->health -= 1; + return; + } + + if (!(self->spawnflags & DROPPED_ITEM) && (deathmatch->value)) + SetRespawn (self, 20); + else + G_FreeEdict (self); +} + +qboolean Pickup_Health (edict_t *ent, edict_t *other) +{ + if (!(ent->style & HEALTH_IGNORE_MAX)) + if (other->health >= other->max_health) + return false; + +//ZOID + if (other->health >= 250 && ent->count > 25) + return false; +//ZOID + + other->health += ent->count; + +//ZOID + if (other->health > 250 && ent->count > 25) + other->health = 250; +//ZOID + + if (!(ent->style & HEALTH_IGNORE_MAX)) + { + if (other->health > other->max_health) + other->health = other->max_health; + } + +//ZOID + if ((ent->style & HEALTH_TIMED) + && !CTFHasRegeneration(other) +//ZOID + ) + { + ent->think = MegaHealth_think; + ent->nextthink = level.time + 5; + ent->owner = other; + ent->flags |= FL_RESPAWN; + ent->svflags |= SVF_NOCLIENT; + ent->solid = SOLID_NOT; + } + else + { + if (!(ent->spawnflags & DROPPED_ITEM) && (deathmatch->value)) + SetRespawn (ent, 30); + } + + return true; +} + +//====================================================================== + +int ArmorIndex (edict_t *ent) +{ + if (!ent->client) + return 0; + + if (ent->client->pers.inventory[jacket_armor_index] > 0) + return jacket_armor_index; + + if (ent->client->pers.inventory[combat_armor_index] > 0) + return combat_armor_index; + + if (ent->client->pers.inventory[body_armor_index] > 0) + return body_armor_index; + + return 0; +} + +qboolean Pickup_Armor (edict_t *ent, edict_t *other) +{ + int old_armor_index; + gitem_armor_t *oldinfo; + gitem_armor_t *newinfo; + int newcount; + float salvage; + int salvagecount; + + // get info on new armor + newinfo = (gitem_armor_t *)ent->item->info; + + old_armor_index = ArmorIndex (other); + + // handle armor shards specially + if (ent->item->tag == ARMOR_SHARD) + { + if (!old_armor_index) + other->client->pers.inventory[jacket_armor_index] = 2; + else + other->client->pers.inventory[old_armor_index] += 2; + } + + // if player has no armor, just use it + else if (!old_armor_index) + { + other->client->pers.inventory[ITEM_INDEX(ent->item)] = newinfo->base_count; + } + + // use the better armor + else + { + // get info on old armor + if (old_armor_index == jacket_armor_index) + oldinfo = &jacketarmor_info; + else if (old_armor_index == combat_armor_index) + oldinfo = &combatarmor_info; + else // (old_armor_index == body_armor_index) + oldinfo = &bodyarmor_info; + + if (newinfo->normal_protection > oldinfo->normal_protection) + { + // calc new armor values + salvage = oldinfo->normal_protection / newinfo->normal_protection; + salvagecount = salvage * other->client->pers.inventory[old_armor_index]; + newcount = newinfo->base_count + salvagecount; + if (newcount > newinfo->max_count) + newcount = newinfo->max_count; + + // zero count of old armor so it goes away + other->client->pers.inventory[old_armor_index] = 0; + + // change armor to new item with computed value + other->client->pers.inventory[ITEM_INDEX(ent->item)] = newcount; + } + else + { + // calc new armor values + salvage = newinfo->normal_protection / oldinfo->normal_protection; + salvagecount = salvage * newinfo->base_count; + newcount = other->client->pers.inventory[old_armor_index] + salvagecount; + if (newcount > oldinfo->max_count) + newcount = oldinfo->max_count; + + // if we're already maxed out then we don't need the new armor + if (other->client->pers.inventory[old_armor_index] >= newcount) + return false; + + // update current armor value + other->client->pers.inventory[old_armor_index] = newcount; + } + } + + if (!(ent->spawnflags & DROPPED_ITEM) && (deathmatch->value)) + SetRespawn (ent, 20); + + return true; +} + +//====================================================================== + +int PowerArmorType (edict_t *ent) +{ + if (!ent->client) + return POWER_ARMOR_NONE; + + if (!(ent->flags & FL_POWER_ARMOR)) + return POWER_ARMOR_NONE; + + if (ent->client->pers.inventory[power_shield_index] > 0) + return POWER_ARMOR_SHIELD; + + if (ent->client->pers.inventory[power_screen_index] > 0) + return POWER_ARMOR_SCREEN; + + return POWER_ARMOR_NONE; +} + +void Use_PowerArmor (edict_t *ent, gitem_t *item) +{ + int index; + + if (ent->flags & FL_POWER_ARMOR) + { + ent->flags &= ~FL_POWER_ARMOR; + gi.sound(ent, CHAN_AUTO, gi.soundindex("misc/power2.wav"), 1, ATTN_NORM, 0); + } + else + { + index = ITEM_INDEX(FindItem("cells")); + if (!ent->client->pers.inventory[index]) + { + gi.cprintf (ent, PRINT_HIGH, "No cells for power armor.\n"); + return; + } + ent->flags |= FL_POWER_ARMOR; + gi.sound(ent, CHAN_AUTO, gi.soundindex("misc/power1.wav"), 1, ATTN_NORM, 0); + } +} + +qboolean Pickup_PowerArmor (edict_t *ent, edict_t *other) +{ + int quantity; + + quantity = other->client->pers.inventory[ITEM_INDEX(ent->item)]; + + other->client->pers.inventory[ITEM_INDEX(ent->item)]++; + + if (deathmatch->value) + { + if (!(ent->spawnflags & DROPPED_ITEM) ) + SetRespawn (ent, ent->item->quantity); + // auto-use for DM only if we didn't already have one + if (!quantity) + ent->item->use (other, ent->item); + } + + return true; +} + +void Drop_PowerArmor (edict_t *ent, gitem_t *item) +{ + if ((ent->flags & FL_POWER_ARMOR) && (ent->client->pers.inventory[ITEM_INDEX(item)] == 1)) + Use_PowerArmor (ent, item); + Drop_General (ent, item); +} + +//====================================================================== + +/* +=============== +Touch_Item +=============== +*/ +void Touch_Item (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + qboolean taken; + + if (!other->client) + return; + if (other->health < 1) + return; // dead people can't pickup + if (!ent->item->pickup) + return; // not a grabbable item? + + if (CTFMatchSetup()) + return; // can't pick stuff up right now + + taken = ent->item->pickup(ent, other); + + if (taken) + { + // flash the screen + other->client->bonus_alpha = 0.25; + + // show icon and name on status bar + other->client->ps.stats[STAT_PICKUP_ICON] = gi.imageindex(ent->item->icon); + other->client->ps.stats[STAT_PICKUP_STRING] = CS_ITEMS+ITEM_INDEX(ent->item); + other->client->pickup_msg_time = level.time + 3.0; + + // change selected item + if (ent->item->use) + other->client->pers.selected_item = other->client->ps.stats[STAT_SELECTED_ITEM] = ITEM_INDEX(ent->item); + + if (ent->item->pickup == Pickup_Health) + { + if (ent->count == 2) + gi.sound(other, CHAN_ITEM, gi.soundindex("items/s_health.wav"), 1, ATTN_NORM, 0); + else if (ent->count == 10) + gi.sound(other, CHAN_ITEM, gi.soundindex("items/n_health.wav"), 1, ATTN_NORM, 0); + else if (ent->count == 25) + gi.sound(other, CHAN_ITEM, gi.soundindex("items/l_health.wav"), 1, ATTN_NORM, 0); + else // (ent->count == 100) + gi.sound(other, CHAN_ITEM, gi.soundindex("items/m_health.wav"), 1, ATTN_NORM, 0); + } + else if (ent->item->pickup_sound) + { + gi.sound(other, CHAN_ITEM, gi.soundindex(ent->item->pickup_sound), 1, ATTN_NORM, 0); + } + } + + if (!(ent->spawnflags & ITEM_TARGETS_USED)) + { + G_UseTargets (ent, other); + ent->spawnflags |= ITEM_TARGETS_USED; + } + + if (!taken) + return; + + if (!((coop->value) && (ent->item->flags & IT_STAY_COOP)) || (ent->spawnflags & (DROPPED_ITEM | DROPPED_PLAYER_ITEM))) + { + if (ent->flags & FL_RESPAWN) + ent->flags &= ~FL_RESPAWN; + else + G_FreeEdict (ent); + } +} + +//====================================================================== + +static void drop_temp_touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (other == ent->owner) + return; + + Touch_Item (ent, other, plane, surf); +} + +static void drop_make_touchable (edict_t *ent) +{ + ent->touch = Touch_Item; + if (deathmatch->value) + { + ent->nextthink = level.time + 29; + ent->think = G_FreeEdict; + } +} + +edict_t *Drop_Item (edict_t *ent, gitem_t *item) +{ + edict_t *dropped; + vec3_t forward, right; + vec3_t offset; + + dropped = G_Spawn(); + + dropped->classname = item->classname; + dropped->item = item; + dropped->spawnflags = DROPPED_ITEM; + dropped->s.effects = item->world_model_flags; + dropped->s.renderfx = RF_GLOW; + VectorSet (dropped->mins, -15, -15, -15); + VectorSet (dropped->maxs, 15, 15, 15); + gi.setmodel (dropped, dropped->item->world_model); + dropped->solid = SOLID_TRIGGER; + dropped->movetype = MOVETYPE_TOSS; + dropped->touch = drop_temp_touch; + dropped->owner = ent; + + if (ent->client) + { + trace_t trace; + + AngleVectors (ent->client->v_angle, forward, right, NULL); + VectorSet(offset, 24, 0, -16); + G_ProjectSource (ent->s.origin, offset, forward, right, dropped->s.origin); + trace = gi.trace (ent->s.origin, dropped->mins, dropped->maxs, + dropped->s.origin, ent, CONTENTS_SOLID); + VectorCopy (trace.endpos, dropped->s.origin); + } + else + { + AngleVectors (ent->s.angles, forward, right, NULL); + VectorCopy (ent->s.origin, dropped->s.origin); + } + + VectorScale (forward, 100, dropped->velocity); + dropped->velocity[2] = 300; + + dropped->think = drop_make_touchable; + dropped->nextthink = level.time + 1; + + gi.linkentity (dropped); + + return dropped; +} + +void Use_Item (edict_t *ent, edict_t *other, edict_t *activator) +{ + ent->svflags &= ~SVF_NOCLIENT; + ent->use = NULL; + + if (ent->spawnflags & ITEM_NO_TOUCH) + { + ent->solid = SOLID_BBOX; + ent->touch = NULL; + } + else + { + ent->solid = SOLID_TRIGGER; + ent->touch = Touch_Item; + } + + gi.linkentity (ent); +} + +//====================================================================== + +/* +================ +droptofloor +================ +*/ +void droptofloor (edict_t *ent) +{ + trace_t tr; + vec3_t dest; + float *v; + + v = tv(-15,-15,-15); + VectorCopy (v, ent->mins); + v = tv(15,15,15); + VectorCopy (v, ent->maxs); + + if (ent->model) + gi.setmodel (ent, ent->model); + else + gi.setmodel (ent, ent->item->world_model); + ent->solid = SOLID_TRIGGER; + ent->movetype = MOVETYPE_TOSS; + ent->touch = Touch_Item; + + v = tv(0,0,-128); + VectorAdd (ent->s.origin, v, dest); + + tr = gi.trace (ent->s.origin, ent->mins, ent->maxs, dest, ent, MASK_SOLID); + if (tr.startsolid) + { + gi.dprintf ("droptofloor: %s startsolid at %s\n", ent->classname, vtos(ent->s.origin)); + G_FreeEdict (ent); + return; + } + + VectorCopy (tr.endpos, ent->s.origin); + + if (ent->team) + { + ent->flags &= ~FL_TEAMSLAVE; + ent->chain = ent->teamchain; + ent->teamchain = NULL; + + ent->svflags |= SVF_NOCLIENT; + ent->solid = SOLID_NOT; + if (ent == ent->teammaster) + { + ent->nextthink = level.time + FRAMETIME; + ent->think = DoRespawn; + } + } + + if (ent->spawnflags & ITEM_NO_TOUCH) + { + ent->solid = SOLID_BBOX; + ent->touch = NULL; + ent->s.effects &= ~EF_ROTATE; + ent->s.renderfx &= ~RF_GLOW; + } + + if (ent->spawnflags & ITEM_TRIGGER_SPAWN) + { + ent->svflags |= SVF_NOCLIENT; + ent->solid = SOLID_NOT; + ent->use = Use_Item; + } + + gi.linkentity (ent); +} + + +/* +=============== +PrecacheItem + +Precaches all data needed for a given item. +This will be called for each item spawned in a level, +and for each item in each client's inventory. +=============== +*/ +void PrecacheItem (gitem_t *it) +{ + char *s, *start; + char data[MAX_QPATH]; + int len; + gitem_t *ammo; + + if (!it) + return; + + if (it->pickup_sound) + gi.soundindex (it->pickup_sound); + if (it->world_model) + gi.modelindex (it->world_model); + if (it->view_model) + gi.modelindex (it->view_model); + if (it->icon) + gi.imageindex (it->icon); + + // parse everything for its ammo + if (it->ammo && it->ammo[0]) + { + ammo = FindItem (it->ammo); + if (ammo != it) + PrecacheItem (ammo); + } + + // parse the space seperated precache string for other items + s = it->precaches; + if (!s || !s[0]) + return; + + while (*s) + { + start = s; + while (*s && *s != ' ') + s++; + + len = s-start; + if (len >= MAX_QPATH || len < 5) + gi.error ("PrecacheItem: %s has bad precache string", it->classname); + memcpy (data, start, len); + data[len] = 0; + if (*s) + s++; + + // determine type based on extension + if (!strcmp(data+len-3, "md2")) + gi.modelindex (data); + else if (!strcmp(data+len-3, "sp2")) + gi.modelindex (data); + else if (!strcmp(data+len-3, "wav")) + gi.soundindex (data); + if (!strcmp(data+len-3, "pcx")) + gi.imageindex (data); + } +} + +/* +============ +SpawnItem + +Sets the clipping size and plants the object on the floor. + +Items can't be immediately dropped to floor, because they might +be on an entity that hasn't spawned yet. +============ +*/ +void SpawnItem (edict_t *ent, gitem_t *item) +{ + PrecacheItem (item); + + if (ent->spawnflags) + { + if (strcmp(ent->classname, "key_power_cube") != 0) + { + ent->spawnflags = 0; + gi.dprintf("%s at %s has invalid spawnflags set\n", ent->classname, vtos(ent->s.origin)); + } + } + + // some items will be prevented in deathmatch + if (deathmatch->value) + { + if ( (int)dmflags->value & DF_NO_ARMOR ) + { + if (item->pickup == Pickup_Armor || item->pickup == Pickup_PowerArmor) + { + G_FreeEdict (ent); + return; + } + } + if ( (int)dmflags->value & DF_NO_ITEMS ) + { + if (item->pickup == Pickup_Powerup) + { + G_FreeEdict (ent); + return; + } + } + if ( (int)dmflags->value & DF_NO_HEALTH ) + { + if (item->pickup == Pickup_Health || item->pickup == Pickup_Adrenaline || item->pickup == Pickup_AncientHead) + { + G_FreeEdict (ent); + return; + } + } + if ( (int)dmflags->value & DF_INFINITE_AMMO ) + { + if ( (item->flags == IT_AMMO) || (strcmp(ent->classname, "weapon_bfg") == 0) ) + { + G_FreeEdict (ent); + return; + } + } + } + + if (coop->value && (strcmp(ent->classname, "key_power_cube") == 0)) + { + ent->spawnflags |= (1 << (8 + level.power_cubes)); + level.power_cubes++; + } + + // don't let them drop items that stay in a coop game + if ((coop->value) && (item->flags & IT_STAY_COOP)) + { + item->drop = NULL; + } + +//ZOID +//Don't spawn the flags unless enabled + if (!ctf->value && + (strcmp(ent->classname, "item_flag_team1") == 0 || + strcmp(ent->classname, "item_flag_team2") == 0)) { + G_FreeEdict(ent); + return; + } +//ZOID + + ent->item = item; + ent->nextthink = level.time + 2 * FRAMETIME; // items start after other solids + ent->think = droptofloor; + ent->s.effects = item->world_model_flags; + ent->s.renderfx = RF_GLOW; + if (ent->model) + gi.modelindex (ent->model); + +//ZOID +//flags are server animated and have special handling + if (strcmp(ent->classname, "item_flag_team1") == 0 || + strcmp(ent->classname, "item_flag_team2") == 0) { + ent->think = CTFFlagSetup; + } +//ZOID + +} + +//====================================================================== + +gitem_t itemlist[] = +{ + { + NULL + }, // leave index 0 alone + + // + // ARMOR + // + +/*QUAKED item_armor_body (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "item_armor_body", + Pickup_Armor, + NULL, + NULL, + NULL, + "misc/ar1_pkup.wav", + "models/items/armor/body/tris.md2", EF_ROTATE, + NULL, +/* icon */ "i_bodyarmor", +/* pickup */ "Body Armor", +/* width */ 3, + 0, + NULL, + IT_ARMOR, + 0, + &bodyarmor_info, + ARMOR_BODY, +/* precache */ "" + }, + +/*QUAKED item_armor_combat (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "item_armor_combat", + Pickup_Armor, + NULL, + NULL, + NULL, + "misc/ar1_pkup.wav", + "models/items/armor/combat/tris.md2", EF_ROTATE, + NULL, +/* icon */ "i_combatarmor", +/* pickup */ "Combat Armor", +/* width */ 3, + 0, + NULL, + IT_ARMOR, + 0, + &combatarmor_info, + ARMOR_COMBAT, +/* precache */ "" + }, + +/*QUAKED item_armor_jacket (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "item_armor_jacket", + Pickup_Armor, + NULL, + NULL, + NULL, + "misc/ar1_pkup.wav", + "models/items/armor/jacket/tris.md2", EF_ROTATE, + NULL, +/* icon */ "i_jacketarmor", +/* pickup */ "Jacket Armor", +/* width */ 3, + 0, + NULL, + IT_ARMOR, + 0, + &jacketarmor_info, + ARMOR_JACKET, +/* precache */ "" + }, + +/*QUAKED item_armor_shard (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "item_armor_shard", + Pickup_Armor, + NULL, + NULL, + NULL, + "misc/ar2_pkup.wav", + "models/items/armor/shard/tris.md2", EF_ROTATE, + NULL, +/* icon */ "i_jacketarmor", +/* pickup */ "Armor Shard", +/* width */ 3, + 0, + NULL, + IT_ARMOR, + 0, + NULL, + ARMOR_SHARD, +/* precache */ "" + }, + + +/*QUAKED item_power_screen (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "item_power_screen", + Pickup_PowerArmor, + Use_PowerArmor, + Drop_PowerArmor, + NULL, + "misc/ar3_pkup.wav", + "models/items/armor/screen/tris.md2", EF_ROTATE, + NULL, +/* icon */ "i_powerscreen", +/* pickup */ "Power Screen", +/* width */ 0, + 60, + NULL, + IT_ARMOR, + 0, + NULL, + 0, +/* precache */ "" + }, + +/*QUAKED item_power_shield (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "item_power_shield", + Pickup_PowerArmor, + Use_PowerArmor, + Drop_PowerArmor, + NULL, + "misc/ar3_pkup.wav", + "models/items/armor/shield/tris.md2", EF_ROTATE, + NULL, +/* icon */ "i_powershield", +/* pickup */ "Power Shield", +/* width */ 0, + 60, + NULL, + IT_ARMOR, + 0, + NULL, + 0, +/* precache */ "misc/power2.wav misc/power1.wav" + }, + + + // + // WEAPONS + // + +/* weapon_grapple (.3 .3 1) (-16 -16 -16) (16 16 16) +always owned, never in the world +*/ + { + "weapon_grapple", + NULL, + Use_Weapon, + NULL, + CTFWeapon_Grapple, + "misc/w_pkup.wav", + NULL, 0, + "models/weapons/grapple/tris.md2", +/* icon */ "w_grapple", +/* pickup */ "Grapple", + 0, + 0, + NULL, + IT_WEAPON, + WEAP_GRAPPLE, + NULL, + 0, +/* precache */ "weapons/grapple/grfire.wav weapons/grapple/grpull.wav weapons/grapple/grhang.wav weapons/grapple/grreset.wav weapons/grapple/grhit.wav" + }, + +/* weapon_blaster (.3 .3 1) (-16 -16 -16) (16 16 16) +always owned, never in the world +*/ + { + "weapon_blaster", + NULL, + Use_Weapon, + NULL, + Weapon_Blaster, + "misc/w_pkup.wav", + NULL, 0, + "models/weapons/v_blast/tris.md2", +/* icon */ "w_blaster", +/* pickup */ "Blaster", + 0, + 0, + NULL, + IT_WEAPON|IT_STAY_COOP, + WEAP_BLASTER, + NULL, + 0, +/* precache */ "weapons/blastf1a.wav misc/lasfly.wav" + }, + + +/*QUAKED weapon_shotgun (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "weapon_shotgun", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_Shotgun, + "misc/w_pkup.wav", + "models/weapons/g_shotg/tris.md2", EF_ROTATE, + "models/weapons/v_shotg/tris.md2", +/* icon */ "w_shotgun", +/* pickup */ "Shotgun", + 0, + 1, + "Shells", + IT_WEAPON|IT_STAY_COOP, + WEAP_SHOTGUN, + NULL, + 0, +/* precache */ "weapons/shotgf1b.wav weapons/shotgr1b.wav" + }, + +/*QUAKED weapon_supershotgun (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "weapon_supershotgun", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_SuperShotgun, + "misc/w_pkup.wav", + "models/weapons/g_shotg2/tris.md2", EF_ROTATE, + "models/weapons/v_shotg2/tris.md2", +/* icon */ "w_sshotgun", +/* pickup */ "Super Shotgun", + 0, + 2, + "Shells", + IT_WEAPON|IT_STAY_COOP, + WEAP_SUPERSHOTGUN, + NULL, + 0, +/* precache */ "weapons/sshotf1b.wav" + }, + +/*QUAKED weapon_machinegun (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "weapon_machinegun", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_Machinegun, + "misc/w_pkup.wav", + "models/weapons/g_machn/tris.md2", EF_ROTATE, + "models/weapons/v_machn/tris.md2", +/* icon */ "w_machinegun", +/* pickup */ "Machinegun", + 0, + 1, + "Bullets", + IT_WEAPON|IT_STAY_COOP, + WEAP_MACHINEGUN, + NULL, + 0, +/* precache */ "weapons/machgf1b.wav weapons/machgf2b.wav weapons/machgf3b.wav weapons/machgf4b.wav weapons/machgf5b.wav" + }, + +/*QUAKED weapon_chaingun (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "weapon_chaingun", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_Chaingun, + "misc/w_pkup.wav", + "models/weapons/g_chain/tris.md2", EF_ROTATE, + "models/weapons/v_chain/tris.md2", +/* icon */ "w_chaingun", +/* pickup */ "Chaingun", + 0, + 1, + "Bullets", + IT_WEAPON|IT_STAY_COOP, + WEAP_CHAINGUN, + NULL, + 0, +/* precache */ "weapons/chngnu1a.wav weapons/chngnl1a.wav weapons/machgf3b.wav` weapons/chngnd1a.wav" + }, + +/*QUAKED ammo_grenades (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "ammo_grenades", + Pickup_Ammo, + Use_Weapon, + Drop_Ammo, + Weapon_Grenade, + "misc/am_pkup.wav", + "models/items/ammo/grenades/medium/tris.md2", 0, + "models/weapons/v_handgr/tris.md2", +/* icon */ "a_grenades", +/* pickup */ "Grenades", +/* width */ 3, + 5, + "grenades", + IT_AMMO|IT_WEAPON, + WEAP_GRENADES, + NULL, + AMMO_GRENADES, +/* precache */ "weapons/hgrent1a.wav weapons/hgrena1b.wav weapons/hgrenc1b.wav weapons/hgrenb1a.wav weapons/hgrenb2a.wav " + }, + +/*QUAKED weapon_grenadelauncher (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "weapon_grenadelauncher", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_GrenadeLauncher, + "misc/w_pkup.wav", + "models/weapons/g_launch/tris.md2", EF_ROTATE, + "models/weapons/v_launch/tris.md2", +/* icon */ "w_glauncher", +/* pickup */ "Grenade Launcher", + 0, + 1, + "Grenades", + IT_WEAPON|IT_STAY_COOP, + WEAP_GRENADELAUNCHER, + NULL, + 0, +/* precache */ "models/objects/grenade/tris.md2 weapons/grenlf1a.wav weapons/grenlr1b.wav weapons/grenlb1b.wav" + }, + +/*QUAKED weapon_rocketlauncher (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "weapon_rocketlauncher", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_RocketLauncher, + "misc/w_pkup.wav", + "models/weapons/g_rocket/tris.md2", EF_ROTATE, + "models/weapons/v_rocket/tris.md2", +/* icon */ "w_rlauncher", +/* pickup */ "Rocket Launcher", + 0, + 1, + "Rockets", + IT_WEAPON|IT_STAY_COOP, + WEAP_ROCKETLAUNCHER, + NULL, + 0, +/* precache */ "models/objects/rocket/tris.md2 weapons/rockfly.wav weapons/rocklf1a.wav weapons/rocklr1b.wav models/objects/debris2/tris.md2" + }, + +/*QUAKED weapon_hyperblaster (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "weapon_hyperblaster", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_HyperBlaster, + "misc/w_pkup.wav", + "models/weapons/g_hyperb/tris.md2", EF_ROTATE, + "models/weapons/v_hyperb/tris.md2", +/* icon */ "w_hyperblaster", +/* pickup */ "HyperBlaster", + 0, + 1, + "Cells", + IT_WEAPON|IT_STAY_COOP, + WEAP_HYPERBLASTER, + NULL, + 0, +/* precache */ "weapons/hyprbu1a.wav weapons/hyprbl1a.wav weapons/hyprbf1a.wav weapons/hyprbd1a.wav misc/lasfly.wav" + }, + +/*QUAKED weapon_railgun (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "weapon_railgun", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_Railgun, + "misc/w_pkup.wav", + "models/weapons/g_rail/tris.md2", EF_ROTATE, + "models/weapons/v_rail/tris.md2", +/* icon */ "w_railgun", +/* pickup */ "Railgun", + 0, + 1, + "Slugs", + IT_WEAPON|IT_STAY_COOP, + WEAP_RAILGUN, + NULL, + 0, +/* precache */ "weapons/rg_hum.wav" + }, + +/*QUAKED weapon_bfg (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "weapon_bfg", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_BFG, + "misc/w_pkup.wav", + "models/weapons/g_bfg/tris.md2", EF_ROTATE, + "models/weapons/v_bfg/tris.md2", +/* icon */ "w_bfg", +/* pickup */ "BFG10K", + 0, + 50, + "Cells", + IT_WEAPON|IT_STAY_COOP, + WEAP_BFG, + NULL, + 0, +/* precache */ "sprites/s_bfg1.sp2 sprites/s_bfg2.sp2 sprites/s_bfg3.sp2 weapons/bfg__f1y.wav weapons/bfg__l1a.wav weapons/bfg__x1b.wav weapons/bfg_hum.wav" + }, + +#if 0 +//ZOID +/*QUAKED weapon_laser (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "weapon_laser", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_Laser, + "misc/w_pkup.wav", + "models/weapons/g_laser/tris.md2", EF_ROTATE, + "models/weapons/v_laser/tris.md2", +/* icon */ "w_bfg", +/* pickup */ "Flashlight Laser", + 0, + 1, + "Cells", + IT_WEAPON, + 0, + NULL, + 0, +/* precache */ "" + }, +#endif + + // + // AMMO ITEMS + // + +/*QUAKED ammo_shells (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "ammo_shells", + Pickup_Ammo, + NULL, + Drop_Ammo, + NULL, + "misc/am_pkup.wav", + "models/items/ammo/shells/medium/tris.md2", 0, + NULL, +/* icon */ "a_shells", +/* pickup */ "Shells", +/* width */ 3, + 10, + NULL, + IT_AMMO, + 0, + NULL, + AMMO_SHELLS, +/* precache */ "" + }, + +/*QUAKED ammo_bullets (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "ammo_bullets", + Pickup_Ammo, + NULL, + Drop_Ammo, + NULL, + "misc/am_pkup.wav", + "models/items/ammo/bullets/medium/tris.md2", 0, + NULL, +/* icon */ "a_bullets", +/* pickup */ "Bullets", +/* width */ 3, + 50, + NULL, + IT_AMMO, + 0, + NULL, + AMMO_BULLETS, +/* precache */ "" + }, + +/*QUAKED ammo_cells (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "ammo_cells", + Pickup_Ammo, + NULL, + Drop_Ammo, + NULL, + "misc/am_pkup.wav", + "models/items/ammo/cells/medium/tris.md2", 0, + NULL, +/* icon */ "a_cells", +/* pickup */ "Cells", +/* width */ 3, + 50, + NULL, + IT_AMMO, + 0, + NULL, + AMMO_CELLS, +/* precache */ "" + }, + +/*QUAKED ammo_rockets (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "ammo_rockets", + Pickup_Ammo, + NULL, + Drop_Ammo, + NULL, + "misc/am_pkup.wav", + "models/items/ammo/rockets/medium/tris.md2", 0, + NULL, +/* icon */ "a_rockets", +/* pickup */ "Rockets", +/* width */ 3, + 5, + NULL, + IT_AMMO, + 0, + NULL, + AMMO_ROCKETS, +/* precache */ "" + }, + +/*QUAKED ammo_slugs (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "ammo_slugs", + Pickup_Ammo, + NULL, + Drop_Ammo, + NULL, + "misc/am_pkup.wav", + "models/items/ammo/slugs/medium/tris.md2", 0, + NULL, +/* icon */ "a_slugs", +/* pickup */ "Slugs", +/* width */ 3, + 10, + NULL, + IT_AMMO, + 0, + NULL, + AMMO_SLUGS, +/* precache */ "" + }, + + + // + // POWERUP ITEMS + // +/*QUAKED item_quad (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "item_quad", + Pickup_Powerup, + Use_Quad, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/quaddama/tris.md2", EF_ROTATE, + NULL, +/* icon */ "p_quad", +/* pickup */ "Quad Damage", +/* width */ 2, + 60, + NULL, + IT_POWERUP, + 0, + NULL, + 0, +/* precache */ "items/damage.wav items/damage2.wav items/damage3.wav" + }, + +/*QUAKED item_invulnerability (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "item_invulnerability", + Pickup_Powerup, + Use_Invulnerability, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/invulner/tris.md2", EF_ROTATE, + NULL, +/* icon */ "p_invulnerability", +/* pickup */ "Invulnerability", +/* width */ 2, + 300, + NULL, + IT_POWERUP, + 0, + NULL, + 0, +/* precache */ "items/protect.wav items/protect2.wav items/protect4.wav" + }, + +/*QUAKED item_silencer (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "item_silencer", + Pickup_Powerup, + Use_Silencer, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/silencer/tris.md2", EF_ROTATE, + NULL, +/* icon */ "p_silencer", +/* pickup */ "Silencer", +/* width */ 2, + 60, + NULL, + IT_POWERUP, + 0, + NULL, + 0, +/* precache */ "" + }, + +/*QUAKED item_breather (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "item_breather", + Pickup_Powerup, + Use_Breather, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/breather/tris.md2", EF_ROTATE, + NULL, +/* icon */ "p_rebreather", +/* pickup */ "Rebreather", +/* width */ 2, + 60, + NULL, + IT_STAY_COOP|IT_POWERUP, + 0, + NULL, + 0, +/* precache */ "items/airout.wav" + }, + +/*QUAKED item_enviro (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "item_enviro", + Pickup_Powerup, + Use_Envirosuit, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/enviro/tris.md2", EF_ROTATE, + NULL, +/* icon */ "p_envirosuit", +/* pickup */ "Environment Suit", +/* width */ 2, + 60, + NULL, + IT_STAY_COOP|IT_POWERUP, + 0, + NULL, + 0, +/* precache */ "items/airout.wav" + }, + +/*QUAKED item_ancient_head (.3 .3 1) (-16 -16 -16) (16 16 16) +Special item that gives +2 to maximum health +*/ + { + "item_ancient_head", + Pickup_AncientHead, + NULL, + NULL, + NULL, + "items/pkup.wav", + "models/items/c_head/tris.md2", EF_ROTATE, + NULL, +/* icon */ "i_fixme", +/* pickup */ "Ancient Head", +/* width */ 2, + 60, + NULL, + 0, + 0, + NULL, + 0, +/* precache */ "" + }, + +/*QUAKED item_adrenaline (.3 .3 1) (-16 -16 -16) (16 16 16) +gives +1 to maximum health +*/ + { + "item_adrenaline", + Pickup_Adrenaline, + NULL, + NULL, + NULL, + "items/pkup.wav", + "models/items/adrenal/tris.md2", EF_ROTATE, + NULL, +/* icon */ "p_adrenaline", +/* pickup */ "Adrenaline", +/* width */ 2, + 60, + NULL, + 0, + 0, + NULL, + 0, +/* precache */ "" + }, + +/*QUAKED item_bandolier (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "item_bandolier", + Pickup_Bandolier, + NULL, + NULL, + NULL, + "items/pkup.wav", + "models/items/band/tris.md2", EF_ROTATE, + NULL, +/* icon */ "p_bandolier", +/* pickup */ "Bandolier", +/* width */ 2, + 60, + NULL, + 0, + 0, + NULL, + 0, +/* precache */ "" + }, + +/*QUAKED item_pack (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "item_pack", + Pickup_Pack, + NULL, + NULL, + NULL, + "items/pkup.wav", + "models/items/pack/tris.md2", EF_ROTATE, + NULL, +/* icon */ "i_pack", +/* pickup */ "Ammo Pack", +/* width */ 2, + 180, + NULL, + 0, + 0, + NULL, + 0, +/* precache */ "" + }, + + // + // KEYS + // +/*QUAKED key_data_cd (0 .5 .8) (-16 -16 -16) (16 16 16) +key for computer centers +*/ + { + "key_data_cd", + Pickup_Key, + NULL, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/keys/data_cd/tris.md2", EF_ROTATE, + NULL, + "k_datacd", + "Data CD", + 2, + 0, + NULL, + IT_STAY_COOP|IT_KEY, + 0, + NULL, + 0, +/* precache */ "" + }, + +/*QUAKED key_power_cube (0 .5 .8) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN NO_TOUCH +warehouse circuits +*/ + { + "key_power_cube", + Pickup_Key, + NULL, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/keys/power/tris.md2", EF_ROTATE, + NULL, + "k_powercube", + "Power Cube", + 2, + 0, + NULL, + IT_STAY_COOP|IT_KEY, + 0, + NULL, + 0, +/* precache */ "" + }, + +/*QUAKED key_pyramid (0 .5 .8) (-16 -16 -16) (16 16 16) +key for the entrance of jail3 +*/ + { + "key_pyramid", + Pickup_Key, + NULL, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/keys/pyramid/tris.md2", EF_ROTATE, + NULL, + "k_pyramid", + "Pyramid Key", + 2, + 0, + NULL, + IT_STAY_COOP|IT_KEY, + 0, + NULL, + 0, +/* precache */ "" + }, + +/*QUAKED key_data_spinner (0 .5 .8) (-16 -16 -16) (16 16 16) +key for the city computer +*/ + { + "key_data_spinner", + Pickup_Key, + NULL, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/keys/spinner/tris.md2", EF_ROTATE, + NULL, + "k_dataspin", + "Data Spinner", + 2, + 0, + NULL, + IT_STAY_COOP|IT_KEY, + 0, + NULL, + 0, +/* precache */ "" + }, + +/*QUAKED key_pass (0 .5 .8) (-16 -16 -16) (16 16 16) +security pass for the security level +*/ + { + "key_pass", + Pickup_Key, + NULL, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/keys/pass/tris.md2", EF_ROTATE, + NULL, + "k_security", + "Security Pass", + 2, + 0, + NULL, + IT_STAY_COOP|IT_KEY, + 0, + NULL, + 0, +/* precache */ "" + }, + +/*QUAKED key_blue_key (0 .5 .8) (-16 -16 -16) (16 16 16) +normal door key - blue +*/ + { + "key_blue_key", + Pickup_Key, + NULL, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/keys/key/tris.md2", EF_ROTATE, + NULL, + "k_bluekey", + "Blue Key", + 2, + 0, + NULL, + IT_STAY_COOP|IT_KEY, + 0, + NULL, + 0, +/* precache */ "" + }, + +/*QUAKED key_red_key (0 .5 .8) (-16 -16 -16) (16 16 16) +normal door key - red +*/ + { + "key_red_key", + Pickup_Key, + NULL, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/keys/red_key/tris.md2", EF_ROTATE, + NULL, + "k_redkey", + "Red Key", + 2, + 0, + NULL, + IT_STAY_COOP|IT_KEY, + 0, + NULL, + 0, +/* precache */ "" + }, + +/*QUAKED key_commander_head (0 .5 .8) (-16 -16 -16) (16 16 16) +tank commander's head +*/ + { + "key_commander_head", + Pickup_Key, + NULL, + Drop_General, + NULL, + "items/pkup.wav", + "models/monsters/commandr/head/tris.md2", EF_GIB, + NULL, +/* icon */ "k_comhead", +/* pickup */ "Commander's Head", +/* width */ 2, + 0, + NULL, + IT_STAY_COOP|IT_KEY, + 0, + NULL, + 0, +/* precache */ "" + }, + +/*QUAKED key_airstrike_target (0 .5 .8) (-16 -16 -16) (16 16 16) +tank commander's head +*/ + { + "key_airstrike_target", + Pickup_Key, + NULL, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/keys/target/tris.md2", EF_ROTATE, + NULL, +/* icon */ "i_airstrike", +/* pickup */ "Airstrike Marker", +/* width */ 2, + 0, + NULL, + IT_STAY_COOP|IT_KEY, + 0, + NULL, + 0, +/* precache */ "" + }, + + { + NULL, + Pickup_Health, + NULL, + NULL, + NULL, + "items/pkup.wav", + NULL, 0, + NULL, +/* icon */ "i_health", +/* pickup */ "Health", +/* width */ 3, + 0, + NULL, + 0, + 0, + NULL, + 0, +/* precache */ "items/s_health.wav items/n_health.wav items/l_health.wav items/m_health.wav" + }, + + +//ZOID +/*QUAKED item_flag_team1 (1 0.2 0) (-16 -16 -24) (16 16 32) +*/ + { + "item_flag_team1", + CTFPickup_Flag, + NULL, + CTFDrop_Flag, //Should this be null if we don't want players to drop it manually? + NULL, + "ctf/flagtk.wav", + "players/male/flag1.md2", EF_FLAG1, + NULL, +/* icon */ "i_ctf1", +/* pickup */ "Red Flag", +/* width */ 2, + 0, + NULL, + 0, + 0, + NULL, + 0, +/* precache */ "ctf/flagcap.wav" + }, + +/*QUAKED item_flag_team2 (1 0.2 0) (-16 -16 -24) (16 16 32) +*/ + { + "item_flag_team2", + CTFPickup_Flag, + NULL, + CTFDrop_Flag, //Should this be null if we don't want players to drop it manually? + NULL, + "ctf/flagtk.wav", + "players/male/flag2.md2", EF_FLAG2, + NULL, +/* icon */ "i_ctf2", +/* pickup */ "Blue Flag", +/* width */ 2, + 0, + NULL, + 0, + 0, + NULL, + 0, +/* precache */ "ctf/flagcap.wav" + }, + +/* Resistance Tech */ + { + "item_tech1", + CTFPickup_Tech, + NULL, + CTFDrop_Tech, //Should this be null if we don't want players to drop it manually? + NULL, + "items/pkup.wav", + "models/ctf/resistance/tris.md2", EF_ROTATE, + NULL, +/* icon */ "tech1", +/* pickup */ "Disruptor Shield", +/* width */ 2, + 0, + NULL, + IT_TECH, + 0, + NULL, + 0, +/* precache */ "ctf/tech1.wav" + }, + +/* Strength Tech */ + { + "item_tech2", + CTFPickup_Tech, + NULL, + CTFDrop_Tech, //Should this be null if we don't want players to drop it manually? + NULL, + "items/pkup.wav", + "models/ctf/strength/tris.md2", EF_ROTATE, + NULL, +/* icon */ "tech2", +/* pickup */ "Power Amplifier", +/* width */ 2, + 0, + NULL, + IT_TECH, + 0, + NULL, + 0, +/* precache */ "ctf/tech2.wav ctf/tech2x.wav" + }, + +/* Haste Tech */ + { + "item_tech3", + CTFPickup_Tech, + NULL, + CTFDrop_Tech, //Should this be null if we don't want players to drop it manually? + NULL, + "items/pkup.wav", + "models/ctf/haste/tris.md2", EF_ROTATE, + NULL, +/* icon */ "tech3", +/* pickup */ "Time Accel", +/* width */ 2, + 0, + NULL, + IT_TECH, + 0, + NULL, + 0, +/* precache */ "ctf/tech3.wav" + }, + +/* Regeneration Tech */ + { + "item_tech4", + CTFPickup_Tech, + NULL, + CTFDrop_Tech, //Should this be null if we don't want players to drop it manually? + NULL, + "items/pkup.wav", + "models/ctf/regeneration/tris.md2", EF_ROTATE, + NULL, +/* icon */ "tech4", +/* pickup */ "AutoDoc", +/* width */ 2, + 0, + NULL, + IT_TECH, + 0, + NULL, + 0, +/* precache */ "ctf/tech4.wav" + }, + +//ZOID + + // end of list marker + {NULL} +}; + + +/*QUAKED item_health (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ +void SP_item_health (edict_t *self) +{ + if ( deathmatch->value && ((int)dmflags->value & DF_NO_HEALTH) ) + { + G_FreeEdict (self); + return; + } + + self->model = "models/items/healing/medium/tris.md2"; + self->count = 10; + SpawnItem (self, FindItem ("Health")); + gi.soundindex ("items/n_health.wav"); +} + +/*QUAKED item_health_small (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ +void SP_item_health_small (edict_t *self) +{ + if ( deathmatch->value && ((int)dmflags->value & DF_NO_HEALTH) ) + { + G_FreeEdict (self); + return; + } + + self->model = "models/items/healing/stimpack/tris.md2"; + self->count = 2; + SpawnItem (self, FindItem ("Health")); + self->style = HEALTH_IGNORE_MAX; + gi.soundindex ("items/s_health.wav"); +} + +/*QUAKED item_health_large (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ +void SP_item_health_large (edict_t *self) +{ + if ( deathmatch->value && ((int)dmflags->value & DF_NO_HEALTH) ) + { + G_FreeEdict (self); + return; + } + + self->model = "models/items/healing/large/tris.md2"; + self->count = 25; + SpawnItem (self, FindItem ("Health")); + gi.soundindex ("items/l_health.wav"); +} + +/*QUAKED item_health_mega (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ +void SP_item_health_mega (edict_t *self) +{ + if ( deathmatch->value && ((int)dmflags->value & DF_NO_HEALTH) ) + { + G_FreeEdict (self); + return; + } + + self->model = "models/items/mega_h/tris.md2"; + self->count = 100; + SpawnItem (self, FindItem ("Health")); + gi.soundindex ("items/m_health.wav"); + self->style = HEALTH_IGNORE_MAX|HEALTH_TIMED; +} + + +void InitItems (void) +{ + game.num_items = sizeof(itemlist)/sizeof(itemlist[0]) - 1; +} + + + +/* +=============== +SetItemNames + +Called by worldspawn +=============== +*/ +void SetItemNames (void) +{ + int i; + gitem_t *it; + + for (i=0 ; ipickup_name); + } + + jacket_armor_index = ITEM_INDEX(FindItem("Jacket Armor")); + combat_armor_index = ITEM_INDEX(FindItem("Combat Armor")); + body_armor_index = ITEM_INDEX(FindItem("Body Armor")); + power_screen_index = ITEM_INDEX(FindItem("Power Screen")); + power_shield_index = ITEM_INDEX(FindItem("Power Shield")); +} diff --git a/original/ctf/g_local.h b/original/ctf/g_local.h new file mode 100644 index 0000000..ae85a0a --- /dev/null +++ b/original/ctf/g_local.h @@ -0,0 +1,1153 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +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. + +*/ +// g_local.h -- local definitions for game module + +#include "q_shared.h" + +// define GAME_INCLUDE so that game.h does not define the +// short, server-visible gclient_t and edict_t structures, +// because we define the full size ones in this file +#define GAME_INCLUDE +#include "game.h" + +//ZOID +#include "p_menu.h" +//ZOID + +// the "gameversion" client command will print this plus compile date +#define GAMEVERSION "baseq2" + +// protocol bytes that can be directly added to messages +#define svc_muzzleflash 1 +#define svc_muzzleflash2 2 +#define svc_temp_entity 3 +#define svc_layout 4 +#define svc_inventory 5 + +//================================================================== + +// view pitching times +#define DAMAGE_TIME 0.5 +#define FALL_TIME 0.3 + + +// edict->spawnflags +// these are set with checkboxes on each entity in the map editor +#define SPAWNFLAG_NOT_EASY 0x00000100 +#define SPAWNFLAG_NOT_MEDIUM 0x00000200 +#define SPAWNFLAG_NOT_HARD 0x00000400 +#define SPAWNFLAG_NOT_DEATHMATCH 0x00000800 +#define SPAWNFLAG_NOT_COOP 0x00001000 + +// edict->flags +#define FL_FLY 0x00000001 +#define FL_SWIM 0x00000002 // implied immunity to drowining +#define FL_IMMUNE_LASER 0x00000004 +#define FL_INWATER 0x00000008 +#define FL_GODMODE 0x00000010 +#define FL_NOTARGET 0x00000020 +#define FL_IMMUNE_SLIME 0x00000040 +#define FL_IMMUNE_LAVA 0x00000080 +#define FL_PARTIALGROUND 0x00000100 // not all corners are valid +#define FL_WATERJUMP 0x00000200 // player jumping out of water +#define FL_TEAMSLAVE 0x00000400 // not the first on the team +#define FL_NO_KNOCKBACK 0x00000800 +#define FL_POWER_ARMOR 0x00001000 // power armor (if any) is active +#define FL_RESPAWN 0x80000000 // used for item respawning + + +#define FRAMETIME 0.1 + +// memory tags to allow dynamic memory to be cleaned up +#define TAG_GAME 765 // clear when unloading the dll +#define TAG_LEVEL 766 // clear when loading a new level + + +#define MELEE_DISTANCE 80 + +#define BODY_QUEUE_SIZE 8 + +typedef enum +{ + DAMAGE_NO, + DAMAGE_YES, // will take damage if hit + DAMAGE_AIM // auto targeting recognizes this +} damage_t; + +typedef enum +{ + WEAPON_READY, + WEAPON_ACTIVATING, + WEAPON_DROPPING, + WEAPON_FIRING +} weaponstate_t; + +typedef enum +{ + AMMO_BULLETS, + AMMO_SHELLS, + AMMO_ROCKETS, + AMMO_GRENADES, + AMMO_CELLS, + AMMO_SLUGS +} ammo_t; + + +//deadflag +#define DEAD_NO 0 +#define DEAD_DYING 1 +#define DEAD_DEAD 2 +#define DEAD_RESPAWNABLE 3 + +//range +#define RANGE_MELEE 0 +#define RANGE_NEAR 1 +#define RANGE_MID 2 +#define RANGE_FAR 3 + +//gib types +#define GIB_ORGANIC 0 +#define GIB_METALLIC 1 + +//monster ai flags +#define AI_STAND_GROUND 0x00000001 +#define AI_TEMP_STAND_GROUND 0x00000002 +#define AI_SOUND_TARGET 0x00000004 +#define AI_LOST_SIGHT 0x00000008 +#define AI_PURSUIT_LAST_SEEN 0x00000010 +#define AI_PURSUE_NEXT 0x00000020 +#define AI_PURSUE_TEMP 0x00000040 +#define AI_HOLD_FRAME 0x00000080 +#define AI_GOOD_GUY 0x00000100 +#define AI_BRUTAL 0x00000200 +#define AI_NOSTEP 0x00000400 +#define AI_DUCKED 0x00000800 +#define AI_COMBAT_POINT 0x00001000 +#define AI_MEDIC 0x00002000 +#define AI_RESURRECTING 0x00004000 + +//monster attack state +#define AS_STRAIGHT 1 +#define AS_SLIDING 2 +#define AS_MELEE 3 +#define AS_MISSILE 4 + +// armor types +#define ARMOR_NONE 0 +#define ARMOR_JACKET 1 +#define ARMOR_COMBAT 2 +#define ARMOR_BODY 3 +#define ARMOR_SHARD 4 + +// power armor types +#define POWER_ARMOR_NONE 0 +#define POWER_ARMOR_SCREEN 1 +#define POWER_ARMOR_SHIELD 2 + +// handedness values +#define RIGHT_HANDED 0 +#define LEFT_HANDED 1 +#define CENTER_HANDED 2 + + +// game.serverflags values +#define SFL_CROSS_TRIGGER_1 0x00000001 +#define SFL_CROSS_TRIGGER_2 0x00000002 +#define SFL_CROSS_TRIGGER_3 0x00000004 +#define SFL_CROSS_TRIGGER_4 0x00000008 +#define SFL_CROSS_TRIGGER_5 0x00000010 +#define SFL_CROSS_TRIGGER_6 0x00000020 +#define SFL_CROSS_TRIGGER_7 0x00000040 +#define SFL_CROSS_TRIGGER_8 0x00000080 +#define SFL_CROSS_TRIGGER_MASK 0x000000ff + + +// noise types for PlayerNoise +#define PNOISE_SELF 0 +#define PNOISE_WEAPON 1 +#define PNOISE_IMPACT 2 + + +// edict->movetype values +typedef enum +{ +MOVETYPE_NONE, // never moves +MOVETYPE_NOCLIP, // origin and angles change with no interaction +MOVETYPE_PUSH, // no clip to world, push on box contact +MOVETYPE_STOP, // no clip to world, stops on box contact + +MOVETYPE_WALK, // gravity +MOVETYPE_STEP, // gravity, special edge handling +MOVETYPE_FLY, +MOVETYPE_TOSS, // gravity +MOVETYPE_FLYMISSILE, // extra size to monsters +MOVETYPE_BOUNCE +} movetype_t; + + + +typedef struct +{ + int base_count; + int max_count; + float normal_protection; + float energy_protection; + int armor; +} gitem_armor_t; + + +// gitem_t->flags +#define IT_WEAPON 1 // use makes active weapon +#define IT_AMMO 2 +#define IT_ARMOR 4 +#define IT_STAY_COOP 8 +#define IT_KEY 16 +#define IT_POWERUP 32 +//ZOID +#define IT_TECH 64 +//ZOID + +// gitem_t->weapmodel for weapons indicates model index +#define WEAP_BLASTER 1 +#define WEAP_SHOTGUN 2 +#define WEAP_SUPERSHOTGUN 3 +#define WEAP_MACHINEGUN 4 +#define WEAP_CHAINGUN 5 +#define WEAP_GRENADES 6 +#define WEAP_GRENADELAUNCHER 7 +#define WEAP_ROCKETLAUNCHER 8 +#define WEAP_HYPERBLASTER 9 +#define WEAP_RAILGUN 10 +#define WEAP_BFG 11 +#define WEAP_GRAPPLE 12 + +typedef struct gitem_s +{ + char *classname; // spawning name + qboolean (*pickup)(struct edict_s *ent, struct edict_s *other); + void (*use)(struct edict_s *ent, struct gitem_s *item); + void (*drop)(struct edict_s *ent, struct gitem_s *item); + void (*weaponthink)(struct edict_s *ent); + char *pickup_sound; + char *world_model; + int world_model_flags; + char *view_model; + + // client side info + char *icon; + char *pickup_name; // for printing on pickup + int count_width; // number of digits to display by icon + + int quantity; // for ammo how much, for weapons how much is used per shot + char *ammo; // for weapons + int flags; // IT_* flags + + int weapmodel; // weapon model index (for weapons) + + void *info; + int tag; + + char *precaches; // string of all models, sounds, and images this item will use +} gitem_t; + + + +// +// this structure is left intact through an entire game +// it should be initialized at dll load time, and read/written to +// the server.ssv file for savegames +// +typedef struct +{ + char helpmessage1[512]; + char helpmessage2[512]; + int helpchanged; // flash F1 icon if non 0, play sound + // and increment only if 1, 2, or 3 + + gclient_t *clients; // [maxclients] + + // can't store spawnpoint in level, because + // it would get overwritten by the savegame restore + char spawnpoint[512]; // needed for coop respawns + + // store latched cvars here that we want to get at often + int maxclients; + int maxentities; + + // cross level triggers + int serverflags; + + // items + int num_items; + + qboolean autosaved; +} game_locals_t; + + +// +// this structure is cleared as each map is entered +// it is read/written to the level.sav file for savegames +// +typedef struct +{ + int framenum; + float time; + + char level_name[MAX_QPATH]; // the descriptive name (Outer Base, etc) + char mapname[MAX_QPATH]; // the server name (base1, etc) + char nextmap[MAX_QPATH]; // go here when fraglimit is hit + char forcemap[MAX_QPATH]; // go here + + // intermission state + float intermissiontime; // time the intermission was started + char *changemap; + int exitintermission; + vec3_t intermission_origin; + vec3_t intermission_angle; + + edict_t *sight_client; // changed once each frame for coop games + + edict_t *sight_entity; + int sight_entity_framenum; + edict_t *sound_entity; + int sound_entity_framenum; + edict_t *sound2_entity; + int sound2_entity_framenum; + + int pic_health; + + int total_secrets; + int found_secrets; + + int total_goals; + int found_goals; + + int total_monsters; + int killed_monsters; + + edict_t *current_entity; // entity running from G_RunFrame + int body_que; // dead bodies + + int power_cubes; // ugly necessity for coop +} level_locals_t; + + +// spawn_temp_t is only used to hold entity field values that +// can be set from the editor, but aren't actualy present +// in edict_t during gameplay +typedef struct +{ + // world vars + char *sky; + float skyrotate; + vec3_t skyaxis; + char *nextmap; + + int lip; + int distance; + int height; + char *noise; + float pausetime; + char *item; + char *gravity; + + float minyaw; + float maxyaw; + float minpitch; + float maxpitch; +} spawn_temp_t; + + +typedef struct +{ + // fixed data + vec3_t start_origin; + vec3_t start_angles; + vec3_t end_origin; + vec3_t end_angles; + + int sound_start; + int sound_middle; + int sound_end; + + float accel; + float speed; + float decel; + float distance; + + float wait; + + // state data + int state; + vec3_t dir; + float current_speed; + float move_speed; + float next_speed; + float remaining_distance; + float decel_distance; + void (*endfunc)(edict_t *); +} moveinfo_t; + + +typedef struct +{ + void (*aifunc)(edict_t *self, float dist); + float dist; + void (*thinkfunc)(edict_t *self); +} mframe_t; + +typedef struct +{ + int firstframe; + int lastframe; + mframe_t *frame; + void (*endfunc)(edict_t *self); +} mmove_t; + +typedef struct +{ + mmove_t *currentmove; + int aiflags; + int nextframe; + float scale; + + void (*stand)(edict_t *self); + void (*idle)(edict_t *self); + void (*search)(edict_t *self); + void (*walk)(edict_t *self); + void (*run)(edict_t *self); + void (*dodge)(edict_t *self, edict_t *other, float eta); + void (*attack)(edict_t *self); + void (*melee)(edict_t *self); + void (*sight)(edict_t *self, edict_t *other); + qboolean (*checkattack)(edict_t *self); + + float pausetime; + float attack_finished; + + vec3_t saved_goal; + float search_time; + float trail_time; + vec3_t last_sighting; + int attack_state; + int lefty; + float idle_time; + int linkcount; + + int power_armor_type; + int power_armor_power; +} monsterinfo_t; + + + +extern game_locals_t game; +extern level_locals_t level; +extern game_import_t gi; +extern game_export_t globals; +extern spawn_temp_t st; + +extern int sm_meat_index; +extern int snd_fry; + +extern int jacket_armor_index; +extern int combat_armor_index; +extern int body_armor_index; + + +// means of death +#define MOD_UNKNOWN 0 +#define MOD_BLASTER 1 +#define MOD_SHOTGUN 2 +#define MOD_SSHOTGUN 3 +#define MOD_MACHINEGUN 4 +#define MOD_CHAINGUN 5 +#define MOD_GRENADE 6 +#define MOD_G_SPLASH 7 +#define MOD_ROCKET 8 +#define MOD_R_SPLASH 9 +#define MOD_HYPERBLASTER 10 +#define MOD_RAILGUN 11 +#define MOD_BFG_LASER 12 +#define MOD_BFG_BLAST 13 +#define MOD_BFG_EFFECT 14 +#define MOD_HANDGRENADE 15 +#define MOD_HG_SPLASH 16 +#define MOD_WATER 17 +#define MOD_SLIME 18 +#define MOD_LAVA 19 +#define MOD_CRUSH 20 +#define MOD_TELEFRAG 21 +#define MOD_FALLING 22 +#define MOD_SUICIDE 23 +#define MOD_HELD_GRENADE 24 +#define MOD_EXPLOSIVE 25 +#define MOD_BARREL 26 +#define MOD_BOMB 27 +#define MOD_EXIT 28 +#define MOD_SPLASH 29 +#define MOD_TARGET_LASER 30 +#define MOD_TRIGGER_HURT 31 +#define MOD_HIT 32 +#define MOD_TARGET_BLASTER 33 +#define MOD_GRAPPLE 34 +#define MOD_FRIENDLY_FIRE 0x8000000 + +extern int meansOfDeath; + + +extern edict_t *g_edicts; + +#define FOFS(x) (int)&(((edict_t *)0)->x) +#define STOFS(x) (int)&(((spawn_temp_t *)0)->x) +#define LLOFS(x) (int)&(((level_locals_t *)0)->x) +#define CLOFS(x) (int)&(((gclient_t *)0)->x) + +#define random() ((rand () & 0x7fff) / ((float)0x7fff)) +#define crandom() (2.0 * (random() - 0.5)) + +extern cvar_t *maxentities; +extern cvar_t *deathmatch; +extern cvar_t *coop; +extern cvar_t *dmflags; +extern cvar_t *skill; +extern cvar_t *fraglimit; +extern cvar_t *timelimit; +//ZOID +extern cvar_t *capturelimit; +extern cvar_t *instantweap; +//ZOID +extern cvar_t *password; +extern cvar_t *g_select_empty; +extern cvar_t *dedicated; + +extern cvar_t *filterban; + +extern cvar_t *sv_gravity; +extern cvar_t *sv_maxvelocity; + +extern cvar_t *gun_x, *gun_y, *gun_z; +extern cvar_t *sv_rollspeed; +extern cvar_t *sv_rollangle; + +extern cvar_t *run_pitch; +extern cvar_t *run_roll; +extern cvar_t *bob_up; +extern cvar_t *bob_pitch; +extern cvar_t *bob_roll; + +extern cvar_t *sv_cheats; +extern cvar_t *maxclients; + +extern cvar_t *flood_msgs; +extern cvar_t *flood_persecond; +extern cvar_t *flood_waitdelay; + +extern cvar_t *sv_maplist; + +//ZOID +extern qboolean is_quad; +//ZOID + +#define world (&g_edicts[0]) + +// item spawnflags +#define ITEM_TRIGGER_SPAWN 0x00000001 +#define ITEM_NO_TOUCH 0x00000002 +// 6 bits reserved for editor flags +// 8 bits used as power cube id bits for coop games +#define DROPPED_ITEM 0x00010000 +#define DROPPED_PLAYER_ITEM 0x00020000 +#define ITEM_TARGETS_USED 0x00040000 + +// +// fields are needed for spawning from the entity string +// and saving / loading games +// +#define FFL_SPAWNTEMP 1 + +typedef enum { + F_INT, + F_FLOAT, + F_LSTRING, // string on disk, pointer in memory, TAG_LEVEL + F_GSTRING, // string on disk, pointer in memory, TAG_GAME + F_VECTOR, + F_ANGLEHACK, + F_EDICT, // index on disk, pointer in memory + F_ITEM, // index on disk, pointer in memory + F_CLIENT, // index on disk, pointer in memory + F_IGNORE +} fieldtype_t; + +typedef struct +{ + char *name; + int ofs; + fieldtype_t type; + int flags; +} field_t; + + +extern field_t fields[]; +extern gitem_t itemlist[]; + + +// +// g_cmds.c +// +qboolean CheckFlood(edict_t *ent); +void Cmd_Help_f (edict_t *ent); +void Cmd_Score_f (edict_t *ent); + +// +// g_items.c +// +void PrecacheItem (gitem_t *it); +void InitItems (void); +void SetItemNames (void); +gitem_t *FindItem (char *pickup_name); +gitem_t *FindItemByClassname (char *classname); +#define ITEM_INDEX(x) ((x)-itemlist) +edict_t *Drop_Item (edict_t *ent, gitem_t *item); +void SetRespawn (edict_t *ent, float delay); +void ChangeWeapon (edict_t *ent); +void SpawnItem (edict_t *ent, gitem_t *item); +void Think_Weapon (edict_t *ent); +int ArmorIndex (edict_t *ent); +int PowerArmorType (edict_t *ent); +gitem_t *GetItemByIndex (int index); +qboolean Add_Ammo (edict_t *ent, gitem_t *item, int count); +void Touch_Item (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf); + +// +// g_utils.c +// +qboolean KillBox (edict_t *ent); +void G_ProjectSource (vec3_t point, vec3_t distance, vec3_t forward, vec3_t right, vec3_t result); +edict_t *G_Find (edict_t *from, int fieldofs, char *match); +edict_t *findradius (edict_t *from, vec3_t org, float rad); +edict_t *G_PickTarget (char *targetname); +void G_UseTargets (edict_t *ent, edict_t *activator); +void G_SetMovedir (vec3_t angles, vec3_t movedir); + +void G_InitEdict (edict_t *e); +edict_t *G_Spawn (void); +void G_FreeEdict (edict_t *e); + +void G_TouchTriggers (edict_t *ent); +void G_TouchSolids (edict_t *ent); + +char *G_CopyString (char *in); + +float *tv (float x, float y, float z); +char *vtos (vec3_t v); + +float vectoyaw (vec3_t vec); +void vectoangles (vec3_t vec, vec3_t angles); + +// +// g_combat.c +// +qboolean OnSameTeam (edict_t *ent1, edict_t *ent2); +qboolean CanDamage (edict_t *targ, edict_t *inflictor); +qboolean CheckTeamDamage (edict_t *targ, edict_t *attacker); +void T_Damage (edict_t *targ, edict_t *inflictor, edict_t *attacker, vec3_t dir, vec3_t point, vec3_t normal, int damage, int knockback, int dflags, int mod); +void T_RadiusDamage (edict_t *inflictor, edict_t *attacker, float damage, edict_t *ignore, float radius, int mod); + +// damage flags +#define DAMAGE_RADIUS 0x00000001 // damage was indirect +#define DAMAGE_NO_ARMOR 0x00000002 // armour does not protect from this damage +#define DAMAGE_ENERGY 0x00000004 // damage is from an energy based weapon +#define DAMAGE_NO_KNOCKBACK 0x00000008 // do not affect velocity, just view angles +#define DAMAGE_BULLET 0x00000010 // damage is from a bullet (used for ricochets) +#define DAMAGE_NO_PROTECTION 0x00000020 // armor, shields, invulnerability, and godmode have no effect + +#define DEFAULT_BULLET_HSPREAD 300 +#define DEFAULT_BULLET_VSPREAD 500 +#define DEFAULT_SHOTGUN_HSPREAD 1000 +#define DEFAULT_SHOTGUN_VSPREAD 500 +#define DEFAULT_DEATHMATCH_SHOTGUN_COUNT 12 +#define DEFAULT_SHOTGUN_COUNT 12 +#define DEFAULT_SSHOTGUN_COUNT 20 + +// +// g_monster.c +// +void monster_fire_bullet (edict_t *self, vec3_t start, vec3_t dir, int damage, int kick, int hspread, int vspread, int flashtype); +void monster_fire_shotgun (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int hspread, int vspread, int count, int flashtype); +void monster_fire_blaster (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int flashtype, int effect); +void monster_fire_grenade (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, int flashtype); +void monster_fire_rocket (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int flashtype); +void monster_fire_railgun (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int flashtype); +void monster_fire_bfg (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, int kick, float damage_radius, int flashtype); +void M_droptofloor (edict_t *ent); +void monster_think (edict_t *self); +void walkmonster_start (edict_t *self); +void swimmonster_start (edict_t *self); +void flymonster_start (edict_t *self); +void AttackFinished (edict_t *self, float time); +void monster_death_use (edict_t *self); +void M_CatagorizePosition (edict_t *ent); +qboolean M_CheckAttack (edict_t *self); +void M_FlyCheck (edict_t *self); +void M_CheckGround (edict_t *ent); + +// +// g_misc.c +// +void ThrowHead (edict_t *self, char *gibname, int damage, int type); +void ThrowClientHead (edict_t *self, int damage); +void ThrowGib (edict_t *self, char *gibname, int damage, int type); +void BecomeExplosion1(edict_t *self); + +// +// g_ai.c +// +void AI_SetSightClient (void); + +void ai_stand (edict_t *self, float dist); +void ai_move (edict_t *self, float dist); +void ai_walk (edict_t *self, float dist); +void ai_turn (edict_t *self, float dist); +void ai_run (edict_t *self, float dist); +void ai_charge (edict_t *self, float dist); +int range (edict_t *self, edict_t *other); + +void FoundTarget (edict_t *self); +qboolean infront (edict_t *self, edict_t *other); +qboolean visible (edict_t *self, edict_t *other); +qboolean FacingIdeal(edict_t *self); + +// +// g_weapon.c +// +void ThrowDebris (edict_t *self, char *modelname, float speed, vec3_t origin); +qboolean fire_hit (edict_t *self, vec3_t aim, int damage, int kick); +void fire_bullet (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int hspread, int vspread, int mod); +void fire_shotgun (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int hspread, int vspread, int count, int mod); +void fire_blaster (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, int effect, qboolean hyper); +void fire_grenade (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, float timer, float damage_radius); +void fire_grenade2 (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, float timer, float damage_radius, qboolean held); +void fire_rocket (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, float damage_radius, int radius_damage); +void fire_rail (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick); +void fire_bfg (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, float damage_radius); + +// +// g_ptrail.c +// +void PlayerTrail_Init (void); +void PlayerTrail_Add (vec3_t spot); +void PlayerTrail_New (vec3_t spot); +edict_t *PlayerTrail_PickFirst (edict_t *self); +edict_t *PlayerTrail_PickNext (edict_t *self); +edict_t *PlayerTrail_LastSpot (void); + + +// +// g_client.c +// +void respawn (edict_t *ent); +void BeginIntermission (edict_t *targ); +void PutClientInServer (edict_t *ent); +void InitClientPersistant (gclient_t *client); +void InitClientResp (gclient_t *client); +void InitBodyQue (void); +void ClientBeginServerFrame (edict_t *ent); +void ClientUserinfoChanged (edict_t *ent, char *userinfo); + +// +// g_player.c +// +void player_pain (edict_t *self, edict_t *other, float kick, int damage); +void player_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point); + +// +// g_svcmds.c +// +void ServerCommand (void); + +// +// p_view.c +// +void ClientEndServerFrame (edict_t *ent); + +// +// p_hud.c +// +void MoveClientToIntermission (edict_t *client); +void G_SetStats (edict_t *ent); +void ValidateSelectedItem (edict_t *ent); +void DeathmatchScoreboardMessage (edict_t *client, edict_t *killer); + +// +// g_pweapon.c +// +void PlayerNoise(edict_t *who, vec3_t where, int type); +void P_ProjectSource (gclient_t *client, vec3_t point, vec3_t distance, vec3_t forward, vec3_t right, vec3_t result); +void Weapon_Generic (edict_t *ent, int FRAME_ACTIVATE_LAST, int FRAME_FIRE_LAST, int FRAME_IDLE_LAST, int FRAME_DEACTIVATE_LAST, int *pause_frames, int *fire_frames, void (*fire)(edict_t *ent)); + +// +// m_move.c +// +qboolean M_CheckBottom (edict_t *ent); +qboolean M_walkmove (edict_t *ent, float yaw, float dist); +void M_MoveToGoal (edict_t *ent, float dist); +void M_ChangeYaw (edict_t *ent); + +// +// g_phys.c +// +void G_RunEntity (edict_t *ent); + +// +// g_main.c +// +void SaveClientData (void); +void FetchClientEntData (edict_t *ent); +void EndDMLevel (void); + +// +// g_svcmds.c +// +qboolean SV_FilterPacket (char *from); + +//============================================================================ + +// client_t->anim_priority +#define ANIM_BASIC 0 // stand / run +#define ANIM_WAVE 1 +#define ANIM_JUMP 2 +#define ANIM_PAIN 3 +#define ANIM_ATTACK 4 +#define ANIM_DEATH 5 +#define ANIM_REVERSE 6 + + +// client data that stays across multiple level loads +typedef struct +{ + char userinfo[MAX_INFO_STRING]; + char netname[16]; + int hand; + + qboolean connected; // a loadgame will leave valid entities that + // just don't have a connection yet + + // values saved and restored from edicts when changing levels + int health; + int max_health; + int savedFlags; + + int selected_item; + int inventory[MAX_ITEMS]; + + // ammo capacities + int max_bullets; + int max_shells; + int max_rockets; + int max_grenades; + int max_cells; + int max_slugs; + + gitem_t *weapon; + gitem_t *lastweapon; + + int power_cubes; // used for tracking the cubes in coop games + int score; // for calculating total unit score in coop games +} client_persistant_t; + +// client data that stays across deathmatch respawns +typedef struct +{ + client_persistant_t coop_respawn; // what to set client->pers to on a respawn + int enterframe; // level.framenum the client entered the game + int score; // frags, etc +//ZOID + int ctf_team; // CTF team + int ctf_state; + float ctf_lasthurtcarrier; + float ctf_lastreturnedflag; + float ctf_flagsince; + float ctf_lastfraggedcarrier; + qboolean id_state; + float lastidtime; + qboolean voted; // for elections + qboolean ready; + qboolean admin; + struct ghost_s *ghost; // for ghost codes +//ZOID + vec3_t cmd_angles; // angles sent over in the last command + int game_helpchanged; + int helpchanged; +} client_respawn_t; + +// this structure is cleared on each PutClientInServer(), +// except for 'client->pers' +struct gclient_s +{ + // known to server + player_state_t ps; // communicated by server to clients + int ping; + + // private to game + client_persistant_t pers; + client_respawn_t resp; + pmove_state_t old_pmove; // for detecting out-of-pmove changes + + qboolean showscores; // set layout stat +//ZOID + qboolean inmenu; // in menu + pmenuhnd_t *menu; // current menu +//ZOID + qboolean showinventory; // set layout stat + qboolean showhelp; + qboolean showhelpicon; + + int ammo_index; + + int buttons; + int oldbuttons; + int latched_buttons; + + qboolean weapon_thunk; + + gitem_t *newweapon; + + // sum up damage over an entire frame, so + // shotgun blasts give a single big kick + int damage_armor; // damage absorbed by armor + int damage_parmor; // damage absorbed by power armor + int damage_blood; // damage taken out of health + int damage_knockback; // impact damage + vec3_t damage_from; // origin for vector calculation + + float killer_yaw; // when dead, look at killer + + weaponstate_t weaponstate; + vec3_t kick_angles; // weapon kicks + vec3_t kick_origin; + float v_dmg_roll, v_dmg_pitch, v_dmg_time; // damage kicks + float fall_time, fall_value; // for view drop on fall + float damage_alpha; + float bonus_alpha; + vec3_t damage_blend; + vec3_t v_angle; // aiming direction + float bobtime; // so off-ground doesn't change it + vec3_t oldviewangles; + vec3_t oldvelocity; + + float next_drown_time; + int old_waterlevel; + int breather_sound; + + int machinegun_shots; // for weapon raising + + // animation vars + int anim_end; + int anim_priority; + qboolean anim_duck; + qboolean anim_run; + + // powerup timers + float quad_framenum; + float invincible_framenum; + float breather_framenum; + float enviro_framenum; + + qboolean grenade_blew_up; + float grenade_time; + int silencer_shots; + int weapon_sound; + + float pickup_msg_time; + + float flood_locktill; // locked from talking + float flood_when[10]; // when messages were said + int flood_whenhead; // head pointer for when said + + float respawn_time; // can respawn when time > this + +//ZOID + void *ctf_grapple; // entity of grapple + int ctf_grapplestate; // true if pulling + float ctf_grapplereleasetime; // time of grapple release + float ctf_regentime; // regen tech + float ctf_techsndtime; + float ctf_lasttechmsg; + edict_t *chase_target; + qboolean update_chase; + float menutime; // time to update menu + qboolean menudirty; +//ZOID +}; + + +struct edict_s +{ + entity_state_t s; + struct gclient_s *client; // NULL if not a player + // the server expects the first part + // of gclient_s to be a player_state_t + // but the rest of it is opaque + + qboolean inuse; + int linkcount; + + // FIXME: move these fields to a server private sv_entity_t + link_t area; // linked to a division node or leaf + + int num_clusters; // if -1, use headnode instead + int clusternums[MAX_ENT_CLUSTERS]; + int headnode; // unused if num_clusters != -1 + int areanum, areanum2; + + //================================ + + int svflags; + vec3_t mins, maxs; + vec3_t absmin, absmax, size; + solid_t solid; + int clipmask; + edict_t *owner; + + + // DO NOT MODIFY ANYTHING ABOVE THIS, THE SERVER + // EXPECTS THE FIELDS IN THAT ORDER! + + //================================ + int movetype; + int flags; + + char *model; + float freetime; // sv.time when the object was freed + + // + // only used locally in game, not by server + // + char *message; + char *classname; + int spawnflags; + + float timestamp; + + float angle; // set in qe3, -1 = up, -2 = down + char *target; + char *targetname; + char *killtarget; + char *team; + char *pathtarget; + char *deathtarget; + char *combattarget; + edict_t *target_ent; + + float speed, accel, decel; + vec3_t movedir; + vec3_t pos1, pos2; + + vec3_t velocity; + vec3_t avelocity; + int mass; + float air_finished; + float gravity; // per entity gravity multiplier (1.0 is normal) + // use for lowgrav artifact, flares + + edict_t *goalentity; + edict_t *movetarget; + float yaw_speed; + float ideal_yaw; + + float nextthink; + void (*prethink) (edict_t *ent); + void (*think)(edict_t *self); + void (*blocked)(edict_t *self, edict_t *other); //move to moveinfo? + void (*touch)(edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf); + void (*use)(edict_t *self, edict_t *other, edict_t *activator); + void (*pain)(edict_t *self, edict_t *other, float kick, int damage); + void (*die)(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point); + + float touch_debounce_time; // are all these legit? do we need more/less of them? + float pain_debounce_time; + float damage_debounce_time; + float fly_sound_debounce_time; //move to clientinfo + float last_move_time; + + int health; + int max_health; + int gib_health; + int deadflag; + qboolean show_hostile; + + float powerarmor_time; + + char *map; // target_changelevel + + int viewheight; // height above origin where eyesight is determined + int takedamage; + int dmg; + int radius_dmg; + float dmg_radius; + int sounds; //make this a spawntemp var? + int count; + + edict_t *chain; + edict_t *enemy; + edict_t *oldenemy; + edict_t *activator; + edict_t *groundentity; + int groundentity_linkcount; + edict_t *teamchain; + edict_t *teammaster; + + edict_t *mynoise; // can go in client only + edict_t *mynoise2; + + int noise_index; + int noise_index2; + float volume; + float attenuation; + + // timing variables + float wait; + float delay; // before firing targets + float random; + + float teleport_time; + + int watertype; + int waterlevel; + + vec3_t move_origin; + vec3_t move_angles; + + // move this to clientinfo? + int light_level; + + int style; // also used as areaportal number + + gitem_t *item; // for bonus items + + // common data blocks + moveinfo_t moveinfo; + monsterinfo_t monsterinfo; +}; + +//ZOID +#include "g_ctf.h" +//ZOID + diff --git a/original/ctf/g_main.c b/original/ctf/g_main.c new file mode 100644 index 0000000..47f37fd --- /dev/null +++ b/original/ctf/g_main.c @@ -0,0 +1,429 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +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. + +*/ + +#include "g_local.h" + +game_locals_t game; +level_locals_t level; +game_import_t gi; +game_export_t globals; +spawn_temp_t st; + +int sm_meat_index; +int snd_fry; +int meansOfDeath; + +edict_t *g_edicts; + +cvar_t *deathmatch; +cvar_t *coop; +cvar_t *dmflags; +cvar_t *skill; +cvar_t *fraglimit; +cvar_t *timelimit; +//ZOID +cvar_t *capturelimit; +cvar_t *instantweap; +//ZOID +cvar_t *password; +cvar_t *maxclients; +cvar_t *maxentities; +cvar_t *g_select_empty; +cvar_t *dedicated; + +cvar_t *filterban; + +cvar_t *sv_maxvelocity; +cvar_t *sv_gravity; + +cvar_t *sv_rollspeed; +cvar_t *sv_rollangle; +cvar_t *gun_x; +cvar_t *gun_y; +cvar_t *gun_z; + +cvar_t *run_pitch; +cvar_t *run_roll; +cvar_t *bob_up; +cvar_t *bob_pitch; +cvar_t *bob_roll; + +cvar_t *sv_cheats; + +cvar_t *flood_msgs; +cvar_t *flood_persecond; +cvar_t *flood_waitdelay; + +cvar_t *sv_maplist; + +void SpawnEntities (char *mapname, char *entities, char *spawnpoint); +void ClientThink (edict_t *ent, usercmd_t *cmd); +qboolean ClientConnect (edict_t *ent, char *userinfo); +void ClientUserinfoChanged (edict_t *ent, char *userinfo); +void ClientDisconnect (edict_t *ent); +void ClientBegin (edict_t *ent); +void ClientCommand (edict_t *ent); +void RunEntity (edict_t *ent); +void WriteGame (char *filename, qboolean autosave); +void ReadGame (char *filename); +void WriteLevel (char *filename); +void ReadLevel (char *filename); +void InitGame (void); +void G_RunFrame (void); + + +//=================================================================== + + +void ShutdownGame (void) +{ + gi.dprintf ("==== ShutdownGame ====\n"); + + gi.FreeTags (TAG_LEVEL); + gi.FreeTags (TAG_GAME); +} + + +/* +================= +GetGameAPI + +Returns a pointer to the structure with all entry points +and global variables +================= +*/ +game_export_t *GetGameAPI (game_import_t *import) +{ + gi = *import; + + globals.apiversion = GAME_API_VERSION; + globals.Init = InitGame; + globals.Shutdown = ShutdownGame; + globals.SpawnEntities = SpawnEntities; + + globals.WriteGame = WriteGame; + globals.ReadGame = ReadGame; + globals.WriteLevel = WriteLevel; + globals.ReadLevel = ReadLevel; + + globals.ClientThink = ClientThink; + globals.ClientConnect = ClientConnect; + globals.ClientUserinfoChanged = ClientUserinfoChanged; + globals.ClientDisconnect = ClientDisconnect; + globals.ClientBegin = ClientBegin; + globals.ClientCommand = ClientCommand; + + globals.RunFrame = G_RunFrame; + + globals.ServerCommand = ServerCommand; + + globals.edict_size = sizeof(edict_t); + + return &globals; +} + +#ifndef GAME_HARD_LINKED +// this is only here so the functions in q_shared.c and q_shwin.c can link +void Sys_Error (char *error, ...) +{ + va_list argptr; + char text[1024]; + + va_start (argptr, error); + vsprintf (text, error, argptr); + va_end (argptr); + + gi.error (ERR_FATAL, "%s", text); +} + +void Com_Printf (char *msg, ...) +{ + va_list argptr; + char text[1024]; + + va_start (argptr, msg); + vsprintf (text, msg, argptr); + va_end (argptr); + + gi.dprintf ("%s", text); +} + +#endif + +//====================================================================== + + +/* +================= +ClientEndServerFrames +================= +*/ +void ClientEndServerFrames (void) +{ + int i; + edict_t *ent; + + // calc the player views now that all pushing + // and damage has been added + for (i=0 ; ivalue ; i++) + { + ent = g_edicts + 1 + i; + if (!ent->inuse || !ent->client) + continue; + ClientEndServerFrame (ent); + } + +} + +/* +================= +CreateTargetChangeLevel + +Returns the created target changelevel +================= +*/ +edict_t *CreateTargetChangeLevel(char *map) +{ + edict_t *ent; + + ent = G_Spawn (); + ent->classname = "target_changelevel"; + Com_sprintf(level.nextmap, sizeof(level.nextmap), "%s", map); + ent->map = level.nextmap; + return ent; +} + +/* +================= +EndDMLevel + +The timelimit or fraglimit has been exceeded +================= +*/ +void EndDMLevel (void) +{ + edict_t *ent; + char *s, *t, *f; + static const char *seps = " ,\n\r"; + + // stay on same level flag + if ((int)dmflags->value & DF_SAME_LEVEL) + { + BeginIntermission (CreateTargetChangeLevel (level.mapname) ); + return; + } + + if (*level.forcemap) { + BeginIntermission (CreateTargetChangeLevel (level.forcemap) ); + return; + } + + // see if it's in the map list + if (*sv_maplist->string) { + s = strdup(sv_maplist->string); + f = NULL; + t = strtok(s, seps); + while (t != NULL) { + if (Q_stricmp(t, level.mapname) == 0) { + // it's in the list, go to the next one + t = strtok(NULL, seps); + if (t == NULL) { // end of list, go to first one + if (f == NULL) // there isn't a first one, same level + BeginIntermission (CreateTargetChangeLevel (level.mapname) ); + else + BeginIntermission (CreateTargetChangeLevel (f) ); + } else + BeginIntermission (CreateTargetChangeLevel (t) ); + free(s); + return; + } + if (!f) + f = t; + t = strtok(NULL, seps); + } + free(s); + } + + if (level.nextmap[0]) // go to a specific map + BeginIntermission (CreateTargetChangeLevel (level.nextmap) ); + else { // search for a changelevel + ent = G_Find (NULL, FOFS(classname), "target_changelevel"); + if (!ent) + { // the map designer didn't include a changelevel, + // so create a fake ent that goes back to the same level + BeginIntermission (CreateTargetChangeLevel (level.mapname) ); + return; + } + BeginIntermission (ent); + } +} + +/* +================= +CheckDMRules +================= +*/ +void CheckDMRules (void) +{ + int i; + gclient_t *cl; + + if (level.intermissiontime) + return; + + if (!deathmatch->value) + return; + +//ZOID + if (ctf->value && CTFCheckRules()) { + EndDMLevel (); + return; + } + if (CTFInMatch()) + return; // no checking in match mode +//ZOID + + if (timelimit->value) + { + if (level.time >= timelimit->value*60) + { + gi.bprintf (PRINT_HIGH, "Timelimit hit.\n"); + EndDMLevel (); + return; + } + } + + if (fraglimit->value) + for (i=0 ; ivalue ; i++) + { + cl = game.clients + i; + if (!g_edicts[i+1].inuse) + continue; + + if (cl->resp.score >= fraglimit->value) + { + gi.bprintf (PRINT_HIGH, "Fraglimit hit.\n"); + EndDMLevel (); + return; + } + } +} + + +/* +============= +ExitLevel +============= +*/ +void ExitLevel (void) +{ + int i; + edict_t *ent; + char command [256]; + + level.exitintermission = 0; + level.intermissiontime = 0; + + if (CTFNextMap()) + return; + + Com_sprintf (command, sizeof(command), "gamemap \"%s\"\n", level.changemap); + gi.AddCommandString (command); + ClientEndServerFrames (); + + level.changemap = NULL; + + // clear some things before going to next level + for (i=0 ; ivalue ; i++) + { + ent = g_edicts + 1 + i; + if (!ent->inuse) + continue; + if (ent->health > ent->client->pers.max_health) + ent->health = ent->client->pers.max_health; + } +} + +/* +================ +G_RunFrame + +Advances the world by 0.1 seconds +================ +*/ +void G_RunFrame (void) +{ + int i; + edict_t *ent; + + level.framenum++; + level.time = level.framenum*FRAMETIME; + + // choose a client for monsters to target this frame + AI_SetSightClient (); + + // exit intermissions + + if (level.exitintermission) + { + ExitLevel (); + return; + } + + // + // treat each object in turn + // even the world gets a chance to think + // + ent = &g_edicts[0]; + for (i=0 ; iinuse) + continue; + + level.current_entity = ent; + + VectorCopy (ent->s.origin, ent->s.old_origin); + + // if the ground entity moved, make sure we are still on it + if ((ent->groundentity) && (ent->groundentity->linkcount != ent->groundentity_linkcount)) + { + ent->groundentity = NULL; + if ( !(ent->flags & (FL_SWIM|FL_FLY)) && (ent->svflags & SVF_MONSTER) ) + { + M_CheckGround (ent); + } + } + + if (i > 0 && i <= maxclients->value) + { + ClientBeginServerFrame (ent); + continue; + } + + G_RunEntity (ent); + } + + // see if it is time to end a deathmatch + CheckDMRules (); + + // build the playerstate_t structures for all players + ClientEndServerFrames (); +} + diff --git a/original/ctf/g_misc.c b/original/ctf/g_misc.c new file mode 100644 index 0000000..ba130ff --- /dev/null +++ b/original/ctf/g_misc.c @@ -0,0 +1,1909 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +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. + +*/ +// g_misc.c + +#include "g_local.h" + + +/*QUAKED func_group (0 0 0) ? +Used to group brushes together just for editor convenience. +*/ + +//===================================================== + +void Use_Areaportal (edict_t *ent, edict_t *other, edict_t *activator) +{ + ent->count ^= 1; // toggle state +// gi.dprintf ("portalstate: %i = %i\n", ent->style, ent->count); + gi.SetAreaPortalState (ent->style, ent->count); +} + +/*QUAKED func_areaportal (0 0 0) ? + +This is a non-visible object that divides the world into +areas that are seperated when this portal is not activated. +Usually enclosed in the middle of a door. +*/ +void SP_func_areaportal (edict_t *ent) +{ + ent->use = Use_Areaportal; + ent->count = 0; // allways start closed; +} + +//===================================================== + + +/* +================= +Misc functions +================= +*/ +void VelocityForDamage (int damage, vec3_t v) +{ + v[0] = 100.0 * crandom(); + v[1] = 100.0 * crandom(); + v[2] = 200.0 + 100.0 * random(); + + if (damage < 50) + VectorScale (v, 0.7, v); + else + VectorScale (v, 1.2, v); +} + +void ClipGibVelocity (edict_t *ent) +{ + if (ent->velocity[0] < -300) + ent->velocity[0] = -300; + else if (ent->velocity[0] > 300) + ent->velocity[0] = 300; + if (ent->velocity[1] < -300) + ent->velocity[1] = -300; + else if (ent->velocity[1] > 300) + ent->velocity[1] = 300; + if (ent->velocity[2] < 200) + ent->velocity[2] = 200; // always some upwards + else if (ent->velocity[2] > 500) + ent->velocity[2] = 500; +} + + +/* +================= +gibs +================= +*/ +void gib_think (edict_t *self) +{ + self->s.frame++; + self->nextthink = level.time + FRAMETIME; + + if (self->s.frame == 10) + { + self->think = G_FreeEdict; + self->nextthink = level.time + 8 + random()*10; + } +} + +void gib_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + vec3_t normal_angles, right; + + if (!self->groundentity) + return; + + self->touch = NULL; + + if (plane) + { + gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/fhit3.wav"), 1, ATTN_NORM, 0); + + vectoangles (plane->normal, normal_angles); + AngleVectors (normal_angles, NULL, right, NULL); + vectoangles (right, self->s.angles); + + if (self->s.modelindex == sm_meat_index) + { + self->s.frame++; + self->think = gib_think; + self->nextthink = level.time + FRAMETIME; + } + } +} + +void gib_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + G_FreeEdict (self); +} + +void ThrowGib (edict_t *self, char *gibname, int damage, int type) +{ + edict_t *gib; + vec3_t vd; + vec3_t origin; + vec3_t size; + float vscale; + + gib = G_Spawn(); + + VectorScale (self->size, 0.5, size); + VectorAdd (self->absmin, size, origin); + gib->s.origin[0] = origin[0] + crandom() * size[0]; + gib->s.origin[1] = origin[1] + crandom() * size[1]; + gib->s.origin[2] = origin[2] + crandom() * size[2]; + + gi.setmodel (gib, gibname); + gib->solid = SOLID_NOT; + gib->s.effects |= EF_GIB; + gib->flags |= FL_NO_KNOCKBACK; + gib->takedamage = DAMAGE_YES; + gib->die = gib_die; + + if (type == GIB_ORGANIC) + { + gib->movetype = MOVETYPE_TOSS; + gib->touch = gib_touch; + vscale = 0.5; + } + else + { + gib->movetype = MOVETYPE_BOUNCE; + vscale = 1.0; + } + + VelocityForDamage (damage, vd); + VectorMA (self->velocity, vscale, vd, gib->velocity); + ClipGibVelocity (gib); + gib->avelocity[0] = random()*600; + gib->avelocity[1] = random()*600; + gib->avelocity[2] = random()*600; + + gib->think = G_FreeEdict; + gib->nextthink = level.time + 10 + random()*10; + + gi.linkentity (gib); +} + +void ThrowHead (edict_t *self, char *gibname, int damage, int type) +{ + vec3_t vd; + float vscale; + + self->s.skinnum = 0; + self->s.frame = 0; + VectorClear (self->mins); + VectorClear (self->maxs); + + self->s.modelindex2 = 0; + gi.setmodel (self, gibname); + self->solid = SOLID_NOT; + self->s.effects |= EF_GIB; + self->s.effects &= ~EF_FLIES; + self->s.sound = 0; + self->flags |= FL_NO_KNOCKBACK; + self->svflags &= ~SVF_MONSTER; + self->takedamage = DAMAGE_YES; + self->die = gib_die; + + if (type == GIB_ORGANIC) + { + self->movetype = MOVETYPE_TOSS; + self->touch = gib_touch; + vscale = 0.5; + } + else + { + self->movetype = MOVETYPE_BOUNCE; + vscale = 1.0; + } + + VelocityForDamage (damage, vd); + VectorMA (self->velocity, vscale, vd, self->velocity); + ClipGibVelocity (self); + + self->avelocity[YAW] = crandom()*600; + + self->think = G_FreeEdict; + self->nextthink = level.time + 10 + random()*10; + + gi.linkentity (self); +} + + +void ThrowClientHead (edict_t *self, int damage) +{ + vec3_t vd; + char *gibname; + + if (rand()&1) + { + gibname = "models/objects/gibs/head2/tris.md2"; + self->s.skinnum = 1; // second skin is player + } + else + { + gibname = "models/objects/gibs/skull/tris.md2"; + self->s.skinnum = 0; + } + + self->s.origin[2] += 32; + self->s.frame = 0; + gi.setmodel (self, gibname); + VectorSet (self->mins, -16, -16, 0); + VectorSet (self->maxs, 16, 16, 16); + + self->takedamage = DAMAGE_NO; + self->solid = SOLID_NOT; + self->s.effects = EF_GIB; + self->s.sound = 0; + self->flags |= FL_NO_KNOCKBACK; + + self->movetype = MOVETYPE_BOUNCE; + VelocityForDamage (damage, vd); + VectorAdd (self->velocity, vd, self->velocity); + + if (self->client) // bodies in the queue don't have a client anymore + { + self->client->anim_priority = ANIM_DEATH; + self->client->anim_end = self->s.frame; + } + + gi.linkentity (self); +} + + +/* +================= +debris +================= +*/ +void debris_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + G_FreeEdict (self); +} + +void ThrowDebris (edict_t *self, char *modelname, float speed, vec3_t origin) +{ + edict_t *chunk; + vec3_t v; + + chunk = G_Spawn(); + VectorCopy (origin, chunk->s.origin); + gi.setmodel (chunk, modelname); + v[0] = 100 * crandom(); + v[1] = 100 * crandom(); + v[2] = 100 + 100 * crandom(); + VectorMA (self->velocity, speed, v, chunk->velocity); + chunk->movetype = MOVETYPE_BOUNCE; + chunk->solid = SOLID_NOT; + chunk->avelocity[0] = random()*600; + chunk->avelocity[1] = random()*600; + chunk->avelocity[2] = random()*600; + chunk->think = G_FreeEdict; + chunk->nextthink = level.time + 5 + random()*5; + chunk->s.frame = 0; + chunk->flags = 0; + chunk->classname = "debris"; + chunk->takedamage = DAMAGE_YES; + chunk->die = debris_die; + gi.linkentity (chunk); +} + + +void BecomeExplosion1 (edict_t *self) +{ +//ZOID + //flags are important + if (strcmp(self->classname, "item_flag_team1") == 0) { + CTFResetFlag(CTF_TEAM1); // this will free self! + gi.bprintf(PRINT_HIGH, "The %s flag has returned!\n", + CTFTeamName(CTF_TEAM1)); + return; + } + if (strcmp(self->classname, "item_flag_team2") == 0) { + CTFResetFlag(CTF_TEAM2); // this will free self! + gi.bprintf(PRINT_HIGH, "The %s flag has returned!\n", + CTFTeamName(CTF_TEAM1)); + return; + } + // techs are important too + if (self->item && (self->item->flags & IT_TECH)) { + CTFRespawnTech(self); // this frees self! + return; + } +//ZOID + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_EXPLOSION1); + gi.WritePosition (self->s.origin); + gi.multicast (self->s.origin, MULTICAST_PVS); + + G_FreeEdict (self); +} + + +void BecomeExplosion2 (edict_t *self) +{ + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_EXPLOSION2); + gi.WritePosition (self->s.origin); + gi.multicast (self->s.origin, MULTICAST_PVS); + + G_FreeEdict (self); +} + + +/*QUAKED path_corner (.5 .3 0) (-8 -8 -8) (8 8 8) TELEPORT +Target: next path corner +Pathtarget: gets used when an entity that has + this path_corner targeted touches it +*/ + +void path_corner_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + vec3_t v; + edict_t *next; + + if (other->movetarget != self) + return; + + if (other->enemy) + return; + + if (self->pathtarget) + { + char *savetarget; + + savetarget = self->target; + self->target = self->pathtarget; + G_UseTargets (self, other); + self->target = savetarget; + } + + if (self->target) + next = G_PickTarget(self->target); + else + next = NULL; + + if ((next) && (next->spawnflags & 1)) + { + VectorCopy (next->s.origin, v); + v[2] += next->mins[2]; + v[2] -= other->mins[2]; + VectorCopy (v, other->s.origin); + next = G_PickTarget(next->target); + } + + other->goalentity = other->movetarget = next; + + if (self->wait) + { + other->monsterinfo.pausetime = level.time + self->wait; + other->monsterinfo.stand (other); + return; + } + + if (!other->movetarget) + { + other->monsterinfo.pausetime = level.time + 100000000; + other->monsterinfo.stand (other); + } + else + { + VectorSubtract (other->goalentity->s.origin, other->s.origin, v); + other->ideal_yaw = vectoyaw (v); + } +} + +void SP_path_corner (edict_t *self) +{ + if (!self->targetname) + { + gi.dprintf ("path_corner with no targetname at %s\n", vtos(self->s.origin)); + G_FreeEdict (self); + return; + } + + self->solid = SOLID_TRIGGER; + self->touch = path_corner_touch; + VectorSet (self->mins, -8, -8, -8); + VectorSet (self->maxs, 8, 8, 8); + self->svflags |= SVF_NOCLIENT; + gi.linkentity (self); +} + + +/*QUAKED point_combat (0.5 0.3 0) (-8 -8 -8) (8 8 8) Hold +Makes this the target of a monster and it will head here +when first activated before going after the activator. If +hold is selected, it will stay here. +*/ +void point_combat_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + edict_t *activator; + + if (other->movetarget != self) + return; + + if (self->target) + { + other->target = self->target; + other->goalentity = other->movetarget = G_PickTarget(other->target); + if (!other->goalentity) + { + gi.dprintf("%s at %s target %s does not exist\n", self->classname, vtos(self->s.origin), self->target); + other->movetarget = self; + } + self->target = NULL; + } + else if ((self->spawnflags & 1) && !(other->flags & (FL_SWIM|FL_FLY))) + { + other->monsterinfo.pausetime = level.time + 100000000; + other->monsterinfo.aiflags |= AI_STAND_GROUND; + other->monsterinfo.stand (other); + } + + if (other->movetarget == self) + { + other->target = NULL; + other->movetarget = NULL; + other->goalentity = other->enemy; + other->monsterinfo.aiflags &= ~AI_COMBAT_POINT; + } + + if (self->pathtarget) + { + char *savetarget; + + savetarget = self->target; + self->target = self->pathtarget; + if (other->enemy && other->enemy->client) + activator = other->enemy; + else if (other->oldenemy && other->oldenemy->client) + activator = other->oldenemy; + else if (other->activator && other->activator->client) + activator = other->activator; + else + activator = other; + G_UseTargets (self, activator); + self->target = savetarget; + } +} + +void SP_point_combat (edict_t *self) +{ + if (deathmatch->value) + { + G_FreeEdict (self); + return; + } + self->solid = SOLID_TRIGGER; + self->touch = point_combat_touch; + VectorSet (self->mins, -8, -8, -16); + VectorSet (self->maxs, 8, 8, 16); + self->svflags = SVF_NOCLIENT; + gi.linkentity (self); +}; + + +/*QUAKED viewthing (0 .5 .8) (-8 -8 -8) (8 8 8) +Just for the debugging level. Don't use +*/ +static int robotron[4]; + +void TH_viewthing(edict_t *ent) +{ + ent->s.frame = (ent->s.frame + 1) % 7; +// ent->s.frame = (ent->s.frame + 1) % 9; + ent->nextthink = level.time + FRAMETIME; +// return; + + if (ent->spawnflags) + { + if (ent->s.frame == 0) + { + ent->spawnflags = (ent->spawnflags + 1) % 4 + 1; + ent->s.modelindex = robotron[ent->spawnflags - 1]; + } + } +} + +void SP_viewthing(edict_t *ent) +{ + gi.dprintf ("viewthing spawned\n"); + + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_BBOX; + ent->s.renderfx = RF_FRAMELERP; + VectorSet (ent->mins, -16, -16, -24); + VectorSet (ent->maxs, 16, 16, 32); +// ent->s.modelindex = gi.modelindex ("models/player_y/tris.md2"); + ent->s.modelindex = gi.modelindex ("models/objects/banner/tris.md2"); + gi.linkentity (ent); + ent->nextthink = level.time + 0.5; + ent->think = TH_viewthing; + return; +} + + +/*QUAKED info_null (0 0.5 0) (-4 -4 -4) (4 4 4) +Used as a positional target for spotlights, etc. +*/ +void SP_info_null (edict_t *self) +{ + G_FreeEdict (self); +}; + + +/*QUAKED info_notnull (0 0.5 0) (-4 -4 -4) (4 4 4) +Used as a positional target for lightning. +*/ +void SP_info_notnull (edict_t *self) +{ + VectorCopy (self->s.origin, self->absmin); + VectorCopy (self->s.origin, self->absmax); +}; + + +/*QUAKED light (0 1 0) (-8 -8 -8) (8 8 8) START_OFF +Non-displayed light. +Default light value is 300. +Default style is 0. +If targeted, will toggle between on and off. +Default _cone value is 10 (used to set size of light for spotlights) +*/ + +#define START_OFF 1 + +static void light_use (edict_t *self, edict_t *other, edict_t *activator) +{ + if (self->spawnflags & START_OFF) + { + gi.configstring (CS_LIGHTS+self->style, "m"); + self->spawnflags &= ~START_OFF; + } + else + { + gi.configstring (CS_LIGHTS+self->style, "a"); + self->spawnflags |= START_OFF; + } +} + +void SP_light (edict_t *self) +{ + // no targeted lights in deathmatch, because they cause global messages + if (!self->targetname || deathmatch->value) + { + G_FreeEdict (self); + return; + } + + if (self->style >= 32) + { + self->use = light_use; + if (self->spawnflags & START_OFF) + gi.configstring (CS_LIGHTS+self->style, "a"); + else + gi.configstring (CS_LIGHTS+self->style, "m"); + } +} + + +/*QUAKED func_wall (0 .5 .8) ? TRIGGER_SPAWN TOGGLE START_ON ANIMATED ANIMATED_FAST +This is just a solid wall if not inhibited + +TRIGGER_SPAWN the wall will not be present until triggered + it will then blink in to existance; it will + kill anything that was in it's way + +TOGGLE only valid for TRIGGER_SPAWN walls + this allows the wall to be turned on and off + +START_ON only valid for TRIGGER_SPAWN walls + the wall will initially be present +*/ + +void func_wall_use (edict_t *self, edict_t *other, edict_t *activator) +{ + if (self->solid == SOLID_NOT) + { + self->solid = SOLID_BSP; + self->svflags &= ~SVF_NOCLIENT; + KillBox (self); + } + else + { + self->solid = SOLID_NOT; + self->svflags |= SVF_NOCLIENT; + } + gi.linkentity (self); + + if (!(self->spawnflags & 2)) + self->use = NULL; +} + +void SP_func_wall (edict_t *self) +{ + self->movetype = MOVETYPE_PUSH; + gi.setmodel (self, self->model); + + if (self->spawnflags & 8) + self->s.effects |= EF_ANIM_ALL; + if (self->spawnflags & 16) + self->s.effects |= EF_ANIM_ALLFAST; + + // just a wall + if ((self->spawnflags & 7) == 0) + { + self->solid = SOLID_BSP; + gi.linkentity (self); + return; + } + + // it must be TRIGGER_SPAWN + if (!(self->spawnflags & 1)) + { +// gi.dprintf("func_wall missing TRIGGER_SPAWN\n"); + self->spawnflags |= 1; + } + + // yell if the spawnflags are odd + if (self->spawnflags & 4) + { + if (!(self->spawnflags & 2)) + { + gi.dprintf("func_wall START_ON without TOGGLE\n"); + self->spawnflags |= 2; + } + } + + self->use = func_wall_use; + if (self->spawnflags & 4) + { + self->solid = SOLID_BSP; + } + else + { + self->solid = SOLID_NOT; + self->svflags |= SVF_NOCLIENT; + } + gi.linkentity (self); +} + + +/*QUAKED func_object (0 .5 .8) ? TRIGGER_SPAWN ANIMATED ANIMATED_FAST +This is solid bmodel that will fall if it's support it removed. +*/ + +void func_object_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + // only squash thing we fall on top of + if (!plane) + return; + if (plane->normal[2] < 1.0) + return; + if (other->takedamage == DAMAGE_NO) + return; + T_Damage (other, self, self, vec3_origin, self->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH); +} + +void func_object_release (edict_t *self) +{ + self->movetype = MOVETYPE_TOSS; + self->touch = func_object_touch; +} + +void func_object_use (edict_t *self, edict_t *other, edict_t *activator) +{ + self->solid = SOLID_BSP; + self->svflags &= ~SVF_NOCLIENT; + self->use = NULL; + KillBox (self); + func_object_release (self); +} + +void SP_func_object (edict_t *self) +{ + gi.setmodel (self, self->model); + + self->mins[0] += 1; + self->mins[1] += 1; + self->mins[2] += 1; + self->maxs[0] -= 1; + self->maxs[1] -= 1; + self->maxs[2] -= 1; + + if (!self->dmg) + self->dmg = 100; + + if (self->spawnflags == 0) + { + self->solid = SOLID_BSP; + self->movetype = MOVETYPE_PUSH; + self->think = func_object_release; + self->nextthink = level.time + 2 * FRAMETIME; + } + else + { + self->solid = SOLID_NOT; + self->movetype = MOVETYPE_PUSH; + self->use = func_object_use; + self->svflags |= SVF_NOCLIENT; + } + + if (self->spawnflags & 2) + self->s.effects |= EF_ANIM_ALL; + if (self->spawnflags & 4) + self->s.effects |= EF_ANIM_ALLFAST; + + self->clipmask = MASK_MONSTERSOLID; + + gi.linkentity (self); +} + + +/*QUAKED func_explosive (0 .5 .8) ? Trigger_Spawn ANIMATED ANIMATED_FAST +Any brush that you want to explode or break apart. If you want an +ex0plosion, set dmg and it will do a radius explosion of that amount +at the center of the bursh. + +If targeted it will not be shootable. + +health defaults to 100. + +mass defaults to 75. This determines how much debris is emitted when +it explodes. You get one large chunk per 100 of mass (up to 8) and +one small chunk per 25 of mass (up to 16). So 800 gives the most. +*/ +void func_explosive_explode (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + vec3_t origin; + vec3_t chunkorigin; + vec3_t size; + int count; + int mass; + + // bmodel origins are (0 0 0), we need to adjust that here + VectorScale (self->size, 0.5, size); + VectorAdd (self->absmin, size, origin); + VectorCopy (origin, self->s.origin); + + self->takedamage = DAMAGE_NO; + + if (self->dmg) + T_RadiusDamage (self, attacker, self->dmg, NULL, self->dmg+40, MOD_EXPLOSIVE); + + VectorSubtract (self->s.origin, inflictor->s.origin, self->velocity); + VectorNormalize (self->velocity); + VectorScale (self->velocity, 150, self->velocity); + + // start chunks towards the center + VectorScale (size, 0.5, size); + + mass = self->mass; + if (!mass) + mass = 75; + + // big chunks + if (mass >= 100) + { + count = mass / 100; + if (count > 8) + count = 8; + while(count--) + { + chunkorigin[0] = origin[0] + crandom() * size[0]; + chunkorigin[1] = origin[1] + crandom() * size[1]; + chunkorigin[2] = origin[2] + crandom() * size[2]; + ThrowDebris (self, "models/objects/debris1/tris.md2", 1, chunkorigin); + } + } + + // small chunks + count = mass / 25; + if (count > 16) + count = 16; + while(count--) + { + chunkorigin[0] = origin[0] + crandom() * size[0]; + chunkorigin[1] = origin[1] + crandom() * size[1]; + chunkorigin[2] = origin[2] + crandom() * size[2]; + ThrowDebris (self, "models/objects/debris2/tris.md2", 2, chunkorigin); + } + + G_UseTargets (self, attacker); + + if (self->dmg) + BecomeExplosion1 (self); + else + G_FreeEdict (self); +} + +void func_explosive_use(edict_t *self, edict_t *other, edict_t *activator) +{ + func_explosive_explode (self, self, other, self->health, vec3_origin); +} + +void func_explosive_spawn (edict_t *self, edict_t *other, edict_t *activator) +{ + self->solid = SOLID_BSP; + self->svflags &= ~SVF_NOCLIENT; + self->use = NULL; + KillBox (self); + gi.linkentity (self); +} + +void SP_func_explosive (edict_t *self) +{ + if (deathmatch->value) + { // auto-remove for deathmatch + G_FreeEdict (self); + return; + } + + self->movetype = MOVETYPE_PUSH; + + gi.modelindex ("models/objects/debris1/tris.md2"); + gi.modelindex ("models/objects/debris2/tris.md2"); + + gi.setmodel (self, self->model); + + if (self->spawnflags & 1) + { + self->svflags |= SVF_NOCLIENT; + self->solid = SOLID_NOT; + self->use = func_explosive_spawn; + } + else + { + self->solid = SOLID_BSP; + if (self->targetname) + self->use = func_explosive_use; + } + + if (self->spawnflags & 2) + self->s.effects |= EF_ANIM_ALL; + if (self->spawnflags & 4) + self->s.effects |= EF_ANIM_ALLFAST; + + if (self->use != func_explosive_use) + { + if (!self->health) + self->health = 100; + self->die = func_explosive_explode; + self->takedamage = DAMAGE_YES; + } + + gi.linkentity (self); +} + + +/*QUAKED misc_explobox (0 .5 .8) (-16 -16 0) (16 16 40) +Large exploding box. You can override its mass (100), +health (80), and dmg (150). +*/ + +void barrel_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) + +{ + float ratio; + vec3_t v; + + if ((!other->groundentity) || (other->groundentity == self)) + return; + + ratio = (float)other->mass / (float)self->mass; + VectorSubtract (self->s.origin, other->s.origin, v); + M_walkmove (self, vectoyaw(v), 20 * ratio * FRAMETIME); +} + +void barrel_explode (edict_t *self) +{ + vec3_t org; + float spd; + vec3_t save; + + T_RadiusDamage (self, self->activator, self->dmg, NULL, self->dmg+40, MOD_BARREL); + + VectorCopy (self->s.origin, save); + VectorMA (self->absmin, 0.5, self->size, self->s.origin); + + // a few big chunks + spd = 1.5 * (float)self->dmg / 200.0; + org[0] = self->s.origin[0] + crandom() * self->size[0]; + org[1] = self->s.origin[1] + crandom() * self->size[1]; + org[2] = self->s.origin[2] + crandom() * self->size[2]; + ThrowDebris (self, "models/objects/debris1/tris.md2", spd, org); + org[0] = self->s.origin[0] + crandom() * self->size[0]; + org[1] = self->s.origin[1] + crandom() * self->size[1]; + org[2] = self->s.origin[2] + crandom() * self->size[2]; + ThrowDebris (self, "models/objects/debris1/tris.md2", spd, org); + + // bottom corners + spd = 1.75 * (float)self->dmg / 200.0; + VectorCopy (self->absmin, org); + ThrowDebris (self, "models/objects/debris3/tris.md2", spd, org); + VectorCopy (self->absmin, org); + org[0] += self->size[0]; + ThrowDebris (self, "models/objects/debris3/tris.md2", spd, org); + VectorCopy (self->absmin, org); + org[1] += self->size[1]; + ThrowDebris (self, "models/objects/debris3/tris.md2", spd, org); + VectorCopy (self->absmin, org); + org[0] += self->size[0]; + org[1] += self->size[1]; + ThrowDebris (self, "models/objects/debris3/tris.md2", spd, org); + + // a bunch of little chunks + spd = 2 * self->dmg / 200; + org[0] = self->s.origin[0] + crandom() * self->size[0]; + org[1] = self->s.origin[1] + crandom() * self->size[1]; + org[2] = self->s.origin[2] + crandom() * self->size[2]; + ThrowDebris (self, "models/objects/debris2/tris.md2", spd, org); + org[0] = self->s.origin[0] + crandom() * self->size[0]; + org[1] = self->s.origin[1] + crandom() * self->size[1]; + org[2] = self->s.origin[2] + crandom() * self->size[2]; + ThrowDebris (self, "models/objects/debris2/tris.md2", spd, org); + org[0] = self->s.origin[0] + crandom() * self->size[0]; + org[1] = self->s.origin[1] + crandom() * self->size[1]; + org[2] = self->s.origin[2] + crandom() * self->size[2]; + ThrowDebris (self, "models/objects/debris2/tris.md2", spd, org); + org[0] = self->s.origin[0] + crandom() * self->size[0]; + org[1] = self->s.origin[1] + crandom() * self->size[1]; + org[2] = self->s.origin[2] + crandom() * self->size[2]; + ThrowDebris (self, "models/objects/debris2/tris.md2", spd, org); + org[0] = self->s.origin[0] + crandom() * self->size[0]; + org[1] = self->s.origin[1] + crandom() * self->size[1]; + org[2] = self->s.origin[2] + crandom() * self->size[2]; + ThrowDebris (self, "models/objects/debris2/tris.md2", spd, org); + org[0] = self->s.origin[0] + crandom() * self->size[0]; + org[1] = self->s.origin[1] + crandom() * self->size[1]; + org[2] = self->s.origin[2] + crandom() * self->size[2]; + ThrowDebris (self, "models/objects/debris2/tris.md2", spd, org); + org[0] = self->s.origin[0] + crandom() * self->size[0]; + org[1] = self->s.origin[1] + crandom() * self->size[1]; + org[2] = self->s.origin[2] + crandom() * self->size[2]; + ThrowDebris (self, "models/objects/debris2/tris.md2", spd, org); + org[0] = self->s.origin[0] + crandom() * self->size[0]; + org[1] = self->s.origin[1] + crandom() * self->size[1]; + org[2] = self->s.origin[2] + crandom() * self->size[2]; + ThrowDebris (self, "models/objects/debris2/tris.md2", spd, org); + + VectorCopy (save, self->s.origin); + if (self->groundentity) + BecomeExplosion2 (self); + else + BecomeExplosion1 (self); +} + +void barrel_delay (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + self->takedamage = DAMAGE_NO; + self->nextthink = level.time + 2 * FRAMETIME; + self->think = barrel_explode; + self->activator = attacker; +} + +void SP_misc_explobox (edict_t *self) +{ + if (deathmatch->value) + { // auto-remove for deathmatch + G_FreeEdict (self); + return; + } + + gi.modelindex ("models/objects/debris1/tris.md2"); + gi.modelindex ("models/objects/debris2/tris.md2"); + gi.modelindex ("models/objects/debris3/tris.md2"); + + self->solid = SOLID_BBOX; + self->movetype = MOVETYPE_STEP; + + self->model = "models/objects/barrels/tris.md2"; + self->s.modelindex = gi.modelindex (self->model); + VectorSet (self->mins, -16, -16, 0); + VectorSet (self->maxs, 16, 16, 40); + + if (!self->mass) + self->mass = 400; + if (!self->health) + self->health = 10; + if (!self->dmg) + self->dmg = 150; + + self->die = barrel_delay; + self->takedamage = DAMAGE_YES; + self->monsterinfo.aiflags = AI_NOSTEP; + + self->touch = barrel_touch; + + self->think = M_droptofloor; + self->nextthink = level.time + 2 * FRAMETIME; + + gi.linkentity (self); +} + + +// +// miscellaneous specialty items +// + +/*QUAKED misc_blackhole (1 .5 0) (-8 -8 -8) (8 8 8) +*/ + +void misc_blackhole_use (edict_t *ent, edict_t *other, edict_t *activator) +{ + /* + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_BOSSTPORT); + gi.WritePosition (ent->s.origin); + gi.multicast (ent->s.origin, MULTICAST_PVS); + */ + G_FreeEdict (ent); +} + +void misc_blackhole_think (edict_t *self) +{ + if (++self->s.frame < 19) + self->nextthink = level.time + FRAMETIME; + else + { + self->s.frame = 0; + self->nextthink = level.time + FRAMETIME; + } +} + +void SP_misc_blackhole (edict_t *ent) +{ + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_NOT; + VectorSet (ent->mins, -64, -64, 0); + VectorSet (ent->maxs, 64, 64, 8); + ent->s.modelindex = gi.modelindex ("models/objects/black/tris.md2"); + ent->s.renderfx = RF_TRANSLUCENT; + ent->use = misc_blackhole_use; + ent->think = misc_blackhole_think; + ent->nextthink = level.time + 2 * FRAMETIME; + gi.linkentity (ent); +} + +/*QUAKED misc_eastertank (1 .5 0) (-32 -32 -16) (32 32 32) +*/ + +void misc_eastertank_think (edict_t *self) +{ + if (++self->s.frame < 293) + self->nextthink = level.time + FRAMETIME; + else + { + self->s.frame = 254; + self->nextthink = level.time + FRAMETIME; + } +} + +void SP_misc_eastertank (edict_t *ent) +{ + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_BBOX; + VectorSet (ent->mins, -32, -32, -16); + VectorSet (ent->maxs, 32, 32, 32); + ent->s.modelindex = gi.modelindex ("models/monsters/tank/tris.md2"); + ent->s.frame = 254; + ent->think = misc_eastertank_think; + ent->nextthink = level.time + 2 * FRAMETIME; + gi.linkentity (ent); +} + +/*QUAKED misc_easterchick (1 .5 0) (-32 -32 0) (32 32 32) +*/ + + +void misc_easterchick_think (edict_t *self) +{ + if (++self->s.frame < 247) + self->nextthink = level.time + FRAMETIME; + else + { + self->s.frame = 208; + self->nextthink = level.time + FRAMETIME; + } +} + +void SP_misc_easterchick (edict_t *ent) +{ + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_BBOX; + VectorSet (ent->mins, -32, -32, 0); + VectorSet (ent->maxs, 32, 32, 32); + ent->s.modelindex = gi.modelindex ("models/monsters/bitch/tris.md2"); + ent->s.frame = 208; + ent->think = misc_easterchick_think; + ent->nextthink = level.time + 2 * FRAMETIME; + gi.linkentity (ent); +} + +/*QUAKED misc_easterchick2 (1 .5 0) (-32 -32 0) (32 32 32) +*/ + + +void misc_easterchick2_think (edict_t *self) +{ + if (++self->s.frame < 287) + self->nextthink = level.time + FRAMETIME; + else + { + self->s.frame = 248; + self->nextthink = level.time + FRAMETIME; + } +} + +void SP_misc_easterchick2 (edict_t *ent) +{ + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_BBOX; + VectorSet (ent->mins, -32, -32, 0); + VectorSet (ent->maxs, 32, 32, 32); + ent->s.modelindex = gi.modelindex ("models/monsters/bitch/tris.md2"); + ent->s.frame = 248; + ent->think = misc_easterchick2_think; + ent->nextthink = level.time + 2 * FRAMETIME; + gi.linkentity (ent); +} + + +/*QUAKED monster_commander_body (1 .5 0) (-32 -32 0) (32 32 48) +Not really a monster, this is the Tank Commander's decapitated body. +There should be a item_commander_head that has this as it's target. +*/ + +void commander_body_think (edict_t *self) +{ + if (++self->s.frame < 24) + self->nextthink = level.time + FRAMETIME; + else + self->nextthink = 0; + + if (self->s.frame == 22) + gi.sound (self, CHAN_BODY, gi.soundindex ("tank/thud.wav"), 1, ATTN_NORM, 0); +} + +void commander_body_use (edict_t *self, edict_t *other, edict_t *activator) +{ + self->think = commander_body_think; + self->nextthink = level.time + FRAMETIME; + gi.sound (self, CHAN_BODY, gi.soundindex ("tank/pain.wav"), 1, ATTN_NORM, 0); +} + +void commander_body_drop (edict_t *self) +{ + self->movetype = MOVETYPE_TOSS; + self->s.origin[2] += 2; +} + +void SP_monster_commander_body (edict_t *self) +{ + self->movetype = MOVETYPE_NONE; + self->solid = SOLID_BBOX; + self->model = "models/monsters/commandr/tris.md2"; + self->s.modelindex = gi.modelindex (self->model); + VectorSet (self->mins, -32, -32, 0); + VectorSet (self->maxs, 32, 32, 48); + self->use = commander_body_use; + self->takedamage = DAMAGE_YES; + self->flags = FL_GODMODE; + self->s.renderfx |= RF_FRAMELERP; + gi.linkentity (self); + + gi.soundindex ("tank/thud.wav"); + gi.soundindex ("tank/pain.wav"); + + self->think = commander_body_drop; + self->nextthink = level.time + 5 * FRAMETIME; +} + + +/*QUAKED misc_banner (1 .5 0) (-4 -4 -4) (4 4 4) +The origin is the bottom of the banner. +The banner is 128 tall. +*/ +void misc_banner_think (edict_t *ent) +{ + ent->s.frame = (ent->s.frame + 1) % 16; + ent->nextthink = level.time + FRAMETIME; +} + +void SP_misc_banner (edict_t *ent) +{ + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_NOT; + ent->s.modelindex = gi.modelindex ("models/objects/banner/tris.md2"); + ent->s.frame = rand() % 16; + gi.linkentity (ent); + + ent->think = misc_banner_think; + ent->nextthink = level.time + FRAMETIME; +} + +/*QUAKED misc_deadsoldier (1 .5 0) (-16 -16 0) (16 16 16) ON_BACK ON_STOMACH BACK_DECAP FETAL_POS SIT_DECAP IMPALED +This is the dead player model. Comes in 6 exciting different poses! +*/ +void misc_deadsoldier_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + int n; + + if (self->health > -80) + return; + + gi.sound (self, CHAN_BODY, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0); + for (n= 0; n < 4; n++) + ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); + ThrowHead (self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC); +} + +void SP_misc_deadsoldier (edict_t *ent) +{ + if (deathmatch->value) + { // auto-remove for deathmatch + G_FreeEdict (ent); + return; + } + + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_BBOX; + ent->s.modelindex=gi.modelindex ("models/deadbods/dude/tris.md2"); + + // Defaults to frame 0 + if (ent->spawnflags & 2) + ent->s.frame = 1; + else if (ent->spawnflags & 4) + ent->s.frame = 2; + else if (ent->spawnflags & 8) + ent->s.frame = 3; + else if (ent->spawnflags & 16) + ent->s.frame = 4; + else if (ent->spawnflags & 32) + ent->s.frame = 5; + else + ent->s.frame = 0; + + VectorSet (ent->mins, -16, -16, 0); + VectorSet (ent->maxs, 16, 16, 16); + ent->deadflag = DEAD_DEAD; + ent->takedamage = DAMAGE_YES; + ent->svflags |= SVF_MONSTER|SVF_DEADMONSTER; + ent->die = misc_deadsoldier_die; + ent->monsterinfo.aiflags |= AI_GOOD_GUY; + + gi.linkentity (ent); +} + +/*QUAKED misc_viper (1 .5 0) (-16 -16 0) (16 16 32) +This is the Viper for the flyby bombing. +It is trigger_spawned, so you must have something use it for it to show up. +There must be a path for it to follow once it is activated. + +"speed" How fast the Viper should fly +*/ + +extern void train_use (edict_t *self, edict_t *other, edict_t *activator); +extern void func_train_find (edict_t *self); + +void misc_viper_use (edict_t *self, edict_t *other, edict_t *activator) +{ + self->svflags &= ~SVF_NOCLIENT; + self->use = train_use; + train_use (self, other, activator); +} + +void SP_misc_viper (edict_t *ent) +{ + if (!ent->target) + { + gi.dprintf ("misc_viper without a target at %s\n", vtos(ent->absmin)); + G_FreeEdict (ent); + return; + } + + if (!ent->speed) + ent->speed = 300; + + ent->movetype = MOVETYPE_PUSH; + ent->solid = SOLID_NOT; + ent->s.modelindex = gi.modelindex ("models/ships/viper/tris.md2"); + VectorSet (ent->mins, -16, -16, 0); + VectorSet (ent->maxs, 16, 16, 32); + + ent->think = func_train_find; + ent->nextthink = level.time + FRAMETIME; + ent->use = misc_viper_use; + ent->svflags |= SVF_NOCLIENT; + ent->moveinfo.accel = ent->moveinfo.decel = ent->moveinfo.speed = ent->speed; + + gi.linkentity (ent); +} + + +/*QUAKED misc_bigviper (1 .5 0) (-176 -120 -24) (176 120 72) +This is a large stationary viper as seen in Paul's intro +*/ +void SP_misc_bigviper (edict_t *ent) +{ + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_BBOX; + VectorSet (ent->mins, -176, -120, -24); + VectorSet (ent->maxs, 176, 120, 72); + ent->s.modelindex = gi.modelindex ("models/ships/bigviper/tris.md2"); + gi.linkentity (ent); +} + + +/*QUAKED misc_viper_bomb (1 0 0) (-8 -8 -8) (8 8 8) +"dmg" how much boom should the bomb make? +*/ +void misc_viper_bomb_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + G_UseTargets (self, self->activator); + + self->s.origin[2] = self->absmin[2] + 1; + T_RadiusDamage (self, self, self->dmg, NULL, self->dmg+40, MOD_BOMB); + BecomeExplosion2 (self); +} + +void misc_viper_bomb_prethink (edict_t *self) +{ + vec3_t v; + float diff; + + self->groundentity = NULL; + + diff = self->timestamp - level.time; + if (diff < -1.0) + diff = -1.0; + + VectorScale (self->moveinfo.dir, 1.0 + diff, v); + v[2] = diff; + + diff = self->s.angles[2]; + vectoangles (v, self->s.angles); + self->s.angles[2] = diff + 10; +} + +void misc_viper_bomb_use (edict_t *self, edict_t *other, edict_t *activator) +{ + edict_t *viper; + + self->solid = SOLID_BBOX; + self->svflags &= ~SVF_NOCLIENT; + self->s.effects |= EF_ROCKET; + self->use = NULL; + self->movetype = MOVETYPE_TOSS; + self->prethink = misc_viper_bomb_prethink; + self->touch = misc_viper_bomb_touch; + self->activator = activator; + + viper = G_Find (NULL, FOFS(classname), "misc_viper"); + VectorScale (viper->moveinfo.dir, viper->moveinfo.speed, self->velocity); + + self->timestamp = level.time; + VectorCopy (viper->moveinfo.dir, self->moveinfo.dir); +} + +void SP_misc_viper_bomb (edict_t *self) +{ + self->movetype = MOVETYPE_NONE; + self->solid = SOLID_NOT; + VectorSet (self->mins, -8, -8, -8); + VectorSet (self->maxs, 8, 8, 8); + + self->s.modelindex = gi.modelindex ("models/objects/bomb/tris.md2"); + + if (!self->dmg) + self->dmg = 1000; + + self->use = misc_viper_bomb_use; + self->svflags |= SVF_NOCLIENT; + + gi.linkentity (self); +} + + +/*QUAKED misc_strogg_ship (1 .5 0) (-16 -16 0) (16 16 32) +This is a Storgg ship for the flybys. +It is trigger_spawned, so you must have something use it for it to show up. +There must be a path for it to follow once it is activated. + +"speed" How fast it should fly +*/ + +extern void train_use (edict_t *self, edict_t *other, edict_t *activator); +extern void func_train_find (edict_t *self); + +void misc_strogg_ship_use (edict_t *self, edict_t *other, edict_t *activator) +{ + self->svflags &= ~SVF_NOCLIENT; + self->use = train_use; + train_use (self, other, activator); +} + +void SP_misc_strogg_ship (edict_t *ent) +{ + if (!ent->target) + { + gi.dprintf ("%s without a target at %s\n", ent->classname, vtos(ent->absmin)); + G_FreeEdict (ent); + return; + } + + if (!ent->speed) + ent->speed = 300; + + ent->movetype = MOVETYPE_PUSH; + ent->solid = SOLID_NOT; + ent->s.modelindex = gi.modelindex ("models/ships/strogg1/tris.md2"); + VectorSet (ent->mins, -16, -16, 0); + VectorSet (ent->maxs, 16, 16, 32); + + ent->think = func_train_find; + ent->nextthink = level.time + FRAMETIME; + ent->use = misc_strogg_ship_use; + ent->svflags |= SVF_NOCLIENT; + ent->moveinfo.accel = ent->moveinfo.decel = ent->moveinfo.speed = ent->speed; + + gi.linkentity (ent); +} + + +/*QUAKED misc_satellite_dish (1 .5 0) (-64 -64 0) (64 64 128) +*/ +void misc_satellite_dish_think (edict_t *self) +{ + self->s.frame++; + if (self->s.frame < 38) + self->nextthink = level.time + FRAMETIME; +} + +void misc_satellite_dish_use (edict_t *self, edict_t *other, edict_t *activator) +{ + self->s.frame = 0; + self->think = misc_satellite_dish_think; + self->nextthink = level.time + FRAMETIME; +} + +void SP_misc_satellite_dish (edict_t *ent) +{ + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_BBOX; + VectorSet (ent->mins, -64, -64, 0); + VectorSet (ent->maxs, 64, 64, 128); + ent->s.modelindex = gi.modelindex ("models/objects/satellite/tris.md2"); + ent->use = misc_satellite_dish_use; + gi.linkentity (ent); +} + + +/*QUAKED light_mine1 (0 1 0) (-2 -2 -12) (2 2 12) +*/ +void SP_light_mine1 (edict_t *ent) +{ + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_BBOX; + ent->s.modelindex = gi.modelindex ("models/objects/minelite/light1/tris.md2"); + gi.linkentity (ent); +} + + +/*QUAKED light_mine2 (0 1 0) (-2 -2 -12) (2 2 12) +*/ +void SP_light_mine2 (edict_t *ent) +{ + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_BBOX; + ent->s.modelindex = gi.modelindex ("models/objects/minelite/light2/tris.md2"); + gi.linkentity (ent); +} + + +/*QUAKED misc_gib_arm (1 0 0) (-8 -8 -8) (8 8 8) +Intended for use with the target_spawner +*/ +void SP_misc_gib_arm (edict_t *ent) +{ + gi.setmodel (ent, "models/objects/gibs/arm/tris.md2"); + ent->solid = SOLID_NOT; + ent->s.effects |= EF_GIB; + ent->takedamage = DAMAGE_YES; + ent->die = gib_die; + ent->movetype = MOVETYPE_TOSS; + ent->svflags |= SVF_MONSTER; + ent->deadflag = DEAD_DEAD; + ent->avelocity[0] = random()*200; + ent->avelocity[1] = random()*200; + ent->avelocity[2] = random()*200; + ent->think = G_FreeEdict; + ent->nextthink = level.time + 30; + gi.linkentity (ent); +} + +/*QUAKED misc_gib_leg (1 0 0) (-8 -8 -8) (8 8 8) +Intended for use with the target_spawner +*/ +void SP_misc_gib_leg (edict_t *ent) +{ + gi.setmodel (ent, "models/objects/gibs/leg/tris.md2"); + ent->solid = SOLID_NOT; + ent->s.effects |= EF_GIB; + ent->takedamage = DAMAGE_YES; + ent->die = gib_die; + ent->movetype = MOVETYPE_TOSS; + ent->svflags |= SVF_MONSTER; + ent->deadflag = DEAD_DEAD; + ent->avelocity[0] = random()*200; + ent->avelocity[1] = random()*200; + ent->avelocity[2] = random()*200; + ent->think = G_FreeEdict; + ent->nextthink = level.time + 30; + gi.linkentity (ent); +} + +/*QUAKED misc_gib_head (1 0 0) (-8 -8 -8) (8 8 8) +Intended for use with the target_spawner +*/ +void SP_misc_gib_head (edict_t *ent) +{ + gi.setmodel (ent, "models/objects/gibs/head/tris.md2"); + ent->solid = SOLID_NOT; + ent->s.effects |= EF_GIB; + ent->takedamage = DAMAGE_YES; + ent->die = gib_die; + ent->movetype = MOVETYPE_TOSS; + ent->svflags |= SVF_MONSTER; + ent->deadflag = DEAD_DEAD; + ent->avelocity[0] = random()*200; + ent->avelocity[1] = random()*200; + ent->avelocity[2] = random()*200; + ent->think = G_FreeEdict; + ent->nextthink = level.time + 30; + gi.linkentity (ent); +} + +//===================================================== + +/*QUAKED target_character (0 0 1) ? +used with target_string (must be on same "team") +"count" is position in the string (starts at 1) +*/ + +void SP_target_character (edict_t *self) +{ + self->movetype = MOVETYPE_PUSH; + gi.setmodel (self, self->model); + self->solid = SOLID_BSP; + self->s.frame = 12; + gi.linkentity (self); + return; +} + + +/*QUAKED target_string (0 0 1) (-8 -8 -8) (8 8 8) +*/ + +void target_string_use (edict_t *self, edict_t *other, edict_t *activator) +{ + edict_t *e; + int n, l; + char c; + + l = strlen(self->message); + for (e = self->teammaster; e; e = e->teamchain) + { + if (!e->count) + continue; + n = e->count - 1; + if (n > l) + { + e->s.frame = 12; + continue; + } + + c = self->message[n]; + if (c >= '0' && c <= '9') + e->s.frame = c - '0'; + else if (c == '-') + e->s.frame = 10; + else if (c == ':') + e->s.frame = 11; + else + e->s.frame = 12; + } +} + +void SP_target_string (edict_t *self) +{ + if (!self->message) + self->message = ""; + self->use = target_string_use; +} + + +/*QUAKED func_clock (0 0 1) (-8 -8 -8) (8 8 8) TIMER_UP TIMER_DOWN START_OFF MULTI_USE +target a target_string with this + +The default is to be a time of day clock + +TIMER_UP and TIMER_DOWN run for "count" seconds and the fire "pathtarget" +If START_OFF, this entity must be used before it starts + +"style" 0 "xx" + 1 "xx:xx" + 2 "xx:xx:xx" +*/ + +#define CLOCK_MESSAGE_SIZE 16 + +// don't let field width of any clock messages change, or it +// could cause an overwrite after a game load + +static void func_clock_reset (edict_t *self) +{ + self->activator = NULL; + if (self->spawnflags & 1) + { + self->health = 0; + self->wait = self->count; + } + else if (self->spawnflags & 2) + { + self->health = self->count; + self->wait = 0; + } +} + +static void func_clock_format_countdown (edict_t *self) +{ + if (self->style == 0) + { + Com_sprintf (self->message, CLOCK_MESSAGE_SIZE, "%2i", self->health); + return; + } + + if (self->style == 1) + { + Com_sprintf(self->message, CLOCK_MESSAGE_SIZE, "%2i:%2i", self->health / 60, self->health % 60); + if (self->message[3] == ' ') + self->message[3] = '0'; + return; + } + + if (self->style == 2) + { + Com_sprintf(self->message, CLOCK_MESSAGE_SIZE, "%2i:%2i:%2i", self->health / 3600, (self->health - (self->health / 3600) * 3600) / 60, self->health % 60); + if (self->message[3] == ' ') + self->message[3] = '0'; + if (self->message[6] == ' ') + self->message[6] = '0'; + return; + } +} + +void func_clock_think (edict_t *self) +{ + if (!self->enemy) + { + self->enemy = G_Find (NULL, FOFS(targetname), self->target); + if (!self->enemy) + return; + } + + if (self->spawnflags & 1) + { + func_clock_format_countdown (self); + self->health++; + } + else if (self->spawnflags & 2) + { + func_clock_format_countdown (self); + self->health--; + } + else + { + struct tm *ltime; + time_t gmtime; + + time(&gmtime); + ltime = localtime(&gmtime); + Com_sprintf (self->message, CLOCK_MESSAGE_SIZE, "%2i:%2i:%2i", ltime->tm_hour, ltime->tm_min, ltime->tm_sec); + if (self->message[3] == ' ') + self->message[3] = '0'; + if (self->message[6] == ' ') + self->message[6] = '0'; + } + + self->enemy->message = self->message; + self->enemy->use (self->enemy, self, self); + + if (((self->spawnflags & 1) && (self->health > self->wait)) || + ((self->spawnflags & 2) && (self->health < self->wait))) + { + if (self->pathtarget) + { + char *savetarget; + char *savemessage; + + savetarget = self->target; + savemessage = self->message; + self->target = self->pathtarget; + self->message = NULL; + G_UseTargets (self, self->activator); + self->target = savetarget; + self->message = savemessage; + } + + if (!(self->spawnflags & 8)) + return; + + func_clock_reset (self); + + if (self->spawnflags & 4) + return; + } + + self->nextthink = level.time + 1; +} + +void func_clock_use (edict_t *self, edict_t *other, edict_t *activator) +{ + if (!(self->spawnflags & 8)) + self->use = NULL; + if (self->activator) + return; + self->activator = activator; + self->think (self); +} + +void SP_func_clock (edict_t *self) +{ + if (!self->target) + { + gi.dprintf("%s with no target at %s\n", self->classname, vtos(self->s.origin)); + G_FreeEdict (self); + return; + } + + if ((self->spawnflags & 2) && (!self->count)) + { + gi.dprintf("%s with no count at %s\n", self->classname, vtos(self->s.origin)); + G_FreeEdict (self); + return; + } + + if ((self->spawnflags & 1) && (!self->count)) + self->count = 60*60;; + + func_clock_reset (self); + + self->message = gi.TagMalloc (CLOCK_MESSAGE_SIZE, TAG_LEVEL); + + self->think = func_clock_think; + + if (self->spawnflags & 4) + self->use = func_clock_use; + else + self->nextthink = level.time + 1; +} + +//================================================================================= + +void teleporter_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + edict_t *dest; + int i; + + if (!other->client) + return; + dest = G_Find (NULL, FOFS(targetname), self->target); + if (!dest) + { + gi.dprintf ("Couldn't find destination\n"); + return; + } + +//ZOID + CTFPlayerResetGrapple(other); +//ZOID + + // unlink to make sure it can't possibly interfere with KillBox + gi.unlinkentity (other); + + VectorCopy (dest->s.origin, other->s.origin); + VectorCopy (dest->s.origin, other->s.old_origin); + other->s.origin[2] += 10; + + // clear the velocity and hold them in place briefly + VectorClear (other->velocity); + other->client->ps.pmove.pm_time = 160>>3; // hold time + other->client->ps.pmove.pm_flags |= PMF_TIME_TELEPORT; + + // draw the teleport splash at source and on the player + self->owner->s.event = EV_PLAYER_TELEPORT; + other->s.event = EV_PLAYER_TELEPORT; + + // set angles + for (i=0 ; i<3 ; i++) + other->client->ps.pmove.delta_angles[i] = ANGLE2SHORT(dest->s.angles[i] - other->client->resp.cmd_angles[i]); + + VectorClear (other->s.angles); + VectorClear (other->client->ps.viewangles); + VectorClear (other->client->v_angle); + + // kill anything at the destination + KillBox (other); + + gi.linkentity (other); +} + +/*QUAKED misc_teleporter (1 0 0) (-32 -32 -24) (32 32 -16) +Stepping onto this disc will teleport players to the targeted misc_teleporter_dest object. +*/ +void SP_misc_teleporter (edict_t *ent) +{ + edict_t *trig; + + if (!ent->target) + { + gi.dprintf ("teleporter without a target.\n"); + G_FreeEdict (ent); + return; + } + + gi.setmodel (ent, "models/objects/dmspot/tris.md2"); + ent->s.skinnum = 1; + ent->s.effects = EF_TELEPORTER; + ent->s.sound = gi.soundindex ("world/amb10.wav"); + ent->solid = SOLID_BBOX; + + VectorSet (ent->mins, -32, -32, -24); + VectorSet (ent->maxs, 32, 32, -16); + gi.linkentity (ent); + + trig = G_Spawn (); + trig->touch = teleporter_touch; + trig->solid = SOLID_TRIGGER; + trig->target = ent->target; + trig->owner = ent; + VectorCopy (ent->s.origin, trig->s.origin); + VectorSet (trig->mins, -8, -8, 8); + VectorSet (trig->maxs, 8, 8, 24); + gi.linkentity (trig); + +} + +/*QUAKED misc_teleporter_dest (1 0 0) (-32 -32 -24) (32 32 -16) +Point teleporters at these. +*/ +void SP_misc_teleporter_dest (edict_t *ent) +{ + gi.setmodel (ent, "models/objects/dmspot/tris.md2"); + ent->s.skinnum = 0; + ent->solid = SOLID_BBOX; +// ent->s.effects |= EF_FLIES; + VectorSet (ent->mins, -32, -32, -24); + VectorSet (ent->maxs, 32, 32, -16); + gi.linkentity (ent); +} + diff --git a/original/ctf/g_monster.c b/original/ctf/g_monster.c new file mode 100644 index 0000000..c7b954b --- /dev/null +++ b/original/ctf/g_monster.c @@ -0,0 +1,740 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +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. + +*/ +#include "g_local.h" + + +// +// monster weapons +// + +//FIXME mosnters should call these with a totally accurate direction +// and we can mess it up based on skill. Spread should be for normal +// and we can tighten or loosen based on skill. We could muck with +// the damages too, but I'm not sure that's such a good idea. +void monster_fire_bullet (edict_t *self, vec3_t start, vec3_t dir, int damage, int kick, int hspread, int vspread, int flashtype) +{ + fire_bullet (self, start, dir, damage, kick, hspread, vspread, MOD_UNKNOWN); + + gi.WriteByte (svc_muzzleflash2); + gi.WriteShort (self - g_edicts); + gi.WriteByte (flashtype); + gi.multicast (start, MULTICAST_PVS); +} + +void monster_fire_shotgun (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int hspread, int vspread, int count, int flashtype) +{ + fire_shotgun (self, start, aimdir, damage, kick, hspread, vspread, count, MOD_UNKNOWN); + + gi.WriteByte (svc_muzzleflash2); + gi.WriteShort (self - g_edicts); + gi.WriteByte (flashtype); + gi.multicast (start, MULTICAST_PVS); +} + +void monster_fire_blaster (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int flashtype, int effect) +{ + fire_blaster (self, start, dir, damage, speed, effect, false); + + gi.WriteByte (svc_muzzleflash2); + gi.WriteShort (self - g_edicts); + gi.WriteByte (flashtype); + gi.multicast (start, MULTICAST_PVS); +} + +void monster_fire_grenade (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, int flashtype) +{ + fire_grenade (self, start, aimdir, damage, speed, 2.5, damage+40); + + gi.WriteByte (svc_muzzleflash2); + gi.WriteShort (self - g_edicts); + gi.WriteByte (flashtype); + gi.multicast (start, MULTICAST_PVS); +} + +void monster_fire_rocket (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int flashtype) +{ + fire_rocket (self, start, dir, damage, speed, damage+20, damage); + + gi.WriteByte (svc_muzzleflash2); + gi.WriteShort (self - g_edicts); + gi.WriteByte (flashtype); + gi.multicast (start, MULTICAST_PVS); +} + +void monster_fire_railgun (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int flashtype) +{ + fire_rail (self, start, aimdir, damage, kick); + + gi.WriteByte (svc_muzzleflash2); + gi.WriteShort (self - g_edicts); + gi.WriteByte (flashtype); + gi.multicast (start, MULTICAST_PVS); +} + +void monster_fire_bfg (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, int kick, float damage_radius, int flashtype) +{ + fire_bfg (self, start, aimdir, damage, speed, damage_radius); + + gi.WriteByte (svc_muzzleflash2); + gi.WriteShort (self - g_edicts); + gi.WriteByte (flashtype); + gi.multicast (start, MULTICAST_PVS); +} + + + +// +// Monster utility functions +// + +static void M_FliesOff (edict_t *self) +{ + self->s.effects &= ~EF_FLIES; + self->s.sound = 0; +} + +static void M_FliesOn (edict_t *self) +{ + if (self->waterlevel) + return; + self->s.effects |= EF_FLIES; + self->s.sound = gi.soundindex ("infantry/inflies1.wav"); + self->think = M_FliesOff; + self->nextthink = level.time + 60; +} + +void M_FlyCheck (edict_t *self) +{ + if (self->waterlevel) + return; + + if (random() > 0.5) + return; + + self->think = M_FliesOn; + self->nextthink = level.time + 5 + 10 * random(); +} + +void AttackFinished (edict_t *self, float time) +{ + self->monsterinfo.attack_finished = level.time + time; +} + + +void M_CheckGround (edict_t *ent) +{ + vec3_t point; + trace_t trace; + + if (ent->flags & (FL_SWIM|FL_FLY)) + return; + + if (ent->velocity[2] > 100) + { + ent->groundentity = NULL; + return; + } + +// if the hull point one-quarter unit down is solid the entity is on ground + point[0] = ent->s.origin[0]; + point[1] = ent->s.origin[1]; + point[2] = ent->s.origin[2] - 0.25; + + trace = gi.trace (ent->s.origin, ent->mins, ent->maxs, point, ent, MASK_MONSTERSOLID); + + // check steepness + if ( trace.plane.normal[2] < 0.7 && !trace.startsolid) + { + ent->groundentity = NULL; + return; + } + +// ent->groundentity = trace.ent; +// ent->groundentity_linkcount = trace.ent->linkcount; +// if (!trace.startsolid && !trace.allsolid) +// VectorCopy (trace.endpos, ent->s.origin); + if (!trace.startsolid && !trace.allsolid) + { + VectorCopy (trace.endpos, ent->s.origin); + ent->groundentity = trace.ent; + ent->groundentity_linkcount = trace.ent->linkcount; + ent->velocity[2] = 0; + } +} + + +void M_CatagorizePosition (edict_t *ent) +{ + vec3_t point; + int cont; + +// +// get waterlevel +// + point[0] = ent->s.origin[0]; + point[1] = ent->s.origin[1]; + point[2] = ent->s.origin[2] + ent->mins[2] + 1; + cont = gi.pointcontents (point); + + if (!(cont & MASK_WATER)) + { + ent->waterlevel = 0; + ent->watertype = 0; + return; + } + + ent->watertype = cont; + ent->waterlevel = 1; + point[2] += 26; + cont = gi.pointcontents (point); + if (!(cont & MASK_WATER)) + return; + + ent->waterlevel = 2; + point[2] += 22; + cont = gi.pointcontents (point); + if (cont & MASK_WATER) + ent->waterlevel = 3; +} + + +void M_WorldEffects (edict_t *ent) +{ + int dmg; + + if (ent->health > 0) + { + if (!(ent->flags & FL_SWIM)) + { + if (ent->waterlevel < 3) + { + ent->air_finished = level.time + 12; + } + else if (ent->air_finished < level.time) + { // drown! + if (ent->pain_debounce_time < level.time) + { + dmg = 2 + 2 * floor(level.time - ent->air_finished); + if (dmg > 15) + dmg = 15; + T_Damage (ent, world, world, vec3_origin, ent->s.origin, vec3_origin, dmg, 0, DAMAGE_NO_ARMOR, MOD_WATER); + ent->pain_debounce_time = level.time + 1; + } + } + } + else + { + if (ent->waterlevel > 0) + { + ent->air_finished = level.time + 9; + } + else if (ent->air_finished < level.time) + { // suffocate! + if (ent->pain_debounce_time < level.time) + { + dmg = 2 + 2 * floor(level.time - ent->air_finished); + if (dmg > 15) + dmg = 15; + T_Damage (ent, world, world, vec3_origin, ent->s.origin, vec3_origin, dmg, 0, DAMAGE_NO_ARMOR, MOD_WATER); + ent->pain_debounce_time = level.time + 1; + } + } + } + } + + if (ent->waterlevel == 0) + { + if (ent->flags & FL_INWATER) + { + gi.sound (ent, CHAN_BODY, gi.soundindex("player/watr_out.wav"), 1, ATTN_NORM, 0); + ent->flags &= ~FL_INWATER; + } + return; + } + + if ((ent->watertype & CONTENTS_LAVA) && !(ent->flags & FL_IMMUNE_LAVA)) + { + if (ent->damage_debounce_time < level.time) + { + ent->damage_debounce_time = level.time + 0.2; + T_Damage (ent, world, world, vec3_origin, ent->s.origin, vec3_origin, 10*ent->waterlevel, 0, 0, MOD_LAVA); + } + } + if ((ent->watertype & CONTENTS_SLIME) && !(ent->flags & FL_IMMUNE_SLIME)) + { + if (ent->damage_debounce_time < level.time) + { + ent->damage_debounce_time = level.time + 1; + T_Damage (ent, world, world, vec3_origin, ent->s.origin, vec3_origin, 4*ent->waterlevel, 0, 0, MOD_SLIME); + } + } + + if ( !(ent->flags & FL_INWATER) ) + { + if (!(ent->svflags & SVF_DEADMONSTER)) + { + if (ent->watertype & CONTENTS_LAVA) + if (random() <= 0.5) + gi.sound (ent, CHAN_BODY, gi.soundindex("player/lava1.wav"), 1, ATTN_NORM, 0); + else + gi.sound (ent, CHAN_BODY, gi.soundindex("player/lava2.wav"), 1, ATTN_NORM, 0); + else if (ent->watertype & CONTENTS_SLIME) + gi.sound (ent, CHAN_BODY, gi.soundindex("player/watr_in.wav"), 1, ATTN_NORM, 0); + else if (ent->watertype & CONTENTS_WATER) + gi.sound (ent, CHAN_BODY, gi.soundindex("player/watr_in.wav"), 1, ATTN_NORM, 0); + } + + ent->flags |= FL_INWATER; + ent->damage_debounce_time = 0; + } +} + + +void M_droptofloor (edict_t *ent) +{ + vec3_t end; + trace_t trace; + + ent->s.origin[2] += 1; + VectorCopy (ent->s.origin, end); + end[2] -= 256; + + trace = gi.trace (ent->s.origin, ent->mins, ent->maxs, end, ent, MASK_MONSTERSOLID); + + if (trace.fraction == 1 || trace.allsolid) + return; + + VectorCopy (trace.endpos, ent->s.origin); + + gi.linkentity (ent); + M_CheckGround (ent); + M_CatagorizePosition (ent); +} + + +void M_SetEffects (edict_t *ent) +{ + ent->s.effects &= ~(EF_COLOR_SHELL|EF_POWERSCREEN); + ent->s.renderfx &= ~(RF_SHELL_RED|RF_SHELL_GREEN|RF_SHELL_BLUE); + + if (ent->monsterinfo.aiflags & AI_RESURRECTING) + { + ent->s.effects |= EF_COLOR_SHELL; + ent->s.renderfx |= RF_SHELL_RED; + } + + if (ent->health <= 0) + return; + + if (ent->powerarmor_time > level.time) + { + if (ent->monsterinfo.power_armor_type == POWER_ARMOR_SCREEN) + { + ent->s.effects |= EF_POWERSCREEN; + } + else if (ent->monsterinfo.power_armor_type == POWER_ARMOR_SHIELD) + { + ent->s.effects |= EF_COLOR_SHELL; + ent->s.renderfx |= RF_SHELL_GREEN; + } + } +} + + +void M_MoveFrame (edict_t *self) +{ + mmove_t *move; + int index; + + move = self->monsterinfo.currentmove; + self->nextthink = level.time + FRAMETIME; + + if ((self->monsterinfo.nextframe) && (self->monsterinfo.nextframe >= move->firstframe) && (self->monsterinfo.nextframe <= move->lastframe)) + { + self->s.frame = self->monsterinfo.nextframe; + self->monsterinfo.nextframe = 0; + } + else + { + if (self->s.frame == move->lastframe) + { + if (move->endfunc) + { + move->endfunc (self); + + // regrab move, endfunc is very likely to change it + move = self->monsterinfo.currentmove; + + // check for death + if (self->svflags & SVF_DEADMONSTER) + return; + } + } + + if (self->s.frame < move->firstframe || self->s.frame > move->lastframe) + { + self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; + self->s.frame = move->firstframe; + } + else + { + if (!(self->monsterinfo.aiflags & AI_HOLD_FRAME)) + { + self->s.frame++; + if (self->s.frame > move->lastframe) + self->s.frame = move->firstframe; + } + } + } + + index = self->s.frame - move->firstframe; + if (move->frame[index].aifunc) + if (!(self->monsterinfo.aiflags & AI_HOLD_FRAME)) + move->frame[index].aifunc (self, move->frame[index].dist * self->monsterinfo.scale); + else + move->frame[index].aifunc (self, 0); + + if (move->frame[index].thinkfunc) + move->frame[index].thinkfunc (self); +} + + +void monster_think (edict_t *self) +{ + M_MoveFrame (self); + if (self->linkcount != self->monsterinfo.linkcount) + { + self->monsterinfo.linkcount = self->linkcount; + M_CheckGround (self); + } + M_CatagorizePosition (self); + M_WorldEffects (self); + M_SetEffects (self); +} + + +/* +================ +monster_use + +Using a monster makes it angry at the current activator +================ +*/ +void monster_use (edict_t *self, edict_t *other, edict_t *activator) +{ + if (self->enemy) + return; + if (self->health <= 0) + return; + if (activator->flags & FL_NOTARGET) + return; + if (!(activator->client) && !(activator->monsterinfo.aiflags & AI_GOOD_GUY)) + return; + +// delay reaction so if the monster is teleported, its sound is still heard + self->enemy = activator; + FoundTarget (self); +} + + +void monster_start_go (edict_t *self); + + +void monster_triggered_spawn (edict_t *self) +{ + self->s.origin[2] += 1; + KillBox (self); + + self->solid = SOLID_BBOX; + self->movetype = MOVETYPE_STEP; + self->svflags &= ~SVF_NOCLIENT; + self->air_finished = level.time + 12; + gi.linkentity (self); + + monster_start_go (self); + + if (self->enemy && !(self->spawnflags & 1) && !(self->enemy->flags & FL_NOTARGET)) + { + FoundTarget (self); + } + else + { + self->enemy = NULL; + } +} + +void monster_triggered_spawn_use (edict_t *self, edict_t *other, edict_t *activator) +{ + // we have a one frame delay here so we don't telefrag the guy who activated us + self->think = monster_triggered_spawn; + self->nextthink = level.time + FRAMETIME; + if (activator->client) + self->enemy = activator; + self->use = monster_use; +} + +void monster_triggered_start (edict_t *self) +{ + self->solid = SOLID_NOT; + self->movetype = MOVETYPE_NONE; + self->svflags |= SVF_NOCLIENT; + self->nextthink = 0; + self->use = monster_triggered_spawn_use; +} + + +/* +================ +monster_death_use + +When a monster dies, it fires all of its targets with the current +enemy as activator. +================ +*/ +void monster_death_use (edict_t *self) +{ + self->flags &= ~(FL_FLY|FL_SWIM); + self->monsterinfo.aiflags &= AI_GOOD_GUY; + + if (self->item) + { + Drop_Item (self, self->item); + self->item = NULL; + } + + if (self->deathtarget) + self->target = self->deathtarget; + + if (!self->target) + return; + + G_UseTargets (self, self->enemy); +} + + +//============================================================================ + +qboolean monster_start (edict_t *self) +{ + if (deathmatch->value) + { + G_FreeEdict (self); + return false; + } + + if ((self->spawnflags & 4) && !(self->monsterinfo.aiflags & AI_GOOD_GUY)) + { + self->spawnflags &= ~4; + self->spawnflags |= 1; +// gi.dprintf("fixed spawnflags on %s at %s\n", self->classname, vtos(self->s.origin)); + } + + if (!(self->monsterinfo.aiflags & AI_GOOD_GUY)) + level.total_monsters++; + + self->nextthink = level.time + FRAMETIME; + self->svflags |= SVF_MONSTER; + self->s.renderfx |= RF_FRAMELERP; + self->takedamage = DAMAGE_AIM; + self->air_finished = level.time + 12; + self->use = monster_use; + self->max_health = self->health; + self->clipmask = MASK_MONSTERSOLID; + + self->s.skinnum = 0; + self->deadflag = DEAD_NO; + self->svflags &= ~SVF_DEADMONSTER; + + if (!self->monsterinfo.checkattack) + self->monsterinfo.checkattack = M_CheckAttack; + VectorCopy (self->s.origin, self->s.old_origin); + + if (st.item) + { + self->item = FindItemByClassname (st.item); + if (!self->item) + gi.dprintf("%s at %s has bad item: %s\n", self->classname, vtos(self->s.origin), st.item); + } + + // randomize what frame they start on + if (self->monsterinfo.currentmove) + self->s.frame = self->monsterinfo.currentmove->firstframe + (rand() % (self->monsterinfo.currentmove->lastframe - self->monsterinfo.currentmove->firstframe + 1)); + + return true; +} + +void monster_start_go (edict_t *self) +{ + vec3_t v; + + if (self->health <= 0) + return; + + // check for target to combat_point and change to combattarget + if (self->target) + { + qboolean notcombat; + qboolean fixup; + edict_t *target; + + target = NULL; + notcombat = false; + fixup = false; + while ((target = G_Find (target, FOFS(targetname), self->target)) != NULL) + { + if (strcmp(target->classname, "point_combat") == 0) + { + self->combattarget = self->target; + fixup = true; + } + else + { + notcombat = true; + } + } + if (notcombat && self->combattarget) + gi.dprintf("%s at %s has target with mixed types\n", self->classname, vtos(self->s.origin)); + if (fixup) + self->target = NULL; + } + + // validate combattarget + if (self->combattarget) + { + edict_t *target; + + target = NULL; + while ((target = G_Find (target, FOFS(targetname), self->combattarget)) != NULL) + { + if (strcmp(target->classname, "point_combat") != 0) + { + gi.dprintf("%s at (%i %i %i) has a bad combattarget %s : %s at (%i %i %i)\n", + self->classname, (int)self->s.origin[0], (int)self->s.origin[1], (int)self->s.origin[2], + self->combattarget, target->classname, (int)target->s.origin[0], (int)target->s.origin[1], + (int)target->s.origin[2]); + } + } + } + + if (self->target) + { + self->goalentity = self->movetarget = G_PickTarget(self->target); + if (!self->movetarget) + { + gi.dprintf ("%s can't find target %s at %s\n", self->classname, self->target, vtos(self->s.origin)); + self->target = NULL; + self->monsterinfo.pausetime = 100000000; + self->monsterinfo.stand (self); + } + else if (strcmp (self->movetarget->classname, "path_corner") == 0) + { + VectorSubtract (self->goalentity->s.origin, self->s.origin, v); + self->ideal_yaw = self->s.angles[YAW] = vectoyaw(v); + self->monsterinfo.walk (self); + self->target = NULL; + } + else + { + self->goalentity = self->movetarget = NULL; + self->monsterinfo.pausetime = 100000000; + self->monsterinfo.stand (self); + } + } + else + { + self->monsterinfo.pausetime = 100000000; + self->monsterinfo.stand (self); + } + + self->think = monster_think; + self->nextthink = level.time + FRAMETIME; +} + + +void walkmonster_start_go (edict_t *self) +{ + if (!(self->spawnflags & 2) && level.time < 1) + { + M_droptofloor (self); + + if (self->groundentity) + if (!M_walkmove (self, 0, 0)) + gi.dprintf ("%s in solid at %s\n", self->classname, vtos(self->s.origin)); + } + + if (!self->yaw_speed) + self->yaw_speed = 20; + self->viewheight = 25; + + monster_start_go (self); + + if (self->spawnflags & 2) + monster_triggered_start (self); +} + +void walkmonster_start (edict_t *self) +{ + self->think = walkmonster_start_go; + monster_start (self); +} + + +void flymonster_start_go (edict_t *self) +{ + if (!M_walkmove (self, 0, 0)) + gi.dprintf ("%s in solid at %s\n", self->classname, vtos(self->s.origin)); + + if (!self->yaw_speed) + self->yaw_speed = 10; + self->viewheight = 25; + + monster_start_go (self); + + if (self->spawnflags & 2) + monster_triggered_start (self); +} + + +void flymonster_start (edict_t *self) +{ + self->flags |= FL_FLY; + self->think = flymonster_start_go; + monster_start (self); +} + + +void swimmonster_start_go (edict_t *self) +{ + if (!self->yaw_speed) + self->yaw_speed = 10; + self->viewheight = 10; + + monster_start_go (self); + + if (self->spawnflags & 2) + monster_triggered_start (self); +} + +void swimmonster_start (edict_t *self) +{ + self->flags |= FL_SWIM; + self->think = swimmonster_start_go; + monster_start (self); +} diff --git a/original/ctf/g_phys.c b/original/ctf/g_phys.c new file mode 100644 index 0000000..6de9e36 --- /dev/null +++ b/original/ctf/g_phys.c @@ -0,0 +1,959 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +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. + +*/ +// g_phys.c + +#include "g_local.h" + +/* + + +pushmove objects do not obey gravity, and do not interact with each other or trigger fields, but block normal movement and push normal objects when they move. + +onground is set for toss objects when they come to a complete rest. it is set for steping or walking objects + +doors, plats, etc are SOLID_BSP, and MOVETYPE_PUSH +bonus items are SOLID_TRIGGER touch, and MOVETYPE_TOSS +corpses are SOLID_NOT and MOVETYPE_TOSS +crates are SOLID_BBOX and MOVETYPE_TOSS +walking monsters are SOLID_SLIDEBOX and MOVETYPE_STEP +flying/floating monsters are SOLID_SLIDEBOX and MOVETYPE_FLY + +solid_edge items only clip against bsp models. + +*/ + + +/* +============ +SV_TestEntityPosition + +============ +*/ +edict_t *SV_TestEntityPosition (edict_t *ent) +{ + trace_t trace; + int mask; + + + if (ent->clipmask) + mask = ent->clipmask; + else + mask = MASK_SOLID; + trace = gi.trace (ent->s.origin, ent->mins, ent->maxs, ent->s.origin, ent, mask); + + if (trace.startsolid) + return g_edicts; + + return NULL; +} + + +/* +================ +SV_CheckVelocity +================ +*/ +void SV_CheckVelocity (edict_t *ent) +{ + int i; + +// +// bound velocity +// + for (i=0 ; i<3 ; i++) + { + if (ent->velocity[i] > sv_maxvelocity->value) + ent->velocity[i] = sv_maxvelocity->value; + else if (ent->velocity[i] < -sv_maxvelocity->value) + ent->velocity[i] = -sv_maxvelocity->value; + } +} + +/* +============= +SV_RunThink + +Runs thinking code for this frame if necessary +============= +*/ +qboolean SV_RunThink (edict_t *ent) +{ + float thinktime; + + thinktime = ent->nextthink; + if (thinktime <= 0) + return true; + if (thinktime > level.time+0.001) + return true; + + ent->nextthink = 0; + if (!ent->think) + gi.error ("NULL ent->think"); + ent->think (ent); + + return false; +} + +/* +================== +SV_Impact + +Two entities have touched, so run their touch functions +================== +*/ +void SV_Impact (edict_t *e1, trace_t *trace) +{ + edict_t *e2; +// cplane_t backplane; + + e2 = trace->ent; + + if (e1->touch && e1->solid != SOLID_NOT) + e1->touch (e1, e2, &trace->plane, trace->surface); + + if (e2->touch && e2->solid != SOLID_NOT) + e2->touch (e2, e1, NULL, NULL); +} + + +/* +================== +ClipVelocity + +Slide off of the impacting object +returns the blocked flags (1 = floor, 2 = step / wall) +================== +*/ +#define STOP_EPSILON 0.1 + +int ClipVelocity (vec3_t in, vec3_t normal, vec3_t out, float overbounce) +{ + float backoff; + float change; + int i, blocked; + + blocked = 0; + if (normal[2] > 0) + blocked |= 1; // floor + if (!normal[2]) + blocked |= 2; // step + + backoff = DotProduct (in, normal) * overbounce; + + for (i=0 ; i<3 ; i++) + { + change = normal[i]*backoff; + out[i] = in[i] - change; + if (out[i] > -STOP_EPSILON && out[i] < STOP_EPSILON) + out[i] = 0; + } + + return blocked; +} + + +/* +============ +SV_FlyMove + +The basic solid body movement clip that slides along multiple planes +Returns the clipflags if the velocity was modified (hit something solid) +1 = floor +2 = wall / step +4 = dead stop +============ +*/ +#define MAX_CLIP_PLANES 5 +int SV_FlyMove (edict_t *ent, float time, int mask) +{ + edict_t *hit; + int bumpcount, numbumps; + vec3_t dir; + float d; + int numplanes; + vec3_t planes[MAX_CLIP_PLANES]; + vec3_t primal_velocity, original_velocity, new_velocity; + int i, j; + trace_t trace; + vec3_t end; + float time_left; + int blocked; + + numbumps = 4; + + blocked = 0; + VectorCopy (ent->velocity, original_velocity); + VectorCopy (ent->velocity, primal_velocity); + numplanes = 0; + + time_left = time; + + ent->groundentity = NULL; + for (bumpcount=0 ; bumpcounts.origin[i] + time_left * ent->velocity[i]; + + trace = gi.trace (ent->s.origin, ent->mins, ent->maxs, end, ent, mask); + + if (trace.allsolid) + { // entity is trapped in another solid + VectorCopy (vec3_origin, ent->velocity); + return 3; + } + + if (trace.fraction > 0) + { // actually covered some distance + VectorCopy (trace.endpos, ent->s.origin); + VectorCopy (ent->velocity, original_velocity); + numplanes = 0; + } + + if (trace.fraction == 1) + break; // moved the entire distance + + hit = trace.ent; + + if (trace.plane.normal[2] > 0.7) + { + blocked |= 1; // floor + if ( hit->solid == SOLID_BSP) + { + ent->groundentity = hit; + ent->groundentity_linkcount = hit->linkcount; + } + } + if (!trace.plane.normal[2]) + { + blocked |= 2; // step + } + +// +// run the impact function +// + SV_Impact (ent, &trace); + if (!ent->inuse) + break; // removed by the impact function + + + time_left -= time_left * trace.fraction; + + // cliped to another plane + if (numplanes >= MAX_CLIP_PLANES) + { // this shouldn't really happen + VectorCopy (vec3_origin, ent->velocity); + return 3; + } + + VectorCopy (trace.plane.normal, planes[numplanes]); + numplanes++; + +// +// modify original_velocity so it parallels all of the clip planes +// + for (i=0 ; ivelocity); + } + else + { // go along the crease + if (numplanes != 2) + { +// gi.dprintf ("clip velocity, numplanes == %i\n",numplanes); + VectorCopy (vec3_origin, ent->velocity); + return 7; + } + CrossProduct (planes[0], planes[1], dir); + d = DotProduct (dir, ent->velocity); + VectorScale (dir, d, ent->velocity); + } + +// +// if original velocity is against the original velocity, stop dead +// to avoid tiny occilations in sloping corners +// + if (DotProduct (ent->velocity, primal_velocity) <= 0) + { + VectorCopy (vec3_origin, ent->velocity); + return blocked; + } + } + + return blocked; +} + + +/* +============ +SV_AddGravity + +============ +*/ +void SV_AddGravity (edict_t *ent) +{ + ent->velocity[2] -= ent->gravity * sv_gravity->value * FRAMETIME; +} + +/* +=============================================================================== + +PUSHMOVE + +=============================================================================== +*/ + +/* +============ +SV_PushEntity + +Does not change the entities velocity at all +============ +*/ +trace_t SV_PushEntity (edict_t *ent, vec3_t push) +{ + trace_t trace; + vec3_t start; + vec3_t end; + int mask; + + VectorCopy (ent->s.origin, start); + VectorAdd (start, push, end); + +retry: + if (ent->clipmask) + mask = ent->clipmask; + else + mask = MASK_SOLID; + + trace = gi.trace (start, ent->mins, ent->maxs, end, ent, mask); + + VectorCopy (trace.endpos, ent->s.origin); + gi.linkentity (ent); + + if (trace.fraction != 1.0) + { + SV_Impact (ent, &trace); + + // if the pushed entity went away and the pusher is still there + if (!trace.ent->inuse && ent->inuse) + { + // move the pusher back and try again + VectorCopy (start, ent->s.origin); + gi.linkentity (ent); + goto retry; + } + } + + if (ent->inuse) + G_TouchTriggers (ent); + + return trace; +} + + +typedef struct +{ + edict_t *ent; + vec3_t origin; + vec3_t angles; + float deltayaw; +} pushed_t; +pushed_t pushed[MAX_EDICTS], *pushed_p; + +edict_t *obstacle; + +/* +============ +SV_Push + +Objects need to be moved back on a failed push, +otherwise riders would continue to slide. +============ +*/ +qboolean SV_Push (edict_t *pusher, vec3_t move, vec3_t amove) +{ + int i, e; + edict_t *check, *block; + vec3_t mins, maxs; + pushed_t *p; + vec3_t org, org2, move2, forward, right, up; + + // clamp the move to 1/8 units, so the position will + // be accurate for client side prediction + for (i=0 ; i<3 ; i++) + { + float temp; + temp = move[i]*8.0; + if (temp > 0.0) + temp += 0.5; + else + temp -= 0.5; + move[i] = 0.125 * (int)temp; + } + + // find the bounding box + for (i=0 ; i<3 ; i++) + { + mins[i] = pusher->absmin[i] + move[i]; + maxs[i] = pusher->absmax[i] + move[i]; + } + +// we need this for pushing things later + VectorSubtract (vec3_origin, amove, org); + AngleVectors (org, forward, right, up); + +// save the pusher's original position + pushed_p->ent = pusher; + VectorCopy (pusher->s.origin, pushed_p->origin); + VectorCopy (pusher->s.angles, pushed_p->angles); + if (pusher->client) + pushed_p->deltayaw = pusher->client->ps.pmove.delta_angles[YAW]; + pushed_p++; + +// move the pusher to it's final position + VectorAdd (pusher->s.origin, move, pusher->s.origin); + VectorAdd (pusher->s.angles, amove, pusher->s.angles); + gi.linkentity (pusher); + +// see if any solid entities are inside the final position + check = g_edicts+1; + for (e = 1; e < globals.num_edicts; e++, check++) + { + if (!check->inuse) + continue; + if (check->movetype == MOVETYPE_PUSH + || check->movetype == MOVETYPE_STOP + || check->movetype == MOVETYPE_NONE + || check->movetype == MOVETYPE_NOCLIP) + continue; + + if (!check->area.prev) + continue; // not linked in anywhere + + // if the entity is standing on the pusher, it will definitely be moved + if (check->groundentity != pusher) + { + // see if the ent needs to be tested + if ( check->absmin[0] >= maxs[0] + || check->absmin[1] >= maxs[1] + || check->absmin[2] >= maxs[2] + || check->absmax[0] <= mins[0] + || check->absmax[1] <= mins[1] + || check->absmax[2] <= mins[2] ) + continue; + + // see if the ent's bbox is inside the pusher's final position + if (!SV_TestEntityPosition (check)) + continue; + } + + if ((pusher->movetype == MOVETYPE_PUSH) || (check->groundentity == pusher)) + { + // move this entity + pushed_p->ent = check; + VectorCopy (check->s.origin, pushed_p->origin); + VectorCopy (check->s.angles, pushed_p->angles); + pushed_p++; + + // try moving the contacted entity + VectorAdd (check->s.origin, move, check->s.origin); + if (check->client) + { // FIXME: doesn't rotate monsters? + check->client->ps.pmove.delta_angles[YAW] += amove[YAW]; + } + + // figure movement due to the pusher's amove + VectorSubtract (check->s.origin, pusher->s.origin, org); + org2[0] = DotProduct (org, forward); + org2[1] = -DotProduct (org, right); + org2[2] = DotProduct (org, up); + VectorSubtract (org2, org, move2); + VectorAdd (check->s.origin, move2, check->s.origin); + + // may have pushed them off an edge + if (check->groundentity != pusher) + check->groundentity = NULL; + + block = SV_TestEntityPosition (check); + if (!block) + { // pushed ok + gi.linkentity (check); + // impact? + continue; + } + + // if it is ok to leave in the old position, do it + // this is only relevent for riding entities, not pushed + // FIXME: this doesn't acount for rotation + VectorSubtract (check->s.origin, move, check->s.origin); + block = SV_TestEntityPosition (check); + if (!block) + { + pushed_p--; + continue; + } + } + + // save off the obstacle so we can call the block function + obstacle = check; + + // move back any entities we already moved + // go backwards, so if the same entity was pushed + // twice, it goes back to the original position + for (p=pushed_p-1 ; p>=pushed ; p--) + { + VectorCopy (p->origin, p->ent->s.origin); + VectorCopy (p->angles, p->ent->s.angles); + if (p->ent->client) + { + p->ent->client->ps.pmove.delta_angles[YAW] = p->deltayaw; + } + gi.linkentity (p->ent); + } + return false; + } + +//FIXME: is there a better way to handle this? + // see if anything we moved has touched a trigger + for (p=pushed_p-1 ; p>=pushed ; p--) + G_TouchTriggers (p->ent); + + return true; +} + +/* +================ +SV_Physics_Pusher + +Bmodel objects don't interact with each other, but +push all box objects +================ +*/ +void SV_Physics_Pusher (edict_t *ent) +{ + vec3_t move, amove; + edict_t *part, *mv; + + // if not a team captain, so movement will be handled elsewhere + if ( ent->flags & FL_TEAMSLAVE) + return; + + // make sure all team slaves can move before commiting + // any moves or calling any think functions + // if the move is blocked, all moved objects will be backed out +//retry: + pushed_p = pushed; + for (part = ent ; part ; part=part->teamchain) + { + if (part->velocity[0] || part->velocity[1] || part->velocity[2] || + part->avelocity[0] || part->avelocity[1] || part->avelocity[2] + ) + { // object is moving + VectorScale (part->velocity, FRAMETIME, move); + VectorScale (part->avelocity, FRAMETIME, amove); + + if (!SV_Push (part, move, amove)) + break; // move was blocked + } + } + if (pushed_p > &pushed[MAX_EDICTS]) + gi.error (ERR_FATAL, "pushed_p > &pushed[MAX_EDICTS], memory corrupted"); + + if (part) + { + // the move failed, bump all nextthink times and back out moves + for (mv = ent ; mv ; mv=mv->teamchain) + { + if (mv->nextthink > 0) + mv->nextthink += FRAMETIME; + } + + // if the pusher has a "blocked" function, call it + // otherwise, just stay in place until the obstacle is gone + if (part->blocked) + part->blocked (part, obstacle); +#if 0 + // if the pushed entity went away and the pusher is still there + if (!obstacle->inuse && part->inuse) + goto retry; +#endif + } + else + { + // the move succeeded, so call all think functions + for (part = ent ; part ; part=part->teamchain) + { + SV_RunThink (part); + } + } +} + +//================================================================== + +/* +============= +SV_Physics_None + +Non moving objects can only think +============= +*/ +void SV_Physics_None (edict_t *ent) +{ +// regular thinking + SV_RunThink (ent); +} + +/* +============= +SV_Physics_Noclip + +A moving object that doesn't obey physics +============= +*/ +void SV_Physics_Noclip (edict_t *ent) +{ +// regular thinking + if (!SV_RunThink (ent)) + return; + + VectorMA (ent->s.angles, FRAMETIME, ent->avelocity, ent->s.angles); + VectorMA (ent->s.origin, FRAMETIME, ent->velocity, ent->s.origin); + + gi.linkentity (ent); +} + +/* +============================================================================== + +TOSS / BOUNCE + +============================================================================== +*/ + +/* +============= +SV_Physics_Toss + +Toss, bounce, and fly movement. When onground, do nothing. +============= +*/ +void SV_Physics_Toss (edict_t *ent) +{ + trace_t trace; + vec3_t move; + float backoff; + edict_t *slave; + qboolean wasinwater; + qboolean isinwater; + vec3_t old_origin; + +// regular thinking + SV_RunThink (ent); + + // if not a team captain, so movement will be handled elsewhere + if ( ent->flags & FL_TEAMSLAVE) + return; + + if (ent->velocity[2] > 0) + ent->groundentity = NULL; + +// check for the groundentity going away + if (ent->groundentity) + if (!ent->groundentity->inuse) + ent->groundentity = NULL; + +// if onground, return without moving + if ( ent->groundentity ) + return; + + VectorCopy (ent->s.origin, old_origin); + + SV_CheckVelocity (ent); + +// add gravity + if (ent->movetype != MOVETYPE_FLY + && ent->movetype != MOVETYPE_FLYMISSILE) + SV_AddGravity (ent); + +// move angles + VectorMA (ent->s.angles, FRAMETIME, ent->avelocity, ent->s.angles); + +// move origin + VectorScale (ent->velocity, FRAMETIME, move); + trace = SV_PushEntity (ent, move); + if (!ent->inuse) + return; + + if (trace.fraction < 1) + { + if (ent->movetype == MOVETYPE_BOUNCE) + backoff = 1.5; + else + backoff = 1; + + ClipVelocity (ent->velocity, trace.plane.normal, ent->velocity, backoff); + + // stop if on ground + if (trace.plane.normal[2] > 0.7) + { + if (ent->velocity[2] < 60 || ent->movetype != MOVETYPE_BOUNCE ) + { + ent->groundentity = trace.ent; + ent->groundentity_linkcount = trace.ent->linkcount; + VectorCopy (vec3_origin, ent->velocity); + VectorCopy (vec3_origin, ent->avelocity); + } + } + +// if (ent->touch) +// ent->touch (ent, trace.ent, &trace.plane, trace.surface); + } + +// check for water transition + wasinwater = (ent->watertype & MASK_WATER); + ent->watertype = gi.pointcontents (ent->s.origin); + isinwater = ent->watertype & MASK_WATER; + + if (isinwater) + ent->waterlevel = 1; + else + ent->waterlevel = 0; + + if (!wasinwater && isinwater) + gi.positioned_sound (old_origin, g_edicts, CHAN_AUTO, gi.soundindex("misc/h2ohit1.wav"), 1, 1, 0); + else if (wasinwater && !isinwater) + gi.positioned_sound (ent->s.origin, g_edicts, CHAN_AUTO, gi.soundindex("misc/h2ohit1.wav"), 1, 1, 0); + +// move teamslaves + for (slave = ent->teamchain; slave; slave = slave->teamchain) + { + VectorCopy (ent->s.origin, slave->s.origin); + gi.linkentity (slave); + } +} + +/* +=============================================================================== + +STEPPING MOVEMENT + +=============================================================================== +*/ + +/* +============= +SV_Physics_Step + +Monsters freefall when they don't have a ground entity, otherwise +all movement is done with discrete steps. + +This is also used for objects that have become still on the ground, but +will fall if the floor is pulled out from under them. +FIXME: is this true? +============= +*/ + +//FIXME: hacked in for E3 demo +#define sv_stopspeed 100 +#define sv_friction 6 +#define sv_waterfriction 1 + +void SV_AddRotationalFriction (edict_t *ent) +{ + int n; + float adjustment; + + VectorMA (ent->s.angles, FRAMETIME, ent->avelocity, ent->s.angles); + adjustment = FRAMETIME * sv_stopspeed * sv_friction; + for (n = 0; n < 3; n++) + { + if (ent->avelocity[n] > 0) + { + ent->avelocity[n] -= adjustment; + if (ent->avelocity[n] < 0) + ent->avelocity[n] = 0; + } + else + { + ent->avelocity[n] += adjustment; + if (ent->avelocity[n] > 0) + ent->avelocity[n] = 0; + } + } +} + +void SV_Physics_Step (edict_t *ent) +{ + qboolean wasonground; + qboolean hitsound = false; + float *vel; + float speed, newspeed, control; + float friction; + edict_t *groundentity; + int mask; + + // airborn monsters should always check for ground + if (!ent->groundentity) + M_CheckGround (ent); + + groundentity = ent->groundentity; + + SV_CheckVelocity (ent); + + if (groundentity) + wasonground = true; + else + wasonground = false; + + if (ent->avelocity[0] || ent->avelocity[1] || ent->avelocity[2]) + SV_AddRotationalFriction (ent); + + // add gravity except: + // flying monsters + // swimming monsters who are in the water + if (! wasonground) + if (!(ent->flags & FL_FLY)) + if (!((ent->flags & FL_SWIM) && (ent->waterlevel > 2))) + { + if (ent->velocity[2] < sv_gravity->value*-0.1) + hitsound = true; + if (ent->waterlevel == 0) + SV_AddGravity (ent); + } + + // friction for flying monsters that have been given vertical velocity + if ((ent->flags & FL_FLY) && (ent->velocity[2] != 0)) + { + speed = fabs(ent->velocity[2]); + control = speed < sv_stopspeed ? sv_stopspeed : speed; + friction = sv_friction/3; + newspeed = speed - (FRAMETIME * control * friction); + if (newspeed < 0) + newspeed = 0; + newspeed /= speed; + ent->velocity[2] *= newspeed; + } + + // friction for flying monsters that have been given vertical velocity + if ((ent->flags & FL_SWIM) && (ent->velocity[2] != 0)) + { + speed = fabs(ent->velocity[2]); + control = speed < sv_stopspeed ? sv_stopspeed : speed; + newspeed = speed - (FRAMETIME * control * sv_waterfriction * ent->waterlevel); + if (newspeed < 0) + newspeed = 0; + newspeed /= speed; + ent->velocity[2] *= newspeed; + } + + if (ent->velocity[2] || ent->velocity[1] || ent->velocity[0]) + { + // apply friction + // let dead monsters who aren't completely onground slide + if ((wasonground) || (ent->flags & (FL_SWIM|FL_FLY))) + if (!(ent->health <= 0.0 && !M_CheckBottom(ent))) + { + vel = ent->velocity; + speed = sqrt(vel[0]*vel[0] +vel[1]*vel[1]); + if (speed) + { + friction = sv_friction; + + control = speed < sv_stopspeed ? sv_stopspeed : speed; + newspeed = speed - FRAMETIME*control*friction; + + if (newspeed < 0) + newspeed = 0; + newspeed /= speed; + + vel[0] *= newspeed; + vel[1] *= newspeed; + } + } + + if (ent->svflags & SVF_MONSTER) + mask = MASK_MONSTERSOLID; + else + mask = MASK_SOLID; + SV_FlyMove (ent, FRAMETIME, mask); + + gi.linkentity (ent); + G_TouchTriggers (ent); + + if (ent->groundentity) + if (!wasonground) + if (hitsound) + gi.sound (ent, 0, gi.soundindex("world/land.wav"), 1, 1, 0); + } + +// regular thinking + SV_RunThink (ent); +} + +//============================================================================ +/* +================ +G_RunEntity + +================ +*/ +void G_RunEntity (edict_t *ent) +{ + if (ent->prethink) + ent->prethink (ent); + + switch ( (int)ent->movetype) + { + case MOVETYPE_PUSH: + case MOVETYPE_STOP: + SV_Physics_Pusher (ent); + break; + case MOVETYPE_NONE: + SV_Physics_None (ent); + break; + case MOVETYPE_NOCLIP: + SV_Physics_Noclip (ent); + break; + case MOVETYPE_STEP: + SV_Physics_Step (ent); + break; + case MOVETYPE_TOSS: + case MOVETYPE_BOUNCE: + case MOVETYPE_FLY: + case MOVETYPE_FLYMISSILE: + SV_Physics_Toss (ent); + break; + default: + gi.error ("SV_Physics: bad movetype %i", (int)ent->movetype); + } +} diff --git a/original/ctf/g_save.c b/original/ctf/g_save.c new file mode 100644 index 0000000..406677d --- /dev/null +++ b/original/ctf/g_save.c @@ -0,0 +1,744 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +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. + +*/ + +#include "g_local.h" + +field_t fields[] = { + {"classname", FOFS(classname), F_LSTRING}, + {"origin", FOFS(s.origin), F_VECTOR}, + {"model", FOFS(model), F_LSTRING}, + {"spawnflags", FOFS(spawnflags), F_INT}, + {"speed", FOFS(speed), F_FLOAT}, + {"accel", FOFS(accel), F_FLOAT}, + {"decel", FOFS(decel), F_FLOAT}, + {"target", FOFS(target), F_LSTRING}, + {"targetname", FOFS(targetname), F_LSTRING}, + {"pathtarget", FOFS(pathtarget), F_LSTRING}, + {"deathtarget", FOFS(deathtarget), F_LSTRING}, + {"killtarget", FOFS(killtarget), F_LSTRING}, + {"combattarget", FOFS(combattarget), F_LSTRING}, + {"message", FOFS(message), F_LSTRING}, + {"team", FOFS(team), F_LSTRING}, + {"wait", FOFS(wait), F_FLOAT}, + {"delay", FOFS(delay), F_FLOAT}, + {"random", FOFS(random), F_FLOAT}, + {"move_origin", FOFS(move_origin), F_VECTOR}, + {"move_angles", FOFS(move_angles), F_VECTOR}, + {"style", FOFS(style), F_INT}, + {"count", FOFS(count), F_INT}, + {"health", FOFS(health), F_INT}, + {"sounds", FOFS(sounds), F_INT}, + {"light", 0, F_IGNORE}, + {"dmg", FOFS(dmg), F_INT}, + {"angles", FOFS(s.angles), F_VECTOR}, + {"angle", FOFS(s.angles), F_ANGLEHACK}, + {"mass", FOFS(mass), F_INT}, + {"volume", FOFS(volume), F_FLOAT}, + {"attenuation", FOFS(attenuation), F_FLOAT}, + {"map", FOFS(map), F_LSTRING}, + + // temp spawn vars -- only valid when the spawn function is called + {"lip", STOFS(lip), F_INT, FFL_SPAWNTEMP}, + {"distance", STOFS(distance), F_INT, FFL_SPAWNTEMP}, + {"height", STOFS(height), F_INT, FFL_SPAWNTEMP}, + {"noise", STOFS(noise), F_LSTRING, FFL_SPAWNTEMP}, + {"pausetime", STOFS(pausetime), F_FLOAT, FFL_SPAWNTEMP}, + {"item", STOFS(item), F_LSTRING, FFL_SPAWNTEMP}, + {"gravity", STOFS(gravity), F_LSTRING, FFL_SPAWNTEMP}, + {"sky", STOFS(sky), F_LSTRING, FFL_SPAWNTEMP}, + {"skyrotate", STOFS(skyrotate), F_FLOAT, FFL_SPAWNTEMP}, + {"skyaxis", STOFS(skyaxis), F_VECTOR, FFL_SPAWNTEMP}, + {"minyaw", STOFS(minyaw), F_FLOAT, FFL_SPAWNTEMP}, + {"maxyaw", STOFS(maxyaw), F_FLOAT, FFL_SPAWNTEMP}, + {"minpitch", STOFS(minpitch), F_FLOAT, FFL_SPAWNTEMP}, + {"maxpitch", STOFS(maxpitch), F_FLOAT, FFL_SPAWNTEMP}, + {"nextmap", STOFS(nextmap), F_LSTRING, FFL_SPAWNTEMP} +}; + +// -------- just for savegames ---------- +// all pointer fields should be listed here, or savegames +// won't work properly (they will crash and burn). +// this wasn't just tacked on to the fields array, because +// these don't need names, we wouldn't want map fields using +// some of these, and if one were accidentally present twice +// it would double swizzle (fuck) the pointer. + +field_t savefields[] = +{ + {"", FOFS(classname), F_LSTRING}, + {"", FOFS(target), F_LSTRING}, + {"", FOFS(targetname), F_LSTRING}, + {"", FOFS(killtarget), F_LSTRING}, + {"", FOFS(team), F_LSTRING}, + {"", FOFS(pathtarget), F_LSTRING}, + {"", FOFS(deathtarget), F_LSTRING}, + {"", FOFS(combattarget), F_LSTRING}, + {"", FOFS(model), F_LSTRING}, + {"", FOFS(map), F_LSTRING}, + {"", FOFS(message), F_LSTRING}, + + {"", FOFS(client), F_CLIENT}, + {"", FOFS(item), F_ITEM}, + + {"", FOFS(goalentity), F_EDICT}, + {"", FOFS(movetarget), F_EDICT}, + {"", FOFS(enemy), F_EDICT}, + {"", FOFS(oldenemy), F_EDICT}, + {"", FOFS(activator), F_EDICT}, + {"", FOFS(groundentity), F_EDICT}, + {"", FOFS(teamchain), F_EDICT}, + {"", FOFS(teammaster), F_EDICT}, + {"", FOFS(owner), F_EDICT}, + {"", FOFS(mynoise), F_EDICT}, + {"", FOFS(mynoise2), F_EDICT}, + {"", FOFS(target_ent), F_EDICT}, + {"", FOFS(chain), F_EDICT}, + + {NULL, 0, F_INT} +}; + +field_t levelfields[] = +{ + {"", LLOFS(changemap), F_LSTRING}, + + {"", LLOFS(sight_client), F_EDICT}, + {"", LLOFS(sight_entity), F_EDICT}, + {"", LLOFS(sound_entity), F_EDICT}, + {"", LLOFS(sound2_entity), F_EDICT}, + + {NULL, 0, F_INT} +}; + +field_t clientfields[] = +{ + {"", CLOFS(pers.weapon), F_ITEM}, + {"", CLOFS(pers.lastweapon), F_ITEM}, + {"", CLOFS(newweapon), F_ITEM}, + + {NULL, 0, F_INT} +}; + +/* +============ +InitGame + +This will be called when the dll is first loaded, which +only happens when a new game is started or a save game +is loaded. +============ +*/ +void InitGame (void) +{ + gi.dprintf ("==== InitGame ====\n"); + + gun_x = gi.cvar ("gun_x", "0", 0); + gun_y = gi.cvar ("gun_y", "0", 0); + gun_z = gi.cvar ("gun_z", "0", 0); + + //FIXME: sv_ prefix is wrong for these + sv_rollspeed = gi.cvar ("sv_rollspeed", "200", 0); + sv_rollangle = gi.cvar ("sv_rollangle", "2", 0); + sv_maxvelocity = gi.cvar ("sv_maxvelocity", "2000", 0); + sv_gravity = gi.cvar ("sv_gravity", "800", 0); + + // noset vars + dedicated = gi.cvar ("dedicated", "0", CVAR_NOSET); + + // latched vars + sv_cheats = gi.cvar ("cheats", "0", CVAR_SERVERINFO|CVAR_LATCH); + gi.cvar ("gamename", GAMEVERSION , CVAR_SERVERINFO | CVAR_LATCH); + gi.cvar ("gamedate", __DATE__ , CVAR_SERVERINFO | CVAR_LATCH); + + maxclients = gi.cvar ("maxclients", "4", CVAR_SERVERINFO | CVAR_LATCH); + deathmatch = gi.cvar ("deathmatch", "0", CVAR_LATCH); + coop = gi.cvar ("coop", "0", CVAR_LATCH); + skill = gi.cvar ("skill", "1", CVAR_LATCH); + maxentities = gi.cvar ("maxentities", "1024", CVAR_LATCH); + +//ZOID +//This game.dll only supports deathmatch + if (!deathmatch->value) { + gi.dprintf("Forcing deathmatch."); + gi.cvar_set("deathmatch", "1"); + } + //force coop off + if (coop->value) + gi.cvar_set("coop", "0"); +//ZOID + + + // change anytime vars + dmflags = gi.cvar ("dmflags", "0", CVAR_SERVERINFO); + fraglimit = gi.cvar ("fraglimit", "0", CVAR_SERVERINFO); + timelimit = gi.cvar ("timelimit", "0", CVAR_SERVERINFO); +//ZOID + capturelimit = gi.cvar ("capturelimit", "0", CVAR_SERVERINFO); + instantweap = gi.cvar ("instantweap", "0", CVAR_SERVERINFO); +//ZOID + password = gi.cvar ("password", "", CVAR_USERINFO); + filterban = gi.cvar ("filterban", "1", 0); + + g_select_empty = gi.cvar ("g_select_empty", "0", CVAR_ARCHIVE); + + run_pitch = gi.cvar ("run_pitch", "0.002", 0); + run_roll = gi.cvar ("run_roll", "0.005", 0); + bob_up = gi.cvar ("bob_up", "0.005", 0); + bob_pitch = gi.cvar ("bob_pitch", "0.002", 0); + bob_roll = gi.cvar ("bob_roll", "0.002", 0); + + // flood control + flood_msgs = gi.cvar ("flood_msgs", "4", 0); + flood_persecond = gi.cvar ("flood_persecond", "4", 0); + flood_waitdelay = gi.cvar ("flood_waitdelay", "10", 0); + + // dm map list + sv_maplist = gi.cvar ("sv_maplist", "", 0); + + // items + InitItems (); + + Com_sprintf (game.helpmessage1, sizeof(game.helpmessage1), ""); + + Com_sprintf (game.helpmessage2, sizeof(game.helpmessage2), ""); + + // initialize all entities for this game + game.maxentities = maxentities->value; + g_edicts = gi.TagMalloc (game.maxentities * sizeof(g_edicts[0]), TAG_GAME); + globals.edicts = g_edicts; + globals.max_edicts = game.maxentities; + + // initialize all clients for this game + game.maxclients = maxclients->value; + game.clients = gi.TagMalloc (game.maxclients * sizeof(game.clients[0]), TAG_GAME); + globals.num_edicts = game.maxclients+1; + +//ZOID + CTFInit(); +//ZOID +} + +//========================================================= + +void WriteField1 (FILE *f, field_t *field, byte *base) +{ + void *p; + int len; + int index; + + p = (void *)(base + field->ofs); + switch (field->type) + { + case F_INT: + case F_FLOAT: + case F_ANGLEHACK: + case F_VECTOR: + case F_IGNORE: + break; + + case F_LSTRING: + case F_GSTRING: + if ( *(char **)p ) + len = strlen(*(char **)p) + 1; + else + len = 0; + *(int *)p = len; + break; + case F_EDICT: + if ( *(edict_t **)p == NULL) + index = -1; + else + index = *(edict_t **)p - g_edicts; + *(int *)p = index; + break; + case F_CLIENT: + if ( *(gclient_t **)p == NULL) + index = -1; + else + index = *(gclient_t **)p - game.clients; + *(int *)p = index; + break; + case F_ITEM: + if ( *(edict_t **)p == NULL) + index = -1; + else + index = *(gitem_t **)p - itemlist; + *(int *)p = index; + break; + + default: + gi.error ("WriteEdict: unknown field type"); + } +} + +void WriteField2 (FILE *f, field_t *field, byte *base) +{ + int len; + void *p; + + p = (void *)(base + field->ofs); + switch (field->type) + { + case F_LSTRING: + case F_GSTRING: + if ( *(char **)p ) + { + len = strlen(*(char **)p) + 1; + fwrite (*(char **)p, len, 1, f); + } + break; + } +} + +void ReadField (FILE *f, field_t *field, byte *base) +{ + void *p; + int len; + int index; + + p = (void *)(base + field->ofs); + switch (field->type) + { + case F_INT: + case F_FLOAT: + case F_ANGLEHACK: + case F_VECTOR: + case F_IGNORE: + break; + + case F_LSTRING: + len = *(int *)p; + if (!len) + *(char **)p = NULL; + else + { + *(char **)p = gi.TagMalloc (len, TAG_LEVEL); + fread (*(char **)p, len, 1, f); + } + break; + case F_GSTRING: + len = *(int *)p; + if (!len) + *(char **)p = NULL; + else + { + *(char **)p = gi.TagMalloc (len, TAG_GAME); + fread (*(char **)p, len, 1, f); + } + break; + case F_EDICT: + index = *(int *)p; + if ( index == -1 ) + *(edict_t **)p = NULL; + else + *(edict_t **)p = &g_edicts[index]; + break; + case F_CLIENT: + index = *(int *)p; + if ( index == -1 ) + *(gclient_t **)p = NULL; + else + *(gclient_t **)p = &game.clients[index]; + break; + case F_ITEM: + index = *(int *)p; + if ( index == -1 ) + *(gitem_t **)p = NULL; + else + *(gitem_t **)p = &itemlist[index]; + break; + + default: + gi.error ("ReadEdict: unknown field type"); + } +} + +//========================================================= + +/* +============== +WriteClient + +All pointer variables (except function pointers) must be handled specially. +============== +*/ +void WriteClient (FILE *f, gclient_t *client) +{ + field_t *field; + gclient_t temp; + + // all of the ints, floats, and vectors stay as they are + temp = *client; + + // change the pointers to lengths or indexes + for (field=clientfields ; field->name ; field++) + { + WriteField1 (f, field, (byte *)&temp); + } + + // write the block + fwrite (&temp, sizeof(temp), 1, f); + + // now write any allocated data following the edict + for (field=clientfields ; field->name ; field++) + { + WriteField2 (f, field, (byte *)client); + } +} + +/* +============== +ReadClient + +All pointer variables (except function pointers) must be handled specially. +============== +*/ +void ReadClient (FILE *f, gclient_t *client) +{ + field_t *field; + + fread (client, sizeof(*client), 1, f); + + for (field=clientfields ; field->name ; field++) + { + ReadField (f, field, (byte *)client); + } +} + +/* +============ +WriteGame + +This will be called whenever the game goes to a new level, +and when the user explicitly saves the game. + +Game information include cross level data, like multi level +triggers, help computer info, and all client states. + +A single player death will automatically restore from the +last save position. +============ +*/ +void WriteGame (char *filename, qboolean autosave) +{ + FILE *f; + int i; + char str[16]; + + if (!autosave) + SaveClientData (); + + f = fopen (filename, "wb"); + if (!f) + gi.error ("Couldn't open %s", filename); + + memset (str, 0, sizeof(str)); + strcpy (str, __DATE__); + fwrite (str, sizeof(str), 1, f); + + game.autosaved = autosave; + fwrite (&game, sizeof(game), 1, f); + game.autosaved = false; + + for (i=0 ; iname ; field++) + { + WriteField1 (f, field, (byte *)&temp); + } + + // write the block + fwrite (&temp, sizeof(temp), 1, f); + + // now write any allocated data following the edict + for (field=savefields ; field->name ; field++) + { + WriteField2 (f, field, (byte *)ent); + } + +} + +/* +============== +WriteLevelLocals + +All pointer variables (except function pointers) must be handled specially. +============== +*/ +void WriteLevelLocals (FILE *f) +{ + field_t *field; + level_locals_t temp; + + // all of the ints, floats, and vectors stay as they are + temp = level; + + // change the pointers to lengths or indexes + for (field=levelfields ; field->name ; field++) + { + WriteField1 (f, field, (byte *)&temp); + } + + // write the block + fwrite (&temp, sizeof(temp), 1, f); + + // now write any allocated data following the edict + for (field=levelfields ; field->name ; field++) + { + WriteField2 (f, field, (byte *)&level); + } +} + + +/* +============== +ReadEdict + +All pointer variables (except function pointers) must be handled specially. +============== +*/ +void ReadEdict (FILE *f, edict_t *ent) +{ + field_t *field; + + fread (ent, sizeof(*ent), 1, f); + + for (field=savefields ; field->name ; field++) + { + ReadField (f, field, (byte *)ent); + } +} + +/* +============== +ReadLevelLocals + +All pointer variables (except function pointers) must be handled specially. +============== +*/ +void ReadLevelLocals (FILE *f) +{ + field_t *field; + + fread (&level, sizeof(level), 1, f); + + for (field=levelfields ; field->name ; field++) + { + ReadField (f, field, (byte *)&level); + } +} + +/* +================= +WriteLevel + +================= +*/ +void WriteLevel (char *filename) +{ + int i; + edict_t *ent; + FILE *f; + void *base; + + f = fopen (filename, "wb"); + if (!f) + gi.error ("Couldn't open %s", filename); + + // write out edict size for checking + i = sizeof(edict_t); + fwrite (&i, sizeof(i), 1, f); + + // write out a function pointer for checking + base = (void *)InitGame; + fwrite (&base, sizeof(base), 1, f); + + // write out level_locals_t + WriteLevelLocals (f); + + // write out all the entities + for (i=0 ; iinuse) + continue; + fwrite (&i, sizeof(i), 1, f); + WriteEdict (f, ent); + } + i = -1; + fwrite (&i, sizeof(i), 1, f); + + fclose (f); +} + + +/* +================= +ReadLevel + +SpawnEntities will allready have been called on the +level the same way it was when the level was saved. + +That is necessary to get the baselines +set up identically. + +The server will have cleared all of the world links before +calling ReadLevel. + +No clients are connected yet. +================= +*/ +void ReadLevel (char *filename) +{ + int entnum; + FILE *f; + int i; + void *base; + edict_t *ent; + + f = fopen (filename, "rb"); + if (!f) + gi.error ("Couldn't open %s", filename); + + // free any dynamic memory allocated by loading the level + // base state + gi.FreeTags (TAG_LEVEL); + + // wipe all the entities + memset (g_edicts, 0, game.maxentities*sizeof(g_edicts[0])); + globals.num_edicts = maxclients->value+1; + + // check edict size + fread (&i, sizeof(i), 1, f); + if (i != sizeof(edict_t)) + { + fclose (f); + gi.error ("ReadLevel: mismatched edict size"); + } + + // check function pointer base address + fread (&base, sizeof(base), 1, f); + if (base != (void *)InitGame) + { + fclose (f); + gi.error ("ReadLevel: function pointers have moved"); + } + + // load the level locals + ReadLevelLocals (f); + + // load all the entities + while (1) + { + if (fread (&entnum, sizeof(entnum), 1, f) != 1) + { + fclose (f); + gi.error ("ReadLevel: failed to read entnum"); + } + if (entnum == -1) + break; + if (entnum >= globals.num_edicts) + globals.num_edicts = entnum+1; + + ent = &g_edicts[entnum]; + ReadEdict (f, ent); + + // let the server rebuild world links for this ent + memset (&ent->area, 0, sizeof(ent->area)); + gi.linkentity (ent); + } + + fclose (f); + + // mark all clients as unconnected + for (i=0 ; ivalue ; i++) + { + ent = &g_edicts[i+1]; + ent->client = game.clients + i; + ent->client->pers.connected = false; + } + + // do any load time things at this point + for (i=0 ; iinuse) + continue; + + // fire any cross-level triggers + if (ent->classname) + if (strcmp(ent->classname, "target_crosslevel_target") == 0) + ent->nextthink = level.time + ent->delay; + } +} + diff --git a/original/ctf/g_spawn.c b/original/ctf/g_spawn.c new file mode 100644 index 0000000..f58e38a --- /dev/null +++ b/original/ctf/g_spawn.c @@ -0,0 +1,989 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +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. + +*/ + +#include "g_local.h" + +typedef struct +{ + char *name; + void (*spawn)(edict_t *ent); +} spawn_t; + + +void SP_item_health (edict_t *self); +void SP_item_health_small (edict_t *self); +void SP_item_health_large (edict_t *self); +void SP_item_health_mega (edict_t *self); + +void SP_info_player_start (edict_t *ent); +void SP_info_player_deathmatch (edict_t *ent); +void SP_info_player_coop (edict_t *ent); +void SP_info_player_intermission (edict_t *ent); + +void SP_func_plat (edict_t *ent); +void SP_func_rotating (edict_t *ent); +void SP_func_button (edict_t *ent); +void SP_func_door (edict_t *ent); +void SP_func_door_secret (edict_t *ent); +void SP_func_door_rotating (edict_t *ent); +void SP_func_water (edict_t *ent); +void SP_func_train (edict_t *ent); +void SP_func_conveyor (edict_t *self); +void SP_func_wall (edict_t *self); +void SP_func_object (edict_t *self); +void SP_func_explosive (edict_t *self); +void SP_func_timer (edict_t *self); +void SP_func_areaportal (edict_t *ent); +void SP_func_clock (edict_t *ent); +void SP_func_killbox (edict_t *ent); + +void SP_trigger_always (edict_t *ent); +void SP_trigger_once (edict_t *ent); +void SP_trigger_multiple (edict_t *ent); +void SP_trigger_relay (edict_t *ent); +void SP_trigger_push (edict_t *ent); +void SP_trigger_hurt (edict_t *ent); +void SP_trigger_key (edict_t *ent); +void SP_trigger_counter (edict_t *ent); +void SP_trigger_elevator (edict_t *ent); +void SP_trigger_gravity (edict_t *ent); +void SP_trigger_monsterjump (edict_t *ent); + +void SP_target_temp_entity (edict_t *ent); +void SP_target_speaker (edict_t *ent); +void SP_target_explosion (edict_t *ent); +void SP_target_changelevel (edict_t *ent); +void SP_target_secret (edict_t *ent); +void SP_target_goal (edict_t *ent); +void SP_target_splash (edict_t *ent); +void SP_target_spawner (edict_t *ent); +void SP_target_blaster (edict_t *ent); +void SP_target_crosslevel_trigger (edict_t *ent); +void SP_target_crosslevel_target (edict_t *ent); +void SP_target_laser (edict_t *self); +void SP_target_help (edict_t *ent); +void SP_target_actor (edict_t *ent); +void SP_target_lightramp (edict_t *self); +void SP_target_earthquake (edict_t *ent); +void SP_target_character (edict_t *ent); +void SP_target_string (edict_t *ent); + +void SP_worldspawn (edict_t *ent); +void SP_viewthing (edict_t *ent); + +void SP_light (edict_t *self); +void SP_light_mine1 (edict_t *ent); +void SP_light_mine2 (edict_t *ent); +void SP_info_null (edict_t *self); +void SP_info_notnull (edict_t *self); +void SP_path_corner (edict_t *self); +void SP_point_combat (edict_t *self); + +void SP_misc_explobox (edict_t *self); +void SP_misc_banner (edict_t *self); +void SP_misc_satellite_dish (edict_t *self); +void SP_misc_actor (edict_t *self); +void SP_misc_gib_arm (edict_t *self); +void SP_misc_gib_leg (edict_t *self); +void SP_misc_gib_head (edict_t *self); +void SP_misc_insane (edict_t *self); +void SP_misc_deadsoldier (edict_t *self); +void SP_misc_viper (edict_t *self); +void SP_misc_viper_bomb (edict_t *self); +void SP_misc_bigviper (edict_t *self); +void SP_misc_strogg_ship (edict_t *self); +void SP_misc_teleporter (edict_t *self); +void SP_misc_teleporter_dest (edict_t *self); +void SP_misc_blackhole (edict_t *self); +void SP_misc_eastertank (edict_t *self); +void SP_misc_easterchick (edict_t *self); +void SP_misc_easterchick2 (edict_t *self); + +void SP_monster_berserk (edict_t *self); +void SP_monster_gladiator (edict_t *self); +void SP_monster_gunner (edict_t *self); +void SP_monster_infantry (edict_t *self); +void SP_monster_soldier_light (edict_t *self); +void SP_monster_soldier (edict_t *self); +void SP_monster_soldier_ss (edict_t *self); +void SP_monster_tank (edict_t *self); +void SP_monster_medic (edict_t *self); +void SP_monster_flipper (edict_t *self); +void SP_monster_chick (edict_t *self); +void SP_monster_parasite (edict_t *self); +void SP_monster_flyer (edict_t *self); +void SP_monster_brain (edict_t *self); +void SP_monster_floater (edict_t *self); +void SP_monster_hover (edict_t *self); +void SP_monster_mutant (edict_t *self); +void SP_monster_supertank (edict_t *self); +void SP_monster_boss2 (edict_t *self); +void SP_monster_jorg (edict_t *self); +void SP_monster_boss3_stand (edict_t *self); + +void SP_monster_commander_body (edict_t *self); + +void SP_turret_breach (edict_t *self); +void SP_turret_base (edict_t *self); +void SP_turret_driver (edict_t *self); + + +spawn_t spawns[] = { + {"item_health", SP_item_health}, + {"item_health_small", SP_item_health_small}, + {"item_health_large", SP_item_health_large}, + {"item_health_mega", SP_item_health_mega}, + + {"info_player_start", SP_info_player_start}, + {"info_player_deathmatch", SP_info_player_deathmatch}, + {"info_player_coop", SP_info_player_coop}, + {"info_player_intermission", SP_info_player_intermission}, +//ZOID + {"info_player_team1", SP_info_player_team1}, + {"info_player_team2", SP_info_player_team2}, +//ZOID + + {"func_plat", SP_func_plat}, + {"func_button", SP_func_button}, + {"func_door", SP_func_door}, + {"func_door_secret", SP_func_door_secret}, + {"func_door_rotating", SP_func_door_rotating}, + {"func_rotating", SP_func_rotating}, + {"func_train", SP_func_train}, + {"func_water", SP_func_water}, + {"func_conveyor", SP_func_conveyor}, + {"func_areaportal", SP_func_areaportal}, + {"func_clock", SP_func_clock}, + {"func_wall", SP_func_wall}, + {"func_object", SP_func_object}, + {"func_timer", SP_func_timer}, + {"func_explosive", SP_func_explosive}, + {"func_killbox", SP_func_killbox}, + + {"trigger_always", SP_trigger_always}, + {"trigger_once", SP_trigger_once}, + {"trigger_multiple", SP_trigger_multiple}, + {"trigger_relay", SP_trigger_relay}, + {"trigger_push", SP_trigger_push}, + {"trigger_hurt", SP_trigger_hurt}, + {"trigger_key", SP_trigger_key}, + {"trigger_counter", SP_trigger_counter}, + {"trigger_elevator", SP_trigger_elevator}, + {"trigger_gravity", SP_trigger_gravity}, + {"trigger_monsterjump", SP_trigger_monsterjump}, + + {"target_temp_entity", SP_target_temp_entity}, + {"target_speaker", SP_target_speaker}, + {"target_explosion", SP_target_explosion}, + {"target_changelevel", SP_target_changelevel}, + {"target_secret", SP_target_secret}, + {"target_goal", SP_target_goal}, + {"target_splash", SP_target_splash}, + {"target_spawner", SP_target_spawner}, + {"target_blaster", SP_target_blaster}, + {"target_crosslevel_trigger", SP_target_crosslevel_trigger}, + {"target_crosslevel_target", SP_target_crosslevel_target}, + {"target_laser", SP_target_laser}, + {"target_help", SP_target_help}, +#if 0 // remove monster code + {"target_actor", SP_target_actor}, +#endif + {"target_lightramp", SP_target_lightramp}, + {"target_earthquake", SP_target_earthquake}, + {"target_character", SP_target_character}, + {"target_string", SP_target_string}, + + {"worldspawn", SP_worldspawn}, + {"viewthing", SP_viewthing}, + + {"light", SP_light}, + {"light_mine1", SP_light_mine1}, + {"light_mine2", SP_light_mine2}, + {"info_null", SP_info_null}, + {"func_group", SP_info_null}, + {"info_notnull", SP_info_notnull}, + {"path_corner", SP_path_corner}, + {"point_combat", SP_point_combat}, + + {"misc_explobox", SP_misc_explobox}, + {"misc_banner", SP_misc_banner}, +//ZOID + {"misc_ctf_banner", SP_misc_ctf_banner}, + {"misc_ctf_small_banner", SP_misc_ctf_small_banner}, +//ZOID + {"misc_satellite_dish", SP_misc_satellite_dish}, +#if 0 // remove monster code + {"misc_actor", SP_misc_actor}, +#endif + {"misc_gib_arm", SP_misc_gib_arm}, + {"misc_gib_leg", SP_misc_gib_leg}, + {"misc_gib_head", SP_misc_gib_head}, +#if 0 // remove monster code + {"misc_insane", SP_misc_insane}, +#endif + {"misc_deadsoldier", SP_misc_deadsoldier}, + {"misc_viper", SP_misc_viper}, + {"misc_viper_bomb", SP_misc_viper_bomb}, + {"misc_bigviper", SP_misc_bigviper}, + {"misc_strogg_ship", SP_misc_strogg_ship}, + {"misc_teleporter", SP_misc_teleporter}, + {"misc_teleporter_dest", SP_misc_teleporter_dest}, +//ZOID + {"trigger_teleport", SP_trigger_teleport}, + {"info_teleport_destination", SP_info_teleport_destination}, +//ZOID + {"misc_blackhole", SP_misc_blackhole}, + {"misc_eastertank", SP_misc_eastertank}, + {"misc_easterchick", SP_misc_easterchick}, + {"misc_easterchick2", SP_misc_easterchick2}, + +#if 0 // remove monster code + {"monster_berserk", SP_monster_berserk}, + {"monster_gladiator", SP_monster_gladiator}, + {"monster_gunner", SP_monster_gunner}, + {"monster_infantry", SP_monster_infantry}, + {"monster_soldier_light", SP_monster_soldier_light}, + {"monster_soldier", SP_monster_soldier}, + {"monster_soldier_ss", SP_monster_soldier_ss}, + {"monster_tank", SP_monster_tank}, + {"monster_tank_commander", SP_monster_tank}, + {"monster_medic", SP_monster_medic}, + {"monster_flipper", SP_monster_flipper}, + {"monster_chick", SP_monster_chick}, + {"monster_parasite", SP_monster_parasite}, + {"monster_flyer", SP_monster_flyer}, + {"monster_brain", SP_monster_brain}, + {"monster_floater", SP_monster_floater}, + {"monster_hover", SP_monster_hover}, + {"monster_mutant", SP_monster_mutant}, + {"monster_supertank", SP_monster_supertank}, + {"monster_boss2", SP_monster_boss2}, + {"monster_boss3_stand", SP_monster_boss3_stand}, + {"monster_jorg", SP_monster_jorg}, + + {"monster_commander_body", SP_monster_commander_body}, + + {"turret_breach", SP_turret_breach}, + {"turret_base", SP_turret_base}, + {"turret_driver", SP_turret_driver}, +#endif + + {NULL, NULL} +}; + +/* +=============== +ED_CallSpawn + +Finds the spawn function for the entity and calls it +=============== +*/ +void ED_CallSpawn (edict_t *ent) +{ + spawn_t *s; + gitem_t *item; + int i; + + if (!ent->classname) + { + gi.dprintf ("ED_CallSpawn: NULL classname\n"); + return; + } + + // check item spawn functions + for (i=0,item=itemlist ; iclassname) + continue; + if (!strcmp(item->classname, ent->classname)) + { // found it + SpawnItem (ent, item); + return; + } + } + + // check normal spawn functions + for (s=spawns ; s->name ; s++) + { + if (!strcmp(s->name, ent->classname)) + { // found it + s->spawn (ent); + return; + } + } + gi.dprintf ("%s doesn't have a spawn function\n", ent->classname); +} + +/* +============= +ED_NewString +============= +*/ +char *ED_NewString (char *string) +{ + char *newb, *new_p; + int i,l; + + l = strlen(string) + 1; + + newb = gi.TagMalloc (l, TAG_LEVEL); + + new_p = newb; + + for (i=0 ; i< l ; i++) + { + if (string[i] == '\\' && i < l-1) + { + i++; + if (string[i] == 'n') + *new_p++ = '\n'; + else + *new_p++ = '\\'; + } + else + *new_p++ = string[i]; + } + + return newb; +} + + + + +/* +=============== +ED_ParseField + +Takes a key/value pair and sets the binary values +in an edict +=============== +*/ +void ED_ParseField (char *key, char *value, edict_t *ent) +{ + field_t *f; + byte *b; + float v; + vec3_t vec; + + for (f=fields ; f->name ; f++) + { + if (!Q_stricmp(f->name, key)) + { // found it + if (f->flags & FFL_SPAWNTEMP) + b = (byte *)&st; + else + b = (byte *)ent; + + switch (f->type) + { + case F_LSTRING: + *(char **)(b+f->ofs) = ED_NewString (value); + break; + case F_VECTOR: + sscanf (value, "%f %f %f", &vec[0], &vec[1], &vec[2]); + ((float *)(b+f->ofs))[0] = vec[0]; + ((float *)(b+f->ofs))[1] = vec[1]; + ((float *)(b+f->ofs))[2] = vec[2]; + break; + case F_INT: + *(int *)(b+f->ofs) = atoi(value); + break; + case F_FLOAT: + *(float *)(b+f->ofs) = atof(value); + break; + case F_ANGLEHACK: + v = atof(value); + ((float *)(b+f->ofs))[0] = 0; + ((float *)(b+f->ofs))[1] = v; + ((float *)(b+f->ofs))[2] = 0; + break; + case F_IGNORE: + break; + } + return; + } + } + gi.dprintf ("%s is not a field\n", key); +} + +/* +==================== +ED_ParseEdict + +Parses an edict out of the given string, returning the new position +ed should be a properly initialized empty edict. +==================== +*/ +char *ED_ParseEdict (char *data, edict_t *ent) +{ + qboolean init; + char keyname[256]; + char *com_token; + + init = false; + memset (&st, 0, sizeof(st)); + +// go through all the dictionary pairs + while (1) + { + // parse key + com_token = COM_Parse (&data); + if (com_token[0] == '}') + break; + if (!data) + gi.error ("ED_ParseEntity: EOF without closing brace"); + + strncpy (keyname, com_token, sizeof(keyname)-1); + + // parse value + com_token = COM_Parse (&data); + if (!data) + gi.error ("ED_ParseEntity: EOF without closing brace"); + + if (com_token[0] == '}') + gi.error ("ED_ParseEntity: closing brace without data"); + + init = true; + + // keynames with a leading underscore are used for utility comments, + // and are immediately discarded by quake + if (keyname[0] == '_') + continue; + + ED_ParseField (keyname, com_token, ent); + } + + if (!init) + memset (ent, 0, sizeof(*ent)); + + return data; +} + + +/* +================ +G_FindTeams + +Chain together all entities with a matching team field. + +All but the first will have the FL_TEAMSLAVE flag set. +All but the last will have the teamchain field set to the next one +================ +*/ +void G_FindTeams (void) +{ + edict_t *e, *e2, *chain; + int i, j; + int c, c2; + + c = 0; + c2 = 0; + for (i=1, e=g_edicts+i ; i < globals.num_edicts ; i++,e++) + { + if (!e->inuse) + continue; + if (!e->team) + continue; + if (e->flags & FL_TEAMSLAVE) + continue; + chain = e; + e->teammaster = e; + c++; + c2++; + for (j=i+1, e2=e+1 ; j < globals.num_edicts ; j++,e2++) + { + if (!e2->inuse) + continue; + if (!e2->team) + continue; + if (e2->flags & FL_TEAMSLAVE) + continue; + if (!strcmp(e->team, e2->team)) + { + c2++; + chain->teamchain = e2; + e2->teammaster = e; + chain = e2; + e2->flags |= FL_TEAMSLAVE; + } + } + } + + gi.dprintf ("%i teams with %i entities\n", c, c2); +} + +/* +============== +SpawnEntities + +Creates a server's entity / program execution context by +parsing textual entity definitions out of an ent file. +============== +*/ +void SpawnEntities (char *mapname, char *entities, char *spawnpoint) +{ + edict_t *ent; + int inhibit; + char *com_token; + int i; + float skill_level; + + skill_level = floor (skill->value); + if (skill_level < 0) + skill_level = 0; + if (skill_level > 3) + skill_level = 3; + if (skill->value != skill_level) + gi.cvar_forceset("skill", va("%f", skill_level)); + + SaveClientData (); + + gi.FreeTags (TAG_LEVEL); + + memset (&level, 0, sizeof(level)); + memset (g_edicts, 0, game.maxentities * sizeof (g_edicts[0])); + + strncpy (level.mapname, mapname, sizeof(level.mapname)-1); + strncpy (game.spawnpoint, spawnpoint, sizeof(game.spawnpoint)-1); + + // set client fields on player ents + for (i=0 ; iclassname, "trigger_once") && !stricmp(ent->model, "*27")) + ent->spawnflags &= ~SPAWNFLAG_NOT_HARD; + + // remove things (except the world) from different skill levels or deathmatch + if (ent != g_edicts) + { + if (deathmatch->value) + { + if ( ent->spawnflags & SPAWNFLAG_NOT_DEATHMATCH ) + { + G_FreeEdict (ent); + inhibit++; + continue; + } + } + else + { + if ( /* ((coop->value) && (ent->spawnflags & SPAWNFLAG_NOT_COOP)) || */ + ((skill->value == 0) && (ent->spawnflags & SPAWNFLAG_NOT_EASY)) || + ((skill->value == 1) && (ent->spawnflags & SPAWNFLAG_NOT_MEDIUM)) || + (((skill->value == 2) || (skill->value == 3)) && (ent->spawnflags & SPAWNFLAG_NOT_HARD)) + ) + { + G_FreeEdict (ent); + inhibit++; + continue; + } + } + + ent->spawnflags &= ~(SPAWNFLAG_NOT_EASY|SPAWNFLAG_NOT_MEDIUM|SPAWNFLAG_NOT_HARD|SPAWNFLAG_NOT_COOP|SPAWNFLAG_NOT_DEATHMATCH); + } + + ED_CallSpawn (ent); + } + + gi.dprintf ("%i entities inhibited\n", inhibit); + + G_FindTeams (); + + PlayerTrail_Init (); + +//ZOID + CTFSpawn(); +//ZOID +} + + +//=================================================================== + +#if 0 + // cursor positioning + xl + xr + yb + yt + xv + yv + + // drawing + statpic + pic + num + string + + // control + if + ifeq + ifbit + endif + +#endif + +char *single_statusbar = +"yb -24 " + +// health +"xv 0 " +"hnum " +"xv 50 " +"pic 0 " + +// ammo +"if 2 " +" xv 100 " +" anum " +" xv 150 " +" pic 2 " +"endif " + +// armor +"if 4 " +" xv 200 " +" rnum " +" xv 250 " +" pic 4 " +"endif " + +// selected item +"if 6 " +" xv 296 " +" pic 6 " +"endif " + +"yb -50 " + +// picked up item +"if 7 " +" xv 0 " +" pic 7 " +" xv 26 " +" yb -42 " +" stat_string 8 " +" yb -50 " +"endif " + +// timer +"if 9 " +" xv 262 " +" num 2 10 " +" xv 296 " +" pic 9 " +"endif " + +// help / weapon icon +"if 11 " +" xv 148 " +" pic 11 " +"endif " +; + +char *dm_statusbar = +"yb -24 " + +// health +"xv 0 " +"hnum " +"xv 50 " +"pic 0 " + +// ammo +"if 2 " +" xv 100 " +" anum " +" xv 150 " +" pic 2 " +"endif " + +// armor +"if 4 " +" xv 200 " +" rnum " +" xv 250 " +" pic 4 " +"endif " + +// selected item +"if 6 " +" xv 296 " +" pic 6 " +"endif " + +"yb -50 " + +// picked up item +"if 7 " +" xv 0 " +" pic 7 " +" xv 26 " +" yb -42 " +" stat_string 8 " +" yb -50 " +"endif " + +// timer +"if 9 " +" xv 246 " +" num 2 10 " +" xv 296 " +" pic 9 " +"endif " + +// help / weapon icon +"if 11 " +" xv 148 " +" pic 11 " +"endif " + +// frags +"xr -50 " +"yt 2 " +"num 3 14" +; + + +/*QUAKED worldspawn (0 0 0) ? + +Only used for the world. +"sky" environment map name +"skyaxis" vector axis for rotating sky +"skyrotate" speed of rotation in degrees/second +"sounds" music cd track number +"gravity" 800 is default gravity +"message" text to print at user logon +*/ +void SP_worldspawn (edict_t *ent) +{ + ent->movetype = MOVETYPE_PUSH; + ent->solid = SOLID_BSP; + ent->inuse = true; // since the world doesn't use G_Spawn() + ent->s.modelindex = 1; // world model is always index 1 + + //--------------- + + // reserve some spots for dead player bodies for coop / deathmatch + InitBodyQue (); + + // set configstrings for items + SetItemNames (); + + if (st.nextmap) + strcpy (level.nextmap, st.nextmap); + + // make some data visible to the server + + if (ent->message && ent->message[0]) + { + gi.configstring (CS_NAME, ent->message); + strncpy (level.level_name, ent->message, sizeof(level.level_name)); + } + else + strncpy (level.level_name, level.mapname, sizeof(level.level_name)); + + if (st.sky && st.sky[0]) + gi.configstring (CS_SKY, st.sky); + else + gi.configstring (CS_SKY, "unit1_"); + + gi.configstring (CS_SKYROTATE, va("%f", st.skyrotate) ); + + gi.configstring (CS_SKYAXIS, va("%f %f %f", + st.skyaxis[0], st.skyaxis[1], st.skyaxis[2]) ); + + gi.configstring (CS_CDTRACK, va("%i", ent->sounds) ); + + gi.configstring (CS_MAXCLIENTS, va("%i", (int)(maxclients->value) ) ); + + // status bar program + if (deathmatch->value) +//ZOID + if (ctf->value) { + gi.configstring (CS_STATUSBAR, ctf_statusbar); + CTFPrecache(); + } else +//ZOID + gi.configstring (CS_STATUSBAR, dm_statusbar); + else + gi.configstring (CS_STATUSBAR, single_statusbar); + + //--------------- + + + // help icon for statusbar + gi.imageindex ("i_help"); + level.pic_health = gi.imageindex ("i_health"); + gi.imageindex ("help"); + gi.imageindex ("field_3"); + + if (!st.gravity) + gi.cvar_set("sv_gravity", "800"); + else + gi.cvar_set("sv_gravity", st.gravity); + + snd_fry = gi.soundindex ("player/fry.wav"); // standing in lava / slime + + PrecacheItem (FindItem ("Blaster")); + + gi.soundindex ("player/lava1.wav"); + gi.soundindex ("player/lava2.wav"); + + gi.soundindex ("misc/pc_up.wav"); + gi.soundindex ("misc/talk1.wav"); + + gi.soundindex ("misc/udeath.wav"); + + // gibs + gi.soundindex ("items/respawn1.wav"); + + // sexed sounds + gi.soundindex ("*death1.wav"); + gi.soundindex ("*death2.wav"); + gi.soundindex ("*death3.wav"); + gi.soundindex ("*death4.wav"); + gi.soundindex ("*fall1.wav"); + gi.soundindex ("*fall2.wav"); + gi.soundindex ("*gurp1.wav"); // drowning damage + gi.soundindex ("*gurp2.wav"); + gi.soundindex ("*jump1.wav"); // player jump + gi.soundindex ("*pain25_1.wav"); + gi.soundindex ("*pain25_2.wav"); + gi.soundindex ("*pain50_1.wav"); + gi.soundindex ("*pain50_2.wav"); + gi.soundindex ("*pain75_1.wav"); + gi.soundindex ("*pain75_2.wav"); + gi.soundindex ("*pain100_1.wav"); + gi.soundindex ("*pain100_2.wav"); + + // sexed models + // THIS ORDER MUST MATCH THE DEFINES IN g_local.h + // you can add more, max 15 + gi.modelindex ("#w_blaster.md2"); + gi.modelindex ("#w_shotgun.md2"); + gi.modelindex ("#w_sshotgun.md2"); + gi.modelindex ("#w_machinegun.md2"); + gi.modelindex ("#w_chaingun.md2"); + gi.modelindex ("#a_grenades.md2"); + gi.modelindex ("#w_glauncher.md2"); + gi.modelindex ("#w_rlauncher.md2"); + gi.modelindex ("#w_hyperblaster.md2"); + gi.modelindex ("#w_railgun.md2"); + gi.modelindex ("#w_bfg.md2"); + gi.modelindex ("#w_grapple.md2"); + + //------------------- + + gi.soundindex ("player/gasp1.wav"); // gasping for air + gi.soundindex ("player/gasp2.wav"); // head breaking surface, not gasping + + gi.soundindex ("player/watr_in.wav"); // feet hitting water + gi.soundindex ("player/watr_out.wav"); // feet leaving water + + gi.soundindex ("player/watr_un.wav"); // head going underwater + + gi.soundindex ("player/u_breath1.wav"); + gi.soundindex ("player/u_breath2.wav"); + + gi.soundindex ("items/pkup.wav"); // bonus item pickup + gi.soundindex ("world/land.wav"); // landing thud + gi.soundindex ("misc/h2ohit1.wav"); // landing splash + + gi.soundindex ("items/damage.wav"); + gi.soundindex ("items/protect.wav"); + gi.soundindex ("items/protect4.wav"); + gi.soundindex ("weapons/noammo.wav"); + + gi.soundindex ("infantry/inflies1.wav"); + + sm_meat_index = gi.modelindex ("models/objects/gibs/sm_meat/tris.md2"); + gi.modelindex ("models/objects/gibs/arm/tris.md2"); + gi.modelindex ("models/objects/gibs/bone/tris.md2"); + gi.modelindex ("models/objects/gibs/bone2/tris.md2"); + gi.modelindex ("models/objects/gibs/chest/tris.md2"); + gi.modelindex ("models/objects/gibs/skull/tris.md2"); + gi.modelindex ("models/objects/gibs/head2/tris.md2"); + +// +// Setup light animation tables. 'a' is total darkness, 'z' is doublebright. +// + + // 0 normal + gi.configstring(CS_LIGHTS+0, "m"); + + // 1 FLICKER (first variety) + gi.configstring(CS_LIGHTS+1, "mmnmmommommnonmmonqnmmo"); + + // 2 SLOW STRONG PULSE + gi.configstring(CS_LIGHTS+2, "abcdefghijklmnopqrstuvwxyzyxwvutsrqponmlkjihgfedcba"); + + // 3 CANDLE (first variety) + gi.configstring(CS_LIGHTS+3, "mmmmmaaaaammmmmaaaaaabcdefgabcdefg"); + + // 4 FAST STROBE + gi.configstring(CS_LIGHTS+4, "mamamamamama"); + + // 5 GENTLE PULSE 1 + gi.configstring(CS_LIGHTS+5,"jklmnopqrstuvwxyzyxwvutsrqponmlkj"); + + // 6 FLICKER (second variety) + gi.configstring(CS_LIGHTS+6, "nmonqnmomnmomomno"); + + // 7 CANDLE (second variety) + gi.configstring(CS_LIGHTS+7, "mmmaaaabcdefgmmmmaaaammmaamm"); + + // 8 CANDLE (third variety) + gi.configstring(CS_LIGHTS+8, "mmmaaammmaaammmabcdefaaaammmmabcdefmmmaaaa"); + + // 9 SLOW STROBE (fourth variety) + gi.configstring(CS_LIGHTS+9, "aaaaaaaazzzzzzzz"); + + // 10 FLUORESCENT FLICKER + gi.configstring(CS_LIGHTS+10, "mmamammmmammamamaaamammma"); + + // 11 SLOW PULSE NOT FADE TO BLACK + gi.configstring(CS_LIGHTS+11, "abcdefghijklmnopqrrqponmlkjihgfedcba"); + + // styles 32-62 are assigned by the light program for switchable lights + + // 63 testing + gi.configstring(CS_LIGHTS+63, "a"); +} + diff --git a/original/ctf/g_svcmds.c b/original/ctf/g_svcmds.c new file mode 100644 index 0000000..6868633 --- /dev/null +++ b/original/ctf/g_svcmds.c @@ -0,0 +1,300 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +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. + +*/ + +#include "g_local.h" + + +void Svcmd_Test_f (void) +{ + gi.cprintf (NULL, PRINT_HIGH, "Svcmd_Test_f()\n"); +} + +/* +============================================================================== + +PACKET FILTERING + + +You can add or remove addresses from the filter list with: + +addip +removeip + +The ip address is specified in dot format, and any unspecified digits will match any value, so you can specify an entire class C network with "addip 192.246.40". + +Removeip will only remove an address specified exactly the same way. You cannot addip a subnet, then removeip a single host. + +listip +Prints the current list of filters. + +writeip +Dumps "addip " commands to listip.cfg so it can be execed at a later date. The filter lists are not saved and restored by default, because I beleive it would cause too much confusion. + +filterban <0 or 1> + +If 1 (the default), then ip addresses matching the current list will be prohibited from entering the game. This is the default setting. + +If 0, then only addresses matching the list will be allowed. This lets you easily set up a private game, or a game that only allows players from your local network. + + +============================================================================== +*/ + +typedef struct +{ + unsigned mask; + unsigned compare; +} ipfilter_t; + +#define MAX_IPFILTERS 1024 + +ipfilter_t ipfilters[MAX_IPFILTERS]; +int numipfilters; + +/* +================= +StringToFilter +================= +*/ +static qboolean StringToFilter (char *s, ipfilter_t *f) +{ + char num[128]; + int i, j; + byte b[4]; + byte m[4]; + + for (i=0 ; i<4 ; i++) + { + b[i] = 0; + m[i] = 0; + } + + for (i=0 ; i<4 ; i++) + { + if (*s < '0' || *s > '9') + { + gi.cprintf(NULL, PRINT_HIGH, "Bad filter address: %s\n", s); + return false; + } + + j = 0; + while (*s >= '0' && *s <= '9') + { + num[j++] = *s++; + } + num[j] = 0; + b[i] = atoi(num); + if (b[i] != 0) + m[i] = 255; + + if (!*s) + break; + s++; + } + + f->mask = *(unsigned *)m; + f->compare = *(unsigned *)b; + + return true; +} + +/* +================= +SV_FilterPacket +================= +*/ +qboolean SV_FilterPacket (char *from) +{ + int i; + unsigned in; + byte m[4]; + char *p; + + i = 0; + p = from; + while (*p && i < 4) { + m[i] = 0; + while (*p >= '0' && *p <= '9') { + m[i] = m[i]*10 + (*p - '0'); + p++; + } + if (!*p || *p == ':') + break; + i++, p++; + } + + in = *(unsigned *)m; + + for (i=0 ; ivalue; + + return (int)!filterban->value; +} + + +/* +================= +SV_AddIP_f +================= +*/ +void SVCmd_AddIP_f (void) +{ + int i; + + if (gi.argc() < 3) { + gi.cprintf(NULL, PRINT_HIGH, "Usage: addip \n"); + return; + } + + for (i=0 ; i\n"); + return; + } + + if (!StringToFilter (gi.argv(2), &f)) + return; + + for (i=0 ; istring) + sprintf (name, "%s/listip.cfg", GAMEVERSION); + else + sprintf (name, "%s/listip.cfg", game->string); + + gi.cprintf (NULL, PRINT_HIGH, "Writing %s.\n", name); + + f = fopen (name, "wb"); + if (!f) + { + gi.cprintf (NULL, PRINT_HIGH, "Couldn't open %s\n", name); + return; + } + + fprintf(f, "set filterban %d\n", (int)filterban->value); + + for (i=0 ; istyle); + gi.WritePosition (ent->s.origin); + gi.multicast (ent->s.origin, MULTICAST_PVS); +} + +void SP_target_temp_entity (edict_t *ent) +{ + ent->use = Use_Target_Tent; +} + + +//========================================================== + +//========================================================== + +/*QUAKED target_speaker (1 0 0) (-8 -8 -8) (8 8 8) looped-on looped-off reliable +"noise" wav file to play +"attenuation" +-1 = none, send to whole level +1 = normal fighting sounds +2 = idle sound level +3 = ambient sound level +"volume" 0.0 to 1.0 + +Normal sounds play each time the target is used. The reliable flag can be set for crucial voiceovers. + +Looped sounds are allways atten 3 / vol 1, and the use function toggles it on/off. +Multiple identical looping sounds will just increase volume without any speed cost. +*/ +void Use_Target_Speaker (edict_t *ent, edict_t *other, edict_t *activator) +{ + int chan; + + if (ent->spawnflags & 3) + { // looping sound toggles + if (ent->s.sound) + ent->s.sound = 0; // turn it off + else + ent->s.sound = ent->noise_index; // start it + } + else + { // normal sound + if (ent->spawnflags & 4) + chan = CHAN_VOICE|CHAN_RELIABLE; + else + chan = CHAN_VOICE; + // use a positioned_sound, because this entity won't normally be + // sent to any clients because it is invisible + gi.positioned_sound (ent->s.origin, ent, chan, ent->noise_index, ent->volume, ent->attenuation, 0); + } +} + +void SP_target_speaker (edict_t *ent) +{ + char buffer[MAX_QPATH]; + + if(!st.noise) + { + gi.dprintf("target_speaker with no noise set at %s\n", vtos(ent->s.origin)); + return; + } + if (!strstr (st.noise, ".wav")) + Com_sprintf (buffer, sizeof(buffer), "%s.wav", st.noise); + else + strncpy (buffer, st.noise, sizeof(buffer)); + ent->noise_index = gi.soundindex (buffer); + + if (!ent->volume) + ent->volume = 1.0; + + if (!ent->attenuation) + ent->attenuation = 1.0; + else if (ent->attenuation == -1) // use -1 so 0 defaults to 1 + ent->attenuation = 0; + + // check for prestarted looping sound + if (ent->spawnflags & 1) + ent->s.sound = ent->noise_index; + + ent->use = Use_Target_Speaker; + + // must link the entity so we get areas and clusters so + // the server can determine who to send updates to + gi.linkentity (ent); +} + + +//========================================================== + +void Use_Target_Help (edict_t *ent, edict_t *other, edict_t *activator) +{ + if (ent->spawnflags & 1) + strncpy (game.helpmessage1, ent->message, sizeof(game.helpmessage2)-1); + else + strncpy (game.helpmessage2, ent->message, sizeof(game.helpmessage1)-1); + + game.helpchanged++; +} + +/*QUAKED target_help (1 0 1) (-16 -16 -24) (16 16 24) help1 +When fired, the "message" key becomes the current personal computer string, and the message light will be set on all clients status bars. +*/ +void SP_target_help(edict_t *ent) +{ + if (deathmatch->value) + { // auto-remove for deathmatch + G_FreeEdict (ent); + return; + } + + if (!ent->message) + { + gi.dprintf ("%s with no message at %s\n", ent->classname, vtos(ent->s.origin)); + G_FreeEdict (ent); + return; + } + ent->use = Use_Target_Help; +} + +//========================================================== + +/*QUAKED target_secret (1 0 1) (-8 -8 -8) (8 8 8) +Counts a secret found. +These are single use targets. +*/ +void use_target_secret (edict_t *ent, edict_t *other, edict_t *activator) +{ + gi.sound (ent, CHAN_VOICE, ent->noise_index, 1, ATTN_NORM, 0); + + level.found_secrets++; + + G_UseTargets (ent, activator); + G_FreeEdict (ent); +} + +void SP_target_secret (edict_t *ent) +{ + if (deathmatch->value) + { // auto-remove for deathmatch + G_FreeEdict (ent); + return; + } + + ent->use = use_target_secret; + if (!st.noise) + st.noise = "misc/secret.wav"; + ent->noise_index = gi.soundindex (st.noise); + ent->svflags = SVF_NOCLIENT; + level.total_secrets++; + // map bug hack + if (!stricmp(level.mapname, "mine3") && ent->s.origin[0] == 280 && ent->s.origin[1] == -2048 && ent->s.origin[2] == -624) + ent->message = "You have found a secret area."; +} + +//========================================================== + +/*QUAKED target_goal (1 0 1) (-8 -8 -8) (8 8 8) +Counts a goal completed. +These are single use targets. +*/ +void use_target_goal (edict_t *ent, edict_t *other, edict_t *activator) +{ + gi.sound (ent, CHAN_VOICE, ent->noise_index, 1, ATTN_NORM, 0); + + level.found_goals++; + + if (level.found_goals == level.total_goals) + gi.configstring (CS_CDTRACK, "0"); + + G_UseTargets (ent, activator); + G_FreeEdict (ent); +} + +void SP_target_goal (edict_t *ent) +{ + if (deathmatch->value) + { // auto-remove for deathmatch + G_FreeEdict (ent); + return; + } + + ent->use = use_target_goal; + if (!st.noise) + st.noise = "misc/secret.wav"; + ent->noise_index = gi.soundindex (st.noise); + ent->svflags = SVF_NOCLIENT; + level.total_goals++; +} + +//========================================================== + + +/*QUAKED target_explosion (1 0 0) (-8 -8 -8) (8 8 8) +Spawns an explosion temporary entity when used. + +"delay" wait this long before going off +"dmg" how much radius damage should be done, defaults to 0 +*/ +void target_explosion_explode (edict_t *self) +{ + float save; + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_EXPLOSION1); + gi.WritePosition (self->s.origin); + gi.multicast (self->s.origin, MULTICAST_PHS); + + T_RadiusDamage (self, self->activator, self->dmg, NULL, self->dmg+40, MOD_EXPLOSIVE); + + save = self->delay; + self->delay = 0; + G_UseTargets (self, self->activator); + self->delay = save; +} + +void use_target_explosion (edict_t *self, edict_t *other, edict_t *activator) +{ + self->activator = activator; + + if (!self->delay) + { + target_explosion_explode (self); + return; + } + + self->think = target_explosion_explode; + self->nextthink = level.time + self->delay; +} + +void SP_target_explosion (edict_t *ent) +{ + ent->use = use_target_explosion; + ent->svflags = SVF_NOCLIENT; +} + + +//========================================================== + +/*QUAKED target_changelevel (1 0 0) (-8 -8 -8) (8 8 8) +Changes level to "map" when fired +*/ +void use_target_changelevel (edict_t *self, edict_t *other, edict_t *activator) +{ + if (level.intermissiontime) + return; // allready activated + + if (!deathmatch->value && !coop->value) + { + if (g_edicts[1].health <= 0) + return; + } + + // if noexit, do a ton of damage to other + if (deathmatch->value && !( (int)dmflags->value & DF_ALLOW_EXIT) && other != world) + { + T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, 10 * other->max_health, 1000, 0, MOD_EXIT); + return; + } + + // if multiplayer, let everyone know who hit the exit + if (deathmatch->value) + { + if (activator && activator->client) + gi.bprintf (PRINT_HIGH, "%s exited the level.\n", activator->client->pers.netname); + } + + // if going to a new unit, clear cross triggers + if (strstr(self->map, "*")) + game.serverflags &= ~(SFL_CROSS_TRIGGER_MASK); + + BeginIntermission (self); +} + +void SP_target_changelevel (edict_t *ent) +{ + if (!ent->map) + { + gi.dprintf("target_changelevel with no map at %s\n", vtos(ent->s.origin)); + G_FreeEdict (ent); + return; + } + + // ugly hack because *SOMEBODY* screwed up their map + if((stricmp(level.mapname, "fact1") == 0) && (stricmp(ent->map, "fact3") == 0)) + ent->map = "fact3$secret1"; + + ent->use = use_target_changelevel; + ent->svflags = SVF_NOCLIENT; +} + + +//========================================================== + +/*QUAKED target_splash (1 0 0) (-8 -8 -8) (8 8 8) +Creates a particle splash effect when used. + +Set "sounds" to one of the following: + 1) sparks + 2) blue water + 3) brown water + 4) slime + 5) lava + 6) blood + +"count" how many pixels in the splash +"dmg" if set, does a radius damage at this location when it splashes + useful for lava/sparks +*/ + +void use_target_splash (edict_t *self, edict_t *other, edict_t *activator) +{ + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_SPLASH); + gi.WriteByte (self->count); + gi.WritePosition (self->s.origin); + gi.WriteDir (self->movedir); + gi.WriteByte (self->sounds); + gi.multicast (self->s.origin, MULTICAST_PVS); + + if (self->dmg) + T_RadiusDamage (self, activator, self->dmg, NULL, self->dmg+40, MOD_SPLASH); +} + +void SP_target_splash (edict_t *self) +{ + self->use = use_target_splash; + G_SetMovedir (self->s.angles, self->movedir); + + if (!self->count) + self->count = 32; + + self->svflags = SVF_NOCLIENT; +} + + +//========================================================== + +/*QUAKED target_spawner (1 0 0) (-8 -8 -8) (8 8 8) +Set target to the type of entity you want spawned. +Useful for spawning monsters and gibs in the factory levels. + +For monsters: + Set direction to the facing you want it to have. + +For gibs: + Set direction if you want it moving and + speed how fast it should be moving otherwise it + will just be dropped +*/ +void ED_CallSpawn (edict_t *ent); + +void use_target_spawner (edict_t *self, edict_t *other, edict_t *activator) +{ + edict_t *ent; + + ent = G_Spawn(); + ent->classname = self->target; + VectorCopy (self->s.origin, ent->s.origin); + VectorCopy (self->s.angles, ent->s.angles); + ED_CallSpawn (ent); + gi.unlinkentity (ent); + KillBox (ent); + gi.linkentity (ent); + if (self->speed) + VectorCopy (self->movedir, ent->velocity); +} + +void SP_target_spawner (edict_t *self) +{ + self->use = use_target_spawner; + self->svflags = SVF_NOCLIENT; + if (self->speed) + { + G_SetMovedir (self->s.angles, self->movedir); + VectorScale (self->movedir, self->speed, self->movedir); + } +} + +//========================================================== + +/*QUAKED target_blaster (1 0 0) (-8 -8 -8) (8 8 8) NOTRAIL NOEFFECTS +Fires a blaster bolt in the set direction when triggered. + +dmg default is 15 +speed default is 1000 +*/ + +void use_target_blaster (edict_t *self, edict_t *other, edict_t *activator) +{ + int effect; + + if (self->spawnflags & 2) + effect = 0; + else if (self->spawnflags & 1) + effect = EF_HYPERBLASTER; + else + effect = EF_BLASTER; + + fire_blaster (self, self->s.origin, self->movedir, self->dmg, self->speed, EF_BLASTER, MOD_TARGET_BLASTER); + gi.sound (self, CHAN_VOICE, self->noise_index, 1, ATTN_NORM, 0); +} + +void SP_target_blaster (edict_t *self) +{ + self->use = use_target_blaster; + G_SetMovedir (self->s.angles, self->movedir); + self->noise_index = gi.soundindex ("weapons/laser2.wav"); + + if (!self->dmg) + self->dmg = 15; + if (!self->speed) + self->speed = 1000; + + self->svflags = SVF_NOCLIENT; +} + + +//========================================================== + +/*QUAKED target_crosslevel_trigger (.5 .5 .5) (-8 -8 -8) (8 8 8) trigger1 trigger2 trigger3 trigger4 trigger5 trigger6 trigger7 trigger8 +Once this trigger is touched/used, any trigger_crosslevel_target with the same trigger number is automatically used when a level is started within the same unit. It is OK to check multiple triggers. Message, delay, target, and killtarget also work. +*/ +void trigger_crosslevel_trigger_use (edict_t *self, edict_t *other, edict_t *activator) +{ + game.serverflags |= self->spawnflags; + G_FreeEdict (self); +} + +void SP_target_crosslevel_trigger (edict_t *self) +{ + self->svflags = SVF_NOCLIENT; + self->use = trigger_crosslevel_trigger_use; +} + +/*QUAKED target_crosslevel_target (.5 .5 .5) (-8 -8 -8) (8 8 8) trigger1 trigger2 trigger3 trigger4 trigger5 trigger6 trigger7 trigger8 +Triggered by a trigger_crosslevel elsewhere within a unit. If multiple triggers are checked, all must be true. Delay, target and +killtarget also work. + +"delay" delay before using targets if the trigger has been activated (default 1) +*/ +void target_crosslevel_target_think (edict_t *self) +{ + if (self->spawnflags == (game.serverflags & SFL_CROSS_TRIGGER_MASK & self->spawnflags)) + { + G_UseTargets (self, self); + G_FreeEdict (self); + } +} + +void SP_target_crosslevel_target (edict_t *self) +{ + if (! self->delay) + self->delay = 1; + self->svflags = SVF_NOCLIENT; + + self->think = target_crosslevel_target_think; + self->nextthink = level.time + self->delay; +} + +//========================================================== + +/*QUAKED target_laser (0 .5 .8) (-8 -8 -8) (8 8 8) START_ON RED GREEN BLUE YELLOW ORANGE FAT +When triggered, fires a laser. You can either set a target +or a direction. +*/ + +void target_laser_think (edict_t *self) +{ + edict_t *ignore; + vec3_t start; + vec3_t end; + trace_t tr; + vec3_t point; + vec3_t last_movedir; + int count; + + if (self->spawnflags & 0x80000000) + count = 8; + else + count = 4; + + if (self->enemy) + { + VectorCopy (self->movedir, last_movedir); + VectorMA (self->enemy->absmin, 0.5, self->enemy->size, point); + VectorSubtract (point, self->s.origin, self->movedir); + VectorNormalize (self->movedir); + if (!VectorCompare(self->movedir, last_movedir)) + self->spawnflags |= 0x80000000; + } + + ignore = self; + VectorCopy (self->s.origin, start); + VectorMA (start, 2048, self->movedir, end); + while(1) + { + tr = gi.trace (start, NULL, NULL, end, ignore, CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_DEADMONSTER); + + if (!tr.ent) + break; + + // hurt it if we can + if ((tr.ent->takedamage) && !(tr.ent->flags & FL_IMMUNE_LASER)) + T_Damage (tr.ent, self, self->activator, self->movedir, tr.endpos, vec3_origin, self->dmg, 1, DAMAGE_ENERGY, MOD_TARGET_LASER); + + // if we hit something that's not a monster or player or is immune to lasers, we're done + if (!(tr.ent->svflags & SVF_MONSTER) && (!tr.ent->client)) + { + if (self->spawnflags & 0x80000000) + { + self->spawnflags &= ~0x80000000; + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_LASER_SPARKS); + gi.WriteByte (count); + gi.WritePosition (tr.endpos); + gi.WriteDir (tr.plane.normal); + gi.WriteByte (self->s.skinnum); + gi.multicast (tr.endpos, MULTICAST_PVS); + } + break; + } + + ignore = tr.ent; + VectorCopy (tr.endpos, start); + } + + VectorCopy (tr.endpos, self->s.old_origin); + + self->nextthink = level.time + FRAMETIME; +} + +void target_laser_on (edict_t *self) +{ + if (!self->activator) + self->activator = self; + self->spawnflags |= 0x80000001; + self->svflags &= ~SVF_NOCLIENT; + target_laser_think (self); +} + +void target_laser_off (edict_t *self) +{ + self->spawnflags &= ~1; + self->svflags |= SVF_NOCLIENT; + self->nextthink = 0; +} + +void target_laser_use (edict_t *self, edict_t *other, edict_t *activator) +{ + self->activator = activator; + if (self->spawnflags & 1) + target_laser_off (self); + else + target_laser_on (self); +} + +void target_laser_start (edict_t *self) +{ + edict_t *ent; + + self->movetype = MOVETYPE_NONE; + self->solid = SOLID_NOT; + self->s.renderfx |= RF_BEAM|RF_TRANSLUCENT; + self->s.modelindex = 1; // must be non-zero + + // set the beam diameter + if (self->spawnflags & 64) + self->s.frame = 16; + else + self->s.frame = 4; + + // set the color + if (self->spawnflags & 2) + self->s.skinnum = 0xf2f2f0f0; + else if (self->spawnflags & 4) + self->s.skinnum = 0xd0d1d2d3; + else if (self->spawnflags & 8) + self->s.skinnum = 0xf3f3f1f1; + else if (self->spawnflags & 16) + self->s.skinnum = 0xdcdddedf; + else if (self->spawnflags & 32) + self->s.skinnum = 0xe0e1e2e3; + + if (!self->enemy) + { + if (self->target) + { + ent = G_Find (NULL, FOFS(targetname), self->target); + if (!ent) + gi.dprintf ("%s at %s: %s is a bad target\n", self->classname, vtos(self->s.origin), self->target); + self->enemy = ent; + } + else + { + G_SetMovedir (self->s.angles, self->movedir); + } + } + self->use = target_laser_use; + self->think = target_laser_think; + + if (!self->dmg) + self->dmg = 1; + + VectorSet (self->mins, -8, -8, -8); + VectorSet (self->maxs, 8, 8, 8); + gi.linkentity (self); + + if (self->spawnflags & 1) + target_laser_on (self); + else + target_laser_off (self); +} + +void SP_target_laser (edict_t *self) +{ + // let everything else get spawned before we start firing + self->think = target_laser_start; + self->nextthink = level.time + 1; +} + +//========================================================== + +/*QUAKED target_lightramp (0 .5 .8) (-8 -8 -8) (8 8 8) TOGGLE +speed How many seconds the ramping will take +message two letters; starting lightlevel and ending lightlevel +*/ + +void target_lightramp_think (edict_t *self) +{ + char style[2]; + + style[0] = 'a' + self->movedir[0] + (level.time - self->timestamp) / FRAMETIME * self->movedir[2]; + style[1] = 0; + gi.configstring (CS_LIGHTS+self->enemy->style, style); + + if ((level.time - self->timestamp) < self->speed) + { + self->nextthink = level.time + FRAMETIME; + } + else if (self->spawnflags & 1) + { + char temp; + + temp = self->movedir[0]; + self->movedir[0] = self->movedir[1]; + self->movedir[1] = temp; + self->movedir[2] *= -1; + } +} + +void target_lightramp_use (edict_t *self, edict_t *other, edict_t *activator) +{ + if (!self->enemy) + { + edict_t *e; + + // check all the targets + e = NULL; + while (1) + { + e = G_Find (e, FOFS(targetname), self->target); + if (!e) + break; + if (strcmp(e->classname, "light") != 0) + { + gi.dprintf("%s at %s ", self->classname, vtos(self->s.origin)); + gi.dprintf("target %s (%s at %s) is not a light\n", self->target, e->classname, vtos(e->s.origin)); + } + else + { + self->enemy = e; + } + } + + if (!self->enemy) + { + gi.dprintf("%s target %s not found at %s\n", self->classname, self->target, vtos(self->s.origin)); + G_FreeEdict (self); + return; + } + } + + self->timestamp = level.time; + target_lightramp_think (self); +} + +void SP_target_lightramp (edict_t *self) +{ + if (!self->message || strlen(self->message) != 2 || self->message[0] < 'a' || self->message[0] > 'z' || self->message[1] < 'a' || self->message[1] > 'z' || self->message[0] == self->message[1]) + { + gi.dprintf("target_lightramp has bad ramp (%s) at %s\n", self->message, vtos(self->s.origin)); + G_FreeEdict (self); + return; + } + + if (deathmatch->value) + { + G_FreeEdict (self); + return; + } + + if (!self->target) + { + gi.dprintf("%s with no target at %s\n", self->classname, vtos(self->s.origin)); + G_FreeEdict (self); + return; + } + + self->svflags |= SVF_NOCLIENT; + self->use = target_lightramp_use; + self->think = target_lightramp_think; + + self->movedir[0] = self->message[0] - 'a'; + self->movedir[1] = self->message[1] - 'a'; + self->movedir[2] = (self->movedir[1] - self->movedir[0]) / (self->speed / FRAMETIME); +} + +//========================================================== + +/*QUAKED target_earthquake (1 0 0) (-8 -8 -8) (8 8 8) +When triggered, this initiates a level-wide earthquake. +All players and monsters are affected. +"speed" severity of the quake (default:200) +"count" duration of the quake (default:5) +*/ + +void target_earthquake_think (edict_t *self) +{ + int i; + edict_t *e; + + if (self->last_move_time < level.time) + { + gi.positioned_sound (self->s.origin, self, CHAN_AUTO, self->noise_index, 1.0, ATTN_NONE, 0); + self->last_move_time = level.time + 0.5; + } + + for (i=1, e=g_edicts+i; i < globals.num_edicts; i++,e++) + { + if (!e->inuse) + continue; + if (!e->client) + continue; + if (!e->groundentity) + continue; + + e->groundentity = NULL; + e->velocity[0] += crandom()* 150; + e->velocity[1] += crandom()* 150; + e->velocity[2] = self->speed * (100.0 / e->mass); + } + + if (level.time < self->timestamp) + self->nextthink = level.time + FRAMETIME; +} + +void target_earthquake_use (edict_t *self, edict_t *other, edict_t *activator) +{ + self->timestamp = level.time + self->count; + self->nextthink = level.time + FRAMETIME; + self->activator = activator; + self->last_move_time = 0; +} + +void SP_target_earthquake (edict_t *self) +{ + if (!self->targetname) + gi.dprintf("untargeted %s at %s\n", self->classname, vtos(self->s.origin)); + + if (!self->count) + self->count = 5; + + if (!self->speed) + self->speed = 200; + + self->svflags |= SVF_NOCLIENT; + self->think = target_earthquake_think; + self->use = target_earthquake_use; + + self->noise_index = gi.soundindex ("world/quake.wav"); +} diff --git a/original/ctf/g_trigger.c b/original/ctf/g_trigger.c new file mode 100644 index 0000000..53a1b6c --- /dev/null +++ b/original/ctf/g_trigger.c @@ -0,0 +1,598 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +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. + +*/ +#include "g_local.h" + + +void InitTrigger (edict_t *self) +{ + if (!VectorCompare (self->s.angles, vec3_origin)) + G_SetMovedir (self->s.angles, self->movedir); + + self->solid = SOLID_TRIGGER; + self->movetype = MOVETYPE_NONE; + gi.setmodel (self, self->model); + self->svflags = SVF_NOCLIENT; +} + + +// the wait time has passed, so set back up for another activation +void multi_wait (edict_t *ent) +{ + ent->nextthink = 0; +} + + +// the trigger was just activated +// ent->activator should be set to the activator so it can be held through a delay +// so wait for the delay time before firing +void multi_trigger (edict_t *ent) +{ + if (ent->nextthink) + return; // already been triggered + + G_UseTargets (ent, ent->activator); + + if (ent->wait > 0) + { + ent->think = multi_wait; + ent->nextthink = level.time + ent->wait; + } + else + { // we can't just remove (self) here, because this is a touch function + // called while looping through area links... + ent->touch = NULL; + ent->nextthink = level.time + FRAMETIME; + ent->think = G_FreeEdict; + } +} + +void Use_Multi (edict_t *ent, edict_t *other, edict_t *activator) +{ + ent->activator = activator; + multi_trigger (ent); +} + +void Touch_Multi (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if(other->client) + { + if (self->spawnflags & 2) + return; + } + else if (other->svflags & SVF_MONSTER) + { + if (!(self->spawnflags & 1)) + return; + } + else + return; + + if (!VectorCompare(self->movedir, vec3_origin)) + { + vec3_t forward; + + AngleVectors(other->s.angles, forward, NULL, NULL); + if (_DotProduct(forward, self->movedir) < 0) + return; + } + + self->activator = other; + multi_trigger (self); +} + +/*QUAKED trigger_multiple (.5 .5 .5) ? MONSTER NOT_PLAYER TRIGGERED +Variable sized repeatable trigger. Must be targeted at one or more entities. +If "delay" is set, the trigger waits some time after activating before firing. +"wait" : Seconds between triggerings. (.2 default) +sounds +1) secret +2) beep beep +3) large switch +4) +set "message" to text string +*/ +void trigger_enable (edict_t *self, edict_t *other, edict_t *activator) +{ + self->solid = SOLID_TRIGGER; + self->use = Use_Multi; + gi.linkentity (self); +} + +void SP_trigger_multiple (edict_t *ent) +{ + if (ent->sounds == 1) + ent->noise_index = gi.soundindex ("misc/secret.wav"); + else if (ent->sounds == 2) + ent->noise_index = gi.soundindex ("misc/talk.wav"); + else if (ent->sounds == 3) + ent->noise_index = gi.soundindex ("misc/trigger1.wav"); + + if (!ent->wait) + ent->wait = 0.2; + ent->touch = Touch_Multi; + ent->movetype = MOVETYPE_NONE; + ent->svflags |= SVF_NOCLIENT; + + + if (ent->spawnflags & 4) + { + ent->solid = SOLID_NOT; + ent->use = trigger_enable; + } + else + { + ent->solid = SOLID_TRIGGER; + ent->use = Use_Multi; + } + + if (!VectorCompare(ent->s.angles, vec3_origin)) + G_SetMovedir (ent->s.angles, ent->movedir); + + gi.setmodel (ent, ent->model); + gi.linkentity (ent); +} + + +/*QUAKED trigger_once (.5 .5 .5) ? x x TRIGGERED +Triggers once, then removes itself. +You must set the key "target" to the name of another object in the level that has a matching "targetname". + +If TRIGGERED, this trigger must be triggered before it is live. + +sounds + 1) secret + 2) beep beep + 3) large switch + 4) + +"message" string to be displayed when triggered +*/ + +void SP_trigger_once(edict_t *ent) +{ + // make old maps work because I messed up on flag assignments here + // triggered was on bit 1 when it should have been on bit 4 + if (ent->spawnflags & 1) + { + vec3_t v; + + VectorMA (ent->mins, 0.5, ent->size, v); + ent->spawnflags &= ~1; + ent->spawnflags |= 4; + gi.dprintf("fixed TRIGGERED flag on %s at %s\n", ent->classname, vtos(v)); + } + + ent->wait = -1; + SP_trigger_multiple (ent); +} + +/*QUAKED trigger_relay (.5 .5 .5) (-8 -8 -8) (8 8 8) +This fixed size trigger cannot be touched, it can only be fired by other events. +*/ +void trigger_relay_use (edict_t *self, edict_t *other, edict_t *activator) +{ + G_UseTargets (self, activator); +} + +void SP_trigger_relay (edict_t *self) +{ + self->use = trigger_relay_use; +} + + +/* +============================================================================== + +trigger_key + +============================================================================== +*/ + +/*QUAKED trigger_key (.5 .5 .5) (-8 -8 -8) (8 8 8) +A relay trigger that only fires it's targets if player has the proper key. +Use "item" to specify the required key, for example "key_data_cd" +*/ +void trigger_key_use (edict_t *self, edict_t *other, edict_t *activator) +{ + int index; + + if (!self->item) + return; + if (!activator->client) + return; + + index = ITEM_INDEX(self->item); + if (!activator->client->pers.inventory[index]) + { + if (level.time < self->touch_debounce_time) + return; + self->touch_debounce_time = level.time + 5.0; + gi.centerprintf (activator, "You need the %s", self->item->pickup_name); + gi.sound (activator, CHAN_AUTO, gi.soundindex ("misc/keytry.wav"), 1, ATTN_NORM, 0); + return; + } + + gi.sound (activator, CHAN_AUTO, gi.soundindex ("misc/keyuse.wav"), 1, ATTN_NORM, 0); + if (coop->value) + { + int player; + edict_t *ent; + + if (strcmp(self->item->classname, "key_power_cube") == 0) + { + int cube; + + for (cube = 0; cube < 8; cube++) + if (activator->client->pers.power_cubes & (1 << cube)) + break; + for (player = 1; player <= game.maxclients; player++) + { + ent = &g_edicts[player]; + if (!ent->inuse) + continue; + if (!ent->client) + continue; + if (ent->client->pers.power_cubes & (1 << cube)) + { + ent->client->pers.inventory[index]--; + ent->client->pers.power_cubes &= ~(1 << cube); + } + } + } + else + { + for (player = 1; player <= game.maxclients; player++) + { + ent = &g_edicts[player]; + if (!ent->inuse) + continue; + if (!ent->client) + continue; + ent->client->pers.inventory[index] = 0; + } + } + } + else + { + activator->client->pers.inventory[index]--; + } + + G_UseTargets (self, activator); + + self->use = NULL; +} + +void SP_trigger_key (edict_t *self) +{ + if (!st.item) + { + gi.dprintf("no key item for trigger_key at %s\n", vtos(self->s.origin)); + return; + } + self->item = FindItemByClassname (st.item); + + if (!self->item) + { + gi.dprintf("item %s not found for trigger_key at %s\n", st.item, vtos(self->s.origin)); + return; + } + + if (!self->target) + { + gi.dprintf("%s at %s has no target\n", self->classname, vtos(self->s.origin)); + return; + } + + gi.soundindex ("misc/keytry.wav"); + gi.soundindex ("misc/keyuse.wav"); + + self->use = trigger_key_use; +} + + +/* +============================================================================== + +trigger_counter + +============================================================================== +*/ + +/*QUAKED trigger_counter (.5 .5 .5) ? nomessage +Acts as an intermediary for an action that takes multiple inputs. + +If nomessage is not set, t will print "1 more.. " etc when triggered and "sequence complete" when finished. + +After the counter has been triggered "count" times (default 2), it will fire all of it's targets and remove itself. +*/ + +void trigger_counter_use(edict_t *self, edict_t *other, edict_t *activator) +{ + if (self->count == 0) + return; + + self->count--; + + if (self->count) + { + if (! (self->spawnflags & 1)) + { + gi.centerprintf(activator, "%i more to go...", self->count); + gi.sound (activator, CHAN_AUTO, gi.soundindex ("misc/talk1.wav"), 1, ATTN_NORM, 0); + } + return; + } + + if (! (self->spawnflags & 1)) + { + gi.centerprintf(activator, "Sequence completed!"); + gi.sound (activator, CHAN_AUTO, gi.soundindex ("misc/talk1.wav"), 1, ATTN_NORM, 0); + } + self->activator = activator; + multi_trigger (self); +} + +void SP_trigger_counter (edict_t *self) +{ + self->wait = -1; + if (!self->count) + self->count = 2; + + self->use = trigger_counter_use; +} + + +/* +============================================================================== + +trigger_always + +============================================================================== +*/ + +/*QUAKED trigger_always (.5 .5 .5) (-8 -8 -8) (8 8 8) +This trigger will always fire. It is activated by the world. +*/ +void SP_trigger_always (edict_t *ent) +{ + // we must have some delay to make sure our use targets are present + if (ent->delay < 0.2) + ent->delay = 0.2; + G_UseTargets(ent, ent); +} + + +/* +============================================================================== + +trigger_push + +============================================================================== +*/ + +#define PUSH_ONCE 1 + +static int windsound; + +void trigger_push_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (strcmp(other->classname, "grenade") == 0) + { + VectorScale (self->movedir, self->speed * 10, other->velocity); + } + else if (other->health > 0) + { + VectorScale (self->movedir, self->speed * 10, other->velocity); + + if (other->client) + { + // don't take falling damage immediately from this + VectorCopy (other->velocity, other->client->oldvelocity); + if (other->fly_sound_debounce_time < level.time) + { + other->fly_sound_debounce_time = level.time + 1.5; + gi.sound (other, CHAN_AUTO, windsound, 1, ATTN_NORM, 0); + } + } + } + if (self->spawnflags & PUSH_ONCE) + G_FreeEdict (self); +} + + +/*QUAKED trigger_push (.5 .5 .5) ? PUSH_ONCE +Pushes the player +"speed" defaults to 1000 +*/ +void SP_trigger_push (edict_t *self) +{ + InitTrigger (self); + windsound = gi.soundindex ("misc/windfly.wav"); + self->touch = trigger_push_touch; + if (!self->speed) + self->speed = 1000; + gi.linkentity (self); +} + + +/* +============================================================================== + +trigger_hurt + +============================================================================== +*/ + +/*QUAKED trigger_hurt (.5 .5 .5) ? START_OFF TOGGLE SILENT NO_PROTECTION SLOW +Any entity that touches this will be hurt. + +It does dmg points of damage each server frame + +SILENT supresses playing the sound +SLOW changes the damage rate to once per second +NO_PROTECTION *nothing* stops the damage + +"dmg" default 5 (whole numbers only) + +*/ +void hurt_use (edict_t *self, edict_t *other, edict_t *activator) +{ + if (self->solid == SOLID_NOT) + self->solid = SOLID_TRIGGER; + else + self->solid = SOLID_NOT; + gi.linkentity (self); + + if (!(self->spawnflags & 2)) + self->use = NULL; +} + + +void hurt_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + int dflags; + + if (!other->takedamage) + return; + + if (self->timestamp > level.time) + return; + + if (self->spawnflags & 16) + self->timestamp = level.time + 1; + else + self->timestamp = level.time + FRAMETIME; + + if (!(self->spawnflags & 4)) + { + if ((level.framenum % 10) == 0) + gi.sound (other, CHAN_AUTO, self->noise_index, 1, ATTN_NORM, 0); + } + + if (self->spawnflags & 8) + dflags = DAMAGE_NO_PROTECTION; + else + dflags = 0; + T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, self->dmg, dflags, MOD_TRIGGER_HURT); +} + +void SP_trigger_hurt (edict_t *self) +{ + InitTrigger (self); + + self->noise_index = gi.soundindex ("world/electro.wav"); + self->touch = hurt_touch; + + if (!self->dmg) + self->dmg = 5; + + if (self->spawnflags & 1) + self->solid = SOLID_NOT; + else + self->solid = SOLID_TRIGGER; + + if (self->spawnflags & 2) + self->use = hurt_use; + + gi.linkentity (self); +} + + +/* +============================================================================== + +trigger_gravity + +============================================================================== +*/ + +/*QUAKED trigger_gravity (.5 .5 .5) ? +Changes the touching entites gravity to +the value of "gravity". 1.0 is standard +gravity for the level. +*/ + +void trigger_gravity_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + other->gravity = self->gravity; +} + +void SP_trigger_gravity (edict_t *self) +{ + if (st.gravity == 0) + { + gi.dprintf("trigger_gravity without gravity set at %s\n", vtos(self->s.origin)); + G_FreeEdict (self); + return; + } + + InitTrigger (self); + self->gravity = atoi(st.gravity); + self->touch = trigger_gravity_touch; +} + + +/* +============================================================================== + +trigger_monsterjump + +============================================================================== +*/ + +/*QUAKED trigger_monsterjump (.5 .5 .5) ? +Walking monsters that touch this will jump in the direction of the trigger's angle +"speed" default to 200, the speed thrown forward +"height" default to 200, the speed thrown upwards +*/ + +void trigger_monsterjump_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (other->flags & (FL_FLY | FL_SWIM) ) + return; + if (other->svflags & SVF_DEADMONSTER) + return; + if ( !(other->svflags & SVF_MONSTER)) + return; + +// set XY even if not on ground, so the jump will clear lips + other->velocity[0] = self->movedir[0] * self->speed; + other->velocity[1] = self->movedir[1] * self->speed; + + if (!other->groundentity) + return; + + other->groundentity = NULL; + other->velocity[2] = self->movedir[2]; +} + +void SP_trigger_monsterjump (edict_t *self) +{ + if (!self->speed) + self->speed = 200; + if (!st.height) + st.height = 200; + if (self->s.angles[YAW] == 0) + self->s.angles[YAW] = 360; + InitTrigger (self); + self->touch = trigger_monsterjump_touch; + self->movedir[2] = st.height; +} + diff --git a/original/ctf/g_utils.c b/original/ctf/g_utils.c new file mode 100644 index 0000000..9dc7f9b --- /dev/null +++ b/original/ctf/g_utils.c @@ -0,0 +1,570 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +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. + +*/ +// g_utils.c -- misc utility functions for game module + +#include "g_local.h" + + +void G_ProjectSource (vec3_t point, vec3_t distance, vec3_t forward, vec3_t right, vec3_t result) +{ + result[0] = point[0] + forward[0] * distance[0] + right[0] * distance[1]; + result[1] = point[1] + forward[1] * distance[0] + right[1] * distance[1]; + result[2] = point[2] + forward[2] * distance[0] + right[2] * distance[1] + distance[2]; +} + + +/* +============= +G_Find + +Searches all active entities for the next one that holds +the matching string at fieldofs (use the FOFS() macro) in the structure. + +Searches beginning at the edict after from, or the beginning if NULL +NULL will be returned if the end of the list is reached. + +============= +*/ +edict_t *G_Find (edict_t *from, int fieldofs, char *match) +{ + char *s; + + if (!from) + from = g_edicts; + else + from++; + + for ( ; from < &g_edicts[globals.num_edicts] ; from++) + { + if (!from->inuse) + continue; + s = *(char **) ((byte *)from + fieldofs); + if (!s) + continue; + if (!Q_stricmp (s, match)) + return from; + } + + return NULL; +} + + +/* +================= +findradius + +Returns entities that have origins within a spherical area + +findradius (origin, radius) +================= +*/ +edict_t *findradius (edict_t *from, vec3_t org, float rad) +{ + vec3_t eorg; + int j; + + if (!from) + from = g_edicts; + else + from++; + for ( ; from < &g_edicts[globals.num_edicts]; from++) + { + if (!from->inuse) + continue; + if (from->solid == SOLID_NOT) + continue; + for (j=0 ; j<3 ; j++) + eorg[j] = org[j] - (from->s.origin[j] + (from->mins[j] + from->maxs[j])*0.5); + if (VectorLength(eorg) > rad) + continue; + return from; + } + + return NULL; +} + + +/* +============= +G_PickTarget + +Searches all active entities for the next one that holds +the matching string at fieldofs (use the FOFS() macro) in the structure. + +Searches beginning at the edict after from, or the beginning if NULL +NULL will be returned if the end of the list is reached. + +============= +*/ +#define MAXCHOICES 8 + +edict_t *G_PickTarget (char *targetname) +{ + edict_t *ent = NULL; + int num_choices = 0; + edict_t *choice[MAXCHOICES]; + + if (!targetname) + { + gi.dprintf("G_PickTarget called with NULL targetname\n"); + return NULL; + } + + while(1) + { + ent = G_Find (ent, FOFS(targetname), targetname); + if (!ent) + break; + choice[num_choices++] = ent; + if (num_choices == MAXCHOICES) + break; + } + + if (!num_choices) + { + gi.dprintf("G_PickTarget: target %s not found\n", targetname); + return NULL; + } + + return choice[rand() % num_choices]; +} + + + +void Think_Delay (edict_t *ent) +{ + G_UseTargets (ent, ent->activator); + G_FreeEdict (ent); +} + +/* +============================== +G_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. + +Search for (string)targetname in all entities that +match (string)self.target and call their .use function + +============================== +*/ +void G_UseTargets (edict_t *ent, edict_t *activator) +{ + edict_t *t; + +// +// check for a delay +// + if (ent->delay) + { + // create a temp object to fire at a later time + t = G_Spawn(); + t->classname = "DelayedUse"; + t->nextthink = level.time + ent->delay; + t->think = Think_Delay; + t->activator = activator; + if (!activator) + gi.dprintf ("Think_Delay with no activator\n"); + t->message = ent->message; + t->target = ent->target; + t->killtarget = ent->killtarget; + return; + } + + +// +// print the message +// + if ((ent->message) && !(activator->svflags & SVF_MONSTER)) + { + gi.centerprintf (activator, "%s", ent->message); + if (ent->noise_index) + gi.sound (activator, CHAN_AUTO, ent->noise_index, 1, ATTN_NORM, 0); + else + gi.sound (activator, CHAN_AUTO, gi.soundindex ("misc/talk1.wav"), 1, ATTN_NORM, 0); + } + +// +// kill killtargets +// + if (ent->killtarget) + { + t = NULL; + while ((t = G_Find (t, FOFS(targetname), ent->killtarget))) + { + G_FreeEdict (t); + if (!ent->inuse) + { + gi.dprintf("entity was removed while using killtargets\n"); + return; + } + } + } + +// gi.dprintf("TARGET: activating %s\n", ent->target); + +// +// fire targets +// + if (ent->target) + { + t = NULL; + while ((t = G_Find (t, FOFS(targetname), ent->target))) + { + // doors fire area portals in a specific way + if (!Q_stricmp(t->classname, "func_areaportal") && + (!Q_stricmp(ent->classname, "func_door") || !Q_stricmp(ent->classname, "func_door_rotating"))) + continue; + + if (t == ent) + { + gi.dprintf ("WARNING: Entity used itself.\n"); + } + else + { + if (t->use) + t->use (t, ent, activator); + } + if (!ent->inuse) + { + gi.dprintf("entity was removed while using targets\n"); + return; + } + } + } +} + + +/* +============= +TempVector + +This is just a convenience function +for making temporary vectors for function calls +============= +*/ +float *tv (float x, float y, float z) +{ + static int index; + static vec3_t vecs[8]; + float *v; + + // use an array so that multiple tempvectors won't collide + // for a while + v = vecs[index]; + index = (index + 1)&7; + + v[0] = x; + v[1] = y; + v[2] = z; + + return v; +} + + +/* +============= +VectorToString + +This is just a convenience function +for printing vectors +============= +*/ +char *vtos (vec3_t v) +{ + static int index; + static char str[8][32]; + char *s; + + // use an array so that multiple vtos won't collide + s = str[index]; + index = (index + 1)&7; + + Com_sprintf (s, 32, "(%i %i %i)", (int)v[0], (int)v[1], (int)v[2]); + + return s; +} + + +vec3_t VEC_UP = {0, -1, 0}; +vec3_t MOVEDIR_UP = {0, 0, 1}; +vec3_t VEC_DOWN = {0, -2, 0}; +vec3_t MOVEDIR_DOWN = {0, 0, -1}; + +void G_SetMovedir (vec3_t angles, vec3_t movedir) +{ + if (VectorCompare (angles, VEC_UP)) + { + VectorCopy (MOVEDIR_UP, movedir); + } + else if (VectorCompare (angles, VEC_DOWN)) + { + VectorCopy (MOVEDIR_DOWN, movedir); + } + else + { + AngleVectors (angles, movedir, NULL, NULL); + } + + VectorClear (angles); +} + + +float vectoyaw (vec3_t vec) +{ + float yaw; + + if (/* vec[YAW] == 0 && */ vec[PITCH] == 0) + { + yaw = 0; + if (vec[YAW] > 0) + yaw = 90; + else if (vec[YAW] < 0) + yaw = -90; + } + else + { + yaw = (int) (atan2(vec[YAW], vec[PITCH]) * 180 / M_PI); + if (yaw < 0) + yaw += 360; + } + + return yaw; +} + + +void vectoangles (vec3_t value1, vec3_t angles) +{ + float forward; + float yaw, pitch; + + if (value1[1] == 0 && value1[0] == 0) + { + yaw = 0; + if (value1[2] > 0) + pitch = 90; + else + pitch = 270; + } + else + { + if (value1[0]) + yaw = (int) (atan2(value1[1], value1[0]) * 180 / M_PI); + else if (value1[1] > 0) + yaw = 90; + else + yaw = -90; + if (yaw < 0) + yaw += 360; + + forward = sqrt (value1[0]*value1[0] + value1[1]*value1[1]); + pitch = (int) (atan2(value1[2], forward) * 180 / M_PI); + if (pitch < 0) + pitch += 360; + } + + angles[PITCH] = -pitch; + angles[YAW] = yaw; + angles[ROLL] = 0; +} + +char *G_CopyString (char *in) +{ + char *out; + + out = gi.TagMalloc (strlen(in)+1, TAG_LEVEL); + strcpy (out, in); + return out; +} + + +void G_InitEdict (edict_t *e) +{ + e->inuse = true; + e->classname = "noclass"; + e->gravity = 1.0; + e->s.number = e - g_edicts; +} + +/* +================= +G_Spawn + +Either finds a free edict, or allocates a new one. +Try to avoid reusing an entity that was recently freed, because it +can cause the client to think the entity morphed into something else +instead of being removed and recreated, which can cause interpolated +angles and bad trails. +================= +*/ +edict_t *G_Spawn (void) +{ + int i; + edict_t *e; + + e = &g_edicts[(int)maxclients->value+1]; + for ( i=maxclients->value+1 ; iinuse && ( e->freetime < 2 || level.time - e->freetime > 0.5 ) ) + { + G_InitEdict (e); + return e; + } + } + + if (i == game.maxentities) + gi.error ("ED_Alloc: no free edicts"); + + globals.num_edicts++; + G_InitEdict (e); + return e; +} + +/* +================= +G_FreeEdict + +Marks the edict as free +================= +*/ +void G_FreeEdict (edict_t *ed) +{ + gi.unlinkentity (ed); // unlink from world + + if ((ed - g_edicts) <= (maxclients->value + BODY_QUEUE_SIZE)) + { +// gi.dprintf("tried to free special edict\n"); + return; + } + + memset (ed, 0, sizeof(*ed)); + ed->classname = "freed"; + ed->freetime = level.time; + ed->inuse = false; +} + + +/* +============ +G_TouchTriggers + +============ +*/ +void G_TouchTriggers (edict_t *ent) +{ + int i, num; + edict_t *touch[MAX_EDICTS], *hit; + + // dead things don't activate triggers! + if ((ent->client || (ent->svflags & SVF_MONSTER)) && (ent->health <= 0)) + return; + + num = gi.BoxEdicts (ent->absmin, ent->absmax, touch + , MAX_EDICTS, AREA_TRIGGERS); + + // be careful, it is possible to have an entity in this + // list removed before we get to it (killtriggered) + for (i=0 ; iinuse) + continue; + if (!hit->touch) + continue; + hit->touch (hit, ent, NULL, NULL); + } +} + +/* +============ +G_TouchSolids + +Call after linking a new trigger in during gameplay +to force all entities it covers to immediately touch it +============ +*/ +void G_TouchSolids (edict_t *ent) +{ + int i, num; + edict_t *touch[MAX_EDICTS], *hit; + + num = gi.BoxEdicts (ent->absmin, ent->absmax, touch + , MAX_EDICTS, AREA_SOLID); + + // be careful, it is possible to have an entity in this + // list removed before we get to it (killtriggered) + for (i=0 ; iinuse) + continue; + if (ent->touch) + ent->touch (hit, ent, NULL, NULL); + if (!ent->inuse) + break; + } +} + + + + +/* +============================================================================== + +Kill box + +============================================================================== +*/ + +/* +================= +KillBox + +Kills all entities that would touch the proposed new positioning +of ent. Ent should be unlinked before calling this! +================= +*/ +qboolean KillBox (edict_t *ent) +{ + trace_t tr; + + while (1) + { + tr = gi.trace (ent->s.origin, ent->mins, ent->maxs, ent->s.origin, NULL, MASK_PLAYERSOLID); + if (!tr.ent) + break; + + // nail it + T_Damage (tr.ent, ent, ent, vec3_origin, ent->s.origin, vec3_origin, 100000, 0, DAMAGE_NO_PROTECTION, MOD_TELEFRAG); + + // if we didn't kill it, fail + if (tr.ent->solid) + return false; + } + + return true; // all clear +} diff --git a/original/ctf/g_weapon.c b/original/ctf/g_weapon.c new file mode 100644 index 0000000..214df86 --- /dev/null +++ b/original/ctf/g_weapon.c @@ -0,0 +1,921 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +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. + +*/ +#include "g_local.h" + + +/* +================= +check_dodge + +This is a support routine used when a client is firing +a non-instant attack weapon. It checks to see if a +monster's dodge function should be called. +================= +*/ +static void check_dodge (edict_t *self, vec3_t start, vec3_t dir, int speed) +{ + vec3_t end; + vec3_t v; + trace_t tr; + float eta; + + // easy mode only ducks one quarter the time + if (skill->value == 0) + { + if (random() > 0.25) + return; + } + VectorMA (start, 8192, dir, end); + tr = gi.trace (start, NULL, NULL, end, self, MASK_SHOT); + if ((tr.ent) && (tr.ent->svflags & SVF_MONSTER) && (tr.ent->health > 0) && (tr.ent->monsterinfo.dodge) && infront(tr.ent, self)) + { + VectorSubtract (tr.endpos, start, v); + eta = (VectorLength(v) - tr.ent->maxs[0]) / speed; + tr.ent->monsterinfo.dodge (tr.ent, self, eta); + } +} + + +/* +================= +fire_hit + +Used for all impact (hit/punch/slash) attacks +================= +*/ +qboolean fire_hit (edict_t *self, vec3_t aim, int damage, int kick) +{ + trace_t tr; + vec3_t forward, right, up; + vec3_t v; + vec3_t point; + float range; + vec3_t dir; + + //see if enemy is in range + VectorSubtract (self->enemy->s.origin, self->s.origin, dir); + range = VectorLength(dir); + if (range > aim[0]) + return false; + + if (aim[1] > self->mins[0] && aim[1] < self->maxs[0]) + { + // the hit is straight on so back the range up to the edge of their bbox + range -= self->enemy->maxs[0]; + } + else + { + // this is a side hit so adjust the "right" value out to the edge of their bbox + if (aim[1] < 0) + aim[1] = self->enemy->mins[0]; + else + aim[1] = self->enemy->maxs[0]; + } + + VectorMA (self->s.origin, range, dir, point); + + tr = gi.trace (self->s.origin, NULL, NULL, point, self, MASK_SHOT); + if (tr.fraction < 1) + { + if (!tr.ent->takedamage) + return false; + // if it will hit any client/monster then hit the one we wanted to hit + if ((tr.ent->svflags & SVF_MONSTER) || (tr.ent->client)) + tr.ent = self->enemy; + } + + AngleVectors(self->s.angles, forward, right, up); + VectorMA (self->s.origin, range, forward, point); + VectorMA (point, aim[1], right, point); + VectorMA (point, aim[2], up, point); + VectorSubtract (point, self->enemy->s.origin, dir); + + // do the damage + T_Damage (tr.ent, self, self, dir, point, vec3_origin, damage, kick/2, DAMAGE_NO_KNOCKBACK, MOD_HIT); + + if (!(tr.ent->svflags & SVF_MONSTER) && (!tr.ent->client)) + return false; + + // do our special form of knockback here + VectorMA (self->enemy->absmin, 0.5, self->enemy->size, v); + VectorSubtract (v, point, v); + VectorNormalize (v); + VectorMA (self->enemy->velocity, kick, v, self->enemy->velocity); + if (self->enemy->velocity[2] > 0) + self->enemy->groundentity = NULL; + return true; +} + + +/* +================= +fire_lead + +This is an internal support routine used for bullet/pellet based weapons. +================= +*/ +static void fire_lead (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int te_impact, int hspread, int vspread, int mod) +{ + trace_t tr; + vec3_t dir; + vec3_t forward, right, up; + vec3_t end; + float r; + float u; + vec3_t water_start; + qboolean water = false; + int content_mask = MASK_SHOT | MASK_WATER; + + tr = gi.trace (self->s.origin, NULL, NULL, start, self, MASK_SHOT); + if (!(tr.fraction < 1.0)) + { + vectoangles (aimdir, dir); + AngleVectors (dir, forward, right, up); + + r = crandom()*hspread; + u = crandom()*vspread; + VectorMA (start, 8192, forward, end); + VectorMA (end, r, right, end); + VectorMA (end, u, up, end); + + if (gi.pointcontents (start) & MASK_WATER) + { + water = true; + VectorCopy (start, water_start); + content_mask &= ~MASK_WATER; + } + + tr = gi.trace (start, NULL, NULL, end, self, content_mask); + + // see if we hit water + if (tr.contents & MASK_WATER) + { + int color; + + water = true; + VectorCopy (tr.endpos, water_start); + + if (!VectorCompare (start, tr.endpos)) + { + if (tr.contents & CONTENTS_WATER) + { + if (strcmp(tr.surface->name, "*brwater") == 0) + color = SPLASH_BROWN_WATER; + else + color = SPLASH_BLUE_WATER; + } + else if (tr.contents & CONTENTS_SLIME) + color = SPLASH_SLIME; + else if (tr.contents & CONTENTS_LAVA) + color = SPLASH_LAVA; + else + color = SPLASH_UNKNOWN; + + if (color != SPLASH_UNKNOWN) + { + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_SPLASH); + gi.WriteByte (8); + gi.WritePosition (tr.endpos); + gi.WriteDir (tr.plane.normal); + gi.WriteByte (color); + gi.multicast (tr.endpos, MULTICAST_PVS); + } + + // change bullet's course when it enters water + VectorSubtract (end, start, dir); + vectoangles (dir, dir); + AngleVectors (dir, forward, right, up); + r = crandom()*hspread*2; + u = crandom()*vspread*2; + VectorMA (water_start, 8192, forward, end); + VectorMA (end, r, right, end); + VectorMA (end, u, up, end); + } + + // re-trace ignoring water this time + tr = gi.trace (water_start, NULL, NULL, end, self, MASK_SHOT); + } + } + + // send gun puff / flash + if (!((tr.surface) && (tr.surface->flags & SURF_SKY))) + { + if (tr.fraction < 1.0) + { + if (tr.ent->takedamage) + { + T_Damage (tr.ent, self, self, aimdir, tr.endpos, tr.plane.normal, damage, kick, DAMAGE_BULLET, mod); + } + else + { + if (strncmp (tr.surface->name, "sky", 3) != 0) + { + gi.WriteByte (svc_temp_entity); + gi.WriteByte (te_impact); + gi.WritePosition (tr.endpos); + gi.WriteDir (tr.plane.normal); + gi.multicast (tr.endpos, MULTICAST_PVS); + + if (self->client) + PlayerNoise(self, tr.endpos, PNOISE_IMPACT); + } + } + } + } + + // if went through water, determine where the end and make a bubble trail + if (water) + { + vec3_t pos; + + VectorSubtract (tr.endpos, water_start, dir); + VectorNormalize (dir); + VectorMA (tr.endpos, -2, dir, pos); + if (gi.pointcontents (pos) & MASK_WATER) + VectorCopy (pos, tr.endpos); + else + tr = gi.trace (pos, NULL, NULL, water_start, tr.ent, MASK_WATER); + + VectorAdd (water_start, tr.endpos, pos); + VectorScale (pos, 0.5, pos); + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_BUBBLETRAIL); + gi.WritePosition (water_start); + gi.WritePosition (tr.endpos); + gi.multicast (pos, MULTICAST_PVS); + } +} + + +/* +================= +fire_bullet + +Fires a single round. Used for machinegun and chaingun. Would be fine for +pistols, rifles, etc.... +================= +*/ +void fire_bullet (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int hspread, int vspread, int mod) +{ + fire_lead (self, start, aimdir, damage, kick, TE_GUNSHOT, hspread, vspread, mod); +} + + +/* +================= +fire_shotgun + +Shoots shotgun pellets. Used by shotgun and super shotgun. +================= +*/ +void fire_shotgun (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int hspread, int vspread, int count, int mod) +{ + int i; + + for (i = 0; i < count; i++) + fire_lead (self, start, aimdir, damage, kick, TE_SHOTGUN, hspread, vspread, mod); +} + + +/* +================= +fire_blaster + +Fires a single blaster bolt. Used by the blaster and hyper blaster. +================= +*/ +void blaster_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + int mod; + + if (other == self->owner) + return; + + if (surf && (surf->flags & SURF_SKY)) + { + G_FreeEdict (self); + return; + } + + if (self->owner->client) + PlayerNoise(self->owner, self->s.origin, PNOISE_IMPACT); + + if (other->takedamage) + { + if (self->spawnflags & 1) + mod = MOD_HYPERBLASTER; + else + mod = MOD_BLASTER; + T_Damage (other, self, self->owner, self->velocity, self->s.origin, plane->normal, self->dmg, 1, DAMAGE_ENERGY, mod); + } + else + { + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_BLASTER); + gi.WritePosition (self->s.origin); + if (!plane) + gi.WriteDir (vec3_origin); + else + gi.WriteDir (plane->normal); + gi.multicast (self->s.origin, MULTICAST_PVS); + } + + G_FreeEdict (self); +} + +void fire_blaster (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int effect, qboolean hyper) +{ + edict_t *bolt; + trace_t tr; + + VectorNormalize (dir); + + bolt = G_Spawn(); + bolt->svflags = SVF_PROJECTILE; // special net code is used for projectiles + VectorCopy (start, bolt->s.origin); + VectorCopy (start, bolt->s.old_origin); + vectoangles (dir, bolt->s.angles); + VectorScale (dir, speed, bolt->velocity); + bolt->movetype = MOVETYPE_FLYMISSILE; + bolt->clipmask = MASK_SHOT; + bolt->solid = SOLID_BBOX; + bolt->s.effects |= effect; + VectorClear (bolt->mins); + VectorClear (bolt->maxs); + bolt->s.modelindex = gi.modelindex ("models/objects/laser/tris.md2"); + bolt->s.sound = gi.soundindex ("misc/lasfly.wav"); + bolt->owner = self; + bolt->touch = blaster_touch; + bolt->nextthink = level.time + 2; + bolt->think = G_FreeEdict; + bolt->dmg = damage; + bolt->classname = "bolt"; + if (hyper) + bolt->spawnflags = 1; + gi.linkentity (bolt); + + if (self->client) + check_dodge (self, bolt->s.origin, dir, speed); + + tr = gi.trace (self->s.origin, NULL, NULL, bolt->s.origin, bolt, MASK_SHOT); + if (tr.fraction < 1.0) + { + VectorMA (bolt->s.origin, -10, dir, bolt->s.origin); + bolt->touch (bolt, tr.ent, NULL, NULL); + } +} + + +/* +================= +fire_grenade +================= +*/ +static void Grenade_Explode (edict_t *ent) +{ + vec3_t origin; + int mod; + + if (ent->owner->client) + PlayerNoise(ent->owner, ent->s.origin, PNOISE_IMPACT); + + //FIXME: if we are onground then raise our Z just a bit since we are a point? + if (ent->enemy) + { + float points; + vec3_t v; + vec3_t dir; + + VectorAdd (ent->enemy->mins, ent->enemy->maxs, v); + VectorMA (ent->enemy->s.origin, 0.5, v, v); + VectorSubtract (ent->s.origin, v, v); + points = ent->dmg - 0.5 * VectorLength (v); + VectorSubtract (ent->enemy->s.origin, ent->s.origin, dir); + if (ent->spawnflags & 1) + mod = MOD_HANDGRENADE; + else + mod = MOD_GRENADE; + T_Damage (ent->enemy, ent, ent->owner, dir, ent->s.origin, vec3_origin, (int)points, (int)points, DAMAGE_RADIUS, mod); + } + + if (ent->spawnflags & 2) + mod = MOD_HELD_GRENADE; + else if (ent->spawnflags & 1) + mod = MOD_HG_SPLASH; + else + mod = MOD_G_SPLASH; + T_RadiusDamage(ent, ent->owner, ent->dmg, ent->enemy, ent->dmg_radius, mod); + + VectorMA (ent->s.origin, -0.02, ent->velocity, origin); + gi.WriteByte (svc_temp_entity); + if (ent->waterlevel) + { + if (ent->groundentity) + gi.WriteByte (TE_GRENADE_EXPLOSION_WATER); + else + gi.WriteByte (TE_ROCKET_EXPLOSION_WATER); + } + else + { + if (ent->groundentity) + gi.WriteByte (TE_GRENADE_EXPLOSION); + else + gi.WriteByte (TE_ROCKET_EXPLOSION); + } + gi.WritePosition (origin); + gi.multicast (ent->s.origin, MULTICAST_PHS); + + G_FreeEdict (ent); +} + +static void Grenade_Touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (other == ent->owner) + return; + + if (surf && (surf->flags & SURF_SKY)) + { + G_FreeEdict (ent); + return; + } + + if (!other->takedamage) + { + if (ent->spawnflags & 1) + { + if (random() > 0.5) + gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/hgrenb1a.wav"), 1, ATTN_NORM, 0); + else + gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/hgrenb2a.wav"), 1, ATTN_NORM, 0); + } + else + { + gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/grenlb1b.wav"), 1, ATTN_NORM, 0); + } + return; + } + + ent->enemy = other; + Grenade_Explode (ent); +} + +void fire_grenade (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, float timer, float damage_radius) +{ + edict_t *grenade; + vec3_t dir; + vec3_t forward, right, up; + + vectoangles (aimdir, dir); + AngleVectors (dir, forward, right, up); + + grenade = G_Spawn(); + VectorCopy (start, grenade->s.origin); + VectorScale (aimdir, speed, grenade->velocity); + VectorMA (grenade->velocity, 200 + crandom() * 10.0, up, grenade->velocity); + VectorMA (grenade->velocity, crandom() * 10.0, right, grenade->velocity); + VectorSet (grenade->avelocity, 300, 300, 300); + grenade->movetype = MOVETYPE_BOUNCE; + grenade->clipmask = MASK_SHOT; + grenade->solid = SOLID_BBOX; + grenade->s.effects |= EF_GRENADE; + VectorClear (grenade->mins); + VectorClear (grenade->maxs); + grenade->s.modelindex = gi.modelindex ("models/objects/grenade/tris.md2"); + grenade->owner = self; + grenade->touch = Grenade_Touch; + grenade->nextthink = level.time + timer; + grenade->think = Grenade_Explode; + grenade->dmg = damage; + grenade->dmg_radius = damage_radius; + grenade->classname = "grenade"; + + gi.linkentity (grenade); +} + +void fire_grenade2 (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, float timer, float damage_radius, qboolean held) +{ + edict_t *grenade; + vec3_t dir; + vec3_t forward, right, up; + + vectoangles (aimdir, dir); + AngleVectors (dir, forward, right, up); + + grenade = G_Spawn(); + VectorCopy (start, grenade->s.origin); + VectorScale (aimdir, speed, grenade->velocity); + VectorMA (grenade->velocity, 200 + crandom() * 10.0, up, grenade->velocity); + VectorMA (grenade->velocity, crandom() * 10.0, right, grenade->velocity); + VectorSet (grenade->avelocity, 300, 300, 300); + grenade->movetype = MOVETYPE_BOUNCE; + grenade->clipmask = MASK_SHOT; + grenade->solid = SOLID_BBOX; + grenade->s.effects |= EF_GRENADE; + VectorClear (grenade->mins); + VectorClear (grenade->maxs); + grenade->s.modelindex = gi.modelindex ("models/objects/grenade2/tris.md2"); + grenade->owner = self; + grenade->touch = Grenade_Touch; + grenade->nextthink = level.time + timer; + grenade->think = Grenade_Explode; + grenade->dmg = damage; + grenade->dmg_radius = damage_radius; + grenade->classname = "hgrenade"; + if (held) + grenade->spawnflags = 3; + else + grenade->spawnflags = 1; + grenade->s.sound = gi.soundindex("weapons/hgrenc1b.wav"); + + if (timer <= 0.0) + Grenade_Explode (grenade); + else + { + gi.sound (self, CHAN_WEAPON, gi.soundindex ("weapons/hgrent1a.wav"), 1, ATTN_NORM, 0); + gi.linkentity (grenade); + } +} + + +/* +================= +fire_rocket +================= +*/ +void rocket_touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + vec3_t origin; + int n; + + if (other == ent->owner) + return; + + if (surf && (surf->flags & SURF_SKY)) + { + G_FreeEdict (ent); + return; + } + + if (ent->owner->client) + PlayerNoise(ent->owner, ent->s.origin, PNOISE_IMPACT); + + // calculate position for the explosion entity + VectorMA (ent->s.origin, -0.02, ent->velocity, origin); + + if (other->takedamage) + { + T_Damage (other, ent, ent->owner, ent->velocity, ent->s.origin, plane->normal, ent->dmg, 0, 0, MOD_ROCKET); + } + else + { + // don't throw any debris in net games + if (!deathmatch->value && !coop->value) + { + if ((surf) && !(surf->flags & (SURF_WARP|SURF_TRANS33|SURF_TRANS66|SURF_FLOWING))) + { + n = rand() % 5; + while(n--) + ThrowDebris (ent, "models/objects/debris2/tris.md2", 2, ent->s.origin); + } + } + } + + T_RadiusDamage(ent, ent->owner, ent->radius_dmg, other, ent->dmg_radius, MOD_R_SPLASH); + + gi.WriteByte (svc_temp_entity); + if (ent->waterlevel) + gi.WriteByte (TE_ROCKET_EXPLOSION_WATER); + else + gi.WriteByte (TE_ROCKET_EXPLOSION); + gi.WritePosition (origin); + gi.multicast (ent->s.origin, MULTICAST_PHS); + + G_FreeEdict (ent); +} + +void fire_rocket (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, float damage_radius, int radius_damage) +{ + edict_t *rocket; + + rocket = G_Spawn(); + VectorCopy (start, rocket->s.origin); + VectorCopy (dir, rocket->movedir); + vectoangles (dir, rocket->s.angles); + VectorScale (dir, speed, rocket->velocity); + rocket->movetype = MOVETYPE_FLYMISSILE; + rocket->clipmask = MASK_SHOT; + rocket->solid = SOLID_BBOX; + rocket->s.effects |= EF_ROCKET; + VectorClear (rocket->mins); + VectorClear (rocket->maxs); + rocket->s.modelindex = gi.modelindex ("models/objects/rocket/tris.md2"); + rocket->owner = self; + rocket->touch = rocket_touch; + rocket->nextthink = level.time + 8000/speed; + rocket->think = G_FreeEdict; + rocket->dmg = damage; + rocket->radius_dmg = radius_damage; + rocket->dmg_radius = damage_radius; + rocket->s.sound = gi.soundindex ("weapons/rockfly.wav"); + rocket->classname = "rocket"; + + if (self->client) + check_dodge (self, rocket->s.origin, dir, speed); + + gi.linkentity (rocket); +} + + +/* +================= +fire_rail +================= +*/ +void fire_rail (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick) +{ + vec3_t from; + vec3_t end; + trace_t tr; + edict_t *ignore; + int mask; + qboolean water; + + VectorMA (start, 8192, aimdir, end); + VectorCopy (start, from); + ignore = self; + water = false; + mask = MASK_SHOT|CONTENTS_SLIME|CONTENTS_LAVA; + while (ignore) + { + tr = gi.trace (from, NULL, NULL, end, ignore, mask); + + if (tr.contents & (CONTENTS_SLIME|CONTENTS_LAVA)) + { + mask &= ~(CONTENTS_SLIME|CONTENTS_LAVA); + water = true; + } + else + { + //ZOID--added so rail goes through SOLID_BBOX entities (gibs, etc) + if ((tr.ent->svflags & SVF_MONSTER) || (tr.ent->client) || + (tr.ent->solid == SOLID_BBOX)) + ignore = tr.ent; + else + ignore = NULL; + + if ((tr.ent != self) && (tr.ent->takedamage)) + T_Damage (tr.ent, self, self, aimdir, tr.endpos, tr.plane.normal, damage, kick, 0, MOD_RAILGUN); + } + + VectorCopy (tr.endpos, from); + } + + // send gun puff / flash + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_RAILTRAIL); + gi.WritePosition (start); + gi.WritePosition (tr.endpos); + gi.multicast (self->s.origin, MULTICAST_PHS); +// gi.multicast (start, MULTICAST_PHS); + if (water) + { + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_RAILTRAIL); + gi.WritePosition (start); + gi.WritePosition (tr.endpos); + gi.multicast (tr.endpos, MULTICAST_PHS); + } + + if (self->client) + PlayerNoise(self, tr.endpos, PNOISE_IMPACT); +} + + +/* +================= +fire_bfg +================= +*/ +void bfg_explode (edict_t *self) +{ + edict_t *ent; + float points; + vec3_t v; + float dist; + + if (self->s.frame == 0) + { + // the BFG effect + ent = NULL; + while ((ent = findradius(ent, self->s.origin, self->dmg_radius)) != NULL) + { + if (!ent->takedamage) + continue; + if (ent == self->owner) + continue; + if (!CanDamage (ent, self)) + continue; + if (!CanDamage (ent, self->owner)) + continue; + + VectorAdd (ent->mins, ent->maxs, v); + VectorMA (ent->s.origin, 0.5, v, v); + VectorSubtract (self->s.origin, v, v); + dist = VectorLength(v); + points = self->radius_dmg * (1.0 - sqrt(dist/self->dmg_radius)); + if (ent == self->owner) + points = points * 0.5; + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_BFG_EXPLOSION); + gi.WritePosition (ent->s.origin); + gi.multicast (ent->s.origin, MULTICAST_PHS); + T_Damage (ent, self, self->owner, self->velocity, ent->s.origin, vec3_origin, (int)points, 0, DAMAGE_ENERGY, MOD_BFG_EFFECT); + } + } + + self->nextthink = level.time + FRAMETIME; + self->s.frame++; + if (self->s.frame == 5) + self->think = G_FreeEdict; +} + +void bfg_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (other == self->owner) + return; + + if (surf && (surf->flags & SURF_SKY)) + { + G_FreeEdict (self); + return; + } + + if (self->owner->client) + PlayerNoise(self->owner, self->s.origin, PNOISE_IMPACT); + + // core explosion - prevents firing it into the wall/floor + if (other->takedamage) + T_Damage (other, self, self->owner, self->velocity, self->s.origin, plane->normal, 200, 0, 0, MOD_BFG_BLAST); + T_RadiusDamage(self, self->owner, 200, other, 100, MOD_BFG_BLAST); + + gi.sound (self, CHAN_VOICE, gi.soundindex ("weapons/bfg__x1b.wav"), 1, ATTN_NORM, 0); + self->solid = SOLID_NOT; + self->touch = NULL; + VectorMA (self->s.origin, -1 * FRAMETIME, self->velocity, self->s.origin); + VectorClear (self->velocity); + self->s.modelindex = gi.modelindex ("sprites/s_bfg3.sp2"); + self->s.frame = 0; + self->s.sound = 0; + self->s.effects &= ~EF_ANIM_ALLFAST; + self->think = bfg_explode; + self->nextthink = level.time + FRAMETIME; + self->enemy = other; + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_BFG_BIGEXPLOSION); + gi.WritePosition (self->s.origin); + gi.multicast (self->s.origin, MULTICAST_PVS); +} + + +void bfg_think (edict_t *self) +{ + edict_t *ent; + edict_t *ignore; + vec3_t point; + vec3_t dir; + vec3_t start; + vec3_t end; + int dmg; + trace_t tr; + + if (deathmatch->value) + dmg = 5; + else + dmg = 10; + + ent = NULL; + while ((ent = findradius(ent, self->s.origin, 256)) != NULL) + { + if (ent == self) + continue; + + if (ent == self->owner) + continue; + + if (!ent->takedamage) + continue; + + if (!(ent->svflags & SVF_MONSTER) && (!ent->client) && (strcmp(ent->classname, "misc_explobox") != 0)) + continue; + +//ZOID + //don't target players in CTF + if (ctf->value && ent->client && + self->owner->client && + ent->client->resp.ctf_team == self->owner->client->resp.ctf_team) + continue; +//ZOID + + VectorMA (ent->absmin, 0.5, ent->size, point); + + VectorSubtract (point, self->s.origin, dir); + VectorNormalize (dir); + + ignore = self; + VectorCopy (self->s.origin, start); + VectorMA (start, 2048, dir, end); + while(1) + { + tr = gi.trace (start, NULL, NULL, end, ignore, CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_DEADMONSTER); + + if (!tr.ent) + break; + + // hurt it if we can + if ((tr.ent->takedamage) && !(tr.ent->flags & FL_IMMUNE_LASER) && (tr.ent != self->owner)) + T_Damage (tr.ent, self, self->owner, dir, tr.endpos, vec3_origin, dmg, 1, DAMAGE_ENERGY, MOD_BFG_LASER); + + // if we hit something that's not a monster or player we're done + if (!(tr.ent->svflags & SVF_MONSTER) && (!tr.ent->client)) + { + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_LASER_SPARKS); + gi.WriteByte (4); + gi.WritePosition (tr.endpos); + gi.WriteDir (tr.plane.normal); + gi.WriteByte (self->s.skinnum); + gi.multicast (tr.endpos, MULTICAST_PVS); + break; + } + + ignore = tr.ent; + VectorCopy (tr.endpos, start); + } + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_BFG_LASER); + gi.WritePosition (self->s.origin); + gi.WritePosition (tr.endpos); + gi.multicast (self->s.origin, MULTICAST_PHS); + } + + self->nextthink = level.time + FRAMETIME; +} + + +void fire_bfg (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, float damage_radius) +{ + edict_t *bfg; + + bfg = G_Spawn(); + VectorCopy (start, bfg->s.origin); + VectorCopy (dir, bfg->movedir); + vectoangles (dir, bfg->s.angles); + VectorScale (dir, speed, bfg->velocity); + bfg->movetype = MOVETYPE_FLYMISSILE; + bfg->clipmask = MASK_SHOT; + bfg->solid = SOLID_BBOX; + bfg->s.effects |= EF_BFG | EF_ANIM_ALLFAST; + VectorClear (bfg->mins); + VectorClear (bfg->maxs); + bfg->s.modelindex = gi.modelindex ("sprites/s_bfg1.sp2"); + bfg->owner = self; + bfg->touch = bfg_touch; + bfg->nextthink = level.time + 8000/speed; + bfg->think = G_FreeEdict; + bfg->radius_dmg = damage; + bfg->dmg_radius = damage_radius; + bfg->classname = "bfg blast"; + bfg->s.sound = gi.soundindex ("weapons/bfg__l1a.wav"); + + bfg->think = bfg_think; + bfg->nextthink = level.time + FRAMETIME; + bfg->teammaster = bfg; + bfg->teamchain = NULL; + + if (self->client) + check_dodge (self, bfg->s.origin, dir, speed); + + gi.linkentity (bfg); +} diff --git a/original/ctf/game.h b/original/ctf/game.h new file mode 100644 index 0000000..318624f --- /dev/null +++ b/original/ctf/game.h @@ -0,0 +1,242 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +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. + +*/ + +// game.h -- game dll information visible to server + +#define GAME_API_VERSION 3 + +// edict->svflags + +#define SVF_NOCLIENT 0x00000001 // don't send entity to clients, even if it has effects +#define SVF_DEADMONSTER 0x00000002 // treat as CONTENTS_DEADMONSTER for collision +#define SVF_MONSTER 0x00000004 // treat as CONTENTS_MONSTER for collision +//ZOID +#define SVF_PROJECTILE 0x00000008 // entity is simple projectile, used for network optimization +// if an entity is projectile, the model index/x/y/z/pitch/yaw are sent, encoded into +// seven (or eight) bytes. This is to speed up projectiles. Currently, only the +// hyperblaster makes use of this. use for items that are moving with a constant +// velocity that don't change direction or model +//ZOID + +// edict->solid values + +typedef enum +{ +SOLID_NOT, // no interaction with other objects +SOLID_TRIGGER, // only touch when inside, after moving +SOLID_BBOX, // touch on edge +SOLID_BSP // bsp clip, touch on edge +} solid_t; + +//=============================================================== + +// link_t is only used for entity area links now +typedef struct link_s +{ + struct link_s *prev, *next; +} link_t; + +#define MAX_ENT_CLUSTERS 16 + + +typedef struct edict_s edict_t; +typedef struct gclient_s gclient_t; + + +#ifndef GAME_INCLUDE + +struct gclient_s +{ + player_state_t ps; // communicated by server to clients + int ping; + // the game dll can add anything it wants after + // this point in the structure +}; + + +struct edict_s +{ + entity_state_t s; + struct gclient_s *client; + qboolean inuse; + int linkcount; + + // FIXME: move these fields to a server private sv_entity_t + link_t area; // linked to a division node or leaf + + int num_clusters; // if -1, use headnode instead + int clusternums[MAX_ENT_CLUSTERS]; + int headnode; // unused if num_clusters != -1 + int areanum, areanum2; + + //================================ + + int svflags; // SVF_NOCLIENT, SVF_DEADMONSTER, SVF_MONSTER, etc + vec3_t mins, maxs; + vec3_t absmin, absmax, size; + solid_t solid; + int clipmask; + edict_t *owner; + + // the game dll can add anything it wants after + // this point in the structure +}; + +#endif // GAME_INCLUDE + +//=============================================================== + +// +// functions provided by the main engine +// +typedef struct +{ + // special messages + void (*bprintf) (int printlevel, char *fmt, ...); + void (*dprintf) (char *fmt, ...); + void (*cprintf) (edict_t *ent, int printlevel, char *fmt, ...); + void (*centerprintf) (edict_t *ent, char *fmt, ...); + void (*sound) (edict_t *ent, int channel, int soundindex, float volume, float attenuation, float timeofs); + void (*positioned_sound) (vec3_t origin, edict_t *ent, int channel, int soundinedex, float volume, float attenuation, float timeofs); + + // config strings hold all the index strings, the lightstyles, + // and misc data like the sky definition and cdtrack. + // All of the current configstrings are sent to clients when + // they connect, and changes are sent to all connected clients. + void (*configstring) (int num, char *string); + + void (*error) (char *fmt, ...); + + // the *index functions create configstrings and some internal server state + int (*modelindex) (char *name); + int (*soundindex) (char *name); + int (*imageindex) (char *name); + + void (*setmodel) (edict_t *ent, char *name); + + // collision detection + trace_t (*trace) (vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, edict_t *passent, int contentmask); + int (*pointcontents) (vec3_t point); + qboolean (*inPVS) (vec3_t p1, vec3_t p2); + qboolean (*inPHS) (vec3_t p1, vec3_t p2); + void (*SetAreaPortalState) (int portalnum, qboolean open); + qboolean (*AreasConnected) (int area1, int area2); + + // an entity will never be sent to a client or used for collision + // if it is not passed to linkentity. If the size, position, or + // solidity changes, it must be relinked. + void (*linkentity) (edict_t *ent); + void (*unlinkentity) (edict_t *ent); // call before removing an interactive edict + int (*BoxEdicts) (vec3_t mins, vec3_t maxs, edict_t **list, int maxcount, int areatype); + void (*Pmove) (pmove_t *pmove); // player movement code common with client prediction + + // network messaging + void (*multicast) (vec3_t origin, multicast_t to); + void (*unicast) (edict_t *ent, qboolean reliable); + void (*WriteChar) (int c); + void (*WriteByte) (int c); + void (*WriteShort) (int c); + void (*WriteLong) (int c); + void (*WriteFloat) (float f); + void (*WriteString) (char *s); + void (*WritePosition) (vec3_t pos); // some fractional bits + void (*WriteDir) (vec3_t pos); // single byte encoded, very coarse + void (*WriteAngle) (float f); + + // managed memory allocation + void *(*TagMalloc) (int size, int tag); + void (*TagFree) (void *block); + void (*FreeTags) (int tag); + + // console variable interaction + cvar_t *(*cvar) (char *var_name, char *value, int flags); + cvar_t *(*cvar_set) (char *var_name, char *value); + cvar_t *(*cvar_forceset) (char *var_name, char *value); + + // ClientCommand and ServerCommand parameter access + int (*argc) (void); + char *(*argv) (int n); + char *(*args) (void); // concatenation of all argv >= 1 + + // add commands to the server console as if they were typed in + // for map changing, etc + void (*AddCommandString) (char *text); + + void (*DebugGraph) (float value, int color); +} game_import_t; + +// +// functions exported by the game subsystem +// +typedef struct +{ + int apiversion; + + // the init function will only be called when a game starts, + // not each time a level is loaded. Persistant data for clients + // and the server can be allocated in init + void (*Init) (void); + void (*Shutdown) (void); + + // each new level entered will cause a call to SpawnEntities + void (*SpawnEntities) (char *mapname, char *entstring, char *spawnpoint); + + // Read/Write Game is for storing persistant cross level information + // about the world state and the clients. + // WriteGame is called every time a level is exited. + // ReadGame is called on a loadgame. + void (*WriteGame) (char *filename, qboolean autosave); + void (*ReadGame) (char *filename); + + // ReadLevel is called after the default map information has been + // loaded with SpawnEntities + void (*WriteLevel) (char *filename); + void (*ReadLevel) (char *filename); + + qboolean (*ClientConnect) (edict_t *ent, char *userinfo); + void (*ClientBegin) (edict_t *ent); + void (*ClientUserinfoChanged) (edict_t *ent, char *userinfo); + void (*ClientDisconnect) (edict_t *ent); + void (*ClientCommand) (edict_t *ent); + void (*ClientThink) (edict_t *ent, usercmd_t *cmd); + + void (*RunFrame) (void); + + // ServerCommand will be called when an "sv " command is issued on the + // server console. + // The game can issue gi.argc() / gi.argv() commands to get the rest + // of the parameters + void (*ServerCommand) (void); + + // + // global variables shared between game and server + // + + // The edict array is allocated in the game dll so it + // can vary in size from one game to another. + // + // The size will be fixed when ge->Init() is called + struct edict_s *edicts; + int edict_size; + int num_edicts; // current number, <= max_edicts + int max_edicts; +} game_export_t; + +game_export_t *GetGameApi (game_import_t *import); diff --git a/original/ctf/m_move.c b/original/ctf/m_move.c new file mode 100644 index 0000000..229cfcd --- /dev/null +++ b/original/ctf/m_move.c @@ -0,0 +1,556 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +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. + +*/ +// m_move.c -- monster movement + +#include "g_local.h" + +#define STEPSIZE 18 + +/* +============= +M_CheckBottom + +Returns false if any part of the bottom of the entity is off an edge that +is not a staircase. + +============= +*/ +int c_yes, c_no; + +qboolean M_CheckBottom (edict_t *ent) +{ + vec3_t mins, maxs, start, stop; + trace_t trace; + int x, y; + float mid, bottom; + + VectorAdd (ent->s.origin, ent->mins, mins); + VectorAdd (ent->s.origin, ent->maxs, maxs); + +// if all of the points under the corners are solid world, don't bother +// with the tougher checks +// the corners must be within 16 of the midpoint + start[2] = mins[2] - 1; + for (x=0 ; x<=1 ; x++) + for (y=0 ; y<=1 ; y++) + { + start[0] = x ? maxs[0] : mins[0]; + start[1] = y ? maxs[1] : mins[1]; + if (gi.pointcontents (start) != CONTENTS_SOLID) + goto realcheck; + } + + c_yes++; + return true; // we got out easy + +realcheck: + c_no++; +// +// check it for real... +// + start[2] = mins[2]; + +// the midpoint must be within 16 of the bottom + start[0] = stop[0] = (mins[0] + maxs[0])*0.5; + start[1] = stop[1] = (mins[1] + maxs[1])*0.5; + stop[2] = start[2] - 2*STEPSIZE; + trace = gi.trace (start, vec3_origin, vec3_origin, stop, ent, MASK_MONSTERSOLID); + + if (trace.fraction == 1.0) + return false; + mid = bottom = trace.endpos[2]; + +// the corners must be within 16 of the midpoint + for (x=0 ; x<=1 ; x++) + for (y=0 ; y<=1 ; y++) + { + start[0] = stop[0] = x ? maxs[0] : mins[0]; + start[1] = stop[1] = y ? maxs[1] : mins[1]; + + trace = gi.trace (start, vec3_origin, vec3_origin, stop, ent, MASK_MONSTERSOLID); + + if (trace.fraction != 1.0 && trace.endpos[2] > bottom) + bottom = trace.endpos[2]; + if (trace.fraction == 1.0 || mid - trace.endpos[2] > STEPSIZE) + return false; + } + + c_yes++; + return true; +} + + +/* +============= +SV_movestep + +Called by monster program code. +The move will be adjusted for slopes and stairs, but if the move isn't +possible, no move is done, false is returned, and +pr_global_struct->trace_normal is set to the normal of the blocking wall +============= +*/ +//FIXME since we need to test end position contents here, can we avoid doing +//it again later in catagorize position? +qboolean SV_movestep (edict_t *ent, vec3_t move, qboolean relink) +{ + float dz; + vec3_t oldorg, neworg, end; + trace_t trace; + int i; + float stepsize; + vec3_t test; + int contents; + +// try the move + VectorCopy (ent->s.origin, oldorg); + VectorAdd (ent->s.origin, move, neworg); + +// flying monsters don't step up + if ( ent->flags & (FL_SWIM | FL_FLY) ) + { + // try one move with vertical motion, then one without + for (i=0 ; i<2 ; i++) + { + VectorAdd (ent->s.origin, move, neworg); + if (i == 0 && ent->enemy) + { + if (!ent->goalentity) + ent->goalentity = ent->enemy; + dz = ent->s.origin[2] - ent->goalentity->s.origin[2]; + if (ent->goalentity->client) + { + if (dz > 40) + neworg[2] -= 8; + if (!((ent->flags & FL_SWIM) && (ent->waterlevel < 2))) + if (dz < 30) + neworg[2] += 8; + } + else + { + if (dz > 8) + neworg[2] -= 8; + else if (dz > 0) + neworg[2] -= dz; + else if (dz < -8) + neworg[2] += 8; + else + neworg[2] += dz; + } + } + trace = gi.trace (ent->s.origin, ent->mins, ent->maxs, neworg, ent, MASK_MONSTERSOLID); + + // fly monsters don't enter water voluntarily + if (ent->flags & FL_FLY) + { + if (!ent->waterlevel) + { + test[0] = trace.endpos[0]; + test[1] = trace.endpos[1]; + test[2] = trace.endpos[2] + ent->mins[2] + 1; + contents = gi.pointcontents(test); + if (contents & MASK_WATER) + return false; + } + } + + // swim monsters don't exit water voluntarily + if (ent->flags & FL_SWIM) + { + if (ent->waterlevel < 2) + { + test[0] = trace.endpos[0]; + test[1] = trace.endpos[1]; + test[2] = trace.endpos[2] + ent->mins[2] + 1; + contents = gi.pointcontents(test); + if (!(contents & MASK_WATER)) + return false; + } + } + + if (trace.fraction == 1) + { + VectorCopy (trace.endpos, ent->s.origin); + if (relink) + { + gi.linkentity (ent); + G_TouchTriggers (ent); + } + return true; + } + + if (!ent->enemy) + break; + } + + return false; + } + +// push down from a step height above the wished position + if (!(ent->monsterinfo.aiflags & AI_NOSTEP)) + stepsize = STEPSIZE; + else + stepsize = 1; + + neworg[2] += stepsize; + VectorCopy (neworg, end); + end[2] -= stepsize*2; + + trace = gi.trace (neworg, ent->mins, ent->maxs, end, ent, MASK_MONSTERSOLID); + + if (trace.allsolid) + return false; + + if (trace.startsolid) + { + neworg[2] -= stepsize; + trace = gi.trace (neworg, ent->mins, ent->maxs, end, ent, MASK_MONSTERSOLID); + if (trace.allsolid || trace.startsolid) + return false; + } + + + // don't go in to water + if (ent->waterlevel == 0) + { + test[0] = trace.endpos[0]; + test[1] = trace.endpos[1]; + test[2] = trace.endpos[2] + ent->mins[2] + 1; + contents = gi.pointcontents(test); + + if (contents & MASK_WATER) + return false; + } + + if (trace.fraction == 1) + { + // if monster had the ground pulled out, go ahead and fall + if ( ent->flags & FL_PARTIALGROUND ) + { + VectorAdd (ent->s.origin, move, ent->s.origin); + if (relink) + { + gi.linkentity (ent); + G_TouchTriggers (ent); + } + ent->groundentity = NULL; + return true; + } + + return false; // walked off an edge + } + +// check point traces down for dangling corners + VectorCopy (trace.endpos, ent->s.origin); + + if (!M_CheckBottom (ent)) + { + if ( ent->flags & FL_PARTIALGROUND ) + { // entity had floor mostly pulled out from underneath it + // and is trying to correct + if (relink) + { + gi.linkentity (ent); + G_TouchTriggers (ent); + } + return true; + } + VectorCopy (oldorg, ent->s.origin); + return false; + } + + if ( ent->flags & FL_PARTIALGROUND ) + { + ent->flags &= ~FL_PARTIALGROUND; + } + ent->groundentity = trace.ent; + ent->groundentity_linkcount = trace.ent->linkcount; + +// the move is ok + if (relink) + { + gi.linkentity (ent); + G_TouchTriggers (ent); + } + return true; +} + + +//============================================================================ + +/* +=============== +M_ChangeYaw + +=============== +*/ +void M_ChangeYaw (edict_t *ent) +{ + float ideal; + float current; + float move; + float speed; + + current = anglemod(ent->s.angles[YAW]); + ideal = ent->ideal_yaw; + + if (current == ideal) + return; + + move = ideal - current; + speed = ent->yaw_speed; + if (ideal > current) + { + if (move >= 180) + move = move - 360; + } + else + { + if (move <= -180) + move = move + 360; + } + if (move > 0) + { + if (move > speed) + move = speed; + } + else + { + if (move < -speed) + move = -speed; + } + + ent->s.angles[YAW] = anglemod (current + move); +} + + +/* +====================== +SV_StepDirection + +Turns to the movement direction, and walks the current distance if +facing it. + +====================== +*/ +qboolean SV_StepDirection (edict_t *ent, float yaw, float dist) +{ + vec3_t move, oldorigin; + float delta; + + ent->ideal_yaw = yaw; + M_ChangeYaw (ent); + + yaw = yaw*M_PI*2 / 360; + move[0] = cos(yaw)*dist; + move[1] = sin(yaw)*dist; + move[2] = 0; + + VectorCopy (ent->s.origin, oldorigin); + if (SV_movestep (ent, move, false)) + { + delta = ent->s.angles[YAW] - ent->ideal_yaw; + if (delta > 45 && delta < 315) + { // not turned far enough, so don't take the step + VectorCopy (oldorigin, ent->s.origin); + } + gi.linkentity (ent); + G_TouchTriggers (ent); + return true; + } + gi.linkentity (ent); + G_TouchTriggers (ent); + return false; +} + +/* +====================== +SV_FixCheckBottom + +====================== +*/ +void SV_FixCheckBottom (edict_t *ent) +{ + ent->flags |= FL_PARTIALGROUND; +} + + + +/* +================ +SV_NewChaseDir + +================ +*/ +#define DI_NODIR -1 +void SV_NewChaseDir (edict_t *actor, edict_t *enemy, float dist) +{ + float deltax,deltay; + float d[3]; + float tdir, olddir, turnaround; + + //FIXME: how did we get here with no enemy + if (!enemy) + return; + + olddir = anglemod( (int)(actor->ideal_yaw/45)*45 ); + turnaround = anglemod(olddir - 180); + + deltax = enemy->s.origin[0] - actor->s.origin[0]; + deltay = enemy->s.origin[1] - actor->s.origin[1]; + if (deltax>10) + d[1]= 0; + else if (deltax<-10) + d[1]= 180; + else + d[1]= DI_NODIR; + if (deltay<-10) + d[2]= 270; + else if (deltay>10) + d[2]= 90; + else + d[2]= DI_NODIR; + +// try direct route + if (d[1] != DI_NODIR && d[2] != DI_NODIR) + { + if (d[1] == 0) + tdir = d[2] == 90 ? 45 : 315; + else + tdir = d[2] == 90 ? 135 : 215; + + if (tdir != turnaround && SV_StepDirection(actor, tdir, dist)) + return; + } + +// try other directions + if ( ((rand()&3) & 1) || abs(deltay)>abs(deltax)) + { + tdir=d[1]; + d[1]=d[2]; + d[2]=tdir; + } + + if (d[1]!=DI_NODIR && d[1]!=turnaround + && SV_StepDirection(actor, d[1], dist)) + return; + + if (d[2]!=DI_NODIR && d[2]!=turnaround + && SV_StepDirection(actor, d[2], dist)) + return; + +/* there is no direct path to the player, so pick another direction */ + + if (olddir!=DI_NODIR && SV_StepDirection(actor, olddir, dist)) + return; + + if (rand()&1) /*randomly determine direction of search*/ + { + for (tdir=0 ; tdir<=315 ; tdir += 45) + if (tdir!=turnaround && SV_StepDirection(actor, tdir, dist) ) + return; + } + else + { + for (tdir=315 ; tdir >=0 ; tdir -= 45) + if (tdir!=turnaround && SV_StepDirection(actor, tdir, dist) ) + return; + } + + if (turnaround != DI_NODIR && SV_StepDirection(actor, turnaround, dist) ) + return; + + actor->ideal_yaw = olddir; // can't move + +// if a bridge was pulled out from underneath a monster, it may not have +// a valid standing position at all + + if (!M_CheckBottom (actor)) + SV_FixCheckBottom (actor); +} + +/* +====================== +SV_CloseEnough + +====================== +*/ +qboolean SV_CloseEnough (edict_t *ent, edict_t *goal, float dist) +{ + int i; + + for (i=0 ; i<3 ; i++) + { + if (goal->absmin[i] > ent->absmax[i] + dist) + return false; + if (goal->absmax[i] < ent->absmin[i] - dist) + return false; + } + return true; +} + + +/* +====================== +M_MoveToGoal +====================== +*/ +void M_MoveToGoal (edict_t *ent, float dist) +{ + edict_t *goal; + + goal = ent->goalentity; + + if (!ent->groundentity && !(ent->flags & (FL_FLY|FL_SWIM))) + return; + +// if the next step hits the enemy, return immediately + if (ent->enemy && SV_CloseEnough (ent, ent->enemy, dist) ) + return; + +// bump around... + if ( (rand()&3)==1 || !SV_StepDirection (ent, ent->ideal_yaw, dist)) + { + if (ent->inuse) + SV_NewChaseDir (ent, goal, dist); + } +} + + +/* +=============== +M_walkmove +=============== +*/ +qboolean M_walkmove (edict_t *ent, float yaw, float dist) +{ + vec3_t move; + + if (!ent->groundentity && !(ent->flags & (FL_FLY|FL_SWIM))) + return false; + + yaw = yaw*M_PI*2 / 360; + + move[0] = cos(yaw)*dist; + move[1] = sin(yaw)*dist; + move[2] = 0; + + return SV_movestep(ent, move, true); +} diff --git a/original/ctf/m_player.h b/original/ctf/m_player.h new file mode 100644 index 0000000..001bd8f --- /dev/null +++ b/original/ctf/m_player.h @@ -0,0 +1,225 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +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. + +*/ +// G:\quake2\baseq2\models/player_x/frames + +// This file generated by qdata - Do NOT Modify + +#define FRAME_stand01 0 +#define FRAME_stand02 1 +#define FRAME_stand03 2 +#define FRAME_stand04 3 +#define FRAME_stand05 4 +#define FRAME_stand06 5 +#define FRAME_stand07 6 +#define FRAME_stand08 7 +#define FRAME_stand09 8 +#define FRAME_stand10 9 +#define FRAME_stand11 10 +#define FRAME_stand12 11 +#define FRAME_stand13 12 +#define FRAME_stand14 13 +#define FRAME_stand15 14 +#define FRAME_stand16 15 +#define FRAME_stand17 16 +#define FRAME_stand18 17 +#define FRAME_stand19 18 +#define FRAME_stand20 19 +#define FRAME_stand21 20 +#define FRAME_stand22 21 +#define FRAME_stand23 22 +#define FRAME_stand24 23 +#define FRAME_stand25 24 +#define FRAME_stand26 25 +#define FRAME_stand27 26 +#define FRAME_stand28 27 +#define FRAME_stand29 28 +#define FRAME_stand30 29 +#define FRAME_stand31 30 +#define FRAME_stand32 31 +#define FRAME_stand33 32 +#define FRAME_stand34 33 +#define FRAME_stand35 34 +#define FRAME_stand36 35 +#define FRAME_stand37 36 +#define FRAME_stand38 37 +#define FRAME_stand39 38 +#define FRAME_stand40 39 +#define FRAME_run1 40 +#define FRAME_run2 41 +#define FRAME_run3 42 +#define FRAME_run4 43 +#define FRAME_run5 44 +#define FRAME_run6 45 +#define FRAME_attack1 46 +#define FRAME_attack2 47 +#define FRAME_attack3 48 +#define FRAME_attack4 49 +#define FRAME_attack5 50 +#define FRAME_attack6 51 +#define FRAME_attack7 52 +#define FRAME_attack8 53 +#define FRAME_pain101 54 +#define FRAME_pain102 55 +#define FRAME_pain103 56 +#define FRAME_pain104 57 +#define FRAME_pain201 58 +#define FRAME_pain202 59 +#define FRAME_pain203 60 +#define FRAME_pain204 61 +#define FRAME_pain301 62 +#define FRAME_pain302 63 +#define FRAME_pain303 64 +#define FRAME_pain304 65 +#define FRAME_jump1 66 +#define FRAME_jump2 67 +#define FRAME_jump3 68 +#define FRAME_jump4 69 +#define FRAME_jump5 70 +#define FRAME_jump6 71 +#define FRAME_flip01 72 +#define FRAME_flip02 73 +#define FRAME_flip03 74 +#define FRAME_flip04 75 +#define FRAME_flip05 76 +#define FRAME_flip06 77 +#define FRAME_flip07 78 +#define FRAME_flip08 79 +#define FRAME_flip09 80 +#define FRAME_flip10 81 +#define FRAME_flip11 82 +#define FRAME_flip12 83 +#define FRAME_salute01 84 +#define FRAME_salute02 85 +#define FRAME_salute03 86 +#define FRAME_salute04 87 +#define FRAME_salute05 88 +#define FRAME_salute06 89 +#define FRAME_salute07 90 +#define FRAME_salute08 91 +#define FRAME_salute09 92 +#define FRAME_salute10 93 +#define FRAME_salute11 94 +#define FRAME_taunt01 95 +#define FRAME_taunt02 96 +#define FRAME_taunt03 97 +#define FRAME_taunt04 98 +#define FRAME_taunt05 99 +#define FRAME_taunt06 100 +#define FRAME_taunt07 101 +#define FRAME_taunt08 102 +#define FRAME_taunt09 103 +#define FRAME_taunt10 104 +#define FRAME_taunt11 105 +#define FRAME_taunt12 106 +#define FRAME_taunt13 107 +#define FRAME_taunt14 108 +#define FRAME_taunt15 109 +#define FRAME_taunt16 110 +#define FRAME_taunt17 111 +#define FRAME_wave01 112 +#define FRAME_wave02 113 +#define FRAME_wave03 114 +#define FRAME_wave04 115 +#define FRAME_wave05 116 +#define FRAME_wave06 117 +#define FRAME_wave07 118 +#define FRAME_wave08 119 +#define FRAME_wave09 120 +#define FRAME_wave10 121 +#define FRAME_wave11 122 +#define FRAME_point01 123 +#define FRAME_point02 124 +#define FRAME_point03 125 +#define FRAME_point04 126 +#define FRAME_point05 127 +#define FRAME_point06 128 +#define FRAME_point07 129 +#define FRAME_point08 130 +#define FRAME_point09 131 +#define FRAME_point10 132 +#define FRAME_point11 133 +#define FRAME_point12 134 +#define FRAME_crstnd01 135 +#define FRAME_crstnd02 136 +#define FRAME_crstnd03 137 +#define FRAME_crstnd04 138 +#define FRAME_crstnd05 139 +#define FRAME_crstnd06 140 +#define FRAME_crstnd07 141 +#define FRAME_crstnd08 142 +#define FRAME_crstnd09 143 +#define FRAME_crstnd10 144 +#define FRAME_crstnd11 145 +#define FRAME_crstnd12 146 +#define FRAME_crstnd13 147 +#define FRAME_crstnd14 148 +#define FRAME_crstnd15 149 +#define FRAME_crstnd16 150 +#define FRAME_crstnd17 151 +#define FRAME_crstnd18 152 +#define FRAME_crstnd19 153 +#define FRAME_crwalk1 154 +#define FRAME_crwalk2 155 +#define FRAME_crwalk3 156 +#define FRAME_crwalk4 157 +#define FRAME_crwalk5 158 +#define FRAME_crwalk6 159 +#define FRAME_crattak1 160 +#define FRAME_crattak2 161 +#define FRAME_crattak3 162 +#define FRAME_crattak4 163 +#define FRAME_crattak5 164 +#define FRAME_crattak6 165 +#define FRAME_crattak7 166 +#define FRAME_crattak8 167 +#define FRAME_crattak9 168 +#define FRAME_crpain1 169 +#define FRAME_crpain2 170 +#define FRAME_crpain3 171 +#define FRAME_crpain4 172 +#define FRAME_crdeath1 173 +#define FRAME_crdeath2 174 +#define FRAME_crdeath3 175 +#define FRAME_crdeath4 176 +#define FRAME_crdeath5 177 +#define FRAME_death101 178 +#define FRAME_death102 179 +#define FRAME_death103 180 +#define FRAME_death104 181 +#define FRAME_death105 182 +#define FRAME_death106 183 +#define FRAME_death201 184 +#define FRAME_death202 185 +#define FRAME_death203 186 +#define FRAME_death204 187 +#define FRAME_death205 188 +#define FRAME_death206 189 +#define FRAME_death301 190 +#define FRAME_death302 191 +#define FRAME_death303 192 +#define FRAME_death304 193 +#define FRAME_death305 194 +#define FRAME_death306 195 +#define FRAME_death307 196 +#define FRAME_death308 197 + +#define MODEL_SCALE 1.000000 + + diff --git a/original/ctf/p_client.c b/original/ctf/p_client.c new file mode 100644 index 0000000..366b72f --- /dev/null +++ b/original/ctf/p_client.c @@ -0,0 +1,1741 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +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. + +*/ +#include "g_local.h" +#include "m_player.h" + +void SP_misc_teleporter_dest (edict_t *ent); + +// +// Gross, ugly, disgustuing hack section +// + +// this function is an ugly as hell hack to fix some map flaws +// +// the coop spawn spots on some maps are SNAFU. There are coop spots +// with the wrong targetname as well as spots with no name at all +// +// we use carnal knowledge of the maps to fix the coop spot targetnames to match +// that of the nearest named single player spot + +static void SP_FixCoopSpots (edict_t *self) +{ + edict_t *spot; + vec3_t d; + + spot = NULL; + + while(1) + { + spot = G_Find(spot, FOFS(classname), "info_player_start"); + if (!spot) + return; + if (!spot->targetname) + continue; + VectorSubtract(self->s.origin, spot->s.origin, d); + if (VectorLength(d) < 384) + { + if ((!self->targetname) || stricmp(self->targetname, spot->targetname) != 0) + { +// gi.dprintf("FixCoopSpots changed %s at %s targetname from %s to %s\n", self->classname, vtos(self->s.origin), self->targetname, spot->targetname); + self->targetname = spot->targetname; + } + return; + } + } +} + +// now if that one wasn't ugly enough for you then try this one on for size +// some maps don't have any coop spots at all, so we need to create them +// where they should have been + +static void SP_CreateCoopSpots (edict_t *self) +{ + edict_t *spot; + + if(stricmp(level.mapname, "security") == 0) + { + spot = G_Spawn(); + spot->classname = "info_player_coop"; + spot->s.origin[0] = 188 - 64; + spot->s.origin[1] = -164; + spot->s.origin[2] = 80; + spot->targetname = "jail3"; + spot->s.angles[1] = 90; + + spot = G_Spawn(); + spot->classname = "info_player_coop"; + spot->s.origin[0] = 188 + 64; + spot->s.origin[1] = -164; + spot->s.origin[2] = 80; + spot->targetname = "jail3"; + spot->s.angles[1] = 90; + + spot = G_Spawn(); + spot->classname = "info_player_coop"; + spot->s.origin[0] = 188 + 128; + spot->s.origin[1] = -164; + spot->s.origin[2] = 80; + spot->targetname = "jail3"; + spot->s.angles[1] = 90; + + return; + } +} + + +/*QUAKED info_player_start (1 0 0) (-16 -16 -24) (16 16 32) +The normal starting point for a level. +*/ +void SP_info_player_start(edict_t *self) +{ + if (!coop->value) + return; + if(stricmp(level.mapname, "security") == 0) + { + // invoke one of our gross, ugly, disgusting hacks + self->think = SP_CreateCoopSpots; + self->nextthink = level.time + FRAMETIME; + } +} + +/*QUAKED info_player_deathmatch (1 0 1) (-16 -16 -24) (16 16 32) +potential spawning position for deathmatch games +*/ +void SP_info_player_deathmatch(edict_t *self) +{ + if (!deathmatch->value) + { + G_FreeEdict (self); + return; + } + SP_misc_teleporter_dest (self); +} + +/*QUAKED info_player_coop (1 0 1) (-16 -16 -24) (16 16 32) +potential spawning position for coop games +*/ + +void SP_info_player_coop(edict_t *self) +{ + if (!coop->value) + { + G_FreeEdict (self); + return; + } + + if((stricmp(level.mapname, "jail2") == 0) || + (stricmp(level.mapname, "jail4") == 0) || + (stricmp(level.mapname, "mine1") == 0) || + (stricmp(level.mapname, "mine2") == 0) || + (stricmp(level.mapname, "mine3") == 0) || + (stricmp(level.mapname, "mine4") == 0) || + (stricmp(level.mapname, "lab") == 0) || + (stricmp(level.mapname, "boss1") == 0) || + (stricmp(level.mapname, "fact3") == 0) || + (stricmp(level.mapname, "biggun") == 0) || + (stricmp(level.mapname, "space") == 0) || + (stricmp(level.mapname, "command") == 0) || + (stricmp(level.mapname, "power2") == 0) || + (stricmp(level.mapname, "strike") == 0)) + { + // invoke one of our gross, ugly, disgusting hacks + self->think = SP_FixCoopSpots; + self->nextthink = level.time + FRAMETIME; + } +} + + +/*QUAKED info_player_intermission (1 0 1) (-16 -16 -24) (16 16 32) +The deathmatch intermission point will be at one of these +Use 'angles' instead of 'angle', so you can set pitch or roll as well as yaw. 'pitch yaw roll' +*/ +void SP_info_player_intermission(void) +{ +} + + +//======================================================================= + + +void player_pain (edict_t *self, edict_t *other, float kick, int damage) +{ + // player pain is handled at the end of the frame in P_DamageFeedback +} + + +qboolean IsFemale (edict_t *ent) +{ + char *info; + + if (!ent->client) + return false; + + info = Info_ValueForKey (ent->client->pers.userinfo, "skin"); + if (info[0] == 'f' || info[0] == 'F') + return true; + return false; +} + + +void ClientObituary (edict_t *self, edict_t *inflictor, edict_t *attacker) +{ + int mod; + char *message; + char *message2; + qboolean ff; + + + if (coop->value && attacker->client) + meansOfDeath |= MOD_FRIENDLY_FIRE; + + if (deathmatch->value || coop->value) + { + ff = meansOfDeath & MOD_FRIENDLY_FIRE; + mod = meansOfDeath & ~MOD_FRIENDLY_FIRE; + message = NULL; + message2 = ""; + + switch (mod) + { + case MOD_SUICIDE: + message = "suicides"; + break; + case MOD_FALLING: + message = "cratered"; + break; + case MOD_CRUSH: + message = "was squished"; + break; + case MOD_WATER: + message = "sank like a rock"; + break; + case MOD_SLIME: + message = "melted"; + break; + case MOD_LAVA: + message = "does a back flip into the lava"; + break; + case MOD_EXPLOSIVE: + case MOD_BARREL: + message = "blew up"; + break; + case MOD_EXIT: + message = "found a way out"; + break; + case MOD_TARGET_LASER: + message = "saw the light"; + break; + case MOD_TARGET_BLASTER: + message = "got blasted"; + break; + case MOD_BOMB: + case MOD_SPLASH: + case MOD_TRIGGER_HURT: + message = "was in the wrong place"; + break; + } + if (attacker == self) + { + switch (mod) + { + case MOD_HELD_GRENADE: + message = "tried to put the pin back in"; + break; + case MOD_HG_SPLASH: + case MOD_G_SPLASH: + if (IsFemale(self)) + message = "tripped on her own grenade"; + else + message = "tripped on his own grenade"; + break; + case MOD_R_SPLASH: + if (IsFemale(self)) + message = "blew herself up"; + else + message = "blew himself up"; + break; + case MOD_BFG_BLAST: + message = "should have used a smaller gun"; + break; + default: + if (IsFemale(self)) + message = "killed herself"; + else + message = "killed himself"; + break; + } + } + if (message) + { + gi.bprintf (PRINT_MEDIUM, "%s %s.\n", self->client->pers.netname, message); + if (deathmatch->value) + self->client->resp.score--; + self->enemy = NULL; + return; + } + + self->enemy = attacker; + if (attacker && attacker->client) + { + switch (mod) + { + case MOD_BLASTER: + message = "was blasted by"; + break; + case MOD_SHOTGUN: + message = "was gunned down by"; + break; + case MOD_SSHOTGUN: + message = "was blown away by"; + message2 = "'s super shotgun"; + break; + case MOD_MACHINEGUN: + message = "was machinegunned by"; + break; + case MOD_CHAINGUN: + message = "was cut in half by"; + message2 = "'s chaingun"; + break; + case MOD_GRENADE: + message = "was popped by"; + message2 = "'s grenade"; + break; + case MOD_G_SPLASH: + message = "was shredded by"; + message2 = "'s shrapnel"; + break; + case MOD_ROCKET: + message = "ate"; + message2 = "'s rocket"; + break; + case MOD_R_SPLASH: + message = "almost dodged"; + message2 = "'s rocket"; + break; + case MOD_HYPERBLASTER: + message = "was melted by"; + message2 = "'s hyperblaster"; + break; + case MOD_RAILGUN: + message = "was railed by"; + break; + case MOD_BFG_LASER: + message = "saw the pretty lights from"; + message2 = "'s BFG"; + break; + case MOD_BFG_BLAST: + message = "was disintegrated by"; + message2 = "'s BFG blast"; + break; + case MOD_BFG_EFFECT: + message = "couldn't hide from"; + message2 = "'s BFG"; + break; + case MOD_HANDGRENADE: + message = "caught"; + message2 = "'s handgrenade"; + break; + case MOD_HG_SPLASH: + message = "didn't see"; + message2 = "'s handgrenade"; + break; + case MOD_HELD_GRENADE: + message = "feels"; + message2 = "'s pain"; + break; + case MOD_TELEFRAG: + message = "tried to invade"; + message2 = "'s personal space"; + break; +//ZOID + case MOD_GRAPPLE: + message = "was caught by"; + message2 = "'s grapple"; + break; +//ZOID + + } + if (message) + { + gi.bprintf (PRINT_MEDIUM,"%s %s %s%s\n", self->client->pers.netname, message, attacker->client->pers.netname, message2); + if (deathmatch->value) + { + if (ff) + attacker->client->resp.score--; + else + attacker->client->resp.score++; + } + return; + } + } + } + + gi.bprintf (PRINT_MEDIUM,"%s died.\n", self->client->pers.netname); + if (deathmatch->value) + self->client->resp.score--; +} + + +void Touch_Item (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf); + +void TossClientWeapon (edict_t *self) +{ + gitem_t *item; + edict_t *drop; + qboolean quad; + float spread; + + if (!deathmatch->value) + return; + + item = self->client->pers.weapon; + if (! self->client->pers.inventory[self->client->ammo_index] ) + item = NULL; + if (item && (strcmp (item->pickup_name, "Blaster") == 0)) + item = NULL; + + if (!((int)(dmflags->value) & DF_QUAD_DROP)) + quad = false; + else + quad = (self->client->quad_framenum > (level.framenum + 10)); + + if (item && quad) + spread = 22.5; + else + spread = 0.0; + + if (item) + { + self->client->v_angle[YAW] -= spread; + drop = Drop_Item (self, item); + self->client->v_angle[YAW] += spread; + drop->spawnflags = DROPPED_PLAYER_ITEM; + } + + if (quad) + { + self->client->v_angle[YAW] += spread; + drop = Drop_Item (self, FindItemByClassname ("item_quad")); + self->client->v_angle[YAW] -= spread; + drop->spawnflags |= DROPPED_PLAYER_ITEM; + + drop->touch = Touch_Item; + drop->nextthink = level.time + (self->client->quad_framenum - level.framenum) * FRAMETIME; + drop->think = G_FreeEdict; + } +} + + +/* +================== +LookAtKiller +================== +*/ +void LookAtKiller (edict_t *self, edict_t *inflictor, edict_t *attacker) +{ + vec3_t dir; + + if (attacker && attacker != world && attacker != self) + { + VectorSubtract (attacker->s.origin, self->s.origin, dir); + } + else if (inflictor && inflictor != world && inflictor != self) + { + VectorSubtract (inflictor->s.origin, self->s.origin, dir); + } + else + { + self->client->killer_yaw = self->s.angles[YAW]; + return; + } + + if (dir[0]) + self->client->killer_yaw = 180/M_PI*atan2(dir[1], dir[0]); + else { + self->client->killer_yaw = 0; + if (dir[1] > 0) + self->client->killer_yaw = 90; + else if (dir[1] < 0) + self->client->killer_yaw = -90; + } + if (self->client->killer_yaw < 0) + self->client->killer_yaw += 360; +} + +/* +================== +player_die +================== +*/ +void player_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + int n; + + VectorClear (self->avelocity); + + self->takedamage = DAMAGE_YES; + self->movetype = MOVETYPE_TOSS; + + self->s.modelindex2 = 0; // remove linked weapon model +//ZOID + self->s.modelindex3 = 0; // remove linked ctf flag +//ZOID + + self->s.angles[0] = 0; + self->s.angles[2] = 0; + + self->s.sound = 0; + self->client->weapon_sound = 0; + + self->maxs[2] = -8; + +// self->solid = SOLID_NOT; + self->svflags |= SVF_DEADMONSTER; + + if (!self->deadflag) + { + self->client->respawn_time = level.time + 1.0; + LookAtKiller (self, inflictor, attacker); + self->client->ps.pmove.pm_type = PM_DEAD; + ClientObituary (self, inflictor, attacker); +//ZOID + // if at start and same team, clear + if (ctf->value && meansOfDeath == MOD_TELEFRAG && + self->client->resp.ctf_state < 2 && + self->client->resp.ctf_team == attacker->client->resp.ctf_team) { + attacker->client->resp.score--; + self->client->resp.ctf_state = 0; + } + + CTFFragBonuses(self, inflictor, attacker); +//ZOID + TossClientWeapon (self); +//ZOID + CTFPlayerResetGrapple(self); + CTFDeadDropFlag(self); + CTFDeadDropTech(self); +//ZOID + if (deathmatch->value && !self->client->showscores) + Cmd_Help_f (self); // show scores + } + + // remove powerups + self->client->quad_framenum = 0; + self->client->invincible_framenum = 0; + self->client->breather_framenum = 0; + self->client->enviro_framenum = 0; + self->flags &= ~FL_POWER_ARMOR; + + // clear inventory + memset(self->client->pers.inventory, 0, sizeof(self->client->pers.inventory)); + + if (self->health < -40) + { // gib + gi.sound (self, CHAN_BODY, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0); + for (n= 0; n < 4; n++) + ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); + ThrowClientHead (self, damage); +//ZOID + self->client->anim_priority = ANIM_DEATH; + self->client->anim_end = 0; +//ZOID + self->takedamage = DAMAGE_NO; + } + else + { // normal death + if (!self->deadflag) + { + static int i; + + i = (i+1)%3; + // start a death animation + self->client->anim_priority = ANIM_DEATH; + if (self->client->ps.pmove.pm_flags & PMF_DUCKED) + { + self->s.frame = FRAME_crdeath1-1; + self->client->anim_end = FRAME_crdeath5; + } + else switch (i) + { + case 0: + self->s.frame = FRAME_death101-1; + self->client->anim_end = FRAME_death106; + break; + case 1: + self->s.frame = FRAME_death201-1; + self->client->anim_end = FRAME_death206; + break; + case 2: + self->s.frame = FRAME_death301-1; + self->client->anim_end = FRAME_death308; + break; + } + gi.sound (self, CHAN_VOICE, gi.soundindex(va("*death%i.wav", (rand()%4)+1)), 1, ATTN_NORM, 0); + } + } + + self->deadflag = DEAD_DEAD; + + gi.linkentity (self); +} + +//======================================================================= + +/* +============== +InitClientPersistant + +This is only called when the game first initializes in single player, +but is called after each death and level change in deathmatch +============== +*/ +void InitClientPersistant (gclient_t *client) +{ + gitem_t *item; + + memset (&client->pers, 0, sizeof(client->pers)); + + item = FindItem("Blaster"); + client->pers.selected_item = ITEM_INDEX(item); + client->pers.inventory[client->pers.selected_item] = 1; + + client->pers.weapon = item; +//ZOID + client->pers.lastweapon = item; +//ZOID + +//ZOID + item = FindItem("Grapple"); + client->pers.inventory[ITEM_INDEX(item)] = 1; +//ZOID + + client->pers.health = 100; + client->pers.max_health = 100; + + client->pers.max_bullets = 200; + client->pers.max_shells = 100; + client->pers.max_rockets = 50; + client->pers.max_grenades = 50; + client->pers.max_cells = 200; + client->pers.max_slugs = 50; + + client->pers.connected = true; +} + + +void InitClientResp (gclient_t *client) +{ +//ZOID + int ctf_team = client->resp.ctf_team; + qboolean id_state = client->resp.id_state; +//ZOID + + memset (&client->resp, 0, sizeof(client->resp)); + +//ZOID + client->resp.ctf_team = ctf_team; + client->resp.id_state = id_state; +//ZOID + + client->resp.enterframe = level.framenum; + client->resp.coop_respawn = client->pers; + +//ZOID + if (ctf->value && client->resp.ctf_team < CTF_TEAM1) + CTFAssignTeam(client); +//ZOID +} + +/* +================== +SaveClientData + +Some information that should be persistant, like health, +is still stored in the edict structure, so it needs to +be mirrored out to the client structure before all the +edicts are wiped. +================== +*/ +void SaveClientData (void) +{ + int i; + edict_t *ent; + + for (i=0 ; iinuse) + continue; + game.clients[i].pers.health = ent->health; + game.clients[i].pers.max_health = ent->max_health; + game.clients[i].pers.savedFlags = (ent->flags & (FL_GODMODE|FL_NOTARGET|FL_POWER_ARMOR)); + if (coop->value) + game.clients[i].pers.score = ent->client->resp.score; + } +} + +void FetchClientEntData (edict_t *ent) +{ + ent->health = ent->client->pers.health; + ent->max_health = ent->client->pers.max_health; + ent->flags |= ent->client->pers.savedFlags; + if (coop->value) + ent->client->resp.score = ent->client->pers.score; +} + + + +/* +======================================================================= + + SelectSpawnPoint + +======================================================================= +*/ + +/* +================ +PlayersRangeFromSpot + +Returns the distance to the nearest player from the given spot +================ +*/ +float PlayersRangeFromSpot (edict_t *spot) +{ + edict_t *player; + float bestplayerdistance; + vec3_t v; + int n; + float playerdistance; + + + bestplayerdistance = 9999999; + + for (n = 1; n <= maxclients->value; n++) + { + player = &g_edicts[n]; + + if (!player->inuse) + continue; + + if (player->health <= 0) + continue; + + VectorSubtract (spot->s.origin, player->s.origin, v); + playerdistance = VectorLength (v); + + if (playerdistance < bestplayerdistance) + bestplayerdistance = playerdistance; + } + + return bestplayerdistance; +} + +/* +================ +SelectRandomDeathmatchSpawnPoint + +go to a random point, but NOT the two points closest +to other players +================ +*/ +edict_t *SelectRandomDeathmatchSpawnPoint (void) +{ + edict_t *spot, *spot1, *spot2; + int count = 0; + int selection; + float range, range1, range2; + + spot = NULL; + range1 = range2 = 99999; + spot1 = spot2 = NULL; + + while ((spot = G_Find (spot, FOFS(classname), "info_player_deathmatch")) != NULL) + { + count++; + range = PlayersRangeFromSpot(spot); + if (range < range1) + { + range1 = range; + spot1 = spot; + } + else if (range < range2) + { + range2 = range; + spot2 = spot; + } + } + + if (!count) + return NULL; + + if (count <= 2) + { + spot1 = spot2 = NULL; + } + else + count -= 2; + + selection = rand() % count; + + spot = NULL; + do + { + spot = G_Find (spot, FOFS(classname), "info_player_deathmatch"); + if (spot == spot1 || spot == spot2) + selection++; + } while(selection--); + + return spot; +} + +/* +================ +SelectFarthestDeathmatchSpawnPoint + +================ +*/ +edict_t *SelectFarthestDeathmatchSpawnPoint (void) +{ + edict_t *bestspot; + float bestdistance, bestplayerdistance; + edict_t *spot; + + + spot = NULL; + bestspot = NULL; + bestdistance = 0; + while ((spot = G_Find (spot, FOFS(classname), "info_player_deathmatch")) != NULL) + { + bestplayerdistance = PlayersRangeFromSpot (spot); + + if (bestplayerdistance > bestdistance) + { + bestspot = spot; + bestdistance = bestplayerdistance; + } + } + + if (bestspot) + { + return bestspot; + } + + // if there is a player just spawned on each and every start spot + // we have no choice to turn one into a telefrag meltdown + spot = G_Find (NULL, FOFS(classname), "info_player_deathmatch"); + + return spot; +} + +edict_t *SelectDeathmatchSpawnPoint (void) +{ + if ( (int)(dmflags->value) & DF_SPAWN_FARTHEST) + return SelectFarthestDeathmatchSpawnPoint (); + else + return SelectRandomDeathmatchSpawnPoint (); +} + + +edict_t *SelectCoopSpawnPoint (edict_t *ent) +{ + int index; + edict_t *spot = NULL; + char *target; + + index = ent->client - game.clients; + + // player 0 starts in normal player spawn point + if (!index) + return NULL; + + spot = NULL; + + // assume there are four coop spots at each spawnpoint + while (1) + { + spot = G_Find (spot, FOFS(classname), "info_player_coop"); + if (!spot) + return NULL; // we didn't have enough... + + target = spot->targetname; + if (!target) + target = ""; + if ( Q_stricmp(game.spawnpoint, target) == 0 ) + { // this is a coop spawn point for one of the clients here + index--; + if (!index) + return spot; // this is it + } + } + + + return spot; +} + + +/* +=========== +SelectSpawnPoint + +Chooses a player start, deathmatch start, coop start, etc +============ +*/ +void SelectSpawnPoint (edict_t *ent, vec3_t origin, vec3_t angles) +{ + edict_t *spot = NULL; + + if (deathmatch->value) +//ZOID + if (ctf->value) + spot = SelectCTFSpawnPoint(ent); + else +//ZOID + spot = SelectDeathmatchSpawnPoint (); + else if (coop->value) + spot = SelectCoopSpawnPoint (ent); + + // find a single player start spot + if (!spot) + { + while ((spot = G_Find (spot, FOFS(classname), "info_player_start")) != NULL) + { + if (!game.spawnpoint[0] && !spot->targetname) + break; + + if (!game.spawnpoint[0] || !spot->targetname) + continue; + + if (Q_stricmp(game.spawnpoint, spot->targetname) == 0) + break; + } + + if (!spot) + { + if (!game.spawnpoint[0]) + { // there wasn't a spawnpoint without a target, so use any + spot = G_Find (spot, FOFS(classname), "info_player_start"); + } + if (!spot) + gi.error ("Couldn't find spawn point %s\n", game.spawnpoint); + } + } + + VectorCopy (spot->s.origin, origin); + origin[2] += 9; + VectorCopy (spot->s.angles, angles); +} + +//====================================================================== + + +void InitBodyQue (void) +{ + int i; + edict_t *ent; + + level.body_que = 0; + for (i=0; iclassname = "bodyque"; + } +} + +void body_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + int n; + + if (self->health < -40) + { + gi.sound (self, CHAN_BODY, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0); + for (n= 0; n < 4; n++) + ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); + self->s.origin[2] -= 48; + ThrowClientHead (self, damage); + self->takedamage = DAMAGE_NO; + } +} + +void CopyToBodyQue (edict_t *ent) +{ + edict_t *body; + + + // grab a body que and cycle to the next one + body = &g_edicts[(int)maxclients->value + level.body_que + 1]; + level.body_que = (level.body_que + 1) % BODY_QUEUE_SIZE; + + // FIXME: send an effect on the removed body + + gi.unlinkentity (ent); + + gi.unlinkentity (body); + body->s = ent->s; + body->s.number = body - g_edicts; + + body->svflags = ent->svflags; + VectorCopy (ent->mins, body->mins); + VectorCopy (ent->maxs, body->maxs); + VectorCopy (ent->absmin, body->absmin); + VectorCopy (ent->absmax, body->absmax); + VectorCopy (ent->size, body->size); + body->solid = ent->solid; + body->clipmask = ent->clipmask; + body->owner = ent->owner; + body->movetype = ent->movetype; + + body->die = body_die; + body->takedamage = DAMAGE_YES; + + gi.linkentity (body); +} + + +void respawn (edict_t *self) +{ + if (deathmatch->value || coop->value) + { + if (self->movetype != MOVETYPE_NOCLIP) + CopyToBodyQue (self); + self->svflags &= ~SVF_NOCLIENT; + PutClientInServer (self); + + // add a teleportation effect + self->s.event = EV_PLAYER_TELEPORT; + + // hold in place briefly + self->client->ps.pmove.pm_flags = PMF_TIME_TELEPORT; + self->client->ps.pmove.pm_time = 14; + + self->client->respawn_time = level.time; + + return; + } + + // restart the entire server + gi.AddCommandString ("menu_loadgame\n"); +} + +//============================================================== + + +/* +=========== +PutClientInServer + +Called when a player connects to a server or respawns in +a deathmatch. +============ +*/ +void PutClientInServer (edict_t *ent) +{ + vec3_t mins = {-16, -16, -24}; + vec3_t maxs = {16, 16, 32}; + int index; + vec3_t spawn_origin, spawn_angles; + gclient_t *client; + int i; + client_persistant_t saved; + client_respawn_t resp; + + // find a spawn point + // do it before setting health back up, so farthest + // ranging doesn't count this client + SelectSpawnPoint (ent, spawn_origin, spawn_angles); + + index = ent-g_edicts-1; + client = ent->client; + + // deathmatch wipes most client data every spawn + if (deathmatch->value) + { + char userinfo[MAX_INFO_STRING]; + + resp = client->resp; + memcpy (userinfo, client->pers.userinfo, sizeof(userinfo)); + InitClientPersistant (client); + ClientUserinfoChanged (ent, userinfo); + } + else if (coop->value) + { + int n; + char userinfo[MAX_INFO_STRING]; + + resp = client->resp; + memcpy (userinfo, client->pers.userinfo, sizeof(userinfo)); + // this is kind of ugly, but it's how we want to handle keys in coop + for (n = 0; n < MAX_ITEMS; n++) + { + if (itemlist[n].flags & IT_KEY) + resp.coop_respawn.inventory[n] = client->pers.inventory[n]; + } + client->pers = resp.coop_respawn; + ClientUserinfoChanged (ent, userinfo); + if (resp.score > client->pers.score) + client->pers.score = resp.score; + } + else + { + memset (&resp, 0, sizeof(resp)); + } + + // clear everything but the persistant data + saved = client->pers; + memset (client, 0, sizeof(*client)); + client->pers = saved; + if (client->pers.health <= 0) + InitClientPersistant(client); + client->resp = resp; + + // copy some data from the client to the entity + FetchClientEntData (ent); + + // clear entity values + ent->groundentity = NULL; + ent->client = &game.clients[index]; + ent->takedamage = DAMAGE_AIM; + ent->movetype = MOVETYPE_WALK; + ent->viewheight = 22; + ent->inuse = true; + ent->classname = "player"; + ent->mass = 200; + ent->solid = SOLID_BBOX; + ent->deadflag = DEAD_NO; + ent->air_finished = level.time + 12; + ent->clipmask = MASK_PLAYERSOLID; + ent->model = "players/male/tris.md2"; + ent->pain = player_pain; + ent->die = player_die; + ent->waterlevel = 0; + ent->watertype = 0; + ent->flags &= ~FL_NO_KNOCKBACK; + ent->svflags &= ~SVF_DEADMONSTER; + + VectorCopy (mins, ent->mins); + VectorCopy (maxs, ent->maxs); + VectorClear (ent->velocity); + + // clear playerstate values + memset (&ent->client->ps, 0, sizeof(client->ps)); + + client->ps.pmove.origin[0] = spawn_origin[0]*8; + client->ps.pmove.origin[1] = spawn_origin[1]*8; + client->ps.pmove.origin[2] = spawn_origin[2]*8; +//ZOID + client->ps.pmove.pm_flags &= ~PMF_NO_PREDICTION; +//ZOID + + if (deathmatch->value && ((int)dmflags->value & DF_FIXED_FOV)) + { + client->ps.fov = 90; + } + else + { + client->ps.fov = atoi(Info_ValueForKey(client->pers.userinfo, "fov")); + if (client->ps.fov < 1) + client->ps.fov = 90; + else if (client->ps.fov > 160) + client->ps.fov = 160; + } + + client->ps.gunindex = gi.modelindex(client->pers.weapon->view_model); + + // clear entity state values + ent->s.effects = 0; + ent->s.skinnum = ent - g_edicts - 1; + ent->s.modelindex = 255; // will use the skin specified model + ent->s.modelindex2 = 255; // custom gun model + // sknum is player num and weapon number + // weapon number will be added in changeweapon + ent->s.skinnum = ent - g_edicts - 1; + + ent->s.frame = 0; + VectorCopy (spawn_origin, ent->s.origin); + ent->s.origin[2] += 1; // make sure off ground + VectorCopy (ent->s.origin, ent->s.old_origin); + + // set the delta angle + for (i=0 ; i<3 ; i++) + client->ps.pmove.delta_angles[i] = ANGLE2SHORT(spawn_angles[i] - client->resp.cmd_angles[i]); + + ent->s.angles[PITCH] = 0; + ent->s.angles[YAW] = spawn_angles[YAW]; + ent->s.angles[ROLL] = 0; + VectorCopy (ent->s.angles, client->ps.viewangles); + VectorCopy (ent->s.angles, client->v_angle); + +//ZOID + if (CTFStartClient(ent)) + return; +//ZOID + + if (!KillBox (ent)) + { // could't spawn in? + } + + gi.linkentity (ent); + + // force the current weapon up + client->newweapon = client->pers.weapon; + ChangeWeapon (ent); +} + +/* +===================== +ClientBeginDeathmatch + +A client has just connected to the server in +deathmatch mode, so clear everything out before starting them. +===================== +*/ +void ClientBeginDeathmatch (edict_t *ent) +{ + G_InitEdict (ent); + + InitClientResp (ent->client); + + // locate ent at a spawn point + PutClientInServer (ent); + + if (level.intermissiontime) + { + MoveClientToIntermission (ent); + } + else + { + // send effect + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + gi.WriteByte (MZ_LOGIN); + gi.multicast (ent->s.origin, MULTICAST_PVS); + } + + gi.bprintf (PRINT_HIGH, "%s entered the game\n", ent->client->pers.netname); + + // make sure all view stuff is valid + ClientEndServerFrame (ent); +} + + +/* +=========== +ClientBegin + +called when a client has finished connecting, and is ready +to be placed into the game. This will happen every level load. +============ +*/ +void ClientBegin (edict_t *ent) +{ + int i; + + ent->client = game.clients + (ent - g_edicts - 1); + + if (deathmatch->value) + { + ClientBeginDeathmatch (ent); + return; + } + + // if there is already a body waiting for us (a loadgame), just + // take it, otherwise spawn one from scratch + if (ent->inuse == true) + { + // the client has cleared the client side viewangles upon + // connecting to the server, which is different than the + // state when the game is saved, so we need to compensate + // with deltaangles + for (i=0 ; i<3 ; i++) + ent->client->ps.pmove.delta_angles[i] = ANGLE2SHORT(ent->client->ps.viewangles[i]); + } + else + { + // a spawn point will completely reinitialize the entity + // except for the persistant data that was initialized at + // ClientConnect() time + G_InitEdict (ent); + ent->classname = "player"; + InitClientResp (ent->client); + PutClientInServer (ent); + } + + if (level.intermissiontime) + { + MoveClientToIntermission (ent); + } + else + { + // send effect if in a multiplayer game + if (game.maxclients > 1) + { + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + gi.WriteByte (MZ_LOGIN); + gi.multicast (ent->s.origin, MULTICAST_PVS); + + gi.bprintf (PRINT_HIGH, "%s entered the game\n", ent->client->pers.netname); + } + } + + // make sure all view stuff is valid + ClientEndServerFrame (ent); +} + +/* +=========== +ClientUserInfoChanged + +called whenever the player updates a userinfo variable. + +The game can override any of the settings in place +(forcing skins or names, etc) before copying it off. +============ +*/ +void ClientUserinfoChanged (edict_t *ent, char *userinfo) +{ + char *s; + int playernum; + + // check for malformed or illegal info strings + if (!Info_Validate(userinfo)) + { + strcpy (userinfo, "\\name\\badinfo\\skin\\male/grunt"); + } + + // set name + s = Info_ValueForKey (userinfo, "name"); + strncpy (ent->client->pers.netname, s, sizeof(ent->client->pers.netname)-1); + + // set skin + s = Info_ValueForKey (userinfo, "skin"); + + playernum = ent-g_edicts-1; + + // combine name and skin into a configstring +//ZOID + if (ctf->value) + CTFAssignSkin(ent, s); + else +//ZOID + gi.configstring (CS_PLAYERSKINS+playernum, va("%s\\%s", ent->client->pers.netname, s) ); + +//ZOID + // set player name field (used in id_state view) + gi.configstring (CS_GENERAL+playernum, ent->client->pers.netname); +//ZOID + + // fov + if (deathmatch->value && ((int)dmflags->value & DF_FIXED_FOV)) + { + ent->client->ps.fov = 90; + } + else + { + ent->client->ps.fov = atoi(Info_ValueForKey(userinfo, "fov")); + if (ent->client->ps.fov < 1) + ent->client->ps.fov = 90; + else if (ent->client->ps.fov > 160) + ent->client->ps.fov = 160; + } + + // handedness + s = Info_ValueForKey (userinfo, "hand"); + if (strlen(s)) + { + ent->client->pers.hand = atoi(s); + } + + // save off the userinfo in case we want to check something later + strncpy (ent->client->pers.userinfo, userinfo, sizeof(ent->client->pers.userinfo)-1); +} + + +/* +=========== +ClientConnect + +Called when a player begins connecting to the server. +The game can refuse entrance to a client by returning false. +If the client is allowed, the connection process will continue +and eventually get to ClientBegin() +Changing levels will NOT cause this to be called again, but +loadgames will. +============ +*/ +qboolean ClientConnect (edict_t *ent, char *userinfo) +{ + char *value; + + // check to see if they are on the banned IP list + value = Info_ValueForKey (userinfo, "ip"); + if (SV_FilterPacket(value)) { + Info_SetValueForKey(userinfo, "rejmsg", "Banned."); + return false; + } + + + // check for a password + value = Info_ValueForKey (userinfo, "password"); + if (*password->string && strcmp(password->string, "none") && + strcmp(password->string, value)) { + Info_SetValueForKey(userinfo, "rejmsg", "Password required or incorrect."); + return false; + } + + // they can connect + ent->client = game.clients + (ent - g_edicts - 1); + + // if there is already a body waiting for us (a loadgame), just + // take it, otherwise spawn one from scratch + if (ent->inuse == false) + { + // clear the respawning variables +//ZOID -- force team join + ent->client->resp.ctf_team = -1; + ent->client->resp.id_state = true; +//ZOID + InitClientResp (ent->client); + if (!game.autosaved || !ent->client->pers.weapon) + InitClientPersistant (ent->client); + } + + ClientUserinfoChanged (ent, userinfo); + + if (game.maxclients > 1) + gi.dprintf ("%s connected\n", ent->client->pers.netname); + + ent->client->pers.connected = true; + return true; +} + +/* +=========== +ClientDisconnect + +Called when a player drops from the server. +Will not be called between levels. +============ +*/ +void ClientDisconnect (edict_t *ent) +{ + int playernum; + + if (!ent->client) + return; + + gi.bprintf (PRINT_HIGH, "%s disconnected\n", ent->client->pers.netname); + +//ZOID + CTFDeadDropFlag(ent); + CTFDeadDropTech(ent); +//ZOID + + // send effect + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + gi.WriteByte (MZ_LOGOUT); + gi.multicast (ent->s.origin, MULTICAST_PVS); + + gi.unlinkentity (ent); + ent->s.modelindex = 0; + ent->solid = SOLID_NOT; + ent->inuse = false; + ent->classname = "disconnected"; + ent->client->pers.connected = false; + + playernum = ent-g_edicts-1; + gi.configstring (CS_PLAYERSKINS+playernum, ""); +} + + +//============================================================== + + +edict_t *pm_passent; + +// pmove doesn't need to know about passent and contentmask +trace_t PM_trace (vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end) +{ + if (pm_passent->health > 0) + return gi.trace (start, mins, maxs, end, pm_passent, MASK_PLAYERSOLID); + else + return gi.trace (start, mins, maxs, end, pm_passent, MASK_DEADSOLID); +} + +unsigned CheckBlock (void *b, int c) +{ + int v,i; + v = 0; + for (i=0 ; is, sizeof(pm->s)); + c2 = CheckBlock (&pm->cmd, sizeof(pm->cmd)); + Com_Printf ("sv %3i:%i %i\n", pm->cmd.impulse, c1, c2); +} + +/* +============== +ClientThink + +This will be called once for each client frame, which will +usually be a couple times for each server frame. +============== +*/ +void ClientThink (edict_t *ent, usercmd_t *ucmd) +{ + gclient_t *client; + edict_t *other; + int i, j; + pmove_t pm; + + level.current_entity = ent; + client = ent->client; + + if (level.intermissiontime) + { + client->ps.pmove.pm_type = PM_FREEZE; + // can exit intermission after five seconds + if (level.time > level.intermissiontime + 5.0 + && (ucmd->buttons & BUTTON_ANY) ) + level.exitintermission = true; + return; + } + + pm_passent = ent; + +//ZOID + if (ent->client->chase_target) { + client->resp.cmd_angles[0] = SHORT2ANGLE(ucmd->angles[0]); + client->resp.cmd_angles[1] = SHORT2ANGLE(ucmd->angles[1]); + client->resp.cmd_angles[2] = SHORT2ANGLE(ucmd->angles[2]); + return; + } +//ZOID + + // set up for pmove + memset (&pm, 0, sizeof(pm)); + + if (ent->movetype == MOVETYPE_NOCLIP) + client->ps.pmove.pm_type = PM_SPECTATOR; + else if (ent->s.modelindex != 255) + client->ps.pmove.pm_type = PM_GIB; + else if (ent->deadflag) + client->ps.pmove.pm_type = PM_DEAD; + else + client->ps.pmove.pm_type = PM_NORMAL; + + client->ps.pmove.gravity = sv_gravity->value; + pm.s = client->ps.pmove; + + for (i=0 ; i<3 ; i++) + { + pm.s.origin[i] = ent->s.origin[i]*8; + pm.s.velocity[i] = ent->velocity[i]*8; + } + + if (memcmp(&client->old_pmove, &pm.s, sizeof(pm.s))) + { + pm.snapinitial = true; +// gi.dprintf ("pmove changed!\n"); + } + + pm.cmd = *ucmd; + + pm.trace = PM_trace; // adds default parms + pm.pointcontents = gi.pointcontents; + + // perform a pmove + gi.Pmove (&pm); + + // save results of pmove + client->ps.pmove = pm.s; + client->old_pmove = pm.s; + + for (i=0 ; i<3 ; i++) + { + ent->s.origin[i] = pm.s.origin[i]*0.125; + ent->velocity[i] = pm.s.velocity[i]*0.125; + } + + VectorCopy (pm.mins, ent->mins); + VectorCopy (pm.maxs, ent->maxs); + + client->resp.cmd_angles[0] = SHORT2ANGLE(ucmd->angles[0]); + client->resp.cmd_angles[1] = SHORT2ANGLE(ucmd->angles[1]); + client->resp.cmd_angles[2] = SHORT2ANGLE(ucmd->angles[2]); + + if (ent->groundentity && !pm.groundentity && (pm.cmd.upmove >= 10) && (pm.waterlevel == 0)) + { + gi.sound(ent, CHAN_VOICE, gi.soundindex("*jump1.wav"), 1, ATTN_NORM, 0); + PlayerNoise(ent, ent->s.origin, PNOISE_SELF); + } + + ent->viewheight = pm.viewheight; + ent->waterlevel = pm.waterlevel; + ent->watertype = pm.watertype; + ent->groundentity = pm.groundentity; + if (pm.groundentity) + ent->groundentity_linkcount = pm.groundentity->linkcount; + + if (ent->deadflag) + { + client->ps.viewangles[ROLL] = 40; + client->ps.viewangles[PITCH] = -15; + client->ps.viewangles[YAW] = client->killer_yaw; + } + else + { + VectorCopy (pm.viewangles, client->v_angle); + VectorCopy (pm.viewangles, client->ps.viewangles); + } + +//ZOID + if (client->ctf_grapple) + CTFGrapplePull(client->ctf_grapple); +//ZOID + + gi.linkentity (ent); + + if (ent->movetype != MOVETYPE_NOCLIP) + G_TouchTriggers (ent); + + // touch other objects + for (i=0 ; itouch) + continue; + other->touch (other, ent, NULL, NULL); + } + + + client->oldbuttons = client->buttons; + client->buttons = ucmd->buttons; + client->latched_buttons |= client->buttons & ~client->oldbuttons; + + // save light level the player is standing on for + // monster sighting AI + ent->light_level = ucmd->lightlevel; + + // fire weapon from final position if needed + if (client->latched_buttons & BUTTON_ATTACK +//ZOID + && ent->movetype != MOVETYPE_NOCLIP +//ZOID + ) + { + if (!client->weapon_thunk) + { + client->weapon_thunk = true; + Think_Weapon (ent); + } + } + +//ZOID +//regen tech + CTFApplyRegeneration(ent); +//ZOID + +//ZOID + for (i = 1; i <= maxclients->value; i++) { + other = g_edicts + i; + if (other->inuse && other->client->chase_target == ent) + UpdateChaseCam(other); + } + + if (client->menudirty && client->menutime <= level.time) { + PMenu_Do_Update(ent); + gi.unicast (ent, true); + client->menutime = level.time; + client->menudirty = false; + } +//ZOID +} + + +/* +============== +ClientBeginServerFrame + +This will be called once for each server frame, before running +any other entities in the world. +============== +*/ +void ClientBeginServerFrame (edict_t *ent) +{ + gclient_t *client; + int buttonMask; + + if (level.intermissiontime) + return; + + client = ent->client; + + // run weapon animations if it hasn't been done by a ucmd_t + if (!client->weapon_thunk +//ZOID + && ent->movetype != MOVETYPE_NOCLIP +//ZOID + ) + Think_Weapon (ent); + else + client->weapon_thunk = false; + + if (ent->deadflag) + { + // wait for any button just going down + if ( level.time > client->respawn_time) + { + // in deathmatch, only wait for attack button + if (deathmatch->value) + buttonMask = BUTTON_ATTACK; + else + buttonMask = -1; + + if ( ( client->latched_buttons & buttonMask ) || + (deathmatch->value && ((int)dmflags->value & DF_FORCE_RESPAWN) ) || + CTFMatchOn()) + { + respawn(ent); + client->latched_buttons = 0; + } + } + return; + } + + // add player trail so monsters can follow + if (!deathmatch->value) + if (!visible (ent, PlayerTrail_LastSpot() ) ) + PlayerTrail_Add (ent->s.old_origin); + + client->latched_buttons = 0; +} diff --git a/original/ctf/p_hud.c b/original/ctf/p_hud.c new file mode 100644 index 0000000..fdccf2a --- /dev/null +++ b/original/ctf/p_hud.c @@ -0,0 +1,544 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +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. + +*/ +#include "g_local.h" + + + +/* +====================================================================== + +INTERMISSION + +====================================================================== +*/ + +void MoveClientToIntermission (edict_t *ent) +{ + if (deathmatch->value || coop->value) + ent->client->showscores = true; + VectorCopy (level.intermission_origin, ent->s.origin); + ent->client->ps.pmove.origin[0] = level.intermission_origin[0]*8; + ent->client->ps.pmove.origin[1] = level.intermission_origin[1]*8; + ent->client->ps.pmove.origin[2] = level.intermission_origin[2]*8; + VectorCopy (level.intermission_angle, ent->client->ps.viewangles); + ent->client->ps.pmove.pm_type = PM_FREEZE; + ent->client->ps.gunindex = 0; + ent->client->ps.blend[3] = 0; + ent->client->ps.rdflags &= ~RDF_UNDERWATER; + + // clean up powerup info + ent->client->quad_framenum = 0; + ent->client->invincible_framenum = 0; + ent->client->breather_framenum = 0; + ent->client->enviro_framenum = 0; + ent->client->grenade_blew_up = false; + ent->client->grenade_time = 0; + + ent->viewheight = 0; + ent->s.modelindex = 0; + ent->s.modelindex2 = 0; + ent->s.modelindex3 = 0; + ent->s.modelindex = 0; + ent->s.effects = 0; + ent->s.sound = 0; + ent->solid = SOLID_NOT; + + // add the layout + + if (deathmatch->value || coop->value) + { + DeathmatchScoreboardMessage (ent, NULL); + gi.unicast (ent, true); + } + +} + +void BeginIntermission (edict_t *targ) +{ + int i, n; + edict_t *ent, *client; + + if (level.intermissiontime) + return; // allready activated + +//ZOID + if (deathmatch->value && ctf->value) + CTFCalcScores(); +//ZOID + + game.autosaved = false; + + // respawn any dead clients + for (i=0 ; ivalue ; i++) + { + client = g_edicts + 1 + i; + if (!client->inuse) + continue; + if (client->health <= 0) + respawn(client); + } + + level.intermissiontime = level.time; + level.changemap = targ->map; + + if (strstr(level.changemap, "*")) + { + if (coop->value) + { + for (i=0 ; ivalue ; i++) + { + client = g_edicts + 1 + i; + if (!client->inuse) + continue; + // strip players of all keys between units + for (n = 0; n < MAX_ITEMS; n++) + { + if (itemlist[n].flags & IT_KEY) + client->client->pers.inventory[n] = 0; + } + } + } + } + else + { + if (!deathmatch->value) + { + level.exitintermission = 1; // go immediately to the next level + return; + } + } + + level.exitintermission = 0; + + // find an intermission spot + ent = G_Find (NULL, FOFS(classname), "info_player_intermission"); + if (!ent) + { // the map creator forgot to put in an intermission point... + ent = G_Find (NULL, FOFS(classname), "info_player_start"); + if (!ent) + ent = G_Find (NULL, FOFS(classname), "info_player_deathmatch"); + } + else + { // chose one of four spots + i = rand() & 3; + while (i--) + { + ent = G_Find (ent, FOFS(classname), "info_player_intermission"); + if (!ent) // wrap around the list + ent = G_Find (ent, FOFS(classname), "info_player_intermission"); + } + } + + VectorCopy (ent->s.origin, level.intermission_origin); + VectorCopy (ent->s.angles, level.intermission_angle); + + // move all clients to the intermission point + for (i=0 ; ivalue ; i++) + { + client = g_edicts + 1 + i; + if (!client->inuse) + continue; + MoveClientToIntermission (client); + } +} + + +/* +================== +DeathmatchScoreboardMessage + +================== +*/ +void DeathmatchScoreboardMessage (edict_t *ent, edict_t *killer) +{ + char entry[1024]; + char string[1400]; + int stringlength; + int i, j, k; + int sorted[MAX_CLIENTS]; + int sortedscores[MAX_CLIENTS]; + int score, total; + int picnum; + int x, y; + gclient_t *cl; + edict_t *cl_ent; + char *tag; + +//ZOID + if (ctf->value) { + CTFScoreboardMessage (ent, killer); + return; + } +//ZOID + + // sort the clients by score + total = 0; + for (i=0 ; iinuse) + continue; + score = game.clients[i].resp.score; + for (j=0 ; j sortedscores[j]) + break; + } + for (k=total ; k>j ; k--) + { + sorted[k] = sorted[k-1]; + sortedscores[k] = sortedscores[k-1]; + } + sorted[j] = i; + sortedscores[j] = score; + total++; + } + + // print level name and exit rules + string[0] = 0; + + stringlength = strlen(string); + + // add the clients in sorted order + if (total > 12) + total = 12; + + for (i=0 ; i=6) ? 160 : 0; + y = 32 + 32 * (i%6); + + // add a dogtag + if (cl_ent == ent) + tag = "tag1"; + else if (cl_ent == killer) + tag = "tag2"; + else + tag = NULL; + if (tag) + { + Com_sprintf (entry, sizeof(entry), + "xv %i yv %i picn %s ",x+32, y, tag); + j = strlen(entry); + if (stringlength + j > 1024) + break; + strcpy (string + stringlength, entry); + stringlength += j; + } + + // send the layout + Com_sprintf (entry, sizeof(entry), + "client %i %i %i %i %i %i ", + x, y, sorted[i], cl->resp.score, cl->ping, (level.framenum - cl->resp.enterframe)/600); + j = strlen(entry); + if (stringlength + j > 1024) + break; + strcpy (string + stringlength, entry); + stringlength += j; + } + + gi.WriteByte (svc_layout); + gi.WriteString (string); +} + + +/* +================== +DeathmatchScoreboard + +Draw instead of help message. +Note that it isn't that hard to overflow the 1400 byte message limit! +================== +*/ +void DeathmatchScoreboard (edict_t *ent) +{ + DeathmatchScoreboardMessage (ent, ent->enemy); + gi.unicast (ent, true); +} + + +/* +================== +Cmd_Score_f + +Display the scoreboard +================== +*/ +void Cmd_Score_f (edict_t *ent) +{ + ent->client->showinventory = false; + ent->client->showhelp = false; +//ZOID + if (ent->client->menu) + PMenu_Close(ent); +//ZOID + + if (!deathmatch->value && !coop->value) + return; + + if (ent->client->showscores) + { + ent->client->showscores = false; + ent->client->update_chase = true; + return; + } + + ent->client->showscores = true; + + DeathmatchScoreboard (ent); +} + + +/* +================== +HelpComputer + +Draw help computer. +================== +*/ +void HelpComputer (edict_t *ent) +{ + char string[1024]; + char *sk; + + if (skill->value == 0) + sk = "easy"; + else if (skill->value == 1) + sk = "medium"; + else if (skill->value == 2) + sk = "hard"; + else + sk = "hard+"; + + // send the layout + Com_sprintf (string, sizeof(string), + "xv 32 yv 8 picn help " // background + "xv 202 yv 12 string2 \"%s\" " // skill + "xv 0 yv 24 cstring2 \"%s\" " // level name + "xv 0 yv 54 cstring2 \"%s\" " // help 1 + "xv 0 yv 110 cstring2 \"%s\" " // help 2 + "xv 50 yv 164 string2 \" kills goals secrets\" " + "xv 50 yv 172 string2 \"%3i/%3i %i/%i %i/%i\" ", + sk, + level.level_name, + game.helpmessage1, + game.helpmessage2, + level.killed_monsters, level.total_monsters, + level.found_goals, level.total_goals, + level.found_secrets, level.total_secrets); + + gi.WriteByte (svc_layout); + gi.WriteString (string); + gi.unicast (ent, true); +} + + +/* +================== +Cmd_Help_f + +Display the current help message +================== +*/ +void Cmd_Help_f (edict_t *ent) +{ + // this is for backwards compatability + if (deathmatch->value) + { + Cmd_Score_f (ent); + return; + } + + ent->client->showinventory = false; + ent->client->showscores = false; + + if (ent->client->showhelp && (ent->client->resp.game_helpchanged == game.helpchanged)) + { + ent->client->showhelp = false; + return; + } + + ent->client->showhelp = true; + ent->client->resp.helpchanged = 0; + HelpComputer (ent); +} + + +//======================================================================= + +/* +=============== +G_SetStats +=============== +*/ +void G_SetStats (edict_t *ent) +{ + gitem_t *item; + int index, cells; + int power_armor_type; + + // + // health + // + ent->client->ps.stats[STAT_HEALTH_ICON] = level.pic_health; + ent->client->ps.stats[STAT_HEALTH] = ent->health; + + // + // ammo + // + if (!ent->client->ammo_index /* || !ent->client->pers.inventory[ent->client->ammo_index] */) + { + ent->client->ps.stats[STAT_AMMO_ICON] = 0; + ent->client->ps.stats[STAT_AMMO] = 0; + } + else + { + item = &itemlist[ent->client->ammo_index]; + ent->client->ps.stats[STAT_AMMO_ICON] = gi.imageindex (item->icon); + ent->client->ps.stats[STAT_AMMO] = ent->client->pers.inventory[ent->client->ammo_index]; + } + + // + // armor + // + power_armor_type = PowerArmorType (ent); + if (power_armor_type) + { + cells = ent->client->pers.inventory[ITEM_INDEX(FindItem ("cells"))]; + if (cells == 0) + { // ran out of cells for power armor + ent->flags &= ~FL_POWER_ARMOR; + gi.sound(ent, CHAN_ITEM, gi.soundindex("misc/power2.wav"), 1, ATTN_NORM, 0); + power_armor_type = 0;; + } + } + + index = ArmorIndex (ent); + if (power_armor_type && (!index || (level.framenum & 8) ) ) + { // flash between power armor and other armor icon + ent->client->ps.stats[STAT_ARMOR_ICON] = gi.imageindex ("i_powershield"); + ent->client->ps.stats[STAT_ARMOR] = cells; + } + else if (index) + { + item = GetItemByIndex (index); + ent->client->ps.stats[STAT_ARMOR_ICON] = gi.imageindex (item->icon); + ent->client->ps.stats[STAT_ARMOR] = ent->client->pers.inventory[index]; + } + else + { + ent->client->ps.stats[STAT_ARMOR_ICON] = 0; + ent->client->ps.stats[STAT_ARMOR] = 0; + } + + // + // pickup message + // + if (level.time > ent->client->pickup_msg_time) + { + ent->client->ps.stats[STAT_PICKUP_ICON] = 0; + ent->client->ps.stats[STAT_PICKUP_STRING] = 0; + } + + // + // timers + // + if (ent->client->quad_framenum > level.framenum) + { + ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex ("p_quad"); + ent->client->ps.stats[STAT_TIMER] = (ent->client->quad_framenum - level.framenum)/10; + } + else if (ent->client->invincible_framenum > level.framenum) + { + ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex ("p_invulnerability"); + ent->client->ps.stats[STAT_TIMER] = (ent->client->invincible_framenum - level.framenum)/10; + } + else if (ent->client->enviro_framenum > level.framenum) + { + ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex ("p_envirosuit"); + ent->client->ps.stats[STAT_TIMER] = (ent->client->enviro_framenum - level.framenum)/10; + } + else if (ent->client->breather_framenum > level.framenum) + { + ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex ("p_rebreather"); + ent->client->ps.stats[STAT_TIMER] = (ent->client->breather_framenum - level.framenum)/10; + } + else + { + ent->client->ps.stats[STAT_TIMER_ICON] = 0; + ent->client->ps.stats[STAT_TIMER] = 0; + } + + // + // selected item + // + if (ent->client->pers.selected_item == -1) + ent->client->ps.stats[STAT_SELECTED_ICON] = 0; + else + ent->client->ps.stats[STAT_SELECTED_ICON] = gi.imageindex (itemlist[ent->client->pers.selected_item].icon); + + ent->client->ps.stats[STAT_SELECTED_ITEM] = ent->client->pers.selected_item; + + // + // layouts + // + ent->client->ps.stats[STAT_LAYOUTS] = 0; + + if (deathmatch->value) + { + if (ent->client->pers.health <= 0 || level.intermissiontime + || ent->client->showscores) + ent->client->ps.stats[STAT_LAYOUTS] |= 1; + if (ent->client->showinventory && ent->client->pers.health > 0) + ent->client->ps.stats[STAT_LAYOUTS] |= 2; + } + else + { + if (ent->client->showscores || ent->client->showhelp) + ent->client->ps.stats[STAT_LAYOUTS] |= 1; + if (ent->client->showinventory && ent->client->pers.health > 0) + ent->client->ps.stats[STAT_LAYOUTS] |= 2; + } + + // + // frags + // + ent->client->ps.stats[STAT_FRAGS] = ent->client->resp.score; + + // + // help icon / current weapon if not shown + // + if (ent->client->resp.helpchanged && (level.framenum&8) ) + ent->client->ps.stats[STAT_HELPICON] = gi.imageindex ("i_help"); + else if ( (ent->client->pers.hand == CENTER_HANDED || ent->client->ps.fov > 91) + && ent->client->pers.weapon) + ent->client->ps.stats[STAT_HELPICON] = gi.imageindex (ent->client->pers.weapon->icon); + else + ent->client->ps.stats[STAT_HELPICON] = 0; + +//ZOID + SetCTFStats(ent); +//ZOID +} + diff --git a/original/ctf/p_menu.c b/original/ctf/p_menu.c new file mode 100644 index 0000000..fe61e80 --- /dev/null +++ b/original/ctf/p_menu.c @@ -0,0 +1,256 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +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. + +*/ +#include "g_local.h" + +// Note that the pmenu entries are duplicated +// this is so that a static set of pmenu entries can be used +// for multiple clients and changed without interference +// note that arg will be freed when the menu is closed, it must be allocated memory +pmenuhnd_t *PMenu_Open(edict_t *ent, pmenu_t *entries, int cur, int num, void *arg) +{ + pmenuhnd_t *hnd; + pmenu_t *p; + int i; + + if (!ent->client) + return NULL; + + if (ent->client->menu) { + gi.dprintf("warning, ent already has a menu\n"); + PMenu_Close(ent); + } + + hnd = malloc(sizeof(*hnd)); + + hnd->arg = arg; + hnd->entries = malloc(sizeof(pmenu_t) * num); + memcpy(hnd->entries, entries, sizeof(pmenu_t) * num); + // duplicate the strings since they may be from static memory + for (i = 0; i < num; i++) + if (entries[i].text) + hnd->entries[i].text = strdup(entries[i].text); + + hnd->num = num; + + if (cur < 0 || !entries[cur].SelectFunc) { + for (i = 0, p = entries; i < num; i++, p++) + if (p->SelectFunc) + break; + } else + i = cur; + + if (i >= num) + hnd->cur = -1; + else + hnd->cur = i; + + ent->client->showscores = true; + ent->client->inmenu = true; + ent->client->menu = hnd; + + PMenu_Do_Update(ent); + gi.unicast (ent, true); + + return hnd; +} + +void PMenu_Close(edict_t *ent) +{ + int i; + pmenuhnd_t *hnd; + + if (!ent->client->menu) + return; + + hnd = ent->client->menu; + for (i = 0; i < hnd->num; i++) + if (hnd->entries[i].text) + free(hnd->entries[i].text); + free(hnd->entries); + if (hnd->arg) + free(hnd->arg); + free(hnd); + ent->client->menu = NULL; + ent->client->showscores = false; +} + +// only use on pmenu's that have been called with PMenu_Open +void PMenu_UpdateEntry(pmenu_t *entry, const char *text, int align, SelectFunc_t SelectFunc) +{ + if (entry->text) + free(entry->text); + entry->text = strdup(text); + entry->align = align; + entry->SelectFunc = SelectFunc; +} + +void PMenu_Do_Update(edict_t *ent) +{ + char string[1400]; + int i; + pmenu_t *p; + int x; + pmenuhnd_t *hnd; + char *t; + qboolean alt = false; + + if (!ent->client->menu) { + gi.dprintf("warning: ent has no menu\n"); + return; + } + + hnd = ent->client->menu; + + strcpy(string, "xv 32 yv 8 picn inventory "); + + for (i = 0, p = hnd->entries; i < hnd->num; i++, p++) { + if (!p->text || !*(p->text)) + continue; // blank line + t = p->text; + if (*t == '*') { + alt = true; + t++; + } + sprintf(string + strlen(string), "yv %d ", 32 + i * 8); + if (p->align == PMENU_ALIGN_CENTER) + x = 196/2 - strlen(t)*4 + 64; + else if (p->align == PMENU_ALIGN_RIGHT) + x = 64 + (196 - strlen(t)*8); + else + x = 64; + + sprintf(string + strlen(string), "xv %d ", + x - ((hnd->cur == i) ? 8 : 0)); + + if (hnd->cur == i) + sprintf(string + strlen(string), "string2 \"\x0d%s\" ", t); + else if (alt) + sprintf(string + strlen(string), "string2 \"%s\" ", t); + else + sprintf(string + strlen(string), "string \"%s\" ", t); + alt = false; + } + + gi.WriteByte (svc_layout); + gi.WriteString (string); +} + +void PMenu_Update(edict_t *ent) +{ + if (!ent->client->menu) { + gi.dprintf("warning: ent has no menu\n"); + return; + } + + if (level.time - ent->client->menutime >= 1.0) { + // been a second or more since last update, update now + PMenu_Do_Update(ent); + gi.unicast (ent, true); + ent->client->menutime = level.time; + ent->client->menudirty = false; + } + ent->client->menutime = level.time + 0.2; + ent->client->menudirty = true; +} + +void PMenu_Next(edict_t *ent) +{ + pmenuhnd_t *hnd; + int i; + pmenu_t *p; + + if (!ent->client->menu) { + gi.dprintf("warning: ent has no menu\n"); + return; + } + + hnd = ent->client->menu; + + if (hnd->cur < 0) + return; // no selectable entries + + i = hnd->cur; + p = hnd->entries + hnd->cur; + do { + i++, p++; + if (i == hnd->num) + i = 0, p = hnd->entries; + if (p->SelectFunc) + break; + } while (i != hnd->cur); + + hnd->cur = i; + + PMenu_Update(ent); +} + +void PMenu_Prev(edict_t *ent) +{ + pmenuhnd_t *hnd; + int i; + pmenu_t *p; + + if (!ent->client->menu) { + gi.dprintf("warning: ent has no menu\n"); + return; + } + + hnd = ent->client->menu; + + if (hnd->cur < 0) + return; // no selectable entries + + i = hnd->cur; + p = hnd->entries + hnd->cur; + do { + if (i == 0) { + i = hnd->num - 1; + p = hnd->entries + i; + } else + i--, p--; + if (p->SelectFunc) + break; + } while (i != hnd->cur); + + hnd->cur = i; + + PMenu_Update(ent); +} + +void PMenu_Select(edict_t *ent) +{ + pmenuhnd_t *hnd; + pmenu_t *p; + + if (!ent->client->menu) { + gi.dprintf("warning: ent has no menu\n"); + return; + } + + hnd = ent->client->menu; + + if (hnd->cur < 0) + return; // no selectable entries + + p = hnd->entries + hnd->cur; + + if (p->SelectFunc) + p->SelectFunc(ent, hnd); +} diff --git a/original/ctf/p_menu.h b/original/ctf/p_menu.h new file mode 100644 index 0000000..d16cc15 --- /dev/null +++ b/original/ctf/p_menu.h @@ -0,0 +1,49 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +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. + +*/ + +enum { + PMENU_ALIGN_LEFT, + PMENU_ALIGN_CENTER, + PMENU_ALIGN_RIGHT +}; + +typedef struct pmenuhnd_s { + struct pmenu_s *entries; + int cur; + int num; + void *arg; +} pmenuhnd_t; + +typedef void (*SelectFunc_t)(edict_t *ent, pmenuhnd_t *hnd); + +typedef struct pmenu_s { + char *text; + int align; + SelectFunc_t SelectFunc; +} pmenu_t; + +pmenuhnd_t *PMenu_Open(edict_t *ent, pmenu_t *entries, int cur, int num, void *arg); +void PMenu_Close(edict_t *ent); +void PMenu_UpdateEntry(pmenu_t *entry, const char *text, int align, SelectFunc_t SelectFunc); +void PMenu_Do_Update(edict_t *ent); +void PMenu_Update(edict_t *ent); +void PMenu_Next(edict_t *ent); +void PMenu_Prev(edict_t *ent); +void PMenu_Select(edict_t *ent); diff --git a/original/ctf/p_trail.c b/original/ctf/p_trail.c new file mode 100644 index 0000000..d968682 --- /dev/null +++ b/original/ctf/p_trail.c @@ -0,0 +1,146 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +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. + +*/ +#include "g_local.h" + + +/* +============================================================================== + +PLAYER TRAIL + +============================================================================== + +This is a circular list containing the a list of points of where +the player has been recently. It is used by monsters for pursuit. + +.origin the spot +.owner forward link +.aiment backward link +*/ + + +#define TRAIL_LENGTH 8 + +edict_t *trail[TRAIL_LENGTH]; +int trail_head; +qboolean trail_active = false; + +#define NEXT(n) (((n) + 1) & (TRAIL_LENGTH - 1)) +#define PREV(n) (((n) - 1) & (TRAIL_LENGTH - 1)) + + +void PlayerTrail_Init (void) +{ + int n; + + if (deathmatch->value /* FIXME || coop */) + return; + + for (n = 0; n < TRAIL_LENGTH; n++) + { + trail[n] = G_Spawn(); + trail[n]->classname = "player_trail"; + } + + trail_head = 0; + trail_active = true; +} + + +void PlayerTrail_Add (vec3_t spot) +{ + vec3_t temp; + + if (!trail_active) + return; + + VectorCopy (spot, trail[trail_head]->s.origin); + + trail[trail_head]->timestamp = level.time; + + VectorSubtract (spot, trail[PREV(trail_head)]->s.origin, temp); + trail[trail_head]->s.angles[1] = vectoyaw (temp); + + trail_head = NEXT(trail_head); +} + + +void PlayerTrail_New (vec3_t spot) +{ + if (!trail_active) + return; + + PlayerTrail_Init (); + PlayerTrail_Add (spot); +} + + +edict_t *PlayerTrail_PickFirst (edict_t *self) +{ + int marker; + int n; + + if (!trail_active) + return NULL; + + for (marker = trail_head, n = TRAIL_LENGTH; n; n--) + { + if(trail[marker]->timestamp <= self->monsterinfo.trail_time) + marker = NEXT(marker); + else + break; + } + + if (visible(self, trail[marker])) + { + return trail[marker]; + } + + if (visible(self, trail[PREV(marker)])) + { + return trail[PREV(marker)]; + } + + return trail[marker]; +} + +edict_t *PlayerTrail_PickNext (edict_t *self) +{ + int marker; + int n; + + if (!trail_active) + return NULL; + + for (marker = trail_head, n = TRAIL_LENGTH; n; n--) + { + if(trail[marker]->timestamp <= self->monsterinfo.trail_time) + marker = NEXT(marker); + else + break; + } + + return trail[marker]; +} + +edict_t *PlayerTrail_LastSpot (void) +{ + return trail[PREV(trail_head)]; +} diff --git a/original/ctf/p_view.c b/original/ctf/p_view.c new file mode 100644 index 0000000..e7412e7 --- /dev/null +++ b/original/ctf/p_view.c @@ -0,0 +1,1129 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +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. + +*/ + +#include "g_local.h" +#include "m_player.h" + + + +static edict_t *current_player; +static gclient_t *current_client; + +static vec3_t forward, right, up; +float xyspeed; + +float bobmove; +int bobcycle; // odd cycles are right foot going forward +float bobfracsin; // sin(bobfrac*M_PI) + +/* +=============== +SV_CalcRoll + +=============== +*/ +float SV_CalcRoll (vec3_t angles, vec3_t velocity) +{ + float sign; + float side; + float value; + + side = DotProduct (velocity, right); + sign = side < 0 ? -1 : 1; + side = fabs(side); + + value = sv_rollangle->value; + + if (side < sv_rollspeed->value) + side = side * value / sv_rollspeed->value; + else + side = value; + + return side*sign; + +} + + +/* +=============== +P_DamageFeedback + +Handles color blends and view kicks +=============== +*/ +void P_DamageFeedback (edict_t *player) +{ + gclient_t *client; + float side; + float realcount, count, kick; + vec3_t v; + int r, l; + static vec3_t power_color = {0.0, 1.0, 0.0}; + static vec3_t acolor = {1.0, 1.0, 1.0}; + static vec3_t bcolor = {1.0, 0.0, 0.0}; + + client = player->client; + + // flash the backgrounds behind the status numbers + client->ps.stats[STAT_FLASHES] = 0; + if (client->damage_blood) + client->ps.stats[STAT_FLASHES] |= 1; + if (client->damage_armor && !(player->flags & FL_GODMODE) && (client->invincible_framenum <= level.framenum)) + client->ps.stats[STAT_FLASHES] |= 2; + + // total points of damage shot at the player this frame + count = (client->damage_blood + client->damage_armor + client->damage_parmor); + if (count == 0) + return; // didn't take any damage + + // start a pain animation if still in the player model + if (client->anim_priority < ANIM_PAIN && player->s.modelindex == 255) + { + static int i; + + client->anim_priority = ANIM_PAIN; + if (client->ps.pmove.pm_flags & PMF_DUCKED) + { + player->s.frame = FRAME_crpain1-1; + client->anim_end = FRAME_crpain4; + } + else + { + i = (i+1)%3; + switch (i) + { + case 0: + player->s.frame = FRAME_pain101-1; + client->anim_end = FRAME_pain104; + break; + case 1: + player->s.frame = FRAME_pain201-1; + client->anim_end = FRAME_pain204; + break; + case 2: + player->s.frame = FRAME_pain301-1; + client->anim_end = FRAME_pain304; + break; + } + } + } + + realcount = count; + if (count < 10) + count = 10; // allways make a visible effect + + // play an apropriate pain sound + if ((level.time > player->pain_debounce_time) && !(player->flags & FL_GODMODE) && (client->invincible_framenum <= level.framenum)) + { + r = 1 + (rand()&1); + player->pain_debounce_time = level.time + 0.7; + if (player->health < 25) + l = 25; + else if (player->health < 50) + l = 50; + else if (player->health < 75) + l = 75; + else + l = 100; + gi.sound (player, CHAN_VOICE, gi.soundindex(va("*pain%i_%i.wav", l, r)), 1, ATTN_NORM, 0); + } + + // the total alpha of the blend is allways proportional to count + if (client->damage_alpha < 0) + client->damage_alpha = 0; + client->damage_alpha += count*0.01; + if (client->damage_alpha < 0.2) + client->damage_alpha = 0.2; + if (client->damage_alpha > 0.6) + client->damage_alpha = 0.6; // don't go too saturated + + // the color of the blend will vary based on how much was absorbed + // by different armors + VectorClear (v); + if (client->damage_parmor) + VectorMA (v, (float)client->damage_parmor/realcount, power_color, v); + if (client->damage_armor) + VectorMA (v, (float)client->damage_armor/realcount, acolor, v); + if (client->damage_blood) + VectorMA (v, (float)client->damage_blood/realcount, bcolor, v); + VectorCopy (v, client->damage_blend); + + + // + // calculate view angle kicks + // + kick = abs(client->damage_knockback); + if (kick && player->health > 0) // kick of 0 means no view adjust at all + { + kick = kick * 100 / player->health; + + if (kick < count*0.5) + kick = count*0.5; + if (kick > 50) + kick = 50; + + VectorSubtract (client->damage_from, player->s.origin, v); + VectorNormalize (v); + + side = DotProduct (v, right); + client->v_dmg_roll = kick*side*0.3; + + side = -DotProduct (v, forward); + client->v_dmg_pitch = kick*side*0.3; + + client->v_dmg_time = level.time + DAMAGE_TIME; + } + + // + // clear totals + // + client->damage_blood = 0; + client->damage_armor = 0; + client->damage_parmor = 0; + client->damage_knockback = 0; +} + + + + +/* +=============== +SV_CalcViewOffset + +Auto pitching on slopes? + + fall from 128: 400 = 160000 + fall from 256: 580 = 336400 + fall from 384: 720 = 518400 + fall from 512: 800 = 640000 + fall from 640: 960 = + + damage = deltavelocity*deltavelocity * 0.0001 + +=============== +*/ +void SV_CalcViewOffset (edict_t *ent) +{ + float *angles; + float bob; + float ratio; + float delta; + vec3_t v; + + +//=================================== + + // base angles + angles = ent->client->ps.kick_angles; + + // if dead, fix the angle and don't add any kick + if (ent->deadflag) + { + VectorClear (angles); + + ent->client->ps.viewangles[ROLL] = 40; + ent->client->ps.viewangles[PITCH] = -15; + ent->client->ps.viewangles[YAW] = ent->client->killer_yaw; + } + else + { + // add angles based on weapon kick + + VectorCopy (ent->client->kick_angles, angles); + + // add angles based on damage kick + + ratio = (ent->client->v_dmg_time - level.time) / DAMAGE_TIME; + if (ratio < 0) + { + ratio = 0; + ent->client->v_dmg_pitch = 0; + ent->client->v_dmg_roll = 0; + } + angles[PITCH] += ratio * ent->client->v_dmg_pitch; + angles[ROLL] += ratio * ent->client->v_dmg_roll; + + // add pitch based on fall kick + + ratio = (ent->client->fall_time - level.time) / FALL_TIME; + if (ratio < 0) + ratio = 0; + angles[PITCH] += ratio * ent->client->fall_value; + + // add angles based on velocity + + delta = DotProduct (ent->velocity, forward); + angles[PITCH] += delta*run_pitch->value; + + delta = DotProduct (ent->velocity, right); + angles[ROLL] += delta*run_roll->value; + + // add angles based on bob + + delta = bobfracsin * bob_pitch->value * xyspeed; + if (ent->client->ps.pmove.pm_flags & PMF_DUCKED) + delta *= 6; // crouching + angles[PITCH] += delta; + delta = bobfracsin * bob_roll->value * xyspeed; + if (ent->client->ps.pmove.pm_flags & PMF_DUCKED) + delta *= 6; // crouching + if (bobcycle & 1) + delta = -delta; + angles[ROLL] += delta; + } + +//=================================== + + // base origin + + VectorClear (v); + + // add view height + + v[2] += ent->viewheight; + + // add fall height + + ratio = (ent->client->fall_time - level.time) / FALL_TIME; + if (ratio < 0) + ratio = 0; + v[2] -= ratio * ent->client->fall_value * 0.4; + + // add bob height + + bob = bobfracsin * xyspeed * bob_up->value; + if (bob > 6) + bob = 6; + //gi.DebugGraph (bob *2, 255); + v[2] += bob; + + // add kick offset + + VectorAdd (v, ent->client->kick_origin, v); + + // absolutely bound offsets + // so the view can never be outside the player box + + if (v[0] < -14) + v[0] = -14; + else if (v[0] > 14) + v[0] = 14; + if (v[1] < -14) + v[1] = -14; + else if (v[1] > 14) + v[1] = 14; + if (v[2] < -22) + v[2] = -22; + else if (v[2] > 30) + v[2] = 30; + + VectorCopy (v, ent->client->ps.viewoffset); +} + +/* +============== +SV_CalcGunOffset +============== +*/ +void SV_CalcGunOffset (edict_t *ent) +{ + int i; + float delta; + + // gun angles from bobbing + ent->client->ps.gunangles[ROLL] = xyspeed * bobfracsin * 0.005; + ent->client->ps.gunangles[YAW] = xyspeed * bobfracsin * 0.01; + if (bobcycle & 1) + { + ent->client->ps.gunangles[ROLL] = -ent->client->ps.gunangles[ROLL]; + ent->client->ps.gunangles[YAW] = -ent->client->ps.gunangles[YAW]; + } + + ent->client->ps.gunangles[PITCH] = xyspeed * bobfracsin * 0.005; + + // gun angles from delta movement + for (i=0 ; i<3 ; i++) + { + delta = ent->client->oldviewangles[i] - ent->client->ps.viewangles[i]; + if (delta > 180) + delta -= 360; + if (delta < -180) + delta += 360; + if (delta > 45) + delta = 45; + if (delta < -45) + delta = -45; + if (i == YAW) + ent->client->ps.gunangles[ROLL] += 0.1*delta; + ent->client->ps.gunangles[i] += 0.2 * delta; + } + + // gun height + VectorClear (ent->client->ps.gunoffset); +// ent->ps->gunorigin[2] += bob; + + // gun_x / gun_y / gun_z are development tools + for (i=0 ; i<3 ; i++) + { + ent->client->ps.gunoffset[i] += forward[i]*(gun_y->value); + ent->client->ps.gunoffset[i] += right[i]*gun_x->value; + ent->client->ps.gunoffset[i] += up[i]* (-gun_z->value); + } +} + + +/* +============= +SV_AddBlend +============= +*/ +void SV_AddBlend (float r, float g, float b, float a, float *v_blend) +{ + float a2, a3; + + if (a <= 0) + return; + a2 = v_blend[3] + (1-v_blend[3])*a; // new total alpha + a3 = v_blend[3]/a2; // fraction of color from old + + v_blend[0] = v_blend[0]*a3 + r*(1-a3); + v_blend[1] = v_blend[1]*a3 + g*(1-a3); + v_blend[2] = v_blend[2]*a3 + b*(1-a3); + v_blend[3] = a2; +} + + +/* +============= +SV_CalcBlend +============= +*/ +void SV_CalcBlend (edict_t *ent) +{ + int contents; + vec3_t vieworg; + int remaining; + + ent->client->ps.blend[0] = ent->client->ps.blend[1] = + ent->client->ps.blend[2] = ent->client->ps.blend[3] = 0; + + // add for contents + VectorAdd (ent->s.origin, ent->client->ps.viewoffset, vieworg); + contents = gi.pointcontents (vieworg); + if (contents & (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER) ) + ent->client->ps.rdflags |= RDF_UNDERWATER; + else + ent->client->ps.rdflags &= ~RDF_UNDERWATER; + + if (contents & (CONTENTS_SOLID|CONTENTS_LAVA)) + SV_AddBlend (1.0, 0.3, 0.0, 0.6, ent->client->ps.blend); + else if (contents & CONTENTS_SLIME) + SV_AddBlend (0.0, 0.1, 0.05, 0.6, ent->client->ps.blend); + else if (contents & CONTENTS_WATER) + SV_AddBlend (0.5, 0.3, 0.2, 0.4, ent->client->ps.blend); + + // add for powerups + if (ent->client->quad_framenum > level.framenum) + { + remaining = ent->client->quad_framenum - level.framenum; + if (remaining == 30) // beginning to fade + gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage2.wav"), 1, ATTN_NORM, 0); + if (remaining > 30 || (remaining & 4) ) + SV_AddBlend (0, 0, 1, 0.08, ent->client->ps.blend); + } + else if (ent->client->invincible_framenum > level.framenum) + { + remaining = ent->client->invincible_framenum - level.framenum; + if (remaining == 30) // beginning to fade + gi.sound(ent, CHAN_ITEM, gi.soundindex("items/protect2.wav"), 1, ATTN_NORM, 0); + if (remaining > 30 || (remaining & 4) ) + SV_AddBlend (1, 1, 0, 0.08, ent->client->ps.blend); + } + else if (ent->client->enviro_framenum > level.framenum) + { + remaining = ent->client->enviro_framenum - level.framenum; + if (remaining == 30) // beginning to fade + gi.sound(ent, CHAN_ITEM, gi.soundindex("items/airout.wav"), 1, ATTN_NORM, 0); + if (remaining > 30 || (remaining & 4) ) + SV_AddBlend (0, 1, 0, 0.08, ent->client->ps.blend); + } + else if (ent->client->breather_framenum > level.framenum) + { + remaining = ent->client->breather_framenum - level.framenum; + if (remaining == 30) // beginning to fade + gi.sound(ent, CHAN_ITEM, gi.soundindex("items/airout.wav"), 1, ATTN_NORM, 0); + if (remaining > 30 || (remaining & 4) ) + SV_AddBlend (0.4, 1, 0.4, 0.04, ent->client->ps.blend); + } + + // add for damage + if (ent->client->damage_alpha > 0) + SV_AddBlend (ent->client->damage_blend[0],ent->client->damage_blend[1] + ,ent->client->damage_blend[2], ent->client->damage_alpha, ent->client->ps.blend); + + if (ent->client->bonus_alpha > 0) + SV_AddBlend (0.85, 0.7, 0.3, ent->client->bonus_alpha, ent->client->ps.blend); + + // drop the damage value + ent->client->damage_alpha -= 0.06; + if (ent->client->damage_alpha < 0) + ent->client->damage_alpha = 0; + + // drop the bonus value + ent->client->bonus_alpha -= 0.1; + if (ent->client->bonus_alpha < 0) + ent->client->bonus_alpha = 0; +} + + +/* +================= +P_FallingDamage +================= +*/ +void P_FallingDamage (edict_t *ent) +{ + float delta; + int damage; + vec3_t dir; + + if (ent->s.modelindex != 255) + return; // not in the player model + + if (ent->movetype == MOVETYPE_NOCLIP) + return; + + if ((ent->client->oldvelocity[2] < 0) && (ent->velocity[2] > ent->client->oldvelocity[2]) && (!ent->groundentity)) + { + delta = ent->client->oldvelocity[2]; + } + else + { + if (!ent->groundentity) + return; + delta = ent->velocity[2] - ent->client->oldvelocity[2]; + } + delta = delta*delta * 0.0001; + +//ZOID + // never take damage if just release grapple or on grapple + if (level.time - ent->client->ctf_grapplereleasetime <= FRAMETIME * 2 || + (ent->client->ctf_grapple && + ent->client->ctf_grapplestate > CTF_GRAPPLE_STATE_FLY)) + return; +//ZOID + + // never take falling damage if completely underwater + if (ent->waterlevel == 3) + return; + if (ent->waterlevel == 2) + delta *= 0.25; + if (ent->waterlevel == 1) + delta *= 0.5; + + if (delta < 1) + return; + + if (delta < 15) + { + ent->s.event = EV_FOOTSTEP; + return; + } + + ent->client->fall_value = delta*0.5; + if (ent->client->fall_value > 40) + ent->client->fall_value = 40; + ent->client->fall_time = level.time + FALL_TIME; + + if (delta > 30) + { + if (ent->health > 0) + { + if (delta >= 55) + ent->s.event = EV_FALLFAR; + else + ent->s.event = EV_FALL; + } + ent->pain_debounce_time = level.time; // no normal pain sound + damage = (delta-30)/2; + if (damage < 1) + damage = 1; + VectorSet (dir, 0, 0, 1); + + if (!deathmatch->value || !((int)dmflags->value & DF_NO_FALLING) ) + T_Damage (ent, world, world, dir, ent->s.origin, vec3_origin, damage, 0, 0, MOD_FALLING); + } + else + { + ent->s.event = EV_FALLSHORT; + return; + } +} + + + +/* +============= +P_WorldEffects +============= +*/ +void P_WorldEffects (void) +{ + qboolean breather; + qboolean envirosuit; + int waterlevel, old_waterlevel; + + if (current_player->movetype == MOVETYPE_NOCLIP) + { + current_player->air_finished = level.time + 12; // don't need air + return; + } + + waterlevel = current_player->waterlevel; + old_waterlevel = current_client->old_waterlevel; + current_client->old_waterlevel = waterlevel; + + breather = current_client->breather_framenum > level.framenum; + envirosuit = current_client->enviro_framenum > level.framenum; + + // + // if just entered a water volume, play a sound + // + if (!old_waterlevel && waterlevel) + { + PlayerNoise(current_player, current_player->s.origin, PNOISE_SELF); + if (current_player->watertype & CONTENTS_LAVA) + gi.sound (current_player, CHAN_BODY, gi.soundindex("player/lava_in.wav"), 1, ATTN_NORM, 0); + else if (current_player->watertype & CONTENTS_SLIME) + gi.sound (current_player, CHAN_BODY, gi.soundindex("player/watr_in.wav"), 1, ATTN_NORM, 0); + else if (current_player->watertype & CONTENTS_WATER) + gi.sound (current_player, CHAN_BODY, gi.soundindex("player/watr_in.wav"), 1, ATTN_NORM, 0); + current_player->flags |= FL_INWATER; + + // clear damage_debounce, so the pain sound will play immediately + current_player->damage_debounce_time = level.time - 1; + } + + // + // if just completely exited a water volume, play a sound + // + if (old_waterlevel && ! waterlevel) + { + PlayerNoise(current_player, current_player->s.origin, PNOISE_SELF); + gi.sound (current_player, CHAN_BODY, gi.soundindex("player/watr_out.wav"), 1, ATTN_NORM, 0); + current_player->flags &= ~FL_INWATER; + } + + // + // check for head just going under water + // + if (old_waterlevel != 3 && waterlevel == 3) + { + gi.sound (current_player, CHAN_BODY, gi.soundindex("player/watr_un.wav"), 1, ATTN_NORM, 0); + } + + // + // check for head just coming out of water + // + if (old_waterlevel == 3 && waterlevel != 3) + { + if (current_player->air_finished < level.time) + { // gasp for air + gi.sound (current_player, CHAN_VOICE, gi.soundindex("player/gasp1.wav"), 1, ATTN_NORM, 0); + PlayerNoise(current_player, current_player->s.origin, PNOISE_SELF); + } + else if (current_player->air_finished < level.time + 11) + { // just break surface + gi.sound (current_player, CHAN_VOICE, gi.soundindex("player/gasp2.wav"), 1, ATTN_NORM, 0); + } + } + + // + // check for drowning + // + if (waterlevel == 3) + { + // breather or envirosuit give air + if (breather || envirosuit) + { + current_player->air_finished = level.time + 10; + + if (((int)(current_client->breather_framenum - level.framenum) % 25) == 0) + { + if (!current_client->breather_sound) + gi.sound (current_player, CHAN_AUTO, gi.soundindex("player/u_breath1.wav"), 1, ATTN_NORM, 0); + else + gi.sound (current_player, CHAN_AUTO, gi.soundindex("player/u_breath2.wav"), 1, ATTN_NORM, 0); + current_client->breather_sound ^= 1; + PlayerNoise(current_player, current_player->s.origin, PNOISE_SELF); + //FIXME: release a bubble? + } + } + + // if out of air, start drowning + if (current_player->air_finished < level.time) + { // drown! + if (current_player->client->next_drown_time < level.time + && current_player->health > 0) + { + current_player->client->next_drown_time = level.time + 1; + + // take more damage the longer underwater + current_player->dmg += 2; + if (current_player->dmg > 15) + current_player->dmg = 15; + + // play a gurp sound instead of a normal pain sound + if (current_player->health <= current_player->dmg) + gi.sound (current_player, CHAN_VOICE, gi.soundindex("player/drown1.wav"), 1, ATTN_NORM, 0); + else if (rand()&1) + gi.sound (current_player, CHAN_VOICE, gi.soundindex("*gurp1.wav"), 1, ATTN_NORM, 0); + else + gi.sound (current_player, CHAN_VOICE, gi.soundindex("*gurp2.wav"), 1, ATTN_NORM, 0); + + current_player->pain_debounce_time = level.time; + + T_Damage (current_player, world, world, vec3_origin, current_player->s.origin, vec3_origin, current_player->dmg, 0, DAMAGE_NO_ARMOR, MOD_WATER); + } + } + } + else + { + current_player->air_finished = level.time + 12; + current_player->dmg = 2; + } + + // + // check for sizzle damage + // + if (waterlevel && (current_player->watertype&(CONTENTS_LAVA|CONTENTS_SLIME)) ) + { + if (current_player->watertype & CONTENTS_LAVA) + { + if (current_player->health > 0 + && current_player->pain_debounce_time <= level.time + && current_client->invincible_framenum < level.framenum) + { + if (rand()&1) + gi.sound (current_player, CHAN_VOICE, gi.soundindex("player/burn1.wav"), 1, ATTN_NORM, 0); + else + gi.sound (current_player, CHAN_VOICE, gi.soundindex("player/burn2.wav"), 1, ATTN_NORM, 0); + current_player->pain_debounce_time = level.time + 1; + } + + if (envirosuit) // take 1/3 damage with envirosuit + T_Damage (current_player, world, world, vec3_origin, current_player->s.origin, vec3_origin, 1*waterlevel, 0, 0, MOD_LAVA); + else + T_Damage (current_player, world, world, vec3_origin, current_player->s.origin, vec3_origin, 3*waterlevel, 0, 0, MOD_LAVA); + } + + if (current_player->watertype & CONTENTS_SLIME) + { + if (!envirosuit) + { // no damage from slime with envirosuit + T_Damage (current_player, world, world, vec3_origin, current_player->s.origin, vec3_origin, 1*waterlevel, 0, 0, MOD_SLIME); + } + } + } +} + + +/* +=============== +G_SetClientEffects +=============== +*/ +void G_SetClientEffects (edict_t *ent) +{ + int pa_type; + int remaining; + + ent->s.effects = 0; + ent->s.renderfx = 0; + + if (ent->health <= 0 || level.intermissiontime) + return; + + if (ent->powerarmor_time > level.time) + { + pa_type = PowerArmorType (ent); + if (pa_type == POWER_ARMOR_SCREEN) + { + ent->s.effects |= EF_POWERSCREEN; + } + else if (pa_type == POWER_ARMOR_SHIELD) + { + ent->s.effects |= EF_COLOR_SHELL; + ent->s.renderfx |= RF_SHELL_GREEN; + } + } + +//ZOID + CTFEffects(ent); +//ZOID + + if (ent->client->quad_framenum > level.framenum) + { + remaining = ent->client->quad_framenum - level.framenum; + if (remaining > 30 || (remaining & 4) ) +// ent->s.effects |= EF_QUAD; + CTFSetPowerUpEffect(ent, EF_QUAD); + } + + if (ent->client->invincible_framenum > level.framenum) + { + remaining = ent->client->invincible_framenum - level.framenum; + if (remaining > 30 || (remaining & 4) ) +// ent->s.effects |= EF_PENT; + CTFSetPowerUpEffect(ent, EF_PENT); + } + + // show cheaters!!! + if (ent->flags & FL_GODMODE) + { + ent->s.effects |= EF_COLOR_SHELL; + ent->s.renderfx |= (RF_SHELL_RED|RF_SHELL_GREEN|RF_SHELL_BLUE); + } +} + + +/* +=============== +G_SetClientEvent +=============== +*/ +void G_SetClientEvent (edict_t *ent) +{ + if (ent->s.event) + return; + + if ( ent->groundentity && xyspeed > 225) + { + if ( (int)(current_client->bobtime+bobmove) != bobcycle ) + ent->s.event = EV_FOOTSTEP; + } +} + +/* +=============== +G_SetClientSound +=============== +*/ +void G_SetClientSound (edict_t *ent) +{ + char *weap; + + if (ent->client->resp.game_helpchanged != game.helpchanged) + { + ent->client->resp.game_helpchanged = game.helpchanged; + ent->client->resp.helpchanged = 1; + } + + // help beep (no more than three times) + if (ent->client->resp.helpchanged && ent->client->resp.helpchanged <= 3 && !(level.framenum&63) ) + { + ent->client->resp.helpchanged++; + gi.sound (ent, CHAN_VOICE, gi.soundindex ("misc/pc_up.wav"), 1, ATTN_STATIC, 0); + } + + + if (ent->client->pers.weapon) + weap = ent->client->pers.weapon->classname; + else + weap = ""; + + if (ent->waterlevel && (ent->watertype&(CONTENTS_LAVA|CONTENTS_SLIME)) ) + ent->s.sound = snd_fry; + else if (strcmp(weap, "weapon_railgun") == 0) + ent->s.sound = gi.soundindex("weapons/rg_hum.wav"); + else if (strcmp(weap, "weapon_bfg") == 0) + ent->s.sound = gi.soundindex("weapons/bfg_hum.wav"); + else if (ent->client->weapon_sound) + ent->s.sound = ent->client->weapon_sound; + else + ent->s.sound = 0; +} + +/* +=============== +G_SetClientFrame +=============== +*/ +void G_SetClientFrame (edict_t *ent) +{ + gclient_t *client; + qboolean duck, run; + + if (ent->s.modelindex != 255) + return; // not in the player model + + client = ent->client; + + if (client->ps.pmove.pm_flags & PMF_DUCKED) + duck = true; + else + duck = false; + if (xyspeed) + run = true; + else + run = false; + + // check for stand/duck and stop/go transitions + if (duck != client->anim_duck && client->anim_priority < ANIM_DEATH) + goto newanim; + if (run != client->anim_run && client->anim_priority == ANIM_BASIC) + goto newanim; + if (!ent->groundentity && client->anim_priority <= ANIM_WAVE) + goto newanim; + + if(client->anim_priority == ANIM_REVERSE) + { + if(ent->s.frame > client->anim_end) + { + ent->s.frame--; + return; + } + } + else if (ent->s.frame < client->anim_end) + { // continue an animation + ent->s.frame++; + return; + } + + if (client->anim_priority == ANIM_DEATH) + return; // stay there + if (client->anim_priority == ANIM_JUMP) + { + if (!ent->groundentity) + return; // stay there + ent->client->anim_priority = ANIM_WAVE; + ent->s.frame = FRAME_jump3; + ent->client->anim_end = FRAME_jump6; + return; + } + +newanim: + // return to either a running or standing frame + client->anim_priority = ANIM_BASIC; + client->anim_duck = duck; + client->anim_run = run; + + if (!ent->groundentity) + { +//ZOID: if on grapple, don't go into jump frame, go into standing +//frame + if (client->ctf_grapple) { + ent->s.frame = FRAME_stand01; + client->anim_end = FRAME_stand40; + } else { +//ZOID + client->anim_priority = ANIM_JUMP; + if (ent->s.frame != FRAME_jump2) + ent->s.frame = FRAME_jump1; + client->anim_end = FRAME_jump2; + } + } + else if (run) + { // running + if (duck) + { + ent->s.frame = FRAME_crwalk1; + client->anim_end = FRAME_crwalk6; + } + else + { + ent->s.frame = FRAME_run1; + client->anim_end = FRAME_run6; + } + } + else + { // standing + if (duck) + { + ent->s.frame = FRAME_crstnd01; + client->anim_end = FRAME_crstnd19; + } + else + { + ent->s.frame = FRAME_stand01; + client->anim_end = FRAME_stand40; + } + } +} + + +/* +================= +ClientEndServerFrame + +Called for each player at the end of the server frame +and right after spawning +================= +*/ +void ClientEndServerFrame (edict_t *ent) +{ + float bobtime; + int i; + + current_player = ent; + current_client = ent->client; + + // + // If the origin or velocity have changed since ClientThink(), + // update the pmove values. This will happen when the client + // is pushed by a bmodel or kicked by an explosion. + // + // If it wasn't updated here, the view position would lag a frame + // behind the body position when pushed -- "sinking into plats" + // + for (i=0 ; i<3 ; i++) + { + current_client->ps.pmove.origin[i] = ent->s.origin[i]*8.0; + current_client->ps.pmove.velocity[i] = ent->velocity[i]*8.0; + } + + // + // If the end of unit layout is displayed, don't give + // the player any normal movement attributes + // + if (level.intermissiontime) + { + // FIXME: add view drifting here? + current_client->ps.blend[3] = 0; + current_client->ps.fov = 90; + G_SetStats (ent); + return; + } + + AngleVectors (ent->client->v_angle, forward, right, up); + + // burn from lava, etc + P_WorldEffects (); + + // + // set model angles from view angles so other things in + // the world can tell which direction you are looking + // + if (ent->client->v_angle[PITCH] > 180) + ent->s.angles[PITCH] = (-360 + ent->client->v_angle[PITCH])/3; + else + ent->s.angles[PITCH] = ent->client->v_angle[PITCH]/3; + ent->s.angles[YAW] = ent->client->v_angle[YAW]; + ent->s.angles[ROLL] = 0; + ent->s.angles[ROLL] = SV_CalcRoll (ent->s.angles, ent->velocity)*4; + + // + // calculate speed and cycle to be used for + // all cyclic walking effects + // + xyspeed = sqrt(ent->velocity[0]*ent->velocity[0] + ent->velocity[1]*ent->velocity[1]); + + if (xyspeed < 5) + { + bobmove = 0; + current_client->bobtime = 0; // start at beginning of cycle again + } + else if (ent->groundentity) + { // so bobbing only cycles when on ground + if (xyspeed > 210) + bobmove = 0.25; + else if (xyspeed > 100) + bobmove = 0.125; + else + bobmove = 0.0625; + } + + bobtime = (current_client->bobtime += bobmove); + + if (current_client->ps.pmove.pm_flags & PMF_DUCKED) + bobtime *= 4; + + bobcycle = (int)bobtime; + bobfracsin = fabs(sin(bobtime*M_PI)); + + // detect hitting the floor + P_FallingDamage (ent); + + // apply all the damage taken this frame + P_DamageFeedback (ent); + + // determine the view offsets + SV_CalcViewOffset (ent); + + // determine the gun offsets + SV_CalcGunOffset (ent); + + // determine the full screen color blend + // must be after viewoffset, so eye contents can be + // accurately determined + // FIXME: with client prediction, the contents + // should be determined by the client + SV_CalcBlend (ent); + +//ZOID + if (!ent->client->chase_target) +//ZOID + G_SetStats (ent); + +//ZOID +//update chasecam follower stats + for (i = 1; i <= maxclients->value; i++) { + edict_t *e = g_edicts + i; + if (!e->inuse || e->client->chase_target != ent) + continue; + memcpy(e->client->ps.stats, + ent->client->ps.stats, + sizeof(ent->client->ps.stats)); + e->client->ps.stats[STAT_LAYOUTS] = 1; + break; + } +//ZOID + + + G_SetClientEvent (ent); + + G_SetClientEffects (ent); + + G_SetClientSound (ent); + + G_SetClientFrame (ent); + + VectorCopy (ent->velocity, ent->client->oldvelocity); + VectorCopy (ent->client->ps.viewangles, ent->client->oldviewangles); + + // clear weapon kicks + VectorClear (ent->client->kick_origin); + VectorClear (ent->client->kick_angles); + + // if the scoreboard is up, update it + if (ent->client->showscores && !(level.framenum & 31) ) + { +//ZOID + if (ent->client->menu) { + PMenu_Do_Update(ent); + ent->client->menudirty = false; + ent->client->menutime = level.time; + } else +//ZOID + DeathmatchScoreboardMessage (ent, ent->enemy); + gi.unicast (ent, false); + } +} + diff --git a/original/ctf/p_weapon.c b/original/ctf/p_weapon.c new file mode 100644 index 0000000..149f496 --- /dev/null +++ b/original/ctf/p_weapon.c @@ -0,0 +1,1465 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +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. + +*/ +// g_weapon.c + +#include "g_local.h" +#include "m_player.h" + + +static qboolean is_quad; +static byte is_silenced; + + +void weapon_grenade_fire (edict_t *ent, qboolean held); + + +void P_ProjectSource (gclient_t *client, vec3_t point, vec3_t distance, vec3_t forward, vec3_t right, vec3_t result) +{ + vec3_t _distance; + + VectorCopy (distance, _distance); + if (client->pers.hand == LEFT_HANDED) + _distance[1] *= -1; + else if (client->pers.hand == CENTER_HANDED) + _distance[1] = 0; + G_ProjectSource (point, _distance, forward, right, result); +} + + +/* +=============== +PlayerNoise + +Each player can have two noise objects associated with it: +a personal noise (jumping, pain, weapon firing), and a weapon +target noise (bullet wall impacts) + +Monsters that don't directly see the player can move +to a noise in hopes of seeing the player from there. +=============== +*/ +void PlayerNoise(edict_t *who, vec3_t where, int type) +{ + edict_t *noise; + + if (type == PNOISE_WEAPON) + { + if (who->client->silencer_shots) + { + who->client->silencer_shots--; + return; + } + } + + if (deathmatch->value) + return; + + if (who->flags & FL_NOTARGET) + return; + + + if (!who->mynoise) + { + noise = G_Spawn(); + noise->classname = "player_noise"; + VectorSet (noise->mins, -8, -8, -8); + VectorSet (noise->maxs, 8, 8, 8); + noise->owner = who; + noise->svflags = SVF_NOCLIENT; + who->mynoise = noise; + + noise = G_Spawn(); + noise->classname = "player_noise"; + VectorSet (noise->mins, -8, -8, -8); + VectorSet (noise->maxs, 8, 8, 8); + noise->owner = who; + noise->svflags = SVF_NOCLIENT; + who->mynoise2 = noise; + } + + if (type == PNOISE_SELF || type == PNOISE_WEAPON) + { + noise = who->mynoise; + level.sound_entity = noise; + level.sound_entity_framenum = level.framenum; + } + else // type == PNOISE_IMPACT + { + noise = who->mynoise2; + level.sound2_entity = noise; + level.sound2_entity_framenum = level.framenum; + } + + VectorCopy (where, noise->s.origin); + VectorSubtract (where, noise->maxs, noise->absmin); + VectorAdd (where, noise->maxs, noise->absmax); + noise->teleport_time = level.time; + gi.linkentity (noise); +} + + +qboolean Pickup_Weapon (edict_t *ent, edict_t *other) +{ + int index; + gitem_t *ammo; + + index = ITEM_INDEX(ent->item); + + if ( ( ((int)(dmflags->value) & DF_WEAPONS_STAY) || coop->value) + && other->client->pers.inventory[index]) + { + if (!(ent->spawnflags & (DROPPED_ITEM | DROPPED_PLAYER_ITEM) ) ) + return false; // leave the weapon for others to pickup + } + + other->client->pers.inventory[index]++; + + if (!(ent->spawnflags & DROPPED_ITEM) ) + { + // give them some ammo with it + ammo = FindItem (ent->item->ammo); + if ( (int)dmflags->value & DF_INFINITE_AMMO ) + Add_Ammo (other, ammo, 1000); + else + Add_Ammo (other, ammo, ammo->quantity); + + if (! (ent->spawnflags & DROPPED_PLAYER_ITEM) ) + { + if (deathmatch->value) + { + if ((int)(dmflags->value) & DF_WEAPONS_STAY) + ent->flags |= FL_RESPAWN; + else + SetRespawn (ent, 30); + } + if (coop->value) + ent->flags |= FL_RESPAWN; + } + } + + if (other->client->pers.weapon != ent->item && + (other->client->pers.inventory[index] == 1) && + ( !deathmatch->value || other->client->pers.weapon == FindItem("blaster") ) ) + other->client->newweapon = ent->item; + + return true; +} + + +/* +=============== +ChangeWeapon + +The old weapon has been dropped all the way, so make the new one +current +=============== +*/ +void ChangeWeapon (edict_t *ent) +{ + int i; + + if (ent->client->grenade_time) + { + ent->client->grenade_time = level.time; + ent->client->weapon_sound = 0; + weapon_grenade_fire (ent, false); + ent->client->grenade_time = 0; + } + + ent->client->pers.lastweapon = ent->client->pers.weapon; + ent->client->pers.weapon = ent->client->newweapon; + ent->client->newweapon = NULL; + ent->client->machinegun_shots = 0; + + // set visible model + if (ent->s.modelindex == 255) { + if (ent->client->pers.weapon) + i = ((ent->client->pers.weapon->weapmodel & 0xff) << 8); + else + i = 0; + ent->s.skinnum = (ent - g_edicts - 1) | i; + } + + if (ent->client->pers.weapon && ent->client->pers.weapon->ammo) + ent->client->ammo_index = ITEM_INDEX(FindItem(ent->client->pers.weapon->ammo)); + else + ent->client->ammo_index = 0; + + if (!ent->client->pers.weapon) + { // dead + ent->client->ps.gunindex = 0; + return; + } + + ent->client->weaponstate = WEAPON_ACTIVATING; + ent->client->ps.gunframe = 0; + ent->client->ps.gunindex = gi.modelindex(ent->client->pers.weapon->view_model); + + ent->client->anim_priority = ANIM_PAIN; + if(ent->client->ps.pmove.pm_flags & PMF_DUCKED) + { + ent->s.frame = FRAME_crpain1; + ent->client->anim_end = FRAME_crpain4; + } + else + { + ent->s.frame = FRAME_pain301; + ent->client->anim_end = FRAME_pain304; + + } +} + +/* +================= +NoAmmoWeaponChange +================= +*/ +void NoAmmoWeaponChange (edict_t *ent) +{ + if ( ent->client->pers.inventory[ITEM_INDEX(FindItem("slugs"))] + && ent->client->pers.inventory[ITEM_INDEX(FindItem("railgun"))] ) + { + ent->client->newweapon = FindItem ("railgun"); + return; + } + if ( ent->client->pers.inventory[ITEM_INDEX(FindItem("cells"))] + && ent->client->pers.inventory[ITEM_INDEX(FindItem("hyperblaster"))] ) + { + ent->client->newweapon = FindItem ("hyperblaster"); + return; + } + if ( ent->client->pers.inventory[ITEM_INDEX(FindItem("bullets"))] + && ent->client->pers.inventory[ITEM_INDEX(FindItem("chaingun"))] ) + { + ent->client->newweapon = FindItem ("chaingun"); + return; + } + if ( ent->client->pers.inventory[ITEM_INDEX(FindItem("bullets"))] + && ent->client->pers.inventory[ITEM_INDEX(FindItem("machinegun"))] ) + { + ent->client->newweapon = FindItem ("machinegun"); + return; + } + if ( ent->client->pers.inventory[ITEM_INDEX(FindItem("shells"))] > 1 + && ent->client->pers.inventory[ITEM_INDEX(FindItem("super shotgun"))] ) + { + ent->client->newweapon = FindItem ("super shotgun"); + return; + } + if ( ent->client->pers.inventory[ITEM_INDEX(FindItem("shells"))] + && ent->client->pers.inventory[ITEM_INDEX(FindItem("shotgun"))] ) + { + ent->client->newweapon = FindItem ("shotgun"); + return; + } + ent->client->newweapon = FindItem ("blaster"); +} + +/* +================= +Think_Weapon + +Called by ClientBeginServerFrame and ClientThink +================= +*/ +void Think_Weapon (edict_t *ent) +{ + // if just died, put the weapon away + if (ent->health < 1) + { + ent->client->newweapon = NULL; + ChangeWeapon (ent); + } + + // call active weapon think routine + if (ent->client->pers.weapon && ent->client->pers.weapon->weaponthink) + { + is_quad = (ent->client->quad_framenum > level.framenum); + if (ent->client->silencer_shots) + is_silenced = MZ_SILENCED; + else + is_silenced = 0; + ent->client->pers.weapon->weaponthink (ent); + } +} + + +/* +================ +Use_Weapon + +Make the weapon ready if there is ammo +================ +*/ +void Use_Weapon (edict_t *ent, gitem_t *item) +{ + int ammo_index; + gitem_t *ammo_item; + + // see if we're already using it + if (item == ent->client->pers.weapon) + return; + + if (item->ammo && !g_select_empty->value && !(item->flags & IT_AMMO)) + { + ammo_item = FindItem(item->ammo); + ammo_index = ITEM_INDEX(ammo_item); + + if (!ent->client->pers.inventory[ammo_index]) + { + gi.cprintf (ent, PRINT_HIGH, "No %s for %s.\n", ammo_item->pickup_name, item->pickup_name); + return; + } + + if (ent->client->pers.inventory[ammo_index] < item->quantity) + { + gi.cprintf (ent, PRINT_HIGH, "Not enough %s for %s.\n", ammo_item->pickup_name, item->pickup_name); + return; + } + } + + // change to this weapon when down + ent->client->newweapon = item; +} + + + +/* +================ +Drop_Weapon +================ +*/ +void Drop_Weapon (edict_t *ent, gitem_t *item) +{ + int index; + + if ((int)(dmflags->value) & DF_WEAPONS_STAY) + return; + + index = ITEM_INDEX(item); + // see if we're already using it + if ( ((item == ent->client->pers.weapon) || (item == ent->client->newweapon))&& (ent->client->pers.inventory[index] == 1) ) + { + gi.cprintf (ent, PRINT_HIGH, "Can't drop current weapon\n"); + return; + } + + Drop_Item (ent, item); + ent->client->pers.inventory[index]--; +} + + +/* +================ +Weapon_Generic + +A generic function to handle the basics of weapon thinking +================ +*/ +#define FRAME_FIRE_FIRST (FRAME_ACTIVATE_LAST + 1) +#define FRAME_IDLE_FIRST (FRAME_FIRE_LAST + 1) +#define FRAME_DEACTIVATE_FIRST (FRAME_IDLE_LAST + 1) + +static void Weapon_Generic2 (edict_t *ent, int FRAME_ACTIVATE_LAST, int FRAME_FIRE_LAST, int FRAME_IDLE_LAST, int FRAME_DEACTIVATE_LAST, int *pause_frames, int *fire_frames, void (*fire)(edict_t *ent)) +{ + int n; + + if(ent->deadflag || ent->s.modelindex != 255) // VWep animations screw up corpses + { + return; + } + + if (ent->client->weaponstate == WEAPON_DROPPING) + { + if (ent->client->ps.gunframe == FRAME_DEACTIVATE_LAST) + { + ChangeWeapon (ent); + return; + } + else if ((FRAME_DEACTIVATE_LAST - ent->client->ps.gunframe) == 4) + { + ent->client->anim_priority = ANIM_REVERSE; + if(ent->client->ps.pmove.pm_flags & PMF_DUCKED) + { + ent->s.frame = FRAME_crpain4+1; + ent->client->anim_end = FRAME_crpain1; + } + else + { + ent->s.frame = FRAME_pain304+1; + ent->client->anim_end = FRAME_pain301; + + } + } + + ent->client->ps.gunframe++; + return; + } + + if (ent->client->weaponstate == WEAPON_ACTIVATING) + { + if (ent->client->ps.gunframe == FRAME_ACTIVATE_LAST || instantweap->value) + { + ent->client->weaponstate = WEAPON_READY; + ent->client->ps.gunframe = FRAME_IDLE_FIRST; + return; + } + + ent->client->ps.gunframe++; + return; + } + + if ((ent->client->newweapon) && (ent->client->weaponstate != WEAPON_FIRING)) + { + ent->client->weaponstate = WEAPON_DROPPING; + if (instantweap->value) { + ChangeWeapon(ent); + return; + } else + ent->client->ps.gunframe = FRAME_DEACTIVATE_FIRST; + + if ((FRAME_DEACTIVATE_LAST - FRAME_DEACTIVATE_FIRST) < 4) + { + ent->client->anim_priority = ANIM_REVERSE; + if(ent->client->ps.pmove.pm_flags & PMF_DUCKED) + { + ent->s.frame = FRAME_crpain4+1; + ent->client->anim_end = FRAME_crpain1; + } + else + { + ent->s.frame = FRAME_pain304+1; + ent->client->anim_end = FRAME_pain301; + + } + } + return; + } + + if (ent->client->weaponstate == WEAPON_READY) + { + if ( ((ent->client->latched_buttons|ent->client->buttons) & BUTTON_ATTACK) ) + { + ent->client->latched_buttons &= ~BUTTON_ATTACK; + if ((!ent->client->ammo_index) || + ( ent->client->pers.inventory[ent->client->ammo_index] >= ent->client->pers.weapon->quantity)) + { + ent->client->ps.gunframe = FRAME_FIRE_FIRST; + ent->client->weaponstate = WEAPON_FIRING; + + // start the animation + ent->client->anim_priority = ANIM_ATTACK; + if (ent->client->ps.pmove.pm_flags & PMF_DUCKED) + { + ent->s.frame = FRAME_crattak1-1; + ent->client->anim_end = FRAME_crattak9; + } + else + { + ent->s.frame = FRAME_attack1-1; + ent->client->anim_end = FRAME_attack8; + } + } + else + { + if (level.time >= ent->pain_debounce_time) + { + gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/noammo.wav"), 1, ATTN_NORM, 0); + ent->pain_debounce_time = level.time + 1; + } + NoAmmoWeaponChange (ent); + } + } + else + { + if (ent->client->ps.gunframe == FRAME_IDLE_LAST) + { + ent->client->ps.gunframe = FRAME_IDLE_FIRST; + return; + } + + if (pause_frames) + { + for (n = 0; pause_frames[n]; n++) + { + if (ent->client->ps.gunframe == pause_frames[n]) + { + if (rand()&15) + return; + } + } + } + + ent->client->ps.gunframe++; + return; + } + } + + if (ent->client->weaponstate == WEAPON_FIRING) + { + for (n = 0; fire_frames[n]; n++) + { + if (ent->client->ps.gunframe == fire_frames[n]) + { +//ZOID + if (!CTFApplyStrengthSound(ent)) +//ZOID + if (ent->client->quad_framenum > level.framenum) + gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage3.wav"), 1, ATTN_NORM, 0); +//ZOID + CTFApplyHasteSound(ent); +//ZOID + + fire (ent); + break; + } + } + + if (!fire_frames[n]) + ent->client->ps.gunframe++; + + if (ent->client->ps.gunframe == FRAME_IDLE_FIRST+1) + ent->client->weaponstate = WEAPON_READY; + } +} + +//ZOID +void Weapon_Generic (edict_t *ent, int FRAME_ACTIVATE_LAST, int FRAME_FIRE_LAST, int FRAME_IDLE_LAST, int FRAME_DEACTIVATE_LAST, int *pause_frames, int *fire_frames, void (*fire)(edict_t *ent)) +{ + int oldstate = ent->client->weaponstate; + + Weapon_Generic2 (ent, FRAME_ACTIVATE_LAST, FRAME_FIRE_LAST, + FRAME_IDLE_LAST, FRAME_DEACTIVATE_LAST, pause_frames, + fire_frames, fire); + + // run the weapon frame again if hasted + if (stricmp(ent->client->pers.weapon->pickup_name, "Grapple") == 0 && + ent->client->weaponstate == WEAPON_FIRING) + return; + + if ((CTFApplyHaste(ent) || + (Q_stricmp(ent->client->pers.weapon->pickup_name, "Grapple") == 0 && + ent->client->weaponstate != WEAPON_FIRING)) + && oldstate == ent->client->weaponstate) { + Weapon_Generic2 (ent, FRAME_ACTIVATE_LAST, FRAME_FIRE_LAST, + FRAME_IDLE_LAST, FRAME_DEACTIVATE_LAST, pause_frames, + fire_frames, fire); + } +} +//ZOID + +/* +====================================================================== + +GRENADE + +====================================================================== +*/ + +#define GRENADE_TIMER 3.0 +#define GRENADE_MINSPEED 400 +#define GRENADE_MAXSPEED 800 + +void weapon_grenade_fire (edict_t *ent, qboolean held) +{ + vec3_t offset; + vec3_t forward, right; + vec3_t start; + int damage = 125; + float timer; + int speed; + float radius; + + radius = damage+40; + if (is_quad) + damage *= 4; + + VectorSet(offset, 8, 8, ent->viewheight-8); + AngleVectors (ent->client->v_angle, forward, right, NULL); + P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start); + + timer = ent->client->grenade_time - level.time; + speed = GRENADE_MINSPEED + (GRENADE_TIMER - timer) * ((GRENADE_MAXSPEED - GRENADE_MINSPEED) / GRENADE_TIMER); + fire_grenade2 (ent, start, forward, damage, speed, timer, radius, held); + + if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) ) + ent->client->pers.inventory[ent->client->ammo_index]--; + + ent->client->grenade_time = level.time + 1.0; + + if(ent->deadflag || ent->s.modelindex != 255) // VWep animations screw up corpses + { + return; + } + + if (ent->client->ps.pmove.pm_flags & PMF_DUCKED) + { + ent->client->anim_priority = ANIM_ATTACK; + ent->s.frame = FRAME_crattak1-1; + ent->client->anim_end = FRAME_crattak3; + } + else + { + ent->client->anim_priority = ANIM_REVERSE; + ent->s.frame = FRAME_wave08; + ent->client->anim_end = FRAME_wave01; + } +} + +void Weapon_Grenade (edict_t *ent) +{ + if ((ent->client->newweapon) && (ent->client->weaponstate == WEAPON_READY)) + { + ChangeWeapon (ent); + return; + } + + if (ent->client->weaponstate == WEAPON_ACTIVATING) + { + ent->client->weaponstate = WEAPON_READY; + ent->client->ps.gunframe = 16; + return; + } + + if (ent->client->weaponstate == WEAPON_READY) + { + if ( ((ent->client->latched_buttons|ent->client->buttons) & BUTTON_ATTACK) ) + { + ent->client->latched_buttons &= ~BUTTON_ATTACK; + if (ent->client->pers.inventory[ent->client->ammo_index]) + { + ent->client->ps.gunframe = 1; + ent->client->weaponstate = WEAPON_FIRING; + ent->client->grenade_time = 0; + } + else + { + if (level.time >= ent->pain_debounce_time) + { + gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/noammo.wav"), 1, ATTN_NORM, 0); + ent->pain_debounce_time = level.time + 1; + } + NoAmmoWeaponChange (ent); + } + return; + } + + if ((ent->client->ps.gunframe == 29) || (ent->client->ps.gunframe == 34) || (ent->client->ps.gunframe == 39) || (ent->client->ps.gunframe == 48)) + { + if (rand()&15) + return; + } + + if (++ent->client->ps.gunframe > 48) + ent->client->ps.gunframe = 16; + return; + } + + if (ent->client->weaponstate == WEAPON_FIRING) + { + if (ent->client->ps.gunframe == 5) + gi.sound(ent, CHAN_WEAPON, gi.soundindex("weapons/hgrena1b.wav"), 1, ATTN_NORM, 0); + + if (ent->client->ps.gunframe == 11) + { + if (!ent->client->grenade_time) + { + ent->client->grenade_time = level.time + GRENADE_TIMER + 0.2; + ent->client->weapon_sound = gi.soundindex("weapons/hgrenc1b.wav"); + } + + // they waited too long, detonate it in their hand + if (!ent->client->grenade_blew_up && level.time >= ent->client->grenade_time) + { + ent->client->weapon_sound = 0; + weapon_grenade_fire (ent, true); + ent->client->grenade_blew_up = true; + } + + if (ent->client->buttons & BUTTON_ATTACK) + return; + + if (ent->client->grenade_blew_up) + { + if (level.time >= ent->client->grenade_time) + { + ent->client->ps.gunframe = 15; + ent->client->grenade_blew_up = false; + } + else + { + return; + } + } + } + + if (ent->client->ps.gunframe == 12) + { + ent->client->weapon_sound = 0; + weapon_grenade_fire (ent, false); + } + + if ((ent->client->ps.gunframe == 15) && (level.time < ent->client->grenade_time)) + return; + + ent->client->ps.gunframe++; + + if (ent->client->ps.gunframe == 16) + { + ent->client->grenade_time = 0; + ent->client->weaponstate = WEAPON_READY; + } + } +} + +/* +====================================================================== + +GRENADE LAUNCHER + +====================================================================== +*/ + +void weapon_grenadelauncher_fire (edict_t *ent) +{ + vec3_t offset; + vec3_t forward, right; + vec3_t start; + int damage = 120; + float radius; + + radius = damage+40; + if (is_quad) + damage *= 4; + + VectorSet(offset, 8, 8, ent->viewheight-8); + AngleVectors (ent->client->v_angle, forward, right, NULL); + P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start); + + VectorScale (forward, -2, ent->client->kick_origin); + ent->client->kick_angles[0] = -1; + + fire_grenade (ent, start, forward, damage, 600, 2.5, radius); + + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + gi.WriteByte (MZ_GRENADE | is_silenced); + gi.multicast (ent->s.origin, MULTICAST_PVS); + + ent->client->ps.gunframe++; + + PlayerNoise(ent, start, PNOISE_WEAPON); + + if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) ) + ent->client->pers.inventory[ent->client->ammo_index]--; +} + +void Weapon_GrenadeLauncher (edict_t *ent) +{ + static int pause_frames[] = {34, 51, 59, 0}; + static int fire_frames[] = {6, 0}; + + Weapon_Generic (ent, 5, 16, 59, 64, pause_frames, fire_frames, weapon_grenadelauncher_fire); +} + +/* +====================================================================== + +ROCKET + +====================================================================== +*/ + +void Weapon_RocketLauncher_Fire (edict_t *ent) +{ + vec3_t offset, start; + vec3_t forward, right; + int damage; + float damage_radius; + int radius_damage; + + damage = 100 + (int)(random() * 20.0); + radius_damage = 120; + damage_radius = 120; + if (is_quad) + { + damage *= 4; + radius_damage *= 4; + } + + AngleVectors (ent->client->v_angle, forward, right, NULL); + + VectorScale (forward, -2, ent->client->kick_origin); + ent->client->kick_angles[0] = -1; + + VectorSet(offset, 8, 8, ent->viewheight-8); + P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start); + fire_rocket (ent, start, forward, damage, 650, damage_radius, radius_damage); + + // send muzzle flash + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + gi.WriteByte (MZ_ROCKET | is_silenced); + gi.multicast (ent->s.origin, MULTICAST_PVS); + + ent->client->ps.gunframe++; + + PlayerNoise(ent, start, PNOISE_WEAPON); + + if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) ) + ent->client->pers.inventory[ent->client->ammo_index]--; +} + +void Weapon_RocketLauncher (edict_t *ent) +{ + static int pause_frames[] = {25, 33, 42, 50, 0}; + static int fire_frames[] = {5, 0}; + + Weapon_Generic (ent, 4, 12, 50, 54, pause_frames, fire_frames, Weapon_RocketLauncher_Fire); +} + + +/* +====================================================================== + +BLASTER / HYPERBLASTER + +====================================================================== +*/ + +void Blaster_Fire (edict_t *ent, vec3_t g_offset, int damage, qboolean hyper, int effect) +{ + vec3_t forward, right; + vec3_t start; + vec3_t offset; + + if (is_quad) + damage *= 4; + AngleVectors (ent->client->v_angle, forward, right, NULL); + VectorSet(offset, 24, 8, ent->viewheight-8); + VectorAdd (offset, g_offset, offset); + P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start); + + VectorScale (forward, -2, ent->client->kick_origin); + ent->client->kick_angles[0] = -1; + + fire_blaster (ent, start, forward, damage, 1000, effect, hyper); + + // send muzzle flash + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + if (hyper) + gi.WriteByte (MZ_HYPERBLASTER | is_silenced); + else + gi.WriteByte (MZ_BLASTER | is_silenced); + gi.multicast (ent->s.origin, MULTICAST_PVS); + + PlayerNoise(ent, start, PNOISE_WEAPON); +} + + +void Weapon_Blaster_Fire (edict_t *ent) +{ + int damage; + + if (deathmatch->value) + damage = 15; + else + damage = 10; + Blaster_Fire (ent, vec3_origin, damage, false, EF_BLASTER); + ent->client->ps.gunframe++; +} + +void Weapon_Blaster (edict_t *ent) +{ + static int pause_frames[] = {19, 32, 0}; + static int fire_frames[] = {5, 0}; + + Weapon_Generic (ent, 4, 8, 52, 55, pause_frames, fire_frames, Weapon_Blaster_Fire); +} + + +void Weapon_HyperBlaster_Fire (edict_t *ent) +{ + float rotation; + vec3_t offset; + int effect; + int damage; + + ent->client->weapon_sound = gi.soundindex("weapons/hyprbl1a.wav"); + + if (!(ent->client->buttons & BUTTON_ATTACK)) + { + ent->client->ps.gunframe++; + } + else + { + if (! ent->client->pers.inventory[ent->client->ammo_index] ) + { + if (level.time >= ent->pain_debounce_time) + { + gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/noammo.wav"), 1, ATTN_NORM, 0); + ent->pain_debounce_time = level.time + 1; + } + NoAmmoWeaponChange (ent); + } + else + { + rotation = (ent->client->ps.gunframe - 5) * 2*M_PI/6; + offset[0] = -4 * sin(rotation); + offset[1] = 0; + offset[2] = 4 * cos(rotation); + + if ((ent->client->ps.gunframe == 6) || (ent->client->ps.gunframe == 9)) + effect = EF_HYPERBLASTER; + else + effect = 0; + if (deathmatch->value) + damage = 15; + else + damage = 20; + Blaster_Fire (ent, offset, damage, true, effect); + if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) ) + ent->client->pers.inventory[ent->client->ammo_index]--; + + ent->client->anim_priority = ANIM_ATTACK; + if (ent->client->ps.pmove.pm_flags & PMF_DUCKED) + { + ent->s.frame = FRAME_crattak1 - 1; + ent->client->anim_end = FRAME_crattak9; + } + else + { + ent->s.frame = FRAME_attack1 - 1; + ent->client->anim_end = FRAME_attack8; + } + } + + ent->client->ps.gunframe++; + if (ent->client->ps.gunframe == 12 && ent->client->pers.inventory[ent->client->ammo_index]) + ent->client->ps.gunframe = 6; + } + + if (ent->client->ps.gunframe == 12) + { + gi.sound(ent, CHAN_AUTO, gi.soundindex("weapons/hyprbd1a.wav"), 1, ATTN_NORM, 0); + ent->client->weapon_sound = 0; + } + +} + +void Weapon_HyperBlaster (edict_t *ent) +{ + static int pause_frames[] = {0}; + static int fire_frames[] = {6, 7, 8, 9, 10, 11, 0}; + + Weapon_Generic (ent, 5, 20, 49, 53, pause_frames, fire_frames, Weapon_HyperBlaster_Fire); +} + +/* +====================================================================== + +MACHINEGUN / CHAINGUN + +====================================================================== +*/ + +void Machinegun_Fire (edict_t *ent) +{ + int i; + vec3_t start; + vec3_t forward, right; + vec3_t angles; + int damage = 8; + int kick = 2; + vec3_t offset; + + if (!(ent->client->buttons & BUTTON_ATTACK)) + { + ent->client->machinegun_shots = 0; + ent->client->ps.gunframe++; + return; + } + + if (ent->client->ps.gunframe == 5) + ent->client->ps.gunframe = 4; + else + ent->client->ps.gunframe = 5; + + if (ent->client->pers.inventory[ent->client->ammo_index] < 1) + { + ent->client->ps.gunframe = 6; + if (level.time >= ent->pain_debounce_time) + { + gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/noammo.wav"), 1, ATTN_NORM, 0); + ent->pain_debounce_time = level.time + 1; + } + NoAmmoWeaponChange (ent); + return; + } + + if (is_quad) + { + damage *= 4; + kick *= 4; + } + + for (i=1 ; i<3 ; i++) + { + ent->client->kick_origin[i] = crandom() * 0.35; + ent->client->kick_angles[i] = crandom() * 0.7; + } + ent->client->kick_origin[0] = crandom() * 0.35; + ent->client->kick_angles[0] = ent->client->machinegun_shots * -1.5; + + // raise the gun as it is firing + if (!deathmatch->value) + { + ent->client->machinegun_shots++; + if (ent->client->machinegun_shots > 9) + ent->client->machinegun_shots = 9; + } + + // get start / end positions + VectorAdd (ent->client->v_angle, ent->client->kick_angles, angles); + AngleVectors (angles, forward, right, NULL); + VectorSet(offset, 0, 8, ent->viewheight-8); + P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start); + fire_bullet (ent, start, forward, damage, kick, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, MOD_MACHINEGUN); + + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + gi.WriteByte (MZ_MACHINEGUN | is_silenced); + gi.multicast (ent->s.origin, MULTICAST_PVS); + + PlayerNoise(ent, start, PNOISE_WEAPON); + + if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) ) + ent->client->pers.inventory[ent->client->ammo_index]--; + + ent->client->anim_priority = ANIM_ATTACK; + if (ent->client->ps.pmove.pm_flags & PMF_DUCKED) + { + ent->s.frame = FRAME_crattak1 - (int) (random()+0.25); + ent->client->anim_end = FRAME_crattak9; + } + else + { + ent->s.frame = FRAME_attack1 - (int) (random()+0.25); + ent->client->anim_end = FRAME_attack8; + } +} + +void Weapon_Machinegun (edict_t *ent) +{ + static int pause_frames[] = {23, 45, 0}; + static int fire_frames[] = {4, 5, 0}; + + Weapon_Generic (ent, 3, 5, 45, 49, pause_frames, fire_frames, Machinegun_Fire); +} + +void Chaingun_Fire (edict_t *ent) +{ + int i; + int shots; + vec3_t start; + vec3_t forward, right, up; + float r, u; + vec3_t offset; + int damage; + int kick = 2; + + if (deathmatch->value) + damage = 6; + else + damage = 8; + + if (ent->client->ps.gunframe == 5) + gi.sound(ent, CHAN_AUTO, gi.soundindex("weapons/chngnu1a.wav"), 1, ATTN_IDLE, 0); + + if ((ent->client->ps.gunframe == 14) && !(ent->client->buttons & BUTTON_ATTACK)) + { + ent->client->ps.gunframe = 32; + ent->client->weapon_sound = 0; + return; + } + else if ((ent->client->ps.gunframe == 21) && (ent->client->buttons & BUTTON_ATTACK) + && ent->client->pers.inventory[ent->client->ammo_index]) + { + ent->client->ps.gunframe = 15; + } + else + { + ent->client->ps.gunframe++; + } + + if (ent->client->ps.gunframe == 22) + { + ent->client->weapon_sound = 0; + gi.sound(ent, CHAN_AUTO, gi.soundindex("weapons/chngnd1a.wav"), 1, ATTN_IDLE, 0); + } + else + { + ent->client->weapon_sound = gi.soundindex("weapons/chngnl1a.wav"); + } + + ent->client->anim_priority = ANIM_ATTACK; + if (ent->client->ps.pmove.pm_flags & PMF_DUCKED) + { + ent->s.frame = FRAME_crattak1 - (ent->client->ps.gunframe & 1); + ent->client->anim_end = FRAME_crattak9; + } + else + { + ent->s.frame = FRAME_attack1 - (ent->client->ps.gunframe & 1); + ent->client->anim_end = FRAME_attack8; + } + + if (ent->client->ps.gunframe <= 9) + shots = 1; + else if (ent->client->ps.gunframe <= 14) + { + if (ent->client->buttons & BUTTON_ATTACK) + shots = 2; + else + shots = 1; + } + else + shots = 3; + + if (ent->client->pers.inventory[ent->client->ammo_index] < shots) + shots = ent->client->pers.inventory[ent->client->ammo_index]; + + if (!shots) + { + if (level.time >= ent->pain_debounce_time) + { + gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/noammo.wav"), 1, ATTN_NORM, 0); + ent->pain_debounce_time = level.time + 1; + } + NoAmmoWeaponChange (ent); + return; + } + + if (is_quad) + { + damage *= 4; + kick *= 4; + } + + for (i=0 ; i<3 ; i++) + { + ent->client->kick_origin[i] = crandom() * 0.35; + ent->client->kick_angles[i] = crandom() * 0.7; + } + + for (i=0 ; iclient->v_angle, forward, right, up); + r = 7 + crandom()*4; + u = crandom()*4; + VectorSet(offset, 0, r, u + ent->viewheight-8); + P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start); + + fire_bullet (ent, start, forward, damage, kick, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, MOD_CHAINGUN); + } + + // send muzzle flash + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + gi.WriteByte ((MZ_CHAINGUN1 + shots - 1) | is_silenced); + gi.multicast (ent->s.origin, MULTICAST_PVS); + + PlayerNoise(ent, start, PNOISE_WEAPON); + + if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) ) + ent->client->pers.inventory[ent->client->ammo_index] -= shots; +} + + +void Weapon_Chaingun (edict_t *ent) +{ + static int pause_frames[] = {38, 43, 51, 61, 0}; + static int fire_frames[] = {5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 0}; + + Weapon_Generic (ent, 4, 31, 61, 64, pause_frames, fire_frames, Chaingun_Fire); +} + + +/* +====================================================================== + +SHOTGUN / SUPERSHOTGUN + +====================================================================== +*/ + +void weapon_shotgun_fire (edict_t *ent) +{ + vec3_t start; + vec3_t forward, right; + vec3_t offset; + int damage = 4; + int kick = 8; + + if (ent->client->ps.gunframe == 9) + { + ent->client->ps.gunframe++; + return; + } + + AngleVectors (ent->client->v_angle, forward, right, NULL); + + VectorScale (forward, -2, ent->client->kick_origin); + ent->client->kick_angles[0] = -2; + + VectorSet(offset, 0, 8, ent->viewheight-8); + P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start); + + if (is_quad) + { + damage *= 4; + kick *= 4; + } + + if (deathmatch->value) + fire_shotgun (ent, start, forward, damage, kick, 500, 500, DEFAULT_DEATHMATCH_SHOTGUN_COUNT, MOD_SHOTGUN); + else + fire_shotgun (ent, start, forward, damage, kick, 500, 500, DEFAULT_SHOTGUN_COUNT, MOD_SHOTGUN); + + // send muzzle flash + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + gi.WriteByte (MZ_SHOTGUN | is_silenced); + gi.multicast (ent->s.origin, MULTICAST_PVS); + + ent->client->ps.gunframe++; + PlayerNoise(ent, start, PNOISE_WEAPON); + + if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) ) + ent->client->pers.inventory[ent->client->ammo_index]--; +} + +void Weapon_Shotgun (edict_t *ent) +{ + static int pause_frames[] = {22, 28, 34, 0}; + static int fire_frames[] = {8, 9, 0}; + + Weapon_Generic (ent, 7, 18, 36, 39, pause_frames, fire_frames, weapon_shotgun_fire); +} + + +void weapon_supershotgun_fire (edict_t *ent) +{ + vec3_t start; + vec3_t forward, right; + vec3_t offset; + vec3_t v; + int damage = 6; + int kick = 12; + + AngleVectors (ent->client->v_angle, forward, right, NULL); + + VectorScale (forward, -2, ent->client->kick_origin); + ent->client->kick_angles[0] = -2; + + VectorSet(offset, 0, 8, ent->viewheight-8); + P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start); + + if (is_quad) + { + damage *= 4; + kick *= 4; + } + + v[PITCH] = ent->client->v_angle[PITCH]; + v[YAW] = ent->client->v_angle[YAW] - 5; + v[ROLL] = ent->client->v_angle[ROLL]; + AngleVectors (v, forward, NULL, NULL); + fire_shotgun (ent, start, forward, damage, kick, DEFAULT_SHOTGUN_HSPREAD, DEFAULT_SHOTGUN_VSPREAD, DEFAULT_SSHOTGUN_COUNT/2, MOD_SSHOTGUN); + v[YAW] = ent->client->v_angle[YAW] + 5; + AngleVectors (v, forward, NULL, NULL); + fire_shotgun (ent, start, forward, damage, kick, DEFAULT_SHOTGUN_HSPREAD, DEFAULT_SHOTGUN_VSPREAD, DEFAULT_SSHOTGUN_COUNT/2, MOD_SSHOTGUN); + + // send muzzle flash + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + gi.WriteByte (MZ_SSHOTGUN | is_silenced); + gi.multicast (ent->s.origin, MULTICAST_PVS); + + ent->client->ps.gunframe++; + PlayerNoise(ent, start, PNOISE_WEAPON); + + if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) ) + ent->client->pers.inventory[ent->client->ammo_index] -= 2; +} + +void Weapon_SuperShotgun (edict_t *ent) +{ + static int pause_frames[] = {29, 42, 57, 0}; + static int fire_frames[] = {7, 0}; + + Weapon_Generic (ent, 6, 17, 57, 61, pause_frames, fire_frames, weapon_supershotgun_fire); +} + + + +/* +====================================================================== + +RAILGUN + +====================================================================== +*/ + +void weapon_railgun_fire (edict_t *ent) +{ + vec3_t start; + vec3_t forward, right; + vec3_t offset; + int damage; + int kick; + + if (deathmatch->value) + { // normal damage is too extreme in dm + damage = 100; + kick = 200; + } + else + { + damage = 150; + kick = 250; + } + + if (is_quad) + { + damage *= 4; + kick *= 4; + } + + AngleVectors (ent->client->v_angle, forward, right, NULL); + + VectorScale (forward, -3, ent->client->kick_origin); + ent->client->kick_angles[0] = -3; + + VectorSet(offset, 0, 7, ent->viewheight-8); + P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start); + fire_rail (ent, start, forward, damage, kick); + + // send muzzle flash + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + gi.WriteByte (MZ_RAILGUN | is_silenced); + gi.multicast (ent->s.origin, MULTICAST_PVS); + + ent->client->ps.gunframe++; + PlayerNoise(ent, start, PNOISE_WEAPON); + + if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) ) + ent->client->pers.inventory[ent->client->ammo_index]--; +} + + +void Weapon_Railgun (edict_t *ent) +{ + static int pause_frames[] = {56, 0}; + static int fire_frames[] = {4, 0}; + + Weapon_Generic (ent, 3, 18, 56, 61, pause_frames, fire_frames, weapon_railgun_fire); +} + + +/* +====================================================================== + +BFG10K + +====================================================================== +*/ + +void weapon_bfg_fire (edict_t *ent) +{ + vec3_t offset, start; + vec3_t forward, right; + int damage; + float damage_radius = 1000; + + if (deathmatch->value) + damage = 200; + else + damage = 500; + + if (ent->client->ps.gunframe == 9) + { + // send muzzle flash + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + gi.WriteByte (MZ_BFG | is_silenced); + gi.multicast (ent->s.origin, MULTICAST_PVS); + + ent->client->ps.gunframe++; + + PlayerNoise(ent, start, PNOISE_WEAPON); + return; + } + + // cells can go down during windup (from power armor hits), so + // check again and abort firing if we don't have enough now + if (ent->client->pers.inventory[ent->client->ammo_index] < 50) + { + ent->client->ps.gunframe++; + return; + } + + if (is_quad) + damage *= 4; + + AngleVectors (ent->client->v_angle, forward, right, NULL); + + VectorScale (forward, -2, ent->client->kick_origin); + + // make a big pitch kick with an inverse fall + ent->client->v_dmg_pitch = -40; + ent->client->v_dmg_roll = crandom()*8; + ent->client->v_dmg_time = level.time + DAMAGE_TIME; + + VectorSet(offset, 8, 8, ent->viewheight-8); + P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start); + fire_bfg (ent, start, forward, damage, 400, damage_radius); + + ent->client->ps.gunframe++; + + PlayerNoise(ent, start, PNOISE_WEAPON); + + if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) ) + ent->client->pers.inventory[ent->client->ammo_index] -= 50; +} + +void Weapon_BFG (edict_t *ent) +{ + static int pause_frames[] = {39, 45, 50, 55, 0}; + static int fire_frames[] = {9, 17, 0}; + + Weapon_Generic (ent, 8, 32, 55, 58, pause_frames, fire_frames, weapon_bfg_fire); +} + + +//====================================================================== diff --git a/original/ctf/q_shared.c b/original/ctf/q_shared.c new file mode 100644 index 0000000..e8520b4 --- /dev/null +++ b/original/ctf/q_shared.c @@ -0,0 +1,1419 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +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. + +*/ +#include "q_shared.h" + +#define DEG2RAD( a ) ( a * M_PI ) / 180.0F + +vec3_t vec3_origin = {0,0,0}; + +//============================================================================ + +#ifdef _WIN32 +#pragma optimize( "", off ) +#endif + +void RotatePointAroundVector( vec3_t dst, const vec3_t dir, const vec3_t point, float degrees ) +{ + float m[3][3]; + float im[3][3]; + float zrot[3][3]; + float tmpmat[3][3]; + float rot[3][3]; + int i; + vec3_t vr, vup, vf; + + vf[0] = dir[0]; + vf[1] = dir[1]; + vf[2] = dir[2]; + + PerpendicularVector( vr, dir ); + CrossProduct( vr, vf, vup ); + + m[0][0] = vr[0]; + m[1][0] = vr[1]; + m[2][0] = vr[2]; + + m[0][1] = vup[0]; + m[1][1] = vup[1]; + m[2][1] = vup[2]; + + m[0][2] = vf[0]; + m[1][2] = vf[1]; + m[2][2] = vf[2]; + + memcpy( im, m, sizeof( im ) ); + + im[0][1] = m[1][0]; + im[0][2] = m[2][0]; + im[1][0] = m[0][1]; + im[1][2] = m[2][1]; + im[2][0] = m[0][2]; + im[2][1] = m[1][2]; + + memset( zrot, 0, sizeof( zrot ) ); + zrot[0][0] = zrot[1][1] = zrot[2][2] = 1.0F; + + zrot[0][0] = cos( DEG2RAD( degrees ) ); + zrot[0][1] = sin( DEG2RAD( degrees ) ); + zrot[1][0] = -sin( DEG2RAD( degrees ) ); + zrot[1][1] = cos( DEG2RAD( degrees ) ); + + R_ConcatRotations( m, zrot, tmpmat ); + R_ConcatRotations( tmpmat, im, rot ); + + for ( i = 0; i < 3; i++ ) + { + dst[i] = rot[i][0] * point[0] + rot[i][1] * point[1] + rot[i][2] * point[2]; + } +} + +#ifdef _WIN32 +#pragma optimize( "", on ) +#endif + + + +void AngleVectors (vec3_t angles, vec3_t forward, vec3_t right, vec3_t up) +{ + float angle; + static float sr, sp, sy, cr, cp, cy; + // static to help MS compiler fp bugs + + angle = angles[YAW] * (M_PI*2 / 360); + sy = sin(angle); + cy = cos(angle); + angle = angles[PITCH] * (M_PI*2 / 360); + sp = sin(angle); + cp = cos(angle); + angle = angles[ROLL] * (M_PI*2 / 360); + sr = sin(angle); + cr = cos(angle); + + if (forward) + { + forward[0] = cp*cy; + forward[1] = cp*sy; + forward[2] = -sp; + } + if (right) + { + right[0] = (-1*sr*sp*cy+-1*cr*-sy); + right[1] = (-1*sr*sp*sy+-1*cr*cy); + right[2] = -1*sr*cp; + } + if (up) + { + up[0] = (cr*sp*cy+-sr*-sy); + up[1] = (cr*sp*sy+-sr*cy); + up[2] = cr*cp; + } +} + + +void ProjectPointOnPlane( vec3_t dst, const vec3_t p, const vec3_t normal ) +{ + float d; + vec3_t n; + float inv_denom; + + inv_denom = 1.0F / DotProduct( normal, normal ); + + d = DotProduct( normal, p ) * inv_denom; + + n[0] = normal[0] * inv_denom; + n[1] = normal[1] * inv_denom; + n[2] = normal[2] * inv_denom; + + dst[0] = p[0] - d * n[0]; + dst[1] = p[1] - d * n[1]; + dst[2] = p[2] - d * n[2]; +} + +/* +** assumes "src" is normalized +*/ +void PerpendicularVector( vec3_t dst, const vec3_t src ) +{ + int pos; + int i; + float minelem = 1.0F; + vec3_t tempvec; + + /* + ** find the smallest magnitude axially aligned vector + */ + for ( pos = 0, i = 0; i < 3; i++ ) + { + if ( fabs( src[i] ) < minelem ) + { + pos = i; + minelem = fabs( src[i] ); + } + } + tempvec[0] = tempvec[1] = tempvec[2] = 0.0F; + tempvec[pos] = 1.0F; + + /* + ** project the point onto the plane defined by src + */ + ProjectPointOnPlane( dst, tempvec, src ); + + /* + ** normalize the result + */ + VectorNormalize( dst ); +} + + + +/* +================ +R_ConcatRotations +================ +*/ +void R_ConcatRotations (float in1[3][3], float in2[3][3], float out[3][3]) +{ + out[0][0] = in1[0][0] * in2[0][0] + in1[0][1] * in2[1][0] + + in1[0][2] * in2[2][0]; + out[0][1] = in1[0][0] * in2[0][1] + in1[0][1] * in2[1][1] + + in1[0][2] * in2[2][1]; + out[0][2] = in1[0][0] * in2[0][2] + in1[0][1] * in2[1][2] + + in1[0][2] * in2[2][2]; + out[1][0] = in1[1][0] * in2[0][0] + in1[1][1] * in2[1][0] + + in1[1][2] * in2[2][0]; + out[1][1] = in1[1][0] * in2[0][1] + in1[1][1] * in2[1][1] + + in1[1][2] * in2[2][1]; + out[1][2] = in1[1][0] * in2[0][2] + in1[1][1] * in2[1][2] + + in1[1][2] * in2[2][2]; + out[2][0] = in1[2][0] * in2[0][0] + in1[2][1] * in2[1][0] + + in1[2][2] * in2[2][0]; + out[2][1] = in1[2][0] * in2[0][1] + in1[2][1] * in2[1][1] + + in1[2][2] * in2[2][1]; + out[2][2] = in1[2][0] * in2[0][2] + in1[2][1] * in2[1][2] + + in1[2][2] * in2[2][2]; +} + + +/* +================ +R_ConcatTransforms +================ +*/ +void R_ConcatTransforms (float in1[3][4], float in2[3][4], float out[3][4]) +{ + out[0][0] = in1[0][0] * in2[0][0] + in1[0][1] * in2[1][0] + + in1[0][2] * in2[2][0]; + out[0][1] = in1[0][0] * in2[0][1] + in1[0][1] * in2[1][1] + + in1[0][2] * in2[2][1]; + out[0][2] = in1[0][0] * in2[0][2] + in1[0][1] * in2[1][2] + + in1[0][2] * in2[2][2]; + out[0][3] = in1[0][0] * in2[0][3] + in1[0][1] * in2[1][3] + + in1[0][2] * in2[2][3] + in1[0][3]; + out[1][0] = in1[1][0] * in2[0][0] + in1[1][1] * in2[1][0] + + in1[1][2] * in2[2][0]; + out[1][1] = in1[1][0] * in2[0][1] + in1[1][1] * in2[1][1] + + in1[1][2] * in2[2][1]; + out[1][2] = in1[1][0] * in2[0][2] + in1[1][1] * in2[1][2] + + in1[1][2] * in2[2][2]; + out[1][3] = in1[1][0] * in2[0][3] + in1[1][1] * in2[1][3] + + in1[1][2] * in2[2][3] + in1[1][3]; + out[2][0] = in1[2][0] * in2[0][0] + in1[2][1] * in2[1][0] + + in1[2][2] * in2[2][0]; + out[2][1] = in1[2][0] * in2[0][1] + in1[2][1] * in2[1][1] + + in1[2][2] * in2[2][1]; + out[2][2] = in1[2][0] * in2[0][2] + in1[2][1] * in2[1][2] + + in1[2][2] * in2[2][2]; + out[2][3] = in1[2][0] * in2[0][3] + in1[2][1] * in2[1][3] + + in1[2][2] * in2[2][3] + in1[2][3]; +} + + +//============================================================================ + + +float Q_fabs (float f) +{ +#if 0 + if (f >= 0) + return f; + return -f; +#else + int tmp = * ( int * ) &f; + tmp &= 0x7FFFFFFF; + return * ( float * ) &tmp; +#endif +} + +#if defined _M_IX86 && !defined C_ONLY +#pragma warning (disable:4035) +__declspec( naked ) long Q_ftol( float f ) +{ + static int tmp; + __asm fld dword ptr [esp+4] + __asm fistp tmp + __asm mov eax, tmp + __asm ret +} +#pragma warning (default:4035) +#endif + +/* +=============== +LerpAngle + +=============== +*/ +float LerpAngle (float a2, float a1, float frac) +{ + if (a1 - a2 > 180) + a1 -= 360; + if (a1 - a2 < -180) + a1 += 360; + return a2 + frac * (a1 - a2); +} + + +float anglemod(float a) +{ +#if 0 + if (a >= 0) + a -= 360*(int)(a/360); + else + a += 360*( 1 + (int)(-a/360) ); +#endif + a = (360.0/65536) * ((int)(a*(65536/360.0)) & 65535); + return a; +} + + int i; + vec3_t corners[2]; + + +// this is the slow, general version +int BoxOnPlaneSide2 (vec3_t emins, vec3_t emaxs, struct cplane_s *p) +{ + int i; + float dist1, dist2; + int sides; + vec3_t corners[2]; + + for (i=0 ; i<3 ; i++) + { + if (p->normal[i] < 0) + { + corners[0][i] = emins[i]; + corners[1][i] = emaxs[i]; + } + else + { + corners[1][i] = emins[i]; + corners[0][i] = emaxs[i]; + } + } + dist1 = DotProduct (p->normal, corners[0]) - p->dist; + dist2 = DotProduct (p->normal, corners[1]) - p->dist; + sides = 0; + if (dist1 >= 0) + sides = 1; + if (dist2 < 0) + sides |= 2; + + return sides; +} + +/* +================== +BoxOnPlaneSide + +Returns 1, 2, or 1 + 2 +================== +*/ +#if !id386 || defined __linux__ +int BoxOnPlaneSide (vec3_t emins, vec3_t emaxs, struct cplane_s *p) +{ + float dist1, dist2; + int sides; + +// fast axial cases + if (p->type < 3) + { + if (p->dist <= emins[p->type]) + return 1; + if (p->dist >= emaxs[p->type]) + return 2; + return 3; + } + +// general case + switch (p->signbits) + { + case 0: +dist1 = p->normal[0]*emaxs[0] + p->normal[1]*emaxs[1] + p->normal[2]*emaxs[2]; +dist2 = p->normal[0]*emins[0] + p->normal[1]*emins[1] + p->normal[2]*emins[2]; + break; + case 1: +dist1 = p->normal[0]*emins[0] + p->normal[1]*emaxs[1] + p->normal[2]*emaxs[2]; +dist2 = p->normal[0]*emaxs[0] + p->normal[1]*emins[1] + p->normal[2]*emins[2]; + break; + case 2: +dist1 = p->normal[0]*emaxs[0] + p->normal[1]*emins[1] + p->normal[2]*emaxs[2]; +dist2 = p->normal[0]*emins[0] + p->normal[1]*emaxs[1] + p->normal[2]*emins[2]; + break; + case 3: +dist1 = p->normal[0]*emins[0] + p->normal[1]*emins[1] + p->normal[2]*emaxs[2]; +dist2 = p->normal[0]*emaxs[0] + p->normal[1]*emaxs[1] + p->normal[2]*emins[2]; + break; + case 4: +dist1 = p->normal[0]*emaxs[0] + p->normal[1]*emaxs[1] + p->normal[2]*emins[2]; +dist2 = p->normal[0]*emins[0] + p->normal[1]*emins[1] + p->normal[2]*emaxs[2]; + break; + case 5: +dist1 = p->normal[0]*emins[0] + p->normal[1]*emaxs[1] + p->normal[2]*emins[2]; +dist2 = p->normal[0]*emaxs[0] + p->normal[1]*emins[1] + p->normal[2]*emaxs[2]; + break; + case 6: +dist1 = p->normal[0]*emaxs[0] + p->normal[1]*emins[1] + p->normal[2]*emins[2]; +dist2 = p->normal[0]*emins[0] + p->normal[1]*emaxs[1] + p->normal[2]*emaxs[2]; + break; + case 7: +dist1 = p->normal[0]*emins[0] + p->normal[1]*emins[1] + p->normal[2]*emins[2]; +dist2 = p->normal[0]*emaxs[0] + p->normal[1]*emaxs[1] + p->normal[2]*emaxs[2]; + break; + default: + dist1 = dist2 = 0; // shut up compiler + assert( 0 ); + break; + } + + sides = 0; + if (dist1 >= p->dist) + sides = 1; + if (dist2 < p->dist) + sides |= 2; + + assert( sides != 0 ); + + return sides; +} +#else +#pragma warning( disable: 4035 ) + +__declspec( naked ) int BoxOnPlaneSide (vec3_t emins, vec3_t emaxs, struct cplane_s *p) +{ + static int bops_initialized; + static int Ljmptab[8]; + + __asm { + + push ebx + + cmp bops_initialized, 1 + je initialized + mov bops_initialized, 1 + + mov Ljmptab[0*4], offset Lcase0 + mov Ljmptab[1*4], offset Lcase1 + mov Ljmptab[2*4], offset Lcase2 + mov Ljmptab[3*4], offset Lcase3 + mov Ljmptab[4*4], offset Lcase4 + mov Ljmptab[5*4], offset Lcase5 + mov Ljmptab[6*4], offset Lcase6 + mov Ljmptab[7*4], offset Lcase7 + +initialized: + + mov edx,ds:dword ptr[4+12+esp] + mov ecx,ds:dword ptr[4+4+esp] + xor eax,eax + mov ebx,ds:dword ptr[4+8+esp] + mov al,ds:byte ptr[17+edx] + cmp al,8 + jge Lerror + fld ds:dword ptr[0+edx] + fld st(0) + jmp dword ptr[Ljmptab+eax*4] +Lcase0: + fmul ds:dword ptr[ebx] + fld ds:dword ptr[0+4+edx] + fxch st(2) + fmul ds:dword ptr[ecx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[4+ebx] + fld ds:dword ptr[0+8+edx] + fxch st(2) + fmul ds:dword ptr[4+ecx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[8+ebx] + fxch st(5) + faddp st(3),st(0) + fmul ds:dword ptr[8+ecx] + fxch st(1) + faddp st(3),st(0) + fxch st(3) + faddp st(2),st(0) + jmp LSetSides +Lcase1: + fmul ds:dword ptr[ecx] + fld ds:dword ptr[0+4+edx] + fxch st(2) + fmul ds:dword ptr[ebx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[4+ebx] + fld ds:dword ptr[0+8+edx] + fxch st(2) + fmul ds:dword ptr[4+ecx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[8+ebx] + fxch st(5) + faddp st(3),st(0) + fmul ds:dword ptr[8+ecx] + fxch st(1) + faddp st(3),st(0) + fxch st(3) + faddp st(2),st(0) + jmp LSetSides +Lcase2: + fmul ds:dword ptr[ebx] + fld ds:dword ptr[0+4+edx] + fxch st(2) + fmul ds:dword ptr[ecx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[4+ecx] + fld ds:dword ptr[0+8+edx] + fxch st(2) + fmul ds:dword ptr[4+ebx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[8+ebx] + fxch st(5) + faddp st(3),st(0) + fmul ds:dword ptr[8+ecx] + fxch st(1) + faddp st(3),st(0) + fxch st(3) + faddp st(2),st(0) + jmp LSetSides +Lcase3: + fmul ds:dword ptr[ecx] + fld ds:dword ptr[0+4+edx] + fxch st(2) + fmul ds:dword ptr[ebx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[4+ecx] + fld ds:dword ptr[0+8+edx] + fxch st(2) + fmul ds:dword ptr[4+ebx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[8+ebx] + fxch st(5) + faddp st(3),st(0) + fmul ds:dword ptr[8+ecx] + fxch st(1) + faddp st(3),st(0) + fxch st(3) + faddp st(2),st(0) + jmp LSetSides +Lcase4: + fmul ds:dword ptr[ebx] + fld ds:dword ptr[0+4+edx] + fxch st(2) + fmul ds:dword ptr[ecx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[4+ebx] + fld ds:dword ptr[0+8+edx] + fxch st(2) + fmul ds:dword ptr[4+ecx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[8+ecx] + fxch st(5) + faddp st(3),st(0) + fmul ds:dword ptr[8+ebx] + fxch st(1) + faddp st(3),st(0) + fxch st(3) + faddp st(2),st(0) + jmp LSetSides +Lcase5: + fmul ds:dword ptr[ecx] + fld ds:dword ptr[0+4+edx] + fxch st(2) + fmul ds:dword ptr[ebx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[4+ebx] + fld ds:dword ptr[0+8+edx] + fxch st(2) + fmul ds:dword ptr[4+ecx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[8+ecx] + fxch st(5) + faddp st(3),st(0) + fmul ds:dword ptr[8+ebx] + fxch st(1) + faddp st(3),st(0) + fxch st(3) + faddp st(2),st(0) + jmp LSetSides +Lcase6: + fmul ds:dword ptr[ebx] + fld ds:dword ptr[0+4+edx] + fxch st(2) + fmul ds:dword ptr[ecx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[4+ecx] + fld ds:dword ptr[0+8+edx] + fxch st(2) + fmul ds:dword ptr[4+ebx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[8+ecx] + fxch st(5) + faddp st(3),st(0) + fmul ds:dword ptr[8+ebx] + fxch st(1) + faddp st(3),st(0) + fxch st(3) + faddp st(2),st(0) + jmp LSetSides +Lcase7: + fmul ds:dword ptr[ecx] + fld ds:dword ptr[0+4+edx] + fxch st(2) + fmul ds:dword ptr[ebx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[4+ecx] + fld ds:dword ptr[0+8+edx] + fxch st(2) + fmul ds:dword ptr[4+ebx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[8+ecx] + fxch st(5) + faddp st(3),st(0) + fmul ds:dword ptr[8+ebx] + fxch st(1) + faddp st(3),st(0) + fxch st(3) + faddp st(2),st(0) +LSetSides: + faddp st(2),st(0) + fcomp ds:dword ptr[12+edx] + xor ecx,ecx + fnstsw ax + fcomp ds:dword ptr[12+edx] + and ah,1 + xor ah,1 + add cl,ah + fnstsw ax + and ah,1 + add ah,ah + add cl,ah + pop ebx + mov eax,ecx + ret +Lerror: + int 3 + } +} +#pragma warning( default: 4035 ) +#endif + +void ClearBounds (vec3_t mins, vec3_t maxs) +{ + mins[0] = mins[1] = mins[2] = 99999; + maxs[0] = maxs[1] = maxs[2] = -99999; +} + +void AddPointToBounds (vec3_t v, vec3_t mins, vec3_t maxs) +{ + int i; + vec_t val; + + for (i=0 ; i<3 ; i++) + { + val = v[i]; + if (val < mins[i]) + mins[i] = val; + if (val > maxs[i]) + maxs[i] = val; + } +} + + +int VectorCompare (vec3_t v1, vec3_t v2) +{ + if (v1[0] != v2[0] || v1[1] != v2[1] || v1[2] != v2[2]) + return 0; + + return 1; +} + + +vec_t VectorNormalize (vec3_t v) +{ + float length, ilength; + + length = v[0]*v[0] + v[1]*v[1] + v[2]*v[2]; + length = sqrt (length); // FIXME + + if (length) + { + ilength = 1/length; + v[0] *= ilength; + v[1] *= ilength; + v[2] *= ilength; + } + + return length; + +} + +vec_t VectorNormalize2 (vec3_t v, vec3_t out) +{ + float length, ilength; + + length = v[0]*v[0] + v[1]*v[1] + v[2]*v[2]; + length = sqrt (length); // FIXME + + if (length) + { + ilength = 1/length; + out[0] = v[0]*ilength; + out[1] = v[1]*ilength; + out[2] = v[2]*ilength; + } + + return length; + +} + +void VectorMA (vec3_t veca, float scale, vec3_t vecb, vec3_t vecc) +{ + vecc[0] = veca[0] + scale*vecb[0]; + vecc[1] = veca[1] + scale*vecb[1]; + vecc[2] = veca[2] + scale*vecb[2]; +} + + +vec_t _DotProduct (vec3_t v1, vec3_t v2) +{ + return v1[0]*v2[0] + v1[1]*v2[1] + v1[2]*v2[2]; +} + +void _VectorSubtract (vec3_t veca, vec3_t vecb, vec3_t out) +{ + out[0] = veca[0]-vecb[0]; + out[1] = veca[1]-vecb[1]; + out[2] = veca[2]-vecb[2]; +} + +void _VectorAdd (vec3_t veca, vec3_t vecb, vec3_t out) +{ + out[0] = veca[0]+vecb[0]; + out[1] = veca[1]+vecb[1]; + out[2] = veca[2]+vecb[2]; +} + +void _VectorCopy (vec3_t in, vec3_t out) +{ + out[0] = in[0]; + out[1] = in[1]; + out[2] = in[2]; +} + +void CrossProduct (vec3_t v1, vec3_t v2, vec3_t cross) +{ + cross[0] = v1[1]*v2[2] - v1[2]*v2[1]; + cross[1] = v1[2]*v2[0] - v1[0]*v2[2]; + cross[2] = v1[0]*v2[1] - v1[1]*v2[0]; +} + +double sqrt(double x); + +vec_t VectorLength(vec3_t v) +{ + int i; + float length; + + length = 0; + for (i=0 ; i< 3 ; i++) + length += v[i]*v[i]; + length = sqrt (length); // FIXME + + return length; +} + +void VectorInverse (vec3_t v) +{ + v[0] = -v[0]; + v[1] = -v[1]; + v[2] = -v[2]; +} + +void VectorScale (vec3_t in, vec_t scale, vec3_t out) +{ + out[0] = in[0]*scale; + out[1] = in[1]*scale; + out[2] = in[2]*scale; +} + + +int Q_log2(int val) +{ + int answer=0; + while (val>>=1) + answer++; + return answer; +} + + + +//==================================================================================== + +/* +============ +COM_SkipPath +============ +*/ +char *COM_SkipPath (char *pathname) +{ + char *last; + + last = pathname; + while (*pathname) + { + if (*pathname=='/') + last = pathname+1; + pathname++; + } + return last; +} + +/* +============ +COM_StripExtension +============ +*/ +void COM_StripExtension (char *in, char *out) +{ + while (*in && *in != '.') + *out++ = *in++; + *out = 0; +} + +/* +============ +COM_FileExtension +============ +*/ +char *COM_FileExtension (char *in) +{ + static char exten[8]; + int i; + + while (*in && *in != '.') + in++; + if (!*in) + return ""; + in++; + for (i=0 ; i<7 && *in ; i++,in++) + exten[i] = *in; + exten[i] = 0; + return exten; +} + +/* +============ +COM_FileBase +============ +*/ +void COM_FileBase (char *in, char *out) +{ + char *s, *s2; + + s = in + strlen(in) - 1; + + while (s != in && *s != '.') + s--; + + for (s2 = s ; s2 != in && *s2 != '/' ; s2--) + ; + + if (s-s2 < 2) + out[0] = 0; + else + { + s--; + strncpy (out,s2+1, s-s2); + out[s-s2] = 0; + } +} + +/* +============ +COM_FilePath + +Returns the path up to, but not including the last / +============ +*/ +void COM_FilePath (char *in, char *out) +{ + char *s; + + s = in + strlen(in) - 1; + + while (s != in && *s != '/') + s--; + + strncpy (out,in, s-in); + out[s-in] = 0; +} + + +/* +================== +COM_DefaultExtension +================== +*/ +void COM_DefaultExtension (char *path, char *extension) +{ + char *src; +// +// if path doesn't have a .EXT, append extension +// (extension should include the .) +// + src = path + strlen(path) - 1; + + while (*src != '/' && src != path) + { + if (*src == '.') + return; // it has an extension + src--; + } + + strcat (path, extension); +} + +/* +============================================================================ + + BYTE ORDER FUNCTIONS + +============================================================================ +*/ + +qboolean bigendien; + +// can't just use function pointers, or dll linkage can +// mess up when qcommon is included in multiple places +short (*_BigShort) (short l); +short (*_LittleShort) (short l); +int (*_BigLong) (int l); +int (*_LittleLong) (int l); +float (*_BigFloat) (float l); +float (*_LittleFloat) (float l); + +short BigShort(short l){return _BigShort(l);} +short LittleShort(short l) {return _LittleShort(l);} +int BigLong (int l) {return _BigLong(l);} +int LittleLong (int l) {return _LittleLong(l);} +float BigFloat (float l) {return _BigFloat(l);} +float LittleFloat (float l) {return _LittleFloat(l);} + +short ShortSwap (short l) +{ + byte b1,b2; + + b1 = l&255; + b2 = (l>>8)&255; + + return (b1<<8) + b2; +} + +short ShortNoSwap (short l) +{ + return l; +} + +int LongSwap (int l) +{ + byte b1,b2,b3,b4; + + b1 = l&255; + b2 = (l>>8)&255; + b3 = (l>>16)&255; + b4 = (l>>24)&255; + + return ((int)b1<<24) + ((int)b2<<16) + ((int)b3<<8) + b4; +} + +int LongNoSwap (int l) +{ + return l; +} + +float FloatSwap (float f) +{ + union + { + float f; + byte b[4]; + } dat1, dat2; + + + dat1.f = f; + dat2.b[0] = dat1.b[3]; + dat2.b[1] = dat1.b[2]; + dat2.b[2] = dat1.b[1]; + dat2.b[3] = dat1.b[0]; + return dat2.f; +} + +float FloatNoSwap (float f) +{ + return f; +} + +/* +================ +Swap_Init +================ +*/ +void Swap_Init (void) +{ + byte swaptest[2] = {1,0}; + +// set the byte swapping variables in a portable manner + if ( *(short *)swaptest == 1) + { + bigendien = false; + _BigShort = ShortSwap; + _LittleShort = ShortNoSwap; + _BigLong = LongSwap; + _LittleLong = LongNoSwap; + _BigFloat = FloatSwap; + _LittleFloat = FloatNoSwap; + } + else + { + bigendien = true; + _BigShort = ShortNoSwap; + _LittleShort = ShortSwap; + _BigLong = LongNoSwap; + _LittleLong = LongSwap; + _BigFloat = FloatNoSwap; + _LittleFloat = FloatSwap; + } + +} + + + +/* +============ +va + +does a varargs printf into a temp buffer, so I don't need to have +varargs versions of all text functions. +FIXME: make this buffer size safe someday +============ +*/ +char *va(char *format, ...) +{ + va_list argptr; + static char string[1024]; + + va_start (argptr, format); + vsprintf (string, format,argptr); + va_end (argptr); + + return string; +} + + +char com_token[MAX_TOKEN_CHARS]; + +/* +============== +COM_Parse + +Parse a token out of a string +============== +*/ +char *COM_Parse (char **data_p) +{ + int c; + int len; + char *data; + + data = *data_p; + len = 0; + com_token[0] = 0; + + if (!data) + { + *data_p = NULL; + return ""; + } + +// skip whitespace +skipwhite: + while ( (c = *data) <= ' ') + { + if (c == 0) + { + *data_p = NULL; + return ""; + } + data++; + } + +// skip // comments + if (c=='/' && data[1] == '/') + { + while (*data && *data != '\n') + data++; + goto skipwhite; + } + + +// handle quoted strings specially + if (c == '\"') + { + data++; + while (1) + { + c = *data++; + if (c=='\"' || !c) + { + com_token[len] = 0; + *data_p = data; + return com_token; + } + if (len < MAX_TOKEN_CHARS) + { + com_token[len] = c; + len++; + } + } + } + +// parse a regular word + do + { + if (len < MAX_TOKEN_CHARS) + { + com_token[len] = c; + len++; + } + data++; + c = *data; + } while (c>32); + + if (len == MAX_TOKEN_CHARS) + { +// Com_Printf ("Token exceeded %i chars, discarded.\n", MAX_TOKEN_CHARS); + len = 0; + } + com_token[len] = 0; + + *data_p = data; + return com_token; +} + + +/* +=============== +Com_PageInMemory + +=============== +*/ +int paged_total; + +void Com_PageInMemory (byte *buffer, int size) +{ + int i; + + for (i=size-1 ; i>0 ; i-=4096) + paged_total += buffer[i]; +} + + + +/* +============================================================================ + + LIBRARY REPLACEMENT FUNCTIONS + +============================================================================ +*/ + +// FIXME: replace all Q_stricmp with Q_strcasecmp +int Q_stricmp (char *s1, char *s2) +{ +#if defined(WIN32) + return _stricmp (s1, s2); +#else + return strcasecmp (s1, s2); +#endif +} + + +int Q_strncasecmp (char *s1, char *s2, int n) +{ + int c1, c2; + + do + { + c1 = *s1++; + c2 = *s2++; + + if (!n--) + return 0; // strings are equal until end point + + if (c1 != c2) + { + if (c1 >= 'a' && c1 <= 'z') + c1 -= ('a' - 'A'); + if (c2 >= 'a' && c2 <= 'z') + c2 -= ('a' - 'A'); + if (c1 != c2) + return -1; // strings not equal + } + } while (c1); + + return 0; // strings are equal +} + +int Q_strcasecmp (char *s1, char *s2) +{ + return Q_strncasecmp (s1, s2, 99999); +} + + + +void Com_sprintf (char *dest, int size, char *fmt, ...) +{ + int len; + va_list argptr; + char bigbuffer[0x10000]; + + va_start (argptr,fmt); + len = vsprintf (bigbuffer,fmt,argptr); + va_end (argptr); + if (len >= size) + Com_Printf ("Com_sprintf: overflow of %i in %i\n", len, size); + strncpy (dest, bigbuffer, size-1); +} + +/* +===================================================================== + + INFO STRINGS + +===================================================================== +*/ + +/* +=============== +Info_ValueForKey + +Searches the string for the given +key and returns the associated value, or an empty string. +=============== +*/ +char *Info_ValueForKey (char *s, char *key) +{ + char pkey[512]; + static char value[2][512]; // use two buffers so compares + // work without stomping on each other + static int valueindex; + char *o; + + valueindex ^= 1; + if (*s == '\\') + s++; + while (1) + { + o = pkey; + while (*s != '\\') + { + if (!*s) + return ""; + *o++ = *s++; + } + *o = 0; + s++; + + o = value[valueindex]; + + while (*s != '\\' && *s) + { + if (!*s) + return ""; + *o++ = *s++; + } + *o = 0; + + if (!strcmp (key, pkey) ) + return value[valueindex]; + + if (!*s) + return ""; + s++; + } +} + +void Info_RemoveKey (char *s, char *key) +{ + char *start; + char pkey[512]; + char value[512]; + char *o; + + if (strstr (key, "\\")) + { +// Com_Printf ("Can't use a key with a \\\n"); + return; + } + + while (1) + { + start = s; + if (*s == '\\') + s++; + o = pkey; + while (*s != '\\') + { + if (!*s) + return; + *o++ = *s++; + } + *o = 0; + s++; + + o = value; + while (*s != '\\' && *s) + { + if (!*s) + return; + *o++ = *s++; + } + *o = 0; + + if (!strcmp (key, pkey) ) + { + strcpy (start, s); // remove this part + return; + } + + if (!*s) + return; + } + +} + + +/* +================== +Info_Validate + +Some characters are illegal in info strings because they +can mess up the server's parsing +================== +*/ +qboolean Info_Validate (char *s) +{ + if (strstr (s, "\"")) + return false; + if (strstr (s, ";")) + return false; + return true; +} + +void Info_SetValueForKey (char *s, char *key, char *value) +{ + char newi[MAX_INFO_STRING], *v; + int c; + int maxsize = MAX_INFO_STRING; + + if (strstr (key, "\\") || strstr (value, "\\") ) + { + Com_Printf ("Can't use keys or values with a \\\n"); + return; + } + + if (strstr (key, ";") ) + { + Com_Printf ("Can't use keys or values with a semicolon\n"); + return; + } + + if (strstr (key, "\"") || strstr (value, "\"") ) + { + Com_Printf ("Can't use keys or values with a \"\n"); + return; + } + + if (strlen(key) > MAX_INFO_KEY-1 || strlen(value) > MAX_INFO_KEY-1) + { + Com_Printf ("Keys and values must be < 64 characters.\n"); + return; + } + Info_RemoveKey (s, key); + if (!value || !strlen(value)) + return; + + Com_sprintf (newi, sizeof(newi), "\\%s\\%s", key, value); + + if (strlen(newi) + strlen(s) > maxsize) + { + Com_Printf ("Info string length exceeded\n"); + return; + } + + // only copy ascii values + s += strlen(s); + v = newi; + while (*v) + { + c = *v++; + c &= 127; // strip high bits + if (c >= 32 && c < 127) + *s++ = c; + } + *s = 0; +} + +//==================================================================== + + diff --git a/original/ctf/q_shared.h b/original/ctf/q_shared.h new file mode 100644 index 0000000..b10b741 --- /dev/null +++ b/original/ctf/q_shared.h @@ -0,0 +1,1200 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +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. + +*/ + +// q_shared.h -- included first by ALL program modules + +#ifdef _WIN32 +// unknown pragmas are SUPPOSED to be ignored, but.... +#pragma warning(disable : 4244) // MIPS +#pragma warning(disable : 4136) // X86 +#pragma warning(disable : 4051) // ALPHA + +#pragma warning(disable : 4018) // signed/unsigned mismatch +#pragma warning(disable : 4305) // truncation from const double to float + +#endif + +#include +#include +#include +#include +#include +#include +#include + +#if (defined _M_IX86 || defined __i386__) && !defined C_ONLY && !defined __sun__ +#define id386 1 +#else +#define id386 0 +#endif + +#if defined _M_ALPHA && !defined C_ONLY +#define idaxp 1 +#else +#define idaxp 0 +#endif + +typedef unsigned char byte; +typedef enum {false, true} qboolean; + + +#ifndef NULL +#define NULL ((void *)0) +#endif + + +// angle indexes +#define PITCH 0 // up / down +#define YAW 1 // left / right +#define ROLL 2 // fall over + +#define MAX_STRING_CHARS 1024 // max length of a string passed to Cmd_TokenizeString +#define MAX_STRING_TOKENS 80 // max tokens resulting from Cmd_TokenizeString +#define MAX_TOKEN_CHARS 128 // max length of an individual token + +#define MAX_QPATH 64 // max length of a quake game pathname +#define MAX_OSPATH 128 // max length of a filesystem pathname + +// +// per-level limits +// +#define MAX_CLIENTS 256 // absolute limit +#define MAX_EDICTS 1024 // must change protocol to increase more +#define MAX_LIGHTSTYLES 256 +#define MAX_MODELS 256 // these are sent over the net as bytes +#define MAX_SOUNDS 256 // so they cannot be blindly increased +#define MAX_IMAGES 256 +#define MAX_ITEMS 256 +#define MAX_GENERAL (MAX_CLIENTS*2) // general config strings + + +// game print flags +#define PRINT_LOW 0 // pickup messages +#define PRINT_MEDIUM 1 // death messages +#define PRINT_HIGH 2 // critical messages +#define PRINT_CHAT 3 // chat messages + + + +#define ERR_FATAL 0 // exit the entire game with a popup window +#define ERR_DROP 1 // print to console and disconnect from game +#define ERR_DISCONNECT 2 // don't kill server + +#define PRINT_ALL 0 +#define PRINT_DEVELOPER 1 // only print when "developer 1" +#define PRINT_ALERT 2 + + +// destination class for gi.multicast() +typedef enum +{ +MULTICAST_ALL, +MULTICAST_PHS, +MULTICAST_PVS, +MULTICAST_ALL_R, +MULTICAST_PHS_R, +MULTICAST_PVS_R +} multicast_t; + + +/* +============================================================== + +MATHLIB + +============================================================== +*/ + +typedef float vec_t; +typedef vec_t vec3_t[3]; +typedef vec_t vec5_t[5]; + +typedef int fixed4_t; +typedef int fixed8_t; +typedef int fixed16_t; + +#ifndef M_PI +#define M_PI 3.14159265358979323846 // matches value in gcc v2 math.h +#endif + +struct cplane_s; + +extern vec3_t vec3_origin; + +#define nanmask (255<<23) + +#define IS_NAN(x) (((*(int *)&x)&nanmask)==nanmask) + +// microsoft's fabs seems to be ungodly slow... +//float Q_fabs (float f); +//#define fabs(f) Q_fabs(f) +#if !defined C_ONLY && !defined __linux__ && !defined __sgi +extern long Q_ftol( float f ); +#else +#define Q_ftol( f ) ( long ) (f) +#endif + +#define DotProduct(x,y) (x[0]*y[0]+x[1]*y[1]+x[2]*y[2]) +#define VectorSubtract(a,b,c) (c[0]=a[0]-b[0],c[1]=a[1]-b[1],c[2]=a[2]-b[2]) +#define VectorAdd(a,b,c) (c[0]=a[0]+b[0],c[1]=a[1]+b[1],c[2]=a[2]+b[2]) +#define VectorCopy(a,b) (b[0]=a[0],b[1]=a[1],b[2]=a[2]) +#define VectorClear(a) (a[0]=a[1]=a[2]=0) +#define VectorNegate(a,b) (b[0]=-a[0],b[1]=-a[1],b[2]=-a[2]) +#define VectorSet(v, x, y, z) (v[0]=(x), v[1]=(y), v[2]=(z)) + +void VectorMA (vec3_t veca, float scale, vec3_t vecb, vec3_t vecc); + +// just in case you do't want to use the macros +vec_t _DotProduct (vec3_t v1, vec3_t v2); +void _VectorSubtract (vec3_t veca, vec3_t vecb, vec3_t out); +void _VectorAdd (vec3_t veca, vec3_t vecb, vec3_t out); +void _VectorCopy (vec3_t in, vec3_t out); + +void ClearBounds (vec3_t mins, vec3_t maxs); +void AddPointToBounds (vec3_t v, vec3_t mins, vec3_t maxs); +int VectorCompare (vec3_t v1, vec3_t v2); +vec_t VectorLength (vec3_t v); +void CrossProduct (vec3_t v1, vec3_t v2, vec3_t cross); +vec_t VectorNormalize (vec3_t v); // returns vector length +vec_t VectorNormalize2 (vec3_t v, vec3_t out); +void VectorInverse (vec3_t v); +void VectorScale (vec3_t in, vec_t scale, vec3_t out); +int Q_log2(int val); + +void R_ConcatRotations (float in1[3][3], float in2[3][3], float out[3][3]); +void R_ConcatTransforms (float in1[3][4], float in2[3][4], float out[3][4]); + +void AngleVectors (vec3_t angles, vec3_t forward, vec3_t right, vec3_t up); +int BoxOnPlaneSide (vec3_t emins, vec3_t emaxs, struct cplane_s *plane); +float anglemod(float a); +float LerpAngle (float a1, float a2, float frac); + +#define BOX_ON_PLANE_SIDE(emins, emaxs, p) \ + (((p)->type < 3)? \ + ( \ + ((p)->dist <= (emins)[(p)->type])? \ + 1 \ + : \ + ( \ + ((p)->dist >= (emaxs)[(p)->type])?\ + 2 \ + : \ + 3 \ + ) \ + ) \ + : \ + BoxOnPlaneSide( (emins), (emaxs), (p))) + +void ProjectPointOnPlane( vec3_t dst, const vec3_t p, const vec3_t normal ); +void PerpendicularVector( vec3_t dst, const vec3_t src ); +void RotatePointAroundVector( vec3_t dst, const vec3_t dir, const vec3_t point, float degrees ); + + +//============================================= + +char *COM_SkipPath (char *pathname); +void COM_StripExtension (char *in, char *out); +void COM_FileBase (char *in, char *out); +void COM_FilePath (char *in, char *out); +void COM_DefaultExtension (char *path, char *extension); + +char *COM_Parse (char **data_p); +// data is an in/out parm, returns a parsed out token + +void Com_sprintf (char *dest, int size, char *fmt, ...); + +void Com_PageInMemory (byte *buffer, int size); + +//============================================= + +// portable case insensitive compare +int Q_stricmp (char *s1, char *s2); +int Q_strcasecmp (char *s1, char *s2); +int Q_strncasecmp (char *s1, char *s2, int n); + +//============================================= + +short BigShort(short l); +short LittleShort(short l); +int BigLong (int l); +int LittleLong (int l); +float BigFloat (float l); +float LittleFloat (float l); + +void Swap_Init (void); +char *va(char *format, ...); + +//============================================= + +// +// key / value info strings +// +#define MAX_INFO_KEY 64 +#define MAX_INFO_VALUE 64 +#define MAX_INFO_STRING 512 + +char *Info_ValueForKey (char *s, char *key); +void Info_RemoveKey (char *s, char *key); +void Info_SetValueForKey (char *s, char *key, char *value); +qboolean Info_Validate (char *s); + +/* +============================================================== + +SYSTEM SPECIFIC + +============================================================== +*/ + +extern int curtime; // time returned by last Sys_Milliseconds + +int Sys_Milliseconds (void); +void Sys_Mkdir (char *path); + +// large block stack allocation routines +void *Hunk_Begin (int maxsize); +void *Hunk_Alloc (int size); +void Hunk_Free (void *buf); +int Hunk_End (void); + +// directory searching +#define SFF_ARCH 0x01 +#define SFF_HIDDEN 0x02 +#define SFF_RDONLY 0x04 +#define SFF_SUBDIR 0x08 +#define SFF_SYSTEM 0x10 + +/* +** pass in an attribute mask of things you wish to REJECT +*/ +char *Sys_FindFirst (char *path, unsigned musthave, unsigned canthave ); +char *Sys_FindNext ( unsigned musthave, unsigned canthave ); +void Sys_FindClose (void); + + +// this is only here so the functions in q_shared.c and q_shwin.c can link +void Sys_Error (char *error, ...); +void Com_Printf (char *msg, ...); + + +/* +========================================================== + +CVARS (console variables) + +========================================================== +*/ + +#ifndef CVAR +#define CVAR + +#define CVAR_ARCHIVE 1 // set to cause it to be saved to vars.rc +#define CVAR_USERINFO 2 // added to userinfo when changed +#define CVAR_SERVERINFO 4 // added to serverinfo when changed +#define CVAR_NOSET 8 // don't allow change from console at all, + // but can be set from the command line +#define CVAR_LATCH 16 // save changes until server restart + +// nothing outside the Cvar_*() functions should modify these fields! +typedef struct cvar_s +{ + char *name; + char *string; + char *latched_string; // for CVAR_LATCH vars + int flags; + qboolean modified; // set each time the cvar is changed + float value; + struct cvar_s *next; +} cvar_t; + +#endif // CVAR + +/* +============================================================== + +COLLISION DETECTION + +============================================================== +*/ + +// lower bits are stronger, and will eat weaker brushes completely +#define CONTENTS_SOLID 1 // an eye is never valid in a solid +#define CONTENTS_WINDOW 2 // translucent, but not watery +#define CONTENTS_AUX 4 +#define CONTENTS_LAVA 8 +#define CONTENTS_SLIME 16 +#define CONTENTS_WATER 32 +#define CONTENTS_MIST 64 +#define LAST_VISIBLE_CONTENTS 64 + +// remaining contents are non-visible, and don't eat brushes + +#define CONTENTS_AREAPORTAL 0x8000 + +#define CONTENTS_PLAYERCLIP 0x10000 +#define CONTENTS_MONSTERCLIP 0x20000 + +// currents can be added to any other contents, and may be mixed +#define CONTENTS_CURRENT_0 0x40000 +#define CONTENTS_CURRENT_90 0x80000 +#define CONTENTS_CURRENT_180 0x100000 +#define CONTENTS_CURRENT_270 0x200000 +#define CONTENTS_CURRENT_UP 0x400000 +#define CONTENTS_CURRENT_DOWN 0x800000 + +#define CONTENTS_ORIGIN 0x1000000 // removed before bsping an entity + +#define CONTENTS_MONSTER 0x2000000 // should never be on a brush, only in game +#define CONTENTS_DEADMONSTER 0x4000000 +#define CONTENTS_DETAIL 0x8000000 // brushes to be added after vis leafs +#define CONTENTS_TRANSLUCENT 0x10000000 // auto set if any surface has trans +#define CONTENTS_LADDER 0x20000000 + + + +#define SURF_LIGHT 0x1 // value will hold the light strength + +#define SURF_SLICK 0x2 // effects game physics + +#define SURF_SKY 0x4 // don't draw, but add to skybox +#define SURF_WARP 0x8 // turbulent water warp +#define SURF_TRANS33 0x10 +#define SURF_TRANS66 0x20 +#define SURF_FLOWING 0x40 // scroll towards angle +#define SURF_NODRAW 0x80 // don't bother referencing the texture + + + +// content masks +#define MASK_ALL (-1) +#define MASK_SOLID (CONTENTS_SOLID|CONTENTS_WINDOW) +#define MASK_PLAYERSOLID (CONTENTS_SOLID|CONTENTS_PLAYERCLIP|CONTENTS_WINDOW|CONTENTS_MONSTER) +#define MASK_DEADSOLID (CONTENTS_SOLID|CONTENTS_PLAYERCLIP|CONTENTS_WINDOW) +#define MASK_MONSTERSOLID (CONTENTS_SOLID|CONTENTS_MONSTERCLIP|CONTENTS_WINDOW|CONTENTS_MONSTER) +#define MASK_WATER (CONTENTS_WATER|CONTENTS_LAVA|CONTENTS_SLIME) +#define MASK_OPAQUE (CONTENTS_SOLID|CONTENTS_SLIME|CONTENTS_LAVA) +#define MASK_SHOT (CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_WINDOW|CONTENTS_DEADMONSTER) +#define MASK_CURRENT (CONTENTS_CURRENT_0|CONTENTS_CURRENT_90|CONTENTS_CURRENT_180|CONTENTS_CURRENT_270|CONTENTS_CURRENT_UP|CONTENTS_CURRENT_DOWN) + + +// gi.BoxEdicts() can return a list of either solid or trigger entities +// FIXME: eliminate AREA_ distinction? +#define AREA_SOLID 1 +#define AREA_TRIGGERS 2 + + +// plane_t structure +// !!! if this is changed, it must be changed in asm code too !!! +typedef struct cplane_s +{ + vec3_t normal; + float dist; + byte type; // for fast side tests + byte signbits; // signx + (signy<<1) + (signz<<1) + byte pad[2]; +} cplane_t; + +// structure offset for asm code +#define CPLANE_NORMAL_X 0 +#define CPLANE_NORMAL_Y 4 +#define CPLANE_NORMAL_Z 8 +#define CPLANE_DIST 12 +#define CPLANE_TYPE 16 +#define CPLANE_SIGNBITS 17 +#define CPLANE_PAD0 18 +#define CPLANE_PAD1 19 + +typedef struct cmodel_s +{ + vec3_t mins, maxs; + vec3_t origin; // for sounds or lights + int headnode; +} cmodel_t; + +typedef struct csurface_s +{ + char name[16]; + int flags; + int value; +} csurface_t; + +typedef struct mapsurface_s // used internally due to name len probs //ZOID +{ + csurface_t c; + char rname[32]; +} mapsurface_t; + +// a trace is returned when a box is swept through the world +typedef struct +{ + qboolean allsolid; // if true, plane is not valid + qboolean startsolid; // if true, the initial point was in a solid area + float fraction; // time completed, 1.0 = didn't hit anything + vec3_t endpos; // final position + cplane_t plane; // surface normal at impact + csurface_t *surface; // surface hit + int contents; // contents on other side of surface hit + struct edict_s *ent; // not set by CM_*() functions +} trace_t; + + + +// pmove_state_t is the information necessary for client side movement +// prediction +typedef enum +{ + // can accelerate and turn + PM_NORMAL, + PM_SPECTATOR, + // no acceleration or turning + PM_DEAD, + PM_GIB, // different bounding box + PM_FREEZE +} pmtype_t; + +// pmove->pm_flags +#define PMF_DUCKED 1 +#define PMF_JUMP_HELD 2 +#define PMF_ON_GROUND 4 +#define PMF_TIME_WATERJUMP 8 // pm_time is waterjump +#define PMF_TIME_LAND 16 // pm_time is time before rejump +#define PMF_TIME_TELEPORT 32 // pm_time is non-moving time +#define PMF_NO_PREDICTION 64 // temporarily disables prediction (used for grappling hook) + +// this structure needs to be communicated bit-accurate +// from the server to the client to guarantee that +// prediction stays in sync, so no floats are used. +// if any part of the game code modifies this struct, it +// will result in a prediction error of some degree. +typedef struct +{ + pmtype_t pm_type; + + short origin[3]; // 12.3 + short velocity[3]; // 12.3 + byte pm_flags; // ducked, jump_held, etc + byte pm_time; // each unit = 8 ms + short gravity; + short delta_angles[3]; // add to command angles to get view direction + // changed by spawns, rotating objects, and teleporters +} pmove_state_t; + + +// +// button bits +// +#define BUTTON_ATTACK 1 +#define BUTTON_USE 2 +#define BUTTON_ANY 128 // any key whatsoever + + +// usercmd_t is sent to the server each client frame +typedef struct usercmd_s +{ + byte msec; + byte buttons; + short angles[3]; + short forwardmove, sidemove, upmove; + byte impulse; // remove? + byte lightlevel; // light level the player is standing on +} usercmd_t; + + +#define MAXTOUCH 32 +typedef struct +{ + // state (in / out) + pmove_state_t s; + + // command (in) + usercmd_t cmd; + qboolean snapinitial; // if s has been changed outside pmove + + // results (out) + int numtouch; + struct edict_s *touchents[MAXTOUCH]; + + vec3_t viewangles; // clamped + float viewheight; + + vec3_t mins, maxs; // bounding box size + + struct edict_s *groundentity; + int watertype; + int waterlevel; + + // callbacks to test the world + trace_t (*trace) (vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end); + int (*pointcontents) (vec3_t point); +} pmove_t; + + +// entity_state_t->effects +// Effects are things handled on the client side (lights, particles, frame animations) +// that happen constantly on the given entity. +// An entity that has effects will be sent to the client +// even if it has a zero index model. +#define EF_ROTATE 0x00000001 // rotate (bonus items) +#define EF_GIB 0x00000002 // leave a trail +#define EF_BLASTER 0x00000008 // redlight + trail +#define EF_ROCKET 0x00000010 // redlight + trail +#define EF_GRENADE 0x00000020 +#define EF_HYPERBLASTER 0x00000040 +#define EF_BFG 0x00000080 +#define EF_COLOR_SHELL 0x00000100 +#define EF_POWERSCREEN 0x00000200 +#define EF_ANIM01 0x00000400 // automatically cycle between frames 0 and 1 at 2 hz +#define EF_ANIM23 0x00000800 // automatically cycle between frames 2 and 3 at 2 hz +#define EF_ANIM_ALL 0x00001000 // automatically cycle through all frames at 2hz +#define EF_ANIM_ALLFAST 0x00002000 // automatically cycle through all frames at 10hz +#define EF_FLIES 0x00004000 +#define EF_QUAD 0x00008000 +#define EF_PENT 0x00010000 +#define EF_TELEPORTER 0x00020000 // particle fountain +#define EF_FLAG1 0x00040000 +#define EF_FLAG2 0x00080000 +// RAFAEL +#define EF_IONRIPPER 0x00100000 +#define EF_GREENGIB 0x00200000 +#define EF_BLUEHYPERBLASTER 0x00400000 +#define EF_SPINNINGLIGHTS 0x00800000 +#define EF_PLASMA 0x01000000 +#define EF_TRAP 0x02000000 + +//ROGUE +#define EF_TRACKER 0x04000000 +#define EF_DOUBLE 0x08000000 +#define EF_SPHERETRANS 0x10000000 +#define EF_TAGTRAIL 0x20000000 +#define EF_HALF_DAMAGE 0x40000000 +#define EF_TRACKERTRAIL 0x80000000 +//ROGUE + +// entity_state_t->renderfx flags +#define RF_MINLIGHT 1 // allways have some light (viewmodel) +#define RF_VIEWERMODEL 2 // don't draw through eyes, only mirrors +#define RF_WEAPONMODEL 4 // only draw through eyes +#define RF_FULLBRIGHT 8 // allways draw full intensity +#define RF_DEPTHHACK 16 // for view weapon Z crunching +#define RF_TRANSLUCENT 32 +#define RF_FRAMELERP 64 +#define RF_BEAM 128 +#define RF_CUSTOMSKIN 256 // skin is an index in image_precache +#define RF_GLOW 512 // pulse lighting for bonus items +#define RF_SHELL_RED 1024 +#define RF_SHELL_GREEN 2048 +#define RF_SHELL_BLUE 4096 + +//ROGUE +#define RF_IR_VISIBLE 0x00008000 // 32768 +#define RF_SHELL_DOUBLE 0x00010000 // 65536 +#define RF_SHELL_HALF_DAM 0x00020000 +#define RF_USE_DISGUISE 0x00040000 +//ROGUE + +// player_state_t->refdef flags +#define RDF_UNDERWATER 1 // warp the screen as apropriate +#define RDF_NOWORLDMODEL 2 // used for player configuration screen + +//ROGUE +#define RDF_IRGOGGLES 4 +#define RDF_UVGOGGLES 8 +//ROGUE + +// +// muzzle flashes / player effects +// +#define MZ_BLASTER 0 +#define MZ_MACHINEGUN 1 +#define MZ_SHOTGUN 2 +#define MZ_CHAINGUN1 3 +#define MZ_CHAINGUN2 4 +#define MZ_CHAINGUN3 5 +#define MZ_RAILGUN 6 +#define MZ_ROCKET 7 +#define MZ_GRENADE 8 +#define MZ_LOGIN 9 +#define MZ_LOGOUT 10 +#define MZ_RESPAWN 11 +#define MZ_BFG 12 +#define MZ_SSHOTGUN 13 +#define MZ_HYPERBLASTER 14 +#define MZ_ITEMRESPAWN 15 +// RAFAEL +#define MZ_IONRIPPER 16 +#define MZ_BLUEHYPERBLASTER 17 +#define MZ_PHALANX 18 +#define MZ_SILENCED 128 // bit flag ORed with one of the above numbers + +//ROGUE +#define MZ_ETF_RIFLE 30 +#define MZ_UNUSED 31 +#define MZ_SHOTGUN2 32 +#define MZ_HEATBEAM 33 +#define MZ_BLASTER2 34 +#define MZ_TRACKER 35 +#define MZ_NUKE1 36 +#define MZ_NUKE2 37 +#define MZ_NUKE4 38 +#define MZ_NUKE8 39 +//ROGUE + +// +// monster muzzle flashes +// +#define MZ2_TANK_BLASTER_1 1 +#define MZ2_TANK_BLASTER_2 2 +#define MZ2_TANK_BLASTER_3 3 +#define MZ2_TANK_MACHINEGUN_1 4 +#define MZ2_TANK_MACHINEGUN_2 5 +#define MZ2_TANK_MACHINEGUN_3 6 +#define MZ2_TANK_MACHINEGUN_4 7 +#define MZ2_TANK_MACHINEGUN_5 8 +#define MZ2_TANK_MACHINEGUN_6 9 +#define MZ2_TANK_MACHINEGUN_7 10 +#define MZ2_TANK_MACHINEGUN_8 11 +#define MZ2_TANK_MACHINEGUN_9 12 +#define MZ2_TANK_MACHINEGUN_10 13 +#define MZ2_TANK_MACHINEGUN_11 14 +#define MZ2_TANK_MACHINEGUN_12 15 +#define MZ2_TANK_MACHINEGUN_13 16 +#define MZ2_TANK_MACHINEGUN_14 17 +#define MZ2_TANK_MACHINEGUN_15 18 +#define MZ2_TANK_MACHINEGUN_16 19 +#define MZ2_TANK_MACHINEGUN_17 20 +#define MZ2_TANK_MACHINEGUN_18 21 +#define MZ2_TANK_MACHINEGUN_19 22 +#define MZ2_TANK_ROCKET_1 23 +#define MZ2_TANK_ROCKET_2 24 +#define MZ2_TANK_ROCKET_3 25 + +#define MZ2_INFANTRY_MACHINEGUN_1 26 +#define MZ2_INFANTRY_MACHINEGUN_2 27 +#define MZ2_INFANTRY_MACHINEGUN_3 28 +#define MZ2_INFANTRY_MACHINEGUN_4 29 +#define MZ2_INFANTRY_MACHINEGUN_5 30 +#define MZ2_INFANTRY_MACHINEGUN_6 31 +#define MZ2_INFANTRY_MACHINEGUN_7 32 +#define MZ2_INFANTRY_MACHINEGUN_8 33 +#define MZ2_INFANTRY_MACHINEGUN_9 34 +#define MZ2_INFANTRY_MACHINEGUN_10 35 +#define MZ2_INFANTRY_MACHINEGUN_11 36 +#define MZ2_INFANTRY_MACHINEGUN_12 37 +#define MZ2_INFANTRY_MACHINEGUN_13 38 + +#define MZ2_SOLDIER_BLASTER_1 39 +#define MZ2_SOLDIER_BLASTER_2 40 +#define MZ2_SOLDIER_SHOTGUN_1 41 +#define MZ2_SOLDIER_SHOTGUN_2 42 +#define MZ2_SOLDIER_MACHINEGUN_1 43 +#define MZ2_SOLDIER_MACHINEGUN_2 44 + +#define MZ2_GUNNER_MACHINEGUN_1 45 +#define MZ2_GUNNER_MACHINEGUN_2 46 +#define MZ2_GUNNER_MACHINEGUN_3 47 +#define MZ2_GUNNER_MACHINEGUN_4 48 +#define MZ2_GUNNER_MACHINEGUN_5 49 +#define MZ2_GUNNER_MACHINEGUN_6 50 +#define MZ2_GUNNER_MACHINEGUN_7 51 +#define MZ2_GUNNER_MACHINEGUN_8 52 +#define MZ2_GUNNER_GRENADE_1 53 +#define MZ2_GUNNER_GRENADE_2 54 +#define MZ2_GUNNER_GRENADE_3 55 +#define MZ2_GUNNER_GRENADE_4 56 + +#define MZ2_CHICK_ROCKET_1 57 + +#define MZ2_FLYER_BLASTER_1 58 +#define MZ2_FLYER_BLASTER_2 59 + +#define MZ2_MEDIC_BLASTER_1 60 + +#define MZ2_GLADIATOR_RAILGUN_1 61 + +#define MZ2_HOVER_BLASTER_1 62 + +#define MZ2_ACTOR_MACHINEGUN_1 63 + +#define MZ2_SUPERTANK_MACHINEGUN_1 64 +#define MZ2_SUPERTANK_MACHINEGUN_2 65 +#define MZ2_SUPERTANK_MACHINEGUN_3 66 +#define MZ2_SUPERTANK_MACHINEGUN_4 67 +#define MZ2_SUPERTANK_MACHINEGUN_5 68 +#define MZ2_SUPERTANK_MACHINEGUN_6 69 +#define MZ2_SUPERTANK_ROCKET_1 70 +#define MZ2_SUPERTANK_ROCKET_2 71 +#define MZ2_SUPERTANK_ROCKET_3 72 + +#define MZ2_BOSS2_MACHINEGUN_L1 73 +#define MZ2_BOSS2_MACHINEGUN_L2 74 +#define MZ2_BOSS2_MACHINEGUN_L3 75 +#define MZ2_BOSS2_MACHINEGUN_L4 76 +#define MZ2_BOSS2_MACHINEGUN_L5 77 +#define MZ2_BOSS2_ROCKET_1 78 +#define MZ2_BOSS2_ROCKET_2 79 +#define MZ2_BOSS2_ROCKET_3 80 +#define MZ2_BOSS2_ROCKET_4 81 + +#define MZ2_FLOAT_BLASTER_1 82 + +#define MZ2_SOLDIER_BLASTER_3 83 +#define MZ2_SOLDIER_SHOTGUN_3 84 +#define MZ2_SOLDIER_MACHINEGUN_3 85 +#define MZ2_SOLDIER_BLASTER_4 86 +#define MZ2_SOLDIER_SHOTGUN_4 87 +#define MZ2_SOLDIER_MACHINEGUN_4 88 +#define MZ2_SOLDIER_BLASTER_5 89 +#define MZ2_SOLDIER_SHOTGUN_5 90 +#define MZ2_SOLDIER_MACHINEGUN_5 91 +#define MZ2_SOLDIER_BLASTER_6 92 +#define MZ2_SOLDIER_SHOTGUN_6 93 +#define MZ2_SOLDIER_MACHINEGUN_6 94 +#define MZ2_SOLDIER_BLASTER_7 95 +#define MZ2_SOLDIER_SHOTGUN_7 96 +#define MZ2_SOLDIER_MACHINEGUN_7 97 +#define MZ2_SOLDIER_BLASTER_8 98 +#define MZ2_SOLDIER_SHOTGUN_8 99 +#define MZ2_SOLDIER_MACHINEGUN_8 100 + +// --- Xian shit below --- +#define MZ2_MAKRON_BFG 101 +#define MZ2_MAKRON_BLASTER_1 102 +#define MZ2_MAKRON_BLASTER_2 103 +#define MZ2_MAKRON_BLASTER_3 104 +#define MZ2_MAKRON_BLASTER_4 105 +#define MZ2_MAKRON_BLASTER_5 106 +#define MZ2_MAKRON_BLASTER_6 107 +#define MZ2_MAKRON_BLASTER_7 108 +#define MZ2_MAKRON_BLASTER_8 109 +#define MZ2_MAKRON_BLASTER_9 110 +#define MZ2_MAKRON_BLASTER_10 111 +#define MZ2_MAKRON_BLASTER_11 112 +#define MZ2_MAKRON_BLASTER_12 113 +#define MZ2_MAKRON_BLASTER_13 114 +#define MZ2_MAKRON_BLASTER_14 115 +#define MZ2_MAKRON_BLASTER_15 116 +#define MZ2_MAKRON_BLASTER_16 117 +#define MZ2_MAKRON_BLASTER_17 118 +#define MZ2_MAKRON_RAILGUN_1 119 +#define MZ2_JORG_MACHINEGUN_L1 120 +#define MZ2_JORG_MACHINEGUN_L2 121 +#define MZ2_JORG_MACHINEGUN_L3 122 +#define MZ2_JORG_MACHINEGUN_L4 123 +#define MZ2_JORG_MACHINEGUN_L5 124 +#define MZ2_JORG_MACHINEGUN_L6 125 +#define MZ2_JORG_MACHINEGUN_R1 126 +#define MZ2_JORG_MACHINEGUN_R2 127 +#define MZ2_JORG_MACHINEGUN_R3 128 +#define MZ2_JORG_MACHINEGUN_R4 129 +#define MZ2_JORG_MACHINEGUN_R5 130 +#define MZ2_JORG_MACHINEGUN_R6 131 +#define MZ2_JORG_BFG_1 132 +#define MZ2_BOSS2_MACHINEGUN_R1 133 +#define MZ2_BOSS2_MACHINEGUN_R2 134 +#define MZ2_BOSS2_MACHINEGUN_R3 135 +#define MZ2_BOSS2_MACHINEGUN_R4 136 +#define MZ2_BOSS2_MACHINEGUN_R5 137 + +//ROGUE +#define MZ2_CARRIER_MACHINEGUN_L1 138 +#define MZ2_CARRIER_MACHINEGUN_R1 139 +#define MZ2_CARRIER_GRENADE 140 +#define MZ2_TURRET_MACHINEGUN 141 +#define MZ2_TURRET_ROCKET 142 +#define MZ2_TURRET_BLASTER 143 +#define MZ2_STALKER_BLASTER 144 +#define MZ2_DAEDALUS_BLASTER 145 +#define MZ2_MEDIC_BLASTER_2 146 +#define MZ2_CARRIER_RAILGUN 147 +#define MZ2_WIDOW_DISRUPTOR 148 +#define MZ2_WIDOW_BLASTER 149 +#define MZ2_WIDOW_RAIL 150 +#define MZ2_WIDOW_PLASMABEAM 151 // PMM - not used +#define MZ2_CARRIER_MACHINEGUN_L2 152 +#define MZ2_CARRIER_MACHINEGUN_R2 153 +#define MZ2_WIDOW_RAIL_LEFT 154 +#define MZ2_WIDOW_RAIL_RIGHT 155 +#define MZ2_WIDOW_BLASTER_SWEEP1 156 +#define MZ2_WIDOW_BLASTER_SWEEP2 157 +#define MZ2_WIDOW_BLASTER_SWEEP3 158 +#define MZ2_WIDOW_BLASTER_SWEEP4 159 +#define MZ2_WIDOW_BLASTER_SWEEP5 160 +#define MZ2_WIDOW_BLASTER_SWEEP6 161 +#define MZ2_WIDOW_BLASTER_SWEEP7 162 +#define MZ2_WIDOW_BLASTER_SWEEP8 163 +#define MZ2_WIDOW_BLASTER_SWEEP9 164 +#define MZ2_WIDOW_BLASTER_100 165 +#define MZ2_WIDOW_BLASTER_90 166 +#define MZ2_WIDOW_BLASTER_80 167 +#define MZ2_WIDOW_BLASTER_70 168 +#define MZ2_WIDOW_BLASTER_60 169 +#define MZ2_WIDOW_BLASTER_50 170 +#define MZ2_WIDOW_BLASTER_40 171 +#define MZ2_WIDOW_BLASTER_30 172 +#define MZ2_WIDOW_BLASTER_20 173 +#define MZ2_WIDOW_BLASTER_10 174 +#define MZ2_WIDOW_BLASTER_0 175 +#define MZ2_WIDOW_BLASTER_10L 176 +#define MZ2_WIDOW_BLASTER_20L 177 +#define MZ2_WIDOW_BLASTER_30L 178 +#define MZ2_WIDOW_BLASTER_40L 179 +#define MZ2_WIDOW_BLASTER_50L 180 +#define MZ2_WIDOW_BLASTER_60L 181 +#define MZ2_WIDOW_BLASTER_70L 182 +#define MZ2_WIDOW_RUN_1 183 +#define MZ2_WIDOW_RUN_2 184 +#define MZ2_WIDOW_RUN_3 185 +#define MZ2_WIDOW_RUN_4 186 +#define MZ2_WIDOW_RUN_5 187 +#define MZ2_WIDOW_RUN_6 188 +#define MZ2_WIDOW_RUN_7 189 +#define MZ2_WIDOW_RUN_8 190 +#define MZ2_CARRIER_ROCKET_1 191 +#define MZ2_CARRIER_ROCKET_2 192 +#define MZ2_CARRIER_ROCKET_3 193 +#define MZ2_CARRIER_ROCKET_4 194 +#define MZ2_WIDOW2_BEAMER_1 195 +#define MZ2_WIDOW2_BEAMER_2 196 +#define MZ2_WIDOW2_BEAMER_3 197 +#define MZ2_WIDOW2_BEAMER_4 198 +#define MZ2_WIDOW2_BEAMER_5 199 +#define MZ2_WIDOW2_BEAM_SWEEP_1 200 +#define MZ2_WIDOW2_BEAM_SWEEP_2 201 +#define MZ2_WIDOW2_BEAM_SWEEP_3 202 +#define MZ2_WIDOW2_BEAM_SWEEP_4 203 +#define MZ2_WIDOW2_BEAM_SWEEP_5 204 +#define MZ2_WIDOW2_BEAM_SWEEP_6 205 +#define MZ2_WIDOW2_BEAM_SWEEP_7 206 +#define MZ2_WIDOW2_BEAM_SWEEP_8 207 +#define MZ2_WIDOW2_BEAM_SWEEP_9 208 +#define MZ2_WIDOW2_BEAM_SWEEP_10 209 +#define MZ2_WIDOW2_BEAM_SWEEP_11 210 + +// ROGUE + +extern vec3_t monster_flash_offset []; + + +// temp entity events +// +// Temp entity events are for things that happen +// at a location seperate from any existing entity. +// Temporary entity messages are explicitly constructed +// and broadcast. +typedef enum +{ + TE_GUNSHOT, + TE_BLOOD, + TE_BLASTER, + TE_RAILTRAIL, + TE_SHOTGUN, + TE_EXPLOSION1, + TE_EXPLOSION2, + TE_ROCKET_EXPLOSION, + TE_GRENADE_EXPLOSION, + TE_SPARKS, + TE_SPLASH, + TE_BUBBLETRAIL, + TE_SCREEN_SPARKS, + TE_SHIELD_SPARKS, + TE_BULLET_SPARKS, + TE_LASER_SPARKS, + TE_PARASITE_ATTACK, + TE_ROCKET_EXPLOSION_WATER, + TE_GRENADE_EXPLOSION_WATER, + TE_MEDIC_CABLE_ATTACK, + TE_BFG_EXPLOSION, + TE_BFG_BIGEXPLOSION, + TE_BOSSTPORT, // used as '22' in a map, so DON'T RENUMBER!!! + TE_BFG_LASER, + TE_GRAPPLE_CABLE, + TE_WELDING_SPARKS, + TE_GREENBLOOD, + TE_BLUEHYPERBLASTER, + TE_PLASMA_EXPLOSION, + TE_TUNNEL_SPARKS, +//ROGUE + TE_BLASTER2, + TE_RAILTRAIL2, + TE_FLAME, + TE_LIGHTNING, + TE_DEBUGTRAIL, + TE_PLAIN_EXPLOSION, + TE_FLASHLIGHT, + TE_FORCEWALL, + TE_HEATBEAM, + TE_MONSTER_HEATBEAM, + TE_STEAM, + TE_BUBBLETRAIL2, + TE_MOREBLOOD, + TE_HEATBEAM_SPARKS, + TE_HEATBEAM_STEAM, + TE_CHAINFIST_SMOKE, + TE_ELECTRIC_SPARKS, + TE_TRACKER_EXPLOSION, + TE_TELEPORT_EFFECT, + TE_DBALL_GOAL, + TE_WIDOWBEAMOUT, + TE_NUKEBLAST, + TE_WIDOWSPLASH, + TE_EXPLOSION1_BIG, + TE_EXPLOSION1_NP, + TE_FLECHETTE +//ROGUE +} temp_event_t; + +#define SPLASH_UNKNOWN 0 +#define SPLASH_SPARKS 1 +#define SPLASH_BLUE_WATER 2 +#define SPLASH_BROWN_WATER 3 +#define SPLASH_SLIME 4 +#define SPLASH_LAVA 5 +#define SPLASH_BLOOD 6 + + +// sound channels +// channel 0 never willingly overrides +// other channels (1-7) allways override a playing sound on that channel +#define CHAN_AUTO 0 +#define CHAN_WEAPON 1 +#define CHAN_VOICE 2 +#define CHAN_ITEM 3 +#define CHAN_BODY 4 +// modifier flags +#define CHAN_NO_PHS_ADD 8 // send to all clients, not just ones in PHS (ATTN 0 will also do this) +#define CHAN_RELIABLE 16 // send by reliable message, not datagram + + +// sound attenuation values +#define ATTN_NONE 0 // full volume the entire level +#define ATTN_NORM 1 +#define ATTN_IDLE 2 +#define ATTN_STATIC 3 // diminish very rapidly with distance + + +// player_state->stats[] indexes +#define STAT_HEALTH_ICON 0 +#define STAT_HEALTH 1 +#define STAT_AMMO_ICON 2 +#define STAT_AMMO 3 +#define STAT_ARMOR_ICON 4 +#define STAT_ARMOR 5 +#define STAT_SELECTED_ICON 6 +#define STAT_PICKUP_ICON 7 +#define STAT_PICKUP_STRING 8 +#define STAT_TIMER_ICON 9 +#define STAT_TIMER 10 +#define STAT_HELPICON 11 +#define STAT_SELECTED_ITEM 12 +#define STAT_LAYOUTS 13 +#define STAT_FRAGS 14 +#define STAT_FLASHES 15 // cleared each frame, 1 = health, 2 = armor +#define STAT_CHASE 16 +#define STAT_SPECTATOR 17 + +#define MAX_STATS 32 + + +// dmflags->value flags +#define DF_NO_HEALTH 0x00000001 // 1 +#define DF_NO_ITEMS 0x00000002 // 2 +#define DF_WEAPONS_STAY 0x00000004 // 4 +#define DF_NO_FALLING 0x00000008 // 8 +#define DF_INSTANT_ITEMS 0x00000010 // 16 +#define DF_SAME_LEVEL 0x00000020 // 32 +#define DF_SKINTEAMS 0x00000040 // 64 +#define DF_MODELTEAMS 0x00000080 // 128 +#define DF_NO_FRIENDLY_FIRE 0x00000100 // 256 +#define DF_SPAWN_FARTHEST 0x00000200 // 512 +#define DF_FORCE_RESPAWN 0x00000400 // 1024 +#define DF_NO_ARMOR 0x00000800 // 2048 +#define DF_ALLOW_EXIT 0x00001000 // 4096 +#define DF_INFINITE_AMMO 0x00002000 // 8192 +#define DF_QUAD_DROP 0x00004000 // 16384 +#define DF_FIXED_FOV 0x00008000 // 32768 + +// RAFAEL +#define DF_QUADFIRE_DROP 0x00010000 // 65536 + +//ROGUE +#define DF_NO_MINES 0x00020000 +#define DF_NO_STACK_DOUBLE 0x00040000 +#define DF_NO_NUKES 0x00080000 +#define DF_NO_SPHERES 0x00100000 +//ROGUE + +/* +ROGUE - VERSIONS +1234 08/13/1998 Activision +1235 08/14/1998 Id Software +1236 08/15/1998 Steve Tietze +1237 08/15/1998 Phil Dobranski +1238 08/15/1998 John Sheley +1239 08/17/1998 Barrett Alexander +1230 08/17/1998 Brandon Fish +1245 08/17/1998 Don MacAskill +1246 08/17/1998 David "Zoid" Kirsch +1247 08/17/1998 Manu Smith +1248 08/17/1998 Geoff Scully +1249 08/17/1998 Andy Van Fossen +1240 08/20/1998 Activision Build 2 +1256 08/20/1998 Ranger Clan +1257 08/20/1998 Ensemble Studios +1258 08/21/1998 Robert Duffy +1259 08/21/1998 Stephen Seachord +1250 08/21/1998 Stephen Heaslip +1267 08/21/1998 Samir Sandesara +1268 08/21/1998 Oliver Wyman +1269 08/21/1998 Steven Marchegiano +1260 08/21/1998 Build #2 for Nihilistic +1278 08/21/1998 Build #2 for Ensemble + +9999 08/20/1998 Internal Use +*/ +#define ROGUE_VERSION_ID 1278 + +#define ROGUE_VERSION_STRING "08/21/1998 Beta 2 for Ensemble" + +// ROGUE +/* +========================================================== + + ELEMENTS COMMUNICATED ACROSS THE NET + +========================================================== +*/ + +#define ANGLE2SHORT(x) ((int)((x)*65536/360) & 65535) +#define SHORT2ANGLE(x) ((x)*(360.0/65536)) + + +// +// config strings are a general means of communication from +// the server to all connected clients. +// Each config string can be at most MAX_QPATH characters. +// +#define CS_NAME 0 +#define CS_CDTRACK 1 +#define CS_SKY 2 +#define CS_SKYAXIS 3 // %f %f %f format +#define CS_SKYROTATE 4 +#define CS_STATUSBAR 5 // display program string + +#define CS_AIRACCEL 29 // air acceleration control +#define CS_MAXCLIENTS 30 +#define CS_MAPCHECKSUM 31 // for catching cheater maps + +#define CS_MODELS 32 +#define CS_SOUNDS (CS_MODELS+MAX_MODELS) +#define CS_IMAGES (CS_SOUNDS+MAX_SOUNDS) +#define CS_LIGHTS (CS_IMAGES+MAX_IMAGES) +#define CS_ITEMS (CS_LIGHTS+MAX_LIGHTSTYLES) +#define CS_PLAYERSKINS (CS_ITEMS+MAX_ITEMS) +#define CS_GENERAL (CS_PLAYERSKINS+MAX_CLIENTS) +#define MAX_CONFIGSTRINGS (CS_GENERAL+MAX_GENERAL) + + +//============================================== + + +// entity_state_t->event values +// ertity events are for effects that take place reletive +// to an existing entities origin. Very network efficient. +// All muzzle flashes really should be converted to events... +typedef enum +{ + EV_NONE, + EV_ITEM_RESPAWN, + EV_FOOTSTEP, + EV_FALLSHORT, + EV_FALL, + EV_FALLFAR, + EV_PLAYER_TELEPORT, + EV_OTHER_TELEPORT +} entity_event_t; + + +// entity_state_t is the information conveyed from the server +// in an update message about entities that the client will +// need to render in some way +typedef struct entity_state_s +{ + int number; // edict index + + vec3_t origin; + vec3_t angles; + vec3_t old_origin; // for lerping + int modelindex; + int modelindex2, modelindex3, modelindex4; // weapons, CTF flags, etc + int frame; + int skinnum; + unsigned int effects; // PGM - we're filling it, so it needs to be unsigned + int renderfx; + int solid; // for client side prediction, 8*(bits 0-4) is x/y radius + // 8*(bits 5-9) is z down distance, 8(bits10-15) is z up + // gi.linkentity sets this properly + int sound; // for looping sounds, to guarantee shutoff + int event; // impulse events -- muzzle flashes, footsteps, etc + // events only go out for a single frame, they + // are automatically cleared each frame +} entity_state_t; + +//============================================== + + +// player_state_t is the information needed in addition to pmove_state_t +// to rendered a view. There will only be 10 player_state_t sent each second, +// but the number of pmove_state_t changes will be reletive to client +// frame rates +typedef struct +{ + pmove_state_t pmove; // for prediction + + // these fields do not need to be communicated bit-precise + + vec3_t viewangles; // for fixed views + vec3_t viewoffset; // add to pmovestate->origin + vec3_t kick_angles; // add to view direction to get render angles + // set by weapon kicks, pain effects, etc + + vec3_t gunangles; + vec3_t gunoffset; + int gunindex; + int gunframe; + + float blend[4]; // rgba full screen effect + + float fov; // horizontal field of view + + int rdflags; // refdef flags + + short stats[MAX_STATS]; // fast status bar updates +} player_state_t; + + +// ================== +// PGM +#define VIDREF_GL 1 +#define VIDREF_SOFT 2 +#define VIDREF_OTHER 3 + +extern int vidref_val; +// PGM +// ================== diff --git a/original/rogue/dm_ball.c b/original/rogue/dm_ball.c new file mode 100644 index 0000000..0a8d842 --- /dev/null +++ b/original/rogue/dm_ball.c @@ -0,0 +1,680 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// dm_ball.c +// pmack +// june 98 + +#include "g_local.h" + +// defines + +#define DBALL_GOAL_TEAM1 0x0001 +#define DBALL_GOAL_TEAM2 0x0002 + +// globals + +edict_t *dball_ball_entity = NULL; +int dball_ball_startpt_count; +int dball_team1_goalscore; +int dball_team2_goalscore; + +cvar_t *dball_team1_skin; +cvar_t *dball_team2_skin; +cvar_t *goallimit; + +// prototypes + +extern void EndDMLevel (void); +extern void ClientUserinfoChanged (edict_t *ent, char *userinfo); +extern void SelectSpawnPoint (edict_t *ent, vec3_t origin, vec3_t angles); +extern float PlayersRangeFromSpot (edict_t *spot); + +void DBall_BallDie (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point); +void DBall_BallRespawn (edict_t *self); + +// ************************** +// Game rules +// ************************** + +int DBall_CheckDMRules (void) +{ + if(goallimit && goallimit->value) + { + if(dball_team1_goalscore >= goallimit->value) + gi.bprintf (PRINT_HIGH, "Team 1 Wins.\n"); + else if(dball_team2_goalscore >= goallimit->value) + gi.bprintf (PRINT_HIGH, "Team 2 Wins.\n"); + else + return 0; + + EndDMLevel (); + return 1; + } + + return 0; +} + +//================== +//================== +void DBall_ClientBegin (edict_t *ent) +{ + int team1, team2, unassigned; + edict_t *other; + char *p; + static char value[512]; + int j; + + team1 = 0; + team2 = 0; + unassigned = 0; + + for (j = 1; j <= game.maxclients; j++) + { + other = &g_edicts[j]; + if (!other->inuse) + continue; + if (!other->client) + continue; + if (other == ent) // don't count the new player + continue; + + strcpy(value, Info_ValueForKey (other->client->pers.userinfo, "skin")); + p = strchr(value, '/'); + if (p) + { + if(!strcmp(dball_team1_skin->string, value)) + team1++; + else if(!strcmp(dball_team2_skin->string, value)) + team2++; + else + unassigned++; + } + else + unassigned++; + } + + if(team1 > team2) + { + gi.dprintf("assigned to team 2\n"); + Info_SetValueForKey(ent->client->pers.userinfo, "skin", dball_team2_skin->string); + } + else + { + gi.dprintf("assigned to team 1\n"); + Info_SetValueForKey(ent->client->pers.userinfo, "skin", dball_team1_skin->string); + } + + ClientUserinfoChanged(ent, ent->client->pers.userinfo); + + if(unassigned) + gi.dprintf("%d unassigned players present!\n", unassigned); +} + +//================== +//================== +void DBall_SelectSpawnPoint (edict_t *ent, vec3_t origin, vec3_t angles) +{ + edict_t *bestspot; + float bestdistance, bestplayerdistance; + edict_t *spot; + char *spottype; + char skin[512]; + + strcpy(skin, Info_ValueForKey (ent->client->pers.userinfo, "skin")); + if(!strcmp(dball_team1_skin->string, skin)) + spottype = "dm_dball_team1_start"; + else if(!strcmp(dball_team2_skin->string, skin)) + spottype = "dm_dball_team2_start"; + else + spottype = "info_player_deathmatch"; + + spot = NULL; + bestspot = NULL; + bestdistance = 0; + while ((spot = G_Find (spot, FOFS(classname), spottype)) != NULL) + { + bestplayerdistance = PlayersRangeFromSpot (spot); + + if (bestplayerdistance > bestdistance) + { + bestspot = spot; + bestdistance = bestplayerdistance; + } + } + + if (bestspot) + { + VectorCopy (bestspot->s.origin, origin); + origin[2] += 9; + VectorCopy (bestspot->s.angles, angles); + return; + } + + // if we didn't find an appropriate spawnpoint, just + // call the standard one. + SelectSpawnPoint(ent, origin, angles); +} + +//================== +//================== +void DBall_GameInit (void) +{ + // we don't want a minimum speed for friction to take effect. + // this will allow any knockback to move stuff. + sv_stopspeed->value = 0; + dball_team1_goalscore = 0; + dball_team2_goalscore = 0; + + dmflags->value = (int)dmflags->value | DF_NO_MINES | DF_NO_NUKES | DF_NO_STACK_DOUBLE | + DF_NO_FRIENDLY_FIRE | DF_SKINTEAMS; + + dball_team1_skin = gi.cvar ("dball_team1_skin", "male/ctf_r", 0); + dball_team2_skin = gi.cvar ("dball_team2_skin", "male/ctf_b", 0); + goallimit = gi.cvar ("goallimit", "0", 0); +} + +//================== +//================== +void DBall_PostInitSetup (void) +{ + edict_t *e; + + e=NULL; + // turn teleporter destinations nonsolid. + while(e = G_Find (e, FOFS(classname), "misc_teleporter_dest")) + { + e->solid = SOLID_NOT; + gi.linkentity (e); + } + + // count the ball start points + dball_ball_startpt_count = 0; + e=NULL; + while(e = G_Find (e, FOFS(classname), "dm_dball_ball_start")) + { + dball_ball_startpt_count++; + } + + if(dball_ball_startpt_count == 0) + gi.dprintf("No Deathball start points!\n"); +} + +//================== +// DBall_ChangeDamage - half damage between players. full if it involves +// the ball entity +//================== +int DBall_ChangeDamage (edict_t *targ, edict_t *attacker, int damage, int mod) +{ + // cut player -> ball damage to 1 + if (targ == dball_ball_entity) + return 1; + + // damage player -> player is halved + if (attacker != dball_ball_entity) + return damage / 2; + + return damage; +} + +//================== +//================== +int DBall_ChangeKnockback (edict_t *targ, edict_t *attacker, int knockback, int mod) +{ + if(targ != dball_ball_entity) + return knockback; + + if(knockback < 1) + { + // FIXME - these don't account for quad/double + if(mod == MOD_ROCKET) // rocket + knockback = 70; + else if(mod == MOD_BFG_EFFECT) // bfg + knockback = 90; + else + gi.dprintf ("zero knockback, mod %d\n", mod); + } + else + { + // FIXME - change this to an array? + switch(mod) + { + case MOD_BLASTER: + knockback *= 3; + break; + case MOD_SHOTGUN: + knockback = (knockback * 3) / 8; + break; + case MOD_SSHOTGUN: + knockback = knockback / 3; + break; + case MOD_MACHINEGUN: + knockback = (knockback * 3) / 2; + break; + case MOD_HYPERBLASTER: + knockback *= 4; + break; + case MOD_GRENADE: + case MOD_HANDGRENADE: + case MOD_PROX: + case MOD_G_SPLASH: + case MOD_HG_SPLASH: + case MOD_HELD_GRENADE: + case MOD_TRACKER: + case MOD_DISINTEGRATOR: + knockback /= 2; + break; + case MOD_R_SPLASH: + knockback = (knockback * 3) / 2; + break; + case MOD_RAILGUN: + case MOD_HEATBEAM: + knockback /= 3; + break; + } + } + +// gi.dprintf("mod: %d knockback: %d\n", mod, knockback); + return knockback; +} + +// ************************** +// Goals +// ************************** + +void DBall_GoalTouch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + int team_score; + int scorechange; + int j; + char value[512]; + char *p; + edict_t *ent; + + if(other != dball_ball_entity) + return; + + self->health = self->max_health; + + // determine which team scored, and bump the team score + if(self->spawnflags & DBALL_GOAL_TEAM1) + { + dball_team1_goalscore += self->wait; + team_score = 1; + } + else + { + dball_team2_goalscore += self->wait; + team_score = 2; + } + + // bump the score for everyone on the correct team. + for (j = 1; j <= game.maxclients; j++) + { + ent = &g_edicts[j]; + if (!ent->inuse) + continue; + if (!ent->client) + continue; + + if (ent == other->enemy) + scorechange = self->wait + 5; + else + scorechange = self->wait; + + strcpy(value, Info_ValueForKey (ent->client->pers.userinfo, "skin")); + p = strchr(value, '/'); + if (p) + { + if(!strcmp(dball_team1_skin->string, value)) + { + if(team_score == 1) + ent->client->resp.score += scorechange; + else if(other->enemy == ent) + ent->client->resp.score -= scorechange; + } + else if(!strcmp(dball_team2_skin->string, value)) + { + if(team_score == 2) + ent->client->resp.score += scorechange; + else if(other->enemy == ent) + ent->client->resp.score -= scorechange; + } + else + gi.dprintf("unassigned player!!!!\n"); + } + } + + if(other->enemy) + gi.dprintf("score for team %d by %s\n", team_score, other->enemy->client->pers.netname); + else + gi.dprintf("score for team %d by someone\n", team_score); + + DBall_BallDie (other, other->enemy, other->enemy, 0, vec3_origin); + + G_UseTargets (self, other); +} + +// ************************** +// Ball +// ************************** + +edict_t *PickBallStart (edict_t *ent) +{ + int which, current; + edict_t *e; + + which = ceil(random() * dball_ball_startpt_count); + e = NULL; + current = 0; + + while(e = G_Find (e, FOFS(classname), "dm_dball_ball_start")) + { + current++; + if(current == which) + return e; + } + + if(current == 0) + gi.dprintf("No ball start points found!\n"); + + return G_Find(NULL, FOFS(classname), "dm_dball_ball_start"); +} + +//================== +// DBall_BallTouch - if the ball hit another player, hurt them +//================== +void DBall_BallTouch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + vec3_t dir; + float dot; + float speed; + + if(other->takedamage == DAMAGE_NO) + return; + + // hit a player + if(other->client) + { + if(ent->velocity[0] || ent->velocity[1] || ent->velocity[2]) + { + speed = VectorLength(ent->velocity); + + VectorSubtract(ent->s.origin, other->s.origin, dir); + dot = DotProduct(dir, ent->velocity); + + if(dot > 0.7) + { + T_Damage (other, ent, ent, vec3_origin, ent->s.origin, vec3_origin, + speed/10, speed/10, 0, MOD_DBALL_CRUSH); + } + } + } +} + +//================== +// DBall_BallPain +//================== +void DBall_BallPain (edict_t *self, edict_t *other, float kick, int damage) +{ + self->enemy = other; + self->health = self->max_health; +// if(other->classname) +// gi.dprintf("hurt by %s -- %d\n", other->classname, self->health); +} + +void DBall_BallDie (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + // do the splash effect + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_DBALL_GOAL); + gi.WritePosition (self->s.origin); + gi.multicast (self->s.origin, MULTICAST_PVS); + + VectorClear(self->s.angles); + VectorClear(self->velocity); + VectorClear(self->avelocity); + + // make it invisible and desolid until respawn time + self->solid = SOLID_NOT; +// self->s.modelindex = 0; + self->think = DBall_BallRespawn; + self->nextthink = level.time + 2; + gi.linkentity(self); + +} + +void DBall_BallRespawn (edict_t *self) +{ + edict_t *start; + + // do the splash effect + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_DBALL_GOAL); + gi.WritePosition (self->s.origin); + gi.multicast (self->s.origin, MULTICAST_PVS); + + // move the ball and stop it + start = PickBallStart(self); + if(start) + { + VectorCopy(start->s.origin, self->s.origin); + VectorCopy(start->s.origin, self->s.old_origin); + } + + VectorClear(self->s.angles); + VectorClear(self->velocity); + VectorClear(self->avelocity); + + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex ("models/objects/dball/tris.md2"); + self->s.event = EV_PLAYER_TELEPORT; + self->groundentity = NULL; + + // kill anything at the destination + KillBox (self); + + gi.linkentity (self); +} + +// ************************ +// SPEED CHANGES +// ************************ + +#define DBALL_SPEED_ONEWAY 1 + +void DBall_SpeedTouch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + float dot; + vec3_t vel; + + if(other != dball_ball_entity) + return; + + if(self->timestamp >= level.time) + return; + + if(VectorLength(other->velocity) < 1) + return; + + if(self->spawnflags & DBALL_SPEED_ONEWAY) + { + VectorCopy (other->velocity, vel); + VectorNormalize (vel); + dot = DotProduct (vel, self->movedir); + if(dot < 0.8) + return; + } + + self->timestamp = level.time + self->delay; + VectorScale (other->velocity, self->speed, other->velocity); +} + +// ************************ +// SPAWN FUNCTIONS +// ************************ + +/*QUAKED dm_dball_ball (1 .5 .5) (-48 -48 -48) (48 48 48) +Deathball Ball +*/ +void SP_dm_dball_ball (edict_t *self) +{ + if(!(deathmatch->value)) + { + G_FreeEdict(self); + return; + } + + if(gamerules && (gamerules->value != RDM_DEATHBALL)) + { + G_FreeEdict(self); + return; + } + + dball_ball_entity = self; +// VectorCopy (self->s.origin, dball_ball_startpt); + + self->s.modelindex = gi.modelindex ("models/objects/dball/tris.md2"); + VectorSet (self->mins, -32, -32, -32); + VectorSet (self->maxs, 32, 32, 32); + self->solid = SOLID_BBOX; + self->movetype = MOVETYPE_NEWTOSS; + self->clipmask = MASK_MONSTERSOLID; + + self->takedamage = DAMAGE_YES; + self->mass = 50; + self->health = 50000; + self->max_health = 50000; + self->pain = DBall_BallPain; + self->die = DBall_BallDie; + self->touch = DBall_BallTouch; + + gi.linkentity (self); +} + +/*QUAKED dm_dball_team1_start (1 .5 .5) (-16 -16 -24) (16 16 32) +Deathball team 1 start point +*/ +void SP_dm_dball_team1_start (edict_t *self) +{ + if (!deathmatch->value) + { + G_FreeEdict (self); + return; + } + if(gamerules && (gamerules->value != RDM_DEATHBALL)) + { + G_FreeEdict(self); + return; + } +} + +/*QUAKED dm_dball_team2_start (1 .5 .5) (-16 -16 -24) (16 16 32) +Deathball team 2 start point +*/ +void SP_dm_dball_team2_start (edict_t *self) +{ + if (!deathmatch->value) + { + G_FreeEdict (self); + return; + } + if(gamerules && (gamerules->value != RDM_DEATHBALL)) + { + G_FreeEdict(self); + return; + } +} + +/*QUAKED dm_dball_ball_start (1 .5 .5) (-48 -48 -48) (48 48 48) +Deathball ball start point +*/ +void SP_dm_dball_ball_start (edict_t *self) +{ + if (!deathmatch->value) + { + G_FreeEdict (self); + return; + } + if(gamerules && (gamerules->value != RDM_DEATHBALL)) + { + G_FreeEdict(self); + return; + } +} + +/*QUAKED dm_dball_speed_change (1 .5 .5) ? ONEWAY +Deathball ball speed changing field. + +speed: multiplier for speed (.5 = half, 2 = double, etc) (default = double) +angle: used with ONEWAY so speed change is only one way. +delay: time between speed changes (default: 0.2 sec) +*/ +void SP_dm_dball_speed_change (edict_t *self) +{ + if (!deathmatch->value) + { + G_FreeEdict (self); + return; + } + if(gamerules && (gamerules->value != RDM_DEATHBALL)) + { + G_FreeEdict(self); + return; + } + + if(!self->speed) + self->speed = 2; + + if(!self->delay) + self->delay = 0.2; + + self->touch = DBall_SpeedTouch; + self->solid = SOLID_TRIGGER; + self->movetype = MOVETYPE_NONE; + self->svflags |= SVF_NOCLIENT; + + if (!VectorCompare(self->s.angles, vec3_origin)) + G_SetMovedir (self->s.angles, self->movedir); + else + VectorSet (self->movedir, 1, 0, 0); + + gi.setmodel (self, self->model); + gi.linkentity (self); +} + +/*QUAKED dm_dball_goal (1 .5 .5) ? TEAM1 TEAM2 +Deathball goal + +Team1/Team2 - beneficiary of this goal. when the ball enters this goal, the beneficiary team will score. + +"wait": score to be given for this goal (default 10) player gets score+5. +*/ +void SP_dm_dball_goal (edict_t *self) +{ + if(!(deathmatch->value)) + { + G_FreeEdict(self); + return; + } + + if(gamerules && (gamerules->value != RDM_DEATHBALL)) + { + G_FreeEdict(self); + return; + } + + if(!self->wait) + self->wait = 10; + + self->touch = DBall_GoalTouch; + self->solid = SOLID_TRIGGER; + self->movetype = MOVETYPE_NONE; + self->svflags |= SVF_NOCLIENT; + + if (!VectorCompare(self->s.angles, vec3_origin)) + G_SetMovedir (self->s.angles, self->movedir); + + gi.setmodel (self, self->model); + gi.linkentity (self); + +} diff --git a/original/rogue/dm_tag.c b/original/rogue/dm_tag.c new file mode 100644 index 0000000..d506686 --- /dev/null +++ b/original/rogue/dm_tag.c @@ -0,0 +1,335 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// dm_tag +// pmack +// june 1998 + +#include "g_local.h" + +extern edict_t *SelectFarthestDeathmatchSpawnPoint (void); +extern void SelectSpawnPoint (edict_t *ent, vec3_t origin, vec3_t angles); + +void SP_dm_tag_token (edict_t *self); + +// *********************** +// Tag Specific Stuff +// *********************** + +edict_t *tag_token; +edict_t *tag_owner; +int tag_count; + +//================= +//================= +void Tag_PlayerDeath(edict_t *targ, edict_t *inflictor, edict_t *attacker) +{ +// gi.dprintf("%s killed %s\n", attacker->classname, targ->classname); +// gi.dprintf("%x killed %x\n", attacker, targ); + + if(tag_token && targ && (targ == tag_owner)) + { +// gi.dprintf("owner died/suicided. dropping\n"); + Tag_DropToken(targ, FindItem( "Tag Token" )); + tag_owner = NULL; + tag_count = 0; + } +// else +// gi.dprintf("unrelated slaying\n"); +} + +//================= +//================= +void Tag_KillItBonus (edict_t *self) +{ + edict_t *armor; + + // if the player is hurt, boost them up to max. + if(self->health < self->max_health) + { + self->health += 200; + if(self->health > self->max_health) + self->health = self->max_health; + } + + // give the player a body armor + armor = G_Spawn(); + armor->spawnflags |= DROPPED_ITEM; + armor->item = FindItem("Body Armor"); + Touch_Item(armor, self, NULL, NULL); + if(armor->inuse) + G_FreeEdict(armor); +} + +//================= +//================= +void Tag_PlayerDisconnect (edict_t *self) +{ + if(tag_token && self && (self == tag_owner)) + { +// gi.dprintf("owner died/suicided. dropping\n"); + Tag_DropToken(self, FindItem( "Tag Token" )); + tag_owner = NULL; + tag_count = 0; + } +} + +//================= +//================= +void Tag_Score (edict_t *attacker, edict_t *victim, int scoreChange) +{ + gitem_t *quad; + int mod; + + mod = meansOfDeath & ~MOD_FRIENDLY_FIRE; + +// gi.dprintf("%s killed %s\n", attacker->classname, victim->classname); +// gi.dprintf("%x killed %x\n", attacker, victim); + if(tag_token && tag_owner) + { + // owner killed somone else + if((scoreChange > 0) && tag_owner == attacker) + { + scoreChange = 3; + tag_count++; +// gi.dprintf("tag total: %d\n", tag_count); + if(tag_count == 5) + { +// gi.dprintf("going to quad\n"); + quad = FindItem ("Quad Damage"); + attacker->client->pers.inventory[ITEM_INDEX(quad)]++; + quad->use (attacker, quad); + tag_count = 0; + } + } + // owner got killed. 5 points and switch owners + else if(tag_owner == victim && tag_owner != attacker) + { +// gi.dprintf("owner killed by another player.\n"); + scoreChange = 5; + if ((mod == MOD_HUNTER_SPHERE) || (mod == MOD_DOPPLE_EXPLODE) || + (mod == MOD_DOPPLE_VENGEANCE) || (mod == MOD_DOPPLE_HUNTER) || + (attacker->health <= 0)) + { + Tag_DropToken(tag_owner, FindItem( "Tag Token" )); + tag_owner = NULL; + tag_count = 0; + } + else + { + Tag_KillItBonus(attacker); + tag_owner = attacker; + tag_count = 0; + } + } +// else +// gi.dprintf("unaffected slaying\n"); + } +// else +// gi.dprintf("no tag token?\n"); + + attacker->client->resp.score += scoreChange; +} + +//================= +//================= +qboolean Tag_PickupToken (edict_t *ent, edict_t *other) +{ + if(gamerules && (gamerules->value != 2)) + { + return false; + } + +// gi.dprintf("tag token picked up by %x\n", other); + // sanity checking is good. + if(tag_token != ent) + tag_token = ent; + + other->client->pers.inventory[ITEM_INDEX(ent->item)]++; + + tag_owner = other; + tag_count = 0; + + Tag_KillItBonus (other); + + return true; +} + +//================= +//================= +void Tag_Respawn (edict_t *ent) +{ + edict_t *spot; + + spot = SelectFarthestDeathmatchSpawnPoint(); + if(spot == NULL) + { +// gi.dprintf("No open spawn point, waiting...\n"); + ent->nextthink = level.time + 1; + return; + } + +// gi.dprintf("Relocating\n"); + + VectorCopy(spot->s.origin, ent->s.origin); + gi.linkentity(ent); +} + +//================= +//================= +void Tag_MakeTouchable (edict_t *ent) +{ + ent->touch = Touch_Item; + + tag_token->think = Tag_Respawn; + + // check here to see if it's in lava or slime. if so, do a respawn sooner + if(gi.pointcontents(ent->s.origin) & (CONTENTS_LAVA|CONTENTS_SLIME)) + { +// gi.dprintf("spawned in slime or lava. quick relocate\n"); + tag_token->nextthink = level.time + 3; + } + else + { +// gi.dprintf("spawned in the clear. regular relocate\n"); + tag_token->nextthink = level.time + 30; + } +} + +//================= +//================= +void Tag_DropToken (edict_t *ent, gitem_t *item) +{ + trace_t trace; + vec3_t forward, right; + vec3_t offset; + +// if(ent->client) +// gi.dprintf("%s dropped the tag token\n", ent->client->pers.netname); +// else +// gi.dprintf("non-client dropped the tag token (%s)\n", ent->classname); + + // reset the score count for next player + tag_count = 0; + tag_owner = NULL; + + tag_token = G_Spawn(); + + tag_token->classname = item->classname; + tag_token->item = item; + tag_token->spawnflags = DROPPED_ITEM; + tag_token->s.effects = EF_ROTATE | EF_TAGTRAIL; + tag_token->s.renderfx = RF_GLOW; + VectorSet (tag_token->mins, -15, -15, -15); + VectorSet (tag_token->maxs, 15, 15, 15); + gi.setmodel (tag_token, tag_token->item->world_model); + tag_token->solid = SOLID_TRIGGER; + tag_token->movetype = MOVETYPE_TOSS; + tag_token->touch = NULL; + tag_token->owner = ent; + + AngleVectors (ent->client->v_angle, forward, right, NULL); + VectorSet(offset, 24, 0, -16); + G_ProjectSource (ent->s.origin, offset, forward, right, tag_token->s.origin); + trace = gi.trace (ent->s.origin, tag_token->mins, tag_token->maxs, + tag_token->s.origin, ent, CONTENTS_SOLID); + VectorCopy (trace.endpos, tag_token->s.origin); + + VectorScale (forward, 100, tag_token->velocity); + tag_token->velocity[2] = 300; + + tag_token->think = Tag_MakeTouchable; + tag_token->nextthink = level.time + 1; + + gi.linkentity (tag_token); + +// tag_token = Drop_Item (ent, item); + ent->client->pers.inventory[ITEM_INDEX(item)]--; + ValidateSelectedItem (ent); +} + +//================= +//================= +void Tag_PlayerEffects (edict_t *ent) +{ + if(ent == tag_owner) + ent->s.effects |= EF_TAGTRAIL; +} + +//================= +//================= +void Tag_DogTag (edict_t *ent, edict_t *killer, char **pic) +{ + if(ent == tag_owner) + (*pic)="tag3"; +} + +//================= +// Tag_ChangeDamage - damage done that does not involve the tag owner +// is at 75% original to encourage folks to go after the tag owner. +//================= +int Tag_ChangeDamage (edict_t *targ, edict_t *attacker, int damage, int mod) +{ + if((targ != tag_owner) && (attacker != tag_owner)) + return (damage * 3 / 4); + + return damage; +} + +//================= +//================= +void Tag_GameInit (void) +{ + tag_token = NULL; + tag_owner = NULL; + tag_count = 0; +} + +//================= +//================= +void Tag_PostInitSetup (void) +{ + edict_t *e; + vec3_t origin, angles; + + // automatic spawning of tag token if one is not present on map. + e = G_Find (NULL, FOFS(classname), "dm_tag_token"); + if(e == NULL) + { + e = G_Spawn(); + e->classname = "dm_tag_token"; + + SelectSpawnPoint (e, origin, angles); + VectorCopy(origin, e->s.origin); + VectorCopy(origin, e->s.old_origin); + VectorCopy(angles, e->s.angles); + SP_dm_tag_token (e); + } +} + +/*QUAKED dm_tag_token (.3 .3 1) (-16 -16 -16) (16 16 16) +The tag token for deathmatch tag games. +*/ +void SP_dm_tag_token (edict_t *self) +{ + if(!(deathmatch->value)) + { + G_FreeEdict(self); + return; + } + + if(gamerules && (gamerules->value != 2)) + { + G_FreeEdict(self); + return; + } + + // store the tag token edict pointer for later use. + tag_token = self; + tag_count = 0; + + self->classname = "dm_tag_token"; + self->model = "models/items/tagtoken/tris.md2"; + self->count = 1; + SpawnItem (self, FindItem ("Tag Token")); +} + diff --git a/original/rogue/g_ai.c b/original/rogue/g_ai.c new file mode 100644 index 0000000..1b0db3c --- /dev/null +++ b/original/rogue/g_ai.c @@ -0,0 +1,1598 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// g_ai.c + +#include "g_local.h" + +qboolean FindTarget (edict_t *self); +extern cvar_t *maxclients; + +qboolean ai_checkattack (edict_t *self, float dist); + +qboolean enemy_vis; +qboolean enemy_infront; +int enemy_range; +float enemy_yaw; + +// ROGUE STUFF +#define SLIDING_TROOPS 1 +#define MAX_SIDESTEP 8.0 +// + +//============================================================================ + + +/* +================= +AI_SetSightClient + +Called once each frame to set level.sight_client to the +player to be checked for in findtarget. + +If all clients are either dead or in notarget, sight_client +will be null. + +In coop games, sight_client will cycle between the clients. +================= +*/ +void AI_SetSightClient (void) +{ + edict_t *ent; + int start, check; + + if (level.sight_client == NULL) + start = 1; + else + start = level.sight_client - g_edicts; + + check = start; + while (1) + { + check++; + if (check > game.maxclients) + check = 1; + ent = &g_edicts[check]; + if (ent->inuse + && ent->health > 0 + && !(ent->flags & (FL_NOTARGET|FL_DISGUISED)) ) + { + level.sight_client = ent; + return; // got one + } + if (check == start) + { + level.sight_client = NULL; + return; // nobody to see + } + } +} + +//============================================================================ + +/* +============= +ai_move + +Move the specified distance at current facing. +This replaces the QC functions: ai_forward, ai_back, ai_pain, and ai_painforward +============== +*/ +void ai_move (edict_t *self, float dist) +{ + M_walkmove (self, self->s.angles[YAW], dist); +} + + +/* +============= +ai_stand + +Used for standing around and looking for players +Distance is for slight position adjustments needed by the animations +============== +*/ +void ai_stand (edict_t *self, float dist) +{ + vec3_t v; + // PMM + qboolean retval; + + if (dist) + M_walkmove (self, self->s.angles[YAW], dist); + + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + { + if (self->enemy) + { + VectorSubtract (self->enemy->s.origin, self->s.origin, v); + self->ideal_yaw = vectoyaw(v); + if (self->s.angles[YAW] != self->ideal_yaw && self->monsterinfo.aiflags & AI_TEMP_STAND_GROUND) + { + self->monsterinfo.aiflags &= ~(AI_STAND_GROUND | AI_TEMP_STAND_GROUND); + self->monsterinfo.run (self); + } + if (!(self->monsterinfo.aiflags & AI_MANUAL_STEERING)) + M_ChangeYaw (self); + // PMM + // find out if we're going to be shooting + retval = ai_checkattack (self, 0); + // record sightings of player + if ((self->enemy) && (self->enemy->inuse) && (visible(self, self->enemy))) + { + self->monsterinfo.aiflags &= ~AI_LOST_SIGHT; + VectorCopy (self->enemy->s.origin, self->monsterinfo.last_sighting); + VectorCopy (self->enemy->s.origin, self->monsterinfo.blind_fire_target); + self->monsterinfo.trail_time = level.time; + self->monsterinfo.blind_fire_delay = 0; + } + // check retval to make sure we're not blindfiring + else if (!retval) + { + FindTarget (self); + return; + } +// ai_checkattack (self, 0); + // pmm + } + else + FindTarget (self); + return; + } + + if (FindTarget (self)) + return; + + if (level.time > self->monsterinfo.pausetime) + { + self->monsterinfo.walk (self); + return; + } + + if (!(self->spawnflags & 1) && (self->monsterinfo.idle) && (level.time > self->monsterinfo.idle_time)) + { + if (self->monsterinfo.idle_time) + { + self->monsterinfo.idle (self); + self->monsterinfo.idle_time = level.time + 15 + random() * 15; + } + else + { + self->monsterinfo.idle_time = level.time + random() * 15; + } + } +} + + +/* +============= +ai_walk + +The monster is walking it's beat +============= +*/ +void ai_walk (edict_t *self, float dist) +{ + M_MoveToGoal (self, dist); + + // check for noticing a player + if (FindTarget (self)) + return; + + if ((self->monsterinfo.search) && (level.time > self->monsterinfo.idle_time)) + { + if (self->monsterinfo.idle_time) + { + self->monsterinfo.search (self); + self->monsterinfo.idle_time = level.time + 15 + random() * 15; + } + else + { + self->monsterinfo.idle_time = level.time + random() * 15; + } + } +} + + +/* +============= +ai_charge + +Turns towards target and advances +Use this call with a distance of 0 to replace ai_face +============== +*/ +void ai_charge (edict_t *self, float dist) +{ + vec3_t v; + // PMM + float ofs; + // PMM + + // PMM - made AI_MANUAL_STEERING affect things differently here .. they turn, but + // don't set the ideal_yaw + + // This is put in there so monsters won't move towards the origin after killing + // a tesla. This could be problematic, so keep an eye on it. + if(!self->enemy || !self->enemy->inuse) //PGM + return; //PGM + + // PMM - save blindfire target + if (visible(self, self->enemy)) + VectorCopy (self->enemy->s.origin, self->monsterinfo.blind_fire_target); + // pmm + + if (!(self->monsterinfo.aiflags & AI_MANUAL_STEERING)) + { + VectorSubtract (self->enemy->s.origin, self->s.origin, v); + self->ideal_yaw = vectoyaw(v); +// gi.dprintf ("enemy = %s\n", vtos (self->enemy->s.origin)); +// gi.dprintf ("enemy: ideal yaw is %f\n", self->ideal_yaw); + } +// if (!(self->monsterinfo.aiflags & AI_MANUAL_STEERING)) + M_ChangeYaw (self); +// PMM +// if (dist) +// M_walkmove (self, self->s.angles[YAW], dist); + + if (dist) + { + if (self->monsterinfo.aiflags & AI_CHARGING) + { + M_MoveToGoal (self, dist); + return; + } + // circle strafe support + if (self->monsterinfo.attack_state == AS_SLIDING) + { + // if we're fighting a tesla, NEVER circle strafe + if ((self->enemy) && (self->enemy->classname) && (!strcmp(self->enemy->classname, "tesla"))) + ofs = 0; + else if (self->monsterinfo.lefty) + ofs = 90; + else + ofs = -90; + + if (M_walkmove (self, self->ideal_yaw + ofs, dist)) + return; + + self->monsterinfo.lefty = 1 - self->monsterinfo.lefty; + M_walkmove (self, self->ideal_yaw - ofs, dist); + } + else + M_walkmove (self, self->s.angles[YAW], dist); + } +// PMM +} + + +/* +============= +ai_turn + +don't move, but turn towards ideal_yaw +Distance is for slight position adjustments needed by the animations +============= +*/ +void ai_turn (edict_t *self, float dist) +{ + if (dist) + M_walkmove (self, self->s.angles[YAW], dist); + + if (FindTarget (self)) + return; + + if (!(self->monsterinfo.aiflags & AI_MANUAL_STEERING)) + M_ChangeYaw (self); +} + + +/* + +.enemy +Will be world if not currently angry at anyone. + +.movetarget +The next path spot to walk toward. If .enemy, ignore .movetarget. +When an enemy is killed, the monster will try to return to it's path. + +.hunt_time +Set to time + something when the player is in sight, but movement straight for +him is blocked. This causes the monster to use wall following code for +movement direction instead of sighting on the player. + +.ideal_yaw +A yaw angle of the intended direction, which will be turned towards at up +to 45 deg / state. If the enemy is in view and hunt_time is not active, +this will be the exact line towards the enemy. + +.pausetime +A monster will leave it's stand state and head towards it's .movetarget when +time > .pausetime. + +walkmove(angle, speed) primitive is all or nothing +*/ + +/* +============= +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 +============= +*/ +int range (edict_t *self, edict_t *other) +{ + vec3_t v; + float len; + + VectorSubtract (self->s.origin, other->s.origin, v); + len = VectorLength (v); + if (len < MELEE_DISTANCE) + return RANGE_MELEE; + if (len < 500) + return RANGE_NEAR; + if (len < 1000) + return RANGE_MID; + return RANGE_FAR; +} + +/* +============= +visible + +returns 1 if the entity is visible to self, even if not infront () +============= +*/ +qboolean visible (edict_t *self, edict_t *other) +{ + vec3_t spot1; + vec3_t spot2; + trace_t trace; + + VectorCopy (self->s.origin, spot1); + spot1[2] += self->viewheight; + VectorCopy (other->s.origin, spot2); + spot2[2] += other->viewheight; + trace = gi.trace (spot1, vec3_origin, vec3_origin, spot2, self, MASK_OPAQUE); + + if (trace.fraction == 1.0 || trace.ent == other) // PGM + return true; + return false; +} + + +/* +============= +infront + +returns 1 if the entity is in front (in sight) of self +============= +*/ +qboolean infront (edict_t *self, edict_t *other) +{ + vec3_t vec; + float dot; + vec3_t forward; + + AngleVectors (self->s.angles, forward, NULL, NULL); + VectorSubtract (other->s.origin, self->s.origin, vec); + VectorNormalize (vec); + dot = DotProduct (vec, forward); + + if (dot > 0.3) + return true; + return false; +} + + +//============================================================================ + +void HuntTarget (edict_t *self) +{ + vec3_t vec; + + self->goalentity = self->enemy; + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + self->monsterinfo.stand (self); + else + self->monsterinfo.run (self); + VectorSubtract (self->enemy->s.origin, self->s.origin, vec); + self->ideal_yaw = vectoyaw(vec); + // wait a while before first attack + if (!(self->monsterinfo.aiflags & AI_STAND_GROUND)) + AttackFinished (self, 1); +} + +void FoundTarget (edict_t *self) +{ + // let other monsters see this monster for a while + if (self->enemy->client) + { + if(self->enemy->flags & FL_DISGUISED) + { +// level.disguise_violator = self->enemy; +// level.disguise_violation_framenum = level.framenum + 5; + self->enemy->flags &= ~FL_DISGUISED; + } + + level.sight_entity = self; + level.sight_entity_framenum = level.framenum; + level.sight_entity->light_level = 128; + } + + self->show_hostile = level.time + 1; // wake up other monsters + + VectorCopy(self->enemy->s.origin, self->monsterinfo.last_sighting); + self->monsterinfo.trail_time = level.time; + // PMM + VectorCopy (self->enemy->s.origin, self->monsterinfo.blind_fire_target); + self->monsterinfo.blind_fire_delay = 0; + // PMM + + if (!self->combattarget) + { + HuntTarget (self); + return; + } + + self->goalentity = self->movetarget = G_PickTarget(self->combattarget); + if (!self->movetarget) + { + self->goalentity = self->movetarget = self->enemy; + HuntTarget (self); + gi.dprintf("%s at %s, combattarget %s not found\n", self->classname, vtos(self->s.origin), self->combattarget); + return; + } + + // clear out our combattarget, these are a one shot deal + self->combattarget = NULL; + self->monsterinfo.aiflags |= AI_COMBAT_POINT; + + // clear the targetname, that point is ours! + self->movetarget->targetname = NULL; + self->monsterinfo.pausetime = 0; + + // run for it + self->monsterinfo.run (self); +} + + +/* +=========== +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. +============ +*/ +qboolean FindTarget (edict_t *self) +{ + edict_t *client; + qboolean heardit; + int r; + + if (self->monsterinfo.aiflags & AI_GOOD_GUY) + { + if (self->goalentity && self->goalentity->inuse && self->goalentity->classname) + { + if (strcmp(self->goalentity->classname, "target_actor") == 0) + return false; + } + + //FIXME look for monsters? + return false; + } + + // if we're going to a combat point, just proceed + if (self->monsterinfo.aiflags & AI_COMBAT_POINT) + return false; + +// if the first spawnflag bit is set, the monster will only wake up on +// really seeing the player, not another monster getting angry or hearing +// something + +// revised behavior so they will wake up if they "see" a player make a noise +// but not weapon impact/explosion noises + + heardit = false; + if ((level.sight_entity_framenum >= (level.framenum - 1)) && !(self->spawnflags & 1) ) + { + client = level.sight_entity; + if (client->enemy == self->enemy) + { + return false; + } + } + else if (level.disguise_violation_framenum > level.framenum) + { + client = level.disguise_violator; + } + else if (level.sound_entity_framenum >= (level.framenum - 1)) + { + client = level.sound_entity; + heardit = true; + } + else if (!(self->enemy) && (level.sound2_entity_framenum >= (level.framenum - 1)) && !(self->spawnflags & 1) ) + { + client = level.sound2_entity; + heardit = true; + } + else + { + client = level.sight_client; + if (!client) + return false; // no clients to get mad at + } + + // if the entity went away, forget it + if (!client->inuse) + return false; + + if (client == self->enemy) + return true; // JDC false; + + //PMM - hintpath coop fix + if ((self->monsterinfo.aiflags & AI_HINT_PATH) && (coop) && (coop->value)) + { +// if ((heardit) && (g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("ignoring coop sound target\n"); + heardit = false; + } + // pmm + + if (client->client) + { + if (client->flags & FL_NOTARGET) + return false; + } + else if (client->svflags & SVF_MONSTER) + { + if (!client->enemy) + return false; + if (client->enemy->flags & FL_NOTARGET) + return false; + } + else if (heardit) + { + // pgm - a little more paranoia won't hurt.... + if ((client->owner) && (client->owner->flags & FL_NOTARGET)) + return false; + } + else + return false; + + if (!heardit) + { + r = range (self, client); + + if (r == RANGE_FAR) + return false; + +// this is where we would check invisibility + + // is client in an spot too dark to be seen? + if (client->light_level <= 5) + return false; + + if (!visible (self, client)) + { + return false; + } + + if (r == RANGE_NEAR) + { + if (client->show_hostile < level.time && !infront (self, client)) + { + return false; + } + } + else if (r == RANGE_MID) + { + if (!infront (self, client)) + { + return false; + } + } + + self->enemy = client; + + if (strcmp(self->enemy->classname, "player_noise") != 0) + { + self->monsterinfo.aiflags &= ~AI_SOUND_TARGET; + + if (!self->enemy->client) + { + self->enemy = self->enemy->enemy; + if (!self->enemy->client) + { + self->enemy = NULL; + return false; + } + } + } + } + else // heardit + { + vec3_t temp; + + if (self->spawnflags & 1) + { + if (!visible (self, client)) + return false; + } + else + { + if (!gi.inPHS(self->s.origin, client->s.origin)) + return false; + } + + VectorSubtract (client->s.origin, self->s.origin, temp); + + if (VectorLength(temp) > 1000) // too far to hear + { + return false; + } + + // check area portals - if they are different and not connected then we can't hear it + if (client->areanum != self->areanum) + if (!gi.AreasConnected(self->areanum, client->areanum)) + return false; + + self->ideal_yaw = vectoyaw(temp); + if (!(self->monsterinfo.aiflags & AI_MANUAL_STEERING)) + M_ChangeYaw (self); + + // hunt the sound for a bit; hopefully find the real player + self->monsterinfo.aiflags |= AI_SOUND_TARGET; + self->enemy = client; + } + +// +// got one +// + // PMM - if we got an enemy, we need to bail out of hint paths, so take over here + if (self->monsterinfo.aiflags & AI_HINT_PATH) + { +// if(g_showlogic && g_showlogic->value) +// gi.dprintf("stopped following hint paths in FindTarget\n"); + + // this calls foundtarget for us + hintpath_stop (self); + } + else + { + FoundTarget (self); + } + // pmm + if (!(self->monsterinfo.aiflags & AI_SOUND_TARGET) && (self->monsterinfo.sight)) + self->monsterinfo.sight (self, self->enemy); + + return true; +} + + +//============================================================================= + +/* +============ +FacingIdeal + +============ +*/ +qboolean FacingIdeal(edict_t *self) +{ + float delta; + + delta = anglemod(self->s.angles[YAW] - self->ideal_yaw); + if (delta > 45 && delta < 315) + return false; + return true; +} + + +//============================================================================= + +qboolean M_CheckAttack (edict_t *self) +{ + vec3_t spot1, spot2; + float chance; + trace_t tr; + + if (self->enemy->health > 0) + { + // see if any entities are in the way of the shot + VectorCopy (self->s.origin, spot1); + spot1[2] += self->viewheight; + VectorCopy (self->enemy->s.origin, spot2); + spot2[2] += self->enemy->viewheight; + + tr = gi.trace (spot1, NULL, NULL, spot2, self, CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_SLIME|CONTENTS_LAVA|CONTENTS_WINDOW); + + // do we have a clear shot? + if (tr.ent != self->enemy) + { + // PGM - we want them to go ahead and shoot at info_notnulls if they can. + if(self->enemy->solid != SOLID_NOT || tr.fraction < 1.0) //PGM + { + // PMM - if we can't see our target, and we're not blocked by a monster, go into blind fire if available + if ((!(tr.ent->svflags & SVF_MONSTER)) && (!visible(self, self->enemy))) + { + if ((self->monsterinfo.blindfire) && (self->monsterinfo.blind_fire_delay <= 20.0)) + { + if (level.time < self->monsterinfo.attack_finished) + { + return false; + } + if (level.time < (self->monsterinfo.trail_time + self->monsterinfo.blind_fire_delay)) + { + // wait for our time + return false; + } + else + { +// gi.WriteByte (svc_temp_entity); +// gi.WriteByte (TE_DEBUGTRAIL); +// gi.WritePosition (spot1); +// gi.WritePosition (self->monsterinfo.blind_fire_target); +// gi.multicast (self->s.origin, MULTICAST_ALL); + // make sure we're not going to shoot a monster + tr = gi.trace (spot1, NULL, NULL, self->monsterinfo.blind_fire_target, self, CONTENTS_MONSTER); + if (tr.allsolid || tr.startsolid || ((tr.fraction < 1.0) && (tr.ent != self->enemy))) + { +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("blindfire blocked\n"); + return false; + } + + self->monsterinfo.attack_state = AS_BLIND; + return true; + } + } + } + // pmm + return false; + } + } + } + + // melee attack + if (enemy_range == RANGE_MELEE) + { + // don't always melee in easy mode + if (skill->value == 0 && (rand()&3) ) + { + // PMM - fix for melee only monsters & strafing + self->monsterinfo.attack_state = AS_STRAIGHT; + return false; + } + if (self->monsterinfo.melee) + self->monsterinfo.attack_state = AS_MELEE; + else + self->monsterinfo.attack_state = AS_MISSILE; + return true; + } + +// missile attack + if (!self->monsterinfo.attack) + { + // PMM - fix for melee only monsters & strafing + self->monsterinfo.attack_state = AS_STRAIGHT; + return false; + } + + if (level.time < self->monsterinfo.attack_finished) + return false; + + if (enemy_range == RANGE_FAR) + return false; + + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + { + chance = 0.4; + } + else if (enemy_range == RANGE_MELEE) + { + chance = 0.2; + } + else if (enemy_range == RANGE_NEAR) + { + chance = 0.1; + } + else if (enemy_range == RANGE_MID) + { + chance = 0.02; + } + else + { + return false; + } + + if (skill->value == 0) + chance *= 0.5; + else if (skill->value >= 2) + chance *= 2; + + // PGM - go ahead and shoot every time if it's a info_notnull + if ((random () < chance) || (self->enemy->solid == SOLID_NOT)) + { + self->monsterinfo.attack_state = AS_MISSILE; + self->monsterinfo.attack_finished = level.time + 2*random(); + return true; + } + + // PMM -daedalus should strafe more .. this can be done here or in a customized + // check_attack code for the hover. + if (self->flags & FL_FLY) + { + // originally, just 0.3 + float strafe_chance; + if (!(strcmp(self->classname, "monster_daedalus"))) + strafe_chance = 0.8; + else + strafe_chance = 0.6; + + // if enemy is tesla, never strafe + if ((self->enemy) && (self->enemy->classname) && (!strcmp(self->enemy->classname, "tesla"))) + strafe_chance = 0; + + if (random() < strafe_chance) + self->monsterinfo.attack_state = AS_SLIDING; + else + self->monsterinfo.attack_state = AS_STRAIGHT; + } +// do we want the monsters strafing? +#ifdef SLIDING_TROOPS + else + { + if (random() < 0.4) + self->monsterinfo.attack_state = AS_SLIDING; + else + self->monsterinfo.attack_state = AS_STRAIGHT; + } +#endif +//-PMM + + return false; +} + + +/* +============= +ai_run_melee + +Turn and close until within an angle to launch a melee attack +============= +*/ +void ai_run_melee(edict_t *self) +{ + self->ideal_yaw = enemy_yaw; + if (!(self->monsterinfo.aiflags & AI_MANUAL_STEERING)) + M_ChangeYaw (self); + + if (FacingIdeal(self)) + { + self->monsterinfo.melee (self); + self->monsterinfo.attack_state = AS_STRAIGHT; + } +} + + +/* +============= +ai_run_missile + +Turn in place until within an angle to launch a missile attack +============= +*/ +void ai_run_missile(edict_t *self) +{ + self->ideal_yaw = enemy_yaw; + if (!(self->monsterinfo.aiflags & AI_MANUAL_STEERING)) + M_ChangeYaw (self); + + if (FacingIdeal(self)) + { + self->monsterinfo.attack (self); +// if (self->monsterinfo.attack_state == AS_MISSILE) + if ((self->monsterinfo.attack_state == AS_MISSILE) || (self->monsterinfo.attack_state == AS_BLIND)) + self->monsterinfo.attack_state = AS_STRAIGHT; +// else if (self->monsterinfo.attack_state != AS_SLIDING) +// gi.dprintf ("ai_run_missile: Unexpected attack state %d !\n", self->monsterinfo.attack_state); + } +}; + + +/* +============= +ai_run_slide + +Strafe sideways, but stay at aproximately the same range +============= +*/ +void ai_run_slide(edict_t *self, float distance) +{ + float ofs; + float angle; + + self->ideal_yaw = enemy_yaw; + +// if (self->flags & FL_FLY) +// angle = 90; +// else +// angle = 45; + + angle = 90; + + if (self->monsterinfo.lefty) + ofs = angle; + else + ofs = -angle; +// +// if (!(self->flags & FL_FLY)) +// { +// // non fliers should actually turn towards the direction their trying to run +// self->ideal_yaw += ofs; +// } +// + if (!(self->monsterinfo.aiflags & AI_MANUAL_STEERING)) + M_ChangeYaw (self); + + /* + if (!(self->flags & FL_FLY)) + { + if (M_walkmove (self, self->ideal_yaw + ofs, distance)) + return; + } + else + { + if (M_walkmove (self, self->ideal_yaw, distance)) + return; + } + */ + // PMM - clamp maximum sideways move for non flyers to make them look less jerky + if (!self->flags & FL_FLY) + distance = min (distance, MAX_SIDESTEP); + if (M_walkmove (self, self->ideal_yaw + ofs, distance)) + return; + // PMM - if we're dodging, give up on it and go straight + if (self->monsterinfo.aiflags & AI_DODGING) + { + monster_done_dodge (self); + // by setting as_straight, caller will know to try straight move + self->monsterinfo.attack_state = AS_STRAIGHT; + return; + } + + self->monsterinfo.lefty = 1 - self->monsterinfo.lefty; + if (M_walkmove (self, self->ideal_yaw - ofs, distance)) + return; + // PMM - if we're dodging, give up on it and go straight + if (self->monsterinfo.aiflags & AI_DODGING) + monster_done_dodge (self); + + // PMM - the move failed, so signal the caller (ai_run) to try going straight + self->monsterinfo.attack_state = AS_STRAIGHT; + /* + if (!(self->flags & FL_FLY)) + { + M_walkmove (self, self->ideal_yaw + ofs, distance); + } + else + { + M_walkmove (self, self->ideal_yaw, distance); + }*/ +} + + +/* +============= +ai_checkattack + +Decides if we're going to attack or do something else +used by ai_run and ai_stand +============= +*/ +qboolean ai_checkattack (edict_t *self, float dist) +{ + vec3_t temp; + qboolean hesDeadJim; + // PMM + qboolean retval; + +// this causes monsters to run blindly to the combat point w/o firing + if (self->goalentity) + { + if (self->monsterinfo.aiflags & AI_COMBAT_POINT) + return false; + + if (self->monsterinfo.aiflags & AI_SOUND_TARGET) + { + if ((level.time - self->enemy->teleport_time) > 5.0) + { + if (self->goalentity == self->enemy) + if (self->movetarget) + self->goalentity = self->movetarget; + else + self->goalentity = NULL; + self->monsterinfo.aiflags &= ~AI_SOUND_TARGET; + if (self->monsterinfo.aiflags & AI_TEMP_STAND_GROUND) + self->monsterinfo.aiflags &= ~(AI_STAND_GROUND | AI_TEMP_STAND_GROUND); + } + else + { + self->show_hostile = level.time + 1; + return false; + } + } + } + + enemy_vis = false; + +// see if the enemy is dead + hesDeadJim = false; + if ((!self->enemy) || (!self->enemy->inuse)) + { + hesDeadJim = true; + } + else if (self->monsterinfo.aiflags & AI_MEDIC) + { + if (!(self->enemy->inuse) || (self->enemy->health > 0)) + { + hesDeadJim = true; +// self->monsterinfo.aiflags &= ~AI_MEDIC; + } + } + else + { + if (self->monsterinfo.aiflags & AI_BRUTAL) + { + if (self->enemy->health <= -80) + hesDeadJim = true; + } + else + { + if (self->enemy->health <= 0) + hesDeadJim = true; + } + } + + if (hesDeadJim) + { + self->monsterinfo.aiflags &= ~AI_MEDIC; + self->enemy = NULL; + // FIXME: look all around for other targets + if (self->oldenemy && self->oldenemy->health > 0) + { + self->enemy = self->oldenemy; + self->oldenemy = NULL; + HuntTarget (self); + } +//ROGUE - multiple teslas make monsters lose track of the player. + else if(self->monsterinfo.last_player_enemy && self->monsterinfo.last_player_enemy->health > 0) + { +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf("resorting to last_player_enemy...\n"); + self->enemy = self->monsterinfo.last_player_enemy; + self->oldenemy = NULL; + self->monsterinfo.last_player_enemy = NULL; + HuntTarget (self); + } +//ROGUE + else + { + if (self->movetarget) + { + self->goalentity = self->movetarget; + self->monsterinfo.walk (self); + } + else + { + // we need the pausetime otherwise the stand code + // will just revert to walking with no target and + // the monsters will wonder around aimlessly trying + // to hunt the world entity + self->monsterinfo.pausetime = level.time + 100000000; + self->monsterinfo.stand (self); + } + return true; + } + } + + self->show_hostile = level.time + 1; // wake up other monsters + +// check knowledge of enemy + enemy_vis = visible(self, self->enemy); + if (enemy_vis) + { + self->monsterinfo.search_time = level.time + 5; + VectorCopy (self->enemy->s.origin, self->monsterinfo.last_sighting); + // PMM + self->monsterinfo.aiflags &= ~AI_LOST_SIGHT; + self->monsterinfo.trail_time = level.time; + VectorCopy (self->enemy->s.origin, self->monsterinfo.blind_fire_target); + self->monsterinfo.blind_fire_delay = 0; + // pmm + } + +// look for other coop players here +// if (coop && self->monsterinfo.search_time < level.time) +// { +// if (FindTarget (self)) +// return true; +// } + + enemy_infront = infront(self, self->enemy); + enemy_range = range(self, self->enemy); + VectorSubtract (self->enemy->s.origin, self->s.origin, temp); + enemy_yaw = vectoyaw(temp); + + + // JDC self->ideal_yaw = enemy_yaw; + + // PMM -- reordered so the monster specific checkattack is called before the run_missle/melee/checkvis + // stuff .. this allows for, among other things, circle strafing and attacking while in ai_run + retval = self->monsterinfo.checkattack (self); + if (retval) + { + // PMM + if (self->monsterinfo.attack_state == AS_MISSILE) + { + ai_run_missile (self); + return true; + } + if (self->monsterinfo.attack_state == AS_MELEE) + { + ai_run_melee (self); + return true; + } + // PMM -- added so monsters can shoot blind + if (self->monsterinfo.attack_state == AS_BLIND) + { + ai_run_missile (self); + return true; + } + // pmm + + // if enemy is not currently visible, we will never attack + if (!enemy_vis) + return false; + // PMM + } + return retval; + // PMM +// return self->monsterinfo.checkattack (self); +} + + +/* +============= +ai_run + +The monster has an enemy it is trying to kill +============= +*/ +void ai_run (edict_t *self, float dist) +{ + vec3_t v; + edict_t *tempgoal; + edict_t *save; + qboolean new; + edict_t *marker; + float d1, d2; + trace_t tr; + vec3_t v_forward, v_right; + float left, center, right; + vec3_t left_target, right_target; + //PMM + qboolean retval; + qboolean alreadyMoved = false; + qboolean gotcha = false; + edict_t *realEnemy; + + // if we're going to a combat point, just proceed + if (self->monsterinfo.aiflags & AI_COMBAT_POINT) + { + M_MoveToGoal (self, dist); + return; + } + + // PMM + if (self->monsterinfo.aiflags & AI_DUCKED) + { +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("%s - duck flag cleaned up!\n", self->classname); + self->monsterinfo.aiflags &= ~AI_DUCKED; + } + if (self->maxs[2] != self->monsterinfo.base_height) + { +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("%s - ducked height corrected!\n", self->classname); + monster_duck_up (self); + } +// if ((self->monsterinfo.aiflags & AI_MANUAL_STEERING) && (strcmp(self->classname, "monster_turret"))) +// { +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("%s - manual steering in ai_run!\n", self->classname); +// } + // pmm + +//========== +//PGM + // if we're currently looking for a hint path + if (self->monsterinfo.aiflags & AI_HINT_PATH) + { + // determine direction to our destination hintpath. + // FIXME - is this needed EVERY time? I was having trouble with them + // sometimes not going after it, and this fixed it. +// VectorSubtract(self->movetarget->s.origin, self->s.origin, v); +// vectoangles(v, v_forward); +// self->ideal_yaw = v_forward[YAW]; +// gi.dprintf("seeking hintpath. origin: %s %0.1f\n", vtos(v), self->ideal_yaw); + M_MoveToGoal (self, dist); + if(!self->inuse) + return; // PGM - g_touchtrigger free problem +// return; + + // if we've already seen the player, and can't see him now, return +// if(self->enemy && !visible(self, self->enemy)) +// return; + + // if not and can't find the player, return +// if(!FindTarget(self)) +// return; + + // first off, make sure we're looking for the player, not a noise he made + if (self->enemy) + { + if (self->enemy->inuse) + { + if (strcmp(self->enemy->classname, "player_noise") != 0) + realEnemy = self->enemy; + else if (self->enemy->owner) + realEnemy = self->enemy->owner; + else // uh oh, can't figure out enemy, bail + { + self->enemy = NULL; + hintpath_stop (self); + return; + } + } + else + { + self->enemy = NULL; + hintpath_stop (self); + return; + } + } + else + { + hintpath_stop (self); + return; + } + + if (coop && coop->value) + { + // if we're in coop, check my real enemy first .. if I SEE him, set gotcha to true + if (self->enemy && visible(self, realEnemy)) + gotcha = true; + else // otherwise, let FindTarget bump us out of hint paths, if appropriate + FindTarget(self); + } + else + { + if(self->enemy && visible(self, realEnemy)) + gotcha = true; + } + + // if we see the player, stop following hintpaths. + if (gotcha) + { +// if(g_showlogic && g_showlogic->value) +// gi.dprintf("stopped following hint paths in ai_run\n"); + + // disconnect from hintpaths and start looking normally for players. + hintpath_stop (self); + // pmm - no longer needed, since hintpath_stop does it +// HuntTarget(self); + } + return; + } +//PGM +//========== + + if (self->monsterinfo.aiflags & AI_SOUND_TARGET) + { + // PMM - paranoia checking + if (self->enemy) + VectorSubtract (self->s.origin, self->enemy->s.origin, v); + + if ((!self->enemy) || (VectorLength(v) < 64)) + // pmm + { + self->monsterinfo.aiflags |= (AI_STAND_GROUND | AI_TEMP_STAND_GROUND); + self->monsterinfo.stand (self); + return; + } + + M_MoveToGoal (self, dist); + // PMM - prevent double moves for sound_targets + alreadyMoved = true; + // pmm + if(!self->inuse) + return; // PGM - g_touchtrigger free problem + + if (!FindTarget (self)) + return; + } + + // PMM -- moved ai_checkattack up here so the monsters can attack while strafing or charging + + // PMM -- if we're dodging, make sure to keep the attack_state AS_SLIDING + + retval = ai_checkattack (self, dist); + + // PMM - don't strafe if we can't see our enemy + if ((!enemy_vis) && (self->monsterinfo.attack_state == AS_SLIDING)) + self->monsterinfo.attack_state = AS_STRAIGHT; + // unless we're dodging (dodging out of view looks smart) + if (self->monsterinfo.aiflags & AI_DODGING) + self->monsterinfo.attack_state = AS_SLIDING; + // pmm + + if (self->monsterinfo.attack_state == AS_SLIDING) + { + // PMM - protect against double moves + if (!alreadyMoved) + ai_run_slide (self, dist); + // PMM + // we're using attack_state as the return value out of ai_run_slide to indicate whether or not the + // move succeeded. If the move succeeded, and we're still sliding, we're done in here (since we've + // had our chance to shoot in ai_checkattack, and have moved). + // if the move failed, our state is as_straight, and it will be taken care of below + if ((!retval) && (self->monsterinfo.attack_state == AS_SLIDING)) + return; + } + else if (self->monsterinfo.aiflags & AI_CHARGING) + { + self->ideal_yaw = enemy_yaw; + if (!(self->monsterinfo.aiflags & AI_MANUAL_STEERING)) + M_ChangeYaw (self); + } + if (retval) + { + // PMM - is this useful? Monsters attacking usually call the ai_charge routine.. + // the only monster this affects should be the soldier + if ((dist != 0) && (!alreadyMoved) && (self->monsterinfo.attack_state == AS_STRAIGHT) && (!(self->monsterinfo.aiflags & AI_STAND_GROUND))) + { + M_MoveToGoal (self, dist); + } + if ((self->enemy) && (self->enemy->inuse) && (enemy_vis)) + { + self->monsterinfo.aiflags &= ~AI_LOST_SIGHT; + VectorCopy (self->enemy->s.origin, self->monsterinfo.last_sighting); + self->monsterinfo.trail_time = level.time; + //PMM + VectorCopy (self->enemy->s.origin, self->monsterinfo.blind_fire_target); + self->monsterinfo.blind_fire_delay = 0; + //pmm + } + return; + } + //PMM +// if (ai_checkattack (self, dist)) +// return; + +// if (self->monsterinfo.attack_state == AS_SLIDING) +// { +// ai_run_slide (self, dist); +// return; +// } + + // PGM - added a little paranoia checking here... 9/22/98 + if ((self->enemy) && (self->enemy->inuse) && (enemy_vis)) + { +// if (self->monsterinfo.aiflags & AI_LOST_SIGHT) +// gi.dprintf("regained sight\n"); + // PMM - check for alreadyMoved + if (!alreadyMoved) + M_MoveToGoal (self, dist); + if(!self->inuse) + return; // PGM - g_touchtrigger free problem + + self->monsterinfo.aiflags &= ~AI_LOST_SIGHT; + VectorCopy (self->enemy->s.origin, self->monsterinfo.last_sighting); + self->monsterinfo.trail_time = level.time; + // PMM + VectorCopy (self->enemy->s.origin, self->monsterinfo.blind_fire_target); + self->monsterinfo.blind_fire_delay = 0; + // pmm + return; + } + +//======= +//PGM + // if we've been looking (unsuccessfully) for the player for 10 seconds + // PMM - reduced to 5, makes them much nastier + if((self->monsterinfo.trail_time + 5) <= level.time) + { + // and we haven't checked for valid hint paths in the last 10 seconds + if((self->monsterinfo.last_hint_time + 10) <= level.time) + { + // check for hint_paths. + self->monsterinfo.last_hint_time = level.time; + if(monsterlost_checkhint(self)) + return; + } + } +//PGM +//======= + +// PMM - moved down here to allow monsters to get on hint paths + // coop will change to another enemy if visible + if (coop->value) + { // FIXME: insane guys get mad with this, which causes crashes! + if (FindTarget (self)) + return; + } +// pmm + + if ((self->monsterinfo.search_time) && (level.time > (self->monsterinfo.search_time + 20))) + { + // PMM - double move protection + if (!alreadyMoved) + M_MoveToGoal (self, dist); + self->monsterinfo.search_time = 0; +// gi.dprintf("search timeout\n"); + return; + } + + save = self->goalentity; + tempgoal = G_Spawn(); + self->goalentity = tempgoal; + + new = false; + + if (!(self->monsterinfo.aiflags & AI_LOST_SIGHT)) + { + // just lost sight of the player, decide where to go first +// gi.dprintf("lost sight of player, last seen at %s\n", vtos(self->monsterinfo.last_sighting)); + self->monsterinfo.aiflags |= (AI_LOST_SIGHT | AI_PURSUIT_LAST_SEEN); + self->monsterinfo.aiflags &= ~(AI_PURSUE_NEXT | AI_PURSUE_TEMP); + new = true; + } + + if (self->monsterinfo.aiflags & AI_PURSUE_NEXT) + { +// vec3_t debug_vec; + + self->monsterinfo.aiflags &= ~AI_PURSUE_NEXT; +// VectorSubtract(self->monsterinfo.last_sighting, self->s.origin, debug_vec); +// gi.dprintf("reached current goal: %s %s %f", vtos(self->s.origin), vtos(self->monsterinfo.last_sighting), VectorLength(debug_vec)); + + // give ourself more time since we got this far + self->monsterinfo.search_time = level.time + 5; + + if (self->monsterinfo.aiflags & AI_PURSUE_TEMP) + { +// gi.dprintf("was temp goal; retrying original\n"); + self->monsterinfo.aiflags &= ~AI_PURSUE_TEMP; + marker = NULL; + VectorCopy (self->monsterinfo.saved_goal, self->monsterinfo.last_sighting); + new = true; + } + else if (self->monsterinfo.aiflags & AI_PURSUIT_LAST_SEEN) + { + self->monsterinfo.aiflags &= ~AI_PURSUIT_LAST_SEEN; + marker = PlayerTrail_PickFirst (self); + } + else + { + marker = PlayerTrail_PickNext (self); + } + + if (marker) + { + VectorCopy (marker->s.origin, self->monsterinfo.last_sighting); + self->monsterinfo.trail_time = marker->timestamp; + self->s.angles[YAW] = self->ideal_yaw = marker->s.angles[YAW]; +// gi.dprintf("heading is %0.1f\n", self->ideal_yaw); + +// debug_drawline(self.origin, self.last_sighting, 52); + new = true; + } + } + + VectorSubtract (self->s.origin, self->monsterinfo.last_sighting, v); + d1 = VectorLength(v); + if (d1 <= dist) + { + self->monsterinfo.aiflags |= AI_PURSUE_NEXT; + dist = d1; + } + + VectorCopy (self->monsterinfo.last_sighting, self->goalentity->s.origin); + + if (new) + { +// gi.dprintf("checking for course correction\n"); + + tr = gi.trace(self->s.origin, self->mins, self->maxs, self->monsterinfo.last_sighting, self, MASK_PLAYERSOLID); + if (tr.fraction < 1) + { + VectorSubtract (self->goalentity->s.origin, self->s.origin, v); + d1 = VectorLength(v); + center = tr.fraction; + d2 = d1 * ((center+1)/2); + self->s.angles[YAW] = self->ideal_yaw = vectoyaw(v); + AngleVectors(self->s.angles, v_forward, v_right, NULL); + + VectorSet(v, d2, -16, 0); + G_ProjectSource (self->s.origin, v, v_forward, v_right, left_target); + tr = gi.trace(self->s.origin, self->mins, self->maxs, left_target, self, MASK_PLAYERSOLID); + left = tr.fraction; + + VectorSet(v, d2, 16, 0); + G_ProjectSource (self->s.origin, v, v_forward, v_right, right_target); + tr = gi.trace(self->s.origin, self->mins, self->maxs, right_target, self, MASK_PLAYERSOLID); + right = tr.fraction; + + center = (d1*center)/d2; + if (left >= center && left > right) + { + if (left < 1) + { + VectorSet(v, d2 * left * 0.5, -16, 0); + G_ProjectSource (self->s.origin, v, v_forward, v_right, left_target); +// gi.dprintf("incomplete path, go part way and adjust again\n"); + } + VectorCopy (self->monsterinfo.last_sighting, self->monsterinfo.saved_goal); + self->monsterinfo.aiflags |= AI_PURSUE_TEMP; + VectorCopy (left_target, self->goalentity->s.origin); + VectorCopy (left_target, self->monsterinfo.last_sighting); + VectorSubtract (self->goalentity->s.origin, self->s.origin, v); + self->s.angles[YAW] = self->ideal_yaw = vectoyaw(v); +// gi.dprintf("adjusted left\n"); +// debug_drawline(self.origin, self.last_sighting, 152); + } + else if (right >= center && right > left) + { + if (right < 1) + { + VectorSet(v, d2 * right * 0.5, 16, 0); + G_ProjectSource (self->s.origin, v, v_forward, v_right, right_target); +// gi.dprintf("incomplete path, go part way and adjust again\n"); + } + VectorCopy (self->monsterinfo.last_sighting, self->monsterinfo.saved_goal); + self->monsterinfo.aiflags |= AI_PURSUE_TEMP; + VectorCopy (right_target, self->goalentity->s.origin); + VectorCopy (right_target, self->monsterinfo.last_sighting); + VectorSubtract (self->goalentity->s.origin, self->s.origin, v); + self->s.angles[YAW] = self->ideal_yaw = vectoyaw(v); +// gi.dprintf("adjusted right\n"); +// debug_drawline(self.origin, self.last_sighting, 152); + } + } +// else gi.dprintf("course was fine\n"); + } + + M_MoveToGoal (self, dist); + if(!self->inuse) + return; // PGM - g_touchtrigger free problem + + G_FreeEdict(tempgoal); + + if (self) + self->goalentity = save; +} diff --git a/original/rogue/g_chase.c b/original/rogue/g_chase.c new file mode 100644 index 0000000..accf8e1 --- /dev/null +++ b/original/rogue/g_chase.c @@ -0,0 +1,158 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +#include "g_local.h" + +void UpdateChaseCam(edict_t *ent) +{ + vec3_t o, ownerv, goal; + edict_t *targ; + vec3_t forward, right; + trace_t trace; + int i; + vec3_t oldgoal; + vec3_t angles; + + // is our chase target gone? + if (!ent->client->chase_target->inuse + || ent->client->chase_target->client->resp.spectator) { + edict_t *old = ent->client->chase_target; + ChaseNext(ent); + if (ent->client->chase_target == old) { + ent->client->chase_target = NULL; + ent->client->ps.pmove.pm_flags &= ~PMF_NO_PREDICTION; + return; + } + } + + targ = ent->client->chase_target; + + VectorCopy(targ->s.origin, ownerv); + VectorCopy(ent->s.origin, oldgoal); + + ownerv[2] += targ->viewheight; + + VectorCopy(targ->client->v_angle, angles); + if (angles[PITCH] > 56) + angles[PITCH] = 56; + AngleVectors (angles, forward, right, NULL); + VectorNormalize(forward); + VectorMA(ownerv, -30, forward, o); + + if (o[2] < targ->s.origin[2] + 20) + o[2] = targ->s.origin[2] + 20; + + // jump animation lifts + if (!targ->groundentity) + o[2] += 16; + + trace = gi.trace(ownerv, vec3_origin, vec3_origin, o, targ, MASK_SOLID); + + VectorCopy(trace.endpos, goal); + + VectorMA(goal, 2, forward, goal); + + // pad for floors and ceilings + VectorCopy(goal, o); + o[2] += 6; + trace = gi.trace(goal, vec3_origin, vec3_origin, o, targ, MASK_SOLID); + if (trace.fraction < 1) { + VectorCopy(trace.endpos, goal); + goal[2] -= 6; + } + + VectorCopy(goal, o); + o[2] -= 6; + trace = gi.trace(goal, vec3_origin, vec3_origin, o, targ, MASK_SOLID); + if (trace.fraction < 1) { + VectorCopy(trace.endpos, goal); + goal[2] += 6; + } + + if (targ->deadflag) + ent->client->ps.pmove.pm_type = PM_DEAD; + else + ent->client->ps.pmove.pm_type = PM_FREEZE; + + VectorCopy(goal, ent->s.origin); + for (i=0 ; i<3 ; i++) + ent->client->ps.pmove.delta_angles[i] = ANGLE2SHORT(targ->client->v_angle[i] - ent->client->resp.cmd_angles[i]); + + if (targ->deadflag) { + ent->client->ps.viewangles[ROLL] = 40; + ent->client->ps.viewangles[PITCH] = -15; + ent->client->ps.viewangles[YAW] = targ->client->killer_yaw; + } else { + VectorCopy(targ->client->v_angle, ent->client->ps.viewangles); + VectorCopy(targ->client->v_angle, ent->client->v_angle); + } + + ent->viewheight = 0; + ent->client->ps.pmove.pm_flags |= PMF_NO_PREDICTION; + gi.linkentity(ent); +} + +void ChaseNext(edict_t *ent) +{ + int i; + edict_t *e; + + if (!ent->client->chase_target) + return; + + i = ent->client->chase_target - g_edicts; + do { + i++; + if (i > maxclients->value) + i = 1; + e = g_edicts + i; + if (!e->inuse) + continue; + if (!e->client->resp.spectator) + break; + } while (e != ent->client->chase_target); + + ent->client->chase_target = e; + ent->client->update_chase = true; +} + +void ChasePrev(edict_t *ent) +{ + int i; + edict_t *e; + + if (!ent->client->chase_target) + return; + + i = ent->client->chase_target - g_edicts; + do { + i--; + if (i < 1) + i = maxclients->value; + e = g_edicts + i; + if (!e->inuse) + continue; + if (!e->client->resp.spectator) + break; + } while (e != ent->client->chase_target); + + ent->client->chase_target = e; + ent->client->update_chase = true; +} + +void GetChaseTarget(edict_t *ent) +{ + int i; + edict_t *other; + + for (i = 1; i <= maxclients->value; i++) { + other = g_edicts + i; + if (other->inuse && !other->client->resp.spectator) { + ent->client->chase_target = other; + ent->client->update_chase = true; + UpdateChaseCam(ent); + return; + } + } + gi.centerprintf(ent, "No other players to chase."); +} + diff --git a/original/rogue/g_cmds.c b/original/rogue/g_cmds.c new file mode 100644 index 0000000..1444b6a --- /dev/null +++ b/original/rogue/g_cmds.c @@ -0,0 +1,1038 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +#include "g_local.h" +#include "m_player.h" + + +char *ClientTeam (edict_t *ent) +{ + char *p; + static char value[512]; + + value[0] = 0; + + if (!ent->client) + return value; + + strcpy(value, Info_ValueForKey (ent->client->pers.userinfo, "skin")); + p = strchr(value, '/'); + if (!p) + return value; + + if ((int)(dmflags->value) & DF_MODELTEAMS) + { + *p = 0; + return value; + } + + // if ((int)(dmflags->value) & DF_SKINTEAMS) + return ++p; +} + +qboolean OnSameTeam (edict_t *ent1, edict_t *ent2) +{ + char ent1Team [512]; + char ent2Team [512]; + + if (!((int)(dmflags->value) & (DF_MODELTEAMS | DF_SKINTEAMS))) + return false; + + strcpy (ent1Team, ClientTeam (ent1)); + strcpy (ent2Team, ClientTeam (ent2)); + + if (strcmp(ent1Team, ent2Team) == 0) + return true; + return false; +} + + +void SelectNextItem (edict_t *ent, int itflags) +{ + gclient_t *cl; + int i, index; + gitem_t *it; + + cl = ent->client; + + if (cl->chase_target) { + ChaseNext(ent); + return; + } + + // scan for the next valid one + for (i=1 ; i<=MAX_ITEMS ; i++) + { + index = (cl->pers.selected_item + i)%MAX_ITEMS; + if (!cl->pers.inventory[index]) + continue; + it = &itemlist[index]; + if (!it->use) + continue; + if (!(it->flags & itflags)) + continue; + + cl->pers.selected_item = index; + return; + } + + cl->pers.selected_item = -1; +} + +void SelectPrevItem (edict_t *ent, int itflags) +{ + gclient_t *cl; + int i, index; + gitem_t *it; + + cl = ent->client; + + if (cl->chase_target) { + ChasePrev(ent); + return; + } + + // scan for the next valid one + for (i=1 ; i<=MAX_ITEMS ; i++) + { + index = (cl->pers.selected_item + MAX_ITEMS - i)%MAX_ITEMS; + if (!cl->pers.inventory[index]) + continue; + it = &itemlist[index]; + if (!it->use) + continue; + if (!(it->flags & itflags)) + continue; + + cl->pers.selected_item = index; + return; + } + + cl->pers.selected_item = -1; +} + +void ValidateSelectedItem (edict_t *ent) +{ + gclient_t *cl; + + cl = ent->client; + + if (cl->pers.inventory[cl->pers.selected_item]) + return; // valid + + SelectNextItem (ent, -1); +} + + +//================================================================================= + +/* +================== +Cmd_Give_f + +Give items to a client +================== +*/ +void Cmd_Give_f (edict_t *ent) +{ + char *name; + gitem_t *it; + int index; + int i; + qboolean give_all; + edict_t *it_ent; + + if (deathmatch->value && !sv_cheats->value) + { + gi.cprintf (ent, PRINT_HIGH, "You must run the server with '+set cheats 1' to enable this command.\n"); + return; + } + + name = gi.args(); + + if (Q_stricmp(name, "all") == 0) + give_all = true; + else + give_all = false; + + if (give_all || Q_stricmp(gi.argv(1), "health") == 0) + { + if (gi.argc() == 3) + ent->health = atoi(gi.argv(2)); + else + ent->health = ent->max_health; + if (!give_all) + return; + } + + if (give_all || Q_stricmp(name, "weapons") == 0) + { + for (i=0 ; ipickup) + continue; + if (!(it->flags & IT_WEAPON)) + continue; + ent->client->pers.inventory[i] += 1; + } + if (!give_all) + return; + } + + if (give_all || Q_stricmp(name, "ammo") == 0) + { + for (i=0 ; ipickup) + continue; + if (!(it->flags & IT_AMMO)) + continue; + Add_Ammo (ent, it, 1000); + } + if (!give_all) + return; + } + + if (give_all || Q_stricmp(name, "armor") == 0) + { + gitem_armor_t *info; + + it = FindItem("Jacket Armor"); + ent->client->pers.inventory[ITEM_INDEX(it)] = 0; + + it = FindItem("Combat Armor"); + ent->client->pers.inventory[ITEM_INDEX(it)] = 0; + + it = FindItem("Body Armor"); + info = (gitem_armor_t *)it->info; + ent->client->pers.inventory[ITEM_INDEX(it)] = info->max_count; + + if (!give_all) + return; + } + + if (give_all || Q_stricmp(name, "Power Shield") == 0) + { + it = FindItem("Power Shield"); + it_ent = G_Spawn(); + it_ent->classname = it->classname; + SpawnItem (it_ent, it); + Touch_Item (it_ent, ent, NULL, NULL); + if (it_ent->inuse) + G_FreeEdict(it_ent); + + if (!give_all) + return; + } + + if (give_all) + { + for (i=0 ; ipickup) + continue; + if (it->flags & IT_NOT_GIVEABLE) // ROGUE + continue; // ROGUE + if (it->flags & (IT_ARMOR|IT_WEAPON|IT_AMMO)) + continue; + ent->client->pers.inventory[i] = 1; + } + return; + } + + it = FindItem (name); + if (!it) + { + name = gi.argv(1); + it = FindItem (name); + if (!it) + { + gi.cprintf (ent, PRINT_HIGH, "unknown item\n"); + return; + } + } + + if (!it->pickup) + { + gi.cprintf (ent, PRINT_HIGH, "non-pickup item\n"); + return; + } + +//ROGUE + if (it->flags & IT_NOT_GIVEABLE) + { + gi.dprintf ("item cannot be given\n"); + return; + } +//ROGUE + + index = ITEM_INDEX(it); + + if (it->flags & IT_AMMO) + { + if (gi.argc() == 3) + ent->client->pers.inventory[index] = atoi(gi.argv(2)); + else + ent->client->pers.inventory[index] += it->quantity; + } + else + { + it_ent = G_Spawn(); + it_ent->classname = it->classname; + SpawnItem (it_ent, it); + // PMM - since some items don't actually spawn when you say to .. + if (!it_ent->inuse) + return; + // pmm + Touch_Item (it_ent, ent, NULL, NULL); + if (it_ent->inuse) + G_FreeEdict(it_ent); + } +} + + +/* +================== +Cmd_God_f + +Sets client to godmode + +argv(0) god +================== +*/ +void Cmd_God_f (edict_t *ent) +{ + char *msg; + + if (deathmatch->value && !sv_cheats->value) + { + gi.cprintf (ent, PRINT_HIGH, "You must run the server with '+set cheats 1' to enable this command.\n"); + return; + } + + ent->flags ^= FL_GODMODE; + if (!(ent->flags & FL_GODMODE) ) + msg = "godmode OFF\n"; + else + msg = "godmode ON\n"; + + gi.cprintf (ent, PRINT_HIGH, msg); +} + + +/* +================== +Cmd_Notarget_f + +Sets client to notarget + +argv(0) notarget +================== +*/ +void Cmd_Notarget_f (edict_t *ent) +{ + char *msg; + + if (deathmatch->value && !sv_cheats->value) + { + gi.cprintf (ent, PRINT_HIGH, "You must run the server with '+set cheats 1' to enable this command.\n"); + return; + } + + ent->flags ^= FL_NOTARGET; + if (!(ent->flags & FL_NOTARGET) ) + msg = "notarget OFF\n"; + else + msg = "notarget ON\n"; + + gi.cprintf (ent, PRINT_HIGH, msg); +} + + +/* +================== +Cmd_Noclip_f + +argv(0) noclip +================== +*/ +void Cmd_Noclip_f (edict_t *ent) +{ + char *msg; + + if (deathmatch->value && !sv_cheats->value) + { + gi.cprintf (ent, PRINT_HIGH, "You must run the server with '+set cheats 1' to enable this command.\n"); + return; + } + + if (ent->movetype == MOVETYPE_NOCLIP) + { + ent->movetype = MOVETYPE_WALK; + msg = "noclip OFF\n"; + } + else + { + ent->movetype = MOVETYPE_NOCLIP; + msg = "noclip ON\n"; + } + + gi.cprintf (ent, PRINT_HIGH, msg); +} + + +/* +================== +Cmd_Use_f + +Use an inventory item +================== +*/ +void Cmd_Use_f (edict_t *ent) +{ + int index; + gitem_t *it; + char *s; + + s = gi.args(); + it = FindItem (s); + if (!it) + { + gi.cprintf (ent, PRINT_HIGH, "unknown item: %s\n", s); + return; + } + if (!it->use) + { + gi.cprintf (ent, PRINT_HIGH, "Item is not usable.\n"); + return; + } + index = ITEM_INDEX(it); + if (!ent->client->pers.inventory[index]) + { + gi.cprintf (ent, PRINT_HIGH, "Out of item: %s\n", s); + return; + } + + it->use (ent, it); +} + + +/* +================== +Cmd_Drop_f + +Drop an inventory item +================== +*/ +void Cmd_Drop_f (edict_t *ent) +{ + int index; + gitem_t *it; + char *s; + + s = gi.args(); + it = FindItem (s); + if (!it) + { + gi.cprintf (ent, PRINT_HIGH, "unknown item: %s\n", s); + return; + } + if (!it->drop) + { + gi.cprintf (ent, PRINT_HIGH, "Item is not dropable.\n"); + return; + } + index = ITEM_INDEX(it); + if (!ent->client->pers.inventory[index]) + { + gi.cprintf (ent, PRINT_HIGH, "Out of item: %s\n", s); + return; + } + + it->drop (ent, it); +} + + +/* +================= +Cmd_Inven_f +================= +*/ +void Cmd_Inven_f (edict_t *ent) +{ + int i; + gclient_t *cl; + + cl = ent->client; + + cl->showscores = false; + cl->showhelp = false; + + if (cl->showinventory) + { + cl->showinventory = false; + return; + } + + cl->showinventory = true; + + gi.WriteByte (svc_inventory); + for (i=0 ; ipers.inventory[i]); + } + gi.unicast (ent, true); +} + +/* +================= +Cmd_InvUse_f +================= +*/ +void Cmd_InvUse_f (edict_t *ent) +{ + gitem_t *it; + + ValidateSelectedItem (ent); + + if (ent->client->pers.selected_item == -1) + { + gi.cprintf (ent, PRINT_HIGH, "No item to use.\n"); + return; + } + + it = &itemlist[ent->client->pers.selected_item]; + if (!it->use) + { + gi.cprintf (ent, PRINT_HIGH, "Item is not usable.\n"); + return; + } + it->use (ent, it); +} + +/* +================= +Cmd_WeapPrev_f +================= +*/ +void Cmd_WeapPrev_f (edict_t *ent) +{ + gclient_t *cl; + int i, index; + gitem_t *it; + int selected_weapon; + + cl = ent->client; + + if (!cl->pers.weapon) + return; + + selected_weapon = ITEM_INDEX(cl->pers.weapon); + + // scan for the next valid one + for (i=1 ; i<=MAX_ITEMS ; i++) + { + // PMM - prevent scrolling through ALL weapons +// index = (selected_weapon + i)%MAX_ITEMS; + index = (selected_weapon + MAX_ITEMS - i)%MAX_ITEMS; + if (!cl->pers.inventory[index]) + continue; + it = &itemlist[index]; + if (!it->use) + continue; + if (! (it->flags & IT_WEAPON) ) + continue; + it->use (ent, it); + // PMM - prevent scrolling through ALL weapons +// if (cl->pers.weapon == it) +// return; // successful + if (cl->newweapon == it) + return; + } +} + +/* +================= +Cmd_WeapNext_f +================= +*/ +void Cmd_WeapNext_f (edict_t *ent) +{ + gclient_t *cl; + int i, index; + gitem_t *it; + int selected_weapon; + + cl = ent->client; + + if (!cl->pers.weapon) + return; + + selected_weapon = ITEM_INDEX(cl->pers.weapon); + + // scan for the next valid one + for (i=1 ; i<=MAX_ITEMS ; i++) + { + // PMM - prevent scrolling through ALL weapons +// index = (selected_weapon + MAX_ITEMS - i)%MAX_ITEMS; + index = (selected_weapon + i)%MAX_ITEMS; + if (!cl->pers.inventory[index]) + continue; + it = &itemlist[index]; + if (!it->use) + continue; + if (! (it->flags & IT_WEAPON) ) + continue; + it->use (ent, it); + // PMM - prevent scrolling through ALL weapons +// if (cl->pers.weapon == it) +// return; // successful + if (cl->newweapon == it) + return; + } +} + +/* +================= +Cmd_WeapLast_f +================= +*/ +void Cmd_WeapLast_f (edict_t *ent) +{ + gclient_t *cl; + int index; + gitem_t *it; + + cl = ent->client; + + if (!cl->pers.weapon || !cl->pers.lastweapon) + return; + + index = ITEM_INDEX(cl->pers.lastweapon); + if (!cl->pers.inventory[index]) + return; + it = &itemlist[index]; + if (!it->use) + return; + if (! (it->flags & IT_WEAPON) ) + return; + it->use (ent, it); +} + +/* +================= +Cmd_InvDrop_f +================= +*/ +void Cmd_InvDrop_f (edict_t *ent) +{ + gitem_t *it; + + ValidateSelectedItem (ent); + + if (ent->client->pers.selected_item == -1) + { + gi.cprintf (ent, PRINT_HIGH, "No item to drop.\n"); + return; + } + + it = &itemlist[ent->client->pers.selected_item]; + if (!it->drop) + { + gi.cprintf (ent, PRINT_HIGH, "Item is not dropable.\n"); + return; + } + it->drop (ent, it); +} + +/* +================= +Cmd_Kill_f +================= +*/ +void Cmd_Kill_f (edict_t *ent) +{ + if((level.time - ent->client->respawn_time) < 5) + return; + ent->flags &= ~FL_GODMODE; + ent->health = 0; + meansOfDeath = MOD_SUICIDE; + +//ROGUE + // make sure no trackers are still hurting us. + if(ent->client->tracker_pain_framenum) + RemoveAttackingPainDaemons (ent); + + if (ent->client->owned_sphere) + { + G_FreeEdict(ent->client->owned_sphere); + ent->client->owned_sphere = NULL; + } +//ROGUE + + player_die (ent, ent, ent, 100000, vec3_origin); +} + +/* +================= +Cmd_PutAway_f +================= +*/ +void Cmd_PutAway_f (edict_t *ent) +{ + ent->client->showscores = false; + ent->client->showhelp = false; + ent->client->showinventory = false; +} + + +int PlayerSort (void const *a, void const *b) +{ + int anum, bnum; + + anum = *(int *)a; + bnum = *(int *)b; + + anum = game.clients[anum].ps.stats[STAT_FRAGS]; + bnum = game.clients[bnum].ps.stats[STAT_FRAGS]; + + if (anum < bnum) + return -1; + if (anum > bnum) + return 1; + return 0; +} + +/* +================= +Cmd_Players_f +================= +*/ +void Cmd_Players_f (edict_t *ent) +{ + int i; + int count; + char small[64]; + char large[1280]; + int index[256]; + + count = 0; + for (i = 0 ; i < maxclients->value ; i++) + if (game.clients[i].pers.connected) + { + index[count] = i; + count++; + } + + // sort by frags + qsort (index, count, sizeof(index[0]), PlayerSort); + + // print information + large[0] = 0; + + for (i = 0 ; i < count ; i++) + { + Com_sprintf (small, sizeof(small), "%3i %s\n", + game.clients[index[i]].ps.stats[STAT_FRAGS], + game.clients[index[i]].pers.netname); + if (strlen (small) + strlen(large) > sizeof(large) - 100 ) + { // can't print all of them in one packet + strcat (large, "...\n"); + break; + } + strcat (large, small); + } + + gi.cprintf (ent, PRINT_HIGH, "%s\n%i players\n", large, count); +} + +/* +================= +Cmd_Wave_f +================= +*/ +void Cmd_Wave_f (edict_t *ent) +{ + int i; + + i = atoi (gi.argv(1)); + + // can't wave when ducked + if (ent->client->ps.pmove.pm_flags & PMF_DUCKED) + return; + + if (ent->client->anim_priority > ANIM_WAVE) + return; + + ent->client->anim_priority = ANIM_WAVE; + + switch (i) + { + case 0: + gi.cprintf (ent, PRINT_HIGH, "flipoff\n"); + ent->s.frame = FRAME_flip01-1; + ent->client->anim_end = FRAME_flip12; + break; + case 1: + gi.cprintf (ent, PRINT_HIGH, "salute\n"); + ent->s.frame = FRAME_salute01-1; + ent->client->anim_end = FRAME_salute11; + break; + case 2: + gi.cprintf (ent, PRINT_HIGH, "taunt\n"); + ent->s.frame = FRAME_taunt01-1; + ent->client->anim_end = FRAME_taunt17; + break; + case 3: + gi.cprintf (ent, PRINT_HIGH, "wave\n"); + ent->s.frame = FRAME_wave01-1; + ent->client->anim_end = FRAME_wave11; + break; + case 4: + default: + gi.cprintf (ent, PRINT_HIGH, "point\n"); + ent->s.frame = FRAME_point01-1; + ent->client->anim_end = FRAME_point12; + break; + } +} + +/* +================== +Cmd_Say_f +================== +*/ +void Cmd_Say_f (edict_t *ent, qboolean team, qboolean arg0) +{ + int i, j; + edict_t *other; + char *p; + char text[2048]; + gclient_t *cl; + + if (gi.argc () < 2 && !arg0) + return; + + if (!((int)(dmflags->value) & (DF_MODELTEAMS | DF_SKINTEAMS))) + team = false; + + if (team) + Com_sprintf (text, sizeof(text), "(%s): ", ent->client->pers.netname); + else + Com_sprintf (text, sizeof(text), "%s: ", ent->client->pers.netname); + + if (arg0) + { + strcat (text, gi.argv(0)); + strcat (text, " "); + strcat (text, gi.args()); + } + else + { + p = gi.args(); + + if (*p == '"') + { + p++; + p[strlen(p)-1] = 0; + } + strcat(text, p); + } + + // don't let text be too long for malicious reasons + if (strlen(text) > 150) + text[150] = 0; + + strcat(text, "\n"); + + if (flood_msgs->value) { + cl = ent->client; + + if (level.time < cl->flood_locktill) { + gi.cprintf(ent, PRINT_HIGH, "You can't talk for %d more seconds\n", + (int)(cl->flood_locktill - level.time)); + return; + } + i = cl->flood_whenhead - flood_msgs->value + 1; + if (i < 0) + i = (sizeof(cl->flood_when)/sizeof(cl->flood_when[0])) + i; + if (cl->flood_when[i] && + level.time - cl->flood_when[i] < flood_persecond->value) { + cl->flood_locktill = level.time + flood_waitdelay->value; + gi.cprintf(ent, PRINT_CHAT, "Flood protection: You can't talk for %d seconds.\n", + (int)flood_waitdelay->value); + return; + } + cl->flood_whenhead = (cl->flood_whenhead + 1) % + (sizeof(cl->flood_when)/sizeof(cl->flood_when[0])); + cl->flood_when[cl->flood_whenhead] = level.time; + } + + if (dedicated->value) + gi.cprintf(NULL, PRINT_CHAT, "%s", text); + + for (j = 1; j <= game.maxclients; j++) + { + other = &g_edicts[j]; + if (!other->inuse) + continue; + if (!other->client) + continue; + if (team) + { + if (!OnSameTeam(ent, other)) + continue; + } + gi.cprintf(other, PRINT_CHAT, "%s", text); + } +} + +//====== +//ROGUE +void Cmd_Ent_Count_f (edict_t *ent) +{ + int x; + edict_t *e; + + x=0; + + for (e=g_edicts;e < &g_edicts[globals.num_edicts] ; e++) + { + if(e->inuse) + x++; + } + + gi.dprintf("%d entites active\n", x); +} +//ROGUE +//====== + +void Cmd_PlayerList_f(edict_t *ent) +{ + int i; + char st[80]; + char text[1400]; + edict_t *e2; + + // connect time, ping, score, name + *text = 0; + for (i = 0, e2 = g_edicts + 1; i < maxclients->value; i++, e2++) { + if (!e2->inuse) + continue; + + Com_sprintf(st, sizeof(st), "%02d:%02d %4d %3d %s%s\n", + (level.framenum - e2->client->resp.enterframe) / 600, + ((level.framenum - e2->client->resp.enterframe) % 600)/10, + e2->client->ping, + e2->client->resp.score, + e2->client->pers.netname, + e2->client->resp.spectator ? " (spectator)" : ""); + if (strlen(text) + strlen(st) > sizeof(text) - 50) { + sprintf(text+strlen(text), "And more...\n"); + gi.cprintf(ent, PRINT_HIGH, "%s", text); + return; + } + strcat(text, st); + } + gi.cprintf(ent, PRINT_HIGH, "%s", text); +} + + +/* +================= +ClientCommand +================= +*/ +void ClientCommand (edict_t *ent) +{ + char *cmd; + + if (!ent->client) + return; // not fully in game yet + + cmd = gi.argv(0); + + if (Q_stricmp (cmd, "players") == 0) + { + Cmd_Players_f (ent); + return; + } + if (Q_stricmp (cmd, "say") == 0) + { + Cmd_Say_f (ent, false, false); + return; + } + if (Q_stricmp (cmd, "say_team") == 0) + { + Cmd_Say_f (ent, true, false); + return; + } + if (Q_stricmp (cmd, "score") == 0) + { + Cmd_Score_f (ent); + return; + } + if (Q_stricmp (cmd, "help") == 0) + { + Cmd_Help_f (ent); + return; + } + + if (level.intermissiontime) + return; + + if (Q_stricmp (cmd, "use") == 0) + Cmd_Use_f (ent); + else if (Q_stricmp (cmd, "drop") == 0) + Cmd_Drop_f (ent); + else if (Q_stricmp (cmd, "give") == 0) + Cmd_Give_f (ent); + else if (Q_stricmp (cmd, "god") == 0) + Cmd_God_f (ent); + else if (Q_stricmp (cmd, "notarget") == 0) + Cmd_Notarget_f (ent); + else if (Q_stricmp (cmd, "noclip") == 0) + Cmd_Noclip_f (ent); + else if (Q_stricmp (cmd, "inven") == 0) + Cmd_Inven_f (ent); + else if (Q_stricmp (cmd, "invnext") == 0) + SelectNextItem (ent, -1); + else if (Q_stricmp (cmd, "invprev") == 0) + SelectPrevItem (ent, -1); + else if (Q_stricmp (cmd, "invnextw") == 0) + SelectNextItem (ent, IT_WEAPON); + else if (Q_stricmp (cmd, "invprevw") == 0) + SelectPrevItem (ent, IT_WEAPON); + else if (Q_stricmp (cmd, "invnextp") == 0) + SelectNextItem (ent, IT_POWERUP); + else if (Q_stricmp (cmd, "invprevp") == 0) + SelectPrevItem (ent, IT_POWERUP); + else if (Q_stricmp (cmd, "invuse") == 0) + Cmd_InvUse_f (ent); + else if (Q_stricmp (cmd, "invdrop") == 0) + Cmd_InvDrop_f (ent); + else if (Q_stricmp (cmd, "weapprev") == 0) + Cmd_WeapPrev_f (ent); + else if (Q_stricmp (cmd, "weapnext") == 0) + Cmd_WeapNext_f (ent); + else if (Q_stricmp (cmd, "weaplast") == 0) + Cmd_WeapLast_f (ent); + else if (Q_stricmp (cmd, "kill") == 0) + Cmd_Kill_f (ent); + else if (Q_stricmp (cmd, "putaway") == 0) + Cmd_PutAway_f (ent); + else if (Q_stricmp (cmd, "wave") == 0) + Cmd_Wave_f (ent); + else if (Q_stricmp(cmd, "playerlist") == 0) + Cmd_PlayerList_f(ent); + else if (Q_stricmp (cmd, "entcount") == 0) // PGM + Cmd_Ent_Count_f (ent); // PGM + else if (Q_stricmp (cmd, "disguise") == 0) // PGM + { + ent->flags |= FL_DISGUISED; + } + else // anything that doesn't match a command will be a chat + Cmd_Say_f (ent, false, true); +} diff --git a/original/rogue/g_combat.c b/original/rogue/g_combat.c new file mode 100644 index 0000000..690b332 --- /dev/null +++ b/original/rogue/g_combat.c @@ -0,0 +1,949 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// g_combat.c + +#include "g_local.h" + +void M_SetEffects (edict_t *self); + +/* +ROGUE +clean up heal targets for medic +*/ +void cleanupHealTarget (edict_t *ent) +{ + ent->monsterinfo.healer = NULL; + ent->takedamage = DAMAGE_YES; + ent->monsterinfo.aiflags &= ~AI_RESURRECTING; + M_SetEffects (ent); +} +/* +============ +CanDamage + +Returns true if the inflictor can directly damage the target. Used for +explosions and melee attacks. +============ +*/ +qboolean CanDamage (edict_t *targ, edict_t *inflictor) +{ + vec3_t dest; + trace_t trace; + +// bmodels need special checking because their origin is 0,0,0 + if (targ->movetype == MOVETYPE_PUSH) + { + VectorAdd (targ->absmin, targ->absmax, dest); + VectorScale (dest, 0.5, dest); + trace = gi.trace (inflictor->s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID); + if (trace.fraction == 1.0) + return true; + if (trace.ent == targ) + return true; + return false; + } + + trace = gi.trace (inflictor->s.origin, vec3_origin, vec3_origin, targ->s.origin, inflictor, MASK_SOLID); + if (trace.fraction == 1.0) + return true; + + VectorCopy (targ->s.origin, dest); + dest[0] += 15.0; + dest[1] += 15.0; + trace = gi.trace (inflictor->s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID); + if (trace.fraction == 1.0) + return true; + + VectorCopy (targ->s.origin, dest); + dest[0] += 15.0; + dest[1] -= 15.0; + trace = gi.trace (inflictor->s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID); + if (trace.fraction == 1.0) + return true; + + VectorCopy (targ->s.origin, dest); + dest[0] -= 15.0; + dest[1] += 15.0; + trace = gi.trace (inflictor->s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID); + if (trace.fraction == 1.0) + return true; + + VectorCopy (targ->s.origin, dest); + dest[0] -= 15.0; + dest[1] -= 15.0; + trace = gi.trace (inflictor->s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID); + if (trace.fraction == 1.0) + return true; + + + return false; +} + + +/* +============ +Killed +============ +*/ +void Killed (edict_t *targ, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + if (targ->health < -999) + targ->health = -999; + + if (targ->monsterinfo.aiflags & AI_MEDIC) + { + if (targ->enemy) // god, I hope so + { + cleanupHealTarget (targ->enemy); + } + + // clean up self + targ->monsterinfo.aiflags &= ~AI_MEDIC; + targ->enemy = attacker; + } + else + { + targ->enemy = attacker; + } + + if ((targ->svflags & SVF_MONSTER) && (targ->deadflag != DEAD_DEAD)) + { +// targ->svflags |= SVF_DEADMONSTER; // now treat as a different content type + //ROGUE - free up slot for spawned monster if it's spawned + if (targ->monsterinfo.aiflags & AI_SPAWNED_CARRIER) + { + if (targ->monsterinfo.commander && targ->monsterinfo.commander->inuse && + !strcmp(targ->monsterinfo.commander->classname, "monster_carrier")) + { + targ->monsterinfo.commander->monsterinfo.monster_slots++; +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("g_combat: freeing up carrier slot - %d left\n", targ->monsterinfo.commander->monsterinfo.monster_slots); + } + } + if (targ->monsterinfo.aiflags & AI_SPAWNED_MEDIC_C) + { + if (targ->monsterinfo.commander) + { + if (targ->monsterinfo.commander->inuse && !strcmp(targ->monsterinfo.commander->classname, "monster_medic_commander")) + { + targ->monsterinfo.commander->monsterinfo.monster_slots++; +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("g_combat: freeing up medic slot - %d left\n", targ->monsterinfo.commander->monsterinfo.monster_slots); + } +// else +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("my commander is dead! he's a %s\n", targ->monsterinfo.commander->classname); + } +// else if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("My commander is GONE\n"); + + } + if (targ->monsterinfo.aiflags & AI_SPAWNED_WIDOW) + { + // need to check this because we can have variable numbers of coop players + if (targ->monsterinfo.commander && targ->monsterinfo.commander->inuse && + !strncmp(targ->monsterinfo.commander->classname, "monster_widow", 13)) + { + if (targ->monsterinfo.commander->monsterinfo.monster_used > 0) + targ->monsterinfo.commander->monsterinfo.monster_used--; +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("g_combat: freeing up black widow slot - %d used\n", targ->monsterinfo.commander->monsterinfo.monster_used); + } + } + //rogue + if ((!(targ->monsterinfo.aiflags & AI_GOOD_GUY)) && (!(targ->monsterinfo.aiflags & AI_DO_NOT_COUNT))) + { + level.killed_monsters++; + if (coop->value && attacker->client) + attacker->client->resp.score++; + // medics won't heal monsters that they kill themselves + // PMM - now they will +// if (strcmp(attacker->classname, "monster_medic") == 0) +// targ->owner = attacker; + } + } + + if (targ->movetype == MOVETYPE_PUSH || targ->movetype == MOVETYPE_STOP || targ->movetype == MOVETYPE_NONE) + { // doors, triggers, etc + targ->die (targ, inflictor, attacker, damage, point); + return; + } + + if ((targ->svflags & SVF_MONSTER) && (targ->deadflag != DEAD_DEAD)) + { + targ->touch = NULL; + monster_death_use (targ); + } + + targ->die (targ, inflictor, attacker, damage, point); +} + + +/* +================ +SpawnDamage +================ +*/ +void SpawnDamage (int type, vec3_t origin, vec3_t normal, int damage) +{ + if (damage > 255) + damage = 255; + gi.WriteByte (svc_temp_entity); + gi.WriteByte (type); +// gi.WriteByte (damage); + gi.WritePosition (origin); + gi.WriteDir (normal); + gi.multicast (origin, MULTICAST_PVS); +} + + +/* +============ +T_Damage + +targ entity that is being damaged +inflictor entity that is causing the damage +attacker entity that caused the inflictor to damage targ + example: targ=monster, inflictor=rocket, attacker=player + +dir direction of the attack +point point at which the damage is being inflicted +normal normal vector from that point +damage amount of damage being inflicted +knockback force to be applied against targ as a result of the damage + +dflags these flags are used to control how T_Damage works + DAMAGE_RADIUS damage was indirect (from a nearby explosion) + DAMAGE_NO_ARMOR armor does not protect from this damage + DAMAGE_ENERGY damage is from an energy based weapon + DAMAGE_NO_KNOCKBACK do not affect velocity, just view angles + DAMAGE_BULLET damage is from a bullet (used for ricochets) + DAMAGE_NO_PROTECTION kills godmode, armor, everything +============ +*/ +static int CheckPowerArmor (edict_t *ent, vec3_t point, vec3_t normal, int damage, int dflags) +{ + gclient_t *client; + int save; + int power_armor_type; + int index; + int damagePerCell; + int pa_te_type; + int power; + int power_used; + + if (!damage) + return 0; + + client = ent->client; + + if (dflags & (DAMAGE_NO_ARMOR | DAMAGE_NO_POWER_ARMOR)) // PGM + return 0; + + if (client) + { + power_armor_type = PowerArmorType (ent); + if (power_armor_type != POWER_ARMOR_NONE) + { + index = ITEM_INDEX(FindItem("Cells")); + power = client->pers.inventory[index]; + } + } + else if (ent->svflags & SVF_MONSTER) + { + power_armor_type = ent->monsterinfo.power_armor_type; + power = ent->monsterinfo.power_armor_power; + } + else + return 0; + + if (power_armor_type == POWER_ARMOR_NONE) + return 0; + if (!power) + return 0; + + if (power_armor_type == POWER_ARMOR_SCREEN) + { + vec3_t vec; + float dot; + vec3_t forward; + + // only works if damage point is in front + AngleVectors (ent->s.angles, forward, NULL, NULL); + VectorSubtract (point, ent->s.origin, vec); + VectorNormalize (vec); + dot = DotProduct (vec, forward); + if (dot <= 0.3) + return 0; + + damagePerCell = 1; + pa_te_type = TE_SCREEN_SPARKS; + damage = damage / 3; + } + else + { + damagePerCell = 2; + pa_te_type = TE_SHIELD_SPARKS; + damage = (2 * damage) / 3; + } + + // etf rifle + if (dflags & DAMAGE_NO_REG_ARMOR) + save = (power * damagePerCell) / 2; + else + save = power * damagePerCell; + + if (!save) + return 0; + if (save > damage) + save = damage; + + SpawnDamage (pa_te_type, point, normal, save); + ent->powerarmor_time = level.time + 0.2; + + if (dflags & DAMAGE_NO_REG_ARMOR) + power_used = (save / damagePerCell) * 2; + else + power_used = save / damagePerCell; + + if (client) + client->pers.inventory[index] -= power_used; + else + ent->monsterinfo.power_armor_power -= power_used; + return save; +} + +static int CheckArmor (edict_t *ent, vec3_t point, vec3_t normal, int damage, int te_sparks, int dflags) +{ + gclient_t *client; + int save; + int index; + gitem_t *armor; + + if (!damage) + return 0; + + client = ent->client; + + if (!client) + return 0; + + // ROGUE - added DAMAGE_NO_REG_ARMOR for atf rifle + if (dflags & (DAMAGE_NO_ARMOR | DAMAGE_NO_REG_ARMOR)) + return 0; + + index = ArmorIndex (ent); + if (!index) + return 0; + + armor = GetItemByIndex (index); + + if (dflags & DAMAGE_ENERGY) + save = ceil(((gitem_armor_t *)armor->info)->energy_protection*damage); + else + save = ceil(((gitem_armor_t *)armor->info)->normal_protection*damage); + if (save >= client->pers.inventory[index]) + save = client->pers.inventory[index]; + + if (!save) + return 0; + + client->pers.inventory[index] -= save; + SpawnDamage (te_sparks, point, normal, save); + + return save; +} + +void M_ReactToDamage (edict_t *targ, edict_t *attacker, edict_t *inflictor) +{ + // pmm + qboolean new_tesla; + + if (!(attacker->client) && !(attacker->svflags & SVF_MONSTER)) + return; + +//======= +//ROGUE + // logic for tesla - if you are hit by a tesla, and can't see who you should be mad at (attacker) + // attack the tesla + // also, target the tesla if it's a "new" tesla + if ((inflictor) && (!strcmp(inflictor->classname, "tesla"))) + { + new_tesla = MarkTeslaArea(targ, inflictor); + if (new_tesla) + TargetTesla (targ, inflictor); + return; + // FIXME - just ignore teslas when you're TARGET_ANGER or MEDIC +/* if (!(targ->enemy && (targ->monsterinfo.aiflags & (AI_TARGET_ANGER|AI_MEDIC)))) + { + // FIXME - coop issues? + if ((!targ->enemy) || (!visible(targ, targ->enemy))) + { + gi.dprintf ("can't see player, switching to tesla\n"); + TargetTesla (targ, inflictor); + return; + } + gi.dprintf ("can see player, ignoring tesla\n"); + } + else if ((g_showlogic) && (g_showlogic->value)) + gi.dprintf ("no enemy, or I'm doing other, more important things, than worrying about a damned tesla!\n"); +*/ + } +//ROGUE +//======= + + if (attacker == targ || attacker == targ->enemy) + return; + + // if we are a good guy monster and our attacker is a player + // or another good guy, do not get mad at them + if (targ->monsterinfo.aiflags & AI_GOOD_GUY) + { + if (attacker->client || (attacker->monsterinfo.aiflags & AI_GOOD_GUY)) + return; + } + +//PGM + // if we're currently mad at something a target_anger made us mad at, ignore + // damage + if (targ->enemy && targ->monsterinfo.aiflags & AI_TARGET_ANGER) + { + float percentHealth; + + // make sure whatever we were pissed at is still around. + if(targ->enemy->inuse) + { + percentHealth = (float)(targ->health) / (float)(targ->max_health); + if( targ->enemy->inuse && percentHealth > 0.33) + return; + } + + // remove the target anger flag + targ->monsterinfo.aiflags &= ~AI_TARGET_ANGER; + } +//PGM + +// PMM +// if we're healing someone, do like above and try to stay with them + if ((targ->enemy) && (targ->monsterinfo.aiflags & AI_MEDIC)) + { + float percentHealth; + + percentHealth = (float)(targ->health) / (float)(targ->max_health); + // ignore it some of the time + if( targ->enemy->inuse && percentHealth > 0.25) + return; + + // remove the medic flag + targ->monsterinfo.aiflags &= ~AI_MEDIC; + cleanupHealTarget (targ->enemy); + } +// PMM + + // we now know that we are not both good guys + + // if attacker is a client, get mad at them because he's good and we're not + if (attacker->client) + { + targ->monsterinfo.aiflags &= ~AI_SOUND_TARGET; + + // this can only happen in coop (both new and old enemies are clients) + // only switch if can't see the current enemy + if (targ->enemy && targ->enemy->client) + { + if (visible(targ, targ->enemy)) + { + targ->oldenemy = attacker; + return; + } + targ->oldenemy = targ->enemy; + } + targ->enemy = attacker; + if (!(targ->monsterinfo.aiflags & AI_DUCKED)) + FoundTarget (targ); + return; + } + + // it's the same base (walk/swim/fly) type and a different classname and it's not a tank + // (they spray too much), get mad at them + // PMM + // added medics to this + // FIXME - + // this really should be turned into an AI flag marking appropriate monsters as "don't shoot me" + // this also leads to the problem of tanks and medics being able to, at will, kill monsters with + // no chance of retaliation. My vote is to make those monsters who are designed as "don't shoot me" + // such that they also ignore being shot by monsters as well + /* + if (((targ->flags & (FL_FLY|FL_SWIM)) == (attacker->flags & (FL_FLY|FL_SWIM))) && + (strcmp (targ->classname, attacker->classname) != 0) && + (strcmp(attacker->classname, "monster_tank") != 0) && + (strcmp(attacker->classname, "monster_supertank") != 0) && + (strcmp(attacker->classname, "monster_makron") != 0) && + (strcmp(attacker->classname, "monster_jorg") != 0) && + (strcmp(attacker->classname, "monster_carrier") != 0) && + (strncmp(attacker->classname, "monster_medic", 12) != 0) ) // this should get medics & medic_commanders + */ + if (((targ->flags & (FL_FLY|FL_SWIM)) == (attacker->flags & (FL_FLY|FL_SWIM))) && + (strcmp (targ->classname, attacker->classname) != 0) && + !(attacker->monsterinfo.aiflags & AI_IGNORE_SHOTS) && + !(targ->monsterinfo.aiflags & AI_IGNORE_SHOTS) ) + { + if (targ->enemy && targ->enemy->client) + targ->oldenemy = targ->enemy; + targ->enemy = attacker; + if (!(targ->monsterinfo.aiflags & AI_DUCKED)) + FoundTarget (targ); + } + // if they *meant* to shoot us, then shoot back + else if (attacker->enemy == targ) + { + if (targ->enemy && targ->enemy->client) + targ->oldenemy = targ->enemy; + targ->enemy = attacker; + if (!(targ->monsterinfo.aiflags & AI_DUCKED)) + FoundTarget (targ); + } + // otherwise get mad at whoever they are mad at (help our buddy) unless it is us! + else if (attacker->enemy && attacker->enemy != targ) + { + if (targ->enemy && targ->enemy->client) + targ->oldenemy = targ->enemy; + targ->enemy = attacker->enemy; + if (!(targ->monsterinfo.aiflags & AI_DUCKED)) + FoundTarget (targ); + } +} + +qboolean CheckTeamDamage (edict_t *targ, edict_t *attacker) +{ + //FIXME make the next line real and uncomment this block + // if ((ability to damage a teammate == OFF) && (targ's team == attacker's team)) + return false; +} + +void T_Damage (edict_t *targ, edict_t *inflictor, edict_t *attacker, vec3_t dir, vec3_t point, vec3_t normal, int damage, int knockback, int dflags, int mod) +{ + gclient_t *client; + int take; + int save; + int asave; + int psave; + int te_sparks; + int sphere_notified; // PGM + + if (!targ->takedamage) + return; + + sphere_notified = false; // PGM + + // friendly fire avoidance + // if enabled you can't hurt teammates (but you can hurt yourself) + // knockback still occurs + if ((targ != attacker) && ((deathmatch->value && ((int)(dmflags->value) & (DF_MODELTEAMS | DF_SKINTEAMS))) || coop->value)) + { + if (OnSameTeam (targ, attacker)) + { + // PMM - nukes kill everyone + if (((int)(dmflags->value) & DF_NO_FRIENDLY_FIRE) && (mod != MOD_NUKE)) + damage = 0; + else + mod |= MOD_FRIENDLY_FIRE; + } + } + meansOfDeath = mod; + +//ROGUE + // allow the deathmatch game to change values + if (deathmatch->value && gamerules && gamerules->value) + { + if(DMGame.ChangeDamage) + damage = DMGame.ChangeDamage(targ, attacker, damage, mod); + if(DMGame.ChangeKnockback) + knockback = DMGame.ChangeKnockback(targ, attacker, knockback, mod); + + if(!damage) + return; + } +//ROGUE + + // easy mode takes half damage + if (skill->value == 0 && deathmatch->value == 0 && targ->client) + { + damage *= 0.5; + if (!damage) + damage = 1; + } + + client = targ->client; + + // PMM - defender sphere takes half damage + if ((client) && (client->owned_sphere) && (client->owned_sphere->spawnflags == 1)) + { + damage *= 0.5; + if (!damage) + damage = 1; + } + + if (dflags & DAMAGE_BULLET) + te_sparks = TE_BULLET_SPARKS; + else + te_sparks = TE_SPARKS; + + VectorNormalize(dir); + +// bonus damage for suprising a monster + if (!(dflags & DAMAGE_RADIUS) && (targ->svflags & SVF_MONSTER) && (attacker->client) && (!targ->enemy) && (targ->health > 0)) + damage *= 2; + + if (targ->flags & FL_NO_KNOCKBACK) + knockback = 0; + +// figure momentum add + if (!(dflags & DAMAGE_NO_KNOCKBACK)) + { + if ((knockback) && (targ->movetype != MOVETYPE_NONE) && (targ->movetype != MOVETYPE_BOUNCE) && (targ->movetype != MOVETYPE_PUSH) && (targ->movetype != MOVETYPE_STOP)) + { + vec3_t kvel; + float mass; + + if (targ->mass < 50) + mass = 50; + else + mass = targ->mass; + + if (targ->client && attacker == targ) + VectorScale (dir, 1600.0 * (float)knockback / mass, kvel); // the rocket jump hack... + else + VectorScale (dir, 500.0 * (float)knockback / mass, kvel); + + VectorAdd (targ->velocity, kvel, targ->velocity); + } + } + + take = damage; + save = 0; + + // check for godmode + if ( (targ->flags & FL_GODMODE) && !(dflags & DAMAGE_NO_PROTECTION) ) + { + take = 0; + save = damage; + SpawnDamage (te_sparks, point, normal, save); + } + + // check for invincibility + if ((client && client->invincible_framenum > level.framenum ) && !(dflags & DAMAGE_NO_PROTECTION)) + { + if (targ->pain_debounce_time < level.time) + { + gi.sound(targ, CHAN_ITEM, gi.soundindex("items/protect4.wav"), 1, ATTN_NORM, 0); + targ->pain_debounce_time = level.time + 2; + } + take = 0; + save = damage; + } + // ROGUE + // check for monster invincibility + if (((targ->svflags & SVF_MONSTER) && targ->monsterinfo.invincible_framenum > level.framenum ) && !(dflags & DAMAGE_NO_PROTECTION)) + { + if (targ->pain_debounce_time < level.time) + { + gi.sound(targ, CHAN_ITEM, gi.soundindex("items/protect4.wav"), 1, ATTN_NORM, 0); + targ->pain_debounce_time = level.time + 2; + } + take = 0; + save = damage; + } + // ROGUE + + psave = CheckPowerArmor (targ, point, normal, take, dflags); + take -= psave; + + asave = CheckArmor (targ, point, normal, take, te_sparks, dflags); + take -= asave; + + //treat cheat/powerup savings the same as armor + asave += save; + + // team damage avoidance + if (!(dflags & DAMAGE_NO_PROTECTION) && CheckTeamDamage (targ, attacker)) + return; + +// ROGUE - this option will do damage both to the armor and person. originally for DPU rounds + if (dflags & DAMAGE_DESTROY_ARMOR) + { + if(!(targ->flags & FL_GODMODE) && !(dflags & DAMAGE_NO_PROTECTION) && + !(client && client->invincible_framenum > level.framenum)) + { + take = damage; + } + } +// ROGUE + +// do the damage + if (take) + { +//PGM need more blood for chainfist. + if(targ->flags & FL_MECHANICAL) + { + SpawnDamage ( TE_ELECTRIC_SPARKS, point, normal, take); + } + else if ((targ->svflags & SVF_MONSTER) || (client)) + { + if(mod == MOD_CHAINFIST) + SpawnDamage (TE_MOREBLOOD, point, normal, 255); + else + SpawnDamage (TE_BLOOD, point, normal, take); + } + else + SpawnDamage (te_sparks, point, normal, take); +//PGM + + targ->health = targ->health - take; + +//PGM - spheres need to know who to shoot at + if(client && client->owned_sphere) + { + sphere_notified = true; + if(client->owned_sphere->pain) + client->owned_sphere->pain (client->owned_sphere, attacker, 0, 0); + } +//PGM + + if (targ->health <= 0) + { + if ((targ->svflags & SVF_MONSTER) || (client)) + targ->flags |= FL_NO_KNOCKBACK; + Killed (targ, inflictor, attacker, take, point); + return; + } + } + +//PGM - spheres need to know who to shoot at + if (!sphere_notified) + { + if(client && client->owned_sphere) + { + sphere_notified = true; + if(client->owned_sphere->pain) + client->owned_sphere->pain (client->owned_sphere, attacker, 0, 0); + } + } +//PGM + + if (targ->svflags & SVF_MONSTER) + { + M_ReactToDamage (targ, attacker, inflictor); + // PMM - fixme - if anyone else but the medic ever uses AI_MEDIC, check for it here instead + // of in the medic's pain function + if (!(targ->monsterinfo.aiflags & AI_DUCKED) && (take)) + { + targ->pain (targ, attacker, knockback, take); + // nightmare mode monsters don't go into pain frames often + if (skill->value == 3) + targ->pain_debounce_time = level.time + 5; + } + } + else if (client) + { + if (!(targ->flags & FL_GODMODE) && (take)) + targ->pain (targ, attacker, knockback, take); + } + else if (take) + { + if (targ->pain) + targ->pain (targ, attacker, knockback, take); + } + + // add to the damage inflicted on a player this frame + // the total will be turned into screen blends and view angle kicks + // at the end of the frame + if (client) + { + client->damage_parmor += psave; + client->damage_armor += asave; + client->damage_blood += take; + client->damage_knockback += knockback; + VectorCopy (point, client->damage_from); + } +} + + +/* +============ +T_RadiusDamage +============ +*/ +void T_RadiusDamage (edict_t *inflictor, edict_t *attacker, float damage, edict_t *ignore, float radius, int mod) +{ + float points; + edict_t *ent = NULL; + vec3_t v; + vec3_t dir; + + while ((ent = findradius(ent, inflictor->s.origin, radius)) != NULL) + { + if (ent == ignore) + continue; + if (!ent->takedamage) + continue; + + VectorAdd (ent->mins, ent->maxs, v); + VectorMA (ent->s.origin, 0.5, v, v); + VectorSubtract (inflictor->s.origin, v, v); + points = damage - 0.5 * VectorLength (v); + if (ent == attacker) + points = points * 0.5; + if (points > 0) + { + if (CanDamage (ent, inflictor)) + { + VectorSubtract (ent->s.origin, inflictor->s.origin, dir); + T_Damage (ent, inflictor, attacker, dir, inflictor->s.origin, vec3_origin, (int)points, (int)points, DAMAGE_RADIUS, mod); + } + } + } +} + +// ********************** +// ROGUE + +/* +============ +T_RadiusNukeDamage + +Like T_RadiusDamage, but ignores walls (skips CanDamage check, among others) +// up to KILLZONE radius, do 10,000 points +// after that, do damage linearly out to KILLZONE2 radius +============ +*/ + +void T_RadiusNukeDamage (edict_t *inflictor, edict_t *attacker, float damage, edict_t *ignore, float radius, int mod) +{ + float points; + edict_t *ent = NULL; + vec3_t v; + vec3_t dir; + float len; + float killzone, killzone2; + trace_t tr; + float dist; + + killzone = radius; + killzone2 = radius*2.0; + + while ((ent = findradius(ent, inflictor->s.origin, killzone2)) != NULL) + { +// ignore nobody + if (ent == ignore) + continue; + if (!ent->takedamage) + continue; + if (!ent->inuse) + continue; + if (!(ent->client || (ent->svflags & SVF_MONSTER) || (ent->svflags & SVF_DAMAGEABLE))) + continue; + + VectorAdd (ent->mins, ent->maxs, v); + VectorMA (ent->s.origin, 0.5, v, v); + VectorSubtract (inflictor->s.origin, v, v); + len = VectorLength(v); + if (len <= killzone) + { + if (ent->client) + ent->flags |= FL_NOGIB; + points = 10000; + } + else if (len <= killzone2) + points = (damage/killzone)*(killzone2 - len); + else + points = 0; +// points = damage - 0.005 * len*len; +// if (ent == attacker) +// points = points * 0.5; +// if ((g_showlogic) && (g_showlogic->value)) +// { +// if (!(strcmp(ent->classname, "player"))) +// gi.dprintf ("dist = %2.2f doing %6.0f damage to %s\n", len, points, inflictor->teammaster->client->pers.netname); +// else +// gi.dprintf ("dist = %2.2f doing %6.0f damage to %s\n", len, points, ent->classname); +// } + if (points > 0) + { +// if (CanDamage (ent, inflictor)) +// { + if (ent->client) + ent->client->nuke_framenum = level.framenum + 20; + VectorSubtract (ent->s.origin, inflictor->s.origin, dir); + T_Damage (ent, inflictor, attacker, dir, inflictor->s.origin, vec3_origin, (int)points, (int)points, DAMAGE_RADIUS, mod); +// } + } + } + ent = g_edicts+1; // skip the worldspawn + // cycle through players + while (ent) + { + if ((ent->client) && (ent->client->nuke_framenum != level.framenum+20) && (ent->inuse)) + { + tr = gi.trace (inflictor->s.origin, NULL, NULL, ent->s.origin, inflictor, MASK_SOLID); + if (tr.fraction == 1.0) + { +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("Undamaged player in LOS with nuke, flashing!\n"); + ent->client->nuke_framenum = level.framenum + 20; + } + else + { + dist = realrange (ent, inflictor); + if (dist < 2048) + ent->client->nuke_framenum = max(ent->client->nuke_framenum,level.framenum + 15); + else + ent->client->nuke_framenum = max(ent->client->nuke_framenum,level.framenum + 10); + } + ent++; + } + else + ent = NULL; + } +} + +/* +============ +T_RadiusClassDamage + +Like T_RadiusDamage, but ignores anything with classname=ignoreClass +============ +*/ +void T_RadiusClassDamage (edict_t *inflictor, edict_t *attacker, float damage, char *ignoreClass, float radius, int mod) +{ + float points; + edict_t *ent = NULL; + vec3_t v; + vec3_t dir; + + while ((ent = findradius(ent, inflictor->s.origin, radius)) != NULL) + { + if (ent->classname && !strcmp(ent->classname, ignoreClass)) + continue; + if (!ent->takedamage) + continue; + + VectorAdd (ent->mins, ent->maxs, v); + VectorMA (ent->s.origin, 0.5, v, v); + VectorSubtract (inflictor->s.origin, v, v); + points = damage - 0.5 * VectorLength (v); + if (ent == attacker) + points = points * 0.5; + if (points > 0) + { + if (CanDamage (ent, inflictor)) + { + VectorSubtract (ent->s.origin, inflictor->s.origin, dir); + T_Damage (ent, inflictor, attacker, dir, inflictor->s.origin, vec3_origin, (int)points, (int)points, DAMAGE_RADIUS, mod); + } + } + } +} + +// ROGUE +// ******************** \ No newline at end of file diff --git a/original/rogue/g_func.c b/original/rogue/g_func.c new file mode 100644 index 0000000..cbbe479 --- /dev/null +++ b/original/rogue/g_func.c @@ -0,0 +1,2827 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +#include "g_local.h" + +/* +========================================================= + + PLATS + + movement options: + + linear + smooth start, hard stop + smooth start, smooth stop + + start + end + acceleration + speed + deceleration + begin sound + end sound + target fired when reaching end + wait at end + + object characteristics that use move segments + --------------------------------------------- + movetype_push, or movetype_stop + action when touched + action when blocked + action when used + disabled? + auto trigger spawning + + +========================================================= +*/ + +#define PLAT_LOW_TRIGGER 1 + +//==== +//PGM +#define PLAT2_TOGGLE 2 +#define PLAT2_TOP 4 +#define PLAT2_TRIGGER_TOP 8 +#define PLAT2_TRIGGER_BOTTOM 16 +#define PLAT2_BOX_LIFT 32 + +void plat2_spawn_danger_area (edict_t *ent); +void plat2_kill_danger_area (edict_t *ent); + +//PGM +//==== + +#define STATE_TOP 0 +#define STATE_BOTTOM 1 +#define STATE_UP 2 +#define STATE_DOWN 3 + +#define DOOR_START_OPEN 1 +#define DOOR_REVERSE 2 +#define DOOR_CRUSHER 4 +#define DOOR_NOMONSTER 8 +#define DOOR_TOGGLE 32 +#define DOOR_X_AXIS 64 +#define DOOR_Y_AXIS 128 +// !easy 256 +// !med 512 +// !hard 1024 +// !dm 2048 +// !coop 4096 +#define DOOR_INACTIVE 8192 + +// +// Support routines for movement (changes in origin using velocity) +// + +void Move_Done (edict_t *ent) +{ + VectorClear (ent->velocity); + ent->moveinfo.endfunc (ent); +} + +void Move_Final (edict_t *ent) +{ + if (ent->moveinfo.remaining_distance == 0) + { + Move_Done (ent); + return; + } + + VectorScale (ent->moveinfo.dir, ent->moveinfo.remaining_distance / FRAMETIME, ent->velocity); + + ent->think = Move_Done; + ent->nextthink = level.time + FRAMETIME; +} + +void Move_Begin (edict_t *ent) +{ + float frames; + + if ((ent->moveinfo.speed * FRAMETIME) >= ent->moveinfo.remaining_distance) + { + Move_Final (ent); + return; + } + VectorScale (ent->moveinfo.dir, ent->moveinfo.speed, ent->velocity); + frames = floor((ent->moveinfo.remaining_distance / ent->moveinfo.speed) / FRAMETIME); + ent->moveinfo.remaining_distance -= frames * ent->moveinfo.speed * FRAMETIME; + ent->nextthink = level.time + (frames * FRAMETIME); + ent->think = Move_Final; +} + +void Think_AccelMove (edict_t *ent); + +void Move_Calc (edict_t *ent, vec3_t dest, void(*func)(edict_t*)) +{ + VectorClear (ent->velocity); + VectorSubtract (dest, ent->s.origin, ent->moveinfo.dir); + ent->moveinfo.remaining_distance = VectorNormalize (ent->moveinfo.dir); + ent->moveinfo.endfunc = func; + + if (ent->moveinfo.speed == ent->moveinfo.accel && ent->moveinfo.speed == ent->moveinfo.decel) + { + if (level.current_entity == ((ent->flags & FL_TEAMSLAVE) ? ent->teammaster : ent)) + { + Move_Begin (ent); + } + else + { + ent->nextthink = level.time + FRAMETIME; + ent->think = Move_Begin; + } + } + else + { + // accelerative + ent->moveinfo.current_speed = 0; + ent->think = Think_AccelMove; + ent->nextthink = level.time + FRAMETIME; + } +} + + +// +// Support routines for angular movement (changes in angle using avelocity) +// + +void AngleMove_Done (edict_t *ent) +{ + VectorClear (ent->avelocity); + ent->moveinfo.endfunc (ent); +} + +void AngleMove_Final (edict_t *ent) +{ + vec3_t move; + + if (ent->moveinfo.state == STATE_UP) + VectorSubtract (ent->moveinfo.end_angles, ent->s.angles, move); + else + VectorSubtract (ent->moveinfo.start_angles, ent->s.angles, move); + + if (VectorCompare (move, vec3_origin)) + { + AngleMove_Done (ent); + return; + } + + VectorScale (move, 1.0/FRAMETIME, ent->avelocity); + + ent->think = AngleMove_Done; + ent->nextthink = level.time + FRAMETIME; +} + +void AngleMove_Begin (edict_t *ent) +{ + vec3_t destdelta; + float len; + float traveltime; + float frames; + +//PGM accelerate as needed + if(ent->moveinfo.speed < ent->speed) + { + ent->moveinfo.speed += ent->accel; + if(ent->moveinfo.speed > ent->speed) + ent->moveinfo.speed = ent->speed; + } +//PGM + + // set destdelta to the vector needed to move + if (ent->moveinfo.state == STATE_UP) + VectorSubtract (ent->moveinfo.end_angles, ent->s.angles, destdelta); + else + VectorSubtract (ent->moveinfo.start_angles, ent->s.angles, destdelta); + + // calculate length of vector + len = VectorLength (destdelta); + + // divide by speed to get time to reach dest + traveltime = len / ent->moveinfo.speed; + + if (traveltime < FRAMETIME) + { + AngleMove_Final (ent); + return; + } + + frames = floor(traveltime / FRAMETIME); + + // scale the destdelta vector by the time spent traveling to get velocity + VectorScale (destdelta, 1.0 / traveltime, ent->avelocity); + +//PGM + // if we're done accelerating, act as a normal rotation + if(ent->moveinfo.speed >= ent->speed) + { + // set nextthink to trigger a think when dest is reached + ent->nextthink = level.time + frames * FRAMETIME; + ent->think = AngleMove_Final; + } + else + { + ent->nextthink = level.time + FRAMETIME; + ent->think = AngleMove_Begin; + } +//PGM +} + +void AngleMove_Calc (edict_t *ent, void(*func)(edict_t*)) +{ + VectorClear (ent->avelocity); + ent->moveinfo.endfunc = func; + +//PGM + // if we're supposed to accelerate, this will tell anglemove_begin to do so + if(ent->accel != ent->speed) + ent->moveinfo.speed = 0; +//PGM + + if (level.current_entity == ((ent->flags & FL_TEAMSLAVE) ? ent->teammaster : ent)) + { + AngleMove_Begin (ent); + } + else + { + ent->nextthink = level.time + FRAMETIME; + ent->think = AngleMove_Begin; + } +} + + +/* +============== +Think_AccelMove + +The team has completed a frame of movement, so +change the speed for the next frame +============== +*/ +#define AccelerationDistance(target, rate) (target * ((target / rate) + 1) / 2) + +void plat_CalcAcceleratedMove(moveinfo_t *moveinfo) +{ + float accel_dist; + float decel_dist; + + moveinfo->move_speed = moveinfo->speed; + + if (moveinfo->remaining_distance < moveinfo->accel) + { + moveinfo->current_speed = moveinfo->remaining_distance; + return; + } + + accel_dist = AccelerationDistance (moveinfo->speed, moveinfo->accel); + decel_dist = AccelerationDistance (moveinfo->speed, moveinfo->decel); + + if ((moveinfo->remaining_distance - accel_dist - decel_dist) < 0) + { + float f; + + f = (moveinfo->accel + moveinfo->decel) / (moveinfo->accel * moveinfo->decel); + moveinfo->move_speed = (-2 + sqrt(4 - 4 * f * (-2 * moveinfo->remaining_distance))) / (2 * f); + decel_dist = AccelerationDistance (moveinfo->move_speed, moveinfo->decel); + } + + moveinfo->decel_distance = decel_dist; +}; + +void plat_Accelerate (moveinfo_t *moveinfo) +{ + // are we decelerating? + if (moveinfo->remaining_distance <= moveinfo->decel_distance) + { + if (moveinfo->remaining_distance < moveinfo->decel_distance) + { + if (moveinfo->next_speed) + { + moveinfo->current_speed = moveinfo->next_speed; + moveinfo->next_speed = 0; + return; + } + if (moveinfo->current_speed > moveinfo->decel) + moveinfo->current_speed -= moveinfo->decel; + } + return; + } + + // are we at full speed and need to start decelerating during this move? + if (moveinfo->current_speed == moveinfo->move_speed) + if ((moveinfo->remaining_distance - moveinfo->current_speed) < moveinfo->decel_distance) + { + float p1_distance; + float p2_distance; + float distance; + + p1_distance = moveinfo->remaining_distance - moveinfo->decel_distance; + p2_distance = moveinfo->move_speed * (1.0 - (p1_distance / moveinfo->move_speed)); + distance = p1_distance + p2_distance; + moveinfo->current_speed = moveinfo->move_speed; + moveinfo->next_speed = moveinfo->move_speed - moveinfo->decel * (p2_distance / distance); + return; + } + + // are we accelerating? + if (moveinfo->current_speed < moveinfo->speed) + { + float old_speed; + float p1_distance; + float p1_speed; + float p2_distance; + float distance; + + old_speed = moveinfo->current_speed; + + // figure simple acceleration up to move_speed + moveinfo->current_speed += moveinfo->accel; + if (moveinfo->current_speed > moveinfo->speed) + moveinfo->current_speed = moveinfo->speed; + + // are we accelerating throughout this entire move? + if ((moveinfo->remaining_distance - moveinfo->current_speed) >= moveinfo->decel_distance) + return; + + // during this move we will accelrate from current_speed to move_speed + // and cross over the decel_distance; figure the average speed for the + // entire move + p1_distance = moveinfo->remaining_distance - moveinfo->decel_distance; + p1_speed = (old_speed + moveinfo->move_speed) / 2.0; + p2_distance = moveinfo->move_speed * (1.0 - (p1_distance / p1_speed)); + distance = p1_distance + p2_distance; + moveinfo->current_speed = (p1_speed * (p1_distance / distance)) + (moveinfo->move_speed * (p2_distance / distance)); + moveinfo->next_speed = moveinfo->move_speed - moveinfo->decel * (p2_distance / distance); + return; + } + + // we are at constant velocity (move_speed) + return; +}; + +void Think_AccelMove (edict_t *ent) +{ + ent->moveinfo.remaining_distance -= ent->moveinfo.current_speed; + + // PGM 04/21/98 - this should fix sthoms' sinking drop pod. Hopefully it wont break stuff. +// if (ent->moveinfo.current_speed == 0) // starting or blocked + plat_CalcAcceleratedMove(&ent->moveinfo); + + plat_Accelerate (&ent->moveinfo); + + // will the entire move complete on next frame? + if (ent->moveinfo.remaining_distance <= ent->moveinfo.current_speed) + { + Move_Final (ent); + return; + } + + VectorScale (ent->moveinfo.dir, ent->moveinfo.current_speed*10, ent->velocity); + ent->nextthink = level.time + FRAMETIME; + ent->think = Think_AccelMove; +} + + +void plat_go_down (edict_t *ent); + +void plat_hit_top (edict_t *ent) +{ + if (!(ent->flags & FL_TEAMSLAVE)) + { + if (ent->moveinfo.sound_end) + gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, ent->moveinfo.sound_end, 1, ATTN_STATIC, 0); + ent->s.sound = 0; + } + ent->moveinfo.state = STATE_TOP; + + ent->think = plat_go_down; + ent->nextthink = level.time + 3; +} + +void plat_hit_bottom (edict_t *ent) +{ + if (!(ent->flags & FL_TEAMSLAVE)) + { + if (ent->moveinfo.sound_end) + gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, ent->moveinfo.sound_end, 1, ATTN_STATIC, 0); + ent->s.sound = 0; + } + ent->moveinfo.state = STATE_BOTTOM; + + plat2_kill_danger_area (ent); // PGM +} + +void plat_go_down (edict_t *ent) +{ + if (!(ent->flags & FL_TEAMSLAVE)) + { + if (ent->moveinfo.sound_start) + gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, ent->moveinfo.sound_start, 1, ATTN_STATIC, 0); + ent->s.sound = ent->moveinfo.sound_middle; + } + ent->moveinfo.state = STATE_DOWN; + Move_Calc (ent, ent->moveinfo.end_origin, plat_hit_bottom); +} + +void plat_go_up (edict_t *ent) +{ + if (!(ent->flags & FL_TEAMSLAVE)) + { + if (ent->moveinfo.sound_start) + gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, ent->moveinfo.sound_start, 1, ATTN_STATIC, 0); + ent->s.sound = ent->moveinfo.sound_middle; + } + ent->moveinfo.state = STATE_UP; + Move_Calc (ent, ent->moveinfo.start_origin, plat_hit_top); + + plat2_spawn_danger_area(ent); // PGM +} + +void plat_blocked (edict_t *self, edict_t *other) +{ + if (!(other->svflags & SVF_MONSTER) && (!other->client) ) + { + // give it a chance to go away on it's own terms (like gibs) + T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, 100000, 1, 0, MOD_CRUSH); + // if it's still there, nuke it + if (other && other->inuse) // PGM + BecomeExplosion1 (other); + return; + } + +//PGM + // gib dead things + if(other->health < 1) + { + T_Damage(other, self, self, vec3_origin, other->s.origin, vec3_origin, 100, 1, 0, MOD_CRUSH); + } +//PGM + + T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH); + + if (self->moveinfo.state == STATE_UP) + plat_go_down (self); + else if (self->moveinfo.state == STATE_DOWN) + plat_go_up (self); +} + + +void Use_Plat (edict_t *ent, edict_t *other, edict_t *activator) +{ +//====== +//ROGUE + // if a monster is using us, then allow the activity when stopped. + if (other->svflags & SVF_MONSTER) + { + if (ent->moveinfo.state == STATE_TOP) + plat_go_down (ent); + else if (ent->moveinfo.state == STATE_BOTTOM) + plat_go_up (ent); + + return; + } +//ROGUE +//====== + + if (ent->think) + return; // already down + plat_go_down (ent); +} + + +void Touch_Plat_Center (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (!other->client) + return; + + if (other->health <= 0) + return; + + ent = ent->enemy; // now point at the plat, not the trigger + if (ent->moveinfo.state == STATE_BOTTOM) + plat_go_up (ent); + else if (ent->moveinfo.state == STATE_TOP) + ent->nextthink = level.time + 1; // the player is still on the plat, so delay going down +} + +// PGM - plat2's change the trigger field +//void plat_spawn_inside_trigger (edict_t *ent) +edict_t *plat_spawn_inside_trigger (edict_t *ent) +{ + edict_t *trigger; + vec3_t tmin, tmax; + +// +// middle trigger +// + trigger = G_Spawn(); + trigger->touch = Touch_Plat_Center; + trigger->movetype = MOVETYPE_NONE; + trigger->solid = SOLID_TRIGGER; + trigger->enemy = ent; + + tmin[0] = ent->mins[0] + 25; + tmin[1] = ent->mins[1] + 25; + tmin[2] = ent->mins[2]; + + tmax[0] = ent->maxs[0] - 25; + tmax[1] = ent->maxs[1] - 25; + tmax[2] = ent->maxs[2] + 8; + + tmin[2] = tmax[2] - (ent->pos1[2] - ent->pos2[2] + st.lip); + + if (ent->spawnflags & PLAT_LOW_TRIGGER) + tmax[2] = tmin[2] + 8; + + if (tmax[0] - tmin[0] <= 0) + { + tmin[0] = (ent->mins[0] + ent->maxs[0]) *0.5; + tmax[0] = tmin[0] + 1; + } + if (tmax[1] - tmin[1] <= 0) + { + tmin[1] = (ent->mins[1] + ent->maxs[1]) *0.5; + tmax[1] = tmin[1] + 1; + } + + VectorCopy (tmin, trigger->mins); + VectorCopy (tmax, trigger->maxs); + + gi.linkentity (trigger); + + return trigger; // PGM 11/17/97 +} + + +/*QUAKED func_plat (0 .5 .8) ? PLAT_LOW_TRIGGER +speed default 150 + +Plats are always drawn in the extended position, so they will light correctly. + +If the plat is the target of another trigger or button, it will start out disabled in the extended position until it is trigger, when it will lower and become a normal plat. + +"speed" overrides default 200. +"accel" overrides default 500 +"lip" overrides default 8 pixel lip + +If the "height" key is set, that will determine the amount the plat moves, instead of being implicitly determoveinfoned by the model's height. + +Set "sounds" to one of the following: +1) base fast +2) chain slow +*/ +void SP_func_plat (edict_t *ent) +{ + VectorClear (ent->s.angles); + ent->solid = SOLID_BSP; + ent->movetype = MOVETYPE_PUSH; + + gi.setmodel (ent, ent->model); + + ent->blocked = plat_blocked; + + if (!ent->speed) + ent->speed = 20; + else + ent->speed *= 0.1; + + if (!ent->accel) + ent->accel = 5; + else + ent->accel *= 0.1; + + if (!ent->decel) + ent->decel = 5; + else + ent->decel *= 0.1; + + if (!ent->dmg) + ent->dmg = 2; + + if (!st.lip) + st.lip = 8; + + // pos1 is the top position, pos2 is the bottom + VectorCopy (ent->s.origin, ent->pos1); + VectorCopy (ent->s.origin, ent->pos2); + if (st.height) + ent->pos2[2] -= st.height; + else + ent->pos2[2] -= (ent->maxs[2] - ent->mins[2]) - st.lip; + + ent->use = Use_Plat; + + plat_spawn_inside_trigger (ent); // the "start moving" trigger + + if (ent->targetname) + { + ent->moveinfo.state = STATE_UP; + } + else + { + VectorCopy (ent->pos2, ent->s.origin); + gi.linkentity (ent); + ent->moveinfo.state = STATE_BOTTOM; + } + + ent->moveinfo.speed = ent->speed; + ent->moveinfo.accel = ent->accel; + ent->moveinfo.decel = ent->decel; + ent->moveinfo.wait = ent->wait; + VectorCopy (ent->pos1, ent->moveinfo.start_origin); + VectorCopy (ent->s.angles, ent->moveinfo.start_angles); + VectorCopy (ent->pos2, ent->moveinfo.end_origin); + VectorCopy (ent->s.angles, ent->moveinfo.end_angles); + + ent->moveinfo.sound_start = gi.soundindex ("plats/pt1_strt.wav"); + ent->moveinfo.sound_middle = gi.soundindex ("plats/pt1_mid.wav"); + ent->moveinfo.sound_end = gi.soundindex ("plats/pt1_end.wav"); +} + +// ========================================== +// PLAT 2 +// ========================================== +#define PLAT2_CALLED 1 +#define PLAT2_MOVING 2 +#define PLAT2_WAITING 4 + +void plat2_go_down (edict_t *ent); +void plat2_go_up (edict_t *ent); + +void plat2_spawn_danger_area (edict_t *ent) +{ + vec3_t mins, maxs; + + VectorCopy(ent->mins, mins); + VectorCopy(ent->maxs, maxs); + maxs[2] = ent->mins[2] + 64; + + SpawnBadArea(mins, maxs, 0, ent); +} + +void plat2_kill_danger_area (edict_t *ent) +{ + edict_t *t; + + t = NULL; + while ((t = G_Find (t, FOFS(classname), "bad_area"))) + { + if(t->owner == ent) + G_FreeEdict(t); + } +} + +void plat2_hit_top (edict_t *ent) +{ + if (!(ent->flags & FL_TEAMSLAVE)) + { + if (ent->moveinfo.sound_end) + gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, ent->moveinfo.sound_end, 1, ATTN_STATIC, 0); + ent->s.sound = 0; + } + ent->moveinfo.state = STATE_TOP; + + if(ent->plat2flags & PLAT2_CALLED) + { + ent->plat2flags = PLAT2_WAITING; + if(!(ent->spawnflags & PLAT2_TOGGLE)) + { + ent->think = plat2_go_down; + ent->nextthink = level.time + 5.0; + } + if(deathmatch->value) + ent->last_move_time = level.time - 1.0; + else + ent->last_move_time = level.time - 2.0; + } + else if(!(ent->spawnflags & PLAT2_TOP) && !(ent->spawnflags & PLAT2_TOGGLE)) + { + ent->plat2flags = 0; + ent->think = plat2_go_down; + ent->nextthink = level.time + 2.0; + ent->last_move_time = level.time; + } + else + { + ent->plat2flags = 0; + ent->last_move_time = level.time; + } + + G_UseTargets (ent, ent); +} + +void plat2_hit_bottom (edict_t *ent) +{ + if (!(ent->flags & FL_TEAMSLAVE)) + { + if (ent->moveinfo.sound_end) + gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, ent->moveinfo.sound_end, 1, ATTN_STATIC, 0); + ent->s.sound = 0; + } + ent->moveinfo.state = STATE_BOTTOM; + + if(ent->plat2flags & PLAT2_CALLED) + { + ent->plat2flags = PLAT2_WAITING; + if(!(ent->spawnflags & PLAT2_TOGGLE)) + { + ent->think = plat2_go_up; + ent->nextthink = level.time + 5.0; + } + if(deathmatch->value) + ent->last_move_time = level.time - 1.0; + else + ent->last_move_time = level.time - 2.0; + } + else if ((ent->spawnflags & PLAT2_TOP) && !(ent->spawnflags & PLAT2_TOGGLE)) + { + ent->plat2flags = 0; + ent->think = plat2_go_up; + ent->nextthink = level.time + 2.0; + ent->last_move_time = level.time; + } + else + { + ent->plat2flags = 0; + ent->last_move_time = level.time; + } + + plat2_kill_danger_area (ent); + G_UseTargets (ent, ent); +} + +void plat2_go_down (edict_t *ent) +{ + if (!(ent->flags & FL_TEAMSLAVE)) + { + if (ent->moveinfo.sound_start) + gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, ent->moveinfo.sound_start, 1, ATTN_STATIC, 0); + ent->s.sound = ent->moveinfo.sound_middle; + } + ent->moveinfo.state = STATE_DOWN; + ent->plat2flags |= PLAT2_MOVING; + + Move_Calc (ent, ent->moveinfo.end_origin, plat2_hit_bottom); +} + +void plat2_go_up (edict_t *ent) +{ + if (!(ent->flags & FL_TEAMSLAVE)) + { + if (ent->moveinfo.sound_start) + gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, ent->moveinfo.sound_start, 1, ATTN_STATIC, 0); + ent->s.sound = ent->moveinfo.sound_middle; + } + ent->moveinfo.state = STATE_UP; + ent->plat2flags |= PLAT2_MOVING; + + plat2_spawn_danger_area(ent); + + Move_Calc (ent, ent->moveinfo.start_origin, plat2_hit_top); +} + +void plat2_operate (edict_t *ent, edict_t *other) +{ + int otherState; + float pauseTime; + float platCenter; + edict_t *trigger; + + trigger = ent; + ent = ent->enemy; // now point at the plat, not the trigger + + if (ent->plat2flags & PLAT2_MOVING) + return; + + if ((ent->last_move_time + 2) > level.time) + return; + + platCenter = (trigger->absmin[2] + trigger->absmax[2]) / 2; + + if(ent->moveinfo.state == STATE_TOP) + { + otherState = STATE_TOP; + if(ent->spawnflags & PLAT2_BOX_LIFT) + { + if(platCenter > other->s.origin[2]) + otherState = STATE_BOTTOM; + } + else + { + if(trigger->absmax[2] > other->s.origin[2]) + otherState = STATE_BOTTOM; + } + } + else + { + otherState = STATE_BOTTOM; + if(other->s.origin[2] > platCenter) + otherState = STATE_TOP; + } + + ent->plat2flags = PLAT2_MOVING; + + if(deathmatch->value) + pauseTime = 0.3; + else + pauseTime = 0.5; + + if(ent->moveinfo.state != otherState) + { + ent->plat2flags |= PLAT2_CALLED; + pauseTime = 0.1; + } + + ent->last_move_time = level.time; + + if(ent->moveinfo.state == STATE_BOTTOM) + { + ent->think = plat2_go_up; + ent->nextthink = level.time + pauseTime; + } + else + { + ent->think = plat2_go_down; + ent->nextthink = level.time + pauseTime; + } +} + +void Touch_Plat_Center2 (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + // this requires monsters to actively trigger plats, not just step on them. + + //FIXME - commented out for E3 + //if (!other->client) + // return; + + if (other->health <= 0) + return; + + // PMM - don't let non-monsters activate plat2s + if ((!(other->svflags & SVF_MONSTER)) && (!other->client)) + return; + + plat2_operate(ent, other); +} + +void plat2_blocked (edict_t *self, edict_t *other) +{ + if (!(other->svflags & SVF_MONSTER) && (!other->client)) + { + // give it a chance to go away on it's own terms (like gibs) + T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, 100000, 1, 0, MOD_CRUSH); + // if it's still there, nuke it + if(other && other->inuse) + BecomeExplosion1 (other); + return; + } + + // gib dead things + if(other->health < 1) + { + T_Damage(other, self, self, vec3_origin, other->s.origin, vec3_origin, 100, 1, 0, MOD_CRUSH); + } + + T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH); + + if (self->moveinfo.state == STATE_UP) + plat2_go_down (self); + else if (self->moveinfo.state == STATE_DOWN) + plat2_go_up (self); +} + +void Use_Plat2 (edict_t *ent, edict_t *other, edict_t *activator) +{ + edict_t *trigger; + int i; + + if(ent->moveinfo.state > STATE_BOTTOM) + return; + if((ent->last_move_time + 2) > level.time) + return; + + for (i = 1, trigger = g_edicts + 1; i < globals.num_edicts; i++, trigger++) + { + if (!trigger->inuse) + continue; + if (trigger->touch == Touch_Plat_Center2) + { + if (trigger->enemy == ent) + { +// Touch_Plat_Center2 (trigger, activator, NULL, NULL); + plat2_operate (trigger, activator); + return; + } + } + } +} + +void plat2_activate (edict_t *ent, edict_t *other, edict_t *activator) +{ + edict_t *trigger; + +// if(ent->targetname) +// ent->targetname[0] = 0; + + ent->use = Use_Plat2; + + trigger = plat_spawn_inside_trigger (ent); // the "start moving" trigger + + trigger->maxs[0]+=10; + trigger->maxs[1]+=10; + trigger->mins[0]-=10; + trigger->mins[1]-=10; + + gi.linkentity (trigger); + + trigger->touch = Touch_Plat_Center2; // Override trigger touch function + + plat2_go_down(ent); +} + +/*QUAKED func_plat2 (0 .5 .8) ? PLAT_LOW_TRIGGER PLAT2_TOGGLE PLAT2_TOP PLAT2_TRIGGER_TOP PLAT2_TRIGGER_BOTTOM BOX_LIFT +speed default 150 + +PLAT_LOW_TRIGGER - creates a short trigger field at the bottom +PLAT2_TOGGLE - plat will not return to default position. +PLAT2_TOP - plat's default position will the the top. +PLAT2_TRIGGER_TOP - plat will trigger it's targets each time it hits top +PLAT2_TRIGGER_BOTTOM - plat will trigger it's targets each time it hits bottom +BOX_LIFT - this indicates that the lift is a box, rather than just a platform + +Plats are always drawn in the extended position, so they will light correctly. + +If the plat is the target of another trigger or button, it will start out disabled in the extended position until it is trigger, when it will lower and become a normal plat. + +"speed" overrides default 200. +"accel" overrides default 500 +"lip" no default + +If the "height" key is set, that will determine the amount the plat moves, instead of being implicitly determoveinfoned by the model's height. + +*/ +void SP_func_plat2 (edict_t *ent) +{ + edict_t *trigger; + + VectorClear (ent->s.angles); + ent->solid = SOLID_BSP; + ent->movetype = MOVETYPE_PUSH; + + gi.setmodel (ent, ent->model); + + ent->blocked = plat2_blocked; + + if (!ent->speed) + ent->speed = 20; + else + ent->speed *= 0.1; + + if (!ent->accel) + ent->accel = 5; + else + ent->accel *= 0.1; + + if (!ent->decel) + ent->decel = 5; + else + ent->decel *= 0.1; + + if (deathmatch->value) + { + ent->speed *= 2; + ent->accel *= 2; + ent->decel *= 2; + } + + + //PMM Added to kill things it's being blocked by + if (!ent->dmg) + ent->dmg = 2; + +// if (!st.lip) +// st.lip = 8; + + // pos1 is the top position, pos2 is the bottom + VectorCopy (ent->s.origin, ent->pos1); + VectorCopy (ent->s.origin, ent->pos2); + + if (st.height) + ent->pos2[2] -= (st.height - st.lip); + else + ent->pos2[2] -= (ent->maxs[2] - ent->mins[2]) - st.lip; + + ent->moveinfo.state = STATE_TOP; + + if(ent->targetname) + { + ent->use = plat2_activate; + } + else + { + ent->use = Use_Plat2; + + trigger = plat_spawn_inside_trigger (ent); // the "start moving" trigger + + // PGM - debugging?? + trigger->maxs[0]+=10; + trigger->maxs[1]+=10; + trigger->mins[0]-=10; + trigger->mins[1]-=10; + + gi.linkentity (trigger); + + trigger->touch = Touch_Plat_Center2; // Override trigger touch function + + if(!(ent->spawnflags & PLAT2_TOP)) + { + VectorCopy (ent->pos2, ent->s.origin); + ent->moveinfo.state = STATE_BOTTOM; + } + } + + gi.linkentity (ent); + + ent->moveinfo.speed = ent->speed; + ent->moveinfo.accel = ent->accel; + ent->moveinfo.decel = ent->decel; + ent->moveinfo.wait = ent->wait; + VectorCopy (ent->pos1, ent->moveinfo.start_origin); + VectorCopy (ent->s.angles, ent->moveinfo.start_angles); + VectorCopy (ent->pos2, ent->moveinfo.end_origin); + VectorCopy (ent->s.angles, ent->moveinfo.end_angles); + + ent->moveinfo.sound_start = gi.soundindex ("plats/pt1_strt.wav"); + ent->moveinfo.sound_middle = gi.soundindex ("plats/pt1_mid.wav"); + ent->moveinfo.sound_end = gi.soundindex ("plats/pt1_end.wav"); +} + + +//==================================================================== + +/*QUAKED func_rotating (0 .5 .8) ? START_ON REVERSE X_AXIS Y_AXIS TOUCH_PAIN STOP ANIMATED ANIMATED_FAST EAST MED HARD DM COOP ACCEL +You need to have an origin brush as part of this entity. The center of that brush will be +the point around which it is rotated. It will rotate around the Z axis by default. You can +check either the X_AXIS or Y_AXIS box to change that. + +func_rotating will use it's targets when it stops and starts. + +"speed" determines how fast it moves; default value is 100. +"dmg" damage to inflict when blocked (2 default) +"accel" if specified, is how much the rotation speed will increase per .1sec. + +REVERSE will cause the it to rotate in the opposite direction. +STOP mean it will stop moving instead of pushing entities +ACCEL means it will accelerate to it's final speed and decelerate when shutting down. +*/ + +//============ +//PGM +void rotating_accel (edict_t *self) +{ + float current_speed; + + current_speed = VectorLength (self->avelocity); + if(current_speed >= (self->speed - self->accel)) // done + { + VectorScale (self->movedir, self->speed, self->avelocity); + G_UseTargets (self, self); + } + else + { + current_speed += self->accel; + VectorScale (self->movedir, current_speed, self->avelocity); + self->think = rotating_accel; + self->nextthink = level.time + FRAMETIME; + } +} + +void rotating_decel (edict_t *self) +{ + float current_speed; + + current_speed = VectorLength (self->avelocity); + if(current_speed <= self->decel) // done + { + VectorClear (self->avelocity); + G_UseTargets (self, self); + self->touch = NULL; + } + else + { + current_speed -= self->decel; + VectorScale (self->movedir, current_speed, self->avelocity); + self->think = rotating_decel; + self->nextthink = level.time + FRAMETIME; + } +} +//PGM +//============ + + +void rotating_blocked (edict_t *self, edict_t *other) +{ + T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH); +} + +void rotating_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (self->avelocity[0] || self->avelocity[1] || self->avelocity[2]) + T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH); +} + +void rotating_use (edict_t *self, edict_t *other, edict_t *activator) +{ + if (!VectorCompare (self->avelocity, vec3_origin)) + { + self->s.sound = 0; +//PGM + if(self->spawnflags & 8192) // Decelerate + rotating_decel (self); + else + { + VectorClear (self->avelocity); + G_UseTargets (self, self); + self->touch = NULL; + } +//PGM + } + else + { + self->s.sound = self->moveinfo.sound_middle; +//PGM + if(self->spawnflags & 8192) // accelerate + rotating_accel (self); + else + { + VectorScale (self->movedir, self->speed, self->avelocity); + G_UseTargets (self, self); + } + if (self->spawnflags & 16) + self->touch = rotating_touch; +//PGM + } +} + +void SP_func_rotating (edict_t *ent) +{ + ent->solid = SOLID_BSP; + if (ent->spawnflags & 32) + ent->movetype = MOVETYPE_STOP; + else + ent->movetype = MOVETYPE_PUSH; + + // set the axis of rotation + VectorClear(ent->movedir); + if (ent->spawnflags & 4) + ent->movedir[2] = 1.0; + else if (ent->spawnflags & 8) + ent->movedir[0] = 1.0; + else // Z_AXIS + ent->movedir[1] = 1.0; + + // check for reverse rotation + if (ent->spawnflags & 2) + VectorNegate (ent->movedir, ent->movedir); + + if (!ent->speed) + ent->speed = 100; + if (!ent->dmg) + ent->dmg = 2; + +// ent->moveinfo.sound_middle = "doors/hydro1.wav"; + + ent->use = rotating_use; + if (ent->dmg) + ent->blocked = rotating_blocked; + + if (ent->spawnflags & 1) + ent->use (ent, NULL, NULL); + + if (ent->spawnflags & 64) + ent->s.effects |= EF_ANIM_ALL; + if (ent->spawnflags & 128) + ent->s.effects |= EF_ANIM_ALLFAST; + +//PGM + if(ent->spawnflags & 8192) // Accelerate / Decelerate + { + if(!ent->accel) + ent->accel = 1; + else if (ent->accel > ent->speed) + ent->accel = ent->speed; + + if(!ent->decel) + ent->decel = 1; + else if (ent->decel > ent->speed) + ent->decel = ent->speed; + } +//PGM + + gi.setmodel (ent, ent->model); + gi.linkentity (ent); +} + +/* +====================================================================== + +BUTTONS + +====================================================================== +*/ + +/*QUAKED func_button (0 .5 .8) ? +When a button is touched, it moves some distance in the direction of it's angle, triggers all of it's targets, waits some time, then returns to it's original position where it can be triggered again. + +"angle" determines the opening direction +"target" all entities with a matching targetname will be used +"speed" override the default 40 speed +"wait" override the default 1 second wait (-1 = never return) +"lip" override the default 4 pixel lip remaining at end of move +"health" if set, the button must be killed instead of touched +"sounds" +1) silent +2) steam metal +3) wooden clunk +4) metallic click +5) in-out +*/ + +void button_done (edict_t *self) +{ + self->moveinfo.state = STATE_BOTTOM; + self->s.effects &= ~EF_ANIM23; + self->s.effects |= EF_ANIM01; +} + +void button_return (edict_t *self) +{ + self->moveinfo.state = STATE_DOWN; + + Move_Calc (self, self->moveinfo.start_origin, button_done); + + self->s.frame = 0; + + if (self->health) + self->takedamage = DAMAGE_YES; +} + +void button_wait (edict_t *self) +{ + self->moveinfo.state = STATE_TOP; + self->s.effects &= ~EF_ANIM01; + self->s.effects |= EF_ANIM23; + + G_UseTargets (self, self->activator); + self->s.frame = 1; + if (self->moveinfo.wait >= 0) + { + self->nextthink = level.time + self->moveinfo.wait; + self->think = button_return; + } +} + +void button_fire (edict_t *self) +{ + if (self->moveinfo.state == STATE_UP || self->moveinfo.state == STATE_TOP) + return; + + self->moveinfo.state = STATE_UP; + if (self->moveinfo.sound_start && !(self->flags & FL_TEAMSLAVE)) + gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_start, 1, ATTN_STATIC, 0); + Move_Calc (self, self->moveinfo.end_origin, button_wait); +} + +void button_use (edict_t *self, edict_t *other, edict_t *activator) +{ + self->activator = activator; + button_fire (self); +} + +void button_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (!other->client) + return; + + if (other->health <= 0) + return; + + self->activator = other; + button_fire (self); +} + +void button_killed (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + self->activator = attacker; + self->health = self->max_health; + self->takedamage = DAMAGE_NO; + button_fire (self); +} + +void SP_func_button (edict_t *ent) +{ + vec3_t abs_movedir; + float dist; + + G_SetMovedir (ent->s.angles, ent->movedir); + ent->movetype = MOVETYPE_STOP; + ent->solid = SOLID_BSP; + gi.setmodel (ent, ent->model); + + if (ent->sounds != 1) + ent->moveinfo.sound_start = gi.soundindex ("switches/butn2.wav"); + + if (!ent->speed) + ent->speed = 40; + if (!ent->accel) + ent->accel = ent->speed; + if (!ent->decel) + ent->decel = ent->speed; + + if (!ent->wait) + ent->wait = 3; + if (!st.lip) + st.lip = 4; + + VectorCopy (ent->s.origin, ent->pos1); + abs_movedir[0] = fabs(ent->movedir[0]); + abs_movedir[1] = fabs(ent->movedir[1]); + abs_movedir[2] = fabs(ent->movedir[2]); + dist = abs_movedir[0] * ent->size[0] + abs_movedir[1] * ent->size[1] + abs_movedir[2] * ent->size[2] - st.lip; + VectorMA (ent->pos1, dist, ent->movedir, ent->pos2); + + ent->use = button_use; + ent->s.effects |= EF_ANIM01; + + if (ent->health) + { + ent->max_health = ent->health; + ent->die = button_killed; + ent->takedamage = DAMAGE_YES; + } + else if (! ent->targetname) + ent->touch = button_touch; + + ent->moveinfo.state = STATE_BOTTOM; + + ent->moveinfo.speed = ent->speed; + ent->moveinfo.accel = ent->accel; + ent->moveinfo.decel = ent->decel; + ent->moveinfo.wait = ent->wait; + VectorCopy (ent->pos1, ent->moveinfo.start_origin); + VectorCopy (ent->s.angles, ent->moveinfo.start_angles); + VectorCopy (ent->pos2, ent->moveinfo.end_origin); + VectorCopy (ent->s.angles, ent->moveinfo.end_angles); + + gi.linkentity (ent); +} + +/* +====================================================================== + +DOORS + + spawn a trigger surrounding the entire team unless it is + already targeted by another + +====================================================================== +*/ + +/*QUAKED func_door (0 .5 .8) ? START_OPEN x CRUSHER NOMONSTER ANIMATED TOGGLE ANIMATED_FAST +TOGGLE wait in both the start and end states for a trigger event. +START_OPEN the door to moves to its destination when spawned, and operate in reverse. It is used to temporarily or permanently close off an area when triggered (not useful for touch or takedamage doors). +NOMONSTER monsters will not trigger this door + +"message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet +"angle" determines the opening direction +"targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door. +"health" if set, door must be shot open +"speed" movement speed (100 default) +"wait" wait before returning (3 default, -1 = never return) +"lip" lip remaining at end of move (8 default) +"dmg" damage to inflict when blocked (2 default) +"sounds" +1) silent +2) light +3) medium +4) heavy +*/ + +void door_use_areaportals (edict_t *self, qboolean open) +{ + edict_t *t = NULL; + + if (!self->target) + return; + + while ((t = G_Find (t, FOFS(targetname), self->target))) + { + if (Q_stricmp(t->classname, "func_areaportal") == 0) + { + gi.SetAreaPortalState (t->style, open); + } + } +} + +void door_go_down (edict_t *self); + +void door_hit_top (edict_t *self) +{ + if (!(self->flags & FL_TEAMSLAVE)) + { + if (self->moveinfo.sound_end) + gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_end, 1, ATTN_STATIC, 0); + self->s.sound = 0; + } + self->moveinfo.state = STATE_TOP; + if (self->spawnflags & DOOR_TOGGLE) + return; + if (self->moveinfo.wait >= 0) + { + self->think = door_go_down; + self->nextthink = level.time + self->moveinfo.wait; + } +} + +void door_hit_bottom (edict_t *self) +{ + if (!(self->flags & FL_TEAMSLAVE)) + { + if (self->moveinfo.sound_end) + gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_end, 1, ATTN_STATIC, 0); + self->s.sound = 0; + } + self->moveinfo.state = STATE_BOTTOM; + door_use_areaportals (self, false); +} + +void door_go_down (edict_t *self) +{ + if (!(self->flags & FL_TEAMSLAVE)) + { + if (self->moveinfo.sound_start) + gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_start, 1, ATTN_STATIC, 0); + self->s.sound = self->moveinfo.sound_middle; + } + if (self->max_health) + { + self->takedamage = DAMAGE_YES; + self->health = self->max_health; + } + + self->moveinfo.state = STATE_DOWN; + if (strcmp(self->classname, "func_door") == 0) + Move_Calc (self, self->moveinfo.start_origin, door_hit_bottom); + else if (strcmp(self->classname, "func_door_rotating") == 0) + AngleMove_Calc (self, door_hit_bottom); +} + +void door_go_up (edict_t *self, edict_t *activator) +{ + if (self->moveinfo.state == STATE_UP) + return; // already going up + + if (self->moveinfo.state == STATE_TOP) + { // reset top wait time + if (self->moveinfo.wait >= 0) + self->nextthink = level.time + self->moveinfo.wait; + return; + } + + if (!(self->flags & FL_TEAMSLAVE)) + { + if (self->moveinfo.sound_start) + gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_start, 1, ATTN_STATIC, 0); + self->s.sound = self->moveinfo.sound_middle; + } + self->moveinfo.state = STATE_UP; + if (strcmp(self->classname, "func_door") == 0) + Move_Calc (self, self->moveinfo.end_origin, door_hit_top); + else if (strcmp(self->classname, "func_door_rotating") == 0) + AngleMove_Calc (self, door_hit_top); + + G_UseTargets (self, activator); + door_use_areaportals (self, true); +} + +//====== +//PGM +void smart_water_go_up (edict_t *self) +{ + float distance; + edict_t *lowestPlayer; + edict_t *ent; + float lowestPlayerPt; + int i; + + if (self->moveinfo.state == STATE_TOP) + { // reset top wait time + if (self->moveinfo.wait >= 0) + self->nextthink = level.time + self->moveinfo.wait; + return; + } + + if (self->health) + { + if(self->absmax[2] >= self->health) + { + VectorClear (self->velocity); + self->nextthink = 0; + self->moveinfo.state = STATE_TOP; + return; + } + } + + if (!(self->flags & FL_TEAMSLAVE)) + { + if (self->moveinfo.sound_start) + gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_start, 1, ATTN_STATIC, 0); + self->s.sound = self->moveinfo.sound_middle; + } + + // find the lowest player point. + lowestPlayerPt = 999999; + lowestPlayer = NULL; + for (i=0 ; iinuse) && (ent->health > 0)) + { + if (ent->absmin[2] < lowestPlayerPt) + { + lowestPlayerPt = ent->absmin[2]; + lowestPlayer = ent; + } + } + } + + if(!lowestPlayer) + { + return; + } + + distance = lowestPlayerPt - self->absmax[2]; + + // for the calculations, make sure we intend to go up at least a little. + if(distance < self->accel) + { + distance = 100; + self->moveinfo.speed = 5; + } + else + self->moveinfo.speed = distance / self->accel; + + if(self->moveinfo.speed < 5) + self->moveinfo.speed = 5; + else if(self->moveinfo.speed > self->speed) + self->moveinfo.speed = self->speed; + + // FIXME - should this allow any movement other than straight up? + VectorSet(self->moveinfo.dir, 0, 0, 1); + VectorScale (self->moveinfo.dir, self->moveinfo.speed, self->velocity); + self->moveinfo.remaining_distance = distance; + + if(self->moveinfo.state != STATE_UP) + { + G_UseTargets (self, lowestPlayer); + door_use_areaportals (self, true); + self->moveinfo.state = STATE_UP; + } + + self->think = smart_water_go_up; + self->nextthink = level.time + FRAMETIME; +} +//PGM +//====== + +void door_use (edict_t *self, edict_t *other, edict_t *activator) +{ + edict_t *ent; + vec3_t center; //PGM + + if (self->flags & FL_TEAMSLAVE) + return; + + if (self->spawnflags & DOOR_TOGGLE) + { + if (self->moveinfo.state == STATE_UP || self->moveinfo.state == STATE_TOP) + { + // trigger all paired doors + for (ent = self ; ent ; ent = ent->teamchain) + { + ent->message = NULL; + ent->touch = NULL; + door_go_down (ent); + } + return; + } + } + +//PGM + // smart water is different + VectorAdd(self->mins, self->maxs, center); + VectorScale(center, 0.5, center); + if ((gi.pointcontents (center) & MASK_WATER) && self->spawnflags & 2) + { + self->message = NULL; + self->touch = NULL; + self->enemy = activator; + smart_water_go_up (self); + return; + } +//PGM + + // trigger all paired doors + for (ent = self ; ent ; ent = ent->teamchain) + { + ent->message = NULL; + ent->touch = NULL; + door_go_up (ent, activator); + } +}; + +void Touch_DoorTrigger (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (other->health <= 0) + return; + + if (!(other->svflags & SVF_MONSTER) && (!other->client)) + return; + + if ((self->owner->spawnflags & DOOR_NOMONSTER) && (other->svflags & SVF_MONSTER)) + return; + + if (level.time < self->touch_debounce_time) + return; + self->touch_debounce_time = level.time + 1.0; + + door_use (self->owner, other, other); +} + +void Think_CalcMoveSpeed (edict_t *self) +{ + edict_t *ent; + float min; + float time; + float newspeed; + float ratio; + float dist; + + if (self->flags & FL_TEAMSLAVE) + return; // only the team master does this + + // find the smallest distance any member of the team will be moving + min = fabs(self->moveinfo.distance); + for (ent = self->teamchain; ent; ent = ent->teamchain) + { + dist = fabs(ent->moveinfo.distance); + if (dist < min) + min = dist; + } + + time = min / self->moveinfo.speed; + + // adjust speeds so they will all complete at the same time + for (ent = self; ent; ent = ent->teamchain) + { + newspeed = fabs(ent->moveinfo.distance) / time; + ratio = newspeed / ent->moveinfo.speed; + if (ent->moveinfo.accel == ent->moveinfo.speed) + ent->moveinfo.accel = newspeed; + else + ent->moveinfo.accel *= ratio; + if (ent->moveinfo.decel == ent->moveinfo.speed) + ent->moveinfo.decel = newspeed; + else + ent->moveinfo.decel *= ratio; + ent->moveinfo.speed = newspeed; + } +} + +void Think_SpawnDoorTrigger (edict_t *ent) +{ + edict_t *other; + vec3_t mins, maxs; + + if (ent->flags & FL_TEAMSLAVE) + return; // only the team leader spawns a trigger + + VectorCopy (ent->absmin, mins); + VectorCopy (ent->absmax, maxs); + + for (other = ent->teamchain ; other ; other=other->teamchain) + { + AddPointToBounds (other->absmin, mins, maxs); + AddPointToBounds (other->absmax, mins, maxs); + } + + // expand + mins[0] -= 60; + mins[1] -= 60; + maxs[0] += 60; + maxs[1] += 60; + + other = G_Spawn (); + VectorCopy (mins, other->mins); + VectorCopy (maxs, other->maxs); + other->owner = ent; + other->solid = SOLID_TRIGGER; + other->movetype = MOVETYPE_NONE; + other->touch = Touch_DoorTrigger; + gi.linkentity (other); + + if (ent->spawnflags & DOOR_START_OPEN) + door_use_areaportals (ent, true); + + Think_CalcMoveSpeed (ent); +} + +void door_blocked (edict_t *self, edict_t *other) +{ + edict_t *ent; + + if (!(other->svflags & SVF_MONSTER) && (!other->client) ) + { + // give it a chance to go away on it's own terms (like gibs) + T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, 100000, 1, 0, MOD_CRUSH); + // if it's still there, nuke it + if (other && other->inuse) + BecomeExplosion1 (other); + return; + } + + T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH); + + if (self->spawnflags & DOOR_CRUSHER) + return; + + +// if a door has a negative wait, it would never come back if blocked, +// so let it just squash the object to death real fast + if (self->moveinfo.wait >= 0) + { + if (self->moveinfo.state == STATE_DOWN) + { + for (ent = self->teammaster ; ent ; ent = ent->teamchain) + door_go_up (ent, ent->activator); + } + else + { + for (ent = self->teammaster ; ent ; ent = ent->teamchain) + door_go_down (ent); + } + } +} + +void door_killed (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + edict_t *ent; + + for (ent = self->teammaster ; ent ; ent = ent->teamchain) + { + ent->health = ent->max_health; + ent->takedamage = DAMAGE_NO; + } + door_use (self->teammaster, attacker, attacker); +} + +void door_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (!other->client) + return; + + if (level.time < self->touch_debounce_time) + return; + self->touch_debounce_time = level.time + 5.0; + + gi.centerprintf (other, "%s", self->message); + gi.sound (other, CHAN_AUTO, gi.soundindex ("misc/talk1.wav"), 1, ATTN_NORM, 0); +} + +void SP_func_door (edict_t *ent) +{ + vec3_t abs_movedir; + + if (ent->sounds != 1) + { + ent->moveinfo.sound_start = gi.soundindex ("doors/dr1_strt.wav"); + ent->moveinfo.sound_middle = gi.soundindex ("doors/dr1_mid.wav"); + ent->moveinfo.sound_end = gi.soundindex ("doors/dr1_end.wav"); + } + + G_SetMovedir (ent->s.angles, ent->movedir); + ent->movetype = MOVETYPE_PUSH; + ent->solid = SOLID_BSP; + gi.setmodel (ent, ent->model); + + ent->blocked = door_blocked; + ent->use = door_use; + + if (!ent->speed) + ent->speed = 100; + if (deathmatch->value) + ent->speed *= 2; + + if (!ent->accel) + ent->accel = ent->speed; + if (!ent->decel) + ent->decel = ent->speed; + + if (!ent->wait) + ent->wait = 3; + if (!st.lip) + st.lip = 8; + if (!ent->dmg) + ent->dmg = 2; + + // calculate second position + VectorCopy (ent->s.origin, ent->pos1); + abs_movedir[0] = fabs(ent->movedir[0]); + abs_movedir[1] = fabs(ent->movedir[1]); + abs_movedir[2] = fabs(ent->movedir[2]); + ent->moveinfo.distance = abs_movedir[0] * ent->size[0] + abs_movedir[1] * ent->size[1] + abs_movedir[2] * ent->size[2] - st.lip; + VectorMA (ent->pos1, ent->moveinfo.distance, ent->movedir, ent->pos2); + + // if it starts open, switch the positions + if (ent->spawnflags & DOOR_START_OPEN) + { + VectorCopy (ent->pos2, ent->s.origin); + VectorCopy (ent->pos1, ent->pos2); + VectorCopy (ent->s.origin, ent->pos1); + } + + ent->moveinfo.state = STATE_BOTTOM; + + if (ent->health) + { + ent->takedamage = DAMAGE_YES; + ent->die = door_killed; + ent->max_health = ent->health; + } + else if (ent->targetname && ent->message) + { + gi.soundindex ("misc/talk.wav"); + ent->touch = door_touch; + } + + ent->moveinfo.speed = ent->speed; + ent->moveinfo.accel = ent->accel; + ent->moveinfo.decel = ent->decel; + ent->moveinfo.wait = ent->wait; + VectorCopy (ent->pos1, ent->moveinfo.start_origin); + VectorCopy (ent->s.angles, ent->moveinfo.start_angles); + VectorCopy (ent->pos2, ent->moveinfo.end_origin); + VectorCopy (ent->s.angles, ent->moveinfo.end_angles); + + if (ent->spawnflags & 16) + ent->s.effects |= EF_ANIM_ALL; + if (ent->spawnflags & 64) + ent->s.effects |= EF_ANIM_ALLFAST; + + // to simplify logic elsewhere, make non-teamed doors into a team of one + if (!ent->team) + ent->teammaster = ent; + + gi.linkentity (ent); + + ent->nextthink = level.time + FRAMETIME; + if (ent->health || ent->targetname) + ent->think = Think_CalcMoveSpeed; + else + ent->think = Think_SpawnDoorTrigger; +} + +//PGM +void Door_Activate (edict_t *self, edict_t *other, edict_t *activator) +{ + self->use = NULL; + + if (self->health) + { + self->takedamage = DAMAGE_YES; + self->die = door_killed; + self->max_health = self->health; + } + + if (self->health) + self->think = Think_CalcMoveSpeed; + else + self->think = Think_SpawnDoorTrigger; + self->nextthink = level.time + FRAMETIME; + +} +//PGM + +/*QUAKED func_door_rotating (0 .5 .8) ? START_OPEN REVERSE CRUSHER NOMONSTER ANIMATED TOGGLE X_AXIS Y_AXIS EASY MED HARD DM COOP INACTIVE +TOGGLE causes the door to wait in both the start and end states for a trigger event. + +START_OPEN the door to moves to its destination when spawned, and operate in reverse. It is used to temporarily or permanently close off an area when triggered (not useful for touch or takedamage doors). +NOMONSTER monsters will not trigger this door + +You need to have an origin brush as part of this entity. The center of that brush will be +the point around which it is rotated. It will rotate around the Z axis by default. You can +check either the X_AXIS or Y_AXIS box to change that. + +"distance" is how many degrees the door will be rotated. +"speed" determines how fast the door moves; default value is 100. +"accel" if specified,is how much the rotation speed will increase each .1 sec. (default: no accel) + +REVERSE will cause the door to rotate in the opposite direction. +INACTIVE will cause the door to be inactive until triggered. + +"message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet +"angle" determines the opening direction +"targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door. +"health" if set, door must be shot open +"speed" movement speed (100 default) +"wait" wait before returning (3 default, -1 = never return) +"dmg" damage to inflict when blocked (2 default) +"sounds" +1) silent +2) light +3) medium +4) heavy +*/ + +void SP_func_door_rotating (edict_t *ent) +{ + VectorClear (ent->s.angles); + + // set the axis of rotation + VectorClear(ent->movedir); + if (ent->spawnflags & DOOR_X_AXIS) + ent->movedir[2] = 1.0; + else if (ent->spawnflags & DOOR_Y_AXIS) + ent->movedir[0] = 1.0; + else // Z_AXIS + ent->movedir[1] = 1.0; + + // check for reverse rotation + if (ent->spawnflags & DOOR_REVERSE) + VectorNegate (ent->movedir, ent->movedir); + + if (!st.distance) + { + gi.dprintf("%s at %s with no distance set\n", ent->classname, vtos(ent->s.origin)); + st.distance = 90; + } + + VectorCopy (ent->s.angles, ent->pos1); + VectorMA (ent->s.angles, st.distance, ent->movedir, ent->pos2); + ent->moveinfo.distance = st.distance; + + ent->movetype = MOVETYPE_PUSH; + ent->solid = SOLID_BSP; + gi.setmodel (ent, ent->model); + + ent->blocked = door_blocked; + ent->use = door_use; + + if (!ent->speed) + ent->speed = 100; + if (!ent->accel) + ent->accel = ent->speed; + if (!ent->decel) + ent->decel = ent->speed; + + if (!ent->wait) + ent->wait = 3; + if (!ent->dmg) + ent->dmg = 2; + + if (ent->sounds != 1) + { + ent->moveinfo.sound_start = gi.soundindex ("doors/dr1_strt.wav"); + ent->moveinfo.sound_middle = gi.soundindex ("doors/dr1_mid.wav"); + ent->moveinfo.sound_end = gi.soundindex ("doors/dr1_end.wav"); + } + + // if it starts open, switch the positions + if (ent->spawnflags & DOOR_START_OPEN) + { + VectorCopy (ent->pos2, ent->s.angles); + VectorCopy (ent->pos1, ent->pos2); + VectorCopy (ent->s.angles, ent->pos1); + VectorNegate (ent->movedir, ent->movedir); + } + + if (ent->health) + { + ent->takedamage = DAMAGE_YES; + ent->die = door_killed; + ent->max_health = ent->health; + } + + if (ent->targetname && ent->message) + { + gi.soundindex ("misc/talk.wav"); + ent->touch = door_touch; + } + + ent->moveinfo.state = STATE_BOTTOM; + ent->moveinfo.speed = ent->speed; + ent->moveinfo.accel = ent->accel; + ent->moveinfo.decel = ent->decel; + ent->moveinfo.wait = ent->wait; + VectorCopy (ent->s.origin, ent->moveinfo.start_origin); + VectorCopy (ent->pos1, ent->moveinfo.start_angles); + VectorCopy (ent->s.origin, ent->moveinfo.end_origin); + VectorCopy (ent->pos2, ent->moveinfo.end_angles); + + if (ent->spawnflags & 16) + ent->s.effects |= EF_ANIM_ALL; + + // to simplify logic elsewhere, make non-teamed doors into a team of one + if (!ent->team) + ent->teammaster = ent; + + gi.linkentity (ent); + + ent->nextthink = level.time + FRAMETIME; + if (ent->health || ent->targetname) + ent->think = Think_CalcMoveSpeed; + else + ent->think = Think_SpawnDoorTrigger; + +//PGM + if (ent->spawnflags & DOOR_INACTIVE) + { + ent->takedamage = DAMAGE_NO; + ent->die = NULL; + ent->think = NULL; + ent->nextthink = 0; + ent->use = Door_Activate; + } +//PGM +} + +void smart_water_blocked (edict_t *self, edict_t *other) +{ + if (!(other->svflags & SVF_MONSTER) && (!other->client) ) + { + // give it a chance to go away on it's own terms (like gibs) + T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, 100000, 1, 0, MOD_LAVA); + // if it's still there, nuke it + if (other && other->inuse) // PGM + BecomeExplosion1 (other); + return; + } + + T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, 100, 1, 0, MOD_LAVA); +} + +/*QUAKED func_water (0 .5 .8) ? START_OPEN SMART +func_water is a moveable water brush. It must be targeted to operate. Use a non-water texture at your own risk. + +START_OPEN causes the water to move to its destination when spawned and operate in reverse. + +SMART causes the water to adjust its speed depending on distance to player. +(speed = distance/accel, min 5, max self->speed) +"accel" for smart water, the divisor to determine water speed. default 20 (smaller = faster) + +"health" maximum height of this water brush +"angle" determines the opening direction (up or down only) +"speed" movement speed (25 default) +"wait" wait before returning (-1 default, -1 = TOGGLE) +"lip" lip remaining at end of move (0 default) +"sounds" (yes, these need to be changed) +0) no sound +1) water +2) lava +*/ + +void SP_func_water (edict_t *self) +{ + vec3_t abs_movedir; + + G_SetMovedir (self->s.angles, self->movedir); + self->movetype = MOVETYPE_PUSH; + self->solid = SOLID_BSP; + gi.setmodel (self, self->model); + + switch (self->sounds) + { + default: + break; + + case 1: // water + self->moveinfo.sound_start = gi.soundindex ("world/mov_watr.wav"); + self->moveinfo.sound_end = gi.soundindex ("world/stp_watr.wav"); + break; + + case 2: // lava + self->moveinfo.sound_start = gi.soundindex ("world/mov_watr.wav"); + self->moveinfo.sound_end = gi.soundindex ("world/stp_watr.wav"); + break; + } + + // calculate second position + VectorCopy (self->s.origin, self->pos1); + abs_movedir[0] = fabs(self->movedir[0]); + abs_movedir[1] = fabs(self->movedir[1]); + abs_movedir[2] = fabs(self->movedir[2]); + self->moveinfo.distance = abs_movedir[0] * self->size[0] + abs_movedir[1] * self->size[1] + abs_movedir[2] * self->size[2] - st.lip; + VectorMA (self->pos1, self->moveinfo.distance, self->movedir, self->pos2); + + // if it starts open, switch the positions + if (self->spawnflags & DOOR_START_OPEN) + { + VectorCopy (self->pos2, self->s.origin); + VectorCopy (self->pos1, self->pos2); + VectorCopy (self->s.origin, self->pos1); + } + + VectorCopy (self->pos1, self->moveinfo.start_origin); + VectorCopy (self->s.angles, self->moveinfo.start_angles); + VectorCopy (self->pos2, self->moveinfo.end_origin); + VectorCopy (self->s.angles, self->moveinfo.end_angles); + + self->moveinfo.state = STATE_BOTTOM; + + if (!self->speed) + self->speed = 25; + self->moveinfo.accel = self->moveinfo.decel = self->moveinfo.speed = self->speed; + + if ( self->spawnflags & 2) // smart water + { + // this is actually the divisor of the lowest player's distance to determine speed. + // self->speed then becomes the cap of the speed. + if(!self->accel) + self->accel = 20; + self->blocked = smart_water_blocked; + } + + if (!self->wait) + self->wait = -1; + self->moveinfo.wait = self->wait; + + self->use = door_use; + + if (self->wait == -1) + self->spawnflags |= DOOR_TOGGLE; + + self->classname = "func_door"; + + gi.linkentity (self); +} + + +#define TRAIN_START_ON 1 +#define TRAIN_TOGGLE 2 +#define TRAIN_BLOCK_STOPS 4 + +/*QUAKED func_train (0 .5 .8) ? START_ON TOGGLE BLOCK_STOPS +Trains are moving platforms that players can ride. +The targets origin specifies the min point of the train at each corner. +The train spawns at the first target it is pointing at. +If the train is the target of a button or trigger, it will not begin moving until activated. +speed default 100 +dmg default 2 +noise looping sound to play when the train is in motion + +To have other entities move with the train, set all the piece's team value to the same thing. They will move in unison. +*/ +void train_next (edict_t *self); + +void train_blocked (edict_t *self, edict_t *other) +{ + if (!(other->svflags & SVF_MONSTER) && (!other->client) ) + { + // give it a chance to go away on it's own terms (like gibs) + T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, 100000, 1, 0, MOD_CRUSH); + // if it's still there, nuke it + if (other && other->inuse) + BecomeExplosion1 (other); + return; + } + + if (level.time < self->touch_debounce_time) + return; + + if (!self->dmg) + return; + self->touch_debounce_time = level.time + 0.5; + T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH); +} + +void train_wait (edict_t *self) +{ + if (self->target_ent->pathtarget) + { + char *savetarget; + edict_t *ent; + + ent = self->target_ent; + savetarget = ent->target; + ent->target = ent->pathtarget; + G_UseTargets (ent, self->activator); + ent->target = savetarget; + + // make sure we didn't get killed by a killtarget + if (!self->inuse) + return; + } + + if (self->moveinfo.wait) + { + if (self->moveinfo.wait > 0) + { + self->nextthink = level.time + self->moveinfo.wait; + self->think = train_next; + } + else if (self->spawnflags & TRAIN_TOGGLE) // && wait < 0 + { + // PMM - clear target_ent, let train_next get called when we get used +// train_next (self); + self->target_ent = NULL; + // pmm + self->spawnflags &= ~TRAIN_START_ON; + VectorClear (self->velocity); + self->nextthink = 0; + } + + if (!(self->flags & FL_TEAMSLAVE)) + { + if (self->moveinfo.sound_end) + gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_end, 1, ATTN_STATIC, 0); + self->s.sound = 0; + } + } + else + { + train_next (self); + } + +} + +//PGM +void train_piece_wait (edict_t *self) +{ +} +//PGM + +void train_next (edict_t *self) +{ + edict_t *ent; + vec3_t dest; + qboolean first; + + first = true; +again: + if (!self->target) + { +// gi.dprintf ("train_next: no next target\n"); + return; + } + + ent = G_PickTarget (self->target); + if (!ent) + { + gi.dprintf ("train_next: bad target %s\n", self->target); + return; + } + + self->target = ent->target; + + // check for a teleport path_corner + if (ent->spawnflags & 1) + { + if (!first) + { + gi.dprintf ("connected teleport path_corners, see %s at %s\n", ent->classname, vtos(ent->s.origin)); + return; + } + first = false; + VectorSubtract (ent->s.origin, self->mins, self->s.origin); + VectorCopy (self->s.origin, self->s.old_origin); + self->s.event = EV_OTHER_TELEPORT; + gi.linkentity (self); + goto again; + } + +//PGM + if (ent->speed) + { + self->speed = ent->speed; + self->moveinfo.speed = ent->speed; + if(ent->accel) + self->moveinfo.accel = ent->accel; + else + self->moveinfo.accel = ent->speed; + if(ent->decel) + self->moveinfo.decel = ent->decel; + else + self->moveinfo.decel = ent->speed; + self->moveinfo.current_speed = 0; + } +//PGM + + self->moveinfo.wait = ent->wait; + self->target_ent = ent; + + if (!(self->flags & FL_TEAMSLAVE)) + { + if (self->moveinfo.sound_start) + gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_start, 1, ATTN_STATIC, 0); + self->s.sound = self->moveinfo.sound_middle; + } + + VectorSubtract (ent->s.origin, self->mins, dest); + self->moveinfo.state = STATE_TOP; + VectorCopy (self->s.origin, self->moveinfo.start_origin); + VectorCopy (dest, self->moveinfo.end_origin); + Move_Calc (self, dest, train_wait); + self->spawnflags |= TRAIN_START_ON; + +//PGM + if(self->team) + { + edict_t *e; + vec3_t dir, dst; + + VectorSubtract (dest, self->s.origin, dir); + for (e=self->teamchain; e ; e = e->teamchain) + { + VectorAdd(dir, e->s.origin, dst); + VectorCopy(e->s.origin, e->moveinfo.start_origin); + VectorCopy(dst, e->moveinfo.end_origin); + + e->moveinfo.state = STATE_TOP; + e->speed = self->speed; + e->moveinfo.speed = self->moveinfo.speed; + e->moveinfo.accel = self->moveinfo.accel; + e->moveinfo.decel = self->moveinfo.decel; + e->movetype = MOVETYPE_PUSH; + Move_Calc (e, dst, train_piece_wait); + } + + } +//PGM +} + +void train_resume (edict_t *self) +{ + edict_t *ent; + vec3_t dest; + + ent = self->target_ent; + + VectorSubtract (ent->s.origin, self->mins, dest); + self->moveinfo.state = STATE_TOP; + VectorCopy (self->s.origin, self->moveinfo.start_origin); + VectorCopy (dest, self->moveinfo.end_origin); + Move_Calc (self, dest, train_wait); + self->spawnflags |= TRAIN_START_ON; +} + +void func_train_find (edict_t *self) +{ + edict_t *ent; + + if (!self->target) + { + gi.dprintf ("train_find: no target\n"); + return; + } + ent = G_PickTarget (self->target); + if (!ent) + { + gi.dprintf ("train_find: target %s not found\n", self->target); + return; + } + self->target = ent->target; + + VectorSubtract (ent->s.origin, self->mins, self->s.origin); + gi.linkentity (self); + + // if not triggered, start immediately + if (!self->targetname) + self->spawnflags |= TRAIN_START_ON; + + if (self->spawnflags & TRAIN_START_ON) + { + self->nextthink = level.time + FRAMETIME; + self->think = train_next; + self->activator = self; + } +} + +void train_use (edict_t *self, edict_t *other, edict_t *activator) +{ + self->activator = activator; + + if (self->spawnflags & TRAIN_START_ON) + { + if (!(self->spawnflags & TRAIN_TOGGLE)) + return; + self->spawnflags &= ~TRAIN_START_ON; + VectorClear (self->velocity); + self->nextthink = 0; + } + else + { + if (self->target_ent) + train_resume(self); + else + train_next(self); + } +} + +void SP_func_train (edict_t *self) +{ + self->movetype = MOVETYPE_PUSH; + + VectorClear (self->s.angles); + self->blocked = train_blocked; + if (self->spawnflags & TRAIN_BLOCK_STOPS) + self->dmg = 0; + else + { + if (!self->dmg) + self->dmg = 100; + } + self->solid = SOLID_BSP; + gi.setmodel (self, self->model); + + if (st.noise) + self->moveinfo.sound_middle = gi.soundindex (st.noise); + + if (!self->speed) + self->speed = 100; + + self->moveinfo.speed = self->speed; + self->moveinfo.accel = self->moveinfo.decel = self->moveinfo.speed; + + self->use = train_use; + + gi.linkentity (self); + + if (self->target) + { + // start trains on the second frame, to make sure their targets have had + // a chance to spawn + self->nextthink = level.time + FRAMETIME; + self->think = func_train_find; + } + else + { + gi.dprintf ("func_train without a target at %s\n", vtos(self->absmin)); + } +} + + +/*QUAKED trigger_elevator (0.3 0.1 0.6) (-8 -8 -8) (8 8 8) +*/ +void trigger_elevator_use (edict_t *self, edict_t *other, edict_t *activator) +{ + edict_t *target; + + if (self->movetarget->nextthink) + { +// gi.dprintf("elevator busy\n"); + return; + } + + if (!other->pathtarget) + { + gi.dprintf("elevator used with no pathtarget\n"); + return; + } + + target = G_PickTarget (other->pathtarget); + if (!target) + { + gi.dprintf("elevator used with bad pathtarget: %s\n", other->pathtarget); + return; + } + + self->movetarget->target_ent = target; + train_resume (self->movetarget); +} + +void trigger_elevator_init (edict_t *self) +{ + if (!self->target) + { + gi.dprintf("trigger_elevator has no target\n"); + return; + } + self->movetarget = G_PickTarget (self->target); + if (!self->movetarget) + { + gi.dprintf("trigger_elevator unable to find target %s\n", self->target); + return; + } + if (strcmp(self->movetarget->classname, "func_train") != 0) + { + gi.dprintf("trigger_elevator target %s is not a train\n", self->target); + return; + } + + self->use = trigger_elevator_use; + self->svflags = SVF_NOCLIENT; + +} + +void SP_trigger_elevator (edict_t *self) +{ + self->think = trigger_elevator_init; + self->nextthink = level.time + FRAMETIME; +} + + +/*QUAKED func_timer (0.3 0.1 0.6) (-8 -8 -8) (8 8 8) START_ON +"wait" base time between triggering all targets, default is 1 +"random" wait variance, default is 0 + +so, the basic time between firing is a random time between +(wait - random) and (wait + random) + +"delay" delay before first firing when turned on, default is 0 + +"pausetime" additional delay used only the very first time + and only if spawned with START_ON + +These can used but not touched. +*/ +void func_timer_think (edict_t *self) +{ + G_UseTargets (self, self->activator); + self->nextthink = level.time + self->wait + crandom() * self->random; +} + +void func_timer_use (edict_t *self, edict_t *other, edict_t *activator) +{ + self->activator = activator; + + // if on, turn it off + if (self->nextthink) + { + self->nextthink = 0; + return; + } + + // turn it on + if (self->delay) + self->nextthink = level.time + self->delay; + else + func_timer_think (self); +} + +void SP_func_timer (edict_t *self) +{ + if (!self->wait) + self->wait = 1.0; + + self->use = func_timer_use; + self->think = func_timer_think; + + if (self->random >= self->wait) + { + self->random = self->wait - FRAMETIME; + gi.dprintf("func_timer at %s has random >= wait\n", vtos(self->s.origin)); + } + + if (self->spawnflags & 1) + { + self->nextthink = level.time + 1.0 + st.pausetime + self->delay + self->wait + crandom() * self->random; + self->activator = self; + } + + self->svflags = SVF_NOCLIENT; +} + + +/*QUAKED func_conveyor (0 .5 .8) ? START_ON TOGGLE +Conveyors are stationary brushes that move what's on them. +The brush should be have a surface with at least one current content enabled. +speed default 100 +*/ + +void func_conveyor_use (edict_t *self, edict_t *other, edict_t *activator) +{ + if (self->spawnflags & 1) + { + self->speed = 0; + self->spawnflags &= ~1; + } + else + { + self->speed = self->count; + self->spawnflags |= 1; + } + + if (!(self->spawnflags & 2)) + self->count = 0; +} + +void SP_func_conveyor (edict_t *self) +{ + if (!self->speed) + self->speed = 100; + + if (!(self->spawnflags & 1)) + { + self->count = self->speed; + self->speed = 0; + } + + self->use = func_conveyor_use; + + gi.setmodel (self, self->model); + self->solid = SOLID_BSP; + gi.linkentity (self); +} + + +/*QUAKED func_door_secret (0 .5 .8) ? always_shoot 1st_left 1st_down +A secret door. Slide back and then to the side. + +open_once doors never closes +1st_left 1st move is left of arrow +1st_down 1st move is down from arrow +always_shoot door is shootebale even if targeted + +"angle" determines the direction +"dmg" damage to inflic when blocked (default 2) +"wait" how long to hold in the open position (default 5, -1 means hold) +*/ + +#define SECRET_ALWAYS_SHOOT 1 +#define SECRET_1ST_LEFT 2 +#define SECRET_1ST_DOWN 4 + +void door_secret_move1 (edict_t *self); +void door_secret_move2 (edict_t *self); +void door_secret_move3 (edict_t *self); +void door_secret_move4 (edict_t *self); +void door_secret_move5 (edict_t *self); +void door_secret_move6 (edict_t *self); +void door_secret_done (edict_t *self); + +void door_secret_use (edict_t *self, edict_t *other, edict_t *activator) +{ + // make sure we're not already moving + if (!VectorCompare(self->s.origin, vec3_origin)) + return; + + Move_Calc (self, self->pos1, door_secret_move1); + door_use_areaportals (self, true); +} + +void door_secret_move1 (edict_t *self) +{ + self->nextthink = level.time + 1.0; + self->think = door_secret_move2; +} + +void door_secret_move2 (edict_t *self) +{ + Move_Calc (self, self->pos2, door_secret_move3); +} + +void door_secret_move3 (edict_t *self) +{ + if (self->wait == -1) + return; + self->nextthink = level.time + self->wait; + self->think = door_secret_move4; +} + +void door_secret_move4 (edict_t *self) +{ + Move_Calc (self, self->pos1, door_secret_move5); +} + +void door_secret_move5 (edict_t *self) +{ + self->nextthink = level.time + 1.0; + self->think = door_secret_move6; +} + +void door_secret_move6 (edict_t *self) +{ + Move_Calc (self, vec3_origin, door_secret_done); +} + +void door_secret_done (edict_t *self) +{ + if (!(self->targetname) || (self->spawnflags & SECRET_ALWAYS_SHOOT)) + { + self->health = 0; + self->takedamage = DAMAGE_YES; + } + door_use_areaportals (self, false); +} + +void door_secret_blocked (edict_t *self, edict_t *other) +{ + if (!(other->svflags & SVF_MONSTER) && (!other->client) ) + { + // give it a chance to go away on it's own terms (like gibs) + T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, 100000, 1, 0, MOD_CRUSH); + // if it's still there, nuke it + if (other && other->inuse) + BecomeExplosion1 (other); + return; + } + + if (level.time < self->touch_debounce_time) + return; + self->touch_debounce_time = level.time + 0.5; + + T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH); +} + +void door_secret_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + self->takedamage = DAMAGE_NO; + door_secret_use (self, attacker, attacker); +} + +void SP_func_door_secret (edict_t *ent) +{ + vec3_t forward, right, up; + float side; + float width; + float length; + + ent->moveinfo.sound_start = gi.soundindex ("doors/dr1_strt.wav"); + ent->moveinfo.sound_middle = gi.soundindex ("doors/dr1_mid.wav"); + ent->moveinfo.sound_end = gi.soundindex ("doors/dr1_end.wav"); + + ent->movetype = MOVETYPE_PUSH; + ent->solid = SOLID_BSP; + gi.setmodel (ent, ent->model); + + ent->blocked = door_secret_blocked; + ent->use = door_secret_use; + + if (!(ent->targetname) || (ent->spawnflags & SECRET_ALWAYS_SHOOT)) + { + ent->health = 0; + ent->takedamage = DAMAGE_YES; + ent->die = door_secret_die; + } + + if (!ent->dmg) + ent->dmg = 2; + + if (!ent->wait) + ent->wait = 5; + + ent->moveinfo.accel = + ent->moveinfo.decel = + ent->moveinfo.speed = 50; + + // calculate positions + AngleVectors (ent->s.angles, forward, right, up); + VectorClear (ent->s.angles); + side = 1.0 - (ent->spawnflags & SECRET_1ST_LEFT); + if (ent->spawnflags & SECRET_1ST_DOWN) + width = fabs(DotProduct(up, ent->size)); + else + width = fabs(DotProduct(right, ent->size)); + length = fabs(DotProduct(forward, ent->size)); + if (ent->spawnflags & SECRET_1ST_DOWN) + VectorMA (ent->s.origin, -1 * width, up, ent->pos1); + else + VectorMA (ent->s.origin, side * width, right, ent->pos1); + VectorMA (ent->pos1, length, forward, ent->pos2); + + if (ent->health) + { + ent->takedamage = DAMAGE_YES; + ent->die = door_killed; + ent->max_health = ent->health; + } + else if (ent->targetname && ent->message) + { + gi.soundindex ("misc/talk.wav"); + ent->touch = door_touch; + } + + ent->classname = "func_door"; + + gi.linkentity (ent); +} + + +/*QUAKED func_killbox (1 0 0) ? +Kills everything inside when fired, irrespective of protection. +*/ +void use_killbox (edict_t *self, edict_t *other, edict_t *activator) +{ + KillBox (self); +} + +void SP_func_killbox (edict_t *ent) +{ + gi.setmodel (ent, ent->model); + ent->use = use_killbox; + ent->svflags = SVF_NOCLIENT; +} + diff --git a/original/rogue/g_items.c b/original/rogue/g_items.c new file mode 100644 index 0000000..a58f4bb --- /dev/null +++ b/original/rogue/g_items.c @@ -0,0 +1,3211 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +#include "g_local.h" + + +qboolean Pickup_Weapon (edict_t *ent, edict_t *other); +void Use_Weapon (edict_t *ent, gitem_t *inv); +void Drop_Weapon (edict_t *ent, gitem_t *inv); + +void Weapon_Blaster (edict_t *ent); +void Weapon_Shotgun (edict_t *ent); +void Weapon_SuperShotgun (edict_t *ent); +void Weapon_Machinegun (edict_t *ent); +void Weapon_Chaingun (edict_t *ent); +void Weapon_HyperBlaster (edict_t *ent); +void Weapon_RocketLauncher (edict_t *ent); +void Weapon_Grenade (edict_t *ent); +void Weapon_GrenadeLauncher (edict_t *ent); +void Weapon_Railgun (edict_t *ent); +void Weapon_BFG (edict_t *ent); + +//========= +//Rogue Weapons +void Weapon_ChainFist (edict_t *ent); +void Weapon_Disintegrator (edict_t *ent); +void Weapon_ETF_Rifle (edict_t *ent); +void Weapon_Heatbeam (edict_t *ent); +void Weapon_Prox (edict_t *ent); +void Weapon_Tesla (edict_t *ent); +void Weapon_ProxLauncher (edict_t *ent); +//void Weapon_Nuke (edict_t *ent); +//Rogue Weapons +//========= + +gitem_armor_t jacketarmor_info = { 25, 50, .30, .00, ARMOR_JACKET}; +gitem_armor_t combatarmor_info = { 50, 100, .60, .30, ARMOR_COMBAT}; +gitem_armor_t bodyarmor_info = {100, 200, .80, .60, ARMOR_BODY}; + +static int jacket_armor_index; +static int combat_armor_index; +static int body_armor_index; +static int power_screen_index; +static int power_shield_index; + +#define HEALTH_IGNORE_MAX 1 +#define HEALTH_TIMED 2 + +void Use_Quad (edict_t *ent, gitem_t *item); +static int quad_drop_timeout_hack; + +//====================================================================== + +/* +=============== +GetItemByIndex +=============== +*/ +gitem_t *GetItemByIndex (int index) +{ + if (index == 0 || index >= game.num_items) + return NULL; + + return &itemlist[index]; +} + + +/* +=============== +FindItemByClassname + +=============== +*/ +gitem_t *FindItemByClassname (char *classname) +{ + int i; + gitem_t *it; + + it = itemlist; + for (i=0 ; iclassname) + continue; + if (!Q_stricmp(it->classname, classname)) + return it; + } + + return NULL; +} + +/* +=============== +FindItem + +=============== +*/ +gitem_t *FindItem (char *pickup_name) +{ + int i; + gitem_t *it; + + it = itemlist; + for (i=0 ; ipickup_name) + continue; + if (!Q_stricmp(it->pickup_name, pickup_name)) + return it; + } + + return NULL; +} + +//====================================================================== + +void DoRespawn (edict_t *ent) +{ + if (ent->team) + { + edict_t *master; + int count; + int choice; + + master = ent->teammaster; + + for (count = 0, ent = master; ent; ent = ent->chain, count++) + ; + + choice = rand() % count; + + for (count = 0, ent = master; count < choice; ent = ent->chain, count++) + ; + } + +//===== +//ROGUE + if(randomrespawn && randomrespawn->value) + { + edict_t *newEnt; + + newEnt = DoRandomRespawn (ent); + + // if we've changed entities, then do some sleight of hand. + // otherwise, the old entity will respawn + if(newEnt) + { + G_FreeEdict (ent); + ent = newEnt; + } + } +//ROGUE +//===== + + ent->svflags &= ~SVF_NOCLIENT; + ent->solid = SOLID_TRIGGER; + gi.linkentity (ent); + + // send an effect + ent->s.event = EV_ITEM_RESPAWN; +} + +void SetRespawn (edict_t *ent, float delay) +{ + ent->flags |= FL_RESPAWN; + ent->svflags |= SVF_NOCLIENT; + ent->solid = SOLID_NOT; + ent->nextthink = level.time + delay; + ent->think = DoRespawn; + gi.linkentity (ent); +} + + +//====================================================================== + +qboolean Pickup_Powerup (edict_t *ent, edict_t *other) +{ + int quantity; + + quantity = other->client->pers.inventory[ITEM_INDEX(ent->item)]; + if ((skill->value == 1 && quantity >= 2) || (skill->value >= 2 && quantity >= 1)) + return false; + + if ((coop->value) && (ent->item->flags & IT_STAY_COOP) && (quantity > 0)) + return false; + + other->client->pers.inventory[ITEM_INDEX(ent->item)]++; + + if (deathmatch->value) + { + if (!(ent->spawnflags & DROPPED_ITEM) ) + SetRespawn (ent, ent->item->quantity); + if (((int)dmflags->value & DF_INSTANT_ITEMS) || ((ent->item->use == Use_Quad) && (ent->spawnflags & DROPPED_PLAYER_ITEM))) + { + if ((ent->item->use == Use_Quad) && (ent->spawnflags & DROPPED_PLAYER_ITEM)) + quad_drop_timeout_hack = (ent->nextthink - level.time) / FRAMETIME; +//PGM + if(ent->item->use) + ent->item->use (other, ent->item); + else + gi.dprintf("Powerup has no use function!\n"); +//PGM + } + } + + return true; +} + +void Drop_General (edict_t *ent, gitem_t *item) +{ + Drop_Item (ent, item); + ent->client->pers.inventory[ITEM_INDEX(item)]--; + ValidateSelectedItem (ent); +} + + +//====================================================================== + +qboolean Pickup_Adrenaline (edict_t *ent, edict_t *other) +{ + if (!deathmatch->value) + other->max_health += 1; + + if (other->health < other->max_health) + other->health = other->max_health; + + if (!(ent->spawnflags & DROPPED_ITEM) && (deathmatch->value)) + SetRespawn (ent, ent->item->quantity); + + return true; +} + +qboolean Pickup_AncientHead (edict_t *ent, edict_t *other) +{ + other->max_health += 2; + + if (!(ent->spawnflags & DROPPED_ITEM) && (deathmatch->value)) + SetRespawn (ent, ent->item->quantity); + + return true; +} + +qboolean Pickup_Bandolier (edict_t *ent, edict_t *other) +{ + gitem_t *item; + int index; + + if (other->client->pers.max_bullets < 250) + other->client->pers.max_bullets = 250; + if (other->client->pers.max_shells < 150) + other->client->pers.max_shells = 150; + if (other->client->pers.max_cells < 250) + other->client->pers.max_cells = 250; + if (other->client->pers.max_slugs < 75) + other->client->pers.max_slugs = 75; + //PMM + if (other->client->pers.max_flechettes < 250) + other->client->pers.max_flechettes = 250; +#ifndef KILL_DISRUPTOR + if (other->client->pers.max_rounds < 150) + other->client->pers.max_rounds = 150; +#endif + //pmm + + item = FindItem("Bullets"); + if (item) + { + index = ITEM_INDEX(item); + other->client->pers.inventory[index] += item->quantity; + if (other->client->pers.inventory[index] > other->client->pers.max_bullets) + other->client->pers.inventory[index] = other->client->pers.max_bullets; + } + + item = FindItem("Shells"); + if (item) + { + index = ITEM_INDEX(item); + other->client->pers.inventory[index] += item->quantity; + if (other->client->pers.inventory[index] > other->client->pers.max_shells) + other->client->pers.inventory[index] = other->client->pers.max_shells; + } + + if (!(ent->spawnflags & DROPPED_ITEM) && (deathmatch->value)) + SetRespawn (ent, ent->item->quantity); + + return true; +} + +qboolean Pickup_Pack (edict_t *ent, edict_t *other) +{ + gitem_t *item; + int index; + + if (other->client->pers.max_bullets < 300) + other->client->pers.max_bullets = 300; + if (other->client->pers.max_shells < 200) + other->client->pers.max_shells = 200; + if (other->client->pers.max_rockets < 100) + other->client->pers.max_rockets = 100; + if (other->client->pers.max_grenades < 100) + other->client->pers.max_grenades = 100; + if (other->client->pers.max_cells < 300) + other->client->pers.max_cells = 300; + if (other->client->pers.max_slugs < 100) + other->client->pers.max_slugs = 100; + //PMM + if (other->client->pers.max_flechettes < 200) + other->client->pers.max_flechettes = 200; +#ifndef KILL_DISRUPTOR + if (other->client->pers.max_rounds < 200) + other->client->pers.max_rounds = 200; +#endif + //pmm + + item = FindItem("Bullets"); + if (item) + { + index = ITEM_INDEX(item); + other->client->pers.inventory[index] += item->quantity; + if (other->client->pers.inventory[index] > other->client->pers.max_bullets) + other->client->pers.inventory[index] = other->client->pers.max_bullets; + } + + item = FindItem("Shells"); + if (item) + { + index = ITEM_INDEX(item); + other->client->pers.inventory[index] += item->quantity; + if (other->client->pers.inventory[index] > other->client->pers.max_shells) + other->client->pers.inventory[index] = other->client->pers.max_shells; + } + + item = FindItem("Cells"); + if (item) + { + index = ITEM_INDEX(item); + other->client->pers.inventory[index] += item->quantity; + if (other->client->pers.inventory[index] > other->client->pers.max_cells) + other->client->pers.inventory[index] = other->client->pers.max_cells; + } + + item = FindItem("Grenades"); + if (item) + { + index = ITEM_INDEX(item); + other->client->pers.inventory[index] += item->quantity; + if (other->client->pers.inventory[index] > other->client->pers.max_grenades) + other->client->pers.inventory[index] = other->client->pers.max_grenades; + } + + item = FindItem("Rockets"); + if (item) + { + index = ITEM_INDEX(item); + other->client->pers.inventory[index] += item->quantity; + if (other->client->pers.inventory[index] > other->client->pers.max_rockets) + other->client->pers.inventory[index] = other->client->pers.max_rockets; + } + + item = FindItem("Slugs"); + if (item) + { + index = ITEM_INDEX(item); + other->client->pers.inventory[index] += item->quantity; + if (other->client->pers.inventory[index] > other->client->pers.max_slugs) + other->client->pers.inventory[index] = other->client->pers.max_slugs; + } +//PMM + item = FindItem("Flechettes"); + if (item) + { + index = ITEM_INDEX(item); + other->client->pers.inventory[index] += item->quantity; + if (other->client->pers.inventory[index] > other->client->pers.max_flechettes) + other->client->pers.inventory[index] = other->client->pers.max_flechettes; + } +#ifndef KILL_DISRUPTOR + item = FindItem("Rounds"); + if (item) + { + index = ITEM_INDEX(item); + other->client->pers.inventory[index] += item->quantity; + if (other->client->pers.inventory[index] > other->client->pers.max_rounds) + other->client->pers.inventory[index] = other->client->pers.max_rounds; + } +#endif +//pmm + if (!(ent->spawnflags & DROPPED_ITEM) && (deathmatch->value)) + SetRespawn (ent, ent->item->quantity); + + return true; +} +// ================ +// PMM +qboolean Pickup_Nuke (edict_t *ent, edict_t *other) +{ + int quantity; + +// if (!deathmatch->value) +// return; + quantity = other->client->pers.inventory[ITEM_INDEX(ent->item)]; +// if ((skill->value == 1 && quantity >= 2) || (skill->value >= 2 && quantity >= 1)) +// return false; + + if (quantity >= 1) + return false; + + if ((coop->value) && (ent->item->flags & IT_STAY_COOP) && (quantity > 0)) + return false; + + other->client->pers.inventory[ITEM_INDEX(ent->item)]++; + + if (deathmatch->value) + { + if (!(ent->spawnflags & DROPPED_ITEM) ) + SetRespawn (ent, ent->item->quantity); + } + + return true; +} + +// ================ +// PGM +void Use_IR (edict_t *ent, gitem_t *item) +{ + ent->client->pers.inventory[ITEM_INDEX(item)]--; + ValidateSelectedItem (ent); + + if (ent->client->ir_framenum > level.framenum) + ent->client->ir_framenum += 600; + else + ent->client->ir_framenum = level.framenum + 600; + + gi.sound(ent, CHAN_ITEM, gi.soundindex("misc/ir_start.wav"), 1, ATTN_NORM, 0); +} + +void Use_Double (edict_t *ent, gitem_t *item) +{ + ent->client->pers.inventory[ITEM_INDEX(item)]--; + ValidateSelectedItem (ent); + + if (ent->client->double_framenum > level.framenum) + ent->client->double_framenum += 300; + else + ent->client->double_framenum = level.framenum + 300; + + gi.sound(ent, CHAN_ITEM, gi.soundindex("misc/ddamage1.wav"), 1, ATTN_NORM, 0); +} + +/* +void Use_Torch (edict_t *ent, gitem_t *item) +{ + ent->client->torch_framenum = level.framenum + 600; +} +*/ + +void Use_Compass (edict_t *ent, gitem_t *item) +{ + int ang; + + ang = (int)(ent->client->v_angle[1]); + if(ang<0) + ang += 360; + + gi.cprintf(ent, PRINT_HIGH, "Origin: %0.0f,%0.0f,%0.0f Dir: %d\n", ent->s.origin[0], ent->s.origin[1], + ent->s.origin[2], ang); +} + +void Use_Nuke (edict_t *ent, gitem_t *item) +{ + vec3_t forward, right, start; + float speed; + + ent->client->pers.inventory[ITEM_INDEX(item)]--; + ValidateSelectedItem (ent); + + AngleVectors (ent->client->v_angle, forward, right, NULL); + + VectorCopy (ent->s.origin, start); + speed = 100; + fire_nuke (ent, start, forward, speed); +} + +void Use_Doppleganger (edict_t *ent, gitem_t *item) +{ + vec3_t forward, right; + vec3_t createPt, spawnPt; + vec3_t ang; + + VectorClear(ang); + ang[YAW] = ent->client->v_angle[YAW]; + AngleVectors (ang, forward, right, NULL); + + VectorMA(ent->s.origin, 48, forward, createPt); + + if(!FindSpawnPoint(createPt, ent->mins, ent->maxs, spawnPt, 32)) + return; + + if(!CheckGroundSpawnPoint(spawnPt, ent->mins, ent->maxs, 64, -1)) + return; + + ent->client->pers.inventory[ITEM_INDEX(item)]--; + ValidateSelectedItem (ent); + + SpawnGrow_Spawn (spawnPt, 0); + fire_doppleganger (ent, spawnPt, forward); +} + +qboolean Pickup_Doppleganger (edict_t *ent, edict_t *other) +{ + int quantity; + + if(!(deathmatch->value)) // item is DM only + return false; + + quantity = other->client->pers.inventory[ITEM_INDEX(ent->item)]; + if (quantity >= 1) // FIXME - apply max to dopplegangers + return false; + + other->client->pers.inventory[ITEM_INDEX(ent->item)]++; + + if (!(ent->spawnflags & DROPPED_ITEM) ) + SetRespawn (ent, ent->item->quantity); + + return true; +} + + +qboolean Pickup_Sphere (edict_t *ent, edict_t *other) +{ + int quantity; + + if(other->client && other->client->owned_sphere) + { +// gi.cprintf(other, PRINT_HIGH, "Only one sphere to a customer!\n"); + return false; + } + + quantity = other->client->pers.inventory[ITEM_INDEX(ent->item)]; + if ((skill->value == 1 && quantity >= 2) || (skill->value >= 2 && quantity >= 1)) + return false; + + if ((coop->value) && (ent->item->flags & IT_STAY_COOP) && (quantity > 0)) + return false; + + other->client->pers.inventory[ITEM_INDEX(ent->item)]++; + + if (deathmatch->value) + { + if (!(ent->spawnflags & DROPPED_ITEM) ) + SetRespawn (ent, ent->item->quantity); + if (((int)dmflags->value & DF_INSTANT_ITEMS)) + { +//PGM + if(ent->item->use) + ent->item->use (other, ent->item); + else + gi.dprintf("Powerup has no use function!\n"); +//PGM + } + } + + return true; +} + +void Use_Defender (edict_t *ent, gitem_t *item) +{ + if(ent->client && ent->client->owned_sphere) + { + gi.cprintf(ent, PRINT_HIGH, "Only one sphere at a time!\n"); + return; + } + + ent->client->pers.inventory[ITEM_INDEX(item)]--; + ValidateSelectedItem (ent); + + Defender_Launch (ent); +} + +void Use_Hunter (edict_t *ent, gitem_t *item) +{ + if(ent->client && ent->client->owned_sphere) + { + gi.cprintf(ent, PRINT_HIGH, "Only one sphere at a time!\n"); + return; + } + + ent->client->pers.inventory[ITEM_INDEX(item)]--; + ValidateSelectedItem (ent); + + Hunter_Launch (ent); +} + +void Use_Vengeance (edict_t *ent, gitem_t *item) +{ + if(ent->client && ent->client->owned_sphere) + { + gi.cprintf(ent, PRINT_HIGH, "Only one sphere at a time!\n"); + return; + } + + ent->client->pers.inventory[ITEM_INDEX(item)]--; + ValidateSelectedItem (ent); + + Vengeance_Launch (ent); +} + +// PGM +// ================ + + +//====================================================================== + +void Use_Quad (edict_t *ent, gitem_t *item) +{ + int timeout; + + ent->client->pers.inventory[ITEM_INDEX(item)]--; + ValidateSelectedItem (ent); + + if (quad_drop_timeout_hack) + { + timeout = quad_drop_timeout_hack; + quad_drop_timeout_hack = 0; + } + else + { + timeout = 300; + } + + if (ent->client->quad_framenum > level.framenum) + ent->client->quad_framenum += timeout; + else + ent->client->quad_framenum = level.framenum + timeout; + + gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage.wav"), 1, ATTN_NORM, 0); +} + +//====================================================================== + +void Use_Breather (edict_t *ent, gitem_t *item) +{ + ent->client->pers.inventory[ITEM_INDEX(item)]--; + ValidateSelectedItem (ent); + + if (ent->client->breather_framenum > level.framenum) + ent->client->breather_framenum += 300; + else + ent->client->breather_framenum = level.framenum + 300; + +// gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage.wav"), 1, ATTN_NORM, 0); +} + +//====================================================================== + +void Use_Envirosuit (edict_t *ent, gitem_t *item) +{ + ent->client->pers.inventory[ITEM_INDEX(item)]--; + ValidateSelectedItem (ent); + + if (ent->client->enviro_framenum > level.framenum) + ent->client->enviro_framenum += 300; + else + ent->client->enviro_framenum = level.framenum + 300; + +// gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage.wav"), 1, ATTN_NORM, 0); +} + +//====================================================================== + +void Use_Invulnerability (edict_t *ent, gitem_t *item) +{ + ent->client->pers.inventory[ITEM_INDEX(item)]--; + ValidateSelectedItem (ent); + + if (ent->client->invincible_framenum > level.framenum) + ent->client->invincible_framenum += 300; + else + ent->client->invincible_framenum = level.framenum + 300; + + gi.sound(ent, CHAN_ITEM, gi.soundindex("items/protect.wav"), 1, ATTN_NORM, 0); +} + +//====================================================================== + +void Use_Silencer (edict_t *ent, gitem_t *item) +{ + ent->client->pers.inventory[ITEM_INDEX(item)]--; + ValidateSelectedItem (ent); + ent->client->silencer_shots += 30; + +// gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage.wav"), 1, ATTN_NORM, 0); +} + +//====================================================================== + +qboolean Pickup_Key (edict_t *ent, edict_t *other) +{ + if (coop->value) + { + if (strcmp(ent->classname, "key_power_cube") == 0) + { + if (other->client->pers.power_cubes & ((ent->spawnflags & 0x0000ff00)>> 8)) + return false; + other->client->pers.inventory[ITEM_INDEX(ent->item)]++; + other->client->pers.power_cubes |= ((ent->spawnflags & 0x0000ff00) >> 8); + } + else + { + if (other->client->pers.inventory[ITEM_INDEX(ent->item)]) + return false; + other->client->pers.inventory[ITEM_INDEX(ent->item)] = 1; + } + return true; + } + other->client->pers.inventory[ITEM_INDEX(ent->item)]++; + return true; +} + +//====================================================================== + +qboolean Add_Ammo (edict_t *ent, gitem_t *item, int count) +{ + int index; + int max; + + if (!ent->client) + return false; + + if (item->tag == AMMO_BULLETS) + max = ent->client->pers.max_bullets; + else if (item->tag == AMMO_SHELLS) + max = ent->client->pers.max_shells; + else if (item->tag == AMMO_ROCKETS) + max = ent->client->pers.max_rockets; + else if (item->tag == AMMO_GRENADES) + max = ent->client->pers.max_grenades; + else if (item->tag == AMMO_CELLS) + max = ent->client->pers.max_cells; + else if (item->tag == AMMO_SLUGS) + max = ent->client->pers.max_slugs; +// ROGUE +// else if (item->tag == AMMO_MINES) +// max = ent->client->pers.max_mines; + else if (item->tag == AMMO_FLECHETTES) + max = ent->client->pers.max_flechettes; + else if (item->tag == AMMO_PROX) + max = ent->client->pers.max_prox; + else if (item->tag == AMMO_TESLA) + max = ent->client->pers.max_tesla; +#ifndef KILL_DISRUPTOR + else if (item->tag == AMMO_DISRUPTOR) + max = ent->client->pers.max_rounds; +#endif +// ROGUE + else + { + gi.dprintf("undefined ammo type\n"); + return false; + } + + index = ITEM_INDEX(item); + + if (ent->client->pers.inventory[index] == max) + return false; + + ent->client->pers.inventory[index] += count; + + if (ent->client->pers.inventory[index] > max) + ent->client->pers.inventory[index] = max; + + return true; +} + +qboolean Pickup_Ammo (edict_t *ent, edict_t *other) +{ + int oldcount; + int count; + qboolean weapon; + + weapon = (ent->item->flags & IT_WEAPON); + if ( (weapon) && ( (int)dmflags->value & DF_INFINITE_AMMO ) ) + count = 1000; + else if (ent->count) + count = ent->count; + else + count = ent->item->quantity; + + oldcount = other->client->pers.inventory[ITEM_INDEX(ent->item)]; + + if (!Add_Ammo (other, ent->item, count)) + return false; + + if (weapon && !oldcount) + { + // don't switch to tesla + if (other->client->pers.weapon != ent->item + && ( !deathmatch->value || other->client->pers.weapon == FindItem("blaster")) + && (strcmp(ent->classname, "ammo_tesla")) ) + + other->client->newweapon = ent->item; + } + + if (!(ent->spawnflags & (DROPPED_ITEM | DROPPED_PLAYER_ITEM)) && (deathmatch->value)) + SetRespawn (ent, 30); + return true; +} + +void Drop_Ammo (edict_t *ent, gitem_t *item) +{ + edict_t *dropped; + int index; + + index = ITEM_INDEX(item); + dropped = Drop_Item (ent, item); + if (ent->client->pers.inventory[index] >= item->quantity) + dropped->count = item->quantity; + else + dropped->count = ent->client->pers.inventory[index]; + + if (ent->client->pers.weapon && + ent->client->pers.weapon->tag == AMMO_GRENADES && + item->tag == AMMO_GRENADES && + ent->client->pers.inventory[index] - dropped->count <= 0) { + gi.cprintf (ent, PRINT_HIGH, "Can't drop current weapon\n"); + G_FreeEdict(dropped); + return; + } + + ent->client->pers.inventory[index] -= dropped->count; + ValidateSelectedItem (ent); +} + + +//====================================================================== + +void MegaHealth_think (edict_t *self) +{ + if (self->owner->health > self->owner->max_health) + { + self->nextthink = level.time + 1; + self->owner->health -= 1; + return; + } + + if (!(self->spawnflags & DROPPED_ITEM) && (deathmatch->value)) + SetRespawn (self, 20); + else + G_FreeEdict (self); +} + +qboolean Pickup_Health (edict_t *ent, edict_t *other) +{ + if (!(ent->style & HEALTH_IGNORE_MAX)) + if (other->health >= other->max_health) + return false; + + other->health += ent->count; + + // PMM - health sound fix + /* + if (ent->count == 2) + ent->item->pickup_sound = "items/s_health.wav"; + else if (ent->count == 10) + ent->item->pickup_sound = "items/n_health.wav"; + else if (ent->count == 25) + ent->item->pickup_sound = "items/l_health.wav"; + else // (ent->count == 100) + ent->item->pickup_sound = "items/m_health.wav"; + */ + + if (!(ent->style & HEALTH_IGNORE_MAX)) + { + if (other->health > other->max_health) + other->health = other->max_health; + } + + if (ent->style & HEALTH_TIMED) + { + ent->think = MegaHealth_think; + ent->nextthink = level.time + 5; + ent->owner = other; + ent->flags |= FL_RESPAWN; + ent->svflags |= SVF_NOCLIENT; + ent->solid = SOLID_NOT; + } + else + { + if (!(ent->spawnflags & DROPPED_ITEM) && (deathmatch->value)) + SetRespawn (ent, 30); + } + + return true; +} + +//====================================================================== + +int ArmorIndex (edict_t *ent) +{ + if (!ent->client) + return 0; + + if (ent->client->pers.inventory[jacket_armor_index] > 0) + return jacket_armor_index; + + if (ent->client->pers.inventory[combat_armor_index] > 0) + return combat_armor_index; + + if (ent->client->pers.inventory[body_armor_index] > 0) + return body_armor_index; + + return 0; +} + +qboolean Pickup_Armor (edict_t *ent, edict_t *other) +{ + int old_armor_index; + gitem_armor_t *oldinfo; + gitem_armor_t *newinfo; + int newcount; + float salvage; + int salvagecount; + + // get info on new armor + newinfo = (gitem_armor_t *)ent->item->info; + + old_armor_index = ArmorIndex (other); + + // handle armor shards specially + if (ent->item->tag == ARMOR_SHARD) + { + if (!old_armor_index) + other->client->pers.inventory[jacket_armor_index] = 2; + else + other->client->pers.inventory[old_armor_index] += 2; + } + + // if player has no armor, just use it + else if (!old_armor_index) + { + other->client->pers.inventory[ITEM_INDEX(ent->item)] = newinfo->base_count; + } + + // use the better armor + else + { + // get info on old armor + if (old_armor_index == jacket_armor_index) + oldinfo = &jacketarmor_info; + else if (old_armor_index == combat_armor_index) + oldinfo = &combatarmor_info; + else // (old_armor_index == body_armor_index) + oldinfo = &bodyarmor_info; + + if (newinfo->normal_protection > oldinfo->normal_protection) + { + // calc new armor values + salvage = oldinfo->normal_protection / newinfo->normal_protection; + salvagecount = salvage * other->client->pers.inventory[old_armor_index]; + newcount = newinfo->base_count + salvagecount; + if (newcount > newinfo->max_count) + newcount = newinfo->max_count; + + // zero count of old armor so it goes away + other->client->pers.inventory[old_armor_index] = 0; + + // change armor to new item with computed value + other->client->pers.inventory[ITEM_INDEX(ent->item)] = newcount; + } + else + { + // calc new armor values + salvage = newinfo->normal_protection / oldinfo->normal_protection; + salvagecount = salvage * newinfo->base_count; + newcount = other->client->pers.inventory[old_armor_index] + salvagecount; + if (newcount > oldinfo->max_count) + newcount = oldinfo->max_count; + + // if we're already maxed out then we don't need the new armor + if (other->client->pers.inventory[old_armor_index] >= newcount) + return false; + + // update current armor value + other->client->pers.inventory[old_armor_index] = newcount; + } + } + + if (!(ent->spawnflags & DROPPED_ITEM) && (deathmatch->value)) + SetRespawn (ent, 20); + + return true; +} + +//====================================================================== + +int PowerArmorType (edict_t *ent) +{ + if (!ent->client) + return POWER_ARMOR_NONE; + + if (!(ent->flags & FL_POWER_ARMOR)) + return POWER_ARMOR_NONE; + + if (ent->client->pers.inventory[power_shield_index] > 0) + return POWER_ARMOR_SHIELD; + + if (ent->client->pers.inventory[power_screen_index] > 0) + return POWER_ARMOR_SCREEN; + + return POWER_ARMOR_NONE; +} + +void Use_PowerArmor (edict_t *ent, gitem_t *item) +{ + int index; + + if (ent->flags & FL_POWER_ARMOR) + { + ent->flags &= ~FL_POWER_ARMOR; + gi.sound(ent, CHAN_AUTO, gi.soundindex("misc/power2.wav"), 1, ATTN_NORM, 0); + } + else + { + index = ITEM_INDEX(FindItem("cells")); + if (!ent->client->pers.inventory[index]) + { + gi.cprintf (ent, PRINT_HIGH, "No cells for power armor.\n"); + return; + } + ent->flags |= FL_POWER_ARMOR; + gi.sound(ent, CHAN_AUTO, gi.soundindex("misc/power1.wav"), 1, ATTN_NORM, 0); + } +} + +qboolean Pickup_PowerArmor (edict_t *ent, edict_t *other) +{ + int quantity; + + quantity = other->client->pers.inventory[ITEM_INDEX(ent->item)]; + + other->client->pers.inventory[ITEM_INDEX(ent->item)]++; + + if (deathmatch->value) + { + if (!(ent->spawnflags & DROPPED_ITEM) ) + SetRespawn (ent, ent->item->quantity); + // auto-use for DM only if we didn't already have one + if (!quantity) + ent->item->use (other, ent->item); + } + + return true; +} + +void Drop_PowerArmor (edict_t *ent, gitem_t *item) +{ + if ((ent->flags & FL_POWER_ARMOR) && (ent->client->pers.inventory[ITEM_INDEX(item)] == 1)) + Use_PowerArmor (ent, item); + Drop_General (ent, item); +} + +//====================================================================== + +/* +=============== +Touch_Item +=============== +*/ +void Touch_Item (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + qboolean taken; + + if (!other->client) + return; + if (other->health < 1) + return; // dead people can't pickup + if (!ent->item->pickup) + return; // not a grabbable item? + + taken = ent->item->pickup(ent, other); + + if (taken) + { + // flash the screen + other->client->bonus_alpha = 0.25; + + // show icon and name on status bar + other->client->ps.stats[STAT_PICKUP_ICON] = gi.imageindex(ent->item->icon); + other->client->ps.stats[STAT_PICKUP_STRING] = CS_ITEMS+ITEM_INDEX(ent->item); + other->client->pickup_msg_time = level.time + 3.0; + + // change selected item + if (ent->item->use) + other->client->pers.selected_item = other->client->ps.stats[STAT_SELECTED_ITEM] = ITEM_INDEX(ent->item); + + // PMM - health sound fix + if (ent->item->pickup == Pickup_Health) + { + if (ent->count == 2) + gi.sound(other, CHAN_ITEM, gi.soundindex("items/s_health.wav"), 1, ATTN_NORM, 0); + else if (ent->count == 10) + gi.sound(other, CHAN_ITEM, gi.soundindex("items/n_health.wav"), 1, ATTN_NORM, 0); + else if (ent->count == 25) + gi.sound(other, CHAN_ITEM, gi.soundindex("items/l_health.wav"), 1, ATTN_NORM, 0); + else // (ent->count == 100) + gi.sound(other, CHAN_ITEM, gi.soundindex("items/m_health.wav"), 1, ATTN_NORM, 0); + } + else if (ent->item->pickup_sound) // PGM - paranoia + { + // + gi.sound(other, CHAN_ITEM, gi.soundindex(ent->item->pickup_sound), 1, ATTN_NORM, 0); + } + } + + if (!(ent->spawnflags & ITEM_TARGETS_USED)) + { + G_UseTargets (ent, other); + ent->spawnflags |= ITEM_TARGETS_USED; + } + + if (!taken) + return; + + if (!((coop->value) && (ent->item->flags & IT_STAY_COOP)) || (ent->spawnflags & (DROPPED_ITEM | DROPPED_PLAYER_ITEM))) + { + if (ent->flags & FL_RESPAWN) + ent->flags &= ~FL_RESPAWN; + else + G_FreeEdict (ent); + } +} + +//====================================================================== + +static void drop_temp_touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (other == ent->owner) + return; + + Touch_Item (ent, other, plane, surf); +} + +static void drop_make_touchable (edict_t *ent) +{ + ent->touch = Touch_Item; + if (deathmatch->value) + { + ent->nextthink = level.time + 29; + ent->think = G_FreeEdict; + } +} + +edict_t *Drop_Item (edict_t *ent, gitem_t *item) +{ + edict_t *dropped; + vec3_t forward, right; + vec3_t offset; + + dropped = G_Spawn(); + + dropped->classname = item->classname; + dropped->item = item; + dropped->spawnflags = DROPPED_ITEM; + dropped->s.effects = item->world_model_flags; + dropped->s.renderfx = RF_GLOW | RF_IR_VISIBLE; // PGM + VectorSet (dropped->mins, -15, -15, -15); + VectorSet (dropped->maxs, 15, 15, 15); + gi.setmodel (dropped, dropped->item->world_model); + dropped->solid = SOLID_TRIGGER; + dropped->movetype = MOVETYPE_TOSS; + dropped->touch = drop_temp_touch; + dropped->owner = ent; + + if (ent->client) + { + trace_t trace; + + AngleVectors (ent->client->v_angle, forward, right, NULL); + VectorSet(offset, 24, 0, -16); + G_ProjectSource (ent->s.origin, offset, forward, right, dropped->s.origin); + trace = gi.trace (ent->s.origin, dropped->mins, dropped->maxs, + dropped->s.origin, ent, CONTENTS_SOLID); + VectorCopy (trace.endpos, dropped->s.origin); + } + else + { + AngleVectors (ent->s.angles, forward, right, NULL); + VectorCopy (ent->s.origin, dropped->s.origin); + } + + VectorScale (forward, 100, dropped->velocity); + dropped->velocity[2] = 300; + + dropped->think = drop_make_touchable; + dropped->nextthink = level.time + 1; + + gi.linkentity (dropped); + + return dropped; +} + +void Use_Item (edict_t *ent, edict_t *other, edict_t *activator) +{ + ent->svflags &= ~SVF_NOCLIENT; + ent->use = NULL; + + if (ent->spawnflags & ITEM_NO_TOUCH) + { + ent->solid = SOLID_BBOX; + ent->touch = NULL; + } + else + { + ent->solid = SOLID_TRIGGER; + ent->touch = Touch_Item; + } + + gi.linkentity (ent); +} + +//====================================================================== + +/* +================ +droptofloor +================ +*/ +void droptofloor (edict_t *ent) +{ + trace_t tr; + vec3_t dest; + float *v; + + v = tv(-15,-15,-15); + VectorCopy (v, ent->mins); + v = tv(15,15,15); + VectorCopy (v, ent->maxs); + + if (ent->model) + gi.setmodel (ent, ent->model); + else if (ent->item->world_model) // PGM we shouldn't need this check, but paranoia... + gi.setmodel (ent, ent->item->world_model); + ent->solid = SOLID_TRIGGER; + ent->movetype = MOVETYPE_TOSS; + ent->touch = Touch_Item; + + v = tv(0,0,-128); + VectorAdd (ent->s.origin, v, dest); + + tr = gi.trace (ent->s.origin, ent->mins, ent->maxs, dest, ent, MASK_SOLID); + if (tr.startsolid) + { + gi.dprintf ("droptofloor: %s startsolid at %s\n", ent->classname, vtos(ent->s.origin)); + G_FreeEdict (ent); + return; + } + + VectorCopy (tr.endpos, ent->s.origin); + + if (ent->team) + { + ent->flags &= ~FL_TEAMSLAVE; + ent->chain = ent->teamchain; + ent->teamchain = NULL; + + ent->svflags |= SVF_NOCLIENT; + ent->solid = SOLID_NOT; + if (ent == ent->teammaster) + { + ent->nextthink = level.time + FRAMETIME; + ent->think = DoRespawn; + } + } + + if (ent->spawnflags & ITEM_NO_TOUCH) + { + ent->solid = SOLID_BBOX; + ent->touch = NULL; + ent->s.effects &= ~EF_ROTATE; + ent->s.renderfx &= ~RF_GLOW; + } + + if (ent->spawnflags & ITEM_TRIGGER_SPAWN) + { + ent->svflags |= SVF_NOCLIENT; + ent->solid = SOLID_NOT; + ent->use = Use_Item; + } + + gi.linkentity (ent); +} + + +/* +=============== +PrecacheItem + +Precaches all data needed for a given item. +This will be called for each item spawned in a level, +and for each item in each client's inventory. +=============== +*/ +void PrecacheItem (gitem_t *it) +{ + char *s, *start; + char data[MAX_QPATH]; + int len; + gitem_t *ammo; + + if (!it) + return; + + if (it->pickup_sound) + gi.soundindex (it->pickup_sound); + if (it->world_model) + gi.modelindex (it->world_model); + if (it->view_model) + gi.modelindex (it->view_model); + if (it->icon) + gi.imageindex (it->icon); + + // parse everything for its ammo + if (it->ammo && it->ammo[0]) + { + ammo = FindItem (it->ammo); + if (ammo != it) + PrecacheItem (ammo); + } + + // parse the space seperated precache string for other items + s = it->precaches; + if (!s || !s[0]) + return; + + while (*s) + { + start = s; + while (*s && *s != ' ') + s++; + + len = s-start; + if (len >= MAX_QPATH || len < 5) + gi.error ("PrecacheItem: %s has bad precache string", it->classname); + memcpy (data, start, len); + data[len] = 0; + if (*s) + s++; + + // determine type based on extension + if (!strcmp(data+len-3, "md2")) + gi.modelindex (data); + else if (!strcmp(data+len-3, "sp2")) + gi.modelindex (data); + else if (!strcmp(data+len-3, "wav")) + gi.soundindex (data); + if (!strcmp(data+len-3, "pcx")) + gi.imageindex (data); + } +} + + +//================= +// Item_TriggeredSpawn - create the item marked for spawn creation +//================= +void Item_TriggeredSpawn (edict_t *self, edict_t *other, edict_t *activator) +{ +// self->nextthink = level.time + 2 * FRAMETIME; // items start after other solids +// self->think = droptofloor; + self->svflags &= ~SVF_NOCLIENT; + self->use = NULL; + if(strcmp(self->classname, "key_power_cube")) // leave them be on key_power_cube.. + self->spawnflags = 0; + droptofloor (self); +} + +//================= +// SetTriggeredSpawn - set up an item to spawn in later. +//================= +void SetTriggeredSpawn (edict_t *ent) +{ + // don't do anything on key_power_cubes. + if(!strcmp(ent->classname, "key_power_cube")) + return; + + ent->think = NULL; + ent->nextthink = 0; + ent->use = Item_TriggeredSpawn; + ent->svflags |= SVF_NOCLIENT; + ent->solid = SOLID_NOT; +} + +/* +============ +SpawnItem + +Sets the clipping size and plants the object on the floor. + +Items can't be immediately dropped to floor, because they might +be on an entity that hasn't spawned yet. +============ +*/ +void SpawnItem (edict_t *ent, gitem_t *item) +{ +#if KILL_DISRUPTOR + if ((!strcmp(ent->classname, "ammo_disruptor")) || (!strcmp(ent->classname, "weapon_disintegrator"))) + { + G_FreeEdict (ent); + return; + } +#endif + +// PGM - since the item may be freed by the following rules, go ahead +// and move the precache until AFTER the following rules have been checked. +// keep an eye on this. +// PrecacheItem (item); + + if (ent->spawnflags > 1) // PGM + { + if (strcmp(ent->classname, "key_power_cube") != 0) + { + ent->spawnflags = 0; + gi.dprintf("%s at %s has invalid spawnflags set\n", ent->classname, vtos(ent->s.origin)); + } + } + + // some items will be prevented in deathmatch + if (deathmatch->value) + { + if ( (int)dmflags->value & DF_NO_ARMOR ) + { + if (item->pickup == Pickup_Armor || item->pickup == Pickup_PowerArmor) + { + G_FreeEdict (ent); + return; + } + } + if ( (int)dmflags->value & DF_NO_ITEMS ) + { + if (item->pickup == Pickup_Powerup) + { + G_FreeEdict (ent); + return; + } + //===== + //ROGUE + if (item->pickup == Pickup_Sphere) + { + G_FreeEdict (ent); + return; + } + if (item->pickup == Pickup_Doppleganger) + { + G_FreeEdict (ent); + return; + } + //ROGUE + //===== + } + if ( (int)dmflags->value & DF_NO_HEALTH ) + { + if (item->pickup == Pickup_Health || item->pickup == Pickup_Adrenaline || item->pickup == Pickup_AncientHead) + { + G_FreeEdict (ent); + return; + } + } + if ( (int)dmflags->value & DF_INFINITE_AMMO ) + { + if ( (item->flags == IT_AMMO) || (strcmp(ent->classname, "weapon_bfg") == 0) ) + { + G_FreeEdict (ent); + return; + } + } + +//========== +//ROGUE + if ( (int)dmflags->value & DF_NO_MINES ) + { + if ( !strcmp(ent->classname, "ammo_prox") || + !strcmp(ent->classname, "ammo_tesla") ) + { + G_FreeEdict (ent); + return; + } + } + if ( (int)dmflags->value & DF_NO_NUKES ) + { + if ( !strcmp(ent->classname, "ammo_nuke") ) + { + G_FreeEdict (ent); + return; + } + } + if ( (int)dmflags->value & DF_NO_SPHERES ) + { + if (item->pickup == Pickup_Sphere) + { + G_FreeEdict (ent); + return; + } + } +//ROGUE +//========== + + } +//========== +//ROGUE +// DM only items + if (!deathmatch->value) + { + if (item->pickup == Pickup_Doppleganger || item->pickup == Pickup_Nuke) + { + G_FreeEdict (ent); + return; + } + if ((item->use == Use_Vengeance) || (item->use == Use_Hunter)) + { + G_FreeEdict (ent); + return; + } + } +//ROGUE +//========== + +//PGM + PrecacheItem (item); +//PGM + + if (coop->value && (strcmp(ent->classname, "key_power_cube") == 0)) + { + ent->spawnflags |= (1 << (8 + level.power_cubes)); + level.power_cubes++; + } + + // don't let them drop items that stay in a coop game + if ((coop->value) && (item->flags & IT_STAY_COOP)) + { + item->drop = NULL; + } + + ent->item = item; + ent->nextthink = level.time + 2 * FRAMETIME; // items start after other solids + ent->think = droptofloor; + ent->s.effects = item->world_model_flags; + ent->s.renderfx = RF_GLOW; + if (ent->model) + gi.modelindex (ent->model); + + if (ent->spawnflags & 1) + SetTriggeredSpawn (ent); +} + +//====================================================================== + +gitem_t itemlist[] = +{ + { + NULL + }, // leave index 0 alone + + // + // ARMOR + // + +/*QUAKED item_armor_body (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN +*/ + { + "item_armor_body", + Pickup_Armor, + NULL, + NULL, + NULL, + "misc/ar1_pkup.wav", + "models/items/armor/body/tris.md2", EF_ROTATE, + NULL, +/* icon */ "i_bodyarmor", +/* pickup */ "Body Armor", +/* width */ 3, + 0, + NULL, + IT_ARMOR, + 0, + &bodyarmor_info, + ARMOR_BODY, +/* precache */ "" + }, + +/*QUAKED item_armor_combat (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN +*/ + { + "item_armor_combat", + Pickup_Armor, + NULL, + NULL, + NULL, + "misc/ar1_pkup.wav", + "models/items/armor/combat/tris.md2", EF_ROTATE, + NULL, +/* icon */ "i_combatarmor", +/* pickup */ "Combat Armor", +/* width */ 3, + 0, + NULL, + IT_ARMOR, + 0, + &combatarmor_info, + ARMOR_COMBAT, +/* precache */ "" + }, + +/*QUAKED item_armor_jacket (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN +*/ + { + "item_armor_jacket", + Pickup_Armor, + NULL, + NULL, + NULL, + "misc/ar1_pkup.wav", + "models/items/armor/jacket/tris.md2", EF_ROTATE, + NULL, +/* icon */ "i_jacketarmor", +/* pickup */ "Jacket Armor", +/* width */ 3, + 0, + NULL, + IT_ARMOR, + 0, + &jacketarmor_info, + ARMOR_JACKET, +/* precache */ "" + }, + +/*QUAKED item_armor_shard (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN +*/ + { + "item_armor_shard", + Pickup_Armor, + NULL, + NULL, + NULL, + "misc/ar2_pkup.wav", + "models/items/armor/shard/tris.md2", EF_ROTATE, + NULL, +/* icon */ "i_jacketarmor", +/* pickup */ "Armor Shard", +/* width */ 3, + 0, + NULL, + IT_ARMOR, + 0, + NULL, + ARMOR_SHARD, +/* precache */ "" + }, + + +/*QUAKED item_power_screen (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN +*/ + { + "item_power_screen", + Pickup_PowerArmor, + Use_PowerArmor, + Drop_PowerArmor, + NULL, + "misc/ar3_pkup.wav", + "models/items/armor/screen/tris.md2", EF_ROTATE, + NULL, +/* icon */ "i_powerscreen", +/* pickup */ "Power Screen", +/* width */ 0, + 60, + NULL, + IT_ARMOR, + 0, + NULL, + 0, +/* precache */ "" + }, + +/*QUAKED item_power_shield (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN +*/ + { + "item_power_shield", + Pickup_PowerArmor, + Use_PowerArmor, + Drop_PowerArmor, + NULL, + "misc/ar3_pkup.wav", + "models/items/armor/shield/tris.md2", EF_ROTATE, + NULL, +/* icon */ "i_powershield", +/* pickup */ "Power Shield", +/* width */ 0, + 60, + NULL, + IT_ARMOR, + 0, + NULL, + 0, +/* precache */ "misc/power2.wav misc/power1.wav" + }, + + + // + // WEAPONS + // + +/* weapon_blaster (.3 .3 1) (-16 -16 -16) (16 16 16) +always owned, never in the world +*/ + { + "weapon_blaster", + NULL, + Use_Weapon, + NULL, + Weapon_Blaster, + "misc/w_pkup.wav", + NULL, 0, + "models/weapons/v_blast/tris.md2", +/* icon */ "w_blaster", +/* pickup */ "Blaster", + 0, + 0, + NULL, + IT_WEAPON|IT_STAY_COOP, + WEAP_BLASTER, + NULL, + 0, +/* precache */ "weapons/blastf1a.wav misc/lasfly.wav" + }, + +/*QUAKED weapon_shotgun (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN +*/ + { + "weapon_shotgun", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_Shotgun, + "misc/w_pkup.wav", + "models/weapons/g_shotg/tris.md2", EF_ROTATE, + "models/weapons/v_shotg/tris.md2", +/* icon */ "w_shotgun", +/* pickup */ "Shotgun", + 0, + 1, + "Shells", + IT_WEAPON|IT_STAY_COOP, + WEAP_SHOTGUN, + NULL, + 0, +/* precache */ "weapons/shotgf1b.wav weapons/shotgr1b.wav" + }, + +/*QUAKED weapon_supershotgun (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN +*/ + { + "weapon_supershotgun", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_SuperShotgun, + "misc/w_pkup.wav", + "models/weapons/g_shotg2/tris.md2", EF_ROTATE, + "models/weapons/v_shotg2/tris.md2", +/* icon */ "w_sshotgun", +/* pickup */ "Super Shotgun", + 0, + 2, + "Shells", + IT_WEAPON|IT_STAY_COOP, + WEAP_SUPERSHOTGUN, + NULL, + 0, +/* precache */ "weapons/sshotf1b.wav" + }, + +/*QUAKED weapon_machinegun (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN +*/ + { + "weapon_machinegun", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_Machinegun, + "misc/w_pkup.wav", + "models/weapons/g_machn/tris.md2", EF_ROTATE, + "models/weapons/v_machn/tris.md2", +/* icon */ "w_machinegun", +/* pickup */ "Machinegun", + 0, + 1, + "Bullets", + IT_WEAPON|IT_STAY_COOP, + WEAP_MACHINEGUN, + NULL, + 0, +/* precache */ "weapons/machgf1b.wav weapons/machgf2b.wav weapons/machgf3b.wav weapons/machgf4b.wav weapons/machgf5b.wav" + }, + +/*QUAKED weapon_chaingun (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN +*/ + { + "weapon_chaingun", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_Chaingun, + "misc/w_pkup.wav", + "models/weapons/g_chain/tris.md2", EF_ROTATE, + "models/weapons/v_chain/tris.md2", +/* icon */ "w_chaingun", +/* pickup */ "Chaingun", + 0, + 1, + "Bullets", + IT_WEAPON|IT_STAY_COOP, + WEAP_CHAINGUN, + NULL, + 0, +/* precache */ "weapons/chngnu1a.wav weapons/chngnl1a.wav weapons/machgf3b.wav` weapons/chngnd1a.wav" + }, + + // ROGUE +/*QUAKED weapon_etf_rifle (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN +*/ + { + "weapon_etf_rifle", // classname + Pickup_Weapon, // pickup function + Use_Weapon, // use function + Drop_Weapon, // drop function + Weapon_ETF_Rifle, // weapon think function + "misc/w_pkup.wav", // pick up sound + "models/weapons/g_etf_rifle/tris.md2", EF_ROTATE, // world model, world model flags + "models/weapons/v_etf_rifle/tris.md2", // view model + "w_etf_rifle", // icon + "ETF Rifle", // name printed when picked up + 0, // number of digits for statusbar + 1, // amount used / contained + "Flechettes", // ammo type used + IT_WEAPON, // inventory flags + WEAP_ETFRIFLE, // visible weapon + NULL, // info (void *) + 0, // tag + "weapons/nail1.wav models/proj/flechette/tris.md2", // precaches + }, + + // rogue +/*QUAKED ammo_grenades (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN +*/ + { + "ammo_grenades", + Pickup_Ammo, + Use_Weapon, + Drop_Ammo, + Weapon_Grenade, + "misc/am_pkup.wav", + "models/items/ammo/grenades/medium/tris.md2", 0, + "models/weapons/v_handgr/tris.md2", +/* icon */ "a_grenades", +/* pickup */ "Grenades", +/* width */ 3, + 5, + "grenades", + IT_AMMO|IT_WEAPON, + WEAP_GRENADES, + NULL, + AMMO_GRENADES, +/* precache */ "weapons/hgrent1a.wav weapons/hgrena1b.wav weapons/hgrenc1b.wav weapons/hgrenb1a.wav weapons/hgrenb2a.wav " + }, + +/*QUAKED weapon_grenadelauncher (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN +*/ + { + "weapon_grenadelauncher", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_GrenadeLauncher, + "misc/w_pkup.wav", + "models/weapons/g_launch/tris.md2", EF_ROTATE, + "models/weapons/v_launch/tris.md2", +/* icon */ "w_glauncher", +/* pickup */ "Grenade Launcher", + 0, + 1, + "Grenades", + IT_WEAPON|IT_STAY_COOP, + WEAP_GRENADELAUNCHER, + NULL, + 0, +/* precache */ "models/objects/grenade/tris.md2 weapons/grenlf1a.wav weapons/grenlr1b.wav weapons/grenlb1b.wav" + }, + + // ROGUE +/*QUAKED weapon_proxlauncher (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN +*/ + { + "weapon_proxlauncher", // classname + Pickup_Weapon, // pickup + Use_Weapon, // use + Drop_Weapon, // drop + Weapon_ProxLauncher, // weapon think + "misc/w_pkup.wav", // pick up sound + "models/weapons/g_plaunch/tris.md2", EF_ROTATE, // world model, world model flags + "models/weapons/v_plaunch/tris.md2", // view model + "w_proxlaunch", // icon + "Prox Launcher", // name printed when picked up + 0, // number of digits for statusbar + 1, // amount used + "Prox", // ammo type used + IT_WEAPON, // inventory flags + WEAP_PROXLAUNCH, // visible weapon + NULL, // info (void *) + AMMO_PROX, // tag + "weapons/grenlf1a.wav weapons/grenlr1b.wav weapons/grenlb1b.wav weapons/proxwarn.wav weapons/proxopen.wav", + }, + // rogue + +/*QUAKED weapon_rocketlauncher (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN +*/ + { + "weapon_rocketlauncher", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_RocketLauncher, + "misc/w_pkup.wav", + "models/weapons/g_rocket/tris.md2", EF_ROTATE, + "models/weapons/v_rocket/tris.md2", +/* icon */ "w_rlauncher", +/* pickup */ "Rocket Launcher", + 0, + 1, + "Rockets", + IT_WEAPON|IT_STAY_COOP, + WEAP_ROCKETLAUNCHER, + NULL, + 0, +/* precache */ "models/objects/rocket/tris.md2 weapons/rockfly.wav weapons/rocklf1a.wav weapons/rocklr1b.wav models/objects/debris2/tris.md2" + }, + +/*QUAKED weapon_hyperblaster (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN +*/ + { + "weapon_hyperblaster", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_HyperBlaster, + "misc/w_pkup.wav", + "models/weapons/g_hyperb/tris.md2", EF_ROTATE, + "models/weapons/v_hyperb/tris.md2", +/* icon */ "w_hyperblaster", +/* pickup */ "HyperBlaster", + 0, + 1, + "Cells", + IT_WEAPON|IT_STAY_COOP, + WEAP_HYPERBLASTER, + NULL, + 0, +/* precache */ "weapons/hyprbu1a.wav weapons/hyprbl1a.wav weapons/hyprbf1a.wav weapons/hyprbd1a.wav misc/lasfly.wav" + }, + + // ROGUE +/*QUAKED weapon_plasmabeam (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN +*/ + { + "weapon_plasmabeam", // classname + Pickup_Weapon, // pickup function + Use_Weapon, // use function + Drop_Weapon, // drop function + Weapon_Heatbeam, // weapon think function + "misc/w_pkup.wav", // pick up sound + "models/weapons/g_beamer/tris.md2", EF_ROTATE, // world model, world model flags + "models/weapons/v_beamer/tris.md2", // view model + "w_heatbeam", // icon + "Plasma Beam", // name printed when picked up + 0, // number of digits for statusbar + // FIXME - if this changes, change it in NoAmmoWeaponChange as well + 2, // amount used / contained + "Cells", // ammo type used + IT_WEAPON, // inventory flags + WEAP_PLASMA, // visible weapon + NULL, // info (void *) + 0, // tag + "models/weapons/v_beamer2/tris.md2 weapons/bfg__l1a.wav", // precaches + }, + //rogue +/*QUAKED weapon_railgun (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN +*/ + { + "weapon_railgun", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_Railgun, + "misc/w_pkup.wav", + "models/weapons/g_rail/tris.md2", EF_ROTATE, + "models/weapons/v_rail/tris.md2", +/* icon */ "w_railgun", +/* pickup */ "Railgun", + 0, + 1, + "Slugs", + IT_WEAPON|IT_STAY_COOP, + WEAP_RAILGUN, + NULL, + 0, +/* precache */ "weapons/rg_hum.wav" + }, + +/*QUAKED weapon_bfg (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN +*/ + { + "weapon_bfg", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_BFG, + "misc/w_pkup.wav", + "models/weapons/g_bfg/tris.md2", EF_ROTATE, + "models/weapons/v_bfg/tris.md2", +/* icon */ "w_bfg", +/* pickup */ "BFG10K", + 0, + 50, + "Cells", + IT_WEAPON|IT_STAY_COOP, + WEAP_BFG, + NULL, + 0, +/* precache */ "sprites/s_bfg1.sp2 sprites/s_bfg2.sp2 sprites/s_bfg3.sp2 weapons/bfg__f1y.wav weapons/bfg__l1a.wav weapons/bfg__x1b.wav weapons/bfg_hum.wav" + }, + +// ========================= +// ROGUE WEAPONS +/*QUAKED weapon_chainfist (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN +*/ + { + "weapon_chainfist", // classname + Pickup_Weapon, // pickup function + Use_Weapon, // use function + Drop_Weapon, // drop function + Weapon_ChainFist, // weapon think function + "misc/w_pkup.wav", // pick up sound + "models/weapons/g_chainf/tris.md2", EF_ROTATE, // world model, world model flags + "models/weapons/v_chainf/tris.md2", // view model + "w_chainfist", // icon + "Chainfist", // name printed when picked up + 0, // number of digits for statusbar + 0, // amount used / contained + NULL, // ammo type used + IT_WEAPON | IT_MELEE, // inventory flags + WEAP_CHAINFIST, // visible weapon + NULL, // info (void *) + 1, // tag + "weapons/sawidle.wav weapons/sawhit.wav", // precaches + }, + +/*QUAKED weapon_disintegrator (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN +*/ + { + "weapon_disintegrator", // classname + Pickup_Weapon, // pickup function + Use_Weapon, // use function + Drop_Weapon, // drop function + Weapon_Disintegrator, // weapon think function + "misc/w_pkup.wav", // pick up sound + "models/weapons/g_dist/tris.md2", EF_ROTATE, // world model, world model flags + "models/weapons/v_dist/tris.md2", // view model + "w_disintegrator", // icon + "Disruptor", // name printed when picked up + 0, // number of digits for statusbar + 1, // amount used / contained + "Rounds", // ammo type used +#ifdef KILL_DISRUPTOR + IT_NOT_GIVEABLE, +#else + IT_WEAPON, // inventory flags +#endif + WEAP_DISRUPTOR, // visible weapon + NULL, // info (void *) + 1, // tag + "models/items/spawngro/tris.md2 models/proj/disintegrator/tris.md2 weapons/disrupt.wav weapons/disint2.wav weapons/disrupthit.wav", // precaches + }, + +// ROGUE WEAPONS +// ========================= + + + // + // AMMO ITEMS + // + +/*QUAKED ammo_shells (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN +*/ + { + "ammo_shells", + Pickup_Ammo, + NULL, + Drop_Ammo, + NULL, + "misc/am_pkup.wav", + "models/items/ammo/shells/medium/tris.md2", 0, + NULL, +/* icon */ "a_shells", +/* pickup */ "Shells", +/* width */ 3, + 10, + NULL, + IT_AMMO, + 0, + NULL, + AMMO_SHELLS, +/* precache */ "" + }, + +/*QUAKED ammo_bullets (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN +*/ + { + "ammo_bullets", + Pickup_Ammo, + NULL, + Drop_Ammo, + NULL, + "misc/am_pkup.wav", + "models/items/ammo/bullets/medium/tris.md2", 0, + NULL, +/* icon */ "a_bullets", +/* pickup */ "Bullets", +/* width */ 3, + 50, + NULL, + IT_AMMO, + 0, + NULL, + AMMO_BULLETS, +/* precache */ "" + }, + +/*QUAKED ammo_cells (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN +*/ + { + "ammo_cells", + Pickup_Ammo, + NULL, + Drop_Ammo, + NULL, + "misc/am_pkup.wav", + "models/items/ammo/cells/medium/tris.md2", 0, + NULL, +/* icon */ "a_cells", +/* pickup */ "Cells", +/* width */ 3, + 50, + NULL, + IT_AMMO, + 0, + NULL, + AMMO_CELLS, +/* precache */ "" + }, + +/*QUAKED ammo_rockets (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN +*/ + { + "ammo_rockets", + Pickup_Ammo, + NULL, + Drop_Ammo, + NULL, + "misc/am_pkup.wav", + "models/items/ammo/rockets/medium/tris.md2", 0, + NULL, +/* icon */ "a_rockets", +/* pickup */ "Rockets", +/* width */ 3, + 5, + NULL, + IT_AMMO, + 0, + NULL, + AMMO_ROCKETS, +/* precache */ "" + }, + +/*QUAKED ammo_slugs (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN +*/ + { + "ammo_slugs", + Pickup_Ammo, + NULL, + Drop_Ammo, + NULL, + "misc/am_pkup.wav", + "models/items/ammo/slugs/medium/tris.md2", 0, + NULL, +/* icon */ "a_slugs", +/* pickup */ "Slugs", +/* width */ 3, + 10, + NULL, + IT_AMMO, + 0, + NULL, + AMMO_SLUGS, +/* precache */ "" + }, + +// ======================================= +// ROGUE AMMO + +/*QUAKED ammo_flechettes (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN +*/ + { + "ammo_flechettes", + Pickup_Ammo, + NULL, + Drop_Ammo, + NULL, + "misc/am_pkup.wav", + "models/ammo/am_flechette/tris.md2", 0, + NULL, + "a_flechettes", + "Flechettes", + 3, + 50, + NULL, + IT_AMMO, + 0, + NULL, + AMMO_FLECHETTES + }, +/*QUAKED ammo_prox (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN +*/ + { + "ammo_prox", // Classname + Pickup_Ammo, // pickup function + NULL, // use function + Drop_Ammo, // drop function + NULL, // weapon think function + "misc/am_pkup.wav", // pickup sound + "models/ammo/am_prox/tris.md2", 0, // world model, world model flags + NULL, // view model + "a_prox", // icon + "Prox", // Name printed when picked up + 3, // number of digits for status bar + 5, // amount contained + NULL, // ammo type used + IT_AMMO, // inventory flags + 0, // vwep index + NULL, // info (void *) + AMMO_PROX, // tag + "models/weapons/g_prox/tris.md2 weapons/proxwarn.wav" // precaches + }, + +/*QUAKED ammo_tesla (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN +*/ + { + "ammo_tesla", + Pickup_Ammo, + Use_Weapon, // PGM + Drop_Ammo, + Weapon_Tesla, // PGM + "misc/am_pkup.wav", +// "models/weapons/g_tesla/tris.md2", 0, + "models/ammo/am_tesl/tris.md2", 0, + "models/weapons/v_tesla/tris.md2", + "a_tesla", + "Tesla", + 3, + 5, + "Tesla", // PGM + IT_AMMO | IT_WEAPON, // inventory flags + 0, + NULL, // info (void *) + AMMO_TESLA, // tag + "models/weapons/v_tesla2/tris.md2 weapons/teslaopen.wav weapons/hgrenb1a.wav weapons/hgrenb2a.wav models/weapons/g_tesla/tris.md2" // precache + }, + +/*QUAKED ammo_nuke (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN +*/ + { + "ammo_nuke", + Pickup_Nuke, + Use_Nuke, // PMM + Drop_Ammo, + NULL, // PMM + "misc/am_pkup.wav", + "models/weapons/g_nuke/tris.md2", EF_ROTATE, + NULL, +/* icon */ "p_nuke", +/* pickup */ "A-M Bomb", +/* width */ 3, + 300, /* quantity (used for respawn time) */ + "A-M Bomb", + IT_POWERUP, + 0, + NULL, + 0, + "weapons/nukewarn2.wav world/rumble.wav" + }, + +/*QUAKED ammo_disruptor (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN +*/ + { + "ammo_disruptor", + Pickup_Ammo, + NULL, + Drop_Ammo, + NULL, + "misc/am_pkup.wav", + "models/ammo/am_disr/tris.md2", 0, + NULL, + "a_disruptor", + "Rounds", // FIXME + 3, + 15, + NULL, +#ifdef KILL_DISRUPTOR + IT_NOT_GIVEABLE, +#else + IT_AMMO, // inventory flags +#endif + 0, + NULL, +#ifdef KILL_DISRUPTOR + 0, +#else + AMMO_DISRUPTOR, +#endif + }, +// ROGUE AMMO +// ======================================= + + // + // POWERUP ITEMS + // +/*QUAKED item_quad (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN +*/ + { + "item_quad", + Pickup_Powerup, + Use_Quad, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/quaddama/tris.md2", EF_ROTATE, + NULL, +/* icon */ "p_quad", +/* pickup */ "Quad Damage", +/* width */ 2, + 60, + NULL, + IT_POWERUP, + 0, + NULL, + 0, +/* precache */ "items/damage.wav items/damage2.wav items/damage3.wav" + }, + +/*QUAKED item_invulnerability (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN +*/ + { + "item_invulnerability", + Pickup_Powerup, + Use_Invulnerability, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/invulner/tris.md2", EF_ROTATE, + NULL, +/* icon */ "p_invulnerability", +/* pickup */ "Invulnerability", +/* width */ 2, + 300, + NULL, + IT_POWERUP, + 0, + NULL, + 0, +/* precache */ "items/protect.wav items/protect2.wav items/protect4.wav" + }, + +/*QUAKED item_silencer (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN +*/ + { + "item_silencer", + Pickup_Powerup, + Use_Silencer, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/silencer/tris.md2", EF_ROTATE, + NULL, +/* icon */ "p_silencer", +/* pickup */ "Silencer", +/* width */ 2, + 60, + NULL, + IT_POWERUP, + 0, + NULL, + 0, +/* precache */ "" + }, + +/*QUAKED item_breather (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN +*/ + { + "item_breather", + Pickup_Powerup, + Use_Breather, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/breather/tris.md2", EF_ROTATE, + NULL, +/* icon */ "p_rebreather", +/* pickup */ "Rebreather", +/* width */ 2, + 60, + NULL, + IT_STAY_COOP|IT_POWERUP, + 0, + NULL, + 0, +/* precache */ "items/airout.wav" + }, + +/*QUAKED item_enviro (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN +*/ + { + "item_enviro", + Pickup_Powerup, + Use_Envirosuit, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/enviro/tris.md2", EF_ROTATE, + NULL, +/* icon */ "p_envirosuit", +/* pickup */ "Environment Suit", +/* width */ 2, + 60, + NULL, + IT_STAY_COOP|IT_POWERUP, + 0, + NULL, + 0, +/* precache */ "items/airout.wav" + }, + +/*QUAKED item_ancient_head (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN +Special item that gives +2 to maximum health +*/ + { + "item_ancient_head", + Pickup_AncientHead, + NULL, + NULL, + NULL, + "items/pkup.wav", + "models/items/c_head/tris.md2", EF_ROTATE, + NULL, +/* icon */ "i_fixme", +/* pickup */ "Ancient Head", +/* width */ 2, + 60, + NULL, + 0, + 0, + NULL, + 0, +/* precache */ "" + }, + +/*QUAKED item_adrenaline (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN +gives +1 to maximum health +*/ + { + "item_adrenaline", + Pickup_Adrenaline, + NULL, + NULL, + NULL, + "items/pkup.wav", + "models/items/adrenal/tris.md2", EF_ROTATE, + NULL, +/* icon */ "p_adrenaline", +/* pickup */ "Adrenaline", +/* width */ 2, + 60, + NULL, + 0, + 0, + NULL, + 0, +/* precache */ "" + }, + +/*QUAKED item_bandolier (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN +*/ + { + "item_bandolier", + Pickup_Bandolier, + NULL, + NULL, + NULL, + "items/pkup.wav", + "models/items/band/tris.md2", EF_ROTATE, + NULL, +/* icon */ "p_bandolier", +/* pickup */ "Bandolier", +/* width */ 2, + 60, + NULL, + 0, + 0, + NULL, + 0, +/* precache */ "" + }, + +/*QUAKED item_pack (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN +*/ + { + "item_pack", + Pickup_Pack, + NULL, + NULL, + NULL, + "items/pkup.wav", + "models/items/pack/tris.md2", EF_ROTATE, + NULL, +/* icon */ "i_pack", +/* pickup */ "Ammo Pack", +/* width */ 2, + 180, + NULL, + 0, + 0, + NULL, + 0, +/* precache */ "" + }, + +// ====================================== +// PGM + +/*QUAKED item_ir_goggles (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN +gives +1 to maximum health +*/ + { + "item_ir_goggles", + Pickup_Powerup, + Use_IR, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/goggles/tris.md2", EF_ROTATE, + NULL, +/* icon */ "p_ir", +/* pickup */ "IR Goggles", +/* width */ 2, + 60, + NULL, + IT_POWERUP, + 0, + NULL, + 0, +/* precache */ "misc/ir_start.wav" + }, + +/*QUAKED item_double (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN +*/ + { + "item_double", + Pickup_Powerup, + Use_Double, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/ddamage/tris.md2", EF_ROTATE, + NULL, +/* icon */ "p_double", +/* pickup */ "Double Damage", +/* width */ 2, + 60, + NULL, + IT_POWERUP, + 0, + NULL, + 0, +/* precache */ "misc/ddamage1.wav misc/ddamage2.wav misc/ddamage3.wav" + }, + +/*Q U A K E D item_torch (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN +*/ +/* + { + "item_torch", + Pickup_Powerup, + Use_Torch, + Drop_General, + NULL, + "items/pkup.wav", + "models/objects/fire/tris.md2", EF_ROTATE, + NULL, + "p_torch", + "torch", + 2, + 60, + NULL, + IT_POWERUP, + 0, + NULL, + 0, + }, +*/ + +/*QUAKED item_compass (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN +*/ + { + "item_compass", + Pickup_Powerup, + Use_Compass, + NULL, + NULL, + "items/pkup.wav", + "models/objects/fire/tris.md2", EF_ROTATE, + NULL, +/* icon */ "p_compass", +/* pickup */ "compass", +/* width */ 2, + 60, + NULL, + IT_POWERUP, + 0, + NULL, + 0, + }, + +/*QUAKED item_sphere_vengeance (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN +*/ + { + "item_sphere_vengeance", + Pickup_Sphere, + Use_Vengeance, + NULL, + NULL, + "items/pkup.wav", + "models/items/vengnce/tris.md2", EF_ROTATE, + NULL, +/* icon */ "p_vengeance", +/* pickup */ "vengeance sphere", +/* width */ 2, + 60, + NULL, + IT_POWERUP, + 0, + NULL, + 0, + "spheres/v_idle.wav" //precache + }, + +/*QUAKED item_sphere_hunter (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN +*/ + { + "item_sphere_hunter", + Pickup_Sphere, + Use_Hunter, + NULL, + NULL, + "items/pkup.wav", + "models/items/hunter/tris.md2", EF_ROTATE, + NULL, +/* icon */ "p_hunter", +/* pickup */ "hunter sphere", +/* width */ 2, + 120, + NULL, + IT_POWERUP, + 0, + NULL, + 0, + "spheres/h_idle.wav spheres/h_active.wav spheres/h_lurk.wav" //precache + }, + +/*QUAKED item_sphere_defender (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN +*/ + { + "item_sphere_defender", + Pickup_Sphere, + Use_Defender, + NULL, + NULL, + "items/pkup.wav", + "models/items/defender/tris.md2", EF_ROTATE, + NULL, +/* icon */ "p_defender", +/* pickup */ "defender sphere", +/* width */ 2, + 60, // respawn time + NULL, // ammo type used + IT_POWERUP, // inventory flags + 0, + NULL, // info (void *) + 0, // tag + "models/proj/laser2/tris.md2 models/items/shell/tris.md2 spheres/d_idle.wav" // precache + }, + +/*QUAKED item_doppleganger (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN +*/ + { + "item_doppleganger", // classname + Pickup_Doppleganger, // pickup function + Use_Doppleganger, // use function + Drop_General, // drop function + NULL, // weapon think function + "items/pkup.wav", // pick up sound + "models/items/dopple/tris.md2", // world model + EF_ROTATE, // world model flags + NULL, // view model + "p_doppleganger", // icon + "Doppleganger", // name printed when picked up + 0, // number of digits for statusbar + 90, // respawn time + NULL, // ammo type used + IT_POWERUP, // inventory flags + 0, + NULL, // info (void *) + 0, // tag + "models/objects/dopplebase/tris.md2 models/items/spawngro2/tris.md2 models/items/hunter/tris.md2 models/items/vengnce/tris.md2", // precaches + }, + + { +// "dm_tag_token", // classname + NULL, // classname + Tag_PickupToken, // pickup function + NULL, // use function + NULL, // drop function + NULL, // weapon think function + "items/pkup.wav", // pick up sound + "models/items/tagtoken/tris.md2", // world model + EF_ROTATE | EF_TAGTRAIL, // world model flags + NULL, // view model + "i_tagtoken", // icon + "Tag Token", // name printed when picked up + 0, // number of digits for statusbar + 0, // amount used / contained + NULL, // ammo type used + IT_POWERUP | IT_NOT_GIVEABLE, // inventory flags + 0, + NULL, // info (void *) + 1, // tag + NULL, // precaches + }, + +// PGM +// ====================================== + + + // + // KEYS + // +/*QUAKED key_data_cd (0 .5 .8) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN +key for computer centers +*/ + { + "key_data_cd", + Pickup_Key, + NULL, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/keys/data_cd/tris.md2", EF_ROTATE, + NULL, + "k_datacd", + "Data CD", + 2, + 0, + NULL, + IT_STAY_COOP|IT_KEY, + 0, + NULL, + 0, +/* precache */ "" + }, + +/*QUAKED key_power_cube (0 .5 .8) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN NO_TOUCH +warehouse circuits +*/ + { + "key_power_cube", + Pickup_Key, + NULL, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/keys/power/tris.md2", EF_ROTATE, + NULL, + "k_powercube", + "Power Cube", + 2, + 0, + NULL, + IT_STAY_COOP|IT_KEY, + 0, + NULL, + 0, +/* precache */ "" + }, + +/*QUAKED key_pyramid (0 .5 .8) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN +key for the entrance of jail3 +*/ + { + "key_pyramid", + Pickup_Key, + NULL, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/keys/pyramid/tris.md2", EF_ROTATE, + NULL, + "k_pyramid", + "Pyramid Key", + 2, + 0, + NULL, + IT_STAY_COOP|IT_KEY, + 0, + NULL, + 0, +/* precache */ "" + }, + +/*QUAKED key_data_spinner (0 .5 .8) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN +key for the city computer +*/ + { + "key_data_spinner", + Pickup_Key, + NULL, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/keys/spinner/tris.md2", EF_ROTATE, + NULL, + "k_dataspin", + "Data Spinner", + 2, + 0, + NULL, + IT_STAY_COOP|IT_KEY, + 0, + NULL, + 0, +/* precache */ "" + }, + +/*QUAKED key_pass (0 .5 .8) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN +security pass for the security level +*/ + { + "key_pass", + Pickup_Key, + NULL, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/keys/pass/tris.md2", EF_ROTATE, + NULL, + "k_security", + "Security Pass", + 2, + 0, + NULL, + IT_STAY_COOP|IT_KEY, + 0, + NULL, + 0, +/* precache */ "" + }, + +/*QUAKED key_blue_key (0 .5 .8) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN +normal door key - blue +*/ + { + "key_blue_key", + Pickup_Key, + NULL, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/keys/key/tris.md2", EF_ROTATE, + NULL, + "k_bluekey", + "Blue Key", + 2, + 0, + NULL, + IT_STAY_COOP|IT_KEY, + 0, + NULL, + 0, +/* precache */ "" + }, + +/*QUAKED key_red_key (0 .5 .8) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN +normal door key - red +*/ + { + "key_red_key", + Pickup_Key, + NULL, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/keys/red_key/tris.md2", EF_ROTATE, + NULL, + "k_redkey", + "Red Key", + 2, + 0, + NULL, + IT_STAY_COOP|IT_KEY, + 0, + NULL, + 0, +/* precache */ "" + }, + +/*QUAKED key_commander_head (0 .5 .8) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN +tank commander's head +*/ + { + "key_commander_head", + Pickup_Key, + NULL, + Drop_General, + NULL, + "items/pkup.wav", + "models/monsters/commandr/head/tris.md2", EF_GIB, + NULL, +/* icon */ "k_comhead", +/* pickup */ "Commander's Head", +/* width */ 2, + 0, + NULL, + IT_STAY_COOP|IT_KEY, + 0, + NULL, + 0, +/* precache */ "" + }, + +/*QUAKED key_airstrike_target (0 .5 .8) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN +tank commander's head +*/ + { + "key_airstrike_target", + Pickup_Key, + NULL, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/keys/target/tris.md2", EF_ROTATE, + NULL, +/* icon */ "i_airstrike", +/* pickup */ "Airstrike Marker", +/* width */ 2, + 0, + NULL, + IT_STAY_COOP|IT_KEY, + 0, + NULL, + 0, +/* precache */ "" + }, + +// ====================================== +// PGM + +/*QUAKED key_nuke_container (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN +*/ + { + "key_nuke_container", // classname + Pickup_Key, // pickup function + NULL, // use function + Drop_General, // drop function + NULL, // weapon think function + "items/pkup.wav", // pick up sound + "models/weapons/g_nuke/tris.md2", // world model + EF_ROTATE, // world model flags + NULL, // view model + "i_contain", // icon + "Antimatter Pod", // name printed when picked up + 2, // number of digits for statusbar + 0, // respawn time + NULL, // ammo type used + IT_STAY_COOP|IT_KEY, // inventory flags + 0, + NULL, // info (void *) + 0, // tag + NULL, // precaches + }, + +/*QUAKED key_nuke (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN +*/ + { + "key_nuke", // classname + Pickup_Key, // pickup function + NULL, // use function + Drop_General, // drop function + NULL, // weapon think function + "items/pkup.wav", // pick up sound + "models/weapons/g_nuke/tris.md2", // world model + EF_ROTATE, // world model flags + NULL, // view model + "i_nuke", // icon + "Antimatter Bomb", // name printed when picked up + 2, // number of digits for statusbar + 0, // respawn time + NULL, // ammo type used + IT_STAY_COOP|IT_KEY, // inventory flags + 0, + NULL, // info (void *) + 0, // tag + NULL, // precaches + }, + +// PGM +// ====================================== + + { + NULL, + Pickup_Health, + NULL, + NULL, + NULL, + "items/pkup.wav", + NULL, 0, + NULL, +/* icon */ "i_health", +/* pickup */ "Health", +/* width */ 3, + 0, + NULL, + 0, + 0, + NULL, + 0, +/* precache */ "items/s_health.wav items/n_health.wav items/l_health.wav items/m_health.wav" // PMM - health sound fix + }, + + // end of list marker + {NULL} +}; + + +/*QUAKED item_health (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN +*/ +void SP_item_health (edict_t *self) +{ + if ( deathmatch->value && ((int)dmflags->value & DF_NO_HEALTH) ) + { + G_FreeEdict (self); + return; + } + + self->model = "models/items/healing/medium/tris.md2"; + self->count = 10; + SpawnItem (self, FindItem ("Health")); + gi.soundindex ("items/n_health.wav"); +} + +/*QUAKED item_health_small (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN +*/ +void SP_item_health_small (edict_t *self) +{ + if ( deathmatch->value && ((int)dmflags->value & DF_NO_HEALTH) ) + { + G_FreeEdict (self); + return; + } + + self->model = "models/items/healing/stimpack/tris.md2"; + self->count = 2; + SpawnItem (self, FindItem ("Health")); + self->style = HEALTH_IGNORE_MAX; + gi.soundindex ("items/s_health.wav"); +} + +/*QUAKED item_health_large (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN +*/ +void SP_item_health_large (edict_t *self) +{ + if ( deathmatch->value && ((int)dmflags->value & DF_NO_HEALTH) ) + { + G_FreeEdict (self); + return; + } + + self->model = "models/items/healing/large/tris.md2"; + self->count = 25; + SpawnItem (self, FindItem ("Health")); + gi.soundindex ("items/l_health.wav"); +} + +/*QUAKED item_health_mega (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN +*/ +void SP_item_health_mega (edict_t *self) +{ + if ( deathmatch->value && ((int)dmflags->value & DF_NO_HEALTH) ) + { + G_FreeEdict (self); + return; + } + + self->model = "models/items/mega_h/tris.md2"; + self->count = 100; + SpawnItem (self, FindItem ("Health")); + gi.soundindex ("items/m_health.wav"); + self->style = HEALTH_IGNORE_MAX|HEALTH_TIMED; +} + + +void InitItems (void) +{ + game.num_items = sizeof(itemlist)/sizeof(itemlist[0]) - 1; +} + + + +/* +=============== +SetItemNames + +Called by worldspawn +=============== +*/ +void SetItemNames (void) +{ + int i; + gitem_t *it; + + for (i=0 ; ipickup_name); + } + + jacket_armor_index = ITEM_INDEX(FindItem("Jacket Armor")); + combat_armor_index = ITEM_INDEX(FindItem("Combat Armor")); + body_armor_index = ITEM_INDEX(FindItem("Body Armor")); + power_screen_index = ITEM_INDEX(FindItem("Power Screen")); + power_shield_index = ITEM_INDEX(FindItem("Power Shield")); +} + + +//=============== +//ROGUE +void SP_xatrix_item (edict_t *self) +{ + gitem_t *item; + int i; + char *spawnClass; + + if(!self->classname) + return; + + if(!strcmp(self->classname, "ammo_magslug")) + spawnClass = "ammo_flechettes"; + else if(!strcmp(self->classname, "ammo_trap")) + spawnClass = "weapon_proxlauncher"; + else if(!strcmp(self->classname, "item_quadfire")) + { + float chance; + + chance = random(); + if(chance < 0.2) + spawnClass = "item_sphere_hunter"; + else if (chance < 0.6) + spawnClass = "item_sphere_vengeance"; + else + spawnClass = "item_sphere_defender"; + } + else if(!strcmp(self->classname, "weapon_boomer")) + spawnClass = "weapon_etf_rifle"; + else if(!strcmp(self->classname, "weapon_phalanx")) + spawnClass = "weapon_plasmabeam"; + + + // check item spawn functions + for (i=0,item=itemlist ; iclassname) + continue; + if (!strcmp(item->classname, spawnClass)) + { // found it + SpawnItem (self, item); + return; + } + } +} +//ROGUE +//=============== diff --git a/original/rogue/g_local.h b/original/rogue/g_local.h new file mode 100644 index 0000000..3bc1200 --- /dev/null +++ b/original/rogue/g_local.h @@ -0,0 +1,1455 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// g_local.h -- local definitions for game module + +#include "q_shared.h" + +// define GAME_INCLUDE so that game.h does not define the +// short, server-visible gclient_t and edict_t structures, +// because we define the full size ones in this file +#define GAME_INCLUDE +#include "game.h" + +// the "gameversion" client command will print this plus compile date +#define GAMEVERSION "baseq2" + +// protocol bytes that can be directly added to messages +#define svc_muzzleflash 1 +#define svc_muzzleflash2 2 +#define svc_temp_entity 3 +#define svc_layout 4 +#define svc_inventory 5 +#define svc_stufftext 11 + +//================================================================== + +#ifndef _WIN32 +#include +#define min(a,b) ((a) < (b) ? (a) : (b)) +#define max(a,b) ((a) > (b) ? (a) : (b)) +#ifdef __sun__ +#define _isnan(a) (NaN((a))) +#else +#define _isnan(a) ((a)==NAN) +#endif +#endif + +//================================================================== + +// view pitching times +#define DAMAGE_TIME 0.5 +#define FALL_TIME 0.3 + +// ROGUE- id killed this weapon +#define KILL_DISRUPTOR 1 +// rogue + +// edict->spawnflags +// these are set with checkboxes on each entity in the map editor +#define SPAWNFLAG_NOT_EASY 0x00000100 +#define SPAWNFLAG_NOT_MEDIUM 0x00000200 +#define SPAWNFLAG_NOT_HARD 0x00000400 +#define SPAWNFLAG_NOT_DEATHMATCH 0x00000800 +#define SPAWNFLAG_NOT_COOP 0x00001000 + +// edict->flags +#define FL_FLY 0x00000001 +#define FL_SWIM 0x00000002 // implied immunity to drowining +#define FL_IMMUNE_LASER 0x00000004 +#define FL_INWATER 0x00000008 +#define FL_GODMODE 0x00000010 +#define FL_NOTARGET 0x00000020 +#define FL_IMMUNE_SLIME 0x00000040 +#define FL_IMMUNE_LAVA 0x00000080 +#define FL_PARTIALGROUND 0x00000100 // not all corners are valid +#define FL_WATERJUMP 0x00000200 // player jumping out of water +#define FL_TEAMSLAVE 0x00000400 // not the first on the team +#define FL_NO_KNOCKBACK 0x00000800 +#define FL_POWER_ARMOR 0x00001000 // power armor (if any) is active +#define FL_RESPAWN 0x80000000 // used for item respawning + +//ROGUE +#define FL_MECHANICAL 0x00002000 // entity is mechanical, use sparks not blood +#define FL_SAM_RAIMI 0x00004000 // entity is in sam raimi cam mode +#define FL_DISGUISED 0x00008000 // entity is in disguise, monsters will not recognize. +#define FL_NOGIB 0x00010000 // player has been vaporized by a nuke, drop no gibs +//ROGUE + +#define FRAMETIME 0.1 + +// memory tags to allow dynamic memory to be cleaned up +#define TAG_GAME 765 // clear when unloading the dll +#define TAG_LEVEL 766 // clear when loading a new level + + +#define MELEE_DISTANCE 80 + +#define BODY_QUEUE_SIZE 8 + +typedef enum +{ + DAMAGE_NO, + DAMAGE_YES, // will take damage if hit + DAMAGE_AIM // auto targeting recognizes this +} damage_t; + +typedef enum +{ + WEAPON_READY, + WEAPON_ACTIVATING, + WEAPON_DROPPING, + WEAPON_FIRING +} weaponstate_t; + +typedef enum +{ + AMMO_BULLETS, + AMMO_SHELLS, + AMMO_ROCKETS, + AMMO_GRENADES, + AMMO_CELLS, + AMMO_SLUGS, + + //ROGUE + AMMO_FLECHETTES, + AMMO_TESLA, +#ifdef KILL_DISRUPTOR + AMMO_PROX +#else + AMMO_PROX, + AMMO_DISRUPTOR +#endif +} ammo_t; + + +//deadflag +#define DEAD_NO 0 +#define DEAD_DYING 1 +#define DEAD_DEAD 2 +#define DEAD_RESPAWNABLE 3 + +//range +#define RANGE_MELEE 0 +#define RANGE_NEAR 1 +#define RANGE_MID 2 +#define RANGE_FAR 3 + +//gib types +#define GIB_ORGANIC 0 +#define GIB_METALLIC 1 + +//monster ai flags +#define AI_STAND_GROUND 0x00000001 +#define AI_TEMP_STAND_GROUND 0x00000002 +#define AI_SOUND_TARGET 0x00000004 +#define AI_LOST_SIGHT 0x00000008 +#define AI_PURSUIT_LAST_SEEN 0x00000010 +#define AI_PURSUE_NEXT 0x00000020 +#define AI_PURSUE_TEMP 0x00000040 +#define AI_HOLD_FRAME 0x00000080 +#define AI_GOOD_GUY 0x00000100 +#define AI_BRUTAL 0x00000200 +#define AI_NOSTEP 0x00000400 +#define AI_DUCKED 0x00000800 +#define AI_COMBAT_POINT 0x00001000 +#define AI_MEDIC 0x00002000 +#define AI_RESURRECTING 0x00004000 + +//ROGUE +#define AI_WALK_WALLS 0x00008000 +#define AI_MANUAL_STEERING 0x00010000 +#define AI_TARGET_ANGER 0x00020000 +#define AI_DODGING 0x00040000 +#define AI_CHARGING 0x00080000 +#define AI_HINT_PATH 0x00100000 +#define AI_IGNORE_SHOTS 0x00200000 +// PMM - FIXME - last second added for E3 .. there's probably a better way to do this, but +// this works +#define AI_DO_NOT_COUNT 0x00400000 // set for healed monsters +#define AI_SPAWNED_CARRIER 0x00800000 // both do_not_count and spawned are set for spawned monsters +#define AI_SPAWNED_MEDIC_C 0x01000000 // both do_not_count and spawned are set for spawned monsters +#define AI_SPAWNED_WIDOW 0x02000000 // both do_not_count and spawned are set for spawned monsters +#define AI_SPAWNED_MASK 0x03800000 // mask to catch all three flavors of spawned +#define AI_BLOCKED 0x04000000 // used by blocked_checkattack: set to say I'm attacking while blocked + // (prevents run-attacks) +//ROGUE + +//monster attack state +#define AS_STRAIGHT 1 +#define AS_SLIDING 2 +#define AS_MELEE 3 +#define AS_MISSILE 4 +#define AS_BLIND 5 // PMM - used by boss code to do nasty things even if it can't see you + +// armor types +#define ARMOR_NONE 0 +#define ARMOR_JACKET 1 +#define ARMOR_COMBAT 2 +#define ARMOR_BODY 3 +#define ARMOR_SHARD 4 + +// power armor types +#define POWER_ARMOR_NONE 0 +#define POWER_ARMOR_SCREEN 1 +#define POWER_ARMOR_SHIELD 2 + +// handedness values +#define RIGHT_HANDED 0 +#define LEFT_HANDED 1 +#define CENTER_HANDED 2 + + +// game.serverflags values +#define SFL_CROSS_TRIGGER_1 0x00000001 +#define SFL_CROSS_TRIGGER_2 0x00000002 +#define SFL_CROSS_TRIGGER_3 0x00000004 +#define SFL_CROSS_TRIGGER_4 0x00000008 +#define SFL_CROSS_TRIGGER_5 0x00000010 +#define SFL_CROSS_TRIGGER_6 0x00000020 +#define SFL_CROSS_TRIGGER_7 0x00000040 +#define SFL_CROSS_TRIGGER_8 0x00000080 +#define SFL_CROSS_TRIGGER_MASK 0x000000ff + + +// noise types for PlayerNoise +#define PNOISE_SELF 0 +#define PNOISE_WEAPON 1 +#define PNOISE_IMPACT 2 + + +// edict->movetype values +typedef enum +{ +MOVETYPE_NONE, // never moves +MOVETYPE_NOCLIP, // origin and angles change with no interaction +MOVETYPE_PUSH, // no clip to world, push on box contact +MOVETYPE_STOP, // no clip to world, stops on box contact + +MOVETYPE_WALK, // gravity +MOVETYPE_STEP, // gravity, special edge handling +MOVETYPE_FLY, +MOVETYPE_TOSS, // gravity +MOVETYPE_FLYMISSILE, // extra size to monsters +MOVETYPE_BOUNCE, +MOVETYPE_NEWTOSS // PGM - for deathball +} movetype_t; + + + +typedef struct +{ + int base_count; + int max_count; + float normal_protection; + float energy_protection; + int armor; +} gitem_armor_t; + + +// gitem_t->flags +#define IT_WEAPON 0x00000001 // use makes active weapon +#define IT_AMMO 0x00000002 +#define IT_ARMOR 0x00000004 +#define IT_STAY_COOP 0x00000008 +#define IT_KEY 0x00000010 +#define IT_POWERUP 0x00000020 + +// ROGUE +#define IT_MELEE 0x00000040 +#define IT_NOT_GIVEABLE 0x00000080 // item can not be given +// ROGUE + +// gitem_t->weapmodel for weapons indicates model index +#define WEAP_BLASTER 1 +#define WEAP_SHOTGUN 2 +#define WEAP_SUPERSHOTGUN 3 +#define WEAP_MACHINEGUN 4 +#define WEAP_CHAINGUN 5 +#define WEAP_GRENADES 6 +#define WEAP_GRENADELAUNCHER 7 +#define WEAP_ROCKETLAUNCHER 8 +#define WEAP_HYPERBLASTER 9 +#define WEAP_RAILGUN 10 +#define WEAP_BFG 11 + +#define WEAP_DISRUPTOR 12 // PGM +#define WEAP_ETFRIFLE 13 // PGM +#define WEAP_PLASMA 14 // PGM +#define WEAP_PROXLAUNCH 15 // PGM +#define WEAP_CHAINFIST 16 // PGM + +typedef struct gitem_s +{ + char *classname; // spawning name + qboolean (*pickup)(struct edict_s *ent, struct edict_s *other); + void (*use)(struct edict_s *ent, struct gitem_s *item); + void (*drop)(struct edict_s *ent, struct gitem_s *item); + void (*weaponthink)(struct edict_s *ent); + char *pickup_sound; + char *world_model; + int world_model_flags; + char *view_model; + + // client side info + char *icon; + char *pickup_name; // for printing on pickup + int count_width; // number of digits to display by icon + + int quantity; // for ammo how much, for weapons how much is used per shot + char *ammo; // for weapons + int flags; // IT_* flags + + int weapmodel; // weapon model index (for weapons) + + void *info; + int tag; + + char *precaches; // string of all models, sounds, and images this item will use +} gitem_t; + + + +// +// this structure is left intact through an entire game +// it should be initialized at dll load time, and read/written to +// the server.ssv file for savegames +// +typedef struct +{ + char helpmessage1[512]; + char helpmessage2[512]; + int helpchanged; // flash F1 icon if non 0, play sound + // and increment only if 1, 2, or 3 + + gclient_t *clients; // [maxclients] + + // can't store spawnpoint in level, because + // it would get overwritten by the savegame restore + char spawnpoint[512]; // needed for coop respawns + + // store latched cvars here that we want to get at often + int maxclients; + int maxentities; + + // cross level triggers + int serverflags; + + // items + int num_items; + + qboolean autosaved; +} game_locals_t; + + +// +// this structure is cleared as each map is entered +// it is read/written to the level.sav file for savegames +// +typedef struct +{ + int framenum; + float time; + + char level_name[MAX_QPATH]; // the descriptive name (Outer Base, etc) + char mapname[MAX_QPATH]; // the server name (base1, etc) + char nextmap[MAX_QPATH]; // go here when fraglimit is hit + + // intermission state + float intermissiontime; // time the intermission was started + char *changemap; + int exitintermission; + vec3_t intermission_origin; + vec3_t intermission_angle; + + edict_t *sight_client; // changed once each frame for coop games + + edict_t *sight_entity; + int sight_entity_framenum; + edict_t *sound_entity; + int sound_entity_framenum; + edict_t *sound2_entity; + int sound2_entity_framenum; + + int pic_health; + + int total_secrets; + int found_secrets; + + int total_goals; + int found_goals; + + int total_monsters; + int killed_monsters; + + edict_t *current_entity; // entity running from G_RunFrame + int body_que; // dead bodies + + int power_cubes; // ugly necessity for coop + + // ROGUE + edict_t *disguise_violator; + int disguise_violation_framenum; + // ROGUE +} level_locals_t; + + +// spawn_temp_t is only used to hold entity field values that +// can be set from the editor, but aren't actualy present +// in edict_t during gameplay +typedef struct +{ + // world vars + char *sky; + float skyrotate; + vec3_t skyaxis; + char *nextmap; + + int lip; + int distance; + int height; + char *noise; + float pausetime; + char *item; + char *gravity; + + float minyaw; + float maxyaw; + float minpitch; + float maxpitch; +} spawn_temp_t; + + +typedef struct +{ + // fixed data + vec3_t start_origin; + vec3_t start_angles; + vec3_t end_origin; + vec3_t end_angles; + + int sound_start; + int sound_middle; + int sound_end; + + float accel; + float speed; + float decel; + float distance; + + float wait; + + // state data + int state; + vec3_t dir; + float current_speed; + float move_speed; + float next_speed; + float remaining_distance; + float decel_distance; + void (*endfunc)(edict_t *); +} moveinfo_t; + + +typedef struct +{ + void (*aifunc)(edict_t *self, float dist); + float dist; + void (*thinkfunc)(edict_t *self); +} mframe_t; + +typedef struct +{ + int firstframe; + int lastframe; + mframe_t *frame; + void (*endfunc)(edict_t *self); +} mmove_t; + +typedef struct +{ + mmove_t *currentmove; + unsigned int aiflags; // PGM - unsigned, since we're close to the max + int nextframe; + float scale; + + void (*stand)(edict_t *self); + void (*idle)(edict_t *self); + void (*search)(edict_t *self); + void (*walk)(edict_t *self); + void (*run)(edict_t *self); + void (*dodge)(edict_t *self, edict_t *other, float eta, trace_t *tr); + void (*attack)(edict_t *self); + void (*melee)(edict_t *self); + void (*sight)(edict_t *self, edict_t *other); + qboolean (*checkattack)(edict_t *self); + + float pausetime; + float attack_finished; + + vec3_t saved_goal; + float search_time; + float trail_time; + vec3_t last_sighting; + int attack_state; + int lefty; + float idle_time; + int linkcount; + + int power_armor_type; + int power_armor_power; + +//ROGUE + qboolean (*blocked)(edict_t *self, float dist); +// edict_t *last_hint; // last hint_path the monster touched + float last_hint_time; // last time the monster checked for hintpaths. + edict_t *goal_hint; // which hint_path we're trying to get to + int medicTries; + edict_t *badMedic1, *badMedic2; // these medics have declared this monster "unhealable" + edict_t *healer; // this is who is healing this monster + void (*duck)(edict_t *self, float eta); + void (*unduck)(edict_t *self); + void (*sidestep)(edict_t *self); + // while abort_duck would be nice, only monsters which duck but don't sidestep would use it .. only the brain + // not really worth it. sidestep is an implied abort_duck +// void (*abort_duck)(edict_t *self); + float base_height; + float next_duck_time; + float duck_wait_time; + edict_t *last_player_enemy; + // blindfire stuff .. the boolean says whether the monster will do it, and blind_fire_time is the timing + // (set in the monster) of the next shot + qboolean blindfire; // will the monster blindfire? + float blind_fire_delay; + vec3_t blind_fire_target; + // used by the spawners to not spawn too much and keep track of #s of monsters spawned + int monster_slots; + int monster_used; + edict_t *commander; + // powerup timers, used by widow, our friend + float quad_framenum; + float invincible_framenum; + float double_framenum; +//ROGUE +} monsterinfo_t; + +// ROGUE +// this determines how long to wait after a duck to duck again. this needs to be longer than +// the time after the monster_duck_up in all of the animation sequences +#define DUCK_INTERVAL 0.5 +// ROGUE + +extern game_locals_t game; +extern level_locals_t level; +extern game_import_t gi; +extern game_export_t globals; +extern spawn_temp_t st; + +extern int sm_meat_index; +extern int snd_fry; + +extern int jacket_armor_index; +extern int combat_armor_index; +extern int body_armor_index; + + +// means of death +#define MOD_UNKNOWN 0 +#define MOD_BLASTER 1 +#define MOD_SHOTGUN 2 +#define MOD_SSHOTGUN 3 +#define MOD_MACHINEGUN 4 +#define MOD_CHAINGUN 5 +#define MOD_GRENADE 6 +#define MOD_G_SPLASH 7 +#define MOD_ROCKET 8 +#define MOD_R_SPLASH 9 +#define MOD_HYPERBLASTER 10 +#define MOD_RAILGUN 11 +#define MOD_BFG_LASER 12 +#define MOD_BFG_BLAST 13 +#define MOD_BFG_EFFECT 14 +#define MOD_HANDGRENADE 15 +#define MOD_HG_SPLASH 16 +#define MOD_WATER 17 +#define MOD_SLIME 18 +#define MOD_LAVA 19 +#define MOD_CRUSH 20 +#define MOD_TELEFRAG 21 +#define MOD_FALLING 22 +#define MOD_SUICIDE 23 +#define MOD_HELD_GRENADE 24 +#define MOD_EXPLOSIVE 25 +#define MOD_BARREL 26 +#define MOD_BOMB 27 +#define MOD_EXIT 28 +#define MOD_SPLASH 29 +#define MOD_TARGET_LASER 30 +#define MOD_TRIGGER_HURT 31 +#define MOD_HIT 32 +#define MOD_TARGET_BLASTER 33 +#define MOD_FRIENDLY_FIRE 0x8000000 + +//======== +//ROGUE +#define MOD_CHAINFIST 40 +#define MOD_DISINTEGRATOR 41 +#define MOD_ETF_RIFLE 42 +#define MOD_BLASTER2 43 +#define MOD_HEATBEAM 44 +#define MOD_TESLA 45 +#define MOD_PROX 46 +#define MOD_NUKE 47 +#define MOD_VENGEANCE_SPHERE 48 +#define MOD_HUNTER_SPHERE 49 +#define MOD_DEFENDER_SPHERE 50 +#define MOD_TRACKER 51 +#define MOD_DBALL_CRUSH 52 +#define MOD_DOPPLE_EXPLODE 53 +#define MOD_DOPPLE_VENGEANCE 54 +#define MOD_DOPPLE_HUNTER 55 +//ROGUE +//======== + +extern int meansOfDeath; + + +extern edict_t *g_edicts; + +#define FOFS(x) (int)&(((edict_t *)0)->x) +#define STOFS(x) (int)&(((spawn_temp_t *)0)->x) +#define LLOFS(x) (int)&(((level_locals_t *)0)->x) +#define CLOFS(x) (int)&(((gclient_t *)0)->x) + +#define random() ((rand () & 0x7fff) / ((float)0x7fff)) +#define crandom() (2.0 * (random() - 0.5)) + +extern cvar_t *maxentities; +extern cvar_t *deathmatch; +extern cvar_t *coop; +extern cvar_t *dmflags; +extern cvar_t *skill; +extern cvar_t *fraglimit; +extern cvar_t *timelimit; +extern cvar_t *password; +extern cvar_t *spectator_password; +extern cvar_t *g_select_empty; +extern cvar_t *dedicated; + +extern cvar_t *filterban; + +extern cvar_t *sv_gravity; +extern cvar_t *sv_maxvelocity; + +extern cvar_t *gun_x, *gun_y, *gun_z; +extern cvar_t *sv_rollspeed; +extern cvar_t *sv_rollangle; + +extern cvar_t *run_pitch; +extern cvar_t *run_roll; +extern cvar_t *bob_up; +extern cvar_t *bob_pitch; +extern cvar_t *bob_roll; + +extern cvar_t *sv_cheats; +extern cvar_t *maxclients; +extern cvar_t *maxspectators; + +extern cvar_t *flood_msgs; +extern cvar_t *flood_persecond; +extern cvar_t *flood_waitdelay; + +extern cvar_t *sv_maplist; + +extern cvar_t *sv_stopspeed; // PGM - this was a define in g_phys.c + +//ROGUE +extern cvar_t *g_showlogic; +extern cvar_t *gamerules; +extern cvar_t *huntercam; +extern cvar_t *strong_mines; +extern cvar_t *randomrespawn; + +// this is for the count of monsters +#define ENT_SLOTS_LEFT (ent->monsterinfo.monster_slots - ent->monsterinfo.monster_used) +#define SELF_SLOTS_LEFT (self->monsterinfo.monster_slots - self->monsterinfo.monster_used) +//ROGUE + +#define world (&g_edicts[0]) + +// item spawnflags +#define ITEM_TRIGGER_SPAWN 0x00000001 +#define ITEM_NO_TOUCH 0x00000002 +// 6 bits reserved for editor flags +// 8 bits used as power cube id bits for coop games +#define DROPPED_ITEM 0x00010000 +#define DROPPED_PLAYER_ITEM 0x00020000 +#define ITEM_TARGETS_USED 0x00040000 + +// +// fields are needed for spawning from the entity string +// and saving / loading games +// +#define FFL_SPAWNTEMP 1 +#define FFL_NOSPAWN 2 + +typedef enum { + F_INT, + F_FLOAT, + F_LSTRING, // string on disk, pointer in memory, TAG_LEVEL + F_GSTRING, // string on disk, pointer in memory, TAG_GAME + F_VECTOR, + F_ANGLEHACK, + F_EDICT, // index on disk, pointer in memory + F_ITEM, // index on disk, pointer in memory + F_CLIENT, // index on disk, pointer in memory + F_FUNCTION, + F_MMOVE, + F_IGNORE +} fieldtype_t; + +typedef struct +{ + char *name; + int ofs; + fieldtype_t type; + int flags; +} field_t; + + +extern field_t fields[]; +extern gitem_t itemlist[]; + + +// +// g_cmds.c +// +void Cmd_Help_f (edict_t *ent); +void Cmd_Score_f (edict_t *ent); + +// +// g_items.c +// +void PrecacheItem (gitem_t *it); +void InitItems (void); +void SetItemNames (void); +gitem_t *FindItem (char *pickup_name); +gitem_t *FindItemByClassname (char *classname); +#define ITEM_INDEX(x) ((x)-itemlist) +edict_t *Drop_Item (edict_t *ent, gitem_t *item); +void SetRespawn (edict_t *ent, float delay); +void ChangeWeapon (edict_t *ent); +void SpawnItem (edict_t *ent, gitem_t *item); +void Think_Weapon (edict_t *ent); +int ArmorIndex (edict_t *ent); +int PowerArmorType (edict_t *ent); +gitem_t *GetItemByIndex (int index); +qboolean Add_Ammo (edict_t *ent, gitem_t *item, int count); +void Touch_Item (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf); + +// +// g_utils.c +// +qboolean KillBox (edict_t *ent); +void G_ProjectSource (vec3_t point, vec3_t distance, vec3_t forward, vec3_t right, vec3_t result); +edict_t *G_Find (edict_t *from, int fieldofs, char *match); +edict_t *findradius (edict_t *from, vec3_t org, float rad); +edict_t *G_PickTarget (char *targetname); +void G_UseTargets (edict_t *ent, edict_t *activator); +void G_SetMovedir (vec3_t angles, vec3_t movedir); + +void G_InitEdict (edict_t *e); +edict_t *G_Spawn (void); +void G_FreeEdict (edict_t *e); + +void G_TouchTriggers (edict_t *ent); +void G_TouchSolids (edict_t *ent); + +char *G_CopyString (char *in); + +float *tv (float x, float y, float z); +char *vtos (vec3_t v); + +float vectoyaw (vec3_t vec); +void vectoangles (vec3_t vec, vec3_t angles); + +//ROGUE +void G_ProjectSource2 (vec3_t point, vec3_t distance, vec3_t forward, vec3_t right, vec3_t up, vec3_t result); +float vectoyaw2 (vec3_t vec); +void vectoangles2 (vec3_t vec, vec3_t angles); +edict_t *findradius2 (edict_t *from, vec3_t org, float rad); +//ROGUE + +// +// g_combat.c +// +qboolean OnSameTeam (edict_t *ent1, edict_t *ent2); +qboolean CanDamage (edict_t *targ, edict_t *inflictor); +void T_Damage (edict_t *targ, edict_t *inflictor, edict_t *attacker, vec3_t dir, vec3_t point, vec3_t normal, int damage, int knockback, int dflags, int mod); +void T_RadiusDamage (edict_t *inflictor, edict_t *attacker, float damage, edict_t *ignore, float radius, int mod); + +//ROGUE +void T_RadiusNukeDamage (edict_t *inflictor, edict_t *attacker, float damage, edict_t *ignore, float radius, int mod); +void T_RadiusClassDamage (edict_t *inflictor, edict_t *attacker, float damage, char *ignoreClass, float radius, int mod); +void cleanupHealTarget (edict_t *ent); +//ROGUE + +// damage flags +#define DAMAGE_RADIUS 0x00000001 // damage was indirect +#define DAMAGE_NO_ARMOR 0x00000002 // armour does not protect from this damage +#define DAMAGE_ENERGY 0x00000004 // damage is from an energy based weapon +#define DAMAGE_NO_KNOCKBACK 0x00000008 // do not affect velocity, just view angles +#define DAMAGE_BULLET 0x00000010 // damage is from a bullet (used for ricochets) +#define DAMAGE_NO_PROTECTION 0x00000020 // armor, shields, invulnerability, and godmode have no effect +//ROGUE +#define DAMAGE_DESTROY_ARMOR 0x00000040 // damage is done to armor and health. +#define DAMAGE_NO_REG_ARMOR 0x00000080 // damage skips regular armor +#define DAMAGE_NO_POWER_ARMOR 0x00000100 // damage skips power armor +//ROGUE + + +#define DEFAULT_BULLET_HSPREAD 300 +#define DEFAULT_BULLET_VSPREAD 500 +#define DEFAULT_SHOTGUN_HSPREAD 1000 +#define DEFAULT_SHOTGUN_VSPREAD 500 +#define DEFAULT_DEATHMATCH_SHOTGUN_COUNT 12 +#define DEFAULT_SHOTGUN_COUNT 12 +#define DEFAULT_SSHOTGUN_COUNT 20 + +// +// g_monster.c +// +void monster_fire_bullet (edict_t *self, vec3_t start, vec3_t dir, int damage, int kick, int hspread, int vspread, int flashtype); +void monster_fire_shotgun (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int hspread, int vspread, int count, int flashtype); +void monster_fire_blaster (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int flashtype, int effect); +void monster_fire_grenade (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, int flashtype); +void monster_fire_rocket (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int flashtype); +void monster_fire_railgun (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int flashtype); +void monster_fire_bfg (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, int kick, float damage_radius, int flashtype); +void M_droptofloor (edict_t *ent); +void monster_think (edict_t *self); +void walkmonster_start (edict_t *self); +void swimmonster_start (edict_t *self); +void flymonster_start (edict_t *self); +void AttackFinished (edict_t *self, float time); +void monster_death_use (edict_t *self); +void M_CatagorizePosition (edict_t *ent); +qboolean M_CheckAttack (edict_t *self); +void M_FlyCheck (edict_t *self); +void M_CheckGround (edict_t *ent); +//ROGUE +void monster_fire_blaster2 (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int flashtype, int effect); +void monster_fire_tracker (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, edict_t *enemy, int flashtype); +void monster_fire_heat (edict_t *self, vec3_t start, vec3_t dir, vec3_t offset, int damage, int kick, int flashtype); +void stationarymonster_start (edict_t *self); +void monster_done_dodge (edict_t *self); +//ROGUE + + +// +// g_misc.c +// +void ThrowHead (edict_t *self, char *gibname, int damage, int type); +void ThrowClientHead (edict_t *self, int damage); +void ThrowGib (edict_t *self, char *gibname, int damage, int type); +void BecomeExplosion1(edict_t *self); + +// +// g_ai.c +// +void AI_SetSightClient (void); + +void ai_stand (edict_t *self, float dist); +void ai_move (edict_t *self, float dist); +void ai_walk (edict_t *self, float dist); +void ai_turn (edict_t *self, float dist); +void ai_run (edict_t *self, float dist); +void ai_charge (edict_t *self, float dist); +int range (edict_t *self, edict_t *other); + +void FoundTarget (edict_t *self); +qboolean infront (edict_t *self, edict_t *other); +qboolean visible (edict_t *self, edict_t *other); +qboolean FacingIdeal(edict_t *self); + +// +// g_weapon.c +// +void ThrowDebris (edict_t *self, char *modelname, float speed, vec3_t origin); +qboolean fire_hit (edict_t *self, vec3_t aim, int damage, int kick); +void fire_bullet (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int hspread, int vspread, int mod); +void fire_shotgun (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int hspread, int vspread, int count, int mod); +void fire_blaster (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, int effect, qboolean hyper); +void fire_grenade (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, float timer, float damage_radius); +void fire_grenade2 (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, float timer, float damage_radius, qboolean held); +void fire_rocket (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, float damage_radius, int radius_damage); +void fire_rail (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick); +void fire_bfg (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, float damage_radius); + +// +// g_ptrail.c +// +void PlayerTrail_Init (void); +void PlayerTrail_Add (vec3_t spot); +void PlayerTrail_New (vec3_t spot); +edict_t *PlayerTrail_PickFirst (edict_t *self); +edict_t *PlayerTrail_PickNext (edict_t *self); +edict_t *PlayerTrail_LastSpot (void); + +// +// g_client.c +// +void respawn (edict_t *ent); +void BeginIntermission (edict_t *targ); +void PutClientInServer (edict_t *ent); +void InitClientPersistant (gclient_t *client); +void InitClientResp (gclient_t *client); +void InitBodyQue (void); +void ClientBeginServerFrame (edict_t *ent); + +// +// g_player.c +// +void player_pain (edict_t *self, edict_t *other, float kick, int damage); +void player_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point); + +// +// g_svcmds.c +// +void ServerCommand (void); +qboolean SV_FilterPacket (char *from); + +// +// p_view.c +// +void ClientEndServerFrame (edict_t *ent); + +// +// p_hud.c +// +void MoveClientToIntermission (edict_t *client); +void G_SetStats (edict_t *ent); +void G_SetSpectatorStats (edict_t *ent); +void G_CheckChaseStats (edict_t *ent); +void ValidateSelectedItem (edict_t *ent); +void DeathmatchScoreboardMessage (edict_t *client, edict_t *killer); + +// +// g_pweapon.c +// +void PlayerNoise(edict_t *who, vec3_t where, int type); + +// +// m_move.c +// +qboolean M_CheckBottom (edict_t *ent); +qboolean M_walkmove (edict_t *ent, float yaw, float dist); +void M_MoveToGoal (edict_t *ent, float dist); +void M_ChangeYaw (edict_t *ent); + +// +// g_phys.c +// +void G_RunEntity (edict_t *ent); + +// +// g_main.c +// +void SaveClientData (void); +void FetchClientEntData (edict_t *ent); + +// +// g_chase.c +// +void UpdateChaseCam(edict_t *ent); +void ChaseNext(edict_t *ent); +void ChasePrev(edict_t *ent); +void GetChaseTarget(edict_t *ent); + + +//==================== +// ROGUE PROTOTYPES +// +// g_newweap.c +// +//extern float nuke_framenum; + +void fire_flechette (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int kick); +void fire_prox (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed); +void fire_nuke (edict_t *self, vec3_t start, vec3_t aimdir, int speed); +void fire_flame (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed); +void fire_burst (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed); +void fire_maintain (edict_t *, edict_t *, vec3_t start, vec3_t aimdir, int damage, int speed); +void fire_incendiary_grenade (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, float timer, float damage_radius); +void fire_player_melee (edict_t *self, vec3_t start, vec3_t aim, int reach, int damage, int kick, int quiet, int mod); +void fire_tesla (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed); +void fire_blaster2 (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, int effect, qboolean hyper); +void fire_heat (edict_t *self, vec3_t start, vec3_t aimdir, vec3_t offset, int damage, int kick, qboolean monster); +void fire_tracker (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, edict_t *enemy); + +// +// g_newai.c +// +qboolean blocked_checkshot (edict_t *self, float shotChance); +qboolean blocked_checkplat (edict_t *self, float dist); +qboolean blocked_checkjump (edict_t *self, float dist, float maxDown, float maxUp); +qboolean blocked_checknewenemy (edict_t *self); +qboolean monsterlost_checkhint (edict_t *self); +qboolean inback (edict_t *self, edict_t *other); +float realrange (edict_t *self, edict_t *other); +edict_t *SpawnBadArea(vec3_t mins, vec3_t maxs, float lifespan, edict_t *owner); +edict_t *CheckForBadArea(edict_t *ent); +qboolean MarkTeslaArea(edict_t *self, edict_t *tesla); +void InitHintPaths (void); +void PredictAim (edict_t *target, vec3_t start, float bolt_speed, qboolean eye_height, float offset, vec3_t aimdir, vec3_t aimpoint); +qboolean below (edict_t *self, edict_t *other); +void drawbbox (edict_t *self); +void M_MonsterDodge (edict_t *self, edict_t *attacker, float eta, trace_t *tr); +void monster_duck_down (edict_t *self); +void monster_duck_hold (edict_t *self); +void monster_duck_up (edict_t *self); +qboolean has_valid_enemy (edict_t *self); +void TargetTesla (edict_t *self, edict_t *tesla); +void hintpath_stop (edict_t *self); +edict_t * PickCoopTarget (edict_t *self); +int CountPlayers (void); +void monster_jump_start (edict_t *self); +qboolean monster_jump_finished (edict_t *self); + + +// +// g_sphere.c +// +void Defender_Launch (edict_t *self); +void Vengeance_Launch (edict_t *self); +void Hunter_Launch (edict_t *self); + +// +// g_newdm.c +// +void InitGameRules(void); +edict_t *DoRandomRespawn (edict_t *ent); +void PrecacheForRandomRespawn (void); +qboolean Tag_PickupToken (edict_t *ent, edict_t *other); +void Tag_DropToken (edict_t *ent, gitem_t *item); +void Tag_PlayerDeath(edict_t *targ, edict_t *inflictor, edict_t *attacker); +void fire_doppleganger (edict_t *ent, vec3_t start, vec3_t aimdir); + +// +// g_spawn.c +// +edict_t *CreateMonster(vec3_t origin, vec3_t angles, char *classname); +edict_t *CreateFlyMonster (vec3_t origin, vec3_t angles, vec3_t mins, vec3_t maxs, char *classname); +edict_t *CreateGroundMonster (vec3_t origin, vec3_t angles, vec3_t mins, vec3_t maxs, char *classname, int height); +qboolean FindSpawnPoint (vec3_t startpoint, vec3_t mins, vec3_t maxs, vec3_t spawnpoint, float maxMoveUp); +qboolean CheckSpawnPoint (vec3_t origin, vec3_t mins, vec3_t maxs); +qboolean CheckGroundSpawnPoint (vec3_t origin, vec3_t entMins, vec3_t entMaxs, float height, float gravity); +void DetermineBBox (char *classname, vec3_t mins, vec3_t maxs); +void SpawnGrow_Spawn (vec3_t startpos, int size); +void Widowlegs_Spawn (vec3_t startpos, vec3_t angles); + +// +// p_client.c +// +void RemoveAttackingPainDaemons (edict_t *self); + + +// ROGUE PROTOTYPES +//==================== + +//============================================================================ + +// client_t->anim_priority +#define ANIM_BASIC 0 // stand / run +#define ANIM_WAVE 1 +#define ANIM_JUMP 2 +#define ANIM_PAIN 3 +#define ANIM_ATTACK 4 +#define ANIM_DEATH 5 +#define ANIM_REVERSE 6 + + +// client data that stays across multiple level loads +typedef struct +{ + char userinfo[MAX_INFO_STRING]; + char netname[16]; + int hand; + + qboolean connected; // a loadgame will leave valid entities that + // just don't have a connection yet + + // values saved and restored from edicts when changing levels + int health; + int max_health; + int savedFlags; + + int selected_item; + int inventory[MAX_ITEMS]; + + // ammo capacities + int max_bullets; + int max_shells; + int max_rockets; + int max_grenades; + int max_cells; + int max_slugs; + + gitem_t *weapon; + gitem_t *lastweapon; + + int power_cubes; // used for tracking the cubes in coop games + int score; // for calculating total unit score in coop games + + int game_helpchanged; + int helpchanged; + + qboolean spectator; // client is a spectator + +//========= +//ROGUE + int max_tesla; + int max_prox; + int max_mines; + int max_flechettes; +#ifndef KILL_DISRUPTOR + int max_rounds; +#endif +//ROGUE +//========= +} client_persistant_t; + +// client data that stays across deathmatch respawns +typedef struct +{ + client_persistant_t coop_respawn; // what to set client->pers to on a respawn + int enterframe; // level.framenum the client entered the game + int score; // frags, etc + vec3_t cmd_angles; // angles sent over in the last command + + qboolean spectator; // client is a spectator +} client_respawn_t; + +// this structure is cleared on each PutClientInServer(), +// except for 'client->pers' +struct gclient_s +{ + // known to server + player_state_t ps; // communicated by server to clients + int ping; + + // private to game + client_persistant_t pers; + client_respawn_t resp; + pmove_state_t old_pmove; // for detecting out-of-pmove changes + + qboolean showscores; // set layout stat + qboolean showinventory; // set layout stat + qboolean showhelp; + qboolean showhelpicon; + + int ammo_index; + + int buttons; + int oldbuttons; + int latched_buttons; + + qboolean weapon_thunk; + + gitem_t *newweapon; + + // sum up damage over an entire frame, so + // shotgun blasts give a single big kick + int damage_armor; // damage absorbed by armor + int damage_parmor; // damage absorbed by power armor + int damage_blood; // damage taken out of health + int damage_knockback; // impact damage + vec3_t damage_from; // origin for vector calculation + + float killer_yaw; // when dead, look at killer + + weaponstate_t weaponstate; + vec3_t kick_angles; // weapon kicks + vec3_t kick_origin; + float v_dmg_roll, v_dmg_pitch, v_dmg_time; // damage kicks + float fall_time, fall_value; // for view drop on fall + float damage_alpha; + float bonus_alpha; + vec3_t damage_blend; + vec3_t v_angle; // aiming direction + float bobtime; // so off-ground doesn't change it + vec3_t oldviewangles; + vec3_t oldvelocity; + + float next_drown_time; + int old_waterlevel; + int breather_sound; + + int machinegun_shots; // for weapon raising + + // animation vars + int anim_end; + int anim_priority; + qboolean anim_duck; + qboolean anim_run; + + // powerup timers + float quad_framenum; + float invincible_framenum; + float breather_framenum; + float enviro_framenum; + + qboolean grenade_blew_up; + float grenade_time; + int silencer_shots; + int weapon_sound; + + float pickup_msg_time; + + float flood_locktill; // locked from talking + float flood_when[10]; // when messages were said + int flood_whenhead; // head pointer for when said + + float respawn_time; // can respawn when time > this + + edict_t *chase_target; // player we are chasing + qboolean update_chase; // need to update chase info? + +//======= +//ROGUE + float double_framenum; + float ir_framenum; +// float torch_framenum; + float nuke_framenum; + float tracker_pain_framenum; + + edict_t *owned_sphere; // this points to the player's sphere +//ROGUE +//======= +}; + + +struct edict_s +{ + entity_state_t s; + struct gclient_s *client; // NULL if not a player + // the server expects the first part + // of gclient_s to be a player_state_t + // but the rest of it is opaque + + qboolean inuse; + int linkcount; + + // FIXME: move these fields to a server private sv_entity_t + link_t area; // linked to a division node or leaf + + int num_clusters; // if -1, use headnode instead + int clusternums[MAX_ENT_CLUSTERS]; + int headnode; // unused if num_clusters != -1 + int areanum, areanum2; + + //================================ + + int svflags; + vec3_t mins, maxs; + vec3_t absmin, absmax, size; + solid_t solid; + int clipmask; + edict_t *owner; + + + // DO NOT MODIFY ANYTHING ABOVE THIS, THE SERVER + // EXPECTS THE FIELDS IN THAT ORDER! + + //================================ + int movetype; + int flags; + + char *model; + float freetime; // sv.time when the object was freed + + // + // only used locally in game, not by server + // + char *message; + char *classname; + int spawnflags; + + float timestamp; + + float angle; // set in qe3, -1 = up, -2 = down + char *target; + char *targetname; + char *killtarget; + char *team; + char *pathtarget; + char *deathtarget; + char *combattarget; + edict_t *target_ent; + + float speed, accel, decel; + vec3_t movedir; + vec3_t pos1, pos2; + + vec3_t velocity; + vec3_t avelocity; + int mass; + float air_finished; + float gravity; // per entity gravity multiplier (1.0 is normal) + // use for lowgrav artifact, flares + + edict_t *goalentity; + edict_t *movetarget; + float yaw_speed; + float ideal_yaw; + + float nextthink; + void (*prethink) (edict_t *ent); + void (*think)(edict_t *self); + void (*blocked)(edict_t *self, edict_t *other); //move to moveinfo? + void (*touch)(edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf); + void (*use)(edict_t *self, edict_t *other, edict_t *activator); + void (*pain)(edict_t *self, edict_t *other, float kick, int damage); + void (*die)(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point); + + float touch_debounce_time; // are all these legit? do we need more/less of them? + float pain_debounce_time; + float damage_debounce_time; + float fly_sound_debounce_time; //move to clientinfo + float last_move_time; + + int health; + int max_health; + int gib_health; + int deadflag; + qboolean show_hostile; + + float powerarmor_time; + + char *map; // target_changelevel + + int viewheight; // height above origin where eyesight is determined + int takedamage; + int dmg; + int radius_dmg; + float dmg_radius; + int sounds; //make this a spawntemp var? + int count; + + edict_t *chain; + edict_t *enemy; + edict_t *oldenemy; + edict_t *activator; + edict_t *groundentity; + int groundentity_linkcount; + edict_t *teamchain; + edict_t *teammaster; + + edict_t *mynoise; // can go in client only + edict_t *mynoise2; + + int noise_index; + int noise_index2; + float volume; + float attenuation; + + // timing variables + float wait; + float delay; // before firing targets + float random; + + float teleport_time; + + int watertype; + int waterlevel; + + vec3_t move_origin; + vec3_t move_angles; + + // move this to clientinfo? + int light_level; + + int style; // also used as areaportal number + + gitem_t *item; // for bonus items + + // common data blocks + moveinfo_t moveinfo; + monsterinfo_t monsterinfo; + +//========= +//ROGUE + int plat2flags; + vec3_t offset; + vec3_t gravityVector; + edict_t *bad_area; + edict_t *hint_chain; + edict_t *monster_hint_chain; + edict_t *target_hint_chain; + int hint_chain_id; + // FIXME - debug help! + float lastMoveTime; +//ROGUE +//========= +}; + +//============= +//ROGUE +#define ROGUE_GRAVITY 1 + +#define SPHERE_DEFENDER 0x0001 +#define SPHERE_HUNTER 0x0002 +#define SPHERE_VENGEANCE 0x0004 +#define SPHERE_DOPPLEGANGER 0x0100 + +#define SPHERE_TYPE 0x00FF +#define SPHERE_FLAGS 0xFF00 + +// +// deathmatch games +// +#define RDM_TAG 2 +#define RDM_DEATHBALL 3 + +typedef struct dm_game_rs +{ + void (*GameInit)(void); + void (*PostInitSetup)(void); + void (*ClientBegin) (edict_t *ent); + void (*SelectSpawnPoint) (edict_t *ent, vec3_t origin, vec3_t angles); + void (*PlayerDeath)(edict_t *targ, edict_t *inflictor, edict_t *attacker); + void (*Score)(edict_t *attacker, edict_t *victim, int scoreChange); + void (*PlayerEffects)(edict_t *ent); + void (*DogTag)(edict_t *ent, edict_t *killer, char **pic); + void (*PlayerDisconnect)(edict_t *ent); + int (*ChangeDamage)(edict_t *targ, edict_t *attacker, int damage, int mod); + int (*ChangeKnockback)(edict_t *targ, edict_t *attacker, int knockback, int mod); + int (*CheckDMRules)(void); +} dm_game_rt; + +extern dm_game_rt DMGame; + +void Tag_GameInit (void); +void Tag_PostInitSetup (void); +void Tag_PlayerDeath (edict_t *targ, edict_t *inflictor, edict_t *attacker); +void Tag_Score (edict_t *attacker, edict_t *victim, int scoreChange); +void Tag_PlayerEffects (edict_t *ent); +void Tag_DogTag (edict_t *ent, edict_t *killer, char **pic); +void Tag_PlayerDisconnect (edict_t *ent); +int Tag_ChangeDamage (edict_t *targ, edict_t *attacker, int damage, int mod); + +void DBall_GameInit (void); +void DBall_ClientBegin (edict_t *ent); +void DBall_SelectSpawnPoint (edict_t *ent, vec3_t origin, vec3_t angles); +int DBall_ChangeKnockback (edict_t *targ, edict_t *attacker, int knockback, int mod); +int DBall_ChangeDamage (edict_t *targ, edict_t *attacker, int damage, int mod); +void DBall_PostInitSetup (void); +int DBall_CheckDMRules (void); +//void Tag_PlayerDeath (edict_t *targ, edict_t *inflictor, edict_t *attacker); +//void Tag_Score (edict_t *attacker, edict_t *victim, int scoreChange); +//void Tag_PlayerEffects (edict_t *ent); +//void Tag_DogTag (edict_t *ent, edict_t *killer, char **pic); +//void Tag_PlayerDisconnect (edict_t *ent); +//int Tag_ChangeDamage (edict_t *targ, edict_t *attacker, int damage); + +//ROGUE +//============ diff --git a/original/rogue/g_main.c b/original/rogue/g_main.c new file mode 100644 index 0000000..c09e9d0 --- /dev/null +++ b/original/rogue/g_main.c @@ -0,0 +1,414 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. + +#include "g_local.h" + +game_locals_t game; +level_locals_t level; +game_import_t gi; +game_export_t globals; +spawn_temp_t st; + +int sm_meat_index; +int snd_fry; +int meansOfDeath; + +edict_t *g_edicts; + +cvar_t *deathmatch; +cvar_t *coop; +cvar_t *dmflags; +cvar_t *skill; +cvar_t *fraglimit; +cvar_t *timelimit; +cvar_t *password; +cvar_t *spectator_password; +cvar_t *maxclients; +cvar_t *maxspectators; +cvar_t *maxentities; +cvar_t *g_select_empty; +cvar_t *dedicated; + +cvar_t *filterban; + +cvar_t *sv_maxvelocity; +cvar_t *sv_gravity; + +cvar_t *sv_rollspeed; +cvar_t *sv_rollangle; +cvar_t *gun_x; +cvar_t *gun_y; +cvar_t *gun_z; + +cvar_t *run_pitch; +cvar_t *run_roll; +cvar_t *bob_up; +cvar_t *bob_pitch; +cvar_t *bob_roll; + +cvar_t *sv_cheats; + +cvar_t *flood_msgs; +cvar_t *flood_persecond; +cvar_t *flood_waitdelay; + +cvar_t *sv_maplist; + +cvar_t *sv_stopspeed; //PGM (this was a define in g_phys.c) + +//ROGUE cvars +cvar_t *g_showlogic; +cvar_t *gamerules; +cvar_t *huntercam; +cvar_t *strong_mines; +cvar_t *randomrespawn; +//ROGUE + +void SpawnEntities (char *mapname, char *entities, char *spawnpoint); +void ClientThink (edict_t *ent, usercmd_t *cmd); +qboolean ClientConnect (edict_t *ent, char *userinfo); +void ClientUserinfoChanged (edict_t *ent, char *userinfo); +void ClientDisconnect (edict_t *ent); +void ClientBegin (edict_t *ent); +void ClientCommand (edict_t *ent); +void RunEntity (edict_t *ent); +void WriteGame (char *filename, qboolean autosave); +void ReadGame (char *filename); +void WriteLevel (char *filename); +void ReadLevel (char *filename); +void InitGame (void); +void G_RunFrame (void); + + +//=================================================================== + + +void ShutdownGame (void) +{ + gi.dprintf ("==== ShutdownGame ====\n"); + + gi.FreeTags (TAG_LEVEL); + gi.FreeTags (TAG_GAME); +} + + +/* +================= +GetGameAPI + +Returns a pointer to the structure with all entry points +and global variables +================= +*/ +game_export_t *GetGameAPI (game_import_t *import) +{ + gi = *import; + + globals.apiversion = GAME_API_VERSION; + globals.Init = InitGame; + globals.Shutdown = ShutdownGame; + globals.SpawnEntities = SpawnEntities; + + globals.WriteGame = WriteGame; + globals.ReadGame = ReadGame; + globals.WriteLevel = WriteLevel; + globals.ReadLevel = ReadLevel; + + globals.ClientThink = ClientThink; + globals.ClientConnect = ClientConnect; + globals.ClientUserinfoChanged = ClientUserinfoChanged; + globals.ClientDisconnect = ClientDisconnect; + globals.ClientBegin = ClientBegin; + globals.ClientCommand = ClientCommand; + + globals.RunFrame = G_RunFrame; + + globals.ServerCommand = ServerCommand; + + globals.edict_size = sizeof(edict_t); + + return &globals; +} + +#ifndef GAME_HARD_LINKED +// this is only here so the functions in q_shared.c and q_shwin.c can link +void Sys_Error (char *error, ...) +{ + va_list argptr; + char text[1024]; + + va_start (argptr, error); + vsprintf (text, error, argptr); + va_end (argptr); + + gi.error (ERR_FATAL, "%s", text); +} + +void Com_Printf (char *msg, ...) +{ + va_list argptr; + char text[1024]; + + va_start (argptr, msg); + vsprintf (text, msg, argptr); + va_end (argptr); + + gi.dprintf ("%s", text); +} + +#endif + +//====================================================================== + + +/* +================= +ClientEndServerFrames +================= +*/ +void ClientEndServerFrames (void) +{ + int i; + edict_t *ent; + + // calc the player views now that all pushing + // and damage has been added + for (i=0 ; ivalue ; i++) + { + ent = g_edicts + 1 + i; + if (!ent->inuse || !ent->client) + continue; + ClientEndServerFrame (ent); + } + +} + +/* +================= +CreateTargetChangeLevel + +Returns the created target changelevel +================= +*/ +edict_t *CreateTargetChangeLevel(char *map) +{ + edict_t *ent; + + ent = G_Spawn (); + ent->classname = "target_changelevel"; + Com_sprintf(level.nextmap, sizeof(level.nextmap), "%s", map); + ent->map = level.nextmap; + return ent; +} + +/* +================= +EndDMLevel + +The timelimit or fraglimit has been exceeded +================= +*/ +void EndDMLevel (void) +{ + edict_t *ent; + char *s, *t, *f; + static const char *seps = " ,\n\r"; + + // stay on same level flag + if ((int)dmflags->value & DF_SAME_LEVEL) + { + BeginIntermission (CreateTargetChangeLevel (level.mapname) ); + return; + } + + // see if it's in the map list + if (*sv_maplist->string) { + s = strdup(sv_maplist->string); + f = NULL; + t = strtok(s, seps); + while (t != NULL) { + if (Q_stricmp(t, level.mapname) == 0) { + // it's in the list, go to the next one + t = strtok(NULL, seps); + if (t == NULL) { // end of list, go to first one + if (f == NULL) // there isn't a first one, same level + BeginIntermission (CreateTargetChangeLevel (level.mapname) ); + else + BeginIntermission (CreateTargetChangeLevel (f) ); + } else + BeginIntermission (CreateTargetChangeLevel (t) ); + free(s); + return; + } + if (!f) + f = t; + t = strtok(NULL, seps); + } + free(s); + } + + if (level.nextmap[0]) // go to a specific map + BeginIntermission (CreateTargetChangeLevel (level.nextmap) ); + else { // search for a changelevel + ent = G_Find (NULL, FOFS(classname), "target_changelevel"); + if (!ent) + { // the map designer didn't include a changelevel, + // so create a fake ent that goes back to the same level + BeginIntermission (CreateTargetChangeLevel (level.mapname) ); + return; + } + BeginIntermission (ent); + } +} + +/* +================= +CheckDMRules +================= +*/ +void CheckDMRules (void) +{ + int i; + gclient_t *cl; + + if (level.intermissiontime) + return; + + if (!deathmatch->value) + return; + +//======= +//ROGUE + if (gamerules && gamerules->value && DMGame.CheckDMRules) + { + if(DMGame.CheckDMRules()) + return; + } +//ROGUE +//======= + + if (timelimit->value) + { + if (level.time >= timelimit->value*60) + { + gi.bprintf (PRINT_HIGH, "Timelimit hit.\n"); + EndDMLevel (); + return; + } + } + + if (fraglimit->value) + { + for (i=0 ; ivalue ; i++) + { + cl = game.clients + i; + if (!g_edicts[i+1].inuse) + continue; + + if (cl->resp.score >= fraglimit->value) + { + gi.bprintf (PRINT_HIGH, "Fraglimit hit.\n"); + EndDMLevel (); + return; + } + } + } +} + + +/* +============= +ExitLevel +============= +*/ +void ExitLevel (void) +{ + int i; + edict_t *ent; + char command [256]; + + Com_sprintf (command, sizeof(command), "gamemap \"%s\"\n", level.changemap); + gi.AddCommandString (command); + level.changemap = NULL; + level.exitintermission = 0; + level.intermissiontime = 0; + ClientEndServerFrames (); + + // clear some things before going to next level + for (i=0 ; ivalue ; i++) + { + ent = g_edicts + 1 + i; + if (!ent->inuse) + continue; + if (ent->health > ent->client->pers.max_health) + ent->health = ent->client->pers.max_health; + } + +} + +/* +================ +G_RunFrame + +Advances the world by 0.1 seconds +================ +*/ +void G_RunFrame (void) +{ + int i; + edict_t *ent; + + level.framenum++; + level.time = level.framenum*FRAMETIME; + + // choose a client for monsters to target this frame + AI_SetSightClient (); + + // exit intermissions + + if (level.exitintermission) + { + ExitLevel (); + return; + } + + // + // treat each object in turn + // even the world gets a chance to think + // + ent = &g_edicts[0]; + for (i=0 ; iinuse) + continue; + + level.current_entity = ent; + + VectorCopy (ent->s.origin, ent->s.old_origin); + + // if the ground entity moved, make sure we are still on it + if ((ent->groundentity) && (ent->groundentity->linkcount != ent->groundentity_linkcount)) + { + ent->groundentity = NULL; + if ( !(ent->flags & (FL_SWIM|FL_FLY)) && (ent->svflags & SVF_MONSTER) ) + { + M_CheckGround (ent); + } + } + + if (i > 0 && i <= maxclients->value) + { + ClientBeginServerFrame (ent); + continue; + } + + G_RunEntity (ent); + } + + // see if it is time to end a deathmatch + CheckDMRules (); + + // build the playerstate_t structures for all players + ClientEndServerFrames (); +} + diff --git a/original/rogue/g_misc.c b/original/rogue/g_misc.c new file mode 100644 index 0000000..a80c63e --- /dev/null +++ b/original/rogue/g_misc.c @@ -0,0 +1,2001 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// g_misc.c + +#include "g_local.h" + +extern void M_WorldEffects (edict_t *ent); + +/*QUAKED func_group (0 0 0) ? +Used to group brushes together just for editor convenience. +*/ + +//===================================================== + +void Use_Areaportal (edict_t *ent, edict_t *other, edict_t *activator) +{ + ent->count ^= 1; // toggle state +// gi.dprintf ("portalstate: %i = %i\n", ent->style, ent->count); + gi.SetAreaPortalState (ent->style, ent->count); +} + +/*QUAKED func_areaportal (0 0 0) ? + +This is a non-visible object that divides the world into +areas that are seperated when this portal is not activated. +Usually enclosed in the middle of a door. +*/ +void SP_func_areaportal (edict_t *ent) +{ + ent->use = Use_Areaportal; + ent->count = 0; // always start closed; +} + +//===================================================== + + +/* +================= +Misc functions +================= +*/ +void VelocityForDamage (int damage, vec3_t v) +{ + v[0] = 100.0 * crandom(); + v[1] = 100.0 * crandom(); + v[2] = 200.0 + 100.0 * random(); + + if (damage < 50) + VectorScale (v, 0.7, v); + else + VectorScale (v, 1.2, v); +} + +void ClipGibVelocity (edict_t *ent) +{ + if (ent->velocity[0] < -300) + ent->velocity[0] = -300; + else if (ent->velocity[0] > 300) + ent->velocity[0] = 300; + if (ent->velocity[1] < -300) + ent->velocity[1] = -300; + else if (ent->velocity[1] > 300) + ent->velocity[1] = 300; + if (ent->velocity[2] < 200) + ent->velocity[2] = 200; // always some upwards + else if (ent->velocity[2] > 500) + ent->velocity[2] = 500; +} + + +/* +================= +gibs +================= +*/ +void gib_think (edict_t *self) +{ + self->s.frame++; + self->nextthink = level.time + FRAMETIME; + + if (self->s.frame == 10) + { + self->think = G_FreeEdict; + self->nextthink = level.time + 8 + random()*10; + } +} + +void gib_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + vec3_t normal_angles, right; + + if (!self->groundentity) + return; + + self->touch = NULL; + + if (plane) + { + gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/fhit3.wav"), 1, ATTN_NORM, 0); + + vectoangles (plane->normal, normal_angles); + AngleVectors (normal_angles, NULL, right, NULL); + vectoangles (right, self->s.angles); + + if (self->s.modelindex == sm_meat_index) + { + self->s.frame++; + self->think = gib_think; + self->nextthink = level.time + FRAMETIME; + } + } +} + +void gib_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + G_FreeEdict (self); +} + +void ThrowGib (edict_t *self, char *gibname, int damage, int type) +{ + edict_t *gib; + vec3_t vd; + vec3_t origin; + vec3_t size; + float vscale; + + gib = G_Spawn(); + + VectorScale (self->size, 0.5, size); + VectorAdd (self->absmin, size, origin); + gib->s.origin[0] = origin[0] + crandom() * size[0]; + gib->s.origin[1] = origin[1] + crandom() * size[1]; + gib->s.origin[2] = origin[2] + crandom() * size[2]; + + gi.setmodel (gib, gibname); + gib->solid = SOLID_NOT; + gib->s.effects |= EF_GIB; + gib->flags |= FL_NO_KNOCKBACK; + gib->takedamage = DAMAGE_YES; + gib->die = gib_die; + + if (type == GIB_ORGANIC) + { + gib->movetype = MOVETYPE_TOSS; + gib->touch = gib_touch; + vscale = 0.5; + } + else + { + gib->movetype = MOVETYPE_BOUNCE; + vscale = 1.0; + } + + VelocityForDamage (damage, vd); + VectorMA (self->velocity, vscale, vd, gib->velocity); + ClipGibVelocity (gib); + gib->avelocity[0] = random()*600; + gib->avelocity[1] = random()*600; + gib->avelocity[2] = random()*600; + + gib->think = G_FreeEdict; + gib->nextthink = level.time + 10 + random()*10; + +//PGM + gib->s.renderfx |= RF_IR_VISIBLE; +//PGM + + gi.linkentity (gib); +} + +void ThrowHead (edict_t *self, char *gibname, int damage, int type) +{ + vec3_t vd; + float vscale; + + self->s.skinnum = 0; + self->s.frame = 0; + VectorClear (self->mins); + VectorClear (self->maxs); + + self->s.modelindex2 = 0; + gi.setmodel (self, gibname); + self->solid = SOLID_NOT; + self->s.effects |= EF_GIB; + self->s.effects &= ~EF_FLIES; + self->s.sound = 0; + self->flags |= FL_NO_KNOCKBACK; + self->svflags &= ~SVF_MONSTER; + self->takedamage = DAMAGE_YES; + self->die = gib_die; + + if (type == GIB_ORGANIC) + { + self->movetype = MOVETYPE_TOSS; + self->touch = gib_touch; + vscale = 0.5; + } + else + { + self->movetype = MOVETYPE_BOUNCE; + vscale = 1.0; + } + + VelocityForDamage (damage, vd); + VectorMA (self->velocity, vscale, vd, self->velocity); + ClipGibVelocity (self); + + self->avelocity[YAW] = crandom()*600; + + self->think = G_FreeEdict; + self->nextthink = level.time + 10 + random()*10; + + gi.linkentity (self); +} + + +void ThrowClientHead (edict_t *self, int damage) +{ + vec3_t vd; + char *gibname; + + if (rand()&1) + { + gibname = "models/objects/gibs/head2/tris.md2"; + self->s.skinnum = 1; // second skin is player + } + else + { + gibname = "models/objects/gibs/skull/tris.md2"; + self->s.skinnum = 0; + } + + self->s.origin[2] += 32; + self->s.frame = 0; + gi.setmodel (self, gibname); + VectorSet (self->mins, -16, -16, 0); + VectorSet (self->maxs, 16, 16, 16); + + self->takedamage = DAMAGE_NO; + self->solid = SOLID_NOT; + self->s.effects = EF_GIB; + self->s.sound = 0; + self->flags |= FL_NO_KNOCKBACK; + + self->movetype = MOVETYPE_BOUNCE; + VelocityForDamage (damage, vd); + VectorAdd (self->velocity, vd, self->velocity); + + if (self->client) // bodies in the queue don't have a client anymore + { + self->client->anim_priority = ANIM_DEATH; + self->client->anim_end = self->s.frame; + } + else + { + self->think = NULL; + self->nextthink = 0; + } + + gi.linkentity (self); +} + + +/* +================= +debris +================= +*/ +void debris_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + G_FreeEdict (self); +} + +void ThrowDebris (edict_t *self, char *modelname, float speed, vec3_t origin) +{ + edict_t *chunk; + vec3_t v; + + chunk = G_Spawn(); + VectorCopy (origin, chunk->s.origin); + gi.setmodel (chunk, modelname); + v[0] = 100 * crandom(); + v[1] = 100 * crandom(); + v[2] = 100 + 100 * crandom(); + VectorMA (self->velocity, speed, v, chunk->velocity); + chunk->movetype = MOVETYPE_BOUNCE; + chunk->solid = SOLID_NOT; + chunk->avelocity[0] = random()*600; + chunk->avelocity[1] = random()*600; + chunk->avelocity[2] = random()*600; + chunk->think = G_FreeEdict; + chunk->nextthink = level.time + 5 + random()*5; + chunk->s.frame = 0; + chunk->flags = 0; + chunk->classname = "debris"; + chunk->takedamage = DAMAGE_YES; + chunk->die = debris_die; + gi.linkentity (chunk); +} + + +void BecomeExplosion1 (edict_t *self) +{ + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_EXPLOSION1); + gi.WritePosition (self->s.origin); + gi.multicast (self->s.origin, MULTICAST_PVS); + + G_FreeEdict (self); +} + + +void BecomeExplosion2 (edict_t *self) +{ + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_EXPLOSION2); + gi.WritePosition (self->s.origin); + gi.multicast (self->s.origin, MULTICAST_PVS); + + G_FreeEdict (self); +} + + +/*QUAKED path_corner (.5 .3 0) (-8 -8 -8) (8 8 8) TELEPORT +Target: next path corner +Pathtarget: gets used when an entity that has + this path_corner targeted touches it +*/ + +void path_corner_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + vec3_t v; + edict_t *next; + + if (other->movetarget != self) + return; + + if (other->enemy) + return; + + if (self->pathtarget) + { + char *savetarget; + + savetarget = self->target; + self->target = self->pathtarget; + G_UseTargets (self, other); + self->target = savetarget; + } + + if (self->target) + next = G_PickTarget(self->target); + else + next = NULL; + + if ((next) && (next->spawnflags & 1)) + { + VectorCopy (next->s.origin, v); + v[2] += next->mins[2]; + v[2] -= other->mins[2]; + VectorCopy (v, other->s.origin); + next = G_PickTarget(next->target); + other->s.event = EV_OTHER_TELEPORT; + } + + other->goalentity = other->movetarget = next; + + if (self->wait) + { + other->monsterinfo.pausetime = level.time + self->wait; + other->monsterinfo.stand (other); + return; + } + + if (!other->movetarget) + { + other->monsterinfo.pausetime = level.time + 100000000; + other->monsterinfo.stand (other); + } + else + { + VectorSubtract (other->goalentity->s.origin, other->s.origin, v); + other->ideal_yaw = vectoyaw (v); + } +} + +void SP_path_corner (edict_t *self) +{ + if (!self->targetname) + { + gi.dprintf ("path_corner with no targetname at %s\n", vtos(self->s.origin)); + G_FreeEdict (self); + return; + } + + self->solid = SOLID_TRIGGER; + self->touch = path_corner_touch; + VectorSet (self->mins, -8, -8, -8); + VectorSet (self->maxs, 8, 8, 8); + self->svflags |= SVF_NOCLIENT; + gi.linkentity (self); +} + + +/*QUAKED point_combat (0.5 0.3 0) (-8 -8 -8) (8 8 8) Hold +Makes this the target of a monster and it will head here +when first activated before going after the activator. If +hold is selected, it will stay here. +*/ +void point_combat_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + edict_t *activator; + + if (other->movetarget != self) + return; + + if (self->target) + { + other->target = self->target; + other->goalentity = other->movetarget = G_PickTarget(other->target); + if (!other->goalentity) + { + gi.dprintf("%s at %s target %s does not exist\n", self->classname, vtos(self->s.origin), self->target); + other->movetarget = self; + } + self->target = NULL; + } + else if ((self->spawnflags & 1) && !(other->flags & (FL_SWIM|FL_FLY))) + { + other->monsterinfo.pausetime = level.time + 100000000; + other->monsterinfo.aiflags |= AI_STAND_GROUND; + other->monsterinfo.stand (other); + } + + if (other->movetarget == self) + { + other->target = NULL; + other->movetarget = NULL; + other->goalentity = other->enemy; + other->monsterinfo.aiflags &= ~AI_COMBAT_POINT; + } + + if (self->pathtarget) + { + char *savetarget; + + savetarget = self->target; + self->target = self->pathtarget; + if (other->enemy && other->enemy->client) + activator = other->enemy; + else if (other->oldenemy && other->oldenemy->client) + activator = other->oldenemy; + else if (other->activator && other->activator->client) + activator = other->activator; + else + activator = other; + G_UseTargets (self, activator); + self->target = savetarget; + } +} + +void SP_point_combat (edict_t *self) +{ + if (deathmatch->value) + { + G_FreeEdict (self); + return; + } + self->solid = SOLID_TRIGGER; + self->touch = point_combat_touch; + VectorSet (self->mins, -8, -8, -16); + VectorSet (self->maxs, 8, 8, 16); + self->svflags = SVF_NOCLIENT; + gi.linkentity (self); +}; + + +/*QUAKED viewthing (0 .5 .8) (-8 -8 -8) (8 8 8) +Just for the debugging level. Don't use +*/ +void TH_viewthing(edict_t *ent) +{ + ent->s.frame = (ent->s.frame + 1) % 7; + ent->nextthink = level.time + FRAMETIME; +} + +void SP_viewthing(edict_t *ent) +{ + gi.dprintf ("viewthing spawned\n"); + + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_BBOX; + ent->s.renderfx = RF_FRAMELERP; + VectorSet (ent->mins, -16, -16, -24); + VectorSet (ent->maxs, 16, 16, 32); + ent->s.modelindex = gi.modelindex ("models/objects/banner/tris.md2"); + gi.linkentity (ent); + ent->nextthink = level.time + 0.5; + ent->think = TH_viewthing; + return; +} + + +/*QUAKED info_null (0 0.5 0) (-4 -4 -4) (4 4 4) +Used as a positional target for spotlights, etc. +*/ +void SP_info_null (edict_t *self) +{ + G_FreeEdict (self); +}; + + +/*QUAKED info_notnull (0 0.5 0) (-4 -4 -4) (4 4 4) +Used as a positional target for lightning. +*/ +void SP_info_notnull (edict_t *self) +{ + VectorCopy (self->s.origin, self->absmin); + VectorCopy (self->s.origin, self->absmax); +}; + + +/*QUAKED light (0 1 0) (-8 -8 -8) (8 8 8) START_OFF +Non-displayed light. +Default light value is 300. +Default style is 0. +If targeted, will toggle between on and off. +Default _cone value is 10 (used to set size of light for spotlights) +*/ + +#define START_OFF 1 + +static void light_use (edict_t *self, edict_t *other, edict_t *activator) +{ + if (self->spawnflags & START_OFF) + { + gi.configstring (CS_LIGHTS+self->style, "m"); + self->spawnflags &= ~START_OFF; + } + else + { + gi.configstring (CS_LIGHTS+self->style, "a"); + self->spawnflags |= START_OFF; + } +} + +void SP_light (edict_t *self) +{ + // no targeted lights in deathmatch, because they cause global messages + if (!self->targetname || deathmatch->value) + { + G_FreeEdict (self); + return; + } + + if (self->style >= 32) + { + self->use = light_use; + if (self->spawnflags & START_OFF) + gi.configstring (CS_LIGHTS+self->style, "a"); + else + gi.configstring (CS_LIGHTS+self->style, "m"); + } +} + + +/*QUAKED func_wall (0 .5 .8) ? TRIGGER_SPAWN TOGGLE START_ON ANIMATED ANIMATED_FAST +This is just a solid wall if not inhibited + +TRIGGER_SPAWN the wall will not be present until triggered + it will then blink in to existance; it will + kill anything that was in it's way + +TOGGLE only valid for TRIGGER_SPAWN walls + this allows the wall to be turned on and off + +START_ON only valid for TRIGGER_SPAWN walls + the wall will initially be present +*/ + +void func_wall_use (edict_t *self, edict_t *other, edict_t *activator) +{ + if (self->solid == SOLID_NOT) + { + self->solid = SOLID_BSP; + self->svflags &= ~SVF_NOCLIENT; + KillBox (self); + } + else + { + self->solid = SOLID_NOT; + self->svflags |= SVF_NOCLIENT; + } + gi.linkentity (self); + + if (!(self->spawnflags & 2)) + self->use = NULL; +} + +void SP_func_wall (edict_t *self) +{ + self->movetype = MOVETYPE_PUSH; + gi.setmodel (self, self->model); + + if (self->spawnflags & 8) + self->s.effects |= EF_ANIM_ALL; + if (self->spawnflags & 16) + self->s.effects |= EF_ANIM_ALLFAST; + + // just a wall + if ((self->spawnflags & 7) == 0) + { + self->solid = SOLID_BSP; + gi.linkentity (self); + return; + } + + // it must be TRIGGER_SPAWN + if (!(self->spawnflags & 1)) + { +// gi.dprintf("func_wall missing TRIGGER_SPAWN\n"); + self->spawnflags |= 1; + } + + // yell if the spawnflags are odd + if (self->spawnflags & 4) + { + if (!(self->spawnflags & 2)) + { + gi.dprintf("func_wall START_ON without TOGGLE\n"); + self->spawnflags |= 2; + } + } + + self->use = func_wall_use; + if (self->spawnflags & 4) + { + self->solid = SOLID_BSP; + } + else + { + self->solid = SOLID_NOT; + self->svflags |= SVF_NOCLIENT; + } + gi.linkentity (self); +} + + +/*QUAKED func_object (0 .5 .8) ? TRIGGER_SPAWN ANIMATED ANIMATED_FAST +This is solid bmodel that will fall if it's support it removed. +*/ + +void func_object_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + // only squash thing we fall on top of + if (!plane) + return; + if (plane->normal[2] < 1.0) + return; + if (other->takedamage == DAMAGE_NO) + return; + T_Damage (other, self, self, vec3_origin, self->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH); +} + +void func_object_release (edict_t *self) +{ + self->movetype = MOVETYPE_TOSS; + self->touch = func_object_touch; +} + +void func_object_use (edict_t *self, edict_t *other, edict_t *activator) +{ + self->solid = SOLID_BSP; + self->svflags &= ~SVF_NOCLIENT; + self->use = NULL; + KillBox (self); + func_object_release (self); +} + +void SP_func_object (edict_t *self) +{ + gi.setmodel (self, self->model); + + self->mins[0] += 1; + self->mins[1] += 1; + self->mins[2] += 1; + self->maxs[0] -= 1; + self->maxs[1] -= 1; + self->maxs[2] -= 1; + + if (!self->dmg) + self->dmg = 100; + + if (self->spawnflags == 0) + { + self->solid = SOLID_BSP; + self->movetype = MOVETYPE_PUSH; + self->think = func_object_release; + self->nextthink = level.time + 2 * FRAMETIME; + } + else + { + self->solid = SOLID_NOT; + self->movetype = MOVETYPE_PUSH; + self->use = func_object_use; + self->svflags |= SVF_NOCLIENT; + } + + if (self->spawnflags & 2) + self->s.effects |= EF_ANIM_ALL; + if (self->spawnflags & 4) + self->s.effects |= EF_ANIM_ALLFAST; + + self->clipmask = MASK_MONSTERSOLID; + + gi.linkentity (self); +} + + +/*QUAKED func_explosive (0 .5 .8) ? Trigger_Spawn ANIMATED ANIMATED_FAST INACTIVE +Any brush that you want to explode or break apart. If you want an +ex0plosion, set dmg and it will do a radius explosion of that amount +at the center of the bursh. + +If targeted it will not be shootable. + +INACTIVE - specifies that the entity is not explodable until triggered. If you use this you must +target the entity you want to trigger it. This is the only entity approved to activate it. + +health defaults to 100. + +mass defaults to 75. This determines how much debris is emitted when +it explodes. You get one large chunk per 100 of mass (up to 8) and +one small chunk per 25 of mass (up to 16). So 800 gives the most. +*/ +void func_explosive_explode (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + vec3_t origin; + vec3_t chunkorigin; + vec3_t size; + int count; + int mass; + edict_t *master; + qboolean done = false; + + // bmodel origins are (0 0 0), we need to adjust that here + VectorScale (self->size, 0.5, size); + VectorAdd (self->absmin, size, origin); + VectorCopy (origin, self->s.origin); + + self->takedamage = DAMAGE_NO; + + if (self->dmg) + T_RadiusDamage (self, attacker, self->dmg, NULL, self->dmg+40, MOD_EXPLOSIVE); + + VectorSubtract (self->s.origin, inflictor->s.origin, self->velocity); + VectorNormalize (self->velocity); + VectorScale (self->velocity, 150, self->velocity); + + // start chunks towards the center + VectorScale (size, 0.5, size); + + mass = self->mass; + if (!mass) + mass = 75; + + // big chunks + if (mass >= 100) + { + count = mass / 100; + if (count > 8) + count = 8; + while(count--) + { + chunkorigin[0] = origin[0] + crandom() * size[0]; + chunkorigin[1] = origin[1] + crandom() * size[1]; + chunkorigin[2] = origin[2] + crandom() * size[2]; + ThrowDebris (self, "models/objects/debris1/tris.md2", 1, chunkorigin); + } + } + + // small chunks + count = mass / 25; + if (count > 16) + count = 16; + while(count--) + { + chunkorigin[0] = origin[0] + crandom() * size[0]; + chunkorigin[1] = origin[1] + crandom() * size[1]; + chunkorigin[2] = origin[2] + crandom() * size[2]; + ThrowDebris (self, "models/objects/debris2/tris.md2", 2, chunkorigin); + } + + // PMM - if we're part of a train, clean ourselves out of it + if (self->flags & FL_TEAMSLAVE) + { +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("Removing func_explosive from train!\n"); + + if (self->teammaster) + { + master = self->teammaster; + if(master && master->inuse) // because mappers (other than jim (usually)) are stupid.... + { + while (!done) + { + if (master->teamchain == self) + { + master->teamchain = self->teamchain; + done = true; + } + master = master->teamchain; + if (!master) + { +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("Couldn't find myself in master's chain, ignoring!\n"); + } + } + } + } + else + { +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("No master to free myself from, ignoring!\n"); + } + } + + G_UseTargets (self, attacker); + + if (self->dmg) + BecomeExplosion1 (self); + else + G_FreeEdict (self); +} + +void func_explosive_use(edict_t *self, edict_t *other, edict_t *activator) +{ + func_explosive_explode (self, self, other, self->health, vec3_origin); +} + +//PGM +void func_explosive_activate(edict_t *self, edict_t *other, edict_t *activator) +{ + int approved; + + approved = 0; + // PMM - looked like target and targetname were flipped here + if (other != NULL && other->target) + { + if(!strcmp(other->target, self->targetname)) + approved = 1; + } + if (!approved && activator!=NULL && activator->target) + { + if(!strcmp(activator->target, self->targetname)) + approved = 1; + } + + if (!approved) + { +// gi.dprintf("func_explosive_activate: incorrect activator\n"); + return; + } + + // PMM - according to mappers, they don't need separate cases for blowupable and triggerable +// if (self->target) +// { + self->use = func_explosive_use; +// } +// else +// { + if (!self->health) + self->health = 100; + self->die = func_explosive_explode; + self->takedamage = DAMAGE_YES; +// } +} +//PGM + +void func_explosive_spawn (edict_t *self, edict_t *other, edict_t *activator) +{ + self->solid = SOLID_BSP; + self->svflags &= ~SVF_NOCLIENT; + self->use = NULL; + KillBox (self); + gi.linkentity (self); +} + +void SP_func_explosive (edict_t *self) +{ + if (deathmatch->value) + { // auto-remove for deathmatch + G_FreeEdict (self); + return; + } + + self->movetype = MOVETYPE_PUSH; + + gi.modelindex ("models/objects/debris1/tris.md2"); + gi.modelindex ("models/objects/debris2/tris.md2"); + + gi.setmodel (self, self->model); + + if (self->spawnflags & 1) + { + self->svflags |= SVF_NOCLIENT; + self->solid = SOLID_NOT; + self->use = func_explosive_spawn; + } +//PGM + else if(self->spawnflags & 8) + { + self->solid = SOLID_BSP; + if(self->targetname) + self->use = func_explosive_activate; + } +//PGM + else + { + self->solid = SOLID_BSP; + if (self->targetname) + self->use = func_explosive_use; + } + + if (self->spawnflags & 2) + self->s.effects |= EF_ANIM_ALL; + if (self->spawnflags & 4) + self->s.effects |= EF_ANIM_ALLFAST; + +//PGM + if ((self->use != func_explosive_use) && (self->use != func_explosive_activate)) +//PGM + { + if (!self->health) + self->health = 100; + self->die = func_explosive_explode; + self->takedamage = DAMAGE_YES; + } + + gi.linkentity (self); +} + + +/*QUAKED misc_explobox (0 .5 .8) (-16 -16 0) (16 16 40) +Large exploding box. You can override its mass (100), +health (80), and dmg (150). +*/ + +void barrel_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) + +{ + float ratio; + vec3_t v; + + if ((!other->groundentity) || (other->groundentity == self)) + return; + + ratio = (float)other->mass / (float)self->mass; + VectorSubtract (self->s.origin, other->s.origin, v); + M_walkmove (self, vectoyaw(v), 20 * ratio * FRAMETIME); +} + +void barrel_explode (edict_t *self) +{ + vec3_t org; + float spd; + vec3_t save; + + T_RadiusDamage (self, self->activator, self->dmg, NULL, self->dmg+40, MOD_BARREL); + + VectorCopy (self->s.origin, save); + VectorMA (self->absmin, 0.5, self->size, self->s.origin); + + // a few big chunks + spd = 1.5 * (float)self->dmg / 200.0; + org[0] = self->s.origin[0] + crandom() * self->size[0]; + org[1] = self->s.origin[1] + crandom() * self->size[1]; + org[2] = self->s.origin[2] + crandom() * self->size[2]; + ThrowDebris (self, "models/objects/debris1/tris.md2", spd, org); + org[0] = self->s.origin[0] + crandom() * self->size[0]; + org[1] = self->s.origin[1] + crandom() * self->size[1]; + org[2] = self->s.origin[2] + crandom() * self->size[2]; + ThrowDebris (self, "models/objects/debris1/tris.md2", spd, org); + + // bottom corners + spd = 1.75 * (float)self->dmg / 200.0; + VectorCopy (self->absmin, org); + ThrowDebris (self, "models/objects/debris3/tris.md2", spd, org); + VectorCopy (self->absmin, org); + org[0] += self->size[0]; + ThrowDebris (self, "models/objects/debris3/tris.md2", spd, org); + VectorCopy (self->absmin, org); + org[1] += self->size[1]; + ThrowDebris (self, "models/objects/debris3/tris.md2", spd, org); + VectorCopy (self->absmin, org); + org[0] += self->size[0]; + org[1] += self->size[1]; + ThrowDebris (self, "models/objects/debris3/tris.md2", spd, org); + + // a bunch of little chunks + spd = 2 * self->dmg / 200; + org[0] = self->s.origin[0] + crandom() * self->size[0]; + org[1] = self->s.origin[1] + crandom() * self->size[1]; + org[2] = self->s.origin[2] + crandom() * self->size[2]; + ThrowDebris (self, "models/objects/debris2/tris.md2", spd, org); + org[0] = self->s.origin[0] + crandom() * self->size[0]; + org[1] = self->s.origin[1] + crandom() * self->size[1]; + org[2] = self->s.origin[2] + crandom() * self->size[2]; + ThrowDebris (self, "models/objects/debris2/tris.md2", spd, org); + org[0] = self->s.origin[0] + crandom() * self->size[0]; + org[1] = self->s.origin[1] + crandom() * self->size[1]; + org[2] = self->s.origin[2] + crandom() * self->size[2]; + ThrowDebris (self, "models/objects/debris2/tris.md2", spd, org); + org[0] = self->s.origin[0] + crandom() * self->size[0]; + org[1] = self->s.origin[1] + crandom() * self->size[1]; + org[2] = self->s.origin[2] + crandom() * self->size[2]; + ThrowDebris (self, "models/objects/debris2/tris.md2", spd, org); + org[0] = self->s.origin[0] + crandom() * self->size[0]; + org[1] = self->s.origin[1] + crandom() * self->size[1]; + org[2] = self->s.origin[2] + crandom() * self->size[2]; + ThrowDebris (self, "models/objects/debris2/tris.md2", spd, org); + org[0] = self->s.origin[0] + crandom() * self->size[0]; + org[1] = self->s.origin[1] + crandom() * self->size[1]; + org[2] = self->s.origin[2] + crandom() * self->size[2]; + ThrowDebris (self, "models/objects/debris2/tris.md2", spd, org); + org[0] = self->s.origin[0] + crandom() * self->size[0]; + org[1] = self->s.origin[1] + crandom() * self->size[1]; + org[2] = self->s.origin[2] + crandom() * self->size[2]; + ThrowDebris (self, "models/objects/debris2/tris.md2", spd, org); + org[0] = self->s.origin[0] + crandom() * self->size[0]; + org[1] = self->s.origin[1] + crandom() * self->size[1]; + org[2] = self->s.origin[2] + crandom() * self->size[2]; + ThrowDebris (self, "models/objects/debris2/tris.md2", spd, org); + + VectorCopy (save, self->s.origin); + if (self->groundentity) + BecomeExplosion2 (self); + else + BecomeExplosion1 (self); +} + +void barrel_delay (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + self->takedamage = DAMAGE_NO; + self->nextthink = level.time + 2 * FRAMETIME; + self->think = barrel_explode; + self->activator = attacker; +} + +//========= +//PGM - change so barrels will think and hence, blow up +void barrel_think (edict_t *self) +{ + // the think needs to be first since later stuff may override. + self->think = barrel_think; + self->nextthink = level.time + FRAMETIME; + + M_CatagorizePosition (self); + self->flags |= FL_IMMUNE_SLIME; + self->air_finished = level.time + 100; + M_WorldEffects (self); +} + +void barrel_start (edict_t *self) +{ + M_droptofloor(self); + self->think = barrel_think; + self->nextthink = level.time + FRAMETIME; +} +//PGM +//========= + +void SP_misc_explobox (edict_t *self) +{ + if (deathmatch->value) + { // auto-remove for deathmatch + G_FreeEdict (self); + return; + } + + gi.modelindex ("models/objects/debris1/tris.md2"); + gi.modelindex ("models/objects/debris2/tris.md2"); + gi.modelindex ("models/objects/debris3/tris.md2"); + + self->solid = SOLID_BBOX; + self->movetype = MOVETYPE_STEP; + + self->model = "models/objects/barrels/tris.md2"; + self->s.modelindex = gi.modelindex (self->model); + VectorSet (self->mins, -16, -16, 0); + VectorSet (self->maxs, 16, 16, 40); + + if (!self->mass) + self->mass = 400; + if (!self->health) + self->health = 10; + if (!self->dmg) + self->dmg = 150; + + self->die = barrel_delay; + self->takedamage = DAMAGE_YES; + self->monsterinfo.aiflags = AI_NOSTEP; + + self->touch = barrel_touch; + +//PGM - change so barrels will think and hence, blow up + self->think = barrel_start; + self->nextthink = level.time + 2 * FRAMETIME; +//PGM + + gi.linkentity (self); +} + + +// +// miscellaneous specialty items +// + +/*QUAKED misc_blackhole (1 .5 0) (-8 -8 -8) (8 8 8) +*/ + +void misc_blackhole_use (edict_t *ent, edict_t *other, edict_t *activator) +{ + /* + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_BOSSTPORT); + gi.WritePosition (ent->s.origin); + gi.multicast (ent->s.origin, MULTICAST_PVS); + */ + G_FreeEdict (ent); +} + +void misc_blackhole_think (edict_t *self) +{ + if (++self->s.frame < 19) + self->nextthink = level.time + FRAMETIME; + else + { + self->s.frame = 0; + self->nextthink = level.time + FRAMETIME; + } +} + +void SP_misc_blackhole (edict_t *ent) +{ + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_NOT; + VectorSet (ent->mins, -64, -64, 0); + VectorSet (ent->maxs, 64, 64, 8); + ent->s.modelindex = gi.modelindex ("models/objects/black/tris.md2"); + ent->s.renderfx = RF_TRANSLUCENT; + ent->use = misc_blackhole_use; + ent->think = misc_blackhole_think; + ent->nextthink = level.time + 2 * FRAMETIME; + gi.linkentity (ent); +} + +/*QUAKED misc_eastertank (1 .5 0) (-32 -32 -16) (32 32 32) +*/ + +void misc_eastertank_think (edict_t *self) +{ + if (++self->s.frame < 293) + self->nextthink = level.time + FRAMETIME; + else + { + self->s.frame = 254; + self->nextthink = level.time + FRAMETIME; + } +} + +void SP_misc_eastertank (edict_t *ent) +{ + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_BBOX; + VectorSet (ent->mins, -32, -32, -16); + VectorSet (ent->maxs, 32, 32, 32); + ent->s.modelindex = gi.modelindex ("models/monsters/tank/tris.md2"); + ent->s.frame = 254; + ent->think = misc_eastertank_think; + ent->nextthink = level.time + 2 * FRAMETIME; + gi.linkentity (ent); +} + +/*QUAKED misc_easterchick (1 .5 0) (-32 -32 0) (32 32 32) +*/ + + +void misc_easterchick_think (edict_t *self) +{ + if (++self->s.frame < 247) + self->nextthink = level.time + FRAMETIME; + else + { + self->s.frame = 208; + self->nextthink = level.time + FRAMETIME; + } +} + +void SP_misc_easterchick (edict_t *ent) +{ + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_BBOX; + VectorSet (ent->mins, -32, -32, 0); + VectorSet (ent->maxs, 32, 32, 32); + ent->s.modelindex = gi.modelindex ("models/monsters/bitch/tris.md2"); + ent->s.frame = 208; + ent->think = misc_easterchick_think; + ent->nextthink = level.time + 2 * FRAMETIME; + gi.linkentity (ent); +} + +/*QUAKED misc_easterchick2 (1 .5 0) (-32 -32 0) (32 32 32) +*/ + + +void misc_easterchick2_think (edict_t *self) +{ + if (++self->s.frame < 287) + self->nextthink = level.time + FRAMETIME; + else + { + self->s.frame = 248; + self->nextthink = level.time + FRAMETIME; + } +} + +void SP_misc_easterchick2 (edict_t *ent) +{ + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_BBOX; + VectorSet (ent->mins, -32, -32, 0); + VectorSet (ent->maxs, 32, 32, 32); + ent->s.modelindex = gi.modelindex ("models/monsters/bitch/tris.md2"); + ent->s.frame = 248; + ent->think = misc_easterchick2_think; + ent->nextthink = level.time + 2 * FRAMETIME; + gi.linkentity (ent); +} + + +/*QUAKED monster_commander_body (1 .5 0) (-32 -32 0) (32 32 48) +Not really a monster, this is the Tank Commander's decapitated body. +There should be a item_commander_head that has this as it's target. +*/ + +void commander_body_think (edict_t *self) +{ + if (++self->s.frame < 24) + self->nextthink = level.time + FRAMETIME; + else + self->nextthink = 0; + + if (self->s.frame == 22) + gi.sound (self, CHAN_BODY, gi.soundindex ("tank/thud.wav"), 1, ATTN_NORM, 0); +} + +void commander_body_use (edict_t *self, edict_t *other, edict_t *activator) +{ + self->think = commander_body_think; + self->nextthink = level.time + FRAMETIME; + gi.sound (self, CHAN_BODY, gi.soundindex ("tank/pain.wav"), 1, ATTN_NORM, 0); +} + +void commander_body_drop (edict_t *self) +{ + self->movetype = MOVETYPE_TOSS; + self->s.origin[2] += 2; +} + +void SP_monster_commander_body (edict_t *self) +{ + self->movetype = MOVETYPE_NONE; + self->solid = SOLID_BBOX; + self->model = "models/monsters/commandr/tris.md2"; + self->s.modelindex = gi.modelindex (self->model); + VectorSet (self->mins, -32, -32, 0); + VectorSet (self->maxs, 32, 32, 48); + self->use = commander_body_use; + self->takedamage = DAMAGE_YES; + self->flags = FL_GODMODE; + self->s.renderfx |= RF_FRAMELERP; + gi.linkentity (self); + + gi.soundindex ("tank/thud.wav"); + gi.soundindex ("tank/pain.wav"); + + self->think = commander_body_drop; + self->nextthink = level.time + 5 * FRAMETIME; +} + + +/*QUAKED misc_banner (1 .5 0) (-4 -4 -4) (4 4 4) +The origin is the bottom of the banner. +The banner is 128 tall. +*/ +void misc_banner_think (edict_t *ent) +{ + ent->s.frame = (ent->s.frame + 1) % 16; + ent->nextthink = level.time + FRAMETIME; +} + +void SP_misc_banner (edict_t *ent) +{ + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_NOT; + ent->s.modelindex = gi.modelindex ("models/objects/banner/tris.md2"); + ent->s.frame = rand() % 16; + gi.linkentity (ent); + + ent->think = misc_banner_think; + ent->nextthink = level.time + FRAMETIME; +} + +/*QUAKED misc_deadsoldier (1 .5 0) (-16 -16 0) (16 16 16) ON_BACK ON_STOMACH BACK_DECAP FETAL_POS SIT_DECAP IMPALED +This is the dead player model. Comes in 6 exciting different poses! +*/ +void misc_deadsoldier_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + int n; + +// if (self->health > -80) + if (self->health > -30) + return; + + gi.sound (self, CHAN_BODY, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0); + for (n= 0; n < 4; n++) + ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); + ThrowHead (self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC); +} + +void SP_misc_deadsoldier (edict_t *ent) +{ + if (deathmatch->value) + { // auto-remove for deathmatch + G_FreeEdict (ent); + return; + } + + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_BBOX; + ent->s.modelindex=gi.modelindex ("models/deadbods/dude/tris.md2"); + + // Defaults to frame 0 + if (ent->spawnflags & 2) + ent->s.frame = 1; + else if (ent->spawnflags & 4) + ent->s.frame = 2; + else if (ent->spawnflags & 8) + ent->s.frame = 3; + else if (ent->spawnflags & 16) + ent->s.frame = 4; + else if (ent->spawnflags & 32) + ent->s.frame = 5; + else + ent->s.frame = 0; + + VectorSet (ent->mins, -16, -16, 0); + VectorSet (ent->maxs, 16, 16, 16); + ent->deadflag = DEAD_DEAD; + ent->takedamage = DAMAGE_YES; + ent->svflags |= SVF_MONSTER|SVF_DEADMONSTER; + ent->die = misc_deadsoldier_die; + ent->monsterinfo.aiflags |= AI_GOOD_GUY; + + gi.linkentity (ent); +} + +/*QUAKED misc_viper (1 .5 0) (-16 -16 0) (16 16 32) +This is the Viper for the flyby bombing. +It is trigger_spawned, so you must have something use it for it to show up. +There must be a path for it to follow once it is activated. + +"speed" How fast the Viper should fly +*/ + +extern void train_use (edict_t *self, edict_t *other, edict_t *activator); +extern void func_train_find (edict_t *self); + +void misc_viper_use (edict_t *self, edict_t *other, edict_t *activator) +{ + self->svflags &= ~SVF_NOCLIENT; + self->use = train_use; + train_use (self, other, activator); +} + +void SP_misc_viper (edict_t *ent) +{ + if (!ent->target) + { + gi.dprintf ("misc_viper without a target at %s\n", vtos(ent->absmin)); + G_FreeEdict (ent); + return; + } + + if (!ent->speed) + ent->speed = 300; + + ent->movetype = MOVETYPE_PUSH; + ent->solid = SOLID_NOT; + ent->s.modelindex = gi.modelindex ("models/ships/viper/tris.md2"); + VectorSet (ent->mins, -16, -16, 0); + VectorSet (ent->maxs, 16, 16, 32); + + ent->think = func_train_find; + ent->nextthink = level.time + FRAMETIME; + ent->use = misc_viper_use; + ent->svflags |= SVF_NOCLIENT; + ent->moveinfo.accel = ent->moveinfo.decel = ent->moveinfo.speed = ent->speed; + + gi.linkentity (ent); +} + + +/*QUAKED misc_bigviper (1 .5 0) (-176 -120 -24) (176 120 72) +This is a large stationary viper as seen in Paul's intro +*/ +void SP_misc_bigviper (edict_t *ent) +{ + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_BBOX; + VectorSet (ent->mins, -176, -120, -24); + VectorSet (ent->maxs, 176, 120, 72); + ent->s.modelindex = gi.modelindex ("models/ships/bigviper/tris.md2"); + gi.linkentity (ent); +} + + +/*QUAKED misc_viper_bomb (1 0 0) (-8 -8 -8) (8 8 8) +"dmg" how much boom should the bomb make? +*/ +void misc_viper_bomb_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + G_UseTargets (self, self->activator); + + self->s.origin[2] = self->absmin[2] + 1; + T_RadiusDamage (self, self, self->dmg, NULL, self->dmg+40, MOD_BOMB); + BecomeExplosion2 (self); +} + +void misc_viper_bomb_prethink (edict_t *self) +{ + vec3_t v; + float diff; + + self->groundentity = NULL; + + diff = self->timestamp - level.time; + if (diff < -1.0) + diff = -1.0; + + VectorScale (self->moveinfo.dir, 1.0 + diff, v); + v[2] = diff; + + diff = self->s.angles[2]; + vectoangles (v, self->s.angles); + self->s.angles[2] = diff + 10; +} + +void misc_viper_bomb_use (edict_t *self, edict_t *other, edict_t *activator) +{ + edict_t *viper; + + self->solid = SOLID_BBOX; + self->svflags &= ~SVF_NOCLIENT; + self->s.effects |= EF_ROCKET; + self->use = NULL; + self->movetype = MOVETYPE_TOSS; + self->prethink = misc_viper_bomb_prethink; + self->touch = misc_viper_bomb_touch; + self->activator = activator; + + viper = G_Find (NULL, FOFS(classname), "misc_viper"); + VectorScale (viper->moveinfo.dir, viper->moveinfo.speed, self->velocity); + + self->timestamp = level.time; + VectorCopy (viper->moveinfo.dir, self->moveinfo.dir); +} + +void SP_misc_viper_bomb (edict_t *self) +{ + self->movetype = MOVETYPE_NONE; + self->solid = SOLID_NOT; + VectorSet (self->mins, -8, -8, -8); + VectorSet (self->maxs, 8, 8, 8); + + self->s.modelindex = gi.modelindex ("models/objects/bomb/tris.md2"); + + if (!self->dmg) + self->dmg = 1000; + + self->use = misc_viper_bomb_use; + self->svflags |= SVF_NOCLIENT; + + gi.linkentity (self); +} + + +/*QUAKED misc_strogg_ship (1 .5 0) (-16 -16 0) (16 16 32) +This is a Storgg ship for the flybys. +It is trigger_spawned, so you must have something use it for it to show up. +There must be a path for it to follow once it is activated. + +"speed" How fast it should fly +*/ + +extern void train_use (edict_t *self, edict_t *other, edict_t *activator); +extern void func_train_find (edict_t *self); + +void misc_strogg_ship_use (edict_t *self, edict_t *other, edict_t *activator) +{ + self->svflags &= ~SVF_NOCLIENT; + self->use = train_use; + train_use (self, other, activator); +} + +void SP_misc_strogg_ship (edict_t *ent) +{ + if (!ent->target) + { + gi.dprintf ("%s without a target at %s\n", ent->classname, vtos(ent->absmin)); + G_FreeEdict (ent); + return; + } + + if (!ent->speed) + ent->speed = 300; + + ent->movetype = MOVETYPE_PUSH; + ent->solid = SOLID_NOT; + ent->s.modelindex = gi.modelindex ("models/ships/strogg1/tris.md2"); + VectorSet (ent->mins, -16, -16, 0); + VectorSet (ent->maxs, 16, 16, 32); + + ent->think = func_train_find; + ent->nextthink = level.time + FRAMETIME; + ent->use = misc_strogg_ship_use; + ent->svflags |= SVF_NOCLIENT; + ent->moveinfo.accel = ent->moveinfo.decel = ent->moveinfo.speed = ent->speed; + + gi.linkentity (ent); +} + + +/*QUAKED misc_satellite_dish (1 .5 0) (-64 -64 0) (64 64 128) +*/ +void misc_satellite_dish_think (edict_t *self) +{ + self->s.frame++; + if (self->s.frame < 38) + self->nextthink = level.time + FRAMETIME; +} + +void misc_satellite_dish_use (edict_t *self, edict_t *other, edict_t *activator) +{ + self->s.frame = 0; + self->think = misc_satellite_dish_think; + self->nextthink = level.time + FRAMETIME; +} + +void SP_misc_satellite_dish (edict_t *ent) +{ + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_BBOX; + VectorSet (ent->mins, -64, -64, 0); + VectorSet (ent->maxs, 64, 64, 128); + ent->s.modelindex = gi.modelindex ("models/objects/satellite/tris.md2"); + ent->use = misc_satellite_dish_use; + gi.linkentity (ent); +} + + +/*QUAKED light_mine1 (0 1 0) (-2 -2 -12) (2 2 12) +*/ +void SP_light_mine1 (edict_t *ent) +{ + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_BBOX; + ent->s.modelindex = gi.modelindex ("models/objects/minelite/light1/tris.md2"); + gi.linkentity (ent); +} + + +/*QUAKED light_mine2 (0 1 0) (-2 -2 -12) (2 2 12) +*/ +void SP_light_mine2 (edict_t *ent) +{ + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_BBOX; + ent->s.modelindex = gi.modelindex ("models/objects/minelite/light2/tris.md2"); + gi.linkentity (ent); +} + + +/*QUAKED misc_gib_arm (1 0 0) (-8 -8 -8) (8 8 8) +Intended for use with the target_spawner +*/ +void SP_misc_gib_arm (edict_t *ent) +{ + gi.setmodel (ent, "models/objects/gibs/arm/tris.md2"); + ent->solid = SOLID_NOT; + ent->s.effects |= EF_GIB; + ent->takedamage = DAMAGE_YES; + ent->die = gib_die; + ent->movetype = MOVETYPE_TOSS; + ent->svflags |= SVF_MONSTER; + ent->deadflag = DEAD_DEAD; + ent->avelocity[0] = random()*200; + ent->avelocity[1] = random()*200; + ent->avelocity[2] = random()*200; + ent->think = G_FreeEdict; + ent->nextthink = level.time + 30; + gi.linkentity (ent); +} + +/*QUAKED misc_gib_leg (1 0 0) (-8 -8 -8) (8 8 8) +Intended for use with the target_spawner +*/ +void SP_misc_gib_leg (edict_t *ent) +{ + gi.setmodel (ent, "models/objects/gibs/leg/tris.md2"); + ent->solid = SOLID_NOT; + ent->s.effects |= EF_GIB; + ent->takedamage = DAMAGE_YES; + ent->die = gib_die; + ent->movetype = MOVETYPE_TOSS; + ent->svflags |= SVF_MONSTER; + ent->deadflag = DEAD_DEAD; + ent->avelocity[0] = random()*200; + ent->avelocity[1] = random()*200; + ent->avelocity[2] = random()*200; + ent->think = G_FreeEdict; + ent->nextthink = level.time + 30; + gi.linkentity (ent); +} + +/*QUAKED misc_gib_head (1 0 0) (-8 -8 -8) (8 8 8) +Intended for use with the target_spawner +*/ +void SP_misc_gib_head (edict_t *ent) +{ + gi.setmodel (ent, "models/objects/gibs/head/tris.md2"); + ent->solid = SOLID_NOT; + ent->s.effects |= EF_GIB; + ent->takedamage = DAMAGE_YES; + ent->die = gib_die; + ent->movetype = MOVETYPE_TOSS; + ent->svflags |= SVF_MONSTER; + ent->deadflag = DEAD_DEAD; + ent->avelocity[0] = random()*200; + ent->avelocity[1] = random()*200; + ent->avelocity[2] = random()*200; + ent->think = G_FreeEdict; + ent->nextthink = level.time + 30; + gi.linkentity (ent); +} + +//===================================================== + +/*QUAKED target_character (0 0 1) ? +used with target_string (must be on same "team") +"count" is position in the string (starts at 1) +*/ + +void SP_target_character (edict_t *self) +{ + self->movetype = MOVETYPE_PUSH; + gi.setmodel (self, self->model); + self->solid = SOLID_BSP; + self->s.frame = 12; + gi.linkentity (self); + return; +} + + +/*QUAKED target_string (0 0 1) (-8 -8 -8) (8 8 8) +*/ + +void target_string_use (edict_t *self, edict_t *other, edict_t *activator) +{ + edict_t *e; + int n, l; + char c; + + l = strlen(self->message); + for (e = self->teammaster; e; e = e->teamchain) + { + if (!e->count) + continue; + n = e->count - 1; + if (n > l) + { + e->s.frame = 12; + continue; + } + + c = self->message[n]; + if (c >= '0' && c <= '9') + e->s.frame = c - '0'; + else if (c == '-') + e->s.frame = 10; + else if (c == ':') + e->s.frame = 11; + else + e->s.frame = 12; + } +} + +void SP_target_string (edict_t *self) +{ + if (!self->message) + self->message = ""; + self->use = target_string_use; +} + + +/*QUAKED func_clock (0 0 1) (-8 -8 -8) (8 8 8) TIMER_UP TIMER_DOWN START_OFF MULTI_USE +target a target_string with this + +The default is to be a time of day clock + +TIMER_UP and TIMER_DOWN run for "count" seconds and the fire "pathtarget" +If START_OFF, this entity must be used before it starts + +"style" 0 "xx" + 1 "xx:xx" + 2 "xx:xx:xx" +*/ + +#define CLOCK_MESSAGE_SIZE 16 + +// don't let field width of any clock messages change, or it +// could cause an overwrite after a game load + +static void func_clock_reset (edict_t *self) +{ + self->activator = NULL; + if (self->spawnflags & 1) + { + self->health = 0; + self->wait = self->count; + } + else if (self->spawnflags & 2) + { + self->health = self->count; + self->wait = 0; + } +} + +static void func_clock_format_countdown (edict_t *self) +{ + if (self->style == 0) + { + Com_sprintf (self->message, CLOCK_MESSAGE_SIZE, "%2i", self->health); + return; + } + + if (self->style == 1) + { + Com_sprintf(self->message, CLOCK_MESSAGE_SIZE, "%2i:%2i", self->health / 60, self->health % 60); + if (self->message[3] == ' ') + self->message[3] = '0'; + return; + } + + if (self->style == 2) + { + Com_sprintf(self->message, CLOCK_MESSAGE_SIZE, "%2i:%2i:%2i", self->health / 3600, (self->health - (self->health / 3600) * 3600) / 60, self->health % 60); + if (self->message[3] == ' ') + self->message[3] = '0'; + if (self->message[6] == ' ') + self->message[6] = '0'; + return; + } +} + +void func_clock_think (edict_t *self) +{ + if (!self->enemy) + { + self->enemy = G_Find (NULL, FOFS(targetname), self->target); + if (!self->enemy) + return; + } + + if (self->spawnflags & 1) + { + func_clock_format_countdown (self); + self->health++; + } + else if (self->spawnflags & 2) + { + func_clock_format_countdown (self); + self->health--; + } + else + { + struct tm *ltime; + time_t gmtime; + + time(&gmtime); + ltime = localtime(&gmtime); + Com_sprintf (self->message, CLOCK_MESSAGE_SIZE, "%2i:%2i:%2i", ltime->tm_hour, ltime->tm_min, ltime->tm_sec); + if (self->message[3] == ' ') + self->message[3] = '0'; + if (self->message[6] == ' ') + self->message[6] = '0'; + } + + self->enemy->message = self->message; + self->enemy->use (self->enemy, self, self); + + if (((self->spawnflags & 1) && (self->health > self->wait)) || + ((self->spawnflags & 2) && (self->health < self->wait))) + { + if (self->pathtarget) + { + char *savetarget; + char *savemessage; + + savetarget = self->target; + savemessage = self->message; + self->target = self->pathtarget; + self->message = NULL; + G_UseTargets (self, self->activator); + self->target = savetarget; + self->message = savemessage; + } + + if (!(self->spawnflags & 8)) + return; + + func_clock_reset (self); + + if (self->spawnflags & 4) + return; + } + + self->nextthink = level.time + 1; +} + +void func_clock_use (edict_t *self, edict_t *other, edict_t *activator) +{ + if (!(self->spawnflags & 8)) + self->use = NULL; + if (self->activator) + return; + self->activator = activator; + self->think (self); +} + +void SP_func_clock (edict_t *self) +{ + if (!self->target) + { + gi.dprintf("%s with no target at %s\n", self->classname, vtos(self->s.origin)); + G_FreeEdict (self); + return; + } + + if ((self->spawnflags & 2) && (!self->count)) + { + gi.dprintf("%s with no count at %s\n", self->classname, vtos(self->s.origin)); + G_FreeEdict (self); + return; + } + + if ((self->spawnflags & 1) && (!self->count)) + self->count = 60*60;; + + func_clock_reset (self); + + self->message = gi.TagMalloc (CLOCK_MESSAGE_SIZE, TAG_LEVEL); + + self->think = func_clock_think; + + if (self->spawnflags & 4) + self->use = func_clock_use; + else + self->nextthink = level.time + 1; +} + +//================================================================================= + +void teleporter_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + edict_t *dest; + int i; + + if (!other->client) + return; + dest = G_Find (NULL, FOFS(targetname), self->target); + if (!dest) + { + gi.dprintf ("Couldn't find destination\n"); + return; + } + + // unlink to make sure it can't possibly interfere with KillBox + gi.unlinkentity (other); + + VectorCopy (dest->s.origin, other->s.origin); + VectorCopy (dest->s.origin, other->s.old_origin); + other->s.origin[2] += 10; + + // clear the velocity and hold them in place briefly + VectorClear (other->velocity); + other->client->ps.pmove.pm_time = 160>>3; // hold time + other->client->ps.pmove.pm_flags |= PMF_TIME_TELEPORT; + + // draw the teleport splash at source and on the player + self->owner->s.event = EV_PLAYER_TELEPORT; + other->s.event = EV_PLAYER_TELEPORT; + + // set angles + for (i=0 ; i<3 ; i++) + other->client->ps.pmove.delta_angles[i] = ANGLE2SHORT(dest->s.angles[i] - other->client->resp.cmd_angles[i]); + + VectorClear (other->s.angles); + VectorClear (other->client->ps.viewangles); + VectorClear (other->client->v_angle); + + // kill anything at the destination + KillBox (other); + + gi.linkentity (other); +} + +/*QUAKED misc_teleporter (1 0 0) (-32 -32 -24) (32 32 -16) +Stepping onto this disc will teleport players to the targeted misc_teleporter_dest object. +*/ +void SP_misc_teleporter (edict_t *ent) +{ + edict_t *trig; + + if (!ent->target) + { + gi.dprintf ("teleporter without a target.\n"); + G_FreeEdict (ent); + return; + } + + gi.setmodel (ent, "models/objects/dmspot/tris.md2"); + ent->s.skinnum = 1; + ent->s.effects = EF_TELEPORTER; + ent->s.sound = gi.soundindex ("world/amb10.wav"); + ent->solid = SOLID_BBOX; + + VectorSet (ent->mins, -32, -32, -24); + VectorSet (ent->maxs, 32, 32, -16); + gi.linkentity (ent); + + trig = G_Spawn (); + trig->touch = teleporter_touch; + trig->solid = SOLID_TRIGGER; + trig->target = ent->target; + trig->owner = ent; + VectorCopy (ent->s.origin, trig->s.origin); + VectorSet (trig->mins, -8, -8, 8); + VectorSet (trig->maxs, 8, 8, 24); + gi.linkentity (trig); + +} + +/*QUAKED misc_teleporter_dest (1 0 0) (-32 -32 -24) (32 32 -16) +Point teleporters at these. +*/ +void SP_misc_teleporter_dest (edict_t *ent) +{ + gi.setmodel (ent, "models/objects/dmspot/tris.md2"); + ent->s.skinnum = 0; + ent->solid = SOLID_BBOX; +// ent->s.effects |= EF_FLIES; + VectorSet (ent->mins, -32, -32, -24); + VectorSet (ent->maxs, 32, 32, -16); + gi.linkentity (ent); +} + +//====================== +//ROGUE +void misc_nuke_core_use (edict_t *self, edict_t *other, edict_t *activator) +{ + if(self->svflags & SVF_NOCLIENT) + self->svflags &= ~SVF_NOCLIENT; + else + self->svflags |= SVF_NOCLIENT; +} + +/*QUAKED misc_nuke_core (1 0 0) (-16 -16 -16) (16 16 16) +toggles visible/not visible. starts visible. +*/ +void SP_misc_nuke_core (edict_t *ent) +{ + gi.setmodel (ent, "models/objects/core/tris.md2"); + gi.linkentity (ent); + + ent->use = misc_nuke_core_use; +} +//ROGUE +//====================== + diff --git a/original/rogue/g_monster.c b/original/rogue/g_monster.c new file mode 100644 index 0000000..68b497b --- /dev/null +++ b/original/rogue/g_monster.c @@ -0,0 +1,981 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +#include "g_local.h" + + +// +// monster weapons +// + +//FIXME mosnters should call these with a totally accurate direction +// and we can mess it up based on skill. Spread should be for normal +// and we can tighten or loosen based on skill. We could muck with +// the damages too, but I'm not sure that's such a good idea. +void monster_fire_bullet (edict_t *self, vec3_t start, vec3_t dir, int damage, int kick, int hspread, int vspread, int flashtype) +{ + fire_bullet (self, start, dir, damage, kick, hspread, vspread, MOD_UNKNOWN); + + gi.WriteByte (svc_muzzleflash2); + gi.WriteShort (self - g_edicts); + gi.WriteByte (flashtype); + gi.multicast (start, MULTICAST_PVS); +} + +void monster_fire_shotgun (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int hspread, int vspread, int count, int flashtype) +{ + fire_shotgun (self, start, aimdir, damage, kick, hspread, vspread, count, MOD_UNKNOWN); + + gi.WriteByte (svc_muzzleflash2); + gi.WriteShort (self - g_edicts); + gi.WriteByte (flashtype); + gi.multicast (start, MULTICAST_PVS); +} + +void monster_fire_blaster (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int flashtype, int effect) +{ + fire_blaster (self, start, dir, damage, speed, effect, false); + + gi.WriteByte (svc_muzzleflash2); + gi.WriteShort (self - g_edicts); + gi.WriteByte (flashtype); + gi.multicast (start, MULTICAST_PVS); +} + +//ROGUE +void monster_fire_blaster2 (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int flashtype, int effect) +{ + fire_blaster2 (self, start, dir, damage, speed, effect, false); + + gi.WriteByte (svc_muzzleflash2); + gi.WriteShort (self - g_edicts); + gi.WriteByte (flashtype); + gi.multicast (start, MULTICAST_PVS); +} + +// FIXME -- add muzzle flash +void monster_fire_tracker (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, edict_t *enemy, int flashtype) +{ + fire_tracker (self, start, dir, damage, speed, enemy); + + gi.WriteByte (svc_muzzleflash2); + gi.WriteShort (self - g_edicts); + gi.WriteByte (flashtype); + gi.multicast (start, MULTICAST_PVS); +} + +void monster_fire_heat (edict_t *self, vec3_t start, vec3_t dir, vec3_t offset, int damage, int kick, int flashtype) +{ + fire_heat (self, start, dir, offset, damage, kick, true); + + gi.WriteByte (svc_muzzleflash2); + gi.WriteShort (self - g_edicts); + gi.WriteByte (flashtype); + gi.multicast (start, MULTICAST_PVS); +} +//ROGUE + +void monster_fire_grenade (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, int flashtype) +{ + fire_grenade (self, start, aimdir, damage, speed, 2.5, damage+40); + + gi.WriteByte (svc_muzzleflash2); + gi.WriteShort (self - g_edicts); + gi.WriteByte (flashtype); + gi.multicast (start, MULTICAST_PVS); +} + +void monster_fire_rocket (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int flashtype) +{ + fire_rocket (self, start, dir, damage, speed, damage+20, damage); + + gi.WriteByte (svc_muzzleflash2); + gi.WriteShort (self - g_edicts); + gi.WriteByte (flashtype); + gi.multicast (start, MULTICAST_PVS); +} + +void monster_fire_railgun (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int flashtype) +{ + // PMM + if (!(gi.pointcontents (start) & MASK_SOLID)) + fire_rail (self, start, aimdir, damage, kick); + + gi.WriteByte (svc_muzzleflash2); + gi.WriteShort (self - g_edicts); + gi.WriteByte (flashtype); + gi.multicast (start, MULTICAST_PVS); +} + +void monster_fire_bfg (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, int kick, float damage_radius, int flashtype) +{ + fire_bfg (self, start, aimdir, damage, speed, damage_radius); + + gi.WriteByte (svc_muzzleflash2); + gi.WriteShort (self - g_edicts); + gi.WriteByte (flashtype); + gi.multicast (start, MULTICAST_PVS); +} + + + +// +// Monster utility functions +// + +void M_FliesOff (edict_t *self) +{ + self->s.effects &= ~EF_FLIES; + self->s.sound = 0; +} + +void M_FliesOn (edict_t *self) +{ + if (self->waterlevel) + return; + self->s.effects |= EF_FLIES; + self->s.sound = gi.soundindex ("infantry/inflies1.wav"); + self->think = M_FliesOff; + self->nextthink = level.time + 60; +} + +void M_FlyCheck (edict_t *self) +{ + if (self->waterlevel) + return; + + if (random() > 0.5) + return; + + self->think = M_FliesOn; + self->nextthink = level.time + 5 + 10 * random(); +} + +void AttackFinished (edict_t *self, float time) +{ + self->monsterinfo.attack_finished = level.time + time; +} + + +void M_CheckGround (edict_t *ent) +{ + vec3_t point; + trace_t trace; + + if (ent->flags & (FL_SWIM|FL_FLY)) + return; + +#ifdef ROGUE_GRAVITY + if ((ent->velocity[2] * ent->gravityVector[2]) < -100) // PGM +#else + if (ent->velocity[2] > 100) +#endif + { + ent->groundentity = NULL; + return; + } + +// if the hull point one-quarter unit down is solid the entity is on ground + point[0] = ent->s.origin[0]; + point[1] = ent->s.origin[1]; +#ifdef ROGUE_GRAVITY + point[2] = ent->s.origin[2] + (0.25 * ent->gravityVector[2]); //PGM +#else + point[2] = ent->s.origin[2] - 0.25; +#endif + + trace = gi.trace (ent->s.origin, ent->mins, ent->maxs, point, ent, MASK_MONSTERSOLID); + + // check steepness +#ifdef ROGUE_GRAVITY +//PGM + if ( ent->gravityVector[2] < 0) // normal gravity + { + if ( trace.plane.normal[2] < 0.7 && !trace.startsolid) + { + ent->groundentity = NULL; + return; + } + } + else // inverted gravity + { + if ( trace.plane.normal[2] > -0.7 && !trace.startsolid) + { + ent->groundentity = NULL; + return; + } + } +//PGM +#else + if ( trace.plane.normal[2] < 0.7 && !trace.startsolid) + { + ent->groundentity = NULL; + return; + } +#endif + +// ent->groundentity = trace.ent; +// ent->groundentity_linkcount = trace.ent->linkcount; +// if (!trace.startsolid && !trace.allsolid) +// VectorCopy (trace.endpos, ent->s.origin); + if (!trace.startsolid && !trace.allsolid) + { + VectorCopy (trace.endpos, ent->s.origin); + ent->groundentity = trace.ent; + ent->groundentity_linkcount = trace.ent->linkcount; + ent->velocity[2] = 0; + } +} + + +void M_CatagorizePosition (edict_t *ent) +{ + vec3_t point; + int cont; + +// +// get waterlevel +// + point[0] = ent->s.origin[0]; + point[1] = ent->s.origin[1]; + point[2] = ent->s.origin[2] + ent->mins[2] + 1; + cont = gi.pointcontents (point); + + if (!(cont & MASK_WATER)) + { + ent->waterlevel = 0; + ent->watertype = 0; + return; + } + + ent->watertype = cont; + ent->waterlevel = 1; + point[2] += 26; + cont = gi.pointcontents (point); + if (!(cont & MASK_WATER)) + return; + + ent->waterlevel = 2; + point[2] += 22; + cont = gi.pointcontents (point); + if (cont & MASK_WATER) + ent->waterlevel = 3; +} + + +void M_WorldEffects (edict_t *ent) +{ + int dmg; + + if (ent->health > 0) + { + if (!(ent->flags & FL_SWIM)) + { + if (ent->waterlevel < 3) + { + ent->air_finished = level.time + 12; + } + else if (ent->air_finished < level.time) + { // drown! + if (ent->pain_debounce_time < level.time) + { + dmg = 2 + 2 * floor(level.time - ent->air_finished); + if (dmg > 15) + dmg = 15; + T_Damage (ent, world, world, vec3_origin, ent->s.origin, vec3_origin, dmg, 0, DAMAGE_NO_ARMOR, MOD_WATER); + ent->pain_debounce_time = level.time + 1; + } + } + } + else + { + if (ent->waterlevel > 0) + { + ent->air_finished = level.time + 9; + } + else if (ent->air_finished < level.time) + { // suffocate! + if (ent->pain_debounce_time < level.time) + { + dmg = 2 + 2 * floor(level.time - ent->air_finished); + if (dmg > 15) + dmg = 15; + T_Damage (ent, world, world, vec3_origin, ent->s.origin, vec3_origin, dmg, 0, DAMAGE_NO_ARMOR, MOD_WATER); + ent->pain_debounce_time = level.time + 1; + } + } + } + } + + if (ent->waterlevel == 0) + { + if (ent->flags & FL_INWATER) + { + gi.sound (ent, CHAN_BODY, gi.soundindex("player/watr_out.wav"), 1, ATTN_NORM, 0); + ent->flags &= ~FL_INWATER; + } + return; + } + + if ((ent->watertype & CONTENTS_LAVA) && !(ent->flags & FL_IMMUNE_LAVA)) + { + if (ent->damage_debounce_time < level.time) + { + ent->damage_debounce_time = level.time + 0.2; + T_Damage (ent, world, world, vec3_origin, ent->s.origin, vec3_origin, 10*ent->waterlevel, 0, 0, MOD_LAVA); + } + } + if ((ent->watertype & CONTENTS_SLIME) && !(ent->flags & FL_IMMUNE_SLIME)) + { + if (ent->damage_debounce_time < level.time) + { + ent->damage_debounce_time = level.time + 1; + T_Damage (ent, world, world, vec3_origin, ent->s.origin, vec3_origin, 4*ent->waterlevel, 0, 0, MOD_SLIME); + } + } + + if ( !(ent->flags & FL_INWATER) ) + { + if (!(ent->svflags & SVF_DEADMONSTER)) + { + if (ent->watertype & CONTENTS_LAVA) + if (random() <= 0.5) + gi.sound (ent, CHAN_BODY, gi.soundindex("player/lava1.wav"), 1, ATTN_NORM, 0); + else + gi.sound (ent, CHAN_BODY, gi.soundindex("player/lava2.wav"), 1, ATTN_NORM, 0); + else if (ent->watertype & CONTENTS_SLIME) + gi.sound (ent, CHAN_BODY, gi.soundindex("player/watr_in.wav"), 1, ATTN_NORM, 0); + else if (ent->watertype & CONTENTS_WATER) + gi.sound (ent, CHAN_BODY, gi.soundindex("player/watr_in.wav"), 1, ATTN_NORM, 0); + } + + ent->flags |= FL_INWATER; + ent->damage_debounce_time = 0; + } +} + + +void M_droptofloor (edict_t *ent) +{ + vec3_t end; + trace_t trace; + +#ifdef ROGUE_GRAVITY +//PGM + if(ent->gravityVector[2] < 0) + { + ent->s.origin[2] += 1; + VectorCopy (ent->s.origin, end); + end[2] -= 256; + } + else + { + ent->s.origin[2] -= 1; + VectorCopy (ent->s.origin, end); + end[2] += 256; + } +//PGM +#else + ent->s.origin[2] += 1; + VectorCopy (ent->s.origin, end); + end[2] -= 256; +#endif + + trace = gi.trace (ent->s.origin, ent->mins, ent->maxs, end, ent, MASK_MONSTERSOLID); + + if (trace.fraction == 1 || trace.allsolid) + return; + + VectorCopy (trace.endpos, ent->s.origin); + + gi.linkentity (ent); + M_CheckGround (ent); + M_CatagorizePosition (ent); +} + + +void M_SetEffects (edict_t *ent) +{ + int remaining; + + ent->s.effects &= ~(EF_COLOR_SHELL|EF_POWERSCREEN|EF_DOUBLE|EF_QUAD|EF_PENT); + ent->s.renderfx &= ~(RF_SHELL_RED|RF_SHELL_GREEN|RF_SHELL_BLUE|RF_SHELL_DOUBLE); + + if (ent->monsterinfo.aiflags & AI_RESURRECTING) + { + ent->s.effects |= EF_COLOR_SHELL; + ent->s.renderfx |= RF_SHELL_RED; + } + + if (ent->health <= 0) + return; + + if (ent->powerarmor_time > level.time) + { + if (ent->monsterinfo.power_armor_type == POWER_ARMOR_SCREEN) + { + ent->s.effects |= EF_POWERSCREEN; + } + else if (ent->monsterinfo.power_armor_type == POWER_ARMOR_SHIELD) + { + ent->s.effects |= EF_COLOR_SHELL; + ent->s.renderfx |= RF_SHELL_GREEN; + } + } + // PMM - new monster powerups + if (ent->monsterinfo.quad_framenum > level.framenum) + { + remaining = ent->monsterinfo.quad_framenum - level.framenum; + if (remaining > 30 || (remaining & 4) ) + ent->s.effects |= EF_QUAD; + } + else + ent->s.effects &= ~EF_QUAD; + + if (ent->monsterinfo.double_framenum > level.framenum) + { + remaining = ent->monsterinfo.double_framenum - level.framenum; + if (remaining > 30 || (remaining & 4) ) + ent->s.effects |= EF_DOUBLE; + } + else + ent->s.effects &= ~EF_DOUBLE; + + if (ent->monsterinfo.invincible_framenum > level.framenum) + { + remaining = ent->monsterinfo.invincible_framenum - level.framenum; + if (remaining > 30 || (remaining & 4) ) + ent->s.effects |= EF_PENT; + } + else + ent->s.effects &= ~EF_PENT; + + // PMM + // PMM - testing +// ent->s.effects |= EF_COLOR_SHELL; +// ent->s.renderfx |= RF_SHELL_HALF_DAM; +/* + if (fmod (level.time, 4.0) > 2.0) + { + gi.dprintf ("invulnerable "); + ent->s.renderfx |= RF_SHELL_RED; + } + else + ent->s.renderfx &= ~RF_SHELL_RED; + + if (fmod (level.time, 8.0) > 4.0) + { + gi.dprintf ("shield "); + ent->s.renderfx |= RF_SHELL_GREEN; + } + else + ent->s.renderfx &= ~RF_SHELL_GREEN; + + if (fmod (level.time, 16.0) > 8.0) + { + gi.dprintf ("quad "); + ent->s.renderfx |= RF_SHELL_BLUE;\ + } + else + ent->s.renderfx &= ~RF_SHELL_BLUE; + + if (fmod (level.time, 32.0) > 16.0) + { + gi.dprintf ("double "); + ent->s.renderfx |= RF_SHELL_DOUBLE; + } + else + ent->s.renderfx &= ~RF_SHELL_DOUBLE; + + if (fmod (level.time, 64.0) > 32.0) + { + gi.dprintf ("half "); + ent->s.renderfx |= RF_SHELL_HALF_DAM; + } + else + ent->s.renderfx &= ~RF_SHELL_HALF_DAM; + + gi.dprintf ("\n"); +*/ +} + + +void M_MoveFrame (edict_t *self) +{ + mmove_t *move; + int index; + + move = self->monsterinfo.currentmove; + self->nextthink = level.time + FRAMETIME; + + if ((self->monsterinfo.nextframe) && (self->monsterinfo.nextframe >= move->firstframe) && (self->monsterinfo.nextframe <= move->lastframe)) + { + self->s.frame = self->monsterinfo.nextframe; + self->monsterinfo.nextframe = 0; + } + else + { + if (self->s.frame == move->lastframe) + { + if (move->endfunc) + { + move->endfunc (self); + + // regrab move, endfunc is very likely to change it + move = self->monsterinfo.currentmove; + + // check for death + if (self->svflags & SVF_DEADMONSTER) + return; + } + } + + if (self->s.frame < move->firstframe || self->s.frame > move->lastframe) + { + self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; + self->s.frame = move->firstframe; + } + else + { + if (!(self->monsterinfo.aiflags & AI_HOLD_FRAME)) + { + self->s.frame++; + if (self->s.frame > move->lastframe) + self->s.frame = move->firstframe; + } + } + } + + index = self->s.frame - move->firstframe; + + if (move->frame[index].aifunc) + if (!(self->monsterinfo.aiflags & AI_HOLD_FRAME)) + move->frame[index].aifunc (self, move->frame[index].dist * self->monsterinfo.scale); + else + move->frame[index].aifunc (self, 0); + + if (move->frame[index].thinkfunc) + move->frame[index].thinkfunc (self); +} + + +void monster_think (edict_t *self) +{ + M_MoveFrame (self); + if (self->linkcount != self->monsterinfo.linkcount) + { + self->monsterinfo.linkcount = self->linkcount; + M_CheckGround (self); + } + M_CatagorizePosition (self); + M_WorldEffects (self); + M_SetEffects (self); +} + + +/* +================ +monster_use + +Using a monster makes it angry at the current activator +================ +*/ +void monster_use (edict_t *self, edict_t *other, edict_t *activator) +{ + if (self->enemy) + return; + if (self->health <= 0) + return; + if (activator->flags & FL_NOTARGET) + return; + if (!(activator->client) && !(activator->monsterinfo.aiflags & AI_GOOD_GUY)) + return; + if (activator->flags & FL_DISGUISED) // PGM + return; // PGM + +// delay reaction so if the monster is teleported, its sound is still heard + self->enemy = activator; + FoundTarget (self); +} + + +void monster_start_go (edict_t *self); + + +void monster_triggered_spawn (edict_t *self) +{ + self->s.origin[2] += 1; + KillBox (self); + + self->solid = SOLID_BBOX; + self->movetype = MOVETYPE_STEP; + self->svflags &= ~SVF_NOCLIENT; + self->air_finished = level.time + 12; + gi.linkentity (self); + + monster_start_go (self); + + if (self->enemy && !(self->spawnflags & 1) && !(self->enemy->flags & FL_NOTARGET)) + { + if(!(self->enemy->flags & FL_DISGUISED)) // PGM + FoundTarget (self); + else // PMM - just in case, make sure to clear the enemy so FindTarget doesn't get confused + self->enemy = NULL; + } + else + { + self->enemy = NULL; + } +} + +void monster_triggered_spawn_use (edict_t *self, edict_t *other, edict_t *activator) +{ + // we have a one frame delay here so we don't telefrag the guy who activated us + self->think = monster_triggered_spawn; + self->nextthink = level.time + FRAMETIME; + if (activator->client) + self->enemy = activator; + self->use = monster_use; +} + +void monster_triggered_start (edict_t *self) +{ + self->solid = SOLID_NOT; + self->movetype = MOVETYPE_NONE; + self->svflags |= SVF_NOCLIENT; + self->nextthink = 0; + self->use = monster_triggered_spawn_use; +} + + +/* +================ +monster_death_use + +When a monster dies, it fires all of its targets with the current +enemy as activator. +================ +*/ +void monster_death_use (edict_t *self) +{ + self->flags &= ~(FL_FLY|FL_SWIM); + self->monsterinfo.aiflags &= AI_GOOD_GUY; + + if (self->item) + { + Drop_Item (self, self->item); + self->item = NULL; + } + + if (self->deathtarget) + self->target = self->deathtarget; + + if (!self->target) + return; + + G_UseTargets (self, self->enemy); +} + + +//============================================================================ + +qboolean monster_start (edict_t *self) +{ + if (deathmatch->value) + { + G_FreeEdict (self); + return false; + } + + if ((self->spawnflags & 4) && !(self->monsterinfo.aiflags & AI_GOOD_GUY)) + { + self->spawnflags &= ~4; + self->spawnflags |= 1; +// gi.dprintf("fixed spawnflags on %s at %s\n", self->classname, vtos(self->s.origin)); + } + + if ((!(self->monsterinfo.aiflags & AI_GOOD_GUY)) && (!(self->monsterinfo.aiflags & AI_DO_NOT_COUNT))) + level.total_monsters++; + + self->nextthink = level.time + FRAMETIME; + self->svflags |= SVF_MONSTER; + self->s.renderfx |= RF_FRAMELERP; + self->takedamage = DAMAGE_AIM; + self->air_finished = level.time + 12; + self->use = monster_use; + self->max_health = self->health; + self->clipmask = MASK_MONSTERSOLID; + + self->s.skinnum = 0; + self->deadflag = DEAD_NO; + self->svflags &= ~SVF_DEADMONSTER; + + if (!self->monsterinfo.checkattack) + self->monsterinfo.checkattack = M_CheckAttack; + VectorCopy (self->s.origin, self->s.old_origin); + + if (st.item) + { + self->item = FindItemByClassname (st.item); + if (!self->item) + gi.dprintf("%s at %s has bad item: %s\n", self->classname, vtos(self->s.origin), st.item); + } + + // randomize what frame they start on + if (self->monsterinfo.currentmove) + self->s.frame = self->monsterinfo.currentmove->firstframe + (rand() % (self->monsterinfo.currentmove->lastframe - self->monsterinfo.currentmove->firstframe + 1)); + + // PMM - get this so I don't have to do it in all of the monsters + self->monsterinfo.base_height = self->maxs[2]; + + // PMM - clear these + self->monsterinfo.quad_framenum = 0; + self->monsterinfo.double_framenum = 0; + self->monsterinfo.invincible_framenum = 0; + + return true; +} + +void monster_start_go (edict_t *self) +{ + vec3_t v; + + if (self->health <= 0) + return; + + // check for target to combat_point and change to combattarget + if (self->target) + { + qboolean notcombat; + qboolean fixup; + edict_t *target; + + target = NULL; + notcombat = false; + fixup = false; + while ((target = G_Find (target, FOFS(targetname), self->target)) != NULL) + { + if (strcmp(target->classname, "point_combat") == 0) + { + self->combattarget = self->target; + fixup = true; + } + else + { + notcombat = true; + } + } + if (notcombat && self->combattarget) + gi.dprintf("%s at %s has target with mixed types\n", self->classname, vtos(self->s.origin)); + if (fixup) + self->target = NULL; + } + + // validate combattarget + if (self->combattarget) + { + edict_t *target; + + target = NULL; + while ((target = G_Find (target, FOFS(targetname), self->combattarget)) != NULL) + { + if (strcmp(target->classname, "point_combat") != 0) + { + gi.dprintf("%s at (%i %i %i) has a bad combattarget %s : %s at (%i %i %i)\n", + self->classname, (int)self->s.origin[0], (int)self->s.origin[1], (int)self->s.origin[2], + self->combattarget, target->classname, (int)target->s.origin[0], (int)target->s.origin[1], + (int)target->s.origin[2]); + } + } + } + + if (self->target) + { + self->goalentity = self->movetarget = G_PickTarget(self->target); + if (!self->movetarget) + { + gi.dprintf ("%s can't find target %s at %s\n", self->classname, self->target, vtos(self->s.origin)); + self->target = NULL; + self->monsterinfo.pausetime = 100000000; + self->monsterinfo.stand (self); + } + else if (strcmp (self->movetarget->classname, "path_corner") == 0) + { + VectorSubtract (self->goalentity->s.origin, self->s.origin, v); + self->ideal_yaw = self->s.angles[YAW] = vectoyaw(v); + self->monsterinfo.walk (self); + self->target = NULL; + } + else + { + self->goalentity = self->movetarget = NULL; + self->monsterinfo.pausetime = 100000000; + self->monsterinfo.stand (self); + } + } + else + { + self->monsterinfo.pausetime = 100000000; + self->monsterinfo.stand (self); + } + + self->think = monster_think; + self->nextthink = level.time + FRAMETIME; +} + + +void walkmonster_start_go (edict_t *self) +{ + if (!(self->spawnflags & 2) && level.time < 1) + { + M_droptofloor (self); + + if (self->groundentity) + if (!M_walkmove (self, 0, 0)) + gi.dprintf ("%s in solid at %s\n", self->classname, vtos(self->s.origin)); + } + + if (!self->yaw_speed) + self->yaw_speed = 20; + // PMM - stalkers are too short for this + if (!(strcmp(self->classname, "monster_stalker"))) + self->viewheight = 15; + else + self->viewheight = 25; + + monster_start_go (self); + + if (self->spawnflags & 2) + monster_triggered_start (self); +} + +void walkmonster_start (edict_t *self) +{ + self->think = walkmonster_start_go; + monster_start (self); +} + + +void flymonster_start_go (edict_t *self) +{ + if (!M_walkmove (self, 0, 0)) + gi.dprintf ("%s in solid at %s\n", self->classname, vtos(self->s.origin)); + + if (!self->yaw_speed) + self->yaw_speed = 10; + self->viewheight = 25; + + monster_start_go (self); + + if (self->spawnflags & 2) + monster_triggered_start (self); +} + + +void flymonster_start (edict_t *self) +{ + self->flags |= FL_FLY; + self->think = flymonster_start_go; + monster_start (self); +} + + +void swimmonster_start_go (edict_t *self) +{ + if (!self->yaw_speed) + self->yaw_speed = 10; + self->viewheight = 10; + + monster_start_go (self); + + if (self->spawnflags & 2) + monster_triggered_start (self); +} + +void swimmonster_start (edict_t *self) +{ + self->flags |= FL_SWIM; + self->think = swimmonster_start_go; + monster_start (self); +} + +//ROGUE + +void stationarymonster_start_go (edict_t *self); + +void stationarymonster_triggered_spawn (edict_t *self) +{ + KillBox (self); + + self->solid = SOLID_BBOX; + self->movetype = MOVETYPE_NONE; + self->svflags &= ~SVF_NOCLIENT; + self->air_finished = level.time + 12; + gi.linkentity (self); + + // FIXME - why doesn't this happen with real monsters? + self->spawnflags &= ~2; + + stationarymonster_start_go (self); + + if (self->enemy && !(self->spawnflags & 1) && !(self->enemy->flags & FL_NOTARGET)) + { + if(!(self->enemy->flags & FL_DISGUISED)) // PGM + FoundTarget (self); + else // PMM - just in case, make sure to clear the enemy so FindTarget doesn't get confused + self->enemy = NULL; + } + else + { + self->enemy = NULL; + } +} + +void stationarymonster_triggered_spawn_use (edict_t *self, edict_t *other, edict_t *activator) +{ + // we have a one frame delay here so we don't telefrag the guy who activated us + self->think = stationarymonster_triggered_spawn; + self->nextthink = level.time + FRAMETIME; + if (activator->client) + self->enemy = activator; + self->use = monster_use; +} + +void stationarymonster_triggered_start (edict_t *self) +{ + self->solid = SOLID_NOT; + self->movetype = MOVETYPE_NONE; + self->svflags |= SVF_NOCLIENT; + self->nextthink = 0; + self->use = stationarymonster_triggered_spawn_use; +} + +void stationarymonster_start_go (edict_t *self) +{ +// PGM - only turrets use this, so remove the error message. They're supposed to be in solid. + +// if (!M_walkmove (self, 0, 0)) +// gi.dprintf ("%s in solid at %s\n", self->classname, vtos(self->s.origin)); + + if (!self->yaw_speed) + self->yaw_speed = 20; +// self->viewheight = 25; + + monster_start_go (self); + + if (self->spawnflags & 2) + stationarymonster_triggered_start (self); +} + +void stationarymonster_start (edict_t *self) +{ + self->think = stationarymonster_start_go; + monster_start (self); +} + +void monster_done_dodge (edict_t *self) +{ +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("%s done dodging\n", self->classname); + self->monsterinfo.aiflags &= ~AI_DODGING; +} +//ROGUE \ No newline at end of file diff --git a/original/rogue/g_newai.c b/original/rogue/g_newai.c new file mode 100644 index 0000000..53915c8 --- /dev/null +++ b/original/rogue/g_newai.c @@ -0,0 +1,2000 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. + +#include "g_local.h" + +//=============================== +// BLOCKED Logic +//=============================== + +/* + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_DEBUGTRAIL); + gi.WritePosition (pt1); + gi.WritePosition (pt2); + gi.multicast (pt1, MULTICAST_PVS); + + self->nextthink = level.time + 10; +*/ + +// plat states, copied from g_func.c + +#define STATE_TOP 0 +#define STATE_BOTTOM 1 +#define STATE_UP 2 +#define STATE_DOWN 3 + +qboolean face_wall (edict_t *self); +void HuntTarget (edict_t *self); + +// PMM +qboolean parasite_drain_attack_ok (vec3_t start, vec3_t end); + + +// blocked_checkshot +// shotchance: 0-1, chance they'll take the shot if it's clear. +qboolean blocked_checkshot (edict_t *self, float shotChance) +{ + qboolean playerVisible; + + if(!self->enemy) + return false; + + // blocked checkshot is only against players. this will + // filter out player sounds and other shit they should + // not be firing at. + if(!(self->enemy->client)) + return false; + + if (random() < shotChance) + return false; + + // PMM - special handling for the parasite + if (!strcmp(self->classname, "monster_parasite")) + { + vec3_t f, r, offset, start, end; + trace_t tr; + AngleVectors (self->s.angles, f, r, NULL); + VectorSet (offset, 24, 0, 6); + G_ProjectSource (self->s.origin, offset, f, r, start); + + VectorCopy (self->enemy->s.origin, end); + if (!parasite_drain_attack_ok(start, end)) + { + end[2] = self->enemy->s.origin[2] + self->enemy->maxs[2] - 8; + if (!parasite_drain_attack_ok(start, end)) + { + end[2] = self->enemy->s.origin[2] + self->enemy->mins[2] + 8; + if (!parasite_drain_attack_ok(start, end)) + return false; + } + } + VectorCopy (self->enemy->s.origin, end); + + tr = gi.trace (start, NULL, NULL, end, self, MASK_SHOT); + if (tr.ent != self->enemy) + { + self->monsterinfo.aiflags |= AI_BLOCKED; + + if(self->monsterinfo.attack) + self->monsterinfo.attack(self); + + self->monsterinfo.aiflags &= ~AI_BLOCKED; + return true; + } + } + + playerVisible = visible (self, self->enemy); + // always shoot at teslas + if(playerVisible) + { + if (!strcmp(self->enemy->classname, "tesla")) + { +// if(g_showlogic && g_showlogic->value) +// gi.dprintf("blocked: taking a shot\n"); + + // turn on AI_BLOCKED to let the monster know the attack is being called + // by the blocked functions... + self->monsterinfo.aiflags |= AI_BLOCKED; + + if(self->monsterinfo.attack) + self->monsterinfo.attack(self); + + self->monsterinfo.aiflags &= ~AI_BLOCKED; + return true; + } + } + + return false; +} + +// blocked_checkplat +// dist: how far they are trying to walk. +qboolean blocked_checkplat (edict_t *self, float dist) +{ + int playerPosition; + trace_t trace; + vec3_t pt1, pt2; + vec3_t forward; + edict_t *plat; + + if(!self->enemy) + return false; + + // check player's relative altitude + if(self->enemy->absmin[2] >= self->absmax[2]) + playerPosition = 1; + else if(self->enemy->absmax[2] <= self->absmin[2]) + playerPosition = -1; + else + playerPosition = 0; + + // if we're close to the same position, don't bother trying plats. + if(playerPosition == 0) + return false; + + plat = NULL; + + // see if we're already standing on a plat. + if(self->groundentity && self->groundentity != world) + { + if(!strncmp(self->groundentity->classname, "func_plat", 8)) + plat = self->groundentity; + } + + // if we're not, check to see if we'll step onto one with this move + if(!plat) + { + AngleVectors (self->s.angles, forward, NULL, NULL); + VectorMA(self->s.origin, dist, forward, pt1); + VectorCopy (pt1, pt2); + pt2[2] -= 384; + + trace = gi.trace(pt1, vec3_origin, vec3_origin, pt2, self, MASK_MONSTERSOLID); + if(trace.fraction < 1 && !trace.allsolid && !trace.startsolid) + { + if(!strncmp(trace.ent->classname, "func_plat", 8)) + { + plat = trace.ent; + } + } + } + + // if we've found a plat, trigger it. + if(plat && plat->use) + { + if (playerPosition == 1) + { + if((self->groundentity == plat && plat->moveinfo.state == STATE_BOTTOM) || + (self->groundentity != plat && plat->moveinfo.state == STATE_TOP)) + { +// if(g_showlogic && g_showlogic->value) +// gi.dprintf("player above, and plat will raise. using!\n"); + plat->use (plat, self, self); + return true; + } + } + else if(playerPosition == -1) + { + if((self->groundentity == plat && plat->moveinfo.state == STATE_TOP) || + (self->groundentity != plat && plat->moveinfo.state == STATE_BOTTOM)) + { +// if(g_showlogic && g_showlogic->value) +// gi.dprintf("player below, and plat will lower. using!\n"); + plat->use (plat, self, self); + return true; + } + } +// if(g_showlogic && g_showlogic->value) +// gi.dprintf("hit a plat, not using. ppos: %d plat: %d\n", playerPosition, plat->moveinfo.state); + } + + return false; +} + +// blocked_checkjump +// dist: how far they are trying to walk. +// maxDown/maxUp: how far they'll ok a jump for. set to 0 to disable that direction. +qboolean blocked_checkjump (edict_t *self, float dist, float maxDown, float maxUp) +{ + int playerPosition; + trace_t trace; + vec3_t pt1, pt2; + vec3_t forward, up; + + if(!self->enemy) + return false; + + AngleVectors (self->s.angles, forward, NULL, up); + + if(self->enemy->absmin[2] > (self->absmin[2] + 16)) + playerPosition = 1; + else if(self->enemy->absmin[2] < (self->absmin[2] - 16)) + playerPosition = -1; + else + playerPosition = 0; + + if(playerPosition == -1 && maxDown) + { + // check to make sure we can even get to the spot we're going to "fall" from + VectorMA(self->s.origin, 48, forward, pt1); + trace = gi.trace(self->s.origin, self->mins, self->maxs, pt1, self, MASK_MONSTERSOLID); + if(trace.fraction < 1) + { +// gi.dprintf("can't get thar from hear...\n"); + return false; + } + + VectorCopy (pt1, pt2); + pt2[2] = self->mins[2] - maxDown - 1; + + trace = gi.trace(pt1, vec3_origin, vec3_origin, pt2, self, MASK_MONSTERSOLID | MASK_WATER); + if(trace.fraction < 1 && !trace.allsolid && !trace.startsolid) + { + if((self->absmin[2] - trace.endpos[2]) >= 24 && trace.contents & MASK_SOLID) + { + if( (self->enemy->absmin[2] - trace.endpos[2]) > 32) + { +// if(g_showlogic && g_showlogic->value) +// gi.dprintf("That'll take me too far down...%0.1f\n", (self->enemy->absmin[2] - trace.endpos[2])); + return false; + } + + if(trace.plane.normal[2] < 0.9) + { +// gi.dprintf("Floor angle too much! %s\n", vtos(trace.plane.normal)); + return false; + } +// if(g_showlogic && g_showlogic->value) +// gi.dprintf("Geronimo! %0.1f\n", (self->absmin[2] - trace.endpos[2])); + return true; + } +// else if(g_showlogic && g_showlogic->value) +// { +// if(!(trace.contents & MASK_SOLID)) +// gi.dprintf("Ooooh... Bad stuff down there...\n"); +// else +// gi.dprintf("Too far to fall\n"); +// } + } +// else if(g_showlogic && g_showlogic->value) +// gi.dprintf("Ooooh... Too far to fall...\n"); + } + else if(playerPosition == 1 && maxUp) + { + VectorMA(self->s.origin, 48, forward, pt1); + VectorCopy(pt1, pt2); + pt1[2] = self->absmax[2] + maxUp; + + trace = gi.trace(pt1, vec3_origin, vec3_origin, pt2, self, MASK_MONSTERSOLID | MASK_WATER); + if(trace.fraction < 1 && !trace.allsolid && !trace.startsolid) + { + if((trace.endpos[2] - self->absmin[2]) <= maxUp && trace.contents & MASK_SOLID) + { +// if(g_showlogic && g_showlogic->value) +// gi.dprintf("Jumping Up! %0.1f\n", (trace.endpos[2] - self->absmin[2])); + + face_wall(self); + return true; + } +// else if(g_showlogic && g_showlogic->value) +// gi.dprintf("Too high to jump %0.1f\n", (trace.endpos[2] - self->absmin[2])); + } +// else if(g_showlogic && g_showlogic->value) +// gi.dprintf("Not something I could jump onto\n"); + } +// else if(g_showlogic && g_showlogic->value) +// gi.dprintf("Player at similar level. No need to jump up?\n"); + + return false; +} + +// checks to see if another coop player is nearby, and will switch. +qboolean blocked_checknewenemy (edict_t *self) +{ +/* + int player; + edict_t *ent; + + if (!(coop->value)) + return false; + + for (player = 1; player <= game.maxclients; player++) + { + ent = &g_edicts[player]; + if (!ent->inuse) + continue; + if (!ent->client) + continue; + if (ent == self->enemy) + continue; + + if (visible (self, ent)) + { + if (g_showlogic && g_showlogic->value) + gi.dprintf ("B_CNE: %s acquired new enemy %s\n", self->classname, ent->client->pers.netname); + + self->enemy = ent; + FoundTarget (self); + return true; + } + } + + return false; +*/ + return false; +} + +// ************************* +// HINT PATHS +// ************************* + +#define HINT_ENDPOINT 0x0001 +#define MAX_HINT_CHAINS 100 + +int hint_paths_present; +edict_t *hint_path_start[MAX_HINT_CHAINS]; +int num_hint_paths; + +// +// AI code +// + +// ============= +// hintpath_findstart - given any hintpath node, finds the start node +// ============= +edict_t *hintpath_findstart(edict_t *ent) +{ + edict_t *e; + edict_t *last; + int field; + + if(ent->target) // starting point + { + last = world; + field = FOFS(targetname); + e = G_Find(NULL, field, ent->target); + while(e) + { + last = e; + if(!e->target) + break; + e = G_Find(NULL, field, e->target); + } + } + else // end point + { + last = world; + field = FOFS(target); + e = G_Find(NULL, field, ent->targetname); + while(e) + { + last = e; + if(!e->targetname) + break; + e = G_Find(NULL, field, e->targetname); + } + } + + if(!(last->spawnflags & HINT_ENDPOINT)) + { +// gi.dprintf ("end of chain is not HINT_ENDPOINT\n"); + return NULL; + } + + if(last == world) + last = NULL; + return last; +} + +// ============= +// hintpath_other_end - given one endpoint of a hintpath, returns the other end. +// ============= +edict_t *hintpath_other_end(edict_t *ent) +{ + edict_t *e; + edict_t *last; + int field; + + if(ent->target) // starting point + { + last = world; + field = FOFS(targetname); + e = G_Find(NULL, field, ent->target); + while(e) + { + last = e; + if(!e->target) + break; + e = G_Find(NULL, field, e->target); + } + } + else // end point + { + last = world; + field = FOFS(target); + e = G_Find(NULL, field, ent->targetname); + while(e) + { + last = e; + if(!e->targetname) + break; + e = G_Find(NULL, field, e->targetname); + } + } + + if(!(last->spawnflags & HINT_ENDPOINT)) + { +// gi.dprintf ("end of chain is not HINT_ENDPOINT\n"); + return NULL; + } + + if(last == world) + last = NULL; + return last; +} + +// ============= +// hintpath_go - starts a monster (self) moving towards the hintpath (point) +// disables all contrary AI flags. +// ============= +void hintpath_go (edict_t *self, edict_t *point) +{ + vec3_t dir; + vec3_t angles; + + VectorSubtract(point->s.origin, self->s.origin, dir); + vectoangles2(dir, angles); + + self->ideal_yaw = angles[YAW]; + self->goalentity = self->movetarget = point; + self->monsterinfo.pausetime = 0; + self->monsterinfo.aiflags |= AI_HINT_PATH; + self->monsterinfo.aiflags &= ~(AI_SOUND_TARGET | AI_PURSUIT_LAST_SEEN | AI_PURSUE_NEXT | AI_PURSUE_TEMP); + // run for it + self->monsterinfo.search_time = level.time; + self->monsterinfo.run (self); +} + +// ============= +// hintpath_stop - bails a monster out of following hint paths +// ============= +void hintpath_stop (edict_t *self) +{ + self->goalentity = NULL; + self->movetarget = NULL; +// self->monsterinfo.last_hint = NULL; + self->monsterinfo.last_hint_time = level.time; + self->monsterinfo.goal_hint = NULL; + self->monsterinfo.aiflags &= ~AI_HINT_PATH; + if (has_valid_enemy(self)) + { + // if we can see our target, go nuts + if (visible(self, self->enemy)) + { + FoundTarget (self); + return; + } + // otherwise, keep chasing + HuntTarget (self); + return; + } + // if our enemy is no longer valid, forget about our enemy and go into stand + self->enemy = NULL; + // we need the pausetime otherwise the stand code + // will just revert to walking with no target and + // the monsters will wonder around aimlessly trying + // to hunt the world entity + self->monsterinfo.pausetime = level.time + 100000000; + self->monsterinfo.stand (self); +} + +// ============= +// monsterlost_checkhint - the monster (self) will check around for valid hintpaths. +// a valid hintpath is one where the two endpoints can see both the monster +// and the monster's enemy. if only one person is visible from the endpoints, +// it will not go for it. +// ============= +qboolean monsterlost_checkhint2 (edict_t *self); + +qboolean monsterlost_checkhint (edict_t *self) +{ + edict_t *e, *monster_pathchain, *target_pathchain, *checkpoint; + edict_t *closest; + float closest_range = 1000000; + edict_t *start, *destination; + int field; + int count1=0, count2=0, count3=0, count4=0, count5=0; + float r; + int i; + qboolean hint_path_represented[MAX_HINT_CHAINS]; + + // if there are no hint paths on this map, exit immediately. + if(!hint_paths_present) + return false; + + if(!self->enemy) + return false; + + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + return false; + + if (!strcmp(self->classname, "monster_turret")) + return false; + + monster_pathchain = NULL; + + field = FOFS(classname); + + // find all the hint_paths. + // FIXME - can we not do this every time? + for (i=0; i < num_hint_paths; i++) + { + e = hint_path_start[i]; + while(e) + { + count1++; + if (e->monster_hint_chain) + { +// gi.dprintf ("uh, oh, I didn't clean up after myself\n"); + e->monster_hint_chain = NULL; + } + if (monster_pathchain) + { + checkpoint->monster_hint_chain = e; + checkpoint = e; + } + else + { + monster_pathchain = e; + checkpoint = e; + } + e = e->hint_chain; + } + } + + // filter them by distance and visibility to the monster + e = monster_pathchain; + checkpoint = NULL; + while (e) + { + r = realrange (self, e); + +// if (r > 512) +// count3++; + + if (r > 512) + { + count2++; +// if (g_showlogic && g_showlogic->value) +// { +// gi.dprintf ("MONSTER (%s) DISTANCE: ", self->classname); +// if (e->targetname) +// gi.dprintf ("targetname %s\n", e->targetname); +// else +// gi.dprintf ("start -> %s\n", e->target); +// } + if (checkpoint) + { + checkpoint->monster_hint_chain = e->monster_hint_chain; + e->monster_hint_chain = NULL; + e = checkpoint->monster_hint_chain; + continue; + } + else + { + // use checkpoint as temp pointer + checkpoint = e; + e = e->monster_hint_chain; + checkpoint->monster_hint_chain = NULL; + // and clear it again + checkpoint = NULL; + // since we have yet to find a valid one (or else checkpoint would be set) move the + // start of monster_pathchain + monster_pathchain = e; + continue; + } + } + if (!visible(self, e)) + { + count4++; +// if (g_showlogic && g_showlogic->value) +// { +// gi.dprintf ("MONSTER (%s) VISIBILITY: ", self->classname); +// if (e->targetname) +// gi.dprintf ("targetname %s\n", e->targetname); +// else +// gi.dprintf ("start -> %s\n", e->target); +// } + if (checkpoint) + { + checkpoint->monster_hint_chain = e->monster_hint_chain; + e->monster_hint_chain = NULL; + e = checkpoint->monster_hint_chain; + continue; + } + else + { + // use checkpoint as temp pointer + checkpoint = e; + e = e->monster_hint_chain; + checkpoint->monster_hint_chain = NULL; + // and clear it again + checkpoint = NULL; + // since we have yet to find a valid one (or else checkpoint would be set) move the + // start of monster_pathchain + monster_pathchain = e; + continue; + } + } + // if it passes all the tests, it's a keeper +// if (g_showlogic && g_showlogic->value) +// { +// gi.dprintf ("MONSTER (%s) ACCEPT: ", self->classname); +// if (e->targetname) +// gi.dprintf ("targetname %s\n", e->targetname); +// else +// gi.dprintf ("start -> %s\n", e->target); +// } + count5++; + checkpoint = e; + e = e->monster_hint_chain; + } + + // at this point, we have a list of all of the eligible hint nodes for the monster + // we now take them, figure out what hint chains they're on, and traverse down those chains, + // seeing whether any can see the player + // + // first, we figure out which hint chains we have represented in monster_pathchain + if (count5 == 0) + { +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("No eligible hint paths found.\n"); + return false; + } + + for (i=0; i < num_hint_paths; i++) + { + hint_path_represented[i] = false; + } + e = monster_pathchain; + checkpoint = NULL; + while (e) + { + if ((e->hint_chain_id < 0) || (e->hint_chain_id > num_hint_paths)) + { +// if (g_showlogic && g_showlogic->value) +// gi.dprintf ("bad hint_chain_id! %d\n", e->hint_chain_id); + return false; + } + hint_path_represented[e->hint_chain_id] = true; + e = e->monster_hint_chain; + } + + count1 = 0; + count2 = 0; + count3 = 0; + count4 = 0; + count5 = 0; + + // now, build the target_pathchain which contains all of the hint_path nodes we need to check for + // validity (within range, visibility) + target_pathchain = NULL; + checkpoint = NULL; + for (i=0; i < num_hint_paths; i++) + { + // if this hint chain is represented in the monster_hint_chain, add all of it's nodes to the target_pathchain + // for validity checking + if (hint_path_represented[i]) + { + e = hint_path_start[i]; + while (e) + { + if (target_pathchain) + { + checkpoint->target_hint_chain = e; + checkpoint = e; + } + else + { + target_pathchain = e; + checkpoint = e; + } + e = e->hint_chain; + } + } + } + + // target_pathchain is a list of all of the hint_path nodes we need to check for validity relative to the target + e = target_pathchain; + checkpoint = NULL; + while (e) + { + r = realrange (self->enemy, e); + +// if (r > 512) +// count3++; + + if (r > 512) + { + count2++; +// if (g_showlogic && g_showlogic->value) +// { +// gi.dprintf ("TARGET RANGE: "); +// if (e->targetname) +// gi.dprintf ("targetname %s\n", e->targetname); +// else +// gi.dprintf ("start -> %s\n", e->target); +// } + if (checkpoint) + { + checkpoint->target_hint_chain = e->target_hint_chain; + e->target_hint_chain = NULL; + e = checkpoint->target_hint_chain; + continue; + } + else + { + // use checkpoint as temp pointer + checkpoint = e; + e = e->target_hint_chain; + checkpoint->target_hint_chain = NULL; + // and clear it again + checkpoint = NULL; + target_pathchain = e; + continue; + } + } + if (!visible(self->enemy, e)) + { + count4++; +// if (g_showlogic && g_showlogic->value) +// { +// gi.dprintf ("TARGET VISIBILITY: "); +// if (e->targetname) +// gi.dprintf ("targetname %s\n", e->targetname); +// else +// gi.dprintf ("start -> %s\n", e->target); +// } + if (checkpoint) + { + checkpoint->target_hint_chain = e->target_hint_chain; + e->target_hint_chain = NULL; + e = checkpoint->target_hint_chain; + continue; + } + else + { + // use checkpoint as temp pointer + checkpoint = e; + e = e->target_hint_chain; + checkpoint->target_hint_chain = NULL; + // and clear it again + checkpoint = NULL; + target_pathchain = e; + continue; + } + } + // if it passes all the tests, it's a keeper +// if (g_showlogic && g_showlogic->value) +// { +// gi.dprintf ("TARGET ACCEPT: "); +// if (e->targetname) +// gi.dprintf ("targetname %s\n", e->targetname); +// else +// gi.dprintf ("start -> %s\n", e->target); +// } + count5++; + checkpoint = e; + e = e->target_hint_chain; + } + + // at this point we should have: + // monster_pathchain - a list of "monster valid" hint_path nodes linked together by monster_hint_chain + // target_pathcain - a list of "target valid" hint_path nodes linked together by target_hint_chain. these + // are filtered such that only nodes which are on the same chain as "monster valid" nodes + // + // Now, we figure out which "monster valid" node we want to use + // + // To do this, we first off make sure we have some target nodes. If we don't, there are no valid hint_path nodes + // for us to take + // + // If we have some, we filter all of our "monster valid" nodes by which ones have "target valid" nodes on them + // + // Once this filter is finished, we select the closest "monster valid" node, and go to it. + + if (count5 == 0) + { +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("No valid target nodes found\n"); + return false; + } + + // reuse the hint_chain_represented array, this time to see which chains are represented by the target + for (i=0; i < num_hint_paths; i++) + { + hint_path_represented[i] = false; + } + + e = target_pathchain; + checkpoint = NULL; + while (e) + { + if ((e->hint_chain_id < 0) || (e->hint_chain_id > num_hint_paths)) + { +// gi.dprintf ("bad hint_chain_id! %d\n", e->hint_chain_id); + return false; + } + hint_path_represented[e->hint_chain_id] = true; + e = e->target_hint_chain; + } + + // traverse the monster_pathchain - if the hint_node isn't represented in the "target valid" chain list, + // remove it + // if it is on the list, check it for range from the monster. If the range is the closest, keep it + // + closest = NULL; + e = monster_pathchain; + while (e) + { + if (!(hint_path_represented[e->hint_chain_id])) + { + checkpoint = e->monster_hint_chain; + e->monster_hint_chain = NULL; + e = checkpoint; + continue; + } + r = realrange(self, e); + if (r < closest_range) + closest = e; + e = e->monster_hint_chain; + } + + if (!closest) + { +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("Failed to find closest node for monster. Shouldn't happen.\n"); + return false; + } + + start = closest; + // now we know which one is the closest to the monster .. this is the one the monster will go to + // we need to finally determine what the DESTINATION node is for the monster .. walk down the hint_chain, + // and find the closest one to the player + + closest = NULL; + closest_range = 10000000; + e = target_pathchain; + while (e) + { + if (start->hint_chain_id == e->hint_chain_id) + { + r = realrange(self, e); + if (r < closest_range) + closest = e; + } + e = e->target_hint_chain; + } + + if (!closest) + { +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("Failed to find closest node for target. Shouldn't happen.\n"); + return false; + } + + destination = closest; + + self->monsterinfo.goal_hint = destination; +// self->monsterinfo.last_hint = NULL; + hintpath_go(self, start); + +// if(g_showlogic && g_showlogic->value) +// { +// gi.dprintf ("found path. proceed to "); +// if (start->targetname) +// gi.dprintf ("%s to get to ", start->targetname); +// else +// gi.dprintf ("start (->%s) to get to ", start->target); +// if (destination->targetname) +// gi.dprintf ("%s.", destination->targetname); +// else +// gi.dprintf ("start (->%s)", destination->target); +// } +// gi.dprintf("found path. proceed to %s to get to %s\n", vtos(start->s.origin), vtos(destination->s.origin)); + + return true; +} +/* +qboolean monsterlost_checkhint2 (edict_t *self) +{ + edict_t *e, *e2, *goPoint; + int field; + int playerVisible, selfVisible; + + // if there are no hint paths on this map, exit immediately. + if(!hint_paths_present) + return false; + + if(!self->enemy) + return false; + + goPoint = NULL; + field = FOFS(classname); + + // check all the hint_paths. + e = G_Find(NULL, field, "hint_path"); + while(e) + { + // if it's an endpoint, check for "validity" + if(e->spawnflags & HINT_ENDPOINT) + { + // check visibility from this spot + selfVisible = visible(e, self); + playerVisible = visible(e, self->enemy); +// gi.dprintf("checking endpoint at %s %d %d\n", vtos(e->s.origin),selfVisible,playerVisible); + + // at least one of us is visible from this endpoint. + // now check the other one if needed. + if(selfVisible || playerVisible) + { + // if endpoint 1 saw me, set my destination to it. + if(selfVisible) + goPoint = e; + + // if both aren't visible, try the other endpoint + if(!selfVisible || !playerVisible) + { + e2 = hintpath_other_end(e); + if(!e2) // could not connect to the other endpoint + { + gi.dprintf("Unlinked hint paths!\n"); + return false; + } + + // if endpoint 1 saw the enemy, see if endpoint 2 sees me + if(!selfVisible) + selfVisible = visible(e2, self); + // if endpoint 1 saw me, see if endpoint 2 sees the enemy + else if(!playerVisible) + playerVisible = visible(e2, self->enemy); + + // if endpoint 2 saw me, set my destination to it. + if(!goPoint && selfVisible) + goPoint = e2; + +// gi.dprintf("checking other endpoint at %s %d %d\n", vtos(e2->s.origin),selfVisible,playerVisible); + } + + // if both are visible from at least one endpoint, + // go for it. + if(selfVisible && playerVisible) + { + // set me to go to goPoint + if(g_showlogic && g_showlogic->value) + gi.dprintf("found path. proceed to %s\n", vtos(goPoint->s.origin)); + + // since this is a new hint path trip, set last_hint to NULL + self->monsterinfo.last_hint = NULL; + hintpath_go(self, goPoint); + return true; + } + } + } + e = G_Find(e, field, "hint_path"); + } + + // if we got here, we didn't find a valid path + if(g_showlogic && g_showlogic->value) + gi.dprintf("blocked_checkhint: found no paths\n"); + return false; +} +*/ +// +// Path code +// + +// ============= +// hint_path_touch - someone's touched the hint_path +// ============= +void hint_path_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + edict_t *e, *goal, *next; +// int chain; // direction - (-1) = upstream, (1) = downstream, (0) = done + qboolean goalFound = false; + + // make sure we're the target of it's obsession + if(other->movetarget == self) + { + goal = other->monsterinfo.goal_hint; + + // if the monster is where he wants to be + if (goal == self) + { +// if(g_showlogic && g_showlogic->value) +// gi.dprintf("Got to goal, detatching\n"); + hintpath_stop (other); + return; + } + else + { + // if we aren't, figure out which way we want to go + e = hint_path_start[self->hint_chain_id]; + while (e) + { + // if we get up to ourselves on the hint chain, we're going down it + if (e == self) + { + next = e->hint_chain; + break; + } + if (e == goal) + goalFound = true; + // if we get to where the next link on the chain is this hint_path and have found the goal on the way + // we're going upstream, so remember who the previous link is + if ((e->hint_chain == self) && goalFound) + { + next = e; + break; + } + e = e->hint_chain; + } + } + + // if we couldn't find it, have the monster go back to normal hunting. + if(!next) + { +// if(g_showlogic && g_showlogic->value) +// gi.dprintf("couldn't figure out next node, dropping hint path\n"); + hintpath_stop(other); + return; + } + + // set the last_hint entry to this hint_path, and + // send him on his way +// other->monsterinfo.last_hint = self; +// if(g_showlogic && g_showlogic->value) +// { +// gi.dprintf("moving to next point, "); +// if (next->targetname) +// gi.dprintf ("targetname %s\n", next->targetname); +// else +// gi.dprintf ("start -> %s\n", next->target); +// } + hintpath_go(other, next); + + // have the monster freeze if the hint path we just touched has a wait time + // on it, for example, when riding a plat. + if(self->wait) + { +// if(g_showlogic && g_showlogic->value) +// gi.dprintf("monster waiting %0.1f\n", self->wait); + other->nextthink = level.time + self->wait; + } + } +} +/* +void hint_path_touch2 (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + edict_t *next, *last; + int chain; + + // make sure we're the target of it's obsession + if(other->movetarget == self) + { + chain = 0; // direction the monster is going in the chain + next = NULL; // next hint_path + +// gi.dprintf("hint_path %s\n", vtos(self->s.origin)); + // is this the first hintpath targeted? if so, we can do this easily. + if(other->monsterinfo.last_hint == NULL) + { + if(self->target) // forward chaining + chain = 1; + else // backward chaining + chain = -1; + } + else + { + // shortcut to last_hint + last = other->monsterinfo.last_hint; + + // make sure it's valid... + if ( (last < g_edicts) || (last >= &g_edicts[globals.num_edicts])) + { + if(g_showlogic && g_showlogic->value) + { + gi.dprintf("bogus last_hint encountered.\n"); + gi.dprintf("detaching from hint path %d\n", chain); + } + hintpath_stop (other); + return; + } + + // if we're an endpoint, then the monster is done moving. + if(self->spawnflags & HINT_ENDPOINT) + { + chain = 0; + } + // if last hint's target is our targetname, it's forward chaining. + else if(last->target && self->targetname && !strcmp(last->target, self->targetname)) + { + chain = 1; + } + // if last hint's targetname is our target, it's backward chaining. + // FIXME - last->targetname was 1, not NULL ???? was a screwed up hintpath + else if(self->target && last->targetname && !strcmp(last->targetname, self->target)) + { + chain = -1; + } + else // if it gets here, i'm not sure how + { + gi.dprintf("hit an uncovered possibility in hint_path_touch\n"); + chain = 0; + } + } + + // find the "next" hint_path + if(chain == 1 && self->target) // forward chaining + next = G_Find(NULL, FOFS(targetname), self->target); + else if(chain == -1 && self->targetname) // backward chaining + next = G_Find(NULL, FOFS(target), self->targetname); + + // if we couldn't find it, have the monster go back to normal hunting. + if(!next) + { + if(g_showlogic && g_showlogic->value) + gi.dprintf("detaching from hint path %d\n", chain); + hintpath_stop(other); + return; + } + + // set the last_hint entry to this hint_path, and + // send him on his way + other->monsterinfo.last_hint = self; + if(g_showlogic && g_showlogic->value) + gi.dprintf("moving to next point, %s\n", vtos(next->s.origin)); + hintpath_go(other, next); + + // have the monster freeze if the hint path we just touched has a wait time + // on it, for example, when riding a plat. + if(self->wait) + { + if(g_showlogic && g_showlogic->value) + gi.dprintf("monster waiting %0.1f\n", self->wait); + other->nextthink = level.time + self->wait; + } + } +} +*/ + +/*QUAKED hint_path (.5 .3 0) (-8 -8 -8) (8 8 8) END +Target: next hint path + +END - set this flag on the endpoints of each hintpath. + +"wait" - set this if you want the monster to freeze when they touch this hintpath +*/ +void SP_hint_path (edict_t *self) +{ + if (deathmatch->value) + { + G_FreeEdict(self); + return; + } + + if (!self->targetname && !self->target) + { + gi.dprintf ("unlinked hint_path at %s\n", vtos(self->s.origin)); + G_FreeEdict (self); + return; + } + + self->solid = SOLID_TRIGGER; + self->touch = hint_path_touch; + VectorSet (self->mins, -8, -8, -8); + VectorSet (self->maxs, 8, 8, 8); + self->svflags |= SVF_NOCLIENT; + gi.linkentity (self); +} + +//int hint_paths_present; +//edict_t *hint_path_start[100]; +//int num_hint_paths; + +// ============ +// InitHintPaths - Called by InitGame (g_save) to enable quick exits if valid +// ============ +void InitHintPaths (void) +{ + edict_t *e, *current; + int field, i, count2; + qboolean errors = false; + + hint_paths_present = 0; + + // check all the hint_paths. + field = FOFS(classname); + e = G_Find(NULL, field, "hint_path"); + if(e) + { +// gi.dprintf("hint paths present on map\n"); + hint_paths_present = 1; + } + else + { +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("hint paths not present on map\n"); + return; + } + + memset (hint_path_start, 0, MAX_HINT_CHAINS*sizeof (edict_t *)); + num_hint_paths = 0; + while(e) + { + if(e->spawnflags & HINT_ENDPOINT) + { + if (e->target) // start point + { + if (e->targetname) // this is a bad end, ignore it + { + gi.dprintf ("Hint path at %s marked as endpoint with both target (%s) and targetname (%s)\n", + vtos (e->s.origin), e->target, e->targetname); + errors = true; + } + else + { + if (num_hint_paths >= MAX_HINT_CHAINS) + { +// gi.dprintf ("Only %d hint chains allowed. Connect some together!\n", MAX_HINT_CHAINS); + break; + } + hint_path_start[num_hint_paths++] = e; + } + } + } + e = G_Find(e, field, "hint_path"); + } + + field = FOFS(targetname); + for (i=0; i< num_hint_paths; i++) + { + count2 = 1; + current = hint_path_start[i]; + current->hint_chain_id = i; +// gi.dprintf ("start "); + e = G_Find(NULL, field, current->target); + if (G_Find(e, field, current->target)) + { + gi.dprintf ("\nForked hint path at %s detected for chain %d, target %s\n", + vtos (current->s.origin), num_hint_paths, current->target); + hint_path_start[i]->hint_chain = NULL; + count2 = 0; + errors = true; + continue; + } + while (e) + { + if (e->hint_chain) + { + gi.dprintf ("\nCircular hint path at %s detected for chain %d, targetname %s\n", + vtos (e->s.origin), num_hint_paths, e->targetname); + hint_path_start[i]->hint_chain = NULL; + count2 = 0; + errors = true; + break; + } + count2++; + current->hint_chain = e; + current = e; + current->hint_chain_id = i; +// gi.dprintf ("-> %s ", current->targetname); + if (!current->target) + break; + e = G_Find(NULL, field, current->target); + if (G_Find(e, field, current->target)) + { + gi.dprintf ("\nForked hint path at %s detected for chain %d, target %s\n", + vtos (current->s.origin), num_hint_paths, current->target); + hint_path_start[i]->hint_chain = NULL; + count2 = 0; + break; + } + } +// if ((g_showlogic) && (g_showlogic->value)) +// if (count2) +// { +// goodcount++; +// gi.dprintf ("\nhint_path #%d, %d elements\n\n", i, count2); +// } +// else +// gi.dprintf ("\nhint_path #%d invalid\n\n", i); + } +// if (errors) +// gi.error ("hint_path processing failed, fix errors\n"); +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("hint_path processing done, %d hint paths linked\n", num_hint_paths); +} + +// ***************************** +// MISCELLANEOUS STUFF +// ***************************** + +// PMM - inback +// use to see if opponent is behind you (not to side) +// if it looks a lot like infront, well, there's a reason + +qboolean inback (edict_t *self, edict_t *other) +{ + vec3_t vec; + float dot; + vec3_t forward; + + AngleVectors (self->s.angles, forward, NULL, NULL); + VectorSubtract (other->s.origin, self->s.origin, vec); + VectorNormalize (vec); + dot = DotProduct (vec, forward); + + if (dot < -0.3) + return true; + return false; +} + +float realrange (edict_t *self, edict_t *other) +{ + vec3_t dir; + + VectorSubtract (self->s.origin, other->s.origin, dir); + + return VectorLength(dir); +} + +qboolean face_wall (edict_t *self) +{ + vec3_t pt; + vec3_t forward; + vec3_t ang; + trace_t tr; + + AngleVectors (self->s.angles, forward, NULL, NULL); + VectorMA(self->s.origin, 64, forward, pt); + tr = gi.trace(self->s.origin, vec3_origin, vec3_origin, pt, self, MASK_MONSTERSOLID); + if(tr.fraction < 1 && !tr.allsolid && !tr.startsolid) + { + vectoangles2(tr.plane.normal, ang); + self->ideal_yaw = ang[YAW] + 180; + if(self->ideal_yaw > 360) + self->ideal_yaw -= 360; + +// if(g_showlogic && g_showlogic->value) +// gi.dprintf("facing wall, dir %0.1f/%0.1f\n", ang[YAW], self->ideal_yaw); + M_ChangeYaw(self); + return true; + } + + return false; +} + +// +// Monster "Bad" Areas +// + +void badarea_touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) +{ +// drawbbox(ent); +} + +edict_t *SpawnBadArea(vec3_t mins, vec3_t maxs, float lifespan, edict_t *owner) +{ + edict_t *badarea; + vec3_t origin; + + VectorAdd(mins, maxs, origin); + VectorScale(origin, 0.5, origin); + + VectorSubtract(maxs, origin, maxs); + VectorSubtract(mins, origin, mins); + + badarea = G_Spawn(); + VectorCopy(origin, badarea->s.origin); + VectorCopy(maxs, badarea->maxs); + VectorCopy(mins, badarea->mins); + badarea->touch = badarea_touch; + badarea->movetype = MOVETYPE_NONE; + badarea->solid = SOLID_TRIGGER; + badarea->classname = "bad_area"; + gi.linkentity (badarea); + +// gi.dprintf("(%s)-(%s)\n", vtos(badarea->absmin), vtos(badarea->absmax)); + + if(lifespan) + { + badarea->think = G_FreeEdict; + badarea->nextthink = level.time + lifespan; + } + if(owner) + { + badarea->owner = owner; + } + +// drawbbox(badarea); + return badarea; +} + +// CheckForBadArea +// This is a customized version of G_TouchTriggers that will check +// for bad area triggers and return them if they're touched. +edict_t *CheckForBadArea(edict_t *ent) +{ + int i, num; + edict_t *touch[MAX_EDICTS], *hit; + vec3_t mins, maxs; + + VectorAdd(ent->s.origin, ent->mins, mins); + VectorAdd(ent->s.origin, ent->maxs, maxs); + + num = gi.BoxEdicts (mins, maxs, touch, MAX_EDICTS, AREA_TRIGGERS); + +// drawbbox(ent); + + // be careful, it is possible to have an entity in this + // list removed before we get to it (killtriggered) + for (i=0 ; iinuse) + continue; + if (hit->touch == badarea_touch) + { + return hit; + } + } + + return NULL; +} + +#define TESLA_DAMAGE_RADIUS 128 + +qboolean MarkTeslaArea(edict_t *self, edict_t *tesla) +{ + vec3_t mins, maxs; + edict_t *e; + edict_t *tail; + edict_t *area; + + if(!tesla || !self) + return false; + + area = NULL; + + // make sure this tesla doesn't have a bad area around it already... + e = tesla->teamchain; + tail = tesla; + while (e) + { + tail = tail->teamchain; + if(!strcmp(e->classname, "bad_area")) + { +// gi.dprintf("tesla already has a bad area marked\n"); + return false; + } + e = e->teamchain; + } + + // see if we can grab the trigger directly + if(tesla->teamchain && tesla->teamchain->inuse) + { + edict_t *trigger; + + trigger = tesla->teamchain; + +// VectorAdd (trigger->s.origin, trigger->mins, mins); +// VectorAdd (trigger->s.origin, trigger->maxs, maxs); + VectorCopy(trigger->absmin, mins); + VectorCopy(trigger->absmax, maxs); + + if(tesla->air_finished) + area = SpawnBadArea (mins, maxs, tesla->air_finished, tesla); + else + area = SpawnBadArea (mins, maxs, tesla->nextthink, tesla); + } + // otherwise we just guess at how long it'll last. + else + { + + VectorSet (mins, -TESLA_DAMAGE_RADIUS, -TESLA_DAMAGE_RADIUS, tesla->mins[2]); + VectorSet (maxs, TESLA_DAMAGE_RADIUS, TESLA_DAMAGE_RADIUS, TESLA_DAMAGE_RADIUS); + + area = SpawnBadArea(mins, maxs, 30, tesla); + } + + // if we spawned a bad area, then link it to the tesla + if(area) + { +// gi.dprintf("bad area marker spawned and linked to tesla\n"); + tail->teamchain = area; + } + return true; +} + +// predictive calculator +// target is who you want to shoot +// start is where the shot comes from +// bolt_speed is how fast the shot is +// eye_height is a boolean to say whether or not to adjust to targets eye_height +// offset is how much time to miss by +// aimdir is the resulting aim direction (pass in NULL if you don't want it) +// aimpoint is the resulting aimpoint (pass in NULL if don't want it) +void PredictAim (edict_t *target, vec3_t start, float bolt_speed, qboolean eye_height, float offset, vec3_t aimdir, vec3_t aimpoint) +{ + vec3_t dir, vec; + float dist, time; + + if (!target || !target->inuse) + { + VectorCopy (vec3_origin, aimdir); + return; + } + + VectorSubtract(target->s.origin, start, dir); + if (eye_height) + dir[2] += target->viewheight; + dist = VectorLength(dir); + time = dist / bolt_speed; + + + VectorMA(target->s.origin, time - offset, target->velocity, vec); + + if (eye_height) + vec[2] += target->viewheight; + + if (aimdir) + { + VectorSubtract (vec, start, aimdir); + VectorNormalize (aimdir); + } + + if (aimpoint) + { + VectorCopy (vec, aimpoint); + } +} + + +qboolean below (edict_t *self, edict_t *other) +{ + vec3_t vec; + float dot; + vec3_t down; + + VectorSubtract (other->s.origin, self->s.origin, vec); + VectorNormalize (vec); + VectorSet (down, 0, 0, -1); + dot = DotProduct (vec, down); + + if (dot > 0.95) // 18 degree arc below + return true; + return false; +} + +void drawbbox (edict_t *self) +{ + int lines[4][3] = { + {1, 2, 4}, + {1, 2, 7}, + {1, 4, 5}, + {2, 4, 7} + }; + + int starts[4] = {0, 3, 5, 6}; + + vec3_t pt[8]; + int i, j, k; + vec3_t coords[2]; + vec3_t newbox; + vec3_t f,r,u, dir; + + VectorCopy (self->absmin, coords[0]); + VectorCopy (self->absmax, coords[1]); + + for (i=0; i<=1; i++) + { + for (j=0; j<=1; j++) + { + for (k=0; k<=1; k++) + { + pt[4*i+2*j+k][0] = coords[i][0]; + pt[4*i+2*j+k][1] = coords[j][1]; + pt[4*i+2*j+k][2] = coords[k][2]; + } + } + } + + for (i=0; i<= 3; i++) + { + for (j=0; j<= 2; j++) + { + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_DEBUGTRAIL); + gi.WritePosition (pt[starts[i]]); + gi.WritePosition (pt[lines[i][j]]); + gi.multicast (pt[starts[i]], MULTICAST_ALL); + } + } + + vectoangles2 (self->s.angles, dir); + AngleVectors (dir, f, r, u); + + VectorMA (self->s.origin, 50, f, newbox); + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_DEBUGTRAIL); + gi.WritePosition (self->s.origin); + gi.WritePosition (newbox); + gi.multicast (self->s.origin, MULTICAST_PVS); + VectorClear (newbox); + + VectorMA (self->s.origin, 50, r, newbox); + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_DEBUGTRAIL); + gi.WritePosition (self->s.origin); + gi.WritePosition (newbox); + gi.multicast (self->s.origin, MULTICAST_PVS); + VectorClear (newbox); + + VectorMA (self->s.origin, 50, u, newbox); + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_DEBUGTRAIL); + gi.WritePosition (self->s.origin); + gi.WritePosition (newbox); + gi.multicast (self->s.origin, MULTICAST_PVS); + VectorClear (newbox); +} + +// +// New dodge code +// +void M_MonsterDodge (edict_t *self, edict_t *attacker, float eta, trace_t *tr) +{ + float r = random(); + float height; + qboolean ducker = false, dodger = false; + + // this needs to be here since this can be called after the monster has "died" + if (self->health < 1) + return; + + if ((self->monsterinfo.duck) && (self->monsterinfo.unduck)) + ducker = true; + if ((self->monsterinfo.sidestep) && !(self->monsterinfo.aiflags & AI_STAND_GROUND)) + dodger = true; + + if ((!ducker) && (!dodger)) + return; + +// if ((g_showlogic) && (g_showlogic->value)) +// { +// if (self->monsterinfo.aiflags & AI_DODGING) +// gi.dprintf ("dodging - "); +// if (self->monsterinfo.aiflags & AI_DUCKED) +// gi.dprintf ("ducked - "); +// } + if (!self->enemy) + { + self->enemy = attacker; + FoundTarget (self); + } + + // PMM - don't bother if it's going to hit anyway; fix for weird in-your-face etas (I was + // seeing numbers like 13 and 14) + if ((eta < 0.1) || (eta > 5)) + { +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("timeout\n"); + return; + } + + // skill level determination.. + if (r > (0.25*((skill->value)+1))) + { +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("skillout\n"); + return; + } + + // stop charging, since we're going to dodge (somehow) instead +// soldier_stop_charge (self); + + if (ducker) + { + height = self->absmax[2]-32-1; // the -1 is because the absmax is s.origin + maxs + 1 + + // FIXME, make smarter + // if we only duck, and ducking won't help or we're already ducking, do nothing + // + // need to add monsterinfo.abort_duck() and monsterinfo.next_duck_time + + if ((!dodger) && ((tr->endpos[2] <= height) || (self->monsterinfo.aiflags & AI_DUCKED))) + return; + } + else + height = self->absmax[2]; + + if (dodger) + { + // if we're already dodging, just finish the sequence, i.e. don't do anything else + if (self->monsterinfo.aiflags & AI_DODGING) + { +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("already dodging\n"); + return; + } + + // if we're ducking already, or the shot is at our knees + if ((tr->endpos[2] <= height) || (self->monsterinfo.aiflags & AI_DUCKED)) + { + vec3_t right, diff; + + AngleVectors (self->s.angles, NULL, right, NULL); + VectorSubtract (tr->endpos, self->s.origin, diff); + + if (DotProduct (right, diff) < 0) + { + self->monsterinfo.lefty = 0; +// gi.dprintf ("left\n"); + } else { + self->monsterinfo.lefty = 1; +// gi.dprintf ("right\n"); + } + + // if we are currently ducked, unduck + + if ((ducker) && (self->monsterinfo.aiflags & AI_DUCKED)) + { +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("unducking - "); + self->monsterinfo.unduck(self); + } + + self->monsterinfo.aiflags |= AI_DODGING; + self->monsterinfo.attack_state = AS_SLIDING; + + // call the monster specific code here + self->monsterinfo.sidestep (self); + return; + } + } + + if (ducker) + { + if (self->monsterinfo.next_duck_time > level.time) + { +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("ducked too often, not ducking\n"); + return; + } + +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("ducking!\n"); + + monster_done_dodge (self); + // set this prematurely; it doesn't hurt, and prevents extra iterations + self->monsterinfo.aiflags |= AI_DUCKED; + + self->monsterinfo.duck (self, eta); + } +} + +void monster_duck_down (edict_t *self) +{ +// if (self->monsterinfo.aiflags & AI_DUCKED) +// return; + self->monsterinfo.aiflags |= AI_DUCKED; + +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("duck down!\n"); +// self->maxs[2] -= 32; + self->maxs[2] = self->monsterinfo.base_height - 32; + self->takedamage = DAMAGE_YES; + if (self->monsterinfo.duck_wait_time < level.time) + self->monsterinfo.duck_wait_time = level.time + 1; + gi.linkentity (self); +} + +void monster_duck_hold (edict_t *self) +{ + if (level.time >= self->monsterinfo.duck_wait_time) + self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; + else + self->monsterinfo.aiflags |= AI_HOLD_FRAME; +} + +void monster_duck_up (edict_t *self) +{ + self->monsterinfo.aiflags &= ~AI_DUCKED; +// self->maxs[2] += 32; + self->maxs[2] = self->monsterinfo.base_height; + self->takedamage = DAMAGE_AIM; + self->monsterinfo.next_duck_time = level.time + DUCK_INTERVAL; + gi.linkentity (self); +} + +//========================= +//========================= +qboolean has_valid_enemy (edict_t *self) +{ + if (!self->enemy) + return false; + + if (!self->enemy->inuse) + return false; + + if (self->enemy->health < 1) + return false; + + return true; +} + +void TargetTesla (edict_t *self, edict_t *tesla) +{ + if ((!self) || (!tesla)) + return; + + // PMM - medic bails on healing things + if (self->monsterinfo.aiflags & AI_MEDIC) + { + if (self->enemy) + cleanupHealTarget(self->enemy); + self->monsterinfo.aiflags &= ~AI_MEDIC; + } + + // store the player enemy in case we lose track of him. + if(self->enemy && self->enemy->client) + self->monsterinfo.last_player_enemy = self->enemy; + + if(self->enemy != tesla) + { + self->oldenemy = self->enemy; + self->enemy = tesla; + if(self->monsterinfo.attack) + { + if (self->health <= 0) + { +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("bad tesla attack avoided!\n"); + return; + } + self->monsterinfo.attack(self); + } + else + { + FoundTarget(self); + } + } +} + +// this returns a randomly selected coop player who is visible to self +// returns NULL if bad + +edict_t * PickCoopTarget (edict_t *self) +{ + // no more than 4 players in coop, so.. + edict_t *targets[4]; + int num_targets = 0, targetID; + edict_t *ent; + int player; + + // if we're not in coop, this is a noop + if (!coop || !coop->value) + return NULL; + + memset (targets, 0, 4*sizeof(edict_t *)); + + for (player = 1; player <= game.maxclients; player++) + { + ent = &g_edicts[player]; + if (!ent->inuse) + continue; + if (!ent->client) + continue; + if (visible(self, ent)) + { +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("%s: found coop player %s - ", self->classname, ent->client->pers.netname); + targets[num_targets++] = ent; + } + } + +/* + ent = g_edicts+1; // skip the worldspawn + // cycle through players + while (ent) + { + if ((ent->client) && (ent->inuse)) + { + if (visible(self, ent)) + { + if ((g_showlogic) && (g_showlogic->value)) + gi.dprintf ("%s: found coop player %s - ", self->classname, ent->client->pers.netname); + targets[num_targets++] = ent; + } + ent++; + } + else + ent = NULL; + } +*/ + + if (!num_targets) + return NULL; + + // get a number from 0 to (num_targets-1) + targetID = (random() * (float)num_targets); + + // just in case we got a 1.0 from random + if (targetID == num_targets) + targetID--; + +// if (g_showlogic && g_showlogic->value) +// gi.dprintf ("using player %s\n", targets[targetID]->client->pers.netname); + return targets[targetID]; +} + +// only meant to be used in coop +int CountPlayers (void) +{ + edict_t *ent; + int count = 0; + int player; + + // if we're not in coop, this is a noop + if (!coop || !coop->value) + return 1; + + for (player = 1; player <= game.maxclients; player++) + { + ent = &g_edicts[player]; + if (!ent->inuse) + continue; + if (!ent->client) + continue; + count++; + } +/* + ent = g_edicts+1; // skip the worldspawn + while (ent) + { + if ((ent->client) && (ent->inuse)) + { + ent++; + count++; + } + else + ent = NULL; + } +*/ + return count; +} + +//******************* +// JUMPING AIDS +//******************* + +void monster_jump_start (edict_t *self) +{ + self->timestamp = level.time; +} + +qboolean monster_jump_finished (edict_t *self) +{ + if ((level.time - self->timestamp) > 3) + { +// if (g_showlogic && g_showlogic->value) +// { +// gi.dprintf("%s jump timed out!\n", self->classname); +// } + return true; + } +} + + + diff --git a/original/rogue/g_newdm.c b/original/rogue/g_newdm.c new file mode 100644 index 0000000..403d2fe --- /dev/null +++ b/original/rogue/g_newdm.c @@ -0,0 +1,386 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// g_newdm.c +// pmack +// june 1998 + +#include "g_local.h" +#include "m_player.h" + +dm_game_rt DMGame; + +// **************************** +// General DM Stuff +// **************************** + +void InitGameRules(void) +{ + int gameNum; + + // clear out the game rule structure before we start + memset(&DMGame, 0, sizeof(dm_game_rt)); + + if(gamerules && gamerules->value) + { + gameNum = gamerules->value; + switch(gameNum) + { + case RDM_TAG: + DMGame.GameInit = Tag_GameInit; + DMGame.PostInitSetup = Tag_PostInitSetup; + DMGame.PlayerDeath = Tag_PlayerDeath; + DMGame.Score = Tag_Score; + DMGame.PlayerEffects = Tag_PlayerEffects; + DMGame.DogTag = Tag_DogTag; + DMGame.PlayerDisconnect = Tag_PlayerDisconnect; + DMGame.ChangeDamage = Tag_ChangeDamage; + break; +/* + case RDM_DEATHBALL: + DMGame.GameInit = DBall_GameInit; + DMGame.ChangeKnockback = DBall_ChangeKnockback; + DMGame.ChangeDamage = DBall_ChangeDamage; + DMGame.ClientBegin = DBall_ClientBegin; + DMGame.SelectSpawnPoint = DBall_SelectSpawnPoint; + DMGame.PostInitSetup = DBall_PostInitSetup; + DMGame.CheckDMRules = DBall_CheckDMRules; + break; +*/ + // reset gamerules if it's not a valid number + default: + gamerules->value = 0; + break; + } + } + + // if we're set up to play, initialize the game as needed. + if(DMGame.GameInit) + DMGame.GameInit(); +} + +//================= +//================= +#define IT_TYPE_MASK (IT_WEAPON|IT_AMMO|IT_POWERUP|IT_ARMOR|IT_KEY) + +extern void ED_CallSpawn (edict_t *ent); +extern qboolean Pickup_Health (edict_t *ent, edict_t *other); +extern qboolean Pickup_Adrenaline (edict_t *ent, edict_t *other); +extern qboolean Pickup_Armor (edict_t *ent, edict_t *other); +extern qboolean Pickup_PowerArmor (edict_t *ent, edict_t *other); + +char *FindSubstituteItem (edict_t *ent) +{ + int i; + int itflags, myflags; + float rnd; + int count; + int pick; + gitem_t *it; + + // there are only two classes of power armor, and we don't want + // to give out power screens. therefore, power shields should + // remain power shields. (powerscreens shouldn't be there at all...) + if (ent->item->pickup == Pickup_PowerArmor) + return NULL; + + // health is special case + if ((ent->item->pickup == Pickup_Health) || (ent->item->pickup == Pickup_Adrenaline)) + { + // health pellets stay health pellets + if(!strcmp(ent->classname, "item_health_small")) + return NULL; + + rnd = random(); + if(rnd < 0.6) + return "item_health"; + else if(rnd < 0.9) + return "item_health_large"; + else if(rnd < 0.99) + return "item_adrenaline"; + else + return "item_health_mega"; + } + // armor is also special case + else if(ent->item->pickup == Pickup_Armor) + { + // armor shards stay armor shards + if (ent->item->tag == ARMOR_SHARD) + return NULL; + + rnd = random(); + if(rnd < 0.6) + return "item_armor_jacket"; + else if(rnd < 0.9) + return "item_armor_combat"; + else + return "item_armor_body"; + } + + + // we want to stay within the item class + myflags = ent->item->flags & IT_TYPE_MASK; + if ((myflags & IT_AMMO) && (myflags & IT_WEAPON)) + myflags = IT_AMMO; + + count = 0; + + // first pass, count the matching items + it = itemlist; + for (i=0 ; iflags; + + if (!itflags || (itflags & IT_NOT_GIVEABLE)) + continue; + + // prox,grenades,etc should count as ammo. + if ((itflags & IT_AMMO) && (itflags & IT_WEAPON)) + itflags = IT_AMMO; + + // don't respawn spheres if they're dmflag disabled. + if ( (int)dmflags->value & DF_NO_SPHERES ) + { + if (!strcmp (ent->classname, "item_sphere_vengeance") || + !strcmp (ent->classname, "item_sphere_hunter") || + !strcmp (ent->classname, "item_spehre_defender")) + { + continue; + } + } + + if ( ((int)dmflags->value & DF_NO_NUKES) && !strcmp(ent->classname, "ammo_nuke") ) + continue; + + if ( ((int)dmflags->value & DF_NO_MINES) && + (!strcmp(ent->classname, "ammo_prox") || !strcmp(ent->classname, "ammo_tesla"))) + continue; + + if ((itflags & IT_TYPE_MASK) == (myflags & IT_TYPE_MASK)) + count++; + } + + if(!count) + return NULL; + + pick = ceil(random() * count); + count = 0; + + // second pass, pick one. + it = itemlist; + for (i=0 ; iflags; + + if (!itflags || (itflags & IT_NOT_GIVEABLE)) + continue; + + // prox,grenades,etc should count as ammo. + if ((itflags & IT_AMMO) && (itflags & IT_WEAPON)) + itflags = IT_AMMO; + + if ( ((int)dmflags->value & DF_NO_NUKES) && !strcmp(ent->classname, "ammo_nuke") ) + continue; + + if ( ((int)dmflags->value & DF_NO_MINES) && + (!strcmp(ent->classname, "ammo_prox") || !strcmp(ent->classname, "ammo_tesla"))) + continue; + + if ((itflags & IT_TYPE_MASK) == (myflags & IT_TYPE_MASK)) + { + count++; + if(pick == count) + return it->classname; + } + } + + return NULL; +} + +//================= +//================= +edict_t *DoRandomRespawn (edict_t *ent) +{ + edict_t *newEnt; + char *classname; + + classname = FindSubstituteItem (ent); + if (classname == NULL) + return NULL; + + gi.unlinkentity (ent); + + newEnt = G_Spawn(); + newEnt->classname = classname; + VectorCopy (ent->s.origin, newEnt->s.origin); + VectorCopy (ent->s.old_origin, newEnt->s.old_origin); + VectorCopy (ent->mins, newEnt->mins); + VectorCopy (ent->maxs, newEnt->maxs); + + VectorSet (newEnt->gravityVector, 0, 0, -1); + + ED_CallSpawn (newEnt); + + newEnt->s.renderfx |= RF_IR_VISIBLE; + + return newEnt; +} + +//================= +//================= +void PrecacheForRandomRespawn (void) +{ + gitem_t *it; + int i; + int itflags; + + it = itemlist; + for (i=0 ; iflags; + + if (!itflags || (itflags & IT_NOT_GIVEABLE)) + continue; + + PrecacheItem(it); + } +} + +// *************************** +// DOPPLEGANGER +// *************************** + +extern edict_t *Sphere_Spawn (edict_t *owner, int spawnflags); + +void fire_doppleganger (edict_t *ent, vec3_t start, vec3_t aimdir); +void doppleganger_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point); + +void doppleganger_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + edict_t *sphere; + float dist; + vec3_t dir; + + if((self->enemy) && (self->enemy != self->teammaster)) + { + VectorSubtract(self->enemy->s.origin, self->s.origin, dir); + dist = VectorLength(dir); + + if(dist > 768) + { + sphere = Sphere_Spawn (self, SPHERE_HUNTER | SPHERE_DOPPLEGANGER); + sphere->pain(sphere, attacker, 0, 0); + } + else //if(dist > 256) + { + sphere = Sphere_Spawn (self, SPHERE_VENGEANCE | SPHERE_DOPPLEGANGER); + sphere->pain(sphere, attacker, 0, 0); + } +// else +// { +// T_RadiusClassDamage (self, self->teammaster, 175, "doppleganger", 384, MOD_DOPPLE_EXPLODE); +// } + } + + if(self->teamchain) + BecomeExplosion1(self->teamchain); + BecomeExplosion1(self); +} + +void doppleganger_pain (edict_t *self, edict_t *other, float kick, int damage) +{ + self->enemy = other; +} + +void doppleganger_timeout (edict_t *self) +{ +// T_RadiusClassDamage (self, self->teammaster, 140, "doppleganger", 256, MOD_DOPPLE_EXPLODE); + + if(self->teamchain) + BecomeExplosion1(self->teamchain); + BecomeExplosion1(self); +} + +void body_think (edict_t *self) +{ + float r; + + if(abs(self->ideal_yaw - anglemod(self->s.angles[YAW])) < 2) + { + if(self->timestamp < level.time) + { + r = random(); + if(r < 0.10) + { + self->ideal_yaw = random() * 350.0; + self->timestamp = level.time + 1; + } + } + } + else + M_ChangeYaw(self); + + self->s.frame ++; + if (self->s.frame > FRAME_stand40) + self->s.frame = FRAME_stand01; + + self->nextthink = level.time + 0.1; +} + +void fire_doppleganger (edict_t *ent, vec3_t start, vec3_t aimdir) +{ + edict_t *base; + edict_t *body; + vec3_t dir; + vec3_t forward, right, up; + int number; + + vectoangles2 (aimdir, dir); + AngleVectors (dir, forward, right, up); + + base = G_Spawn(); + VectorCopy (start, base->s.origin); + VectorCopy (dir, base->s.angles); + VectorClear (base->velocity); + VectorClear (base->avelocity); + base->movetype = MOVETYPE_TOSS; + base->solid = SOLID_BBOX; + base->s.renderfx |= RF_IR_VISIBLE; + base->s.angles[PITCH]=0; + VectorSet (base->mins, -16, -16, -24); + VectorSet (base->maxs, 16, 16, 32); +// base->s.modelindex = gi.modelindex ("models/objects/dopplebase/tris.md2"); + base->s.modelindex = 0; + base->teammaster = ent; + base->svflags |= SVF_DAMAGEABLE; + base->takedamage = DAMAGE_AIM; + base->health = 30; + base->pain = doppleganger_pain; + base->die = doppleganger_die; + + // FIXME - remove with style + base->nextthink = level.time + 30; + base->think = doppleganger_timeout; + + base->classname = "doppleganger"; + + gi.linkentity (base); + + body = G_Spawn(); + number = body->s.number; + body->s = ent->s; + body->s.sound = 0; + body->s.event = 0; +// body->s.modelindex2 = 0; // no attached items (CTF flag, etc) + body->s.number = number; + body->yaw_speed = 30; + body->ideal_yaw = 0; + VectorCopy (start, body->s.origin); + body->s.origin[2] += 8; + body->think = body_think; + body->nextthink = level.time + FRAMETIME; + gi.linkentity (body); + + base->teamchain = body; + body->teammaster = base; +} + diff --git a/original/rogue/g_newfnc.c b/original/rogue/g_newfnc.c new file mode 100644 index 0000000..a3815d0 --- /dev/null +++ b/original/rogue/g_newfnc.c @@ -0,0 +1,349 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +#include "g_local.h" + +//void plat_CalcMove (edict_t *ent, vec3_t dest, void(*func)(edict_t*)); +void Move_Calc (edict_t *ent, vec3_t dest, void(*func)(edict_t*)); + +void fd_secret_move1(edict_t *self); +void fd_secret_move2(edict_t *self); +void fd_secret_move3(edict_t *self); +void fd_secret_move4(edict_t *self); +void fd_secret_move5(edict_t *self); +void fd_secret_move6(edict_t *self); +void fd_secret_done(edict_t *self); + +/* +============================================================================= + +SECRET DOORS + +============================================================================= +*/ + +#define SEC_OPEN_ONCE 1 // stays open +#define SEC_1ST_LEFT 2 // 1st move is left of arrow +#define SEC_1ST_DOWN 4 // 1st move is down from arrow +#define SEC_NO_SHOOT 8 // only opened by trigger +#define SEC_YES_SHOOT 16 // shootable even if targeted +#define SEC_MOVE_RIGHT 32 +#define SEC_MOVE_FORWARD 64 + +void fd_secret_use (edict_t *self, edict_t *other, edict_t *activator) +{ + edict_t *ent; + +// gi.dprintf("fd_secret_use\n"); + if (self->flags & FL_TEAMSLAVE) + return; + + // trigger all paired doors + for (ent = self ; ent ; ent = ent->teamchain) + Move_Calc(ent, ent->moveinfo.start_origin, fd_secret_move1); + +} + +void fd_secret_killed (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ +// gi.dprintf("fd_secret_killed\n"); + self->health = self->max_health; + self->takedamage = DAMAGE_NO; + + if (self->flags & FL_TEAMSLAVE && self->teammaster && self->teammaster->takedamage != DAMAGE_NO) + fd_secret_killed (self->teammaster, inflictor, attacker, damage, point); + else + fd_secret_use (self, inflictor, attacker); +} + +// Wait after first movement... +void fd_secret_move1(edict_t *self) +{ +// gi.dprintf("fd_secret_move1\n"); + self->nextthink = level.time + 1.0; + self->think = fd_secret_move2; +} + +// Start moving sideways w/sound... +void fd_secret_move2(edict_t *self) +{ +// gi.dprintf("fd_secret_move2\n"); + Move_Calc(self, self->moveinfo.end_origin, fd_secret_move3); +} + +// Wait here until time to go back... +void fd_secret_move3(edict_t *self) +{ +// gi.dprintf("fd_secret_move3\n"); + if (!(self->spawnflags & SEC_OPEN_ONCE)) + { + self->nextthink = level.time + self->wait; + self->think = fd_secret_move4; + } +} + +// Move backward... +void fd_secret_move4(edict_t *self) +{ +// gi.dprintf("fd_secret_move4\n"); + Move_Calc(self, self->moveinfo.start_origin, fd_secret_move5); +} + +// Wait 1 second... +void fd_secret_move5(edict_t *self) +{ +// gi.dprintf("fd_secret_move5\n"); + self->nextthink = level.time + 1.0; + self->think = fd_secret_move6; +} + +void fd_secret_move6(edict_t *self) +{ +// gi.dprintf("fd_secret_move6\n"); + Move_Calc(self, self->move_origin, fd_secret_done); +} + +void fd_secret_done(edict_t *self) +{ +// gi.dprintf("fd_secret_done\n"); + if (!self->targetname || self->spawnflags & SEC_YES_SHOOT) + { + self->health = 1; + self->takedamage = DAMAGE_YES; + self->die = fd_secret_killed; + } +} + +void secret_blocked(edict_t *self, edict_t *other) +{ + if (!(self->flags & FL_TEAMSLAVE)) + T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 0, 0, MOD_CRUSH); + +// 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(edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (other->health <= 0) + return; + + if (!(other->client)) + return; + + if (self->monsterinfo.attack_finished > level.time) + return; + + self->monsterinfo.attack_finished = level.time + 2; + + if (self->message) + { + gi.centerprintf (other, self->message); +// fixme - put this sound back?? +// gi.sound (other, CHAN_BODY, "misc/talk.wav", 1, ATTN_NORM); + } +} + + +/*QUAKED func_door_secret2 (0 .5 .8) ? open_once 1st_left 1st_down no_shoot always_shoot slide_right slide_forward +Basic secret door. Slides back, then to the left. Angle determines direction. + +FLAGS: +open_once = not implemented yet +1st_left = 1st move is left/right of arrow +1st_down = 1st move is forwards/backwards +no_shoot = not implemented yet +always_shoot = even if targeted, keep shootable +reverse_left = the sideways move will be to right of arrow +reverse_back = the to/fro move will be forward + +VALUES: +wait = # of seconds before coming back (5 default) +dmg = damage to inflict when blocked (2 default) + +*/ + +void SP_func_door_secret2 (edict_t *ent) +{ + vec3_t forward,right,up; + float lrSize, fbSize; + + ent->moveinfo.sound_start = gi.soundindex ("doors/dr1_strt.wav"); + ent->moveinfo.sound_middle = gi.soundindex ("doors/dr1_mid.wav"); + ent->moveinfo.sound_end = gi.soundindex ("doors/dr1_end.wav"); + + if (!ent->dmg) + ent->dmg = 2; + + AngleVectors(ent->s.angles, forward, right, up); + VectorCopy(ent->s.origin, ent->move_origin); + VectorCopy(ent->s.angles, ent->move_angles); + + G_SetMovedir (ent->s.angles, ent->movedir); + ent->movetype = MOVETYPE_PUSH; + ent->solid = SOLID_BSP; + gi.setmodel (ent, ent->model); + + if(ent->move_angles[1] == 0 || ent->move_angles[1] == 180) + { + lrSize = ent->size[1]; + fbSize = ent->size[0]; + } + else if(ent->move_angles[1] == 90 || ent->move_angles[1] == 270) + { + lrSize = ent->size[0]; + fbSize = ent->size[1]; + } + else + { + gi.dprintf("Secret door not at 0,90,180,270!\n"); + } + + if(ent->spawnflags & SEC_MOVE_FORWARD) + VectorScale(forward, fbSize, forward); + else + { + VectorScale(forward, fbSize * -1 , forward); + } + + if(ent->spawnflags & SEC_MOVE_RIGHT) + VectorScale(right, lrSize, right); + else + { + VectorScale(right, lrSize * -1, right); + } + + if(ent->spawnflags & SEC_1ST_DOWN) + { + VectorAdd(ent->s.origin, forward, ent->moveinfo.start_origin); + VectorAdd(ent->moveinfo.start_origin, right, ent->moveinfo.end_origin); + } + else + { + VectorAdd(ent->s.origin, right, ent->moveinfo.start_origin); + VectorAdd(ent->moveinfo.start_origin, forward, ent->moveinfo.end_origin); + } + + ent->touch = secret_touch; + ent->blocked = secret_blocked; + ent->use = fd_secret_use; + ent->moveinfo.speed = 50; + ent->moveinfo.accel = 50; + ent->moveinfo.decel = 50; + + if (!ent->targetname || ent->spawnflags & SEC_YES_SHOOT) + { + ent->health = 1; + ent->max_health = ent->health; + ent->takedamage = DAMAGE_YES; + ent->die = fd_secret_killed; + } + if (!ent->wait) + ent->wait = 5; // 5 seconds before closing + + gi.linkentity(ent); +} + +// ================================================== + +#define FWALL_START_ON 1 + +void force_wall_think(edict_t *self) +{ + if(!self->wait) + { + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_FORCEWALL); + gi.WritePosition (self->pos1); + gi.WritePosition (self->pos2); + gi.WriteByte (self->style); + gi.multicast (self->offset, MULTICAST_PVS); + } + + self->think = force_wall_think; + self->nextthink = level.time + 0.1; +} + +void force_wall_use (edict_t *self, edict_t *other, edict_t *activator) +{ + if(!self->wait) + { + self->wait = 1; + self->think = NULL; + self->nextthink = 0; + self->solid = SOLID_NOT; + gi.linkentity( self ); + } + else + { + self->wait = 0; + self->think = force_wall_think; + self->nextthink = level.time + 0.1; + self->solid = SOLID_BSP; + KillBox(self); // Is this appropriate? + gi.linkentity (self); + } +} + +/*QUAKED func_force_wall (1 0 1) ? start_on +A vertical particle force wall. Turns on and solid when triggered. +If someone is in the force wall when it turns on, they're telefragged. + +start_on - forcewall begins activated. triggering will turn it off. +style - color of particles to use. + 208: green, 240: red, 241: blue, 224: orange +*/ +void SP_func_force_wall(edict_t *ent) +{ + gi.setmodel (ent, ent->model); + + ent->offset[0] = (ent->absmax[0] + ent->absmin[0]) / 2; + ent->offset[1] = (ent->absmax[1] + ent->absmin[1]) / 2; + ent->offset[2] = (ent->absmax[2] + ent->absmin[2]) / 2; + + ent->pos1[2] = ent->absmax[2]; + ent->pos2[2] = ent->absmax[2]; + if(ent->size[0] > ent->size[1]) + { + ent->pos1[0] = ent->absmin[0]; + ent->pos2[0] = ent->absmax[0]; + ent->pos1[1] = ent->offset[1]; + ent->pos2[1] = ent->offset[1]; + } + else + { + ent->pos1[0] = ent->offset[0]; + ent->pos2[0] = ent->offset[0]; + ent->pos1[1] = ent->absmin[1]; + ent->pos2[1] = ent->absmax[1]; + } + + if(!ent->style) + ent->style = 208; + + ent->movetype = MOVETYPE_NONE; + ent->wait = 1; + + if(ent->spawnflags & FWALL_START_ON) + { + ent->solid = SOLID_BSP; + ent->think = force_wall_think; + ent->nextthink = level.time + 0.1; + } + else + ent->solid = SOLID_NOT; + + ent->use = force_wall_use; + + ent->svflags = SVF_NOCLIENT; + + gi.linkentity(ent); +} diff --git a/original/rogue/g_newtarg.c b/original/rogue/g_newtarg.c new file mode 100644 index 0000000..b3ad1f6 --- /dev/null +++ b/original/rogue/g_newtarg.c @@ -0,0 +1,354 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +#include "g_local.h" + +//========================================================== + +/*QUAKED target_steam (1 0 0) (-8 -8 -8) (8 8 8) +Creates a steam effect (particles w/ velocity in a line). + + speed = velocity of particles (default 50) + count = number of particles (default 32) + sounds = color of particles (default 8 for steam) + the color range is from this color to this color + 6 + wait = seconds to run before stopping (overrides default + value derived from func_timer) + + best way to use this is to tie it to a func_timer that "pokes" + it every second (or however long you set the wait time, above) + + note that the width of the base is proportional to the speed + good colors to use: + 6-9 - varying whites (darker to brighter) + 224 - sparks + 176 - blue water + 80 - brown water + 208 - slime + 232 - blood +*/ + +void use_target_steam (edict_t *self, edict_t *other, edict_t *activator) +{ + // FIXME - this needs to be a global + static int nextid; + vec3_t point; + + if (nextid > 20000) + nextid = nextid %20000; + + nextid++; + + // automagically set wait from func_timer unless they set it already, or + // default to 1000 if not called by a func_timer (eek!) + if (!self->wait) + if (other) + self->wait = other->wait * 1000; + else + self->wait = 1000; + + if (self->enemy) + { + VectorMA (self->enemy->absmin, 0.5, self->enemy->size, point); + VectorSubtract (point, self->s.origin, self->movedir); + VectorNormalize (self->movedir); + } + + VectorMA (self->s.origin, self->plat2flags*0.5, self->movedir, point); + if (self->wait > 100) + { + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_STEAM); + gi.WriteShort (nextid); + gi.WriteByte (self->count); + gi.WritePosition (self->s.origin); + gi.WriteDir (self->movedir); + gi.WriteByte (self->sounds&0xff); + gi.WriteShort ( (short int)(self->plat2flags) ); + gi.WriteLong ( (int)(self->wait) ); + gi.multicast (self->s.origin, MULTICAST_PVS); + } + else + { + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_STEAM); + gi.WriteShort ((short int)-1); + gi.WriteByte (self->count); + gi.WritePosition (self->s.origin); + gi.WriteDir (self->movedir); + gi.WriteByte (self->sounds&0xff); + gi.WriteShort ( (short int)(self->plat2flags) ); + gi.multicast (self->s.origin, MULTICAST_PVS); + } +} + +void target_steam_start (edict_t *self) +{ + edict_t *ent; + + self->use = use_target_steam; + + if (self->target) + { + ent = G_Find (NULL, FOFS(targetname), self->target); + if (!ent) + gi.dprintf ("%s at %s: %s is a bad target\n", self->classname, vtos(self->s.origin), self->target); + self->enemy = ent; + } + else + { + G_SetMovedir (self->s.angles, self->movedir); + } + + if (!self->count) + self->count = 32; + if (!self->plat2flags) + self->plat2flags = 75; + if (!self->sounds) + self->sounds = 8; + if (self->wait) + self->wait *= 1000; // we want it in milliseconds, not seconds + + // paranoia is good + self->sounds &= 0xff; + self->count &= 0xff; + + self->svflags = SVF_NOCLIENT; + + gi.linkentity (self); +} + +void SP_target_steam (edict_t *self) +{ + self->plat2flags = self->speed; + + if (self->target) + { + self->think = target_steam_start; + self->nextthink = level.time + 1; + } + else + target_steam_start (self); +} + + +//========================================================== +// target_anger +//========================================================== + +void target_anger_use (edict_t *self, edict_t *other, edict_t *activator) +{ + edict_t *target; + edict_t *t; + + t = NULL; + target = G_Find (t, FOFS(targetname), self->killtarget); + + if (target && self->target) + { + // Make whatever a "good guy" so the monster will try to kill it! + target->monsterinfo.aiflags |= AI_GOOD_GUY; + target->svflags |= SVF_MONSTER; + target->health = 300; + + t = NULL; + while ((t = G_Find (t, FOFS(targetname), self->target))) + { + if (t == self) + { + gi.dprintf ("WARNING: entity used itself.\n"); + } + else + { + if (t->use) + { + if (t->health < 0) + { +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("target_anger with dead monster!\n"); + return; + } + t->enemy = target; + t->monsterinfo.aiflags |= AI_TARGET_ANGER; + FoundTarget (t); + } + } + if (!self->inuse) + { + gi.dprintf("entity was removed while using targets\n"); + return; + } + } + } + +} + +/*QUAKED target_anger (1 0 0) (-8 -8 -8) (8 8 8) +This trigger will cause an entity to be angry at another entity when a player touches it. Target the +entity you want to anger, and killtarget the entity you want it to be angry at. + +target - entity to piss off +killtarget - entity to be pissed off at +*/ +void SP_target_anger (edict_t *self) +{ + if (!self->target) + { + gi.dprintf("target_anger without target!\n"); + G_FreeEdict (self); + return; + } + if (!self->killtarget) + { + gi.dprintf("target_anger without killtarget!\n"); + G_FreeEdict (self); + return; + } + + self->use = target_anger_use; + self->svflags = SVF_NOCLIENT; +} + +// ================ +// target_spawn +// ================ +/* +extern edict_t *CreateMonster(vec3_t origin, vec3_t angles, char *classname); + +void target_spawn_use (edict_t *self, edict_t *other, edict_t *activator) +{ + edict_t *newEnt; + + newEnt = CreateMonster (self->s.origin, self->s.angles, "monster_infantry"); + if(newEnt) + newEnt->enemy = other; +} +*/ + +/*Q U AKED target_spawn (1 0 0) (-32 -32 -24) (32 32 72) +*/ +/* +void SP_target_spawn (edict_t *self) +{ + self->use = target_spawn_use; + self->svflags = SVF_NOCLIENT; +} +*/ + +// *********************************** +// target_killplayers +// *********************************** + +void target_killplayers_use (edict_t *self, edict_t *other, edict_t *activator) +{ + int i; + edict_t *ent, *player; + + // kill the players + for (i=0 ; iinuse) + continue; + + // nail it + T_Damage (player, self, self, vec3_origin, self->s.origin, vec3_origin, 100000, 0, DAMAGE_NO_PROTECTION, MOD_TELEFRAG); + } + + // kill any visible monsters + for (ent = g_edicts; ent < &g_edicts[globals.num_edicts] ; ent++) + { + if (!ent->inuse) + continue; + if (ent->health < 1) + continue; + if (!ent->takedamage) + continue; + + for(i=0;iinuse) + continue; + + if(visible(player, ent)) + { + T_Damage (ent, self, self, vec3_origin, ent->s.origin, vec3_origin, + ent->health, 0, DAMAGE_NO_PROTECTION, MOD_TELEFRAG); + break; + } + } + } + +} + +/*QUAKED target_killplayers (1 0 0) (-8 -8 -8) (8 8 8) +When triggered, this will kill all the players on the map. +*/ +void SP_target_killplayers (edict_t *self) +{ + self->use = target_killplayers_use; + self->svflags = SVF_NOCLIENT; +} + +/*QUAKED target_blacklight (1 0 1) (-16 -16 -24) (16 16 24) +Pulsing black light with sphere in the center +*/ +void blacklight_think (edict_t *self) +{ + self->s.angles[0] = rand()%360; + self->s.angles[1] = rand()%360; + self->s.angles[2] = rand()%360; + self->nextthink = level.time + 0.1; +} + +void SP_target_blacklight(edict_t *ent) +{ + if (deathmatch->value) + { // auto-remove for deathmatch + G_FreeEdict (ent); + return; + } + + VectorClear (ent->mins); + VectorClear (ent->maxs); + + ent->s.effects |= (EF_TRACKERTRAIL|EF_TRACKER); + ent->think = blacklight_think; + ent->s.modelindex = gi.modelindex ("models/items/spawngro2/tris.md2"); + ent->s.frame = 1; + ent->nextthink = level.time + 0.1; + gi.linkentity (ent); +} + +/*QUAKED target_orb (1 0 1) (-16 -16 -24) (16 16 24) +Translucent pulsing orb with speckles +*/ +void orb_think (edict_t *self) +{ + self->s.angles[0] = rand()%360; + self->s.angles[1] = rand()%360; + self->s.angles[2] = rand()%360; +// self->s.effects |= (EF_TRACKERTRAIL|EF_DOUBLE); + self->nextthink = level.time + 0.1; +} + +void SP_target_orb(edict_t *ent) +{ + if (deathmatch->value) + { // auto-remove for deathmatch + G_FreeEdict (ent); + return; + } + + VectorClear (ent->mins); + VectorClear (ent->maxs); + +// ent->s.effects |= EF_TRACKERTRAIL; + ent->think = orb_think; + ent->nextthink = level.time + 0.1; + ent->s.modelindex = gi.modelindex ("models/items/spawngro2/tris.md2"); + ent->s.frame = 2; + ent->s.effects |= EF_SPHERETRANS; + gi.linkentity (ent); +} + diff --git a/original/rogue/g_newtrig.c b/original/rogue/g_newtrig.c new file mode 100644 index 0000000..1457262 --- /dev/null +++ b/original/rogue/g_newtrig.c @@ -0,0 +1,178 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// g_newtrig.c +// pmack +// october 1997 + +#include "g_local.h" + +#define TELEPORT_PLAYER_ONLY 1 +#define TELEPORT_SILENT 2 +#define TELEPORT_CTF_ONLY 4 +#define TELEPORT_START_ON 8 + +extern void TeleportEffect (vec3_t origin); + +/*QUAKED info_teleport_destination (.5 .5 .5) (-16 -16 -24) (16 16 32) +Destination marker for a teleporter. +*/ +void SP_info_teleport_destination (edict_t *self) +{ +} + +/*QUAKED trigger_teleport (.5 .5 .5) ? player_only silent ctf_only start_on +Any object touching this will be transported to the corresponding +info_teleport_destination entity. You must set the "target" field, +and create an object with a "targetname" field that matches. + +If the trigger_teleport has a targetname, it will only teleport +entities when it has been fired. + +player_only: only players are teleported +silent: +ctf_only: +start_on: when trigger has targetname, start active, deactivate when used. +*/ +void trigger_teleport_touch(edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + edict_t *dest; + int i; + + if(/*(self->spawnflags & TELEPORT_PLAYER_ONLY) &&*/ !(other->client)) + return; + + if(self->delay) + return; + + dest = G_Find (NULL, FOFS(targetname), self->target); + if(!dest) + { + gi.dprintf("Teleport Destination not found!\n"); + return; + } + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_TELEPORT_EFFECT); + gi.WritePosition (other->s.origin); + gi.multicast (other->s.origin, MULTICAST_PVS); + + // unlink to make sure it can't possibly interfere with KillBox + gi.unlinkentity (other); + + VectorCopy (dest->s.origin, other->s.origin); + VectorCopy (dest->s.origin, other->s.old_origin); + other->s.origin[2] += 10; + + // clear the velocity and hold them in place briefly + VectorClear (other->velocity); + if(other->client) + { + other->client->ps.pmove.pm_time = 160>>3; // hold time + other->client->ps.pmove.pm_flags |= PMF_TIME_TELEPORT; + + // draw the teleport splash at source and on the player +// self->s.event = EV_PLAYER_TELEPORT; + other->s.event = EV_PLAYER_TELEPORT; + + // set angles + for (i=0 ; i<3 ; i++) + other->client->ps.pmove.delta_angles[i] = ANGLE2SHORT(dest->s.angles[i] - other->client->resp.cmd_angles[i]); + + VectorClear (other->client->ps.viewangles); + VectorClear (other->client->v_angle); + } + + + VectorClear (other->s.angles); + + // kill anything at the destination + KillBox (other); + + gi.linkentity (other); +} + +void trigger_teleport_use (edict_t *self, edict_t *other, edict_t *activator) +{ + if(self->delay) + self->delay = 0; + else + self->delay = 1; +} + +void SP_trigger_teleport(edict_t *self) +{ + if (!self->wait) + self->wait = 0.2; + + self->delay = 0; + + if (self->targetname) + { + self->use = trigger_teleport_use; + if(!(self->spawnflags & TELEPORT_START_ON)) + self->delay = 1; + } + + self->touch = trigger_teleport_touch; + + self->solid = SOLID_TRIGGER; + self->movetype = MOVETYPE_NONE; +// self->flags |= FL_NOCLIENT; + + if (!VectorCompare(self->s.angles, vec3_origin)) + G_SetMovedir (self->s.angles, self->movedir); + + gi.setmodel (self, self->model); + gi.linkentity (self); +} + +// *************************** +// TRIGGER_DISGUISE +// *************************** + +/*QUAKED trigger_disguise (.5 .5 .5) ? TOGGLE START_ON REMOVE +Anything passing through this trigger when it is active will +be marked as disguised. + +TOGGLE - field is turned off and on when used. +START_ON - field is active when spawned. +REMOVE - field removes the disguise +*/ + +void trigger_disguise_touch(edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (other->client) + { + if(self->spawnflags & 4) + other->flags &= ~FL_DISGUISED; + else + other->flags |= FL_DISGUISED; + } +} + +void trigger_disguise_use (edict_t *self, edict_t *other, edict_t *activator) +{ + if(self->solid == SOLID_NOT) + self->solid = SOLID_TRIGGER; + else + self->solid = SOLID_NOT; + + gi.linkentity(self); +} + +void SP_trigger_disguise (edict_t *self) +{ + if(self->spawnflags & 2) + self->solid = SOLID_TRIGGER; + else + self->solid = SOLID_NOT; + + self->touch = trigger_disguise_touch; + self->use = trigger_disguise_use; + self->movetype = MOVETYPE_NONE; + self->svflags = SVF_NOCLIENT; + + gi.setmodel (self, self->model); + gi.linkentity(self); + +} diff --git a/original/rogue/g_newweap.c b/original/rogue/g_newweap.c new file mode 100644 index 0000000..02cc673 --- /dev/null +++ b/original/rogue/g_newweap.c @@ -0,0 +1,2283 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +#include "g_local.h" + +#define INCLUDE_ETF_RIFLE 1 +#define INCLUDE_PROX 1 +//#define INCLUDE_FLAMETHROWER 1 +//#define INCLUDE_INCENDIARY 1 +#define INCLUDE_NUKE 1 +#define INCLUDE_MELEE 1 +#define INCLUDE_TESLA 1 +#define INCLUDE_BEAMS 1 + +extern void check_dodge (edict_t *self, vec3_t start, vec3_t dir, int speed); +extern void hurt_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf); +extern void droptofloor (edict_t *ent); +extern void Grenade_Explode (edict_t *ent); + +extern void drawbbox (edict_t *ent); + +#ifdef INCLUDE_ETF_RIFLE +/* +======================== +fire_flechette +======================== +*/ +void flechette_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + vec3_t dir; + + if (other == self->owner) + return; + + if (surf && (surf->flags & SURF_SKY)) + { + G_FreeEdict (self); + return; + } + + if (self->client) + PlayerNoise(self->owner, self->s.origin, PNOISE_IMPACT); + + if (other->takedamage) + { +//gi.dprintf("t_damage %s\n", other->classname); + T_Damage (other, self, self->owner, self->velocity, self->s.origin, plane->normal, + self->dmg, self->dmg_radius, DAMAGE_NO_REG_ARMOR, MOD_ETF_RIFLE); + } + else + { + if(!plane) + VectorClear (dir); + else + VectorScale (plane->normal, 256, dir); + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_FLECHETTE); + gi.WritePosition (self->s.origin); + gi.WriteDir (dir); + gi.multicast (self->s.origin, MULTICAST_PVS); + +// T_RadiusDamage(self, self->owner, 24, self, 48, MOD_ETF_RIFLE); + } + + G_FreeEdict (self); +} + +void fire_flechette (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int kick) +{ + edict_t *flechette; + + VectorNormalize (dir); + + flechette = G_Spawn(); + VectorCopy (start, flechette->s.origin); + VectorCopy (start, flechette->s.old_origin); + vectoangles2 (dir, flechette->s.angles); + + VectorScale (dir, speed, flechette->velocity); + flechette->movetype = MOVETYPE_FLYMISSILE; + flechette->clipmask = MASK_SHOT; + flechette->solid = SOLID_BBOX; + flechette->s.renderfx = RF_FULLBRIGHT; + VectorClear (flechette->mins); + VectorClear (flechette->maxs); + + flechette->s.modelindex = gi.modelindex ("models/proj/flechette/tris.md2"); + +// flechette->s.sound = gi.soundindex (""); // FIXME - correct sound! + flechette->owner = self; + flechette->touch = flechette_touch; + flechette->nextthink = level.time + 8000/speed; + flechette->think = G_FreeEdict; + flechette->dmg = damage; + flechette->dmg_radius = kick; + + gi.linkentity (flechette); + + if (self->client) + check_dodge (self, flechette->s.origin, dir, speed); +} +#endif + +// ************************** +// PROX +// ************************** + +#ifdef INCLUDE_PROX +#define PROX_TIME_TO_LIVE 45 // 45, 30, 15, 10 +#define PROX_TIME_DELAY 0.5 +#define PROX_BOUND_SIZE 96 +#define PROX_DAMAGE_RADIUS 192 +#define PROX_HEALTH 20 +#define PROX_DAMAGE 90 + +//=============== +//=============== +void Prox_Explode (edict_t *ent) +{ + vec3_t origin; + edict_t *owner; + +// free the trigger field + + //PMM - changed teammaster to "mover" .. owner of the field is the prox + if(ent->teamchain && ent->teamchain->owner == ent) + G_FreeEdict(ent->teamchain); + + owner = ent; + if(ent->teammaster) + { + owner = ent->teammaster; + PlayerNoise(owner, ent->s.origin, PNOISE_IMPACT); + } + + // play quad sound if appopriate + if (ent->dmg > PROX_DAMAGE) + gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage3.wav"), 1, ATTN_NORM, 0); + + ent->takedamage = DAMAGE_NO; + T_RadiusDamage(ent, owner, ent->dmg, ent, PROX_DAMAGE_RADIUS, MOD_PROX); + + VectorMA (ent->s.origin, -0.02, ent->velocity, origin); + gi.WriteByte (svc_temp_entity); + if (ent->groundentity) + gi.WriteByte (TE_GRENADE_EXPLOSION); + else + gi.WriteByte (TE_ROCKET_EXPLOSION); + gi.WritePosition (origin); + gi.multicast (ent->s.origin, MULTICAST_PVS); + + G_FreeEdict (ent); +} + +//=============== +//=============== +void prox_die(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ +// gi.dprintf("prox_die\n"); + // if set off by another prox, delay a little (chained explosions) + if (strcmp(inflictor->classname, "prox")) + { + self->takedamage = DAMAGE_NO; + Prox_Explode(self); + } + else + { + self->takedamage = DAMAGE_NO; + self->think = Prox_Explode; + self->nextthink = level.time + FRAMETIME; + } +} + +//=============== +//=============== +void Prox_Field_Touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + edict_t *prox; + + if (!(other->svflags & SVF_MONSTER) && !other->client) + return; + + // trigger the prox mine if it's still there, and still mine. + prox = ent->owner; + + if (other == prox) // don't set self off + return; + + if (prox->think == Prox_Explode) // we're set to blow! + { +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("%f - prox already gone off!\n", level.time); + return; + } + + if(prox->teamchain == ent) + { + gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/proxwarn.wav"), 1, ATTN_NORM, 0); + prox->think = Prox_Explode; + prox->nextthink = level.time + PROX_TIME_DELAY; + return; + } + + ent->solid = SOLID_NOT; + G_FreeEdict(ent); +} + +//=============== +//=============== +void prox_seek (edict_t *ent) +{ + if(level.time > ent->wait) + { + Prox_Explode(ent); + } + else + { + ent->s.frame++; + if(ent->s.frame > 13) + ent->s.frame = 9; + ent->think = prox_seek; + ent->nextthink = level.time + 0.1; + } +} + +//=============== +//=============== +void prox_open (edict_t *ent) +{ + edict_t *search; + + search = NULL; +// gi.dprintf("prox_open %d\n", ent->s.frame); +// gi.dprintf("%f\n", ent->velocity[2]); + if(ent->s.frame == 9) // end of opening animation + { + // set the owner to NULL so the owner can shoot it, etc. needs to be done here so the owner + // doesn't get stuck on it while it's opening if fired at point blank wall + ent->s.sound = 0; + ent->owner = NULL; + if(ent->teamchain) + ent->teamchain->touch = Prox_Field_Touch; + while ((search = findradius(search, ent->s.origin, PROX_DAMAGE_RADIUS+10)) != NULL) + { + if (!search->classname) // tag token and other weird shit + continue; + +// if (!search->takedamage) +// continue; + // if it's a monster or player with health > 0 + // or it's a player start point + // and we can see it + // blow up + if ( + ( + (((search->svflags & SVF_MONSTER) || (search->client)) && (search->health > 0)) || + ( + (deathmatch->value) && + ( + (!strcmp(search->classname, "info_player_deathmatch")) || + (!strcmp(search->classname, "info_player_start")) || + (!strcmp(search->classname, "info_player_coop")) || + (!strcmp(search->classname, "misc_teleporter_dest")) + ) + ) + ) + && (visible (search, ent)) + ) + { + gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/proxwarn.wav"), 1, ATTN_NORM, 0); + Prox_Explode (ent); + return; + } + } + + if (strong_mines && (strong_mines->value)) + ent->wait = level.time + PROX_TIME_TO_LIVE; + else + { + switch (ent->dmg/PROX_DAMAGE) + { + case 1: + ent->wait = level.time + PROX_TIME_TO_LIVE; + break; + case 2: + ent->wait = level.time + 30; + break; + case 4: + ent->wait = level.time + 15; + break; + case 8: + ent->wait = level.time + 10; + break; + default: +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("prox with unknown multiplier %d!\n", ent->dmg/PROX_DAMAGE); + ent->wait = level.time + PROX_TIME_TO_LIVE; + break; + } + } + +// ent->wait = level.time + PROX_TIME_TO_LIVE; + ent->think = prox_seek; + ent->nextthink = level.time + 0.2; + } + else + { + if (ent->s.frame == 0) + gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/proxopen.wav"), 1, ATTN_NORM, 0); + //ent->s.sound = gi.soundindex ("weapons/proxopen.wav"); + ent->s.frame++; + ent->think = prox_open; + ent->nextthink = level.time + 0.05; + } +} + +//=============== +//=============== +void prox_land (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + edict_t *field; + vec3_t dir; + vec3_t forward, right, up; + int makeslave = 0; + int movetype = MOVETYPE_NONE; + int stick_ok = 0; + vec3_t land_point; + + // must turn off owner so owner can shoot it and set it off + // moved to prox_open so owner can get away from it if fired at pointblank range into + // wall +// ent->owner = NULL; + +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("land - %2.2f %2.2f %2.2f\n", ent->velocity[0], ent->velocity[1], ent->velocity[2]); + + if (surf && (surf->flags & SURF_SKY)) + { + G_FreeEdict(ent); + return; + } + + if (plane->normal) + { + VectorMA (ent->s.origin, -10.0, plane->normal, land_point); + if (gi.pointcontents (land_point) & (CONTENTS_SLIME|CONTENTS_LAVA)) + { + Prox_Explode (ent); + return; + } + } + + if ((other->svflags & SVF_MONSTER) || other->client || (other->svflags & SVF_DAMAGEABLE)) + { + if(other != ent->teammaster) + Prox_Explode(ent); + + return; + } + +#define STOP_EPSILON 0.1 + + else if (other != world) + { + //Here we need to check to see if we can stop on this entity. + //Note that plane can be NULL + + //PMM - code stolen from g_phys (ClipVelocity) + vec3_t out; + float backoff, change; + int i; + + if (!plane->normal) // this happens if you hit a point object, maybe other cases + { + // Since we can't tell what's going to happen, just blow up +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("bad normal for surface, exploding!\n"); + + Prox_Explode(ent); + return; + } + + if ((other->movetype == MOVETYPE_PUSH) && (plane->normal[2] > 0.7)) + stick_ok = 1; + else + stick_ok = 0; + + backoff = DotProduct (ent->velocity, plane->normal) * 1.5; + for (i=0 ; i<3 ; i++) + { + change = plane->normal[i]*backoff; + out[i] = ent->velocity[i] - change; + if (out[i] > -STOP_EPSILON && out[i] < STOP_EPSILON) + out[i] = 0; + } + + if (out[2] > 60) + return; + + movetype = MOVETYPE_BOUNCE; + + // if we're here, we're going to stop on an entity + if (stick_ok) + { // it's a happy entity + VectorCopy (vec3_origin, ent->velocity); + VectorCopy (vec3_origin, ent->avelocity); + } + else // no-stick. teflon time + { + if (plane->normal[2] > 0.7) + { +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("stuck on entity, blowing up!\n"); + + Prox_Explode(ent); + return; + } + return; + } + } + else if (other->s.modelindex != 1) + return; + + vectoangles2 (plane->normal, dir); + AngleVectors (dir, forward, right, up); + + if (gi.pointcontents (ent->s.origin) & (CONTENTS_LAVA|CONTENTS_SLIME)) + { + Prox_Explode (ent); + return; + } + + field = G_Spawn(); + + VectorCopy (ent->s.origin, field->s.origin); + VectorClear(field->velocity); + VectorClear(field->avelocity); + VectorSet(field->mins, -PROX_BOUND_SIZE, -PROX_BOUND_SIZE, -PROX_BOUND_SIZE); + VectorSet(field->maxs, PROX_BOUND_SIZE, PROX_BOUND_SIZE, PROX_BOUND_SIZE); + field->movetype = MOVETYPE_NONE; + field->solid = SOLID_TRIGGER; + field->owner = ent; + field->classname = "prox_field"; + field->teammaster = ent; + gi.linkentity (field); + + VectorClear(ent->velocity); + VectorClear(ent->avelocity); + // rotate to vertical + dir[PITCH] = dir[PITCH] + 90; + VectorCopy (dir, ent->s.angles); + ent->takedamage = DAMAGE_AIM; + ent->movetype = movetype; // either bounce or none, depending on whether we stuck to something + ent->die = prox_die; + ent->teamchain = field; + ent->health = PROX_HEALTH; + ent->nextthink = level.time + 0.05; + ent->think = prox_open; + ent->touch = NULL; + ent->solid = SOLID_BBOX; + // record who we're attached to +// ent->teammaster = other; + + gi.linkentity(ent); +} + +//=============== +//=============== +void fire_prox (edict_t *self, vec3_t start, vec3_t aimdir, int damage_multiplier, int speed) +{ + edict_t *prox; + vec3_t dir; + vec3_t forward, right, up; + + vectoangles2 (aimdir, dir); + AngleVectors (dir, forward, right, up); + +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("start %s aim %s speed %d\n", vtos(start), vtos(aimdir), speed); + prox = G_Spawn(); + VectorCopy (start, prox->s.origin); + VectorScale (aimdir, speed, prox->velocity); + VectorMA (prox->velocity, 200 + crandom() * 10.0, up, prox->velocity); + VectorMA (prox->velocity, crandom() * 10.0, right, prox->velocity); + VectorCopy (dir, prox->s.angles); + prox->s.angles[PITCH]-=90; + prox->movetype = MOVETYPE_BOUNCE; + prox->solid = SOLID_BBOX; + prox->s.effects |= EF_GRENADE; + prox->clipmask = MASK_SHOT|CONTENTS_LAVA|CONTENTS_SLIME; + prox->s.renderfx |= RF_IR_VISIBLE; + //FIXME - this needs to be bigger. Has other effects, though. Maybe have to change origin to compensate + // so it sinks in correctly. Also in lavacheck, might have to up the distance + VectorSet (prox->mins, -6, -6, -6); + VectorSet (prox->maxs, 6, 6, 6); + prox->s.modelindex = gi.modelindex ("models/weapons/g_prox/tris.md2"); + prox->owner = self; + prox->teammaster = self; + prox->touch = prox_land; +// prox->nextthink = level.time + PROX_TIME_TO_LIVE; + prox->think = Prox_Explode; + prox->dmg = PROX_DAMAGE*damage_multiplier; + prox->classname = "prox"; + prox->svflags |= SVF_DAMAGEABLE; + prox->flags |= FL_MECHANICAL; + + switch (damage_multiplier) + { + case 1: + prox->nextthink = level.time + PROX_TIME_TO_LIVE; + break; + case 2: + prox->nextthink = level.time + 30; + break; + case 4: + prox->nextthink = level.time + 15; + break; + case 8: + prox->nextthink = level.time + 10; + break; + default: +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("prox with unknown multiplier %d!\n", damage_multiplier); + prox->nextthink = level.time + PROX_TIME_TO_LIVE; + break; + } + + gi.linkentity (prox); +} +#endif + +// ************************* +// FLAMETHROWER +// ************************* + +#ifdef INCLUDE_FLAMETHROWER +#define FLAMETHROWER_RADIUS 8 + +void fire_remove (edict_t *ent) +{ + if(ent == ent->owner->teamchain) + ent->owner->teamchain = NULL; + + G_FreeEdict(ent); +} + +void fire_flame (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed) +{ + edict_t *flame; + vec3_t dir; + vec3_t forward, right, up; + + vectoangles2 (aimdir, dir); + AngleVectors (dir, forward, right, up); + + flame = G_Spawn(); + + // the origin is the first control point, put it speed forward. + VectorMA(start, speed, forward, flame->s.origin); + + // record that velocity + VectorScale (aimdir, speed, flame->velocity); + + VectorCopy (dir, flame->s.angles); + flame->movetype = MOVETYPE_NONE; + flame->solid = SOLID_NOT; + + VectorSet(flame->mins, -FLAMETHROWER_RADIUS, -FLAMETHROWER_RADIUS, -FLAMETHROWER_RADIUS); + VectorSet(flame->maxs, FLAMETHROWER_RADIUS, FLAMETHROWER_RADIUS, FLAMETHROWER_RADIUS); + + flame->s.sound = gi.soundindex ("weapons/flame.wav"); + flame->owner = self; + flame->dmg = damage; + flame->classname = "flame"; + + // clear control points and velocities + VectorCopy (flame->s.origin, flame->flameinfo.pos1); + VectorCopy (flame->velocity, flame->flameinfo.vel1); + VectorCopy (flame->s.origin, flame->flameinfo.pos2); + VectorCopy (flame->velocity, flame->flameinfo.vel2); + VectorCopy (flame->s.origin, flame->flameinfo.pos3); + VectorCopy (flame->velocity, flame->flameinfo.vel3); + VectorCopy (flame->s.origin, flame->flameinfo.pos4); + + // hook flame stream to owner + self->teamchain = flame; + + gi.linkentity (flame); +} + +// fixme - change to use start location, not entity origin +void fire_maintain (edict_t *ent, edict_t *flame, vec3_t start, vec3_t aimdir, int damage, int speed) +{ + trace_t tr; + + // move the control points out the appropriate direction and velocity + VectorAdd(flame->flameinfo.pos3, flame->flameinfo.vel3, flame->flameinfo.pos4); + VectorAdd(flame->flameinfo.pos2, flame->flameinfo.vel2, flame->flameinfo.pos3); + VectorAdd(flame->flameinfo.pos1, flame->flameinfo.vel1, flame->flameinfo.pos2); + VectorAdd(flame->s.origin, flame->velocity, flame->flameinfo.pos1); + + // move the velocities for the control points + VectorCopy(flame->flameinfo.vel2, flame->flameinfo.vel3); + VectorCopy(flame->flameinfo.vel1, flame->flameinfo.vel2); + VectorCopy(flame->velocity, flame->flameinfo.vel1); + + // set velocity and location for new control point 0. + VectorMA(start, speed, aimdir, flame->s.origin); + VectorScale(aimdir, speed, flame->velocity); + + // + // does it hit a wall? if so, when? + // + + // player fire point to flame origin. + tr = gi.trace(start, flame->mins, flame->maxs, + flame->s.origin, flame, MASK_SHOT); + if(tr.fraction == 1.0) + { + // origin to point 1 + tr = gi.trace(flame->s.origin, flame->mins, flame->maxs, + flame->flameinfo.pos1, flame, MASK_SHOT); + if(tr.fraction == 1.0) + { + // point 1 to point 2 + tr = gi.trace(flame->flameinfo.pos1, flame->mins, flame->maxs, + flame->flameinfo.pos2, flame, MASK_SHOT); + if(tr.fraction == 1.0) + { + // point 2 to point 3 + tr = gi.trace(flame->flameinfo.pos2, flame->mins, flame->maxs, + flame->flameinfo.pos3, flame, MASK_SHOT); + if(tr.fraction == 1.0) + { + // point 3 to point 4, point 3 valid + tr = gi.trace(flame->flameinfo.pos3, flame->mins, flame->maxs, + flame->flameinfo.pos4, flame, MASK_SHOT); + if(tr.fraction < 1.0) // point 4 blocked + { + VectorCopy(tr.endpos, flame->flameinfo.pos4); + } + } + else // point 3 blocked, point 2 valid + { + VectorCopy(flame->flameinfo.vel2, flame->flameinfo.vel3); + VectorCopy(tr.endpos, flame->flameinfo.pos3); + VectorCopy(tr.endpos, flame->flameinfo.pos4); + } + } + else // point 2 blocked, point 1 valid + { + VectorCopy(flame->flameinfo.vel1, flame->flameinfo.vel2); + VectorCopy(flame->flameinfo.vel1, flame->flameinfo.vel3); + VectorCopy(tr.endpos, flame->flameinfo.pos2); + VectorCopy(tr.endpos, flame->flameinfo.pos3); + VectorCopy(tr.endpos, flame->flameinfo.pos4); + } + } + else // point 1 blocked, origin valid + { + VectorCopy(flame->velocity, flame->flameinfo.vel1); + VectorCopy(flame->velocity, flame->flameinfo.vel2); + VectorCopy(flame->velocity, flame->flameinfo.vel3); + VectorCopy(tr.endpos, flame->flameinfo.pos1); + VectorCopy(tr.endpos, flame->flameinfo.pos2); + VectorCopy(tr.endpos, flame->flameinfo.pos3); + VectorCopy(tr.endpos, flame->flameinfo.pos4); + } + } + else // origin blocked! + { +// gi.dprintf("point 2 blocked\n"); + VectorCopy(flame->velocity, flame->flameinfo.vel1); + VectorCopy(flame->velocity, flame->flameinfo.vel2); + VectorCopy(flame->velocity, flame->flameinfo.vel3); + VectorCopy(tr.endpos, flame->s.origin); + VectorCopy(tr.endpos, flame->flameinfo.pos1); + VectorCopy(tr.endpos, flame->flameinfo.pos2); + VectorCopy(tr.endpos, flame->flameinfo.pos3); + VectorCopy(tr.endpos, flame->flameinfo.pos4); + } + + if(tr.fraction < 1.0 && tr.ent->takedamage) + { + T_Damage (tr.ent, flame, ent, flame->velocity, tr.endpos, tr.plane.normal, + damage, 0, DAMAGE_NO_KNOCKBACK | DAMAGE_ENERGY | DAMAGE_FIRE); + } + + gi.linkentity(flame); + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_FLAME); + gi.WriteShort(ent - g_edicts); + gi.WriteShort(6); + gi.WritePosition (start); + gi.WritePosition (flame->s.origin); + gi.WritePosition (flame->flameinfo.pos1); + gi.WritePosition (flame->flameinfo.pos2); + gi.WritePosition (flame->flameinfo.pos3); + gi.WritePosition (flame->flameinfo.pos4); + gi.multicast (flame->s.origin, MULTICAST_PVS); +} + +/*QUAKED trap_flameshooter (1 0 0) (-8 -8 -8) (8 8 8) +*/ +#define FLAMESHOOTER_VELOCITY 50 +#define FLAMESHOOTER_DAMAGE 20 +#define FLAMESHOOTER_BURST_VELOCITY 300 +#define FLAMESHOOTER_BURST_DAMAGE 30 + +//#define FLAMESHOOTER_PUFF 1 +#define FLAMESHOOTER_STREAM 1 + +void flameshooter_think (edict_t *self) +{ + vec3_t forward, right, up; + edict_t *flame; + + if(self->delay) + { + if(self->teamchain) + fire_remove (self->teamchain); + return; + } + + self->s.angles[1] += self->speed; + if(self->s.angles[1] > 135 || self->s.angles[1] < 45) + self->speed = -self->speed; + + AngleVectors (self->s.angles, forward, right, up); + +#ifdef FLAMESHOOTER_STREAM + flame = self->teamchain; + if(!self->teamchain) + fire_flame (self, self->s.origin, forward, FLAMESHOOTER_DAMAGE, FLAMESHOOTER_VELOCITY); + else + fire_maintain (self, flame, self->s.origin, forward, FLAMESHOOTER_DAMAGE, FLAMESHOOTER_VELOCITY); + + self->think = flameshooter_think; + self->nextthink = level.time + 0.05; +#else + fire_burst (self, self->s.origin, forward, FLAMESHOOTER_BURST_DAMAGE, FLAMESHOOTER_BURST_VELOCITY); + + self->think = flameshooter_think; + self->nextthink = level.time + 0.1; +#endif +} + +void flameshooter_use (edict_t *self, edict_t *other, edict_t *activator) +{ + if(self->delay) + { + self->delay = 0; + self->think = flameshooter_think; + self->nextthink = level.time + 0.1; + } + else + self->delay = 1; +} + +void SP_trap_flameshooter(edict_t *self) +{ + vec3_t tempAngles; + + self->solid = SOLID_NOT; + self->movetype = MOVETYPE_NONE; + + self->delay = 0; + + self->use = flameshooter_use; + if(self->delay == 0) + { + self->think = flameshooter_think; + self->nextthink = level.time + 0.1; + } + +// self->flags |= FL_NOCLIENT; + + self->speed = 10; + +// self->speed = 0; // FIXME this stops the spraying + + VectorCopy(self->s.angles, tempAngles); + + if (!VectorCompare(self->s.angles, vec3_origin)) + G_SetMovedir (self->s.angles, self->movedir); + + VectorCopy(tempAngles, self->s.angles); + +// gi.setmodel (self, self->model); + gi.linkentity (self); +} + +// ************************* +// fire_burst +// ************************* + +#define FLAME_BURST_MAX_SIZE 64 +#define FLAME_BURST_FRAMES 20 +#define FLAME_BURST_MIDPOINT 10 + +void fire_burst_touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + int powerunits; + int damage, radius; + vec3_t origin; + + if (surf && (surf->flags & SURF_SKY)) + { +// gi.dprintf("Hit sky. Removed\n"); + G_FreeEdict (ent); + return; + } + + if(other == ent->owner || ent == other) + return; + + // don't let flame puffs blow each other up + if(other->classname && !strcmp(other->classname, ent->classname)) + return; + + if(ent->waterlevel) + { +// gi.dprintf("Hit water. Removed\n"); + G_FreeEdict(ent); + } + + if(!(other->svflags & SVF_MONSTER) && !other->client) + { + powerunits = FLAME_BURST_FRAMES - ent->s.frame; + damage = powerunits * 6; + radius = powerunits * 4; + +// T_RadiusDamage (inflictor, attacker, damage, ignore, radius) + T_RadiusDamage(ent, ent->owner, damage, ent, radius, DAMAGE_FIRE); + +// gi.dprintf("Hit world: %d pts, %d rad\n", damage, radius); + + // calculate position for the explosion entity + VectorMA (ent->s.origin, -0.02, ent->velocity, origin); + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_PLAIN_EXPLOSION); + gi.WritePosition (origin); + gi.multicast (ent->s.origin, MULTICAST_PVS); + + G_FreeEdict (ent); + } +} + +void fire_burst_think (edict_t *self) +{ + int current_radius; + + if(self->waterlevel) + { + G_FreeEdict(self); + return; + } + + self->s.frame++; + if(self->s.frame >= FLAME_BURST_FRAMES) + { + G_FreeEdict(self); + return; + } + + else if(self->s.frame < FLAME_BURST_MIDPOINT) + { + current_radius = (FLAME_BURST_MAX_SIZE / FLAME_BURST_MIDPOINT) * self->s.frame; + } + else + { + current_radius = (FLAME_BURST_MAX_SIZE / FLAME_BURST_MIDPOINT) * (FLAME_BURST_FRAMES - self->s.frame); + } + + if(self->s.frame == 3) + self->s.skinnum = 1; + else if (self->s.frame == 7) + self->s.skinnum = 2; + else if (self->s.frame == 10) + self->s.skinnum = 3; + else if (self->s.frame == 13) + self->s.skinnum = 4; + else if (self->s.frame == 16) + self->s.skinnum = 5; + else if (self->s.frame == 19) + self->s.skinnum = 6; + + if(current_radius < 8) + current_radius = 8; + else if(current_radius > FLAME_BURST_MAX_SIZE) + current_radius = FLAME_BURST_MAX_SIZE; + + T_RadiusDamage(self, self->owner, self->dmg, self, current_radius, DAMAGE_FIRE); + + self->think = fire_burst_think; + self->nextthink = level.time + 0.1; +} + +void fire_burst (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed) +{ + edict_t *flame; + vec3_t dir; + vec3_t baseVel; + vec3_t forward, right, up; + + vectoangles2 (aimdir, dir); + AngleVectors (dir, forward, right, up); + + flame = G_Spawn(); + VectorCopy(start, flame->s.origin); +// VectorScale (aimdir, speed, flame->velocity); + + // scale down so only 30% of player's velocity is taken into account. + VectorScale (self->velocity, 0.3, baseVel); + VectorMA(baseVel, speed, aimdir, flame->velocity); + + VectorCopy (dir, flame->s.angles); + flame->movetype = MOVETYPE_FLY; + flame->solid = SOLID_TRIGGER; + + VectorSet(flame->mins, -FLAMETHROWER_RADIUS, -FLAMETHROWER_RADIUS, -FLAMETHROWER_RADIUS); + VectorSet(flame->maxs, FLAMETHROWER_RADIUS, FLAMETHROWER_RADIUS, FLAMETHROWER_RADIUS); + + flame->s.sound = gi.soundindex ("weapons/flame.wav"); + flame->s.modelindex = gi.modelindex ("models/projectiles/puff/tris.md2"); + flame->owner = self; + flame->touch = fire_burst_touch; + flame->think = fire_burst_think; + flame->nextthink = level.time + 0.1; + flame->dmg = damage; + flame->classname = "flameburst"; + flame->s.effects = EF_FIRE_PUFF; + + gi.linkentity (flame); +} +#endif + +// ************************* +// INCENDIARY GRENADES +// ************************* + +#ifdef INCLUDE_INCENDIARY +void FireThink (edict_t *ent) +{ + if(level.time > ent->wait) + G_FreeEdict(ent); + else + { + ent->s.frame++; + if(ent->s.frame>10) + ent->s.frame = 0; + ent->nextthink = level.time + 0.05; + ent->think = FireThink; + } +} + +#define FIRE_HEIGHT 64 +#define FIRE_RADIUS 64 +#define FIRE_DAMAGE 3 +#define FIRE_DURATION 15 + +edict_t *StartFire(edict_t *fireOwner, vec3_t fireOrigin, float fireDuration, float fireDamage) +{ + edict_t *fire; + + fire = G_Spawn(); + VectorCopy (fireOrigin, fire->s.origin); + fire->movetype = MOVETYPE_TOSS; + fire->solid = SOLID_TRIGGER; + VectorSet(fire->mins, -FIRE_RADIUS, -FIRE_RADIUS, 0); + VectorSet(fire->maxs, FIRE_RADIUS, FIRE_RADIUS, FIRE_HEIGHT); + + fire->s.sound = gi.soundindex ("weapons/incend.wav"); + fire->s.modelindex = gi.modelindex ("models/objects/fire/tris.md2"); + + fire->owner = fireOwner; + fire->touch = hurt_touch; + fire->nextthink = level.time + 0.05; + fire->wait = level.time + fireDuration; + fire->think = FireThink; +// fire->nextthink = level.time + fireDuration; +// fire->think = G_FreeEdict; + fire->dmg = fireDamage; + fire->classname = "incendiary_fire"; + + gi.linkentity (fire); + +// gi.sound (fire, CHAN_VOICE, gi.soundindex ("weapons/incend.wav"), 1, ATTN_NORM, 0); + return fire; +} + +static void Incendiary_Explode (edict_t *ent) +{ + vec3_t origin; + + if (ent->owner->client) + PlayerNoise(ent->owner, ent->s.origin, PNOISE_IMPACT); + + //FIXME: if we are onground then raise our Z just a bit since we are a point? + T_RadiusDamage(ent, ent->owner, ent->dmg, NULL, ent->dmg_radius, DAMAGE_FIRE); + + VectorMA (ent->s.origin, -0.02, ent->velocity, origin); + gi.WriteByte (svc_temp_entity); + if (ent->groundentity) + gi.WriteByte (TE_GRENADE_EXPLOSION); + else + gi.WriteByte (TE_ROCKET_EXPLOSION); + gi.WritePosition (origin); + gi.multicast (ent->s.origin, MULTICAST_PVS); + + StartFire(ent->owner, ent->s.origin, FIRE_DURATION, FIRE_DAMAGE); + + G_FreeEdict (ent); + +} + +static void Incendiary_Touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (other == ent->owner) + return; + + if (surf && (surf->flags & SURF_SKY)) + { + G_FreeEdict (ent); + return; + } + + if (!(other->svflags & SVF_MONSTER) && !(ent->client)) +// if (!other->takedamage) + { + if (ent->spawnflags & 1) + { + if (random() > 0.5) + gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/hgrenb1a.wav"), 1, ATTN_NORM, 0); + else + gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/hgrenb2a.wav"), 1, ATTN_NORM, 0); + } + else + { + if (random() > 0.5) + gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/grenlb1b.wav"), 1, ATTN_NORM, 0); + else + gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/grenlb2b.wav"), 1, ATTN_NORM, 0); + } + return; + } + + Incendiary_Explode (ent); +} + +void fire_incendiary_grenade (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, float timer, float damage_radius) +{ + edict_t *grenade; + vec3_t dir; + vec3_t forward, right, up; + + vectoangles2 (aimdir, dir); + AngleVectors (dir, forward, right, up); + + grenade = G_Spawn(); + VectorCopy (start, grenade->s.origin); + VectorScale (aimdir, speed, grenade->velocity); + VectorMA (grenade->velocity, 200 + crandom() * 10.0, up, grenade->velocity); + VectorMA (grenade->velocity, crandom() * 10.0, right, grenade->velocity); + VectorSet (grenade->avelocity, 300, 300, 300); + grenade->movetype = MOVETYPE_BOUNCE; + grenade->clipmask = MASK_SHOT; + grenade->solid = SOLID_BBOX; + grenade->s.effects |= EF_GRENADE; +// if (self->client) +// grenade->s.effects &= ~EF_TELEPORT; + VectorClear (grenade->mins); + VectorClear (grenade->maxs); + grenade->s.modelindex = gi.modelindex ("models/projectiles/incend/tris.md2"); + grenade->owner = self; + grenade->touch = Incendiary_Touch; + grenade->nextthink = level.time + timer; + grenade->think = Incendiary_Explode; + grenade->dmg = damage; + grenade->dmg_radius = damage_radius; + grenade->classname = "incendiary_grenade"; + + gi.linkentity (grenade); +} +#endif + +// ************************* +// MELEE WEAPONS +// ************************* + +#ifdef INCLUDE_MELEE +void fire_player_melee (edict_t *self, vec3_t start, vec3_t aim, int reach, int damage, int kick, int quiet, int mod) +{ + vec3_t forward, right, up; + vec3_t v; + vec3_t point; + trace_t tr; + + vectoangles2 (aim, v); + AngleVectors (v, forward, right, up); + VectorNormalize (forward); + VectorMA( start, reach, forward, point); + + //see if the hit connects + tr = gi.trace(start, NULL, NULL, point, self, MASK_SHOT); + if(tr.fraction == 1.0) + { + if(!quiet) + gi.sound (self, CHAN_WEAPON, gi.soundindex ("weapons/swish.wav"), 1, ATTN_NORM, 0); + //FIXME some sound here? + return; + } + + if(tr.ent->takedamage == DAMAGE_YES || tr.ent->takedamage == DAMAGE_AIM) + { + // pull the player forward if you do damage + VectorMA(self->velocity, 75, forward, self->velocity); + VectorMA(self->velocity, 75, up, self->velocity); + + // do the damage + // FIXME - make the damage appear at right spot and direction + if(mod == MOD_CHAINFIST) + T_Damage (tr.ent, self, self, vec3_origin, tr.ent->s.origin, vec3_origin, damage, kick/2, + DAMAGE_DESTROY_ARMOR | DAMAGE_NO_KNOCKBACK, mod); + else + T_Damage (tr.ent, self, self, vec3_origin, tr.ent->s.origin, vec3_origin, damage, kick/2, DAMAGE_NO_KNOCKBACK, mod); + + if(!quiet) + gi.sound (self, CHAN_WEAPON, gi.soundindex ("weapons/meatht.wav"), 1, ATTN_NORM, 0); + } + else + { + if(!quiet) + gi.sound (self, CHAN_WEAPON, gi.soundindex ("weapons/tink1.wav"), 1, ATTN_NORM, 0); + + VectorScale (tr.plane.normal, 256, point); + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_GUNSHOT); + gi.WritePosition (tr.endpos); + gi.WriteDir (point); + gi.multicast (tr.endpos, MULTICAST_PVS); + } +} +#endif + +// ************************* +// NUKE +// ************************* + +#ifdef INCLUDE_NUKE +#define NUKE_DELAY 4 +#define NUKE_TIME_TO_LIVE 6 +//#define NUKE_TIME_TO_LIVE 40 +#define NUKE_RADIUS 512 +#define NUKE_DAMAGE 400 +#define NUKE_QUAKE_TIME 3 +#define NUKE_QUAKE_STRENGTH 100 + +void Nuke_Quake (edict_t *self) +{ + int i; + edict_t *e; + + if (self->last_move_time < level.time) + { + gi.positioned_sound (self->s.origin, self, CHAN_AUTO, self->noise_index, 0.75, ATTN_NONE, 0); + self->last_move_time = level.time + 0.5; + } + + for (i=1, e=g_edicts+i; i < globals.num_edicts; i++,e++) + { + if (!e->inuse) + continue; + if (!e->client) + continue; + if (!e->groundentity) + continue; + + e->groundentity = NULL; + e->velocity[0] += crandom()* 150; + e->velocity[1] += crandom()* 150; + e->velocity[2] = self->speed * (100.0 / e->mass); + } + + if (level.time < self->timestamp) + self->nextthink = level.time + FRAMETIME; + else + G_FreeEdict (self); +} + + +static void Nuke_Explode (edict_t *ent) +{ +// vec3_t origin; + +// nuke_framenum = level.framenum + 20; + + if (ent->teammaster->client) + PlayerNoise(ent->teammaster, ent->s.origin, PNOISE_IMPACT); + + T_RadiusNukeDamage(ent, ent->teammaster, ent->dmg, ent, ent->dmg_radius, MOD_NUKE); + +// VectorMA (ent->s.origin, -0.02, ent->velocity, origin); + if (ent->dmg > NUKE_DAMAGE) + gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage3.wav"), 1, ATTN_NORM, 0); + + gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, gi.soundindex ("weapons/grenlx1a.wav"), 1, ATTN_NONE, 0); +/* + gi.WriteByte (svc_temp_entity); + if (ent->groundentity) + gi.WriteByte (TE_GRENADE_EXPLOSION); + else + gi.WriteByte (TE_ROCKET_EXPLOSION); + gi.WritePosition (origin); + gi.multicast (ent->s.origin, MULTICAST_PVS); +*/ + + // BecomeExplosion1(ent); + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_EXPLOSION1_BIG); + gi.WritePosition (ent->s.origin); + gi.multicast (ent->s.origin, MULTICAST_PVS); + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_NUKEBLAST); + gi.WritePosition (ent->s.origin); + gi.multicast (ent->s.origin, MULTICAST_ALL); + + // become a quake + ent->svflags |= SVF_NOCLIENT; + ent->noise_index = gi.soundindex ("world/rumble.wav"); + ent->think = Nuke_Quake; + ent->speed = NUKE_QUAKE_STRENGTH; + ent->timestamp = level.time + NUKE_QUAKE_TIME; + ent->nextthink = level.time + FRAMETIME; + ent->last_move_time = 0; +} + +void nuke_die(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + self->takedamage = DAMAGE_NO; + if ((attacker) && !(strcmp(attacker->classname, "nuke"))) + { +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("nuke nuked by a nuke, not nuking\n"); + G_FreeEdict (self); + return; + } + Nuke_Explode(self); +} + +void Nuke_Think(edict_t *ent) +{ + float attenuation, default_atten = 1.8; + int damage_multiplier, muzzleflash; + +// gi.dprintf ("player range: %2.2f damage radius: %2.2f\n", realrange (ent, ent->teammaster), ent->dmg_radius*2); + + damage_multiplier = ent->dmg/NUKE_DAMAGE; + switch (damage_multiplier) + { + case 1: + attenuation = default_atten/1.4; + muzzleflash = MZ_NUKE1; + break; + case 2: + attenuation = default_atten/2.0; + muzzleflash = MZ_NUKE2; + break; + case 4: + attenuation = default_atten/3.0; + muzzleflash = MZ_NUKE4; + break; + case 8: + attenuation = default_atten/5.0; + muzzleflash = MZ_NUKE8; + break; + default: +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("default attenuation used for nuke!\n"); + attenuation = default_atten; + muzzleflash = MZ_NUKE1; + break; + } + + if(ent->wait < level.time) + Nuke_Explode(ent); + else if (level.time >= (ent->wait - NUKE_TIME_TO_LIVE)) + { + ent->s.frame++; +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("nuke frame %d\n", ent->s.frame); + if(ent->s.frame > 11) + ent->s.frame = 6; + + if (gi.pointcontents (ent->s.origin) & (CONTENTS_SLIME|CONTENTS_LAVA)) + { + Nuke_Explode (ent); + return; + } + + ent->think = Nuke_Think; + ent->nextthink = level.time + 0.1; + ent->health = 1; + ent->owner = NULL; + + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + gi.WriteByte (muzzleflash); + gi.multicast (ent->s.origin, MULTICAST_PVS); + + if (ent->timestamp <= level.time) + { +/* gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/nukewarn.wav"), 1, ATTN_NORM, 0); + ent->timestamp += 10.0; + } +*/ + + if ((ent->wait - level.time) <= (NUKE_TIME_TO_LIVE/2.0)) + { +// ent->s.sound = gi.soundindex ("weapons/nukewarn.wav"); +// gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, gi.soundindex ("weapons/nukewarn2.wav"), 1, ATTN_NORM, 0); + gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, gi.soundindex ("weapons/nukewarn2.wav"), 1, attenuation, 0); +// gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/nukewarn2.wav"), 1, ATTN_NORM, 0); +// gi.dprintf ("time %2.2f\n", ent->wait-level.time); + ent->timestamp = level.time + 0.3; + } + else + { + gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, gi.soundindex ("weapons/nukewarn2.wav"), 1, attenuation, 0); +// gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/nukewarn2.wav"), 1, ATTN_NORM, 0); + ent->timestamp = level.time + 0.5; +// gi.dprintf ("time %2.2f\n", ent->wait-level.time); + } + } + } + else + { + if (ent->timestamp <= level.time) + { + gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, gi.soundindex ("weapons/nukewarn2.wav"), 1, attenuation, 0); +// gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/nukewarn2.wav"), 1, ATTN_NORM, 0); +// gi.dprintf ("time %2.2f\n", ent->wait-level.time); + ent->timestamp = level.time + 1.0; + } + ent->nextthink = level.time + FRAMETIME; + } +} + +void nuke_bounce (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (random() > 0.5) + gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/hgrenb1a.wav"), 1, ATTN_NORM, 0); + else + gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/hgrenb2a.wav"), 1, ATTN_NORM, 0); +} + + +extern byte P_DamageModifier(edict_t *ent); + +void fire_nuke (edict_t *self, vec3_t start, vec3_t aimdir, int speed) +{ + edict_t *nuke; + vec3_t dir; + vec3_t forward, right, up; + int damage_modifier; + + damage_modifier = (int) P_DamageModifier (self); + + vectoangles2 (aimdir, dir); + AngleVectors (dir, forward, right, up); + + nuke = G_Spawn(); + VectorCopy (start, nuke->s.origin); + VectorScale (aimdir, speed, nuke->velocity); + + VectorMA (nuke->velocity, 200 + crandom() * 10.0, up, nuke->velocity); + VectorMA (nuke->velocity, crandom() * 10.0, right, nuke->velocity); + VectorClear (nuke->avelocity); + VectorClear (nuke->s.angles); + nuke->movetype = MOVETYPE_BOUNCE; + nuke->clipmask = MASK_SHOT; + nuke->solid = SOLID_BBOX; + nuke->s.effects |= EF_GRENADE; + nuke->s.renderfx |= RF_IR_VISIBLE; + VectorSet (nuke->mins, -8, -8, 0); + VectorSet (nuke->maxs, 8, 8, 16); + nuke->s.modelindex = gi.modelindex ("models/weapons/g_nuke/tris.md2"); + nuke->owner = self; + nuke->teammaster = self; + nuke->nextthink = level.time + FRAMETIME; + nuke->wait = level.time + NUKE_DELAY + NUKE_TIME_TO_LIVE; + nuke->think = Nuke_Think; + nuke->touch = nuke_bounce; + + nuke->health = 10000; + nuke->takedamage = DAMAGE_YES; + nuke->svflags |= SVF_DAMAGEABLE; + nuke->dmg = NUKE_DAMAGE * damage_modifier; + if (damage_modifier == 1) + nuke->dmg_radius = NUKE_RADIUS; + else + nuke->dmg_radius = NUKE_RADIUS + NUKE_RADIUS*(0.25*(float)damage_modifier); + // this yields 1.0, 1.5, 2.0, 3.0 times radius + +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("nuke modifier = %d, damage = %d, radius = %f\n", damage_modifier, nuke->dmg, nuke->dmg_radius); + + nuke->classname = "nuke"; + nuke->die = nuke_die; + + gi.linkentity (nuke); +} +#endif + +// ************************* +// TESLA +// ************************* + +#ifdef INCLUDE_TESLA +#define TESLA_TIME_TO_LIVE 30 +#define TESLA_DAMAGE_RADIUS 128 +#define TESLA_DAMAGE 3 // 3 +#define TESLA_KNOCKBACK 8 + +#define TESLA_ACTIVATE_TIME 3 + +#define TESLA_EXPLOSION_DAMAGE_MULT 50 // this is the amount the damage is multiplied by for underwater explosions +#define TESLA_EXPLOSION_RADIUS 200 + +void tesla_remove (edict_t *self) +{ + edict_t *cur, *next; + + self->takedamage = DAMAGE_NO; + if(self->teamchain) + { + cur = self->teamchain; + while(cur) + { + next = cur->teamchain; + G_FreeEdict ( cur ); + cur = next; + } + } + else if (self->air_finished) + gi.dprintf ("tesla without a field!\n"); + + self->owner = self->teammaster; // Going away, set the owner correctly. + // PGM - grenade explode does damage to self->enemy + self->enemy = NULL; + + // play quad sound if quadded and an underwater explosion + if ((self->dmg_radius) && (self->dmg > (TESLA_DAMAGE*TESLA_EXPLOSION_DAMAGE_MULT))) + gi.sound(self, CHAN_ITEM, gi.soundindex("items/damage3.wav"), 1, ATTN_NORM, 0); + + Grenade_Explode(self); +} + +void tesla_die(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ +// gi.dprintf("tesla killed\n"); + tesla_remove(self); +} + +void tesla_blow (edict_t *self) +{ +// T_RadiusDamage(self, self->owner, TESLA_EXPLOSION_DAMAGE, NULL, TESLA_EXPLOSION_RADIUS, MOD_TESLA); + self->dmg = self->dmg * TESLA_EXPLOSION_DAMAGE_MULT; + self->dmg_radius = TESLA_EXPLOSION_RADIUS; + tesla_remove(self); +} + + +void tesla_zap (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ +} + +void tesla_think_active (edict_t *self) +{ + int i,num; + edict_t *touch[MAX_EDICTS], *hit; + vec3_t dir, start; + trace_t tr; + + if(level.time > self->air_finished) + { + tesla_remove(self); + return; + } + + VectorCopy(self->s.origin, start); + start[2] += 16; + + num = gi.BoxEdicts(self->teamchain->absmin, self->teamchain->absmax, touch, MAX_EDICTS, AREA_SOLID); + for(i=0;iinuse)) + break; + + hit=touch[i]; + if(!hit->inuse) + continue; + if(hit == self) + continue; + if(hit->health < 1) + continue; + // don't hit clients in single-player or coop + if(hit->client) + if (coop->value || !deathmatch->value) + continue; + if(!(hit->svflags & (SVF_MONSTER | SVF_DAMAGEABLE)) && !hit->client) + continue; + + tr = gi.trace(start, vec3_origin, vec3_origin, hit->s.origin, self, MASK_SHOT); + if(tr.fraction==1 || tr.ent==hit)// || tr.ent->client || (tr.ent->svflags & (SVF_MONSTER | SVF_DAMAGEABLE))) + { + VectorSubtract(hit->s.origin, start, dir); + + // PMM - play quad sound if it's above the "normal" damage + if (self->dmg > TESLA_DAMAGE) + gi.sound(self, CHAN_ITEM, gi.soundindex("items/damage3.wav"), 1, ATTN_NORM, 0); + + // PGM - don't do knockback to walking monsters + if((hit->svflags & SVF_MONSTER) && !(hit->flags & (FL_FLY|FL_SWIM))) + T_Damage (hit, self, self->teammaster, dir, tr.endpos, tr.plane.normal, + self->dmg, 0, 0, MOD_TESLA); + else + T_Damage (hit, self, self->teammaster, dir, tr.endpos, tr.plane.normal, + self->dmg, TESLA_KNOCKBACK, 0, MOD_TESLA); + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_LIGHTNING); + gi.WriteShort (hit - g_edicts); // destination entity + gi.WriteShort (self - g_edicts); // source entity + gi.WritePosition (tr.endpos); + gi.WritePosition (start); + gi.multicast (start, MULTICAST_PVS); + } + } + + if(self->inuse) + { + self->think = tesla_think_active; + self->nextthink = level.time + FRAMETIME; + } +} + +void tesla_activate (edict_t *self) +{ + edict_t *trigger; + edict_t *search; + + if (gi.pointcontents (self->s.origin) & (CONTENTS_SLIME|CONTENTS_LAVA|CONTENTS_WATER)) + { + tesla_blow (self); + return; + } + + // only check for spawn points in deathmatch + if (deathmatch->value) + { + search = NULL; + while ((search = findradius(search, self->s.origin, 1.5*TESLA_DAMAGE_RADIUS)) != NULL) + { + //if (!search->takedamage) + // continue; + // if it's a monster or player with health > 0 + // or it's a deathmatch start point + // and we can see it + // blow up + if(search->classname) + { + if ( ( (!strcmp(search->classname, "info_player_deathmatch")) + || (!strcmp(search->classname, "info_player_start")) + || (!strcmp(search->classname, "info_player_coop")) + || (!strcmp(search->classname, "misc_teleporter_dest")) + ) + && (visible (search, self)) + ) + { +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("Tesla to close to %s, removing!\n", search->classname); + tesla_remove (self); + return; + } + } + } + } + + trigger = G_Spawn(); +// if (trigger->nextthink) +// { +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("tesla_activate: fixing nextthink\n"); +// trigger->nextthink = 0; +// } + VectorCopy (self->s.origin, trigger->s.origin); + VectorSet (trigger->mins, -TESLA_DAMAGE_RADIUS, -TESLA_DAMAGE_RADIUS, self->mins[2]); + VectorSet (trigger->maxs, TESLA_DAMAGE_RADIUS, TESLA_DAMAGE_RADIUS, TESLA_DAMAGE_RADIUS); + trigger->movetype = MOVETYPE_NONE; + trigger->solid = SOLID_TRIGGER; + trigger->owner = self; + trigger->touch = tesla_zap; + trigger->classname = "tesla trigger"; + // doesn't need to be marked as a teamslave since the move code for bounce looks for teamchains + gi.linkentity (trigger); + + VectorClear (self->s.angles); + // clear the owner if in deathmatch + if (deathmatch->value) + self->owner = NULL; + self->teamchain = trigger; + self->think = tesla_think_active; + self->nextthink = level.time + FRAMETIME; + self->air_finished = level.time + TESLA_TIME_TO_LIVE; +} + +void tesla_think (edict_t *ent) +{ + if (gi.pointcontents (ent->s.origin) & (CONTENTS_SLIME|CONTENTS_LAVA)) + { + tesla_remove (ent); + return; + } + VectorClear (ent->s.angles); + + if(!(ent->s.frame)) + gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/teslaopen.wav"), 1, ATTN_NORM, 0); + + ent->s.frame++; + if(ent->s.frame > 14) + { + ent->s.frame = 14; + ent->think = tesla_activate; + ent->nextthink = level.time + 0.1; + } + else + { + if(ent->s.frame > 9) + { + if(ent->s.frame == 10) + { + if (ent->owner && ent->owner->client) + { + PlayerNoise(ent->owner, ent->s.origin, PNOISE_WEAPON); // PGM + } + ent->s.skinnum = 1; + } + else if(ent->s.frame == 12) + ent->s.skinnum = 2; + else if(ent->s.frame == 14) + ent->s.skinnum = 3; + } + ent->think = tesla_think; + ent->nextthink = level.time + 0.1; + } +} + +void tesla_lava (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + vec3_t land_point; + + if (plane->normal) + { + VectorMA (ent->s.origin, -20.0, plane->normal, land_point); + if (gi.pointcontents (land_point) & (CONTENTS_SLIME|CONTENTS_LAVA)) + { + tesla_blow (ent); + return; + } + } + if (random() > 0.5) + gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/hgrenb1a.wav"), 1, ATTN_NORM, 0); + else + gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/hgrenb2a.wav"), 1, ATTN_NORM, 0); +} + +void fire_tesla (edict_t *self, vec3_t start, vec3_t aimdir, int damage_multiplier, int speed) +{ + edict_t *tesla; + vec3_t dir; + vec3_t forward, right, up; + + vectoangles2 (aimdir, dir); + AngleVectors (dir, forward, right, up); + + tesla = G_Spawn(); + VectorCopy (start, tesla->s.origin); + VectorScale (aimdir, speed, tesla->velocity); + VectorMA (tesla->velocity, 200 + crandom() * 10.0, up, tesla->velocity); + VectorMA (tesla->velocity, crandom() * 10.0, right, tesla->velocity); +// VectorCopy (dir, tesla->s.angles); + VectorClear (tesla->s.angles); + tesla->movetype = MOVETYPE_BOUNCE; + tesla->solid = SOLID_BBOX; + tesla->s.effects |= EF_GRENADE; + tesla->s.renderfx |= RF_IR_VISIBLE; +// VectorClear (tesla->mins); +// VectorClear (tesla->maxs); + VectorSet (tesla->mins, -12, -12, 0); + VectorSet (tesla->maxs, 12, 12, 20); + tesla->s.modelindex = gi.modelindex ("models/weapons/g_tesla/tris.md2"); + + tesla->owner = self; // PGM - we don't want it owned by self YET. + tesla->teammaster = self; + + tesla->wait = level.time + TESLA_TIME_TO_LIVE; + tesla->think = tesla_think; + tesla->nextthink = level.time + TESLA_ACTIVATE_TIME; + + // blow up on contact with lava & slime code + tesla->touch = tesla_lava; + + if(deathmatch->value) + // PMM - lowered from 50 - 7/29/1998 + tesla->health = 20; + else + tesla->health = 30; // FIXME - change depending on skill? + + tesla->takedamage = DAMAGE_YES; + tesla->die = tesla_die; + tesla->dmg = TESLA_DAMAGE*damage_multiplier; +// tesla->dmg = 0; + tesla->classname = "tesla"; + tesla->svflags |= SVF_DAMAGEABLE; + tesla->clipmask = MASK_SHOT|CONTENTS_SLIME|CONTENTS_LAVA; + tesla->flags |= FL_MECHANICAL; + + gi.linkentity (tesla); +} +#endif + +// ************************* +// HEATBEAM +// ************************* + +#ifdef INCLUDE_BEAMS +static void fire_beams (edict_t *self, vec3_t start, vec3_t aimdir, vec3_t offset, int damage, int kick, int te_beam, int te_impact, int mod) +{ + trace_t tr; + vec3_t dir; + vec3_t forward, right, up; + vec3_t end; + vec3_t water_start, endpoint; + qboolean water = false, underwater = false; + int content_mask = MASK_SHOT | MASK_WATER; + vec3_t beam_endpt; + +// tr = gi.trace (self->s.origin, NULL, NULL, start, self, MASK_SHOT); +// if (!(tr.fraction < 1.0)) +// { + vectoangles2 (aimdir, dir); + AngleVectors (dir, forward, right, up); + + VectorMA (start, 8192, forward, end); + + if (gi.pointcontents (start) & MASK_WATER) + { +// gi.dprintf ("Heat beam under water\n"); + underwater = true; + VectorCopy (start, water_start); + content_mask &= ~MASK_WATER; + } + + tr = gi.trace (start, NULL, NULL, end, self, content_mask); + + // see if we hit water + if (tr.contents & MASK_WATER) + { + water = true; + VectorCopy (tr.endpos, water_start); + + if (!VectorCompare (start, tr.endpos)) + { + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_HEATBEAM_SPARKS); +// gi.WriteByte (50); + gi.WritePosition (water_start); + gi.WriteDir (tr.plane.normal); +// gi.WriteByte (8); +// gi.WriteShort (60); + gi.multicast (tr.endpos, MULTICAST_PVS); + } + // re-trace ignoring water this time + tr = gi.trace (water_start, NULL, NULL, end, self, MASK_SHOT); + } + VectorCopy (tr.endpos, endpoint); +// } + + // halve the damage if target underwater + if (water) + { + damage = damage /2; + } + + // send gun puff / flash + if (!((tr.surface) && (tr.surface->flags & SURF_SKY))) + { + if (tr.fraction < 1.0) + { + if (tr.ent->takedamage) + { + T_Damage (tr.ent, self, self, aimdir, tr.endpos, tr.plane.normal, damage, kick, DAMAGE_ENERGY, mod); + } + else + { + if ((!water) && (strncmp (tr.surface->name, "sky", 3))) + { + // This is the truncated steam entry - uses 1+1+2 extra bytes of data + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_HEATBEAM_STEAM); +// gi.WriteByte (20); + gi.WritePosition (tr.endpos); + gi.WriteDir (tr.plane.normal); +// gi.WriteByte (0xe0); +// gi.WriteShort (60); + gi.multicast (tr.endpos, MULTICAST_PVS); + + if (self->client) + PlayerNoise(self, tr.endpos, PNOISE_IMPACT); + } + } + } + } + + // if went through water, determine where the end and make a bubble trail + if ((water) || (underwater)) + { + vec3_t pos; + + VectorSubtract (tr.endpos, water_start, dir); + VectorNormalize (dir); + VectorMA (tr.endpos, -2, dir, pos); + if (gi.pointcontents (pos) & MASK_WATER) + VectorCopy (pos, tr.endpos); + else + tr = gi.trace (pos, NULL, NULL, water_start, tr.ent, MASK_WATER); + + VectorAdd (water_start, tr.endpos, pos); + VectorScale (pos, 0.5, pos); + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_BUBBLETRAIL2); +// gi.WriteByte (8); + gi.WritePosition (water_start); + gi.WritePosition (tr.endpos); + gi.multicast (pos, MULTICAST_PVS); + } + + if ((!underwater) && (!water)) + { + VectorCopy (tr.endpos, beam_endpt); + } + else + { + VectorCopy (endpoint, beam_endpt); + } + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (te_beam); + gi.WriteShort (self - g_edicts); + gi.WritePosition (start); + gi.WritePosition (beam_endpt); + gi.multicast (self->s.origin, MULTICAST_ALL); + +} + + +/* +================= +fire_heat + +Fires a single heat beam. Zap. +================= +*/ +void fire_heat (edict_t *self, vec3_t start, vec3_t aimdir, vec3_t offset, int damage, int kick, qboolean monster) +{ + if (monster) + fire_beams (self, start, aimdir, offset, damage, kick, TE_MONSTER_HEATBEAM, TE_HEATBEAM_SPARKS, MOD_HEATBEAM); + else + fire_beams (self, start, aimdir, offset, damage, kick, TE_HEATBEAM, TE_HEATBEAM_SPARKS, MOD_HEATBEAM); +} + +#endif + + +// ************************* +// BLASTER 2 +// ************************* + +/* +================= +fire_blaster2 + +Fires a single green blaster bolt. Used by monsters, generally. +================= +*/ +void blaster2_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + int mod; + int damagestat; + + if (other == self->owner) + return; + + if (surf && (surf->flags & SURF_SKY)) + { + G_FreeEdict (self); + return; + } + + if (self->owner && self->owner->client) + PlayerNoise(self->owner, self->s.origin, PNOISE_IMPACT); + + if (other->takedamage) + { + // the only time players will be firing blaster2 bolts will be from the + // defender sphere. + if(self->owner->client) + mod = MOD_DEFENDER_SPHERE; + else + mod = MOD_BLASTER2; + + if (self->owner) + { + damagestat = self->owner->takedamage; + self->owner->takedamage = DAMAGE_NO; + if (self->dmg >= 5) + T_RadiusDamage(self, self->owner, self->dmg*3, other, self->dmg_radius, 0); + T_Damage (other, self, self->owner, self->velocity, self->s.origin, plane->normal, self->dmg, 1, DAMAGE_ENERGY, mod); + self->owner->takedamage = damagestat; + } + else + { + if (self->dmg >= 5) + T_RadiusDamage(self, self->owner, self->dmg*3, other, self->dmg_radius, 0); + T_Damage (other, self, self->owner, self->velocity, self->s.origin, plane->normal, self->dmg, 1, DAMAGE_ENERGY, mod); + } + } + else + { + //PMM - yeowch this will get expensive + if (self->dmg >= 5) + T_RadiusDamage(self, self->owner, self->dmg*3, self->owner, self->dmg_radius, 0); + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_BLASTER2); + gi.WritePosition (self->s.origin); + if (!plane) + gi.WriteDir (vec3_origin); + else + gi.WriteDir (plane->normal); + gi.multicast (self->s.origin, MULTICAST_PVS); + } + + G_FreeEdict (self); +} + +void fire_blaster2 (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int effect, qboolean hyper) +{ + edict_t *bolt; + trace_t tr; + + VectorNormalize (dir); + + bolt = G_Spawn(); + VectorCopy (start, bolt->s.origin); + VectorCopy (start, bolt->s.old_origin); + vectoangles2 (dir, bolt->s.angles); + VectorScale (dir, speed, bolt->velocity); + bolt->movetype = MOVETYPE_FLYMISSILE; + bolt->clipmask = MASK_SHOT; + bolt->solid = SOLID_BBOX; + bolt->s.effects |= effect; + VectorClear (bolt->mins); + VectorClear (bolt->maxs); + + if (effect) + bolt->s.effects |= EF_TRACKER; + bolt->dmg_radius = 128; + bolt->s.modelindex = gi.modelindex ("models/proj/laser2/tris.md2"); + bolt->touch = blaster2_touch; + + bolt->owner = self; + bolt->nextthink = level.time + 2; + bolt->think = G_FreeEdict; + bolt->dmg = damage; + bolt->classname = "bolt"; + gi.linkentity (bolt); + + if (self->client) + check_dodge (self, bolt->s.origin, dir, speed); + + tr = gi.trace (self->s.origin, NULL, NULL, bolt->s.origin, bolt, MASK_SHOT); + if (tr.fraction < 1.0) + { + VectorMA (bolt->s.origin, -10, dir, bolt->s.origin); + bolt->touch (bolt, tr.ent, NULL, NULL); + } +} + +// ************************* +// tracker +// ************************* + +/* +void tracker_boom_think (edict_t *self) +{ + self->s.frame--; + if(self->s.frame < 0) + G_FreeEdict(self); + else + self->nextthink = level.time + 0.1; +} + +void tracker_boom_spawn (vec3_t origin) +{ + edict_t *boom; + + boom = G_Spawn(); + VectorCopy (origin, boom->s.origin); + boom->s.modelindex = gi.modelindex ("models/items/spawngro/tris.md2"); + boom->s.skinnum = 1; + boom->s.frame = 2; + boom->classname = "tracker boom"; + gi.linkentity (boom); + + boom->think = tracker_boom_think; + boom->nextthink = level.time + 0.1; + //PMM +// boom->s.renderfx |= RF_TRANSLUCENT; + boom->s.effects |= EF_SPHERETRANS; + //pmm +} +*/ + +#define TRACKER_DAMAGE_FLAGS (DAMAGE_NO_POWER_ARMOR | DAMAGE_ENERGY | DAMAGE_NO_KNOCKBACK) +#define TRACKER_IMPACT_FLAGS (DAMAGE_NO_POWER_ARMOR | DAMAGE_ENERGY) + +#define TRACKER_DAMAGE_TIME 0.5 // seconds + +void tracker_pain_daemon_think (edict_t *self) +{ + static vec3_t pain_normal = { 0, 0, 1 }; + int hurt; + + if(!self->inuse) + return; + + if((level.time - self->timestamp) > TRACKER_DAMAGE_TIME) + { + if(!self->enemy->client) + self->enemy->s.effects &= ~EF_TRACKERTRAIL; + G_FreeEdict (self); + } + else + { + if(self->enemy->health > 0) + { +// gi.dprintf("ouch %x\n", self); + T_Damage (self->enemy, self, self->owner, vec3_origin, self->enemy->s.origin, pain_normal, + self->dmg, 0, TRACKER_DAMAGE_FLAGS, MOD_TRACKER); + + // if we kill the player, we'll be removed. + if(self->inuse) + { + // if we killed a monster, gib them. + if (self->enemy->health < 1) + { + if(self->enemy->gib_health) + hurt = - self->enemy->gib_health; + else + hurt = 500; + +// gi.dprintf("non-player killed. ensuring gib! %d\n", hurt); + T_Damage (self->enemy, self, self->owner, vec3_origin, self->enemy->s.origin, + pain_normal, hurt, 0, TRACKER_DAMAGE_FLAGS, MOD_TRACKER); + } + + if(self->enemy->client) + self->enemy->client->tracker_pain_framenum = level.framenum + 1; + else + self->enemy->s.effects |= EF_TRACKERTRAIL; + + self->nextthink = level.time + FRAMETIME; + } + } + else + { + if(!self->enemy->client) + self->enemy->s.effects &= ~EF_TRACKERTRAIL; + G_FreeEdict (self); + } + } +} + +void tracker_pain_daemon_spawn (edict_t *owner, edict_t *enemy, int damage) +{ + edict_t *daemon; + + if(enemy == NULL) + return; + + daemon = G_Spawn(); + daemon->classname = "pain daemon"; + daemon->think = tracker_pain_daemon_think; + daemon->nextthink = level.time + FRAMETIME; + daemon->timestamp = level.time; + daemon->owner = owner; + daemon->enemy = enemy; + daemon->dmg = damage; +} + +void tracker_explode (edict_t *self, cplane_t *plane) +{ + vec3_t dir; + + if(!plane) + VectorClear (dir); + else + VectorScale (plane->normal, 256, dir); + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_TRACKER_EXPLOSION); + gi.WritePosition (self->s.origin); + gi.multicast (self->s.origin, MULTICAST_PVS); + +// gi.sound (self, CHAN_VOICE, gi.soundindex ("weapons/disrupthit.wav"), 1, ATTN_NORM, 0); +// tracker_boom_spawn(self->s.origin); + + G_FreeEdict (self); +} + +void tracker_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + float damagetime; + + if (other == self->owner) + return; + + if (surf && (surf->flags & SURF_SKY)) + { + G_FreeEdict (self); + return; + } + + if (self->client) + PlayerNoise(self->owner, self->s.origin, PNOISE_IMPACT); + + if (other->takedamage) + { + if((other->svflags & SVF_MONSTER) || other->client) + { + if(other->health > 0) // knockback only for living creatures + { + // PMM - kickback was times 4 .. reduced to 3 + // now this does no damage, just knockback + T_Damage (other, self, self->owner, self->velocity, self->s.origin, plane->normal, + /* self->dmg */ 0, (self->dmg*3), TRACKER_IMPACT_FLAGS, MOD_TRACKER); + + if (!(other->flags & (FL_FLY|FL_SWIM))) + other->velocity[2] += 140; + + damagetime = ((float)self->dmg)*FRAMETIME; + damagetime = damagetime / TRACKER_DAMAGE_TIME; +// gi.dprintf ("damage is %f\n", damagetime); + + tracker_pain_daemon_spawn (self->owner, other, (int)damagetime); + } + else // lots of damage (almost autogib) for dead bodies + { + T_Damage (other, self, self->owner, self->velocity, self->s.origin, plane->normal, + self->dmg*4, (self->dmg*3), TRACKER_IMPACT_FLAGS, MOD_TRACKER); + } + } + else // full damage in one shot for inanimate objects + { + T_Damage (other, self, self->owner, self->velocity, self->s.origin, plane->normal, + self->dmg, (self->dmg*3), TRACKER_IMPACT_FLAGS, MOD_TRACKER); + } + } + + tracker_explode (self, plane); + return; +} + +void tracker_fly (edict_t *self) +{ + vec3_t dest; + vec3_t dir; + vec3_t center; + + if ((!self->enemy) || (!self->enemy->inuse) || (self->enemy->health < 1)) + { + tracker_explode (self, NULL); + return; + } +/* + VectorCopy (self->enemy->s.origin, dest); + if(self->enemy->client) + dest[2] += self->enemy->viewheight; +*/ + // PMM - try to hunt for center of enemy, if possible and not client + if(self->enemy->client) + { + VectorCopy (self->enemy->s.origin, dest); + dest[2] += self->enemy->viewheight; + } + // paranoia + else if (VectorCompare(self->enemy->absmin, vec3_origin) || VectorCompare(self->enemy->absmax, vec3_origin)) + { + VectorCopy (self->enemy->s.origin, dest); + } + else + { + VectorMA (vec3_origin, 0.5, self->enemy->absmin, center); + VectorMA (center, 0.5, self->enemy->absmax, center); + VectorCopy (center, dest); + } + + VectorSubtract (dest, self->s.origin, dir); + VectorNormalize (dir); + vectoangles2 (dir, self->s.angles); + VectorScale (dir, self->speed, self->velocity); + VectorCopy(dest, self->monsterinfo.saved_goal); + + self->nextthink = level.time + 0.1; +} + +void fire_tracker (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, edict_t *enemy) +{ + edict_t *bolt; + trace_t tr; + + VectorNormalize (dir); + + bolt = G_Spawn(); + VectorCopy (start, bolt->s.origin); + VectorCopy (start, bolt->s.old_origin); + vectoangles2 (dir, bolt->s.angles); + VectorScale (dir, speed, bolt->velocity); + bolt->movetype = MOVETYPE_FLYMISSILE; + bolt->clipmask = MASK_SHOT; + bolt->solid = SOLID_BBOX; + bolt->speed = speed; + bolt->s.effects = EF_TRACKER; + bolt->s.sound = gi.soundindex ("weapons/disrupt.wav"); + VectorClear (bolt->mins); + VectorClear (bolt->maxs); + + bolt->s.modelindex = gi.modelindex ("models/proj/disintegrator/tris.md2"); + bolt->touch = tracker_touch; + bolt->enemy = enemy; + bolt->owner = self; + bolt->dmg = damage; + bolt->classname = "tracker"; + gi.linkentity (bolt); + + if(enemy) + { + bolt->nextthink = level.time + 0.1; + bolt->think = tracker_fly; + } + else + { + bolt->nextthink = level.time + 10; + bolt->think = G_FreeEdict; + } + + if (self->client) + check_dodge (self, bolt->s.origin, dir, speed); + + tr = gi.trace (self->s.origin, NULL, NULL, bolt->s.origin, bolt, MASK_SHOT); + if (tr.fraction < 1.0) + { + VectorMA (bolt->s.origin, -10, dir, bolt->s.origin); + bolt->touch (bolt, tr.ent, NULL, NULL); + } +} diff --git a/original/rogue/g_phys.c b/original/rogue/g_phys.c new file mode 100644 index 0000000..9eb15e7 --- /dev/null +++ b/original/rogue/g_phys.c @@ -0,0 +1,1124 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// g_phys.c + +#include "g_local.h" + +/* + + +pushmove objects do not obey gravity, and do not interact with each other or trigger fields, but block normal movement and push normal objects when they move. + +onground is set for toss objects when they come to a complete rest. it is set for steping or walking objects + +doors, plats, etc are SOLID_BSP, and MOVETYPE_PUSH +bonus items are SOLID_TRIGGER touch, and MOVETYPE_TOSS +corpses are SOLID_NOT and MOVETYPE_TOSS +crates are SOLID_BBOX and MOVETYPE_TOSS +walking monsters are SOLID_SLIDEBOX and MOVETYPE_STEP +flying/floating monsters are SOLID_SLIDEBOX and MOVETYPE_FLY + +solid_edge items only clip against bsp models. + +*/ + +void SV_Physics_NewToss (edict_t *ent); // PGM + + +/* +============ +SV_TestEntityPosition + +============ +*/ +edict_t *SV_TestEntityPosition (edict_t *ent) +{ + trace_t trace; + int mask; + + if (ent->clipmask) + mask = ent->clipmask; + else + mask = MASK_SOLID; + trace = gi.trace (ent->s.origin, ent->mins, ent->maxs, ent->s.origin, ent, mask); + + if (trace.startsolid) + return g_edicts; + + return NULL; +} + + +/* +================ +SV_CheckVelocity +================ +*/ +void SV_CheckVelocity (edict_t *ent) +{ + int i; + +// +// bound velocity +// + for (i=0 ; i<3 ; i++) + { + if (ent->velocity[i] > sv_maxvelocity->value) + ent->velocity[i] = sv_maxvelocity->value; + else if (ent->velocity[i] < -sv_maxvelocity->value) + ent->velocity[i] = -sv_maxvelocity->value; + } +} + +/* +============= +SV_RunThink + +Runs thinking code for this frame if necessary +============= +*/ +qboolean SV_RunThink (edict_t *ent) +{ + float thinktime; + + thinktime = ent->nextthink; + if (thinktime <= 0) + return true; + if (thinktime > level.time+0.001) + return true; + + ent->nextthink = 0; + if (!ent->think) + gi.error ("NULL ent->think"); + ent->think (ent); + + return false; +} + +/* +================== +SV_Impact + +Two entities have touched, so run their touch functions +================== +*/ +void SV_Impact (edict_t *e1, trace_t *trace) +{ + edict_t *e2; +// cplane_t backplane; + + e2 = trace->ent; + + if (e1->touch && e1->solid != SOLID_NOT) + e1->touch (e1, e2, &trace->plane, trace->surface); + + if (e2->touch && e2->solid != SOLID_NOT) + e2->touch (e2, e1, NULL, NULL); +} + + +/* +================== +ClipVelocity + +Slide off of the impacting object +returns the blocked flags (1 = floor, 2 = step / wall) +================== +*/ +#define STOP_EPSILON 0.1 + +int ClipVelocity (vec3_t in, vec3_t normal, vec3_t out, float overbounce) +{ + float backoff; + float change; + int i, blocked; + + blocked = 0; + if (normal[2] > 0) + blocked |= 1; // floor + if (!normal[2]) + blocked |= 2; // step + + backoff = DotProduct (in, normal) * overbounce; + + for (i=0 ; i<3 ; i++) + { + change = normal[i]*backoff; + out[i] = in[i] - change; + if (out[i] > -STOP_EPSILON && out[i] < STOP_EPSILON) + out[i] = 0; + } + + return blocked; +} + + +/* +============ +SV_FlyMove + +The basic solid body movement clip that slides along multiple planes +Returns the clipflags if the velocity was modified (hit something solid) +1 = floor +2 = wall / step +4 = dead stop +============ +*/ +#define MAX_CLIP_PLANES 5 +int SV_FlyMove (edict_t *ent, float time, int mask) +{ + edict_t *hit; + int bumpcount, numbumps; + vec3_t dir; + float d; + int numplanes; + vec3_t planes[MAX_CLIP_PLANES]; + vec3_t primal_velocity, original_velocity, new_velocity; + int i, j; + trace_t trace; + vec3_t end; + float time_left; + int blocked; + + numbumps = 4; + + blocked = 0; + VectorCopy (ent->velocity, original_velocity); + VectorCopy (ent->velocity, primal_velocity); + numplanes = 0; + + time_left = time; + + ent->groundentity = NULL; + for (bumpcount=0 ; bumpcounts.origin[i] + time_left * ent->velocity[i]; + + trace = gi.trace (ent->s.origin, ent->mins, ent->maxs, end, ent, mask); + + if (trace.allsolid) + { // entity is trapped in another solid + VectorCopy (vec3_origin, ent->velocity); + return 3; + } + + if (trace.fraction > 0) + { // actually covered some distance + VectorCopy (trace.endpos, ent->s.origin); + VectorCopy (ent->velocity, original_velocity); + numplanes = 0; + } + + if (trace.fraction == 1) + break; // moved the entire distance + + hit = trace.ent; + + if (trace.plane.normal[2] > 0.7) + { + blocked |= 1; // floor + if ( hit->solid == SOLID_BSP) + { + ent->groundentity = hit; + ent->groundentity_linkcount = hit->linkcount; + } + } + if (!trace.plane.normal[2]) + { + blocked |= 2; // step + } + +// +// run the impact function +// + SV_Impact (ent, &trace); + if (!ent->inuse) + break; // removed by the impact function + + + time_left -= time_left * trace.fraction; + + // cliped to another plane + if (numplanes >= MAX_CLIP_PLANES) + { // this shouldn't really happen + VectorCopy (vec3_origin, ent->velocity); + return 3; + } + + VectorCopy (trace.plane.normal, planes[numplanes]); + numplanes++; + +// +// modify original_velocity so it parallels all of the clip planes +// + for (i=0 ; ivelocity); + } + else + { // go along the crease + if (numplanes != 2) + { +// gi.dprintf ("clip velocity, numplanes == %i\n",numplanes); + VectorCopy (vec3_origin, ent->velocity); + return 7; + } + CrossProduct (planes[0], planes[1], dir); + d = DotProduct (dir, ent->velocity); + VectorScale (dir, d, ent->velocity); + } + +// +// if original velocity is against the original velocity, stop dead +// to avoid tiny occilations in sloping corners +// + if (DotProduct (ent->velocity, primal_velocity) <= 0) + { + VectorCopy (vec3_origin, ent->velocity); + return blocked; + } + } + + return blocked; +} + + +/* +============ +SV_AddGravity + +============ +*/ +void SV_AddGravity (edict_t *ent) +{ +#ifdef ROGUE_GRAVITY + if(ent->gravityVector[2] > 0) + { + VectorMA(ent->velocity, + ent->gravity * sv_gravity->value * FRAMETIME, + ent->gravityVector, + ent->velocity); + } + else + ent->velocity[2] -= ent->gravity * sv_gravity->value * FRAMETIME; +#else + ent->velocity[2] -= ent->gravity * sv_gravity->value * FRAMETIME; +#endif +} + +/* +=============================================================================== + +PUSHMOVE + +=============================================================================== +*/ + +/* +============ +SV_PushEntity + +Does not change the entities velocity at all +============ +*/ +trace_t SV_PushEntity (edict_t *ent, vec3_t push) +{ + trace_t trace; + vec3_t start; + vec3_t end; + int mask; + + VectorCopy (ent->s.origin, start); + VectorAdd (start, push, end); + +retry: + if (ent->clipmask) + mask = ent->clipmask; + else + mask = MASK_SOLID; + + trace = gi.trace (start, ent->mins, ent->maxs, end, ent, mask); + + VectorCopy (trace.endpos, ent->s.origin); + gi.linkentity (ent); + + if (trace.fraction != 1.0) + { + SV_Impact (ent, &trace); + + // if the pushed entity went away and the pusher is still there + if (!trace.ent->inuse && ent->inuse) + { + // move the pusher back and try again + VectorCopy (start, ent->s.origin); + gi.linkentity (ent); + goto retry; + } + } + +// ================ +// PGM + // FIXME - is this needed? + ent->gravity = 1.0; +// PGM +// ================ + + if (ent->inuse) + G_TouchTriggers (ent); + + return trace; +} + + +typedef struct +{ + edict_t *ent; + vec3_t origin; + vec3_t angles; + float deltayaw; +} pushed_t; +pushed_t pushed[MAX_EDICTS], *pushed_p; + +edict_t *obstacle; + +/* +============ +SV_Push + +Objects need to be moved back on a failed push, +otherwise riders would continue to slide. +============ +*/ +qboolean SV_Push (edict_t *pusher, vec3_t move, vec3_t amove) +{ + int i, e; + edict_t *check, *block; + vec3_t mins, maxs; + pushed_t *p; + vec3_t org, org2, move2, forward, right, up; + + // clamp the move to 1/8 units, so the position will + // be accurate for client side prediction + for (i=0 ; i<3 ; i++) + { + float temp; + temp = move[i]*8.0; + if (temp > 0.0) + temp += 0.5; + else + temp -= 0.5; + move[i] = 0.125 * (int)temp; + } + + // find the bounding box + for (i=0 ; i<3 ; i++) + { + mins[i] = pusher->absmin[i] + move[i]; + maxs[i] = pusher->absmax[i] + move[i]; + } + +// we need this for pushing things later + VectorSubtract (vec3_origin, amove, org); + AngleVectors (org, forward, right, up); + +// save the pusher's original position + pushed_p->ent = pusher; + VectorCopy (pusher->s.origin, pushed_p->origin); + VectorCopy (pusher->s.angles, pushed_p->angles); + if (pusher->client) + pushed_p->deltayaw = pusher->client->ps.pmove.delta_angles[YAW]; + pushed_p++; + +// move the pusher to it's final position + VectorAdd (pusher->s.origin, move, pusher->s.origin); + VectorAdd (pusher->s.angles, amove, pusher->s.angles); + gi.linkentity (pusher); + +// see if any solid entities are inside the final position + check = g_edicts+1; + for (e = 1; e < globals.num_edicts; e++, check++) + { + if (!check->inuse) + continue; + if (check->movetype == MOVETYPE_PUSH + || check->movetype == MOVETYPE_STOP + || check->movetype == MOVETYPE_NONE + || check->movetype == MOVETYPE_NOCLIP) + continue; + + if (!check->area.prev) + continue; // not linked in anywhere + + // if the entity is standing on the pusher, it will definitely be moved + if (check->groundentity != pusher) + { + // see if the ent needs to be tested + if ( check->absmin[0] >= maxs[0] + || check->absmin[1] >= maxs[1] + || check->absmin[2] >= maxs[2] + || check->absmax[0] <= mins[0] + || check->absmax[1] <= mins[1] + || check->absmax[2] <= mins[2] ) + continue; + + // see if the ent's bbox is inside the pusher's final position + if (!SV_TestEntityPosition (check)) + continue; + } + + if ((pusher->movetype == MOVETYPE_PUSH) || (check->groundentity == pusher)) + { + // move this entity + pushed_p->ent = check; + VectorCopy (check->s.origin, pushed_p->origin); + VectorCopy (check->s.angles, pushed_p->angles); + pushed_p++; + + // try moving the contacted entity + VectorAdd (check->s.origin, move, check->s.origin); + if (check->client) + { // FIXME: doesn't rotate monsters? + check->client->ps.pmove.delta_angles[YAW] += amove[YAW]; + } + + // figure movement due to the pusher's amove + VectorSubtract (check->s.origin, pusher->s.origin, org); + org2[0] = DotProduct (org, forward); + org2[1] = -DotProduct (org, right); + org2[2] = DotProduct (org, up); + VectorSubtract (org2, org, move2); + VectorAdd (check->s.origin, move2, check->s.origin); + + // may have pushed them off an edge + if (check->groundentity != pusher) + check->groundentity = NULL; + + block = SV_TestEntityPosition (check); + if (!block) + { // pushed ok + gi.linkentity (check); + // impact? + continue; + } + + // if it is ok to leave in the old position, do it + // this is only relevent for riding entities, not pushed + // FIXME: this doesn't acount for rotation + VectorSubtract (check->s.origin, move, check->s.origin); + block = SV_TestEntityPosition (check); + if (!block) + { + pushed_p--; + continue; + } + } + + // save off the obstacle so we can call the block function + obstacle = check; + + // move back any entities we already moved + // go backwards, so if the same entity was pushed + // twice, it goes back to the original position + for (p=pushed_p-1 ; p>=pushed ; p--) + { + VectorCopy (p->origin, p->ent->s.origin); + VectorCopy (p->angles, p->ent->s.angles); + if (p->ent->client) + { + p->ent->client->ps.pmove.delta_angles[YAW] = p->deltayaw; + } + gi.linkentity (p->ent); + } + return false; + } + +//FIXME: is there a better way to handle this? + // see if anything we moved has touched a trigger + for (p=pushed_p-1 ; p>=pushed ; p--) + G_TouchTriggers (p->ent); + + return true; +} + +/* +================ +SV_Physics_Pusher + +Bmodel objects don't interact with each other, but +push all box objects +================ +*/ +void SV_Physics_Pusher (edict_t *ent) +{ + vec3_t move, amove; + edict_t *part, *mv; + + // if not a team captain, so movement will be handled elsewhere + if ( ent->flags & FL_TEAMSLAVE) + return; + + // make sure all team slaves can move before commiting + // any moves or calling any think functions + // if the move is blocked, all moved objects will be backed out +//retry: + pushed_p = pushed; + for (part = ent ; part ; part=part->teamchain) + { + if (part->velocity[0] || part->velocity[1] || part->velocity[2] || + part->avelocity[0] || part->avelocity[1] || part->avelocity[2] + ) + { // object is moving + VectorScale (part->velocity, FRAMETIME, move); + VectorScale (part->avelocity, FRAMETIME, amove); + + if (!SV_Push (part, move, amove)) + break; // move was blocked + } + } + if (pushed_p > &pushed[MAX_EDICTS]) + gi.error (ERR_FATAL, "pushed_p > &pushed[MAX_EDICTS], memory corrupted"); + + if (part) + { + // the move failed, bump all nextthink times and back out moves + for (mv = ent ; mv ; mv=mv->teamchain) + { + if (mv->nextthink > 0) + mv->nextthink += FRAMETIME; + } + + // if the pusher has a "blocked" function, call it + // otherwise, just stay in place until the obstacle is gone + if (part->blocked) + part->blocked (part, obstacle); +#if 0 + // if the pushed entity went away and the pusher is still there + if (!obstacle->inuse && part->inuse) + goto retry; +#endif + } + else + { + // the move succeeded, so call all think functions + for (part = ent ; part ; part=part->teamchain) + { + // prevent entities that are on trains that have gone away from thinking! + if (part->inuse) + SV_RunThink (part); + } + } +} + +//================================================================== + +/* +============= +SV_Physics_None + +Non moving objects can only think +============= +*/ +void SV_Physics_None (edict_t *ent) +{ +// regular thinking + SV_RunThink (ent); +} + +/* +============= +SV_Physics_Noclip + +A moving object that doesn't obey physics +============= +*/ +void SV_Physics_Noclip (edict_t *ent) +{ +// regular thinking + if (!SV_RunThink (ent)) + return; + + VectorMA (ent->s.angles, FRAMETIME, ent->avelocity, ent->s.angles); + VectorMA (ent->s.origin, FRAMETIME, ent->velocity, ent->s.origin); + + gi.linkentity (ent); +} + +/* +============================================================================== + +TOSS / BOUNCE + +============================================================================== +*/ + +/* +============= +SV_Physics_Toss + +Toss, bounce, and fly movement. When onground, do nothing. +============= +*/ +void SV_Physics_Toss (edict_t *ent) +{ + trace_t trace; + vec3_t move; + float backoff; + edict_t *slave; + qboolean wasinwater; + qboolean isinwater; + vec3_t old_origin; + +// regular thinking + SV_RunThink (ent); + + // if not a team captain, so movement will be handled elsewhere + if ( ent->flags & FL_TEAMSLAVE) + return; + + if (ent->velocity[2] > 0) + ent->groundentity = NULL; + +// check for the groundentity going away + if (ent->groundentity) + if (!ent->groundentity->inuse) + ent->groundentity = NULL; + +// if onground, return without moving + if ( ent->groundentity && ent->gravity > 0.0) // PGM - gravity hack + return; + + VectorCopy (ent->s.origin, old_origin); + + SV_CheckVelocity (ent); + +// add gravity + if (ent->movetype != MOVETYPE_FLY + && ent->movetype != MOVETYPE_FLYMISSILE) + SV_AddGravity (ent); + +// move angles + VectorMA (ent->s.angles, FRAMETIME, ent->avelocity, ent->s.angles); + +// move origin + VectorScale (ent->velocity, FRAMETIME, move); + trace = SV_PushEntity (ent, move); + if (!ent->inuse) + return; + + if (trace.fraction < 1) + { + if (ent->movetype == MOVETYPE_BOUNCE) + backoff = 1.5; + else + backoff = 1; + + ClipVelocity (ent->velocity, trace.plane.normal, ent->velocity, backoff); + + // stop if on ground + if (trace.plane.normal[2] > 0.7) + { + if (ent->velocity[2] < 60 || ent->movetype != MOVETYPE_BOUNCE ) + { + ent->groundentity = trace.ent; + ent->groundentity_linkcount = trace.ent->linkcount; + VectorCopy (vec3_origin, ent->velocity); + VectorCopy (vec3_origin, ent->avelocity); + } + } + +// if (ent->touch) +// ent->touch (ent, trace.ent, &trace.plane, trace.surface); + } + +// check for water transition + wasinwater = (ent->watertype & MASK_WATER); + ent->watertype = gi.pointcontents (ent->s.origin); + isinwater = ent->watertype & MASK_WATER; + + if (isinwater) + ent->waterlevel = 1; + else + ent->waterlevel = 0; + + if (!wasinwater && isinwater) + gi.positioned_sound (old_origin, g_edicts, CHAN_AUTO, gi.soundindex("misc/h2ohit1.wav"), 1, 1, 0); + else if (wasinwater && !isinwater) + gi.positioned_sound (ent->s.origin, g_edicts, CHAN_AUTO, gi.soundindex("misc/h2ohit1.wav"), 1, 1, 0); + +// move teamslaves + for (slave = ent->teamchain; slave; slave = slave->teamchain) + { + VectorCopy (ent->s.origin, slave->s.origin); + gi.linkentity (slave); + } +} + +/* +=============================================================================== + +STEPPING MOVEMENT + +=============================================================================== +*/ + +/* +============= +SV_Physics_Step + +Monsters freefall when they don't have a ground entity, otherwise +all movement is done with discrete steps. + +This is also used for objects that have become still on the ground, but +will fall if the floor is pulled out from under them. +FIXME: is this true? +============= +*/ + +//FIXME: hacked in for E3 demo +//#define sv_stopspeed 100 +#define sv_friction 6 +#define sv_waterfriction 1 + +void SV_AddRotationalFriction (edict_t *ent) +{ + int n; + float adjustment; + + VectorMA (ent->s.angles, FRAMETIME, ent->avelocity, ent->s.angles); + adjustment = FRAMETIME * sv_stopspeed->value * sv_friction; //PGM now a cvar + for (n = 0; n < 3; n++) + { + if (ent->avelocity[n] > 0) + { + ent->avelocity[n] -= adjustment; + if (ent->avelocity[n] < 0) + ent->avelocity[n] = 0; + } + else + { + ent->avelocity[n] += adjustment; + if (ent->avelocity[n] > 0) + ent->avelocity[n] = 0; + } + } +} + +void SV_Physics_Step (edict_t *ent) +{ + qboolean wasonground; + qboolean hitsound = false; + float *vel; + float speed, newspeed, control; + float friction; + edict_t *groundentity; + int mask; + + // airborn monsters should always check for ground + if (!ent->groundentity) + M_CheckGround (ent); + + groundentity = ent->groundentity; + + SV_CheckVelocity (ent); + + if (groundentity) + wasonground = true; + else + wasonground = false; + + if (ent->avelocity[0] || ent->avelocity[1] || ent->avelocity[2]) + SV_AddRotationalFriction (ent); + + // add gravity except: + // flying monsters + // swimming monsters who are in the water + if (! wasonground) + if (!(ent->flags & FL_FLY)) + if (!((ent->flags & FL_SWIM) && (ent->waterlevel > 2))) + { + if (ent->velocity[2] < sv_gravity->value*-0.1) + hitsound = true; + if (ent->waterlevel == 0) + SV_AddGravity (ent); + } + + // friction for flying monsters that have been given vertical velocity + if ((ent->flags & FL_FLY) && (ent->velocity[2] != 0)) + { + speed = fabs(ent->velocity[2]); + control = speed < sv_stopspeed->value ? sv_stopspeed->value : speed; + friction = sv_friction/3; + newspeed = speed - (FRAMETIME * control * friction); + if (newspeed < 0) + newspeed = 0; + newspeed /= speed; + ent->velocity[2] *= newspeed; + } + + // friction for flying monsters that have been given vertical velocity + if ((ent->flags & FL_SWIM) && (ent->velocity[2] != 0)) + { + speed = fabs(ent->velocity[2]); + control = speed < sv_stopspeed->value ? sv_stopspeed->value : speed; + newspeed = speed - (FRAMETIME * control * sv_waterfriction * ent->waterlevel); + if (newspeed < 0) + newspeed = 0; + newspeed /= speed; + ent->velocity[2] *= newspeed; + } + + if (ent->velocity[2] || ent->velocity[1] || ent->velocity[0]) + { + // apply friction + // let dead monsters who aren't completely onground slide + if ((wasonground) || (ent->flags & (FL_SWIM|FL_FLY))) + if (!(ent->health <= 0.0 && !M_CheckBottom(ent))) + { + vel = ent->velocity; + speed = sqrt(vel[0]*vel[0] +vel[1]*vel[1]); + if (speed) + { + friction = sv_friction; + + control = speed < sv_stopspeed->value ? sv_stopspeed->value : speed; + newspeed = speed - FRAMETIME*control*friction; + + if (newspeed < 0) + newspeed = 0; + newspeed /= speed; + + vel[0] *= newspeed; + vel[1] *= newspeed; + } + } + + if (ent->svflags & SVF_MONSTER) + mask = MASK_MONSTERSOLID; + else + mask = MASK_SOLID; + SV_FlyMove (ent, FRAMETIME, mask); + + gi.linkentity (ent); + +// ======== +// PGM - reset this every time they move. +// G_touchtriggers will set it back if appropriate + ent->gravity = 1.0; +// ======== + + G_TouchTriggers (ent); + if (!ent->inuse) + return; + + if (ent->groundentity) + if (!wasonground) + if (hitsound) + gi.sound (ent, 0, gi.soundindex("world/land.wav"), 1, 1, 0); + } + + if(!ent->inuse) // PGM g_touchtrigger free problem + return; + +// regular thinking + SV_RunThink (ent); +} + +//============================================================================ +/* +================ +G_RunEntity + +================ +*/ +void G_RunEntity (edict_t *ent) +{ +//PGM + trace_t trace; + vec3_t previous_origin; + + if(ent->movetype == MOVETYPE_STEP) + VectorCopy(ent->s.origin, previous_origin); +//PGM + + if (ent->prethink) + ent->prethink (ent); + + switch ( (int)ent->movetype) + { + case MOVETYPE_PUSH: + case MOVETYPE_STOP: + SV_Physics_Pusher (ent); + break; + case MOVETYPE_NONE: + SV_Physics_None (ent); + break; + case MOVETYPE_NOCLIP: + SV_Physics_Noclip (ent); + break; + case MOVETYPE_STEP: + SV_Physics_Step (ent); + break; + case MOVETYPE_TOSS: + case MOVETYPE_BOUNCE: + case MOVETYPE_FLY: + case MOVETYPE_FLYMISSILE: + SV_Physics_Toss (ent); + break; + case MOVETYPE_NEWTOSS: + SV_Physics_NewToss (ent); + break; + default: + gi.error ("SV_Physics: bad movetype %i", (int)ent->movetype); + } + +//PGM + if(ent->movetype == MOVETYPE_STEP) + { + // if we moved, check and fix origin if needed + if (!VectorCompare(ent->s.origin, previous_origin)) + { + trace = gi.trace (ent->s.origin, ent->mins, ent->maxs, previous_origin, ent, MASK_MONSTERSOLID); + if(trace.allsolid || trace.startsolid) + VectorCopy (previous_origin, ent->s.origin); + } + } +//PGM +} + +//============ +//ROGUE +/* +============= +SV_Physics_NewToss + +Toss, bounce, and fly movement. When on ground and no velocity, do nothing. With velocity, +slide. +============= +*/ +void SV_Physics_NewToss (edict_t *ent) +{ + trace_t trace; + vec3_t move; +// float backoff; + edict_t *slave; + qboolean wasinwater; + qboolean isinwater; + qboolean wasonground; + float speed, newspeed; + vec3_t old_origin; +// float firstmove; +// int mask; + + // regular thinking + SV_RunThink (ent); + + // if not a team captain, so movement will be handled elsewhere + if ( ent->flags & FL_TEAMSLAVE) + return; + + if (ent->groundentity) + wasonground = true; + else + wasonground = false; + + wasinwater = ent->waterlevel; + + // find out what we're sitting on. + VectorCopy (ent->s.origin, move); + move[2] -= 0.25; + trace = gi.trace (ent->s.origin, ent->mins, ent->maxs, move, ent, ent->clipmask); + if(ent->groundentity && ent->groundentity->inuse) + ent->groundentity = trace.ent; + else + ent->groundentity = NULL; + + // if we're sitting on something flat and have no velocity of our own, return. + if (ent->groundentity && (trace.plane.normal[2] == 1.0) && + !ent->velocity[0] && !ent->velocity[1] && !ent->velocity[2]) + { + return; + } + + // store the old origin + VectorCopy (ent->s.origin, old_origin); + + SV_CheckVelocity (ent); + + // add gravity + SV_AddGravity (ent); + + if (ent->avelocity[0] || ent->avelocity[1] || ent->avelocity[2]) + SV_AddRotationalFriction (ent); + + // add friction + speed = VectorLength(ent->velocity); + if(ent->waterlevel) // friction for water movement + { + newspeed = speed - (sv_waterfriction * 6 * ent->waterlevel); + if (newspeed < 0) + newspeed = 0; + newspeed /= speed; + VectorScale (ent->velocity, newspeed, ent->velocity); + } + else if (!ent->groundentity) // friction for air movement + { + newspeed = speed - ((sv_friction)); + if (newspeed < 0) + newspeed = 0; + newspeed /= speed; + VectorScale (ent->velocity, newspeed, ent->velocity); + } + else // use ground friction + { + newspeed = speed - (sv_friction * 6); + if (newspeed < 0) + newspeed = 0; + newspeed /= speed; + VectorScale (ent->velocity, newspeed, ent->velocity); + } + + SV_FlyMove (ent, FRAMETIME, ent->clipmask); + gi.linkentity (ent); + + G_TouchTriggers (ent); + +// check for water transition + wasinwater = (ent->watertype & MASK_WATER); + ent->watertype = gi.pointcontents (ent->s.origin); + isinwater = ent->watertype & MASK_WATER; + + if (isinwater) + ent->waterlevel = 1; + else + ent->waterlevel = 0; + + if (!wasinwater && isinwater) + gi.positioned_sound (old_origin, g_edicts, CHAN_AUTO, gi.soundindex("misc/h2ohit1.wav"), 1, 1, 0); + else if (wasinwater && !isinwater) + gi.positioned_sound (ent->s.origin, g_edicts, CHAN_AUTO, gi.soundindex("misc/h2ohit1.wav"), 1, 1, 0); + +// move teamslaves + for (slave = ent->teamchain; slave; slave = slave->teamchain) + { + VectorCopy (ent->s.origin, slave->s.origin); + gi.linkentity (slave); + } +} + +//ROGUE +//============ \ No newline at end of file diff --git a/original/rogue/g_save.c b/original/rogue/g_save.c new file mode 100644 index 0000000..8d2e9f4 --- /dev/null +++ b/original/rogue/g_save.c @@ -0,0 +1,800 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. + +#include "g_local.h" + +#define Function(f) {#f, f} + +mmove_t mmove_reloc; + +field_t fields[] = { + {"classname", FOFS(classname), F_LSTRING}, + {"model", FOFS(model), F_LSTRING}, + {"spawnflags", FOFS(spawnflags), F_INT}, + {"speed", FOFS(speed), F_FLOAT}, + {"accel", FOFS(accel), F_FLOAT}, + {"decel", FOFS(decel), F_FLOAT}, + {"target", FOFS(target), F_LSTRING}, + {"targetname", FOFS(targetname), F_LSTRING}, + {"pathtarget", FOFS(pathtarget), F_LSTRING}, + {"deathtarget", FOFS(deathtarget), F_LSTRING}, + {"killtarget", FOFS(killtarget), F_LSTRING}, + {"combattarget", FOFS(combattarget), F_LSTRING}, + {"message", FOFS(message), F_LSTRING}, + {"team", FOFS(team), F_LSTRING}, + {"wait", FOFS(wait), F_FLOAT}, + {"delay", FOFS(delay), F_FLOAT}, + {"random", FOFS(random), F_FLOAT}, + {"move_origin", FOFS(move_origin), F_VECTOR}, + {"move_angles", FOFS(move_angles), F_VECTOR}, + {"style", FOFS(style), F_INT}, + {"count", FOFS(count), F_INT}, + {"health", FOFS(health), F_INT}, + {"sounds", FOFS(sounds), F_INT}, + {"light", 0, F_IGNORE}, + {"dmg", FOFS(dmg), F_INT}, + {"mass", FOFS(mass), F_INT}, + {"volume", FOFS(volume), F_FLOAT}, + {"attenuation", FOFS(attenuation), F_FLOAT}, + {"map", FOFS(map), F_LSTRING}, + {"origin", FOFS(s.origin), F_VECTOR}, + {"angles", FOFS(s.angles), F_VECTOR}, + {"angle", FOFS(s.angles), F_ANGLEHACK}, + + {"goalentity", FOFS(goalentity), F_EDICT, FFL_NOSPAWN}, + {"movetarget", FOFS(movetarget), F_EDICT, FFL_NOSPAWN}, + {"enemy", FOFS(enemy), F_EDICT, FFL_NOSPAWN}, + {"oldenemy", FOFS(oldenemy), F_EDICT, FFL_NOSPAWN}, + {"activator", FOFS(activator), F_EDICT, FFL_NOSPAWN}, + {"groundentity", FOFS(groundentity), F_EDICT, FFL_NOSPAWN}, + {"teamchain", FOFS(teamchain), F_EDICT, FFL_NOSPAWN}, + {"teammaster", FOFS(teammaster), F_EDICT, FFL_NOSPAWN}, + {"owner", FOFS(owner), F_EDICT, FFL_NOSPAWN}, + {"mynoise", FOFS(mynoise), F_EDICT, FFL_NOSPAWN}, + {"mynoise2", FOFS(mynoise2), F_EDICT, FFL_NOSPAWN}, + {"target_ent", FOFS(target_ent), F_EDICT, FFL_NOSPAWN}, + {"chain", FOFS(chain), F_EDICT, FFL_NOSPAWN}, + + {"prethink", FOFS(prethink), F_FUNCTION, FFL_NOSPAWN}, + {"think", FOFS(think), F_FUNCTION, FFL_NOSPAWN}, + {"blocked", FOFS(blocked), F_FUNCTION, FFL_NOSPAWN}, + {"touch", FOFS(touch), F_FUNCTION, FFL_NOSPAWN}, + {"use", FOFS(use), F_FUNCTION, FFL_NOSPAWN}, + {"pain", FOFS(pain), F_FUNCTION, FFL_NOSPAWN}, + {"die", FOFS(die), F_FUNCTION, FFL_NOSPAWN}, + + {"stand", FOFS(monsterinfo.stand), F_FUNCTION, FFL_NOSPAWN}, + {"idle", FOFS(monsterinfo.idle), F_FUNCTION, FFL_NOSPAWN}, + {"search", FOFS(monsterinfo.search), F_FUNCTION, FFL_NOSPAWN}, + {"walk", FOFS(monsterinfo.walk), F_FUNCTION, FFL_NOSPAWN}, + {"run", FOFS(monsterinfo.run), F_FUNCTION, FFL_NOSPAWN}, + {"dodge", FOFS(monsterinfo.dodge), F_FUNCTION, FFL_NOSPAWN}, + {"attack", FOFS(monsterinfo.attack), F_FUNCTION, FFL_NOSPAWN}, + {"melee", FOFS(monsterinfo.melee), F_FUNCTION, FFL_NOSPAWN}, + {"sight", FOFS(monsterinfo.sight), F_FUNCTION, FFL_NOSPAWN}, + {"checkattack", FOFS(monsterinfo.checkattack), F_FUNCTION, FFL_NOSPAWN}, + {"currentmove", FOFS(monsterinfo.currentmove), F_MMOVE, FFL_NOSPAWN}, + + {"endfunc", FOFS(moveinfo.endfunc), F_FUNCTION, FFL_NOSPAWN}, + + // temp spawn vars -- only valid when the spawn function is called + {"lip", STOFS(lip), F_INT, FFL_SPAWNTEMP}, + {"distance", STOFS(distance), F_INT, FFL_SPAWNTEMP}, + {"height", STOFS(height), F_INT, FFL_SPAWNTEMP}, + {"noise", STOFS(noise), F_LSTRING, FFL_SPAWNTEMP}, + {"pausetime", STOFS(pausetime), F_FLOAT, FFL_SPAWNTEMP}, + {"item", STOFS(item), F_LSTRING, FFL_SPAWNTEMP}, + +//need for item field in edict struct, FFL_SPAWNTEMP item will be skipped on saves + {"item", FOFS(item), F_ITEM}, + + {"gravity", STOFS(gravity), F_LSTRING, FFL_SPAWNTEMP}, + {"sky", STOFS(sky), F_LSTRING, FFL_SPAWNTEMP}, + {"skyrotate", STOFS(skyrotate), F_FLOAT, FFL_SPAWNTEMP}, + {"skyaxis", STOFS(skyaxis), F_VECTOR, FFL_SPAWNTEMP}, + {"minyaw", STOFS(minyaw), F_FLOAT, FFL_SPAWNTEMP}, + {"maxyaw", STOFS(maxyaw), F_FLOAT, FFL_SPAWNTEMP}, + {"minpitch", STOFS(minpitch), F_FLOAT, FFL_SPAWNTEMP}, + {"maxpitch", STOFS(maxpitch), F_FLOAT, FFL_SPAWNTEMP}, + {"nextmap", STOFS(nextmap), F_LSTRING, FFL_SPAWNTEMP}, + + // ROGUE + {"bad_area", FOFS(bad_area), F_EDICT}, + // while the hint_path stuff could be reassembled on the fly, no reason to be different + {"hint_chain", FOFS(hint_chain), F_EDICT}, + {"monster_hint_chain", FOFS(monster_hint_chain), F_EDICT}, + {"target_hint_chain", FOFS(target_hint_chain), F_EDICT}, + // + {"goal_hint", FOFS(monsterinfo.goal_hint), F_EDICT}, + {"badMedic1", FOFS(monsterinfo.badMedic1), F_EDICT}, + {"badMedic2", FOFS(monsterinfo.badMedic2), F_EDICT}, + {"last_player_enemy", FOFS(monsterinfo.last_player_enemy), F_EDICT}, + {"commander", FOFS(monsterinfo.commander), F_EDICT}, + {"blocked", FOFS(monsterinfo.blocked), F_MMOVE, FFL_NOSPAWN}, + {"duck", FOFS(monsterinfo.duck), F_MMOVE, FFL_NOSPAWN}, + {"unduck", FOFS(monsterinfo.unduck), F_MMOVE, FFL_NOSPAWN}, + {"sidestep", FOFS(monsterinfo.sidestep), F_MMOVE, FFL_NOSPAWN}, + // ROGUE + + {0, 0, 0, 0} + +}; + +field_t levelfields[] = +{ + {"changemap", LLOFS(changemap), F_LSTRING}, + + {"sight_client", LLOFS(sight_client), F_EDICT}, + {"sight_entity", LLOFS(sight_entity), F_EDICT}, + {"sound_entity", LLOFS(sound_entity), F_EDICT}, + {"sound2_entity", LLOFS(sound2_entity), F_EDICT}, + + // ROGUE + {"disguise_violator", LLOFS(disguise_violator), F_EDICT}, + // ROGUE + + {NULL, 0, F_INT} +}; + +field_t clientfields[] = +{ + {"pers.weapon", CLOFS(pers.weapon), F_ITEM}, + {"pers.lastweapon", CLOFS(pers.lastweapon), F_ITEM}, + {"newweapon", CLOFS(newweapon), F_ITEM}, + // ROGUE + {"owned_sphere", CLOFS(owned_sphere), F_EDICT}, + // ROGUE + + {NULL, 0, F_INT} +}; + +/* +============ +InitGame + +This will be called when the dll is first loaded, which +only happens when a new game is started or a save game +is loaded. +============ +*/ +void InitGame (void) +{ + gi.dprintf ("==== InitGame ====\n"); + + gun_x = gi.cvar ("gun_x", "0", 0); + gun_y = gi.cvar ("gun_y", "0", 0); + gun_z = gi.cvar ("gun_z", "0", 0); + + //FIXME: sv_ prefix is wrong for these + sv_rollspeed = gi.cvar ("sv_rollspeed", "200", 0); + sv_rollangle = gi.cvar ("sv_rollangle", "2", 0); + sv_maxvelocity = gi.cvar ("sv_maxvelocity", "2000", 0); + sv_gravity = gi.cvar ("sv_gravity", "800", 0); + + sv_stopspeed = gi.cvar ("sv_stopspeed", "100", 0); // PGM - was #define in g_phys.c + +//ROGUE + g_showlogic = gi.cvar ("g_showlogic", "0", 0); + huntercam = gi.cvar ("huntercam", "1", CVAR_SERVERINFO|CVAR_LATCH); + strong_mines = gi.cvar ("strong_mines", "0", 0); + randomrespawn = gi.cvar ("randomrespawn", "0", 0); +//ROGUE + + // noset vars + dedicated = gi.cvar ("dedicated", "0", CVAR_NOSET); + + // latched vars + sv_cheats = gi.cvar ("cheats", "0", CVAR_SERVERINFO|CVAR_LATCH); + gi.cvar ("gamename", GAMEVERSION , CVAR_SERVERINFO | CVAR_LATCH); + gi.cvar ("gamedate", __DATE__ , CVAR_SERVERINFO | CVAR_LATCH); + + maxclients = gi.cvar ("maxclients", "4", CVAR_SERVERINFO | CVAR_LATCH); + maxspectators = gi.cvar ("maxspectators", "4", CVAR_SERVERINFO); + deathmatch = gi.cvar ("deathmatch", "0", CVAR_LATCH); + coop = gi.cvar ("coop", "0", CVAR_LATCH); + skill = gi.cvar ("skill", "1", CVAR_LATCH); + maxentities = gi.cvar ("maxentities", "1024", CVAR_LATCH); + gamerules = gi.cvar ("gamerules", "0", CVAR_LATCH); //PGM + + // change anytime vars + dmflags = gi.cvar ("dmflags", "0", CVAR_SERVERINFO); + fraglimit = gi.cvar ("fraglimit", "0", CVAR_SERVERINFO); + timelimit = gi.cvar ("timelimit", "0", CVAR_SERVERINFO); + password = gi.cvar ("password", "", CVAR_USERINFO); + spectator_password = gi.cvar ("spectator_password", "", CVAR_USERINFO); + filterban = gi.cvar ("filterban", "1", 0); + + g_select_empty = gi.cvar ("g_select_empty", "0", CVAR_ARCHIVE); + + run_pitch = gi.cvar ("run_pitch", "0.002", 0); + run_roll = gi.cvar ("run_roll", "0.005", 0); + bob_up = gi.cvar ("bob_up", "0.005", 0); + bob_pitch = gi.cvar ("bob_pitch", "0.002", 0); + bob_roll = gi.cvar ("bob_roll", "0.002", 0); + + // flood control + flood_msgs = gi.cvar ("flood_msgs", "4", 0); + flood_persecond = gi.cvar ("flood_persecond", "4", 0); + flood_waitdelay = gi.cvar ("flood_waitdelay", "10", 0); + + // dm map list + sv_maplist = gi.cvar ("sv_maplist", "", 0); + + // items + InitItems (); + + Com_sprintf (game.helpmessage1, sizeof(game.helpmessage1), ""); + + Com_sprintf (game.helpmessage2, sizeof(game.helpmessage2), ""); + + // initialize all entities for this game + game.maxentities = maxentities->value; + g_edicts = gi.TagMalloc (game.maxentities * sizeof(g_edicts[0]), TAG_GAME); + globals.edicts = g_edicts; + globals.max_edicts = game.maxentities; + + // initialize all clients for this game + game.maxclients = maxclients->value; + game.clients = gi.TagMalloc (game.maxclients * sizeof(game.clients[0]), TAG_GAME); + globals.num_edicts = game.maxclients+1; + +//====== +//ROGUE + if(gamerules) + { + InitGameRules(); // if there are game rules to set up, do so now. + } +//ROGUE +//====== +} + +//========================================================= + +void WriteField1 (FILE *f, field_t *field, byte *base) +{ + void *p; + int len; + int index; + + if (field->flags & FFL_SPAWNTEMP) + return; + + p = (void *)(base + field->ofs); + switch (field->type) + { + case F_INT: + case F_FLOAT: + case F_ANGLEHACK: + case F_VECTOR: + case F_IGNORE: + break; + + case F_LSTRING: + case F_GSTRING: + if ( *(char **)p ) + len = strlen(*(char **)p) + 1; + else + len = 0; + *(int *)p = len; + break; + case F_EDICT: + if ( *(edict_t **)p == NULL) + index = -1; + else + index = *(edict_t **)p - g_edicts; + *(int *)p = index; + break; + case F_CLIENT: + if ( *(gclient_t **)p == NULL) + index = -1; + else + index = *(gclient_t **)p - game.clients; + *(int *)p = index; + break; + case F_ITEM: + if ( *(edict_t **)p == NULL) + index = -1; + else + index = *(gitem_t **)p - itemlist; + *(int *)p = index; + break; + + //relative to code segment + case F_FUNCTION: + if (*(byte **)p == NULL) + index = 0; + else + index = *(byte **)p - ((byte *)InitGame); + *(int *)p = index; + break; + + //relative to data segment + case F_MMOVE: + if (*(byte **)p == NULL) + index = 0; + else + index = *(byte **)p - (byte *)&mmove_reloc; + *(int *)p = index; + break; + + default: + gi.error ("WriteEdict: unknown field type"); + } +} + + +void WriteField2 (FILE *f, field_t *field, byte *base) +{ + int len; + void *p; + + if (field->flags & FFL_SPAWNTEMP) + return; + + p = (void *)(base + field->ofs); + switch (field->type) + { + case F_LSTRING: + if ( *(char **)p ) + { + len = strlen(*(char **)p) + 1; + fwrite (*(char **)p, len, 1, f); + } + break; + } +} + +void ReadField (FILE *f, field_t *field, byte *base) +{ + void *p; + int len; + int index; + + if (field->flags & FFL_SPAWNTEMP) + return; + + p = (void *)(base + field->ofs); + switch (field->type) + { + case F_INT: + case F_FLOAT: + case F_ANGLEHACK: + case F_VECTOR: + case F_IGNORE: + break; + + case F_LSTRING: + len = *(int *)p; + if (!len) + *(char **)p = NULL; + else + { + *(char **)p = gi.TagMalloc (len, TAG_LEVEL); + fread (*(char **)p, len, 1, f); + } + break; + case F_EDICT: + index = *(int *)p; + if ( index == -1 ) + *(edict_t **)p = NULL; + else + *(edict_t **)p = &g_edicts[index]; + break; + case F_CLIENT: + index = *(int *)p; + if ( index == -1 ) + *(gclient_t **)p = NULL; + else + *(gclient_t **)p = &game.clients[index]; + break; + case F_ITEM: + index = *(int *)p; + if ( index == -1 ) + *(gitem_t **)p = NULL; + else + *(gitem_t **)p = &itemlist[index]; + break; + + //relative to code segment + case F_FUNCTION: + index = *(int *)p; + if ( index == 0 ) + *(byte **)p = NULL; + else + *(byte **)p = ((byte *)InitGame) + index; + break; + + //relative to data segment + case F_MMOVE: + index = *(int *)p; + if (index == 0) + *(byte **)p = NULL; + else + *(byte **)p = (byte *)&mmove_reloc + index; + break; + + default: + gi.error ("ReadEdict: unknown field type"); + } +} + +//========================================================= + +/* +============== +WriteClient + +All pointer variables (except function pointers) must be handled specially. +============== +*/ +void WriteClient (FILE *f, gclient_t *client) +{ + field_t *field; + gclient_t temp; + + // all of the ints, floats, and vectors stay as they are + temp = *client; + + // change the pointers to lengths or indexes + for (field=clientfields ; field->name ; field++) + { + WriteField1 (f, field, (byte *)&temp); + } + + // write the block + fwrite (&temp, sizeof(temp), 1, f); + + // now write any allocated data following the edict + for (field=clientfields ; field->name ; field++) + { + WriteField2 (f, field, (byte *)client); + } +} + +/* +============== +ReadClient + +All pointer variables (except function pointers) must be handled specially. +============== +*/ +void ReadClient (FILE *f, gclient_t *client) +{ + field_t *field; + + fread (client, sizeof(*client), 1, f); + + for (field=clientfields ; field->name ; field++) + { + ReadField (f, field, (byte *)client); + } +} + +/* +============ +WriteGame + +This will be called whenever the game goes to a new level, +and when the user explicitly saves the game. + +Game information include cross level data, like multi level +triggers, help computer info, and all client states. + +A single player death will automatically restore from the +last save position. +============ +*/ +void WriteGame (char *filename, qboolean autosave) +{ + FILE *f; + int i; + char str[16]; + + if (!autosave) + SaveClientData (); + + f = fopen (filename, "wb"); + if (!f) + gi.error ("Couldn't open %s", filename); + + memset (str, 0, sizeof(str)); + strcpy (str, __DATE__); + fwrite (str, sizeof(str), 1, f); + + game.autosaved = autosave; + fwrite (&game, sizeof(game), 1, f); + game.autosaved = false; + + for (i=0 ; iname ; field++) + { + WriteField1 (f, field, (byte *)&temp); + } + + // write the block + fwrite (&temp, sizeof(temp), 1, f); + + // now write any allocated data following the edict + for (field=fields ; field->name ; field++) + { + WriteField2 (f, field, (byte *)ent); + } + +} + +/* +============== +WriteLevelLocals + +All pointer variables (except function pointers) must be handled specially. +============== +*/ +void WriteLevelLocals (FILE *f) +{ + field_t *field; + level_locals_t temp; + + // all of the ints, floats, and vectors stay as they are + temp = level; + + // change the pointers to lengths or indexes + for (field=levelfields ; field->name ; field++) + { + WriteField1 (f, field, (byte *)&temp); + } + + // write the block + fwrite (&temp, sizeof(temp), 1, f); + + // now write any allocated data following the edict + for (field=levelfields ; field->name ; field++) + { + WriteField2 (f, field, (byte *)&level); + } +} + + +/* +============== +ReadEdict + +All pointer variables (except function pointers) must be handled specially. +============== +*/ +void ReadEdict (FILE *f, edict_t *ent) +{ + field_t *field; + + fread (ent, sizeof(*ent), 1, f); + + for (field=fields ; field->name ; field++) + { + ReadField (f, field, (byte *)ent); + } +} + +/* +============== +ReadLevelLocals + +All pointer variables (except function pointers) must be handled specially. +============== +*/ +void ReadLevelLocals (FILE *f) +{ + field_t *field; + + fread (&level, sizeof(level), 1, f); + + for (field=levelfields ; field->name ; field++) + { + ReadField (f, field, (byte *)&level); + } +} + +/* +================= +WriteLevel + +================= +*/ +void WriteLevel (char *filename) +{ + int i; + edict_t *ent; + FILE *f; + void *base; + + f = fopen (filename, "wb"); + if (!f) + gi.error ("Couldn't open %s", filename); + + // write out edict size for checking + i = sizeof(edict_t); + fwrite (&i, sizeof(i), 1, f); + + // write out a function pointer for checking + base = (void *)InitGame; + fwrite (&base, sizeof(base), 1, f); + + // write out level_locals_t + WriteLevelLocals (f); + + // write out all the entities + for (i=0 ; iinuse) + continue; + fwrite (&i, sizeof(i), 1, f); + WriteEdict (f, ent); + } + i = -1; + fwrite (&i, sizeof(i), 1, f); + + fclose (f); +} + + +/* +================= +ReadLevel + +SpawnEntities will already have been called on the +level the same way it was when the level was saved. + +That is necessary to get the baselines +set up identically. + +The server will have cleared all of the world links before +calling ReadLevel. + +No clients are connected yet. +================= +*/ +void ReadLevel (char *filename) +{ + int entnum; + FILE *f; + int i; + void *base; + edict_t *ent; + + f = fopen (filename, "rb"); + if (!f) + gi.error ("Couldn't open %s", filename); + + // free any dynamic memory allocated by loading the level + // base state + gi.FreeTags (TAG_LEVEL); + + // wipe all the entities + memset (g_edicts, 0, game.maxentities*sizeof(g_edicts[0])); + globals.num_edicts = maxclients->value+1; + + // check edict size + fread (&i, sizeof(i), 1, f); + if (i != sizeof(edict_t)) + { + fclose (f); + gi.error ("ReadLevel: mismatched edict size"); + } + + // check function pointer base address + fread (&base, sizeof(base), 1, f); +#ifdef _WIN32 + if (base != (void *)InitGame) + { + fclose (f); + gi.error ("ReadLevel: function pointers have moved"); + } +#else + gi.dprintf("Function offsets %d\n", ((byte *)base) - ((byte *)InitGame)); +#endif + + // load the level locals + ReadLevelLocals (f); + + // load all the entities + while (1) + { + if (fread (&entnum, sizeof(entnum), 1, f) != 1) + { + fclose (f); + gi.error ("ReadLevel: failed to read entnum"); + } + if (entnum == -1) + break; + if (entnum >= globals.num_edicts) + globals.num_edicts = entnum+1; + + ent = &g_edicts[entnum]; + ReadEdict (f, ent); + + // let the server rebuild world links for this ent + memset (&ent->area, 0, sizeof(ent->area)); + gi.linkentity (ent); + } + + fclose (f); + + // PMM - rebuild the hint path chains +// InitHintPaths(); + // pmm + + // mark all clients as unconnected + for (i=0 ; ivalue ; i++) + { + ent = &g_edicts[i+1]; + ent->client = game.clients + i; + ent->client->pers.connected = false; + } + + // do any load time things at this point + for (i=0 ; iinuse) + continue; + + // fire any cross-level triggers + if (ent->classname) + if (strcmp(ent->classname, "target_crosslevel_target") == 0) + ent->nextthink = level.time + ent->delay; + } +} diff --git a/original/rogue/g_spawn.c b/original/rogue/g_spawn.c new file mode 100644 index 0000000..29c0404 --- /dev/null +++ b/original/rogue/g_spawn.c @@ -0,0 +1,1776 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. + +#include "g_local.h" + +typedef struct +{ + char *name; + void (*spawn)(edict_t *ent); +} spawn_t; + + +void SP_item_health (edict_t *self); +void SP_item_health_small (edict_t *self); +void SP_item_health_large (edict_t *self); +void SP_item_health_mega (edict_t *self); + +void SP_info_player_start (edict_t *ent); +void SP_info_player_deathmatch (edict_t *ent); +void SP_info_player_coop (edict_t *ent); +void SP_info_player_intermission (edict_t *ent); + +void SP_func_plat (edict_t *ent); +void SP_func_rotating (edict_t *ent); +void SP_func_button (edict_t *ent); +void SP_func_door (edict_t *ent); +void SP_func_door_secret (edict_t *ent); +void SP_func_door_rotating (edict_t *ent); +void SP_func_water (edict_t *ent); +void SP_func_train (edict_t *ent); +void SP_func_conveyor (edict_t *self); +void SP_func_wall (edict_t *self); +void SP_func_object (edict_t *self); +void SP_func_explosive (edict_t *self); +void SP_func_timer (edict_t *self); +void SP_func_areaportal (edict_t *ent); +void SP_func_clock (edict_t *ent); +void SP_func_killbox (edict_t *ent); + +void SP_trigger_always (edict_t *ent); +void SP_trigger_once (edict_t *ent); +void SP_trigger_multiple (edict_t *ent); +void SP_trigger_relay (edict_t *ent); +void SP_trigger_push (edict_t *ent); +void SP_trigger_hurt (edict_t *ent); +void SP_trigger_key (edict_t *ent); +void SP_trigger_counter (edict_t *ent); +void SP_trigger_elevator (edict_t *ent); +void SP_trigger_gravity (edict_t *ent); +void SP_trigger_monsterjump (edict_t *ent); + +void SP_target_temp_entity (edict_t *ent); +void SP_target_speaker (edict_t *ent); +void SP_target_explosion (edict_t *ent); +void SP_target_changelevel (edict_t *ent); +void SP_target_secret (edict_t *ent); +void SP_target_goal (edict_t *ent); +void SP_target_splash (edict_t *ent); +void SP_target_spawner (edict_t *ent); +void SP_target_blaster (edict_t *ent); +void SP_target_crosslevel_trigger (edict_t *ent); +void SP_target_crosslevel_target (edict_t *ent); +void SP_target_laser (edict_t *self); +void SP_target_help (edict_t *ent); +void SP_target_actor (edict_t *ent); +void SP_target_lightramp (edict_t *self); +void SP_target_earthquake (edict_t *ent); +void SP_target_character (edict_t *ent); +void SP_target_string (edict_t *ent); + +void SP_worldspawn (edict_t *ent); +void SP_viewthing (edict_t *ent); + +void SP_light (edict_t *self); +void SP_light_mine1 (edict_t *ent); +void SP_light_mine2 (edict_t *ent); +void SP_info_null (edict_t *self); +void SP_info_notnull (edict_t *self); +void SP_path_corner (edict_t *self); +void SP_point_combat (edict_t *self); + +void SP_misc_explobox (edict_t *self); +void SP_misc_banner (edict_t *self); +void SP_misc_satellite_dish (edict_t *self); +void SP_misc_actor (edict_t *self); +void SP_misc_gib_arm (edict_t *self); +void SP_misc_gib_leg (edict_t *self); +void SP_misc_gib_head (edict_t *self); +void SP_misc_insane (edict_t *self); +void SP_misc_deadsoldier (edict_t *self); +void SP_misc_viper (edict_t *self); +void SP_misc_viper_bomb (edict_t *self); +void SP_misc_bigviper (edict_t *self); +void SP_misc_strogg_ship (edict_t *self); +void SP_misc_teleporter (edict_t *self); +void SP_misc_teleporter_dest (edict_t *self); +void SP_misc_blackhole (edict_t *self); +void SP_misc_eastertank (edict_t *self); +void SP_misc_easterchick (edict_t *self); +void SP_misc_easterchick2 (edict_t *self); + +void SP_monster_berserk (edict_t *self); +void SP_monster_gladiator (edict_t *self); +void SP_monster_gunner (edict_t *self); +void SP_monster_infantry (edict_t *self); +void SP_monster_soldier_light (edict_t *self); +void SP_monster_soldier (edict_t *self); +void SP_monster_soldier_ss (edict_t *self); +void SP_monster_tank (edict_t *self); +void SP_monster_medic (edict_t *self); +void SP_monster_flipper (edict_t *self); +void SP_monster_chick (edict_t *self); +void SP_monster_parasite (edict_t *self); +void SP_monster_flyer (edict_t *self); +void SP_monster_brain (edict_t *self); +void SP_monster_floater (edict_t *self); +void SP_monster_hover (edict_t *self); +void SP_monster_mutant (edict_t *self); +void SP_monster_supertank (edict_t *self); +void SP_monster_boss2 (edict_t *self); +void SP_monster_jorg (edict_t *self); +void SP_monster_boss3_stand (edict_t *self); + +void SP_monster_commander_body (edict_t *self); + +void SP_turret_breach (edict_t *self); +void SP_turret_base (edict_t *self); +void SP_turret_driver (edict_t *self); + +//=========== +//ROGUE +void SP_func_plat2 (edict_t *ent); +void SP_func_door_secret2(edict_t *ent); +void SP_func_force_wall(edict_t *ent); +void SP_info_player_coop_lava (edict_t *self); +void SP_info_teleport_destination (edict_t *self); +void SP_trigger_teleport (edict_t *self); +void SP_trigger_disguise (edict_t *self); +void SP_monster_stalker (edict_t *self); +void SP_monster_turret (edict_t *self); +void SP_target_steam (edict_t *self); +void SP_target_anger (edict_t *self); +void SP_target_killplayers (edict_t *self); +// PMM - still experimental! +void SP_target_blacklight (edict_t *self); +void SP_target_orb (edict_t *self); +// pmm +//void SP_target_spawn (edict_t *self); +void SP_hint_path (edict_t *self); +void SP_monster_carrier (edict_t *self); +void SP_monster_widow (edict_t *self); +void SP_monster_widow2 (edict_t *self); +void SP_dm_tag_token (edict_t *self); +void SP_dm_dball_goal (edict_t *self); +void SP_dm_dball_ball (edict_t *self); +void SP_dm_dball_team1_start (edict_t *self); +void SP_dm_dball_team2_start (edict_t *self); +void SP_dm_dball_ball_start (edict_t *self); +void SP_dm_dball_speed_change (edict_t *self); +void SP_monster_kamikaze (edict_t *self); +//void SP_monster_chick2 (edict_t *self); +void SP_turret_invisible_brain (edict_t *self); +void SP_xatrix_item (edict_t *self); +void SP_misc_nuke_core (edict_t *self); +//ROGUE +//=========== + +spawn_t spawns[] = { + {"item_health", SP_item_health}, + {"item_health_small", SP_item_health_small}, + {"item_health_large", SP_item_health_large}, + {"item_health_mega", SP_item_health_mega}, + + {"info_player_start", SP_info_player_start}, + {"info_player_deathmatch", SP_info_player_deathmatch}, + {"info_player_coop", SP_info_player_coop}, + {"info_player_intermission", SP_info_player_intermission}, + + {"func_plat", SP_func_plat}, + {"func_button", SP_func_button}, + {"func_door", SP_func_door}, + {"func_door_secret", SP_func_door_secret}, + {"func_door_rotating", SP_func_door_rotating}, + {"func_rotating", SP_func_rotating}, + {"func_train", SP_func_train}, + {"func_water", SP_func_water}, + {"func_conveyor", SP_func_conveyor}, + {"func_areaportal", SP_func_areaportal}, + {"func_clock", SP_func_clock}, + {"func_wall", SP_func_wall}, + {"func_object", SP_func_object}, + {"func_timer", SP_func_timer}, + {"func_explosive", SP_func_explosive}, + {"func_killbox", SP_func_killbox}, + + {"trigger_always", SP_trigger_always}, + {"trigger_once", SP_trigger_once}, + {"trigger_multiple", SP_trigger_multiple}, + {"trigger_relay", SP_trigger_relay}, + {"trigger_push", SP_trigger_push}, + {"trigger_hurt", SP_trigger_hurt}, + {"trigger_key", SP_trigger_key}, + {"trigger_counter", SP_trigger_counter}, + {"trigger_elevator", SP_trigger_elevator}, + {"trigger_gravity", SP_trigger_gravity}, + {"trigger_monsterjump", SP_trigger_monsterjump}, + + {"target_temp_entity", SP_target_temp_entity}, + {"target_speaker", SP_target_speaker}, + {"target_explosion", SP_target_explosion}, + {"target_changelevel", SP_target_changelevel}, + {"target_secret", SP_target_secret}, + {"target_goal", SP_target_goal}, + {"target_splash", SP_target_splash}, + {"target_spawner", SP_target_spawner}, + {"target_blaster", SP_target_blaster}, + {"target_crosslevel_trigger", SP_target_crosslevel_trigger}, + {"target_crosslevel_target", SP_target_crosslevel_target}, + {"target_laser", SP_target_laser}, + {"target_help", SP_target_help}, + {"target_actor", SP_target_actor}, + {"target_lightramp", SP_target_lightramp}, + {"target_earthquake", SP_target_earthquake}, + {"target_character", SP_target_character}, + {"target_string", SP_target_string}, + + {"worldspawn", SP_worldspawn}, + {"viewthing", SP_viewthing}, + + {"light", SP_light}, + {"light_mine1", SP_light_mine1}, + {"light_mine2", SP_light_mine2}, + {"info_null", SP_info_null}, + {"func_group", SP_info_null}, + {"info_notnull", SP_info_notnull}, + {"path_corner", SP_path_corner}, + {"point_combat", SP_point_combat}, + + {"misc_explobox", SP_misc_explobox}, + {"misc_banner", SP_misc_banner}, + {"misc_satellite_dish", SP_misc_satellite_dish}, + {"misc_actor", SP_misc_actor}, + {"misc_gib_arm", SP_misc_gib_arm}, + {"misc_gib_leg", SP_misc_gib_leg}, + {"misc_gib_head", SP_misc_gib_head}, + {"misc_insane", SP_misc_insane}, + {"misc_deadsoldier", SP_misc_deadsoldier}, + {"misc_viper", SP_misc_viper}, + {"misc_viper_bomb", SP_misc_viper_bomb}, + {"misc_bigviper", SP_misc_bigviper}, + {"misc_strogg_ship", SP_misc_strogg_ship}, + {"misc_teleporter", SP_misc_teleporter}, + {"misc_teleporter_dest", SP_misc_teleporter_dest}, + {"misc_blackhole", SP_misc_blackhole}, + {"misc_eastertank", SP_misc_eastertank}, + {"misc_easterchick", SP_misc_easterchick}, + {"misc_easterchick2", SP_misc_easterchick2}, + + {"monster_berserk", SP_monster_berserk}, + {"monster_gladiator", SP_monster_gladiator}, + {"monster_gunner", SP_monster_gunner}, + {"monster_infantry", SP_monster_infantry}, + {"monster_soldier_light", SP_monster_soldier_light}, + {"monster_soldier", SP_monster_soldier}, + {"monster_soldier_ss", SP_monster_soldier_ss}, + {"monster_tank", SP_monster_tank}, + {"monster_tank_commander", SP_monster_tank}, + {"monster_medic", SP_monster_medic}, + {"monster_flipper", SP_monster_flipper}, + {"monster_chick", SP_monster_chick}, + {"monster_parasite", SP_monster_parasite}, + {"monster_flyer", SP_monster_flyer}, + {"monster_brain", SP_monster_brain}, + {"monster_floater", SP_monster_floater}, + {"monster_hover", SP_monster_hover}, + {"monster_mutant", SP_monster_mutant}, + {"monster_supertank", SP_monster_supertank}, + {"monster_boss2", SP_monster_boss2}, + {"monster_boss3_stand", SP_monster_boss3_stand}, + {"monster_jorg", SP_monster_jorg}, + + {"monster_commander_body", SP_monster_commander_body}, + + {"turret_breach", SP_turret_breach}, + {"turret_base", SP_turret_base}, + {"turret_driver", SP_turret_driver}, + +//============== +//ROGUE + {"func_plat2", SP_func_plat2}, + {"func_door_secret2", SP_func_door_secret2}, + {"func_force_wall", SP_func_force_wall}, + {"trigger_teleport", SP_trigger_teleport}, + {"trigger_disguise", SP_trigger_disguise}, + {"info_teleport_destination", SP_info_teleport_destination}, + {"info_player_coop_lava", SP_info_player_coop_lava}, + {"monster_stalker", SP_monster_stalker}, + {"monster_turret", SP_monster_turret}, + {"target_steam", SP_target_steam}, + {"target_anger", SP_target_anger}, +// {"target_spawn", SP_target_spawn}, + {"target_killplayers", SP_target_killplayers}, + // PMM - experiment + {"target_blacklight", SP_target_blacklight}, + {"target_orb", SP_target_orb}, + // pmm + {"monster_daedalus", SP_monster_hover}, + {"hint_path", SP_hint_path}, + {"monster_carrier", SP_monster_carrier}, + {"monster_widow", SP_monster_widow}, + {"monster_widow2", SP_monster_widow2}, + {"monster_medic_commander", SP_monster_medic}, + {"dm_tag_token", SP_dm_tag_token}, + {"dm_dball_goal", SP_dm_dball_goal}, + {"dm_dball_ball", SP_dm_dball_ball}, + {"dm_dball_team1_start", SP_dm_dball_team1_start}, + {"dm_dball_team2_start", SP_dm_dball_team2_start}, + {"dm_dball_ball_start", SP_dm_dball_ball_start}, + {"dm_dball_speed_change", SP_dm_dball_speed_change}, + {"monster_kamikaze", SP_monster_kamikaze}, +// {"monster_chick2", SP_monster_chick2}, + {"turret_invisible_brain", SP_turret_invisible_brain}, + {"misc_nuke_core", SP_misc_nuke_core}, + + {"ammo_magslug", SP_xatrix_item}, + {"ammo_trap", SP_xatrix_item}, + {"item_quadfire", SP_xatrix_item}, + {"weapon_boomer", SP_xatrix_item}, + {"weapon_phalanx", SP_xatrix_item}, +//ROGUE +//============== + + {NULL, NULL} +}; + +/* +=============== +ED_CallSpawn + +Finds the spawn function for the entity and calls it +=============== +*/ +void ED_CallSpawn (edict_t *ent) +{ + spawn_t *s; + gitem_t *item; + int i; + + if (!ent->classname) + { + gi.dprintf ("ED_CallSpawn: NULL classname\n"); + return; + } + +//PGM - do this before calling the spawn function so it can be overridden. +#ifdef ROGUE_GRAVITY + ent->gravityVector[0] = 0.0; + ent->gravityVector[1] = 0.0; + ent->gravityVector[2] = -1.0; +#endif +//PGM + + // FIXME - PMM classnames hack + if (!strcmp(ent->classname, "weapon_nailgun")) + ent->classname = (FindItem("ETF Rifle"))->classname; + if (!strcmp(ent->classname, "ammo_nails")) + ent->classname = (FindItem("Flechettes"))->classname; + if (!strcmp(ent->classname, "weapon_heatbeam")) + ent->classname = (FindItem("Plasma Beam"))->classname; + // pmm + + // check item spawn functions + for (i=0,item=itemlist ; iclassname) + continue; + if (!strcmp(item->classname, ent->classname)) + { // found it + SpawnItem (ent, item); + return; + } + } + + // check normal spawn functions + for (s=spawns ; s->name ; s++) + { + if (!strcmp(s->name, ent->classname)) + { // found it + s->spawn (ent); + return; + } + } + gi.dprintf ("%s doesn't have a spawn function\n", ent->classname); +} + +/* +============= +ED_NewString +============= +*/ +char *ED_NewString (char *string) +{ + char *newb, *new_p; + int i,l; + + l = strlen(string) + 1; + + newb = gi.TagMalloc (l, TAG_LEVEL); + + new_p = newb; + + for (i=0 ; i< l ; i++) + { + if (string[i] == '\\' && i < l-1) + { + i++; + if (string[i] == 'n') + *new_p++ = '\n'; + else + *new_p++ = '\\'; + } + else + *new_p++ = string[i]; + } + + return newb; +} + + + + +/* +=============== +ED_ParseField + +Takes a key/value pair and sets the binary values +in an edict +=============== +*/ +void ED_ParseField (char *key, char *value, edict_t *ent) +{ + field_t *f; + byte *b; + float v; + vec3_t vec; + + for (f=fields ; f->name ; f++) + { + if (!(f->flags & FFL_NOSPAWN) && !Q_stricmp(f->name, key)) + { // found it + if (f->flags & FFL_SPAWNTEMP) + b = (byte *)&st; + else + b = (byte *)ent; + + switch (f->type) + { + case F_LSTRING: + *(char **)(b+f->ofs) = ED_NewString (value); + break; + case F_VECTOR: + sscanf (value, "%f %f %f", &vec[0], &vec[1], &vec[2]); + ((float *)(b+f->ofs))[0] = vec[0]; + ((float *)(b+f->ofs))[1] = vec[1]; + ((float *)(b+f->ofs))[2] = vec[2]; + break; + case F_INT: + *(int *)(b+f->ofs) = atoi(value); + break; + case F_FLOAT: + *(float *)(b+f->ofs) = atof(value); + break; + case F_ANGLEHACK: + v = atof(value); + ((float *)(b+f->ofs))[0] = 0; + ((float *)(b+f->ofs))[1] = v; + ((float *)(b+f->ofs))[2] = 0; + break; + case F_IGNORE: + break; + } + return; + } + } + gi.dprintf ("%s is not a field\n", key); +} + +/* +==================== +ED_ParseEdict + +Parses an edict out of the given string, returning the new position +ed should be a properly initialized empty edict. +==================== +*/ +char *ED_ParseEdict (char *data, edict_t *ent) +{ + qboolean init; + char keyname[256]; + char *com_token; + + init = false; + memset (&st, 0, sizeof(st)); + +// go through all the dictionary pairs + while (1) + { + // parse key + com_token = COM_Parse (&data); + if (com_token[0] == '}') + break; + if (!data) + gi.error ("ED_ParseEntity: EOF without closing brace"); + + strncpy (keyname, com_token, sizeof(keyname)-1); + + // parse value + com_token = COM_Parse (&data); + if (!data) + gi.error ("ED_ParseEntity: EOF without closing brace"); + + if (com_token[0] == '}') + gi.error ("ED_ParseEntity: closing brace without data"); + + init = true; + + // keynames with a leading underscore are used for utility comments, + // and are immediately discarded by quake + if (keyname[0] == '_') + continue; + + ED_ParseField (keyname, com_token, ent); + } + + if (!init) + memset (ent, 0, sizeof(*ent)); + + return data; +} + + +/* +================ +G_FindTeams + +Chain together all entities with a matching team field. + +All but the first will have the FL_TEAMSLAVE flag set. +All but the last will have the teamchain field set to the next one +================ +*/ +void G_FixTeams (void) +{ + edict_t *e, *e2, *chain; + int i, j; + int c, c2; + + c = 0; + c2 = 0; + for (i=1, e=g_edicts+i ; i < globals.num_edicts ; i++,e++) + { + if (!e->inuse) + continue; + if (!e->team) + continue; + if (!strcmp(e->classname, "func_train")) + { + if(e->flags & FL_TEAMSLAVE) + { + chain = e; + e->teammaster = e; + e->teamchain = NULL; + e->flags &= ~FL_TEAMSLAVE; + c++; + c2++; + for (j=1, e2=g_edicts+j ; j < globals.num_edicts ; j++,e2++) + { + if (e2 == e) + continue; + if (!e2->inuse) + continue; + if (!e2->team) + continue; + if (!strcmp(e->team, e2->team)) + { + c2++; + chain->teamchain = e2; + e2->teammaster = e; + e2->teamchain = NULL; + chain = e2; + e2->flags |= FL_TEAMSLAVE; + e2->movetype = MOVETYPE_PUSH; + e2->speed = e->speed; + } + } + } + } + } + gi.dprintf ("%i teams repaired\n", c); +} + +void G_FindTeams (void) +{ + edict_t *e, *e2, *chain; + int i, j; + int c, c2; + + c = 0; + c2 = 0; + for (i=1, e=g_edicts+i ; i < globals.num_edicts ; i++,e++) + { + if (!e->inuse) + continue; + if (!e->team) + continue; + if (e->flags & FL_TEAMSLAVE) + continue; + chain = e; + e->teammaster = e; + c++; + c2++; + for (j=i+1, e2=e+1 ; j < globals.num_edicts ; j++,e2++) + { + if (!e2->inuse) + continue; + if (!e2->team) + continue; + if (e2->flags & FL_TEAMSLAVE) + continue; + if (!strcmp(e->team, e2->team)) + { + c2++; + chain->teamchain = e2; + e2->teammaster = e; + chain = e2; + e2->flags |= FL_TEAMSLAVE; + } + } + } + + G_FixTeams(); + + gi.dprintf ("%i teams with %i entities\n", c, c2); +} + +/* +============== +SpawnEntities + +Creates a server's entity / program execution context by +parsing textual entity definitions out of an ent file. +============== +*/ +void SpawnEntities (char *mapname, char *entities, char *spawnpoint) +{ + edict_t *ent; + int inhibit; + char *com_token; + int i; + float skill_level; + + skill_level = floor (skill->value); + if (skill_level < 0) + skill_level = 0; + if (skill_level > 3) + skill_level = 3; + if (skill->value != skill_level) + gi.cvar_forceset("skill", va("%f", skill_level)); + + SaveClientData (); + + gi.FreeTags (TAG_LEVEL); + + memset (&level, 0, sizeof(level)); + memset (g_edicts, 0, game.maxentities * sizeof (g_edicts[0])); + + strncpy (level.mapname, mapname, sizeof(level.mapname)-1); + strncpy (game.spawnpoint, spawnpoint, sizeof(game.spawnpoint)-1); + + // set client fields on player ents + for (i=0 ; iclassname, "trigger_once") && !Q_stricmp(ent->model, "*27")) + ent->spawnflags &= ~SPAWNFLAG_NOT_HARD; + + // ROGUE + //ahh, the joys of map hacks .. + if (!Q_stricmp(level.mapname, "rhangar2") && !Q_stricmp(ent->classname, "func_door_rotating") && ent->targetname && !Q_stricmp(ent->targetname, "t265")) + ent->spawnflags &= ~SPAWNFLAG_NOT_COOP; + if (!Q_stricmp(level.mapname, "rhangar2") && !Q_stricmp(ent->classname, "trigger_always") && ent->target && !Q_stricmp(ent->target, "t265")) + ent->spawnflags |= SPAWNFLAG_NOT_COOP; + if (!Q_stricmp(level.mapname, "rhangar2") && !Q_stricmp(ent->classname, "func_wall") && !Q_stricmp(ent->model, "*15")) + ent->spawnflags |= SPAWNFLAG_NOT_COOP; + // rogue + + // remove things (except the world) from different skill levels or deathmatch + if (ent != g_edicts) + { + if (deathmatch->value) + { + if ( ent->spawnflags & SPAWNFLAG_NOT_DEATHMATCH ) + { + G_FreeEdict (ent); + inhibit++; + continue; + } + } + else if(coop->value) + { + if (ent->spawnflags & SPAWNFLAG_NOT_COOP) + { + G_FreeEdict (ent); + inhibit++; + continue; + } + + // stuff marked !easy & !med & !hard are coop only, all levels + if(!((ent->spawnflags & SPAWNFLAG_NOT_EASY) && + (ent->spawnflags & SPAWNFLAG_NOT_MEDIUM) && + (ent->spawnflags & SPAWNFLAG_NOT_HARD)) ) + { + if( ((skill->value == 0) && (ent->spawnflags & SPAWNFLAG_NOT_EASY)) || + ((skill->value == 1) && (ent->spawnflags & SPAWNFLAG_NOT_MEDIUM)) || + (((skill->value == 2) || (skill->value == 3)) && (ent->spawnflags & SPAWNFLAG_NOT_HARD)) + ) + { + G_FreeEdict (ent); + inhibit++; + continue; + } + } + } + else + { + if ( /*((coop->value) && (ent->spawnflags & SPAWNFLAG_NOT_COOP)) || */ + ((skill->value == 0) && (ent->spawnflags & SPAWNFLAG_NOT_EASY)) || + ((skill->value == 1) && (ent->spawnflags & SPAWNFLAG_NOT_MEDIUM)) || + (((skill->value == 2) || (skill->value == 3)) && (ent->spawnflags & SPAWNFLAG_NOT_HARD)) + ) + { + G_FreeEdict (ent); + inhibit++; + continue; + } + } + + ent->spawnflags &= ~(SPAWNFLAG_NOT_EASY|SPAWNFLAG_NOT_MEDIUM|SPAWNFLAG_NOT_HARD|SPAWNFLAG_NOT_COOP|SPAWNFLAG_NOT_DEATHMATCH); + } + +//PGM - do this before calling the spawn function so it can be overridden. +#ifdef ROGUE_GRAVITY + ent->gravityVector[0] = 0.0; + ent->gravityVector[1] = 0.0; + ent->gravityVector[2] = -1.0; +#endif +//PGM + ED_CallSpawn (ent); + + ent->s.renderfx |= RF_IR_VISIBLE; //PGM + } + + gi.dprintf ("%i entities inhibited\n", inhibit); + +#ifdef DEBUG + i = 1; + ent = EDICT_NUM(i); + while (i < globals.num_edicts) { + if (ent->inuse != 0 || ent->inuse != 1) + Com_DPrintf("Invalid entity %d\n", i); + i++, ent++; + } +#endif + + G_FindTeams (); + + PlayerTrail_Init (); + +//ROGUE + if(deathmatch->value) + { + if(randomrespawn && randomrespawn->value) + PrecacheForRandomRespawn(); + } + else + { + InitHintPaths(); // if there aren't hintpaths on this map, enable quick aborts + } +//ROGUE + +// ROGUE -- allow dm games to do init stuff right before game starts. + if(deathmatch->value && gamerules && gamerules->value) + { + if(DMGame.PostInitSetup) + DMGame.PostInitSetup (); + } +// ROGUE +} + + +//=================================================================== + +#if 0 + // cursor positioning + xl + xr + yb + yt + xv + yv + + // drawing + statpic + pic + num + string + + // control + if + ifeq + ifbit + endif + +#endif + +char *single_statusbar = +"yb -24 " + +// health +"xv 0 " +"hnum " +"xv 50 " +"pic 0 " + +// ammo +"if 2 " +" xv 100 " +" anum " +" xv 150 " +" pic 2 " +"endif " + +// armor +"if 4 " +" xv 200 " +" rnum " +" xv 250 " +" pic 4 " +"endif " + +// selected item +"if 6 " +" xv 296 " +" pic 6 " +"endif " + +"yb -50 " + +// picked up item +"if 7 " +" xv 0 " +" pic 7 " +" xv 26 " +" yb -42 " +" stat_string 8 " +" yb -50 " +"endif " + +// timer +"if 9 " +" xv 262 " +" num 2 10 " +" xv 296 " +" pic 9 " +"endif " + +// help / weapon icon +"if 11 " +" xv 148 " +" pic 11 " +"endif " +; + +char *dm_statusbar = +"yb -24 " + +// health +"xv 0 " +"hnum " +"xv 50 " +"pic 0 " + +// ammo +"if 2 " +" xv 100 " +" anum " +" xv 150 " +" pic 2 " +"endif " + +// armor +"if 4 " +" xv 200 " +" rnum " +" xv 250 " +" pic 4 " +"endif " + +// selected item +"if 6 " +" xv 296 " +" pic 6 " +"endif " + +"yb -50 " + +// picked up item +"if 7 " +" xv 0 " +" pic 7 " +" xv 26 " +" yb -42 " +" stat_string 8 " +" yb -50 " +"endif " + +// timer +"if 9 " +" xv 246 " +" num 2 10 " +" xv 296 " +" pic 9 " +"endif " + +// help / weapon icon +"if 11 " +" xv 148 " +" pic 11 " +"endif " + +// frags +"xr -50 " +"yt 2 " +"num 3 14 " + +// spectator +"if 17 " + "xv 0 " + "yb -58 " + "string2 \"SPECTATOR MODE\" " +"endif " + +// chase camera +"if 16 " + "xv 0 " + "yb -68 " + "string \"Chasing\" " + "xv 64 " + "stat_string 16 " +"endif " +; + + +/*QUAKED worldspawn (0 0 0) ? + +Only used for the world. +"sky" environment map name +"skyaxis" vector axis for rotating sky +"skyrotate" speed of rotation in degrees/second +"sounds" music cd track number +"gravity" 800 is default gravity +"message" text to print at user logon +*/ +void SP_worldspawn (edict_t *ent) +{ + ent->movetype = MOVETYPE_PUSH; + ent->solid = SOLID_BSP; + ent->inuse = true; // since the world doesn't use G_Spawn() + ent->s.modelindex = 1; // world model is always index 1 + + //--------------- + + // reserve some spots for dead player bodies for coop / deathmatch + InitBodyQue (); + + // set configstrings for items + SetItemNames (); + + if (st.nextmap) + strcpy (level.nextmap, st.nextmap); + + // make some data visible to the server + + if (ent->message && ent->message[0]) + { + gi.configstring (CS_NAME, ent->message); + strncpy (level.level_name, ent->message, sizeof(level.level_name)); + } + else + strncpy (level.level_name, level.mapname, sizeof(level.level_name)); + + if (st.sky && st.sky[0]) + gi.configstring (CS_SKY, st.sky); + else + gi.configstring (CS_SKY, "unit1_"); + + gi.configstring (CS_SKYROTATE, va("%f", st.skyrotate) ); + + gi.configstring (CS_SKYAXIS, va("%f %f %f", + st.skyaxis[0], st.skyaxis[1], st.skyaxis[2]) ); + + gi.configstring (CS_CDTRACK, va("%i", ent->sounds) ); + + gi.configstring (CS_MAXCLIENTS, va("%i", (int)(maxclients->value) ) ); + + // status bar program + if (deathmatch->value) + gi.configstring (CS_STATUSBAR, dm_statusbar); + else + gi.configstring (CS_STATUSBAR, single_statusbar); + + //--------------- + + + // help icon for statusbar + gi.imageindex ("i_help"); + level.pic_health = gi.imageindex ("i_health"); + gi.imageindex ("help"); + gi.imageindex ("field_3"); + + if (!st.gravity) + gi.cvar_set("sv_gravity", "800"); + else + gi.cvar_set("sv_gravity", st.gravity); + + snd_fry = gi.soundindex ("player/fry.wav"); // standing in lava / slime + + PrecacheItem (FindItem ("Blaster")); + + gi.soundindex ("player/lava1.wav"); + gi.soundindex ("player/lava2.wav"); + + gi.soundindex ("misc/pc_up.wav"); + gi.soundindex ("misc/talk1.wav"); + + gi.soundindex ("misc/udeath.wav"); + + // gibs + gi.soundindex ("items/respawn1.wav"); + + // sexed sounds + gi.soundindex ("*death1.wav"); + gi.soundindex ("*death2.wav"); + gi.soundindex ("*death3.wav"); + gi.soundindex ("*death4.wav"); + gi.soundindex ("*fall1.wav"); + gi.soundindex ("*fall2.wav"); + gi.soundindex ("*gurp1.wav"); // drowning damage + gi.soundindex ("*gurp2.wav"); + gi.soundindex ("*jump1.wav"); // player jump + gi.soundindex ("*pain25_1.wav"); + gi.soundindex ("*pain25_2.wav"); + gi.soundindex ("*pain50_1.wav"); + gi.soundindex ("*pain50_2.wav"); + gi.soundindex ("*pain75_1.wav"); + gi.soundindex ("*pain75_2.wav"); + gi.soundindex ("*pain100_1.wav"); + gi.soundindex ("*pain100_2.wav"); + + // sexed models + // THIS ORDER MUST MATCH THE DEFINES IN g_local.h + // you can add more, max 19 (pete change) + // these models are only loaded in coop or deathmatch. not singleplayer. + if (coop->value || deathmatch->value) + { + gi.modelindex ("#w_blaster.md2"); + gi.modelindex ("#w_shotgun.md2"); + gi.modelindex ("#w_sshotgun.md2"); + gi.modelindex ("#w_machinegun.md2"); + gi.modelindex ("#w_chaingun.md2"); + gi.modelindex ("#a_grenades.md2"); + gi.modelindex ("#w_glauncher.md2"); + gi.modelindex ("#w_rlauncher.md2"); + gi.modelindex ("#w_hyperblaster.md2"); + gi.modelindex ("#w_railgun.md2"); + gi.modelindex ("#w_bfg.md2"); + + gi.modelindex ("#w_disrupt.md2"); // PGM + gi.modelindex ("#w_etfrifle.md2"); // PGM + gi.modelindex ("#w_plasma.md2"); // PGM + gi.modelindex ("#w_plauncher.md2"); // PGM + gi.modelindex ("#w_chainfist.md2"); // PGM + } + + //------------------- + + gi.soundindex ("player/gasp1.wav"); // gasping for air + gi.soundindex ("player/gasp2.wav"); // head breaking surface, not gasping + + gi.soundindex ("player/watr_in.wav"); // feet hitting water + gi.soundindex ("player/watr_out.wav"); // feet leaving water + + gi.soundindex ("player/watr_un.wav"); // head going underwater + + gi.soundindex ("player/u_breath1.wav"); + gi.soundindex ("player/u_breath2.wav"); + + gi.soundindex ("items/pkup.wav"); // bonus item pickup + gi.soundindex ("world/land.wav"); // landing thud + gi.soundindex ("misc/h2ohit1.wav"); // landing splash + + gi.soundindex ("items/damage.wav"); + // ROGUE - double damage + gi.soundindex ("misc/ddamage1.wav"); + // rogue + gi.soundindex ("items/protect.wav"); + gi.soundindex ("items/protect4.wav"); + gi.soundindex ("weapons/noammo.wav"); + + gi.soundindex ("infantry/inflies1.wav"); + + sm_meat_index = gi.modelindex ("models/objects/gibs/sm_meat/tris.md2"); + gi.modelindex ("models/objects/gibs/arm/tris.md2"); + gi.modelindex ("models/objects/gibs/bone/tris.md2"); + gi.modelindex ("models/objects/gibs/bone2/tris.md2"); + gi.modelindex ("models/objects/gibs/chest/tris.md2"); + gi.modelindex ("models/objects/gibs/skull/tris.md2"); + gi.modelindex ("models/objects/gibs/head2/tris.md2"); + +// +// Setup light animation tables. 'a' is total darkness, 'z' is doublebright. +// + + // 0 normal + gi.configstring(CS_LIGHTS+0, "m"); + + // 1 FLICKER (first variety) + gi.configstring(CS_LIGHTS+1, "mmnmmommommnonmmonqnmmo"); + + // 2 SLOW STRONG PULSE + gi.configstring(CS_LIGHTS+2, "abcdefghijklmnopqrstuvwxyzyxwvutsrqponmlkjihgfedcba"); + + // 3 CANDLE (first variety) + gi.configstring(CS_LIGHTS+3, "mmmmmaaaaammmmmaaaaaabcdefgabcdefg"); + + // 4 FAST STROBE + gi.configstring(CS_LIGHTS+4, "mamamamamama"); + + // 5 GENTLE PULSE 1 + gi.configstring(CS_LIGHTS+5,"jklmnopqrstuvwxyzyxwvutsrqponmlkj"); + + // 6 FLICKER (second variety) + gi.configstring(CS_LIGHTS+6, "nmonqnmomnmomomno"); + + // 7 CANDLE (second variety) + gi.configstring(CS_LIGHTS+7, "mmmaaaabcdefgmmmmaaaammmaamm"); + + // 8 CANDLE (third variety) + gi.configstring(CS_LIGHTS+8, "mmmaaammmaaammmabcdefaaaammmmabcdefmmmaaaa"); + + // 9 SLOW STROBE (fourth variety) + gi.configstring(CS_LIGHTS+9, "aaaaaaaazzzzzzzz"); + + // 10 FLUORESCENT FLICKER + gi.configstring(CS_LIGHTS+10, "mmamammmmammamamaaamammma"); + + // 11 SLOW PULSE NOT FADE TO BLACK + gi.configstring(CS_LIGHTS+11, "abcdefghijklmnopqrrqponmlkjihgfedcba"); + + // styles 32-62 are assigned by the light program for switchable lights + + // 63 testing + gi.configstring(CS_LIGHTS+63, "a"); +} + +// +//ROGUE +// + +// +// Monster spawning code +// +// Used by the carrier, the medic_commander, and the black widow +// +// The sequence to create a flying monster is: +// +// FindSpawnPoint - tries to find suitable spot to spawn the monster in +// CreateFlyMonster - this verifies the point as good and creates the monster + +// To create a ground walking monster: +// +// FindSpawnPoint - same thing +// CreateGroundMonster - this checks the volume and makes sure the floor under the volume is suitable +// + +// FIXME - for the black widow, if we want the stalkers coming in on the roof, we'll have to tweak some things + +// +// CreateMonster +// +edict_t *CreateMonster(vec3_t origin, vec3_t angles, char *classname) +{ + edict_t *newEnt; + + newEnt = G_Spawn(); + + VectorCopy(origin, newEnt->s.origin); + VectorCopy(angles, newEnt->s.angles); + newEnt->classname = ED_NewString (classname); + newEnt->monsterinfo.aiflags |= AI_DO_NOT_COUNT; + + VectorSet(newEnt->gravityVector, 0, 0, -1); + ED_CallSpawn(newEnt); + newEnt->s.renderfx |= RF_IR_VISIBLE; + + return newEnt; +} + +edict_t *CreateFlyMonster (vec3_t origin, vec3_t angles, vec3_t mins, vec3_t maxs, char *classname) +{ + if (!mins || !maxs || VectorCompare (mins, vec3_origin) || VectorCompare (maxs, vec3_origin)) + { + DetermineBBox (classname, mins, maxs); + } + + if (!CheckSpawnPoint(origin, mins, maxs)) + return NULL; + + return (CreateMonster (origin, angles, classname)); +} + +// This is just a wrapper for CreateMonster that looks down height # of CMUs and sees if there +// are bad things down there or not +// +// this is from m_move.c +#define STEPSIZE 18 + +edict_t *CreateGroundMonster (vec3_t origin, vec3_t angles, vec3_t entMins, vec3_t entMaxs, char *classname, int height) +{ +// trace_t tr; + edict_t *newEnt; +// vec3_t start, stop; +// int failure = 0; +// vec3_t mins, maxs; +// int x, y; +// float mid, bottom; + vec3_t mins, maxs; + + // if they don't provide us a bounding box, figure it out + if (!entMins || !entMaxs || VectorCompare (entMins, vec3_origin) || VectorCompare (entMaxs, vec3_origin)) + { + DetermineBBox (classname, mins, maxs); + } + else + { + VectorCopy (entMins, mins); + VectorCopy (entMaxs, maxs); + } + + // check the ground to make sure it's there, it's relatively flat, and it's not toxic + if (!CheckGroundSpawnPoint(origin, mins, maxs, height, -1)) + return NULL; + + newEnt = CreateMonster (origin, angles, classname); + if (!newEnt) + return NULL; + + return newEnt; +} + + +// FindSpawnPoint +// PMM - this is used by the medic commander (possibly by the carrier) to find a good spawn point +// if the startpoint is bad, try above the startpoint for a bit + +qboolean FindSpawnPoint (vec3_t startpoint, vec3_t mins, vec3_t maxs, vec3_t spawnpoint, float maxMoveUp) +{ + trace_t tr; + float height; + vec3_t top; + + height = maxs[2] - mins[2]; + + tr = gi.trace (startpoint, mins, maxs, startpoint, NULL, MASK_MONSTERSOLID|CONTENTS_PLAYERCLIP); + if((tr.startsolid || tr.allsolid) || (tr.ent != world)) + { +// if ( ((tr.ent->svflags & SVF_MONSTER) && (tr.ent->health <= 0)) || +// (tr.ent->svflags & SVF_DAMAGEABLE) ) +// { +// T_Damage (tr.ent, self, self, vec3_origin, self->enemy->s.origin, +// pain_normal, hurt, 0, 0, MOD_UNKNOWN); + + VectorCopy (startpoint, top); + top[2] += maxMoveUp; +/* + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_DEBUGTRAIL); + gi.WritePosition (top); + gi.WritePosition (startpoint); + gi.multicast (startpoint, MULTICAST_ALL); +*/ + tr = gi.trace (top, mins, maxs, startpoint, NULL, MASK_MONSTERSOLID); + if (tr.startsolid || tr.allsolid) + { +// if ((g_showlogic) && (g_showlogic->value)) +// if (tr.ent) +// gi.dprintf("FindSpawnPoint: failed to find a point -- blocked by %s\n", tr.ent->classname); +// else +// gi.dprintf("FindSpawnPoint: failed to find a point\n"); + + return false; + } + else + { +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("FindSpawnPoint: %s -> %s\n", vtos (startpoint), vtos (tr.endpos)); + VectorCopy (tr.endpos, spawnpoint); + return true; + } + } + else + { + VectorCopy (startpoint, spawnpoint); + return true; + } +} + +// FIXME - all of this needs to be tweaked to handle the new gravity rules +// if we ever want to spawn stuff on the roof + +// +// CheckSpawnPoint +// +// PMM - checks volume to make sure we can spawn a monster there (is it solid?) +// +// This is all fliers should need + +qboolean CheckSpawnPoint (vec3_t origin, vec3_t mins, vec3_t maxs) +{ + trace_t tr; + + if (!mins || !maxs || VectorCompare(mins, vec3_origin) || VectorCompare (maxs, vec3_origin)) + { + return false; + } + + tr = gi.trace (origin, mins, maxs, origin, NULL, MASK_MONSTERSOLID); + if(tr.startsolid || tr.allsolid) + { +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf("createmonster in wall. removing\n"); + return false; + } + if (tr.ent != world) + { +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf("createmonster in entity %s\n", tr.ent->classname); + return false; + } + return true; +} + +// +// CheckGroundSpawnPoint +// +// PMM - used for walking monsters +// checks: +// 1) is there a ground within the specified height of the origin? +// 2) is the ground non-water? +// 3) is the ground flat enough to walk on? +// + +qboolean CheckGroundSpawnPoint (vec3_t origin, vec3_t entMins, vec3_t entMaxs, float height, float gravity) +{ + trace_t tr; + vec3_t start, stop; + int failure = 0; + vec3_t mins, maxs; + int x, y; + float mid, bottom; + + if (!CheckSpawnPoint (origin, entMins, entMaxs)) + return false; + + // FIXME - this is too conservative about angled surfaces + + VectorCopy (origin, stop); + // FIXME - gravity vector + stop[2] = origin[2] + entMins[2] - height; + + /* + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_DEBUGTRAIL); + gi.WritePosition (origin); + gi.WritePosition (stop); + gi.multicast (start, MULTICAST_ALL); + */ + + tr = gi.trace (origin, entMins, entMaxs, stop, NULL, MASK_MONSTERSOLID | MASK_WATER); + // it's not going to be all solid or start solid, since that's checked above + + if ((tr.fraction < 1) && (tr.contents & MASK_MONSTERSOLID)) + { + // we found a non-water surface down there somewhere. now we need to check to make sure it's not too sloped + // + // algorithm straight out of m_move.c:M_CheckBottom() + // + + // first, do the midpoint trace + + VectorAdd (tr.endpos, entMins, mins); + VectorAdd (tr.endpos, entMaxs, maxs); + + + // first, do the easy flat check + // +#ifdef ROGUE_GRAVITY + // FIXME - this will only handle 0,0,1 and 0,0,-1 gravity vectors + if(gravity > 0) + start[2] = maxs[2] + 1; + else + start[2] = mins[2] - 1; +#else + start[2] = mins[2] - 1; +#endif + for (x=0 ; x<=1 ; x++) + { + for (y=0 ; y<=1 ; y++) + { + start[0] = x ? maxs[0] : mins[0]; + start[1] = y ? maxs[1] : mins[1]; + if (gi.pointcontents (start) != CONTENTS_SOLID) + goto realcheck; + } + } + + // if it passed all four above checks, we're done + return true; + +realcheck: + + // check it for real + + start[0] = stop[0] = (mins[0] + maxs[0])*0.5; + start[1] = stop[1] = (mins[1] + maxs[1])*0.5; + start[2] = mins[2]; + + tr = gi.trace (start, vec3_origin, vec3_origin, stop, NULL, MASK_MONSTERSOLID); + + if (tr.fraction == 1.0) + return false; + mid = bottom = tr.endpos[2]; + +#ifdef ROGUE_GRAVITY + if(gravity < 0) + { + start[2] = mins[2]; + stop[2] = start[2] - STEPSIZE - STEPSIZE; + mid = bottom = tr.endpos[2] + entMins[2]; + } + else + { + start[2] = maxs[2]; + stop[2] = start[2] + STEPSIZE + STEPSIZE; + mid = bottom = tr.endpos[2] - entMaxs[2]; + } +#else + stop[2] = start[2] - 2*STEPSIZE; + mid = bottom = tr.endpos[2] + entMins[2]; +#endif + + for (x=0 ; x<=1 ; x++) + for (y=0 ; y<=1 ; y++) + { + start[0] = stop[0] = x ? maxs[0] : mins[0]; + start[1] = stop[1] = y ? maxs[1] : mins[1]; + + /* + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_DEBUGTRAIL); + gi.WritePosition (start); + gi.WritePosition (stop); + gi.multicast (start, MULTICAST_ALL); + */ + tr = gi.trace (start, vec3_origin, vec3_origin, stop, NULL, MASK_MONSTERSOLID); + +//PGM +#ifdef ROGUE_GRAVITY +// FIXME - this will only handle 0,0,1 and 0,0,-1 gravity vectors + if(gravity > 0) + { + if (tr.fraction != 1.0 && tr.endpos[2] < bottom) + bottom = tr.endpos[2]; + if (tr.fraction == 1.0 || tr.endpos[2] - mid > STEPSIZE) + { +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("spawn - rejecting due to uneven ground\n"); + return false; + } + } + else + { + if (tr.fraction != 1.0 && tr.endpos[2] > bottom) + bottom = tr.endpos[2]; + if (tr.fraction == 1.0 || mid - tr.endpos[2] > STEPSIZE) + { +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("spawn - rejecting due to uneven ground\n"); + return false; + } + } +#else + if (tr.fraction != 1.0 && tr.endpos[2] > bottom) + bottom = tr.endpos[2]; + if (tr.fraction == 1.0 || mid - tr.endpos[2] > STEPSIZE) + { + return false; + } +#endif + } + + return true; // we can land on it, it's ok + } + + // otherwise, it's either water (bad) or not there (too far) + // if we're here, it's bad below +// if ((g_showlogic) && (g_showlogic->value)) +// { +// if (tr.fraction < 1) +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf("groundmonster would fall into water/slime/lava\n"); +// else +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf("groundmonster would fall too far\n"); +// } + + return false; +} + +void DetermineBBox (char *classname, vec3_t mins, vec3_t maxs) +{ + // FIXME - cache this stuff + edict_t *newEnt; + + newEnt = G_Spawn(); + + VectorCopy(vec3_origin, newEnt->s.origin); + VectorCopy(vec3_origin, newEnt->s.angles); + newEnt->classname = ED_NewString (classname); + newEnt->monsterinfo.aiflags |= AI_DO_NOT_COUNT; + + ED_CallSpawn(newEnt); + + VectorCopy (newEnt->mins, mins); + VectorCopy (newEnt->maxs, maxs); + + G_FreeEdict (newEnt); +} + +// **************************** +// SPAWNGROW stuff +// **************************** + +#define SPAWNGROW_LIFESPAN 0.3 + +void spawngrow_think (edict_t *self) +{ + int i; + + for (i=0; i<2; i++) + { + self->s.angles[0] = rand()%360; + self->s.angles[1] = rand()%360; + self->s.angles[2] = rand()%360; + } + if ((level.time < self->wait) && (self->s.frame < 2)) + self->s.frame++; + if (level.time >= self->wait) + { + if (self->s.effects & EF_SPHERETRANS) + { + G_FreeEdict (self); + return; + } + else if (self->s.frame > 0) + self->s.frame--; + else + { + G_FreeEdict (self); + return; + } + } + self->nextthink += FRAMETIME; +} + +void SpawnGrow_Spawn (vec3_t startpos, int size) +{ + edict_t *ent; + int i; + float lifespan; + + ent = G_Spawn(); + VectorCopy(startpos, ent->s.origin); + for (i=0; i<2; i++) + { + ent->s.angles[0] = rand()%360; + ent->s.angles[1] = rand()%360; + ent->s.angles[2] = rand()%360; + } + ent->solid = SOLID_NOT; +// ent->s.renderfx = RF_FULLBRIGHT | RF_IR_VISIBLE; + ent->s.renderfx = RF_IR_VISIBLE; + ent->movetype = MOVETYPE_NONE; + ent->classname = "spawngro"; + + if (size <= 1) + { + lifespan = SPAWNGROW_LIFESPAN; + ent->s.modelindex = gi.modelindex("models/items/spawngro2/tris.md2"); + } + else if (size == 2) + { + ent->s.modelindex = gi.modelindex("models/items/spawngro3/tris.md2"); + lifespan = 2; + } + else + { + ent->s.modelindex = gi.modelindex("models/items/spawngro/tris.md2"); + lifespan = SPAWNGROW_LIFESPAN; + } + + ent->think = spawngrow_think; + + ent->wait = level.time + lifespan; + ent->nextthink = level.time + FRAMETIME; + if (size != 2) + ent->s.effects |= EF_SPHERETRANS; + gi.linkentity (ent); +} + + +// **************************** +// WidowLeg stuff +// **************************** + +#define MAX_LEGSFRAME 23 +#define LEG_WAIT_TIME 1 + +void ThrowMoreStuff (edict_t *self, vec3_t point); +void ThrowSmallStuff (edict_t *self, vec3_t point); +void ThrowWidowGibLoc (edict_t *self, char *gibname, int damage, int type, vec3_t startpos, qboolean fade); +void ThrowWidowGibSized (edict_t *self, char *gibname, int damage, int type, vec3_t startpos, int hitsound, qboolean fade); + +void widowlegs_think (edict_t *self) +{ + vec3_t offset; + vec3_t point; + vec3_t f,r,u; + + if (self->s.frame == 17) + { + VectorSet (offset, 11.77, -7.24, 23.31); + AngleVectors (self->s.angles, f, r, u); + G_ProjectSource2 (self->s.origin, offset, f, r, u, point); + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_EXPLOSION1); + gi.WritePosition (point); + gi.multicast (point, MULTICAST_ALL); + ThrowSmallStuff (self, point); + } + + if (self->s.frame < MAX_LEGSFRAME) + { + self->s.frame++; + self->nextthink = level.time + FRAMETIME; + return; + } + else if (self->wait == 0) + { + self->wait = level.time + LEG_WAIT_TIME; + } + if (level.time > self->wait) + { + AngleVectors (self->s.angles, f, r, u); + + VectorSet (offset, -65.6, -8.44, 28.59); + G_ProjectSource2 (self->s.origin, offset, f, r, u, point); + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_EXPLOSION1); + gi.WritePosition (point); + gi.multicast (point, MULTICAST_ALL); + ThrowSmallStuff (self, point); + + ThrowWidowGibSized (self, "models/monsters/blackwidow/gib1/tris.md2", 80 + (int)(random()*20.0), GIB_METALLIC, point, 0, true); + ThrowWidowGibSized (self, "models/monsters/blackwidow/gib2/tris.md2", 80 + (int)(random()*20.0), GIB_METALLIC, point, 0, true); + + VectorSet (offset, -1.04, -51.18, 7.04); + G_ProjectSource2 (self->s.origin, offset, f, r, u, point); + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_EXPLOSION1); + gi.WritePosition (point); + gi.multicast (point, MULTICAST_ALL); + ThrowSmallStuff (self, point); + + ThrowWidowGibSized (self, "models/monsters/blackwidow/gib1/tris.md2", 80 + (int)(random()*20.0), GIB_METALLIC, point, 0, true); + ThrowWidowGibSized (self, "models/monsters/blackwidow/gib2/tris.md2", 80 + (int)(random()*20.0), GIB_METALLIC, point, 0, true); + ThrowWidowGibSized (self, "models/monsters/blackwidow/gib3/tris.md2", 80 + (int)(random()*20.0), GIB_METALLIC, point, 0, true); + + G_FreeEdict (self); + return; + } + if ((level.time > (self->wait - 0.5)) && (self->count == 0)) + { + self->count = 1; + AngleVectors (self->s.angles, f, r, u); + + VectorSet (offset, 31, -88.7, 10.96); + G_ProjectSource2 (self->s.origin, offset, f, r, u, point); + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_EXPLOSION1); + gi.WritePosition (point); + gi.multicast (point, MULTICAST_ALL); +// ThrowSmallStuff (self, point); + + VectorSet (offset, -12.67, -4.39, 15.68); + G_ProjectSource2 (self->s.origin, offset, f, r, u, point); + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_EXPLOSION1); + gi.WritePosition (point); + gi.multicast (point, MULTICAST_ALL); +// ThrowSmallStuff (self, point); + + self->nextthink = level.time + FRAMETIME; + return; + } + self->nextthink = level.time + FRAMETIME; +} + +void Widowlegs_Spawn (vec3_t startpos, vec3_t angles) +{ + edict_t *ent; + + ent = G_Spawn(); + VectorCopy(startpos, ent->s.origin); + VectorCopy(angles, ent->s.angles); + ent->solid = SOLID_NOT; + ent->s.renderfx = RF_IR_VISIBLE; + ent->movetype = MOVETYPE_NONE; + ent->classname = "widowlegs"; + + ent->s.modelindex = gi.modelindex("models/monsters/legs/tris.md2"); + ent->think = widowlegs_think; + + ent->nextthink = level.time + FRAMETIME; + gi.linkentity (ent); +} diff --git a/original/rogue/g_sphere.c b/original/rogue/g_sphere.c new file mode 100644 index 0000000..1e15b05 --- /dev/null +++ b/original/rogue/g_sphere.c @@ -0,0 +1,791 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// g_sphere.c +// pmack +// april 1998 + +// defender - actively finds and shoots at enemies +// hunter - waits until < 25% health and vore ball tracks person who hurt you +// vengeance - kills person who killed you. + +#include "g_local.h" + +#define DEFENDER_LIFESPAN 30 +#define HUNTER_LIFESPAN 30 +#define VENGEANCE_LIFESPAN 30 +#define MINIMUM_FLY_TIME 15 +//#define MINIMUM_FLY_TIME 30 + +// FIXME - do we need to be calling ED_NewString at all? +extern char *ED_NewString (char *string); +void LookAtKiller (edict_t *self, edict_t *inflictor, edict_t *attacker); + + +void defender_think (edict_t *self); +void hunter_think (edict_t *self); +void vengeance_think (edict_t *self); +void vengeance_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf); +void hunter_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf); + +// ************************* +// General Sphere Code +// ************************* + +// ================= +// ================= +void sphere_think_explode (edict_t *self) +{ + if(self->owner && self->owner->client && !(self->spawnflags & SPHERE_DOPPLEGANGER)) + { + self->owner->client->owned_sphere = NULL; + } + BecomeExplosion1 (self); +} + +// ================= +// sphere_explode +// ================= +void sphere_explode (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ +// if(self->owner && self->owner->client) +// gi.cprintf(self->owner, PRINT_HIGH, "Sphere timed out\n"); +// gi.dprintf("player died, blowing up\n"); + sphere_think_explode (self); +} + +// ================= +// sphere_if_idle_die - if the sphere is not currently attacking, blow up. +// ================= +void sphere_if_idle_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + if(!self->enemy) + { +// gi.dprintf("player died, blowing up\n"); + sphere_think_explode(self); + } +} + +// ************************* +// Sphere Movement +// ************************* + +// ================= +// ================= +void sphere_fly (edict_t *self) +{ + vec3_t dest; + vec3_t dir; + + if(level.time >= self->wait) + { +// gi.dprintf("fly: timed out\n"); + sphere_think_explode(self); + return; + } + + VectorCopy (self->owner->s.origin, dest); + dest[2] = self->owner->absmax[2] + 4; + + if(level.time == (float)(int)level.time) + { + if(!visible(self, self->owner)) + { + VectorCopy(dest, self->s.origin); + gi.linkentity(self); + return; + } + } + + VectorSubtract (dest, self->s.origin, dir); + VectorScale (dir, 5, self->velocity); +} + +// ================= +// ================= +void sphere_chase (edict_t *self, int stupidChase) +{ + vec3_t dest; + vec3_t dir; + float dist; + + if(level.time >= self->wait || (self->enemy && self->enemy->health < 1)) + { + sphere_think_explode(self); + return; + } + + VectorCopy (self->enemy->s.origin, dest); + if(self->enemy->client) + dest[2] += self->enemy->viewheight; + + if(visible(self, self->enemy) || stupidChase) + { + // if moving, hunter sphere uses active sound + if(!stupidChase) + self->s.sound = gi.soundindex ("spheres/h_active.wav"); + + VectorSubtract (dest, self->s.origin, dir); + VectorNormalize (dir); + vectoangles2(dir, self->s.angles); + VectorScale (dir, 500, self->velocity); + VectorCopy(dest, self->monsterinfo.saved_goal); + } + else if (VectorCompare (self->monsterinfo.saved_goal, vec3_origin)) + { + VectorSubtract(self->enemy->s.origin, self->s.origin, dir); + dist = VectorNormalize(dir); + vectoangles2(dir, self->s.angles); + + // if lurking, hunter sphere uses lurking sound + self->s.sound = gi.soundindex ("spheres/h_lurk.wav"); + VectorClear (self->velocity); + } + else + { + VectorSubtract(self->monsterinfo.saved_goal, self->s.origin, dir); + dist = VectorNormalize(dir); + + if(dist > 1) + { + vectoangles2(dir, self->s.angles); + + if(dist > 500) + VectorScale(dir, 500, self->velocity); + else if (dist < 20) + VectorScale(dir, (dist / FRAMETIME), self->velocity); + else + VectorScale(dir, dist, self->velocity); + + // if moving, hunter sphere uses active sound + if(!stupidChase) + self->s.sound = gi.soundindex ("spheres/h_active.wav"); + } + else + { + VectorSubtract(self->enemy->s.origin, self->s.origin, dir); + dist = VectorNormalize(dir); + vectoangles2(dir, self->s.angles); + + // if not moving, hunter sphere uses lurk sound + if(!stupidChase) + self->s.sound = gi.soundindex ("spheres/h_lurk.wav"); + + VectorClear(self->velocity); + } + } +} + +// ************************* +// Attack related stuff +// ************************* + +// ================= +// ================= +void sphere_fire (edict_t *self, edict_t *enemy) +{ + vec3_t dest; + vec3_t dir; + + if(level.time >= self->wait || !enemy) + { + sphere_think_explode(self); + return; + } + + VectorCopy (enemy->s.origin, dest); + self->s.effects |= EF_ROCKET; + + VectorSubtract (dest, self->s.origin, dir); + VectorNormalize (dir); + vectoangles2 ( dir, self->s.angles ); + VectorScale (dir, 1000, self->velocity); + + self->touch = vengeance_touch; + self->think = sphere_think_explode; + self->nextthink = self->wait; +} + +// ================= +// ================= +void sphere_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf, int mod) +{ + if(self->spawnflags & SPHERE_DOPPLEGANGER) + { + if (other == self->teammaster) + return; + + self->takedamage = DAMAGE_NO; + self->owner = self->teammaster; + self->teammaster = NULL; + } + else + { + if (other == self->owner) + return; + // PMM - don't blow up on bodies + if (!strcmp(other->classname, "bodyque")) + return; + } + + if (surf && (surf->flags & SURF_SKY)) + { + G_FreeEdict (self); + return; + } + + if (other->takedamage) + { + T_Damage (other, self, self->owner, self->velocity, self->s.origin, plane->normal, + 10000, 1, DAMAGE_DESTROY_ARMOR, mod); + } + else + { + T_RadiusDamage (self, self->owner, 512, self->owner, 256, mod); + } + + sphere_think_explode (self); +} + +// ================= +// ================= +void vengeance_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if(self->spawnflags & SPHERE_DOPPLEGANGER) + sphere_touch (self, other, plane, surf, MOD_DOPPLE_VENGEANCE); + else + sphere_touch (self, other, plane, surf, MOD_VENGEANCE_SPHERE); +} + +// ================= +// ================= +void hunter_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + edict_t *owner; + + // don't blow up if you hit the world.... sheesh. + if(other==world) + return; + + if(self->owner) + { + // if owner is flying with us, make sure they stop too. + owner=self->owner; + if(owner->flags & FL_SAM_RAIMI) + { + VectorClear(owner->velocity); + owner->movetype = MOVETYPE_NONE; + gi.linkentity(owner); + } + } + + if(self->spawnflags & SPHERE_DOPPLEGANGER) + sphere_touch (self, other, plane, surf, MOD_DOPPLE_HUNTER); + else + sphere_touch (self, other, plane, surf, MOD_HUNTER_SPHERE); +} + +// ================= +// ================= +void defender_shoot (edict_t *self, edict_t *enemy) +{ + vec3_t dir; + vec3_t start; + + if(!(enemy->inuse) || enemy->health <= 0) + return; + + if(enemy == self->owner) + return; + + VectorSubtract (enemy->s.origin, self->s.origin, dir); + VectorNormalize (dir); + + if(self->monsterinfo.attack_finished > level.time) + return; + + if(!visible(self, self->enemy)) + return; + + VectorCopy(self->s.origin, start); + start[2] += 2; + fire_blaster2 (self->owner, start, dir, 10, 1000, EF_BLASTER, 0); + + self->monsterinfo.attack_finished = level.time + 0.4; +} + +// ************************* +// Activation Related Stuff +// ************************* + +// ================= +// ================= +void body_gib (edict_t *self) +{ + int n; + + gi.sound (self, CHAN_BODY, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0); + for (n= 0; n < 4; n++) + ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", 50, GIB_ORGANIC); + ThrowGib (self, "models/objects/gibs/skull/tris.md2", 50, GIB_ORGANIC); +} + +// ================= +// ================= +void hunter_pain (edict_t *self, edict_t *other, float kick, int damage) +{ + edict_t *owner; + float dist; + vec3_t dir; + + if(self->enemy) + return; + + owner = self->owner; + + if(!(self->spawnflags & SPHERE_DOPPLEGANGER)) + { + if(owner && (owner->health > 0)) + return; + + //PMM + if(other == owner) + { +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("hunter: won't get mad at my owner!\n"); + return; + } + //pmm + } + else + { + // if fired by a doppleganger, set it to 10 second timeout + self->wait = level.time + MINIMUM_FLY_TIME; + } + + if((self->wait - level.time) < MINIMUM_FLY_TIME) + self->wait = level.time + MINIMUM_FLY_TIME; + self->s.effects |= EF_BLASTER | EF_TRACKER; + self->touch = hunter_touch; + self->enemy = other; + +// if(g_showlogic && g_showlogic->value) +// gi.dprintf("hunter_pain: mad at %s\n", other->classname); + + // if we're not owned by a player, no sam raimi + // if we're spawned by a doppleganger, no sam raimi + if((self->spawnflags & SPHERE_DOPPLEGANGER) || !(owner && owner->client)) + return; + + // sam raimi cam is disabled if FORCE_RESPAWN is set. + // sam raimi cam is also disabled if huntercam->value is 0. + if(!((int)dmflags->value & DF_FORCE_RESPAWN) && (huntercam && (huntercam->value))) + { + VectorSubtract(other->s.origin, self->s.origin, dir); + dist=VectorLength(dir); + + if(owner && (dist >= 192)) + { + // detach owner from body and send him flying + owner->movetype = MOVETYPE_FLYMISSILE; + + // gib like we just died, even though we didn't, really. + body_gib(owner); + + // move the sphere to the owner's current viewpoint. + // we know it's a valid spot (or will be momentarily) + VectorCopy(owner->s.origin, self->s.origin); + self->s.origin[2] += owner->viewheight; + + // move the player's origin to the sphere's new origin + VectorCopy(self->s.origin, owner->s.origin); + VectorCopy(self->s.angles, owner->s.angles); + VectorCopy(self->s.angles, owner->client->v_angle); + VectorClear(owner->mins); + VectorClear(owner->maxs); + VectorSet(owner->mins, -5, -5, -5); + VectorSet(owner->maxs, 5, 5, 5); + owner->client->ps.fov = 140; + owner->s.modelindex = 0; + owner->s.modelindex2 = 0; + owner->viewheight = 8; + owner->solid = SOLID_NOT; + owner->flags |= FL_SAM_RAIMI; + gi.linkentity(owner); + + // PMM - set bounding box so we don't clip out of world +// VectorSet(self->mins, -5, -5, -5); +// VectorSet(self->maxs, 5, 5, 5); + self->solid = SOLID_BBOX; + gi.linkentity (self); + } +// else +// gi.dprintf("too close for sam raimi cam\n"); + } +} + +// ================= +// ================= +void defender_pain (edict_t *self, edict_t *other, float kick, int damage) +{ + //PMM + if(other == self->owner) + { +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("defender: won't get mad at my owner!\n"); + return; + } + //pmm + self->enemy = other; +} + +// ================= +// ================= +void vengeance_pain (edict_t *self, edict_t *other, float kick, int damage) +{ + if(self->enemy) + return; + + if(!(self->spawnflags & SPHERE_DOPPLEGANGER)) + { + if(self->owner->health >= 25) + return; + + //PMM + if(other == self->owner) + { + // if ((g_showlogic) && (g_showlogic->value)) + // gi.dprintf ("vengeance: won't get mad at my owner!\n"); + return; + } + //pmm + } + else + { + self->wait = level.time + MINIMUM_FLY_TIME; + } + + if((self->wait - level.time) < MINIMUM_FLY_TIME) + self->wait = level.time + MINIMUM_FLY_TIME; + self->s.effects |= EF_ROCKET; + self->touch = vengeance_touch; + self->enemy = other; +} + +// ************************* +// Think Functions +// ************************* + +// =================== +// =================== +void defender_think (edict_t *self) +{ + if(!self->owner) + { +// gi.dprintf("think: no owner\n"); + G_FreeEdict(self); + return; + } + + // if we've exited the level, just remove ourselves. + if (level.intermissiontime) + { + sphere_think_explode(self); + return; + } + + if(self->owner->health <=0) + { + sphere_think_explode(self); + return; + } + +// if(level.time - self->timestamp > 1) +// { +// gi.sound (self, CHAN_VOICE, gi.soundindex ("powerup/dsphere.wav"), 0.6, ATTN_NORM, 0); +// self->timestamp = level.time; +// } + + self->s.frame++; + if(self->s.frame>19) + self->s.frame = 0; + + if(self->enemy) + { + if(self->enemy->health > 0) + { +// gi.dprintf( "shooting at %s\n", self->enemy->classname); + defender_shoot (self, self->enemy); + } + else + self->enemy = NULL; + } +// else +// { +// self->ideal_yaw+=3; +// M_ChangeYaw (self); +// } + + sphere_fly (self); + + if(self->inuse) + self->nextthink = level.time + 0.1; +} + +// ================= +// ================= +void hunter_think (edict_t *self) +{ + edict_t *owner; + vec3_t dir, ang; + + // if we've exited the level, just remove ourselves. + if (level.intermissiontime) + { + sphere_think_explode(self); + return; + } + + owner = self->owner; + if(!owner && !(self->spawnflags & SPHERE_DOPPLEGANGER)) + { +// gi.dprintf("think: no owner\n"); + G_FreeEdict(self); + return; + } + + if(owner) + self->ideal_yaw = owner->s.angles[YAW]; + else if(self->enemy) // fired by doppleganger + { + VectorSubtract(self->enemy->s.origin, self->s.origin, dir); + vectoangles2(dir, ang); + self->ideal_yaw = ang[YAW]; + } + + M_ChangeYaw(self); + +// if(level.time - self->timestamp > 1) +// { +// gi.sound (self, CHAN_VOICE, gi.soundindex ("powerup/hsphere.wav"), 0.5, ATTN_NORM, 0); +// self->timestamp = level.time; +// } + + if(self->enemy) + { + sphere_chase (self, 0); + + // deal with sam raimi cam + if(owner && (owner->flags & FL_SAM_RAIMI)) + { + if(self->inuse) + { + owner->movetype = MOVETYPE_FLYMISSILE; +// VectorCopy(self->s.angles, owner->s.angles); +// VectorCopy(self->s.angles, owner->client->v_angle); + LookAtKiller (owner, self, self->enemy); +// owner->viewheight = 22; + +// owner->client->v_angle[YAW]+=5; + // owner is flying with us, move him too + owner->movetype = MOVETYPE_FLYMISSILE; + owner->viewheight = self->s.origin[2] - owner->s.origin[2]; +// VectorCopy(self->s.angles, owner->s.angles); +// VectorCopy(self->s.angles, owner->client->v_angle); + VectorCopy(self->s.origin, owner->s.origin); + VectorCopy(self->velocity, owner->velocity); + VectorClear(owner->mins); + VectorClear(owner->maxs); + gi.linkentity(owner); + } + else // sphere timed out + { + VectorClear(owner->velocity); + owner->movetype = MOVETYPE_NONE; + gi.linkentity(owner); + } + } + } + else + { +// self->ideal_yaw+=3; +// M_ChangeYaw (self); + sphere_fly (self); + } + + if(self->inuse) + self->nextthink = level.time + 0.1; +} + +// ================= +// ================= +void vengeance_think (edict_t *self) +{ + // if we've exited the level, just remove ourselves. + if (level.intermissiontime) + { + sphere_think_explode(self); + return; + } + + if(!(self->owner) && !(self->spawnflags & SPHERE_DOPPLEGANGER)) + { +// gi.dprintf("think: no owner\n"); + G_FreeEdict(self); + return; + } + +// if(level.time - self->timestamp > 1) +// { +// gi.sound (self, CHAN_VOICE, gi.soundindex ("powerup/vsphere.wav"), 0.5, ATTN_NORM, 0); +// self->timestamp = level.time; +// } + + if(self->enemy) + { +// sphere_fire (self, self->owner->enemy); + sphere_chase (self, 1); + } + else + sphere_fly (self); + + if(self->inuse) + self->nextthink = level.time + 0.1; +} + +// ************************* +// Spawning / Creation +// ************************* + +// monsterinfo_t +// ================= +// ================= +edict_t *Sphere_Spawn (edict_t *owner, int spawnflags) +{ + edict_t *sphere; + + sphere = G_Spawn(); + VectorCopy(owner->s.origin, sphere->s.origin); + sphere->s.origin[2] = owner->absmax[2]; + sphere->s.angles[YAW] = owner->s.angles[YAW]; + sphere->solid = SOLID_BBOX; + sphere->clipmask = MASK_SHOT; + sphere->s.renderfx = RF_FULLBRIGHT | RF_IR_VISIBLE; + sphere->movetype = MOVETYPE_FLYMISSILE; + + if(spawnflags & SPHERE_DOPPLEGANGER) + sphere->teammaster = owner->teammaster; + else + sphere->owner = owner; + + sphere->classname = "sphere"; + sphere->yaw_speed = 40; + sphere->monsterinfo.attack_finished = 0; + sphere->spawnflags = spawnflags; // need this for the HUD to recognize sphere + //PMM + sphere->takedamage = DAMAGE_NO; + + switch(spawnflags & SPHERE_TYPE) + { + case SPHERE_DEFENDER: + sphere->s.modelindex = gi.modelindex("models/items/defender/tris.md2"); + // PMM - this doesn't work, causes problems with other stuff +// sphere->s.modelindex2 = gi.modelindex("models/items/shell/tris.md2") | 0x80; + sphere->s.modelindex2 = gi.modelindex("models/items/shell/tris.md2"); + sphere->s.sound = gi.soundindex ("spheres/d_idle.wav"); + sphere->pain = defender_pain; + sphere->wait = level.time + DEFENDER_LIFESPAN; + sphere->die = sphere_explode; + sphere->think = defender_think; + break; + case SPHERE_HUNTER: + sphere->s.modelindex = gi.modelindex("models/items/hunter/tris.md2"); + sphere->s.sound = gi.soundindex ("spheres/h_idle.wav"); + sphere->wait = level.time + HUNTER_LIFESPAN; + sphere->pain = hunter_pain; + sphere->die = sphere_if_idle_die; + sphere->think = hunter_think; + break; + case SPHERE_VENGEANCE: + sphere->s.modelindex = gi.modelindex("models/items/vengnce/tris.md2"); + sphere->s.sound = gi.soundindex ("spheres/v_idle.wav"); + sphere->wait = level.time + VENGEANCE_LIFESPAN; + sphere->pain = vengeance_pain; + sphere->die = sphere_if_idle_die; + sphere->think = vengeance_think; + VectorSet (sphere->avelocity, 30, 30, 0); + break; + default: + gi.dprintf("Tried to create an invalid sphere\n"); + G_FreeEdict(sphere); + return NULL; + } + + sphere->nextthink = level.time + 0.1; + + gi.linkentity (sphere); + + return sphere; +} + +// ================= +// Own_Sphere - attach the sphere to the client so we can +// directly access it later +// ================= +void Own_Sphere (edict_t *self, edict_t *sphere) +{ + if(!sphere) + return; + + // ownership only for players + if(self->client) + { + // if they don't have one + if(!(self->client->owned_sphere)) + { + self->client->owned_sphere = sphere; + } + // they already have one, take care of the old one + else + { + if(self->client->owned_sphere->inuse) + { + G_FreeEdict(self->client->owned_sphere); + self->client->owned_sphere = sphere; + } + else + { + self->client->owned_sphere = sphere; + } + } + } +} + +// ================= +// ================= +void Defender_Launch (edict_t *self) +{ + edict_t *sphere; + + sphere = Sphere_Spawn (self, SPHERE_DEFENDER); + Own_Sphere (self, sphere); +} + +// ================= +// ================= +void Hunter_Launch (edict_t *self) +{ + edict_t *sphere; + + sphere = Sphere_Spawn (self, SPHERE_HUNTER); + Own_Sphere (self, sphere); +} + +// ================= +// ================= +void Vengeance_Launch (edict_t *self) +{ + edict_t *sphere; + + sphere = Sphere_Spawn (self, SPHERE_VENGEANCE); + Own_Sphere (self, sphere); +} diff --git a/original/rogue/g_svcmds.c b/original/rogue/g_svcmds.c new file mode 100644 index 0000000..b20d25a --- /dev/null +++ b/original/rogue/g_svcmds.c @@ -0,0 +1,283 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. + +#include "g_local.h" + + +void Svcmd_Test_f (void) +{ + gi.cprintf (NULL, PRINT_HIGH, "Svcmd_Test_f()\n"); +} + +/* +============================================================================== + +PACKET FILTERING + + +You can add or remove addresses from the filter list with: + +addip +removeip + +The ip address is specified in dot format, and any unspecified digits will match any value, so you can specify an entire class C network with "addip 192.246.40". + +Removeip will only remove an address specified exactly the same way. You cannot addip a subnet, then removeip a single host. + +listip +Prints the current list of filters. + +writeip +Dumps "addip " commands to listip.cfg so it can be execed at a later date. The filter lists are not saved and restored by default, because I beleive it would cause too much confusion. + +filterban <0 or 1> + +If 1 (the default), then ip addresses matching the current list will be prohibited from entering the game. This is the default setting. + +If 0, then only addresses matching the list will be allowed. This lets you easily set up a private game, or a game that only allows players from your local network. + + +============================================================================== +*/ + +typedef struct +{ + unsigned mask; + unsigned compare; +} ipfilter_t; + +#define MAX_IPFILTERS 1024 + +ipfilter_t ipfilters[MAX_IPFILTERS]; +int numipfilters; + +/* +================= +StringToFilter +================= +*/ +static qboolean StringToFilter (char *s, ipfilter_t *f) +{ + char num[128]; + int i, j; + byte b[4]; + byte m[4]; + + for (i=0 ; i<4 ; i++) + { + b[i] = 0; + m[i] = 0; + } + + for (i=0 ; i<4 ; i++) + { + if (*s < '0' || *s > '9') + { + gi.cprintf(NULL, PRINT_HIGH, "Bad filter address: %s\n", s); + return false; + } + + j = 0; + while (*s >= '0' && *s <= '9') + { + num[j++] = *s++; + } + num[j] = 0; + b[i] = atoi(num); + if (b[i] != 0) + m[i] = 255; + + if (!*s) + break; + s++; + } + + f->mask = *(unsigned *)m; + f->compare = *(unsigned *)b; + + return true; +} + +/* +================= +SV_FilterPacket +================= +*/ +qboolean SV_FilterPacket (char *from) +{ + int i; + unsigned in; + byte m[4]; + char *p; + + i = 0; + p = from; + while (*p && i < 4) { + m[i] = 0; + while (*p >= '0' && *p <= '9') { + m[i] = m[i]*10 + (*p - '0'); + p++; + } + if (!*p || *p == ':') + break; + i++, p++; + } + + in = *(unsigned *)m; + + for (i=0 ; ivalue; + + return (int)!filterban->value; +} + + +/* +================= +SV_AddIP_f +================= +*/ +void SVCmd_AddIP_f (void) +{ + int i; + + if (gi.argc() < 3) { + gi.cprintf(NULL, PRINT_HIGH, "Usage: addip \n"); + return; + } + + for (i=0 ; i\n"); + return; + } + + if (!StringToFilter (gi.argv(2), &f)) + return; + + for (i=0 ; istring) + sprintf (name, "%s/listip.cfg", GAMEVERSION); + else + sprintf (name, "%s/listip.cfg", game->string); + + gi.cprintf (NULL, PRINT_HIGH, "Writing %s.\n", name); + + f = fopen (name, "wb"); + if (!f) + { + gi.cprintf (NULL, PRINT_HIGH, "Couldn't open %s\n", name); + return; + } + + fprintf(f, "set filterban %d\n", (int)filterban->value); + + for (i=0 ; istyle); + gi.WritePosition (ent->s.origin); + gi.multicast (ent->s.origin, MULTICAST_PVS); +} + +void SP_target_temp_entity (edict_t *ent) +{ + ent->use = Use_Target_Tent; +} + + +//========================================================== + +//========================================================== + +/*QUAKED target_speaker (1 0 0) (-8 -8 -8) (8 8 8) looped-on looped-off reliable +"noise" wav file to play +"attenuation" +-1 = none, send to whole level +1 = normal fighting sounds +2 = idle sound level +3 = ambient sound level +"volume" 0.0 to 1.0 + +Normal sounds play each time the target is used. The reliable flag can be set for crucial voiceovers. + +Looped sounds are always atten 3 / vol 1, and the use function toggles it on/off. +Multiple identical looping sounds will just increase volume without any speed cost. +*/ +void Use_Target_Speaker (edict_t *ent, edict_t *other, edict_t *activator) +{ + int chan; + + if (ent->spawnflags & 3) + { // looping sound toggles + if (ent->s.sound) + ent->s.sound = 0; // turn it off + else + ent->s.sound = ent->noise_index; // start it + } + else + { // normal sound + if (ent->spawnflags & 4) + chan = CHAN_VOICE|CHAN_RELIABLE; + else + chan = CHAN_VOICE; + // use a positioned_sound, because this entity won't normally be + // sent to any clients because it is invisible + gi.positioned_sound (ent->s.origin, ent, chan, ent->noise_index, ent->volume, ent->attenuation, 0); + } +} + +void SP_target_speaker (edict_t *ent) +{ + char buffer[MAX_QPATH]; + + if(!st.noise) + { + gi.dprintf("target_speaker with no noise set at %s\n", vtos(ent->s.origin)); + return; + } + if (!strstr (st.noise, ".wav")) + Com_sprintf (buffer, sizeof(buffer), "%s.wav", st.noise); + else + strncpy (buffer, st.noise, sizeof(buffer)); + ent->noise_index = gi.soundindex (buffer); + + if (!ent->volume) + ent->volume = 1.0; + + if (!ent->attenuation) + ent->attenuation = 1.0; + else if (ent->attenuation == -1) // use -1 so 0 defaults to 1 + ent->attenuation = 0; + + // check for prestarted looping sound + if (ent->spawnflags & 1) + ent->s.sound = ent->noise_index; + + ent->use = Use_Target_Speaker; + + // must link the entity so we get areas and clusters so + // the server can determine who to send updates to + gi.linkentity (ent); +} + + +//========================================================== + +void Use_Target_Help (edict_t *ent, edict_t *other, edict_t *activator) +{ + if (ent->spawnflags & 1) + strncpy (game.helpmessage1, ent->message, sizeof(game.helpmessage2)-1); + else + strncpy (game.helpmessage2, ent->message, sizeof(game.helpmessage1)-1); + + game.helpchanged++; +} + +/*QUAKED target_help (1 0 1) (-16 -16 -24) (16 16 24) help1 +When fired, the "message" key becomes the current personal computer string, and the message light will be set on all clients status bars. +*/ +void SP_target_help(edict_t *ent) +{ + if (deathmatch->value) + { // auto-remove for deathmatch + G_FreeEdict (ent); + return; + } + + if (!ent->message) + { + gi.dprintf ("%s with no message at %s\n", ent->classname, vtos(ent->s.origin)); + G_FreeEdict (ent); + return; + } + ent->use = Use_Target_Help; +} + +//========================================================== + +/*QUAKED target_secret (1 0 1) (-8 -8 -8) (8 8 8) +Counts a secret found. +These are single use targets. +*/ +void use_target_secret (edict_t *ent, edict_t *other, edict_t *activator) +{ + gi.sound (ent, CHAN_VOICE, ent->noise_index, 1, ATTN_NORM, 0); + + level.found_secrets++; + + G_UseTargets (ent, activator); + G_FreeEdict (ent); +} + +void SP_target_secret (edict_t *ent) +{ + if (deathmatch->value) + { // auto-remove for deathmatch + G_FreeEdict (ent); + return; + } + + ent->use = use_target_secret; + if (!st.noise) + st.noise = "misc/secret.wav"; + ent->noise_index = gi.soundindex (st.noise); + ent->svflags = SVF_NOCLIENT; + level.total_secrets++; + // map bug hack + if (!Q_stricmp(level.mapname, "mine3") && ent->s.origin[0] == 280 && ent->s.origin[1] == -2048 && ent->s.origin[2] == -624) + ent->message = "You have found a secret area."; +} + +//========================================================== + +/*QUAKED target_goal (1 0 1) (-8 -8 -8) (8 8 8) +Counts a goal completed. +These are single use targets. +*/ +void use_target_goal (edict_t *ent, edict_t *other, edict_t *activator) +{ + gi.sound (ent, CHAN_VOICE, ent->noise_index, 1, ATTN_NORM, 0); + + level.found_goals++; + + if (level.found_goals == level.total_goals) + gi.configstring (CS_CDTRACK, "0"); + + G_UseTargets (ent, activator); + G_FreeEdict (ent); +} + +void SP_target_goal (edict_t *ent) +{ + if (deathmatch->value) + { // auto-remove for deathmatch + G_FreeEdict (ent); + return; + } + + ent->use = use_target_goal; + if (!st.noise) + st.noise = "misc/secret.wav"; + ent->noise_index = gi.soundindex (st.noise); + ent->svflags = SVF_NOCLIENT; + level.total_goals++; +} + +//========================================================== + + +/*QUAKED target_explosion (1 0 0) (-8 -8 -8) (8 8 8) +Spawns an explosion temporary entity when used. + +"delay" wait this long before going off +"dmg" how much radius damage should be done, defaults to 0 +*/ +void target_explosion_explode (edict_t *self) +{ + float save; + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_EXPLOSION1); + gi.WritePosition (self->s.origin); + gi.multicast (self->s.origin, MULTICAST_PHS); + + T_RadiusDamage (self, self->activator, self->dmg, NULL, self->dmg+40, MOD_EXPLOSIVE); + + save = self->delay; + self->delay = 0; + G_UseTargets (self, self->activator); + self->delay = save; +} + +void use_target_explosion (edict_t *self, edict_t *other, edict_t *activator) +{ + self->activator = activator; + + if (!self->delay) + { + target_explosion_explode (self); + return; + } + + self->think = target_explosion_explode; + self->nextthink = level.time + self->delay; +} + +void SP_target_explosion (edict_t *ent) +{ + ent->use = use_target_explosion; + ent->svflags = SVF_NOCLIENT; +} + + +//========================================================== + +/*QUAKED target_changelevel (1 0 0) (-8 -8 -8) (8 8 8) +Changes level to "map" when fired +*/ +void use_target_changelevel (edict_t *self, edict_t *other, edict_t *activator) +{ + if (level.intermissiontime) + return; // already activated + + if (!deathmatch->value && !coop->value) + { + if (g_edicts[1].health <= 0) + return; + } + + // if noexit, do a ton of damage to other + if (deathmatch->value && !( (int)dmflags->value & DF_ALLOW_EXIT) && other != world) + { + T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, 10 * other->max_health, 1000, 0, MOD_EXIT); + return; + } + + // if multiplayer, let everyone know who hit the exit + if (deathmatch->value) + { + if (activator && activator->client) + gi.bprintf (PRINT_HIGH, "%s exited the level.\n", activator->client->pers.netname); + } + + // if going to a new unit, clear cross triggers + if (strstr(self->map, "*")) + game.serverflags &= ~(SFL_CROSS_TRIGGER_MASK); + + BeginIntermission (self); +} + +void SP_target_changelevel (edict_t *ent) +{ + if (!ent->map) + { + gi.dprintf("target_changelevel with no map at %s\n", vtos(ent->s.origin)); + G_FreeEdict (ent); + return; + } + + // ugly hack because *SOMEBODY* screwed up their map + if((Q_stricmp(level.mapname, "fact1") == 0) && (Q_stricmp(ent->map, "fact3") == 0)) + ent->map = "fact3$secret1"; + + ent->use = use_target_changelevel; + ent->svflags = SVF_NOCLIENT; +} + + +//========================================================== + +/*QUAKED target_splash (1 0 0) (-8 -8 -8) (8 8 8) +Creates a particle splash effect when used. + +Set "sounds" to one of the following: + 1) sparks + 2) blue water + 3) brown water + 4) slime + 5) lava + 6) blood + +"count" how many pixels in the splash +"dmg" if set, does a radius damage at this location when it splashes + useful for lava/sparks +*/ + +void use_target_splash (edict_t *self, edict_t *other, edict_t *activator) +{ + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_SPLASH); + gi.WriteByte (self->count); + gi.WritePosition (self->s.origin); + gi.WriteDir (self->movedir); + gi.WriteByte (self->sounds); + gi.multicast (self->s.origin, MULTICAST_PVS); + + if (self->dmg) + T_RadiusDamage (self, activator, self->dmg, NULL, self->dmg+40, MOD_SPLASH); +} + +void SP_target_splash (edict_t *self) +{ + self->use = use_target_splash; + G_SetMovedir (self->s.angles, self->movedir); + + if (!self->count) + self->count = 32; + + self->svflags = SVF_NOCLIENT; +} + + +//========================================================== + +/*QUAKED target_spawner (1 0 0) (-8 -8 -8) (8 8 8) +Set target to the type of entity you want spawned. +Useful for spawning monsters and gibs in the factory levels. + +For monsters: + Set direction to the facing you want it to have. + +For gibs: + Set direction if you want it moving and + speed how fast it should be moving otherwise it + will just be dropped +*/ +void ED_CallSpawn (edict_t *ent); + +void use_target_spawner (edict_t *self, edict_t *other, edict_t *activator) +{ + edict_t *ent; + + ent = G_Spawn(); + ent->classname = self->target; + VectorCopy (self->s.origin, ent->s.origin); + VectorCopy (self->s.angles, ent->s.angles); + ED_CallSpawn (ent); + gi.unlinkentity (ent); + KillBox (ent); + gi.linkentity (ent); + if (self->speed) + VectorCopy (self->movedir, ent->velocity); + + ent->s.renderfx |= RF_IR_VISIBLE; //PGM +} + +void SP_target_spawner (edict_t *self) +{ + self->use = use_target_spawner; + self->svflags = SVF_NOCLIENT; + if (self->speed) + { + G_SetMovedir (self->s.angles, self->movedir); + VectorScale (self->movedir, self->speed, self->movedir); + } +} + +//========================================================== + +/*QUAKED target_blaster (1 0 0) (-8 -8 -8) (8 8 8) NOTRAIL NOEFFECTS +Fires a blaster bolt in the set direction when triggered. + +dmg default is 15 +speed default is 1000 +*/ + +void use_target_blaster (edict_t *self, edict_t *other, edict_t *activator) +{ + int effect; + + if (self->spawnflags & 2) + effect = 0; + else if (self->spawnflags & 1) + effect = EF_HYPERBLASTER; + else + effect = EF_BLASTER; + + fire_blaster (self, self->s.origin, self->movedir, self->dmg, self->speed, EF_BLASTER, MOD_TARGET_BLASTER); + gi.sound (self, CHAN_VOICE, self->noise_index, 1, ATTN_NORM, 0); +} + +void SP_target_blaster (edict_t *self) +{ + self->use = use_target_blaster; + G_SetMovedir (self->s.angles, self->movedir); + self->noise_index = gi.soundindex ("weapons/laser2.wav"); + + if (!self->dmg) + self->dmg = 15; + if (!self->speed) + self->speed = 1000; + + self->svflags = SVF_NOCLIENT; +} + + +//========================================================== + +/*QUAKED target_crosslevel_trigger (.5 .5 .5) (-8 -8 -8) (8 8 8) trigger1 trigger2 trigger3 trigger4 trigger5 trigger6 trigger7 trigger8 +Once this trigger is touched/used, any trigger_crosslevel_target with the same trigger number is automatically used when a level is started within the same unit. It is OK to check multiple triggers. Message, delay, target, and killtarget also work. +*/ +void trigger_crosslevel_trigger_use (edict_t *self, edict_t *other, edict_t *activator) +{ + game.serverflags |= self->spawnflags; + G_FreeEdict (self); +} + +void SP_target_crosslevel_trigger (edict_t *self) +{ + self->svflags = SVF_NOCLIENT; + self->use = trigger_crosslevel_trigger_use; +} + +/*QUAKED target_crosslevel_target (.5 .5 .5) (-8 -8 -8) (8 8 8) trigger1 trigger2 trigger3 trigger4 trigger5 trigger6 trigger7 trigger8 +Triggered by a trigger_crosslevel elsewhere within a unit. If multiple triggers are checked, all must be true. Delay, target and +killtarget also work. + +"delay" delay before using targets if the trigger has been activated (default 1) +*/ +void target_crosslevel_target_think (edict_t *self) +{ + if (self->spawnflags == (game.serverflags & SFL_CROSS_TRIGGER_MASK & self->spawnflags)) + { + G_UseTargets (self, self); + G_FreeEdict (self); + } +} + +void SP_target_crosslevel_target (edict_t *self) +{ + if (! self->delay) + self->delay = 1; + self->svflags = SVF_NOCLIENT; + + self->think = target_crosslevel_target_think; + self->nextthink = level.time + self->delay; +} + +//========================================================== + +/*QUAKED target_laser (0 .5 .8) (-8 -8 -8) (8 8 8) START_ON RED GREEN BLUE YELLOW ORANGE FAT WINDOWSTOP +When triggered, fires a laser. You can either set a target +or a direction. + +WINDOWSTOP - stops at CONTENTS_WINDOW +*/ + +//====== +// PGM +#define LASER_ON 0x0001 +#define LASER_RED 0x0002 +#define LASER_GREEN 0x0004 +#define LASER_BLUE 0x0008 +#define LASER_YELLOW 0x0010 +#define LASER_ORANGE 0x0020 +#define LASER_FAT 0x0040 +#define LASER_STOPWINDOW 0x0080 +// PGM +//====== + +void target_laser_think (edict_t *self) +{ + edict_t *ignore; + vec3_t start; + vec3_t end; + trace_t tr; + vec3_t point; + vec3_t last_movedir; + int count; + + if (self->spawnflags & 0x80000000) + count = 8; + else + count = 4; + + if (self->enemy) + { + VectorCopy (self->movedir, last_movedir); + VectorMA (self->enemy->absmin, 0.5, self->enemy->size, point); + VectorSubtract (point, self->s.origin, self->movedir); + VectorNormalize (self->movedir); + if (!VectorCompare(self->movedir, last_movedir)) + self->spawnflags |= 0x80000000; + } + + ignore = self; + VectorCopy (self->s.origin, start); + VectorMA (start, 2048, self->movedir, end); + while(1) + { +//====== +// PGM + if(self->spawnflags & LASER_STOPWINDOW) + tr = gi.trace (start, NULL, NULL, end, ignore, MASK_SHOT); + else + tr = gi.trace (start, NULL, NULL, end, ignore, CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_DEADMONSTER); +// PGM +//====== + + if (!tr.ent) + break; + + // hurt it if we can + if ((tr.ent->takedamage) && !(tr.ent->flags & FL_IMMUNE_LASER)) + T_Damage (tr.ent, self, self->activator, self->movedir, tr.endpos, vec3_origin, self->dmg, 1, DAMAGE_ENERGY, MOD_TARGET_LASER); + + // if we hit something that's not a monster or player or is immune to lasers, we're done +// if (!(tr.ent->svflags & SVF_MONSTER) && (!tr.ent->client)) + //PMM added SVF_DAMAGEABLE + if (!(tr.ent->svflags & SVF_MONSTER) && (!tr.ent->client) && !(tr.ent->svflags & SVF_DAMAGEABLE)) + { + if (self->spawnflags & 0x80000000) + { + self->spawnflags &= ~0x80000000; + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_LASER_SPARKS); + gi.WriteByte (count); + gi.WritePosition (tr.endpos); + gi.WriteDir (tr.plane.normal); + gi.WriteByte (self->s.skinnum); + gi.multicast (tr.endpos, MULTICAST_PVS); + } + break; + } + + ignore = tr.ent; + VectorCopy (tr.endpos, start); + } + + VectorCopy (tr.endpos, self->s.old_origin); + + self->nextthink = level.time + FRAMETIME; +} + +void target_laser_on (edict_t *self) +{ + if (!self->activator) + self->activator = self; + self->spawnflags |= 0x80000001; + self->svflags &= ~SVF_NOCLIENT; + target_laser_think (self); +} + +void target_laser_off (edict_t *self) +{ + self->spawnflags &= ~1; + self->svflags |= SVF_NOCLIENT; + self->nextthink = 0; +} + +void target_laser_use (edict_t *self, edict_t *other, edict_t *activator) +{ + self->activator = activator; + if (self->spawnflags & 1) + target_laser_off (self); + else + target_laser_on (self); +} + +void target_laser_start (edict_t *self) +{ + edict_t *ent; + + self->movetype = MOVETYPE_NONE; + self->solid = SOLID_NOT; + self->s.renderfx |= RF_BEAM|RF_TRANSLUCENT; + self->s.modelindex = 1; // must be non-zero + + // set the beam diameter + if (self->spawnflags & 64) + self->s.frame = 16; + else + self->s.frame = 4; + + // set the color + if (self->spawnflags & 2) + self->s.skinnum = 0xf2f2f0f0; + else if (self->spawnflags & 4) + self->s.skinnum = 0xd0d1d2d3; + else if (self->spawnflags & 8) + self->s.skinnum = 0xf3f3f1f1; + else if (self->spawnflags & 16) + self->s.skinnum = 0xdcdddedf; + else if (self->spawnflags & 32) + self->s.skinnum = 0xe0e1e2e3; + + if (!self->enemy) + { + if (self->target) + { + ent = G_Find (NULL, FOFS(targetname), self->target); + if (!ent) + gi.dprintf ("%s at %s: %s is a bad target\n", self->classname, vtos(self->s.origin), self->target); + self->enemy = ent; + } + else + { + G_SetMovedir (self->s.angles, self->movedir); + } + } + self->use = target_laser_use; + self->think = target_laser_think; + + if (!self->dmg) + self->dmg = 1; + + VectorSet (self->mins, -8, -8, -8); + VectorSet (self->maxs, 8, 8, 8); + gi.linkentity (self); + + if (self->spawnflags & 1) + target_laser_on (self); + else + target_laser_off (self); +} + +void SP_target_laser (edict_t *self) +{ + // let everything else get spawned before we start firing + self->think = target_laser_start; + self->nextthink = level.time + 1; +} + +//========================================================== + +/*QUAKED target_lightramp (0 .5 .8) (-8 -8 -8) (8 8 8) TOGGLE +speed How many seconds the ramping will take +message two letters; starting lightlevel and ending lightlevel +*/ + +void target_lightramp_think (edict_t *self) +{ + char style[2]; + + style[0] = 'a' + self->movedir[0] + (level.time - self->timestamp) / FRAMETIME * self->movedir[2]; + style[1] = 0; + gi.configstring (CS_LIGHTS+self->enemy->style, style); + + if ((level.time - self->timestamp) < self->speed) + { + self->nextthink = level.time + FRAMETIME; + } + else if (self->spawnflags & 1) + { + char temp; + + temp = self->movedir[0]; + self->movedir[0] = self->movedir[1]; + self->movedir[1] = temp; + self->movedir[2] *= -1; + } +} + +void target_lightramp_use (edict_t *self, edict_t *other, edict_t *activator) +{ + if (!self->enemy) + { + edict_t *e; + + // check all the targets + e = NULL; + while (1) + { + e = G_Find (e, FOFS(targetname), self->target); + if (!e) + break; + if (strcmp(e->classname, "light") != 0) + { + gi.dprintf("%s at %s ", self->classname, vtos(self->s.origin)); + gi.dprintf("target %s (%s at %s) is not a light\n", self->target, e->classname, vtos(e->s.origin)); + } + else + { + self->enemy = e; + } + } + + if (!self->enemy) + { + gi.dprintf("%s target %s not found at %s\n", self->classname, self->target, vtos(self->s.origin)); + G_FreeEdict (self); + return; + } + } + + self->timestamp = level.time; + target_lightramp_think (self); +} + +void SP_target_lightramp (edict_t *self) +{ + if (!self->message || strlen(self->message) != 2 || self->message[0] < 'a' || self->message[0] > 'z' || self->message[1] < 'a' || self->message[1] > 'z' || self->message[0] == self->message[1]) + { + gi.dprintf("target_lightramp has bad ramp (%s) at %s\n", self->message, vtos(self->s.origin)); + G_FreeEdict (self); + return; + } + + if (deathmatch->value) + { + G_FreeEdict (self); + return; + } + + if (!self->target) + { + gi.dprintf("%s with no target at %s\n", self->classname, vtos(self->s.origin)); + G_FreeEdict (self); + return; + } + + self->svflags |= SVF_NOCLIENT; + self->use = target_lightramp_use; + self->think = target_lightramp_think; + + self->movedir[0] = self->message[0] - 'a'; + self->movedir[1] = self->message[1] - 'a'; + self->movedir[2] = (self->movedir[1] - self->movedir[0]) / (self->speed / FRAMETIME); +} + +//========================================================== + +/*QUAKED target_earthquake (1 0 0) (-8 -8 -8) (8 8 8) SILENT +When triggered, this initiates a level-wide earthquake. +All players and monsters are affected. +"speed" severity of the quake (default:200) +"count" duration of the quake (default:5) +*/ + +void target_earthquake_think (edict_t *self) +{ + int i; + edict_t *e; + + if(!(self->spawnflags & 1)) // PGM + { // PGM + if (self->last_move_time < level.time) + { + gi.positioned_sound (self->s.origin, self, CHAN_AUTO, self->noise_index, 1.0, ATTN_NONE, 0); + self->last_move_time = level.time + 0.5; + } + } // PGM + + for (i=1, e=g_edicts+i; i < globals.num_edicts; i++,e++) + { + if (!e->inuse) + continue; + if (!e->client) + continue; + if (!e->groundentity) + continue; + + e->groundentity = NULL; + e->velocity[0] += crandom()* 150; + e->velocity[1] += crandom()* 150; + e->velocity[2] = self->speed * (100.0 / e->mass); + } + + if (level.time < self->timestamp) + self->nextthink = level.time + FRAMETIME; +} + +void target_earthquake_use (edict_t *self, edict_t *other, edict_t *activator) +{ + // PGM +// if(g_showlogic && g_showlogic->value) +// gi.dprintf("earthquake: %0.1f\n", self->speed); + // PGM + + self->timestamp = level.time + self->count; + self->nextthink = level.time + FRAMETIME; + self->activator = activator; + self->last_move_time = 0; +} + +void SP_target_earthquake (edict_t *self) +{ + if (!self->targetname) + gi.dprintf("untargeted %s at %s\n", self->classname, vtos(self->s.origin)); + + if (!self->count) + self->count = 5; + + if (!self->speed) + self->speed = 200; + + self->svflags |= SVF_NOCLIENT; + self->think = target_earthquake_think; + self->use = target_earthquake_use; + + if(!(self->spawnflags & 1)) // PGM + self->noise_index = gi.soundindex ("world/quake.wav"); +} diff --git a/original/rogue/g_trigger.c b/original/rogue/g_trigger.c new file mode 100644 index 0000000..2c2cf70 --- /dev/null +++ b/original/rogue/g_trigger.c @@ -0,0 +1,672 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +#include "g_local.h" + +//PGM - some of these are mine, some id's. I added the define's. +#define TRIGGER_MONSTER 0x01 +#define TRIGGER_NOT_PLAYER 0x02 +#define TRIGGER_TRIGGERED 0x04 +#define TRIGGER_TOGGLE 0x08 +//PGM + +void InitTrigger (edict_t *self) +{ + if (!VectorCompare (self->s.angles, vec3_origin)) + G_SetMovedir (self->s.angles, self->movedir); + + self->solid = SOLID_TRIGGER; + self->movetype = MOVETYPE_NONE; + gi.setmodel (self, self->model); + self->svflags = SVF_NOCLIENT; +} + + +// the wait time has passed, so set back up for another activation +void multi_wait (edict_t *ent) +{ + ent->nextthink = 0; +} + + +// the trigger was just activated +// ent->activator should be set to the activator so it can be held through a delay +// so wait for the delay time before firing +void multi_trigger (edict_t *ent) +{ + if (ent->nextthink) + return; // already been triggered + + G_UseTargets (ent, ent->activator); + + if (ent->wait > 0) + { + ent->think = multi_wait; + ent->nextthink = level.time + ent->wait; + } + else + { // we can't just remove (self) here, because this is a touch function + // called while looping through area links... + ent->touch = NULL; + ent->nextthink = level.time + FRAMETIME; + ent->think = G_FreeEdict; + } +} + +void Use_Multi (edict_t *ent, edict_t *other, edict_t *activator) +{ +//PGM + if(ent->spawnflags & TRIGGER_TOGGLE) + { + if(ent->solid == SOLID_TRIGGER) + ent->solid = SOLID_NOT; + else + ent->solid = SOLID_TRIGGER; + gi.linkentity (ent); + } + else + { + ent->activator = activator; + multi_trigger (ent); + } +//PGM +} + +void Touch_Multi (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if(other->client) + { + if (self->spawnflags & 2) + return; + } + else if (other->svflags & SVF_MONSTER) + { + if (!(self->spawnflags & 1)) + return; + } + else + return; + + if (!VectorCompare(self->movedir, vec3_origin)) + { + vec3_t forward; + + AngleVectors(other->s.angles, forward, NULL, NULL); + if (_DotProduct(forward, self->movedir) < 0) + return; + } + + self->activator = other; + multi_trigger (self); +} + +/*QUAKED trigger_multiple (.5 .5 .5) ? MONSTER NOT_PLAYER TRIGGERED TOGGLE +Variable sized repeatable trigger. Must be targeted at one or more entities. +If "delay" is set, the trigger waits some time after activating before firing. +"wait" : Seconds between triggerings. (.2 default) + +TOGGLE - using this trigger will activate/deactivate it. trigger will begin inactive. + +sounds +1) secret +2) beep beep +3) large switch +4) +set "message" to text string +*/ +void trigger_enable (edict_t *self, edict_t *other, edict_t *activator) +{ + self->solid = SOLID_TRIGGER; + self->use = Use_Multi; + gi.linkentity (self); +} + +void SP_trigger_multiple (edict_t *ent) +{ + if (ent->sounds == 1) + ent->noise_index = gi.soundindex ("misc/secret.wav"); + else if (ent->sounds == 2) + ent->noise_index = gi.soundindex ("misc/talk.wav"); + else if (ent->sounds == 3) + ent->noise_index = gi.soundindex ("misc/trigger1.wav"); + + if (!ent->wait) + ent->wait = 0.2; + ent->touch = Touch_Multi; + ent->movetype = MOVETYPE_NONE; + ent->svflags |= SVF_NOCLIENT; + +//PGM + if (ent->spawnflags & (TRIGGER_TRIGGERED | TRIGGER_TOGGLE)) + { + ent->solid = SOLID_NOT; + ent->use = trigger_enable; + } + else + { + ent->solid = SOLID_TRIGGER; + ent->use = Use_Multi; + } +//PGM + + if (!VectorCompare(ent->s.angles, vec3_origin)) + G_SetMovedir (ent->s.angles, ent->movedir); + + gi.setmodel (ent, ent->model); + gi.linkentity (ent); +} + + +/*QUAKED trigger_once (.5 .5 .5) ? x x TRIGGERED +Triggers once, then removes itself. +You must set the key "target" to the name of another object in the level that has a matching "targetname". + +If TRIGGERED, this trigger must be triggered before it is live. + +sounds + 1) secret + 2) beep beep + 3) large switch + 4) + +"message" string to be displayed when triggered +*/ + +void SP_trigger_once(edict_t *ent) +{ + // make old maps work because I messed up on flag assignments here + // triggered was on bit 1 when it should have been on bit 4 + if (ent->spawnflags & 1) + { + vec3_t v; + + VectorMA (ent->mins, 0.5, ent->size, v); + ent->spawnflags &= ~1; + ent->spawnflags |= 4; + gi.dprintf("fixed TRIGGERED flag on %s at %s\n", ent->classname, vtos(v)); + } + + ent->wait = -1; + SP_trigger_multiple (ent); +} + +/*QUAKED trigger_relay (.5 .5 .5) (-8 -8 -8) (8 8 8) +This fixed size trigger cannot be touched, it can only be fired by other events. +*/ +void trigger_relay_use (edict_t *self, edict_t *other, edict_t *activator) +{ + G_UseTargets (self, activator); +} + +void SP_trigger_relay (edict_t *self) +{ + self->use = trigger_relay_use; +} + + +/* +============================================================================== + +trigger_key + +============================================================================== +*/ + +/*QUAKED trigger_key (.5 .5 .5) (-8 -8 -8) (8 8 8) +A relay trigger that only fires it's targets if player has the proper key. +Use "item" to specify the required key, for example "key_data_cd" +*/ +void trigger_key_use (edict_t *self, edict_t *other, edict_t *activator) +{ + int index; + + if (!self->item) + return; + if (!activator->client) + return; + + index = ITEM_INDEX(self->item); + if (!activator->client->pers.inventory[index]) + { + if (level.time < self->touch_debounce_time) + return; + self->touch_debounce_time = level.time + 5.0; + gi.centerprintf (activator, "You need the %s", self->item->pickup_name); + gi.sound (activator, CHAN_AUTO, gi.soundindex ("misc/keytry.wav"), 1, ATTN_NORM, 0); + return; + } + + gi.sound (activator, CHAN_AUTO, gi.soundindex ("misc/keyuse.wav"), 1, ATTN_NORM, 0); + if (coop->value) + { + int player; + edict_t *ent; + + if (strcmp(self->item->classname, "key_power_cube") == 0) + { + int cube; + + for (cube = 0; cube < 8; cube++) + if (activator->client->pers.power_cubes & (1 << cube)) + break; + for (player = 1; player <= game.maxclients; player++) + { + ent = &g_edicts[player]; + if (!ent->inuse) + continue; + if (!ent->client) + continue; + if (ent->client->pers.power_cubes & (1 << cube)) + { + ent->client->pers.inventory[index]--; + ent->client->pers.power_cubes &= ~(1 << cube); + } + } + } + else + { + for (player = 1; player <= game.maxclients; player++) + { + ent = &g_edicts[player]; + if (!ent->inuse) + continue; + if (!ent->client) + continue; + ent->client->pers.inventory[index] = 0; + } + } + } + else + { + activator->client->pers.inventory[index]--; + } + + G_UseTargets (self, activator); + + self->use = NULL; +} + +void SP_trigger_key (edict_t *self) +{ + if (!st.item) + { + gi.dprintf("no key item for trigger_key at %s\n", vtos(self->s.origin)); + return; + } + self->item = FindItemByClassname (st.item); + + if (!self->item) + { + gi.dprintf("item %s not found for trigger_key at %s\n", st.item, vtos(self->s.origin)); + return; + } + + if (!self->target) + { + gi.dprintf("%s at %s has no target\n", self->classname, vtos(self->s.origin)); + return; + } + + gi.soundindex ("misc/keytry.wav"); + gi.soundindex ("misc/keyuse.wav"); + + self->use = trigger_key_use; +} + + +/* +============================================================================== + +trigger_counter + +============================================================================== +*/ + +/*QUAKED trigger_counter (.5 .5 .5) ? nomessage +Acts as an intermediary for an action that takes multiple inputs. + +If nomessage is not set, t will print "1 more.. " etc when triggered and "sequence complete" when finished. + +After the counter has been triggered "count" times (default 2), it will fire all of it's targets and remove itself. +*/ + +void trigger_counter_use(edict_t *self, edict_t *other, edict_t *activator) +{ + if (self->count == 0) + return; + + self->count--; + + if (self->count) + { + if (! (self->spawnflags & 1)) + { + gi.centerprintf(activator, "%i more to go...", self->count); + gi.sound (activator, CHAN_AUTO, gi.soundindex ("misc/talk1.wav"), 1, ATTN_NORM, 0); + } + return; + } + + if (! (self->spawnflags & 1)) + { + gi.centerprintf(activator, "Sequence completed!"); + gi.sound (activator, CHAN_AUTO, gi.soundindex ("misc/talk1.wav"), 1, ATTN_NORM, 0); + } + self->activator = activator; + multi_trigger (self); +} + +void SP_trigger_counter (edict_t *self) +{ + self->wait = -1; + if (!self->count) + self->count = 2; + + self->use = trigger_counter_use; +} + + +/* +============================================================================== + +trigger_always + +============================================================================== +*/ + +/*QUAKED trigger_always (.5 .5 .5) (-8 -8 -8) (8 8 8) +This trigger will always fire. It is activated by the world. +*/ +void SP_trigger_always (edict_t *ent) +{ + // we must have some delay to make sure our use targets are present + if (ent->delay < 0.2) + ent->delay = 0.2; + G_UseTargets(ent, ent); +} + + +/* +============================================================================== + +trigger_push + +============================================================================== +*/ + +// PGM +#define PUSH_ONCE 0x01 +#define PUSH_START_OFF 0x02 +#define PUSH_SILENT 0x04 +// PGM + +static int windsound; + +void trigger_push_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (strcmp(other->classname, "grenade") == 0) + { + VectorScale (self->movedir, self->speed * 10, other->velocity); + } + else if (other->health > 0) + { + VectorScale (self->movedir, self->speed * 10, other->velocity); + + if (other->client) + { + // don't take falling damage immediately from this + VectorCopy (other->velocity, other->client->oldvelocity); + if (!(self->spawnflags & PUSH_SILENT) && (other->fly_sound_debounce_time < level.time)) + { + other->fly_sound_debounce_time = level.time + 1.5; + gi.sound (other, CHAN_AUTO, windsound, 1, ATTN_NORM, 0); + } + } + } + if (self->spawnflags & PUSH_ONCE) + G_FreeEdict (self); +} + +//====== +//PGM +void trigger_push_use (edict_t *self, edict_t *other, edict_t *activator) +{ + if (self->solid == SOLID_NOT) + self->solid = SOLID_TRIGGER; + else + self->solid = SOLID_NOT; + gi.linkentity (self); +} +//PGM +//====== + +/*QUAKED trigger_push (.5 .5 .5) ? PUSH_ONCE START_OFF SILENT +Pushes the player +"speed" defaults to 1000 + +If targeted, it will toggle on and off when used. + +START_OFF - toggled trigger_push begins in off setting +SILENT - doesn't make wind noise +*/ +void SP_trigger_push (edict_t *self) +{ + InitTrigger (self); + windsound = gi.soundindex ("misc/windfly.wav"); + self->touch = trigger_push_touch; + if (!self->speed) + self->speed = 1000; + +//PGM + if(self->targetname) // toggleable + { + self->use = trigger_push_use; + if(self->spawnflags & PUSH_START_OFF) + self->solid = SOLID_NOT; + } + else if(self->spawnflags & PUSH_START_OFF) + { + gi.dprintf ("trigger_push is START_OFF but not targeted.\n"); + self->svflags = 0; + self->touch = NULL; + self->solid = SOLID_BSP; + self->movetype = MOVETYPE_PUSH; + } +//PGM + + gi.linkentity (self); +} + + +/* +============================================================================== + +trigger_hurt + +============================================================================== +*/ + +/*QUAKED trigger_hurt (.5 .5 .5) ? START_OFF TOGGLE SILENT NO_PROTECTION SLOW +Any entity that touches this will be hurt. + +It does dmg points of damage each server frame + +SILENT supresses playing the sound +SLOW changes the damage rate to once per second +NO_PROTECTION *nothing* stops the damage + +"dmg" default 5 (whole numbers only) + +*/ +void hurt_use (edict_t *self, edict_t *other, edict_t *activator) +{ + if (self->solid == SOLID_NOT) + self->solid = SOLID_TRIGGER; + else + self->solid = SOLID_NOT; + gi.linkentity (self); + + if (!(self->spawnflags & 2)) + self->use = NULL; +} + + +void hurt_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + int dflags; + + if (!other->takedamage) + return; + + if (self->timestamp > level.time) + return; + + if (self->spawnflags & 16) + self->timestamp = level.time + 1; + else + self->timestamp = level.time + FRAMETIME; + + if (!(self->spawnflags & 4)) + { + if ((level.framenum % 10) == 0) + gi.sound (other, CHAN_AUTO, self->noise_index, 1, ATTN_NORM, 0); + } + + if (self->spawnflags & 8) + dflags = DAMAGE_NO_PROTECTION; + else + dflags = 0; + T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, self->dmg, dflags, MOD_TRIGGER_HURT); +} + +void SP_trigger_hurt (edict_t *self) +{ + InitTrigger (self); + + self->noise_index = gi.soundindex ("world/electro.wav"); + self->touch = hurt_touch; + + if (!self->dmg) + self->dmg = 5; + + if (self->spawnflags & 1) + self->solid = SOLID_NOT; + else + self->solid = SOLID_TRIGGER; + + if (self->spawnflags & 2) + self->use = hurt_use; + + gi.linkentity (self); +} + + +/* +============================================================================== + +trigger_gravity + +============================================================================== +*/ + +//PGM +void trigger_gravity_use (edict_t *self, edict_t *other, edict_t *activator) +{ + if (self->solid == SOLID_NOT) + self->solid = SOLID_TRIGGER; + else + self->solid = SOLID_NOT; + gi.linkentity (self); +} +//PGM + +void trigger_gravity_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + other->gravity = self->gravity; +} + +/*QUAKED trigger_gravity (.5 .5 .5) ? TOGGLE START_OFF +Changes the touching entites gravity to +the value of "gravity". 1.0 is standard +gravity for the level. + +TOGGLE - trigger_gravity can be turned on and off +START_OFF - trigger_gravity starts turned off (implies TOGGLE) +*/ +void SP_trigger_gravity (edict_t *self) +{ + if (st.gravity == 0) + { + gi.dprintf("trigger_gravity without gravity set at %s\n", vtos(self->s.origin)); + G_FreeEdict (self); + return; + } + + InitTrigger (self); + +//PGM +// self->gravity = atoi(st.gravity); + self->gravity = atof(st.gravity); + + if(self->spawnflags & 1) // TOGGLE + self->use = trigger_gravity_use; + + if(self->spawnflags & 2) // START_OFF + { + self->use = trigger_gravity_use; + self->solid = SOLID_NOT; + } + + self->touch = trigger_gravity_touch; +//PGM + + gi.linkentity (self); +} + + +/* +============================================================================== + +trigger_monsterjump + +============================================================================== +*/ + +/*QUAKED trigger_monsterjump (.5 .5 .5) ? +Walking monsters that touch this will jump in the direction of the trigger's angle +"speed" default to 200, the speed thrown forward +"height" default to 200, the speed thrown upwards +*/ + +void trigger_monsterjump_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (other->flags & (FL_FLY | FL_SWIM) ) + return; + if (other->svflags & SVF_DEADMONSTER) + return; + if ( !(other->svflags & SVF_MONSTER)) + return; + +// set XY even if not on ground, so the jump will clear lips + other->velocity[0] = self->movedir[0] * self->speed; + other->velocity[1] = self->movedir[1] * self->speed; + + if (!other->groundentity) + return; + + other->groundentity = NULL; + other->velocity[2] = self->movedir[2]; +} + +void SP_trigger_monsterjump (edict_t *self) +{ + if (!self->speed) + self->speed = 200; + if (!st.height) + st.height = 200; + if (self->s.angles[YAW] == 0) + self->s.angles[YAW] = 360; + InitTrigger (self); + self->touch = trigger_monsterjump_touch; + self->movedir[2] = st.height; +} + diff --git a/original/rogue/g_turret.c b/original/rogue/g_turret.c new file mode 100644 index 0000000..8dba542 --- /dev/null +++ b/original/rogue/g_turret.c @@ -0,0 +1,605 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// g_turret.c + +#include "g_local.h" + +void SpawnTargetingSystem (edict_t *turret); // PGM + +void AnglesNormalize(vec3_t vec) +{ + while(vec[0] > 360) + vec[0] -= 360; + while(vec[0] < 0) + vec[0] += 360; + while(vec[1] > 360) + vec[1] -= 360; + while(vec[1] < 0) + vec[1] += 360; +} + +float SnapToEights(float x) +{ + x *= 8.0; + if (x > 0.0) + x += 0.5; + else + x -= 0.5; + return 0.125 * (int)x; +} + + +void turret_blocked(edict_t *self, edict_t *other) +{ + edict_t *attacker; + + if (other->takedamage) + { + if (self->teammaster->owner) + attacker = self->teammaster->owner; + else + attacker = self->teammaster; + T_Damage (other, self, attacker, vec3_origin, other->s.origin, vec3_origin, self->teammaster->dmg, 10, 0, MOD_CRUSH); + } +} + +/*QUAKED turret_breach (0 0 0) ? +This portion of the turret can change both pitch and yaw. +The model should be made with a flat pitch. +It (and the associated base) need to be oriented towards 0. +Use "angle" to set the starting angle. + +"speed" default 50 +"dmg" default 10 +"angle" point this forward +"target" point this at an info_notnull at the muzzle tip +"minpitch" min acceptable pitch angle : default -30 +"maxpitch" max acceptable pitch angle : default 30 +"minyaw" min acceptable yaw angle : default 0 +"maxyaw" max acceptable yaw angle : default 360 +*/ + +void turret_breach_fire (edict_t *self) +{ + vec3_t f, r, u; + vec3_t start; + int damage; + int speed; + + AngleVectors (self->s.angles, f, r, u); + VectorMA (self->s.origin, self->move_origin[0], f, start); + VectorMA (start, self->move_origin[1], r, start); + VectorMA (start, self->move_origin[2], u, start); + + damage = 100 + random() * 50; + speed = 550 + 50 * skill->value; + fire_rocket (self->teammaster->owner, start, f, damage, speed, 150, damage); + gi.positioned_sound (start, self, CHAN_WEAPON, gi.soundindex("weapons/rocklf1a.wav"), 1, ATTN_NORM, 0); +} + +void turret_breach_think (edict_t *self) +{ + edict_t *ent; + vec3_t current_angles; + vec3_t delta; + + VectorCopy (self->s.angles, current_angles); + AnglesNormalize(current_angles); + + AnglesNormalize(self->move_angles); + if (self->move_angles[PITCH] > 180) + self->move_angles[PITCH] -= 360; + + // clamp angles to mins & maxs + if (self->move_angles[PITCH] > self->pos1[PITCH]) + self->move_angles[PITCH] = self->pos1[PITCH]; + else if (self->move_angles[PITCH] < self->pos2[PITCH]) + self->move_angles[PITCH] = self->pos2[PITCH]; + + if ((self->move_angles[YAW] < self->pos1[YAW]) || (self->move_angles[YAW] > self->pos2[YAW])) + { + float dmin, dmax; + + dmin = fabs(self->pos1[YAW] - self->move_angles[YAW]); + if (dmin < -180) + dmin += 360; + else if (dmin > 180) + dmin -= 360; + dmax = fabs(self->pos2[YAW] - self->move_angles[YAW]); + if (dmax < -180) + dmax += 360; + else if (dmax > 180) + dmax -= 360; + if (fabs(dmin) < fabs(dmax)) + self->move_angles[YAW] = self->pos1[YAW]; + else + self->move_angles[YAW] = self->pos2[YAW]; + } + + VectorSubtract (self->move_angles, current_angles, delta); + if (delta[0] < -180) + delta[0] += 360; + else if (delta[0] > 180) + delta[0] -= 360; + if (delta[1] < -180) + delta[1] += 360; + else if (delta[1] > 180) + delta[1] -= 360; + delta[2] = 0; + + if (delta[0] > self->speed * FRAMETIME) + delta[0] = self->speed * FRAMETIME; + if (delta[0] < -1 * self->speed * FRAMETIME) + delta[0] = -1 * self->speed * FRAMETIME; + if (delta[1] > self->speed * FRAMETIME) + delta[1] = self->speed * FRAMETIME; + if (delta[1] < -1 * self->speed * FRAMETIME) + delta[1] = -1 * self->speed * FRAMETIME; + + VectorScale (delta, 1.0/FRAMETIME, self->avelocity); + + self->nextthink = level.time + FRAMETIME; + + for (ent = self->teammaster; ent; ent = ent->teamchain) + ent->avelocity[1] = self->avelocity[1]; + + // if we have adriver, adjust his velocities + if (self->owner) + { + float angle; + float target_z; + float diff; + vec3_t target; + vec3_t dir; + + // angular is easy, just copy ours + self->owner->avelocity[0] = self->avelocity[0]; + self->owner->avelocity[1] = self->avelocity[1]; + + // x & y + angle = self->s.angles[1] + self->owner->move_origin[1]; + angle *= (M_PI*2 / 360); + target[0] = SnapToEights(self->s.origin[0] + cos(angle) * self->owner->move_origin[0]); + target[1] = SnapToEights(self->s.origin[1] + sin(angle) * self->owner->move_origin[0]); + target[2] = self->owner->s.origin[2]; + + VectorSubtract (target, self->owner->s.origin, dir); + self->owner->velocity[0] = dir[0] * 1.0 / FRAMETIME; + self->owner->velocity[1] = dir[1] * 1.0 / FRAMETIME; + + // z + angle = self->s.angles[PITCH] * (M_PI*2 / 360); + target_z = SnapToEights(self->s.origin[2] + self->owner->move_origin[0] * tan(angle) + self->owner->move_origin[2]); + + diff = target_z - self->owner->s.origin[2]; + self->owner->velocity[2] = diff * 1.0 / FRAMETIME; + + if (self->spawnflags & 65536) + { + turret_breach_fire (self); + self->spawnflags &= ~65536; + } + } +} + +void turret_breach_finish_init (edict_t *self) +{ + // get and save info for muzzle location + if (!self->target) + { + gi.dprintf("%s at %s needs a target\n", self->classname, vtos(self->s.origin)); + } + else + { + self->target_ent = G_PickTarget (self->target); + if(self->target_ent) + { + VectorSubtract (self->target_ent->s.origin, self->s.origin, self->move_origin); + G_FreeEdict(self->target_ent); + } + else + gi.dprintf("could not find target entity for %s at %s\n", self->classname, vtos(self->s.origin)); + } + + self->teammaster->dmg = self->dmg; + self->think = turret_breach_think; + self->think (self); +} + +void SP_turret_breach (edict_t *self) +{ + self->solid = SOLID_BSP; + self->movetype = MOVETYPE_PUSH; + gi.setmodel (self, self->model); + + if (!self->speed) + self->speed = 50; + if (!self->dmg) + self->dmg = 10; + + if (!st.minpitch) + st.minpitch = -30; + if (!st.maxpitch) + st.maxpitch = 30; + if (!st.maxyaw) + st.maxyaw = 360; + + self->pos1[PITCH] = -1 * st.minpitch; + self->pos1[YAW] = st.minyaw; + self->pos2[PITCH] = -1 * st.maxpitch; + self->pos2[YAW] = st.maxyaw; + + self->ideal_yaw = self->s.angles[YAW]; + self->move_angles[YAW] = self->ideal_yaw; + + self->blocked = turret_blocked; + + self->think = turret_breach_finish_init; + self->nextthink = level.time + FRAMETIME; + gi.linkentity (self); +} + + +/*QUAKED turret_base (0 0 0) ? +This portion of the turret changes yaw only. +MUST be teamed with a turret_breach. +*/ + +void SP_turret_base (edict_t *self) +{ + self->solid = SOLID_BSP; + self->movetype = MOVETYPE_PUSH; + gi.setmodel (self, self->model); + self->blocked = turret_blocked; + gi.linkentity (self); +} + + +/*QUAKED turret_driver (1 .5 0) (-16 -16 -24) (16 16 32) +Must NOT be on the team with the rest of the turret parts. +Instead it must target the turret_breach. +*/ + +void infantry_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage); +void infantry_stand (edict_t *self); +void monster_use (edict_t *self, edict_t *other, edict_t *activator); + +void turret_driver_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + edict_t *ent; + + // level the gun + self->target_ent->move_angles[0] = 0; + + // remove the driver from the end of them team chain + for (ent = self->target_ent->teammaster; ent->teamchain != self; ent = ent->teamchain) + ; + ent->teamchain = NULL; + self->teammaster = NULL; + self->flags &= ~FL_TEAMSLAVE; + + self->target_ent->owner = NULL; + self->target_ent->teammaster->owner = NULL; + + infantry_die (self, inflictor, attacker, damage); +} + +qboolean FindTarget (edict_t *self); + +void turret_driver_think (edict_t *self) +{ + vec3_t target; + vec3_t dir; + float reaction_time; + + self->nextthink = level.time + FRAMETIME; + + if (self->enemy && (!self->enemy->inuse || self->enemy->health <= 0)) + self->enemy = NULL; + + if (!self->enemy) + { + if (!FindTarget (self)) + return; + self->monsterinfo.trail_time = level.time; + self->monsterinfo.aiflags &= ~AI_LOST_SIGHT; + } + else + { + if (visible (self, self->enemy)) + { + if (self->monsterinfo.aiflags & AI_LOST_SIGHT) + { + self->monsterinfo.trail_time = level.time; + self->monsterinfo.aiflags &= ~AI_LOST_SIGHT; + } + } + else + { + self->monsterinfo.aiflags |= AI_LOST_SIGHT; + return; + } + } + + // let the turret know where we want it to aim + VectorCopy (self->enemy->s.origin, target); + target[2] += self->enemy->viewheight; + VectorSubtract (target, self->target_ent->s.origin, dir); + vectoangles (dir, self->target_ent->move_angles); + + // decide if we should shoot + if (level.time < self->monsterinfo.attack_finished) + return; + + reaction_time = (3 - skill->value) * 1.0; + if ((level.time - self->monsterinfo.trail_time) < reaction_time) + return; + + self->monsterinfo.attack_finished = level.time + reaction_time + 1.0; + //FIXME how do we really want to pass this along? + self->target_ent->spawnflags |= 65536; +} + +void turret_driver_link (edict_t *self) +{ + vec3_t vec; + edict_t *ent; + + self->think = turret_driver_think; + self->nextthink = level.time + FRAMETIME; + + self->target_ent = G_PickTarget (self->target); + self->target_ent->owner = self; + self->target_ent->teammaster->owner = self; + VectorCopy (self->target_ent->s.angles, self->s.angles); + + vec[0] = self->target_ent->s.origin[0] - self->s.origin[0]; + vec[1] = self->target_ent->s.origin[1] - self->s.origin[1]; + vec[2] = 0; + self->move_origin[0] = VectorLength(vec); + + VectorSubtract (self->s.origin, self->target_ent->s.origin, vec); + vectoangles (vec, vec); + AnglesNormalize(vec); + self->move_origin[1] = vec[1]; + + self->move_origin[2] = self->s.origin[2] - self->target_ent->s.origin[2]; + + // add the driver to the end of them team chain + for (ent = self->target_ent->teammaster; ent->teamchain; ent = ent->teamchain) + ; + ent->teamchain = self; + self->teammaster = self->target_ent->teammaster; + self->flags |= FL_TEAMSLAVE; +} + +void SP_turret_driver (edict_t *self) +{ + if (deathmatch->value) + { + G_FreeEdict (self); + return; + } + + self->movetype = MOVETYPE_PUSH; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex("models/monsters/infantry/tris.md2"); + VectorSet (self->mins, -16, -16, -24); + VectorSet (self->maxs, 16, 16, 32); + + self->health = 100; + self->gib_health = 0; + self->mass = 200; + self->viewheight = 24; + + self->die = turret_driver_die; + self->monsterinfo.stand = infantry_stand; + + self->flags |= FL_NO_KNOCKBACK; + + level.total_monsters++; + + self->svflags |= SVF_MONSTER; + self->s.renderfx |= RF_FRAMELERP; + self->takedamage = DAMAGE_AIM; + self->use = monster_use; + self->clipmask = MASK_MONSTERSOLID; + VectorCopy (self->s.origin, self->s.old_origin); + self->monsterinfo.aiflags |= AI_STAND_GROUND|AI_DUCKED; + + if (st.item) + { + self->item = FindItemByClassname (st.item); + if (!self->item) + gi.dprintf("%s at %s has bad item: %s\n", self->classname, vtos(self->s.origin), st.item); + } + + self->think = turret_driver_link; + self->nextthink = level.time + FRAMETIME; + + gi.linkentity (self); +} + +//============ +// ROGUE + +// invisible turret drivers so we can have unmanned turrets. +// originally designed to shoot at func_trains and such, so they +// fire at the center of the bounding box, rather than the entity's +// origin. + +void turret_brain_think (edict_t *self) +{ + vec3_t target; + vec3_t dir; + vec3_t endpos; + float reaction_time; + trace_t trace; + + self->nextthink = level.time + FRAMETIME; + + if (self->enemy) + { + if(!self->enemy->inuse) + self->enemy = NULL; + else if(self->enemy->takedamage && self->enemy->health <= 0) + self->enemy = NULL; + } + + if (!self->enemy) + { + if (!FindTarget (self)) + return; + self->monsterinfo.trail_time = level.time; + self->monsterinfo.aiflags &= ~AI_LOST_SIGHT; + } + else + { + VectorAdd (self->enemy->absmax, self->enemy->absmin, endpos); + VectorScale (endpos, 0.5, endpos); + + trace = gi.trace (self->target_ent->s.origin, vec3_origin, vec3_origin, endpos, self->target_ent, MASK_SHOT); + if(trace.fraction == 1 || trace.ent == self->enemy) + { + if (self->monsterinfo.aiflags & AI_LOST_SIGHT) + { + self->monsterinfo.trail_time = level.time; + self->monsterinfo.aiflags &= ~AI_LOST_SIGHT; + } + } + else + { + self->monsterinfo.aiflags |= AI_LOST_SIGHT; + return; + } + } + + // let the turret know where we want it to aim + VectorCopy (endpos, target); + VectorSubtract (target, self->target_ent->s.origin, dir); + vectoangles (dir, self->target_ent->move_angles); + + // decide if we should shoot + if (level.time < self->monsterinfo.attack_finished) + return; + + if(self->delay) + reaction_time = self->delay; + else + reaction_time = (3 - skill->value) * 1.0; + if ((level.time - self->monsterinfo.trail_time) < reaction_time) + return; + + self->monsterinfo.attack_finished = level.time + reaction_time + 1.0; + //FIXME how do we really want to pass this along? + self->target_ent->spawnflags |= 65536; +} + +// ================= +// ================= +void turret_brain_link (edict_t *self) +{ + vec3_t vec; + edict_t *ent; + + if (self->killtarget) + { + self->enemy = G_PickTarget (self->killtarget); + } + + self->think = turret_brain_think; + self->nextthink = level.time + FRAMETIME; + + self->target_ent = G_PickTarget (self->target); + self->target_ent->owner = self; + self->target_ent->teammaster->owner = self; + VectorCopy (self->target_ent->s.angles, self->s.angles); + + vec[0] = self->target_ent->s.origin[0] - self->s.origin[0]; + vec[1] = self->target_ent->s.origin[1] - self->s.origin[1]; + vec[2] = 0; + self->move_origin[0] = VectorLength(vec); + + VectorSubtract (self->s.origin, self->target_ent->s.origin, vec); + vectoangles (vec, vec); + AnglesNormalize(vec); + self->move_origin[1] = vec[1]; + + self->move_origin[2] = self->s.origin[2] - self->target_ent->s.origin[2]; + + // add the driver to the end of them team chain + for (ent = self->target_ent->teammaster; ent->teamchain; ent = ent->teamchain) + ; + ent->teamchain = self; + self->teammaster = self->target_ent->teammaster; + self->flags |= FL_TEAMSLAVE; +} + +// ================= +// ================= +void turret_brain_deactivate (edict_t *self, edict_t *other, edict_t *activator) +{ + self->think = NULL; + self->nextthink = 0; +} + +// ================= +// ================= +void turret_brain_activate (edict_t *self, edict_t *other, edict_t *activator) +{ + if (!self->enemy) + { + self->enemy = activator; + } + + // wait at least 3 seconds to fire. + self->monsterinfo.attack_finished = level.time + 3; + self->use = turret_brain_deactivate; + + self->think = turret_brain_link; + self->nextthink = level.time + FRAMETIME; +} + +/*QUAKED turret_invisible_brain (1 .5 0) (-16 -16 -16) (16 16 16) +Invisible brain to drive the turret. + +Does not search for targets. If targeted, can only be turned on once +and then off once. After that they are completely disabled. + +"delay" the delay between firing (default ramps for skill level) +"Target" the turret breach +"Killtarget" the item you want it to attack. +Target the brain if you want it activated later, instead of immediately. It will wait 3 seconds +before firing to acquire the target. +*/ +void SP_turret_invisible_brain (edict_t *self) +{ + if (!self->killtarget) + { + gi.dprintf("turret_invisible_brain with no killtarget!\n"); + G_FreeEdict (self); + return; + } + if (!self->target) + { + gi.dprintf("turret_invisible_brain with no target!\n"); + G_FreeEdict (self); + return; + } + + if (self->targetname) + { + self->use = turret_brain_activate; + } + else + { + self->think = turret_brain_link; + self->nextthink = level.time + FRAMETIME; + } + + self->movetype = MOVETYPE_PUSH; + gi.linkentity (self); +} + +// ROGUE +//============ diff --git a/original/rogue/g_utils.c b/original/rogue/g_utils.c new file mode 100644 index 0000000..ba34a4c --- /dev/null +++ b/original/rogue/g_utils.c @@ -0,0 +1,714 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// g_utils.c -- misc utility functions for game module + +#include "g_local.h" + + +void G_ProjectSource (vec3_t point, vec3_t distance, vec3_t forward, vec3_t right, vec3_t result) +{ + result[0] = point[0] + forward[0] * distance[0] + right[0] * distance[1]; + result[1] = point[1] + forward[1] * distance[0] + right[1] * distance[1]; + result[2] = point[2] + forward[2] * distance[0] + right[2] * distance[1] + distance[2]; +} + +void G_ProjectSource2 (vec3_t point, vec3_t distance, vec3_t forward, vec3_t right, vec3_t up, vec3_t result) +{ + result[0] = point[0] + forward[0] * distance[0] + right[0] * distance[1] + up[0] * distance[2]; + result[1] = point[1] + forward[1] * distance[0] + right[1] * distance[1] + up[1] * distance[2]; + result[2] = point[2] + forward[2] * distance[0] + right[2] * distance[1] + up[2] * distance[2]; +} + +/* +============= +G_Find + +Searches all active entities for the next one that holds +the matching string at fieldofs (use the FOFS() macro) in the structure. + +Searches beginning at the edict after from, or the beginning if NULL +NULL will be returned if the end of the list is reached. + +============= +*/ +edict_t *G_Find (edict_t *from, int fieldofs, char *match) +{ + char *s; + + if (!from) + from = g_edicts; + else + from++; + + for ( ; from < &g_edicts[globals.num_edicts] ; from++) + { + if (!from->inuse) + continue; + s = *(char **) ((byte *)from + fieldofs); + if (!s) + continue; + if (!Q_stricmp (s, match)) + return from; + } + + return NULL; +} + + +/* +================= +findradius + +Returns entities that have origins within a spherical area + +findradius (origin, radius) +================= +*/ +edict_t *findradius (edict_t *from, vec3_t org, float rad) +{ + vec3_t eorg; + int j; + + if (!from) + from = g_edicts; + else + from++; + for ( ; from < &g_edicts[globals.num_edicts]; from++) + { + if (!from->inuse) + continue; + if (from->solid == SOLID_NOT) + continue; + for (j=0 ; j<3 ; j++) + eorg[j] = org[j] - (from->s.origin[j] + (from->mins[j] + from->maxs[j])*0.5); + if (VectorLength(eorg) > rad) + continue; + return from; + } + + return NULL; +} + +/* +================= +findradius2 + +Returns entities that have origins within a spherical area + +ROGUE - tweaks for performance for tesla specific code +only returns entities that can be damaged +only returns entities that are SVF_DAMAGEABLE + +findradius2 (origin, radius) +================= +*/ +edict_t *findradius2 (edict_t *from, vec3_t org, float rad) +{ + // rad must be positive + vec3_t eorg; + int j; + + if (!from) + from = g_edicts; + else + from++; + for ( ; from < &g_edicts[globals.num_edicts]; from++) + { + if (!from->inuse) + continue; + if (from->solid == SOLID_NOT) + continue; + if (!from->takedamage) + continue; + if (!(from->svflags & SVF_DAMAGEABLE)) + continue; + for (j=0 ; j<3 ; j++) + eorg[j] = org[j] - (from->s.origin[j] + (from->mins[j] + from->maxs[j])*0.5); + if (VectorLength(eorg) > rad) + continue; + return from; + } + + return NULL; +} + + +/* +============= +G_PickTarget + +Searches all active entities for the next one that holds +the matching string at fieldofs (use the FOFS() macro) in the structure. + +Searches beginning at the edict after from, or the beginning if NULL +NULL will be returned if the end of the list is reached. + +============= +*/ +#define MAXCHOICES 8 + +edict_t *G_PickTarget (char *targetname) +{ + edict_t *ent = NULL; + int num_choices = 0; + edict_t *choice[MAXCHOICES]; + + if (!targetname) + { + gi.dprintf("G_PickTarget called with NULL targetname\n"); + return NULL; + } + + while(1) + { + ent = G_Find (ent, FOFS(targetname), targetname); + if (!ent) + break; + choice[num_choices++] = ent; + if (num_choices == MAXCHOICES) + break; + } + + if (!num_choices) + { + gi.dprintf("G_PickTarget: target %s not found\n", targetname); + return NULL; + } + + return choice[rand() % num_choices]; +} + + + +void Think_Delay (edict_t *ent) +{ + G_UseTargets (ent, ent->activator); + G_FreeEdict (ent); +} + +/* +============================== +G_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. + +Search for (string)targetname in all entities that +match (string)self.target and call their .use function + +============================== +*/ +void G_UseTargets (edict_t *ent, edict_t *activator) +{ + edict_t *t; + edict_t *master; + qboolean done = false; + +// +// check for a delay +// + if (ent->delay) + { + // create a temp object to fire at a later time + t = G_Spawn(); + t->classname = "DelayedUse"; + t->nextthink = level.time + ent->delay; + t->think = Think_Delay; + t->activator = activator; + if (!activator) + gi.dprintf ("Think_Delay with no activator\n"); + t->message = ent->message; + t->target = ent->target; + t->killtarget = ent->killtarget; + return; + } + + +// +// print the message +// + if ((ent->message) && !(activator->svflags & SVF_MONSTER)) + { + gi.centerprintf (activator, "%s", ent->message); + if (ent->noise_index) + gi.sound (activator, CHAN_AUTO, ent->noise_index, 1, ATTN_NORM, 0); + else + gi.sound (activator, CHAN_AUTO, gi.soundindex ("misc/talk1.wav"), 1, ATTN_NORM, 0); + } + +// +// kill killtargets +// + if (ent->killtarget) + { + t = NULL; + while ((t = G_Find (t, FOFS(targetname), ent->killtarget))) + { + // PMM - if this entity is part of a train, cleanly remove it + if (t->flags & FL_TEAMSLAVE) + { +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("Removing %s from train!\n", t->classname); + + if (t->teammaster) + { + master = t->teammaster; + while (!done) + { + if (master->teamchain == t) + { + master->teamchain = t->teamchain; + done = true; + } + master = master->teamchain; + if (!master) + { +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("Couldn't find myself in master's chain, ignoring!\n"); + } + } + } + else + { +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("No master to free myself from, ignoring!\n"); + } + } + // PMM + G_FreeEdict (t); + if (!ent->inuse) + { + gi.dprintf("entity was removed while using killtargets\n"); + return; + } + } + } + +// +// fire targets +// + if (ent->target) + { + t = NULL; + while ((t = G_Find (t, FOFS(targetname), ent->target))) + { + // doors fire area portals in a specific way + if (!Q_stricmp(t->classname, "func_areaportal") && + (!Q_stricmp(ent->classname, "func_door") || !Q_stricmp(ent->classname, "func_door_rotating"))) + continue; + + if (t == ent) + { + gi.dprintf ("WARNING: Entity used itself.\n"); + } + else + { + if (t->use) + t->use (t, ent, activator); + } + if (!ent->inuse) + { + gi.dprintf("entity was removed while using targets\n"); + return; + } + } + } +} + + +/* +============= +TempVector + +This is just a convenience function +for making temporary vectors for function calls +============= +*/ +float *tv (float x, float y, float z) +{ + static int index; + static vec3_t vecs[8]; + float *v; + + // use an array so that multiple tempvectors won't collide + // for a while + v = vecs[index]; + index = (index + 1)&7; + + v[0] = x; + v[1] = y; + v[2] = z; + + return v; +} + + +/* +============= +VectorToString + +This is just a convenience function +for printing vectors +============= +*/ +char *vtos (vec3_t v) +{ + static int index; + static char str[8][32]; + char *s; + + // use an array so that multiple vtos won't collide + s = str[index]; + index = (index + 1)&7; + + Com_sprintf (s, 32, "(%i %i %i)", (int)v[0], (int)v[1], (int)v[2]); + + return s; +} + + +vec3_t VEC_UP = {0, -1, 0}; +vec3_t MOVEDIR_UP = {0, 0, 1}; +vec3_t VEC_DOWN = {0, -2, 0}; +vec3_t MOVEDIR_DOWN = {0, 0, -1}; + +void G_SetMovedir (vec3_t angles, vec3_t movedir) +{ + if (VectorCompare (angles, VEC_UP)) + { + VectorCopy (MOVEDIR_UP, movedir); + } + else if (VectorCompare (angles, VEC_DOWN)) + { + VectorCopy (MOVEDIR_DOWN, movedir); + } + else + { + AngleVectors (angles, movedir, NULL, NULL); + } + + VectorClear (angles); +} + + +float vectoyaw (vec3_t vec) +{ + float yaw; + + // PMM - fixed to correct for pitch of 0 + if (/*vec[YAW] == 0 &&*/ vec[PITCH] == 0) + if (vec[YAW] == 0) + yaw = 0; + else if (vec[YAW] > 0) + yaw = 90; + else + yaw = 270; + else + { + yaw = (int) (atan2(vec[YAW], vec[PITCH]) * 180 / M_PI); + if (yaw < 0) + yaw += 360; + } + + return yaw; +} + +float vectoyaw2 (vec3_t vec) +{ + float yaw; + + // PMM - fixed to correct for pitch of 0 + if (/*vec[YAW] == 0 &&*/ vec[PITCH] == 0) + if (vec[YAW] == 0) + yaw = 0; + else if (vec[YAW] > 0) + yaw = 90; + else + yaw = 270; + else + { + yaw = (atan2(vec[YAW], vec[PITCH]) * 180 / M_PI); + if (yaw < 0) + yaw += 360; + } + + return yaw; +} + + +void vectoangles (vec3_t value1, vec3_t angles) +{ + float forward; + float yaw, pitch; + + if (value1[1] == 0 && value1[0] == 0) + { + yaw = 0; + if (value1[2] > 0) + pitch = 90; + else + pitch = 270; + } + else + { + // PMM - fixed to correct for pitch of 0 + if (value1[0]) + yaw = (int) (atan2(value1[1], value1[0]) * 180 / M_PI); + else if (value1[1] > 0) + yaw = 90; + else + yaw = 270; + if (yaw < 0) + yaw += 360; + + forward = sqrt (value1[0]*value1[0] + value1[1]*value1[1]); + pitch = (int) (atan2(value1[2], forward) * 180 / M_PI); + if (pitch < 0) + pitch += 360; + } + + angles[PITCH] = -pitch; + angles[YAW] = yaw; + angles[ROLL] = 0; +} + +void vectoangles2 (vec3_t value1, vec3_t angles) +{ + float forward; + float yaw, pitch; + + if (value1[1] == 0 && value1[0] == 0) + { + yaw = 0; + if (value1[2] > 0) + pitch = 90; + else + pitch = 270; + } + else + { + // PMM - fixed to correct for pitch of 0 + if (value1[0]) + yaw = (atan2(value1[1], value1[0]) * 180 / M_PI); + else if (value1[1] > 0) + yaw = 90; + else + yaw = 270; + + if (yaw < 0) + yaw += 360; + + forward = sqrt (value1[0]*value1[0] + value1[1]*value1[1]); + pitch = (atan2(value1[2], forward) * 180 / M_PI); + if (pitch < 0) + pitch += 360; + } + + angles[PITCH] = -pitch; + angles[YAW] = yaw; + angles[ROLL] = 0; +} + +char *G_CopyString (char *in) +{ + char *out; + + out = gi.TagMalloc (strlen(in)+1, TAG_LEVEL); + strcpy (out, in); + return out; +} + + +void G_InitEdict (edict_t *e) +{ + // ROGUE + // FIXME - + // this fixes a bug somewhere that is settling "nextthink" for an entity that has + // already been released. nextthink is being set to FRAMETIME after level.time, + // since freetime = nextthink - 0.1 + if (e->nextthink) + { +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("G_SPAWN: Fixed bad nextthink time\n"); + e->nextthink = 0; + } + // ROGUE + + e->inuse = true; + e->classname = "noclass"; + e->gravity = 1.0; + e->s.number = e - g_edicts; + +//PGM - do this before calling the spawn function so it can be overridden. +#ifdef ROGUE_GRAVITY + e->gravityVector[0] = 0.0; + e->gravityVector[1] = 0.0; + e->gravityVector[2] = -1.0; +#endif +//PGM +} + +/* +================= +G_Spawn + +Either finds a free edict, or allocates a new one. +Try to avoid reusing an entity that was recently freed, because it +can cause the client to think the entity morphed into something else +instead of being removed and recreated, which can cause interpolated +angles and bad trails. +================= +*/ +edict_t *G_Spawn (void) +{ + int i; + edict_t *e; + + e = &g_edicts[(int)maxclients->value+1]; + for ( i=maxclients->value+1 ; iinuse && ( e->freetime < 2 || level.time - e->freetime > 0.5 ) ) + { + G_InitEdict (e); + return e; + } + } + + if (i == game.maxentities) + gi.error ("ED_Alloc: no free edicts"); + + globals.num_edicts++; + G_InitEdict (e); + return e; +} + +/* +================= +G_FreeEdict + +Marks the edict as free +================= +*/ +void G_FreeEdict (edict_t *ed) +{ + gi.unlinkentity (ed); // unlink from world + + if ((ed - g_edicts) <= (maxclients->value + BODY_QUEUE_SIZE)) + { +// gi.dprintf("tried to free special edict\n"); + return; + } + + memset (ed, 0, sizeof(*ed)); + ed->classname = "freed"; + ed->freetime = level.time; + ed->inuse = false; +} + + +/* +============ +G_TouchTriggers + +============ +*/ +void G_TouchTriggers (edict_t *ent) +{ + int i, num; + edict_t *touch[MAX_EDICTS], *hit; + + // dead things don't activate triggers! + if ((ent->client || (ent->svflags & SVF_MONSTER)) && (ent->health <= 0)) + return; + + num = gi.BoxEdicts (ent->absmin, ent->absmax, touch + , MAX_EDICTS, AREA_TRIGGERS); + + // be careful, it is possible to have an entity in this + // list removed before we get to it (killtriggered) + for (i=0 ; iinuse) + continue; + if (!hit->touch) + continue; + hit->touch (hit, ent, NULL, NULL); + } +} + +/* +============ +G_TouchSolids + +Call after linking a new trigger in during gameplay +to force all entities it covers to immediately touch it +============ +*/ +void G_TouchSolids (edict_t *ent) +{ + int i, num; + edict_t *touch[MAX_EDICTS], *hit; + + num = gi.BoxEdicts (ent->absmin, ent->absmax, touch + , MAX_EDICTS, AREA_SOLID); + + // be careful, it is possible to have an entity in this + // list removed before we get to it (killtriggered) + for (i=0 ; iinuse) + continue; + if (ent->touch) + ent->touch (hit, ent, NULL, NULL); + if (!ent->inuse) + break; + } +} + + + + +/* +============================================================================== + +Kill box + +============================================================================== +*/ + +/* +================= +KillBox + +Kills all entities that would touch the proposed new positioning +of ent. Ent should be unlinked before calling this! +================= +*/ +qboolean KillBox (edict_t *ent) +{ + trace_t tr; + + while (1) + { + tr = gi.trace (ent->s.origin, ent->mins, ent->maxs, ent->s.origin, NULL, MASK_PLAYERSOLID); + if (!tr.ent) + break; + + // nail it + T_Damage (tr.ent, ent, ent, vec3_origin, ent->s.origin, vec3_origin, 100000, 0, DAMAGE_NO_PROTECTION, MOD_TELEFRAG); + + // if we didn't kill it, fail + if (tr.ent->solid) + return false; + } + + return true; // all clear +} diff --git a/original/rogue/g_weapon.c b/original/rogue/g_weapon.c new file mode 100644 index 0000000..a295ae8 --- /dev/null +++ b/original/rogue/g_weapon.c @@ -0,0 +1,911 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +#include "g_local.h" + + +/* +================= +check_dodge + +This is a support routine used when a client is firing +a non-instant attack weapon. It checks to see if a +monster's dodge function should be called. +================= +*/ +//static void check_dodge (edict_t *self, vec3_t start, vec3_t dir, int speed) +void check_dodge (edict_t *self, vec3_t start, vec3_t dir, int speed) //PGM +{ + vec3_t end; + vec3_t v; + trace_t tr; + float eta; + + // easy mode only ducks one quarter the time + if (skill->value == 0) + { + if (random() > 0.25) + return; + } + VectorMA (start, 8192, dir, end); + tr = gi.trace (start, NULL, NULL, end, self, MASK_SHOT); + if ((tr.ent) && (tr.ent->svflags & SVF_MONSTER) && (tr.ent->health > 0) && (tr.ent->monsterinfo.dodge) && infront(tr.ent, self)) + { + VectorSubtract (tr.endpos, start, v); + eta = (VectorLength(v) - tr.ent->maxs[0]) / speed; +// tr.ent->monsterinfo.dodge (tr.ent, self, eta); + tr.ent->monsterinfo.dodge (tr.ent, self, eta, &tr); + } +} + + +/* +================= +fire_hit + +Used for all impact (hit/punch/slash) attacks +================= +*/ +qboolean fire_hit (edict_t *self, vec3_t aim, int damage, int kick) +{ + trace_t tr; + vec3_t forward, right, up; + vec3_t v; + vec3_t point; + float range; + vec3_t dir; + + //see if enemy is in range + VectorSubtract (self->enemy->s.origin, self->s.origin, dir); + range = VectorLength(dir); + if (range > aim[0]) + return false; + + if (aim[1] > self->mins[0] && aim[1] < self->maxs[0]) + { + // the hit is straight on so back the range up to the edge of their bbox + range -= self->enemy->maxs[0]; + } + else + { + // this is a side hit so adjust the "right" value out to the edge of their bbox + if (aim[1] < 0) + aim[1] = self->enemy->mins[0]; + else + aim[1] = self->enemy->maxs[0]; + } + + VectorMA (self->s.origin, range, dir, point); + + tr = gi.trace (self->s.origin, NULL, NULL, point, self, MASK_SHOT); + if (tr.fraction < 1) + { + if (!tr.ent->takedamage) + return false; + // if it will hit any client/monster then hit the one we wanted to hit + if ((tr.ent->svflags & SVF_MONSTER) || (tr.ent->client)) + tr.ent = self->enemy; + } + + AngleVectors(self->s.angles, forward, right, up); + VectorMA (self->s.origin, range, forward, point); + VectorMA (point, aim[1], right, point); + VectorMA (point, aim[2], up, point); + VectorSubtract (point, self->enemy->s.origin, dir); + + // do the damage + T_Damage (tr.ent, self, self, dir, point, vec3_origin, damage, kick/2, DAMAGE_NO_KNOCKBACK, MOD_HIT); + + if (!(tr.ent->svflags & SVF_MONSTER) && (!tr.ent->client)) + return false; + + // do our special form of knockback here + VectorMA (self->enemy->absmin, 0.5, self->enemy->size, v); + VectorSubtract (v, point, v); + VectorNormalize (v); + VectorMA (self->enemy->velocity, kick, v, self->enemy->velocity); + if (self->enemy->velocity[2] > 0) + self->enemy->groundentity = NULL; + return true; +} + + +/* +================= +fire_lead + +This is an internal support routine used for bullet/pellet based weapons. +================= +*/ +static void fire_lead (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int te_impact, int hspread, int vspread, int mod) +{ + trace_t tr; + vec3_t dir; + vec3_t forward, right, up; + vec3_t end; + float r; + float u; + vec3_t water_start; + qboolean water = false; + int content_mask = MASK_SHOT | MASK_WATER; + + tr = gi.trace (self->s.origin, NULL, NULL, start, self, MASK_SHOT); + if (!(tr.fraction < 1.0)) + { + vectoangles (aimdir, dir); + AngleVectors (dir, forward, right, up); + + r = crandom()*hspread; + u = crandom()*vspread; + VectorMA (start, 8192, forward, end); + VectorMA (end, r, right, end); + VectorMA (end, u, up, end); + + if (gi.pointcontents (start) & MASK_WATER) + { + water = true; + VectorCopy (start, water_start); + content_mask &= ~MASK_WATER; + } + + tr = gi.trace (start, NULL, NULL, end, self, content_mask); + + // see if we hit water + if (tr.contents & MASK_WATER) + { + int color; + + water = true; + VectorCopy (tr.endpos, water_start); + + if (!VectorCompare (start, tr.endpos)) + { + if (tr.contents & CONTENTS_WATER) + { + if (strcmp(tr.surface->name, "*brwater") == 0) + color = SPLASH_BROWN_WATER; + else + color = SPLASH_BLUE_WATER; + } + else if (tr.contents & CONTENTS_SLIME) + color = SPLASH_SLIME; + else if (tr.contents & CONTENTS_LAVA) + color = SPLASH_LAVA; + else + color = SPLASH_UNKNOWN; + + if (color != SPLASH_UNKNOWN) + { + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_SPLASH); + gi.WriteByte (8); + gi.WritePosition (tr.endpos); + gi.WriteDir (tr.plane.normal); + gi.WriteByte (color); + gi.multicast (tr.endpos, MULTICAST_PVS); + } + + // change bullet's course when it enters water + VectorSubtract (end, start, dir); + vectoangles (dir, dir); + AngleVectors (dir, forward, right, up); + r = crandom()*hspread*2; + u = crandom()*vspread*2; + VectorMA (water_start, 8192, forward, end); + VectorMA (end, r, right, end); + VectorMA (end, u, up, end); + } + + // re-trace ignoring water this time + tr = gi.trace (water_start, NULL, NULL, end, self, MASK_SHOT); + } + } + + // send gun puff / flash + if (!((tr.surface) && (tr.surface->flags & SURF_SKY))) + { + if (tr.fraction < 1.0) + { + if (tr.ent->takedamage) + { + T_Damage (tr.ent, self, self, aimdir, tr.endpos, tr.plane.normal, damage, kick, DAMAGE_BULLET, mod); + } + else + { + if (strncmp (tr.surface->name, "sky", 3) != 0) + { + gi.WriteByte (svc_temp_entity); + gi.WriteByte (te_impact); + gi.WritePosition (tr.endpos); + gi.WriteDir (tr.plane.normal); + gi.multicast (tr.endpos, MULTICAST_PVS); + + if (self->client) + PlayerNoise(self, tr.endpos, PNOISE_IMPACT); + } + } + } + } + + // if went through water, determine where the end and make a bubble trail + if (water) + { + vec3_t pos; + + VectorSubtract (tr.endpos, water_start, dir); + VectorNormalize (dir); + VectorMA (tr.endpos, -2, dir, pos); + if (gi.pointcontents (pos) & MASK_WATER) + VectorCopy (pos, tr.endpos); + else + tr = gi.trace (pos, NULL, NULL, water_start, tr.ent, MASK_WATER); + + VectorAdd (water_start, tr.endpos, pos); + VectorScale (pos, 0.5, pos); + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_BUBBLETRAIL); + gi.WritePosition (water_start); + gi.WritePosition (tr.endpos); + gi.multicast (pos, MULTICAST_PVS); + } +} + + +/* +================= +fire_bullet + +Fires a single round. Used for machinegun and chaingun. Would be fine for +pistols, rifles, etc.... +================= +*/ +void fire_bullet (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int hspread, int vspread, int mod) +{ + fire_lead (self, start, aimdir, damage, kick, TE_GUNSHOT, hspread, vspread, mod); +} + + +/* +================= +fire_shotgun + +Shoots shotgun pellets. Used by shotgun and super shotgun. +================= +*/ +void fire_shotgun (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int hspread, int vspread, int count, int mod) +{ + int i; + + for (i = 0; i < count; i++) + fire_lead (self, start, aimdir, damage, kick, TE_SHOTGUN, hspread, vspread, mod); +} + + +/* +================= +fire_blaster + +Fires a single blaster bolt. Used by the blaster and hyper blaster. +================= +*/ +void blaster_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + int mod; + + if (other == self->owner) + return; + + if (surf && (surf->flags & SURF_SKY)) + { + G_FreeEdict (self); + return; + } + + // PMM - crash prevention + if (self->owner && self->owner->client) + PlayerNoise(self->owner, self->s.origin, PNOISE_IMPACT); + + if (other->takedamage) + { + if (self->spawnflags & 1) + mod = MOD_HYPERBLASTER; + else + mod = MOD_BLASTER; + T_Damage (other, self, self->owner, self->velocity, self->s.origin, plane->normal, self->dmg, 1, DAMAGE_ENERGY, mod); + } + else + { + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_BLASTER); + gi.WritePosition (self->s.origin); + if (!plane) + gi.WriteDir (vec3_origin); + else + gi.WriteDir (plane->normal); + gi.multicast (self->s.origin, MULTICAST_PVS); + } + + G_FreeEdict (self); +} + +void fire_blaster (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int effect, qboolean hyper) +{ + edict_t *bolt; + trace_t tr; + + VectorNormalize (dir); + + bolt = G_Spawn(); + bolt->svflags = SVF_DEADMONSTER; + // yes, I know it looks weird that projectiles are deadmonsters + // what this means is that when prediction is used against the object + // (blaster/hyperblaster shots), the player won't be solid clipped against + // the object. Right now trying to run into a firing hyperblaster + // is very jerky since you are predicted 'against' the shots. + VectorCopy (start, bolt->s.origin); + VectorCopy (start, bolt->s.old_origin); + vectoangles (dir, bolt->s.angles); + VectorScale (dir, speed, bolt->velocity); + bolt->movetype = MOVETYPE_FLYMISSILE; + bolt->clipmask = MASK_SHOT; + bolt->solid = SOLID_BBOX; + bolt->s.effects |= effect; + VectorClear (bolt->mins); + VectorClear (bolt->maxs); + bolt->s.modelindex = gi.modelindex ("models/objects/laser/tris.md2"); + bolt->s.sound = gi.soundindex ("misc/lasfly.wav"); + bolt->owner = self; + bolt->touch = blaster_touch; + bolt->nextthink = level.time + 2; + bolt->think = G_FreeEdict; + bolt->dmg = damage; + bolt->classname = "bolt"; + if (hyper) + bolt->spawnflags = 1; + gi.linkentity (bolt); + + if (self->client) + check_dodge (self, bolt->s.origin, dir, speed); + + tr = gi.trace (self->s.origin, NULL, NULL, bolt->s.origin, bolt, MASK_SHOT); + if (tr.fraction < 1.0) + { + VectorMA (bolt->s.origin, -10, dir, bolt->s.origin); + bolt->touch (bolt, tr.ent, NULL, NULL); + } +} + + +/* +================= +fire_grenade +================= +*/ +//static void Grenade_Explode (edict_t *ent) +void Grenade_Explode (edict_t *ent) +{ + vec3_t origin; + int mod; + + if (ent->owner->client) + PlayerNoise(ent->owner, ent->s.origin, PNOISE_IMPACT); + + //FIXME: if we are onground then raise our Z just a bit since we are a point? + if (ent->enemy) + { + float points; + vec3_t v; + vec3_t dir; + + VectorAdd (ent->enemy->mins, ent->enemy->maxs, v); + VectorMA (ent->enemy->s.origin, 0.5, v, v); + VectorSubtract (ent->s.origin, v, v); + points = ent->dmg - 0.5 * VectorLength (v); + VectorSubtract (ent->enemy->s.origin, ent->s.origin, dir); + if (ent->spawnflags & 1) + mod = MOD_HANDGRENADE; + else + mod = MOD_GRENADE; + T_Damage (ent->enemy, ent, ent->owner, dir, ent->s.origin, vec3_origin, (int)points, (int)points, DAMAGE_RADIUS, mod); + } + + if (ent->spawnflags & 2) + mod = MOD_HELD_GRENADE; + else if (ent->spawnflags & 1) + mod = MOD_HG_SPLASH; + else + mod = MOD_G_SPLASH; + T_RadiusDamage(ent, ent->owner, ent->dmg, ent->enemy, ent->dmg_radius, mod); + + VectorMA (ent->s.origin, -0.02, ent->velocity, origin); + gi.WriteByte (svc_temp_entity); + if (ent->waterlevel) + { + if (ent->groundentity) + gi.WriteByte (TE_GRENADE_EXPLOSION_WATER); + else + gi.WriteByte (TE_ROCKET_EXPLOSION_WATER); + } + else + { + if (ent->groundentity) + gi.WriteByte (TE_GRENADE_EXPLOSION); + else + gi.WriteByte (TE_ROCKET_EXPLOSION); + } + gi.WritePosition (origin); + gi.multicast (ent->s.origin, MULTICAST_PHS); + + G_FreeEdict (ent); +} + +static void Grenade_Touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (other == ent->owner) + return; + + if (surf && (surf->flags & SURF_SKY)) + { + G_FreeEdict (ent); + return; + } + + if (!other->takedamage) + { + if (ent->spawnflags & 1) + { + if (random() > 0.5) + gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/hgrenb1a.wav"), 1, ATTN_NORM, 0); + else + gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/hgrenb2a.wav"), 1, ATTN_NORM, 0); + } + else + { + gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/grenlb1b.wav"), 1, ATTN_NORM, 0); + } + return; + } + + ent->enemy = other; + Grenade_Explode (ent); +} + +void fire_grenade (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, float timer, float damage_radius) +{ + edict_t *grenade; + vec3_t dir; + vec3_t forward, right, up; + + vectoangles (aimdir, dir); + AngleVectors (dir, forward, right, up); + + grenade = G_Spawn(); + VectorCopy (start, grenade->s.origin); + VectorScale (aimdir, speed, grenade->velocity); + VectorMA (grenade->velocity, 200 + crandom() * 10.0, up, grenade->velocity); + VectorMA (grenade->velocity, crandom() * 10.0, right, grenade->velocity); + VectorSet (grenade->avelocity, 300, 300, 300); + grenade->movetype = MOVETYPE_BOUNCE; + grenade->clipmask = MASK_SHOT; + grenade->solid = SOLID_BBOX; + grenade->s.effects |= EF_GRENADE; + VectorClear (grenade->mins); + VectorClear (grenade->maxs); + grenade->s.modelindex = gi.modelindex ("models/objects/grenade/tris.md2"); + grenade->owner = self; + grenade->touch = Grenade_Touch; + grenade->nextthink = level.time + timer; + grenade->think = Grenade_Explode; + grenade->dmg = damage; + grenade->dmg_radius = damage_radius; + grenade->classname = "grenade"; + + gi.linkentity (grenade); +} + +void fire_grenade2 (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, float timer, float damage_radius, qboolean held) +{ + edict_t *grenade; + vec3_t dir; + vec3_t forward, right, up; + + vectoangles (aimdir, dir); + AngleVectors (dir, forward, right, up); + + grenade = G_Spawn(); + VectorCopy (start, grenade->s.origin); + VectorScale (aimdir, speed, grenade->velocity); + VectorMA (grenade->velocity, 200 + crandom() * 10.0, up, grenade->velocity); + VectorMA (grenade->velocity, crandom() * 10.0, right, grenade->velocity); + VectorSet (grenade->avelocity, 300, 300, 300); + grenade->movetype = MOVETYPE_BOUNCE; + grenade->clipmask = MASK_SHOT; + grenade->solid = SOLID_BBOX; + grenade->s.effects |= EF_GRENADE; + VectorClear (grenade->mins); + VectorClear (grenade->maxs); + grenade->s.modelindex = gi.modelindex ("models/objects/grenade2/tris.md2"); + grenade->owner = self; + grenade->touch = Grenade_Touch; + grenade->nextthink = level.time + timer; + grenade->think = Grenade_Explode; + grenade->dmg = damage; + grenade->dmg_radius = damage_radius; + grenade->classname = "hgrenade"; + if (held) + grenade->spawnflags = 3; + else + grenade->spawnflags = 1; + grenade->s.sound = gi.soundindex("weapons/hgrenc1b.wav"); + + if (timer <= 0.0) + Grenade_Explode (grenade); + else + { + gi.sound (self, CHAN_WEAPON, gi.soundindex ("weapons/hgrent1a.wav"), 1, ATTN_NORM, 0); + gi.linkentity (grenade); + } +} + + +/* +================= +fire_rocket +================= +*/ +void rocket_touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + vec3_t origin; + int n; + + if (other == ent->owner) + return; + + if (surf && (surf->flags & SURF_SKY)) + { + G_FreeEdict (ent); + return; + } + + if (ent->owner->client) + PlayerNoise(ent->owner, ent->s.origin, PNOISE_IMPACT); + + // calculate position for the explosion entity + VectorMA (ent->s.origin, -0.02, ent->velocity, origin); + + if (other->takedamage) + { + T_Damage (other, ent, ent->owner, ent->velocity, ent->s.origin, plane->normal, ent->dmg, 0, 0, MOD_ROCKET); + } + else + { + // don't throw any debris in net games + if (!deathmatch->value && !coop->value) + { + if ((surf) && !(surf->flags & (SURF_WARP|SURF_TRANS33|SURF_TRANS66|SURF_FLOWING))) + { + n = rand() % 5; + while(n--) + ThrowDebris (ent, "models/objects/debris2/tris.md2", 2, ent->s.origin); + } + } + } + + T_RadiusDamage(ent, ent->owner, ent->radius_dmg, other, ent->dmg_radius, MOD_R_SPLASH); + + gi.WriteByte (svc_temp_entity); + if (ent->waterlevel) + gi.WriteByte (TE_ROCKET_EXPLOSION_WATER); + else + gi.WriteByte (TE_ROCKET_EXPLOSION); + gi.WritePosition (origin); + gi.multicast (ent->s.origin, MULTICAST_PHS); + + G_FreeEdict (ent); +} + +void fire_rocket (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, float damage_radius, int radius_damage) +{ + edict_t *rocket; + + rocket = G_Spawn(); + VectorCopy (start, rocket->s.origin); + VectorCopy (dir, rocket->movedir); + vectoangles (dir, rocket->s.angles); + VectorScale (dir, speed, rocket->velocity); + rocket->movetype = MOVETYPE_FLYMISSILE; + rocket->clipmask = MASK_SHOT; + rocket->solid = SOLID_BBOX; + rocket->s.effects |= EF_ROCKET; + VectorClear (rocket->mins); + VectorClear (rocket->maxs); + rocket->s.modelindex = gi.modelindex ("models/objects/rocket/tris.md2"); + rocket->owner = self; + rocket->touch = rocket_touch; + rocket->nextthink = level.time + 8000/speed; + rocket->think = G_FreeEdict; + rocket->dmg = damage; + rocket->radius_dmg = radius_damage; + rocket->dmg_radius = damage_radius; + rocket->s.sound = gi.soundindex ("weapons/rockfly.wav"); + rocket->classname = "rocket"; + + if (self->client) + check_dodge (self, rocket->s.origin, dir, speed); + + gi.linkentity (rocket); +} + + +/* +================= +fire_rail +================= +*/ +void fire_rail (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick) +{ + vec3_t from; + vec3_t end; + trace_t tr; + edict_t *ignore; + int mask; + qboolean water; + + VectorMA (start, 8192, aimdir, end); + VectorCopy (start, from); + ignore = self; + water = false; + mask = MASK_SHOT|CONTENTS_SLIME|CONTENTS_LAVA; + while (ignore) + { + tr = gi.trace (from, NULL, NULL, end, ignore, mask); + + if (tr.contents & (CONTENTS_SLIME|CONTENTS_LAVA)) + { + mask &= ~(CONTENTS_SLIME|CONTENTS_LAVA); + water = true; + } + else + { + //ZOID--added so rail goes through SOLID_BBOX entities (gibs, etc) + if ((tr.ent->svflags & SVF_MONSTER) || (tr.ent->client) || + (tr.ent->svflags & SVF_DAMAGEABLE) || + (tr.ent->solid == SOLID_BBOX)) + ignore = tr.ent; + else + ignore = NULL; + + if ((tr.ent != self) && (tr.ent->takedamage)) + T_Damage (tr.ent, self, self, aimdir, tr.endpos, tr.plane.normal, damage, kick, 0, MOD_RAILGUN); + } + + VectorCopy (tr.endpos, from); + } + + // send gun puff / flash + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_RAILTRAIL); + gi.WritePosition (start); + gi.WritePosition (tr.endpos); + gi.multicast (self->s.origin, MULTICAST_PHS); +// gi.multicast (start, MULTICAST_PHS); + if (water) + { + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_RAILTRAIL); + gi.WritePosition (start); + gi.WritePosition (tr.endpos); + gi.multicast (tr.endpos, MULTICAST_PHS); + } + + if (self->client) + PlayerNoise(self, tr.endpos, PNOISE_IMPACT); +} + + +/* +================= +fire_bfg +================= +*/ +void bfg_explode (edict_t *self) +{ + edict_t *ent; + float points; + vec3_t v; + float dist; + + if (self->s.frame == 0) + { + // the BFG effect + ent = NULL; + while ((ent = findradius(ent, self->s.origin, self->dmg_radius)) != NULL) + { + if (!ent->takedamage) + continue; + if (ent == self->owner) + continue; + if (!CanDamage (ent, self)) + continue; + if (!CanDamage (ent, self->owner)) + continue; + + VectorAdd (ent->mins, ent->maxs, v); + VectorMA (ent->s.origin, 0.5, v, v); + VectorSubtract (self->s.origin, v, v); + dist = VectorLength(v); + points = self->radius_dmg * (1.0 - sqrt(dist/self->dmg_radius)); +// PMM - happened to notice this copy/paste bug +// if (ent == self->owner) +// points = points * 0.5; + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_BFG_EXPLOSION); + gi.WritePosition (ent->s.origin); + gi.multicast (ent->s.origin, MULTICAST_PHS); + T_Damage (ent, self, self->owner, self->velocity, ent->s.origin, vec3_origin, (int)points, 0, DAMAGE_ENERGY, MOD_BFG_EFFECT); + } + } + + self->nextthink = level.time + FRAMETIME; + self->s.frame++; + if (self->s.frame == 5) + self->think = G_FreeEdict; +} + +void bfg_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (other == self->owner) + return; + + if (surf && (surf->flags & SURF_SKY)) + { + G_FreeEdict (self); + return; + } + + if (self->owner->client) + PlayerNoise(self->owner, self->s.origin, PNOISE_IMPACT); + + // core explosion - prevents firing it into the wall/floor + if (other->takedamage) + T_Damage (other, self, self->owner, self->velocity, self->s.origin, plane->normal, 200, 0, 0, MOD_BFG_BLAST); + T_RadiusDamage(self, self->owner, 200, other, 100, MOD_BFG_BLAST); + + gi.sound (self, CHAN_VOICE, gi.soundindex ("weapons/bfg__x1b.wav"), 1, ATTN_NORM, 0); + self->solid = SOLID_NOT; + self->touch = NULL; + VectorMA (self->s.origin, -1 * FRAMETIME, self->velocity, self->s.origin); + VectorClear (self->velocity); + self->s.modelindex = gi.modelindex ("sprites/s_bfg3.sp2"); + self->s.frame = 0; + self->s.sound = 0; + self->s.effects &= ~EF_ANIM_ALLFAST; + self->think = bfg_explode; + self->nextthink = level.time + FRAMETIME; + self->enemy = other; + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_BFG_BIGEXPLOSION); + gi.WritePosition (self->s.origin); + gi.multicast (self->s.origin, MULTICAST_PVS); +} + + +void bfg_think (edict_t *self) +{ + edict_t *ent; + edict_t *ignore; + vec3_t point; + vec3_t dir; + vec3_t start; + vec3_t end; + int dmg; + trace_t tr; + + if (deathmatch->value) + dmg = 5; + else + dmg = 10; + + ent = NULL; + while ((ent = findradius(ent, self->s.origin, 256)) != NULL) + { + if (ent == self) + continue; + + if (ent == self->owner) + continue; + + if (!ent->takedamage) + continue; + + //ROGUE - make tesla hurt by bfg + if (!(ent->svflags & SVF_MONSTER) && !(ent->svflags & SVF_DAMAGEABLE) && (!ent->client) && (strcmp(ent->classname, "misc_explobox") != 0)) +// if (!(ent->svflags & SVF_MONSTER) && (!ent->client) && (strcmp(ent->classname, "misc_explobox") != 0) +// && (strcmp(ent->classname, "tesla") != 0)) + continue; + + VectorMA (ent->absmin, 0.5, ent->size, point); + + VectorSubtract (point, self->s.origin, dir); + VectorNormalize (dir); + + ignore = self; + VectorCopy (self->s.origin, start); + VectorMA (start, 2048, dir, end); + while(1) + { + tr = gi.trace (start, NULL, NULL, end, ignore, CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_DEADMONSTER); + + if (!tr.ent) + break; + + // hurt it if we can + if ((tr.ent->takedamage) && !(tr.ent->flags & FL_IMMUNE_LASER) && (tr.ent != self->owner)) + T_Damage (tr.ent, self, self->owner, dir, tr.endpos, vec3_origin, dmg, 1, DAMAGE_ENERGY, MOD_BFG_LASER); + + // if we hit something that's not a monster or player we're done +// if (!(tr.ent->svflags & SVF_MONSTER) && (!tr.ent->client)) + if (!(tr.ent->svflags & SVF_MONSTER) && !(tr.ent->svflags & SVF_DAMAGEABLE) && (!tr.ent->client)) + { + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_LASER_SPARKS); + gi.WriteByte (4); + gi.WritePosition (tr.endpos); + gi.WriteDir (tr.plane.normal); + gi.WriteByte (self->s.skinnum); + gi.multicast (tr.endpos, MULTICAST_PVS); + break; + } + + ignore = tr.ent; + VectorCopy (tr.endpos, start); + } + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_BFG_LASER); + gi.WritePosition (self->s.origin); + gi.WritePosition (tr.endpos); + gi.multicast (self->s.origin, MULTICAST_PHS); + } + + self->nextthink = level.time + FRAMETIME; +} + + +void fire_bfg (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, float damage_radius) +{ + edict_t *bfg; + + bfg = G_Spawn(); + VectorCopy (start, bfg->s.origin); + VectorCopy (dir, bfg->movedir); + vectoangles (dir, bfg->s.angles); + VectorScale (dir, speed, bfg->velocity); + bfg->movetype = MOVETYPE_FLYMISSILE; + bfg->clipmask = MASK_SHOT; + bfg->solid = SOLID_BBOX; + bfg->s.effects |= EF_BFG | EF_ANIM_ALLFAST; + VectorClear (bfg->mins); + VectorClear (bfg->maxs); + bfg->s.modelindex = gi.modelindex ("sprites/s_bfg1.sp2"); + bfg->owner = self; + bfg->touch = bfg_touch; + bfg->nextthink = level.time + 8000/speed; + bfg->think = G_FreeEdict; + bfg->radius_dmg = damage; + bfg->dmg_radius = damage_radius; + bfg->classname = "bfg blast"; + bfg->s.sound = gi.soundindex ("weapons/bfg__l1a.wav"); + + bfg->think = bfg_think; + bfg->nextthink = level.time + FRAMETIME; + bfg->teammaster = bfg; + bfg->teamchain = NULL; + + if (self->client) + check_dodge (self, bfg->s.origin, dir, speed); + + gi.linkentity (bfg); +} diff --git a/original/rogue/game.def b/original/rogue/game.def new file mode 100644 index 0000000..4461243 --- /dev/null +++ b/original/rogue/game.def @@ -0,0 +1,2 @@ +EXPORTS + GetGameAPI diff --git a/original/rogue/game.dsp b/original/rogue/game.dsp new file mode 100644 index 0000000..0ee2553 --- /dev/null +++ b/original/rogue/game.dsp @@ -0,0 +1,1211 @@ +# Microsoft Developer Studio Project File - Name="game" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 5.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102 +# TARGTYPE "Win32 (ALPHA) Dynamic-Link Library" 0x0602 + +CFG=game - Win32 Debug Alpha +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "game.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "game.mak" CFG="game - Win32 Debug Alpha" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "game - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "game - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "game - Win32 Debug Alpha" (based on\ + "Win32 (ALPHA) Dynamic-Link Library") +!MESSAGE "game - Win32 Release Alpha" (based on\ + "Win32 (ALPHA) Dynamic-Link Library") +!MESSAGE + +# Begin Project +# PROP Scc_ProjName ""$/q2xpack/game", TADAAAAA" +# PROP Scc_LocalPath "." + +!IF "$(CFG)" == "game - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir ".\Release" +# PROP BASE Intermediate_Dir ".\Release" +# PROP BASE Target_Dir "." +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "..\..\release" +# PROP Intermediate_Dir "..\Release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "." +CPP=cl.exe +# ADD BASE CPP /nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /YX /c +# ADD CPP /nologo /G5 /MT /W3 /GX /Zd /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FR /YX /FD /c +MTL=midl.exe +# ADD BASE MTL /nologo /D "NDEBUG" /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +RSC=rc.exe +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /dll /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winmm.lib /nologo /base:"0x20000000" /subsystem:windows /dll /map /machine:I386 /out:"..\..\Release/gamex86.dll" +# SUBTRACT LINK32 /incremental:yes /debug + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir ".\Debug" +# PROP BASE Intermediate_Dir ".\Debug" +# PROP BASE Target_Dir "." +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "..\..\debug" +# PROP Intermediate_Dir "debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "." +CPP=cl.exe +# ADD BASE CPP /nologo /MTd /W3 /Gm /GX /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /YX /c +# ADD CPP /nologo /G5 /MTd /W3 /Gm /GX /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "BUILDING_REF_GL" /FR /YX /FD /c +MTL=midl.exe +# ADD BASE MTL /nologo /D "_DEBUG" /win32 +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +RSC=rc.exe +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /dll /debug /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winmm.lib /nologo /base:"0x20000000" /subsystem:windows /dll /incremental:no /map /debug /machine:I386 /out:"..\..\debug\gamex86.dll" + +!ELSEIF "$(CFG)" == "game - Win32 Debug Alpha" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug Alpha" +# PROP BASE Intermediate_Dir "Debug Alpha" +# PROP BASE Ignore_Export_Lib 0 +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "..\DebugAxp" +# PROP Intermediate_Dir ".\DebugAxp" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +MTL=midl.exe +# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /o NUL /win32 +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /o NUL /win32 +CPP=cl.exe +# ADD BASE CPP /nologo /Gt0 /W3 /GX /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /YX /FD /MTd /c +# ADD CPP /nologo /Gt0 /W3 /GX /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /YX /FD /QA21164 /MTd /c +RSC=rc.exe +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib /nologo /base:"0x20000000" /subsystem:windows /dll /debug /machine:ALPHA +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib /nologo /base:"0x20000000" /subsystem:windows /dll /debug /machine:ALPHA /out:"..\DebugAxp/gameaxp.dll" + +!ELSEIF "$(CFG)" == "game - Win32 Release Alpha" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "game___W" +# PROP BASE Intermediate_Dir "game___W" +# PROP BASE Ignore_Export_Lib 0 +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "..\ReleaseAXP" +# PROP Intermediate_Dir ".\ReleaseAXP" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +MTL=midl.exe +# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /o NUL /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /o NUL /win32 +CPP=cl.exe +# ADD BASE CPP /nologo /MT /Gt0 /W3 /GX /Zd /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /YX /FD /c +# ADD CPP /nologo /Gt0 /W3 /GX /Zd /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /YX /FD /QA21164 /c +RSC=rc.exe +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib /nologo /base:"0x20000000" /subsystem:windows /dll /machine:ALPHA /out:"..\Release/gamex86.dll" +# ADD LINK32 kernel32.lib user32.lib gdi32.lib /nologo /base:"0x20000000" /subsystem:windows /dll /machine:ALPHA /out:"..\ReleaseAXP/gameaxp.dll" + +!ENDIF + +# Begin Target + +# Name "game - Win32 Release" +# Name "game - Win32 Debug" +# Name "game - Win32 Debug Alpha" +# Name "game - Win32 Release Alpha" +# Begin Group "Source Files" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=.\dm_ball.c + +!IF "$(CFG)" == "game - Win32 Release" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +!ELSEIF "$(CFG)" == "game - Win32 Debug Alpha" + +!ELSEIF "$(CFG)" == "game - Win32 Release Alpha" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\dm_tag.c + +!IF "$(CFG)" == "game - Win32 Release" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +!ELSEIF "$(CFG)" == "game - Win32 Debug Alpha" + +!ELSEIF "$(CFG)" == "game - Win32 Release Alpha" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\g_ai.c + +!IF "$(CFG)" == "game - Win32 Release" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +!ELSEIF "$(CFG)" == "game - Win32 Debug Alpha" + +!ELSEIF "$(CFG)" == "game - Win32 Release Alpha" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\g_chase.c + +!IF "$(CFG)" == "game - Win32 Release" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +!ELSEIF "$(CFG)" == "game - Win32 Debug Alpha" + +!ELSEIF "$(CFG)" == "game - Win32 Release Alpha" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\g_cmds.c + +!IF "$(CFG)" == "game - Win32 Release" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +!ELSEIF "$(CFG)" == "game - Win32 Debug Alpha" + +!ELSEIF "$(CFG)" == "game - Win32 Release Alpha" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\g_combat.c + +!IF "$(CFG)" == "game - Win32 Release" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +!ELSEIF "$(CFG)" == "game - Win32 Debug Alpha" + +!ELSEIF "$(CFG)" == "game - Win32 Release Alpha" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\g_func.c + +!IF "$(CFG)" == "game - Win32 Release" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +!ELSEIF "$(CFG)" == "game - Win32 Debug Alpha" + +!ELSEIF "$(CFG)" == "game - Win32 Release Alpha" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\g_items.c + +!IF "$(CFG)" == "game - Win32 Release" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +!ELSEIF "$(CFG)" == "game - Win32 Debug Alpha" + +!ELSEIF "$(CFG)" == "game - Win32 Release Alpha" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\g_main.c + +!IF "$(CFG)" == "game - Win32 Release" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +!ELSEIF "$(CFG)" == "game - Win32 Debug Alpha" + +!ELSEIF "$(CFG)" == "game - Win32 Release Alpha" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\g_misc.c + +!IF "$(CFG)" == "game - Win32 Release" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +!ELSEIF "$(CFG)" == "game - Win32 Debug Alpha" + +!ELSEIF "$(CFG)" == "game - Win32 Release Alpha" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\g_monster.c + +!IF "$(CFG)" == "game - Win32 Release" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +!ELSEIF "$(CFG)" == "game - Win32 Debug Alpha" + +!ELSEIF "$(CFG)" == "game - Win32 Release Alpha" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\g_newai.c + +!IF "$(CFG)" == "game - Win32 Release" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +!ELSEIF "$(CFG)" == "game - Win32 Debug Alpha" + +!ELSEIF "$(CFG)" == "game - Win32 Release Alpha" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\g_newdm.c + +!IF "$(CFG)" == "game - Win32 Release" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +!ELSEIF "$(CFG)" == "game - Win32 Debug Alpha" + +!ELSEIF "$(CFG)" == "game - Win32 Release Alpha" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\g_newfnc.c + +!IF "$(CFG)" == "game - Win32 Release" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +!ELSEIF "$(CFG)" == "game - Win32 Debug Alpha" + +!ELSEIF "$(CFG)" == "game - Win32 Release Alpha" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\g_newtarg.c + +!IF "$(CFG)" == "game - Win32 Release" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +!ELSEIF "$(CFG)" == "game - Win32 Debug Alpha" + +!ELSEIF "$(CFG)" == "game - Win32 Release Alpha" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\g_newtrig.c + +!IF "$(CFG)" == "game - Win32 Release" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +!ELSEIF "$(CFG)" == "game - Win32 Debug Alpha" + +!ELSEIF "$(CFG)" == "game - Win32 Release Alpha" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\g_newweap.c + +!IF "$(CFG)" == "game - Win32 Release" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +!ELSEIF "$(CFG)" == "game - Win32 Debug Alpha" + +!ELSEIF "$(CFG)" == "game - Win32 Release Alpha" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\g_phys.c + +!IF "$(CFG)" == "game - Win32 Release" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +!ELSEIF "$(CFG)" == "game - Win32 Debug Alpha" + +!ELSEIF "$(CFG)" == "game - Win32 Release Alpha" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\g_save.c + +!IF "$(CFG)" == "game - Win32 Release" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +!ELSEIF "$(CFG)" == "game - Win32 Debug Alpha" + +!ELSEIF "$(CFG)" == "game - Win32 Release Alpha" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\g_spawn.c + +!IF "$(CFG)" == "game - Win32 Release" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +!ELSEIF "$(CFG)" == "game - Win32 Debug Alpha" + +!ELSEIF "$(CFG)" == "game - Win32 Release Alpha" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\g_sphere.c + +!IF "$(CFG)" == "game - Win32 Release" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +!ELSEIF "$(CFG)" == "game - Win32 Debug Alpha" + +!ELSEIF "$(CFG)" == "game - Win32 Release Alpha" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\g_svcmds.c + +!IF "$(CFG)" == "game - Win32 Release" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +!ELSEIF "$(CFG)" == "game - Win32 Debug Alpha" + +!ELSEIF "$(CFG)" == "game - Win32 Release Alpha" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\g_target.c + +!IF "$(CFG)" == "game - Win32 Release" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +!ELSEIF "$(CFG)" == "game - Win32 Debug Alpha" + +!ELSEIF "$(CFG)" == "game - Win32 Release Alpha" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\g_trigger.c + +!IF "$(CFG)" == "game - Win32 Release" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +!ELSEIF "$(CFG)" == "game - Win32 Debug Alpha" + +!ELSEIF "$(CFG)" == "game - Win32 Release Alpha" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\g_turret.c + +!IF "$(CFG)" == "game - Win32 Release" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +!ELSEIF "$(CFG)" == "game - Win32 Debug Alpha" + +!ELSEIF "$(CFG)" == "game - Win32 Release Alpha" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\g_utils.c + +!IF "$(CFG)" == "game - Win32 Release" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +!ELSEIF "$(CFG)" == "game - Win32 Debug Alpha" + +!ELSEIF "$(CFG)" == "game - Win32 Release Alpha" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\g_weapon.c + +!IF "$(CFG)" == "game - Win32 Release" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +!ELSEIF "$(CFG)" == "game - Win32 Debug Alpha" + +!ELSEIF "$(CFG)" == "game - Win32 Release Alpha" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\game.def +# End Source File +# Begin Source File + +SOURCE=.\m_actor.c + +!IF "$(CFG)" == "game - Win32 Release" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +!ELSEIF "$(CFG)" == "game - Win32 Debug Alpha" + +!ELSEIF "$(CFG)" == "game - Win32 Release Alpha" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\m_berserk.c + +!IF "$(CFG)" == "game - Win32 Release" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +!ELSEIF "$(CFG)" == "game - Win32 Debug Alpha" + +!ELSEIF "$(CFG)" == "game - Win32 Release Alpha" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\m_boss2.c + +!IF "$(CFG)" == "game - Win32 Release" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +!ELSEIF "$(CFG)" == "game - Win32 Debug Alpha" + +!ELSEIF "$(CFG)" == "game - Win32 Release Alpha" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\m_boss3.c + +!IF "$(CFG)" == "game - Win32 Release" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +!ELSEIF "$(CFG)" == "game - Win32 Debug Alpha" + +!ELSEIF "$(CFG)" == "game - Win32 Release Alpha" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\m_boss31.c + +!IF "$(CFG)" == "game - Win32 Release" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +!ELSEIF "$(CFG)" == "game - Win32 Debug Alpha" + +!ELSEIF "$(CFG)" == "game - Win32 Release Alpha" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\m_boss32.c + +!IF "$(CFG)" == "game - Win32 Release" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +!ELSEIF "$(CFG)" == "game - Win32 Debug Alpha" + +!ELSEIF "$(CFG)" == "game - Win32 Release Alpha" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\m_brain.c + +!IF "$(CFG)" == "game - Win32 Release" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +!ELSEIF "$(CFG)" == "game - Win32 Debug Alpha" + +!ELSEIF "$(CFG)" == "game - Win32 Release Alpha" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\m_carrier.c + +!IF "$(CFG)" == "game - Win32 Release" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +!ELSEIF "$(CFG)" == "game - Win32 Debug Alpha" + +!ELSEIF "$(CFG)" == "game - Win32 Release Alpha" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\m_chick.c + +!IF "$(CFG)" == "game - Win32 Release" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +!ELSEIF "$(CFG)" == "game - Win32 Debug Alpha" + +!ELSEIF "$(CFG)" == "game - Win32 Release Alpha" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\m_flash.c + +!IF "$(CFG)" == "game - Win32 Release" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +!ELSEIF "$(CFG)" == "game - Win32 Debug Alpha" + +!ELSEIF "$(CFG)" == "game - Win32 Release Alpha" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\m_flipper.c + +!IF "$(CFG)" == "game - Win32 Release" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +!ELSEIF "$(CFG)" == "game - Win32 Debug Alpha" + +!ELSEIF "$(CFG)" == "game - Win32 Release Alpha" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\m_float.c + +!IF "$(CFG)" == "game - Win32 Release" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +!ELSEIF "$(CFG)" == "game - Win32 Debug Alpha" + +!ELSEIF "$(CFG)" == "game - Win32 Release Alpha" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\m_flyer.c + +!IF "$(CFG)" == "game - Win32 Release" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +!ELSEIF "$(CFG)" == "game - Win32 Debug Alpha" + +!ELSEIF "$(CFG)" == "game - Win32 Release Alpha" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\m_gladiator.c + +!IF "$(CFG)" == "game - Win32 Release" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +!ELSEIF "$(CFG)" == "game - Win32 Debug Alpha" + +!ELSEIF "$(CFG)" == "game - Win32 Release Alpha" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\m_gunner.c + +!IF "$(CFG)" == "game - Win32 Release" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +!ELSEIF "$(CFG)" == "game - Win32 Debug Alpha" + +!ELSEIF "$(CFG)" == "game - Win32 Release Alpha" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\m_hover.c + +!IF "$(CFG)" == "game - Win32 Release" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +!ELSEIF "$(CFG)" == "game - Win32 Debug Alpha" + +!ELSEIF "$(CFG)" == "game - Win32 Release Alpha" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\m_infantry.c + +!IF "$(CFG)" == "game - Win32 Release" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +!ELSEIF "$(CFG)" == "game - Win32 Debug Alpha" + +!ELSEIF "$(CFG)" == "game - Win32 Release Alpha" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\m_insane.c + +!IF "$(CFG)" == "game - Win32 Release" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +!ELSEIF "$(CFG)" == "game - Win32 Debug Alpha" + +!ELSEIF "$(CFG)" == "game - Win32 Release Alpha" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\m_medic.c + +!IF "$(CFG)" == "game - Win32 Release" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +!ELSEIF "$(CFG)" == "game - Win32 Debug Alpha" + +!ELSEIF "$(CFG)" == "game - Win32 Release Alpha" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\m_move.c + +!IF "$(CFG)" == "game - Win32 Release" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +!ELSEIF "$(CFG)" == "game - Win32 Debug Alpha" + +!ELSEIF "$(CFG)" == "game - Win32 Release Alpha" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\m_mutant.c + +!IF "$(CFG)" == "game - Win32 Release" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +!ELSEIF "$(CFG)" == "game - Win32 Debug Alpha" + +!ELSEIF "$(CFG)" == "game - Win32 Release Alpha" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\m_parasite.c + +!IF "$(CFG)" == "game - Win32 Release" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +!ELSEIF "$(CFG)" == "game - Win32 Debug Alpha" + +!ELSEIF "$(CFG)" == "game - Win32 Release Alpha" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\m_soldier.c + +!IF "$(CFG)" == "game - Win32 Release" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +!ELSEIF "$(CFG)" == "game - Win32 Debug Alpha" + +!ELSEIF "$(CFG)" == "game - Win32 Release Alpha" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\m_stalker.c + +!IF "$(CFG)" == "game - Win32 Release" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +!ELSEIF "$(CFG)" == "game - Win32 Debug Alpha" + +!ELSEIF "$(CFG)" == "game - Win32 Release Alpha" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\m_supertank.c + +!IF "$(CFG)" == "game - Win32 Release" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +!ELSEIF "$(CFG)" == "game - Win32 Debug Alpha" + +!ELSEIF "$(CFG)" == "game - Win32 Release Alpha" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\m_tank.c + +!IF "$(CFG)" == "game - Win32 Release" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +!ELSEIF "$(CFG)" == "game - Win32 Debug Alpha" + +!ELSEIF "$(CFG)" == "game - Win32 Release Alpha" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\m_turret.c + +!IF "$(CFG)" == "game - Win32 Release" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +!ELSEIF "$(CFG)" == "game - Win32 Debug Alpha" + +!ELSEIF "$(CFG)" == "game - Win32 Release Alpha" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\m_widow.c + +!IF "$(CFG)" == "game - Win32 Release" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +!ELSEIF "$(CFG)" == "game - Win32 Debug Alpha" + +!ELSEIF "$(CFG)" == "game - Win32 Release Alpha" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\m_widow2.c + +!IF "$(CFG)" == "game - Win32 Release" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +!ELSEIF "$(CFG)" == "game - Win32 Debug Alpha" + +!ELSEIF "$(CFG)" == "game - Win32 Release Alpha" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\p_client.c + +!IF "$(CFG)" == "game - Win32 Release" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +!ELSEIF "$(CFG)" == "game - Win32 Debug Alpha" + +!ELSEIF "$(CFG)" == "game - Win32 Release Alpha" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\p_hud.c + +!IF "$(CFG)" == "game - Win32 Release" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +!ELSEIF "$(CFG)" == "game - Win32 Debug Alpha" + +!ELSEIF "$(CFG)" == "game - Win32 Release Alpha" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\p_trail.c + +!IF "$(CFG)" == "game - Win32 Release" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +!ELSEIF "$(CFG)" == "game - Win32 Debug Alpha" + +!ELSEIF "$(CFG)" == "game - Win32 Release Alpha" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\p_view.c + +!IF "$(CFG)" == "game - Win32 Release" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +!ELSEIF "$(CFG)" == "game - Win32 Debug Alpha" + +!ELSEIF "$(CFG)" == "game - Win32 Release Alpha" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\p_weapon.c + +!IF "$(CFG)" == "game - Win32 Release" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +!ELSEIF "$(CFG)" == "game - Win32 Debug Alpha" + +!ELSEIF "$(CFG)" == "game - Win32 Release Alpha" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\q_shared.c + +!IF "$(CFG)" == "game - Win32 Release" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +!ELSEIF "$(CFG)" == "game - Win32 Debug Alpha" + +!ELSEIF "$(CFG)" == "game - Win32 Release Alpha" + +!ENDIF + +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=.\g_local.h +# End Source File +# Begin Source File + +SOURCE=.\game.h +# End Source File +# Begin Source File + +SOURCE=.\m_berserk.h +# End Source File +# Begin Source File + +SOURCE=.\m_boss2.h +# End Source File +# Begin Source File + +SOURCE=.\m_brain.h +# End Source File +# Begin Source File + +SOURCE=.\m_carrier.h +# End Source File +# Begin Source File + +SOURCE=.\m_chick.h +# End Source File +# Begin Source File + +SOURCE=.\m_flipper.h +# End Source File +# Begin Source File + +SOURCE=.\m_float.h +# End Source File +# Begin Source File + +SOURCE=.\m_flyer.h +# End Source File +# Begin Source File + +SOURCE=.\m_gladiator.h +# End Source File +# Begin Source File + +SOURCE=.\m_gunner.h +# End Source File +# Begin Source File + +SOURCE=.\m_hover.h +# End Source File +# Begin Source File + +SOURCE=.\m_infantry.h +# End Source File +# Begin Source File + +SOURCE=.\m_insane.h +# End Source File +# Begin Source File + +SOURCE=.\m_medic.h +# End Source File +# Begin Source File + +SOURCE=.\m_mutant.h +# End Source File +# Begin Source File + +SOURCE=.\m_parasite.h +# End Source File +# Begin Source File + +SOURCE=.\m_soldier.h +# End Source File +# Begin Source File + +SOURCE=.\m_stalker.h +# End Source File +# Begin Source File + +SOURCE=.\m_supertank.h +# End Source File +# Begin Source File + +SOURCE=.\m_tank.h +# End Source File +# Begin Source File + +SOURCE=.\m_turret.h +# End Source File +# Begin Source File + +SOURCE=.\m_widow.h +# End Source File +# Begin Source File + +SOURCE=.\m_widow2.h +# End Source File +# Begin Source File + +SOURCE=.\q_shared.h +# End Source File +# End Group +# Begin Group "Resource Files" + +# PROP Default_Filter "" +# End Group +# End Target +# End Project diff --git a/original/rogue/game.h b/original/rogue/game.h new file mode 100644 index 0000000..45bbc66 --- /dev/null +++ b/original/rogue/game.h @@ -0,0 +1,222 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. + +// game.h -- game dll information visible to server + +#define GAME_API_VERSION 3 + +// edict->svflags + +#define SVF_NOCLIENT 0x00000001 // don't send entity to clients, even if it has effects +#define SVF_DEADMONSTER 0x00000002 // treat as CONTENTS_DEADMONSTER for collision +#define SVF_MONSTER 0x00000004 // treat as CONTENTS_MONSTER for collision +//ROGUE -- added for things that are damageable, but not monsters +// right now, only the tesla has this +#define SVF_DAMAGEABLE 0x00000008 +//ROGUE end + +// edict->solid values + +typedef enum +{ +SOLID_NOT, // no interaction with other objects +SOLID_TRIGGER, // only touch when inside, after moving +SOLID_BBOX, // touch on edge +SOLID_BSP // bsp clip, touch on edge +} solid_t; + +//=============================================================== + +// link_t is only used for entity area links now +typedef struct link_s +{ + struct link_s *prev, *next; +} link_t; + +#define MAX_ENT_CLUSTERS 16 + + +typedef struct edict_s edict_t; +typedef struct gclient_s gclient_t; + + +#ifndef GAME_INCLUDE + +struct gclient_s +{ + player_state_t ps; // communicated by server to clients + int ping; + // the game dll can add anything it wants after + // this point in the structure +}; + + +struct edict_s +{ + entity_state_t s; + struct gclient_s *client; + qboolean inuse; + int linkcount; + + // FIXME: move these fields to a server private sv_entity_t + link_t area; // linked to a division node or leaf + + int num_clusters; // if -1, use headnode instead + int clusternums[MAX_ENT_CLUSTERS]; + int headnode; // unused if num_clusters != -1 + int areanum, areanum2; + + //================================ + + int svflags; // SVF_NOCLIENT, SVF_DEADMONSTER, SVF_MONSTER, etc + vec3_t mins, maxs; + vec3_t absmin, absmax, size; + solid_t solid; + int clipmask; + edict_t *owner; + + // the game dll can add anything it wants after + // this point in the structure +}; + +#endif // GAME_INCLUDE + +//=============================================================== + +// +// functions provided by the main engine +// +typedef struct +{ + // special messages + void (*bprintf) (int printlevel, char *fmt, ...); + void (*dprintf) (char *fmt, ...); + void (*cprintf) (edict_t *ent, int printlevel, char *fmt, ...); + void (*centerprintf) (edict_t *ent, char *fmt, ...); + void (*sound) (edict_t *ent, int channel, int soundindex, float volume, float attenuation, float timeofs); + void (*positioned_sound) (vec3_t origin, edict_t *ent, int channel, int soundinedex, float volume, float attenuation, float timeofs); + + // config strings hold all the index strings, the lightstyles, + // and misc data like the sky definition and cdtrack. + // All of the current configstrings are sent to clients when + // they connect, and changes are sent to all connected clients. + void (*configstring) (int num, char *string); + + void (*error) (char *fmt, ...); + + // the *index functions create configstrings and some internal server state + int (*modelindex) (char *name); + int (*soundindex) (char *name); + int (*imageindex) (char *name); + + void (*setmodel) (edict_t *ent, char *name); + + // collision detection + trace_t (*trace) (vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, edict_t *passent, int contentmask); + int (*pointcontents) (vec3_t point); + qboolean (*inPVS) (vec3_t p1, vec3_t p2); + qboolean (*inPHS) (vec3_t p1, vec3_t p2); + void (*SetAreaPortalState) (int portalnum, qboolean open); + qboolean (*AreasConnected) (int area1, int area2); + + // an entity will never be sent to a client or used for collision + // if it is not passed to linkentity. If the size, position, or + // solidity changes, it must be relinked. + void (*linkentity) (edict_t *ent); + void (*unlinkentity) (edict_t *ent); // call before removing an interactive edict + int (*BoxEdicts) (vec3_t mins, vec3_t maxs, edict_t **list, int maxcount, int areatype); + void (*Pmove) (pmove_t *pmove); // player movement code common with client prediction + + // network messaging + void (*multicast) (vec3_t origin, multicast_t to); + void (*unicast) (edict_t *ent, qboolean reliable); + void (*WriteChar) (int c); + void (*WriteByte) (int c); + void (*WriteShort) (int c); + void (*WriteLong) (int c); + void (*WriteFloat) (float f); + void (*WriteString) (char *s); + void (*WritePosition) (vec3_t pos); // some fractional bits + void (*WriteDir) (vec3_t pos); // single byte encoded, very coarse + void (*WriteAngle) (float f); + + // managed memory allocation + void *(*TagMalloc) (int size, int tag); + void (*TagFree) (void *block); + void (*FreeTags) (int tag); + + // console variable interaction + cvar_t *(*cvar) (char *var_name, char *value, int flags); + cvar_t *(*cvar_set) (char *var_name, char *value); + cvar_t *(*cvar_forceset) (char *var_name, char *value); + + // ClientCommand and ServerCommand parameter access + int (*argc) (void); + char *(*argv) (int n); + char *(*args) (void); // concatenation of all argv >= 1 + + // add commands to the server console as if they were typed in + // for map changing, etc + void (*AddCommandString) (char *text); + + void (*DebugGraph) (float value, int color); +} game_import_t; + +// +// functions exported by the game subsystem +// +typedef struct +{ + int apiversion; + + // the init function will only be called when a game starts, + // not each time a level is loaded. Persistant data for clients + // and the server can be allocated in init + void (*Init) (void); + void (*Shutdown) (void); + + // each new level entered will cause a call to SpawnEntities + void (*SpawnEntities) (char *mapname, char *entstring, char *spawnpoint); + + // Read/Write Game is for storing persistant cross level information + // about the world state and the clients. + // WriteGame is called every time a level is exited. + // ReadGame is called on a loadgame. + void (*WriteGame) (char *filename, qboolean autosave); + void (*ReadGame) (char *filename); + + // ReadLevel is called after the default map information has been + // loaded with SpawnEntities + void (*WriteLevel) (char *filename); + void (*ReadLevel) (char *filename); + + qboolean (*ClientConnect) (edict_t *ent, char *userinfo); + void (*ClientBegin) (edict_t *ent); + void (*ClientUserinfoChanged) (edict_t *ent, char *userinfo); + void (*ClientDisconnect) (edict_t *ent); + void (*ClientCommand) (edict_t *ent); + void (*ClientThink) (edict_t *ent, usercmd_t *cmd); + + void (*RunFrame) (void); + + // ServerCommand will be called when an "sv " command is issued on the + // server console. + // The game can issue gi.argc() / gi.argv() commands to get the rest + // of the parameters + void (*ServerCommand) (void); + + // + // global variables shared between game and server + // + + // The edict array is allocated in the game dll so it + // can vary in size from one game to another. + // + // The size will be fixed when ge->Init() is called + struct edict_s *edicts; + int edict_size; + int num_edicts; // current number, <= max_edicts + int max_edicts; +} game_export_t; + +game_export_t *GetGameApi (game_import_t *import); diff --git a/original/rogue/game.plg b/original/rogue/game.plg new file mode 100644 index 0000000..9464dab --- /dev/null +++ b/original/rogue/game.plg @@ -0,0 +1,154 @@ +--------------------Configuration: game - Win32 Debug-------------------- +Begining build with project "E:\quake2\code\game\game.dsp", at root. +Active configuration is Win32 (x86) Dynamic-Link Library (based on Win32 (x86) Dynamic-Link Library) + +Project's tools are: + "32-bit C/C++ Compiler for 80x86" with flags "/nologo /G5 /MTd /W3 /Gm /GX /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "BUILDING_REF_GL" /FR"debug/" /Fp"debug/game.pch" /YX /Fo"debug/" /Fd"debug/" /FD /c " + "OLE Type Library Maker" with flags "/nologo /D "_DEBUG" /mktyplib203 /win32 " + "Win32 Resource Compiler" with flags "/l 0x409 /d "_DEBUG" " + "Browser Database Maker" with flags "/nologo /o"..\..\debug/game.bsc" " + "COFF Linker for 80x86" with flags "kernel32.lib user32.lib gdi32.lib winmm.lib /nologo /base:"0x20000000" /subsystem:windows /dll /incremental:no /pdb:"..\..\debug/gamex86.pdb" /map:"debug/gamex86.map" /debug /machine:I386 /def:".\game.def" /out:"..\..\debug\gamex86.dll" /implib:"..\..\debug/gamex86.lib" " + "Custom Build" with flags "" + "" with flags "" + +Creating temp file "C:\TEMP\RSP2F.tmp" with contents +Creating command line "cl.exe @C:\TEMP\RSP2F.tmp" +Creating temp file "C:\TEMP\RSP30.tmp" with contents +Creating command line "link.exe @C:\TEMP\RSP30.tmp" +Compiling... +g_spawn.c +Linking... + Creating library ..\..\debug/gamex86.lib and object ..\..\debug/gamex86.exp +Creating temp file "C:\TEMP\RSP31.tmp" with contents +Creating command line "bscmake.exe @C:\TEMP\RSP31.tmp" +Creating browse info file... + + + +gamex86.dll - 0 error(s), 0 warning(s) diff --git a/original/rogue/m_actor.c b/original/rogue/m_actor.c new file mode 100644 index 0000000..9f6cbd6 --- /dev/null +++ b/original/rogue/m_actor.c @@ -0,0 +1,592 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// g_actor.c + +#include "g_local.h" +#include "m_actor.h" + +#define MAX_ACTOR_NAMES 8 +char *actor_names[MAX_ACTOR_NAMES] = +{ + "Hellrot", + "Tokay", + "Killme", + "Disruptor", + "Adrianator", + "Rambear", + "Titus", + "Bitterman" +}; + + +mframe_t actor_frames_stand [] = +{ + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL +}; +mmove_t actor_move_stand = {FRAME_stand101, FRAME_stand140, actor_frames_stand, NULL}; + +void actor_stand (edict_t *self) +{ + self->monsterinfo.currentmove = &actor_move_stand; + + // randomize on startup + if (level.time < 1.0) + self->s.frame = self->monsterinfo.currentmove->firstframe + (rand() % (self->monsterinfo.currentmove->lastframe - self->monsterinfo.currentmove->firstframe + 1)); +} + + +mframe_t actor_frames_walk [] = +{ + ai_walk, 0, NULL, + ai_walk, 6, NULL, + ai_walk, 10, NULL, + ai_walk, 3, NULL, + ai_walk, 2, NULL, + ai_walk, 7, NULL, + ai_walk, 10, NULL, + ai_walk, 1, NULL, + ai_walk, 4, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL +}; +mmove_t actor_move_walk = {FRAME_walk01, FRAME_walk08, actor_frames_walk, NULL}; + +void actor_walk (edict_t *self) +{ + self->monsterinfo.currentmove = &actor_move_walk; +} + + +mframe_t actor_frames_run [] = +{ + ai_run, 4, NULL, + ai_run, 15, NULL, + ai_run, 15, NULL, + ai_run, 8, NULL, + ai_run, 20, NULL, + ai_run, 15, NULL, + ai_run, 8, NULL, + ai_run, 17, NULL, + ai_run, 12, NULL, + ai_run, -2, NULL, + ai_run, -2, NULL, + ai_run, -1, NULL +}; +mmove_t actor_move_run = {FRAME_run02, FRAME_run07, actor_frames_run, NULL}; + +void actor_run (edict_t *self) +{ + if ((level.time < self->pain_debounce_time) && (!self->enemy)) + { + if (self->movetarget) + actor_walk(self); + else + actor_stand(self); + return; + } + + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + { + actor_stand(self); + return; + } + + self->monsterinfo.currentmove = &actor_move_run; +} + + +mframe_t actor_frames_pain1 [] = +{ + ai_move, -5, NULL, + ai_move, 4, NULL, + ai_move, 1, NULL +}; +mmove_t actor_move_pain1 = {FRAME_pain101, FRAME_pain103, actor_frames_pain1, actor_run}; + +mframe_t actor_frames_pain2 [] = +{ + ai_move, -4, NULL, + ai_move, 4, NULL, + ai_move, 0, NULL +}; +mmove_t actor_move_pain2 = {FRAME_pain201, FRAME_pain203, actor_frames_pain2, actor_run}; + +mframe_t actor_frames_pain3 [] = +{ + ai_move, -1, NULL, + ai_move, 1, NULL, + ai_move, 0, NULL +}; +mmove_t actor_move_pain3 = {FRAME_pain301, FRAME_pain303, actor_frames_pain3, actor_run}; + +mframe_t actor_frames_flipoff [] = +{ + ai_turn, 0, NULL, + ai_turn, 0, NULL, + ai_turn, 0, NULL, + ai_turn, 0, NULL, + ai_turn, 0, NULL, + ai_turn, 0, NULL, + ai_turn, 0, NULL, + ai_turn, 0, NULL, + ai_turn, 0, NULL, + ai_turn, 0, NULL, + ai_turn, 0, NULL, + ai_turn, 0, NULL, + ai_turn, 0, NULL, + ai_turn, 0, NULL +}; +mmove_t actor_move_flipoff = {FRAME_flip01, FRAME_flip14, actor_frames_flipoff, actor_run}; + +mframe_t actor_frames_taunt [] = +{ + ai_turn, 0, NULL, + ai_turn, 0, NULL, + ai_turn, 0, NULL, + ai_turn, 0, NULL, + ai_turn, 0, NULL, + ai_turn, 0, NULL, + ai_turn, 0, NULL, + ai_turn, 0, NULL, + ai_turn, 0, NULL, + ai_turn, 0, NULL, + ai_turn, 0, NULL, + ai_turn, 0, NULL, + ai_turn, 0, NULL, + ai_turn, 0, NULL, + ai_turn, 0, NULL, + ai_turn, 0, NULL, + ai_turn, 0, NULL +}; +mmove_t actor_move_taunt = {FRAME_taunt01, FRAME_taunt17, actor_frames_taunt, actor_run}; + +char *messages[] = +{ + "Watch it", + "#$@*&", + "Idiot", + "Check your targets" +}; + +void actor_pain (edict_t *self, edict_t *other, float kick, int damage) +{ + int n; + + if (self->health < (self->max_health / 2)) + self->s.skinnum = 1; + + if (level.time < self->pain_debounce_time) + return; + + self->pain_debounce_time = level.time + 3; +// gi.sound (self, CHAN_VOICE, actor.sound_pain, 1, ATTN_NORM, 0); + + if ((other->client) && (random() < 0.4)) + { + vec3_t v; + char *name; + + VectorSubtract (other->s.origin, self->s.origin, v); + self->ideal_yaw = vectoyaw (v); + if (random() < 0.5) + self->monsterinfo.currentmove = &actor_move_flipoff; + else + self->monsterinfo.currentmove = &actor_move_taunt; + name = actor_names[(self - g_edicts)%MAX_ACTOR_NAMES]; + gi.cprintf (other, PRINT_CHAT, "%s: %s!\n", name, messages[rand()%3]); + return; + } + + n = rand() % 3; + if (n == 0) + self->monsterinfo.currentmove = &actor_move_pain1; + else if (n == 1) + self->monsterinfo.currentmove = &actor_move_pain2; + else + self->monsterinfo.currentmove = &actor_move_pain3; +} + + +void actorMachineGun (edict_t *self) +{ + vec3_t start, target; + vec3_t forward, right; + + AngleVectors (self->s.angles, forward, right, NULL); + G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_ACTOR_MACHINEGUN_1], forward, right, start); + if (self->enemy) + { + if (self->enemy->health > 0) + { + VectorMA (self->enemy->s.origin, -0.2, self->enemy->velocity, target); + target[2] += self->enemy->viewheight; + } + else + { + VectorCopy (self->enemy->absmin, target); + target[2] += (self->enemy->size[2] / 2); + } + VectorSubtract (target, start, forward); + VectorNormalize (forward); + } + else + { + AngleVectors (self->s.angles, forward, NULL, NULL); + } + monster_fire_bullet (self, start, forward, 3, 4, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, MZ2_ACTOR_MACHINEGUN_1); +} + + +void actor_dead (edict_t *self) +{ + VectorSet (self->mins, -16, -16, -24); + VectorSet (self->maxs, 16, 16, -8); + self->movetype = MOVETYPE_TOSS; + self->svflags |= SVF_DEADMONSTER; + self->nextthink = 0; + gi.linkentity (self); +} + +mframe_t actor_frames_death1 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, -13, NULL, + ai_move, 14, NULL, + ai_move, 3, NULL, + ai_move, -2, NULL, + ai_move, 1, NULL +}; +mmove_t actor_move_death1 = {FRAME_death101, FRAME_death107, actor_frames_death1, actor_dead}; + +mframe_t actor_frames_death2 [] = +{ + ai_move, 0, NULL, + ai_move, 7, NULL, + ai_move, -6, NULL, + ai_move, -5, NULL, + ai_move, 1, NULL, + ai_move, 0, NULL, + ai_move, -1, NULL, + ai_move, -2, NULL, + ai_move, -1, NULL, + ai_move, -9, NULL, + ai_move, -13, NULL, + ai_move, -13, NULL, + ai_move, 0, NULL +}; +mmove_t actor_move_death2 = {FRAME_death201, FRAME_death213, actor_frames_death2, actor_dead}; + +void actor_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + int n; + +// check for gib + if (self->health <= -80) + { +// gi.sound (self, CHAN_VOICE, actor.sound_gib, 1, ATTN_NORM, 0); + for (n= 0; n < 2; n++) + ThrowGib (self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC); + for (n= 0; n < 4; n++) + ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); + ThrowHead (self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC); + self->deadflag = DEAD_DEAD; + return; + } + + if (self->deadflag == DEAD_DEAD) + return; + +// regular death +// gi.sound (self, CHAN_VOICE, actor.sound_die, 1, ATTN_NORM, 0); + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_YES; + + n = rand() % 2; + if (n == 0) + self->monsterinfo.currentmove = &actor_move_death1; + else + self->monsterinfo.currentmove = &actor_move_death2; +} + + +void actor_fire (edict_t *self) +{ + actorMachineGun (self); + + if (level.time >= self->monsterinfo.pausetime) + self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; + else + self->monsterinfo.aiflags |= AI_HOLD_FRAME; +} + +mframe_t actor_frames_attack [] = +{ + ai_charge, -2, actor_fire, + ai_charge, -2, NULL, + ai_charge, 3, NULL, + ai_charge, 2, NULL +}; +mmove_t actor_move_attack = {FRAME_attak01, FRAME_attak04, actor_frames_attack, actor_run}; + +void actor_attack(edict_t *self) +{ + int n; + + self->monsterinfo.currentmove = &actor_move_attack; + n = (rand() & 15) + 3 + 7; + self->monsterinfo.pausetime = level.time + n * FRAMETIME; +} + + +void actor_use (edict_t *self, edict_t *other, edict_t *activator) +{ + vec3_t v; + + self->goalentity = self->movetarget = G_PickTarget(self->target); + if ((!self->movetarget) || (strcmp(self->movetarget->classname, "target_actor") != 0)) + { + gi.dprintf ("%s has bad target %s at %s\n", self->classname, self->target, vtos(self->s.origin)); + self->target = NULL; + self->monsterinfo.pausetime = 100000000; + self->monsterinfo.stand (self); + return; + } + + VectorSubtract (self->goalentity->s.origin, self->s.origin, v); + self->ideal_yaw = self->s.angles[YAW] = vectoyaw(v); + self->monsterinfo.walk (self); + self->target = NULL; +} + + +/*QUAKED misc_actor (1 .5 0) (-16 -16 -24) (16 16 32) +*/ + +void SP_misc_actor (edict_t *self) +{ + if (deathmatch->value) + { + G_FreeEdict (self); + return; + } + + if (!self->targetname) + { + gi.dprintf("untargeted %s at %s\n", self->classname, vtos(self->s.origin)); + G_FreeEdict (self); + return; + } + + if (!self->target) + { + gi.dprintf("%s with no target at %s\n", self->classname, vtos(self->s.origin)); + G_FreeEdict (self); + return; + } + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex("players/male/tris.md2"); + VectorSet (self->mins, -16, -16, -24); + VectorSet (self->maxs, 16, 16, 32); + + if (!self->health) + self->health = 100; + self->mass = 200; + + self->pain = actor_pain; + self->die = actor_die; + + self->monsterinfo.stand = actor_stand; + self->monsterinfo.walk = actor_walk; + self->monsterinfo.run = actor_run; + self->monsterinfo.attack = actor_attack; + self->monsterinfo.melee = NULL; + self->monsterinfo.sight = NULL; + + self->monsterinfo.aiflags |= AI_GOOD_GUY; + + gi.linkentity (self); + + self->monsterinfo.currentmove = &actor_move_stand; + self->monsterinfo.scale = MODEL_SCALE; + + walkmonster_start (self); + + // actors always start in a dormant state, they *must* be used to get going + self->use = actor_use; +} + + +/*QUAKED target_actor (.5 .3 0) (-8 -8 -8) (8 8 8) JUMP SHOOT ATTACK x HOLD BRUTAL +JUMP jump in set direction upon reaching this target +SHOOT take a single shot at the pathtarget +ATTACK attack pathtarget until it or actor is dead + +"target" next target_actor +"pathtarget" target of any action to be taken at this point +"wait" amount of time actor should pause at this point +"message" actor will "say" this to the player + +for JUMP only: +"speed" speed thrown forward (default 200) +"height" speed thrown upwards (default 200) +*/ + +void target_actor_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + vec3_t v; + + if (other->movetarget != self) + return; + + if (other->enemy) + return; + + other->goalentity = other->movetarget = NULL; + + if (self->message) + { + int n; + edict_t *ent; + + for (n = 1; n <= game.maxclients; n++) + { + ent = &g_edicts[n]; + if (!ent->inuse) + continue; + gi.cprintf (ent, PRINT_CHAT, "%s: %s\n", actor_names[(other - g_edicts)%MAX_ACTOR_NAMES], self->message); + } + } + + if (self->spawnflags & 1) //jump + { + other->velocity[0] = self->movedir[0] * self->speed; + other->velocity[1] = self->movedir[1] * self->speed; + + if (other->groundentity) + { + other->groundentity = NULL; + other->velocity[2] = self->movedir[2]; + gi.sound(other, CHAN_VOICE, gi.soundindex("player/male/jump1.wav"), 1, ATTN_NORM, 0); + } + } + + if (self->spawnflags & 2) //shoot + { + } + else if (self->spawnflags & 4) //attack + { + other->enemy = G_PickTarget(self->pathtarget); + if (other->enemy) + { + other->goalentity = other->enemy; + if (self->spawnflags & 32) + other->monsterinfo.aiflags |= AI_BRUTAL; + if (self->spawnflags & 16) + { + other->monsterinfo.aiflags |= AI_STAND_GROUND; + actor_stand (other); + } + else + { + actor_run (other); + } + } + } + + if (!(self->spawnflags & 6) && (self->pathtarget)) + { + char *savetarget; + + savetarget = self->target; + self->target = self->pathtarget; + G_UseTargets (self, other); + self->target = savetarget; + } + + other->movetarget = G_PickTarget(self->target); + + if (!other->goalentity) + other->goalentity = other->movetarget; + + if (!other->movetarget && !other->enemy) + { + other->monsterinfo.pausetime = level.time + 100000000; + other->monsterinfo.stand (other); + } + else if (other->movetarget == other->goalentity) + { + VectorSubtract (other->movetarget->s.origin, other->s.origin, v); + other->ideal_yaw = vectoyaw (v); + } +} + +void SP_target_actor (edict_t *self) +{ + if (!self->targetname) + gi.dprintf ("%s with no targetname at %s\n", self->classname, vtos(self->s.origin)); + + self->solid = SOLID_TRIGGER; + self->touch = target_actor_touch; + VectorSet (self->mins, -8, -8, -8); + VectorSet (self->maxs, 8, 8, 8); + self->svflags = SVF_NOCLIENT; + + if (self->spawnflags & 1) + { + if (!self->speed) + self->speed = 200; + if (!st.height) + st.height = 200; + if (self->s.angles[YAW] == 0) + self->s.angles[YAW] = 360; + G_SetMovedir (self->s.angles, self->movedir); + self->movedir[2] = st.height; + } + + gi.linkentity (self); +} diff --git a/original/rogue/m_actor.h b/original/rogue/m_actor.h new file mode 100644 index 0000000..41354a4 --- /dev/null +++ b/original/rogue/m_actor.h @@ -0,0 +1,489 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// G:\quake2\baseq2\models/player_y + +// This file generated by ModelGen - Do NOT Modify + +#define FRAME_attak01 0 +#define FRAME_attak02 1 +#define FRAME_attak03 2 +#define FRAME_attak04 3 +#define FRAME_death101 4 +#define FRAME_death102 5 +#define FRAME_death103 6 +#define FRAME_death104 7 +#define FRAME_death105 8 +#define FRAME_death106 9 +#define FRAME_death107 10 +#define FRAME_death201 11 +#define FRAME_death202 12 +#define FRAME_death203 13 +#define FRAME_death204 14 +#define FRAME_death205 15 +#define FRAME_death206 16 +#define FRAME_death207 17 +#define FRAME_death208 18 +#define FRAME_death209 19 +#define FRAME_death210 20 +#define FRAME_death211 21 +#define FRAME_death212 22 +#define FRAME_death213 23 +#define FRAME_death301 24 +#define FRAME_death302 25 +#define FRAME_death303 26 +#define FRAME_death304 27 +#define FRAME_death305 28 +#define FRAME_death306 29 +#define FRAME_death307 30 +#define FRAME_death308 31 +#define FRAME_death309 32 +#define FRAME_death310 33 +#define FRAME_death311 34 +#define FRAME_death312 35 +#define FRAME_death313 36 +#define FRAME_death314 37 +#define FRAME_death315 38 +#define FRAME_flip01 39 +#define FRAME_flip02 40 +#define FRAME_flip03 41 +#define FRAME_flip04 42 +#define FRAME_flip05 43 +#define FRAME_flip06 44 +#define FRAME_flip07 45 +#define FRAME_flip08 46 +#define FRAME_flip09 47 +#define FRAME_flip10 48 +#define FRAME_flip11 49 +#define FRAME_flip12 50 +#define FRAME_flip13 51 +#define FRAME_flip14 52 +#define FRAME_grenad01 53 +#define FRAME_grenad02 54 +#define FRAME_grenad03 55 +#define FRAME_grenad04 56 +#define FRAME_grenad05 57 +#define FRAME_grenad06 58 +#define FRAME_grenad07 59 +#define FRAME_grenad08 60 +#define FRAME_grenad09 61 +#define FRAME_grenad10 62 +#define FRAME_grenad11 63 +#define FRAME_grenad12 64 +#define FRAME_grenad13 65 +#define FRAME_grenad14 66 +#define FRAME_grenad15 67 +#define FRAME_jump01 68 +#define FRAME_jump02 69 +#define FRAME_jump03 70 +#define FRAME_jump04 71 +#define FRAME_jump05 72 +#define FRAME_jump06 73 +#define FRAME_pain101 74 +#define FRAME_pain102 75 +#define FRAME_pain103 76 +#define FRAME_pain201 77 +#define FRAME_pain202 78 +#define FRAME_pain203 79 +#define FRAME_pain301 80 +#define FRAME_pain302 81 +#define FRAME_pain303 82 +#define FRAME_push01 83 +#define FRAME_push02 84 +#define FRAME_push03 85 +#define FRAME_push04 86 +#define FRAME_push05 87 +#define FRAME_push06 88 +#define FRAME_push07 89 +#define FRAME_push08 90 +#define FRAME_push09 91 +#define FRAME_run01 92 +#define FRAME_run02 93 +#define FRAME_run03 94 +#define FRAME_run04 95 +#define FRAME_run05 96 +#define FRAME_run06 97 +#define FRAME_run07 98 +#define FRAME_run08 99 +#define FRAME_run09 100 +#define FRAME_run10 101 +#define FRAME_run11 102 +#define FRAME_run12 103 +#define FRAME_runs01 104 +#define FRAME_runs02 105 +#define FRAME_runs03 106 +#define FRAME_runs04 107 +#define FRAME_runs05 108 +#define FRAME_runs06 109 +#define FRAME_runs07 110 +#define FRAME_runs08 111 +#define FRAME_runs09 112 +#define FRAME_runs10 113 +#define FRAME_runs11 114 +#define FRAME_runs12 115 +#define FRAME_salute01 116 +#define FRAME_salute02 117 +#define FRAME_salute03 118 +#define FRAME_salute04 119 +#define FRAME_salute05 120 +#define FRAME_salute06 121 +#define FRAME_salute07 122 +#define FRAME_salute08 123 +#define FRAME_salute09 124 +#define FRAME_salute10 125 +#define FRAME_salute11 126 +#define FRAME_salute12 127 +#define FRAME_stand101 128 +#define FRAME_stand102 129 +#define FRAME_stand103 130 +#define FRAME_stand104 131 +#define FRAME_stand105 132 +#define FRAME_stand106 133 +#define FRAME_stand107 134 +#define FRAME_stand108 135 +#define FRAME_stand109 136 +#define FRAME_stand110 137 +#define FRAME_stand111 138 +#define FRAME_stand112 139 +#define FRAME_stand113 140 +#define FRAME_stand114 141 +#define FRAME_stand115 142 +#define FRAME_stand116 143 +#define FRAME_stand117 144 +#define FRAME_stand118 145 +#define FRAME_stand119 146 +#define FRAME_stand120 147 +#define FRAME_stand121 148 +#define FRAME_stand122 149 +#define FRAME_stand123 150 +#define FRAME_stand124 151 +#define FRAME_stand125 152 +#define FRAME_stand126 153 +#define FRAME_stand127 154 +#define FRAME_stand128 155 +#define FRAME_stand129 156 +#define FRAME_stand130 157 +#define FRAME_stand131 158 +#define FRAME_stand132 159 +#define FRAME_stand133 160 +#define FRAME_stand134 161 +#define FRAME_stand135 162 +#define FRAME_stand136 163 +#define FRAME_stand137 164 +#define FRAME_stand138 165 +#define FRAME_stand139 166 +#define FRAME_stand140 167 +#define FRAME_stand201 168 +#define FRAME_stand202 169 +#define FRAME_stand203 170 +#define FRAME_stand204 171 +#define FRAME_stand205 172 +#define FRAME_stand206 173 +#define FRAME_stand207 174 +#define FRAME_stand208 175 +#define FRAME_stand209 176 +#define FRAME_stand210 177 +#define FRAME_stand211 178 +#define FRAME_stand212 179 +#define FRAME_stand213 180 +#define FRAME_stand214 181 +#define FRAME_stand215 182 +#define FRAME_stand216 183 +#define FRAME_stand217 184 +#define FRAME_stand218 185 +#define FRAME_stand219 186 +#define FRAME_stand220 187 +#define FRAME_stand221 188 +#define FRAME_stand222 189 +#define FRAME_stand223 190 +#define FRAME_swim01 191 +#define FRAME_swim02 192 +#define FRAME_swim03 193 +#define FRAME_swim04 194 +#define FRAME_swim05 195 +#define FRAME_swim06 196 +#define FRAME_swim07 197 +#define FRAME_swim08 198 +#define FRAME_swim09 199 +#define FRAME_swim10 200 +#define FRAME_swim11 201 +#define FRAME_swim12 202 +#define FRAME_sw_atk01 203 +#define FRAME_sw_atk02 204 +#define FRAME_sw_atk03 205 +#define FRAME_sw_atk04 206 +#define FRAME_sw_atk05 207 +#define FRAME_sw_atk06 208 +#define FRAME_sw_pan01 209 +#define FRAME_sw_pan02 210 +#define FRAME_sw_pan03 211 +#define FRAME_sw_pan04 212 +#define FRAME_sw_pan05 213 +#define FRAME_sw_std01 214 +#define FRAME_sw_std02 215 +#define FRAME_sw_std03 216 +#define FRAME_sw_std04 217 +#define FRAME_sw_std05 218 +#define FRAME_sw_std06 219 +#define FRAME_sw_std07 220 +#define FRAME_sw_std08 221 +#define FRAME_sw_std09 222 +#define FRAME_sw_std10 223 +#define FRAME_sw_std11 224 +#define FRAME_sw_std12 225 +#define FRAME_sw_std13 226 +#define FRAME_sw_std14 227 +#define FRAME_sw_std15 228 +#define FRAME_sw_std16 229 +#define FRAME_sw_std17 230 +#define FRAME_sw_std18 231 +#define FRAME_sw_std19 232 +#define FRAME_sw_std20 233 +#define FRAME_taunt01 234 +#define FRAME_taunt02 235 +#define FRAME_taunt03 236 +#define FRAME_taunt04 237 +#define FRAME_taunt05 238 +#define FRAME_taunt06 239 +#define FRAME_taunt07 240 +#define FRAME_taunt08 241 +#define FRAME_taunt09 242 +#define FRAME_taunt10 243 +#define FRAME_taunt11 244 +#define FRAME_taunt12 245 +#define FRAME_taunt13 246 +#define FRAME_taunt14 247 +#define FRAME_taunt15 248 +#define FRAME_taunt16 249 +#define FRAME_taunt17 250 +#define FRAME_walk01 251 +#define FRAME_walk02 252 +#define FRAME_walk03 253 +#define FRAME_walk04 254 +#define FRAME_walk05 255 +#define FRAME_walk06 256 +#define FRAME_walk07 257 +#define FRAME_walk08 258 +#define FRAME_walk09 259 +#define FRAME_walk10 260 +#define FRAME_walk11 261 +#define FRAME_wave01 262 +#define FRAME_wave02 263 +#define FRAME_wave03 264 +#define FRAME_wave04 265 +#define FRAME_wave05 266 +#define FRAME_wave06 267 +#define FRAME_wave07 268 +#define FRAME_wave08 269 +#define FRAME_wave09 270 +#define FRAME_wave10 271 +#define FRAME_wave11 272 +#define FRAME_wave12 273 +#define FRAME_wave13 274 +#define FRAME_wave14 275 +#define FRAME_wave15 276 +#define FRAME_wave16 277 +#define FRAME_wave17 278 +#define FRAME_wave18 279 +#define FRAME_wave19 280 +#define FRAME_wave20 281 +#define FRAME_wave21 282 +#define FRAME_bl_atk01 283 +#define FRAME_bl_atk02 284 +#define FRAME_bl_atk03 285 +#define FRAME_bl_atk04 286 +#define FRAME_bl_atk05 287 +#define FRAME_bl_atk06 288 +#define FRAME_bl_flp01 289 +#define FRAME_bl_flp02 290 +#define FRAME_bl_flp13 291 +#define FRAME_bl_flp14 292 +#define FRAME_bl_flp15 293 +#define FRAME_bl_jmp01 294 +#define FRAME_bl_jmp02 295 +#define FRAME_bl_jmp03 296 +#define FRAME_bl_jmp04 297 +#define FRAME_bl_jmp05 298 +#define FRAME_bl_jmp06 299 +#define FRAME_bl_pn101 300 +#define FRAME_bl_pn102 301 +#define FRAME_bl_pn103 302 +#define FRAME_bl_pn201 303 +#define FRAME_bl_pn202 304 +#define FRAME_bl_pn203 305 +#define FRAME_bl_pn301 306 +#define FRAME_bl_pn302 307 +#define FRAME_bl_pn303 308 +#define FRAME_bl_psh08 309 +#define FRAME_bl_psh09 310 +#define FRAME_bl_run01 311 +#define FRAME_bl_run02 312 +#define FRAME_bl_run03 313 +#define FRAME_bl_run04 314 +#define FRAME_bl_run05 315 +#define FRAME_bl_run06 316 +#define FRAME_bl_run07 317 +#define FRAME_bl_run08 318 +#define FRAME_bl_run09 319 +#define FRAME_bl_run10 320 +#define FRAME_bl_run11 321 +#define FRAME_bl_run12 322 +#define FRAME_bl_rns03 323 +#define FRAME_bl_rns04 324 +#define FRAME_bl_rns05 325 +#define FRAME_bl_rns06 326 +#define FRAME_bl_rns07 327 +#define FRAME_bl_rns08 328 +#define FRAME_bl_rns09 329 +#define FRAME_bl_sal10 330 +#define FRAME_bl_sal11 331 +#define FRAME_bl_sal12 332 +#define FRAME_bl_std01 333 +#define FRAME_bl_std02 334 +#define FRAME_bl_std03 335 +#define FRAME_bl_std04 336 +#define FRAME_bl_std05 337 +#define FRAME_bl_std06 338 +#define FRAME_bl_std07 339 +#define FRAME_bl_std08 340 +#define FRAME_bl_std09 341 +#define FRAME_bl_std10 342 +#define FRAME_bl_std11 343 +#define FRAME_bl_std12 344 +#define FRAME_bl_std13 345 +#define FRAME_bl_std14 346 +#define FRAME_bl_std15 347 +#define FRAME_bl_std16 348 +#define FRAME_bl_std17 349 +#define FRAME_bl_std18 350 +#define FRAME_bl_std19 351 +#define FRAME_bl_std20 352 +#define FRAME_bl_std21 353 +#define FRAME_bl_std22 354 +#define FRAME_bl_std23 355 +#define FRAME_bl_std24 356 +#define FRAME_bl_std25 357 +#define FRAME_bl_std26 358 +#define FRAME_bl_std27 359 +#define FRAME_bl_std28 360 +#define FRAME_bl_std29 361 +#define FRAME_bl_std30 362 +#define FRAME_bl_std31 363 +#define FRAME_bl_std32 364 +#define FRAME_bl_std33 365 +#define FRAME_bl_std34 366 +#define FRAME_bl_std35 367 +#define FRAME_bl_std36 368 +#define FRAME_bl_std37 369 +#define FRAME_bl_std38 370 +#define FRAME_bl_std39 371 +#define FRAME_bl_std40 372 +#define FRAME_bl_swm01 373 +#define FRAME_bl_swm02 374 +#define FRAME_bl_swm03 375 +#define FRAME_bl_swm04 376 +#define FRAME_bl_swm05 377 +#define FRAME_bl_swm06 378 +#define FRAME_bl_swm07 379 +#define FRAME_bl_swm08 380 +#define FRAME_bl_swm09 381 +#define FRAME_bl_swm10 382 +#define FRAME_bl_swm11 383 +#define FRAME_bl_swm12 384 +#define FRAME_bl_swk01 385 +#define FRAME_bl_swk02 386 +#define FRAME_bl_swk03 387 +#define FRAME_bl_swk04 388 +#define FRAME_bl_swk05 389 +#define FRAME_bl_swk06 390 +#define FRAME_bl_swp01 391 +#define FRAME_bl_swp02 392 +#define FRAME_bl_swp03 393 +#define FRAME_bl_swp04 394 +#define FRAME_bl_swp05 395 +#define FRAME_bl_sws01 396 +#define FRAME_bl_sws02 397 +#define FRAME_bl_sws03 398 +#define FRAME_bl_sws04 399 +#define FRAME_bl_sws05 400 +#define FRAME_bl_sws06 401 +#define FRAME_bl_sws07 402 +#define FRAME_bl_sws08 403 +#define FRAME_bl_sws09 404 +#define FRAME_bl_sws10 405 +#define FRAME_bl_sws11 406 +#define FRAME_bl_sws12 407 +#define FRAME_bl_sws13 408 +#define FRAME_bl_sws14 409 +#define FRAME_bl_tau14 410 +#define FRAME_bl_tau15 411 +#define FRAME_bl_tau16 412 +#define FRAME_bl_tau17 413 +#define FRAME_bl_wlk01 414 +#define FRAME_bl_wlk02 415 +#define FRAME_bl_wlk03 416 +#define FRAME_bl_wlk04 417 +#define FRAME_bl_wlk05 418 +#define FRAME_bl_wlk06 419 +#define FRAME_bl_wlk07 420 +#define FRAME_bl_wlk08 421 +#define FRAME_bl_wlk09 422 +#define FRAME_bl_wlk10 423 +#define FRAME_bl_wlk11 424 +#define FRAME_bl_wav19 425 +#define FRAME_bl_wav20 426 +#define FRAME_bl_wav21 427 +#define FRAME_cr_atk01 428 +#define FRAME_cr_atk02 429 +#define FRAME_cr_atk03 430 +#define FRAME_cr_atk04 431 +#define FRAME_cr_atk05 432 +#define FRAME_cr_atk06 433 +#define FRAME_cr_atk07 434 +#define FRAME_cr_atk08 435 +#define FRAME_cr_pan01 436 +#define FRAME_cr_pan02 437 +#define FRAME_cr_pan03 438 +#define FRAME_cr_pan04 439 +#define FRAME_cr_std01 440 +#define FRAME_cr_std02 441 +#define FRAME_cr_std03 442 +#define FRAME_cr_std04 443 +#define FRAME_cr_std05 444 +#define FRAME_cr_std06 445 +#define FRAME_cr_std07 446 +#define FRAME_cr_std08 447 +#define FRAME_cr_wlk01 448 +#define FRAME_cr_wlk02 449 +#define FRAME_cr_wlk03 450 +#define FRAME_cr_wlk04 451 +#define FRAME_cr_wlk05 452 +#define FRAME_cr_wlk06 453 +#define FRAME_cr_wlk07 454 +#define FRAME_crbl_a01 455 +#define FRAME_crbl_a02 456 +#define FRAME_crbl_a03 457 +#define FRAME_crbl_a04 458 +#define FRAME_crbl_a05 459 +#define FRAME_crbl_a06 460 +#define FRAME_crbl_a07 461 +#define FRAME_crbl_p01 462 +#define FRAME_crbl_p02 463 +#define FRAME_crbl_p03 464 +#define FRAME_crbl_p04 465 +#define FRAME_crbl_s01 466 +#define FRAME_crbl_s02 467 +#define FRAME_crbl_s03 468 +#define FRAME_crbl_s04 469 +#define FRAME_crbl_s05 470 +#define FRAME_crbl_s06 471 +#define FRAME_crbl_s07 472 +#define FRAME_crbl_s08 473 +#define FRAME_crbl_w01 474 +#define FRAME_crbl_w02 475 +#define FRAME_crbl_w03 476 +#define FRAME_crbl_w04 477 +#define FRAME_crbl_w05 478 +#define FRAME_crbl_w06 479 +#define FRAME_crbl_w07 480 + +#define MODEL_SCALE 1.000000 diff --git a/original/rogue/m_berserk.c b/original/rogue/m_berserk.c new file mode 100644 index 0000000..e27d996 --- /dev/null +++ b/original/rogue/m_berserk.c @@ -0,0 +1,560 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +/* +============================================================================== + +BERSERK + +============================================================================== +*/ + +#include "g_local.h" +#include "m_berserk.h" + + +static int sound_pain; +static int sound_die; +static int sound_idle; +static int sound_punch; +static int sound_sight; +static int sound_search; + +void berserk_sight (edict_t *self, edict_t *other) +{ + gi.sound (self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); +} + +void berserk_search (edict_t *self) +{ + gi.sound (self, CHAN_VOICE, sound_search, 1, ATTN_NORM, 0); +} + + +void berserk_fidget (edict_t *self); +mframe_t berserk_frames_stand [] = +{ + ai_stand, 0, berserk_fidget, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL +}; +mmove_t berserk_move_stand = {FRAME_stand1, FRAME_stand5, berserk_frames_stand, NULL}; + +void berserk_stand (edict_t *self) +{ + self->monsterinfo.currentmove = &berserk_move_stand; +} + +mframe_t berserk_frames_stand_fidget [] = +{ + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL +}; +mmove_t berserk_move_stand_fidget = {FRAME_standb1, FRAME_standb20, berserk_frames_stand_fidget, berserk_stand}; + +void berserk_fidget (edict_t *self) +{ + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + return; + if (random() > 0.15) + return; + + self->monsterinfo.currentmove = &berserk_move_stand_fidget; + gi.sound (self, CHAN_WEAPON, sound_idle, 1, ATTN_IDLE, 0); +} + + +mframe_t berserk_frames_walk [] = +{ + ai_walk, 9.1, NULL, + ai_walk, 6.3, NULL, + ai_walk, 4.9, NULL, + ai_walk, 6.7, NULL, + ai_walk, 6.0, NULL, + ai_walk, 8.2, NULL, + ai_walk, 7.2, NULL, + ai_walk, 6.1, NULL, + ai_walk, 4.9, NULL, + ai_walk, 4.7, NULL, + ai_walk, 4.7, NULL, + ai_walk, 4.8, NULL +}; +mmove_t berserk_move_walk = {FRAME_walkc1, FRAME_walkc11, berserk_frames_walk, NULL}; + +void berserk_walk (edict_t *self) +{ + self->monsterinfo.currentmove = &berserk_move_walk; +} + +/* + + ***************************** + SKIPPED THIS FOR NOW! + ***************************** + + Running -> Arm raised in air + +void() berserk_runb1 =[ $r_att1 , berserk_runb2 ] {ai_run(21);}; +void() berserk_runb2 =[ $r_att2 , berserk_runb3 ] {ai_run(11);}; +void() berserk_runb3 =[ $r_att3 , berserk_runb4 ] {ai_run(21);}; +void() berserk_runb4 =[ $r_att4 , berserk_runb5 ] {ai_run(25);}; +void() berserk_runb5 =[ $r_att5 , berserk_runb6 ] {ai_run(18);}; +void() berserk_runb6 =[ $r_att6 , berserk_runb7 ] {ai_run(19);}; +// running with arm in air : start loop +void() berserk_runb7 =[ $r_att7 , berserk_runb8 ] {ai_run(21);}; +void() berserk_runb8 =[ $r_att8 , berserk_runb9 ] {ai_run(11);}; +void() berserk_runb9 =[ $r_att9 , berserk_runb10 ] {ai_run(21);}; +void() berserk_runb10 =[ $r_att10 , berserk_runb11 ] {ai_run(25);}; +void() berserk_runb11 =[ $r_att11 , berserk_runb12 ] {ai_run(18);}; +void() berserk_runb12 =[ $r_att12 , berserk_runb7 ] {ai_run(19);}; +// running with arm in air : end loop +*/ + + +mframe_t berserk_frames_run1 [] = +{ + ai_run, 21, NULL, + ai_run, 11, NULL, + ai_run, 21, NULL, + // PMM .. from NULL + ai_run, 25, monster_done_dodge, + ai_run, 18, NULL, + ai_run, 19, NULL +}; +mmove_t berserk_move_run1 = {FRAME_run1, FRAME_run6, berserk_frames_run1, NULL}; + +void berserk_run (edict_t *self) +{ + monster_done_dodge (self); + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + self->monsterinfo.currentmove = &berserk_move_stand; + else + self->monsterinfo.currentmove = &berserk_move_run1; +} + + +void berserk_attack_spike (edict_t *self) +{ + static vec3_t aim = {MELEE_DISTANCE, 0, -24}; + fire_hit (self, aim, (15 + (rand() % 6)), 400); // Faster attack -- upwards and backwards +} + + +void berserk_swing (edict_t *self) +{ + gi.sound (self, CHAN_WEAPON, sound_punch, 1, ATTN_NORM, 0); +} + +mframe_t berserk_frames_attack_spike [] = +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, berserk_swing, + ai_charge, 0, berserk_attack_spike, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL +}; +mmove_t berserk_move_attack_spike = {FRAME_att_c1, FRAME_att_c8, berserk_frames_attack_spike, berserk_run}; + + +void berserk_attack_club (edict_t *self) +{ + vec3_t aim; + + VectorSet (aim, MELEE_DISTANCE, self->mins[0], -4); + fire_hit (self, aim, (5 + (rand() % 6)), 400); // Slower attack +} + +mframe_t berserk_frames_attack_club [] = +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, berserk_swing, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, berserk_attack_club, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL +}; +mmove_t berserk_move_attack_club = {FRAME_att_c9, FRAME_att_c20, berserk_frames_attack_club, berserk_run}; + + +void berserk_strike (edict_t *self) +{ + //FIXME play impact sound +} + + +mframe_t berserk_frames_attack_strike [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, berserk_swing, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, berserk_strike, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 9.7, NULL, + ai_move, 13.6, NULL +}; + +mmove_t berserk_move_attack_strike = {FRAME_att_c21, FRAME_att_c34, berserk_frames_attack_strike, berserk_run}; + + +void berserk_melee (edict_t *self) +{ + monster_done_dodge (self); + + if ((rand() % 2) == 0) + self->monsterinfo.currentmove = &berserk_move_attack_spike; + else + self->monsterinfo.currentmove = &berserk_move_attack_club; +} + + +/* +void() berserk_atke1 =[ $r_attb1, berserk_atke2 ] {ai_run(9);}; +void() berserk_atke2 =[ $r_attb2, berserk_atke3 ] {ai_run(6);}; +void() berserk_atke3 =[ $r_attb3, berserk_atke4 ] {ai_run(18.4);}; +void() berserk_atke4 =[ $r_attb4, berserk_atke5 ] {ai_run(25);}; +void() berserk_atke5 =[ $r_attb5, berserk_atke6 ] {ai_run(14);}; +void() berserk_atke6 =[ $r_attb6, berserk_atke7 ] {ai_run(20);}; +void() berserk_atke7 =[ $r_attb7, berserk_atke8 ] {ai_run(8.5);}; +void() berserk_atke8 =[ $r_attb8, berserk_atke9 ] {ai_run(3);}; +void() berserk_atke9 =[ $r_attb9, berserk_atke10 ] {ai_run(17.5);}; +void() berserk_atke10 =[ $r_attb10, berserk_atke11 ] {ai_run(17);}; +void() berserk_atke11 =[ $r_attb11, berserk_atke12 ] {ai_run(9);}; +void() berserk_atke12 =[ $r_attb12, berserk_atke13 ] {ai_run(25);}; +void() berserk_atke13 =[ $r_attb13, berserk_atke14 ] {ai_run(3.7);}; +void() berserk_atke14 =[ $r_attb14, berserk_atke15 ] {ai_run(2.6);}; +void() berserk_atke15 =[ $r_attb15, berserk_atke16 ] {ai_run(19);}; +void() berserk_atke16 =[ $r_attb16, berserk_atke17 ] {ai_run(25);}; +void() berserk_atke17 =[ $r_attb17, berserk_atke18 ] {ai_run(19.6);}; +void() berserk_atke18 =[ $r_attb18, berserk_run1 ] {ai_run(7.8);}; +*/ + + +mframe_t berserk_frames_pain1 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t berserk_move_pain1 = {FRAME_painc1, FRAME_painc4, berserk_frames_pain1, berserk_run}; + + +mframe_t berserk_frames_pain2 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t berserk_move_pain2 = {FRAME_painb1, FRAME_painb20, berserk_frames_pain2, berserk_run}; + +void berserk_pain (edict_t *self, edict_t *other, float kick, int damage) +{ + if (self->health < (self->max_health / 2)) + self->s.skinnum = 1; + + if (level.time < self->pain_debounce_time) + return; + + self->pain_debounce_time = level.time + 3; + gi.sound (self, CHAN_VOICE, sound_pain, 1, ATTN_NORM, 0); + + if (skill->value == 3) + return; // no pain anims in nightmare + + monster_done_dodge (self); + + if ((damage < 20) || (random() < 0.5)) + self->monsterinfo.currentmove = &berserk_move_pain1; + else + self->monsterinfo.currentmove = &berserk_move_pain2; +} + + +void berserk_dead (edict_t *self) +{ + VectorSet (self->mins, -16, -16, -24); + VectorSet (self->maxs, 16, 16, -8); + self->movetype = MOVETYPE_TOSS; + self->svflags |= SVF_DEADMONSTER; + self->nextthink = 0; + gi.linkentity (self); +} + + +mframe_t berserk_frames_death1 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL + +}; +mmove_t berserk_move_death1 = {FRAME_death1, FRAME_death13, berserk_frames_death1, berserk_dead}; + + +mframe_t berserk_frames_death2 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t berserk_move_death2 = {FRAME_deathc1, FRAME_deathc8, berserk_frames_death2, berserk_dead}; + + +void berserk_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + int n; + + if (self->health <= self->gib_health) + { + gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0); + for (n= 0; n < 2; n++) + ThrowGib (self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC); + for (n= 0; n < 4; n++) + ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); + ThrowHead (self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC); + self->deadflag = DEAD_DEAD; + return; + } + + if (self->deadflag == DEAD_DEAD) + return; + + gi.sound (self, CHAN_VOICE, sound_die, 1, ATTN_NORM, 0); + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_YES; + + if (damage >= 50) + self->monsterinfo.currentmove = &berserk_move_death1; + else + self->monsterinfo.currentmove = &berserk_move_death2; +} + +//=========== +//PGM +void berserk_jump_now (edict_t *self) +{ + vec3_t forward,up; + + monster_jump_start (self); + + AngleVectors (self->s.angles, forward, NULL, up); + VectorMA(self->velocity, 100, forward, self->velocity); + VectorMA(self->velocity, 300, up, self->velocity); +} + +void berserk_jump2_now (edict_t *self) +{ + vec3_t forward,up; + + monster_jump_start (self); + + AngleVectors (self->s.angles, forward, NULL, up); + VectorMA(self->velocity, 150, forward, self->velocity); + VectorMA(self->velocity, 400, up, self->velocity); +} + +void berserk_jump_wait_land (edict_t *self) +{ + if(self->groundentity == NULL) + { + self->monsterinfo.nextframe = self->s.frame; + + if(monster_jump_finished (self)) + self->monsterinfo.nextframe = self->s.frame + 1; + } + else + self->monsterinfo.nextframe = self->s.frame + 1; +} + +mframe_t berserk_frames_jump [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, berserk_jump_now, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, berserk_jump_wait_land, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t berserk_move_jump = { FRAME_jump1, FRAME_jump9, berserk_frames_jump, berserk_run }; + +mframe_t berserk_frames_jump2 [] = +{ + ai_move, -8, NULL, + ai_move, -4, NULL, + ai_move, -4, NULL, + ai_move, 0, berserk_jump_now, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, berserk_jump_wait_land, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t berserk_move_jump2 = { FRAME_jump1, FRAME_jump9, berserk_frames_jump2, berserk_run }; + +void berserk_jump (edict_t *self) +{ + if(!self->enemy) + return; + + monster_done_dodge (self); + + if(self->enemy->s.origin[2] > self->s.origin[2]) + self->monsterinfo.currentmove = &berserk_move_jump2; + else + self->monsterinfo.currentmove = &berserk_move_jump; +} + +qboolean berserk_blocked (edict_t *self, float dist) +{ + if(blocked_checkjump (self, dist, 256, 40)) + { + berserk_jump(self); + return true; + } + + if(blocked_checkplat (self, dist)) + return true; + + return false; +} +//PGM +//=========== + +void berserk_sidestep (edict_t *self) +{ + // if we're jumping, don't dodge + if ((self->monsterinfo.currentmove == &berserk_move_jump) || + (self->monsterinfo.currentmove == &berserk_move_jump2)) + { + return; + } + + // don't check for attack; the eta should suffice for melee monsters + + if (self->monsterinfo.currentmove != &berserk_move_run1) + self->monsterinfo.currentmove = &berserk_move_run1; +} + + +/*QUAKED monster_berserk (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight +*/ +void SP_monster_berserk (edict_t *self) +{ + if (deathmatch->value) + { + G_FreeEdict (self); + return; + } + + // pre-caches + sound_pain = gi.soundindex ("berserk/berpain2.wav"); + sound_die = gi.soundindex ("berserk/berdeth2.wav"); + sound_idle = gi.soundindex ("berserk/beridle1.wav"); + sound_punch = gi.soundindex ("berserk/attack.wav"); + sound_search = gi.soundindex ("berserk/bersrch1.wav"); + sound_sight = gi.soundindex ("berserk/sight.wav"); + + self->s.modelindex = gi.modelindex("models/monsters/berserk/tris.md2"); + VectorSet (self->mins, -16, -16, -24); + VectorSet (self->maxs, 16, 16, 32); + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + + self->health = 240; + self->gib_health = -60; + self->mass = 250; + + self->pain = berserk_pain; + self->die = berserk_die; + + self->monsterinfo.stand = berserk_stand; + self->monsterinfo.walk = berserk_walk; + self->monsterinfo.run = berserk_run; + // pmm +// self->monsterinfo.dodge = NULL; + self->monsterinfo.dodge = M_MonsterDodge; + self->monsterinfo.sidestep = berserk_sidestep; + // pmm + self->monsterinfo.attack = NULL; + self->monsterinfo.melee = berserk_melee; + self->monsterinfo.sight = berserk_sight; + self->monsterinfo.search = berserk_search; + self->monsterinfo.blocked = berserk_blocked; //PGM + + self->monsterinfo.currentmove = &berserk_move_stand; + self->monsterinfo.scale = MODEL_SCALE; + + gi.linkentity (self); + + walkmonster_start (self); +} diff --git a/original/rogue/m_berserk.h b/original/rogue/m_berserk.h new file mode 100644 index 0000000..7bc8b0e --- /dev/null +++ b/original/rogue/m_berserk.h @@ -0,0 +1,264 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// G:\quake2\baseq2\models/monsters/berserk + +// This file generated by ModelGen - Do NOT Modify + +#define FRAME_stand1 0 +#define FRAME_stand2 1 +#define FRAME_stand3 2 +#define FRAME_stand4 3 +#define FRAME_stand5 4 +#define FRAME_standb1 5 +#define FRAME_standb2 6 +#define FRAME_standb3 7 +#define FRAME_standb4 8 +#define FRAME_standb5 9 +#define FRAME_standb6 10 +#define FRAME_standb7 11 +#define FRAME_standb8 12 +#define FRAME_standb9 13 +#define FRAME_standb10 14 +#define FRAME_standb11 15 +#define FRAME_standb12 16 +#define FRAME_standb13 17 +#define FRAME_standb14 18 +#define FRAME_standb15 19 +#define FRAME_standb16 20 +#define FRAME_standb17 21 +#define FRAME_standb18 22 +#define FRAME_standb19 23 +#define FRAME_standb20 24 +#define FRAME_walkc1 25 +#define FRAME_walkc2 26 +#define FRAME_walkc3 27 +#define FRAME_walkc4 28 +#define FRAME_walkc5 29 +#define FRAME_walkc6 30 +#define FRAME_walkc7 31 +#define FRAME_walkc8 32 +#define FRAME_walkc9 33 +#define FRAME_walkc10 34 +#define FRAME_walkc11 35 +#define FRAME_run1 36 +#define FRAME_run2 37 +#define FRAME_run3 38 +#define FRAME_run4 39 +#define FRAME_run5 40 +#define FRAME_run6 41 +#define FRAME_att_a1 42 +#define FRAME_att_a2 43 +#define FRAME_att_a3 44 +#define FRAME_att_a4 45 +#define FRAME_att_a5 46 +#define FRAME_att_a6 47 +#define FRAME_att_a7 48 +#define FRAME_att_a8 49 +#define FRAME_att_a9 50 +#define FRAME_att_a10 51 +#define FRAME_att_a11 52 +#define FRAME_att_a12 53 +#define FRAME_att_a13 54 +#define FRAME_att_b1 55 +#define FRAME_att_b2 56 +#define FRAME_att_b3 57 +#define FRAME_att_b4 58 +#define FRAME_att_b5 59 +#define FRAME_att_b6 60 +#define FRAME_att_b7 61 +#define FRAME_att_b8 62 +#define FRAME_att_b9 63 +#define FRAME_att_b10 64 +#define FRAME_att_b11 65 +#define FRAME_att_b12 66 +#define FRAME_att_b13 67 +#define FRAME_att_b14 68 +#define FRAME_att_b15 69 +#define FRAME_att_b16 70 +#define FRAME_att_b17 71 +#define FRAME_att_b18 72 +#define FRAME_att_b19 73 +#define FRAME_att_b20 74 +#define FRAME_att_b21 75 +#define FRAME_att_c1 76 +#define FRAME_att_c2 77 +#define FRAME_att_c3 78 +#define FRAME_att_c4 79 +#define FRAME_att_c5 80 +#define FRAME_att_c6 81 +#define FRAME_att_c7 82 +#define FRAME_att_c8 83 +#define FRAME_att_c9 84 +#define FRAME_att_c10 85 +#define FRAME_att_c11 86 +#define FRAME_att_c12 87 +#define FRAME_att_c13 88 +#define FRAME_att_c14 89 +#define FRAME_att_c15 90 +#define FRAME_att_c16 91 +#define FRAME_att_c17 92 +#define FRAME_att_c18 93 +#define FRAME_att_c19 94 +#define FRAME_att_c20 95 +#define FRAME_att_c21 96 +#define FRAME_att_c22 97 +#define FRAME_att_c23 98 +#define FRAME_att_c24 99 +#define FRAME_att_c25 100 +#define FRAME_att_c26 101 +#define FRAME_att_c27 102 +#define FRAME_att_c28 103 +#define FRAME_att_c29 104 +#define FRAME_att_c30 105 +#define FRAME_att_c31 106 +#define FRAME_att_c32 107 +#define FRAME_att_c33 108 +#define FRAME_att_c34 109 +#define FRAME_r_att1 110 +#define FRAME_r_att2 111 +#define FRAME_r_att3 112 +#define FRAME_r_att4 113 +#define FRAME_r_att5 114 +#define FRAME_r_att6 115 +#define FRAME_r_att7 116 +#define FRAME_r_att8 117 +#define FRAME_r_att9 118 +#define FRAME_r_att10 119 +#define FRAME_r_att11 120 +#define FRAME_r_att12 121 +#define FRAME_r_att13 122 +#define FRAME_r_att14 123 +#define FRAME_r_att15 124 +#define FRAME_r_att16 125 +#define FRAME_r_att17 126 +#define FRAME_r_att18 127 +#define FRAME_r_attb1 128 +#define FRAME_r_attb2 129 +#define FRAME_r_attb3 130 +#define FRAME_r_attb4 131 +#define FRAME_r_attb5 132 +#define FRAME_r_attb6 133 +#define FRAME_r_attb7 134 +#define FRAME_r_attb8 135 +#define FRAME_r_attb9 136 +#define FRAME_r_attb10 137 +#define FRAME_r_attb11 138 +#define FRAME_r_attb12 139 +#define FRAME_r_attb13 140 +#define FRAME_r_attb14 141 +#define FRAME_r_attb15 142 +#define FRAME_r_attb16 143 +#define FRAME_r_attb17 144 +#define FRAME_r_attb18 145 +#define FRAME_slam1 146 +#define FRAME_slam2 147 +#define FRAME_slam3 148 +#define FRAME_slam4 149 +#define FRAME_slam5 150 +#define FRAME_slam6 151 +#define FRAME_slam7 152 +#define FRAME_slam8 153 +#define FRAME_slam9 154 +#define FRAME_slam10 155 +#define FRAME_slam11 156 +#define FRAME_slam12 157 +#define FRAME_slam13 158 +#define FRAME_slam14 159 +#define FRAME_slam15 160 +#define FRAME_slam16 161 +#define FRAME_slam17 162 +#define FRAME_slam18 163 +#define FRAME_slam19 164 +#define FRAME_slam20 165 +#define FRAME_slam21 166 +#define FRAME_slam22 167 +#define FRAME_slam23 168 +#define FRAME_duck1 169 +#define FRAME_duck2 170 +#define FRAME_duck3 171 +#define FRAME_duck4 172 +#define FRAME_duck5 173 +#define FRAME_duck6 174 +#define FRAME_duck7 175 +#define FRAME_duck8 176 +#define FRAME_duck9 177 +#define FRAME_duck10 178 +#define FRAME_fall1 179 +#define FRAME_fall2 180 +#define FRAME_fall3 181 +#define FRAME_fall4 182 +#define FRAME_fall5 183 +#define FRAME_fall6 184 +#define FRAME_fall7 185 +#define FRAME_fall8 186 +#define FRAME_fall9 187 +#define FRAME_fall10 188 +#define FRAME_fall11 189 +#define FRAME_fall12 190 +#define FRAME_fall13 191 +#define FRAME_fall14 192 +#define FRAME_fall15 193 +#define FRAME_fall16 194 +#define FRAME_fall17 195 +#define FRAME_fall18 196 +#define FRAME_fall19 197 +#define FRAME_fall20 198 +#define FRAME_painc1 199 +#define FRAME_painc2 200 +#define FRAME_painc3 201 +#define FRAME_painc4 202 +#define FRAME_painb1 203 +#define FRAME_painb2 204 +#define FRAME_painb3 205 +#define FRAME_painb4 206 +#define FRAME_painb5 207 +#define FRAME_painb6 208 +#define FRAME_painb7 209 +#define FRAME_painb8 210 +#define FRAME_painb9 211 +#define FRAME_painb10 212 +#define FRAME_painb11 213 +#define FRAME_painb12 214 +#define FRAME_painb13 215 +#define FRAME_painb14 216 +#define FRAME_painb15 217 +#define FRAME_painb16 218 +#define FRAME_painb17 219 +#define FRAME_painb18 220 +#define FRAME_painb19 221 +#define FRAME_painb20 222 +#define FRAME_death1 223 +#define FRAME_death2 224 +#define FRAME_death3 225 +#define FRAME_death4 226 +#define FRAME_death5 227 +#define FRAME_death6 228 +#define FRAME_death7 229 +#define FRAME_death8 230 +#define FRAME_death9 231 +#define FRAME_death10 232 +#define FRAME_death11 233 +#define FRAME_death12 234 +#define FRAME_death13 235 +#define FRAME_deathc1 236 +#define FRAME_deathc2 237 +#define FRAME_deathc3 238 +#define FRAME_deathc4 239 +#define FRAME_deathc5 240 +#define FRAME_deathc6 241 +#define FRAME_deathc7 242 +#define FRAME_deathc8 243 + +//PGM +#define FRAME_jump1 244 +#define FRAME_jump2 245 +#define FRAME_jump3 246 +#define FRAME_jump4 247 +#define FRAME_jump5 248 +#define FRAME_jump6 249 +#define FRAME_jump7 250 +#define FRAME_jump8 251 +#define FRAME_jump9 252 +//PGM + +#define MODEL_SCALE 1.000000 diff --git a/original/rogue/m_boss2.c b/original/rogue/m_boss2.c new file mode 100644 index 0000000..0f354a2 --- /dev/null +++ b/original/rogue/m_boss2.c @@ -0,0 +1,777 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +/* +============================================================================== + +boss2 + +============================================================================== +*/ + +#include "g_local.h" +#include "m_boss2.h" + +void BossExplode (edict_t *self); + +qboolean infront (edict_t *self, edict_t *other); + +static int sound_pain1; +static int sound_pain2; +static int sound_pain3; +static int sound_death; +static int sound_search1; + +void boss2_search (edict_t *self) +{ + if (random() < 0.5) + gi.sound (self, CHAN_VOICE, sound_search1, 1, ATTN_NONE, 0); +} + +void boss2_run (edict_t *self); +void boss2_stand (edict_t *self); +void boss2_dead (edict_t *self); +void boss2_attack (edict_t *self); +void boss2_attack_mg (edict_t *self); +void boss2_reattack_mg (edict_t *self); +void boss2_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point); + +#define BOSS2_ROCKET_SPEED 750 + +void Boss2PredictiveRocket (edict_t *self) +{ + vec3_t forward, right; + vec3_t start; + vec3_t dir; + vec3_t vec; + float time, dist; + +gi.dprintf("predictive fire\n"); + + AngleVectors (self->s.angles, forward, right, NULL); + +//1 + G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_BOSS2_ROCKET_1], forward, right, start); + VectorSubtract(self->enemy->s.origin, start, dir); +// dir[2] += self->enemy->viewheight; + dist = VectorLength(dir); + time = dist / BOSS2_ROCKET_SPEED; + VectorMA(self->enemy->s.origin, time-0.3, self->enemy->velocity, vec); + +// VectorCopy (self->enemy->s.origin, vec); +// vec[2] += self->enemy->viewheight; + VectorSubtract (vec, start, dir); + VectorNormalize (dir); + monster_fire_rocket (self, start, dir, 50, BOSS2_ROCKET_SPEED, MZ2_BOSS2_ROCKET_1); + +//2 + G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_BOSS2_ROCKET_2], forward, right, start); + VectorSubtract(self->enemy->s.origin, start, dir); +// dir[2] += self->enemy->viewheight; + dist = VectorLength(dir); + time = dist / BOSS2_ROCKET_SPEED; + VectorMA(self->enemy->s.origin, time-0.15, self->enemy->velocity, vec); + +// VectorCopy (self->enemy->s.origin, vec); +// vec[2] += self->enemy->viewheight; + VectorSubtract (vec, start, dir); + VectorNormalize (dir); + monster_fire_rocket (self, start, dir, 50, BOSS2_ROCKET_SPEED, MZ2_BOSS2_ROCKET_2); + +//3 + G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_BOSS2_ROCKET_3], forward, right, start); + VectorSubtract(self->enemy->s.origin, start, dir); +// dir[2] += self->enemy->viewheight; + dist = VectorLength(dir); + time = dist / BOSS2_ROCKET_SPEED; + VectorMA(self->enemy->s.origin, time, self->enemy->velocity, vec); + +// VectorCopy (self->enemy->s.origin, vec); +// vec[2] += self->enemy->viewheight; + VectorSubtract (vec, start, dir); + VectorNormalize (dir); + monster_fire_rocket (self, start, dir, 50, BOSS2_ROCKET_SPEED, MZ2_BOSS2_ROCKET_3); + +//4 + G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_BOSS2_ROCKET_4], forward, right, start); + VectorSubtract(self->enemy->s.origin, start, dir); +// dir[2] += self->enemy->viewheight; + dist = VectorLength(dir); + time = dist / BOSS2_ROCKET_SPEED; + VectorMA(self->enemy->s.origin, time+0.15, self->enemy->velocity, vec); + +// VectorCopy (self->enemy->s.origin, vec); +// vec[2] += self->enemy->viewheight; + VectorSubtract (vec, start, dir); + VectorNormalize (dir); + monster_fire_rocket (self, start, dir, 50, BOSS2_ROCKET_SPEED, MZ2_BOSS2_ROCKET_4); +} + +void Boss2Rocket (edict_t *self) +{ + vec3_t forward, right; + vec3_t start; + vec3_t dir; + vec3_t vec; + + if(self->enemy) + { + if(self->enemy->client && random() < 0.9) + { + Boss2PredictiveRocket(self); + return; + } + } + + AngleVectors (self->s.angles, forward, right, NULL); + +//1 + G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_BOSS2_ROCKET_1], forward, right, start); + VectorCopy (self->enemy->s.origin, vec); +// vec[2] += self->enemy->viewheight; + vec[2] -= 15; + VectorSubtract (vec, start, dir); + VectorNormalize (dir); + VectorMA (dir, 0.4, right, dir); + VectorNormalize (dir); + monster_fire_rocket (self, start, dir, 50, 500, MZ2_BOSS2_ROCKET_1); + +//2 + G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_BOSS2_ROCKET_2], forward, right, start); + VectorCopy (self->enemy->s.origin, vec); +// vec[2] += self->enemy->viewheight; + VectorSubtract (vec, start, dir); + VectorNormalize (dir); + VectorMA (dir, 0.025, right, dir); + VectorNormalize (dir); + monster_fire_rocket (self, start, dir, 50, 500, MZ2_BOSS2_ROCKET_2); + +//3 + G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_BOSS2_ROCKET_3], forward, right, start); + VectorCopy (self->enemy->s.origin, vec); +// vec[2] += self->enemy->viewheight; + VectorSubtract (vec, start, dir); + VectorNormalize (dir); + VectorMA (dir, -0.025, right, dir); + VectorNormalize (dir); + monster_fire_rocket (self, start, dir, 50, 500, MZ2_BOSS2_ROCKET_3); + +//4 + G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_BOSS2_ROCKET_4], forward, right, start); + VectorCopy (self->enemy->s.origin, vec); +// vec[2] += self->enemy->viewheight; + vec[2] -= 15; + VectorSubtract (vec, start, dir); + VectorNormalize (dir); + VectorMA (dir, -0.4, right, dir); + VectorNormalize (dir); + monster_fire_rocket (self, start, dir, 50, 500, MZ2_BOSS2_ROCKET_4); + +//5 +// G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_BOSS2_ROCKET_4], forward, right, start); +// VectorCopy (self->enemy->s.origin, vec); +// vec[2] += self->enemy->viewheight; +// VectorSubtract (vec, start, dir); +// VectorNormalize (dir); +// monster_fire_rocket (self, start, dir, 50, 500, MZ2_BOSS2_ROCKET_2); +} + +void boss2_firebullet_right (edict_t *self) +{ + vec3_t forward, right, target; + vec3_t start; + + AngleVectors (self->s.angles, forward, right, NULL); + G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_BOSS2_MACHINEGUN_R1], forward, right, start); + +// VectorMA (self->enemy->s.origin, -0.2, self->enemy->velocity, target); + VectorMA (self->enemy->s.origin, 0.2, self->enemy->velocity, target); + target[2] += self->enemy->viewheight; + VectorSubtract (target, start, forward); + VectorNormalize (forward); + + monster_fire_bullet (self, start, forward, 6, 4, DEFAULT_BULLET_HSPREAD*3, DEFAULT_BULLET_VSPREAD, MZ2_BOSS2_MACHINEGUN_R1); +// monster_fire_bullet (self, start, forward, 6, 4, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, MZ2_BOSS2_MACHINEGUN_R1); +} + +void boss2_firebullet_left (edict_t *self) +{ + vec3_t forward, right, target; + vec3_t start; + + AngleVectors (self->s.angles, forward, right, NULL); + G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_BOSS2_MACHINEGUN_L1], forward, right, start); + +// VectorMA (self->enemy->s.origin, 0.2, self->enemy->velocity, target); + VectorMA (self->enemy->s.origin, -0.2, self->enemy->velocity, target); + + target[2] += self->enemy->viewheight; + VectorSubtract (target, start, forward); + VectorNormalize (forward); + + monster_fire_bullet (self, start, forward, 6, 4, DEFAULT_BULLET_HSPREAD*3, DEFAULT_BULLET_VSPREAD, MZ2_BOSS2_MACHINEGUN_L1); +// monster_fire_bullet (self, start, forward, 6, 4, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, MZ2_BOSS2_MACHINEGUN_L1); +} + +void Boss2MachineGun (edict_t *self) +{ +/* vec3_t forward, right; + vec3_t start; + vec3_t dir; + vec3_t vec; + int flash_number; + + AngleVectors (self->s.angles, forward, right, NULL); + + flash_number = MZ2_BOSS2_MACHINEGUN_1 + (self->s.frame - FRAME_attack10); + G_ProjectSource (self->s.origin, monster_flash_offset[flash_number], forward, right, start); + + VectorCopy (self->enemy->s.origin, vec); + vec[2] += self->enemy->viewheight; + VectorSubtract (vec, start, dir); + VectorNormalize (dir); + monster_fire_bullet (self, start, dir, 3, 4, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, flash_number); +*/ + boss2_firebullet_left(self); + boss2_firebullet_right(self); +} + + +mframe_t boss2_frames_stand [] = +{ + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL +}; +mmove_t boss2_move_stand = {FRAME_stand30, FRAME_stand50, boss2_frames_stand, NULL}; + +mframe_t boss2_frames_fidget [] = +{ + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL +}; +mmove_t boss2_move_fidget = {FRAME_stand1, FRAME_stand30, boss2_frames_fidget, NULL}; + +mframe_t boss2_frames_walk [] = +{ + /* PMM - normally, this is all 8's .. I upped it */ + ai_walk, 10, NULL, + ai_walk, 10, NULL, + ai_walk, 10, NULL, + ai_walk, 10, NULL, + ai_walk, 10, NULL, + ai_walk, 10, NULL, + ai_walk, 10, NULL, + ai_walk, 10, NULL, + ai_walk, 10, NULL, + ai_walk, 10, NULL, + ai_walk, 10, NULL, + ai_walk, 10, NULL, + ai_walk, 10, NULL, + ai_walk, 10, NULL, + ai_walk, 10, NULL, + ai_walk, 10, NULL, + ai_walk, 10, NULL, + ai_walk, 10, NULL, + ai_walk, 10, NULL, + ai_walk, 10, NULL +}; +mmove_t boss2_move_walk = {FRAME_walk1, FRAME_walk20, boss2_frames_walk, NULL}; + + +mframe_t boss2_frames_run [] = +{ + /* PMM - normally, this is all 8's .. I upped it */ + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL +}; +mmove_t boss2_move_run = {FRAME_walk1, FRAME_walk20, boss2_frames_run, NULL}; + +mframe_t boss2_frames_attack_pre_mg [] = +{ + /* used to be all 1's .. what a slow guy */ + ai_charge, 2, NULL, + ai_charge, 2, NULL, + ai_charge, 2, NULL, + ai_charge, 2, NULL, + ai_charge, 2, NULL, + ai_charge, 2, NULL, + ai_charge, 2, NULL, + ai_charge, 2, NULL, + ai_charge, 2, boss2_attack_mg +}; +mmove_t boss2_move_attack_pre_mg = {FRAME_attack1, FRAME_attack9, boss2_frames_attack_pre_mg, NULL}; + + +// Loop this +mframe_t boss2_frames_attack_mg [] = +{ + /* used to be all 1's .. what a slow guy */ + ai_charge, 2, Boss2MachineGun, + ai_charge, 2, Boss2MachineGun, + ai_charge, 2, Boss2MachineGun, + ai_charge, 2, Boss2MachineGun, + ai_charge, 2, Boss2MachineGun, + ai_charge, 2, boss2_reattack_mg +}; +mmove_t boss2_move_attack_mg = {FRAME_attack10, FRAME_attack15, boss2_frames_attack_mg, NULL}; + +mframe_t boss2_frames_attack_post_mg [] = +{ + /* used to be all 1's .. what a slow guy */ + ai_charge, 2, NULL, + ai_charge, 2, NULL, + ai_charge, 2, NULL, + ai_charge, 2, NULL +}; +mmove_t boss2_move_attack_post_mg = {FRAME_attack16, FRAME_attack19, boss2_frames_attack_post_mg, boss2_run}; + +mframe_t boss2_frames_attack_rocket [] = +{ + /* used to be all 1's .. except the Boss2Rocket frame, which was -20(!) */ + ai_charge, 2, NULL, + ai_charge, 2, NULL, + ai_charge, 2, NULL, + ai_charge, 2, NULL, + ai_charge, 2, NULL, + ai_charge, 2, NULL, + ai_charge, 2, NULL, + ai_charge, 2, NULL, + ai_charge, 2, NULL, + ai_charge, 2, NULL, + ai_charge, 2, NULL, + ai_charge, 2, NULL, + ai_move, -5, Boss2Rocket, + ai_charge, 2, NULL, + ai_charge, 2, NULL, + ai_charge, 2, NULL, + ai_charge, 2, NULL, + ai_charge, 2, NULL, + ai_charge, 2, NULL, + ai_charge, 2, NULL, + ai_charge, 2, NULL +}; +mmove_t boss2_move_attack_rocket = {FRAME_attack20, FRAME_attack40, boss2_frames_attack_rocket, boss2_run}; + +mframe_t boss2_frames_pain_heavy [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t boss2_move_pain_heavy = {FRAME_pain2, FRAME_pain19, boss2_frames_pain_heavy, boss2_run}; + +mframe_t boss2_frames_pain_light [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t boss2_move_pain_light = {FRAME_pain20, FRAME_pain23, boss2_frames_pain_light, boss2_run}; + +mframe_t boss2_frames_death [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, BossExplode +}; +mmove_t boss2_move_death = {FRAME_death2, FRAME_death50, boss2_frames_death, boss2_dead}; + +void boss2_stand (edict_t *self) +{ + self->monsterinfo.currentmove = &boss2_move_stand; +} + +void boss2_run (edict_t *self) +{ + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + self->monsterinfo.currentmove = &boss2_move_stand; + else + self->monsterinfo.currentmove = &boss2_move_run; +} + +void boss2_walk (edict_t *self) +{ + self->monsterinfo.currentmove = &boss2_move_walk; +} + +void boss2_attack (edict_t *self) +{ + vec3_t vec; + float range; + + VectorSubtract (self->enemy->s.origin, self->s.origin, vec); + range = VectorLength (vec); + + if (range <= 125) + { + self->monsterinfo.currentmove = &boss2_move_attack_pre_mg; + } + else + { + if (random() <= 0.6) + self->monsterinfo.currentmove = &boss2_move_attack_pre_mg; + else + self->monsterinfo.currentmove = &boss2_move_attack_rocket; + } +} + +void boss2_attack_mg (edict_t *self) +{ + self->monsterinfo.currentmove = &boss2_move_attack_mg; +} + +void boss2_reattack_mg (edict_t *self) +{ + if ( infront(self, self->enemy) ) + if (random() <= 0.7) + self->monsterinfo.currentmove = &boss2_move_attack_mg; + else + self->monsterinfo.currentmove = &boss2_move_attack_post_mg; + else + self->monsterinfo.currentmove = &boss2_move_attack_post_mg; +} + + +void boss2_pain (edict_t *self, edict_t *other, float kick, int damage) +{ + if (self->health < (self->max_health / 2)) + self->s.skinnum = 1; + + if (level.time < self->pain_debounce_time) + return; + + self->pain_debounce_time = level.time + 3; +// American wanted these at no attenuation + if (damage < 10) + { + gi.sound (self, CHAN_VOICE, sound_pain3, 1, ATTN_NONE, 0); + self->monsterinfo.currentmove = &boss2_move_pain_light; + } + else if (damage < 30) + { + gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NONE, 0); + self->monsterinfo.currentmove = &boss2_move_pain_light; + } + else + { + gi.sound (self, CHAN_VOICE, sound_pain2, 1, ATTN_NONE, 0); + self->monsterinfo.currentmove = &boss2_move_pain_heavy; + } +} + +void boss2_dead (edict_t *self) +{ + VectorSet (self->mins, -56, -56, 0); + VectorSet (self->maxs, 56, 56, 80); + self->movetype = MOVETYPE_TOSS; + self->svflags |= SVF_DEADMONSTER; + self->nextthink = 0; + gi.linkentity (self); +} + +void boss2_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + gi.sound (self, CHAN_VOICE, sound_death, 1, ATTN_NONE, 0); + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_NO; + self->count = 0; + self->monsterinfo.currentmove = &boss2_move_death; +#if 0 + int n; + + self->s.sound = 0; + // check for gib + if (self->health <= self->gib_health) + { + gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0); + for (n= 0; n < 2; n++) + ThrowGib (self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC); + for (n= 0; n < 4; n++) + ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); + ThrowHead (self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC); + self->deadflag = DEAD_DEAD; + return; + } + + if (self->deadflag == DEAD_DEAD) + return; + + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_YES; + self->monsterinfo.currentmove = &boss2_move_death; +#endif +} + +qboolean Boss2_CheckAttack (edict_t *self) +{ + vec3_t spot1, spot2; + vec3_t temp; + float chance; + trace_t tr; + qboolean enemy_infront; + int enemy_range; + float enemy_yaw; + + if (self->enemy->health > 0) + { + // see if any entities are in the way of the shot + VectorCopy (self->s.origin, spot1); + spot1[2] += self->viewheight; + VectorCopy (self->enemy->s.origin, spot2); + spot2[2] += self->enemy->viewheight; + + tr = gi.trace (spot1, NULL, NULL, spot2, self, CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_SLIME|CONTENTS_LAVA); + + // do we have a clear shot? + if (tr.ent != self->enemy) + { + // PGM - we want them to go ahead and shoot at info_notnulls if they can. + if(self->enemy->solid != SOLID_NOT || tr.fraction < 1.0) //PGM + return false; + } + } + + enemy_infront = infront(self, self->enemy); + enemy_range = range(self, self->enemy); + VectorSubtract (self->enemy->s.origin, self->s.origin, temp); + enemy_yaw = vectoyaw(temp); + + self->ideal_yaw = enemy_yaw; + + + // melee attack + if (enemy_range == RANGE_MELEE) + { + if (self->monsterinfo.melee) + self->monsterinfo.attack_state = AS_MELEE; + else + self->monsterinfo.attack_state = AS_MISSILE; + return true; + } + +// missile attack + if (!self->monsterinfo.attack) + return false; + + if (level.time < self->monsterinfo.attack_finished) + return false; + + if (enemy_range == RANGE_FAR) + return false; + + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + { + chance = 0.4; + } + else if (enemy_range == RANGE_MELEE) + { + chance = 0.8; + } + else if (enemy_range == RANGE_NEAR) + { + chance = 0.8; + } + else if (enemy_range == RANGE_MID) + { + chance = 0.8; + } + else + { + return false; + } + + // PGM - go ahead and shoot every time if it's a info_notnull + if ((random () < chance) || (self->enemy->solid == SOLID_NOT)) + { + self->monsterinfo.attack_state = AS_MISSILE; + self->monsterinfo.attack_finished = level.time + 2*random(); + return true; + } + + if (self->flags & FL_FLY) + { + if (random() < 0.3) + self->monsterinfo.attack_state = AS_SLIDING; + else + self->monsterinfo.attack_state = AS_STRAIGHT; + } + + return false; +} + + + +/*QUAKED monster_boss2 (1 .5 0) (-56 -56 0) (56 56 80) Ambush Trigger_Spawn Sight +*/ +void SP_monster_boss2 (edict_t *self) +{ + if (deathmatch->value) + { + G_FreeEdict (self); + return; + } + + sound_pain1 = gi.soundindex ("bosshovr/bhvpain1.wav"); + sound_pain2 = gi.soundindex ("bosshovr/bhvpain2.wav"); + sound_pain3 = gi.soundindex ("bosshovr/bhvpain3.wav"); + sound_death = gi.soundindex ("bosshovr/bhvdeth1.wav"); + sound_search1 = gi.soundindex ("bosshovr/bhvunqv1.wav"); + + self->s.sound = gi.soundindex ("bosshovr/bhvengn1.wav"); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex ("models/monsters/boss2/tris.md2"); + VectorSet (self->mins, -56, -56, 0); + VectorSet (self->maxs, 56, 56, 80); + + self->health = 2000; + self->gib_health = -200; + self->mass = 1000; + + self->yaw_speed = 50; + + self->flags |= FL_IMMUNE_LASER; + + self->pain = boss2_pain; + self->die = boss2_die; + + self->monsterinfo.stand = boss2_stand; + self->monsterinfo.walk = boss2_walk; + self->monsterinfo.run = boss2_run; + self->monsterinfo.attack = boss2_attack; + self->monsterinfo.search = boss2_search; + self->monsterinfo.checkattack = Boss2_CheckAttack; + gi.linkentity (self); + + self->monsterinfo.currentmove = &boss2_move_stand; + self->monsterinfo.scale = MODEL_SCALE; + + flymonster_start (self); +} diff --git a/original/rogue/m_boss2.h b/original/rogue/m_boss2.h new file mode 100644 index 0000000..9951203 --- /dev/null +++ b/original/rogue/m_boss2.h @@ -0,0 +1,189 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// G:\quake2\baseq2\models/monsters/boss2 + +// This file generated by ModelGen - Do NOT Modify + +#define FRAME_stand30 0 +#define FRAME_stand31 1 +#define FRAME_stand32 2 +#define FRAME_stand33 3 +#define FRAME_stand34 4 +#define FRAME_stand35 5 +#define FRAME_stand36 6 +#define FRAME_stand37 7 +#define FRAME_stand38 8 +#define FRAME_stand39 9 +#define FRAME_stand40 10 +#define FRAME_stand41 11 +#define FRAME_stand42 12 +#define FRAME_stand43 13 +#define FRAME_stand44 14 +#define FRAME_stand45 15 +#define FRAME_stand46 16 +#define FRAME_stand47 17 +#define FRAME_stand48 18 +#define FRAME_stand49 19 +#define FRAME_stand50 20 +#define FRAME_stand1 21 +#define FRAME_stand2 22 +#define FRAME_stand3 23 +#define FRAME_stand4 24 +#define FRAME_stand5 25 +#define FRAME_stand6 26 +#define FRAME_stand7 27 +#define FRAME_stand8 28 +#define FRAME_stand9 29 +#define FRAME_stand10 30 +#define FRAME_stand11 31 +#define FRAME_stand12 32 +#define FRAME_stand13 33 +#define FRAME_stand14 34 +#define FRAME_stand15 35 +#define FRAME_stand16 36 +#define FRAME_stand17 37 +#define FRAME_stand18 38 +#define FRAME_stand19 39 +#define FRAME_stand20 40 +#define FRAME_stand21 41 +#define FRAME_stand22 42 +#define FRAME_stand23 43 +#define FRAME_stand24 44 +#define FRAME_stand25 45 +#define FRAME_stand26 46 +#define FRAME_stand27 47 +#define FRAME_stand28 48 +#define FRAME_stand29 49 +#define FRAME_walk1 50 +#define FRAME_walk2 51 +#define FRAME_walk3 52 +#define FRAME_walk4 53 +#define FRAME_walk5 54 +#define FRAME_walk6 55 +#define FRAME_walk7 56 +#define FRAME_walk8 57 +#define FRAME_walk9 58 +#define FRAME_walk10 59 +#define FRAME_walk11 60 +#define FRAME_walk12 61 +#define FRAME_walk13 62 +#define FRAME_walk14 63 +#define FRAME_walk15 64 +#define FRAME_walk16 65 +#define FRAME_walk17 66 +#define FRAME_walk18 67 +#define FRAME_walk19 68 +#define FRAME_walk20 69 +#define FRAME_attack1 70 +#define FRAME_attack2 71 +#define FRAME_attack3 72 +#define FRAME_attack4 73 +#define FRAME_attack5 74 +#define FRAME_attack6 75 +#define FRAME_attack7 76 +#define FRAME_attack8 77 +#define FRAME_attack9 78 +#define FRAME_attack10 79 +#define FRAME_attack11 80 +#define FRAME_attack12 81 +#define FRAME_attack13 82 +#define FRAME_attack14 83 +#define FRAME_attack15 84 +#define FRAME_attack16 85 +#define FRAME_attack17 86 +#define FRAME_attack18 87 +#define FRAME_attack19 88 +#define FRAME_attack20 89 +#define FRAME_attack21 90 +#define FRAME_attack22 91 +#define FRAME_attack23 92 +#define FRAME_attack24 93 +#define FRAME_attack25 94 +#define FRAME_attack26 95 +#define FRAME_attack27 96 +#define FRAME_attack28 97 +#define FRAME_attack29 98 +#define FRAME_attack30 99 +#define FRAME_attack31 100 +#define FRAME_attack32 101 +#define FRAME_attack33 102 +#define FRAME_attack34 103 +#define FRAME_attack35 104 +#define FRAME_attack36 105 +#define FRAME_attack37 106 +#define FRAME_attack38 107 +#define FRAME_attack39 108 +#define FRAME_attack40 109 +#define FRAME_pain2 110 +#define FRAME_pain3 111 +#define FRAME_pain4 112 +#define FRAME_pain5 113 +#define FRAME_pain6 114 +#define FRAME_pain7 115 +#define FRAME_pain8 116 +#define FRAME_pain9 117 +#define FRAME_pain10 118 +#define FRAME_pain11 119 +#define FRAME_pain12 120 +#define FRAME_pain13 121 +#define FRAME_pain14 122 +#define FRAME_pain15 123 +#define FRAME_pain16 124 +#define FRAME_pain17 125 +#define FRAME_pain18 126 +#define FRAME_pain19 127 +#define FRAME_pain20 128 +#define FRAME_pain21 129 +#define FRAME_pain22 130 +#define FRAME_pain23 131 +#define FRAME_death2 132 +#define FRAME_death3 133 +#define FRAME_death4 134 +#define FRAME_death5 135 +#define FRAME_death6 136 +#define FRAME_death7 137 +#define FRAME_death8 138 +#define FRAME_death9 139 +#define FRAME_death10 140 +#define FRAME_death11 141 +#define FRAME_death12 142 +#define FRAME_death13 143 +#define FRAME_death14 144 +#define FRAME_death15 145 +#define FRAME_death16 146 +#define FRAME_death17 147 +#define FRAME_death18 148 +#define FRAME_death19 149 +#define FRAME_death20 150 +#define FRAME_death21 151 +#define FRAME_death22 152 +#define FRAME_death23 153 +#define FRAME_death24 154 +#define FRAME_death25 155 +#define FRAME_death26 156 +#define FRAME_death27 157 +#define FRAME_death28 158 +#define FRAME_death29 159 +#define FRAME_death30 160 +#define FRAME_death31 161 +#define FRAME_death32 162 +#define FRAME_death33 163 +#define FRAME_death34 164 +#define FRAME_death35 165 +#define FRAME_death36 166 +#define FRAME_death37 167 +#define FRAME_death38 168 +#define FRAME_death39 169 +#define FRAME_death40 170 +#define FRAME_death41 171 +#define FRAME_death42 172 +#define FRAME_death43 173 +#define FRAME_death44 174 +#define FRAME_death45 175 +#define FRAME_death46 176 +#define FRAME_death47 177 +#define FRAME_death48 178 +#define FRAME_death49 179 +#define FRAME_death50 180 + +#define MODEL_SCALE 1.000000 diff --git a/original/rogue/m_boss3.c b/original/rogue/m_boss3.c new file mode 100644 index 0000000..22d2df1 --- /dev/null +++ b/original/rogue/m_boss3.c @@ -0,0 +1,59 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +/* +============================================================================== + +boss3 + +============================================================================== +*/ + +#include "g_local.h" +#include "m_boss32.h" + +void Use_Boss3 (edict_t *ent, edict_t *other, edict_t *activator) +{ + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_BOSSTPORT); + gi.WritePosition (ent->s.origin); + gi.multicast (ent->s.origin, MULTICAST_PVS); + G_FreeEdict (ent); +} + +void Think_Boss3Stand (edict_t *ent) +{ + if (ent->s.frame == FRAME_stand260) + ent->s.frame = FRAME_stand201; + else + ent->s.frame++; + ent->nextthink = level.time + FRAMETIME; +} + +/*QUAKED monster_boss3_stand (1 .5 0) (-32 -32 0) (32 32 90) + +Just stands and cycles in one place until targeted, then teleports away. +*/ +void SP_monster_boss3_stand (edict_t *self) +{ + if (deathmatch->value) + { + G_FreeEdict (self); + return; + } + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->model = "models/monsters/boss3/rider/tris.md2"; + self->s.modelindex = gi.modelindex (self->model); + self->s.frame = FRAME_stand201; + + gi.soundindex ("misc/bigtele.wav"); + + VectorSet (self->mins, -32, -32, 0); + VectorSet (self->maxs, 32, 32, 90); + + self->use = Use_Boss3; + self->think = Think_Boss3Stand; + self->nextthink = level.time + FRAMETIME; + gi.linkentity (self); +} diff --git a/original/rogue/m_boss31.c b/original/rogue/m_boss31.c new file mode 100644 index 0000000..9dc1483 --- /dev/null +++ b/original/rogue/m_boss31.c @@ -0,0 +1,736 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +/* +============================================================================== + +jorg + +============================================================================== +*/ + +#include "g_local.h" +#include "m_boss31.h" + +extern SP_monster_makron (edict_t *self); +qboolean visible (edict_t *self, edict_t *other); + +static int sound_pain1; +static int sound_pain2; +static int sound_pain3; +static int sound_idle; +static int sound_death; +static int sound_search1; +static int sound_search2; +static int sound_search3; +static int sound_attack1; +static int sound_attack2; +static int sound_firegun; +static int sound_step_left; +static int sound_step_right; +static int sound_death_hit; + +void BossExplode (edict_t *self); +void MakronToss (edict_t *self); + + +void jorg_search (edict_t *self) +{ + float r; + + r = random(); + + if (r <= 0.3) + gi.sound (self, CHAN_VOICE, sound_search1, 1, ATTN_NORM, 0); + else if (r <= 0.6) + gi.sound (self, CHAN_VOICE, sound_search2, 1, ATTN_NORM, 0); + else + gi.sound (self, CHAN_VOICE, sound_search3, 1, ATTN_NORM, 0); +} + + +void jorg_dead (edict_t *self); +void jorgBFG (edict_t *self); +void jorgMachineGun (edict_t *self); +void jorg_firebullet (edict_t *self); +void jorg_reattack1(edict_t *self); +void jorg_attack1(edict_t *self); +void jorg_idle(edict_t *self); +void jorg_step_left(edict_t *self); +void jorg_step_right(edict_t *self); +void jorg_death_hit(edict_t *self); + +// +// stand +// + +mframe_t jorg_frames_stand []= +{ + ai_stand, 0, jorg_idle, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, // 10 + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, // 20 + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, // 30 + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 19, NULL, + ai_stand, 11, jorg_step_left, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 6, NULL, + ai_stand, 9, jorg_step_right, + ai_stand, 0, NULL, // 40 + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, -2, NULL, + ai_stand, -17, jorg_step_left, + ai_stand, 0, NULL, + ai_stand, -12, NULL, // 50 + ai_stand, -14, jorg_step_right // 51 +}; +mmove_t jorg_move_stand = {FRAME_stand01, FRAME_stand51, jorg_frames_stand, NULL}; + +void jorg_idle (edict_t *self) +{ + gi.sound (self, CHAN_VOICE, sound_idle, 1, ATTN_NORM,0); +} + +void jorg_death_hit (edict_t *self) +{ + gi.sound (self, CHAN_BODY, sound_death_hit, 1, ATTN_NORM,0); +} + + +void jorg_step_left (edict_t *self) +{ + gi.sound (self, CHAN_BODY, sound_step_left, 1, ATTN_NORM,0); +} + +void jorg_step_right (edict_t *self) +{ + gi.sound (self, CHAN_BODY, sound_step_right, 1, ATTN_NORM,0); +} + + +void jorg_stand (edict_t *self) +{ + self->monsterinfo.currentmove = &jorg_move_stand; +} + +mframe_t jorg_frames_run [] = +{ + ai_run, 17, jorg_step_left, + ai_run, 0, NULL, + ai_run, 0, NULL, + ai_run, 0, NULL, + ai_run, 12, NULL, + ai_run, 8, NULL, + ai_run, 10, NULL, + ai_run, 33, jorg_step_right, + ai_run, 0, NULL, + ai_run, 0, NULL, + ai_run, 0, NULL, + ai_run, 9, NULL, + ai_run, 9, NULL, + ai_run, 9, NULL +}; +mmove_t jorg_move_run = {FRAME_walk06, FRAME_walk19, jorg_frames_run, NULL}; + +// +// walk +// + +mframe_t jorg_frames_start_walk [] = +{ + ai_walk, 5, NULL, + ai_walk, 6, NULL, + ai_walk, 7, NULL, + ai_walk, 9, NULL, + ai_walk, 15, NULL +}; +mmove_t jorg_move_start_walk = {FRAME_walk01, FRAME_walk05, jorg_frames_start_walk, NULL}; + +mframe_t jorg_frames_walk [] = +{ + ai_walk, 17, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 12, NULL, + ai_walk, 8, NULL, + ai_walk, 10, NULL, + ai_walk, 33, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 9, NULL, + ai_walk, 9, NULL, + ai_walk, 9, NULL +}; +mmove_t jorg_move_walk = {FRAME_walk06, FRAME_walk19, jorg_frames_walk, NULL}; + +mframe_t jorg_frames_end_walk [] = +{ + ai_walk, 11, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 8, NULL, + ai_walk, -8, NULL +}; +mmove_t jorg_move_end_walk = {FRAME_walk20, FRAME_walk25, jorg_frames_end_walk, NULL}; + +void jorg_walk (edict_t *self) +{ + self->monsterinfo.currentmove = &jorg_move_walk; +} + +void jorg_run (edict_t *self) +{ + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + self->monsterinfo.currentmove = &jorg_move_stand; + else + self->monsterinfo.currentmove = &jorg_move_run; +} + +mframe_t jorg_frames_pain3 [] = +{ + ai_move, -28, NULL, + ai_move, -6, NULL, + ai_move, -3, jorg_step_left, + ai_move, -9, NULL, + ai_move, 0, jorg_step_right, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, -7, NULL, + ai_move, 1, NULL, + ai_move, -11, NULL, + ai_move, -4, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 10, NULL, + ai_move, 11, NULL, + ai_move, 0, NULL, + ai_move, 10, NULL, + ai_move, 3, NULL, + ai_move, 10, NULL, + ai_move, 7, jorg_step_left, + ai_move, 17, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, jorg_step_right +}; +mmove_t jorg_move_pain3 = {FRAME_pain301, FRAME_pain325, jorg_frames_pain3, jorg_run}; + +mframe_t jorg_frames_pain2 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t jorg_move_pain2 = {FRAME_pain201, FRAME_pain203, jorg_frames_pain2, jorg_run}; + +mframe_t jorg_frames_pain1 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t jorg_move_pain1 = {FRAME_pain101, FRAME_pain103, jorg_frames_pain1, jorg_run}; + +mframe_t jorg_frames_death1 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, // 10 + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, // 20 + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, // 30 + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, // 40 + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, MakronToss, + ai_move, 0, BossExplode // 50 +}; +mmove_t jorg_move_death = {FRAME_death01, FRAME_death50, jorg_frames_death1, jorg_dead}; + +mframe_t jorg_frames_attack2 []= +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, jorgBFG, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t jorg_move_attack2 = {FRAME_attak201, FRAME_attak213, jorg_frames_attack2, jorg_run}; + +mframe_t jorg_frames_start_attack1 [] = +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL +}; +mmove_t jorg_move_start_attack1 = {FRAME_attak101, FRAME_attak108, jorg_frames_start_attack1, jorg_attack1}; + +mframe_t jorg_frames_attack1[]= +{ + ai_charge, 0, jorg_firebullet, + ai_charge, 0, jorg_firebullet, + ai_charge, 0, jorg_firebullet, + ai_charge, 0, jorg_firebullet, + ai_charge, 0, jorg_firebullet, + ai_charge, 0, jorg_firebullet +}; +mmove_t jorg_move_attack1 = {FRAME_attak109, FRAME_attak114, jorg_frames_attack1, jorg_reattack1}; + +mframe_t jorg_frames_end_attack1[]= +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t jorg_move_end_attack1 = {FRAME_attak115, FRAME_attak118, jorg_frames_end_attack1, jorg_run}; + +void jorg_reattack1(edict_t *self) +{ + if (visible(self, self->enemy)) + if (random() < 0.9) + self->monsterinfo.currentmove = &jorg_move_attack1; + else + { + self->s.sound = 0; + self->monsterinfo.currentmove = &jorg_move_end_attack1; + } + else + { + self->s.sound = 0; + self->monsterinfo.currentmove = &jorg_move_end_attack1; + } +} + +void jorg_attack1(edict_t *self) +{ + self->monsterinfo.currentmove = &jorg_move_attack1; +} + +void jorg_pain (edict_t *self, edict_t *other, float kick, int damage) +{ + + if (self->health < (self->max_health / 2)) + self->s.skinnum = 1; + + self->s.sound = 0; + + if (level.time < self->pain_debounce_time) + return; + + // Lessen the chance of him going into his pain frames if he takes little damage + if (damage <= 40) + if (random()<=0.6) + return; + + /* + If he's entering his attack1 or using attack1, lessen the chance of him + going into pain + */ + + if ( (self->s.frame >= FRAME_attak101) && (self->s.frame <= FRAME_attak108) ) + if (random() <= 0.005) + return; + + if ( (self->s.frame >= FRAME_attak109) && (self->s.frame <= FRAME_attak114) ) + if (random() <= 0.00005) + return; + + + if ( (self->s.frame >= FRAME_attak201) && (self->s.frame <= FRAME_attak208) ) + if (random() <= 0.005) + return; + + + self->pain_debounce_time = level.time + 3; + if (skill->value == 3) + return; // no pain anims in nightmare + + if (damage <= 50) + { + gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM,0); + self->monsterinfo.currentmove = &jorg_move_pain1; + } + else if (damage <= 100) + { + gi.sound (self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM,0); + self->monsterinfo.currentmove = &jorg_move_pain2; + } + else + { + if (random() <= 0.3) + { + gi.sound (self, CHAN_VOICE, sound_pain3, 1, ATTN_NORM,0); + self->monsterinfo.currentmove = &jorg_move_pain3; + } + } +}; + +void jorgBFG (edict_t *self) +{ + vec3_t forward, right; + vec3_t start; + vec3_t dir; + vec3_t vec; + + AngleVectors (self->s.angles, forward, right, NULL); + G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_JORG_BFG_1], forward, right, start); + + VectorCopy (self->enemy->s.origin, vec); + vec[2] += self->enemy->viewheight; + VectorSubtract (vec, start, dir); + VectorNormalize (dir); + gi.sound (self, CHAN_VOICE, sound_attack2, 1, ATTN_NORM, 0); + /*void monster_fire_bfg (edict_t *self, + vec3_t start, + vec3_t aimdir, + int damage, + int speed, + int kick, + float damage_radius, + int flashtype)*/ + monster_fire_bfg (self, start, dir, 50, 300, 100, 200, MZ2_JORG_BFG_1); +} + +void jorg_firebullet_right (edict_t *self) +{ + vec3_t forward, right, target; + vec3_t start; + + AngleVectors (self->s.angles, forward, right, NULL); + G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_JORG_MACHINEGUN_R1], forward, right, start); + + VectorMA (self->enemy->s.origin, -0.2, self->enemy->velocity, target); + target[2] += self->enemy->viewheight; + VectorSubtract (target, start, forward); + VectorNormalize (forward); + + monster_fire_bullet (self, start, forward, 6, 4, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, MZ2_JORG_MACHINEGUN_R1); +} + +void jorg_firebullet_left (edict_t *self) +{ + vec3_t forward, right, target; + vec3_t start; + + AngleVectors (self->s.angles, forward, right, NULL); + G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_JORG_MACHINEGUN_L1], forward, right, start); + + VectorMA (self->enemy->s.origin, -0.2, self->enemy->velocity, target); + target[2] += self->enemy->viewheight; + VectorSubtract (target, start, forward); + VectorNormalize (forward); + + monster_fire_bullet (self, start, forward, 6, 4, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, MZ2_JORG_MACHINEGUN_L1); +} + +void jorg_firebullet (edict_t *self) +{ + jorg_firebullet_left(self); + jorg_firebullet_right(self); +}; + +void jorg_attack(edict_t *self) +{ + vec3_t vec; + float range; + + VectorSubtract (self->enemy->s.origin, self->s.origin, vec); + range = VectorLength (vec); + + if (random() <= 0.75) + { + gi.sound (self, CHAN_VOICE, sound_attack1, 1, ATTN_NORM,0); + self->s.sound = gi.soundindex ("boss3/w_loop.wav"); + self->monsterinfo.currentmove = &jorg_move_start_attack1; + } + else + { + gi.sound (self, CHAN_VOICE, sound_attack2, 1, ATTN_NORM,0); + self->monsterinfo.currentmove = &jorg_move_attack2; + } +} + +void jorg_dead (edict_t *self) +{ +#if 0 + edict_t *tempent; + /* + VectorSet (self->mins, -16, -16, -24); + VectorSet (self->maxs, 16, 16, -8); + */ + + // Jorg is on modelindex2. Do not clear him. + VectorSet (self->mins, -60, -60, 0); + VectorSet (self->maxs, 60, 60, 72); + self->movetype = MOVETYPE_TOSS; + self->nextthink = 0; + gi.linkentity (self); + + tempent = G_Spawn(); + VectorCopy (self->s.origin, tempent->s.origin); + VectorCopy (self->s.angles, tempent->s.angles); + tempent->killtarget = self->killtarget; + tempent->target = self->target; + tempent->activator = self->enemy; + self->killtarget = 0; + self->target = 0; + SP_monster_makron (tempent); +#endif +} + + +void jorg_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + gi.sound (self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0); + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_NO; + self->s.sound = 0; + self->count = 0; + self->monsterinfo.currentmove = &jorg_move_death; +} + +qboolean Jorg_CheckAttack (edict_t *self) +{ + vec3_t spot1, spot2; + vec3_t temp; + float chance; + trace_t tr; + qboolean enemy_infront; + int enemy_range; + float enemy_yaw; + + if (self->enemy->health > 0) + { + // see if any entities are in the way of the shot + VectorCopy (self->s.origin, spot1); + spot1[2] += self->viewheight; + VectorCopy (self->enemy->s.origin, spot2); + spot2[2] += self->enemy->viewheight; + + tr = gi.trace (spot1, NULL, NULL, spot2, self, CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_SLIME|CONTENTS_LAVA); + + // do we have a clear shot? + if (tr.ent != self->enemy) + return false; + } + + enemy_infront = infront(self, self->enemy); + enemy_range = range(self, self->enemy); + VectorSubtract (self->enemy->s.origin, self->s.origin, temp); + enemy_yaw = vectoyaw(temp); + + self->ideal_yaw = enemy_yaw; + + + // melee attack + if (enemy_range == RANGE_MELEE) + { + if (self->monsterinfo.melee) + self->monsterinfo.attack_state = AS_MELEE; + else + self->monsterinfo.attack_state = AS_MISSILE; + return true; + } + +// missile attack + if (!self->monsterinfo.attack) + return false; + + if (level.time < self->monsterinfo.attack_finished) + return false; + + if (enemy_range == RANGE_FAR) + return false; + + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + { + chance = 0.4; + } + else if (enemy_range == RANGE_MELEE) + { + chance = 0.8; + } + else if (enemy_range == RANGE_NEAR) + { + chance = 0.4; + } + else if (enemy_range == RANGE_MID) + { + chance = 0.2; + } + else + { + return false; + } + + if (random () < chance) + { + self->monsterinfo.attack_state = AS_MISSILE; + self->monsterinfo.attack_finished = level.time + 2*random(); + return true; + } + + if (self->flags & FL_FLY) + { + if (random() < 0.3) + self->monsterinfo.attack_state = AS_SLIDING; + else + self->monsterinfo.attack_state = AS_STRAIGHT; + } + + return false; +} + + +void MakronPrecache (void); + +/*QUAKED monster_jorg (1 .5 0) (-80 -80 0) (90 90 140) Ambush Trigger_Spawn Sight +*/ +void SP_monster_jorg (edict_t *self) +{ + if (deathmatch->value) + { + G_FreeEdict (self); + return; + } + + sound_pain1 = gi.soundindex ("boss3/bs3pain1.wav"); + sound_pain2 = gi.soundindex ("boss3/bs3pain2.wav"); + sound_pain3 = gi.soundindex ("boss3/bs3pain3.wav"); + sound_death = gi.soundindex ("boss3/bs3deth1.wav"); + sound_attack1 = gi.soundindex ("boss3/bs3atck1.wav"); + sound_attack2 = gi.soundindex ("boss3/bs3atck2.wav"); + sound_search1 = gi.soundindex ("boss3/bs3srch1.wav"); + sound_search2 = gi.soundindex ("boss3/bs3srch2.wav"); + sound_search3 = gi.soundindex ("boss3/bs3srch3.wav"); + sound_idle = gi.soundindex ("boss3/bs3idle1.wav"); + sound_step_left = gi.soundindex ("boss3/step1.wav"); + sound_step_right = gi.soundindex ("boss3/step2.wav"); + sound_firegun = gi.soundindex ("boss3/xfire.wav"); + sound_death_hit = gi.soundindex ("boss3/d_hit.wav"); + + MakronPrecache (); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex ("models/monsters/boss3/rider/tris.md2"); + self->s.modelindex2 = gi.modelindex ("models/monsters/boss3/jorg/tris.md2"); + VectorSet (self->mins, -80, -80, 0); + VectorSet (self->maxs, 80, 80, 140); + + self->health = 3000; + self->gib_health = -2000; + self->mass = 1000; + + self->pain = jorg_pain; + self->die = jorg_die; + self->monsterinfo.stand = jorg_stand; + self->monsterinfo.walk = jorg_walk; + self->monsterinfo.run = jorg_run; + self->monsterinfo.dodge = NULL; + self->monsterinfo.attack = jorg_attack; + self->monsterinfo.search = jorg_search; + self->monsterinfo.melee = NULL; + self->monsterinfo.sight = NULL; + self->monsterinfo.checkattack = Jorg_CheckAttack; + gi.linkentity (self); + + self->monsterinfo.currentmove = &jorg_move_stand; + self->monsterinfo.scale = MODEL_SCALE; + + walkmonster_start(self); + //PMM + self->monsterinfo.aiflags |= AI_IGNORE_SHOTS; + //pmm + +} diff --git a/original/rogue/m_boss31.h b/original/rogue/m_boss31.h new file mode 100644 index 0000000..e950987 --- /dev/null +++ b/original/rogue/m_boss31.h @@ -0,0 +1,196 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// G:\quake2\baseq2\models/monsters/boss3/jorg + +// This file generated by ModelGen - Do NOT Modify + +#define FRAME_attak101 0 +#define FRAME_attak102 1 +#define FRAME_attak103 2 +#define FRAME_attak104 3 +#define FRAME_attak105 4 +#define FRAME_attak106 5 +#define FRAME_attak107 6 +#define FRAME_attak108 7 +#define FRAME_attak109 8 +#define FRAME_attak110 9 +#define FRAME_attak111 10 +#define FRAME_attak112 11 +#define FRAME_attak113 12 +#define FRAME_attak114 13 +#define FRAME_attak115 14 +#define FRAME_attak116 15 +#define FRAME_attak117 16 +#define FRAME_attak118 17 +#define FRAME_attak201 18 +#define FRAME_attak202 19 +#define FRAME_attak203 20 +#define FRAME_attak204 21 +#define FRAME_attak205 22 +#define FRAME_attak206 23 +#define FRAME_attak207 24 +#define FRAME_attak208 25 +#define FRAME_attak209 26 +#define FRAME_attak210 27 +#define FRAME_attak211 28 +#define FRAME_attak212 29 +#define FRAME_attak213 30 +#define FRAME_death01 31 +#define FRAME_death02 32 +#define FRAME_death03 33 +#define FRAME_death04 34 +#define FRAME_death05 35 +#define FRAME_death06 36 +#define FRAME_death07 37 +#define FRAME_death08 38 +#define FRAME_death09 39 +#define FRAME_death10 40 +#define FRAME_death11 41 +#define FRAME_death12 42 +#define FRAME_death13 43 +#define FRAME_death14 44 +#define FRAME_death15 45 +#define FRAME_death16 46 +#define FRAME_death17 47 +#define FRAME_death18 48 +#define FRAME_death19 49 +#define FRAME_death20 50 +#define FRAME_death21 51 +#define FRAME_death22 52 +#define FRAME_death23 53 +#define FRAME_death24 54 +#define FRAME_death25 55 +#define FRAME_death26 56 +#define FRAME_death27 57 +#define FRAME_death28 58 +#define FRAME_death29 59 +#define FRAME_death30 60 +#define FRAME_death31 61 +#define FRAME_death32 62 +#define FRAME_death33 63 +#define FRAME_death34 64 +#define FRAME_death35 65 +#define FRAME_death36 66 +#define FRAME_death37 67 +#define FRAME_death38 68 +#define FRAME_death39 69 +#define FRAME_death40 70 +#define FRAME_death41 71 +#define FRAME_death42 72 +#define FRAME_death43 73 +#define FRAME_death44 74 +#define FRAME_death45 75 +#define FRAME_death46 76 +#define FRAME_death47 77 +#define FRAME_death48 78 +#define FRAME_death49 79 +#define FRAME_death50 80 +#define FRAME_pain101 81 +#define FRAME_pain102 82 +#define FRAME_pain103 83 +#define FRAME_pain201 84 +#define FRAME_pain202 85 +#define FRAME_pain203 86 +#define FRAME_pain301 87 +#define FRAME_pain302 88 +#define FRAME_pain303 89 +#define FRAME_pain304 90 +#define FRAME_pain305 91 +#define FRAME_pain306 92 +#define FRAME_pain307 93 +#define FRAME_pain308 94 +#define FRAME_pain309 95 +#define FRAME_pain310 96 +#define FRAME_pain311 97 +#define FRAME_pain312 98 +#define FRAME_pain313 99 +#define FRAME_pain314 100 +#define FRAME_pain315 101 +#define FRAME_pain316 102 +#define FRAME_pain317 103 +#define FRAME_pain318 104 +#define FRAME_pain319 105 +#define FRAME_pain320 106 +#define FRAME_pain321 107 +#define FRAME_pain322 108 +#define FRAME_pain323 109 +#define FRAME_pain324 110 +#define FRAME_pain325 111 +#define FRAME_stand01 112 +#define FRAME_stand02 113 +#define FRAME_stand03 114 +#define FRAME_stand04 115 +#define FRAME_stand05 116 +#define FRAME_stand06 117 +#define FRAME_stand07 118 +#define FRAME_stand08 119 +#define FRAME_stand09 120 +#define FRAME_stand10 121 +#define FRAME_stand11 122 +#define FRAME_stand12 123 +#define FRAME_stand13 124 +#define FRAME_stand14 125 +#define FRAME_stand15 126 +#define FRAME_stand16 127 +#define FRAME_stand17 128 +#define FRAME_stand18 129 +#define FRAME_stand19 130 +#define FRAME_stand20 131 +#define FRAME_stand21 132 +#define FRAME_stand22 133 +#define FRAME_stand23 134 +#define FRAME_stand24 135 +#define FRAME_stand25 136 +#define FRAME_stand26 137 +#define FRAME_stand27 138 +#define FRAME_stand28 139 +#define FRAME_stand29 140 +#define FRAME_stand30 141 +#define FRAME_stand31 142 +#define FRAME_stand32 143 +#define FRAME_stand33 144 +#define FRAME_stand34 145 +#define FRAME_stand35 146 +#define FRAME_stand36 147 +#define FRAME_stand37 148 +#define FRAME_stand38 149 +#define FRAME_stand39 150 +#define FRAME_stand40 151 +#define FRAME_stand41 152 +#define FRAME_stand42 153 +#define FRAME_stand43 154 +#define FRAME_stand44 155 +#define FRAME_stand45 156 +#define FRAME_stand46 157 +#define FRAME_stand47 158 +#define FRAME_stand48 159 +#define FRAME_stand49 160 +#define FRAME_stand50 161 +#define FRAME_stand51 162 +#define FRAME_walk01 163 +#define FRAME_walk02 164 +#define FRAME_walk03 165 +#define FRAME_walk04 166 +#define FRAME_walk05 167 +#define FRAME_walk06 168 +#define FRAME_walk07 169 +#define FRAME_walk08 170 +#define FRAME_walk09 171 +#define FRAME_walk10 172 +#define FRAME_walk11 173 +#define FRAME_walk12 174 +#define FRAME_walk13 175 +#define FRAME_walk14 176 +#define FRAME_walk15 177 +#define FRAME_walk16 178 +#define FRAME_walk17 179 +#define FRAME_walk18 180 +#define FRAME_walk19 181 +#define FRAME_walk20 182 +#define FRAME_walk21 183 +#define FRAME_walk22 184 +#define FRAME_walk23 185 +#define FRAME_walk24 186 +#define FRAME_walk25 187 + +#define MODEL_SCALE 1.000000 diff --git a/original/rogue/m_boss32.c b/original/rogue/m_boss32.c new file mode 100644 index 0000000..e6e97fe --- /dev/null +++ b/original/rogue/m_boss32.c @@ -0,0 +1,900 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +/* +============================================================================== + +Makron -- Final Boss + +============================================================================== +*/ + +#include "g_local.h" +#include "m_boss32.h" + +qboolean visible (edict_t *self, edict_t *other); + +void MakronRailgun (edict_t *self); +void MakronSaveloc (edict_t *self); +void MakronHyperblaster (edict_t *self); +void makron_step_left (edict_t *self); +void makron_step_right (edict_t *self); +void makronBFG (edict_t *self); +void makron_dead (edict_t *self); + +static int sound_pain4; +static int sound_pain5; +static int sound_pain6; +static int sound_death; +static int sound_step_left; +static int sound_step_right; +static int sound_attack_bfg; +static int sound_brainsplorch; +static int sound_prerailgun; +static int sound_popup; +static int sound_taunt1; +static int sound_taunt2; +static int sound_taunt3; +static int sound_hit; + +void makron_taunt (edict_t *self) +{ + float r; + + r=random(); + if (r <= 0.3) + gi.sound (self, CHAN_AUTO, sound_taunt1, 1, ATTN_NONE, 0); + else if (r <= 0.6) + gi.sound (self, CHAN_AUTO, sound_taunt2, 1, ATTN_NONE, 0); + else + gi.sound (self, CHAN_AUTO, sound_taunt3, 1, ATTN_NONE, 0); +} + +// +// stand +// + +mframe_t makron_frames_stand []= +{ + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, // 10 + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, // 20 + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, // 30 + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, // 40 + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, // 50 + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL // 60 +}; +mmove_t makron_move_stand = {FRAME_stand201, FRAME_stand260, makron_frames_stand, NULL}; + +void makron_stand (edict_t *self) +{ + self->monsterinfo.currentmove = &makron_move_stand; +} + +mframe_t makron_frames_run [] = +{ + ai_run, 3, makron_step_left, + ai_run, 12, NULL, + ai_run, 8, NULL, + ai_run, 8, NULL, + ai_run, 8, makron_step_right, + ai_run, 6, NULL, + ai_run, 12, NULL, + ai_run, 9, NULL, + ai_run, 6, NULL, + ai_run, 12, NULL +}; +mmove_t makron_move_run = {FRAME_walk204, FRAME_walk213, makron_frames_run, NULL}; + +void makron_hit (edict_t *self) +{ + gi.sound (self, CHAN_AUTO, sound_hit, 1, ATTN_NONE,0); +} + +void makron_popup (edict_t *self) +{ + gi.sound (self, CHAN_BODY, sound_popup, 1, ATTN_NONE,0); +} + +void makron_step_left (edict_t *self) +{ + gi.sound (self, CHAN_BODY, sound_step_left, 1, ATTN_NORM,0); +} + +void makron_step_right (edict_t *self) +{ + gi.sound (self, CHAN_BODY, sound_step_right, 1, ATTN_NORM,0); +} + +void makron_brainsplorch (edict_t *self) +{ + gi.sound (self, CHAN_VOICE, sound_brainsplorch, 1, ATTN_NORM,0); +} + +void makron_prerailgun (edict_t *self) +{ + gi.sound (self, CHAN_WEAPON, sound_prerailgun, 1, ATTN_NORM,0); +} + + +mframe_t makron_frames_walk [] = +{ + ai_walk, 3, makron_step_left, + ai_walk, 12, NULL, + ai_walk, 8, NULL, + ai_walk, 8, NULL, + ai_walk, 8, makron_step_right, + ai_walk, 6, NULL, + ai_walk, 12, NULL, + ai_walk, 9, NULL, + ai_walk, 6, NULL, + ai_walk, 12, NULL +}; +mmove_t makron_move_walk = {FRAME_walk204, FRAME_walk213, makron_frames_run, NULL}; + +void makron_walk (edict_t *self) +{ + self->monsterinfo.currentmove = &makron_move_walk; +} + +void makron_run (edict_t *self) +{ + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + self->monsterinfo.currentmove = &makron_move_stand; + else + self->monsterinfo.currentmove = &makron_move_run; +} + +mframe_t makron_frames_pain6 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, // 10 + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, makron_popup, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, // 20 + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, makron_taunt, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t makron_move_pain6 = {FRAME_pain601, FRAME_pain627, makron_frames_pain6, makron_run}; + +mframe_t makron_frames_pain5 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t makron_move_pain5 = {FRAME_pain501, FRAME_pain504, makron_frames_pain5, makron_run}; + +mframe_t makron_frames_pain4 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t makron_move_pain4 = {FRAME_pain401, FRAME_pain404, makron_frames_pain4, makron_run}; + +mframe_t makron_frames_death2 [] = +{ + ai_move, -15, NULL, + ai_move, 3, NULL, + ai_move, -12, NULL, + ai_move, 0, makron_step_left, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, // 10 + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 11, NULL, + ai_move, 12, NULL, + ai_move, 11, makron_step_right, + ai_move, 0, NULL, + ai_move, 0, NULL, // 20 + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, // 30 + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 5, NULL, + ai_move, 7, NULL, + ai_move, 6, makron_step_left, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, -1, NULL, + ai_move, 2, NULL, // 40 + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, // 50 + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, -6, NULL, + ai_move, -4, NULL, + ai_move, -6, makron_step_right, + ai_move, -4, NULL, + ai_move, -4, makron_step_left, + ai_move, 0, NULL, + ai_move, 0, NULL, // 60 + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, -2, NULL, + ai_move, -5, NULL, + ai_move, -3, makron_step_right, + ai_move, -8, NULL, + ai_move, -3, makron_step_left, + ai_move, -7, NULL, + ai_move, -4, NULL, + ai_move, -4, makron_step_right, // 70 + ai_move, -6, NULL, + ai_move, -7, NULL, + ai_move, 0, makron_step_left, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, // 80 + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, -2, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 2, NULL, + ai_move, 0, NULL, // 90 + ai_move, 27, makron_hit, + ai_move, 26, NULL, + ai_move, 0, makron_brainsplorch, + ai_move, 0, NULL, + ai_move, 0, NULL // 95 +}; +mmove_t makron_move_death2 = {FRAME_death201, FRAME_death295, makron_frames_death2, makron_dead}; + +mframe_t makron_frames_death3 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t makron_move_death3 = {FRAME_death301, FRAME_death320, makron_frames_death3, NULL}; + +mframe_t makron_frames_sight [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t makron_move_sight= {FRAME_active01, FRAME_active13, makron_frames_sight, makron_run}; + +void makronBFG (edict_t *self) +{ + vec3_t forward, right; + vec3_t start; + vec3_t dir; + vec3_t vec; + + AngleVectors (self->s.angles, forward, right, NULL); + G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_MAKRON_BFG], forward, right, start); + + VectorCopy (self->enemy->s.origin, vec); + vec[2] += self->enemy->viewheight; + VectorSubtract (vec, start, dir); + VectorNormalize (dir); + gi.sound (self, CHAN_VOICE, sound_attack_bfg, 1, ATTN_NORM, 0); + monster_fire_bfg (self, start, dir, 50, 300, 100, 300, MZ2_MAKRON_BFG); +} + + +mframe_t makron_frames_attack3 []= +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, makronBFG, // FIXME: BFG Attack here + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t makron_move_attack3 = {FRAME_attak301, FRAME_attak308, makron_frames_attack3, makron_run}; + +mframe_t makron_frames_attack4[]= +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_move, 0, MakronHyperblaster, // fire + ai_move, 0, MakronHyperblaster, // fire + ai_move, 0, MakronHyperblaster, // fire + ai_move, 0, MakronHyperblaster, // fire + ai_move, 0, MakronHyperblaster, // fire + ai_move, 0, MakronHyperblaster, // fire + ai_move, 0, MakronHyperblaster, // fire + ai_move, 0, MakronHyperblaster, // fire + ai_move, 0, MakronHyperblaster, // fire + ai_move, 0, MakronHyperblaster, // fire + ai_move, 0, MakronHyperblaster, // fire + ai_move, 0, MakronHyperblaster, // fire + ai_move, 0, MakronHyperblaster, // fire + ai_move, 0, MakronHyperblaster, // fire + ai_move, 0, MakronHyperblaster, // fire + ai_move, 0, MakronHyperblaster, // fire + ai_move, 0, MakronHyperblaster, // fire + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t makron_move_attack4 = {FRAME_attak401, FRAME_attak426, makron_frames_attack4, makron_run}; + +mframe_t makron_frames_attack5[]= +{ + ai_charge, 0, makron_prerailgun, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, MakronSaveloc, + ai_move, 0, MakronRailgun, // Fire railgun + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t makron_move_attack5 = {FRAME_attak501, FRAME_attak516, makron_frames_attack5, makron_run}; + +void MakronSaveloc (edict_t *self) +{ + VectorCopy (self->enemy->s.origin, self->pos1); //save for aiming the shot + self->pos1[2] += self->enemy->viewheight; +}; + +// FIXME: He's not firing from the proper Z +void MakronRailgun (edict_t *self) +{ + vec3_t start; + vec3_t dir; + vec3_t forward, right; + + AngleVectors (self->s.angles, forward, right, NULL); + G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_MAKRON_RAILGUN_1], forward, right, start); + + // calc direction to where we targted + VectorSubtract (self->pos1, start, dir); + VectorNormalize (dir); + + monster_fire_railgun (self, start, dir, 50, 100, MZ2_MAKRON_RAILGUN_1); +} + +// FIXME: This is all wrong. He's not firing at the proper angles. +void MakronHyperblaster (edict_t *self) +{ + vec3_t dir; + vec3_t vec; + vec3_t start; + vec3_t forward, right; + int flash_number; + + flash_number = MZ2_MAKRON_BLASTER_1 + (self->s.frame - FRAME_attak405); + + AngleVectors (self->s.angles, forward, right, NULL); + G_ProjectSource (self->s.origin, monster_flash_offset[flash_number], forward, right, start); + + if (self->enemy) + { + VectorCopy (self->enemy->s.origin, vec); + vec[2] += self->enemy->viewheight; + VectorSubtract (vec, start, vec); + vectoangles (vec, vec); + dir[0] = vec[0]; + } + else + { + dir[0] = 0; + } + if (self->s.frame <= FRAME_attak413) + dir[1] = self->s.angles[1] - 10 * (self->s.frame - FRAME_attak413); + else + dir[1] = self->s.angles[1] + 10 * (self->s.frame - FRAME_attak421); + dir[2] = 0; + + AngleVectors (dir, forward, NULL, NULL); + + monster_fire_blaster (self, start, forward, 15, 1000, MZ2_MAKRON_BLASTER_1, EF_BLASTER); +} + + +void makron_pain (edict_t *self, edict_t *other, float kick, int damage) +{ + + if (self->health < (self->max_health / 2)) + self->s.skinnum = 1; + + if (level.time < self->pain_debounce_time) + return; + + // Lessen the chance of him going into his pain frames + if (damage <=25) + if (random()<0.2) + return; + + self->pain_debounce_time = level.time + 3; + if (skill->value == 3) + return; // no pain anims in nightmare + + + if (damage <= 40) + { + gi.sound (self, CHAN_VOICE, sound_pain4, 1, ATTN_NONE,0); + self->monsterinfo.currentmove = &makron_move_pain4; + } + else if (damage <= 110) + { + gi.sound (self, CHAN_VOICE, sound_pain5, 1, ATTN_NONE,0); + self->monsterinfo.currentmove = &makron_move_pain5; + } + else + { + if (damage <= 150) + if (random() <= 0.45) + { + gi.sound (self, CHAN_VOICE, sound_pain6, 1, ATTN_NONE,0); + self->monsterinfo.currentmove = &makron_move_pain6; + } + else + if (random() <= 0.35) + { + gi.sound (self, CHAN_VOICE, sound_pain6, 1, ATTN_NONE,0); + self->monsterinfo.currentmove = &makron_move_pain6; + } + } +}; + +void makron_sight(edict_t *self, edict_t *other) +{ + self->monsterinfo.currentmove = &makron_move_sight; +}; + +void makron_attack(edict_t *self) +{ + vec3_t vec; + float range; + float r; + + r = random(); + + VectorSubtract (self->enemy->s.origin, self->s.origin, vec); + range = VectorLength (vec); + + + if (r <= 0.3) + self->monsterinfo.currentmove = &makron_move_attack3; + else if (r <= 0.6) + self->monsterinfo.currentmove = &makron_move_attack4; + else + self->monsterinfo.currentmove = &makron_move_attack5; +} + +/* +--- +Makron Torso. This needs to be spawned in +--- +*/ + +void makron_torso_think (edict_t *self) +{ + if (++self->s.frame < 365) + self->nextthink = level.time + FRAMETIME; + else + { + self->s.frame = 346; + self->nextthink = level.time + FRAMETIME; + } +} + +void makron_torso (edict_t *ent) +{ + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_NOT; + VectorSet (ent->mins, -8, -8, 0); + VectorSet (ent->maxs, 8, 8, 8); + ent->s.frame = 346; + ent->s.modelindex = gi.modelindex ("models/monsters/boss3/rider/tris.md2"); + ent->think = makron_torso_think; + ent->nextthink = level.time + 2 * FRAMETIME; + ent->s.sound = gi.soundindex ("makron/spine.wav"); + gi.linkentity (ent); +} + + +// +// death +// + +void makron_dead (edict_t *self) +{ + VectorSet (self->mins, -60, -60, 0); + VectorSet (self->maxs, 60, 60, 72); + self->movetype = MOVETYPE_TOSS; + self->svflags |= SVF_DEADMONSTER; + self->nextthink = 0; + gi.linkentity (self); +} + + +void makron_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + edict_t *tempent; + + int n; + + self->s.sound = 0; + // check for gib + if (self->health <= self->gib_health) + { + gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0); + for (n= 0; n < 1 /*4*/; n++) + ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); + for (n= 0; n < 4; n++) + ThrowGib (self, "models/objects/gibs/sm_metal/tris.md2", damage, GIB_METALLIC); + ThrowHead (self, "models/objects/gibs/gear/tris.md2", damage, GIB_METALLIC); + self->deadflag = DEAD_DEAD; + return; + } + + if (self->deadflag == DEAD_DEAD) + return; + +// regular death + gi.sound (self, CHAN_VOICE, sound_death, 1, ATTN_NONE, 0); + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_YES; + + tempent = G_Spawn(); + VectorCopy (self->s.origin, tempent->s.origin); + VectorCopy (self->s.angles, tempent->s.angles); + tempent->s.origin[1] -= 84; + makron_torso (tempent); + + self->monsterinfo.currentmove = &makron_move_death2; + +} + +qboolean Makron_CheckAttack (edict_t *self) +{ + vec3_t spot1, spot2; + vec3_t temp; + float chance; + trace_t tr; + qboolean enemy_infront; + int enemy_range; + float enemy_yaw; + + if (self->enemy->health > 0) + { + // see if any entities are in the way of the shot + VectorCopy (self->s.origin, spot1); + spot1[2] += self->viewheight; + VectorCopy (self->enemy->s.origin, spot2); + spot2[2] += self->enemy->viewheight; + + tr = gi.trace (spot1, NULL, NULL, spot2, self, CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_SLIME|CONTENTS_LAVA); + + // do we have a clear shot? + if (tr.ent != self->enemy) + return false; + } + + enemy_infront = infront(self, self->enemy); + enemy_range = range(self, self->enemy); + VectorSubtract (self->enemy->s.origin, self->s.origin, temp); + enemy_yaw = vectoyaw(temp); + + self->ideal_yaw = enemy_yaw; + + + // melee attack + if (enemy_range == RANGE_MELEE) + { + if (self->monsterinfo.melee) + self->monsterinfo.attack_state = AS_MELEE; + else + self->monsterinfo.attack_state = AS_MISSILE; + return true; + } + +// missile attack + if (!self->monsterinfo.attack) + return false; + + if (level.time < self->monsterinfo.attack_finished) + return false; + + if (enemy_range == RANGE_FAR) + return false; + + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + { + chance = 0.4; + } + else if (enemy_range == RANGE_MELEE) + { + chance = 0.8; + } + else if (enemy_range == RANGE_NEAR) + { + chance = 0.4; + } + else if (enemy_range == RANGE_MID) + { + chance = 0.2; + } + else + { + return false; + } + + if (random () < chance) + { + self->monsterinfo.attack_state = AS_MISSILE; + self->monsterinfo.attack_finished = level.time + 2*random(); + return true; + } + + if (self->flags & FL_FLY) + { + if (random() < 0.3) + self->monsterinfo.attack_state = AS_SLIDING; + else + self->monsterinfo.attack_state = AS_STRAIGHT; + } + + return false; +} + + +// +// monster_makron +// + +void MakronPrecache (void) +{ + sound_pain4 = gi.soundindex ("makron/pain3.wav"); + sound_pain5 = gi.soundindex ("makron/pain2.wav"); + sound_pain6 = gi.soundindex ("makron/pain1.wav"); + sound_death = gi.soundindex ("makron/death.wav"); + sound_step_left = gi.soundindex ("makron/step1.wav"); + sound_step_right = gi.soundindex ("makron/step2.wav"); + sound_attack_bfg = gi.soundindex ("makron/bfg_fire.wav"); + sound_brainsplorch = gi.soundindex ("makron/brain1.wav"); + sound_prerailgun = gi.soundindex ("makron/rail_up.wav"); + sound_popup = gi.soundindex ("makron/popup.wav"); + sound_taunt1 = gi.soundindex ("makron/voice4.wav"); + sound_taunt2 = gi.soundindex ("makron/voice3.wav"); + sound_taunt3 = gi.soundindex ("makron/voice.wav"); + sound_hit = gi.soundindex ("makron/bhit.wav"); + + gi.modelindex ("models/monsters/boss3/rider/tris.md2"); +} + +/*QUAKED monster_makron (1 .5 0) (-30 -30 0) (30 30 90) Ambush Trigger_Spawn Sight +*/ +void SP_monster_makron (edict_t *self) +{ + if (deathmatch->value) + { + G_FreeEdict (self); + return; + } + + MakronPrecache (); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex ("models/monsters/boss3/rider/tris.md2"); + VectorSet (self->mins, -30, -30, 0); + VectorSet (self->maxs, 30, 30, 90); + + self->health = 3000; + self->gib_health = -2000; + self->mass = 500; + + self->pain = makron_pain; + self->die = makron_die; + self->monsterinfo.stand = makron_stand; + self->monsterinfo.walk = makron_walk; + self->monsterinfo.run = makron_run; + self->monsterinfo.dodge = NULL; + self->monsterinfo.attack = makron_attack; + self->monsterinfo.melee = NULL; + self->monsterinfo.sight = makron_sight; + self->monsterinfo.checkattack = Makron_CheckAttack; + + gi.linkentity (self); + +// self->monsterinfo.currentmove = &makron_move_stand; + self->monsterinfo.currentmove = &makron_move_sight; + self->monsterinfo.scale = MODEL_SCALE; + + walkmonster_start(self); + + //PMM + self->monsterinfo.aiflags |= AI_IGNORE_SHOTS; + //pmm +} + + +/* +================= +MakronSpawn + +================= +*/ +void MakronSpawn (edict_t *self) +{ + vec3_t vec; + edict_t *player; + + SP_monster_makron (self); + + // jump at player + player = level.sight_client; + if (!player) + return; + + VectorSubtract (player->s.origin, self->s.origin, vec); + self->s.angles[YAW] = vectoyaw(vec); + VectorNormalize (vec); + VectorMA (vec3_origin, 400, vec, self->velocity); + self->velocity[2] = 200; + self->groundentity = NULL; +} + +/* +================= +MakronToss + +Jorg is just about dead, so set up to launch Makron out +================= +*/ +void MakronToss (edict_t *self) +{ + edict_t *ent; + + ent = G_Spawn (); + ent->nextthink = level.time + 0.8; + ent->think = MakronSpawn; + ent->target = self->target; + VectorCopy (self->s.origin, ent->s.origin); +} diff --git a/original/rogue/m_boss32.h b/original/rogue/m_boss32.h new file mode 100644 index 0000000..097831a --- /dev/null +++ b/original/rogue/m_boss32.h @@ -0,0 +1,499 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// G:\quake2\baseq2\models/monsters/boss3/rider + +// This file generated by ModelGen - Do NOT Modify + +#define FRAME_attak101 0 +#define FRAME_attak102 1 +#define FRAME_attak103 2 +#define FRAME_attak104 3 +#define FRAME_attak105 4 +#define FRAME_attak106 5 +#define FRAME_attak107 6 +#define FRAME_attak108 7 +#define FRAME_attak109 8 +#define FRAME_attak110 9 +#define FRAME_attak111 10 +#define FRAME_attak112 11 +#define FRAME_attak113 12 +#define FRAME_attak114 13 +#define FRAME_attak115 14 +#define FRAME_attak116 15 +#define FRAME_attak117 16 +#define FRAME_attak118 17 +#define FRAME_attak201 18 +#define FRAME_attak202 19 +#define FRAME_attak203 20 +#define FRAME_attak204 21 +#define FRAME_attak205 22 +#define FRAME_attak206 23 +#define FRAME_attak207 24 +#define FRAME_attak208 25 +#define FRAME_attak209 26 +#define FRAME_attak210 27 +#define FRAME_attak211 28 +#define FRAME_attak212 29 +#define FRAME_attak213 30 +#define FRAME_death01 31 +#define FRAME_death02 32 +#define FRAME_death03 33 +#define FRAME_death04 34 +#define FRAME_death05 35 +#define FRAME_death06 36 +#define FRAME_death07 37 +#define FRAME_death08 38 +#define FRAME_death09 39 +#define FRAME_death10 40 +#define FRAME_death11 41 +#define FRAME_death12 42 +#define FRAME_death13 43 +#define FRAME_death14 44 +#define FRAME_death15 45 +#define FRAME_death16 46 +#define FRAME_death17 47 +#define FRAME_death18 48 +#define FRAME_death19 49 +#define FRAME_death20 50 +#define FRAME_death21 51 +#define FRAME_death22 52 +#define FRAME_death23 53 +#define FRAME_death24 54 +#define FRAME_death25 55 +#define FRAME_death26 56 +#define FRAME_death27 57 +#define FRAME_death28 58 +#define FRAME_death29 59 +#define FRAME_death30 60 +#define FRAME_death31 61 +#define FRAME_death32 62 +#define FRAME_death33 63 +#define FRAME_death34 64 +#define FRAME_death35 65 +#define FRAME_death36 66 +#define FRAME_death37 67 +#define FRAME_death38 68 +#define FRAME_death39 69 +#define FRAME_death40 70 +#define FRAME_death41 71 +#define FRAME_death42 72 +#define FRAME_death43 73 +#define FRAME_death44 74 +#define FRAME_death45 75 +#define FRAME_death46 76 +#define FRAME_death47 77 +#define FRAME_death48 78 +#define FRAME_death49 79 +#define FRAME_death50 80 +#define FRAME_pain101 81 +#define FRAME_pain102 82 +#define FRAME_pain103 83 +#define FRAME_pain201 84 +#define FRAME_pain202 85 +#define FRAME_pain203 86 +#define FRAME_pain301 87 +#define FRAME_pain302 88 +#define FRAME_pain303 89 +#define FRAME_pain304 90 +#define FRAME_pain305 91 +#define FRAME_pain306 92 +#define FRAME_pain307 93 +#define FRAME_pain308 94 +#define FRAME_pain309 95 +#define FRAME_pain310 96 +#define FRAME_pain311 97 +#define FRAME_pain312 98 +#define FRAME_pain313 99 +#define FRAME_pain314 100 +#define FRAME_pain315 101 +#define FRAME_pain316 102 +#define FRAME_pain317 103 +#define FRAME_pain318 104 +#define FRAME_pain319 105 +#define FRAME_pain320 106 +#define FRAME_pain321 107 +#define FRAME_pain322 108 +#define FRAME_pain323 109 +#define FRAME_pain324 110 +#define FRAME_pain325 111 +#define FRAME_stand01 112 +#define FRAME_stand02 113 +#define FRAME_stand03 114 +#define FRAME_stand04 115 +#define FRAME_stand05 116 +#define FRAME_stand06 117 +#define FRAME_stand07 118 +#define FRAME_stand08 119 +#define FRAME_stand09 120 +#define FRAME_stand10 121 +#define FRAME_stand11 122 +#define FRAME_stand12 123 +#define FRAME_stand13 124 +#define FRAME_stand14 125 +#define FRAME_stand15 126 +#define FRAME_stand16 127 +#define FRAME_stand17 128 +#define FRAME_stand18 129 +#define FRAME_stand19 130 +#define FRAME_stand20 131 +#define FRAME_stand21 132 +#define FRAME_stand22 133 +#define FRAME_stand23 134 +#define FRAME_stand24 135 +#define FRAME_stand25 136 +#define FRAME_stand26 137 +#define FRAME_stand27 138 +#define FRAME_stand28 139 +#define FRAME_stand29 140 +#define FRAME_stand30 141 +#define FRAME_stand31 142 +#define FRAME_stand32 143 +#define FRAME_stand33 144 +#define FRAME_stand34 145 +#define FRAME_stand35 146 +#define FRAME_stand36 147 +#define FRAME_stand37 148 +#define FRAME_stand38 149 +#define FRAME_stand39 150 +#define FRAME_stand40 151 +#define FRAME_stand41 152 +#define FRAME_stand42 153 +#define FRAME_stand43 154 +#define FRAME_stand44 155 +#define FRAME_stand45 156 +#define FRAME_stand46 157 +#define FRAME_stand47 158 +#define FRAME_stand48 159 +#define FRAME_stand49 160 +#define FRAME_stand50 161 +#define FRAME_stand51 162 +#define FRAME_walk01 163 +#define FRAME_walk02 164 +#define FRAME_walk03 165 +#define FRAME_walk04 166 +#define FRAME_walk05 167 +#define FRAME_walk06 168 +#define FRAME_walk07 169 +#define FRAME_walk08 170 +#define FRAME_walk09 171 +#define FRAME_walk10 172 +#define FRAME_walk11 173 +#define FRAME_walk12 174 +#define FRAME_walk13 175 +#define FRAME_walk14 176 +#define FRAME_walk15 177 +#define FRAME_walk16 178 +#define FRAME_walk17 179 +#define FRAME_walk18 180 +#define FRAME_walk19 181 +#define FRAME_walk20 182 +#define FRAME_walk21 183 +#define FRAME_walk22 184 +#define FRAME_walk23 185 +#define FRAME_walk24 186 +#define FRAME_walk25 187 +#define FRAME_active01 188 +#define FRAME_active02 189 +#define FRAME_active03 190 +#define FRAME_active04 191 +#define FRAME_active05 192 +#define FRAME_active06 193 +#define FRAME_active07 194 +#define FRAME_active08 195 +#define FRAME_active09 196 +#define FRAME_active10 197 +#define FRAME_active11 198 +#define FRAME_active12 199 +#define FRAME_active13 200 +#define FRAME_attak301 201 +#define FRAME_attak302 202 +#define FRAME_attak303 203 +#define FRAME_attak304 204 +#define FRAME_attak305 205 +#define FRAME_attak306 206 +#define FRAME_attak307 207 +#define FRAME_attak308 208 +#define FRAME_attak401 209 +#define FRAME_attak402 210 +#define FRAME_attak403 211 +#define FRAME_attak404 212 +#define FRAME_attak405 213 +#define FRAME_attak406 214 +#define FRAME_attak407 215 +#define FRAME_attak408 216 +#define FRAME_attak409 217 +#define FRAME_attak410 218 +#define FRAME_attak411 219 +#define FRAME_attak412 220 +#define FRAME_attak413 221 +#define FRAME_attak414 222 +#define FRAME_attak415 223 +#define FRAME_attak416 224 +#define FRAME_attak417 225 +#define FRAME_attak418 226 +#define FRAME_attak419 227 +#define FRAME_attak420 228 +#define FRAME_attak421 229 +#define FRAME_attak422 230 +#define FRAME_attak423 231 +#define FRAME_attak424 232 +#define FRAME_attak425 233 +#define FRAME_attak426 234 +#define FRAME_attak501 235 +#define FRAME_attak502 236 +#define FRAME_attak503 237 +#define FRAME_attak504 238 +#define FRAME_attak505 239 +#define FRAME_attak506 240 +#define FRAME_attak507 241 +#define FRAME_attak508 242 +#define FRAME_attak509 243 +#define FRAME_attak510 244 +#define FRAME_attak511 245 +#define FRAME_attak512 246 +#define FRAME_attak513 247 +#define FRAME_attak514 248 +#define FRAME_attak515 249 +#define FRAME_attak516 250 +#define FRAME_death201 251 +#define FRAME_death202 252 +#define FRAME_death203 253 +#define FRAME_death204 254 +#define FRAME_death205 255 +#define FRAME_death206 256 +#define FRAME_death207 257 +#define FRAME_death208 258 +#define FRAME_death209 259 +#define FRAME_death210 260 +#define FRAME_death211 261 +#define FRAME_death212 262 +#define FRAME_death213 263 +#define FRAME_death214 264 +#define FRAME_death215 265 +#define FRAME_death216 266 +#define FRAME_death217 267 +#define FRAME_death218 268 +#define FRAME_death219 269 +#define FRAME_death220 270 +#define FRAME_death221 271 +#define FRAME_death222 272 +#define FRAME_death223 273 +#define FRAME_death224 274 +#define FRAME_death225 275 +#define FRAME_death226 276 +#define FRAME_death227 277 +#define FRAME_death228 278 +#define FRAME_death229 279 +#define FRAME_death230 280 +#define FRAME_death231 281 +#define FRAME_death232 282 +#define FRAME_death233 283 +#define FRAME_death234 284 +#define FRAME_death235 285 +#define FRAME_death236 286 +#define FRAME_death237 287 +#define FRAME_death238 288 +#define FRAME_death239 289 +#define FRAME_death240 290 +#define FRAME_death241 291 +#define FRAME_death242 292 +#define FRAME_death243 293 +#define FRAME_death244 294 +#define FRAME_death245 295 +#define FRAME_death246 296 +#define FRAME_death247 297 +#define FRAME_death248 298 +#define FRAME_death249 299 +#define FRAME_death250 300 +#define FRAME_death251 301 +#define FRAME_death252 302 +#define FRAME_death253 303 +#define FRAME_death254 304 +#define FRAME_death255 305 +#define FRAME_death256 306 +#define FRAME_death257 307 +#define FRAME_death258 308 +#define FRAME_death259 309 +#define FRAME_death260 310 +#define FRAME_death261 311 +#define FRAME_death262 312 +#define FRAME_death263 313 +#define FRAME_death264 314 +#define FRAME_death265 315 +#define FRAME_death266 316 +#define FRAME_death267 317 +#define FRAME_death268 318 +#define FRAME_death269 319 +#define FRAME_death270 320 +#define FRAME_death271 321 +#define FRAME_death272 322 +#define FRAME_death273 323 +#define FRAME_death274 324 +#define FRAME_death275 325 +#define FRAME_death276 326 +#define FRAME_death277 327 +#define FRAME_death278 328 +#define FRAME_death279 329 +#define FRAME_death280 330 +#define FRAME_death281 331 +#define FRAME_death282 332 +#define FRAME_death283 333 +#define FRAME_death284 334 +#define FRAME_death285 335 +#define FRAME_death286 336 +#define FRAME_death287 337 +#define FRAME_death288 338 +#define FRAME_death289 339 +#define FRAME_death290 340 +#define FRAME_death291 341 +#define FRAME_death292 342 +#define FRAME_death293 343 +#define FRAME_death294 344 +#define FRAME_death295 345 +#define FRAME_death301 346 +#define FRAME_death302 347 +#define FRAME_death303 348 +#define FRAME_death304 349 +#define FRAME_death305 350 +#define FRAME_death306 351 +#define FRAME_death307 352 +#define FRAME_death308 353 +#define FRAME_death309 354 +#define FRAME_death310 355 +#define FRAME_death311 356 +#define FRAME_death312 357 +#define FRAME_death313 358 +#define FRAME_death314 359 +#define FRAME_death315 360 +#define FRAME_death316 361 +#define FRAME_death317 362 +#define FRAME_death318 363 +#define FRAME_death319 364 +#define FRAME_death320 365 +#define FRAME_jump01 366 +#define FRAME_jump02 367 +#define FRAME_jump03 368 +#define FRAME_jump04 369 +#define FRAME_jump05 370 +#define FRAME_jump06 371 +#define FRAME_jump07 372 +#define FRAME_jump08 373 +#define FRAME_jump09 374 +#define FRAME_jump10 375 +#define FRAME_jump11 376 +#define FRAME_jump12 377 +#define FRAME_jump13 378 +#define FRAME_pain401 379 +#define FRAME_pain402 380 +#define FRAME_pain403 381 +#define FRAME_pain404 382 +#define FRAME_pain501 383 +#define FRAME_pain502 384 +#define FRAME_pain503 385 +#define FRAME_pain504 386 +#define FRAME_pain601 387 +#define FRAME_pain602 388 +#define FRAME_pain603 389 +#define FRAME_pain604 390 +#define FRAME_pain605 391 +#define FRAME_pain606 392 +#define FRAME_pain607 393 +#define FRAME_pain608 394 +#define FRAME_pain609 395 +#define FRAME_pain610 396 +#define FRAME_pain611 397 +#define FRAME_pain612 398 +#define FRAME_pain613 399 +#define FRAME_pain614 400 +#define FRAME_pain615 401 +#define FRAME_pain616 402 +#define FRAME_pain617 403 +#define FRAME_pain618 404 +#define FRAME_pain619 405 +#define FRAME_pain620 406 +#define FRAME_pain621 407 +#define FRAME_pain622 408 +#define FRAME_pain623 409 +#define FRAME_pain624 410 +#define FRAME_pain625 411 +#define FRAME_pain626 412 +#define FRAME_pain627 413 +#define FRAME_stand201 414 +#define FRAME_stand202 415 +#define FRAME_stand203 416 +#define FRAME_stand204 417 +#define FRAME_stand205 418 +#define FRAME_stand206 419 +#define FRAME_stand207 420 +#define FRAME_stand208 421 +#define FRAME_stand209 422 +#define FRAME_stand210 423 +#define FRAME_stand211 424 +#define FRAME_stand212 425 +#define FRAME_stand213 426 +#define FRAME_stand214 427 +#define FRAME_stand215 428 +#define FRAME_stand216 429 +#define FRAME_stand217 430 +#define FRAME_stand218 431 +#define FRAME_stand219 432 +#define FRAME_stand220 433 +#define FRAME_stand221 434 +#define FRAME_stand222 435 +#define FRAME_stand223 436 +#define FRAME_stand224 437 +#define FRAME_stand225 438 +#define FRAME_stand226 439 +#define FRAME_stand227 440 +#define FRAME_stand228 441 +#define FRAME_stand229 442 +#define FRAME_stand230 443 +#define FRAME_stand231 444 +#define FRAME_stand232 445 +#define FRAME_stand233 446 +#define FRAME_stand234 447 +#define FRAME_stand235 448 +#define FRAME_stand236 449 +#define FRAME_stand237 450 +#define FRAME_stand238 451 +#define FRAME_stand239 452 +#define FRAME_stand240 453 +#define FRAME_stand241 454 +#define FRAME_stand242 455 +#define FRAME_stand243 456 +#define FRAME_stand244 457 +#define FRAME_stand245 458 +#define FRAME_stand246 459 +#define FRAME_stand247 460 +#define FRAME_stand248 461 +#define FRAME_stand249 462 +#define FRAME_stand250 463 +#define FRAME_stand251 464 +#define FRAME_stand252 465 +#define FRAME_stand253 466 +#define FRAME_stand254 467 +#define FRAME_stand255 468 +#define FRAME_stand256 469 +#define FRAME_stand257 470 +#define FRAME_stand258 471 +#define FRAME_stand259 472 +#define FRAME_stand260 473 +#define FRAME_walk201 474 +#define FRAME_walk202 475 +#define FRAME_walk203 476 +#define FRAME_walk204 477 +#define FRAME_walk205 478 +#define FRAME_walk206 479 +#define FRAME_walk207 480 +#define FRAME_walk208 481 +#define FRAME_walk209 482 +#define FRAME_walk210 483 +#define FRAME_walk211 484 +#define FRAME_walk212 485 +#define FRAME_walk213 486 +#define FRAME_walk214 487 +#define FRAME_walk215 488 +#define FRAME_walk216 489 +#define FRAME_walk217 490 + +#define MODEL_SCALE 1.000000 diff --git a/original/rogue/m_brain.c b/original/rogue/m_brain.c new file mode 100644 index 0000000..7d37ceb --- /dev/null +++ b/original/rogue/m_brain.c @@ -0,0 +1,703 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +/* +============================================================================== + +brain + +============================================================================== +*/ + +#include "g_local.h" +#include "m_brain.h" + + +static int sound_chest_open; +static int sound_tentacles_extend; +static int sound_tentacles_retract; +static int sound_death; +static int sound_idle1; +static int sound_idle2; +static int sound_idle3; +static int sound_pain1; +static int sound_pain2; +static int sound_sight; +static int sound_search; +static int sound_melee1; +static int sound_melee2; +static int sound_melee3; + + +void brain_sight (edict_t *self, edict_t *other) +{ + gi.sound (self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); +} + +void brain_search (edict_t *self) +{ + gi.sound (self, CHAN_VOICE, sound_search, 1, ATTN_NORM, 0); +} + + +void brain_run (edict_t *self); +void brain_dead (edict_t *self); + + +// +// STAND +// + +mframe_t brain_frames_stand [] = +{ + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL +}; +mmove_t brain_move_stand = {FRAME_stand01, FRAME_stand30, brain_frames_stand, NULL}; + +void brain_stand (edict_t *self) +{ + self->monsterinfo.currentmove = &brain_move_stand; +} + + +// +// IDLE +// + +mframe_t brain_frames_idle [] = +{ + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL +}; +mmove_t brain_move_idle = {FRAME_stand31, FRAME_stand60, brain_frames_idle, brain_stand}; + +void brain_idle (edict_t *self) +{ + gi.sound (self, CHAN_AUTO, sound_idle3, 1, ATTN_IDLE, 0); + self->monsterinfo.currentmove = &brain_move_idle; +} + + +// +// WALK +// +mframe_t brain_frames_walk1 [] = +{ + ai_walk, 7, NULL, + ai_walk, 2, NULL, + ai_walk, 3, NULL, + ai_walk, 3, NULL, + ai_walk, 1, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 9, NULL, + ai_walk, -4, NULL, + ai_walk, -1, NULL, + ai_walk, 2, NULL +}; +mmove_t brain_move_walk1 = {FRAME_walk101, FRAME_walk111, brain_frames_walk1, NULL}; + +// walk2 is FUBAR, do not use +#if 0 +void brain_walk2_cycle (edict_t *self) +{ + if (random() > 0.1) + self->monsterinfo.nextframe = FRAME_walk220; +} + +mframe_t brain_frames_walk2 [] = +{ + ai_walk, 3, NULL, + ai_walk, -2, NULL, + ai_walk, -4, NULL, + ai_walk, -3, NULL, + ai_walk, 0, NULL, + ai_walk, 1, NULL, + ai_walk, 12, NULL, + ai_walk, 0, NULL, + ai_walk, -3, NULL, + ai_walk, 0, NULL, + + ai_walk, -2, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 1, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 10, NULL, // Cycle Start + + ai_walk, -1, NULL, + ai_walk, 7, NULL, + ai_walk, 0, NULL, + ai_walk, 3, NULL, + ai_walk, -3, NULL, + ai_walk, 2, NULL, + ai_walk, 4, NULL, + ai_walk, -3, NULL, + ai_walk, 2, NULL, + ai_walk, 0, NULL, + + ai_walk, 4, brain_walk2_cycle, + ai_walk, -1, NULL, + ai_walk, -1, NULL, + ai_walk, -8, NULL, + ai_walk, 0, NULL, + ai_walk, 1, NULL, + ai_walk, 5, NULL, + ai_walk, 2, NULL, + ai_walk, -1, NULL, + ai_walk, -5, NULL +}; +mmove_t brain_move_walk2 = {FRAME_walk201, FRAME_walk240, brain_frames_walk2, NULL}; +#endif + +void brain_walk (edict_t *self) +{ +// if (random() <= 0.5) + self->monsterinfo.currentmove = &brain_move_walk1; +// else +// self->monsterinfo.currentmove = &brain_move_walk2; +} + + + +mframe_t brain_frames_defense [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t brain_move_defense = {FRAME_defens01, FRAME_defens08, brain_frames_defense, NULL}; + +mframe_t brain_frames_pain3 [] = +{ + ai_move, -2, NULL, + ai_move, 2, NULL, + ai_move, 1, NULL, + ai_move, 3, NULL, + ai_move, 0, NULL, + ai_move, -4, NULL +}; +mmove_t brain_move_pain3 = {FRAME_pain301, FRAME_pain306, brain_frames_pain3, brain_run}; + +mframe_t brain_frames_pain2 [] = +{ + ai_move, -2, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 3, NULL, + ai_move, 1, NULL, + ai_move, -2, NULL +}; +mmove_t brain_move_pain2 = {FRAME_pain201, FRAME_pain208, brain_frames_pain2, brain_run}; + +mframe_t brain_frames_pain1 [] = +{ + ai_move, -6, NULL, + ai_move, -2, NULL, + ai_move, -6, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 2, NULL, + ai_move, 0, NULL, + ai_move, 2, NULL, + ai_move, 1, NULL, + ai_move, 7, NULL, + ai_move, 0, NULL, + ai_move, 3, NULL, + ai_move, -1, NULL +}; +mmove_t brain_move_pain1 = {FRAME_pain101, FRAME_pain121, brain_frames_pain1, brain_run}; + +mframe_t brain_frames_duck [] = +{ + ai_move, 0, NULL, + ai_move, -2, monster_duck_down, + ai_move, 17, monster_duck_hold, + ai_move, -3, NULL, + ai_move, -1, monster_duck_up, + ai_move, -5, NULL, + ai_move, -6, NULL, + ai_move, -6, NULL +}; +mmove_t brain_move_duck = {FRAME_duck01, FRAME_duck08, brain_frames_duck, brain_run}; + +/* +void brain_dodge (edict_t *self, edict_t *attacker, float eta, trace_t *tr) +{ + //======== + //PMM - new dodge code + float r; + float height; + + if (!self->enemy) + { + self->enemy = attacker; + FoundTarget (self); + } + + // PMM - don't bother if it's going to hit anyway; fix for weird in-your-face etas (I was + // seeing numbers like 13 and 14) + if ((eta < 0.1) || (eta > 5)) + return; + + r = random(); + if (r > (0.25*((skill->value)+1))) + return; + + if (self->monsterinfo.aiflags & AI_DODGING) + { + height = self->absmax[2]; + } + else + { + height = self->absmax[2]-32-1; // the -1 is because the absmax is s.origin + maxs + 1 + } + + // check to see if it makes sense to duck + if (tr->endpos[2] <= height) + { + // if it doesn't sense to duck, try to strafe and shoot + // FIXME - this guy is so slow, it's not worth it + + //vec3_t forward,right,up,diff; + + monster_done_dodge (self); + return; + } + + if (skill->value == 0) + { + self->monsterinfo.currentmove = &brain_move_duck; + // PMM - stupid dodge + self->monsterinfo.duck_wait_time = level.time + eta + 1; + self->monsterinfo.aiflags |= AI_DODGING; + return; + } + + self->monsterinfo.currentmove = &brain_move_duck; + self->monsterinfo.duck_wait_time = level.time + eta + (0.1 * (3 - skill->value)); + self->monsterinfo.aiflags |= AI_DODGING; + return; + //============ + //PMM +} +*/ + +mframe_t brain_frames_death2 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 9, NULL, + ai_move, 0, NULL +}; +mmove_t brain_move_death2 = {FRAME_death201, FRAME_death205, brain_frames_death2, brain_dead}; + +mframe_t brain_frames_death1 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, -2, NULL, + ai_move, 9, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t brain_move_death1 = {FRAME_death101, FRAME_death118, brain_frames_death1, brain_dead}; + + +// +// MELEE +// + +void brain_swing_right (edict_t *self) +{ + gi.sound (self, CHAN_BODY, sound_melee1, 1, ATTN_NORM, 0); +} + +void brain_hit_right (edict_t *self) +{ + vec3_t aim; + + VectorSet (aim, MELEE_DISTANCE, self->maxs[0], 8); + if (fire_hit (self, aim, (15 + (rand() %5)), 40)) + gi.sound (self, CHAN_WEAPON, sound_melee3, 1, ATTN_NORM, 0); +} + +void brain_swing_left (edict_t *self) +{ + gi.sound (self, CHAN_BODY, sound_melee2, 1, ATTN_NORM, 0); +} + +void brain_hit_left (edict_t *self) +{ + vec3_t aim; + + VectorSet (aim, MELEE_DISTANCE, self->mins[0], 8); + if (fire_hit (self, aim, (15 + (rand() %5)), 40)) + gi.sound (self, CHAN_WEAPON, sound_melee3, 1, ATTN_NORM, 0); +} + +mframe_t brain_frames_attack1 [] = +{ + ai_charge, 8, NULL, + ai_charge, 3, NULL, + ai_charge, 5, NULL, + ai_charge, 0, NULL, + ai_charge, -3, brain_swing_right, + ai_charge, 0, NULL, + ai_charge, -5, NULL, + ai_charge, -7, brain_hit_right, + ai_charge, 0, NULL, + ai_charge, 6, brain_swing_left, + ai_charge, 1, NULL, + ai_charge, 2, brain_hit_left, + ai_charge, -3, NULL, + ai_charge, 6, NULL, + ai_charge, -1, NULL, + ai_charge, -3, NULL, + ai_charge, 2, NULL, + ai_charge, -11,NULL +}; +mmove_t brain_move_attack1 = {FRAME_attak101, FRAME_attak118, brain_frames_attack1, brain_run}; + +void brain_chest_open (edict_t *self) +{ + self->spawnflags &= ~65536; + self->monsterinfo.power_armor_type = POWER_ARMOR_NONE; + gi.sound (self, CHAN_BODY, sound_chest_open, 1, ATTN_NORM, 0); +} + +void brain_tentacle_attack (edict_t *self) +{ + vec3_t aim; + + VectorSet (aim, MELEE_DISTANCE, 0, 8); + if (fire_hit (self, aim, (10 + (rand() %5)), -600) && skill->value > 0) + self->spawnflags |= 65536; + gi.sound (self, CHAN_WEAPON, sound_tentacles_retract, 1, ATTN_NORM, 0); +} + +void brain_chest_closed (edict_t *self) +{ + self->monsterinfo.power_armor_type = POWER_ARMOR_SCREEN; + if (self->spawnflags & 65536) + { + self->spawnflags &= ~65536; + self->monsterinfo.currentmove = &brain_move_attack1; + } +} + +mframe_t brain_frames_attack2 [] = +{ + ai_charge, 5, NULL, + ai_charge, -4, NULL, + ai_charge, -4, NULL, + ai_charge, -3, NULL, + ai_charge, 0, brain_chest_open, + ai_charge, 0, NULL, + ai_charge, 13, brain_tentacle_attack, + ai_charge, 0, NULL, + ai_charge, 2, NULL, + ai_charge, 0, NULL, + ai_charge, -9, brain_chest_closed, + ai_charge, 0, NULL, + ai_charge, 4, NULL, + ai_charge, 3, NULL, + ai_charge, 2, NULL, + ai_charge, -3, NULL, + ai_charge, -6, NULL +}; +mmove_t brain_move_attack2 = {FRAME_attak201, FRAME_attak217, brain_frames_attack2, brain_run}; + +void brain_melee(edict_t *self) +{ + if (random() <= 0.5) + self->monsterinfo.currentmove = &brain_move_attack1; + else + self->monsterinfo.currentmove = &brain_move_attack2; +} + + +// +// RUN +// + +mframe_t brain_frames_run [] = +{ + ai_run, 9, NULL, + ai_run, 2, NULL, + ai_run, 3, NULL, + ai_run, 3, NULL, + ai_run, 1, NULL, + ai_run, 0, NULL, + ai_run, 0, NULL, + ai_run, 10, NULL, + ai_run, -4, NULL, + ai_run, -1, NULL, + ai_run, 2, NULL +}; +mmove_t brain_move_run = {FRAME_walk101, FRAME_walk111, brain_frames_run, NULL}; + +void brain_run (edict_t *self) +{ + self->monsterinfo.power_armor_type = POWER_ARMOR_SCREEN; + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + self->monsterinfo.currentmove = &brain_move_stand; + else + self->monsterinfo.currentmove = &brain_move_run; +} + + +void brain_pain (edict_t *self, edict_t *other, float kick, int damage) +{ + float r; + + if (self->health < (self->max_health / 2)) + self->s.skinnum = 1; + + if (level.time < self->pain_debounce_time) + return; + + self->pain_debounce_time = level.time + 3; + + if (skill->value == 3) + return; // no pain anims in nightmare + + r = random(); + if (r < 0.33) + { + gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &brain_move_pain1; + } + else if (r < 0.66) + { + gi.sound (self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &brain_move_pain2; + } + else + { + gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &brain_move_pain3; + } + // PMM - clear duck flag + if (self->monsterinfo.aiflags & AI_DUCKED) + monster_duck_up(self); +} + +void brain_dead (edict_t *self) +{ + VectorSet (self->mins, -16, -16, -24); + VectorSet (self->maxs, 16, 16, -8); + self->movetype = MOVETYPE_TOSS; + self->svflags |= SVF_DEADMONSTER; + self->nextthink = 0; + gi.linkentity (self); +} + + + +void brain_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + int n; + + self->s.effects = 0; + self->monsterinfo.power_armor_type = POWER_ARMOR_NONE; + +// check for gib + if (self->health <= self->gib_health) + { + gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0); + for (n= 0; n < 2; n++) + ThrowGib (self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC); + for (n= 0; n < 4; n++) + ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); + ThrowHead (self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC); + self->deadflag = DEAD_DEAD; + return; + } + + if (self->deadflag == DEAD_DEAD) + return; + +// regular death + gi.sound (self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0); + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_YES; + if (random() <= 0.5) + self->monsterinfo.currentmove = &brain_move_death1; + else + self->monsterinfo.currentmove = &brain_move_death2; +} + +void brain_duck (edict_t *self, float eta) +{ + // has to be done immediately otherwise he can get stuck + monster_duck_down(self); + + if (skill->value == 0) + // PMM - stupid dodge + self->monsterinfo.duck_wait_time = level.time + eta + 1; + else + self->monsterinfo.duck_wait_time = level.time + eta + (0.1 * (3 - skill->value)); + + self->monsterinfo.currentmove = &brain_move_duck; + self->monsterinfo.nextframe = FRAME_duck01; + return; +} + + +/*QUAKED monster_brain (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight +*/ +void SP_monster_brain (edict_t *self) +{ + if (deathmatch->value) + { + G_FreeEdict (self); + return; + } + + sound_chest_open = gi.soundindex ("brain/brnatck1.wav"); + sound_tentacles_extend = gi.soundindex ("brain/brnatck2.wav"); + sound_tentacles_retract = gi.soundindex ("brain/brnatck3.wav"); + sound_death = gi.soundindex ("brain/brndeth1.wav"); + sound_idle1 = gi.soundindex ("brain/brnidle1.wav"); + sound_idle2 = gi.soundindex ("brain/brnidle2.wav"); + sound_idle3 = gi.soundindex ("brain/brnlens1.wav"); + sound_pain1 = gi.soundindex ("brain/brnpain1.wav"); + sound_pain2 = gi.soundindex ("brain/brnpain2.wav"); + sound_sight = gi.soundindex ("brain/brnsght1.wav"); + sound_search = gi.soundindex ("brain/brnsrch1.wav"); + sound_melee1 = gi.soundindex ("brain/melee1.wav"); + sound_melee2 = gi.soundindex ("brain/melee2.wav"); + sound_melee3 = gi.soundindex ("brain/melee3.wav"); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex ("models/monsters/brain/tris.md2"); + VectorSet (self->mins, -16, -16, -24); + VectorSet (self->maxs, 16, 16, 32); + + self->health = 300; + self->gib_health = -150; + self->mass = 400; + + self->pain = brain_pain; + self->die = brain_die; + + self->monsterinfo.stand = brain_stand; + self->monsterinfo.walk = brain_walk; + self->monsterinfo.run = brain_run; +// PMM + self->monsterinfo.dodge = M_MonsterDodge; + self->monsterinfo.duck = brain_duck; + self->monsterinfo.unduck = monster_duck_up; +// self->monsterinfo.dodge = brain_dodge; +// pmm +// self->monsterinfo.attack = brain_attack; + self->monsterinfo.melee = brain_melee; + self->monsterinfo.sight = brain_sight; + self->monsterinfo.search = brain_search; + self->monsterinfo.idle = brain_idle; + + self->monsterinfo.power_armor_type = POWER_ARMOR_SCREEN; + self->monsterinfo.power_armor_power = 100; + + gi.linkentity (self); + + self->monsterinfo.currentmove = &brain_move_stand; + self->monsterinfo.scale = MODEL_SCALE; + + walkmonster_start (self); +} diff --git a/original/rogue/m_brain.h b/original/rogue/m_brain.h new file mode 100644 index 0000000..acdb7b3 --- /dev/null +++ b/original/rogue/m_brain.h @@ -0,0 +1,230 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// G:\quake2\baseq2\models/monsters/brain + +// This file generated by ModelGen - Do NOT Modify + +#define FRAME_walk101 0 +#define FRAME_walk102 1 +#define FRAME_walk103 2 +#define FRAME_walk104 3 +#define FRAME_walk105 4 +#define FRAME_walk106 5 +#define FRAME_walk107 6 +#define FRAME_walk108 7 +#define FRAME_walk109 8 +#define FRAME_walk110 9 +#define FRAME_walk111 10 +#define FRAME_walk112 11 +#define FRAME_walk113 12 +#define FRAME_walk201 13 +#define FRAME_walk202 14 +#define FRAME_walk203 15 +#define FRAME_walk204 16 +#define FRAME_walk205 17 +#define FRAME_walk206 18 +#define FRAME_walk207 19 +#define FRAME_walk208 20 +#define FRAME_walk209 21 +#define FRAME_walk210 22 +#define FRAME_walk211 23 +#define FRAME_walk212 24 +#define FRAME_walk213 25 +#define FRAME_walk214 26 +#define FRAME_walk215 27 +#define FRAME_walk216 28 +#define FRAME_walk217 29 +#define FRAME_walk218 30 +#define FRAME_walk219 31 +#define FRAME_walk220 32 +#define FRAME_walk221 33 +#define FRAME_walk222 34 +#define FRAME_walk223 35 +#define FRAME_walk224 36 +#define FRAME_walk225 37 +#define FRAME_walk226 38 +#define FRAME_walk227 39 +#define FRAME_walk228 40 +#define FRAME_walk229 41 +#define FRAME_walk230 42 +#define FRAME_walk231 43 +#define FRAME_walk232 44 +#define FRAME_walk233 45 +#define FRAME_walk234 46 +#define FRAME_walk235 47 +#define FRAME_walk236 48 +#define FRAME_walk237 49 +#define FRAME_walk238 50 +#define FRAME_walk239 51 +#define FRAME_walk240 52 +#define FRAME_attak101 53 +#define FRAME_attak102 54 +#define FRAME_attak103 55 +#define FRAME_attak104 56 +#define FRAME_attak105 57 +#define FRAME_attak106 58 +#define FRAME_attak107 59 +#define FRAME_attak108 60 +#define FRAME_attak109 61 +#define FRAME_attak110 62 +#define FRAME_attak111 63 +#define FRAME_attak112 64 +#define FRAME_attak113 65 +#define FRAME_attak114 66 +#define FRAME_attak115 67 +#define FRAME_attak116 68 +#define FRAME_attak117 69 +#define FRAME_attak118 70 +#define FRAME_attak201 71 +#define FRAME_attak202 72 +#define FRAME_attak203 73 +#define FRAME_attak204 74 +#define FRAME_attak205 75 +#define FRAME_attak206 76 +#define FRAME_attak207 77 +#define FRAME_attak208 78 +#define FRAME_attak209 79 +#define FRAME_attak210 80 +#define FRAME_attak211 81 +#define FRAME_attak212 82 +#define FRAME_attak213 83 +#define FRAME_attak214 84 +#define FRAME_attak215 85 +#define FRAME_attak216 86 +#define FRAME_attak217 87 +#define FRAME_pain101 88 +#define FRAME_pain102 89 +#define FRAME_pain103 90 +#define FRAME_pain104 91 +#define FRAME_pain105 92 +#define FRAME_pain106 93 +#define FRAME_pain107 94 +#define FRAME_pain108 95 +#define FRAME_pain109 96 +#define FRAME_pain110 97 +#define FRAME_pain111 98 +#define FRAME_pain112 99 +#define FRAME_pain113 100 +#define FRAME_pain114 101 +#define FRAME_pain115 102 +#define FRAME_pain116 103 +#define FRAME_pain117 104 +#define FRAME_pain118 105 +#define FRAME_pain119 106 +#define FRAME_pain120 107 +#define FRAME_pain121 108 +#define FRAME_pain201 109 +#define FRAME_pain202 110 +#define FRAME_pain203 111 +#define FRAME_pain204 112 +#define FRAME_pain205 113 +#define FRAME_pain206 114 +#define FRAME_pain207 115 +#define FRAME_pain208 116 +#define FRAME_pain301 117 +#define FRAME_pain302 118 +#define FRAME_pain303 119 +#define FRAME_pain304 120 +#define FRAME_pain305 121 +#define FRAME_pain306 122 +#define FRAME_death101 123 +#define FRAME_death102 124 +#define FRAME_death103 125 +#define FRAME_death104 126 +#define FRAME_death105 127 +#define FRAME_death106 128 +#define FRAME_death107 129 +#define FRAME_death108 130 +#define FRAME_death109 131 +#define FRAME_death110 132 +#define FRAME_death111 133 +#define FRAME_death112 134 +#define FRAME_death113 135 +#define FRAME_death114 136 +#define FRAME_death115 137 +#define FRAME_death116 138 +#define FRAME_death117 139 +#define FRAME_death118 140 +#define FRAME_death201 141 +#define FRAME_death202 142 +#define FRAME_death203 143 +#define FRAME_death204 144 +#define FRAME_death205 145 +#define FRAME_duck01 146 +#define FRAME_duck02 147 +#define FRAME_duck03 148 +#define FRAME_duck04 149 +#define FRAME_duck05 150 +#define FRAME_duck06 151 +#define FRAME_duck07 152 +#define FRAME_duck08 153 +#define FRAME_defens01 154 +#define FRAME_defens02 155 +#define FRAME_defens03 156 +#define FRAME_defens04 157 +#define FRAME_defens05 158 +#define FRAME_defens06 159 +#define FRAME_defens07 160 +#define FRAME_defens08 161 +#define FRAME_stand01 162 +#define FRAME_stand02 163 +#define FRAME_stand03 164 +#define FRAME_stand04 165 +#define FRAME_stand05 166 +#define FRAME_stand06 167 +#define FRAME_stand07 168 +#define FRAME_stand08 169 +#define FRAME_stand09 170 +#define FRAME_stand10 171 +#define FRAME_stand11 172 +#define FRAME_stand12 173 +#define FRAME_stand13 174 +#define FRAME_stand14 175 +#define FRAME_stand15 176 +#define FRAME_stand16 177 +#define FRAME_stand17 178 +#define FRAME_stand18 179 +#define FRAME_stand19 180 +#define FRAME_stand20 181 +#define FRAME_stand21 182 +#define FRAME_stand22 183 +#define FRAME_stand23 184 +#define FRAME_stand24 185 +#define FRAME_stand25 186 +#define FRAME_stand26 187 +#define FRAME_stand27 188 +#define FRAME_stand28 189 +#define FRAME_stand29 190 +#define FRAME_stand30 191 +#define FRAME_stand31 192 +#define FRAME_stand32 193 +#define FRAME_stand33 194 +#define FRAME_stand34 195 +#define FRAME_stand35 196 +#define FRAME_stand36 197 +#define FRAME_stand37 198 +#define FRAME_stand38 199 +#define FRAME_stand39 200 +#define FRAME_stand40 201 +#define FRAME_stand41 202 +#define FRAME_stand42 203 +#define FRAME_stand43 204 +#define FRAME_stand44 205 +#define FRAME_stand45 206 +#define FRAME_stand46 207 +#define FRAME_stand47 208 +#define FRAME_stand48 209 +#define FRAME_stand49 210 +#define FRAME_stand50 211 +#define FRAME_stand51 212 +#define FRAME_stand52 213 +#define FRAME_stand53 214 +#define FRAME_stand54 215 +#define FRAME_stand55 216 +#define FRAME_stand56 217 +#define FRAME_stand57 218 +#define FRAME_stand58 219 +#define FRAME_stand59 220 +#define FRAME_stand60 221 + +#define MODEL_SCALE 1.000000 diff --git a/original/rogue/m_carrier.c b/original/rogue/m_carrier.c new file mode 100644 index 0000000..329dec0 --- /dev/null +++ b/original/rogue/m_carrier.c @@ -0,0 +1,1307 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +/* +============================================================================== + +carrier + +============================================================================== +*/ + +// self->timestamp used for frame calculations in grenade & spawn code +// self->wait used to prevent rapid refire of rocket launcher + +#include "g_local.h" +#include "m_carrier.h" + +#define CARRIER_ROCKET_TIME 2 // number of seconds between rocket shots +#define CARRIER_ROCKET_SPEED 750 +#define NUM_FLYERS_SPAWNED 6 // max # of flyers he can spawn + +#define RAIL_FIRE_TIME 3 + +void BossExplode (edict_t *self); +void Grenade_Explode (edict_t *ent); + +qboolean infront (edict_t *self, edict_t *other); +qboolean inback (edict_t *self, edict_t *other); +qboolean below (edict_t *self, edict_t *other); +void drawbbox (edict_t *self); + +//char *ED_NewString (char *string); +void ED_CallSpawn (edict_t *ent); + +static int sound_pain1; +static int sound_pain2; +static int sound_pain3; +static int sound_death; +//static int sound_search1; +static int sound_sight; +static int sound_rail; +static int sound_spawn; + +float orig_yaw_speed; + +vec3_t flyer_mins = {-16, -16, -24}; +vec3_t flyer_maxs = {16, 16, 16}; + +extern mmove_t flyer_move_attack2, flyer_move_attack3, flyer_move_kamikaze; + + +void carrier_run (edict_t *self); +void carrier_stand (edict_t *self); +void carrier_dead (edict_t *self); +void carrier_attack (edict_t *self); +void carrier_attack_mg (edict_t *self); +void carrier_reattack_mg (edict_t *self); +void carrier_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point); + +void carrier_attack_gren (edict_t *self); +void carrier_reattack_gren (edict_t *self); + +void carrier_start_spawn (edict_t *self); +void carrier_spawn_check (edict_t *self); +void carrier_prep_spawn (edict_t *self); + +void CarrierMachineGunHold (edict_t *self); +void CarrierRocket (edict_t *self); + + +void carrier_sight (edict_t *self, edict_t *other) +{ + gi.sound (self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); +} + +// code starts here +//void carrier_search (edict_t *self) +//{ +// if (random() < 0.5) +// gi.sound (self, CHAN_VOICE, sound_search1, 1, ATTN_NONE, 0); +//} + +// +// this is the smarts for the rocket launcher in coop +// +// if there is a player behind/below the carrier, and we can shoot, and we can trace a LOS to them .. +// pick one of the group, and let it rip +void CarrierCoopCheck (edict_t *self) +{ + // no more than 4 players in coop, so.. + edict_t *targets[4]; + int num_targets = 0, target, player; + edict_t *ent; + trace_t tr; + + // if we're not in coop, this is a noop + if (!coop || !coop->value) + return; + // if we are, and we have recently fired, bail + if (self->wait > level.time) + return; + + memset (targets, 0, 4*sizeof(edict_t *)); + + // cycle through players + for (player = 1; player <= game.maxclients; player++) + { + ent = &g_edicts[player]; + if (!ent->inuse) + continue; + if (!ent->client) + continue; + if (inback(self, ent) || below(self, ent)) + { + tr = gi.trace (self->s.origin, NULL, NULL, ent->s.origin, self, MASK_SOLID); + if (tr.fraction == 1.0) + { +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("Carrier: found a player who I can shoot\n"); + targets[num_targets++] = ent; + } + } + } + + if (!num_targets) + return; + + // get a number from 0 to (num_targets-1) + target = random() * num_targets; + + // just in case we got a 1.0 from random + if (target == num_targets) + target--; + + // make sure to prevent rapid fire rockets + self->wait = level.time + CARRIER_ROCKET_TIME; + + // save off the real enemy + ent = self->enemy; + // set the new guy as temporary enemy + self->enemy = targets[target]; + CarrierRocket (self); + // put the real enemy back + self->enemy = ent; + + // we're done + return; +} + +void CarrierGrenade (edict_t *self) +{ + vec3_t start; + vec3_t forward, right, up; + vec3_t aim; + int flash_number; + float direction; // from lower left to upper right, or lower right to upper left + float spreadR, spreadU; + int mytime; + + CarrierCoopCheck(self); + + if (!self->enemy) + return; + + if (random() < 0.5) + direction = -1.0; + else + direction = 1.0; + + mytime = (int)((level.time - self->timestamp)/0.4); + + if (mytime == 0) + { + spreadR = 0.15 * direction; +// spreadU = 0.1 * direction; + spreadU = 0.1 - 0.1 * direction; + } + else if (mytime == 1) + { + spreadR = 0; +// spreadU = 0; + spreadU = 0.1; + } + else if (mytime == 2) + { + spreadR = -0.15 * direction; +// spreadU = -0.1 * direction; + spreadU = 0.1 - -0.1 * direction; + } + else if (mytime == 3) + { + spreadR = 0; +// spreadU = 0; + spreadU = 0.1; + } + else + { + // error, shoot straight +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("CarrierGrenade: bad time %2.2f %2.2f\n", level.time, self->timestamp); + spreadR = 0; + spreadU = 0; + } + + AngleVectors (self->s.angles, forward, right, up); + G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_CARRIER_GRENADE], forward, right, start); + + VectorSubtract (self->enemy->s.origin, start, aim); + VectorNormalize (aim); + + VectorMA (aim, spreadR, right, aim); + VectorMA (aim, spreadU, up, aim); + + if(aim[2] > 0.15) + aim[2] = 0.15; + else if(aim[2] < -0.5) + aim[2] = -0.5; + + flash_number = MZ2_GUNNER_GRENADE_1; + monster_fire_grenade (self, start, aim, 50, 600, flash_number); +} + +void CarrierPredictiveRocket (edict_t *self) +{ + vec3_t forward, right; + vec3_t start; + vec3_t dir; + +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf("predictive fire\n"); + + AngleVectors (self->s.angles, forward, right, NULL); + +//1 + G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_CARRIER_ROCKET_1], forward, right, start); + PredictAim (self->enemy, start, CARRIER_ROCKET_SPEED, false, -0.3, dir, NULL); + monster_fire_rocket (self, start, dir, 50, CARRIER_ROCKET_SPEED, MZ2_CARRIER_ROCKET_1); + +//2 + G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_CARRIER_ROCKET_2], forward, right, start); + PredictAim (self->enemy, start, CARRIER_ROCKET_SPEED, false, -0.15, dir, NULL); + monster_fire_rocket (self, start, dir, 50, CARRIER_ROCKET_SPEED, MZ2_CARRIER_ROCKET_2); + +//3 + G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_CARRIER_ROCKET_3], forward, right, start); + PredictAim (self->enemy, start, CARRIER_ROCKET_SPEED, false, 0, dir, NULL); + monster_fire_rocket (self, start, dir, 50, CARRIER_ROCKET_SPEED, MZ2_CARRIER_ROCKET_3); + +//4 + G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_CARRIER_ROCKET_4], forward, right, start); + PredictAim (self->enemy, start, CARRIER_ROCKET_SPEED, false, 0.15, dir, NULL); + monster_fire_rocket (self, start, dir, 50, CARRIER_ROCKET_SPEED, MZ2_CARRIER_ROCKET_4); +} + +void CarrierRocket (edict_t *self) +{ + vec3_t forward, right; + vec3_t start; + vec3_t dir; + vec3_t vec; + + if(self->enemy) + { + if(self->enemy->client && random() < 0.5) + { + CarrierPredictiveRocket(self); + return; + } + } + else + return; + + AngleVectors (self->s.angles, forward, right, NULL); + +//1 + G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_CARRIER_ROCKET_1], forward, right, start); + VectorCopy (self->enemy->s.origin, vec); +// vec[2] += self->enemy->viewheight; + vec[2] -= 15; + VectorSubtract (vec, start, dir); + VectorNormalize (dir); + VectorMA (dir, 0.4, right, dir); + VectorNormalize (dir); + monster_fire_rocket (self, start, dir, 50, 500, MZ2_CARRIER_ROCKET_1); + +//2 + G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_CARRIER_ROCKET_2], forward, right, start); + VectorCopy (self->enemy->s.origin, vec); +// vec[2] += self->enemy->viewheight; + VectorSubtract (vec, start, dir); + VectorNormalize (dir); + VectorMA (dir, 0.025, right, dir); + VectorNormalize (dir); + monster_fire_rocket (self, start, dir, 50, 500, MZ2_CARRIER_ROCKET_2); + +//3 + G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_CARRIER_ROCKET_3], forward, right, start); + VectorCopy (self->enemy->s.origin, vec); +// vec[2] += self->enemy->viewheight; + VectorSubtract (vec, start, dir); + VectorNormalize (dir); + VectorMA (dir, -0.025, right, dir); + VectorNormalize (dir); + monster_fire_rocket (self, start, dir, 50, 500, MZ2_CARRIER_ROCKET_3); + +//4 + G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_CARRIER_ROCKET_4], forward, right, start); + VectorCopy (self->enemy->s.origin, vec); +// vec[2] += self->enemy->viewheight; + vec[2] -= 15; + VectorSubtract (vec, start, dir); + VectorNormalize (dir); + VectorMA (dir, -0.4, right, dir); + VectorNormalize (dir); + monster_fire_rocket (self, start, dir, 50, 500, MZ2_CARRIER_ROCKET_4); + +//5 +// G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_BOSS2_ROCKET_4], forward, right, start); +// VectorCopy (self->enemy->s.origin, vec); +// vec[2] += self->enemy->viewheight; +// VectorSubtract (vec, start, dir); +// VectorNormalize (dir); +// monster_fire_rocket (self, start, dir, 50, 500, MZ2_BOSS2_ROCKET_2); +} + +void carrier_firebullet_right (edict_t *self) +{ + vec3_t forward, right, target; + vec3_t start; + int flashnum; + + // if we're in manual steering mode, it means we're leaning down .. use the lower shot + if (self->monsterinfo.aiflags & AI_MANUAL_STEERING) + flashnum = MZ2_CARRIER_MACHINEGUN_R2; + else + flashnum = MZ2_CARRIER_MACHINEGUN_R1; + + AngleVectors (self->s.angles, forward, right, NULL); + G_ProjectSource (self->s.origin, monster_flash_offset[flashnum], forward, right, start); + +// VectorMA (self->enemy->s.origin, -0.2, self->enemy->velocity, target); + VectorMA (self->enemy->s.origin, 0.2, self->enemy->velocity, target); + target[2] += self->enemy->viewheight; +/* + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_DEBUGTRAIL); + gi.WritePosition (start); + gi.WritePosition (target); + gi.multicast (start, MULTICAST_ALL); +*/ + VectorSubtract (target, start, forward); + VectorNormalize (forward); + + monster_fire_bullet (self, start, forward, 6, 4, DEFAULT_BULLET_HSPREAD*3, DEFAULT_BULLET_VSPREAD, flashnum); +} + +void carrier_firebullet_left (edict_t *self) +{ + vec3_t forward, right, target; + vec3_t start; + int flashnum; + + // if we're in manual steering mode, it means we're leaning down .. use the lower shot + if (self->monsterinfo.aiflags & AI_MANUAL_STEERING) + flashnum = MZ2_CARRIER_MACHINEGUN_L2; + else + flashnum = MZ2_CARRIER_MACHINEGUN_L1; + + AngleVectors (self->s.angles, forward, right, NULL); + G_ProjectSource (self->s.origin, monster_flash_offset[flashnum], forward, right, start); + +// VectorMA (self->enemy->s.origin, 0.2, self->enemy->velocity, target); + VectorMA (self->enemy->s.origin, -0.2, self->enemy->velocity, target); + + target[2] += self->enemy->viewheight; + VectorSubtract (target, start, forward); +/* + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_DEBUGTRAIL); + gi.WritePosition (start); + gi.WritePosition (target); + gi.multicast (start, MULTICAST_ALL); +*/ + VectorNormalize (forward); + + monster_fire_bullet (self, start, forward, 6, 4, DEFAULT_BULLET_HSPREAD*3, DEFAULT_BULLET_VSPREAD, flashnum); +} + +void CarrierMachineGun (edict_t *self) +{ + CarrierCoopCheck(self); + if (self->enemy) + carrier_firebullet_left(self); + if (self->enemy) + carrier_firebullet_right(self); +} + +void CarrierSpawn (edict_t *self) +{ + vec3_t f, r, offset, startpoint, spawnpoint; + edict_t *ent; + int mytime; + +// VectorSet (offset, 105, 0, -30); // real distance needed is (sqrt (56*56*2) + sqrt(16*16*2)) or 101.8 + VectorSet (offset, 105, 0, -58); // real distance needed is (sqrt (56*56*2) + sqrt(16*16*2)) or 101.8 + AngleVectors (self->s.angles, f, r, NULL); + + G_ProjectSource (self->s.origin, offset, f, r, startpoint); + + // the +0.1 is because level.time is sometimes a little low + mytime = (int)((level.time + 0.1 - self->timestamp)/0.5); +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("mytime = %d, (%2.2f)\n", mytime, level.time - self->timestamp); + + if (FindSpawnPoint (startpoint, flyer_mins, flyer_maxs, spawnpoint, 32)) + { + // the second flier should be a kamikaze flyer + if (mytime != 2) + ent = CreateMonster (spawnpoint, self->s.angles, "monster_flyer"); + else + ent = CreateMonster (spawnpoint, self->s.angles, "monster_kamikaze"); + + if (!ent) + return; + + gi.sound (self, CHAN_BODY, sound_spawn, 1, ATTN_NONE, 0); + + self->monsterinfo.monster_slots--; +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("carrier: post-spawn : %d slots left\n", self->monsterinfo.monster_slots); + + ent->nextthink = level.time; + ent->think (ent); + + ent->monsterinfo.aiflags |= AI_SPAWNED_CARRIER|AI_DO_NOT_COUNT|AI_IGNORE_SHOTS; + ent->monsterinfo.commander = self; + + if ((self->enemy->inuse) && (self->enemy->health > 0)) + { + ent->enemy = self->enemy; + FoundTarget (ent); + if (mytime == 1) + { + ent->monsterinfo.lefty = 0; + ent->monsterinfo.attack_state = AS_SLIDING; + ent->monsterinfo.currentmove = &flyer_move_attack3; + } + else if (mytime == 2) + { + ent->monsterinfo.lefty = 0; + ent->monsterinfo.attack_state = AS_STRAIGHT; + ent->monsterinfo.currentmove = &flyer_move_kamikaze; + ent->mass = 100; + ent->monsterinfo.aiflags |= AI_CHARGING; + } + else if (mytime == 3) + { + ent->monsterinfo.lefty = 1; + ent->monsterinfo.attack_state = AS_SLIDING; + ent->monsterinfo.currentmove = &flyer_move_attack3; + } +// else if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("carrier: unexpected time %d!\n", mytime); + } + } +} + +void carrier_prep_spawn (edict_t *self) +{ + CarrierCoopCheck(self); + self->monsterinfo.aiflags |= AI_MANUAL_STEERING; + self->timestamp = level.time; + self->yaw_speed = 10; + CarrierMachineGun(self); +} + +void carrier_spawn_check (edict_t *self) +{ +// gi.dprintf ("times - %2.2f %2.2f\n", level.time, self->timestamp); + CarrierCoopCheck(self); + CarrierMachineGun(self); + CarrierSpawn (self); + + if (level.time > (self->timestamp + 1.1)) // 0.5 seconds per flyer. this gets three + { + self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING; + self->yaw_speed = orig_yaw_speed; + return; + } + else + self->monsterinfo.nextframe = FRAME_spawn08; +} + +void carrier_ready_spawn (edict_t *self) +{ + float current_yaw; + vec3_t offset, f, r, startpoint, spawnpoint; + + CarrierCoopCheck(self); + CarrierMachineGun(self); + + current_yaw = anglemod(self->s.angles[YAW]); + +// gi.dprintf ("yaws = %2.2f %2.2f\n", current_yaw, self->ideal_yaw); + + if (fabs(current_yaw - self->ideal_yaw) > 0.1) + { + self->monsterinfo.aiflags |= AI_HOLD_FRAME; + self->timestamp += FRAMETIME; + return; + } + + self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; + + VectorSet (offset, 105,0,-58); + AngleVectors (self->s.angles, f, r, NULL); + G_ProjectSource (self->s.origin, offset, f, r, startpoint); + if (FindSpawnPoint (startpoint, flyer_mins, flyer_maxs, spawnpoint, 32)) + { + SpawnGrow_Spawn (spawnpoint, 0); + } +} + +void carrier_start_spawn (edict_t *self) +{ + int mytime; + float enemy_yaw; + vec3_t temp; +// vec3_t offset, f, r, startpoint; + + CarrierCoopCheck(self); + if (!orig_yaw_speed) + orig_yaw_speed = self->yaw_speed; + + if (!self->enemy) + return; + + mytime = (int)((level.time - self->timestamp)/0.5); + + VectorSubtract (self->enemy->s.origin, self->s.origin, temp); + enemy_yaw = vectoyaw2(temp); + + // note that the offsets are based on a forward of 105 from the end angle + if (mytime == 0) + { + self->ideal_yaw = anglemod(enemy_yaw - 30); +// VectorSet (offset, 90.9, 52.5, 0); + } + else if (mytime == 1) + { + self->ideal_yaw = anglemod(enemy_yaw); +// VectorSet (offset, 90.9, -52.5, 0); + } + else if (mytime == 2) + { + self->ideal_yaw = anglemod(enemy_yaw + 30); +// VectorSet (offset, 90.9, -52.5, 0); + } +// else if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("carrier: bad spawntime\n"); + + CarrierMachineGun (self); +} + +mframe_t carrier_frames_stand [] = +{ +// ai_stand, 0, drawbbox, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL +}; +mmove_t carrier_move_stand = {FRAME_search01, FRAME_search13, carrier_frames_stand, NULL}; + +mframe_t carrier_frames_walk [] = +{ +// ai_walk, 12, drawbbox, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL +}; +mmove_t carrier_move_walk = {FRAME_search01, FRAME_search13, carrier_frames_walk, NULL}; + + +mframe_t carrier_frames_run [] = +{ +// ai_run, 12, drawbbox, + ai_run, 6, CarrierCoopCheck, + ai_run, 6, CarrierCoopCheck, + ai_run, 6, CarrierCoopCheck, + ai_run, 6, CarrierCoopCheck, + ai_run, 6, CarrierCoopCheck, + ai_run, 6, CarrierCoopCheck, + ai_run, 6, CarrierCoopCheck, + ai_run, 6, CarrierCoopCheck, + ai_run, 6, CarrierCoopCheck, + ai_run, 6, CarrierCoopCheck, + ai_run, 6, CarrierCoopCheck, + ai_run, 6, CarrierCoopCheck, + ai_run, 6, CarrierCoopCheck +}; +mmove_t carrier_move_run = {FRAME_search01, FRAME_search13, carrier_frames_run, NULL}; + +mframe_t carrier_frames_attack_pre_mg [] = +{ + ai_charge, 4, CarrierCoopCheck, + ai_charge, 4, CarrierCoopCheck, + ai_charge, 4, CarrierCoopCheck, + ai_charge, 4, CarrierCoopCheck, + ai_charge, 4, CarrierCoopCheck, + ai_charge, 4, CarrierCoopCheck, + ai_charge, 4, CarrierCoopCheck, + ai_charge, 4, carrier_attack_mg +}; +mmove_t carrier_move_attack_pre_mg = {FRAME_firea01, FRAME_firea08, carrier_frames_attack_pre_mg, NULL}; + + +// Loop this +mframe_t carrier_frames_attack_mg [] = +{ + ai_charge, -2, CarrierMachineGun, + ai_charge, -2, CarrierMachineGun, + ai_charge, -2, carrier_reattack_mg +/* + ai_charge, 0, CarrierMachineGunHold, +// ai_charge, 0, CarrierMachineGun, + ai_charge, 0, CarrierMachineGun, + ai_charge, 0, carrier_reattack_mg +*/ +}; +mmove_t carrier_move_attack_mg = {FRAME_firea09, FRAME_firea11, carrier_frames_attack_mg, NULL}; + +mframe_t carrier_frames_attack_post_mg [] = +{ + ai_charge, 4, CarrierCoopCheck, + ai_charge, 4, CarrierCoopCheck, + ai_charge, 4, CarrierCoopCheck, + ai_charge, 4, CarrierCoopCheck +}; +mmove_t carrier_move_attack_post_mg = {FRAME_firea12, FRAME_firea15, carrier_frames_attack_post_mg, carrier_run}; + +mframe_t carrier_frames_attack_pre_gren [] = +{ + ai_charge, 4, CarrierCoopCheck, + ai_charge, 4, CarrierCoopCheck, + ai_charge, 4, CarrierCoopCheck, + ai_charge, 4, CarrierCoopCheck, + ai_charge, 4, CarrierCoopCheck, + ai_charge, 4, carrier_attack_gren +}; +mmove_t carrier_move_attack_pre_gren = {FRAME_fireb01, FRAME_fireb06, carrier_frames_attack_pre_gren, NULL}; + +mframe_t carrier_frames_attack_gren [] = +{ + ai_charge, -15, CarrierGrenade, + ai_charge, 4, CarrierCoopCheck, + ai_charge, 4, CarrierCoopCheck, + ai_charge, 4, carrier_reattack_gren +}; +mmove_t carrier_move_attack_gren = {FRAME_fireb07, FRAME_fireb10, carrier_frames_attack_gren, NULL}; + +mframe_t carrier_frames_attack_post_gren [] = +{ + ai_charge, 4, CarrierCoopCheck, + ai_charge, 4, CarrierCoopCheck, + ai_charge, 4, CarrierCoopCheck, + ai_charge, 4, CarrierCoopCheck, + ai_charge, 4, CarrierCoopCheck, + ai_charge, 4, CarrierCoopCheck +}; +mmove_t carrier_move_attack_post_gren = {FRAME_fireb11, FRAME_fireb16, carrier_frames_attack_post_gren, carrier_run}; + +mframe_t carrier_frames_attack_rocket [] = +{ + ai_charge, 15, CarrierRocket +}; +mmove_t carrier_move_attack_rocket = {FRAME_fireb01, FRAME_fireb01, carrier_frames_attack_rocket, carrier_run}; + +void CarrierRail (edict_t *self) +{ + vec3_t start; + vec3_t dir; + vec3_t forward, right; + + CarrierCoopCheck(self); + AngleVectors (self->s.angles, forward, right, NULL); + G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_CARRIER_RAILGUN], forward, right, start); + + // calc direction to where we targeted + VectorSubtract (self->pos1, start, dir); + VectorNormalize (dir); + + monster_fire_railgun (self, start, dir, 50, 100, MZ2_CARRIER_RAILGUN); + self->monsterinfo.attack_finished = level.time + RAIL_FIRE_TIME; +} + +void CarrierSaveLoc (edict_t *self) +{ + CarrierCoopCheck(self); + VectorCopy (self->enemy->s.origin, self->pos1); //save for aiming the shot + self->pos1[2] += self->enemy->viewheight; +}; + +mframe_t carrier_frames_attack_rail [] = +{ + ai_charge, 2, CarrierCoopCheck, + ai_charge, 2, CarrierSaveLoc, + ai_charge, 2, CarrierCoopCheck, + ai_charge, -20, CarrierRail, + ai_charge, 2, CarrierCoopCheck, + ai_charge, 2, CarrierCoopCheck, + ai_charge, 2, CarrierCoopCheck, + ai_charge, 2, CarrierCoopCheck, + ai_charge, 2, CarrierCoopCheck +}; +mmove_t carrier_move_attack_rail = {FRAME_search01, FRAME_search09, carrier_frames_attack_rail, carrier_run}; + +mframe_t carrier_frames_spawn [] = +{ + ai_charge, -2, CarrierMachineGun, + ai_charge, -2, CarrierMachineGun, + ai_charge, -2, CarrierMachineGun, + ai_charge, -2, CarrierMachineGun, + ai_charge, -2, CarrierMachineGun, + ai_charge, -2, CarrierMachineGun, + ai_charge, -2, carrier_prep_spawn, // 7 - end of wind down + ai_charge, -2, carrier_start_spawn, // 8 - start of spawn + ai_charge, -2, carrier_ready_spawn, + ai_charge, -2, CarrierMachineGun, + ai_charge, -2, CarrierMachineGun, + ai_charge, -10, carrier_spawn_check, //12 - actual spawn + ai_charge, -2, CarrierMachineGun, //13 - begin of wind down + ai_charge, -2, CarrierMachineGun, + ai_charge, -2, CarrierMachineGun, + ai_charge, -2, CarrierMachineGun, + ai_charge, -2, CarrierMachineGun, + ai_charge, -2, carrier_reattack_mg //18 - end of wind down +}; +mmove_t carrier_move_spawn = {FRAME_spawn01, FRAME_spawn18, carrier_frames_spawn, NULL}; + +mframe_t carrier_frames_pain_heavy [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t carrier_move_pain_heavy = {FRAME_death01, FRAME_death10, carrier_frames_pain_heavy, carrier_run}; + +mframe_t carrier_frames_pain_light [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t carrier_move_pain_light = {FRAME_spawn01, FRAME_spawn04, carrier_frames_pain_light, carrier_run}; + +mframe_t carrier_frames_death [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, BossExplode +}; +mmove_t carrier_move_death = {FRAME_death01, FRAME_death16, carrier_frames_death, carrier_dead}; + +void carrier_stand (edict_t *self) +{ +// gi.dprintf ("carrier stand\n"); + self->monsterinfo.currentmove = &carrier_move_stand; +} + +void carrier_run (edict_t *self) +{ + +// gi.dprintf ("carrier run - %2.2f - %s \n", level.time, self->enemy->classname); + self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; + + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + self->monsterinfo.currentmove = &carrier_move_stand; + else + self->monsterinfo.currentmove = &carrier_move_run; +} + +void carrier_walk (edict_t *self) +{ + self->monsterinfo.currentmove = &carrier_move_walk; +} + +void CarrierMachineGunHold (edict_t *self) +{ +// self->monsterinfo.aiflags |= AI_HOLD_FRAME; +// self->yaw_speed = 0; +// self->monsterinfo.currentmove = &carrier_move_attack_mg; + CarrierMachineGun (self); +} + +void carrier_attack (edict_t *self) +{ + vec3_t vec; + float range, luck; + qboolean enemy_inback, enemy_infront, enemy_below; + +// gi.dprintf ("carrier attack\n"); + + self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; + + if ((!self->enemy) || (!self->enemy->inuse)) + return; + + enemy_inback = inback(self, self->enemy); + enemy_infront = infront (self, self->enemy); + enemy_below = below (self, self->enemy); + + if (self->bad_area) + { + if ((enemy_inback) || (enemy_below)) + self->monsterinfo.currentmove = &carrier_move_attack_rocket; + else if ((random() < 0.1) || (level.time < self->monsterinfo.attack_finished)) + self->monsterinfo.currentmove = &carrier_move_attack_pre_mg; + else + { + gi.sound (self, CHAN_WEAPON, sound_rail, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &carrier_move_attack_rail; + } + return; + } + + if (self->monsterinfo.attack_state == AS_BLIND) + { + self->monsterinfo.currentmove = &carrier_move_spawn; + return; + } + + if (!enemy_inback && !enemy_infront && !enemy_below) // to side and not under + { + if ((random() < 0.1) || (level.time < self->monsterinfo.attack_finished)) + self->monsterinfo.currentmove = &carrier_move_attack_pre_mg; + else + { + gi.sound (self, CHAN_WEAPON, sound_rail, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &carrier_move_attack_rail; + } + return; + } + +/* if ((g_showlogic) && (g_showlogic->value)) + { + gi.dprintf ("checking enemy .."); + if (enemy_inback) + gi.dprintf (" in back\n"); + else if (enemy_infront) + gi.dprintf (" in front\n"); + else + gi.dprintf (" inaccessible\n"); + } +*/ + if (enemy_infront) + { + VectorSubtract (self->enemy->s.origin, self->s.origin, vec); + range = VectorLength (vec); + if (range <= 125) + { + if ((random() < 0.8) || (level.time < self->monsterinfo.attack_finished)) + self->monsterinfo.currentmove = &carrier_move_attack_pre_mg; + else + { + gi.sound (self, CHAN_WEAPON, sound_rail, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &carrier_move_attack_rail; + } + } + else if (range < 600) + { + luck = random(); + if (self->monsterinfo.monster_slots > 2) + { + if (luck <= 0.20) + self->monsterinfo.currentmove = &carrier_move_attack_pre_mg; + else if (luck <= 0.40) + self->monsterinfo.currentmove = &carrier_move_attack_pre_gren; + else if ((luck <= 0.7) && !(level.time < self->monsterinfo.attack_finished)) + { + gi.sound (self, CHAN_WEAPON, sound_rail, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &carrier_move_attack_rail; + } + else + self->monsterinfo.currentmove = &carrier_move_spawn; + } + else + { + if (luck <= 0.30) + self->monsterinfo.currentmove = &carrier_move_attack_pre_mg; + else if (luck <= 0.65) + self->monsterinfo.currentmove = &carrier_move_attack_pre_gren; + else if (level.time >= self->monsterinfo.attack_finished) + { + gi.sound (self, CHAN_WEAPON, sound_rail, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &carrier_move_attack_rail; + } + else + self->monsterinfo.currentmove = &carrier_move_attack_pre_mg; + } + } + else // won't use grenades at this range + { + luck = random(); + if (self->monsterinfo.monster_slots > 2) + { + if (luck < 0.3) + self->monsterinfo.currentmove = &carrier_move_attack_pre_mg; + else if ((luck < 0.65) && !(level.time < self->monsterinfo.attack_finished)) + { + gi.sound (self, CHAN_WEAPON, sound_rail, 1, ATTN_NORM, 0); + VectorCopy (self->enemy->s.origin, self->pos1); //save for aiming the shot + self->pos1[2] += self->enemy->viewheight; + self->monsterinfo.currentmove = &carrier_move_attack_rail; + } + else + self->monsterinfo.currentmove = &carrier_move_spawn; + } + else + { + if ((luck < 0.45) || (level.time < self->monsterinfo.attack_finished)) + self->monsterinfo.currentmove = &carrier_move_attack_pre_mg; + else + { + gi.sound (self, CHAN_WEAPON, sound_rail, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &carrier_move_attack_rail; + } + } + } + } + else if ((enemy_below) || (enemy_inback)) + { + self->monsterinfo.currentmove = &carrier_move_attack_rocket; + } +} + +void carrier_attack_mg (edict_t *self) +{ + CarrierCoopCheck(self); + self->monsterinfo.currentmove = &carrier_move_attack_mg; +} + +void carrier_reattack_mg (edict_t *self) +{ + CarrierCoopCheck(self); + if ( infront(self, self->enemy) ) + if (random() <= 0.5) + if ((random() < 0.7) || (self->monsterinfo.monster_slots <= 2)) + self->monsterinfo.currentmove = &carrier_move_attack_mg; + else + self->monsterinfo.currentmove = &carrier_move_spawn; + else + self->monsterinfo.currentmove = &carrier_move_attack_post_mg; + else + self->monsterinfo.currentmove = &carrier_move_attack_post_mg; +} + + +void carrier_attack_gren (edict_t *self) +{ +// gi.dprintf ("carrier_attack_gren - %2.2f\n",level.time); + CarrierCoopCheck(self); + self->timestamp = level.time; + self->monsterinfo.currentmove = &carrier_move_attack_gren; +} + +void carrier_reattack_gren (edict_t *self) +{ + CarrierCoopCheck(self); +// gi.dprintf ("carrier_reattack - %2.2f", level.time); + if ( infront(self, self->enemy) ) + if (self->timestamp + 1.3 > level.time ) // four grenades + { +// gi.dprintf (" attacking\n"); + self->monsterinfo.currentmove = &carrier_move_attack_gren; + return; + } +// gi.dprintf ("not attacking\n"); + self->monsterinfo.currentmove = &carrier_move_attack_post_gren; +} + + +void carrier_pain (edict_t *self, edict_t *other, float kick, int damage) +{ + qboolean changed = false; + + if (self->health < (self->max_health / 2)) + self->s.skinnum = 1; + + if (skill->value == 3) + return; // no pain anims in nightmare + + // gi.dprintf ("carrier pain\n"); + if (level.time < self->pain_debounce_time) + return; + + self->pain_debounce_time = level.time + 5; + + if (damage < 10) + { + gi.sound (self, CHAN_VOICE, sound_pain3, 1, ATTN_NONE, 0); + } + else if (damage < 30) + { + gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NONE, 0); + if (random() < 0.5) + { + changed = true; + self->monsterinfo.currentmove = &carrier_move_pain_light; + } + } + else + { + gi.sound (self, CHAN_VOICE, sound_pain2, 1, ATTN_NONE, 0); + self->monsterinfo.currentmove = &carrier_move_pain_heavy; + changed = true; + } + + // if we changed frames, clean up our little messes + if (changed) + { + self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; + self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING; + self->yaw_speed = orig_yaw_speed; + } +} + +void carrier_dead (edict_t *self) +{ + VectorSet (self->mins, -56, -56, 0); + VectorSet (self->maxs, 56, 56, 80); + self->movetype = MOVETYPE_TOSS; + self->svflags |= SVF_DEADMONSTER; + self->nextthink = 0; + gi.linkentity (self); +} + +void carrier_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + gi.sound (self, CHAN_VOICE, sound_death, 1, ATTN_NONE, 0); + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_NO; + self->count = 0; + self->monsterinfo.currentmove = &carrier_move_death; +} + +qboolean Carrier_CheckAttack (edict_t *self) +{ + vec3_t spot1, spot2; + vec3_t temp; + float chance; + trace_t tr; + qboolean enemy_infront, enemy_inback, enemy_below; + int enemy_range; + float enemy_yaw; + + if (self->enemy->health > 0) + { + // see if any entities are in the way of the shot + VectorCopy (self->s.origin, spot1); + spot1[2] += self->viewheight; + VectorCopy (self->enemy->s.origin, spot2); + spot2[2] += self->enemy->viewheight; + + tr = gi.trace (spot1, NULL, NULL, spot2, self, CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_SLIME|CONTENTS_LAVA); + + // do we have a clear shot? + if (tr.ent != self->enemy) + { + // go ahead and spawn stuff if we're mad a a client + if (self->enemy->client && self->monsterinfo.monster_slots > 2) + { + self->monsterinfo.attack_state = AS_BLIND; + return true; + } + + // PGM - we want them to go ahead and shoot at info_notnulls if they can. + if(self->enemy->solid != SOLID_NOT || tr.fraction < 1.0) //PGM + return false; + } + } + + enemy_infront = infront(self, self->enemy); + enemy_inback = inback(self, self->enemy); + enemy_below = below (self, self->enemy); + + enemy_range = range(self, self->enemy); + VectorSubtract (self->enemy->s.origin, self->s.origin, temp); + enemy_yaw = vectoyaw2(temp); + + self->ideal_yaw = enemy_yaw; + + // PMM - shoot out the back if appropriate + if ((enemy_inback) || (!enemy_infront && enemy_below)) + { + // this is using wait because the attack is supposed to be independent + if (level.time >= self->wait) + { + self->wait = level.time + CARRIER_ROCKET_TIME; + self->monsterinfo.attack(self); + if (random() < 0.6) + self->monsterinfo.attack_state = AS_SLIDING; + else + self->monsterinfo.attack_state = AS_STRAIGHT; + return true; + } + } + + // melee attack + if (enemy_range == RANGE_MELEE) + { + self->monsterinfo.attack_state = AS_MISSILE; + return true; + } + +// if (level.time < self->monsterinfo.attack_finished) +// return false; + + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + { + chance = 0.4; + } + else if (enemy_range == RANGE_MELEE) + { + chance = 0.8; + } + else if (enemy_range == RANGE_NEAR) + { + chance = 0.8; + } + else if (enemy_range == RANGE_MID) + { + chance = 0.8; + } + else if (enemy_range == RANGE_FAR) + { + chance = 0.5; + } + + // PGM - go ahead and shoot every time if it's a info_notnull + if ((random () < chance) || (self->enemy->solid == SOLID_NOT)) + { + self->monsterinfo.attack_state = AS_MISSILE; +// self->monsterinfo.attack_finished = level.time + 2*random(); + return true; + } + + if (self->flags & FL_FLY) + { + if (random() < 0.6) + self->monsterinfo.attack_state = AS_SLIDING; + else + self->monsterinfo.attack_state = AS_STRAIGHT; + } + + return false; +} + +void CarrierPrecache () +{ + gi.soundindex ("flyer/flysght1.wav"); + gi.soundindex ("flyer/flysrch1.wav"); + gi.soundindex ("flyer/flypain1.wav"); + gi.soundindex ("flyer/flypain2.wav"); + gi.soundindex ("flyer/flyatck2.wav"); + gi.soundindex ("flyer/flyatck1.wav"); + gi.soundindex ("flyer/flydeth1.wav"); + gi.soundindex ("flyer/flyatck3.wav"); + gi.soundindex ("flyer/flyidle1.wav"); + gi.soundindex ("weapons/rockfly.wav"); + gi.soundindex ("infantry/infatck1.wav"); + gi.soundindex ("gunner/gunatck3.wav"); + gi.soundindex ("weapons/grenlb1b.wav"); + gi.soundindex ("tank/rocket.wav"); + + gi.modelindex ("models/monsters/flyer/tris.md2"); + gi.modelindex ("models/objects/rocket/tris.md2"); + gi.modelindex ("models/objects/debris2/tris.md2"); + gi.modelindex ("models/objects/grenade/tris.md2"); + gi.modelindex("models/items/spawngro/tris.md2"); + gi.modelindex("models/items/spawngro2/tris.md2"); + gi.modelindex ("models/objects/gibs/sm_metal/tris.md2"); + gi.modelindex ("models/objects/gibs/gear/tris.md2"); +} + + +/*QUAKED monster_carrier (1 .5 0) (-56 -56 -44) (56 56 44) Ambush Trigger_Spawn Sight +*/ +void SP_monster_carrier (edict_t *self) +{ + if (deathmatch->value) + { + G_FreeEdict (self); + return; + } + + sound_pain1 = gi.soundindex ("carrier/pain_md.wav"); + sound_pain2 = gi.soundindex ("carrier/pain_lg.wav"); + sound_pain3 = gi.soundindex ("carrier/pain_sm.wav"); + sound_death = gi.soundindex ("carrier/death.wav"); +// sound_search1 = gi.soundindex ("bosshovr/bhvunqv1.wav"); + sound_rail = gi.soundindex ("gladiator/railgun.wav"); + sound_sight = gi.soundindex ("carrier/sight.wav"); + sound_spawn = gi.soundindex ("medic_commander/monsterspawn1.wav"); + + self->s.sound = gi.soundindex ("bosshovr/bhvengn1.wav"); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex ("models/monsters/carrier/tris.md2"); + VectorSet (self->mins, -56, -56, -44); + VectorSet (self->maxs, 56, 56, 44); + + // 2000 - 4000 health + self->health = max (2000, 2000 + 1000*((skill->value)-1)); + // add health in coop (500 * skill) + if (coop->value) + self->health += 500*(skill->value); + + self->gib_health = -200; + self->mass = 1000; + + self->yaw_speed = 15; + orig_yaw_speed = self->yaw_speed; +// self->yaw_speed = 1; + + self->flags |= FL_IMMUNE_LASER; + self->monsterinfo.aiflags |= AI_IGNORE_SHOTS; + + self->pain = carrier_pain; + self->die = carrier_die; + + self->monsterinfo.melee = NULL; + self->monsterinfo.stand = carrier_stand; + self->monsterinfo.walk = carrier_walk; + self->monsterinfo.run = carrier_run; + self->monsterinfo.attack = carrier_attack; +// self->monsterinfo.search = carrier_search; + self->monsterinfo.sight = carrier_sight; + self->monsterinfo.checkattack = Carrier_CheckAttack; + gi.linkentity (self); + + self->monsterinfo.currentmove = &carrier_move_stand; + self->monsterinfo.scale = MODEL_SCALE; + + CarrierPrecache(); + + flymonster_start (self); + + self->monsterinfo.attack_finished = 0; + switch ((int)skill->value) + { + case 0: + self->monsterinfo.monster_slots = 3; + break; + case 1: + case 2: + self->monsterinfo.monster_slots = 6; + break; + case 3: + self->monsterinfo.monster_slots = 9; + break; + default: + self->monsterinfo.monster_slots = 6; + break; + } +} + diff --git a/original/rogue/m_carrier.h b/original/rogue/m_carrier.h new file mode 100644 index 0000000..ef3d84c --- /dev/null +++ b/original/rogue/m_carrier.h @@ -0,0 +1,86 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// G:\quake2\xpack\models/monsters/carrier + +// This file generated by qdata - Do NOT Modify + +#define FRAME_search01 0 +#define FRAME_search02 1 +#define FRAME_search03 2 +#define FRAME_search04 3 +#define FRAME_search05 4 +#define FRAME_search06 5 +#define FRAME_search07 6 +#define FRAME_search08 7 +#define FRAME_search09 8 +#define FRAME_search10 9 +#define FRAME_search11 10 +#define FRAME_search12 11 +#define FRAME_search13 12 +#define FRAME_firea01 13 +#define FRAME_firea02 14 +#define FRAME_firea03 15 +#define FRAME_firea04 16 +#define FRAME_firea05 17 +#define FRAME_firea06 18 +#define FRAME_firea07 19 +#define FRAME_firea08 20 +#define FRAME_firea09 21 +#define FRAME_firea10 22 +#define FRAME_firea11 23 +#define FRAME_firea12 24 +#define FRAME_firea13 25 +#define FRAME_firea14 26 +#define FRAME_firea15 27 +#define FRAME_fireb01 28 +#define FRAME_fireb02 29 +#define FRAME_fireb03 30 +#define FRAME_fireb04 31 +#define FRAME_fireb05 32 +#define FRAME_fireb06 33 +#define FRAME_fireb07 34 +#define FRAME_fireb08 35 +#define FRAME_fireb09 36 +#define FRAME_fireb10 37 +#define FRAME_fireb11 38 +#define FRAME_fireb12 39 +#define FRAME_fireb13 40 +#define FRAME_fireb14 41 +#define FRAME_fireb15 42 +#define FRAME_fireb16 43 +#define FRAME_spawn01 44 +#define FRAME_spawn02 45 +#define FRAME_spawn03 46 +#define FRAME_spawn04 47 +#define FRAME_spawn05 48 +#define FRAME_spawn06 49 +#define FRAME_spawn07 50 +#define FRAME_spawn08 51 +#define FRAME_spawn09 52 +#define FRAME_spawn10 53 +#define FRAME_spawn11 54 +#define FRAME_spawn12 55 +#define FRAME_spawn13 56 +#define FRAME_spawn14 57 +#define FRAME_spawn15 58 +#define FRAME_spawn16 59 +#define FRAME_spawn17 60 +#define FRAME_spawn18 61 +#define FRAME_death01 62 +#define FRAME_death02 63 +#define FRAME_death03 64 +#define FRAME_death04 65 +#define FRAME_death05 66 +#define FRAME_death06 67 +#define FRAME_death07 68 +#define FRAME_death08 69 +#define FRAME_death09 70 +#define FRAME_death10 71 +#define FRAME_death11 72 +#define FRAME_death12 73 +#define FRAME_death13 74 +#define FRAME_death14 75 +#define FRAME_death15 76 +#define FRAME_death16 77 + +#define MODEL_SCALE 1.000000 diff --git a/original/rogue/m_chick.c b/original/rogue/m_chick.c new file mode 100644 index 0000000..1aef62a --- /dev/null +++ b/original/rogue/m_chick.c @@ -0,0 +1,948 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +/* +============================================================================== + +chick + +============================================================================== +*/ + +#include "g_local.h" +#include "m_chick.h" + +// ROGUE +#define LEAD_TARGET 1 +// ROGUE + +qboolean visible (edict_t *self, edict_t *other); + +void chick_stand (edict_t *self); +void chick_run (edict_t *self); +void chick_reslash(edict_t *self); +void chick_rerocket(edict_t *self); +void chick_attack1(edict_t *self); + +static int sound_missile_prelaunch; +static int sound_missile_launch; +static int sound_melee_swing; +static int sound_melee_hit; +static int sound_missile_reload; +static int sound_death1; +static int sound_death2; +static int sound_fall_down; +static int sound_idle1; +static int sound_idle2; +static int sound_pain1; +static int sound_pain2; +static int sound_pain3; +static int sound_sight; +static int sound_search; + +void ChickMoan (edict_t *self) +{ + if (random() < 0.5) + gi.sound (self, CHAN_VOICE, sound_idle1, 1, ATTN_IDLE, 0); + else + gi.sound (self, CHAN_VOICE, sound_idle2, 1, ATTN_IDLE, 0); +} + +mframe_t chick_frames_fidget [] = +{ + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, ChickMoan, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL +}; +mmove_t chick_move_fidget = {FRAME_stand201, FRAME_stand230, chick_frames_fidget, chick_stand}; + +void chick_fidget (edict_t *self) +{ + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + return; + if (random() <= 0.3) + self->monsterinfo.currentmove = &chick_move_fidget; +} + +mframe_t chick_frames_stand [] = +{ + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, chick_fidget, + +}; +mmove_t chick_move_stand = {FRAME_stand101, FRAME_stand130, chick_frames_stand, NULL}; + +void chick_stand (edict_t *self) +{ + self->monsterinfo.currentmove = &chick_move_stand; +} + +mframe_t chick_frames_start_run [] = +{ + ai_run, 1, NULL, + ai_run, 0, NULL, + ai_run, 0, NULL, + ai_run, -1, NULL, + ai_run, -1, NULL, + ai_run, 0, NULL, + ai_run, 1, NULL, + ai_run, 3, NULL, + ai_run, 6, NULL, + ai_run, 3, NULL +}; +mmove_t chick_move_start_run = {FRAME_walk01, FRAME_walk10, chick_frames_start_run, chick_run}; + +mframe_t chick_frames_run [] = +{ + ai_run, 6, NULL, + ai_run, 8, NULL, + ai_run, 13, NULL, + ai_run, 5, monster_done_dodge, // make sure to clear dodge bit + ai_run, 7, NULL, + ai_run, 4, NULL, + ai_run, 11, NULL, + ai_run, 5, NULL, + ai_run, 9, NULL, + ai_run, 7, NULL +}; + +mmove_t chick_move_run = {FRAME_walk11, FRAME_walk20, chick_frames_run, NULL}; + +mframe_t chick_frames_walk [] = +{ + ai_walk, 6, NULL, + ai_walk, 8, NULL, + ai_walk, 13, NULL, + ai_walk, 5, NULL, + ai_walk, 7, NULL, + ai_walk, 4, NULL, + ai_walk, 11, NULL, + ai_walk, 5, NULL, + ai_walk, 9, NULL, + ai_walk, 7, NULL +}; + +mmove_t chick_move_walk = {FRAME_walk11, FRAME_walk20, chick_frames_walk, NULL}; + +void chick_walk (edict_t *self) +{ + self->monsterinfo.currentmove = &chick_move_walk; +} + +void chick_run (edict_t *self) +{ + monster_done_dodge (self); + + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + { + self->monsterinfo.currentmove = &chick_move_stand; + return; + } + + if (self->monsterinfo.currentmove == &chick_move_walk || + self->monsterinfo.currentmove == &chick_move_start_run) + { + self->monsterinfo.currentmove = &chick_move_run; + } + else + { + self->monsterinfo.currentmove = &chick_move_start_run; + } +} + +mframe_t chick_frames_pain1 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t chick_move_pain1 = {FRAME_pain101, FRAME_pain105, chick_frames_pain1, chick_run}; + +mframe_t chick_frames_pain2 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t chick_move_pain2 = {FRAME_pain201, FRAME_pain205, chick_frames_pain2, chick_run}; + +mframe_t chick_frames_pain3 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, -6, NULL, + ai_move, 3, NULL, + ai_move, 11, NULL, + ai_move, 3, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 4, NULL, + ai_move, 1, NULL, + ai_move, 0, NULL, + ai_move, -3, NULL, + ai_move, -4, NULL, + ai_move, 5, NULL, + ai_move, 7, NULL, + ai_move, -2, NULL, + ai_move, 3, NULL, + ai_move, -5, NULL, + ai_move, -2, NULL, + ai_move, -8, NULL, + ai_move, 2, NULL +}; +mmove_t chick_move_pain3 = {FRAME_pain301, FRAME_pain321, chick_frames_pain3, chick_run}; + +void chick_pain (edict_t *self, edict_t *other, float kick, int damage) +{ + float r; + + monster_done_dodge(self); + + if (self->health < (self->max_health / 2)) + self->s.skinnum = 1; + + if (level.time < self->pain_debounce_time) + return; + + self->pain_debounce_time = level.time + 3; + + r = random(); + if (r < 0.33) + gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); + else if (r < 0.66) + gi.sound (self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0); + else + gi.sound (self, CHAN_VOICE, sound_pain3, 1, ATTN_NORM, 0); + + if (skill->value == 3) + return; // no pain anims in nightmare + + // PMM - clear this from blindfire + self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING; + + if (damage <= 10) + self->monsterinfo.currentmove = &chick_move_pain1; + else if (damage <= 25) + self->monsterinfo.currentmove = &chick_move_pain2; + else + self->monsterinfo.currentmove = &chick_move_pain3; + + // PMM - clear duck flag + if (self->monsterinfo.aiflags & AI_DUCKED) + monster_duck_up(self); +} + +void chick_dead (edict_t *self) +{ + VectorSet (self->mins, -16, -16, 0); + VectorSet (self->maxs, 16, 16, 16); + self->movetype = MOVETYPE_TOSS; + self->svflags |= SVF_DEADMONSTER; + self->nextthink = 0; + gi.linkentity (self); +} + +mframe_t chick_frames_death2 [] = +{ + ai_move, -6, NULL, + ai_move, 0, NULL, + ai_move, -1, NULL, + ai_move, -5, NULL, + ai_move, 0, NULL, + ai_move, -1, NULL, + ai_move, -2, NULL, + ai_move, 1, NULL, + ai_move, 10, NULL, + ai_move, 2, NULL, + ai_move, 3, NULL, + ai_move, 1, NULL, + ai_move, 2, NULL, + ai_move, 0, NULL, + ai_move, 3, NULL, + ai_move, 3, NULL, + ai_move, 1, NULL, + ai_move, -3, NULL, + ai_move, -5, NULL, + ai_move, 4, NULL, + ai_move, 15, NULL, + ai_move, 14, NULL, + ai_move, 1, NULL +}; +mmove_t chick_move_death2 = {FRAME_death201, FRAME_death223, chick_frames_death2, chick_dead}; + +mframe_t chick_frames_death1 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, -7, NULL, + ai_move, 4, NULL, + ai_move, 11, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL + +}; +mmove_t chick_move_death1 = {FRAME_death101, FRAME_death112, chick_frames_death1, chick_dead}; + +void chick_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + int n; + +// check for gib + if (self->health <= self->gib_health) + { + gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0); + for (n= 0; n < 2; n++) + ThrowGib (self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC); + for (n= 0; n < 4; n++) + ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); + ThrowHead (self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC); + self->deadflag = DEAD_DEAD; + return; + } + + if (self->deadflag == DEAD_DEAD) + return; + +// regular death + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_YES; + + n = rand() % 2; + if (n == 0) + { + self->monsterinfo.currentmove = &chick_move_death1; + gi.sound (self, CHAN_VOICE, sound_death1, 1, ATTN_NORM, 0); + } + else + { + self->monsterinfo.currentmove = &chick_move_death2; + gi.sound (self, CHAN_VOICE, sound_death2, 1, ATTN_NORM, 0); + } +} + +// PMM - changes to duck code for new dodge + +mframe_t chick_frames_duck [] = +{ + ai_move, 0, monster_duck_down, + ai_move, 1, NULL, + ai_move, 4, monster_duck_hold, + ai_move, -4, NULL, + ai_move, -5, monster_duck_up, + ai_move, 3, NULL, + ai_move, 1, NULL +}; +mmove_t chick_move_duck = {FRAME_duck01, FRAME_duck07, chick_frames_duck, chick_run}; + +/* +void chick_dodge (edict_t *self, edict_t *attacker, float eta, trace_t *tr) +{ +// begin orig code + if (random() > 0.25) + return; + + if (!self->enemy) + self->enemy = attacker; + + self->monsterinfo.currentmove = &chick_move_duck; +// end + + float r; + float height; + int shooting = 0; + + if (!self->enemy) + { + self->enemy = attacker; + FoundTarget (self); + } + + // PMM - don't bother if it's going to hit anyway; fix for weird in-your-face etas (I was + // seeing numbers like 13 and 14) + if ((eta < 0.1) || (eta > 5)) + return; + + r = random(); + if (r > (0.25*((skill->value)+1))) + return; + + if ((self->monsterinfo.currentmove == &chick_move_start_attack1) || + (self->monsterinfo.currentmove == &chick_move_attack1)) + { + shooting = 1; + } + if (self->monsterinfo.aiflags & AI_DODGING) + { + height = self->absmax[2]; + } + else + { + height = self->absmax[2]-32-1; // the -1 is because the absmax is s.origin + maxs + 1 + } + + // check to see if it makes sense to duck + if (tr->endpos[2] <= height) + { + vec3_t right, diff; + if (shooting) + { + self->monsterinfo.attack_state = AS_SLIDING; + return; + } + AngleVectors (self->s.angles, NULL, right, NULL); + VectorSubtract (tr->endpos, self->s.origin, diff); + if (DotProduct (right, diff) < 0) + { + self->monsterinfo.lefty = 1; + } + // if it doesn't sense to duck, try to strafe away + monster_done_dodge (self); + self->monsterinfo.currentmove = &chick_move_run; + self->monsterinfo.attack_state = AS_SLIDING; + return; + } + + if (skill->value == 0) + { + self->monsterinfo.currentmove = &chick_move_duck; + // PMM - stupid dodge + self->monsterinfo.duck_wait_time = level.time + eta + 1; + self->monsterinfo.aiflags |= AI_DODGING; + return; + } + + if (!shooting) + { + self->monsterinfo.currentmove = &chick_move_duck; + self->monsterinfo.duck_wait_time = level.time + eta + (0.1 * (3 - skill->value)); + self->monsterinfo.aiflags |= AI_DODGING; + } + return; + +} +*/ +void ChickSlash (edict_t *self) +{ + vec3_t aim; + + VectorSet (aim, MELEE_DISTANCE, self->mins[0], 10); + gi.sound (self, CHAN_WEAPON, sound_melee_swing, 1, ATTN_NORM, 0); + fire_hit (self, aim, (10 + (rand() %6)), 100); +} + + +void ChickRocket (edict_t *self) +{ + vec3_t forward, right; + vec3_t start; + vec3_t dir; + vec3_t vec; + trace_t trace; // PMM - check target + int rocketSpeed; + float dist; + // pmm - blindfire + vec3_t target; + qboolean blindfire = false; + + if (self->monsterinfo.aiflags & AI_MANUAL_STEERING) + blindfire = true; + else + blindfire = false; + + if(!self->enemy || !self->enemy->inuse) //PGM + return; //PGM + + AngleVectors (self->s.angles, forward, right, NULL); + G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_CHICK_ROCKET_1], forward, right, start); + + rocketSpeed = 500 + (100 * skill->value); // PGM rock & roll.... :) + + // put a debug trail from start to endpoint, confirm that the start point is + // correct for the trace + + // PMM + if (blindfire) + VectorCopy (self->monsterinfo.blind_fire_target, target); + else + VectorCopy (self->enemy->s.origin, target); + // pmm +//PGM + // PMM - blindfire shooting + if (blindfire) + { + VectorCopy (target, vec); + VectorSubtract (vec, start, dir); + } + // pmm + // don't shoot at feet if they're above where i'm shooting from. + else if(random() < 0.33 || (start[2] < self->enemy->absmin[2])) + { +// gi.dprintf("normal shot\n"); + VectorCopy (target, vec); + vec[2] += self->enemy->viewheight; + VectorSubtract (vec, start, dir); + } + else + { +// gi.dprintf("shooting at feet!\n"); + VectorCopy (target, vec); + vec[2] = self->enemy->absmin[2]; + VectorSubtract (vec, start, dir); + } +//PGM + +//====== +//PMM - lead target (not when blindfiring) + // 20, 35, 50, 65 chance of leading + if((!blindfire) && ((random() < (0.2 + ((3 - skill->value) * 0.15))))) + { + float time; + +// gi.dprintf ("leading target\n"); + dist = VectorLength (dir); + time = dist/rocketSpeed; + VectorMA(vec, time, self->enemy->velocity, vec); + VectorSubtract(vec, start, dir); + } +//PMM - lead target +//====== + + VectorNormalize (dir); + + // pmm blindfire doesn't check target (done in checkattack) + // paranoia, make sure we're not shooting a target right next to us + trace = gi.trace(start, vec3_origin, vec3_origin, vec, self, MASK_SHOT); + if (blindfire) + { + // blindfire has different fail criteria for the trace + if (!(trace.startsolid || trace.allsolid || (trace.fraction < 0.5))) + monster_fire_rocket (self, start, dir, 50, rocketSpeed, MZ2_CHICK_ROCKET_1); + else + { + // geez, this is bad. she's avoiding about 80% of her blindfires due to hitting things. + // hunt around for a good shot + // try shifting the target to the left a little (to help counter her large offset) + VectorCopy (target, vec); + VectorMA (vec, -10, right, vec); + VectorSubtract(vec, start, dir); + VectorNormalize (dir); + trace = gi.trace(start, vec3_origin, vec3_origin, vec, self, MASK_SHOT); + if (!(trace.startsolid || trace.allsolid || (trace.fraction < 0.5))) + monster_fire_rocket (self, start, dir, 50, rocketSpeed, MZ2_CHICK_ROCKET_1); + else + { + // ok, that failed. try to the right + VectorCopy (target, vec); + VectorMA (vec, 10, right, vec); + VectorSubtract(vec, start, dir); + VectorNormalize (dir); + trace = gi.trace(start, vec3_origin, vec3_origin, vec, self, MASK_SHOT); + if (!(trace.startsolid || trace.allsolid || (trace.fraction < 0.5))) + monster_fire_rocket (self, start, dir, 50, rocketSpeed, MZ2_CHICK_ROCKET_1); +// else if ((g_showlogic) && (g_showlogic->value)) +// // ok, I give up +// gi.dprintf ("chick avoiding blindfire shot\n"); + } + } + } + else + { + trace = gi.trace(start, vec3_origin, vec3_origin, vec, self, MASK_SHOT); + if(trace.ent == self->enemy || trace.ent == world) + { + if(trace.fraction > 0.5 || (trace.ent && trace.ent->client)) + monster_fire_rocket (self, start, dir, 50, rocketSpeed, MZ2_CHICK_ROCKET_1); + // else + // gi.dprintf("didn't make it halfway to target...aborting\n"); + } + } +} + +void Chick_PreAttack1 (edict_t *self) +{ + gi.sound (self, CHAN_VOICE, sound_missile_prelaunch, 1, ATTN_NORM, 0); +} + +void ChickReload (edict_t *self) +{ + gi.sound (self, CHAN_VOICE, sound_missile_reload, 1, ATTN_NORM, 0); +} + + +mframe_t chick_frames_start_attack1 [] = +{ + ai_charge, 0, Chick_PreAttack1, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 4, NULL, + ai_charge, 0, NULL, + ai_charge, -3, NULL, + ai_charge, 3, NULL, + ai_charge, 5, NULL, + ai_charge, 7, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, chick_attack1 +}; +mmove_t chick_move_start_attack1 = {FRAME_attak101, FRAME_attak113, chick_frames_start_attack1, NULL}; + + +mframe_t chick_frames_attack1 [] = +{ + ai_charge, 19, ChickRocket, + ai_charge, -6, NULL, + ai_charge, -5, NULL, + ai_charge, -2, NULL, + ai_charge, -7, NULL, + ai_charge, 0, NULL, + ai_charge, 1, NULL, + ai_charge, 10, ChickReload, + ai_charge, 4, NULL, + ai_charge, 5, NULL, + ai_charge, 6, NULL, + ai_charge, 6, NULL, + ai_charge, 4, NULL, + ai_charge, 3, chick_rerocket + +}; +mmove_t chick_move_attack1 = {FRAME_attak114, FRAME_attak127, chick_frames_attack1, NULL}; + +mframe_t chick_frames_end_attack1 [] = +{ + ai_charge, -3, NULL, + ai_charge, 0, NULL, + ai_charge, -6, NULL, + ai_charge, -4, NULL, + ai_charge, -2, NULL +}; +mmove_t chick_move_end_attack1 = {FRAME_attak128, FRAME_attak132, chick_frames_end_attack1, chick_run}; + +void chick_rerocket(edict_t *self) +{ + if (self->monsterinfo.aiflags & AI_MANUAL_STEERING) + { + self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING; + self->monsterinfo.currentmove = &chick_move_end_attack1; + return; + } + if (self->enemy->health > 0) + { + if (range (self, self->enemy) > RANGE_MELEE) + if ( visible (self, self->enemy) ) + if (random() <= (0.6 + (0.05*((float)skill->value)))) + { + self->monsterinfo.currentmove = &chick_move_attack1; + return; + } + } + self->monsterinfo.currentmove = &chick_move_end_attack1; +} + +void chick_attack1(edict_t *self) +{ + self->monsterinfo.currentmove = &chick_move_attack1; +} + +mframe_t chick_frames_slash [] = +{ + ai_charge, 1, NULL, + ai_charge, 7, ChickSlash, + ai_charge, -7, NULL, + ai_charge, 1, NULL, + ai_charge, -1, NULL, + ai_charge, 1, NULL, + ai_charge, 0, NULL, + ai_charge, 1, NULL, + ai_charge, -2, chick_reslash +}; +mmove_t chick_move_slash = {FRAME_attak204, FRAME_attak212, chick_frames_slash, NULL}; + +mframe_t chick_frames_end_slash [] = +{ + ai_charge, -6, NULL, + ai_charge, -1, NULL, + ai_charge, -6, NULL, + ai_charge, 0, NULL +}; +mmove_t chick_move_end_slash = {FRAME_attak213, FRAME_attak216, chick_frames_end_slash, chick_run}; + + +void chick_reslash(edict_t *self) +{ + if (self->enemy->health > 0) + { + if (range (self, self->enemy) == RANGE_MELEE) + if (random() <= 0.9) + { + self->monsterinfo.currentmove = &chick_move_slash; + return; + } + else + { + self->monsterinfo.currentmove = &chick_move_end_slash; + return; + } + } + self->monsterinfo.currentmove = &chick_move_end_slash; +} + +void chick_slash(edict_t *self) +{ + self->monsterinfo.currentmove = &chick_move_slash; +} + + +mframe_t chick_frames_start_slash [] = +{ + ai_charge, 1, NULL, + ai_charge, 8, NULL, + ai_charge, 3, NULL +}; +mmove_t chick_move_start_slash = {FRAME_attak201, FRAME_attak203, chick_frames_start_slash, chick_slash}; + + + +void chick_melee(edict_t *self) +{ + self->monsterinfo.currentmove = &chick_move_start_slash; +} + + +void chick_attack(edict_t *self) +{ + float r, chance; + + monster_done_dodge (self); + + // PMM + if (self->monsterinfo.attack_state == AS_BLIND) + { + // setup shot probabilities + if (self->monsterinfo.blind_fire_delay < 1.0) + chance = 1.0; + else if (self->monsterinfo.blind_fire_delay < 7.5) + chance = 0.4; + else + chance = 0.1; + + r = random(); + + // minimum of 2 seconds, plus 0-3, after the shots are done + self->monsterinfo.blind_fire_delay += 4.0 + 1.5 + random(); + + // don't shoot at the origin + if (VectorCompare (self->monsterinfo.blind_fire_target, vec3_origin)) + return; + + // don't shoot if the dice say not to + if (r > chance) + { +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("blindfire - NO SHOT\n"); + return; + } + + // turn on manual steering to signal both manual steering and blindfire + self->monsterinfo.aiflags |= AI_MANUAL_STEERING; + self->monsterinfo.currentmove = &chick_move_start_attack1; + self->monsterinfo.attack_finished = level.time + 2*random(); + return; + } + // pmm + + self->monsterinfo.currentmove = &chick_move_start_attack1; +} + +void chick_sight(edict_t *self, edict_t *other) +{ + gi.sound (self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); +} + +//=========== +//PGM +qboolean chick_blocked (edict_t *self, float dist) +{ + if(blocked_checkshot (self, 0.25 + (0.05 * skill->value) )) + return true; + + if(blocked_checkplat (self, dist)) + return true; + + return false; +} +//PGM +//=========== + +void chick_duck (edict_t *self, float eta) +{ + if ((self->monsterinfo.currentmove == &chick_move_start_attack1) || + (self->monsterinfo.currentmove == &chick_move_attack1)) + { + // if we're shooting, and not on easy, don't dodge + if (skill->value) + { + self->monsterinfo.aiflags &= ~AI_DUCKED; + return; + } + } + + if (skill->value == 0) + // PMM - stupid dodge + self->monsterinfo.duck_wait_time = level.time + eta + 1; + else + self->monsterinfo.duck_wait_time = level.time + eta + (0.1 * (3 - skill->value)); + + // has to be done immediately otherwise she can get stuck + monster_duck_down(self); + + self->monsterinfo.nextframe = FRAME_duck01; + self->monsterinfo.currentmove = &chick_move_duck; + return; +} + +void chick_sidestep (edict_t *self) +{ + if ((self->monsterinfo.currentmove == &chick_move_start_attack1) || + (self->monsterinfo.currentmove == &chick_move_attack1)) + { + // if we're shooting, and not on easy, don't dodge + if (skill->value) + { + self->monsterinfo.aiflags &= ~AI_DODGING; + return; + } + } + + if (self->monsterinfo.currentmove != &chick_move_run) + self->monsterinfo.currentmove = &chick_move_run; +} + +/*QUAKED monster_chick (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight +*/ +void SP_monster_chick (edict_t *self) +{ + if (deathmatch->value) + { + G_FreeEdict (self); + return; + } + + sound_missile_prelaunch = gi.soundindex ("chick/chkatck1.wav"); + sound_missile_launch = gi.soundindex ("chick/chkatck2.wav"); + sound_melee_swing = gi.soundindex ("chick/chkatck3.wav"); + sound_melee_hit = gi.soundindex ("chick/chkatck4.wav"); + sound_missile_reload = gi.soundindex ("chick/chkatck5.wav"); + sound_death1 = gi.soundindex ("chick/chkdeth1.wav"); + sound_death2 = gi.soundindex ("chick/chkdeth2.wav"); + sound_fall_down = gi.soundindex ("chick/chkfall1.wav"); + sound_idle1 = gi.soundindex ("chick/chkidle1.wav"); + sound_idle2 = gi.soundindex ("chick/chkidle2.wav"); + sound_pain1 = gi.soundindex ("chick/chkpain1.wav"); + sound_pain2 = gi.soundindex ("chick/chkpain2.wav"); + sound_pain3 = gi.soundindex ("chick/chkpain3.wav"); + sound_sight = gi.soundindex ("chick/chksght1.wav"); + sound_search = gi.soundindex ("chick/chksrch1.wav"); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex ("models/monsters/bitch2/tris.md2"); + VectorSet (self->mins, -16, -16, 0); + VectorSet (self->maxs, 16, 16, 56); + + self->health = 175; + self->gib_health = -70; + self->mass = 200; + + self->pain = chick_pain; + self->die = chick_die; + + self->monsterinfo.stand = chick_stand; + self->monsterinfo.walk = chick_walk; + self->monsterinfo.run = chick_run; + // pmm + self->monsterinfo.dodge = M_MonsterDodge; + self->monsterinfo.duck = chick_duck; + self->monsterinfo.unduck = monster_duck_up; + self->monsterinfo.sidestep = chick_sidestep; +// self->monsterinfo.dodge = chick_dodge; + // pmm + self->monsterinfo.attack = chick_attack; + self->monsterinfo.melee = chick_melee; + self->monsterinfo.sight = chick_sight; + self->monsterinfo.blocked = chick_blocked; // PGM + + gi.linkentity (self); + + self->monsterinfo.currentmove = &chick_move_stand; + self->monsterinfo.scale = MODEL_SCALE; + + // PMM + self->monsterinfo.blindfire = true; + // pmm + walkmonster_start (self); +} + diff --git a/original/rogue/m_chick.h b/original/rogue/m_chick.h new file mode 100644 index 0000000..fdf85d0 --- /dev/null +++ b/original/rogue/m_chick.h @@ -0,0 +1,296 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// G:\quake2\baseq2\models/monsters/bitch + +// This file generated by qdata - Do NOT Modify + +#define FRAME_attak101 0 +#define FRAME_attak102 1 +#define FRAME_attak103 2 +#define FRAME_attak104 3 +#define FRAME_attak105 4 +#define FRAME_attak106 5 +#define FRAME_attak107 6 +#define FRAME_attak108 7 +#define FRAME_attak109 8 +#define FRAME_attak110 9 +#define FRAME_attak111 10 +#define FRAME_attak112 11 +#define FRAME_attak113 12 +#define FRAME_attak114 13 +#define FRAME_attak115 14 +#define FRAME_attak116 15 +#define FRAME_attak117 16 +#define FRAME_attak118 17 +#define FRAME_attak119 18 +#define FRAME_attak120 19 +#define FRAME_attak121 20 +#define FRAME_attak122 21 +#define FRAME_attak123 22 +#define FRAME_attak124 23 +#define FRAME_attak125 24 +#define FRAME_attak126 25 +#define FRAME_attak127 26 +#define FRAME_attak128 27 +#define FRAME_attak129 28 +#define FRAME_attak130 29 +#define FRAME_attak131 30 +#define FRAME_attak132 31 +#define FRAME_attak201 32 +#define FRAME_attak202 33 +#define FRAME_attak203 34 +#define FRAME_attak204 35 +#define FRAME_attak205 36 +#define FRAME_attak206 37 +#define FRAME_attak207 38 +#define FRAME_attak208 39 +#define FRAME_attak209 40 +#define FRAME_attak210 41 +#define FRAME_attak211 42 +#define FRAME_attak212 43 +#define FRAME_attak213 44 +#define FRAME_attak214 45 +#define FRAME_attak215 46 +#define FRAME_attak216 47 +#define FRAME_death101 48 +#define FRAME_death102 49 +#define FRAME_death103 50 +#define FRAME_death104 51 +#define FRAME_death105 52 +#define FRAME_death106 53 +#define FRAME_death107 54 +#define FRAME_death108 55 +#define FRAME_death109 56 +#define FRAME_death110 57 +#define FRAME_death111 58 +#define FRAME_death112 59 +#define FRAME_death201 60 +#define FRAME_death202 61 +#define FRAME_death203 62 +#define FRAME_death204 63 +#define FRAME_death205 64 +#define FRAME_death206 65 +#define FRAME_death207 66 +#define FRAME_death208 67 +#define FRAME_death209 68 +#define FRAME_death210 69 +#define FRAME_death211 70 +#define FRAME_death212 71 +#define FRAME_death213 72 +#define FRAME_death214 73 +#define FRAME_death215 74 +#define FRAME_death216 75 +#define FRAME_death217 76 +#define FRAME_death218 77 +#define FRAME_death219 78 +#define FRAME_death220 79 +#define FRAME_death221 80 +#define FRAME_death222 81 +#define FRAME_death223 82 +#define FRAME_duck01 83 +#define FRAME_duck02 84 +#define FRAME_duck03 85 +#define FRAME_duck04 86 +#define FRAME_duck05 87 +#define FRAME_duck06 88 +#define FRAME_duck07 89 +#define FRAME_pain101 90 +#define FRAME_pain102 91 +#define FRAME_pain103 92 +#define FRAME_pain104 93 +#define FRAME_pain105 94 +#define FRAME_pain201 95 +#define FRAME_pain202 96 +#define FRAME_pain203 97 +#define FRAME_pain204 98 +#define FRAME_pain205 99 +#define FRAME_pain301 100 +#define FRAME_pain302 101 +#define FRAME_pain303 102 +#define FRAME_pain304 103 +#define FRAME_pain305 104 +#define FRAME_pain306 105 +#define FRAME_pain307 106 +#define FRAME_pain308 107 +#define FRAME_pain309 108 +#define FRAME_pain310 109 +#define FRAME_pain311 110 +#define FRAME_pain312 111 +#define FRAME_pain313 112 +#define FRAME_pain314 113 +#define FRAME_pain315 114 +#define FRAME_pain316 115 +#define FRAME_pain317 116 +#define FRAME_pain318 117 +#define FRAME_pain319 118 +#define FRAME_pain320 119 +#define FRAME_pain321 120 +#define FRAME_stand101 121 +#define FRAME_stand102 122 +#define FRAME_stand103 123 +#define FRAME_stand104 124 +#define FRAME_stand105 125 +#define FRAME_stand106 126 +#define FRAME_stand107 127 +#define FRAME_stand108 128 +#define FRAME_stand109 129 +#define FRAME_stand110 130 +#define FRAME_stand111 131 +#define FRAME_stand112 132 +#define FRAME_stand113 133 +#define FRAME_stand114 134 +#define FRAME_stand115 135 +#define FRAME_stand116 136 +#define FRAME_stand117 137 +#define FRAME_stand118 138 +#define FRAME_stand119 139 +#define FRAME_stand120 140 +#define FRAME_stand121 141 +#define FRAME_stand122 142 +#define FRAME_stand123 143 +#define FRAME_stand124 144 +#define FRAME_stand125 145 +#define FRAME_stand126 146 +#define FRAME_stand127 147 +#define FRAME_stand128 148 +#define FRAME_stand129 149 +#define FRAME_stand130 150 +#define FRAME_stand201 151 +#define FRAME_stand202 152 +#define FRAME_stand203 153 +#define FRAME_stand204 154 +#define FRAME_stand205 155 +#define FRAME_stand206 156 +#define FRAME_stand207 157 +#define FRAME_stand208 158 +#define FRAME_stand209 159 +#define FRAME_stand210 160 +#define FRAME_stand211 161 +#define FRAME_stand212 162 +#define FRAME_stand213 163 +#define FRAME_stand214 164 +#define FRAME_stand215 165 +#define FRAME_stand216 166 +#define FRAME_stand217 167 +#define FRAME_stand218 168 +#define FRAME_stand219 169 +#define FRAME_stand220 170 +#define FRAME_stand221 171 +#define FRAME_stand222 172 +#define FRAME_stand223 173 +#define FRAME_stand224 174 +#define FRAME_stand225 175 +#define FRAME_stand226 176 +#define FRAME_stand227 177 +#define FRAME_stand228 178 +#define FRAME_stand229 179 +#define FRAME_stand230 180 +#define FRAME_walk01 181 +#define FRAME_walk02 182 +#define FRAME_walk03 183 +#define FRAME_walk04 184 +#define FRAME_walk05 185 +#define FRAME_walk06 186 +#define FRAME_walk07 187 +#define FRAME_walk08 188 +#define FRAME_walk09 189 +#define FRAME_walk10 190 +#define FRAME_walk11 191 +#define FRAME_walk12 192 +#define FRAME_walk13 193 +#define FRAME_walk14 194 +#define FRAME_walk15 195 +#define FRAME_walk16 196 +#define FRAME_walk17 197 +#define FRAME_walk18 198 +#define FRAME_walk19 199 +#define FRAME_walk20 200 +#define FRAME_walk21 201 +#define FRAME_walk22 202 +#define FRAME_walk23 203 +#define FRAME_walk24 204 +#define FRAME_walk25 205 +#define FRAME_walk26 206 +#define FRAME_walk27 207 +#define FRAME_recln201 208 +#define FRAME_recln202 209 +#define FRAME_recln203 210 +#define FRAME_recln204 211 +#define FRAME_recln205 212 +#define FRAME_recln206 213 +#define FRAME_recln207 214 +#define FRAME_recln208 215 +#define FRAME_recln209 216 +#define FRAME_recln210 217 +#define FRAME_recln211 218 +#define FRAME_recln212 219 +#define FRAME_recln213 220 +#define FRAME_recln214 221 +#define FRAME_recln215 222 +#define FRAME_recln216 223 +#define FRAME_recln217 224 +#define FRAME_recln218 225 +#define FRAME_recln219 226 +#define FRAME_recln220 227 +#define FRAME_recln221 228 +#define FRAME_recln222 229 +#define FRAME_recln223 230 +#define FRAME_recln224 231 +#define FRAME_recln225 232 +#define FRAME_recln226 233 +#define FRAME_recln227 234 +#define FRAME_recln228 235 +#define FRAME_recln229 236 +#define FRAME_recln230 237 +#define FRAME_recln231 238 +#define FRAME_recln232 239 +#define FRAME_recln233 240 +#define FRAME_recln234 241 +#define FRAME_recln235 242 +#define FRAME_recln236 243 +#define FRAME_recln237 244 +#define FRAME_recln238 245 +#define FRAME_recln239 246 +#define FRAME_recln240 247 +#define FRAME_recln101 248 +#define FRAME_recln102 249 +#define FRAME_recln103 250 +#define FRAME_recln104 251 +#define FRAME_recln105 252 +#define FRAME_recln106 253 +#define FRAME_recln107 254 +#define FRAME_recln108 255 +#define FRAME_recln109 256 +#define FRAME_recln110 257 +#define FRAME_recln111 258 +#define FRAME_recln112 259 +#define FRAME_recln113 260 +#define FRAME_recln114 261 +#define FRAME_recln115 262 +#define FRAME_recln116 263 +#define FRAME_recln117 264 +#define FRAME_recln118 265 +#define FRAME_recln119 266 +#define FRAME_recln120 267 +#define FRAME_recln121 268 +#define FRAME_recln122 269 +#define FRAME_recln123 270 +#define FRAME_recln124 271 +#define FRAME_recln125 272 +#define FRAME_recln126 273 +#define FRAME_recln127 274 +#define FRAME_recln128 275 +#define FRAME_recln129 276 +#define FRAME_recln130 277 +#define FRAME_recln131 278 +#define FRAME_recln132 279 +#define FRAME_recln133 280 +#define FRAME_recln134 281 +#define FRAME_recln135 282 +#define FRAME_recln136 283 +#define FRAME_recln137 284 +#define FRAME_recln138 285 +#define FRAME_recln139 286 +#define FRAME_recln140 287 + +#define MODEL_SCALE 1.000000 diff --git a/original/rogue/m_flash.c b/original/rogue/m_flash.c new file mode 100644 index 0000000..9dc5c4d --- /dev/null +++ b/original/rogue/m_flash.c @@ -0,0 +1,471 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// m_flash.c + +#include "q_shared.h" + +// this file is included in both the game dll and quake2, +// the game needs it to source shot locations, the client +// needs it to position muzzle flashes +vec3_t monster_flash_offset [] = +{ +// flash 0 is not used + 0.0, 0.0, 0.0, + +// MZ2_TANK_BLASTER_1 1 + 20.7, -18.5, 28.7, +// MZ2_TANK_BLASTER_2 2 + 16.6, -21.5, 30.1, +// MZ2_TANK_BLASTER_3 3 + 11.8, -23.9, 32.1, +// MZ2_TANK_MACHINEGUN_1 4 + 22.9, -0.7, 25.3, +// MZ2_TANK_MACHINEGUN_2 5 + 22.2, 6.2, 22.3, +// MZ2_TANK_MACHINEGUN_3 6 + 19.4, 13.1, 18.6, +// MZ2_TANK_MACHINEGUN_4 7 + 19.4, 18.8, 18.6, +// MZ2_TANK_MACHINEGUN_5 8 + 17.9, 25.0, 18.6, +// MZ2_TANK_MACHINEGUN_6 9 + 14.1, 30.5, 20.6, +// MZ2_TANK_MACHINEGUN_7 10 + 9.3, 35.3, 22.1, +// MZ2_TANK_MACHINEGUN_8 11 + 4.7, 38.4, 22.1, +// MZ2_TANK_MACHINEGUN_9 12 + -1.1, 40.4, 24.1, +// MZ2_TANK_MACHINEGUN_10 13 + -6.5, 41.2, 24.1, +// MZ2_TANK_MACHINEGUN_11 14 + 3.2, 40.1, 24.7, +// MZ2_TANK_MACHINEGUN_12 15 + 11.7, 36.7, 26.0, +// MZ2_TANK_MACHINEGUN_13 16 + 18.9, 31.3, 26.0, +// MZ2_TANK_MACHINEGUN_14 17 + 24.4, 24.4, 26.4, +// MZ2_TANK_MACHINEGUN_15 18 + 27.1, 17.1, 27.2, +// MZ2_TANK_MACHINEGUN_16 19 + 28.5, 9.1, 28.0, +// MZ2_TANK_MACHINEGUN_17 20 + 27.1, 2.2, 28.0, +// MZ2_TANK_MACHINEGUN_18 21 + 24.9, -2.8, 28.0, +// MZ2_TANK_MACHINEGUN_19 22 + 21.6, -7.0, 26.4, +// MZ2_TANK_ROCKET_1 23 + 6.2, 29.1, 49.1, +// MZ2_TANK_ROCKET_2 24 + 6.9, 23.8, 49.1, +// MZ2_TANK_ROCKET_3 25 + 8.3, 17.8, 49.5, + +// MZ2_INFANTRY_MACHINEGUN_1 26 + 26.6, 7.1, 13.1, +// MZ2_INFANTRY_MACHINEGUN_2 27 + 18.2, 7.5, 15.4, +// MZ2_INFANTRY_MACHINEGUN_3 28 + 17.2, 10.3, 17.9, +// MZ2_INFANTRY_MACHINEGUN_4 29 + 17.0, 12.8, 20.1, +// MZ2_INFANTRY_MACHINEGUN_5 30 + 15.1, 14.1, 21.8, +// MZ2_INFANTRY_MACHINEGUN_6 31 + 11.8, 17.2, 23.1, +// MZ2_INFANTRY_MACHINEGUN_7 32 + 11.4, 20.2, 21.0, +// MZ2_INFANTRY_MACHINEGUN_8 33 + 9.0, 23.0, 18.9, +// MZ2_INFANTRY_MACHINEGUN_9 34 + 13.9, 18.6, 17.7, +// MZ2_INFANTRY_MACHINEGUN_10 35 + 15.4, 15.6, 15.8, +// MZ2_INFANTRY_MACHINEGUN_11 36 + 10.2, 15.2, 25.1, +// MZ2_INFANTRY_MACHINEGUN_12 37 + -1.9, 15.1, 28.2, +// MZ2_INFANTRY_MACHINEGUN_13 38 + -12.4, 13.0, 20.2, + +// MZ2_SOLDIER_BLASTER_1 39 + 10.6 * 1.2, 7.7 * 1.2, 7.8 * 1.2, +// MZ2_SOLDIER_BLASTER_2 40 + 21.1 * 1.2, 3.6 * 1.2, 19.0 * 1.2, +// MZ2_SOLDIER_SHOTGUN_1 41 + 10.6 * 1.2, 7.7 * 1.2, 7.8 * 1.2, +// MZ2_SOLDIER_SHOTGUN_2 42 + 21.1 * 1.2, 3.6 * 1.2, 19.0 * 1.2, +// MZ2_SOLDIER_MACHINEGUN_1 43 + 10.6 * 1.2, 7.7 * 1.2, 7.8 * 1.2, +// MZ2_SOLDIER_MACHINEGUN_2 44 + 21.1 * 1.2, 3.6 * 1.2, 19.0 * 1.2, + +// MZ2_GUNNER_MACHINEGUN_1 45 + 30.1 * 1.15, 3.9 * 1.15, 19.6 * 1.15, +// MZ2_GUNNER_MACHINEGUN_2 46 + 29.1 * 1.15, 2.5 * 1.15, 20.7 * 1.15, +// MZ2_GUNNER_MACHINEGUN_3 47 + 28.2 * 1.15, 2.5 * 1.15, 22.2 * 1.15, +// MZ2_GUNNER_MACHINEGUN_4 48 + 28.2 * 1.15, 3.6 * 1.15, 22.0 * 1.15, +// MZ2_GUNNER_MACHINEGUN_5 49 + 26.9 * 1.15, 2.0 * 1.15, 23.4 * 1.15, +// MZ2_GUNNER_MACHINEGUN_6 50 + 26.5 * 1.15, 0.6 * 1.15, 20.8 * 1.15, +// MZ2_GUNNER_MACHINEGUN_7 51 + 26.9 * 1.15, 0.5 * 1.15, 21.5 * 1.15, +// MZ2_GUNNER_MACHINEGUN_8 52 + 29.0 * 1.15, 2.4 * 1.15, 19.5 * 1.15, +// MZ2_GUNNER_GRENADE_1 53 + 4.6 * 1.15, -16.8 * 1.15, 7.3 * 1.15, +// MZ2_GUNNER_GRENADE_2 54 + 4.6 * 1.15, -16.8 * 1.15, 7.3 * 1.15, +// MZ2_GUNNER_GRENADE_3 55 + 4.6 * 1.15, -16.8 * 1.15, 7.3 * 1.15, +// MZ2_GUNNER_GRENADE_4 56 + 4.6 * 1.15, -16.8 * 1.15, 7.3 * 1.15, + +// MZ2_CHICK_ROCKET_1 57 +// -24.8, -9.0, 39.0, + 24.8, -9.0, 39.0, // PGM - this was incorrect in Q2 + +// MZ2_FLYER_BLASTER_1 58 + 12.1, 13.4, -14.5, +// MZ2_FLYER_BLASTER_2 59 + 12.1, -7.4, -14.5, + +// MZ2_MEDIC_BLASTER_1 60 + 12.1, 5.4, 16.5, + +// MZ2_GLADIATOR_RAILGUN_1 61 + 30.0, 18.0, 28.0, + +// MZ2_HOVER_BLASTER_1 62 + 32.5, -0.8, 10.0, + +// MZ2_ACTOR_MACHINEGUN_1 63 + 18.4, 7.4, 9.6, + +// MZ2_SUPERTANK_MACHINEGUN_1 64 + 30.0, 30.0, 88.5, +// MZ2_SUPERTANK_MACHINEGUN_2 65 + 30.0, 30.0, 88.5, +// MZ2_SUPERTANK_MACHINEGUN_3 66 + 30.0, 30.0, 88.5, +// MZ2_SUPERTANK_MACHINEGUN_4 67 + 30.0, 30.0, 88.5, +// MZ2_SUPERTANK_MACHINEGUN_5 68 + 30.0, 30.0, 88.5, +// MZ2_SUPERTANK_MACHINEGUN_6 69 + 30.0, 30.0, 88.5, +// MZ2_SUPERTANK_ROCKET_1 70 + 16.0, -22.5, 91.2, +// MZ2_SUPERTANK_ROCKET_2 71 + 16.0, -33.4, 86.7, +// MZ2_SUPERTANK_ROCKET_3 72 + 16.0, -42.8, 83.3, + +// --- Start Xian Stuff --- +// MZ2_BOSS2_MACHINEGUN_L1 73 + 32, -40, 70, +// MZ2_BOSS2_MACHINEGUN_L2 74 + 32, -40, 70, +// MZ2_BOSS2_MACHINEGUN_L3 75 + 32, -40, 70, +// MZ2_BOSS2_MACHINEGUN_L4 76 + 32, -40, 70, +// MZ2_BOSS2_MACHINEGUN_L5 77 + 32, -40, 70, +// --- End Xian Stuff + +// MZ2_BOSS2_ROCKET_1 78 + 22.0, 16.0, 10.0, +// MZ2_BOSS2_ROCKET_2 79 + 22.0, 8.0, 10.0, +// MZ2_BOSS2_ROCKET_3 80 + 22.0, -8.0, 10.0, +// MZ2_BOSS2_ROCKET_4 81 + 22.0, -16.0, 10.0, + +// MZ2_FLOAT_BLASTER_1 82 + 32.5, -0.8, 10, + +// MZ2_SOLDIER_BLASTER_3 83 + 20.8 * 1.2, 10.1 * 1.2, -2.7 * 1.2, +// MZ2_SOLDIER_SHOTGUN_3 84 + 20.8 * 1.2, 10.1 * 1.2, -2.7 * 1.2, +// MZ2_SOLDIER_MACHINEGUN_3 85 + 20.8 * 1.2, 10.1 * 1.2, -2.7 * 1.2, +// MZ2_SOLDIER_BLASTER_4 86 + 7.6 * 1.2, 9.3 * 1.2, 0.8 * 1.2, +// MZ2_SOLDIER_SHOTGUN_4 87 + 7.6 * 1.2, 9.3 * 1.2, 0.8 * 1.2, +// MZ2_SOLDIER_MACHINEGUN_4 88 + 7.6 * 1.2, 9.3 * 1.2, 0.8 * 1.2, +// MZ2_SOLDIER_BLASTER_5 89 + 30.5 * 1.2, 9.9 * 1.2, -18.7 * 1.2, +// MZ2_SOLDIER_SHOTGUN_5 90 + 30.5 * 1.2, 9.9 * 1.2, -18.7 * 1.2, +// MZ2_SOLDIER_MACHINEGUN_5 91 + 30.5 * 1.2, 9.9 * 1.2, -18.7 * 1.2, +// MZ2_SOLDIER_BLASTER_6 92 + 27.6 * 1.2, 3.4 * 1.2, -10.4 * 1.2, +// MZ2_SOLDIER_SHOTGUN_6 93 + 27.6 * 1.2, 3.4 * 1.2, -10.4 * 1.2, +// MZ2_SOLDIER_MACHINEGUN_6 94 + 27.6 * 1.2, 3.4 * 1.2, -10.4 * 1.2, +// MZ2_SOLDIER_BLASTER_7 95 + 28.9 * 1.2, 4.6 * 1.2, -8.1 * 1.2, +// MZ2_SOLDIER_SHOTGUN_7 96 + 28.9 * 1.2, 4.6 * 1.2, -8.1 * 1.2, +// MZ2_SOLDIER_MACHINEGUN_7 97 + 28.9 * 1.2, 4.6 * 1.2, -8.1 * 1.2, +// MZ2_SOLDIER_BLASTER_8 98 +// 34.5 * 1.2, 9.6 * 1.2, 6.1 * 1.2, + 31.5 * 1.2, 9.6 * 1.2, 10.1 * 1.2, +// MZ2_SOLDIER_SHOTGUN_8 99 + 34.5 * 1.2, 9.6 * 1.2, 6.1 * 1.2, +// MZ2_SOLDIER_MACHINEGUN_8 100 + 34.5 * 1.2, 9.6 * 1.2, 6.1 * 1.2, + +// --- Xian shit below --- +// MZ2_MAKRON_BFG 101 + 17, -19.5, 62.9, +// MZ2_MAKRON_BLASTER_1 102 + -3.6, -24.1, 59.5, +// MZ2_MAKRON_BLASTER_2 103 + -1.6, -19.3, 59.5, +// MZ2_MAKRON_BLASTER_3 104 + -0.1, -14.4, 59.5, +// MZ2_MAKRON_BLASTER_4 105 + 2.0, -7.6, 59.5, +// MZ2_MAKRON_BLASTER_5 106 + 3.4, 1.3, 59.5, +// MZ2_MAKRON_BLASTER_6 107 + 3.7, 11.1, 59.5, +// MZ2_MAKRON_BLASTER_7 108 + -0.3, 22.3, 59.5, +// MZ2_MAKRON_BLASTER_8 109 + -6, 33, 59.5, +// MZ2_MAKRON_BLASTER_9 110 + -9.3, 36.4, 59.5, +// MZ2_MAKRON_BLASTER_10 111 + -7, 35, 59.5, +// MZ2_MAKRON_BLASTER_11 112 + -2.1, 29, 59.5, +// MZ2_MAKRON_BLASTER_12 113 + 3.9, 17.3, 59.5, +// MZ2_MAKRON_BLASTER_13 114 + 6.1, 5.8, 59.5, +// MZ2_MAKRON_BLASTER_14 115 + 5.9, -4.4, 59.5, +// MZ2_MAKRON_BLASTER_15 116 + 4.2, -14.1, 59.5, +// MZ2_MAKRON_BLASTER_16 117 + 2.4, -18.8, 59.5, +// MZ2_MAKRON_BLASTER_17 118 + -1.8, -25.5, 59.5, +// MZ2_MAKRON_RAILGUN_1 119 + -17.3, 7.8, 72.4, + +// MZ2_JORG_MACHINEGUN_L1 120 + 78.5, -47.1, 96, +// MZ2_JORG_MACHINEGUN_L2 121 + 78.5, -47.1, 96, +// MZ2_JORG_MACHINEGUN_L3 122 + 78.5, -47.1, 96, +// MZ2_JORG_MACHINEGUN_L4 123 + 78.5, -47.1, 96, +// MZ2_JORG_MACHINEGUN_L5 124 + 78.5, -47.1, 96, +// MZ2_JORG_MACHINEGUN_L6 125 + 78.5, -47.1, 96, +// MZ2_JORG_MACHINEGUN_R1 126 + 78.5, 46.7, 96, +// MZ2_JORG_MACHINEGUN_R2 127 + 78.5, 46.7, 96, +// MZ2_JORG_MACHINEGUN_R3 128 + 78.5, 46.7, 96, +// MZ2_JORG_MACHINEGUN_R4 129 + 78.5, 46.7, 96, +// MZ2_JORG_MACHINEGUN_R5 130 + 78.5, 46.7, 96, +// MZ2_JORG_MACHINEGUN_R6 131 + 78.5, 46.7, 96, +// MZ2_JORG_BFG_1 132 + 6.3, -9, 111.2, + +// MZ2_BOSS2_MACHINEGUN_R1 73 + 32, 40, 70, +// MZ2_BOSS2_MACHINEGUN_R2 74 + 32, 40, 70, +// MZ2_BOSS2_MACHINEGUN_R3 75 + 32, 40, 70, +// MZ2_BOSS2_MACHINEGUN_R4 76 + 32, 40, 70, +// MZ2_BOSS2_MACHINEGUN_R5 77 + 32, 40, 70, + +// --- End Xian Shit --- + +// ROGUE +// note that the above really ends at 137 +// carrier machineguns +// MZ2_CARRIER_MACHINEGUN_L1 + 56, -32, 32, +// MZ2_CARRIER_MACHINEGUN_R1 + 56, 32, 32, +// MZ2_CARRIER_GRENADE + 42, 24, 50, +// MZ2_TURRET_MACHINEGUN 141 + 16, 0, 0, +// MZ2_TURRET_ROCKET 142 + 16, 0, 0, +// MZ2_TURRET_BLASTER 143 + 16, 0, 0, +// MZ2_STALKER_BLASTER 144 + 24, 0, 6, +// MZ2_DAEDALUS_BLASTER 145 + 32.5, -0.8, 10.0, +// MZ2_MEDIC_BLASTER_2 146 + 12.1, 5.4, 16.5, +// MZ2_CARRIER_RAILGUN 147 + 32, 0, 6, +// MZ2_WIDOW_DISRUPTOR 148 + 57.72, 14.50, 88.81, +// MZ2_WIDOW_BLASTER 149 + 56, 32, 32, +// MZ2_WIDOW_RAIL 150 + 62, -20, 84, +// MZ2_WIDOW_PLASMABEAM 151 // PMM - not used! + 32, 0, 6, +// MZ2_CARRIER_MACHINEGUN_L2 152 + 61, -32, 12, +// MZ2_CARRIER_MACHINEGUN_R2 153 + 61, 32, 12, +// MZ2_WIDOW_RAIL_LEFT 154 + 17, -62, 91, +// MZ2_WIDOW_RAIL_RIGHT 155 + 68, 12, 86, +// MZ2_WIDOW_BLASTER_SWEEP1 156 pmm - the sweeps need to be in sequential order + 47.5, 56, 89, +// MZ2_WIDOW_BLASTER_SWEEP2 157 + 54, 52, 91, +// MZ2_WIDOW_BLASTER_SWEEP3 158 + 58, 40, 91, +// MZ2_WIDOW_BLASTER_SWEEP4 159 + 68, 30, 88, +// MZ2_WIDOW_BLASTER_SWEEP5 160 + 74, 20, 88, +// MZ2_WIDOW_BLASTER_SWEEP6 161 + 73, 11, 87, +// MZ2_WIDOW_BLASTER_SWEEP7 162 + 73, 3, 87, +// MZ2_WIDOW_BLASTER_SWEEP8 163 + 70, -12, 87, +// MZ2_WIDOW_BLASTER_SWEEP9 164 + 67, -20, 90, +// MZ2_WIDOW_BLASTER_100 165 + -20, 76, 90, +// MZ2_WIDOW_BLASTER_90 166 + -8, 74, 90, +// MZ2_WIDOW_BLASTER_80 167 + 0, 72, 90, +// MZ2_WIDOW_BLASTER_70 168 d06 + 10, 71, 89, +// MZ2_WIDOW_BLASTER_60 169 d07 + 23, 70, 87, +// MZ2_WIDOW_BLASTER_50 170 d08 + 32, 64, 85, +// MZ2_WIDOW_BLASTER_40 171 + 40, 58, 84, +// MZ2_WIDOW_BLASTER_30 172 d10 + 48, 50, 83, +// MZ2_WIDOW_BLASTER_20 173 + 54, 42, 82, +// MZ2_WIDOW_BLASTER_10 174 d12 + 56, 34, 82, +// MZ2_WIDOW_BLASTER_0 175 + 58, 26, 82, +// MZ2_WIDOW_BLASTER_10L 176 d14 + 60, 16, 82, +// MZ2_WIDOW_BLASTER_20L 177 + 59, 6, 81, +// MZ2_WIDOW_BLASTER_30L 178 d16 + 58, -2, 80, +// MZ2_WIDOW_BLASTER_40L 179 + 57, -10, 79, +// MZ2_WIDOW_BLASTER_50L 180 d18 + 54, -18, 78, +// MZ2_WIDOW_BLASTER_60L 181 + 42, -32, 80, +// MZ2_WIDOW_BLASTER_70L 182 d20 + 36, -40, 78, +// MZ2_WIDOW_RUN_1 183 + 68.4, 10.88, 82.08, +// MZ2_WIDOW_RUN_2 184 + 68.51, 8.64, 85.14, +// MZ2_WIDOW_RUN_3 185 + 68.66, 6.38, 88.78, +// MZ2_WIDOW_RUN_4 186 + 68.73, 5.1, 84.47, +// MZ2_WIDOW_RUN_5 187 + 68.82, 4.79, 80.52, +// MZ2_WIDOW_RUN_6 188 + 68.77, 6.11, 85.37, +// MZ2_WIDOW_RUN_7 189 + 68.67, 7.99, 90.24, +// MZ2_WIDOW_RUN_8 190 + 68.55, 9.54, 87.36, +// MZ2_CARRIER_ROCKET_1 191 + 0, 0, -5, +// MZ2_CARRIER_ROCKET_2 192 + 0, 0, -5, +// MZ2_CARRIER_ROCKET_3 193 + 0, 0, -5, +// MZ2_CARRIER_ROCKET_4 194 + 0, 0, -5, +// MZ2_WIDOW2_BEAMER_1 195 +// 72.13, -17.63, 93.77, + 69.00, -17.63, 93.77, +// MZ2_WIDOW2_BEAMER_2 196 +// 71.46, -17.08, 89.82, + 69.00, -17.08, 89.82, +// MZ2_WIDOW2_BEAMER_3 197 +// 71.47, -18.40, 90.70, + 69.00, -18.40, 90.70, +// MZ2_WIDOW2_BEAMER_4 198 +// 71.96, -18.34, 94.32, + 69.00, -18.34, 94.32, +// MZ2_WIDOW2_BEAMER_5 199 +// 72.25, -18.30, 97.98, + 69.00, -18.30, 97.98, +// MZ2_WIDOW2_BEAM_SWEEP_1 200 + 45.04, -59.02, 92.24, +// MZ2_WIDOW2_BEAM_SWEEP_2 201 + 50.68, -54.70, 91.96, +// MZ2_WIDOW2_BEAM_SWEEP_3 202 + 56.57, -47.72, 91.65, +// MZ2_WIDOW2_BEAM_SWEEP_4 203 + 61.75, -38.75, 91.38, +// MZ2_WIDOW2_BEAM_SWEEP_5 204 + 65.55, -28.76, 91.24, +// MZ2_WIDOW2_BEAM_SWEEP_6 205 + 67.79, -18.90, 91.22, +// MZ2_WIDOW2_BEAM_SWEEP_7 206 + 68.60, -9.52, 91.23, +// MZ2_WIDOW2_BEAM_SWEEP_8 207 + 68.08, 0.18, 91.32, +// MZ2_WIDOW2_BEAM_SWEEP_9 208 + 66.14, 9.79, 91.44, +// MZ2_WIDOW2_BEAM_SWEEP_10 209 + 62.77, 18.91, 91.65, +// MZ2_WIDOW2_BEAM_SWEEP_11 210 + 58.29, 27.11, 92.00, + +// end of table + 0.0, 0.0, 0.0 +}; diff --git a/original/rogue/m_flipper.c b/original/rogue/m_flipper.c new file mode 100644 index 0000000..586b87f --- /dev/null +++ b/original/rogue/m_flipper.c @@ -0,0 +1,386 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +/* +============================================================================== + +FLIPPER + +============================================================================== +*/ + +#include "g_local.h" +#include "m_flipper.h" + + +static int sound_chomp; +static int sound_attack; +static int sound_pain1; +static int sound_pain2; +static int sound_death; +static int sound_idle; +static int sound_search; +static int sound_sight; + + +void flipper_stand (edict_t *self); + +mframe_t flipper_frames_stand [] = +{ + ai_stand, 0, NULL +}; + +mmove_t flipper_move_stand = {FRAME_flphor01, FRAME_flphor01, flipper_frames_stand, NULL}; + +void flipper_stand (edict_t *self) +{ + self->monsterinfo.currentmove = &flipper_move_stand; +} + +#define FLIPPER_RUN_SPEED 24 + +mframe_t flipper_frames_run [] = +{ + ai_run, FLIPPER_RUN_SPEED, NULL, // 6 + ai_run, FLIPPER_RUN_SPEED, NULL, + ai_run, FLIPPER_RUN_SPEED, NULL, + ai_run, FLIPPER_RUN_SPEED, NULL, + ai_run, FLIPPER_RUN_SPEED, NULL, // 10 + + ai_run, FLIPPER_RUN_SPEED, NULL, + ai_run, FLIPPER_RUN_SPEED, NULL, + ai_run, FLIPPER_RUN_SPEED, NULL, + ai_run, FLIPPER_RUN_SPEED, NULL, + ai_run, FLIPPER_RUN_SPEED, NULL, + ai_run, FLIPPER_RUN_SPEED, NULL, + ai_run, FLIPPER_RUN_SPEED, NULL, + ai_run, FLIPPER_RUN_SPEED, NULL, + ai_run, FLIPPER_RUN_SPEED, NULL, + ai_run, FLIPPER_RUN_SPEED, NULL, // 20 + + ai_run, FLIPPER_RUN_SPEED, NULL, + ai_run, FLIPPER_RUN_SPEED, NULL, + ai_run, FLIPPER_RUN_SPEED, NULL, + ai_run, FLIPPER_RUN_SPEED, NULL, + ai_run, FLIPPER_RUN_SPEED, NULL, + ai_run, FLIPPER_RUN_SPEED, NULL, + ai_run, FLIPPER_RUN_SPEED, NULL, + ai_run, FLIPPER_RUN_SPEED, NULL, + ai_run, FLIPPER_RUN_SPEED, NULL // 29 +}; +mmove_t flipper_move_run_loop = {FRAME_flpver06, FRAME_flpver29, flipper_frames_run, NULL}; + +void flipper_run_loop (edict_t *self) +{ + self->monsterinfo.currentmove = &flipper_move_run_loop; +} + +mframe_t flipper_frames_run_start [] = +{ + ai_run, 8, NULL, + ai_run, 8, NULL, + ai_run, 8, NULL, + ai_run, 8, NULL, + ai_run, 8, NULL, + ai_run, 8, NULL +}; +mmove_t flipper_move_run_start = {FRAME_flpver01, FRAME_flpver06, flipper_frames_run_start, flipper_run_loop}; + +void flipper_run (edict_t *self) +{ + self->monsterinfo.currentmove = &flipper_move_run_start; +} + +/* Standard Swimming */ +mframe_t flipper_frames_walk [] = +{ + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL +}; +mmove_t flipper_move_walk = {FRAME_flphor01, FRAME_flphor24, flipper_frames_walk, NULL}; + +void flipper_walk (edict_t *self) +{ + self->monsterinfo.currentmove = &flipper_move_walk; +} + +mframe_t flipper_frames_start_run [] = +{ + ai_run, 8, NULL, + ai_run, 8, NULL, + ai_run, 8, NULL, + ai_run, 8, NULL, + ai_run, 8, flipper_run +}; +mmove_t flipper_move_start_run = {FRAME_flphor01, FRAME_flphor05, flipper_frames_start_run, NULL}; + +void flipper_start_run (edict_t *self) +{ + self->monsterinfo.currentmove = &flipper_move_start_run; +} + +mframe_t flipper_frames_pain2 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t flipper_move_pain2 = {FRAME_flppn101, FRAME_flppn105, flipper_frames_pain2, flipper_run}; + +mframe_t flipper_frames_pain1 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t flipper_move_pain1 = {FRAME_flppn201, FRAME_flppn205, flipper_frames_pain1, flipper_run}; + +void flipper_bite (edict_t *self) +{ + vec3_t aim; + + VectorSet (aim, MELEE_DISTANCE, 0, 0); + fire_hit (self, aim, 5, 0); +} + +void flipper_preattack (edict_t *self) +{ + gi.sound (self, CHAN_WEAPON, sound_chomp, 1, ATTN_NORM, 0); +} + +mframe_t flipper_frames_attack [] = +{ + ai_charge, 0, flipper_preattack, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, flipper_bite, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, flipper_bite, + ai_charge, 0, NULL +}; +mmove_t flipper_move_attack = {FRAME_flpbit01, FRAME_flpbit20, flipper_frames_attack, flipper_run}; + +void flipper_melee(edict_t *self) +{ + self->monsterinfo.currentmove = &flipper_move_attack; +} + +void flipper_pain (edict_t *self, edict_t *other, float kick, int damage) +{ + int n; + + if (self->health < (self->max_health / 2)) + self->s.skinnum = 1; + + if (level.time < self->pain_debounce_time) + return; + + self->pain_debounce_time = level.time + 3; + + if (skill->value == 3) + return; // no pain anims in nightmare + + n = (rand() + 1) % 2; + if (n == 0) + { + gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &flipper_move_pain1; + } + else + { + gi.sound (self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &flipper_move_pain2; + } +} + +void flipper_dead (edict_t *self) +{ + VectorSet (self->mins, -16, -16, -24); + VectorSet (self->maxs, 16, 16, -8); + self->movetype = MOVETYPE_TOSS; + self->svflags |= SVF_DEADMONSTER; + self->nextthink = 0; + gi.linkentity (self); +} + +mframe_t flipper_frames_death [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t flipper_move_death = {FRAME_flpdth01, FRAME_flpdth56, flipper_frames_death, flipper_dead}; + +void flipper_sight (edict_t *self, edict_t *other) +{ + gi.sound (self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); +} + +void flipper_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + int n; + +// check for gib + if (self->health <= self->gib_health) + { + gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0); + for (n= 0; n < 2; n++) + ThrowGib (self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC); + for (n= 0; n < 2; n++) + ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); + ThrowHead (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); + self->deadflag = DEAD_DEAD; + return; + } + + if (self->deadflag == DEAD_DEAD) + return; + +// regular death + gi.sound (self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0); + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_YES; + self->monsterinfo.currentmove = &flipper_move_death; +} + +/*QUAKED monster_flipper (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight +*/ +void SP_monster_flipper (edict_t *self) +{ + if (deathmatch->value) + { + G_FreeEdict (self); + return; + } + + sound_pain1 = gi.soundindex ("flipper/flppain1.wav"); + sound_pain2 = gi.soundindex ("flipper/flppain2.wav"); + sound_death = gi.soundindex ("flipper/flpdeth1.wav"); + sound_chomp = gi.soundindex ("flipper/flpatck1.wav"); + sound_attack = gi.soundindex ("flipper/flpatck2.wav"); + sound_idle = gi.soundindex ("flipper/flpidle1.wav"); + sound_search = gi.soundindex ("flipper/flpsrch1.wav"); + sound_sight = gi.soundindex ("flipper/flpsght1.wav"); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex ("models/monsters/flipper/tris.md2"); + VectorSet (self->mins, -16, -16, 0); + VectorSet (self->maxs, 16, 16, 32); + + self->health = 50; + self->gib_health = -30; + self->mass = 100; + + self->pain = flipper_pain; + self->die = flipper_die; + + self->monsterinfo.stand = flipper_stand; + self->monsterinfo.walk = flipper_walk; + self->monsterinfo.run = flipper_start_run; + self->monsterinfo.melee = flipper_melee; + self->monsterinfo.sight = flipper_sight; + + gi.linkentity (self); + + self->monsterinfo.currentmove = &flipper_move_stand; + self->monsterinfo.scale = MODEL_SCALE; + + swimmonster_start (self); +} diff --git a/original/rogue/m_flipper.h b/original/rogue/m_flipper.h new file mode 100644 index 0000000..c7afb96 --- /dev/null +++ b/original/rogue/m_flipper.h @@ -0,0 +1,168 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// G:\quake2\baseq2\models/monsters/flipper + +// This file generated by ModelGen - Do NOT Modify + +#define FRAME_flpbit01 0 +#define FRAME_flpbit02 1 +#define FRAME_flpbit03 2 +#define FRAME_flpbit04 3 +#define FRAME_flpbit05 4 +#define FRAME_flpbit06 5 +#define FRAME_flpbit07 6 +#define FRAME_flpbit08 7 +#define FRAME_flpbit09 8 +#define FRAME_flpbit10 9 +#define FRAME_flpbit11 10 +#define FRAME_flpbit12 11 +#define FRAME_flpbit13 12 +#define FRAME_flpbit14 13 +#define FRAME_flpbit15 14 +#define FRAME_flpbit16 15 +#define FRAME_flpbit17 16 +#define FRAME_flpbit18 17 +#define FRAME_flpbit19 18 +#define FRAME_flpbit20 19 +#define FRAME_flptal01 20 +#define FRAME_flptal02 21 +#define FRAME_flptal03 22 +#define FRAME_flptal04 23 +#define FRAME_flptal05 24 +#define FRAME_flptal06 25 +#define FRAME_flptal07 26 +#define FRAME_flptal08 27 +#define FRAME_flptal09 28 +#define FRAME_flptal10 29 +#define FRAME_flptal11 30 +#define FRAME_flptal12 31 +#define FRAME_flptal13 32 +#define FRAME_flptal14 33 +#define FRAME_flptal15 34 +#define FRAME_flptal16 35 +#define FRAME_flptal17 36 +#define FRAME_flptal18 37 +#define FRAME_flptal19 38 +#define FRAME_flptal20 39 +#define FRAME_flptal21 40 +#define FRAME_flphor01 41 +#define FRAME_flphor02 42 +#define FRAME_flphor03 43 +#define FRAME_flphor04 44 +#define FRAME_flphor05 45 +#define FRAME_flphor06 46 +#define FRAME_flphor07 47 +#define FRAME_flphor08 48 +#define FRAME_flphor09 49 +#define FRAME_flphor10 50 +#define FRAME_flphor11 51 +#define FRAME_flphor12 52 +#define FRAME_flphor13 53 +#define FRAME_flphor14 54 +#define FRAME_flphor15 55 +#define FRAME_flphor16 56 +#define FRAME_flphor17 57 +#define FRAME_flphor18 58 +#define FRAME_flphor19 59 +#define FRAME_flphor20 60 +#define FRAME_flphor21 61 +#define FRAME_flphor22 62 +#define FRAME_flphor23 63 +#define FRAME_flphor24 64 +#define FRAME_flpver01 65 +#define FRAME_flpver02 66 +#define FRAME_flpver03 67 +#define FRAME_flpver04 68 +#define FRAME_flpver05 69 +#define FRAME_flpver06 70 +#define FRAME_flpver07 71 +#define FRAME_flpver08 72 +#define FRAME_flpver09 73 +#define FRAME_flpver10 74 +#define FRAME_flpver11 75 +#define FRAME_flpver12 76 +#define FRAME_flpver13 77 +#define FRAME_flpver14 78 +#define FRAME_flpver15 79 +#define FRAME_flpver16 80 +#define FRAME_flpver17 81 +#define FRAME_flpver18 82 +#define FRAME_flpver19 83 +#define FRAME_flpver20 84 +#define FRAME_flpver21 85 +#define FRAME_flpver22 86 +#define FRAME_flpver23 87 +#define FRAME_flpver24 88 +#define FRAME_flpver25 89 +#define FRAME_flpver26 90 +#define FRAME_flpver27 91 +#define FRAME_flpver28 92 +#define FRAME_flpver29 93 +#define FRAME_flppn101 94 +#define FRAME_flppn102 95 +#define FRAME_flppn103 96 +#define FRAME_flppn104 97 +#define FRAME_flppn105 98 +#define FRAME_flppn201 99 +#define FRAME_flppn202 100 +#define FRAME_flppn203 101 +#define FRAME_flppn204 102 +#define FRAME_flppn205 103 +#define FRAME_flpdth01 104 +#define FRAME_flpdth02 105 +#define FRAME_flpdth03 106 +#define FRAME_flpdth04 107 +#define FRAME_flpdth05 108 +#define FRAME_flpdth06 109 +#define FRAME_flpdth07 110 +#define FRAME_flpdth08 111 +#define FRAME_flpdth09 112 +#define FRAME_flpdth10 113 +#define FRAME_flpdth11 114 +#define FRAME_flpdth12 115 +#define FRAME_flpdth13 116 +#define FRAME_flpdth14 117 +#define FRAME_flpdth15 118 +#define FRAME_flpdth16 119 +#define FRAME_flpdth17 120 +#define FRAME_flpdth18 121 +#define FRAME_flpdth19 122 +#define FRAME_flpdth20 123 +#define FRAME_flpdth21 124 +#define FRAME_flpdth22 125 +#define FRAME_flpdth23 126 +#define FRAME_flpdth24 127 +#define FRAME_flpdth25 128 +#define FRAME_flpdth26 129 +#define FRAME_flpdth27 130 +#define FRAME_flpdth28 131 +#define FRAME_flpdth29 132 +#define FRAME_flpdth30 133 +#define FRAME_flpdth31 134 +#define FRAME_flpdth32 135 +#define FRAME_flpdth33 136 +#define FRAME_flpdth34 137 +#define FRAME_flpdth35 138 +#define FRAME_flpdth36 139 +#define FRAME_flpdth37 140 +#define FRAME_flpdth38 141 +#define FRAME_flpdth39 142 +#define FRAME_flpdth40 143 +#define FRAME_flpdth41 144 +#define FRAME_flpdth42 145 +#define FRAME_flpdth43 146 +#define FRAME_flpdth44 147 +#define FRAME_flpdth45 148 +#define FRAME_flpdth46 149 +#define FRAME_flpdth47 150 +#define FRAME_flpdth48 151 +#define FRAME_flpdth49 152 +#define FRAME_flpdth50 153 +#define FRAME_flpdth51 154 +#define FRAME_flpdth52 155 +#define FRAME_flpdth53 156 +#define FRAME_flpdth54 157 +#define FRAME_flpdth55 158 +#define FRAME_flpdth56 159 + +#define MODEL_SCALE 1.000000 diff --git a/original/rogue/m_float.c b/original/rogue/m_float.c new file mode 100644 index 0000000..f7b5ed1 --- /dev/null +++ b/original/rogue/m_float.c @@ -0,0 +1,706 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +/* +============================================================================== + +floater + +============================================================================== +*/ + +#include "g_local.h" +#include "m_float.h" + + +static int sound_attack2; +static int sound_attack3; +static int sound_death1; +static int sound_idle; +static int sound_pain1; +static int sound_pain2; +static int sound_sight; + + +void floater_sight (edict_t *self, edict_t *other) +{ + gi.sound (self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); +} + +void floater_idle (edict_t *self) +{ + gi.sound (self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0); +} + + +//void floater_stand1 (edict_t *self); +void floater_dead (edict_t *self); +void floater_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point); +void floater_run (edict_t *self); +void floater_wham (edict_t *self); +void floater_zap (edict_t *self); + + +void floater_fire_blaster (edict_t *self) +{ + vec3_t start; + vec3_t forward, right; + vec3_t end; + vec3_t dir; + int effect; + + if(!self->enemy || !self->enemy->inuse) //PGM + return; //PGM + + if ((self->s.frame == FRAME_attak104) || (self->s.frame == FRAME_attak107)) + effect = EF_HYPERBLASTER; + else + effect = 0; + AngleVectors (self->s.angles, forward, right, NULL); + G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_FLOAT_BLASTER_1], forward, right, start); + + VectorCopy (self->enemy->s.origin, end); + end[2] += self->enemy->viewheight; + VectorSubtract (end, start, dir); + + monster_fire_blaster (self, start, dir, 1, 1000, MZ2_FLOAT_BLASTER_1, effect); +} + + +mframe_t floater_frames_stand1 [] = +{ + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL +}; +mmove_t floater_move_stand1 = {FRAME_stand101, FRAME_stand152, floater_frames_stand1, NULL}; + +mframe_t floater_frames_stand2 [] = +{ + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL +}; +mmove_t floater_move_stand2 = {FRAME_stand201, FRAME_stand252, floater_frames_stand2, NULL}; + +void floater_stand (edict_t *self) +{ + if (random() <= 0.5) + self->monsterinfo.currentmove = &floater_move_stand1; + else + self->monsterinfo.currentmove = &floater_move_stand2; +} + +mframe_t floater_frames_activate [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t floater_move_activate = {FRAME_actvat01, FRAME_actvat31, floater_frames_activate, NULL}; + +mframe_t floater_frames_attack1 [] = +{ + ai_charge, 0, NULL, // Blaster attack + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, floater_fire_blaster, // BOOM (0, -25.8, 32.5) -- LOOP Starts + ai_charge, 0, floater_fire_blaster, + ai_charge, 0, floater_fire_blaster, + ai_charge, 0, floater_fire_blaster, + ai_charge, 0, floater_fire_blaster, + ai_charge, 0, floater_fire_blaster, + ai_charge, 0, floater_fire_blaster, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL // -- LOOP Ends +}; +mmove_t floater_move_attack1 = {FRAME_attak101, FRAME_attak114, floater_frames_attack1, floater_run}; + +// PMM - circle strafe frames +mframe_t floater_frames_attack1a [] = +{ + ai_charge, 10, NULL, // Blaster attack + ai_charge, 10, NULL, + ai_charge, 10, NULL, + ai_charge, 10, floater_fire_blaster, // BOOM (0, -25.8, 32.5) -- LOOP Starts + ai_charge, 10, floater_fire_blaster, + ai_charge, 10, floater_fire_blaster, + ai_charge, 10, floater_fire_blaster, + ai_charge, 10, floater_fire_blaster, + ai_charge, 10, floater_fire_blaster, + ai_charge, 10, floater_fire_blaster, + ai_charge, 10, NULL, + ai_charge, 10, NULL, + ai_charge, 10, NULL, + ai_charge, 10, NULL // -- LOOP Ends +}; +mmove_t floater_move_attack1a = {FRAME_attak101, FRAME_attak114, floater_frames_attack1a, floater_run}; +//pmm +mframe_t floater_frames_attack2 [] = +{ + ai_charge, 0, NULL, // Claws + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, floater_wham, // WHAM (0, -45, 29.6) -- LOOP Starts + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, // -- LOOP Ends + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL +}; +mmove_t floater_move_attack2 = {FRAME_attak201, FRAME_attak225, floater_frames_attack2, floater_run}; + +mframe_t floater_frames_attack3 [] = +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, floater_zap, // -- LOOP Starts + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, // -- LOOP Ends + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL +}; +mmove_t floater_move_attack3 = {FRAME_attak301, FRAME_attak334, floater_frames_attack3, floater_run}; + +mframe_t floater_frames_death [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t floater_move_death = {FRAME_death01, FRAME_death13, floater_frames_death, floater_dead}; + +mframe_t floater_frames_pain1 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t floater_move_pain1 = {FRAME_pain101, FRAME_pain107, floater_frames_pain1, floater_run}; + +mframe_t floater_frames_pain2 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t floater_move_pain2 = {FRAME_pain201, FRAME_pain208, floater_frames_pain2, floater_run}; + +mframe_t floater_frames_pain3 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t floater_move_pain3 = {FRAME_pain301, FRAME_pain312, floater_frames_pain3, floater_run}; + +mframe_t floater_frames_walk [] = +{ + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL +}; +mmove_t floater_move_walk = {FRAME_stand101, FRAME_stand152, floater_frames_walk, NULL}; + +mframe_t floater_frames_run [] = +{ + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL +}; +mmove_t floater_move_run = {FRAME_stand101, FRAME_stand152, floater_frames_run, NULL}; + +void floater_run (edict_t *self) +{ + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + self->monsterinfo.currentmove = &floater_move_stand1; + else + self->monsterinfo.currentmove = &floater_move_run; +} + +void floater_walk (edict_t *self) +{ + self->monsterinfo.currentmove = &floater_move_walk; +} + +void floater_wham (edict_t *self) +{ + static vec3_t aim = {MELEE_DISTANCE, 0, 0}; + gi.sound (self, CHAN_WEAPON, sound_attack3, 1, ATTN_NORM, 0); + fire_hit (self, aim, 5 + rand() % 6, -50); +} + +void floater_zap (edict_t *self) +{ + vec3_t forward, right; + vec3_t origin; + vec3_t dir; + vec3_t offset; + + VectorSubtract (self->enemy->s.origin, self->s.origin, dir); + + AngleVectors (self->s.angles, forward, right, NULL); + //FIXME use a flash and replace these two lines with the commented one + VectorSet (offset, 18.5, -0.9, 10); + G_ProjectSource (self->s.origin, offset, forward, right, origin); +// G_ProjectSource (self->s.origin, monster_flash_offset[flash_number], forward, right, origin); + + gi.sound (self, CHAN_WEAPON, sound_attack2, 1, ATTN_NORM, 0); + + //FIXME use the flash, Luke + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_SPLASH); + gi.WriteByte (32); + gi.WritePosition (origin); + gi.WriteDir (dir); + gi.WriteByte (1); //sparks + gi.multicast (origin, MULTICAST_PVS); + + T_Damage (self->enemy, self, self, dir, self->enemy->s.origin, vec3_origin, 5 + rand() % 6, -10, DAMAGE_ENERGY, MOD_UNKNOWN); +} + +void floater_attack(edict_t *self) +{ + float chance; +/* if (random() <= 0.5) + self->monsterinfo.currentmove = &flyer_move_attack1; + else */ + // 0% chance of circle in easy + // 50% chance in normal + // 75% chance in hard + // 86.67% chance in nightmare + if (!skill->value) + chance = 0; + else + chance = 1.0 - (0.5/(float)(skill->value)); + + if (random() > chance) + { + self->monsterinfo.attack_state = AS_STRAIGHT; + self->monsterinfo.currentmove = &floater_move_attack1; + } + else // circle strafe + { + if (random () <= 0.5) // switch directions + self->monsterinfo.lefty = 1 - self->monsterinfo.lefty; + self->monsterinfo.attack_state = AS_SLIDING; + self->monsterinfo.currentmove = &floater_move_attack1a; + } +} + + +void floater_melee(edict_t *self) +{ + if (random() < 0.5) + self->monsterinfo.currentmove = &floater_move_attack3; + else + self->monsterinfo.currentmove = &floater_move_attack2; +} + + +void floater_pain (edict_t *self, edict_t *other, float kick, int damage) +{ + int n; + + if (self->health < (self->max_health / 2)) + self->s.skinnum = 1; + + if (level.time < self->pain_debounce_time) + return; + + self->pain_debounce_time = level.time + 3; + if (skill->value == 3) + return; // no pain anims in nightmare + + n = (rand() + 1) % 3; + if (n == 0) + { + gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &floater_move_pain1; + } + else + { + gi.sound (self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &floater_move_pain2; + } +} + +void floater_dead (edict_t *self) +{ + VectorSet (self->mins, -16, -16, -24); + VectorSet (self->maxs, 16, 16, -8); + self->movetype = MOVETYPE_TOSS; + self->svflags |= SVF_DEADMONSTER; + self->nextthink = 0; + gi.linkentity (self); +} + +void floater_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + gi.sound (self, CHAN_VOICE, sound_death1, 1, ATTN_NORM, 0); + BecomeExplosion1(self); +} + +//=========== +//PGM +qboolean floater_blocked (edict_t *self, float dist) +{ + if(blocked_checkshot (self, 0.25 + (0.05 * skill->value) )) + return true; + + return false; +} +//PGM +//=========== + +/*QUAKED monster_floater (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight +*/ +void SP_monster_floater (edict_t *self) +{ + if (deathmatch->value) + { + G_FreeEdict (self); + return; + } + + sound_attack2 = gi.soundindex ("floater/fltatck2.wav"); + sound_attack3 = gi.soundindex ("floater/fltatck3.wav"); + sound_death1 = gi.soundindex ("floater/fltdeth1.wav"); + sound_idle = gi.soundindex ("floater/fltidle1.wav"); + sound_pain1 = gi.soundindex ("floater/fltpain1.wav"); + sound_pain2 = gi.soundindex ("floater/fltpain2.wav"); + sound_sight = gi.soundindex ("floater/fltsght1.wav"); + + gi.soundindex ("floater/fltatck1.wav"); + + self->s.sound = gi.soundindex ("floater/fltsrch1.wav"); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex ("models/monsters/float/tris.md2"); + VectorSet (self->mins, -24, -24, -24); + VectorSet (self->maxs, 24, 24, 32); + + self->health = 200; + self->gib_health = -80; + self->mass = 300; + + self->pain = floater_pain; + self->die = floater_die; + + self->monsterinfo.stand = floater_stand; + self->monsterinfo.walk = floater_walk; + self->monsterinfo.run = floater_run; +// self->monsterinfo.dodge = floater_dodge; + self->monsterinfo.attack = floater_attack; + self->monsterinfo.melee = floater_melee; + self->monsterinfo.sight = floater_sight; + self->monsterinfo.idle = floater_idle; + self->monsterinfo.blocked = floater_blocked; // PGM + + gi.linkentity (self); + + if (random() <= 0.5) + self->monsterinfo.currentmove = &floater_move_stand1; + else + self->monsterinfo.currentmove = &floater_move_stand2; + + self->monsterinfo.scale = MODEL_SCALE; + + flymonster_start (self); +} diff --git a/original/rogue/m_float.h b/original/rogue/m_float.h new file mode 100644 index 0000000..3d53ff1 --- /dev/null +++ b/original/rogue/m_float.h @@ -0,0 +1,256 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// G:\quake2\baseq2\models/monsters/float + +// This file generated by ModelGen - Do NOT Modify + +#define FRAME_actvat01 0 +#define FRAME_actvat02 1 +#define FRAME_actvat03 2 +#define FRAME_actvat04 3 +#define FRAME_actvat05 4 +#define FRAME_actvat06 5 +#define FRAME_actvat07 6 +#define FRAME_actvat08 7 +#define FRAME_actvat09 8 +#define FRAME_actvat10 9 +#define FRAME_actvat11 10 +#define FRAME_actvat12 11 +#define FRAME_actvat13 12 +#define FRAME_actvat14 13 +#define FRAME_actvat15 14 +#define FRAME_actvat16 15 +#define FRAME_actvat17 16 +#define FRAME_actvat18 17 +#define FRAME_actvat19 18 +#define FRAME_actvat20 19 +#define FRAME_actvat21 20 +#define FRAME_actvat22 21 +#define FRAME_actvat23 22 +#define FRAME_actvat24 23 +#define FRAME_actvat25 24 +#define FRAME_actvat26 25 +#define FRAME_actvat27 26 +#define FRAME_actvat28 27 +#define FRAME_actvat29 28 +#define FRAME_actvat30 29 +#define FRAME_actvat31 30 +#define FRAME_attak101 31 +#define FRAME_attak102 32 +#define FRAME_attak103 33 +#define FRAME_attak104 34 +#define FRAME_attak105 35 +#define FRAME_attak106 36 +#define FRAME_attak107 37 +#define FRAME_attak108 38 +#define FRAME_attak109 39 +#define FRAME_attak110 40 +#define FRAME_attak111 41 +#define FRAME_attak112 42 +#define FRAME_attak113 43 +#define FRAME_attak114 44 +#define FRAME_attak201 45 +#define FRAME_attak202 46 +#define FRAME_attak203 47 +#define FRAME_attak204 48 +#define FRAME_attak205 49 +#define FRAME_attak206 50 +#define FRAME_attak207 51 +#define FRAME_attak208 52 +#define FRAME_attak209 53 +#define FRAME_attak210 54 +#define FRAME_attak211 55 +#define FRAME_attak212 56 +#define FRAME_attak213 57 +#define FRAME_attak214 58 +#define FRAME_attak215 59 +#define FRAME_attak216 60 +#define FRAME_attak217 61 +#define FRAME_attak218 62 +#define FRAME_attak219 63 +#define FRAME_attak220 64 +#define FRAME_attak221 65 +#define FRAME_attak222 66 +#define FRAME_attak223 67 +#define FRAME_attak224 68 +#define FRAME_attak225 69 +#define FRAME_attak301 70 +#define FRAME_attak302 71 +#define FRAME_attak303 72 +#define FRAME_attak304 73 +#define FRAME_attak305 74 +#define FRAME_attak306 75 +#define FRAME_attak307 76 +#define FRAME_attak308 77 +#define FRAME_attak309 78 +#define FRAME_attak310 79 +#define FRAME_attak311 80 +#define FRAME_attak312 81 +#define FRAME_attak313 82 +#define FRAME_attak314 83 +#define FRAME_attak315 84 +#define FRAME_attak316 85 +#define FRAME_attak317 86 +#define FRAME_attak318 87 +#define FRAME_attak319 88 +#define FRAME_attak320 89 +#define FRAME_attak321 90 +#define FRAME_attak322 91 +#define FRAME_attak323 92 +#define FRAME_attak324 93 +#define FRAME_attak325 94 +#define FRAME_attak326 95 +#define FRAME_attak327 96 +#define FRAME_attak328 97 +#define FRAME_attak329 98 +#define FRAME_attak330 99 +#define FRAME_attak331 100 +#define FRAME_attak332 101 +#define FRAME_attak333 102 +#define FRAME_attak334 103 +#define FRAME_death01 104 +#define FRAME_death02 105 +#define FRAME_death03 106 +#define FRAME_death04 107 +#define FRAME_death05 108 +#define FRAME_death06 109 +#define FRAME_death07 110 +#define FRAME_death08 111 +#define FRAME_death09 112 +#define FRAME_death10 113 +#define FRAME_death11 114 +#define FRAME_death12 115 +#define FRAME_death13 116 +#define FRAME_pain101 117 +#define FRAME_pain102 118 +#define FRAME_pain103 119 +#define FRAME_pain104 120 +#define FRAME_pain105 121 +#define FRAME_pain106 122 +#define FRAME_pain107 123 +#define FRAME_pain201 124 +#define FRAME_pain202 125 +#define FRAME_pain203 126 +#define FRAME_pain204 127 +#define FRAME_pain205 128 +#define FRAME_pain206 129 +#define FRAME_pain207 130 +#define FRAME_pain208 131 +#define FRAME_pain301 132 +#define FRAME_pain302 133 +#define FRAME_pain303 134 +#define FRAME_pain304 135 +#define FRAME_pain305 136 +#define FRAME_pain306 137 +#define FRAME_pain307 138 +#define FRAME_pain308 139 +#define FRAME_pain309 140 +#define FRAME_pain310 141 +#define FRAME_pain311 142 +#define FRAME_pain312 143 +#define FRAME_stand101 144 +#define FRAME_stand102 145 +#define FRAME_stand103 146 +#define FRAME_stand104 147 +#define FRAME_stand105 148 +#define FRAME_stand106 149 +#define FRAME_stand107 150 +#define FRAME_stand108 151 +#define FRAME_stand109 152 +#define FRAME_stand110 153 +#define FRAME_stand111 154 +#define FRAME_stand112 155 +#define FRAME_stand113 156 +#define FRAME_stand114 157 +#define FRAME_stand115 158 +#define FRAME_stand116 159 +#define FRAME_stand117 160 +#define FRAME_stand118 161 +#define FRAME_stand119 162 +#define FRAME_stand120 163 +#define FRAME_stand121 164 +#define FRAME_stand122 165 +#define FRAME_stand123 166 +#define FRAME_stand124 167 +#define FRAME_stand125 168 +#define FRAME_stand126 169 +#define FRAME_stand127 170 +#define FRAME_stand128 171 +#define FRAME_stand129 172 +#define FRAME_stand130 173 +#define FRAME_stand131 174 +#define FRAME_stand132 175 +#define FRAME_stand133 176 +#define FRAME_stand134 177 +#define FRAME_stand135 178 +#define FRAME_stand136 179 +#define FRAME_stand137 180 +#define FRAME_stand138 181 +#define FRAME_stand139 182 +#define FRAME_stand140 183 +#define FRAME_stand141 184 +#define FRAME_stand142 185 +#define FRAME_stand143 186 +#define FRAME_stand144 187 +#define FRAME_stand145 188 +#define FRAME_stand146 189 +#define FRAME_stand147 190 +#define FRAME_stand148 191 +#define FRAME_stand149 192 +#define FRAME_stand150 193 +#define FRAME_stand151 194 +#define FRAME_stand152 195 +#define FRAME_stand201 196 +#define FRAME_stand202 197 +#define FRAME_stand203 198 +#define FRAME_stand204 199 +#define FRAME_stand205 200 +#define FRAME_stand206 201 +#define FRAME_stand207 202 +#define FRAME_stand208 203 +#define FRAME_stand209 204 +#define FRAME_stand210 205 +#define FRAME_stand211 206 +#define FRAME_stand212 207 +#define FRAME_stand213 208 +#define FRAME_stand214 209 +#define FRAME_stand215 210 +#define FRAME_stand216 211 +#define FRAME_stand217 212 +#define FRAME_stand218 213 +#define FRAME_stand219 214 +#define FRAME_stand220 215 +#define FRAME_stand221 216 +#define FRAME_stand222 217 +#define FRAME_stand223 218 +#define FRAME_stand224 219 +#define FRAME_stand225 220 +#define FRAME_stand226 221 +#define FRAME_stand227 222 +#define FRAME_stand228 223 +#define FRAME_stand229 224 +#define FRAME_stand230 225 +#define FRAME_stand231 226 +#define FRAME_stand232 227 +#define FRAME_stand233 228 +#define FRAME_stand234 229 +#define FRAME_stand235 230 +#define FRAME_stand236 231 +#define FRAME_stand237 232 +#define FRAME_stand238 233 +#define FRAME_stand239 234 +#define FRAME_stand240 235 +#define FRAME_stand241 236 +#define FRAME_stand242 237 +#define FRAME_stand243 238 +#define FRAME_stand244 239 +#define FRAME_stand245 240 +#define FRAME_stand246 241 +#define FRAME_stand247 242 +#define FRAME_stand248 243 +#define FRAME_stand249 244 +#define FRAME_stand250 245 +#define FRAME_stand251 246 +#define FRAME_stand252 247 + +#define MODEL_SCALE 1.000000 diff --git a/original/rogue/m_flyer.c b/original/rogue/m_flyer.c new file mode 100644 index 0000000..f049c11 --- /dev/null +++ b/original/rogue/m_flyer.c @@ -0,0 +1,874 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +/* +============================================================================== + +flyer + +============================================================================== +*/ + +#include "g_local.h" +#include "m_flyer.h" + +qboolean visible (edict_t *self, edict_t *other); + +static int nextmove; // Used for start/stop frames + +static int sound_sight; +static int sound_idle; +static int sound_pain1; +static int sound_pain2; +static int sound_slash; +static int sound_sproing; +static int sound_die; + + +void flyer_check_melee(edict_t *self); +void flyer_loop_melee (edict_t *self); +void flyer_melee (edict_t *self); +void flyer_setstart (edict_t *self); +void flyer_stand (edict_t *self); +void flyer_nextmove (edict_t *self); + +// ROGUE - kamikaze stuff +void flyer_kamikaze (edict_t *self); +void flyer_kamikaze_check (edict_t *self); +void flyer_die(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point); +/* +void showme1 (edict_t *self) +{ + if (!self->dmg) + { + gi.dprintf ("straight - %d\n", self->monsterinfo.lefty); + self->dmg = 1; + } +} + +void showme2 (edict_t *self) +{ + if (!self->dmg) + { + gi.dprintf ("strafe - %d\n", self->monsterinfo.lefty); + self->dmg = 1; + } +} +*/ + +void flyer_sight (edict_t *self, edict_t *other) +{ + gi.sound (self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); +} + +void flyer_idle (edict_t *self) +{ + gi.sound (self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0); +} + +void flyer_pop_blades (edict_t *self) +{ + gi.sound (self, CHAN_VOICE, sound_sproing, 1, ATTN_NORM, 0); +} + + +mframe_t flyer_frames_stand [] = +{ + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL +}; +mmove_t flyer_move_stand = {FRAME_stand01, FRAME_stand45, flyer_frames_stand, NULL}; + + +mframe_t flyer_frames_walk [] = +{ + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL +}; +mmove_t flyer_move_walk = {FRAME_stand01, FRAME_stand45, flyer_frames_walk, NULL}; + +mframe_t flyer_frames_run [] = +{ + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL +}; +mmove_t flyer_move_run = {FRAME_stand01, FRAME_stand45, flyer_frames_run, NULL}; + +mframe_t flyer_frames_kamizake [] = +{ + ai_charge, 40, flyer_kamikaze_check, + ai_charge, 40, flyer_kamikaze_check, + ai_charge, 40, flyer_kamikaze_check, + ai_charge, 40, flyer_kamikaze_check, + ai_charge, 40, flyer_kamikaze_check +}; +mmove_t flyer_move_kamikaze = {FRAME_rollr02, FRAME_rollr06, flyer_frames_kamizake, flyer_kamikaze}; + +void flyer_run (edict_t *self) +{ + if (self->mass > 50) + self->monsterinfo.currentmove = &flyer_move_kamikaze; + else + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + self->monsterinfo.currentmove = &flyer_move_stand; + else + self->monsterinfo.currentmove = &flyer_move_run; +} + +void flyer_walk (edict_t *self) +{ + if (self->mass > 50) + flyer_run (self); + else + self->monsterinfo.currentmove = &flyer_move_walk; +} + +void flyer_stand (edict_t *self) +{ + if (self->mass > 50) + flyer_run (self); + else + self->monsterinfo.currentmove = &flyer_move_stand; +} + +// ROGUE - kamikaze stuff + +void flyer_kamikaze_explode (edict_t *self) +{ + vec3_t dir; + + if (self->monsterinfo.commander && self->monsterinfo.commander->inuse && + !strcmp(self->monsterinfo.commander->classname, "monster_carrier")) + { + self->monsterinfo.commander->monsterinfo.monster_slots++; +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("suicide hit!. %d slots left\n", self->monsterinfo.commander->monsterinfo.monster_slots); + } + +// gi.dprintf ("boom!\n"); +// T_RadiusDamage(self, self->owner, 125, self, self->dmg_radius, MOD_NUKE); +// T_RadiusDamage(self, self->owner, 125, self, 150, MOD_NUKE); + if (self->enemy) + { + VectorSubtract (self->enemy->s.origin, self->s.origin, dir); +//void T_Damage (edict_t *targ, edict_t *inflictor, edict_t *attacker, vec3_t dir, vec3_t point, +// vec3_t normal, int damage, int knockback, int dflags, int mod) + T_Damage (self->enemy, self, self, dir, self->s.origin, vec3_origin, (int)50, (int)50, DAMAGE_RADIUS, MOD_UNKNOWN); + } + + flyer_die (self, NULL, NULL, 0, dir); +/* VectorMA (self->s.origin, -0.02, self->velocity, origin); + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_ROCKET_EXPLOSION); + gi.WritePosition (origin); + gi.multicast (self->s.origin, MULTICAST_PHS); + + G_FreeEdict (self); +*/ +} + +void flyer_kamikaze (edict_t *self) +{ + self->monsterinfo.currentmove = &flyer_move_kamikaze; +} + +void flyer_kamikaze_check (edict_t *self) +{ + float dist; + + // PMM - this needed because we could have gone away before we get here (blocked code) + if (!self->inuse) + return; + + if ((!self->enemy) || (!self->enemy->inuse)) + { + flyer_kamikaze_explode (self); + return; + } + + self->goalentity = self->enemy; + + dist = realrange (self, self->enemy); + + if (dist < 90) + flyer_kamikaze_explode (self); +} + +// rogue - kamikaze + +mframe_t flyer_frames_start [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, flyer_nextmove +}; +mmove_t flyer_move_start = {FRAME_start01, FRAME_start06, flyer_frames_start, NULL}; + +mframe_t flyer_frames_stop [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, flyer_nextmove +}; +mmove_t flyer_move_stop = {FRAME_stop01, FRAME_stop07, flyer_frames_stop, NULL}; + +void flyer_stop (edict_t *self) +{ + self->monsterinfo.currentmove = &flyer_move_stop; +} + +void flyer_start (edict_t *self) +{ + self->monsterinfo.currentmove = &flyer_move_start; +} + + +mframe_t flyer_frames_rollright [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t flyer_move_rollright = {FRAME_rollr01, FRAME_rollr09, flyer_frames_rollright, NULL}; + +mframe_t flyer_frames_rollleft [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t flyer_move_rollleft = {FRAME_rollf01, FRAME_rollf09, flyer_frames_rollleft, NULL}; + +mframe_t flyer_frames_pain3 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t flyer_move_pain3 = {FRAME_pain301, FRAME_pain304, flyer_frames_pain3, flyer_run}; + +mframe_t flyer_frames_pain2 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t flyer_move_pain2 = {FRAME_pain201, FRAME_pain204, flyer_frames_pain2, flyer_run}; + +mframe_t flyer_frames_pain1 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t flyer_move_pain1 = {FRAME_pain101, FRAME_pain109, flyer_frames_pain1, flyer_run}; + +mframe_t flyer_frames_defense [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, // Hold this frame + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t flyer_move_defense = {FRAME_defens01, FRAME_defens06, flyer_frames_defense, NULL}; + +mframe_t flyer_frames_bankright [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t flyer_move_bankright = {FRAME_bankr01, FRAME_bankr07, flyer_frames_bankright, NULL}; + +mframe_t flyer_frames_bankleft [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t flyer_move_bankleft = {FRAME_bankl01, FRAME_bankl07, flyer_frames_bankleft, NULL}; + + +void flyer_fire (edict_t *self, int flash_number) +{ + vec3_t start; + vec3_t forward, right; + vec3_t end; + vec3_t dir; + int effect; + + if(!self->enemy || !self->enemy->inuse) //PGM + return; //PGM + + if ((self->s.frame == FRAME_attak204) || (self->s.frame == FRAME_attak207) || (self->s.frame == FRAME_attak210)) + effect = EF_HYPERBLASTER; + else + effect = 0; + AngleVectors (self->s.angles, forward, right, NULL); + G_ProjectSource (self->s.origin, monster_flash_offset[flash_number], forward, right, start); + + VectorCopy (self->enemy->s.origin, end); + end[2] += self->enemy->viewheight; + VectorSubtract (end, start, dir); + + monster_fire_blaster (self, start, dir, 1, 1000, flash_number, effect); +} + +void flyer_fireleft (edict_t *self) +{ + flyer_fire (self, MZ2_FLYER_BLASTER_1); +} + +void flyer_fireright (edict_t *self) +{ + flyer_fire (self, MZ2_FLYER_BLASTER_2); +} + + +mframe_t flyer_frames_attack2 [] = +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, -10, flyer_fireleft, // left gun + ai_charge, -10, flyer_fireright, // right gun + ai_charge, -10, flyer_fireleft, // left gun + ai_charge, -10, flyer_fireright, // right gun + ai_charge, -10, flyer_fireleft, // left gun + ai_charge, -10, flyer_fireright, // right gun + ai_charge, -10, flyer_fireleft, // left gun + ai_charge, -10, flyer_fireright, // right gun + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL +}; +mmove_t flyer_move_attack2 = {FRAME_attak201, FRAME_attak217, flyer_frames_attack2, flyer_run}; + +// PMM +// circle strafe frames + +mframe_t flyer_frames_attack3 [] = +{ + ai_charge, 10, NULL, + ai_charge, 10, NULL, + ai_charge, 10, NULL, + ai_charge, 10, flyer_fireleft, // left gun + ai_charge, 10, flyer_fireright, // right gun + ai_charge, 10, flyer_fireleft, // left gun + ai_charge, 10, flyer_fireright, // right gun + ai_charge, 10, flyer_fireleft, // left gun + ai_charge, 10, flyer_fireright, // right gun + ai_charge, 10, flyer_fireleft, // left gun + ai_charge, 10, flyer_fireright, // right gun + ai_charge, 10, NULL, + ai_charge, 10, NULL, + ai_charge, 10, NULL, + ai_charge, 10, NULL, + ai_charge, 10, NULL, + ai_charge, 10, NULL +}; +mmove_t flyer_move_attack3 = {FRAME_attak201, FRAME_attak217, flyer_frames_attack3, flyer_run}; +// pmm + +void flyer_slash_left (edict_t *self) +{ + vec3_t aim; + + VectorSet (aim, MELEE_DISTANCE, self->mins[0], 0); + fire_hit (self, aim, 5, 0); + gi.sound (self, CHAN_WEAPON, sound_slash, 1, ATTN_NORM, 0); +} + +void flyer_slash_right (edict_t *self) +{ + vec3_t aim; + + VectorSet (aim, MELEE_DISTANCE, self->maxs[0], 0); + fire_hit (self, aim, 5, 0); + gi.sound (self, CHAN_WEAPON, sound_slash, 1, ATTN_NORM, 0); +} + +mframe_t flyer_frames_start_melee [] = +{ + ai_charge, 0, flyer_pop_blades, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL +}; +mmove_t flyer_move_start_melee = {FRAME_attak101, FRAME_attak106, flyer_frames_start_melee, flyer_loop_melee}; + +mframe_t flyer_frames_end_melee [] = +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL +}; +mmove_t flyer_move_end_melee = {FRAME_attak119, FRAME_attak121, flyer_frames_end_melee, flyer_run}; + + +mframe_t flyer_frames_loop_melee [] = +{ + ai_charge, 0, NULL, // Loop Start + ai_charge, 0, NULL, + ai_charge, 0, flyer_slash_left, // Left Wing Strike + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, flyer_slash_right, // Right Wing Strike + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL // Loop Ends + +}; +mmove_t flyer_move_loop_melee = {FRAME_attak107, FRAME_attak118, flyer_frames_loop_melee, flyer_check_melee}; + +void flyer_loop_melee (edict_t *self) +{ +/* if (random() <= 0.5) + self->monsterinfo.currentmove = &flyer_move_attack1; + else */ + self->monsterinfo.currentmove = &flyer_move_loop_melee; +} + + + +void flyer_attack (edict_t *self) +{ + float chance; +/* if (random() <= 0.5) + self->monsterinfo.currentmove = &flyer_move_attack1; + else */ + // 0% chance of circle in easy + // 50% chance in normal + // 75% chance in hard + // 86.67% chance in nightmare + + if (self->mass > 50) + { + flyer_run (self); + return; + } + + if (!skill->value) + chance = 0; + else + chance = 1.0 - (0.5/(float)(skill->value)); + + if (random() > chance) + { + self->monsterinfo.attack_state = AS_STRAIGHT; + self->monsterinfo.currentmove = &flyer_move_attack2; + } + else // circle strafe + { + if (random () <= 0.5) // switch directions + self->monsterinfo.lefty = 1 - self->monsterinfo.lefty; + self->monsterinfo.attack_state = AS_SLIDING; + self->monsterinfo.currentmove = &flyer_move_attack3; + } +} + +void flyer_setstart (edict_t *self) +{ + nextmove = ACTION_run; + self->monsterinfo.currentmove = &flyer_move_start; +} + +void flyer_nextmove (edict_t *self) +{ + if (nextmove == ACTION_attack1) + self->monsterinfo.currentmove = &flyer_move_start_melee; + else if (nextmove == ACTION_attack2) + self->monsterinfo.currentmove = &flyer_move_attack2; + else if (nextmove == ACTION_run) + self->monsterinfo.currentmove = &flyer_move_run; +} + +void flyer_melee (edict_t *self) +{ +// flyer.nextmove = ACTION_attack1; +// self->monsterinfo.currentmove = &flyer_move_stop; + if (self->mass > 50) + flyer_run (self); + else + self->monsterinfo.currentmove = &flyer_move_start_melee; +} + +void flyer_check_melee(edict_t *self) +{ + if (range (self, self->enemy) == RANGE_MELEE) + if (random() <= 0.8) + self->monsterinfo.currentmove = &flyer_move_loop_melee; + else + self->monsterinfo.currentmove = &flyer_move_end_melee; + else + self->monsterinfo.currentmove = &flyer_move_end_melee; +} + +void flyer_pain (edict_t *self, edict_t *other, float kick, int damage) +{ + int n; + + // pmm - kamikaze's don't feel pain + if (self->mass != 50) + return; + // pmm + + if (self->health < (self->max_health / 2)) + self->s.skinnum = 1; + + if (level.time < self->pain_debounce_time) + return; + + self->pain_debounce_time = level.time + 3; + if (skill->value == 3) + return; // no pain anims in nightmare + + n = rand() % 3; + if (n == 0) + { + gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &flyer_move_pain1; + } + else if (n == 1) + { + gi.sound (self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &flyer_move_pain2; + } + else + { + gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &flyer_move_pain3; + } +} + + +void flyer_die(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + gi.sound (self, CHAN_VOICE, sound_die, 1, ATTN_NORM, 0); + BecomeExplosion1(self); +} + +// PMM - kamikaze code .. blow up if blocked +int flyer_blocked (edict_t *self, float dist) +{ + vec3_t origin; + + // kamikaze = 100, normal = 50 + if (self->mass == 100) + { + flyer_kamikaze_check(self); + + // if the above didn't blow us up (i.e. I got blocked by the player) + if (self->inuse) + { + if (self->monsterinfo.commander && self->monsterinfo.commander->inuse && + !strcmp(self->monsterinfo.commander->classname, "monster_carrier")) + { + self->monsterinfo.commander->monsterinfo.monster_slots++; +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("suicide blocked, exploding. %d slots left\n", self->monsterinfo.commander->monsterinfo.monster_slots); + } + + VectorMA (self->s.origin, -0.02, self->velocity, origin); + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_ROCKET_EXPLOSION); + gi.WritePosition (origin); + gi.multicast (self->s.origin, MULTICAST_PHS); + + G_FreeEdict (self); + } + return true; + } + // we're a normal flyer + if(blocked_checkshot (self, 0.25 + (0.05 * skill->value) )) + return true; + + return false; +} + +/*QUAKED monster_flyer (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight +*/ +void SP_monster_flyer (edict_t *self) +{ + if (deathmatch->value) + { + G_FreeEdict (self); + return; + } + + // fix a map bug in jail5.bsp + if (!Q_stricmp(level.mapname, "jail5") && (self->s.origin[2] == -104)) + { + self->targetname = self->target; + self->target = NULL; + } + + sound_sight = gi.soundindex ("flyer/flysght1.wav"); + sound_idle = gi.soundindex ("flyer/flysrch1.wav"); + sound_pain1 = gi.soundindex ("flyer/flypain1.wav"); + sound_pain2 = gi.soundindex ("flyer/flypain2.wav"); + sound_slash = gi.soundindex ("flyer/flyatck2.wav"); + sound_sproing = gi.soundindex ("flyer/flyatck1.wav"); + sound_die = gi.soundindex ("flyer/flydeth1.wav"); + + gi.soundindex ("flyer/flyatck3.wav"); + + self->s.modelindex = gi.modelindex ("models/monsters/flyer/tris.md2"); + VectorSet (self->mins, -16, -16, -24); + // PMM - shortened to 16 from 32 + VectorSet (self->maxs, 16, 16, 16); + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + + self->s.sound = gi.soundindex ("flyer/flyidle1.wav"); + + self->health = 50; + self->mass = 50; + + self->pain = flyer_pain; + self->die = flyer_die; + + self->monsterinfo.stand = flyer_stand; + self->monsterinfo.walk = flyer_walk; + self->monsterinfo.run = flyer_run; + self->monsterinfo.attack = flyer_attack; + self->monsterinfo.melee = flyer_melee; + self->monsterinfo.sight = flyer_sight; + self->monsterinfo.idle = flyer_idle; + self->monsterinfo.blocked = flyer_blocked; + + gi.linkentity (self); + + self->monsterinfo.currentmove = &flyer_move_stand; + self->monsterinfo.scale = MODEL_SCALE; + + flymonster_start (self); +} + +// PMM - suicide fliers +void SP_monster_kamikaze (edict_t *self) +{ + if (deathmatch->value) + { + G_FreeEdict (self); + return; + } + + sound_sight = gi.soundindex ("flyer/flysght1.wav"); + sound_idle = gi.soundindex ("flyer/flysrch1.wav"); + sound_pain1 = gi.soundindex ("flyer/flypain1.wav"); + sound_pain2 = gi.soundindex ("flyer/flypain2.wav"); + sound_slash = gi.soundindex ("flyer/flyatck2.wav"); + sound_sproing = gi.soundindex ("flyer/flyatck1.wav"); + sound_die = gi.soundindex ("flyer/flydeth1.wav"); + + gi.soundindex ("flyer/flyatck3.wav"); + + self->s.modelindex = gi.modelindex ("models/monsters/flyer/tris.md2"); + VectorSet (self->mins, -16, -16, -24); + // used to be 32 tall .. was WAY too big + VectorSet (self->maxs, 16, 16, 16); + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + + self->s.sound = gi.soundindex ("flyer/flyidle1.wav"); + + self->s.effects |= EF_ROCKET; + + self->health = 50; + // PMM - normal flyer has mass of 50 + self->mass = 100; + + self->pain = flyer_pain; + self->die = flyer_die; + + self->monsterinfo.stand = flyer_stand; + self->monsterinfo.walk = flyer_walk; + self->monsterinfo.run = flyer_run; + self->monsterinfo.attack = flyer_attack; + self->monsterinfo.melee = flyer_melee; + self->monsterinfo.sight = flyer_sight; + self->monsterinfo.idle = flyer_idle; + + self->monsterinfo.blocked = flyer_blocked; + + gi.linkentity (self); + + self->monsterinfo.currentmove = &flyer_move_stand; + self->monsterinfo.scale = MODEL_SCALE; + + flymonster_start (self); +} diff --git a/original/rogue/m_flyer.h b/original/rogue/m_flyer.h new file mode 100644 index 0000000..47b2111 --- /dev/null +++ b/original/rogue/m_flyer.h @@ -0,0 +1,165 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// G:\quake2\baseq2\models/monsters/flyer + +// This file generated by ModelGen - Do NOT Modify + +#define ACTION_nothing 0 +#define ACTION_attack1 1 +#define ACTION_attack2 2 +#define ACTION_run 3 +#define ACTION_walk 4 + +#define FRAME_start01 0 +#define FRAME_start02 1 +#define FRAME_start03 2 +#define FRAME_start04 3 +#define FRAME_start05 4 +#define FRAME_start06 5 +#define FRAME_stop01 6 +#define FRAME_stop02 7 +#define FRAME_stop03 8 +#define FRAME_stop04 9 +#define FRAME_stop05 10 +#define FRAME_stop06 11 +#define FRAME_stop07 12 +#define FRAME_stand01 13 +#define FRAME_stand02 14 +#define FRAME_stand03 15 +#define FRAME_stand04 16 +#define FRAME_stand05 17 +#define FRAME_stand06 18 +#define FRAME_stand07 19 +#define FRAME_stand08 20 +#define FRAME_stand09 21 +#define FRAME_stand10 22 +#define FRAME_stand11 23 +#define FRAME_stand12 24 +#define FRAME_stand13 25 +#define FRAME_stand14 26 +#define FRAME_stand15 27 +#define FRAME_stand16 28 +#define FRAME_stand17 29 +#define FRAME_stand18 30 +#define FRAME_stand19 31 +#define FRAME_stand20 32 +#define FRAME_stand21 33 +#define FRAME_stand22 34 +#define FRAME_stand23 35 +#define FRAME_stand24 36 +#define FRAME_stand25 37 +#define FRAME_stand26 38 +#define FRAME_stand27 39 +#define FRAME_stand28 40 +#define FRAME_stand29 41 +#define FRAME_stand30 42 +#define FRAME_stand31 43 +#define FRAME_stand32 44 +#define FRAME_stand33 45 +#define FRAME_stand34 46 +#define FRAME_stand35 47 +#define FRAME_stand36 48 +#define FRAME_stand37 49 +#define FRAME_stand38 50 +#define FRAME_stand39 51 +#define FRAME_stand40 52 +#define FRAME_stand41 53 +#define FRAME_stand42 54 +#define FRAME_stand43 55 +#define FRAME_stand44 56 +#define FRAME_stand45 57 +#define FRAME_attak101 58 +#define FRAME_attak102 59 +#define FRAME_attak103 60 +#define FRAME_attak104 61 +#define FRAME_attak105 62 +#define FRAME_attak106 63 +#define FRAME_attak107 64 +#define FRAME_attak108 65 +#define FRAME_attak109 66 +#define FRAME_attak110 67 +#define FRAME_attak111 68 +#define FRAME_attak112 69 +#define FRAME_attak113 70 +#define FRAME_attak114 71 +#define FRAME_attak115 72 +#define FRAME_attak116 73 +#define FRAME_attak117 74 +#define FRAME_attak118 75 +#define FRAME_attak119 76 +#define FRAME_attak120 77 +#define FRAME_attak121 78 +#define FRAME_attak201 79 +#define FRAME_attak202 80 +#define FRAME_attak203 81 +#define FRAME_attak204 82 +#define FRAME_attak205 83 +#define FRAME_attak206 84 +#define FRAME_attak207 85 +#define FRAME_attak208 86 +#define FRAME_attak209 87 +#define FRAME_attak210 88 +#define FRAME_attak211 89 +#define FRAME_attak212 90 +#define FRAME_attak213 91 +#define FRAME_attak214 92 +#define FRAME_attak215 93 +#define FRAME_attak216 94 +#define FRAME_attak217 95 +#define FRAME_bankl01 96 +#define FRAME_bankl02 97 +#define FRAME_bankl03 98 +#define FRAME_bankl04 99 +#define FRAME_bankl05 100 +#define FRAME_bankl06 101 +#define FRAME_bankl07 102 +#define FRAME_bankr01 103 +#define FRAME_bankr02 104 +#define FRAME_bankr03 105 +#define FRAME_bankr04 106 +#define FRAME_bankr05 107 +#define FRAME_bankr06 108 +#define FRAME_bankr07 109 +#define FRAME_rollf01 110 +#define FRAME_rollf02 111 +#define FRAME_rollf03 112 +#define FRAME_rollf04 113 +#define FRAME_rollf05 114 +#define FRAME_rollf06 115 +#define FRAME_rollf07 116 +#define FRAME_rollf08 117 +#define FRAME_rollf09 118 +#define FRAME_rollr01 119 +#define FRAME_rollr02 120 +#define FRAME_rollr03 121 +#define FRAME_rollr04 122 +#define FRAME_rollr05 123 +#define FRAME_rollr06 124 +#define FRAME_rollr07 125 +#define FRAME_rollr08 126 +#define FRAME_rollr09 127 +#define FRAME_defens01 128 +#define FRAME_defens02 129 +#define FRAME_defens03 130 +#define FRAME_defens04 131 +#define FRAME_defens05 132 +#define FRAME_defens06 133 +#define FRAME_pain101 134 +#define FRAME_pain102 135 +#define FRAME_pain103 136 +#define FRAME_pain104 137 +#define FRAME_pain105 138 +#define FRAME_pain106 139 +#define FRAME_pain107 140 +#define FRAME_pain108 141 +#define FRAME_pain109 142 +#define FRAME_pain201 143 +#define FRAME_pain202 144 +#define FRAME_pain203 145 +#define FRAME_pain204 146 +#define FRAME_pain301 147 +#define FRAME_pain302 148 +#define FRAME_pain303 149 +#define FRAME_pain304 150 + +#define MODEL_SCALE 1.000000 diff --git a/original/rogue/m_gladiator.c b/original/rogue/m_gladiator.c new file mode 100644 index 0000000..747526d --- /dev/null +++ b/original/rogue/m_gladiator.c @@ -0,0 +1,385 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +/* +============================================================================== + +GLADIATOR + +============================================================================== +*/ + +#include "g_local.h" +#include "m_gladiator.h" + + +static int sound_pain1; +static int sound_pain2; +static int sound_die; +static int sound_gun; +static int sound_cleaver_swing; +static int sound_cleaver_hit; +static int sound_cleaver_miss; +static int sound_idle; +static int sound_search; +static int sound_sight; + + +void gladiator_idle (edict_t *self) +{ + gi.sound (self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0); +} + +void gladiator_sight (edict_t *self, edict_t *other) +{ + gi.sound (self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); +} + +void gladiator_search (edict_t *self) +{ + gi.sound (self, CHAN_VOICE, sound_search, 1, ATTN_NORM, 0); +} + +void gladiator_cleaver_swing (edict_t *self) +{ + gi.sound (self, CHAN_WEAPON, sound_cleaver_swing, 1, ATTN_NORM, 0); +} + +mframe_t gladiator_frames_stand [] = +{ + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL +}; +mmove_t gladiator_move_stand = {FRAME_stand1, FRAME_stand7, gladiator_frames_stand, NULL}; + +void gladiator_stand (edict_t *self) +{ + self->monsterinfo.currentmove = &gladiator_move_stand; +} + + +mframe_t gladiator_frames_walk [] = +{ + ai_walk, 15, NULL, + ai_walk, 7, NULL, + ai_walk, 6, NULL, + ai_walk, 5, NULL, + ai_walk, 2, NULL, + ai_walk, 0, NULL, + ai_walk, 2, NULL, + ai_walk, 8, NULL, + ai_walk, 12, NULL, + ai_walk, 8, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 2, NULL, + ai_walk, 2, NULL, + ai_walk, 1, NULL, + ai_walk, 8, NULL +}; +mmove_t gladiator_move_walk = {FRAME_walk1, FRAME_walk16, gladiator_frames_walk, NULL}; + +void gladiator_walk (edict_t *self) +{ + self->monsterinfo.currentmove = &gladiator_move_walk; +} + + +mframe_t gladiator_frames_run [] = +{ + ai_run, 23, NULL, + ai_run, 14, NULL, + ai_run, 14, NULL, + ai_run, 21, NULL, + ai_run, 12, NULL, + ai_run, 13, NULL +}; +mmove_t gladiator_move_run = {FRAME_run1, FRAME_run6, gladiator_frames_run, NULL}; + +void gladiator_run (edict_t *self) +{ + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + self->monsterinfo.currentmove = &gladiator_move_stand; + else + self->monsterinfo.currentmove = &gladiator_move_run; +} + + +void GaldiatorMelee (edict_t *self) +{ + vec3_t aim; + + VectorSet (aim, MELEE_DISTANCE, self->mins[0], -4); + if (fire_hit (self, aim, (20 + (rand() %5)), 300)) + gi.sound (self, CHAN_AUTO, sound_cleaver_hit, 1, ATTN_NORM, 0); + else + gi.sound (self, CHAN_AUTO, sound_cleaver_miss, 1, ATTN_NORM, 0); +} + +mframe_t gladiator_frames_attack_melee [] = +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, gladiator_cleaver_swing, + ai_charge, 0, NULL, + ai_charge, 0, GaldiatorMelee, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, gladiator_cleaver_swing, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, GaldiatorMelee, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL +}; +mmove_t gladiator_move_attack_melee = {FRAME_melee1, FRAME_melee17, gladiator_frames_attack_melee, gladiator_run}; + +void gladiator_melee(edict_t *self) +{ + self->monsterinfo.currentmove = &gladiator_move_attack_melee; +} + + +void GladiatorGun (edict_t *self) +{ + vec3_t start; + vec3_t dir; + vec3_t forward, right; + + AngleVectors (self->s.angles, forward, right, NULL); + G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_GLADIATOR_RAILGUN_1], forward, right, start); + + // calc direction to where we targted + VectorSubtract (self->pos1, start, dir); + VectorNormalize (dir); + + monster_fire_railgun (self, start, dir, 50, 100, MZ2_GLADIATOR_RAILGUN_1); +} + +mframe_t gladiator_frames_attack_gun [] = +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, GladiatorGun, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL +}; +mmove_t gladiator_move_attack_gun = {FRAME_attack1, FRAME_attack9, gladiator_frames_attack_gun, gladiator_run}; + +void gladiator_attack(edict_t *self) +{ + float range; + vec3_t v; + + // a small safe zone + VectorSubtract (self->s.origin, self->enemy->s.origin, v); + range = VectorLength(v); + if (range <= (MELEE_DISTANCE + 32)) + return; + + // charge up the railgun + gi.sound (self, CHAN_WEAPON, sound_gun, 1, ATTN_NORM, 0); + VectorCopy (self->enemy->s.origin, self->pos1); //save for aiming the shot + self->pos1[2] += self->enemy->viewheight; + self->monsterinfo.currentmove = &gladiator_move_attack_gun; +} + + +mframe_t gladiator_frames_pain [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t gladiator_move_pain = {FRAME_pain1, FRAME_pain6, gladiator_frames_pain, gladiator_run}; + +mframe_t gladiator_frames_pain_air [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t gladiator_move_pain_air = {FRAME_painup1, FRAME_painup7, gladiator_frames_pain_air, gladiator_run}; + +void gladiator_pain (edict_t *self, edict_t *other, float kick, int damage) +{ + + if (self->health < (self->max_health / 2)) + self->s.skinnum = 1; + + if (level.time < self->pain_debounce_time) + { + if ((self->velocity[2] > 100) && (self->monsterinfo.currentmove == &gladiator_move_pain)) + self->monsterinfo.currentmove = &gladiator_move_pain_air; + return; + } + + self->pain_debounce_time = level.time + 3; + + if (random() < 0.5) + gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); + else + gi.sound (self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0); + + if (skill->value == 3) + return; // no pain anims in nightmare + + if (self->velocity[2] > 100) + self->monsterinfo.currentmove = &gladiator_move_pain_air; + else + self->monsterinfo.currentmove = &gladiator_move_pain; + +} + + +void gladiator_dead (edict_t *self) +{ + VectorSet (self->mins, -16, -16, -24); + VectorSet (self->maxs, 16, 16, -8); + self->movetype = MOVETYPE_TOSS; + self->svflags |= SVF_DEADMONSTER; + self->nextthink = 0; + gi.linkentity (self); +} + +mframe_t gladiator_frames_death [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t gladiator_move_death = {FRAME_death1, FRAME_death22, gladiator_frames_death, gladiator_dead}; + +void gladiator_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + int n; + +// check for gib + if (self->health <= self->gib_health) + { + gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0); + for (n= 0; n < 2; n++) + ThrowGib (self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC); + for (n= 0; n < 4; n++) + ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); + ThrowHead (self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC); + self->deadflag = DEAD_DEAD; + return; + } + + if (self->deadflag == DEAD_DEAD) + return; + +// regular death + gi.sound (self, CHAN_VOICE, sound_die, 1, ATTN_NORM, 0); + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_YES; + + self->monsterinfo.currentmove = &gladiator_move_death; +} + +//=========== +//PGM +qboolean gladiator_blocked (edict_t *self, float dist) +{ + if(blocked_checkshot (self, 0.25 + (0.05 * skill->value) )) + return true; + + if(blocked_checkplat (self, dist)) + return true; + + return false; +} +//PGM +//=========== + +/*QUAKED monster_gladiator (1 .5 0) (-32 -32 -24) (32 32 64) Ambush Trigger_Spawn Sight +*/ +void SP_monster_gladiator (edict_t *self) +{ + if (deathmatch->value) + { + G_FreeEdict (self); + return; + } + + + sound_pain1 = gi.soundindex ("gladiator/pain.wav"); + sound_pain2 = gi.soundindex ("gladiator/gldpain2.wav"); + sound_die = gi.soundindex ("gladiator/glddeth2.wav"); + sound_gun = gi.soundindex ("gladiator/railgun.wav"); + sound_cleaver_swing = gi.soundindex ("gladiator/melee1.wav"); + sound_cleaver_hit = gi.soundindex ("gladiator/melee2.wav"); + sound_cleaver_miss = gi.soundindex ("gladiator/melee3.wav"); + sound_idle = gi.soundindex ("gladiator/gldidle1.wav"); + sound_search = gi.soundindex ("gladiator/gldsrch1.wav"); + sound_sight = gi.soundindex ("gladiator/sight.wav"); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex ("models/monsters/gladiatr/tris.md2"); + VectorSet (self->mins, -32, -32, -24); + VectorSet (self->maxs, 32, 32, 64); + + self->health = 400; + self->gib_health = -175; + self->mass = 400; + + self->pain = gladiator_pain; + self->die = gladiator_die; + + self->monsterinfo.stand = gladiator_stand; + self->monsterinfo.walk = gladiator_walk; + self->monsterinfo.run = gladiator_run; + self->monsterinfo.dodge = NULL; + self->monsterinfo.attack = gladiator_attack; + self->monsterinfo.melee = gladiator_melee; + self->monsterinfo.sight = gladiator_sight; + self->monsterinfo.idle = gladiator_idle; + self->monsterinfo.search = gladiator_search; + self->monsterinfo.blocked = gladiator_blocked; // PGM + + gi.linkentity (self); + self->monsterinfo.currentmove = &gladiator_move_stand; + self->monsterinfo.scale = MODEL_SCALE; + + walkmonster_start (self); +} diff --git a/original/rogue/m_gladiator.h b/original/rogue/m_gladiator.h new file mode 100644 index 0000000..94fac63 --- /dev/null +++ b/original/rogue/m_gladiator.h @@ -0,0 +1,98 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// G:\quake2\baseq2\models/monsters/gladiatr + +// This file generated by ModelGen - Do NOT Modify + +#define FRAME_stand1 0 +#define FRAME_stand2 1 +#define FRAME_stand3 2 +#define FRAME_stand4 3 +#define FRAME_stand5 4 +#define FRAME_stand6 5 +#define FRAME_stand7 6 +#define FRAME_walk1 7 +#define FRAME_walk2 8 +#define FRAME_walk3 9 +#define FRAME_walk4 10 +#define FRAME_walk5 11 +#define FRAME_walk6 12 +#define FRAME_walk7 13 +#define FRAME_walk8 14 +#define FRAME_walk9 15 +#define FRAME_walk10 16 +#define FRAME_walk11 17 +#define FRAME_walk12 18 +#define FRAME_walk13 19 +#define FRAME_walk14 20 +#define FRAME_walk15 21 +#define FRAME_walk16 22 +#define FRAME_run1 23 +#define FRAME_run2 24 +#define FRAME_run3 25 +#define FRAME_run4 26 +#define FRAME_run5 27 +#define FRAME_run6 28 +#define FRAME_melee1 29 +#define FRAME_melee2 30 +#define FRAME_melee3 31 +#define FRAME_melee4 32 +#define FRAME_melee5 33 +#define FRAME_melee6 34 +#define FRAME_melee7 35 +#define FRAME_melee8 36 +#define FRAME_melee9 37 +#define FRAME_melee10 38 +#define FRAME_melee11 39 +#define FRAME_melee12 40 +#define FRAME_melee13 41 +#define FRAME_melee14 42 +#define FRAME_melee15 43 +#define FRAME_melee16 44 +#define FRAME_melee17 45 +#define FRAME_attack1 46 +#define FRAME_attack2 47 +#define FRAME_attack3 48 +#define FRAME_attack4 49 +#define FRAME_attack5 50 +#define FRAME_attack6 51 +#define FRAME_attack7 52 +#define FRAME_attack8 53 +#define FRAME_attack9 54 +#define FRAME_pain1 55 +#define FRAME_pain2 56 +#define FRAME_pain3 57 +#define FRAME_pain4 58 +#define FRAME_pain5 59 +#define FRAME_pain6 60 +#define FRAME_death1 61 +#define FRAME_death2 62 +#define FRAME_death3 63 +#define FRAME_death4 64 +#define FRAME_death5 65 +#define FRAME_death6 66 +#define FRAME_death7 67 +#define FRAME_death8 68 +#define FRAME_death9 69 +#define FRAME_death10 70 +#define FRAME_death11 71 +#define FRAME_death12 72 +#define FRAME_death13 73 +#define FRAME_death14 74 +#define FRAME_death15 75 +#define FRAME_death16 76 +#define FRAME_death17 77 +#define FRAME_death18 78 +#define FRAME_death19 79 +#define FRAME_death20 80 +#define FRAME_death21 81 +#define FRAME_death22 82 +#define FRAME_painup1 83 +#define FRAME_painup2 84 +#define FRAME_painup3 85 +#define FRAME_painup4 86 +#define FRAME_painup5 87 +#define FRAME_painup6 88 +#define FRAME_painup7 89 + +#define MODEL_SCALE 1.000000 diff --git a/original/rogue/m_gunner.c b/original/rogue/m_gunner.c new file mode 100644 index 0000000..90df49a --- /dev/null +++ b/original/rogue/m_gunner.c @@ -0,0 +1,1064 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +/* +============================================================================== + +GUNNER + +============================================================================== +*/ + +#include "g_local.h" +#include "m_gunner.h" + + +static int sound_pain; +static int sound_pain2; +static int sound_death; +static int sound_idle; +static int sound_open; +static int sound_search; +static int sound_sight; + +void gunner_idlesound (edict_t *self) +{ + gi.sound (self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0); +} + +void gunner_sight (edict_t *self, edict_t *other) +{ + gi.sound (self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); +} + +void gunner_search (edict_t *self) +{ + gi.sound (self, CHAN_VOICE, sound_search, 1, ATTN_NORM, 0); +} + + +qboolean visible (edict_t *self, edict_t *other); +void GunnerGrenade (edict_t *self); +void GunnerFire (edict_t *self); +void gunner_fire_chain(edict_t *self); +void gunner_refire_chain(edict_t *self); + + +void gunner_stand (edict_t *self); + +mframe_t gunner_frames_fidget [] = +{ + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, gunner_idlesound, + ai_stand, 0, NULL, + + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL +}; +mmove_t gunner_move_fidget = {FRAME_stand31, FRAME_stand70, gunner_frames_fidget, gunner_stand}; + +void gunner_fidget (edict_t *self) +{ + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + return; + if (random() <= 0.05) + self->monsterinfo.currentmove = &gunner_move_fidget; +} + +mframe_t gunner_frames_stand [] = +{ + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, gunner_fidget, + + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, gunner_fidget, + + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, gunner_fidget +}; +mmove_t gunner_move_stand = {FRAME_stand01, FRAME_stand30, gunner_frames_stand, NULL}; + +void gunner_stand (edict_t *self) +{ + self->monsterinfo.currentmove = &gunner_move_stand; +} + + +mframe_t gunner_frames_walk [] = +{ + ai_walk, 0, NULL, + ai_walk, 3, NULL, + ai_walk, 4, NULL, + ai_walk, 5, NULL, + ai_walk, 7, NULL, + ai_walk, 2, NULL, + ai_walk, 6, NULL, + ai_walk, 4, NULL, + ai_walk, 2, NULL, + ai_walk, 7, NULL, + ai_walk, 5, NULL, + ai_walk, 7, NULL, + ai_walk, 4, NULL +}; +mmove_t gunner_move_walk = {FRAME_walk07, FRAME_walk19, gunner_frames_walk, NULL}; + +void gunner_walk (edict_t *self) +{ + self->monsterinfo.currentmove = &gunner_move_walk; +} + +mframe_t gunner_frames_run [] = +{ + ai_run, 26, NULL, + ai_run, 9, NULL, + ai_run, 9, NULL, + ai_run, 9, monster_done_dodge, + ai_run, 15, NULL, + ai_run, 10, NULL, + ai_run, 13, NULL, + ai_run, 6, NULL +}; + +mmove_t gunner_move_run = {FRAME_run01, FRAME_run08, gunner_frames_run, NULL}; + +void gunner_run (edict_t *self) +{ + monster_done_dodge(self); + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + self->monsterinfo.currentmove = &gunner_move_stand; + else + self->monsterinfo.currentmove = &gunner_move_run; +} + +mframe_t gunner_frames_runandshoot [] = +{ + ai_run, 32, NULL, + ai_run, 15, NULL, + ai_run, 10, NULL, + ai_run, 18, NULL, + ai_run, 8, NULL, + ai_run, 20, NULL +}; + +mmove_t gunner_move_runandshoot = {FRAME_runs01, FRAME_runs06, gunner_frames_runandshoot, NULL}; + +void gunner_runandshoot (edict_t *self) +{ + self->monsterinfo.currentmove = &gunner_move_runandshoot; +} + +mframe_t gunner_frames_pain3 [] = +{ + ai_move, -3, NULL, + ai_move, 1, NULL, + ai_move, 1, NULL, + ai_move, 0, NULL, + ai_move, 1, NULL +}; +mmove_t gunner_move_pain3 = {FRAME_pain301, FRAME_pain305, gunner_frames_pain3, gunner_run}; + +mframe_t gunner_frames_pain2 [] = +{ + ai_move, -2, NULL, + ai_move, 11, NULL, + ai_move, 6, NULL, + ai_move, 2, NULL, + ai_move, -1, NULL, + ai_move, -7, NULL, + ai_move, -2, NULL, + ai_move, -7, NULL +}; +mmove_t gunner_move_pain2 = {FRAME_pain201, FRAME_pain208, gunner_frames_pain2, gunner_run}; + +mframe_t gunner_frames_pain1 [] = +{ + ai_move, 2, NULL, + ai_move, 0, NULL, + ai_move, -5, NULL, + ai_move, 3, NULL, + ai_move, -1, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 1, NULL, + ai_move, 1, NULL, + ai_move, 2, NULL, + ai_move, 1, NULL, + ai_move, 0, NULL, + ai_move, -2, NULL, + ai_move, -2, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t gunner_move_pain1 = {FRAME_pain101, FRAME_pain118, gunner_frames_pain1, gunner_run}; + +void gunner_pain (edict_t *self, edict_t *other, float kick, int damage) +{ + if (self->health < (self->max_health / 2)) + self->s.skinnum = 1; + + monster_done_dodge (self); + + if (!self->groundentity) + { +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("gunner: pain avoided due to no ground\n"); + return; + } + + if (level.time < self->pain_debounce_time) + return; + + self->pain_debounce_time = level.time + 3; + + if (rand()&1) + gi.sound (self, CHAN_VOICE, sound_pain, 1, ATTN_NORM, 0); + else + gi.sound (self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0); + + if (skill->value == 3) + return; // no pain anims in nightmare + + if (damage <= 10) + self->monsterinfo.currentmove = &gunner_move_pain3; + else if (damage <= 25) + self->monsterinfo.currentmove = &gunner_move_pain2; + else + self->monsterinfo.currentmove = &gunner_move_pain1; + + self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING; + + // PMM - clear duck flag + if (self->monsterinfo.aiflags & AI_DUCKED) + monster_duck_up(self); +} + +void gunner_dead (edict_t *self) +{ + VectorSet (self->mins, -16, -16, -24); + VectorSet (self->maxs, 16, 16, -8); + self->movetype = MOVETYPE_TOSS; + self->svflags |= SVF_DEADMONSTER; + self->nextthink = 0; + gi.linkentity (self); +} + +mframe_t gunner_frames_death [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, -7, NULL, + ai_move, -3, NULL, + ai_move, -5, NULL, + ai_move, 8, NULL, + ai_move, 6, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t gunner_move_death = {FRAME_death01, FRAME_death11, gunner_frames_death, gunner_dead}; + +void gunner_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + int n; + +// check for gib + if (self->health <= self->gib_health) + { + gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0); + for (n= 0; n < 2; n++) + ThrowGib (self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC); + for (n= 0; n < 4; n++) + ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); + ThrowHead (self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC); + self->deadflag = DEAD_DEAD; + return; + } + + if (self->deadflag == DEAD_DEAD) + return; + +// regular death + gi.sound (self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0); + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_YES; + self->monsterinfo.currentmove = &gunner_move_death; +} + +// PMM - changed to duck code for new dodge + +// +// this is specific to the gunner, leave it be +// +void gunner_duck_down (edict_t *self) +{ +// if (self->monsterinfo.aiflags & AI_DUCKED) +// return; + self->monsterinfo.aiflags |= AI_DUCKED; + if (skill->value >= 2) + { + if (random() > 0.5) + GunnerGrenade (self); + } + +// self->maxs[2] -= 32; + self->maxs[2] = self->monsterinfo.base_height - 32; + self->takedamage = DAMAGE_YES; + if (self->monsterinfo.duck_wait_time < level.time) + self->monsterinfo.duck_wait_time = level.time + 1; + gi.linkentity (self); +} + +mframe_t gunner_frames_duck [] = +{ + ai_move, 1, gunner_duck_down, + ai_move, 1, NULL, + ai_move, 1, monster_duck_hold, + ai_move, 0, NULL, + ai_move, -1, NULL, + ai_move, -1, NULL, + ai_move, 0, monster_duck_up, + ai_move, -1, NULL +}; +mmove_t gunner_move_duck = {FRAME_duck01, FRAME_duck08, gunner_frames_duck, gunner_run}; + +// PMM - gunner dodge moved below so I know about attack sequences + +void gunner_opengun (edict_t *self) +{ + gi.sound (self, CHAN_VOICE, sound_open, 1, ATTN_IDLE, 0); +} + +void GunnerFire (edict_t *self) +{ + vec3_t start; + vec3_t forward, right; + vec3_t target; + vec3_t aim; + int flash_number; + + if(!self->enemy || !self->enemy->inuse) //PGM + return; //PGM + + flash_number = MZ2_GUNNER_MACHINEGUN_1 + (self->s.frame - FRAME_attak216); + + AngleVectors (self->s.angles, forward, right, NULL); + G_ProjectSource (self->s.origin, monster_flash_offset[flash_number], forward, right, start); + + // project enemy back a bit and target there + VectorCopy (self->enemy->s.origin, target); + VectorMA (target, -0.2, self->enemy->velocity, target); + target[2] += self->enemy->viewheight; + + VectorSubtract (target, start, aim); + VectorNormalize (aim); + monster_fire_bullet (self, start, aim, 3, 4, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, flash_number); +} + +qboolean gunner_grenade_check(edict_t *self) +{ + vec3_t start; + vec3_t forward, right; + trace_t tr; + vec3_t target, dir; + + if(!self->enemy) + return false; + + // if the player is above my head, use machinegun. + + // check for flag telling us that we're blindfiring + if (self->monsterinfo.aiflags & AI_MANUAL_STEERING) + { + if (self->s.origin[2]+self->viewheight < self->monsterinfo.blind_fire_target[2]) + { +// if(g_showlogic && g_showlogic->value) +// gi.dprintf("blind_fire_target is above my head, using machinegun\n"); + return false; + } + } + else if(self->absmax[2] <= self->enemy->absmin[2]) + { +// if(g_showlogic && g_showlogic->value) +// gi.dprintf("player is above my head, using machinegun\n"); + return false; + } + + // check to see that we can trace to the player before we start + // tossing grenades around. + AngleVectors (self->s.angles, forward, right, NULL); + G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_GUNNER_GRENADE_1], forward, right, start); + + // pmm - check for blindfire flag + if (self->monsterinfo.aiflags & AI_MANUAL_STEERING) + VectorCopy (self->monsterinfo.blind_fire_target, target); + else + VectorCopy (self->enemy->s.origin, target); + + // see if we're too close + VectorSubtract (self->s.origin, target, dir); + + if (VectorLength(dir) < 100) + return false; + + tr = gi.trace(start, vec3_origin, vec3_origin, target, self, MASK_SHOT); + if(tr.ent == self->enemy || tr.fraction == 1) + return true; + +// if(g_showlogic && g_showlogic->value) +// gi.dprintf("can't trace to target, using machinegun\n"); + return false; +} + +void GunnerGrenade (edict_t *self) +{ + vec3_t start; + vec3_t forward, right, up; + vec3_t aim; + int flash_number; + float spread; + float pitch; + // PMM + vec3_t target; + qboolean blindfire; + + if(!self->enemy || !self->enemy->inuse) //PGM + return; //PGM + + // pmm + if (self->monsterinfo.aiflags & AI_MANUAL_STEERING) + blindfire = true; + + if (self->s.frame == FRAME_attak105) + { + spread = .02; + flash_number = MZ2_GUNNER_GRENADE_1; + } + else if (self->s.frame == FRAME_attak108) + { + spread = .05; + flash_number = MZ2_GUNNER_GRENADE_2; + } + else if (self->s.frame == FRAME_attak111) + { + spread = .08; + flash_number = MZ2_GUNNER_GRENADE_3; + } + else // (self->s.frame == FRAME_attak114) + { + self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING; + spread = .11; + flash_number = MZ2_GUNNER_GRENADE_4; + } + + // pmm + // if we're shooting blind and we still can't see our enemy + if ((blindfire) && (!visible(self, self->enemy))) + { + // and we have a valid blind_fire_target + if (VectorCompare (self->monsterinfo.blind_fire_target, vec3_origin)) + return; + +// gi.dprintf ("blind_fire_target = %s\n", vtos (self->monsterinfo.blind_fire_target)); +// gi.dprintf ("GunnerGrenade: ideal yaw is %f\n", self->ideal_yaw); + VectorCopy (self->monsterinfo.blind_fire_target, target); + } + else + VectorCopy (self->s.origin, target); + // pmm + + AngleVectors (self->s.angles, forward, right, up); //PGM + G_ProjectSource (self->s.origin, monster_flash_offset[flash_number], forward, right, start); + +//PGM + if(self->enemy) + { + float dist; + +// VectorSubtract(self->enemy->s.origin, self->s.origin, aim); + VectorSubtract(target, self->s.origin, aim); + dist = VectorLength(aim); + + // aim up if they're on the same level as me and far away. + if((dist > 512) && (aim[2] < 64) && (aim[2] > -64)) + { + aim[2] += (dist - 512); + } + + VectorNormalize (aim); + pitch = aim[2]; + if(pitch > 0.4) + { + pitch = 0.4; + } + else if(pitch < -0.5) + pitch = -0.5; + } +//PGM + + //FIXME : do a spread -225 -75 75 225 degrees around forward +// VectorCopy (forward, aim); + VectorMA (forward, spread, right, aim); + VectorMA (aim, pitch, up, aim); + + monster_fire_grenade (self, start, aim, 50, 600, flash_number); +} + +mframe_t gunner_frames_attack_chain [] = +{ + /* + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + */ + ai_charge, 0, gunner_opengun, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL +}; +mmove_t gunner_move_attack_chain = {FRAME_attak209, FRAME_attak215, gunner_frames_attack_chain, gunner_fire_chain}; + +mframe_t gunner_frames_fire_chain [] = +{ + ai_charge, 0, GunnerFire, + ai_charge, 0, GunnerFire, + ai_charge, 0, GunnerFire, + ai_charge, 0, GunnerFire, + ai_charge, 0, GunnerFire, + ai_charge, 0, GunnerFire, + ai_charge, 0, GunnerFire, + ai_charge, 0, GunnerFire +}; +mmove_t gunner_move_fire_chain = {FRAME_attak216, FRAME_attak223, gunner_frames_fire_chain, gunner_refire_chain}; + +mframe_t gunner_frames_endfire_chain [] = +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL +}; +mmove_t gunner_move_endfire_chain = {FRAME_attak224, FRAME_attak230, gunner_frames_endfire_chain, gunner_run}; + +void gunner_blind_check (edict_t *self) +{ + vec3_t aim; + + if (self->monsterinfo.aiflags & AI_MANUAL_STEERING) + { + VectorSubtract(self->monsterinfo.blind_fire_target, self->s.origin, aim); + self->ideal_yaw = vectoyaw(aim); + +// gi.dprintf ("blind_fire_target = %s\n", vtos (self->monsterinfo.blind_fire_target)); +// gi.dprintf ("gunner_attack: ideal yaw is %f\n", self->ideal_yaw); + } +} + +mframe_t gunner_frames_attack_grenade [] = +{ + ai_charge, 0, gunner_blind_check, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, GunnerGrenade, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, GunnerGrenade, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, GunnerGrenade, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, GunnerGrenade, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL +}; +mmove_t gunner_move_attack_grenade = {FRAME_attak101, FRAME_attak121, gunner_frames_attack_grenade, gunner_run}; + +void gunner_attack(edict_t *self) +{ + float chance, r; + + monster_done_dodge(self); + + // PMM + if (self->monsterinfo.attack_state == AS_BLIND) + { + // setup shot probabilities + if (self->monsterinfo.blind_fire_delay < 1.0) + chance = 1.0; + else if (self->monsterinfo.blind_fire_delay < 7.5) + chance = 0.4; + else + chance = 0.1; + + r = random(); + + // minimum of 2 seconds, plus 0-3, after the shots are done + self->monsterinfo.blind_fire_delay += 2.1 + 2.0 + random()*3.0; + + // don't shoot at the origin + if (VectorCompare (self->monsterinfo.blind_fire_target, vec3_origin)) + return; + + // don't shoot if the dice say not to + if (r > chance) + { +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("blindfire - NO SHOT\n"); + return; + } + + // turn on manual steering to signal both manual steering and blindfire + self->monsterinfo.aiflags |= AI_MANUAL_STEERING; + if (gunner_grenade_check(self)) + { + // if the check passes, go for the attack + self->monsterinfo.currentmove = &gunner_move_attack_grenade; + self->monsterinfo.attack_finished = level.time + 2*random(); + } + // pmm - should this be active? +// else +// self->monsterinfo.currentmove = &gunner_move_attack_chain; +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("blind grenade check failed, doing nothing\n"); + + // turn off blindfire flag + self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING; + return; + } + // pmm + + // PGM - gunner needs to use his chaingun if he's being attacked by a tesla. + if ((range (self, self->enemy) == RANGE_MELEE) || self->bad_area) + { + self->monsterinfo.currentmove = &gunner_move_attack_chain; + } + else + { + if (random() <= 0.5 && gunner_grenade_check(self)) + self->monsterinfo.currentmove = &gunner_move_attack_grenade; + else + self->monsterinfo.currentmove = &gunner_move_attack_chain; + } +} + +void gunner_fire_chain(edict_t *self) +{ + self->monsterinfo.currentmove = &gunner_move_fire_chain; +} + +void gunner_refire_chain(edict_t *self) +{ + if (self->enemy->health > 0) + if ( visible (self, self->enemy) ) + if (random() <= 0.5) + { + self->monsterinfo.currentmove = &gunner_move_fire_chain; + return; + } + self->monsterinfo.currentmove = &gunner_move_endfire_chain; +} +/* +void gunner_dodge (edict_t *self, edict_t *attacker, float eta, trace_t *tr) +{ +// original quake2 dodge code + + if (random() > 0.25) + return; + + if (!self->enemy) + self->enemy = attacker; + + self->monsterinfo.currentmove = &gunner_move_duck; + +//=========== +//PMM - rogue rewrite of gunner dodge code. + float r; + float height; + int shooting = 0; + + if (!self->enemy) + { + self->enemy = attacker; + FoundTarget (self); + } + + // PMM - don't bother if it's going to hit anyway; fix for weird in-your-face etas (I was + // seeing numbers like 13 and 14) + if ((eta < 0.1) || (eta > 5)) + return; + + r = random(); + if (r > (0.25*((skill->value)+1))) + return; + + if ((self->monsterinfo.currentmove == &gunner_move_attack_chain) || + (self->monsterinfo.currentmove == &gunner_move_fire_chain) || + (self->monsterinfo.currentmove == &gunner_move_attack_grenade) + ) + { + shooting = 1; + } + if (self->monsterinfo.aiflags & AI_DODGING) + { + height = self->absmax[2]; + } + else + { + height = self->absmax[2]-32-1; // the -1 is because the absmax is s.origin + maxs + 1 + } + + // check to see if it makes sense to duck + if (tr->endpos[2] <= height) + { + vec3_t right, diff; + if (shooting) + { + self->monsterinfo.attack_state = AS_SLIDING; + return; + } + AngleVectors (self->s.angles, NULL, right, NULL); + VectorSubtract (tr->endpos, self->s.origin, diff); + if (DotProduct (right, diff) < 0) + { + self->monsterinfo.lefty = 1; + } + // if it doesn't sense to duck, try to strafe away + monster_done_dodge (self); + self->monsterinfo.currentmove = &gunner_move_run; + self->monsterinfo.attack_state = AS_SLIDING; + return; + } + + if (skill->value == 0) + { + self->monsterinfo.currentmove = &gunner_move_duck; + // PMM - stupid dodge + self->monsterinfo.duck_wait_time = level.time + eta + 1; + self->monsterinfo.aiflags |= AI_DODGING; + return; + } + + if (!shooting) + { + self->monsterinfo.currentmove = &gunner_move_duck; + self->monsterinfo.duck_wait_time = level.time + eta + (0.1 * (3 - skill->value)); + self->monsterinfo.aiflags |= AI_DODGING; + } + return; +//PMM +//=========== +} +*/ +//=========== +//PGM +void gunner_jump_now (edict_t *self) +{ + vec3_t forward,up; + + monster_jump_start (self); + + AngleVectors (self->s.angles, forward, NULL, up); + VectorMA(self->velocity, 100, forward, self->velocity); + VectorMA(self->velocity, 300, up, self->velocity); +} + +void gunner_jump2_now (edict_t *self) +{ + vec3_t forward,up; + + monster_jump_start (self); + + AngleVectors (self->s.angles, forward, NULL, up); + VectorMA(self->velocity, 150, forward, self->velocity); + VectorMA(self->velocity, 400, up, self->velocity); +} + +void gunner_jump_wait_land (edict_t *self) +{ + if(self->groundentity == NULL) + { + self->monsterinfo.nextframe = self->s.frame; + + if(monster_jump_finished (self)) + self->monsterinfo.nextframe = self->s.frame + 1; + } + else + self->monsterinfo.nextframe = self->s.frame + 1; +} + +mframe_t gunner_frames_jump [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, gunner_jump_now, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, gunner_jump_wait_land, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t gunner_move_jump = { FRAME_jump01, FRAME_jump10, gunner_frames_jump, gunner_run }; + +mframe_t gunner_frames_jump2 [] = +{ + ai_move, -8, NULL, + ai_move, -4, NULL, + ai_move, -4, NULL, + ai_move, 0, gunner_jump_now, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, gunner_jump_wait_land, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t gunner_move_jump2 = { FRAME_jump01, FRAME_jump10, gunner_frames_jump2, gunner_run }; + +void gunner_jump (edict_t *self) +{ + if(!self->enemy) + return; + + monster_done_dodge (self); + + if(self->enemy->s.origin[2] > self->s.origin[2]) + self->monsterinfo.currentmove = &gunner_move_jump2; + else + self->monsterinfo.currentmove = &gunner_move_jump; +} + +//=========== +//PGM +qboolean gunner_blocked (edict_t *self, float dist) +{ + if(blocked_checkshot (self, 0.25 + (0.05 * skill->value) )) + return true; + + if(blocked_checkplat (self, dist)) + return true; + + if(blocked_checkjump (self, dist, 192, 40)) + { + gunner_jump(self); + return true; + } + + return false; +} +//PGM +//=========== + +// PMM - new duck code +void gunner_duck (edict_t *self, float eta) +{ + if ((self->monsterinfo.currentmove == &gunner_move_jump2) || + (self->monsterinfo.currentmove == &gunner_move_jump)) + { + return; + } + + if ((self->monsterinfo.currentmove == &gunner_move_attack_chain) || + (self->monsterinfo.currentmove == &gunner_move_fire_chain) || + (self->monsterinfo.currentmove == &gunner_move_attack_grenade) + ) + { + // if we're shooting, and not on easy, don't dodge + if (skill->value) + { + self->monsterinfo.aiflags &= ~AI_DUCKED; + return; + } + } + + if (skill->value == 0) + // PMM - stupid dodge + self->monsterinfo.duck_wait_time = level.time + eta + 1; + else + self->monsterinfo.duck_wait_time = level.time + eta + (0.1 * (3 - skill->value)); + + // has to be done immediately otherwise he can get stuck + gunner_duck_down(self); + + self->monsterinfo.nextframe = FRAME_duck01; + self->monsterinfo.currentmove = &gunner_move_duck; + return; +} + +void gunner_sidestep (edict_t *self) +{ + if ((self->monsterinfo.currentmove == &gunner_move_jump2) || + (self->monsterinfo.currentmove == &gunner_move_jump)) + { + return; + } + + if ((self->monsterinfo.currentmove == &gunner_move_attack_chain) || + (self->monsterinfo.currentmove == &gunner_move_fire_chain) || + (self->monsterinfo.currentmove == &gunner_move_attack_grenade) + ) + { + // if we're shooting, and not on easy, don't dodge + if (skill->value) + { + self->monsterinfo.aiflags &= ~AI_DODGING; + return; + } + } + + if (self->monsterinfo.currentmove != &gunner_move_run) + self->monsterinfo.currentmove = &gunner_move_run; +} + + +/*QUAKED monster_gunner (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight +*/ +void SP_monster_gunner (edict_t *self) +{ + if (deathmatch->value) + { + G_FreeEdict (self); + return; + } + + sound_death = gi.soundindex ("gunner/death1.wav"); + sound_pain = gi.soundindex ("gunner/gunpain2.wav"); + sound_pain2 = gi.soundindex ("gunner/gunpain1.wav"); + sound_idle = gi.soundindex ("gunner/gunidle1.wav"); + sound_open = gi.soundindex ("gunner/gunatck1.wav"); + sound_search = gi.soundindex ("gunner/gunsrch1.wav"); + sound_sight = gi.soundindex ("gunner/sight1.wav"); + + gi.soundindex ("gunner/gunatck2.wav"); + gi.soundindex ("gunner/gunatck3.wav"); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex ("models/monsters/gunner/tris.md2"); + VectorSet (self->mins, -16, -16, -24); + VectorSet (self->maxs, 16, 16, 32); + + self->health = 175; + self->gib_health = -70; + self->mass = 200; + + self->pain = gunner_pain; + self->die = gunner_die; + + self->monsterinfo.stand = gunner_stand; + self->monsterinfo.walk = gunner_walk; + self->monsterinfo.run = gunner_run; + // pmm + self->monsterinfo.dodge = M_MonsterDodge; + self->monsterinfo.duck = gunner_duck; + self->monsterinfo.unduck = monster_duck_up; + self->monsterinfo.sidestep = gunner_sidestep; +// self->monsterinfo.dodge = gunner_dodge; + // pmm + self->monsterinfo.attack = gunner_attack; + self->monsterinfo.melee = NULL; + self->monsterinfo.sight = gunner_sight; + self->monsterinfo.search = gunner_search; + self->monsterinfo.blocked = gunner_blocked; //PGM + + gi.linkentity (self); + + self->monsterinfo.currentmove = &gunner_move_stand; + self->monsterinfo.scale = MODEL_SCALE; + + // PMM + self->monsterinfo.blindfire = true; + + walkmonster_start (self); +} diff --git a/original/rogue/m_gunner.h b/original/rogue/m_gunner.h new file mode 100644 index 0000000..4952d0e --- /dev/null +++ b/original/rogue/m_gunner.h @@ -0,0 +1,227 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// G:\quake2\baseq2\models/gunner + +// This file generated by ModelGen - Do NOT Modify + +#define FRAME_stand01 0 +#define FRAME_stand02 1 +#define FRAME_stand03 2 +#define FRAME_stand04 3 +#define FRAME_stand05 4 +#define FRAME_stand06 5 +#define FRAME_stand07 6 +#define FRAME_stand08 7 +#define FRAME_stand09 8 +#define FRAME_stand10 9 +#define FRAME_stand11 10 +#define FRAME_stand12 11 +#define FRAME_stand13 12 +#define FRAME_stand14 13 +#define FRAME_stand15 14 +#define FRAME_stand16 15 +#define FRAME_stand17 16 +#define FRAME_stand18 17 +#define FRAME_stand19 18 +#define FRAME_stand20 19 +#define FRAME_stand21 20 +#define FRAME_stand22 21 +#define FRAME_stand23 22 +#define FRAME_stand24 23 +#define FRAME_stand25 24 +#define FRAME_stand26 25 +#define FRAME_stand27 26 +#define FRAME_stand28 27 +#define FRAME_stand29 28 +#define FRAME_stand30 29 +#define FRAME_stand31 30 +#define FRAME_stand32 31 +#define FRAME_stand33 32 +#define FRAME_stand34 33 +#define FRAME_stand35 34 +#define FRAME_stand36 35 +#define FRAME_stand37 36 +#define FRAME_stand38 37 +#define FRAME_stand39 38 +#define FRAME_stand40 39 +#define FRAME_stand41 40 +#define FRAME_stand42 41 +#define FRAME_stand43 42 +#define FRAME_stand44 43 +#define FRAME_stand45 44 +#define FRAME_stand46 45 +#define FRAME_stand47 46 +#define FRAME_stand48 47 +#define FRAME_stand49 48 +#define FRAME_stand50 49 +#define FRAME_stand51 50 +#define FRAME_stand52 51 +#define FRAME_stand53 52 +#define FRAME_stand54 53 +#define FRAME_stand55 54 +#define FRAME_stand56 55 +#define FRAME_stand57 56 +#define FRAME_stand58 57 +#define FRAME_stand59 58 +#define FRAME_stand60 59 +#define FRAME_stand61 60 +#define FRAME_stand62 61 +#define FRAME_stand63 62 +#define FRAME_stand64 63 +#define FRAME_stand65 64 +#define FRAME_stand66 65 +#define FRAME_stand67 66 +#define FRAME_stand68 67 +#define FRAME_stand69 68 +#define FRAME_stand70 69 +#define FRAME_walk01 70 +#define FRAME_walk02 71 +#define FRAME_walk03 72 +#define FRAME_walk04 73 +#define FRAME_walk05 74 +#define FRAME_walk06 75 +#define FRAME_walk07 76 +#define FRAME_walk08 77 +#define FRAME_walk09 78 +#define FRAME_walk10 79 +#define FRAME_walk11 80 +#define FRAME_walk12 81 +#define FRAME_walk13 82 +#define FRAME_walk14 83 +#define FRAME_walk15 84 +#define FRAME_walk16 85 +#define FRAME_walk17 86 +#define FRAME_walk18 87 +#define FRAME_walk19 88 +#define FRAME_walk20 89 +#define FRAME_walk21 90 +#define FRAME_walk22 91 +#define FRAME_walk23 92 +#define FRAME_walk24 93 +#define FRAME_run01 94 +#define FRAME_run02 95 +#define FRAME_run03 96 +#define FRAME_run04 97 +#define FRAME_run05 98 +#define FRAME_run06 99 +#define FRAME_run07 100 +#define FRAME_run08 101 +#define FRAME_runs01 102 +#define FRAME_runs02 103 +#define FRAME_runs03 104 +#define FRAME_runs04 105 +#define FRAME_runs05 106 +#define FRAME_runs06 107 +#define FRAME_attak101 108 +#define FRAME_attak102 109 +#define FRAME_attak103 110 +#define FRAME_attak104 111 +#define FRAME_attak105 112 +#define FRAME_attak106 113 +#define FRAME_attak107 114 +#define FRAME_attak108 115 +#define FRAME_attak109 116 +#define FRAME_attak110 117 +#define FRAME_attak111 118 +#define FRAME_attak112 119 +#define FRAME_attak113 120 +#define FRAME_attak114 121 +#define FRAME_attak115 122 +#define FRAME_attak116 123 +#define FRAME_attak117 124 +#define FRAME_attak118 125 +#define FRAME_attak119 126 +#define FRAME_attak120 127 +#define FRAME_attak121 128 +#define FRAME_attak201 129 +#define FRAME_attak202 130 +#define FRAME_attak203 131 +#define FRAME_attak204 132 +#define FRAME_attak205 133 +#define FRAME_attak206 134 +#define FRAME_attak207 135 +#define FRAME_attak208 136 +#define FRAME_attak209 137 +#define FRAME_attak210 138 +#define FRAME_attak211 139 +#define FRAME_attak212 140 +#define FRAME_attak213 141 +#define FRAME_attak214 142 +#define FRAME_attak215 143 +#define FRAME_attak216 144 +#define FRAME_attak217 145 +#define FRAME_attak218 146 +#define FRAME_attak219 147 +#define FRAME_attak220 148 +#define FRAME_attak221 149 +#define FRAME_attak222 150 +#define FRAME_attak223 151 +#define FRAME_attak224 152 +#define FRAME_attak225 153 +#define FRAME_attak226 154 +#define FRAME_attak227 155 +#define FRAME_attak228 156 +#define FRAME_attak229 157 +#define FRAME_attak230 158 +#define FRAME_pain101 159 +#define FRAME_pain102 160 +#define FRAME_pain103 161 +#define FRAME_pain104 162 +#define FRAME_pain105 163 +#define FRAME_pain106 164 +#define FRAME_pain107 165 +#define FRAME_pain108 166 +#define FRAME_pain109 167 +#define FRAME_pain110 168 +#define FRAME_pain111 169 +#define FRAME_pain112 170 +#define FRAME_pain113 171 +#define FRAME_pain114 172 +#define FRAME_pain115 173 +#define FRAME_pain116 174 +#define FRAME_pain117 175 +#define FRAME_pain118 176 +#define FRAME_pain201 177 +#define FRAME_pain202 178 +#define FRAME_pain203 179 +#define FRAME_pain204 180 +#define FRAME_pain205 181 +#define FRAME_pain206 182 +#define FRAME_pain207 183 +#define FRAME_pain208 184 +#define FRAME_pain301 185 +#define FRAME_pain302 186 +#define FRAME_pain303 187 +#define FRAME_pain304 188 +#define FRAME_pain305 189 +#define FRAME_death01 190 +#define FRAME_death02 191 +#define FRAME_death03 192 +#define FRAME_death04 193 +#define FRAME_death05 194 +#define FRAME_death06 195 +#define FRAME_death07 196 +#define FRAME_death08 197 +#define FRAME_death09 198 +#define FRAME_death10 199 +#define FRAME_death11 200 +#define FRAME_duck01 201 +#define FRAME_duck02 202 +#define FRAME_duck03 203 +#define FRAME_duck04 204 +#define FRAME_duck05 205 +#define FRAME_duck06 206 +#define FRAME_duck07 207 +#define FRAME_duck08 208 +#define FRAME_jump01 209 +#define FRAME_jump02 210 +#define FRAME_jump03 211 +#define FRAME_jump04 212 +#define FRAME_jump05 213 +#define FRAME_jump06 214 +#define FRAME_jump07 215 +#define FRAME_jump08 216 +#define FRAME_jump09 217 +#define FRAME_jump10 218 + +#define MODEL_SCALE 1.150000 diff --git a/original/rogue/m_hover.c b/original/rogue/m_hover.c new file mode 100644 index 0000000..e3d3552 --- /dev/null +++ b/original/rogue/m_hover.c @@ -0,0 +1,792 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +/* +============================================================================== + +hover + +============================================================================== +*/ + +#include "g_local.h" +#include "m_hover.h" + +qboolean visible (edict_t *self, edict_t *other); + + +static int sound_pain1; +static int sound_pain2; +static int sound_death1; +static int sound_death2; +static int sound_sight; +static int sound_search1; +static int sound_search2; + +// daedalus sounds +static int daed_sound_pain1; +static int daed_sound_pain2; +static int daed_sound_death1; +static int daed_sound_death2; +static int daed_sound_sight; +static int daed_sound_search1; +static int daed_sound_search2; + + +void hover_sight (edict_t *self, edict_t *other) +{ + // PMM - daedalus sounds + if (self->mass < 225) + gi.sound (self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); + else + gi.sound (self, CHAN_VOICE, daed_sound_sight, 1, ATTN_NORM, 0); +} + +void hover_search (edict_t *self) +{ + // PMM - daedalus sounds + if (self->mass < 225) + { + if (random() < 0.5) + gi.sound (self, CHAN_VOICE, sound_search1, 1, ATTN_NORM, 0); + else + gi.sound (self, CHAN_VOICE, sound_search2, 1, ATTN_NORM, 0); + } + else + { + if (random() < 0.5) + gi.sound (self, CHAN_VOICE, daed_sound_search1, 1, ATTN_NORM, 0); + else + gi.sound (self, CHAN_VOICE, daed_sound_search2, 1, ATTN_NORM, 0); + } +} + + +void hover_run (edict_t *self); +void hover_stand (edict_t *self); +void hover_dead (edict_t *self); +void hover_attack (edict_t *self); +void hover_reattack (edict_t *self); +void hover_fire_blaster (edict_t *self); +void hover_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point); + +mframe_t hover_frames_stand [] = +{ + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL +}; +mmove_t hover_move_stand = {FRAME_stand01, FRAME_stand30, hover_frames_stand, NULL}; +/* +mframe_t hover_frames_stop1 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t hover_move_stop1 = {FRAME_stop101, FRAME_stop109, hover_frames_stop1, NULL}; + +mframe_t hover_frames_stop2 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t hover_move_stop2 = {FRAME_stop201, FRAME_stop208, hover_frames_stop2, NULL}; +*/ +/* +mframe_t hover_frames_takeoff [] = +{ + ai_move, 0, NULL, + ai_move, -2, NULL, + ai_move, 5, NULL, + ai_move, -1, NULL, + ai_move, 1, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, -1, NULL, + ai_move, -1, NULL, + ai_move, -1, NULL, + ai_move, 0, NULL, + ai_move, 2, NULL, + ai_move, 2, NULL, + ai_move, 1, NULL, + ai_move, 1, NULL, + ai_move, -6, NULL, + ai_move, -9, NULL, + ai_move, 1, NULL, + ai_move, 0, NULL, + ai_move, 2, NULL, + ai_move, 2, NULL, + ai_move, 1, NULL, + ai_move, 1, NULL, + ai_move, 1, NULL, + ai_move, 2, NULL, + ai_move, 0, NULL, + ai_move, 2, NULL, + ai_move, 3, NULL, + ai_move, 2, NULL, + ai_move, 0, NULL +}; +mmove_t hover_move_takeoff = {FRAME_takeof01, FRAME_takeof30, hover_frames_takeoff, NULL}; +*/ +mframe_t hover_frames_pain3 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t hover_move_pain3 = {FRAME_pain301, FRAME_pain309, hover_frames_pain3, hover_run}; + +mframe_t hover_frames_pain2 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t hover_move_pain2 = {FRAME_pain201, FRAME_pain212, hover_frames_pain2, hover_run}; + +mframe_t hover_frames_pain1 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 2, NULL, + ai_move, -8, NULL, + ai_move, -4, NULL, + ai_move, -6, NULL, + ai_move, -4, NULL, + ai_move, -3, NULL, + ai_move, 1, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 3, NULL, + ai_move, 1, NULL, + ai_move, 0, NULL, + ai_move, 2, NULL, + ai_move, 3, NULL, + ai_move, 2, NULL, + ai_move, 7, NULL, + ai_move, 1, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 2, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 5, NULL, + ai_move, 3, NULL, + ai_move, 4, NULL +}; +mmove_t hover_move_pain1 = {FRAME_pain101, FRAME_pain128, hover_frames_pain1, hover_run}; + +/* +mframe_t hover_frames_land [] = +{ + ai_move, 0, NULL +}; +mmove_t hover_move_land = {FRAME_land01, FRAME_land01, hover_frames_land, NULL}; +*/ +/* +mframe_t hover_frames_forward [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t hover_move_forward = {FRAME_forwrd01, FRAME_forwrd35, hover_frames_forward, NULL}; +*/ +mframe_t hover_frames_walk [] = +{ + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL +}; +mmove_t hover_move_walk = {FRAME_forwrd01, FRAME_forwrd35, hover_frames_walk, NULL}; + +mframe_t hover_frames_run [] = +{ + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL +}; +mmove_t hover_move_run = {FRAME_forwrd01, FRAME_forwrd35, hover_frames_run, NULL}; + +mframe_t hover_frames_death1 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, -10,NULL, + ai_move, 3, NULL, + ai_move, 5, NULL, + ai_move, 4, NULL, + ai_move, 7, NULL +}; +mmove_t hover_move_death1 = {FRAME_death101, FRAME_death111, hover_frames_death1, hover_dead}; +/* +mframe_t hover_frames_backward [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t hover_move_backward = {FRAME_backwd01, FRAME_backwd24, hover_frames_backward, NULL}; +*/ +mframe_t hover_frames_start_attack [] = +{ + ai_charge, 1, NULL, + ai_charge, 1, NULL, + ai_charge, 1, NULL +}; +mmove_t hover_move_start_attack = {FRAME_attak101, FRAME_attak103, hover_frames_start_attack, hover_attack}; + +mframe_t hover_frames_attack1 [] = +{ + ai_charge, -10, hover_fire_blaster, + ai_charge, -10, hover_fire_blaster, + ai_charge, 0, hover_reattack, +}; +mmove_t hover_move_attack1 = {FRAME_attak104, FRAME_attak106, hover_frames_attack1, NULL}; + + +mframe_t hover_frames_end_attack [] = +{ + ai_charge, 1, NULL, + ai_charge, 1, NULL +}; +mmove_t hover_move_end_attack = {FRAME_attak107, FRAME_attak108, hover_frames_end_attack, hover_run}; + +/* PMM - circle strafing code */ + +mframe_t hover_frames_start_attack2 [] = +{ + ai_charge, 15, NULL, + ai_charge, 15, NULL, + ai_charge, 15, NULL +}; +mmove_t hover_move_start_attack2 = {FRAME_attak101, FRAME_attak103, hover_frames_start_attack2, hover_attack}; + +mframe_t hover_frames_attack2 [] = +{ + ai_charge, 10, hover_fire_blaster, + ai_charge, 10, hover_fire_blaster, + ai_charge, 10, hover_reattack, +}; +mmove_t hover_move_attack2 = {FRAME_attak104, FRAME_attak106, hover_frames_attack2, NULL}; + + +mframe_t hover_frames_end_attack2 [] = +{ + ai_charge, 15, NULL, + ai_charge, 15, NULL +}; +mmove_t hover_move_end_attack2 = {FRAME_attak107, FRAME_attak108, hover_frames_end_attack2, hover_run}; + +// end of circle strafe + + +void hover_reattack (edict_t *self) +{ + if (self->enemy->health > 0 ) + if (visible (self, self->enemy) ) + if (random() <= 0.6) + { + if (self->monsterinfo.attack_state == AS_STRAIGHT) + { + self->monsterinfo.currentmove = &hover_move_attack1; + return; + } + else if (self->monsterinfo.attack_state == AS_SLIDING) + { + self->monsterinfo.currentmove = &hover_move_attack2; + return; + } + else + gi.dprintf ("hover_reattack: unexpected state %d\n", self->monsterinfo.attack_state); + } + self->monsterinfo.currentmove = &hover_move_end_attack; +} + + +void hover_fire_blaster (edict_t *self) +{ + vec3_t start; + vec3_t forward, right; + vec3_t end; + vec3_t dir; + int effect; + + if(!self->enemy || !self->enemy->inuse) //PGM + return; //PGM + + if (self->s.frame == FRAME_attak104) + effect = EF_HYPERBLASTER; + else + effect = 0; + + AngleVectors (self->s.angles, forward, right, NULL); + G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_HOVER_BLASTER_1], forward, right, start); + + VectorCopy (self->enemy->s.origin, end); + end[2] += self->enemy->viewheight; + VectorSubtract (end, start, dir); + +//PGM - daedalus fires blaster2 + if(self->mass < 200) + monster_fire_blaster (self, start, dir, 1, 1000, MZ2_HOVER_BLASTER_1, effect); + else + monster_fire_blaster2 (self, start, dir, 1, 1000, MZ2_DAEDALUS_BLASTER, EF_BLASTER); + // fixme - different muzzle flash +//PGM +} + + +void hover_stand (edict_t *self) +{ + self->monsterinfo.currentmove = &hover_move_stand; +} + +void hover_run (edict_t *self) +{ + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + self->monsterinfo.currentmove = &hover_move_stand; + else + self->monsterinfo.currentmove = &hover_move_run; +} + +void hover_walk (edict_t *self) +{ + self->monsterinfo.currentmove = &hover_move_walk; +} + +void hover_start_attack (edict_t *self) +{ + self->monsterinfo.currentmove = &hover_move_start_attack; +} + +void hover_attack(edict_t *self) +{ + float chance; +/* if (random() <= 0.5) + self->monsterinfo.currentmove = &flyer_move_attack1; + else */ + // 0% chance of circle in easy + // 50% chance in normal + // 75% chance in hard + // 86.67% chance in nightmare + if (!skill->value) + chance = 0; + else + chance = 1.0 - (0.5/(float)(skill->value)); + + if (self->mass > 150) // the daedalus strafes more + chance += 0.1; + + if (random() > chance) + { + self->monsterinfo.currentmove = &hover_move_attack1; + self->monsterinfo.attack_state = AS_STRAIGHT; + } + else // circle strafe + { + if (random () <= 0.5) // switch directions + self->monsterinfo.lefty = 1 - self->monsterinfo.lefty; + self->monsterinfo.currentmove = &hover_move_attack2; + self->monsterinfo.attack_state = AS_SLIDING; + } +} + + +void hover_pain (edict_t *self, edict_t *other, float kick, int damage) +{ + if (self->health < (self->max_health / 2)) + self->s.skinnum |= 1; // PGM support for skins 2 & 3. + + if (level.time < self->pain_debounce_time) + return; + + self->pain_debounce_time = level.time + 3; + + if (skill->value == 3) + return; // no pain anims in nightmare + + if (damage <= 25) + { + if (random() < 0.5) + { + // PMM - daedalus sounds + if (self->mass < 225) + gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); + else + gi.sound (self, CHAN_VOICE, daed_sound_pain1, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &hover_move_pain3; + } + else + { + // PMM - daedalus sounds + if (self->mass < 225) + gi.sound (self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0); + else + gi.sound (self, CHAN_VOICE, daed_sound_pain2, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &hover_move_pain2; + } + } + else + { +//==== +//PGM pain sequence is WAY too long + if (random() < (0.45 - (0.1 * skill->value))) + { + // PMM - daedalus sounds + if (self->mass < 225) + gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); + else + gi.sound (self, CHAN_VOICE, daed_sound_pain1, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &hover_move_pain1; + } + else + { + // PMM - daedalus sounds + if (self->mass < 225) + gi.sound (self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0); + else + gi.sound (self, CHAN_VOICE, daed_sound_pain2, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &hover_move_pain2; + } +//PGM +//==== + } +} + +void hover_deadthink (edict_t *self) +{ + if (!self->groundentity && level.time < self->timestamp) + { + self->nextthink = level.time + FRAMETIME; + return; + } + BecomeExplosion1(self); +} + +void hover_dead (edict_t *self) +{ + VectorSet (self->mins, -16, -16, -24); + VectorSet (self->maxs, 16, 16, -8); + self->movetype = MOVETYPE_TOSS; + self->think = hover_deadthink; + self->nextthink = level.time + FRAMETIME; + self->timestamp = level.time + 15; + gi.linkentity (self); +} + +void hover_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + int n; + + self->s.effects = 0; + self->monsterinfo.power_armor_type = POWER_ARMOR_NONE; + +// check for gib + if (self->health <= self->gib_health) + { + gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0); + for (n= 0; n < 2; n++) + ThrowGib (self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC); + for (n= 0; n < 2; n++) + ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); + ThrowHead (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); + self->deadflag = DEAD_DEAD; + return; + } + + if (self->deadflag == DEAD_DEAD) + return; + +// regular death + // PMM - daedalus sounds + if (self->mass < 225) + { + if (random() < 0.5) + gi.sound (self, CHAN_VOICE, sound_death1, 1, ATTN_NORM, 0); + else + gi.sound (self, CHAN_VOICE, sound_death2, 1, ATTN_NORM, 0); + } + else + { + if (random() < 0.5) + gi.sound (self, CHAN_VOICE, daed_sound_death1, 1, ATTN_NORM, 0); + else + gi.sound (self, CHAN_VOICE, daed_sound_death2, 1, ATTN_NORM, 0); + } + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_YES; + self->monsterinfo.currentmove = &hover_move_death1; +} + +//=========== +//PGM +qboolean hover_blocked (edict_t *self, float dist) +{ + if(blocked_checkshot (self, 0.25 + (0.05 * skill->value) )) + return true; + + return false; +} +//PGM +//=========== + +/*QUAKED monster_hover (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight +*/ +/*QUAKED monster_daedalus (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight +This is the improved icarus monster. +*/ +void SP_monster_hover (edict_t *self) +{ + if (deathmatch->value) + { + G_FreeEdict (self); + return; + } + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex("models/monsters/hover/tris.md2"); + VectorSet (self->mins, -24, -24, -24); + VectorSet (self->maxs, 24, 24, 32); + + self->health = 240; + self->gib_health = -100; + self->mass = 150; + + self->pain = hover_pain; + self->die = hover_die; + + self->monsterinfo.stand = hover_stand; + self->monsterinfo.walk = hover_walk; + self->monsterinfo.run = hover_run; +// self->monsterinfo.dodge = hover_dodge; + self->monsterinfo.attack = hover_start_attack; + self->monsterinfo.sight = hover_sight; + self->monsterinfo.search = hover_search; + self->monsterinfo.blocked = hover_blocked; // PGM + +//PGM + if (strcmp(self->classname, "monster_daedalus") == 0) + { + self->health = 450; + self->mass = 225; + self->yaw_speed = 25; + self->monsterinfo.power_armor_type = POWER_ARMOR_SCREEN; + self->monsterinfo.power_armor_power = 100; + // PMM - daedalus sounds + self->s.sound = gi.soundindex ("daedalus/daedidle1.wav"); + daed_sound_pain1 = gi.soundindex ("daedalus/daedpain1.wav"); + daed_sound_pain2 = gi.soundindex ("daedalus/daedpain2.wav"); + daed_sound_death1 = gi.soundindex ("daedalus/daeddeth1.wav"); + daed_sound_death2 = gi.soundindex ("daedalus/daeddeth2.wav"); + daed_sound_sight = gi.soundindex ("daedalus/daedsght1.wav"); + daed_sound_search1 = gi.soundindex ("daedalus/daedsrch1.wav"); + daed_sound_search2 = gi.soundindex ("daedalus/daedsrch2.wav"); + gi.soundindex ("tank/tnkatck3.wav"); + // pmm + } + else + { + sound_pain1 = gi.soundindex ("hover/hovpain1.wav"); + sound_pain2 = gi.soundindex ("hover/hovpain2.wav"); + sound_death1 = gi.soundindex ("hover/hovdeth1.wav"); + sound_death2 = gi.soundindex ("hover/hovdeth2.wav"); + sound_sight = gi.soundindex ("hover/hovsght1.wav"); + sound_search1 = gi.soundindex ("hover/hovsrch1.wav"); + sound_search2 = gi.soundindex ("hover/hovsrch2.wav"); + gi.soundindex ("hover/hovatck1.wav"); + + self->s.sound = gi.soundindex ("hover/hovidle1.wav"); + } +//PGM + + gi.linkentity (self); + + self->monsterinfo.currentmove = &hover_move_stand; + self->monsterinfo.scale = MODEL_SCALE; + + flymonster_start (self); + +//PGM + if (strcmp(self->classname, "monster_daedalus") == 0) + self->s.skinnum = 2; +//PGM + +} diff --git a/original/rogue/m_hover.h b/original/rogue/m_hover.h new file mode 100644 index 0000000..c1754f0 --- /dev/null +++ b/original/rogue/m_hover.h @@ -0,0 +1,213 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// G:\quake2\baseq2\models/monsters/hover + +// This file generated by ModelGen - Do NOT Modify + +#define FRAME_stand01 0 +#define FRAME_stand02 1 +#define FRAME_stand03 2 +#define FRAME_stand04 3 +#define FRAME_stand05 4 +#define FRAME_stand06 5 +#define FRAME_stand07 6 +#define FRAME_stand08 7 +#define FRAME_stand09 8 +#define FRAME_stand10 9 +#define FRAME_stand11 10 +#define FRAME_stand12 11 +#define FRAME_stand13 12 +#define FRAME_stand14 13 +#define FRAME_stand15 14 +#define FRAME_stand16 15 +#define FRAME_stand17 16 +#define FRAME_stand18 17 +#define FRAME_stand19 18 +#define FRAME_stand20 19 +#define FRAME_stand21 20 +#define FRAME_stand22 21 +#define FRAME_stand23 22 +#define FRAME_stand24 23 +#define FRAME_stand25 24 +#define FRAME_stand26 25 +#define FRAME_stand27 26 +#define FRAME_stand28 27 +#define FRAME_stand29 28 +#define FRAME_stand30 29 +#define FRAME_forwrd01 30 +#define FRAME_forwrd02 31 +#define FRAME_forwrd03 32 +#define FRAME_forwrd04 33 +#define FRAME_forwrd05 34 +#define FRAME_forwrd06 35 +#define FRAME_forwrd07 36 +#define FRAME_forwrd08 37 +#define FRAME_forwrd09 38 +#define FRAME_forwrd10 39 +#define FRAME_forwrd11 40 +#define FRAME_forwrd12 41 +#define FRAME_forwrd13 42 +#define FRAME_forwrd14 43 +#define FRAME_forwrd15 44 +#define FRAME_forwrd16 45 +#define FRAME_forwrd17 46 +#define FRAME_forwrd18 47 +#define FRAME_forwrd19 48 +#define FRAME_forwrd20 49 +#define FRAME_forwrd21 50 +#define FRAME_forwrd22 51 +#define FRAME_forwrd23 52 +#define FRAME_forwrd24 53 +#define FRAME_forwrd25 54 +#define FRAME_forwrd26 55 +#define FRAME_forwrd27 56 +#define FRAME_forwrd28 57 +#define FRAME_forwrd29 58 +#define FRAME_forwrd30 59 +#define FRAME_forwrd31 60 +#define FRAME_forwrd32 61 +#define FRAME_forwrd33 62 +#define FRAME_forwrd34 63 +#define FRAME_forwrd35 64 +#define FRAME_stop101 65 +#define FRAME_stop102 66 +#define FRAME_stop103 67 +#define FRAME_stop104 68 +#define FRAME_stop105 69 +#define FRAME_stop106 70 +#define FRAME_stop107 71 +#define FRAME_stop108 72 +#define FRAME_stop109 73 +#define FRAME_stop201 74 +#define FRAME_stop202 75 +#define FRAME_stop203 76 +#define FRAME_stop204 77 +#define FRAME_stop205 78 +#define FRAME_stop206 79 +#define FRAME_stop207 80 +#define FRAME_stop208 81 +#define FRAME_takeof01 82 +#define FRAME_takeof02 83 +#define FRAME_takeof03 84 +#define FRAME_takeof04 85 +#define FRAME_takeof05 86 +#define FRAME_takeof06 87 +#define FRAME_takeof07 88 +#define FRAME_takeof08 89 +#define FRAME_takeof09 90 +#define FRAME_takeof10 91 +#define FRAME_takeof11 92 +#define FRAME_takeof12 93 +#define FRAME_takeof13 94 +#define FRAME_takeof14 95 +#define FRAME_takeof15 96 +#define FRAME_takeof16 97 +#define FRAME_takeof17 98 +#define FRAME_takeof18 99 +#define FRAME_takeof19 100 +#define FRAME_takeof20 101 +#define FRAME_takeof21 102 +#define FRAME_takeof22 103 +#define FRAME_takeof23 104 +#define FRAME_takeof24 105 +#define FRAME_takeof25 106 +#define FRAME_takeof26 107 +#define FRAME_takeof27 108 +#define FRAME_takeof28 109 +#define FRAME_takeof29 110 +#define FRAME_takeof30 111 +#define FRAME_land01 112 +#define FRAME_pain101 113 +#define FRAME_pain102 114 +#define FRAME_pain103 115 +#define FRAME_pain104 116 +#define FRAME_pain105 117 +#define FRAME_pain106 118 +#define FRAME_pain107 119 +#define FRAME_pain108 120 +#define FRAME_pain109 121 +#define FRAME_pain110 122 +#define FRAME_pain111 123 +#define FRAME_pain112 124 +#define FRAME_pain113 125 +#define FRAME_pain114 126 +#define FRAME_pain115 127 +#define FRAME_pain116 128 +#define FRAME_pain117 129 +#define FRAME_pain118 130 +#define FRAME_pain119 131 +#define FRAME_pain120 132 +#define FRAME_pain121 133 +#define FRAME_pain122 134 +#define FRAME_pain123 135 +#define FRAME_pain124 136 +#define FRAME_pain125 137 +#define FRAME_pain126 138 +#define FRAME_pain127 139 +#define FRAME_pain128 140 +#define FRAME_pain201 141 +#define FRAME_pain202 142 +#define FRAME_pain203 143 +#define FRAME_pain204 144 +#define FRAME_pain205 145 +#define FRAME_pain206 146 +#define FRAME_pain207 147 +#define FRAME_pain208 148 +#define FRAME_pain209 149 +#define FRAME_pain210 150 +#define FRAME_pain211 151 +#define FRAME_pain212 152 +#define FRAME_pain301 153 +#define FRAME_pain302 154 +#define FRAME_pain303 155 +#define FRAME_pain304 156 +#define FRAME_pain305 157 +#define FRAME_pain306 158 +#define FRAME_pain307 159 +#define FRAME_pain308 160 +#define FRAME_pain309 161 +#define FRAME_death101 162 +#define FRAME_death102 163 +#define FRAME_death103 164 +#define FRAME_death104 165 +#define FRAME_death105 166 +#define FRAME_death106 167 +#define FRAME_death107 168 +#define FRAME_death108 169 +#define FRAME_death109 170 +#define FRAME_death110 171 +#define FRAME_death111 172 +#define FRAME_backwd01 173 +#define FRAME_backwd02 174 +#define FRAME_backwd03 175 +#define FRAME_backwd04 176 +#define FRAME_backwd05 177 +#define FRAME_backwd06 178 +#define FRAME_backwd07 179 +#define FRAME_backwd08 180 +#define FRAME_backwd09 181 +#define FRAME_backwd10 182 +#define FRAME_backwd11 183 +#define FRAME_backwd12 184 +#define FRAME_backwd13 185 +#define FRAME_backwd14 186 +#define FRAME_backwd15 187 +#define FRAME_backwd16 188 +#define FRAME_backwd17 189 +#define FRAME_backwd18 190 +#define FRAME_backwd19 191 +#define FRAME_backwd20 192 +#define FRAME_backwd21 193 +#define FRAME_backwd22 194 +#define FRAME_backwd23 195 +#define FRAME_backwd24 196 +#define FRAME_attak101 197 +#define FRAME_attak102 198 +#define FRAME_attak103 199 +#define FRAME_attak104 200 +#define FRAME_attak105 201 +#define FRAME_attak106 202 +#define FRAME_attak107 203 +#define FRAME_attak108 204 + +#define MODEL_SCALE 1.000000 diff --git a/original/rogue/m_infantry.c b/original/rogue/m_infantry.c new file mode 100644 index 0000000..9cbd215 --- /dev/null +++ b/original/rogue/m_infantry.c @@ -0,0 +1,824 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +/* +============================================================================== + +INFANTRY + +============================================================================== +*/ + +#include "g_local.h" +#include "m_infantry.h" + +void InfantryMachineGun (edict_t *self); + + +static int sound_pain1; +static int sound_pain2; +static int sound_die1; +static int sound_die2; + +static int sound_gunshot; +static int sound_weapon_cock; +static int sound_punch_swing; +static int sound_punch_hit; +static int sound_sight; +static int sound_search; +static int sound_idle; + +mframe_t infantry_frames_stand [] = +{ + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL +}; +mmove_t infantry_move_stand = {FRAME_stand50, FRAME_stand71, infantry_frames_stand, NULL}; + +void infantry_stand (edict_t *self) +{ + self->monsterinfo.currentmove = &infantry_move_stand; +} + + +mframe_t infantry_frames_fidget [] = +{ + ai_stand, 1, NULL, + ai_stand, 0, NULL, + ai_stand, 1, NULL, + ai_stand, 3, NULL, + ai_stand, 6, NULL, + ai_stand, 3, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 1, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 1, NULL, + ai_stand, 0, NULL, + ai_stand, -1, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 1, NULL, + ai_stand, 0, NULL, + ai_stand, -2, NULL, + ai_stand, 1, NULL, + ai_stand, 1, NULL, + ai_stand, 1, NULL, + ai_stand, -1, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, -1, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, -1, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 1, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, -1, NULL, + ai_stand, -1, NULL, + ai_stand, 0, NULL, + ai_stand, -3, NULL, + ai_stand, -2, NULL, + ai_stand, -3, NULL, + ai_stand, -3, NULL, + ai_stand, -2, NULL +}; +mmove_t infantry_move_fidget = {FRAME_stand01, FRAME_stand49, infantry_frames_fidget, infantry_stand}; + +void infantry_fidget (edict_t *self) +{ + self->monsterinfo.currentmove = &infantry_move_fidget; + gi.sound (self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0); +} + +mframe_t infantry_frames_walk [] = +{ + ai_walk, 5, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 5, NULL, + ai_walk, 4, NULL, + ai_walk, 5, NULL, + ai_walk, 6, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 5, NULL +}; +mmove_t infantry_move_walk = {FRAME_walk03, FRAME_walk14, infantry_frames_walk, NULL}; + +void infantry_walk (edict_t *self) +{ + self->monsterinfo.currentmove = &infantry_move_walk; +} + +mframe_t infantry_frames_run [] = +{ + ai_run, 10, NULL, + ai_run, 20, NULL, + ai_run, 5, NULL, + ai_run, 7, monster_done_dodge, + ai_run, 30, NULL, + ai_run, 35, NULL, + ai_run, 2, NULL, + ai_run, 6, NULL +}; +mmove_t infantry_move_run = {FRAME_run01, FRAME_run08, infantry_frames_run, NULL}; + +void infantry_run (edict_t *self) +{ + monster_done_dodge (self); + + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + self->monsterinfo.currentmove = &infantry_move_stand; + else + self->monsterinfo.currentmove = &infantry_move_run; +} + + +mframe_t infantry_frames_pain1 [] = +{ + ai_move, -3, NULL, + ai_move, -2, NULL, + ai_move, -1, NULL, + ai_move, -2, NULL, + ai_move, -1, NULL, + ai_move, 1, NULL, + ai_move, -1, NULL, + ai_move, 1, NULL, + ai_move, 6, NULL, + ai_move, 2, NULL +}; +mmove_t infantry_move_pain1 = {FRAME_pain101, FRAME_pain110, infantry_frames_pain1, infantry_run}; + +mframe_t infantry_frames_pain2 [] = +{ + ai_move, -3, NULL, + ai_move, -3, NULL, + ai_move, 0, NULL, + ai_move, -1, NULL, + ai_move, -2, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 2, NULL, + ai_move, 5, NULL, + ai_move, 2, NULL +}; +mmove_t infantry_move_pain2 = {FRAME_pain201, FRAME_pain210, infantry_frames_pain2, infantry_run}; + +void infantry_pain (edict_t *self, edict_t *other, float kick, int damage) +{ + int n; + + if (self->health < (self->max_health / 2)) + self->s.skinnum = 1; + + if (!self->groundentity) + { +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("infantry: pain avoided due to no ground\n"); + return; + } + + monster_done_dodge (self); + + if (level.time < self->pain_debounce_time) + return; + + self->pain_debounce_time = level.time + 3; + + if (skill->value == 3) + return; // no pain anims in nightmare + + n = rand() % 2; + if (n == 0) + { + self->monsterinfo.currentmove = &infantry_move_pain1; + gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); + } + else + { + self->monsterinfo.currentmove = &infantry_move_pain2; + gi.sound (self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0); + } + + // PMM - clear duck flag + if (self->monsterinfo.aiflags & AI_DUCKED) + monster_duck_up(self); +} + + +vec3_t aimangles[] = +{ + 0.0, 5.0, 0.0, + 10.0, 15.0, 0.0, + 20.0, 25.0, 0.0, + 25.0, 35.0, 0.0, + 30.0, 40.0, 0.0, + 30.0, 45.0, 0.0, + 25.0, 50.0, 0.0, + 20.0, 40.0, 0.0, + 15.0, 35.0, 0.0, + 40.0, 35.0, 0.0, + 70.0, 35.0, 0.0, + 90.0, 35.0, 0.0 +}; + +void InfantryMachineGun (edict_t *self) +{ + vec3_t start, target; + vec3_t forward, right; + vec3_t vec; + int flash_number; + + if(!self->enemy || !self->enemy->inuse) //PGM + return; //PGM + + // pmm - new attack start frame + if (self->s.frame == FRAME_attak104) + { + flash_number = MZ2_INFANTRY_MACHINEGUN_1; + AngleVectors (self->s.angles, forward, right, NULL); + G_ProjectSource (self->s.origin, monster_flash_offset[flash_number], forward, right, start); + + if (self->enemy) + { + VectorMA (self->enemy->s.origin, -0.2, self->enemy->velocity, target); + target[2] += self->enemy->viewheight; + VectorSubtract (target, start, forward); + VectorNormalize (forward); + } + else + { + AngleVectors (self->s.angles, forward, right, NULL); + } + } + else + { + flash_number = MZ2_INFANTRY_MACHINEGUN_2 + (self->s.frame - FRAME_death211); + + AngleVectors (self->s.angles, forward, right, NULL); + G_ProjectSource (self->s.origin, monster_flash_offset[flash_number], forward, right, start); + + VectorSubtract (self->s.angles, aimangles[flash_number-MZ2_INFANTRY_MACHINEGUN_2], vec); + AngleVectors (vec, forward, NULL, NULL); + } + + monster_fire_bullet (self, start, forward, 3, 4, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, flash_number); +} + +void infantry_sight (edict_t *self, edict_t *other) +{ + gi.sound (self, CHAN_BODY, sound_sight, 1, ATTN_NORM, 0); +} + +void infantry_dead (edict_t *self) +{ + VectorSet (self->mins, -16, -16, -24); + VectorSet (self->maxs, 16, 16, -8); + self->movetype = MOVETYPE_TOSS; + self->svflags |= SVF_DEADMONSTER; + gi.linkentity (self); + + M_FlyCheck (self); +} + +mframe_t infantry_frames_death1 [] = +{ + ai_move, -4, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, -1, NULL, + ai_move, -4, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, -1, NULL, + ai_move, 3, NULL, + ai_move, 1, NULL, + ai_move, 1, NULL, + ai_move, -2, NULL, + ai_move, 2, NULL, + ai_move, 2, NULL, + ai_move, 9, NULL, + ai_move, 9, NULL, + ai_move, 5, NULL, + ai_move, -3, NULL, + ai_move, -3, NULL +}; +mmove_t infantry_move_death1 = {FRAME_death101, FRAME_death120, infantry_frames_death1, infantry_dead}; + +// Off with his head +mframe_t infantry_frames_death2 [] = +{ + ai_move, 0, NULL, + ai_move, 1, NULL, + ai_move, 5, NULL, + ai_move, -1, NULL, + ai_move, 0, NULL, + ai_move, 1, NULL, + ai_move, 1, NULL, + ai_move, 4, NULL, + ai_move, 3, NULL, + ai_move, 0, NULL, + ai_move, -2, InfantryMachineGun, + ai_move, -2, InfantryMachineGun, + ai_move, -3, InfantryMachineGun, + ai_move, -1, InfantryMachineGun, + ai_move, -2, InfantryMachineGun, + ai_move, 0, InfantryMachineGun, + ai_move, 2, InfantryMachineGun, + ai_move, 2, InfantryMachineGun, + ai_move, 3, InfantryMachineGun, + ai_move, -10, InfantryMachineGun, + ai_move, -7, InfantryMachineGun, + ai_move, -8, InfantryMachineGun, + ai_move, -6, NULL, + ai_move, 4, NULL, + ai_move, 0, NULL +}; +mmove_t infantry_move_death2 = {FRAME_death201, FRAME_death225, infantry_frames_death2, infantry_dead}; + +mframe_t infantry_frames_death3 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, -6, NULL, + ai_move, -11, NULL, + ai_move, -3, NULL, + ai_move, -11, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t infantry_move_death3 = {FRAME_death301, FRAME_death309, infantry_frames_death3, infantry_dead}; + + +void infantry_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + int n; + +// check for gib + if (self->health <= self->gib_health) + { + gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0); + for (n= 0; n < 2; n++) + ThrowGib (self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC); + for (n= 0; n < 4; n++) + ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); + ThrowHead (self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC); + self->deadflag = DEAD_DEAD; + return; + } + + if (self->deadflag == DEAD_DEAD) + return; + +// regular death + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_YES; + + n = rand() % 3; + if (n == 0) + { + self->monsterinfo.currentmove = &infantry_move_death1; + gi.sound (self, CHAN_VOICE, sound_die2, 1, ATTN_NORM, 0); + } + else if (n == 1) + { + self->monsterinfo.currentmove = &infantry_move_death2; + gi.sound (self, CHAN_VOICE, sound_die1, 1, ATTN_NORM, 0); + } + else + { + self->monsterinfo.currentmove = &infantry_move_death3; + gi.sound (self, CHAN_VOICE, sound_die2, 1, ATTN_NORM, 0); + } +} + +mframe_t infantry_frames_duck [] = +{ + ai_move, -2, monster_duck_down, + ai_move, -5, monster_duck_hold, + ai_move, 3, NULL, + ai_move, 4, monster_duck_up, + ai_move, 0, NULL +}; +mmove_t infantry_move_duck = {FRAME_duck01, FRAME_duck05, infantry_frames_duck, infantry_run}; + +// PMM - dodge code moved below so I can see the attack frames + + +void infantry_cock_gun (edict_t *self) +{ + // pmm .. code that was here no longer needed + gi.sound (self, CHAN_WEAPON, sound_weapon_cock, 1, ATTN_NORM, 0); +} + +void infantry_fire (edict_t *self) +{ + InfantryMachineGun (self); + if (level.time >= self->monsterinfo.pausetime) + self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; + else + self->monsterinfo.aiflags |= AI_HOLD_FRAME; +} + +// this is here instead of cock_gun + +void infantry_fire_prep (edict_t *self) +{ + int n; + n = (rand() & 15) + 3 + 1; + self->monsterinfo.pausetime = level.time + n*FRAMETIME; +} + +// pmm +// frames reordered, tweaked for new frames + +mframe_t infantry_frames_attack1 [] = +{ + ai_charge, -3, NULL, //101 + ai_charge, -2, NULL, //102 + ai_charge, -1, infantry_fire_prep, //103 + ai_charge, 5, infantry_fire, //104 + ai_charge, 1, NULL, //105 + ai_charge, -3, NULL, //106 + ai_charge, -2, NULL, //107 + ai_charge, 2, infantry_cock_gun, //108 + ai_charge, 1, NULL, //109 + ai_charge, 1, NULL, //110 + ai_charge, -1, NULL, //111 + ai_charge, 0, NULL, //112 + ai_charge, -1, NULL, //113 + ai_charge, -1, NULL, //114 + ai_charge, 4, NULL //115 +}; +mmove_t infantry_move_attack1 = {FRAME_attak101, FRAME_attak115, infantry_frames_attack1, infantry_run}; + + +void infantry_swing (edict_t *self) +{ + gi.sound (self, CHAN_WEAPON, sound_punch_swing, 1, ATTN_NORM, 0); +} + +void infantry_smack (edict_t *self) +{ + vec3_t aim; + + VectorSet (aim, MELEE_DISTANCE, 0, 0); + if (fire_hit (self, aim, (5 + (rand() % 5)), 50)) + gi.sound (self, CHAN_WEAPON, sound_punch_hit, 1, ATTN_NORM, 0); +} + +mframe_t infantry_frames_attack2 [] = +{ + ai_charge, 3, NULL, + ai_charge, 6, NULL, + ai_charge, 0, infantry_swing, + ai_charge, 8, NULL, + ai_charge, 5, NULL, + ai_charge, 8, infantry_smack, + ai_charge, 6, NULL, + ai_charge, 3, NULL, +}; +mmove_t infantry_move_attack2 = {FRAME_attak201, FRAME_attak208, infantry_frames_attack2, infantry_run}; + +void infantry_attack(edict_t *self) +{ + monster_done_dodge (self); + + if (range (self, self->enemy) == RANGE_MELEE) + self->monsterinfo.currentmove = &infantry_move_attack2; + else + self->monsterinfo.currentmove = &infantry_move_attack1; +} + +//=========== +//PGM +void infantry_jump_now (edict_t *self) +{ + vec3_t forward,up; + + monster_jump_start (self); + + AngleVectors (self->s.angles, forward, NULL, up); + VectorMA(self->velocity, 100, forward, self->velocity); + VectorMA(self->velocity, 300, up, self->velocity); +} + +void infantry_jump2_now (edict_t *self) +{ + vec3_t forward,up; + + monster_jump_start (self); + + AngleVectors (self->s.angles, forward, NULL, up); + VectorMA(self->velocity, 150, forward, self->velocity); + VectorMA(self->velocity, 400, up, self->velocity); +} + +void infantry_jump_wait_land (edict_t *self) +{ + if(self->groundentity == NULL) + { + self->monsterinfo.nextframe = self->s.frame; + + if(monster_jump_finished (self)) + self->monsterinfo.nextframe = self->s.frame + 1; + } + else + self->monsterinfo.nextframe = self->s.frame + 1; +} + +mframe_t infantry_frames_jump [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, infantry_jump_now, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, infantry_jump_wait_land, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t infantry_move_jump = { FRAME_jump01, FRAME_jump10, infantry_frames_jump, infantry_run }; + +mframe_t infantry_frames_jump2 [] = +{ + ai_move, -8, NULL, + ai_move, -4, NULL, + ai_move, -4, NULL, + ai_move, 0, infantry_jump_now, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, infantry_jump_wait_land, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t infantry_move_jump2 = { FRAME_jump01, FRAME_jump10, infantry_frames_jump2, infantry_run }; + +void infantry_jump (edict_t *self) +{ + if(!self->enemy) + return; + + monster_done_dodge(self); + + if(self->enemy->s.origin[2] > self->s.origin[2]) + self->monsterinfo.currentmove = &infantry_move_jump2; + else + self->monsterinfo.currentmove = &infantry_move_jump; +} + +qboolean infantry_blocked (edict_t *self, float dist) +{ + if(blocked_checkshot (self, 0.25 + (0.05 * skill->value) )) + return true; + + if(blocked_checkjump (self, dist, 192, 40)) + { + infantry_jump(self); + return true; + } + + if(blocked_checkplat (self, dist)) + return true; + + return false; +} +//PGM +//=========== +/* +void infantry_dodge (edict_t *self, edict_t *attacker, float eta, trace_t *tr) +{ +//=========== +//PMM - rogue rewrite of gunner dodge code. + float r; + float height; + int shooting = 0; + + if (!self->enemy) + { + self->enemy = attacker; + FoundTarget (self); + } + + // PMM - don't bother if it's going to hit anyway; fix for weird in-your-face etas (I was + // seeing numbers like 13 and 14) + if ((eta < 0.1) || (eta > 5)) + return; + + r = random(); + if (r > (0.25*((skill->value)+1))) + return; + + if ((self->monsterinfo.currentmove == &infantry_move_attack1) || + (self->monsterinfo.currentmove == &infantry_move_attack2)) + { + shooting = 1; + } + if (self->monsterinfo.aiflags & AI_DODGING) + { + height = self->absmax[2]; + } + else + { + height = self->absmax[2]-32-1; // the -1 is because the absmax is s.origin + maxs + 1 + } + + // check to see if it makes sense to duck + if (tr->endpos[2] <= height) + { + vec3_t right,diff; + + if (shooting) + { + self->monsterinfo.attack_state = AS_SLIDING; + return; + } + AngleVectors (self->s.angles, NULL, right, NULL); + VectorSubtract (tr->endpos, self->s.origin, diff); + if (DotProduct (right, diff) < 0) + { + self->monsterinfo.lefty = 1; + } + // if it doesn't sense to duck, try to strafe away + monster_done_dodge (self); + self->monsterinfo.currentmove = &infantry_move_run; + self->monsterinfo.attack_state = AS_SLIDING; + return; + } + + if (skill->value == 0) + { + self->monsterinfo.currentmove = &infantry_move_duck; + // PMM - stupid dodge + self->monsterinfo.duck_wait_time = level.time + eta + 1; + self->monsterinfo.aiflags |= AI_DODGING; + return; + } + + if (!shooting) + { + self->monsterinfo.currentmove = &infantry_move_duck; + self->monsterinfo.duck_wait_time = level.time + eta + (0.1 * (3 - skill->value)); + self->monsterinfo.aiflags |= AI_DODGING; + } + return; +//PMM +//=========== +*/ + +void infantry_duck (edict_t *self, float eta) +{ + // if we're jumping, don't dodge + if ((self->monsterinfo.currentmove == &infantry_move_jump) || + (self->monsterinfo.currentmove == &infantry_move_jump2)) + { + return; + } + + if ((self->monsterinfo.currentmove == &infantry_move_attack1) || + (self->monsterinfo.currentmove == &infantry_move_attack2)) + { + // if we're shooting, and not on easy, don't dodge + if (skill->value) + { + self->monsterinfo.aiflags &= ~AI_DUCKED; + return; + } + } + + if (skill->value == 0) + // PMM - stupid dodge + self->monsterinfo.duck_wait_time = level.time + eta + 1; + else + self->monsterinfo.duck_wait_time = level.time + eta + (0.1 * (3 - skill->value)); + + // has to be done immediately otherwise he can get stuck + monster_duck_down(self); + + self->monsterinfo.nextframe = FRAME_duck01; + self->monsterinfo.currentmove = &infantry_move_duck; + return; +} + +void infantry_sidestep (edict_t *self) +{ + // if we're jumping, don't dodge + if ((self->monsterinfo.currentmove == &infantry_move_jump) || + (self->monsterinfo.currentmove == &infantry_move_jump2)) + { + return; + } + + if ((self->monsterinfo.currentmove == &infantry_move_attack1) || + (self->monsterinfo.currentmove == &infantry_move_attack2)) + { + // if we're shooting, and not on easy, don't dodge + if (skill->value) + { + self->monsterinfo.aiflags &= ~AI_DODGING; + return; + } + } + + if (self->monsterinfo.currentmove != &infantry_move_run) + self->monsterinfo.currentmove = &infantry_move_run; +} + +/*QUAKED monster_infantry (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight +*/ +void SP_monster_infantry (edict_t *self) +{ + if (deathmatch->value) + { + G_FreeEdict (self); + return; + } + + sound_pain1 = gi.soundindex ("infantry/infpain1.wav"); + sound_pain2 = gi.soundindex ("infantry/infpain2.wav"); + sound_die1 = gi.soundindex ("infantry/infdeth1.wav"); + sound_die2 = gi.soundindex ("infantry/infdeth2.wav"); + + sound_gunshot = gi.soundindex ("infantry/infatck1.wav"); + sound_weapon_cock = gi.soundindex ("infantry/infatck3.wav"); + sound_punch_swing = gi.soundindex ("infantry/infatck2.wav"); + sound_punch_hit = gi.soundindex ("infantry/melee2.wav"); + + sound_sight = gi.soundindex ("infantry/infsght1.wav"); + sound_search = gi.soundindex ("infantry/infsrch1.wav"); + sound_idle = gi.soundindex ("infantry/infidle1.wav"); + + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex("models/monsters/infantry/tris.md2"); + VectorSet (self->mins, -16, -16, -24); + VectorSet (self->maxs, 16, 16, 32); + + self->health = 100; + self->gib_health = -40; + self->mass = 200; + + self->pain = infantry_pain; + self->die = infantry_die; + + self->monsterinfo.stand = infantry_stand; + self->monsterinfo.walk = infantry_walk; + self->monsterinfo.run = infantry_run; + // pmm + self->monsterinfo.dodge = M_MonsterDodge; + self->monsterinfo.duck = infantry_duck; + self->monsterinfo.unduck = monster_duck_up; + self->monsterinfo.sidestep = infantry_sidestep; +// self->monsterinfo.dodge = infantry_dodge; + // pmm + self->monsterinfo.attack = infantry_attack; + self->monsterinfo.melee = NULL; + self->monsterinfo.sight = infantry_sight; + self->monsterinfo.idle = infantry_fidget; + self->monsterinfo.blocked = infantry_blocked; + + gi.linkentity (self); + + self->monsterinfo.currentmove = &infantry_move_stand; + self->monsterinfo.scale = MODEL_SCALE; + + walkmonster_start (self); +} diff --git a/original/rogue/m_infantry.h b/original/rogue/m_infantry.h new file mode 100644 index 0000000..2e48aab --- /dev/null +++ b/original/rogue/m_infantry.h @@ -0,0 +1,228 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// G:\quake2\baseq2\models/monsters/infantry + +// This file generated by ModelGen - Do NOT Modify + +#define FRAME_gun02 0 +#define FRAME_stand01 1 +#define FRAME_stand02 2 +#define FRAME_stand03 3 +#define FRAME_stand04 4 +#define FRAME_stand05 5 +#define FRAME_stand06 6 +#define FRAME_stand07 7 +#define FRAME_stand08 8 +#define FRAME_stand09 9 +#define FRAME_stand10 10 +#define FRAME_stand11 11 +#define FRAME_stand12 12 +#define FRAME_stand13 13 +#define FRAME_stand14 14 +#define FRAME_stand15 15 +#define FRAME_stand16 16 +#define FRAME_stand17 17 +#define FRAME_stand18 18 +#define FRAME_stand19 19 +#define FRAME_stand20 20 +#define FRAME_stand21 21 +#define FRAME_stand22 22 +#define FRAME_stand23 23 +#define FRAME_stand24 24 +#define FRAME_stand25 25 +#define FRAME_stand26 26 +#define FRAME_stand27 27 +#define FRAME_stand28 28 +#define FRAME_stand29 29 +#define FRAME_stand30 30 +#define FRAME_stand31 31 +#define FRAME_stand32 32 +#define FRAME_stand33 33 +#define FRAME_stand34 34 +#define FRAME_stand35 35 +#define FRAME_stand36 36 +#define FRAME_stand37 37 +#define FRAME_stand38 38 +#define FRAME_stand39 39 +#define FRAME_stand40 40 +#define FRAME_stand41 41 +#define FRAME_stand42 42 +#define FRAME_stand43 43 +#define FRAME_stand44 44 +#define FRAME_stand45 45 +#define FRAME_stand46 46 +#define FRAME_stand47 47 +#define FRAME_stand48 48 +#define FRAME_stand49 49 +#define FRAME_stand50 50 +#define FRAME_stand51 51 +#define FRAME_stand52 52 +#define FRAME_stand53 53 +#define FRAME_stand54 54 +#define FRAME_stand55 55 +#define FRAME_stand56 56 +#define FRAME_stand57 57 +#define FRAME_stand58 58 +#define FRAME_stand59 59 +#define FRAME_stand60 60 +#define FRAME_stand61 61 +#define FRAME_stand62 62 +#define FRAME_stand63 63 +#define FRAME_stand64 64 +#define FRAME_stand65 65 +#define FRAME_stand66 66 +#define FRAME_stand67 67 +#define FRAME_stand68 68 +#define FRAME_stand69 69 +#define FRAME_stand70 70 +#define FRAME_stand71 71 +#define FRAME_walk01 72 +#define FRAME_walk02 73 +#define FRAME_walk03 74 +#define FRAME_walk04 75 +#define FRAME_walk05 76 +#define FRAME_walk06 77 +#define FRAME_walk07 78 +#define FRAME_walk08 79 +#define FRAME_walk09 80 +#define FRAME_walk10 81 +#define FRAME_walk11 82 +#define FRAME_walk12 83 +#define FRAME_walk13 84 +#define FRAME_walk14 85 +#define FRAME_walk15 86 +#define FRAME_walk16 87 +#define FRAME_walk17 88 +#define FRAME_walk18 89 +#define FRAME_walk19 90 +#define FRAME_walk20 91 +#define FRAME_run01 92 +#define FRAME_run02 93 +#define FRAME_run03 94 +#define FRAME_run04 95 +#define FRAME_run05 96 +#define FRAME_run06 97 +#define FRAME_run07 98 +#define FRAME_run08 99 +#define FRAME_pain101 100 +#define FRAME_pain102 101 +#define FRAME_pain103 102 +#define FRAME_pain104 103 +#define FRAME_pain105 104 +#define FRAME_pain106 105 +#define FRAME_pain107 106 +#define FRAME_pain108 107 +#define FRAME_pain109 108 +#define FRAME_pain110 109 +#define FRAME_pain201 110 +#define FRAME_pain202 111 +#define FRAME_pain203 112 +#define FRAME_pain204 113 +#define FRAME_pain205 114 +#define FRAME_pain206 115 +#define FRAME_pain207 116 +#define FRAME_pain208 117 +#define FRAME_pain209 118 +#define FRAME_pain210 119 +#define FRAME_duck01 120 +#define FRAME_duck02 121 +#define FRAME_duck03 122 +#define FRAME_duck04 123 +#define FRAME_duck05 124 +#define FRAME_death101 125 +#define FRAME_death102 126 +#define FRAME_death103 127 +#define FRAME_death104 128 +#define FRAME_death105 129 +#define FRAME_death106 130 +#define FRAME_death107 131 +#define FRAME_death108 132 +#define FRAME_death109 133 +#define FRAME_death110 134 +#define FRAME_death111 135 +#define FRAME_death112 136 +#define FRAME_death113 137 +#define FRAME_death114 138 +#define FRAME_death115 139 +#define FRAME_death116 140 +#define FRAME_death117 141 +#define FRAME_death118 142 +#define FRAME_death119 143 +#define FRAME_death120 144 +#define FRAME_death201 145 +#define FRAME_death202 146 +#define FRAME_death203 147 +#define FRAME_death204 148 +#define FRAME_death205 149 +#define FRAME_death206 150 +#define FRAME_death207 151 +#define FRAME_death208 152 +#define FRAME_death209 153 +#define FRAME_death210 154 +#define FRAME_death211 155 +#define FRAME_death212 156 +#define FRAME_death213 157 +#define FRAME_death214 158 +#define FRAME_death215 159 +#define FRAME_death216 160 +#define FRAME_death217 161 +#define FRAME_death218 162 +#define FRAME_death219 163 +#define FRAME_death220 164 +#define FRAME_death221 165 +#define FRAME_death222 166 +#define FRAME_death223 167 +#define FRAME_death224 168 +#define FRAME_death225 169 +#define FRAME_death301 170 +#define FRAME_death302 171 +#define FRAME_death303 172 +#define FRAME_death304 173 +#define FRAME_death305 174 +#define FRAME_death306 175 +#define FRAME_death307 176 +#define FRAME_death308 177 +#define FRAME_death309 178 +#define FRAME_block01 179 +#define FRAME_block02 180 +#define FRAME_block03 181 +#define FRAME_block04 182 +#define FRAME_block05 183 +#define FRAME_attak101 184 +#define FRAME_attak102 185 +#define FRAME_attak103 186 +#define FRAME_attak104 187 +#define FRAME_attak105 188 +#define FRAME_attak106 189 +#define FRAME_attak107 190 +#define FRAME_attak108 191 +#define FRAME_attak109 192 +#define FRAME_attak110 193 +#define FRAME_attak111 194 +#define FRAME_attak112 195 +#define FRAME_attak113 196 +#define FRAME_attak114 197 +#define FRAME_attak115 198 +#define FRAME_attak201 199 +#define FRAME_attak202 200 +#define FRAME_attak203 201 +#define FRAME_attak204 202 +#define FRAME_attak205 203 +#define FRAME_attak206 204 +#define FRAME_attak207 205 +#define FRAME_attak208 206 + +//PGM +#define FRAME_jump01 207 +#define FRAME_jump02 208 +#define FRAME_jump03 209 +#define FRAME_jump04 210 +#define FRAME_jump05 211 +#define FRAME_jump06 212 +#define FRAME_jump07 213 +#define FRAME_jump08 214 +#define FRAME_jump09 215 +#define FRAME_jump10 216 +//PGM + +#define MODEL_SCALE 1.000000 diff --git a/original/rogue/m_insane.c b/original/rogue/m_insane.c new file mode 100644 index 0000000..c570bad --- /dev/null +++ b/original/rogue/m_insane.c @@ -0,0 +1,676 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +/* +============================================================================== + +insane + +============================================================================== +*/ + +#include "g_local.h" +#include "m_insane.h" + + +static int sound_fist; +static int sound_shake; +static int sound_moan; +static int sound_scream[8]; + +void insane_fist (edict_t *self) +{ + gi.sound (self, CHAN_VOICE, sound_fist, 1, ATTN_IDLE, 0); +} + +void insane_shake (edict_t *self) +{ + gi.sound (self, CHAN_VOICE, sound_shake, 1, ATTN_IDLE, 0); +} + +void insane_moan (edict_t *self) +{ + gi.sound (self, CHAN_VOICE, sound_moan, 1, ATTN_IDLE, 0); +} + +void insane_scream (edict_t *self) +{ + gi.sound (self, CHAN_VOICE, sound_scream[rand()%8], 1, ATTN_IDLE, 0); +} + + +void insane_stand (edict_t *self); +void insane_dead (edict_t *self); +void insane_cross (edict_t *self); +void insane_walk (edict_t *self); +void insane_run (edict_t *self); +void insane_checkdown (edict_t *self); +void insane_checkup (edict_t *self); +void insane_onground (edict_t *self); + + +mframe_t insane_frames_stand_normal [] = +{ + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, insane_checkdown +}; +mmove_t insane_move_stand_normal = {FRAME_stand60, FRAME_stand65, insane_frames_stand_normal, insane_stand}; + +mframe_t insane_frames_stand_insane [] = +{ + ai_stand, 0, insane_shake, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, insane_checkdown +}; +mmove_t insane_move_stand_insane = {FRAME_stand65, FRAME_stand94, insane_frames_stand_insane, insane_stand}; + +mframe_t insane_frames_uptodown [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, insane_moan, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 2.7, NULL, + ai_move, 4.1, NULL, + ai_move, 6, NULL, + ai_move, 7.6, NULL, + ai_move, 3.6, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, insane_fist, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, insane_fist, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t insane_move_uptodown = {FRAME_stand1, FRAME_stand40, insane_frames_uptodown, insane_onground}; + + +mframe_t insane_frames_downtoup [] = +{ + ai_move, -0.7, NULL, // 41 + ai_move, -1.2, NULL, // 42 + ai_move, -1.5, NULL, // 43 + ai_move, -4.5, NULL, // 44 + ai_move, -3.5, NULL, // 45 + ai_move, -0.2, NULL, // 46 + ai_move, 0, NULL, // 47 + ai_move, -1.3, NULL, // 48 + ai_move, -3, NULL, // 49 + ai_move, -2, NULL, // 50 + ai_move, 0, NULL, // 51 + ai_move, 0, NULL, // 52 + ai_move, 0, NULL, // 53 + ai_move, -3.3, NULL, // 54 + ai_move, -1.6, NULL, // 55 + ai_move, -0.3, NULL, // 56 + ai_move, 0, NULL, // 57 + ai_move, 0, NULL, // 58 + ai_move, 0, NULL // 59 +}; +mmove_t insane_move_downtoup = {FRAME_stand41, FRAME_stand59, insane_frames_downtoup, insane_stand}; + +mframe_t insane_frames_jumpdown [] = +{ + ai_move, 0.2, NULL, + ai_move, 11.5, NULL, + ai_move, 5.1, NULL, + ai_move, 7.1, NULL, + ai_move, 0, NULL +}; +mmove_t insane_move_jumpdown = {FRAME_stand96, FRAME_stand100, insane_frames_jumpdown, insane_onground}; + + +mframe_t insane_frames_down [] = +{ + ai_move, 0, NULL, // 100 + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, // 110 + ai_move, -1.7, NULL, + ai_move, -1.6, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, insane_fist, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, // 120 + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, // 130 + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, insane_moan, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, // 140 + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, // 150 + ai_move, 0.5, NULL, + ai_move, 0, NULL, + ai_move, -0.2, insane_scream, + ai_move, 0, NULL, + ai_move, 0.2, NULL, + ai_move, 0.4, NULL, + ai_move, 0.6, NULL, + ai_move, 0.8, NULL, + ai_move, 0.7, NULL, + ai_move, 0, insane_checkup // 160 +}; +mmove_t insane_move_down = {FRAME_stand100, FRAME_stand160, insane_frames_down, insane_onground}; + +mframe_t insane_frames_walk_normal [] = +{ + ai_walk, 0, insane_scream, + ai_walk, 2.5, NULL, + ai_walk, 3.5, NULL, + ai_walk, 1.7, NULL, + ai_walk, 2.3, NULL, + ai_walk, 2.4, NULL, + ai_walk, 2.2, NULL, + ai_walk, 4.2, NULL, + ai_walk, 5.6, NULL, + ai_walk, 3.3, NULL, + ai_walk, 2.4, NULL, + ai_walk, 0.9, NULL, + ai_walk, 0, NULL +}; +mmove_t insane_move_walk_normal = {FRAME_walk27, FRAME_walk39, insane_frames_walk_normal, insane_walk}; +mmove_t insane_move_run_normal = {FRAME_walk27, FRAME_walk39, insane_frames_walk_normal, insane_run}; + +mframe_t insane_frames_walk_insane [] = +{ + ai_walk, 0, insane_scream, // walk 1 + ai_walk, 3.4, NULL, // walk 2 + ai_walk, 3.6, NULL, // 3 + ai_walk, 2.9, NULL, // 4 + ai_walk, 2.2, NULL, // 5 + ai_walk, 2.6, NULL, // 6 + ai_walk, 0, NULL, // 7 + ai_walk, 0.7, NULL, // 8 + ai_walk, 4.8, NULL, // 9 + ai_walk, 5.3, NULL, // 10 + ai_walk, 1.1, NULL, // 11 + ai_walk, 2, NULL, // 12 + ai_walk, 0.5, NULL, // 13 + ai_walk, 0, NULL, // 14 + ai_walk, 0, NULL, // 15 + ai_walk, 4.9, NULL, // 16 + ai_walk, 6.7, NULL, // 17 + ai_walk, 3.8, NULL, // 18 + ai_walk, 2, NULL, // 19 + ai_walk, 0.2, NULL, // 20 + ai_walk, 0, NULL, // 21 + ai_walk, 3.4, NULL, // 22 + ai_walk, 6.4, NULL, // 23 + ai_walk, 5, NULL, // 24 + ai_walk, 1.8, NULL, // 25 + ai_walk, 0, NULL // 26 +}; +mmove_t insane_move_walk_insane = {FRAME_walk1, FRAME_walk26, insane_frames_walk_insane, insane_walk}; +mmove_t insane_move_run_insane = {FRAME_walk1, FRAME_walk26, insane_frames_walk_insane, insane_run}; + +mframe_t insane_frames_stand_pain [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t insane_move_stand_pain = {FRAME_st_pain2, FRAME_st_pain12, insane_frames_stand_pain, insane_run}; + +mframe_t insane_frames_stand_death [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t insane_move_stand_death = {FRAME_st_death2, FRAME_st_death18, insane_frames_stand_death, insane_dead}; + +mframe_t insane_frames_crawl [] = +{ + ai_walk, 0, insane_scream, + ai_walk, 1.5, NULL, + ai_walk, 2.1, NULL, + ai_walk, 3.6, NULL, + ai_walk, 2, NULL, + ai_walk, 0.9, NULL, + ai_walk, 3, NULL, + ai_walk, 3.4, NULL, + ai_walk, 2.4, NULL +}; +mmove_t insane_move_crawl = {FRAME_crawl1, FRAME_crawl9, insane_frames_crawl, NULL}; +mmove_t insane_move_runcrawl = {FRAME_crawl1, FRAME_crawl9, insane_frames_crawl, NULL}; + +mframe_t insane_frames_crawl_pain [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t insane_move_crawl_pain = {FRAME_cr_pain2, FRAME_cr_pain10, insane_frames_crawl_pain, insane_run}; + +mframe_t insane_frames_crawl_death [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t insane_move_crawl_death = {FRAME_cr_death10, FRAME_cr_death16, insane_frames_crawl_death, insane_dead}; + +mframe_t insane_frames_cross [] = +{ + ai_move, 0, insane_moan, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t insane_move_cross = {FRAME_cross1, FRAME_cross15, insane_frames_cross, insane_cross}; + +mframe_t insane_frames_struggle_cross [] = +{ + ai_move, 0, insane_scream, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t insane_move_struggle_cross = {FRAME_cross16, FRAME_cross30, insane_frames_struggle_cross, insane_cross}; + +void insane_cross (edict_t *self) +{ + if (random() < 0.8) + self->monsterinfo.currentmove = &insane_move_cross; + else + self->monsterinfo.currentmove = &insane_move_struggle_cross; +} + +void insane_walk (edict_t *self) +{ + if ( self->spawnflags & 16 ) // Hold Ground? + if (self->s.frame == FRAME_cr_pain10) + { + self->monsterinfo.currentmove = &insane_move_down; + return; + } + if (self->spawnflags & 4) + self->monsterinfo.currentmove = &insane_move_crawl; + else + if (random() <= 0.5) + self->monsterinfo.currentmove = &insane_move_walk_normal; + else + self->monsterinfo.currentmove = &insane_move_walk_insane; +} + +void insane_run (edict_t *self) +{ + if ( self->spawnflags & 16 ) // Hold Ground? + if (self->s.frame == FRAME_cr_pain10) + { + self->monsterinfo.currentmove = &insane_move_down; + return; + } + if (self->spawnflags & 4) // Crawling? + self->monsterinfo.currentmove = &insane_move_runcrawl; + else + if (random() <= 0.5) // Else, mix it up + self->monsterinfo.currentmove = &insane_move_run_normal; + else + self->monsterinfo.currentmove = &insane_move_run_insane; +} + + +void insane_pain (edict_t *self, edict_t *other, float kick, int damage) +{ + int l,r; + +// if (self->health < (self->max_health / 2)) +// self->s.skinnum = 1; + + if (level.time < self->pain_debounce_time) + return; + + self->pain_debounce_time = level.time + 3; + + r = 1 + (rand()&1); + if (self->health < 25) + l = 25; + else if (self->health < 50) + l = 50; + else if (self->health < 75) + l = 75; + else + l = 100; + gi.sound (self, CHAN_VOICE, gi.soundindex (va("player/male/pain%i_%i.wav", l, r)), 1, ATTN_IDLE, 0); + + if (skill->value == 3) + return; // no pain anims in nightmare + + // Don't go into pain frames if crucified. + if (self->spawnflags & 8) + { + self->monsterinfo.currentmove = &insane_move_struggle_cross; + return; + } + + if ( ((self->s.frame >= FRAME_crawl1) && (self->s.frame <= FRAME_crawl9)) || ((self->s.frame >= FRAME_stand99) && (self->s.frame <= FRAME_stand160)) ) + { + self->monsterinfo.currentmove = &insane_move_crawl_pain; + } + else + self->monsterinfo.currentmove = &insane_move_stand_pain; + +} + +void insane_onground (edict_t *self) +{ + self->monsterinfo.currentmove = &insane_move_down; +} + +void insane_checkdown (edict_t *self) +{ +// if ( (self->s.frame == FRAME_stand94) || (self->s.frame == FRAME_stand65) ) + if (self->spawnflags & 32) // Always stand + return; + if (random() < 0.3) + if (random() < 0.5) + self->monsterinfo.currentmove = &insane_move_uptodown; + else + self->monsterinfo.currentmove = &insane_move_jumpdown; +} + +void insane_checkup (edict_t *self) +{ + // If Hold_Ground and Crawl are set + if ( (self->spawnflags & 4) && (self->spawnflags & 16) ) + return; + if (random() < 0.5) + self->monsterinfo.currentmove = &insane_move_downtoup; + +} + +void insane_stand (edict_t *self) +{ + if (self->spawnflags & 8) // If crucified + { + self->monsterinfo.currentmove = &insane_move_cross; + self->monsterinfo.aiflags |= AI_STAND_GROUND; + } + // If Hold_Ground and Crawl are set + else if ( (self->spawnflags & 4) && (self->spawnflags & 16) ) + self->monsterinfo.currentmove = &insane_move_down; + else + if (random() < 0.5) + self->monsterinfo.currentmove = &insane_move_stand_normal; + else + self->monsterinfo.currentmove = &insane_move_stand_insane; +} + +void insane_dead (edict_t *self) +{ + if (self->spawnflags & 8) + { + self->flags |= FL_FLY; + } + else + { + VectorSet (self->mins, -16, -16, -24); + VectorSet (self->maxs, 16, 16, -8); + self->movetype = MOVETYPE_TOSS; + } + self->svflags |= SVF_DEADMONSTER; + self->nextthink = 0; + gi.linkentity (self); +} + + +void insane_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + int n; + + if (self->health <= self->gib_health) + { + gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_IDLE, 0); + for (n= 0; n < 2; n++) + ThrowGib (self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC); + for (n= 0; n < 4; n++) + ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); + ThrowHead (self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC); + self->deadflag = DEAD_DEAD; + return; + } + + if (self->deadflag == DEAD_DEAD) + return; + + gi.sound (self, CHAN_VOICE, gi.soundindex(va("player/male/death%i.wav", (rand()%4)+1)), 1, ATTN_IDLE, 0); + + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_YES; + + if (self->spawnflags & 8) + { + insane_dead (self); + } + else + { + if ( ((self->s.frame >= FRAME_crawl1) && (self->s.frame <= FRAME_crawl9)) || ((self->s.frame >= FRAME_stand99) && (self->s.frame <= FRAME_stand160)) ) + self->monsterinfo.currentmove = &insane_move_crawl_death; + else + self->monsterinfo.currentmove = &insane_move_stand_death; + } +} + + +/*QUAKED misc_insane (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn CRAWL CRUCIFIED STAND_GROUND ALWAYS_STAND +*/ +void SP_misc_insane (edict_t *self) +{ +// static int skin = 0; //@@ + + if (deathmatch->value) + { + G_FreeEdict (self); + return; + } + + sound_fist = gi.soundindex ("insane/insane11.wav"); + sound_shake = gi.soundindex ("insane/insane5.wav"); + sound_moan = gi.soundindex ("insane/insane7.wav"); + sound_scream[0] = gi.soundindex ("insane/insane1.wav"); + sound_scream[1] = gi.soundindex ("insane/insane2.wav"); + sound_scream[2] = gi.soundindex ("insane/insane3.wav"); + sound_scream[3] = gi.soundindex ("insane/insane4.wav"); + sound_scream[4] = gi.soundindex ("insane/insane6.wav"); + sound_scream[5] = gi.soundindex ("insane/insane8.wav"); + sound_scream[6] = gi.soundindex ("insane/insane9.wav"); + sound_scream[7] = gi.soundindex ("insane/insane10.wav"); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex("models/monsters/insane/tris.md2"); + + VectorSet (self->mins, -16, -16, -24); + VectorSet (self->maxs, 16, 16, 32); + + self->health = 100; + self->gib_health = -50; + self->mass = 300; + + self->pain = insane_pain; + self->die = insane_die; + + self->monsterinfo.stand = insane_stand; + self->monsterinfo.walk = insane_walk; + self->monsterinfo.run = insane_run; + self->monsterinfo.dodge = NULL; + self->monsterinfo.attack = NULL; + self->monsterinfo.melee = NULL; + self->monsterinfo.sight = NULL; + self->monsterinfo.aiflags |= AI_GOOD_GUY; + +//@@ +// self->s.skinnum = skin; +// skin++; +// if (skin > 12) +// skin = 0; + + gi.linkentity (self); + + if (self->spawnflags & 16) // Stand Ground + self->monsterinfo.aiflags |= AI_STAND_GROUND; + + self->monsterinfo.currentmove = &insane_move_stand_normal; + + self->monsterinfo.scale = MODEL_SCALE; + + if (self->spawnflags & 8) // Crucified ? + { + VectorSet (self->mins, -16, 0, 0); + VectorSet (self->maxs, 16, 8, 32); + self->flags |= FL_NO_KNOCKBACK; + flymonster_start (self); + } + else + { + walkmonster_start (self); + self->s.skinnum = rand()%3; + } +} diff --git a/original/rogue/m_insane.h b/original/rogue/m_insane.h new file mode 100644 index 0000000..a9ce838 --- /dev/null +++ b/original/rogue/m_insane.h @@ -0,0 +1,290 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// G:\quake2\baseq2\models/monsters/insane + +// This file generated by ModelGen - Do NOT Modify + +#define FRAME_stand1 0 +#define FRAME_stand2 1 +#define FRAME_stand3 2 +#define FRAME_stand4 3 +#define FRAME_stand5 4 +#define FRAME_stand6 5 +#define FRAME_stand7 6 +#define FRAME_stand8 7 +#define FRAME_stand9 8 +#define FRAME_stand10 9 +#define FRAME_stand11 10 +#define FRAME_stand12 11 +#define FRAME_stand13 12 +#define FRAME_stand14 13 +#define FRAME_stand15 14 +#define FRAME_stand16 15 +#define FRAME_stand17 16 +#define FRAME_stand18 17 +#define FRAME_stand19 18 +#define FRAME_stand20 19 +#define FRAME_stand21 20 +#define FRAME_stand22 21 +#define FRAME_stand23 22 +#define FRAME_stand24 23 +#define FRAME_stand25 24 +#define FRAME_stand26 25 +#define FRAME_stand27 26 +#define FRAME_stand28 27 +#define FRAME_stand29 28 +#define FRAME_stand30 29 +#define FRAME_stand31 30 +#define FRAME_stand32 31 +#define FRAME_stand33 32 +#define FRAME_stand34 33 +#define FRAME_stand35 34 +#define FRAME_stand36 35 +#define FRAME_stand37 36 +#define FRAME_stand38 37 +#define FRAME_stand39 38 +#define FRAME_stand40 39 +#define FRAME_stand41 40 +#define FRAME_stand42 41 +#define FRAME_stand43 42 +#define FRAME_stand44 43 +#define FRAME_stand45 44 +#define FRAME_stand46 45 +#define FRAME_stand47 46 +#define FRAME_stand48 47 +#define FRAME_stand49 48 +#define FRAME_stand50 49 +#define FRAME_stand51 50 +#define FRAME_stand52 51 +#define FRAME_stand53 52 +#define FRAME_stand54 53 +#define FRAME_stand55 54 +#define FRAME_stand56 55 +#define FRAME_stand57 56 +#define FRAME_stand58 57 +#define FRAME_stand59 58 +#define FRAME_stand60 59 +#define FRAME_stand61 60 +#define FRAME_stand62 61 +#define FRAME_stand63 62 +#define FRAME_stand64 63 +#define FRAME_stand65 64 +#define FRAME_stand66 65 +#define FRAME_stand67 66 +#define FRAME_stand68 67 +#define FRAME_stand69 68 +#define FRAME_stand70 69 +#define FRAME_stand71 70 +#define FRAME_stand72 71 +#define FRAME_stand73 72 +#define FRAME_stand74 73 +#define FRAME_stand75 74 +#define FRAME_stand76 75 +#define FRAME_stand77 76 +#define FRAME_stand78 77 +#define FRAME_stand79 78 +#define FRAME_stand80 79 +#define FRAME_stand81 80 +#define FRAME_stand82 81 +#define FRAME_stand83 82 +#define FRAME_stand84 83 +#define FRAME_stand85 84 +#define FRAME_stand86 85 +#define FRAME_stand87 86 +#define FRAME_stand88 87 +#define FRAME_stand89 88 +#define FRAME_stand90 89 +#define FRAME_stand91 90 +#define FRAME_stand92 91 +#define FRAME_stand93 92 +#define FRAME_stand94 93 +#define FRAME_stand95 94 +#define FRAME_stand96 95 +#define FRAME_stand97 96 +#define FRAME_stand98 97 +#define FRAME_stand99 98 +#define FRAME_stand100 99 +#define FRAME_stand101 100 +#define FRAME_stand102 101 +#define FRAME_stand103 102 +#define FRAME_stand104 103 +#define FRAME_stand105 104 +#define FRAME_stand106 105 +#define FRAME_stand107 106 +#define FRAME_stand108 107 +#define FRAME_stand109 108 +#define FRAME_stand110 109 +#define FRAME_stand111 110 +#define FRAME_stand112 111 +#define FRAME_stand113 112 +#define FRAME_stand114 113 +#define FRAME_stand115 114 +#define FRAME_stand116 115 +#define FRAME_stand117 116 +#define FRAME_stand118 117 +#define FRAME_stand119 118 +#define FRAME_stand120 119 +#define FRAME_stand121 120 +#define FRAME_stand122 121 +#define FRAME_stand123 122 +#define FRAME_stand124 123 +#define FRAME_stand125 124 +#define FRAME_stand126 125 +#define FRAME_stand127 126 +#define FRAME_stand128 127 +#define FRAME_stand129 128 +#define FRAME_stand130 129 +#define FRAME_stand131 130 +#define FRAME_stand132 131 +#define FRAME_stand133 132 +#define FRAME_stand134 133 +#define FRAME_stand135 134 +#define FRAME_stand136 135 +#define FRAME_stand137 136 +#define FRAME_stand138 137 +#define FRAME_stand139 138 +#define FRAME_stand140 139 +#define FRAME_stand141 140 +#define FRAME_stand142 141 +#define FRAME_stand143 142 +#define FRAME_stand144 143 +#define FRAME_stand145 144 +#define FRAME_stand146 145 +#define FRAME_stand147 146 +#define FRAME_stand148 147 +#define FRAME_stand149 148 +#define FRAME_stand150 149 +#define FRAME_stand151 150 +#define FRAME_stand152 151 +#define FRAME_stand153 152 +#define FRAME_stand154 153 +#define FRAME_stand155 154 +#define FRAME_stand156 155 +#define FRAME_stand157 156 +#define FRAME_stand158 157 +#define FRAME_stand159 158 +#define FRAME_stand160 159 +#define FRAME_walk27 160 +#define FRAME_walk28 161 +#define FRAME_walk29 162 +#define FRAME_walk30 163 +#define FRAME_walk31 164 +#define FRAME_walk32 165 +#define FRAME_walk33 166 +#define FRAME_walk34 167 +#define FRAME_walk35 168 +#define FRAME_walk36 169 +#define FRAME_walk37 170 +#define FRAME_walk38 171 +#define FRAME_walk39 172 +#define FRAME_walk1 173 +#define FRAME_walk2 174 +#define FRAME_walk3 175 +#define FRAME_walk4 176 +#define FRAME_walk5 177 +#define FRAME_walk6 178 +#define FRAME_walk7 179 +#define FRAME_walk8 180 +#define FRAME_walk9 181 +#define FRAME_walk10 182 +#define FRAME_walk11 183 +#define FRAME_walk12 184 +#define FRAME_walk13 185 +#define FRAME_walk14 186 +#define FRAME_walk15 187 +#define FRAME_walk16 188 +#define FRAME_walk17 189 +#define FRAME_walk18 190 +#define FRAME_walk19 191 +#define FRAME_walk20 192 +#define FRAME_walk21 193 +#define FRAME_walk22 194 +#define FRAME_walk23 195 +#define FRAME_walk24 196 +#define FRAME_walk25 197 +#define FRAME_walk26 198 +#define FRAME_st_pain2 199 +#define FRAME_st_pain3 200 +#define FRAME_st_pain4 201 +#define FRAME_st_pain5 202 +#define FRAME_st_pain6 203 +#define FRAME_st_pain7 204 +#define FRAME_st_pain8 205 +#define FRAME_st_pain9 206 +#define FRAME_st_pain10 207 +#define FRAME_st_pain11 208 +#define FRAME_st_pain12 209 +#define FRAME_st_death2 210 +#define FRAME_st_death3 211 +#define FRAME_st_death4 212 +#define FRAME_st_death5 213 +#define FRAME_st_death6 214 +#define FRAME_st_death7 215 +#define FRAME_st_death8 216 +#define FRAME_st_death9 217 +#define FRAME_st_death10 218 +#define FRAME_st_death11 219 +#define FRAME_st_death12 220 +#define FRAME_st_death13 221 +#define FRAME_st_death14 222 +#define FRAME_st_death15 223 +#define FRAME_st_death16 224 +#define FRAME_st_death17 225 +#define FRAME_st_death18 226 +#define FRAME_crawl1 227 +#define FRAME_crawl2 228 +#define FRAME_crawl3 229 +#define FRAME_crawl4 230 +#define FRAME_crawl5 231 +#define FRAME_crawl6 232 +#define FRAME_crawl7 233 +#define FRAME_crawl8 234 +#define FRAME_crawl9 235 +#define FRAME_cr_pain2 236 +#define FRAME_cr_pain3 237 +#define FRAME_cr_pain4 238 +#define FRAME_cr_pain5 239 +#define FRAME_cr_pain6 240 +#define FRAME_cr_pain7 241 +#define FRAME_cr_pain8 242 +#define FRAME_cr_pain9 243 +#define FRAME_cr_pain10 244 +#define FRAME_cr_death10 245 +#define FRAME_cr_death11 246 +#define FRAME_cr_death12 247 +#define FRAME_cr_death13 248 +#define FRAME_cr_death14 249 +#define FRAME_cr_death15 250 +#define FRAME_cr_death16 251 +#define FRAME_cross1 252 +#define FRAME_cross2 253 +#define FRAME_cross3 254 +#define FRAME_cross4 255 +#define FRAME_cross5 256 +#define FRAME_cross6 257 +#define FRAME_cross7 258 +#define FRAME_cross8 259 +#define FRAME_cross9 260 +#define FRAME_cross10 261 +#define FRAME_cross11 262 +#define FRAME_cross12 263 +#define FRAME_cross13 264 +#define FRAME_cross14 265 +#define FRAME_cross15 266 +#define FRAME_cross16 267 +#define FRAME_cross17 268 +#define FRAME_cross18 269 +#define FRAME_cross19 270 +#define FRAME_cross20 271 +#define FRAME_cross21 272 +#define FRAME_cross22 273 +#define FRAME_cross23 274 +#define FRAME_cross24 275 +#define FRAME_cross25 276 +#define FRAME_cross26 277 +#define FRAME_cross27 278 +#define FRAME_cross28 279 +#define FRAME_cross29 280 +#define FRAME_cross30 281 + +#define MODEL_SCALE 1.000000 diff --git a/original/rogue/m_medic.c b/original/rogue/m_medic.c new file mode 100644 index 0000000..8844e29 --- /dev/null +++ b/original/rogue/m_medic.c @@ -0,0 +1,1801 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +/* +============================================================================== + +MEDIC + +============================================================================== +*/ + +#include "g_local.h" +#include "m_medic.h" + +#define MEDIC_MIN_DISTANCE 32 +#define MEDIC_MAX_HEAL_DISTANCE 400 +#define MEDIC_TRY_TIME 10.0 + +// FIXME - +// +// owner moved to monsterinfo.healer instead +// +// For some reason, the healed monsters are rarely ending up in the floor +// +// 5/15/1998 I think I fixed these, keep an eye on them + +qboolean visible (edict_t *self, edict_t *other); +void M_SetEffects (edict_t *ent); +qboolean FindTarget (edict_t *self); +void HuntTarget (edict_t *self); +void FoundTarget (edict_t *self); +char *ED_NewString (char *string); +void spawngrow_think (edict_t *self); +void SpawnGrow_Spawn (vec3_t startpos, int size); +void ED_CallSpawn (edict_t *ent); +void M_FliesOff (edict_t *self); +void M_FliesOn (edict_t *self); + + +static int sound_idle1; +static int sound_pain1; +static int sound_pain2; +static int sound_die; +static int sound_sight; +static int sound_search; +static int sound_hook_launch; +static int sound_hook_hit; +static int sound_hook_heal; +static int sound_hook_retract; + +// PMM - commander sounds +static int commander_sound_idle1; +static int commander_sound_pain1; +static int commander_sound_pain2; +static int commander_sound_die; +static int commander_sound_sight; +static int commander_sound_search; +static int commander_sound_hook_launch; +static int commander_sound_hook_hit; +static int commander_sound_hook_heal; +static int commander_sound_hook_retract; +static int commander_sound_spawn; + +char * reinforcements[] = { + {"monster_soldier_light"}, // 0 + {"monster_soldier"}, // 1 + {"monster_soldier_ss"}, // 2 + {"monster_infantry"}, // 3 + {"monster_gunner"}, // 4 +// {"monster_chick"}, // 4 + {"monster_medic"}, // 5 + {"monster_gladiator"} // 6 +}; + +vec3_t reinforcement_mins[] = { + {-16, -16, -24}, + {-16, -16, -24}, + {-16, -16, -24}, + {-16, -16, -24}, + {-16, -16, -24}, + {-16, -16, -24}, + {-32, -32, -24} +}; + +vec3_t reinforcement_maxs[] = { + {16, 16, 32}, + {16, 16, 32}, + {16, 16, 32}, + {16, 16, 32}, + {16, 16, 32}, + {16, 16, 32}, + {32, 32, 64} +}; + +vec3_t reinforcement_position[] = { + {80, 0, 0}, + {40, 60, 0}, + {40, -60, 0}, + {0, 80, 0}, + {0, -80, 0} +}; + +void cleanupHeal (edict_t *self, qboolean change_frame) +{ + // clean up target, if we have one and it's legit + if (self->enemy && self->enemy->inuse) + { + self->enemy->monsterinfo.healer = NULL; + self->enemy->monsterinfo.aiflags &= ~AI_RESURRECTING; + self->enemy->takedamage = DAMAGE_YES; + M_SetEffects (self->enemy); + } + + if (change_frame) + self->monsterinfo.nextframe = FRAME_attack52; +} + +void abortHeal (edict_t *self, qboolean change_frame, qboolean gib, qboolean mark) +{ + int hurt; + static vec3_t pain_normal = { 0, 0, 1 }; + + // clean up target + cleanupHeal (self, change_frame); + // gib em! + if ((mark) && (self->enemy) && (self->enemy->inuse)) + { +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("%s - marking target as bad\n", self->classname); + // if the first badMedic slot is filled by a medic, skip it and use the second one + if ((self->enemy->monsterinfo.badMedic1) && (self->enemy->monsterinfo.badMedic1->inuse) + && (!strncmp(self->enemy->monsterinfo.badMedic1->classname, "monster_medic", 13)) ) + { + self->enemy->monsterinfo.badMedic2 = self; + } + else + { + self->enemy->monsterinfo.badMedic1 = self; + } + } + if ((gib) && (self->enemy) && (self->enemy->inuse)) + { +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("%s - gibbing bad heal target", self->classname); + + if(self->enemy->gib_health) + hurt = - self->enemy->gib_health; + else + hurt = 500; + + T_Damage (self->enemy, self, self, vec3_origin, self->enemy->s.origin, + pain_normal, hurt, 0, 0, MOD_UNKNOWN); + } + // clean up self + + self->monsterinfo.aiflags &= ~AI_MEDIC; + if ((self->oldenemy) && (self->oldenemy->inuse)) + self->enemy = self->oldenemy; + else + self->enemy = NULL; + + self->monsterinfo.medicTries = 0; +} + +qboolean canReach (edict_t *self, edict_t *other) +{ + vec3_t spot1; + vec3_t spot2; + trace_t trace; + + VectorCopy (self->s.origin, spot1); + spot1[2] += self->viewheight; + VectorCopy (other->s.origin, spot2); + spot2[2] += other->viewheight; + trace = gi.trace (spot1, vec3_origin, vec3_origin, spot2, self, MASK_SHOT|MASK_WATER); + + if (trace.fraction == 1.0 || trace.ent == other) // PGM + return true; + return false; +} + +edict_t *medic_FindDeadMonster (edict_t *self) +{ + float radius; + edict_t *ent = NULL; + edict_t *best = NULL; + + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + radius = MEDIC_MAX_HEAL_DISTANCE; + else + radius = 1024; + + while ((ent = findradius(ent, self->s.origin, radius)) != NULL) + { + if (ent == self) + continue; + if (!(ent->svflags & SVF_MONSTER)) + continue; + if (ent->monsterinfo.aiflags & AI_GOOD_GUY) + continue; + // check to make sure we haven't bailed on this guy already + if ((ent->monsterinfo.badMedic1 == self) || (ent->monsterinfo.badMedic2 == self)) + continue; + if (ent->monsterinfo.healer) + // FIXME - this is correcting a bug that is somewhere else + // if the healer is a monster, and it's in medic mode .. continue .. otherwise + // we will override the healer, if it passes all the other tests + if ((ent->monsterinfo.healer->inuse) && (ent->monsterinfo.healer->health > 0) && + (ent->monsterinfo.healer->svflags & SVF_MONSTER) && (ent->monsterinfo.healer->monsterinfo.aiflags & AI_MEDIC)) + continue; + if (ent->health > 0) + continue; + if ((ent->nextthink) && !((ent->think == M_FliesOn) || (ent->think == M_FliesOff))) + continue; + if (!visible(self, ent)) +// if (!canReach(self, ent)) + continue; + if (!strncmp(ent->classname, "player", 6)) // stop it from trying to heal player_noise entities + continue; + // FIXME - there's got to be a better way .. + // make sure we don't spawn people right on top of us + if (realrange(self, ent) <= MEDIC_MIN_DISTANCE) + continue; + if (!best) + { + best = ent; + continue; + } + if (ent->max_health <= best->max_health) + continue; + best = ent; + } + + if (best) + self->timestamp = level.time + MEDIC_TRY_TIME; + + return best; +} + +void medic_idle (edict_t *self) +{ + edict_t *ent; + + // PMM - commander sounds + if (self->mass == 400) + gi.sound (self, CHAN_VOICE, sound_idle1, 1, ATTN_IDLE, 0); + else + gi.sound (self, CHAN_VOICE, commander_sound_idle1, 1, ATTN_IDLE, 0); + + + if (!self->oldenemy) + { + ent = medic_FindDeadMonster(self); + if (ent) + { + self->oldenemy = self->enemy; + self->enemy = ent; + self->enemy->monsterinfo.healer = self; + self->monsterinfo.aiflags |= AI_MEDIC; + FoundTarget (self); + } + } +} + +void medic_search (edict_t *self) +{ + edict_t *ent; + + // PMM - commander sounds + if (self->mass == 400) + gi.sound (self, CHAN_VOICE, sound_search, 1, ATTN_IDLE, 0); + else + gi.sound (self, CHAN_VOICE, commander_sound_search, 1, ATTN_IDLE, 0); + + if (!self->oldenemy) + { + ent = medic_FindDeadMonster(self); + if (ent) + { + self->oldenemy = self->enemy; + self->enemy = ent; + self->enemy->monsterinfo.healer = self; + self->monsterinfo.aiflags |= AI_MEDIC; + FoundTarget (self); + } + } +} + +void medic_sight (edict_t *self, edict_t *other) +{ + // PMM - commander sounds + if (self->mass == 400) + gi.sound (self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); + else + gi.sound (self, CHAN_VOICE, commander_sound_sight, 1, ATTN_NORM, 0); +} + + +mframe_t medic_frames_stand [] = +{ + ai_stand, 0, medic_idle, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + +}; +mmove_t medic_move_stand = {FRAME_wait1, FRAME_wait90, medic_frames_stand, NULL}; + +void medic_stand (edict_t *self) +{ + self->monsterinfo.currentmove = &medic_move_stand; +} + + +mframe_t medic_frames_walk [] = +{ + ai_walk, 6.2, NULL, + ai_walk, 18.1, NULL, + ai_walk, 1, NULL, + ai_walk, 9, NULL, + ai_walk, 10, NULL, + ai_walk, 9, NULL, + ai_walk, 11, NULL, + ai_walk, 11.6, NULL, + ai_walk, 2, NULL, + ai_walk, 9.9, NULL, + ai_walk, 14, NULL, + ai_walk, 9.3, NULL +}; +mmove_t medic_move_walk = {FRAME_walk1, FRAME_walk12, medic_frames_walk, NULL}; + +void medic_walk (edict_t *self) +{ + self->monsterinfo.currentmove = &medic_move_walk; +} + + +mframe_t medic_frames_run [] = +{ + ai_run, 18, NULL, + ai_run, 22.5, NULL, + ai_run, 25.4, monster_done_dodge, + ai_run, 23.4, NULL, + ai_run, 24, NULL, + ai_run, 35.6, NULL //pmm + +}; +mmove_t medic_move_run = {FRAME_run1, FRAME_run6, medic_frames_run, NULL}; + +void medic_run (edict_t *self) +{ + monster_done_dodge (self); + if (!(self->monsterinfo.aiflags & AI_MEDIC)) + { + edict_t *ent; + + ent = medic_FindDeadMonster(self); + if (ent) + { + self->oldenemy = self->enemy; + self->enemy = ent; + self->enemy->monsterinfo.healer = self; + self->monsterinfo.aiflags |= AI_MEDIC; + FoundTarget (self); + return; + } + } +// else if (!canReach(self, self->enemy)) +// { +// abortHeal (self, 0); +// } + + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + self->monsterinfo.currentmove = &medic_move_stand; + else + self->monsterinfo.currentmove = &medic_move_run; +} + + +mframe_t medic_frames_pain1 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t medic_move_pain1 = {FRAME_paina1, FRAME_paina8, medic_frames_pain1, medic_run}; + +mframe_t medic_frames_pain2 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t medic_move_pain2 = {FRAME_painb1, FRAME_painb15, medic_frames_pain2, medic_run}; + +void medic_pain (edict_t *self, edict_t *other, float kick, int damage) +{ + monster_done_dodge (self); + + if ((self->health < (self->max_health / 2))) + if (self->mass > 400) + self->s.skinnum = 3; + else + self->s.skinnum = 1; + + if (level.time < self->pain_debounce_time) + return; + + self->pain_debounce_time = level.time + 3; + + if (skill->value == 3) + return; // no pain anims in nightmare + + // if we're healing someone, we ignore pain + if (self->monsterinfo.aiflags & AI_MEDIC) + return; + + if (self->mass > 400) + { + if (damage < 35) + { + gi.sound (self, CHAN_VOICE, commander_sound_pain1, 1, ATTN_NORM, 0); + return; + } + + self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING; + self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; + + gi.sound (self, CHAN_VOICE, commander_sound_pain2, 1, ATTN_NORM, 0); + + if (random() < (min(((float)damage * 0.005), 0.5))) // no more than 50% chance of big pain + self->monsterinfo.currentmove = &medic_move_pain2; + else + self->monsterinfo.currentmove = &medic_move_pain1; + } + else if (random() < 0.5) + { + self->monsterinfo.currentmove = &medic_move_pain1; + gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); + } + else + { + self->monsterinfo.currentmove = &medic_move_pain2; + gi.sound (self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0); + } + // PMM - clear duck flag + if (self->monsterinfo.aiflags & AI_DUCKED) + monster_duck_up(self); +} + +void medic_fire_blaster (edict_t *self) +{ + vec3_t start; + vec3_t forward, right; + vec3_t end; + vec3_t dir; + int effect; + int damage = 2; + + // paranoia checking + if (!(self->enemy && self->enemy->inuse)) + return; + + if ((self->s.frame == FRAME_attack9) || (self->s.frame == FRAME_attack12)) + effect = EF_BLASTER; + else if ((self->s.frame == FRAME_attack19) || (self->s.frame == FRAME_attack22) || (self->s.frame == FRAME_attack25) || (self->s.frame == FRAME_attack28)) + effect = EF_HYPERBLASTER; + else + effect = 0; + + AngleVectors (self->s.angles, forward, right, NULL); + G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_MEDIC_BLASTER_1], forward, right, start); + + VectorCopy (self->enemy->s.origin, end); + end[2] += self->enemy->viewheight; + VectorSubtract (end, start, dir); + + if (!strcmp(self->enemy->classname, "tesla")) + damage = 3; + + // medic commander shoots blaster2 + if (self->mass > 400) + monster_fire_blaster2 (self, start, dir, damage, 1000, MZ2_MEDIC_BLASTER_2, effect); + else + monster_fire_blaster (self, start, dir, damage, 1000, MZ2_MEDIC_BLASTER_1, effect); +} + +void medic_dead (edict_t *self) +{ + VectorSet (self->mins, -16, -16, -24); + VectorSet (self->maxs, 16, 16, -8); + self->movetype = MOVETYPE_TOSS; + self->svflags |= SVF_DEADMONSTER; + self->nextthink = 0; + gi.linkentity (self); +} + +mframe_t medic_frames_death [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t medic_move_death = {FRAME_death1, FRAME_death30, medic_frames_death, medic_dead}; + +void medic_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + int n; + + // if we had a pending patient, he was already freed up in Killed + +// check for gib + if (self->health <= self->gib_health) + { + gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0); + for (n= 0; n < 2; n++) + ThrowGib (self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC); + for (n= 0; n < 4; n++) + ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); + ThrowHead (self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC); + self->deadflag = DEAD_DEAD; + return; + } + + if (self->deadflag == DEAD_DEAD) + return; + +// regular death + // PMM + if (self->mass == 400) + gi.sound (self, CHAN_VOICE, sound_die, 1, ATTN_NORM, 0); + else + gi.sound (self, CHAN_VOICE, commander_sound_die, 1, ATTN_NORM, 0); + // + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_YES; + + self->monsterinfo.currentmove = &medic_move_death; +} + +mframe_t medic_frames_duck [] = +{ + ai_move, -1, NULL, + ai_move, -1, NULL, + ai_move, -1, monster_duck_down, + ai_move, -1, monster_duck_hold, + ai_move, -1, NULL, + ai_move, -1, NULL, + ai_move, -1, NULL, // PMM - duck up used to be here + ai_move, -1, NULL, + ai_move, -1, NULL, + ai_move, -1, NULL, + ai_move, -1, NULL, + ai_move, -1, NULL, + ai_move, -1, NULL, + ai_move, -1, monster_duck_up, + ai_move, -1, NULL, + ai_move, -1, NULL +}; +mmove_t medic_move_duck = {FRAME_duck1, FRAME_duck16, medic_frames_duck, medic_run}; + +// PMM -- moved dodge code to after attack code so I can reference attack frames + +mframe_t medic_frames_attackHyperBlaster [] = +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, medic_fire_blaster, + ai_charge, 0, medic_fire_blaster, + ai_charge, 0, medic_fire_blaster, + ai_charge, 0, medic_fire_blaster, + ai_charge, 0, medic_fire_blaster, + ai_charge, 0, medic_fire_blaster, + ai_charge, 0, medic_fire_blaster, + ai_charge, 0, medic_fire_blaster, + ai_charge, 0, medic_fire_blaster, + ai_charge, 0, medic_fire_blaster, + ai_charge, 0, medic_fire_blaster, + ai_charge, 0, medic_fire_blaster +}; +mmove_t medic_move_attackHyperBlaster = {FRAME_attack15, FRAME_attack30, medic_frames_attackHyperBlaster, medic_run}; + + +void medic_continue (edict_t *self) +{ + if (visible (self, self->enemy) ) + if (random() <= 0.95) + self->monsterinfo.currentmove = &medic_move_attackHyperBlaster; +} + + +mframe_t medic_frames_attackBlaster [] = +{ + ai_charge, 0, NULL, + ai_charge, 5, NULL, + ai_charge, 5, NULL, + ai_charge, 3, NULL, + ai_charge, 2, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, medic_fire_blaster, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, medic_fire_blaster, + ai_charge, 0, NULL, + ai_charge, 0, medic_continue // Change to medic_continue... Else, go to frame 32 +}; +mmove_t medic_move_attackBlaster = {FRAME_attack1, FRAME_attack14, medic_frames_attackBlaster, medic_run}; + + +void medic_hook_launch (edict_t *self) +{ + // PMM - commander sounds + if (self->mass == 400) + gi.sound (self, CHAN_WEAPON, sound_hook_launch, 1, ATTN_NORM, 0); + else + gi.sound (self, CHAN_WEAPON, commander_sound_hook_launch, 1, ATTN_NORM, 0); +} + +static vec3_t medic_cable_offsets[] = +{ + 45.0, -9.2, 15.5, + 48.4, -9.7, 15.2, + 47.8, -9.8, 15.8, + 47.3, -9.3, 14.3, + 45.4, -10.1, 13.1, + 41.9, -12.7, 12.0, + 37.8, -15.8, 11.2, + 34.3, -18.4, 10.7, + 32.7, -19.7, 10.4, + 32.7, -19.7, 10.4 +}; + +void medic_cable_attack (edict_t *self) +{ + vec3_t offset, start, end, f, r; + trace_t tr; + vec3_t dir; +// vec3_t angles; + float distance; + + if ((!self->enemy) || (!self->enemy->inuse) || (self->enemy->s.effects & EF_GIB)) + { +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("medic_commander - aborting heal due to target's disappearance\n"); + abortHeal (self, true, false, false); + return; + } + + // see if our enemy has changed to a client, or our target has more than 0 health, + // abort it .. we got switched to someone else due to damage + if ((self->enemy->client) || (self->enemy->health > 0)) + { + abortHeal (self, true, false, false); + return; + } + AngleVectors (self->s.angles, f, r, NULL); + VectorCopy (medic_cable_offsets[self->s.frame - FRAME_attack42], offset); + G_ProjectSource (self->s.origin, offset, f, r, start); + + // check for max distance + // not needed, done in checkattack + // check for min distance + VectorSubtract (start, self->enemy->s.origin, dir); + distance = VectorLength(dir); + if (distance < MEDIC_MIN_DISTANCE) + { +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("medic_commander - aborting heal due to proximity to target "); + abortHeal (self, true, true, false ); + return; + } +// if ((g_showlogic)&&(g_showlogic->value)) +// gi.dprintf ("distance to target is %f\n", distance); +// if (distance > 300) +// return; + + // check for min/max pitch + // PMM -- took out since it doesn't look bad when it fails +// vectoangles (dir, angles); +// if (angles[0] < -180) +// angles[0] += 360; +// if (fabs(angles[0]) > 45) +// { +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("medic_commander - aborting heal due to bad angle\n"); +// abortHeal(self); +// return; +// } + + tr = gi.trace (start, NULL, NULL, self->enemy->s.origin, self, MASK_SOLID); + if (tr.fraction != 1.0 && tr.ent != self->enemy) + { + if (tr.ent == world) + { + // give up on second try + if (self->monsterinfo.medicTries > 1) + { + abortHeal (self, true, false, true); + return; + } + self->monsterinfo.medicTries++; + cleanupHeal (self, 1); + return; + } +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("medic_commander - aborting heal due to beamus interruptus\n"); + abortHeal (self, true, false, false); + return; + } + + if (self->s.frame == FRAME_attack43) + { + // PMM - commander sounds + if (self->mass == 400) + gi.sound (self->enemy, CHAN_AUTO, sound_hook_hit, 1, ATTN_NORM, 0); + else + gi.sound (self->enemy, CHAN_AUTO, commander_sound_hook_hit, 1, ATTN_NORM, 0); + + self->enemy->monsterinfo.aiflags |= AI_RESURRECTING; + self->enemy->takedamage = DAMAGE_NO; + M_SetEffects (self->enemy); + } + else if (self->s.frame == FRAME_attack50) + { + vec3_t maxs; + self->enemy->spawnflags = 0; + self->enemy->monsterinfo.aiflags = 0; + self->enemy->target = NULL; + self->enemy->targetname = NULL; + self->enemy->combattarget = NULL; + self->enemy->deathtarget = NULL; + self->enemy->monsterinfo.healer = self; + + VectorCopy (self->enemy->maxs, maxs); + maxs[2] += 48; // compensate for change when they die + + tr = gi.trace (self->enemy->s.origin, self->enemy->mins, maxs, self->enemy->s.origin, self->enemy, MASK_MONSTERSOLID); + if (tr.startsolid || tr.allsolid) + { +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("Spawn point obstructed, aborting heal!\n"); + abortHeal (self, true, true, false); + return; + } + else if (tr.ent != world) + { +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf("heal in entity %s\n", tr.ent->classname); + abortHeal (self, true, true, false); + return; + } +/* else if (tr.ent == world) + { + if ((g_showlogic) && (g_showlogic->value)) + gi.dprintf ("heal in world, aborting!\n"); + abortHeal (self, 1); + return; + } +*/ else + { + self->enemy->monsterinfo.aiflags |= AI_DO_NOT_COUNT; + ED_CallSpawn (self->enemy); + + if (self->enemy->think) + { + self->enemy->nextthink = level.time; + self->enemy->think (self->enemy); + } + // self->enemy->monsterinfo.aiflags |= AI_RESURRECTING; + self->enemy->monsterinfo.aiflags &= ~AI_RESURRECTING; + self->enemy->monsterinfo.aiflags |= AI_IGNORE_SHOTS|AI_DO_NOT_COUNT; + // turn off flies + self->enemy->s.effects &= ~EF_FLIES; + self->enemy->monsterinfo.healer = NULL; + + if ((self->oldenemy) && (self->oldenemy->inuse) && (self->oldenemy->health > 0)) + { +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("setting heal target's enemy to %s\n", self->oldenemy->classname); + self->enemy->enemy = self->oldenemy; +// HuntTarget (self->enemy); + FoundTarget (self->enemy); + } + else + { +// if (g_showlogic && g_showlogic->value) +// gi.dprintf ("no valid enemy to set!\n"); + self->enemy->enemy = NULL; + if (!FindTarget (self->enemy)) + { + // no valid enemy, so stop acting + self->enemy->monsterinfo.pausetime = level.time + 100000000; + self->enemy->monsterinfo.stand (self->enemy); + } + self->enemy = NULL; + self->oldenemy = NULL; + if (!FindTarget (self)) + { + // no valid enemy, so stop acting + self->monsterinfo.pausetime = level.time + 100000000; + self->monsterinfo.stand (self); + return; + } + } + } + } + else + { + if (self->s.frame == FRAME_attack44) + // PMM - medic commander sounds + if (self->mass == 400) + gi.sound (self, CHAN_WEAPON, sound_hook_heal, 1, ATTN_NORM, 0); + else + gi.sound (self, CHAN_WEAPON, commander_sound_hook_heal, 1, ATTN_NORM, 0); + } + + // adjust start for beam origin being in middle of a segment + VectorMA (start, 8, f, start); + + // adjust end z for end spot since the monster is currently dead + VectorCopy (self->enemy->s.origin, end); + end[2] = self->enemy->absmin[2] + self->enemy->size[2] / 2; + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_MEDIC_CABLE_ATTACK); + gi.WriteShort (self - g_edicts); + gi.WritePosition (start); + gi.WritePosition (end); + gi.multicast (self->s.origin, MULTICAST_PVS); +} + +void medic_hook_retract (edict_t *self) +{ + if (self->mass == 400) + gi.sound (self, CHAN_WEAPON, sound_hook_retract, 1, ATTN_NORM, 0); + else + gi.sound (self, CHAN_WEAPON, sound_hook_retract, 1, ATTN_NORM, 0); + + self->monsterinfo.aiflags &= ~AI_MEDIC; + if ((self->oldenemy) && (self->oldenemy->inuse)) + self->enemy = self->oldenemy; + else + { + self->enemy = NULL; + self->oldenemy = NULL; + if (!FindTarget (self)) + { + // no valid enemy, so stop acting + self->monsterinfo.pausetime = level.time + 100000000; + self->monsterinfo.stand (self); + return; + } + } +} + +mframe_t medic_frames_attackCable [] = +{ +// ROGUE - negated 36-40 so he scoots back from his target a little +// ROGUE - switched 33-36 to ai_charge +// ROGUE - changed frame 52 to 0 to compensate for changes in 36-40 + ai_charge, 2, NULL, //33 + ai_charge, 3, NULL, + ai_charge, 5, NULL, + ai_charge, -4.4, NULL, //36 + ai_charge, -4.7, NULL, //37 + ai_charge, -5, NULL, + ai_charge, -6, NULL, + ai_charge, -4, NULL, //40 + ai_charge, 0, NULL, + ai_move, 0, medic_hook_launch, //42 + ai_move, 0, medic_cable_attack, //43 + ai_move, 0, medic_cable_attack, + ai_move, 0, medic_cable_attack, + ai_move, 0, medic_cable_attack, + ai_move, 0, medic_cable_attack, + ai_move, 0, medic_cable_attack, + ai_move, 0, medic_cable_attack, + ai_move, 0, medic_cable_attack, + ai_move, 0, medic_cable_attack, //51 + ai_move, 0, medic_hook_retract, //52 + ai_move, -1.5, NULL, + ai_move, -1.2, NULL, + ai_move, -3, NULL, + ai_move, -2, NULL, + ai_move, 0.3, NULL, + ai_move, 0.7, NULL, + ai_move, 1.2, NULL, + ai_move, 1.3, NULL //60 +}; +mmove_t medic_move_attackCable = {FRAME_attack33, FRAME_attack60, medic_frames_attackCable, medic_run}; + + +void medic_start_spawn (edict_t *self) +{ + gi.sound (self, CHAN_WEAPON, commander_sound_spawn, 1, ATTN_NORM, 0); + self->monsterinfo.nextframe = FRAME_attack48; +} + +void medic_determine_spawn (edict_t *self) +{ + vec3_t f, r, offset, startpoint, spawnpoint; + float lucky; + int summonStr; + int count; + int inc; + int num_summoned; // should be 1, 3, or 5 + int num_success = 0; + + lucky = random(); + summonStr = skill->value; + + // bell curve - 0 = 67%, 1 = 93%, 2 = 99% -- too steep + // this ends up with + // -3 = 5% + // -2 = 10% + // -1 = 15% + // 0 = 40% + // +1 = 15% + // +2 = 10% + // +3 = 5% + if (lucky < 0.05) + summonStr -= 3; + else if (lucky < 0.15) + summonStr -= 2; + else if (lucky < 0.3) + summonStr -= 1; + else if (lucky > 0.95) + summonStr += 3; + else if (lucky > 0.85) + summonStr += 2; + else if (lucky > 0.7) + summonStr += 1; + + if (summonStr < 0) + summonStr = 0; + + //FIXME - need to remember this, might as well use this int that isn't used for monsters + self->plat2flags = summonStr; + AngleVectors (self->s.angles, f, r, NULL); + +// this yields either 1, 3, or 5 + if (summonStr) + num_summoned = (summonStr - 1) + (summonStr % 2); + else + num_summoned = 1; + +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("medic_commander: summonStr = %d num = %d\n", summonStr, num_summoned); + + for (count = 0; count < num_summoned; count++) + { + inc = count + (count%2); // 0, 2, 2, 4, 4 + VectorCopy (reinforcement_position[count], offset); + + G_ProjectSource (self->s.origin, offset, f, r, startpoint); + // a little off the ground + startpoint[2] += 10; + + if (FindSpawnPoint (startpoint, reinforcement_mins[summonStr-inc], reinforcement_maxs[summonStr-inc], spawnpoint, 32)) + { + if (CheckGroundSpawnPoint(spawnpoint, + reinforcement_mins[summonStr-inc], reinforcement_maxs[summonStr-inc], + 256, -1)) + { + num_success++; + // we found a spot, we're done here + count = num_summoned; + } +// else if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("medic_commander: CheckGroundSpawnPoint says bad stuff down there!\n"); + } + } + + if (num_success == 0) + { + for (count = 0; count < num_summoned; count++) + { + inc = count + (count%2); // 0, 2, 2, 4, 4 + VectorCopy (reinforcement_position[count], offset); + + // check behind + offset[0] *= -1.0; + offset[1] *= -1.0; + G_ProjectSource (self->s.origin, offset, f, r, startpoint); + // a little off the ground + startpoint[2] += 10; + + if (FindSpawnPoint (startpoint, reinforcement_mins[summonStr-inc], reinforcement_maxs[summonStr-inc], spawnpoint, 32)) + { + if (CheckGroundSpawnPoint(spawnpoint, + reinforcement_mins[summonStr-inc], reinforcement_maxs[summonStr-inc], + 256, -1)) + { + num_success++; + // we found a spot, we're done here + count = num_summoned; + } + } + } + + if (num_success) + { + self->monsterinfo.aiflags |= AI_MANUAL_STEERING; + self->ideal_yaw = anglemod(self->s.angles[YAW]) + 180; + if (self->ideal_yaw > 360.0) + self->ideal_yaw -= 360.0; +// self->plat2flags *= -1; + } + } + + if (num_success == 0) + { +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("medic_commander: failed to find any spawn points, aborting!\n"); + self->monsterinfo.nextframe = FRAME_attack53; + } +} + +void medic_spawngrows (edict_t *self) +{ + vec3_t f, r, offset, startpoint, spawnpoint; + int summonStr; + int count; + int inc; + int num_summoned; // should be 1, 3, or 5 + int num_success = 0; + float current_yaw; + qboolean behind = false; + + // if we've been directed to turn around + if (self->monsterinfo.aiflags & AI_MANUAL_STEERING) + { + current_yaw = anglemod(self->s.angles[YAW]); + if (fabs(current_yaw - self->ideal_yaw) > 0.1) + { + self->monsterinfo.aiflags |= AI_HOLD_FRAME; + return; + } + + // done turning around + self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; + self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING; + } + +// if (self->plat2flags < 0) +// { +// summonStr = -1.0 * self->plat2flags; +// behind = true; +// } +// else + summonStr = self->plat2flags; + + AngleVectors (self->s.angles, f, r, NULL); + +// num_summoned = ((((summonStr-1)/2)+1)*2)-1; // this yields either 1, 3, or 5 + if (summonStr) + num_summoned = (summonStr - 1) + (summonStr % 2); + else + num_summoned = 1; + + for (count = 0; count < num_summoned; count++) + { + inc = count + (count%2); // 0, 2, 2, 4, 4 + VectorCopy (reinforcement_position[count], offset); + + G_ProjectSource (self->s.origin, offset, f, r, startpoint); + // a little off the ground + startpoint[2] += 10; + + if (FindSpawnPoint (startpoint, reinforcement_mins[summonStr-inc], reinforcement_maxs[summonStr-inc], spawnpoint, 32)) + { + if (CheckGroundSpawnPoint(spawnpoint, + reinforcement_mins[summonStr-inc], reinforcement_maxs[summonStr-inc], + 256, -1)) + { + num_success++; + if ((summonStr-inc) > 3) + SpawnGrow_Spawn (spawnpoint, 1); // big monster + else + SpawnGrow_Spawn (spawnpoint, 0); // normal size + } + } + } + + if (num_success == 0) + { +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("medic_commander: spawngrows bad, aborting!\n"); + self->monsterinfo.nextframe = FRAME_attack53; + } +} + +void medic_finish_spawn (edict_t *self) +{ + edict_t *ent; + vec3_t f, r, offset, startpoint, spawnpoint; + int summonStr; + int count; + int inc; + int num_summoned; // should be 1, 3, or 5 + qboolean behind = false; + edict_t *designated_enemy; + +// trace_t tr; +// vec3_t mins, maxs; + + // this is one bigger than the soldier's real mins .. just for paranoia's sake +// VectorSet (mins, -17, -17, -25); +// VectorSet (maxs, 17, 17, 33); + + //FIXME - better place to store this info? + if (self->plat2flags < 0) + { + behind = true; + self->plat2flags *= -1; + } + summonStr = self->plat2flags; + + AngleVectors (self->s.angles, f, r, NULL); + +// num_summoned = ((((summonStr-1)/2)+1)*2)-1; // this yields either 1, 3, or 5 + if (summonStr) + num_summoned = (summonStr - 1) + (summonStr % 2); + else + num_summoned = 1; + +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("medic_commander: summonStr = %d num = %d\n", summonStr, num_summoned); + + for (count = 0; count < num_summoned; count++) + { + inc = count + (count%2); // 0, 2, 2, 4, 4 + VectorCopy (reinforcement_position[count], offset); + + G_ProjectSource (self->s.origin, offset, f, r, startpoint); + + // a little off the ground + startpoint[2] += 10; + + ent = NULL; + if (FindSpawnPoint (startpoint, reinforcement_mins[summonStr-inc], reinforcement_maxs[summonStr-inc], spawnpoint, 32)) + { + if (CheckSpawnPoint (spawnpoint, reinforcement_mins[summonStr-inc], reinforcement_maxs[summonStr-inc])) + ent = CreateGroundMonster (spawnpoint, self->s.angles, + reinforcement_mins[summonStr-inc], reinforcement_maxs[summonStr-inc], + reinforcements[summonStr-inc], 256); +// else if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("CheckSpawnPoint failed volume check!\n"); + } + else + { +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("FindSpawnPoint failed to find a point!\n"); + } + + if (!ent) + { +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("Spawn point obstructed for %s, aborting!\n", reinforcements[summonStr-inc]); + continue; + } + +// gi.sound (self, CHAN_WEAPON, commander_sound_spawn, 1, ATTN_NORM, 0); + + if (ent->think) + { + ent->nextthink = level.time; + ent->think (ent); + } + + ent->monsterinfo.aiflags |= AI_IGNORE_SHOTS|AI_DO_NOT_COUNT|AI_SPAWNED_MEDIC_C; + ent->monsterinfo.commander = self; + self->monsterinfo.monster_slots--; +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("medic_commander: %d slots remaining\n", self->monsterinfo.monster_slots); + + if (self->monsterinfo.aiflags & AI_MEDIC) + designated_enemy = self->oldenemy; + else + designated_enemy = self->enemy; + + if (coop && coop->value) + { + designated_enemy = PickCoopTarget(ent); + if (designated_enemy) + { + // try to avoid using my enemy + if (designated_enemy == self->enemy) + { + designated_enemy = PickCoopTarget(ent); + if (designated_enemy) + { +// if ((g_showlogic) && (g_showlogic->value)) +// { +// gi.dprintf ("PickCoopTarget returned a %s - ", designated_enemy->classname); +// if (designated_enemy->client) +// gi.dprintf ("with name %s\n", designated_enemy->client->pers.netname); +// else +// gi.dprintf ("NOT A CLIENT\n"); +// } + } + else + { +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("pick coop failed, using my current enemy\n"); + designated_enemy = self->enemy; + } + } + } + else + { +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("pick coop failed, using my current enemy\n"); + designated_enemy = self->enemy; + } + } + + if ((designated_enemy) && (designated_enemy->inuse) && (designated_enemy->health > 0)) + { + // fixme +// if ((g_showlogic) && (g_showlogic -> value)) +// gi.dprintf ("setting enemy to %s\n", designated_enemy->classname); + ent->enemy = designated_enemy; + FoundTarget (ent); + } + else + { + ent->enemy = NULL; + ent->monsterinfo.stand (ent); + } +// ent->s.event = EV_PLAYER_TELEPORT; + } +} + +mframe_t medic_frames_callReinforcements [] = +{ + // ROGUE - 33-36 now ai_charge + ai_charge, 2, NULL, //33 + ai_charge, 3, NULL, + ai_charge, 5, NULL, + ai_charge, 4.4, NULL, //36 + ai_charge, 4.7, NULL, + ai_charge, 5, NULL, + ai_charge, 6, NULL, + ai_charge, 4, NULL, //40 + ai_charge, 0, NULL, + ai_move, 0, medic_start_spawn, //42 + ai_move, 0, NULL, //43 -- 43 through 47 are skipped + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, medic_determine_spawn, //48 + ai_charge, 0, medic_spawngrows, //49 + ai_move, 0, NULL, //50 + ai_move, 0, NULL, //51 + ai_move, -15, medic_finish_spawn, //52 + ai_move, -1.5, NULL, + ai_move, -1.2, NULL, + ai_move, -3, NULL, + ai_move, -2, NULL, + ai_move, 0.3, NULL, + ai_move, 0.7, NULL, + ai_move, 1.2, NULL, + ai_move, 1.3, NULL //60 +}; +mmove_t medic_move_callReinforcements = {FRAME_attack33, FRAME_attack60, medic_frames_callReinforcements, medic_run}; + +void medic_attack(edict_t *self) +{ + int enemy_range; + float r; + + monster_done_dodge (self); + + enemy_range = range(self, self->enemy); + + // signal from checkattack to spawn + if (self->monsterinfo.aiflags & AI_BLOCKED) + { + self->monsterinfo.currentmove = &medic_move_callReinforcements; + self->monsterinfo.aiflags &= ~AI_BLOCKED; + } + + r = random(); + if (self->monsterinfo.aiflags & AI_MEDIC) + { + if ((self->mass > 400) && (r > 0.8) && (self->monsterinfo.monster_slots > 2)) + self->monsterinfo.currentmove = &medic_move_callReinforcements; + else + self->monsterinfo.currentmove = &medic_move_attackCable; + } + else + { + if (self->monsterinfo.attack_state == AS_BLIND) + { + self->monsterinfo.currentmove = &medic_move_callReinforcements; + return; + } + if ((self->mass > 400) && (r > 0.2) && (enemy_range != RANGE_MELEE) && (self->monsterinfo.monster_slots > 2)) + self->monsterinfo.currentmove = &medic_move_callReinforcements; + else + self->monsterinfo.currentmove = &medic_move_attackBlaster; + } +} + +qboolean medic_checkattack (edict_t *self) +{ + if (self->monsterinfo.aiflags & AI_MEDIC) + { + // if our target went away + if ((!self->enemy) || (!self->enemy->inuse)) + { +// if (g_showlogic && g_showlogic->value) +// gi.dprintf ("aborting heal target due to gib\n"); + abortHeal (self, true, false, false); + return false; + } + + // if we ran out of time, give up + if (self->timestamp < level.time) + { +// if (g_showlogic && g_showlogic->value) +// gi.dprintf ("aborting heal target (%s) due to time\n", self->enemy->classname); + abortHeal (self, true, false, true); + self->timestamp = 0; + return false; + } + + if (realrange(self, self->enemy) < MEDIC_MAX_HEAL_DISTANCE+10) + { + medic_attack(self); + return true; + } + else + { + self->monsterinfo.attack_state = AS_STRAIGHT; + return false; + } + } + + if (self->enemy->client && !visible (self, self->enemy) && (self->monsterinfo.monster_slots > 2)) + { + self->monsterinfo.attack_state = AS_BLIND; + return true; + } + + // give a LARGE bias to spawning things when we have room + // use AI_BLOCKED as a signal to attack to spawn + if ((random() < 0.8) && (self->monsterinfo.monster_slots > 5) && (realrange(self, self->enemy) > 150)) + { + self->monsterinfo.aiflags |= AI_BLOCKED; + self->monsterinfo.attack_state = AS_MISSILE; + return true; + } + + // ROGUE + // since his idle animation looks kinda bad in combat, if we're not in easy mode, always attack + // when he's on a combat point + if (skill->value > 0) + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + { + self->monsterinfo.attack_state = AS_MISSILE; + return true; + } + + return M_CheckAttack (self); +} +/* +void medic_dodge (edict_t *self, edict_t *attacker, float eta, trace_t *tr) +{ + + if (random() > 0.25) + return; + + if (!self->enemy) + self->enemy = attacker; + + self->monsterinfo.currentmove = &medic_move_duck; + +//=========== +//PMM - rogue rewrite of dodge code. + float r; + float height; + int shooting = 0; + + // this needs to be before the AI_MEDIC check, because + // a) if AI_MEDIC is set, we should have an enemy anyway and + // b) FoundTarget calls medic_run, which can set AI_MEDIC + if (!self->enemy) + { + self->enemy = attacker; + FoundTarget (self); + } + +// don't dodge if you're healing + if (self->monsterinfo.aiflags & AI_MEDIC) + return; + + // PMM - don't bother if it's going to hit anyway; fix for weird in-your-face etas (I was + // seeing numbers like 13 and 14) + if ((eta < 0.1) || (eta > 5)) + return; + + r = random(); + if (r > (0.25*((skill->value)+1))) + return; + + if ((self->monsterinfo.currentmove == &medic_move_attackHyperBlaster) || + (self->monsterinfo.currentmove == &medic_move_attackCable) || + (self->monsterinfo.currentmove == &medic_move_attackBlaster)) + { + shooting = 1; + } + if (self->monsterinfo.aiflags & AI_DODGING) + { + height = self->absmax[2]; + } + else + { + height = self->absmax[2]-32-1; // the -1 is because the absmax is s.origin + maxs + 1 + } + + // check to see if it makes sense to duck + if (tr->endpos[2] <= height) + { + vec3_t right, diff; + if (shooting) + { + self->monsterinfo.attack_state = AS_SLIDING; + return; + } + AngleVectors (self->s.angles, NULL, right, NULL); + VectorSubtract (tr->endpos, self->s.origin, diff); + if (DotProduct (right, diff) < 0) + { + self->monsterinfo.lefty = 1; + } + // if it doesn't sense to duck, try to strafe away + monster_done_dodge (self); + self->monsterinfo.currentmove = &medic_move_run; + self->monsterinfo.attack_state = AS_SLIDING; + return; + } + + if (skill->value == 0) + { + self->monsterinfo.currentmove = &medic_move_duck; + // PMM - stupid dodge + self->monsterinfo.duck_wait_time = level.time + eta + 1; + self->monsterinfo.aiflags |= AI_DODGING; + return; + } + + if (!shooting) + { + self->monsterinfo.currentmove = &medic_move_duck; + self->monsterinfo.duck_wait_time = level.time + eta + (0.1 * (3 - skill->value)); + self->monsterinfo.aiflags |= AI_DODGING; + } + return; + +//PMM +//=========== + +} +*/ +void MedicCommanderCache (void) +{ + edict_t *newEnt; + int modelidx; + int i; + + //FIXME - better way to do this? this is quick and dirty + for (i=0; i < 7; i++) + { + newEnt = G_Spawn(); + + VectorCopy(vec3_origin, newEnt->s.origin); + VectorCopy(vec3_origin, newEnt->s.angles); + newEnt->classname = ED_NewString (reinforcements[i]); + + newEnt->monsterinfo.aiflags |= AI_DO_NOT_COUNT; + + ED_CallSpawn(newEnt); + // FIXME - could copy mins/maxs into reinforcements from here + G_FreeEdict (newEnt); + } + + modelidx = gi.modelindex("models/items/spawngro/tris.md2"); + modelidx = gi.modelindex("models/items/spawngro2/tris.md2"); +} + +void medic_duck (edict_t *self, float eta) +{ +// don't dodge if you're healing + if (self->monsterinfo.aiflags & AI_MEDIC) + return; + + if ((self->monsterinfo.currentmove == &medic_move_attackHyperBlaster) || + (self->monsterinfo.currentmove == &medic_move_attackCable) || + (self->monsterinfo.currentmove == &medic_move_attackBlaster) || + (self->monsterinfo.currentmove == &medic_move_callReinforcements)) + { + // he ignores skill + self->monsterinfo.aiflags &= ~AI_DUCKED; + return; + } + + if (skill->value == 0) + // PMM - stupid dodge + self->monsterinfo.duck_wait_time = level.time + eta + 1; + else + self->monsterinfo.duck_wait_time = level.time + eta + (0.1 * (3 - skill->value)); + + // has to be done immediately otherwise he can get stuck + monster_duck_down(self); + + self->monsterinfo.nextframe = FRAME_duck1; + self->monsterinfo.currentmove = &medic_move_duck; + return; +} + +void medic_sidestep (edict_t *self) +{ + if ((self->monsterinfo.currentmove == &medic_move_attackHyperBlaster) || + (self->monsterinfo.currentmove == &medic_move_attackCable) || + (self->monsterinfo.currentmove == &medic_move_attackBlaster) || + (self->monsterinfo.currentmove == &medic_move_callReinforcements)) + { + // if we're shooting, and not on easy, don't dodge + if (skill->value) + { + self->monsterinfo.aiflags &= ~AI_DODGING; + return; + } + } + + if (self->monsterinfo.currentmove != &medic_move_run) + self->monsterinfo.currentmove = &medic_move_run; +} + +//=========== +//PGM +qboolean medic_blocked (edict_t *self, float dist) +{ + if(blocked_checkshot (self, 0.25 + (0.05 * skill->value) )) + return true; + + if(blocked_checkplat (self, dist)) + return true; + + return false; +} +//PGM +//=========== + +/*QUAKED monster_medic_commander (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight +*/ +/*QUAKED monster_medic (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight +*/ +void SP_monster_medic (edict_t *self) +{ + if (deathmatch->value) + { + G_FreeEdict (self); + return; + } + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex ("models/monsters/medic/tris.md2"); + VectorSet (self->mins, -24, -24, -24); + VectorSet (self->maxs, 24, 24, 32); + +//PMM + if (strcmp(self->classname, "monster_medic_commander") == 0) + { + self->health = 600; // fixme + self->gib_health = -130; + self->mass = 600; + self->yaw_speed = 40; // default is 20 + MedicCommanderCache(); +// self->s.skinnum = 2; + } + else + { +//PMM + self->health = 300; + self->gib_health = -130; + self->mass = 400; +// self->s.skinnum = 0; + } + + self->pain = medic_pain; + self->die = medic_die; + + self->monsterinfo.stand = medic_stand; + self->monsterinfo.walk = medic_walk; + self->monsterinfo.run = medic_run; + // pmm + self->monsterinfo.dodge = M_MonsterDodge; + self->monsterinfo.duck = medic_duck; + self->monsterinfo.unduck = monster_duck_up; + self->monsterinfo.sidestep = medic_sidestep; +// self->monsterinfo.dodge = medic_dodge; + // pmm + self->monsterinfo.attack = medic_attack; + self->monsterinfo.melee = NULL; + self->monsterinfo.sight = medic_sight; + self->monsterinfo.idle = medic_idle; + self->monsterinfo.search = medic_search; + self->monsterinfo.checkattack = medic_checkattack; + self->monsterinfo.blocked = medic_blocked; + + gi.linkentity (self); + + self->monsterinfo.currentmove = &medic_move_stand; + self->monsterinfo.scale = MODEL_SCALE; + + walkmonster_start (self); + + //PMM + self->monsterinfo.aiflags |= AI_IGNORE_SHOTS; + + + if (self->mass > 400) + { + self->s.skinnum = 2; + if (skill->value == 0) + self->monsterinfo.monster_slots = 3; + else if (skill->value == 1) + self->monsterinfo.monster_slots = 4; + else if (skill->value == 2) + self->monsterinfo.monster_slots = 6; + else if (skill->value == 3) + self->monsterinfo.monster_slots = 6; + // commander sounds + commander_sound_idle1 = gi.soundindex ("medic_commander/medidle.wav"); + commander_sound_pain1 = gi.soundindex ("medic_commander/medpain1.wav"); + commander_sound_pain2 = gi.soundindex ("medic_commander/medpain2.wav"); + commander_sound_die = gi.soundindex ("medic_commander/meddeth.wav"); + commander_sound_sight = gi.soundindex ("medic_commander/medsght.wav"); + commander_sound_search = gi.soundindex ("medic_commander/medsrch.wav"); + commander_sound_hook_launch = gi.soundindex ("medic_commander/medatck2c.wav"); + commander_sound_hook_hit = gi.soundindex ("medic_commander/medatck3a.wav"); + commander_sound_hook_heal = gi.soundindex ("medic_commander/medatck4a.wav"); + commander_sound_hook_retract = gi.soundindex ("medic_commander/medatck5a.wav"); + commander_sound_spawn = gi.soundindex ("medic_commander/monsterspawn1.wav"); + gi.soundindex ("tank/tnkatck3.wav"); + } + else + { + sound_idle1 = gi.soundindex ("medic/idle.wav"); + sound_pain1 = gi.soundindex ("medic/medpain1.wav"); + sound_pain2 = gi.soundindex ("medic/medpain2.wav"); + sound_die = gi.soundindex ("medic/meddeth1.wav"); + sound_sight = gi.soundindex ("medic/medsght1.wav"); + sound_search = gi.soundindex ("medic/medsrch1.wav"); + sound_hook_launch = gi.soundindex ("medic/medatck2.wav"); + sound_hook_hit = gi.soundindex ("medic/medatck3.wav"); + sound_hook_heal = gi.soundindex ("medic/medatck4.wav"); + sound_hook_retract = gi.soundindex ("medic/medatck5.wav"); + gi.soundindex ("medic/medatck1.wav"); + + self->s.skinnum = 0; + } + //pmm +} diff --git a/original/rogue/m_medic.h b/original/rogue/m_medic.h new file mode 100644 index 0000000..947e824 --- /dev/null +++ b/original/rogue/m_medic.h @@ -0,0 +1,245 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// G:\quake2\baseq2\models/monsters/medic + +// This file generated by ModelGen - Do NOT Modify + +#define FRAME_walk1 0 +#define FRAME_walk2 1 +#define FRAME_walk3 2 +#define FRAME_walk4 3 +#define FRAME_walk5 4 +#define FRAME_walk6 5 +#define FRAME_walk7 6 +#define FRAME_walk8 7 +#define FRAME_walk9 8 +#define FRAME_walk10 9 +#define FRAME_walk11 10 +#define FRAME_walk12 11 +#define FRAME_wait1 12 +#define FRAME_wait2 13 +#define FRAME_wait3 14 +#define FRAME_wait4 15 +#define FRAME_wait5 16 +#define FRAME_wait6 17 +#define FRAME_wait7 18 +#define FRAME_wait8 19 +#define FRAME_wait9 20 +#define FRAME_wait10 21 +#define FRAME_wait11 22 +#define FRAME_wait12 23 +#define FRAME_wait13 24 +#define FRAME_wait14 25 +#define FRAME_wait15 26 +#define FRAME_wait16 27 +#define FRAME_wait17 28 +#define FRAME_wait18 29 +#define FRAME_wait19 30 +#define FRAME_wait20 31 +#define FRAME_wait21 32 +#define FRAME_wait22 33 +#define FRAME_wait23 34 +#define FRAME_wait24 35 +#define FRAME_wait25 36 +#define FRAME_wait26 37 +#define FRAME_wait27 38 +#define FRAME_wait28 39 +#define FRAME_wait29 40 +#define FRAME_wait30 41 +#define FRAME_wait31 42 +#define FRAME_wait32 43 +#define FRAME_wait33 44 +#define FRAME_wait34 45 +#define FRAME_wait35 46 +#define FRAME_wait36 47 +#define FRAME_wait37 48 +#define FRAME_wait38 49 +#define FRAME_wait39 50 +#define FRAME_wait40 51 +#define FRAME_wait41 52 +#define FRAME_wait42 53 +#define FRAME_wait43 54 +#define FRAME_wait44 55 +#define FRAME_wait45 56 +#define FRAME_wait46 57 +#define FRAME_wait47 58 +#define FRAME_wait48 59 +#define FRAME_wait49 60 +#define FRAME_wait50 61 +#define FRAME_wait51 62 +#define FRAME_wait52 63 +#define FRAME_wait53 64 +#define FRAME_wait54 65 +#define FRAME_wait55 66 +#define FRAME_wait56 67 +#define FRAME_wait57 68 +#define FRAME_wait58 69 +#define FRAME_wait59 70 +#define FRAME_wait60 71 +#define FRAME_wait61 72 +#define FRAME_wait62 73 +#define FRAME_wait63 74 +#define FRAME_wait64 75 +#define FRAME_wait65 76 +#define FRAME_wait66 77 +#define FRAME_wait67 78 +#define FRAME_wait68 79 +#define FRAME_wait69 80 +#define FRAME_wait70 81 +#define FRAME_wait71 82 +#define FRAME_wait72 83 +#define FRAME_wait73 84 +#define FRAME_wait74 85 +#define FRAME_wait75 86 +#define FRAME_wait76 87 +#define FRAME_wait77 88 +#define FRAME_wait78 89 +#define FRAME_wait79 90 +#define FRAME_wait80 91 +#define FRAME_wait81 92 +#define FRAME_wait82 93 +#define FRAME_wait83 94 +#define FRAME_wait84 95 +#define FRAME_wait85 96 +#define FRAME_wait86 97 +#define FRAME_wait87 98 +#define FRAME_wait88 99 +#define FRAME_wait89 100 +#define FRAME_wait90 101 +#define FRAME_run1 102 +#define FRAME_run2 103 +#define FRAME_run3 104 +#define FRAME_run4 105 +#define FRAME_run5 106 +#define FRAME_run6 107 +#define FRAME_paina1 108 +#define FRAME_paina2 109 +#define FRAME_paina3 110 +#define FRAME_paina4 111 +#define FRAME_paina5 112 +#define FRAME_paina6 113 +#define FRAME_paina7 114 +#define FRAME_paina8 115 +#define FRAME_painb1 116 +#define FRAME_painb2 117 +#define FRAME_painb3 118 +#define FRAME_painb4 119 +#define FRAME_painb5 120 +#define FRAME_painb6 121 +#define FRAME_painb7 122 +#define FRAME_painb8 123 +#define FRAME_painb9 124 +#define FRAME_painb10 125 +#define FRAME_painb11 126 +#define FRAME_painb12 127 +#define FRAME_painb13 128 +#define FRAME_painb14 129 +#define FRAME_painb15 130 +#define FRAME_duck1 131 +#define FRAME_duck2 132 +#define FRAME_duck3 133 +#define FRAME_duck4 134 +#define FRAME_duck5 135 +#define FRAME_duck6 136 +#define FRAME_duck7 137 +#define FRAME_duck8 138 +#define FRAME_duck9 139 +#define FRAME_duck10 140 +#define FRAME_duck11 141 +#define FRAME_duck12 142 +#define FRAME_duck13 143 +#define FRAME_duck14 144 +#define FRAME_duck15 145 +#define FRAME_duck16 146 +#define FRAME_death1 147 +#define FRAME_death2 148 +#define FRAME_death3 149 +#define FRAME_death4 150 +#define FRAME_death5 151 +#define FRAME_death6 152 +#define FRAME_death7 153 +#define FRAME_death8 154 +#define FRAME_death9 155 +#define FRAME_death10 156 +#define FRAME_death11 157 +#define FRAME_death12 158 +#define FRAME_death13 159 +#define FRAME_death14 160 +#define FRAME_death15 161 +#define FRAME_death16 162 +#define FRAME_death17 163 +#define FRAME_death18 164 +#define FRAME_death19 165 +#define FRAME_death20 166 +#define FRAME_death21 167 +#define FRAME_death22 168 +#define FRAME_death23 169 +#define FRAME_death24 170 +#define FRAME_death25 171 +#define FRAME_death26 172 +#define FRAME_death27 173 +#define FRAME_death28 174 +#define FRAME_death29 175 +#define FRAME_death30 176 +#define FRAME_attack1 177 +#define FRAME_attack2 178 +#define FRAME_attack3 179 +#define FRAME_attack4 180 +#define FRAME_attack5 181 +#define FRAME_attack6 182 +#define FRAME_attack7 183 +#define FRAME_attack8 184 +#define FRAME_attack9 185 +#define FRAME_attack10 186 +#define FRAME_attack11 187 +#define FRAME_attack12 188 +#define FRAME_attack13 189 +#define FRAME_attack14 190 +#define FRAME_attack15 191 +#define FRAME_attack16 192 +#define FRAME_attack17 193 +#define FRAME_attack18 194 +#define FRAME_attack19 195 +#define FRAME_attack20 196 +#define FRAME_attack21 197 +#define FRAME_attack22 198 +#define FRAME_attack23 199 +#define FRAME_attack24 200 +#define FRAME_attack25 201 +#define FRAME_attack26 202 +#define FRAME_attack27 203 +#define FRAME_attack28 204 +#define FRAME_attack29 205 +#define FRAME_attack30 206 +#define FRAME_attack31 207 +#define FRAME_attack32 208 +#define FRAME_attack33 209 +#define FRAME_attack34 210 +#define FRAME_attack35 211 +#define FRAME_attack36 212 +#define FRAME_attack37 213 +#define FRAME_attack38 214 +#define FRAME_attack39 215 +#define FRAME_attack40 216 +#define FRAME_attack41 217 +#define FRAME_attack42 218 +#define FRAME_attack43 219 +#define FRAME_attack44 220 +#define FRAME_attack45 221 +#define FRAME_attack46 222 +#define FRAME_attack47 223 +#define FRAME_attack48 224 +#define FRAME_attack49 225 +#define FRAME_attack50 226 +#define FRAME_attack51 227 +#define FRAME_attack52 228 +#define FRAME_attack53 229 +#define FRAME_attack54 230 +#define FRAME_attack55 231 +#define FRAME_attack56 232 +#define FRAME_attack57 233 +#define FRAME_attack58 234 +#define FRAME_attack59 235 +#define FRAME_attack60 236 + +#define MODEL_SCALE 1.000000 diff --git a/original/rogue/m_move.c b/original/rogue/m_move.c new file mode 100644 index 0000000..42f698b --- /dev/null +++ b/original/rogue/m_move.c @@ -0,0 +1,847 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// m_move.c -- monster movement + +#include "g_local.h" + +#define STEPSIZE 18 + +// this is used for communications out of sv_movestep to say what entity +// is blocking us +edict_t *new_bad; //pmm + +/* +============= +M_CheckBottom + +Returns false if any part of the bottom of the entity is off an edge that +is not a staircase. + +============= +*/ +int c_yes, c_no; + +qboolean M_CheckBottom (edict_t *ent) +{ + vec3_t mins, maxs, start, stop; + trace_t trace; + int x, y; + float mid, bottom; + + VectorAdd (ent->s.origin, ent->mins, mins); + VectorAdd (ent->s.origin, ent->maxs, maxs); + +// if all of the points under the corners are solid world, don't bother +// with the tougher checks +// the corners must be within 16 of the midpoint + +//PGM +#ifdef ROGUE_GRAVITY + // FIXME - this will only handle 0,0,1 and 0,0,-1 gravity vectors + start[2] = mins[2] - 1; + if(ent->gravityVector[2] > 0) + start[2] = maxs[2] + 1; +#else + start[2] = mins[2] - 1; +#endif +//PGM + + for (x=0 ; x<=1 ; x++) + for (y=0 ; y<=1 ; y++) + { + start[0] = x ? maxs[0] : mins[0]; + start[1] = y ? maxs[1] : mins[1]; + if (gi.pointcontents (start) != CONTENTS_SOLID) + goto realcheck; + } + + c_yes++; + return true; // we got out easy + +realcheck: + c_no++; +// +// check it for real... +// + start[2] = mins[2]; + +// the midpoint must be within 16 of the bottom + start[0] = stop[0] = (mins[0] + maxs[0])*0.5; + start[1] = stop[1] = (mins[1] + maxs[1])*0.5; + +//PGM +#ifdef ROGUE_GRAVITY + if(ent->gravityVector[2] < 0) + { + start[2] = mins[2]; + stop[2] = start[2] - STEPSIZE - STEPSIZE; + } + else + { + start[2] = maxs[2]; + stop[2] = start[2] + STEPSIZE + STEPSIZE; + } +#else + stop[2] = start[2] - 2*STEPSIZE; +#endif +//PGM + + trace = gi.trace (start, vec3_origin, vec3_origin, stop, ent, MASK_MONSTERSOLID); + + if (trace.fraction == 1.0) + return false; + mid = bottom = trace.endpos[2]; + +// the corners must be within 16 of the midpoint + for (x=0 ; x<=1 ; x++) + for (y=0 ; y<=1 ; y++) + { + start[0] = stop[0] = x ? maxs[0] : mins[0]; + start[1] = stop[1] = y ? maxs[1] : mins[1]; + + trace = gi.trace (start, vec3_origin, vec3_origin, stop, ent, MASK_MONSTERSOLID); + +//PGM +#ifdef ROGUE_GRAVITY + // FIXME - this will only handle 0,0,1 and 0,0,-1 gravity vectors + if(ent->gravityVector[2] > 0) + { + if (trace.fraction != 1.0 && trace.endpos[2] < bottom) + bottom = trace.endpos[2]; + if (trace.fraction == 1.0 || trace.endpos[2] - mid > STEPSIZE) + return false; + } + else + { + if (trace.fraction != 1.0 && trace.endpos[2] > bottom) + bottom = trace.endpos[2]; + if (trace.fraction == 1.0 || mid - trace.endpos[2] > STEPSIZE) + return false; + } +#else + if (trace.fraction != 1.0 && trace.endpos[2] > bottom) + bottom = trace.endpos[2]; + if (trace.fraction == 1.0 || mid - trace.endpos[2] > STEPSIZE) + return false; +#endif +//PGM + } + + c_yes++; + return true; +} + +//============ +// ROGUE +qboolean IsBadAhead (edict_t *self, edict_t *bad, vec3_t move) +{ + vec3_t dir; + vec3_t forward; + float dp_bad, dp_move; + vec3_t move_copy; + + VectorCopy (move, move_copy); + + VectorSubtract (bad->s.origin, self->s.origin, dir); + VectorNormalize (dir); + AngleVectors (self->s.angles, forward, NULL, NULL); + dp_bad = DotProduct (forward, dir); + + VectorNormalize (move_copy); + AngleVectors (self->s.angles, forward, NULL, NULL); + dp_move = DotProduct (forward, move_copy); + + if ((dp_bad < 0) && (dp_move < 0)) + return true; + if ((dp_bad > 0) && (dp_move > 0)) + return true; + + return false; +/* + if(DotProduct(forward, dir) > 0) + { +// gi.dprintf ("bad ahead...\n"); + return true; + } + +// gi.dprintf ("bad behind...\n"); + return false; + */ +} +// ROGUE +//============ + +/* +============= +SV_movestep + +Called by monster program code. +The move will be adjusted for slopes and stairs, but if the move isn't +possible, no move is done, false is returned, and +pr_global_struct->trace_normal is set to the normal of the blocking wall +============= +*/ +//FIXME since we need to test end position contents here, can we avoid doing +//it again later in catagorize position? +qboolean SV_movestep (edict_t *ent, vec3_t move, qboolean relink) +{ + float dz; + vec3_t oldorg, neworg, end; + trace_t trace; + int i; + float stepsize; + vec3_t test; + int contents; + edict_t *current_bad; // PGM + float minheight; // pmm + +//====== +//PGM + + // PMM - who cares about bad areas if you're dead? + if (ent->health > 0) + { + current_bad = CheckForBadArea(ent); + if(current_bad) + { + ent->bad_area = current_bad; + + if(ent->enemy && !strcmp(ent->enemy->classname, "tesla")) + { + // if the tesla is in front of us, back up... + if (IsBadAhead (ent, current_bad, move)) + VectorScale(move, -1, move); + } + } + else if(ent->bad_area) + { + // if we're no longer in a bad area, get back to business. + ent->bad_area = NULL; + if(ent->oldenemy)// && ent->bad_area->owner == ent->enemy) + { + // gi.dprintf("resuming being pissed at %s\n", ent->oldenemy->classname); + ent->enemy = ent->oldenemy; + ent->goalentity = ent->oldenemy; + FoundTarget(ent); + // FIXME - remove this when ready!!! +// if (ent->lastMoveTime == level.time) +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("Duplicate move detected for %s, please tell programmers!\n", ent->classname); +// ent->lastMoveTime = level.time; + // FIXME + + return true; + } + } + } +//PGM +//====== + +// try the move + VectorCopy (ent->s.origin, oldorg); + VectorAdd (ent->s.origin, move, neworg); + +// flying monsters don't step up + if ( ent->flags & (FL_SWIM | FL_FLY) ) + { + // try one move with vertical motion, then one without + for (i=0 ; i<2 ; i++) + { + VectorAdd (ent->s.origin, move, neworg); + if (i == 0 && ent->enemy) + { + if (!ent->goalentity) + ent->goalentity = ent->enemy; + dz = ent->s.origin[2] - ent->goalentity->s.origin[2]; + if (ent->goalentity->client) + { + // we want the carrier to stay a certain distance off the ground, to help prevent him + // from shooting his fliers, who spawn in below him + // + if (!strcmp(ent->classname, "monster_carrier")) + minheight = 104; + else + minheight = 40; +// if (dz > 40) + if (dz > minheight) +// pmm + neworg[2] -= 8; + if (!((ent->flags & FL_SWIM) && (ent->waterlevel < 2))) + if (dz < (minheight - 10)) + neworg[2] += 8; + } + else + { + if (dz > 8) + neworg[2] -= 8; + else if (dz > 0) + neworg[2] -= dz; + else if (dz < -8) + neworg[2] += 8; + else + neworg[2] += dz; + } + } + + trace = gi.trace (ent->s.origin, ent->mins, ent->maxs, neworg, ent, MASK_MONSTERSOLID); + + // fly monsters don't enter water voluntarily + if (ent->flags & FL_FLY) + { + if (!ent->waterlevel) + { + test[0] = trace.endpos[0]; + test[1] = trace.endpos[1]; + test[2] = trace.endpos[2] + ent->mins[2] + 1; + contents = gi.pointcontents(test); + if (contents & MASK_WATER) + return false; + } + } + + // swim monsters don't exit water voluntarily + if (ent->flags & FL_SWIM) + { + if (ent->waterlevel < 2) + { + test[0] = trace.endpos[0]; + test[1] = trace.endpos[1]; + test[2] = trace.endpos[2] + ent->mins[2] + 1; + contents = gi.pointcontents(test); + if (!(contents & MASK_WATER)) + return false; + } + } + +// if (trace.fraction == 1) + + // PMM - changed above to this + if ((trace.fraction == 1) && (!trace.allsolid) && (!trace.startsolid)) + { + VectorCopy (trace.endpos, ent->s.origin); +//===== +//PGM + if(!current_bad && CheckForBadArea(ent)) + { +// gi.dprintf("Oooh! Bad Area!\n"); + VectorCopy (oldorg, ent->s.origin); + } + else + { + if (relink) + { + gi.linkentity (ent); + G_TouchTriggers (ent); + } + // FIXME - remove this when ready!!! +// if (ent->lastMoveTime == level.time) +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("Duplicate move detected for %s, please tell programmers!\n", ent->classname); +// ent->lastMoveTime = level.time; + // FIXME + + return true; + } +//PGM +//===== + } + + if (!ent->enemy) + break; + } + + return false; + } + +// push down from a step height above the wished position + if (!(ent->monsterinfo.aiflags & AI_NOSTEP)) + stepsize = STEPSIZE; + else + stepsize = 1; + +//PGM +#ifdef ROGUE_GRAVITY + // trace from 1 stepsize gravityUp to 2 stepsize gravityDown. + VectorMA(neworg, -1 * stepsize, ent->gravityVector, neworg); + VectorMA(neworg, 2 * stepsize, ent->gravityVector, end); +#else + neworg[2] += stepsize; + VectorCopy (neworg, end); + end[2] -= stepsize*2; +#endif +//PGM + + trace = gi.trace (neworg, ent->mins, ent->maxs, end, ent, MASK_MONSTERSOLID); + + if (trace.allsolid) + return false; + + if (trace.startsolid) + { + neworg[2] -= stepsize; + trace = gi.trace (neworg, ent->mins, ent->maxs, end, ent, MASK_MONSTERSOLID); + if (trace.allsolid || trace.startsolid) + return false; + } + + + // don't go in to water + if (ent->waterlevel == 0) + { +//PGM +#ifdef ROGUE_GRAVITY + test[0] = trace.endpos[0]; + test[1] = trace.endpos[1]; + if(ent->gravityVector[2] > 0) + test[2] = trace.endpos[2] + ent->maxs[2] - 1; + else + test[2] = trace.endpos[2] + ent->mins[2] + 1; +#else + test[0] = trace.endpos[0]; + test[1] = trace.endpos[1]; + test[2] = trace.endpos[2] + ent->mins[2] + 1; +#endif +//PGM + + contents = gi.pointcontents(test); + + if (contents & MASK_WATER) + return false; + } + + if (trace.fraction == 1) + { + // if monster had the ground pulled out, go ahead and fall + if ( ent->flags & FL_PARTIALGROUND ) + { + VectorAdd (ent->s.origin, move, ent->s.origin); + if (relink) + { + gi.linkentity (ent); + G_TouchTriggers (ent); + } + ent->groundentity = NULL; + // FIXME - remove this when ready!!! +// if (ent->lastMoveTime == level.time) +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("Duplicate move detected for %s, please tell programmers!\n", ent->classname); +// ent->lastMoveTime = level.time; + // FIXME + + return true; + } + + return false; // walked off an edge + } + +// check point traces down for dangling corners + VectorCopy (trace.endpos, ent->s.origin); + +//PGM + // PMM - don't bother with bad areas if we're dead + if (ent->health > 0) + { + // use AI_BLOCKED to tell the calling layer that we're now mad at a tesla + new_bad = CheckForBadArea(ent); + if(!current_bad && new_bad) + { + if (new_bad->owner) + { +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf("Blocked -"); + if (!strcmp(new_bad->owner->classname, "tesla")) + { +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("it's a tesla -"); + if ((!(ent->enemy)) || (!(ent->enemy->inuse))) + { +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("I don't have a valid enemy, attacking tesla!\n"); + TargetTesla (ent, new_bad->owner); + ent->monsterinfo.aiflags |= AI_BLOCKED; + } + else if (!strcmp(ent->enemy->classname, "telsa")) + { +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("but we're already mad at a tesla\n"); + } + else if ((ent->enemy) && (ent->enemy->client)) + { +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("we have a player enemy -"); + if (visible(ent, ent->enemy)) + { +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("we can see him -"); + } + else + { +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("can't see him, kill the tesla! -"); + TargetTesla (ent, new_bad->owner); + ent->monsterinfo.aiflags |= AI_BLOCKED; + } + } + else + { +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("the enemy isn't a player, killing tesla -"); + TargetTesla (ent, new_bad->owner); + ent->monsterinfo.aiflags |= AI_BLOCKED; + } + } +// else if ((g_showlogic) && (g_showlogic->value)) +// { +// gi.dprintf(" by non-tesla bad area!"); +// } + } +// gi.dprintf ("\n"); + + VectorCopy (oldorg, ent->s.origin); + return false; + } + } +//PGM + + if (!M_CheckBottom (ent)) + { + if ( ent->flags & FL_PARTIALGROUND ) + { // entity had floor mostly pulled out from underneath it + // and is trying to correct + if (relink) + { + gi.linkentity (ent); + G_TouchTriggers (ent); + } + // FIXME - remove this when ready!!! +// if (ent->lastMoveTime == level.time) +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("Duplicate move detected for %s, please tell programmers!\n", ent->classname); +// ent->lastMoveTime = level.time; + // FIXME + + return true; + } + VectorCopy (oldorg, ent->s.origin); + return false; + } + + if ( ent->flags & FL_PARTIALGROUND ) + { + ent->flags &= ~FL_PARTIALGROUND; + } + ent->groundentity = trace.ent; + ent->groundentity_linkcount = trace.ent->linkcount; + +// the move is ok + if (relink) + { + gi.linkentity (ent); + G_TouchTriggers (ent); + } + // FIXME - remove this when ready!!! +// if (ent->lastMoveTime == level.time) +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("Duplicate move detected for %s, please tell programmers!\n", ent->classname); +// ent->lastMoveTime = level.time; + // FIXME + + return true; +} + + +//============================================================================ + +/* +=============== +M_ChangeYaw + +=============== +*/ +void M_ChangeYaw (edict_t *ent) +{ + float ideal; + float current; + float move; + float speed; + + current = anglemod(ent->s.angles[YAW]); + ideal = ent->ideal_yaw; + + if (current == ideal) + return; + + move = ideal - current; + speed = ent->yaw_speed; + if (ideal > current) + { + if (move >= 180) + move = move - 360; + } + else + { + if (move <= -180) + move = move + 360; + } + if (move > 0) + { + if (move > speed) + move = speed; + } + else + { + if (move < -speed) + move = -speed; + } + + ent->s.angles[YAW] = anglemod (current + move); +} + + +/* +====================== +SV_StepDirection + +Turns to the movement direction, and walks the current distance if +facing it. + +====================== +*/ +qboolean SV_StepDirection (edict_t *ent, float yaw, float dist) +{ + vec3_t move, oldorigin; + float delta; + + if(!ent->inuse) return true; // PGM g_touchtrigger free problem + + ent->ideal_yaw = yaw; + M_ChangeYaw (ent); + + yaw = yaw*M_PI*2 / 360; + move[0] = cos(yaw)*dist; + move[1] = sin(yaw)*dist; + move[2] = 0; + + VectorCopy (ent->s.origin, oldorigin); + if (SV_movestep (ent, move, false)) + { + ent->monsterinfo.aiflags &= ~AI_BLOCKED; + if(!ent->inuse) return true; // PGM g_touchtrigger free problem + + delta = ent->s.angles[YAW] - ent->ideal_yaw; + if (strncmp(ent->classname, "monster_widow", 13)) + { + if (delta > 45 && delta < 315) + { // not turned far enough, so don't take the step + VectorCopy (oldorigin, ent->s.origin); + } + } + gi.linkentity (ent); + G_TouchTriggers (ent); + return true; + } + gi.linkentity (ent); + G_TouchTriggers (ent); + return false; +} + +/* +====================== +SV_FixCheckBottom + +====================== +*/ +void SV_FixCheckBottom (edict_t *ent) +{ + ent->flags |= FL_PARTIALGROUND; +} + + + +/* +================ +SV_NewChaseDir + +================ +*/ +#define DI_NODIR -1 +void SV_NewChaseDir (edict_t *actor, edict_t *enemy, float dist) +{ + float deltax,deltay; + float d[3]; + float tdir, olddir, turnaround; + + //FIXME: how did we get here with no enemy + if (!enemy) + return; + + olddir = anglemod( (int)(actor->ideal_yaw/45)*45 ); + turnaround = anglemod(olddir - 180); + + deltax = enemy->s.origin[0] - actor->s.origin[0]; + deltay = enemy->s.origin[1] - actor->s.origin[1]; + if (deltax>10) + d[1]= 0; + else if (deltax<-10) + d[1]= 180; + else + d[1]= DI_NODIR; + if (deltay<-10) + d[2]= 270; + else if (deltay>10) + d[2]= 90; + else + d[2]= DI_NODIR; + +// try direct route + if (d[1] != DI_NODIR && d[2] != DI_NODIR) + { + if (d[1] == 0) + tdir = d[2] == 90 ? 45 : 315; + else + tdir = d[2] == 90 ? 135 : 215; + + if (tdir != turnaround && SV_StepDirection(actor, tdir, dist)) + return; + } + +// try other directions + if ( ((rand()&3) & 1) || abs(deltay)>abs(deltax)) + { + tdir=d[1]; + d[1]=d[2]; + d[2]=tdir; + } + + if (d[1]!=DI_NODIR && d[1]!=turnaround + && SV_StepDirection(actor, d[1], dist)) + return; + + if (d[2]!=DI_NODIR && d[2]!=turnaround + && SV_StepDirection(actor, d[2], dist)) + return; + +//ROGUE + if(actor->monsterinfo.blocked) + { + if ((actor->inuse) && (actor->health > 0)) + { + if((actor->monsterinfo.blocked)(actor, dist)) + return; + } + } +//ROGUE + +/* there is no direct path to the player, so pick another direction */ + + if (olddir!=DI_NODIR && SV_StepDirection(actor, olddir, dist)) + return; + + if (rand()&1) /*randomly determine direction of search*/ + { + for (tdir=0 ; tdir<=315 ; tdir += 45) + if (tdir!=turnaround && SV_StepDirection(actor, tdir, dist) ) + return; + } + else + { + for (tdir=315 ; tdir >=0 ; tdir -= 45) + if (tdir!=turnaround && SV_StepDirection(actor, tdir, dist) ) + return; + } + + if (turnaround != DI_NODIR && SV_StepDirection(actor, turnaround, dist) ) + return; + + actor->ideal_yaw = olddir; // can't move + +// if a bridge was pulled out from underneath a monster, it may not have +// a valid standing position at all + + if (!M_CheckBottom (actor)) + SV_FixCheckBottom (actor); +} + +/* +====================== +SV_CloseEnough + +====================== +*/ +qboolean SV_CloseEnough (edict_t *ent, edict_t *goal, float dist) +{ + int i; + + for (i=0 ; i<3 ; i++) + { + if (goal->absmin[i] > ent->absmax[i] + dist) + return false; + if (goal->absmax[i] < ent->absmin[i] - dist) + return false; + } + return true; +} + +/* +====================== +M_MoveToGoal +====================== +*/ +void M_MoveToGoal (edict_t *ent, float dist) +{ + edict_t *goal; + + goal = ent->goalentity; + + if (!ent->groundentity && !(ent->flags & (FL_FLY|FL_SWIM))) + return; + +// if the next step hits the enemy, return immediately + if (ent->enemy && SV_CloseEnough (ent, ent->enemy, dist) ) + return; + +// bump around... +// if ( (rand()&3)==1 || !SV_StepDirection (ent, ent->ideal_yaw, dist)) +// PMM - charging monsters (AI_CHARGING) don't deflect unless they have to + if ( (((rand()&3)==1) && !(ent->monsterinfo.aiflags & AI_CHARGING)) || !SV_StepDirection (ent, ent->ideal_yaw, dist)) + { + if (ent->monsterinfo.aiflags & AI_BLOCKED) + { +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("tesla attack detected, not changing direction!\n"); + ent->monsterinfo.aiflags &= ~AI_BLOCKED; + return; + } + if (ent->inuse) + SV_NewChaseDir (ent, goal, dist); + } +} + + +/* +=============== +M_walkmove +=============== +*/ +qboolean M_walkmove (edict_t *ent, float yaw, float dist) +{ + vec3_t move; + // PMM + qboolean retval; + + if (!ent->groundentity && !(ent->flags & (FL_FLY|FL_SWIM))) + return false; + + yaw = yaw*M_PI*2 / 360; + + move[0] = cos(yaw)*dist; + move[1] = sin(yaw)*dist; + move[2] = 0; + + // PMM + retval = SV_movestep(ent, move, true); + ent->monsterinfo.aiflags &= ~AI_BLOCKED; + return retval; + // pmm + //return SV_movestep(ent, move, true); +} diff --git a/original/rogue/m_move2.c b/original/rogue/m_move2.c new file mode 100644 index 0000000..8551640 --- /dev/null +++ b/original/rogue/m_move2.c @@ -0,0 +1,737 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// m_move.c -- monster movement + +#include "g_local.h" + +#define STEPSIZE 18 + +// this is used for communications out of sv_movestep to say what entity +// is blocking us +edict_t *new_bad; //pmm + +/* +============= +M_CheckBottom + +Returns false if any part of the bottom of the entity is off an edge that +is not a staircase. + +============= +*/ +int c_yes, c_no; + +qboolean M_CheckBottom (edict_t *ent) +{ + vec3_t mins, maxs, start, stop; + trace_t trace; + int x, y; + float mid, bottom; + + VectorAdd (ent->s.origin, ent->mins, mins); + VectorAdd (ent->s.origin, ent->maxs, maxs); + +// if all of the points under the corners are solid world, don't bother +// with the tougher checks +// the corners must be within 16 of the midpoint + +//PGM +#ifdef ROGUE_GRAVITY + // FIXME - this will only handle 0,0,1 and 0,0,-1 gravity vectors + start[2] = mins[2] - 1; + if(ent->gravityVector[2] > 0) + start[2] = maxs[2] + 1; +#else + start[2] = mins[2] - 1; +#endif +//PGM + + for (x=0 ; x<=1 ; x++) + for (y=0 ; y<=1 ; y++) + { + start[0] = x ? maxs[0] : mins[0]; + start[1] = y ? maxs[1] : mins[1]; + if (gi.pointcontents (start) != CONTENTS_SOLID) + goto realcheck; + } + + c_yes++; + return true; // we got out easy + +realcheck: + c_no++; +// +// check it for real... +// + start[2] = mins[2]; + +// the midpoint must be within 16 of the bottom + start[0] = stop[0] = (mins[0] + maxs[0])*0.5; + start[1] = stop[1] = (mins[1] + maxs[1])*0.5; + +//PGM +#ifdef ROGUE_GRAVITY + if(ent->gravityVector[2] < 0) + { + start[2] = mins[2]; + stop[2] = start[2] - STEPSIZE - STEPSIZE; + } + else + { + start[2] = maxs[2]; + stop[2] = start[2] + STEPSIZE + STEPSIZE; + } +#else + stop[2] = start[2] - 2*STEPSIZE; +#endif +//PGM + + trace = gi.trace (start, vec3_origin, vec3_origin, stop, ent, MASK_MONSTERSOLID); + + if (trace.fraction == 1.0) + return false; + mid = bottom = trace.endpos[2]; + +// the corners must be within 16 of the midpoint + for (x=0 ; x<=1 ; x++) + for (y=0 ; y<=1 ; y++) + { + start[0] = stop[0] = x ? maxs[0] : mins[0]; + start[1] = stop[1] = y ? maxs[1] : mins[1]; + + trace = gi.trace (start, vec3_origin, vec3_origin, stop, ent, MASK_MONSTERSOLID); + +//PGM +#ifdef ROGUE_GRAVITY + // FIXME - this will only handle 0,0,1 and 0,0,-1 gravity vectors + if(ent->gravityVector[2] > 0) + { + if (trace.fraction != 1.0 && trace.endpos[2] < bottom) + bottom = trace.endpos[2]; + if (trace.fraction == 1.0 || trace.endpos[2] - mid > STEPSIZE) + return false; + } + else + { + if (trace.fraction != 1.0 && trace.endpos[2] > bottom) + bottom = trace.endpos[2]; + if (trace.fraction == 1.0 || mid - trace.endpos[2] > STEPSIZE) + return false; + } +#else + if (trace.fraction != 1.0 && trace.endpos[2] > bottom) + bottom = trace.endpos[2]; + if (trace.fraction == 1.0 || mid - trace.endpos[2] > STEPSIZE) + return false; +#endif +//PGM + } + + c_yes++; + return true; +} + + +/* +============= +SV_movestep + +Called by monster program code. +The move will be adjusted for slopes and stairs, but if the move isn't +possible, no move is done, false is returned, and +pr_global_struct->trace_normal is set to the normal of the blocking wall +============= +*/ +//FIXME since we need to test end position contents here, can we avoid doing +//it again later in catagorize position? +qboolean SV_movestep (edict_t *ent, vec3_t move, qboolean relink) +{ + float dz; + vec3_t oldorg, neworg, end; + trace_t trace; + int i; + float stepsize; + vec3_t test; + int contents; + edict_t *current_bad; // PGM + float minheight; // pmm + +//====== +//PGM + current_bad = CheckForBadArea(ent); + if(current_bad) + { +// gi.dprintf("in bad area\n"); + ent->bad_area = current_bad; + + if(ent->enemy && !strcmp(ent->enemy->classname, "tesla")) + { +// gi.dprintf("%s -->> ", vtos(move)); + VectorScale(move, -1, move); +// gi.dprintf("%s\n", vtos(move)); + } + } + else if(ent->bad_area) + { + // if we're no longer in a bad area, get back to business. + ent->bad_area = NULL; + if(ent->oldenemy)// && ent->bad_area->owner == ent->enemy) + { +// gi.dprintf("resuming being pissed at %s\n", ent->oldenemy->classname); + ent->enemy = ent->oldenemy; + ent->goalentity = ent->oldenemy; + FoundTarget(ent); + return true; + } + } +//PGM +//====== + +// try the move + VectorCopy (ent->s.origin, oldorg); + VectorAdd (ent->s.origin, move, neworg); + +// flying monsters don't step up + if ( ent->flags & (FL_SWIM | FL_FLY) ) + { + // try one move with vertical motion, then one without + for (i=0 ; i<2 ; i++) + { + VectorAdd (ent->s.origin, move, neworg); + if (i == 0 && ent->enemy) + { + if (!ent->goalentity) + ent->goalentity = ent->enemy; + dz = ent->s.origin[2] - ent->goalentity->s.origin[2]; + if (ent->goalentity->client) + { + // we want the carrier to stay a certain distance off the ground, to help prevent him + // from shooting his fliers, who spawn in below him + // + if (!strcmp(ent->classname, "monster_carrier")) + minheight = 104; + else + minheight = 40; +// if (dz > 40) + if (dz > minheight) +// pmm + neworg[2] -= 8; + if (!((ent->flags & FL_SWIM) && (ent->waterlevel < 2))) + if (dz < (minheight - 10)) + neworg[2] += 8; + } + else + { + if (dz > 8) + neworg[2] -= 8; + else if (dz > 0) + neworg[2] -= dz; + else if (dz < -8) + neworg[2] += 8; + else + neworg[2] += dz; + } + } + + trace = gi.trace (ent->s.origin, ent->mins, ent->maxs, neworg, ent, MASK_MONSTERSOLID); + + // fly monsters don't enter water voluntarily + if (ent->flags & FL_FLY) + { + if (!ent->waterlevel) + { + test[0] = trace.endpos[0]; + test[1] = trace.endpos[1]; + test[2] = trace.endpos[2] + ent->mins[2] + 1; + contents = gi.pointcontents(test); + if (contents & MASK_WATER) + return false; + } + } + + // swim monsters don't exit water voluntarily + if (ent->flags & FL_SWIM) + { + if (ent->waterlevel < 2) + { + test[0] = trace.endpos[0]; + test[1] = trace.endpos[1]; + test[2] = trace.endpos[2] + ent->mins[2] + 1; + contents = gi.pointcontents(test); + if (!(contents & MASK_WATER)) + return false; + } + } + +// if (trace.fraction == 1) + + // PMM - changed above to this + if ((trace.fraction == 1) && (!trace.allsolid) && (!trace.startsolid)) + { + VectorCopy (trace.endpos, ent->s.origin); +//===== +//PGM + if(!current_bad && CheckForBadArea(ent)) + { +// gi.dprintf("Oooh! Bad Area!\n"); + VectorCopy (oldorg, ent->s.origin); + } + else + { + if (relink) + { + gi.linkentity (ent); + G_TouchTriggers (ent); + } + return true; + } +//PGM +//===== + } + + if (!ent->enemy) + break; + } + + return false; + } + +// push down from a step height above the wished position + if (!(ent->monsterinfo.aiflags & AI_NOSTEP)) + stepsize = STEPSIZE; + else + stepsize = 1; + +//PGM +#ifdef ROGUE_GRAVITY + // trace from 1 stepsize gravityUp to 2 stepsize gravityDown. + VectorMA(neworg, -1 * stepsize, ent->gravityVector, neworg); + VectorMA(neworg, 2 * stepsize, ent->gravityVector, end); +#else + neworg[2] += stepsize; + VectorCopy (neworg, end); + end[2] -= stepsize*2; +#endif +//PGM + + trace = gi.trace (neworg, ent->mins, ent->maxs, end, ent, MASK_MONSTERSOLID); + + if (trace.allsolid) + return false; + + if (trace.startsolid) + { + neworg[2] -= stepsize; + trace = gi.trace (neworg, ent->mins, ent->maxs, end, ent, MASK_MONSTERSOLID); + if (trace.allsolid || trace.startsolid) + return false; + } + + + // don't go in to water + if (ent->waterlevel == 0) + { +//PGM +#ifdef ROGUE_GRAVITY + test[0] = trace.endpos[0]; + test[1] = trace.endpos[1]; + if(ent->gravityVector[2] > 0) + test[2] = trace.endpos[2] + ent->maxs[2] - 1; + else + test[2] = trace.endpos[2] + ent->mins[2] + 1; +#else + test[0] = trace.endpos[0]; + test[1] = trace.endpos[1]; + test[2] = trace.endpos[2] + ent->mins[2] + 1; +#endif +//PGM + + contents = gi.pointcontents(test); + + if (contents & MASK_WATER) + return false; + } + + if (trace.fraction == 1) + { + // if monster had the ground pulled out, go ahead and fall + if ( ent->flags & FL_PARTIALGROUND ) + { + VectorAdd (ent->s.origin, move, ent->s.origin); + if (relink) + { + gi.linkentity (ent); + G_TouchTriggers (ent); + } + ent->groundentity = NULL; + return true; + } + + return false; // walked off an edge + } + +// check point traces down for dangling corners + VectorCopy (trace.endpos, ent->s.origin); + +//PGM + new_bad = CheckForBadArea(ent); + if(!current_bad && new_bad) + { + if (new_bad->owner) + { + if ((g_showlogic) && (g_showlogic->value)) + gi.dprintf("Blocked -"); + if (!strcmp(new_bad->owner->classname, "tesla")) + { + if ((g_showlogic) && (g_showlogic->value)) + gi.dprintf ("it's a tesla -"); + if ((!(ent->enemy)) || (!(ent->enemy->inuse))) + { + if ((g_showlogic) && (g_showlogic->value)) + gi.dprintf ("I don't have a valid enemy!\n"); + } + else if (!strcmp(ent->enemy->classname, "telsa")) + { + if ((g_showlogic) && (g_showlogic->value)) + gi.dprintf ("but we're already mad at a tesla\n"); + } + else if ((ent->enemy) && (ent->enemy->client)) + { + if ((g_showlogic) && (g_showlogic->value)) + gi.dprintf ("we have a player enemy -"); + if (visible(ent, ent->enemy)) + { + if ((g_showlogic) && (g_showlogic->value)) + gi.dprintf ("we can see him -"); + } + else + { + if ((g_showlogic) && (g_showlogic->value)) + gi.dprintf ("can't see him, kill the tesla! -"); + } + } + else + { + if ((g_showlogic) && (g_showlogic->value)) + gi.dprintf ("the enemy isn't a player -"); + } + } + } + gi.dprintf ("\n"); + + + VectorCopy (oldorg, ent->s.origin); + return false; + } +//PGM + + if (!M_CheckBottom (ent)) + { + if ( ent->flags & FL_PARTIALGROUND ) + { // entity had floor mostly pulled out from underneath it + // and is trying to correct + if (relink) + { + gi.linkentity (ent); + G_TouchTriggers (ent); + } + return true; + } + VectorCopy (oldorg, ent->s.origin); + return false; + } + + if ( ent->flags & FL_PARTIALGROUND ) + { + ent->flags &= ~FL_PARTIALGROUND; + } + ent->groundentity = trace.ent; + ent->groundentity_linkcount = trace.ent->linkcount; + +// the move is ok + if (relink) + { + gi.linkentity (ent); + G_TouchTriggers (ent); + } + return true; +} + + +//============================================================================ + +/* +=============== +M_ChangeYaw + +=============== +*/ +void M_ChangeYaw (edict_t *ent) +{ + float ideal; + float current; + float move; + float speed; + + current = anglemod(ent->s.angles[YAW]); + ideal = ent->ideal_yaw; + + if (current == ideal) + return; + + move = ideal - current; + speed = ent->yaw_speed; + if (ideal > current) + { + if (move >= 180) + move = move - 360; + } + else + { + if (move <= -180) + move = move + 360; + } + if (move > 0) + { + if (move > speed) + move = speed; + } + else + { + if (move < -speed) + move = -speed; + } + + ent->s.angles[YAW] = anglemod (current + move); +} + + +/* +====================== +SV_StepDirection + +Turns to the movement direction, and walks the current distance if +facing it. + +====================== +*/ +qboolean SV_StepDirection (edict_t *ent, float yaw, float dist) +{ + vec3_t move, oldorigin; + float delta; + + if(!ent->inuse) return true; // PGM g_touchtrigger free problem + + ent->ideal_yaw = yaw; + M_ChangeYaw (ent); + + yaw = yaw*M_PI*2 / 360; + move[0] = cos(yaw)*dist; + move[1] = sin(yaw)*dist; + move[2] = 0; + + VectorCopy (ent->s.origin, oldorigin); + if (SV_movestep (ent, move, false)) + { + if(!ent->inuse) return true; // PGM g_touchtrigger free problem + + delta = ent->s.angles[YAW] - ent->ideal_yaw; + if (strncmp(ent->classname, "monster_widow", 13)) + { + if (delta > 45 && delta < 315) + { // not turned far enough, so don't take the step + VectorCopy (oldorigin, ent->s.origin); + } + } + gi.linkentity (ent); + G_TouchTriggers (ent); + return true; + } + gi.linkentity (ent); + G_TouchTriggers (ent); + return false; +} + +/* +====================== +SV_FixCheckBottom + +====================== +*/ +void SV_FixCheckBottom (edict_t *ent) +{ + ent->flags |= FL_PARTIALGROUND; +} + + + +/* +================ +SV_NewChaseDir + +================ +*/ +#define DI_NODIR -1 +void SV_NewChaseDir (edict_t *actor, edict_t *enemy, float dist) +{ + float deltax,deltay; + float d[3]; + float tdir, olddir, turnaround; + + //FIXME: how did we get here with no enemy + if (!enemy) + return; + + olddir = anglemod( (int)(actor->ideal_yaw/45)*45 ); + turnaround = anglemod(olddir - 180); + + deltax = enemy->s.origin[0] - actor->s.origin[0]; + deltay = enemy->s.origin[1] - actor->s.origin[1]; + if (deltax>10) + d[1]= 0; + else if (deltax<-10) + d[1]= 180; + else + d[1]= DI_NODIR; + if (deltay<-10) + d[2]= 270; + else if (deltay>10) + d[2]= 90; + else + d[2]= DI_NODIR; + +// try direct route + if (d[1] != DI_NODIR && d[2] != DI_NODIR) + { + if (d[1] == 0) + tdir = d[2] == 90 ? 45 : 315; + else + tdir = d[2] == 90 ? 135 : 215; + + if (tdir != turnaround && SV_StepDirection(actor, tdir, dist)) + return; + } + +// try other directions + if ( ((rand()&3) & 1) || abs(deltay)>abs(deltax)) + { + tdir=d[1]; + d[1]=d[2]; + d[2]=tdir; + } + + if (d[1]!=DI_NODIR && d[1]!=turnaround + && SV_StepDirection(actor, d[1], dist)) + return; + + if (d[2]!=DI_NODIR && d[2]!=turnaround + && SV_StepDirection(actor, d[2], dist)) + return; + +//ROGUE + if(actor->monsterinfo.blocked) + { + if((actor->monsterinfo.blocked)(actor, dist)) + return; + } +//ROGUE + +/* there is no direct path to the player, so pick another direction */ + + if (olddir!=DI_NODIR && SV_StepDirection(actor, olddir, dist)) + return; + + if (rand()&1) /*randomly determine direction of search*/ + { + for (tdir=0 ; tdir<=315 ; tdir += 45) + if (tdir!=turnaround && SV_StepDirection(actor, tdir, dist) ) + return; + } + else + { + for (tdir=315 ; tdir >=0 ; tdir -= 45) + if (tdir!=turnaround && SV_StepDirection(actor, tdir, dist) ) + return; + } + + if (turnaround != DI_NODIR && SV_StepDirection(actor, turnaround, dist) ) + return; + + actor->ideal_yaw = olddir; // can't move + +// if a bridge was pulled out from underneath a monster, it may not have +// a valid standing position at all + + if (!M_CheckBottom (actor)) + SV_FixCheckBottom (actor); +} + +/* +====================== +SV_CloseEnough + +====================== +*/ +qboolean SV_CloseEnough (edict_t *ent, edict_t *goal, float dist) +{ + int i; + + for (i=0 ; i<3 ; i++) + { + if (goal->absmin[i] > ent->absmax[i] + dist) + return false; + if (goal->absmax[i] < ent->absmin[i] - dist) + return false; + } + return true; +} + +/* +====================== +M_MoveToGoal +====================== +*/ +void M_MoveToGoal (edict_t *ent, float dist) +{ + edict_t *goal; + + goal = ent->goalentity; + + if (!ent->groundentity && !(ent->flags & (FL_FLY|FL_SWIM))) + return; + +// if the next step hits the enemy, return immediately + if (ent->enemy && SV_CloseEnough (ent, ent->enemy, dist) ) + return; + +// bump around... +// if ( (rand()&3)==1 || !SV_StepDirection (ent, ent->ideal_yaw, dist)) +// PMM - charging monsters (AI_CHARGING) don't deflect unless they have to + if ( (((rand()&3)==1) && !(ent->monsterinfo.aiflags & AI_CHARGING)) || !SV_StepDirection (ent, ent->ideal_yaw, dist)) + { + if (ent->inuse) + SV_NewChaseDir (ent, goal, dist); + } +} + + +/* +=============== +M_walkmove +=============== +*/ +qboolean M_walkmove (edict_t *ent, float yaw, float dist) +{ + vec3_t move; + + if (!ent->groundentity && !(ent->flags & (FL_FLY|FL_SWIM))) + return false; + + yaw = yaw*M_PI*2 / 360; + + move[0] = cos(yaw)*dist; + move[1] = sin(yaw)*dist; + move[2] = 0; + + return SV_movestep(ent, move, true); +} diff --git a/original/rogue/m_mutant.c b/original/rogue/m_mutant.c new file mode 100644 index 0000000..5034b71 --- /dev/null +++ b/original/rogue/m_mutant.c @@ -0,0 +1,726 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +/* +============================================================================== + +mutant + +============================================================================== +*/ + +#include "g_local.h" +#include "m_mutant.h" + + +static int sound_swing; +static int sound_hit; +static int sound_hit2; +static int sound_death; +static int sound_idle; +static int sound_pain1; +static int sound_pain2; +static int sound_sight; +static int sound_search; +static int sound_step1; +static int sound_step2; +static int sound_step3; +static int sound_thud; + +// +// SOUNDS +// + +void mutant_step (edict_t *self) +{ + int n; + n = (rand() + 1) % 3; + if (n == 0) + gi.sound (self, CHAN_VOICE, sound_step1, 1, ATTN_NORM, 0); + else if (n == 1) + gi.sound (self, CHAN_VOICE, sound_step2, 1, ATTN_NORM, 0); + else + gi.sound (self, CHAN_VOICE, sound_step3, 1, ATTN_NORM, 0); +} + +void mutant_sight (edict_t *self, edict_t *other) +{ + gi.sound (self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); +} + +void mutant_search (edict_t *self) +{ + gi.sound (self, CHAN_VOICE, sound_search, 1, ATTN_NORM, 0); +} + +void mutant_swing (edict_t *self) +{ + gi.sound (self, CHAN_VOICE, sound_swing, 1, ATTN_NORM, 0); +} + + +// +// STAND +// + +mframe_t mutant_frames_stand [] = +{ + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, // 10 + + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, // 20 + + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, // 30 + + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, // 40 + + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, // 50 + + ai_stand, 0, NULL +}; +mmove_t mutant_move_stand = {FRAME_stand101, FRAME_stand151, mutant_frames_stand, NULL}; + +void mutant_stand (edict_t *self) +{ + self->monsterinfo.currentmove = &mutant_move_stand; +} + + +// +// IDLE +// + +void mutant_idle_loop (edict_t *self) +{ + if (random() < 0.75) + self->monsterinfo.nextframe = FRAME_stand155; +} + +mframe_t mutant_frames_idle [] = +{ + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, // scratch loop start + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, mutant_idle_loop, // scratch loop end + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL +}; +mmove_t mutant_move_idle = {FRAME_stand152, FRAME_stand164, mutant_frames_idle, mutant_stand}; + +void mutant_idle (edict_t *self) +{ + self->monsterinfo.currentmove = &mutant_move_idle; + gi.sound (self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0); +} + + +// +// WALK +// + +void mutant_walk (edict_t *self); + +mframe_t mutant_frames_walk [] = +{ + ai_walk, 3, NULL, + ai_walk, 1, NULL, + ai_walk, 5, NULL, + ai_walk, 10, NULL, + ai_walk, 13, NULL, + ai_walk, 10, NULL, + ai_walk, 0, NULL, + ai_walk, 5, NULL, + ai_walk, 6, NULL, + ai_walk, 16, NULL, + ai_walk, 15, NULL, + ai_walk, 6, NULL +}; +mmove_t mutant_move_walk = {FRAME_walk05, FRAME_walk16, mutant_frames_walk, NULL}; + +void mutant_walk_loop (edict_t *self) +{ + self->monsterinfo.currentmove = &mutant_move_walk; +} + +mframe_t mutant_frames_start_walk [] = +{ + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, -2, NULL, + ai_walk, 1, NULL +}; +mmove_t mutant_move_start_walk = {FRAME_walk01, FRAME_walk04, mutant_frames_start_walk, mutant_walk_loop}; + +void mutant_walk (edict_t *self) +{ + self->monsterinfo.currentmove = &mutant_move_start_walk; +} + + +// +// RUN +// + +mframe_t mutant_frames_run [] = +{ + ai_run, 40, NULL, + ai_run, 40, mutant_step, + ai_run, 24, NULL, + ai_run, 5, mutant_step, + ai_run, 17, NULL, + ai_run, 10, NULL +}; +mmove_t mutant_move_run = {FRAME_run03, FRAME_run08, mutant_frames_run, NULL}; + +void mutant_run (edict_t *self) +{ + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + self->monsterinfo.currentmove = &mutant_move_stand; + else + self->monsterinfo.currentmove = &mutant_move_run; +} + + +// +// MELEE +// + +void mutant_hit_left (edict_t *self) +{ + vec3_t aim; + + VectorSet (aim, MELEE_DISTANCE, self->mins[0], 8); + if (fire_hit (self, aim, (10 + (rand() %5)), 100)) + gi.sound (self, CHAN_WEAPON, sound_hit, 1, ATTN_NORM, 0); + else + gi.sound (self, CHAN_WEAPON, sound_swing, 1, ATTN_NORM, 0); +} + +void mutant_hit_right (edict_t *self) +{ + vec3_t aim; + + VectorSet (aim, MELEE_DISTANCE, self->maxs[0], 8); + if (fire_hit (self, aim, (10 + (rand() %5)), 100)) + gi.sound (self, CHAN_WEAPON, sound_hit2, 1, ATTN_NORM, 0); + else + gi.sound (self, CHAN_WEAPON, sound_swing, 1, ATTN_NORM, 0); +} + +void mutant_check_refire (edict_t *self) +{ + if (!self->enemy || !self->enemy->inuse || self->enemy->health <= 0) + return; + + if ( ((skill->value == 3) && (random() < 0.5)) || (range(self, self->enemy) == RANGE_MELEE) ) + self->monsterinfo.nextframe = FRAME_attack09; +} + +mframe_t mutant_frames_attack [] = +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, mutant_hit_left, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, mutant_hit_right, + ai_charge, 0, mutant_check_refire +}; +mmove_t mutant_move_attack = {FRAME_attack09, FRAME_attack15, mutant_frames_attack, mutant_run}; + +void mutant_melee (edict_t *self) +{ + self->monsterinfo.currentmove = &mutant_move_attack; +} + + +// +// ATTACK +// + +void mutant_jump_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (self->health <= 0) + { + self->touch = NULL; + return; + } + + if (other->takedamage) + { + if (VectorLength(self->velocity) > 400) + { + vec3_t point; + vec3_t normal; + int damage; + + VectorCopy (self->velocity, normal); + VectorNormalize(normal); + VectorMA (self->s.origin, self->maxs[0], normal, point); + damage = 40 + 10 * random(); + T_Damage (other, self, self, self->velocity, point, normal, damage, damage, 0, MOD_UNKNOWN); + } + } + + if (!M_CheckBottom (self)) + { + if (self->groundentity) + { + self->monsterinfo.nextframe = FRAME_attack02; + self->touch = NULL; + } + return; + } + + self->touch = NULL; +} + +void mutant_jump_takeoff (edict_t *self) +{ + vec3_t forward; + + gi.sound (self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); + AngleVectors (self->s.angles, forward, NULL, NULL); + self->s.origin[2] += 1; + VectorScale (forward, 600, self->velocity); + self->velocity[2] = 250; + self->groundentity = NULL; + self->monsterinfo.aiflags |= AI_DUCKED; + self->monsterinfo.attack_finished = level.time + 3; + self->touch = mutant_jump_touch; +} + +void mutant_check_landing (edict_t *self) +{ + if (self->groundentity) + { + gi.sound (self, CHAN_WEAPON, sound_thud, 1, ATTN_NORM, 0); + self->monsterinfo.attack_finished = 0; + self->monsterinfo.aiflags &= ~AI_DUCKED; + return; + } + + if (level.time > self->monsterinfo.attack_finished) + self->monsterinfo.nextframe = FRAME_attack02; + else + self->monsterinfo.nextframe = FRAME_attack05; +} + +mframe_t mutant_frames_jump [] = +{ + ai_charge, 0, NULL, + ai_charge, 17, NULL, + ai_charge, 15, mutant_jump_takeoff, + ai_charge, 15, NULL, + ai_charge, 15, mutant_check_landing, + ai_charge, 0, NULL, + ai_charge, 3, NULL, + ai_charge, 0, NULL +}; +mmove_t mutant_move_jump = {FRAME_attack01, FRAME_attack08, mutant_frames_jump, mutant_run}; + +void mutant_jump (edict_t *self) +{ + self->monsterinfo.currentmove = &mutant_move_jump; +} + + +// +// CHECKATTACK +// + +qboolean mutant_check_melee (edict_t *self) +{ + if (range (self, self->enemy) == RANGE_MELEE) + return true; + return false; +} + +qboolean mutant_check_jump (edict_t *self) +{ + vec3_t v; + float distance; + + if (self->absmin[2] > (self->enemy->absmin[2] + 0.75 * self->enemy->size[2])) + return false; + + if (self->absmax[2] < (self->enemy->absmin[2] + 0.25 * self->enemy->size[2])) + return false; + + v[0] = self->s.origin[0] - self->enemy->s.origin[0]; + v[1] = self->s.origin[1] - self->enemy->s.origin[1]; + v[2] = 0; + distance = VectorLength(v); + + if (distance < 100) + return false; + if (distance > 100) + { + if (random() < 0.9) + return false; + } + + return true; +} + +qboolean mutant_checkattack (edict_t *self) +{ + if (!self->enemy || self->enemy->health <= 0) + return false; + + if (mutant_check_melee(self)) + { + self->monsterinfo.attack_state = AS_MELEE; + return true; + } + + if (mutant_check_jump(self)) + { + self->monsterinfo.attack_state = AS_MISSILE; + // FIXME play a jump sound here + return true; + } + + return false; +} + + +// +// PAIN +// + +mframe_t mutant_frames_pain1 [] = +{ + ai_move, 4, NULL, + ai_move, -3, NULL, + ai_move, -8, NULL, + ai_move, 2, NULL, + ai_move, 5, NULL +}; +mmove_t mutant_move_pain1 = {FRAME_pain101, FRAME_pain105, mutant_frames_pain1, mutant_run}; + +mframe_t mutant_frames_pain2 [] = +{ + ai_move, -24,NULL, + ai_move, 11, NULL, + ai_move, 5, NULL, + ai_move, -2, NULL, + ai_move, 6, NULL, + ai_move, 4, NULL +}; +mmove_t mutant_move_pain2 = {FRAME_pain201, FRAME_pain206, mutant_frames_pain2, mutant_run}; + +mframe_t mutant_frames_pain3 [] = +{ + ai_move, -22,NULL, + ai_move, 3, NULL, + ai_move, 3, NULL, + ai_move, 2, NULL, + ai_move, 1, NULL, + ai_move, 1, NULL, + ai_move, 6, NULL, + ai_move, 3, NULL, + ai_move, 2, NULL, + ai_move, 0, NULL, + ai_move, 1, NULL +}; +mmove_t mutant_move_pain3 = {FRAME_pain301, FRAME_pain311, mutant_frames_pain3, mutant_run}; + +void mutant_pain (edict_t *self, edict_t *other, float kick, int damage) +{ + float r; + + if (self->health < (self->max_health / 2)) + self->s.skinnum = 1; + + if (level.time < self->pain_debounce_time) + return; + + self->pain_debounce_time = level.time + 3; + + if (skill->value == 3) + return; // no pain anims in nightmare + + r = random(); + if (r < 0.33) + { + gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &mutant_move_pain1; + } + else if (r < 0.66) + { + gi.sound (self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &mutant_move_pain2; + } + else + { + gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &mutant_move_pain3; + } +} + + +// +// DEATH +// + +void mutant_dead (edict_t *self) +{ + VectorSet (self->mins, -16, -16, -24); + VectorSet (self->maxs, 16, 16, -8); + self->movetype = MOVETYPE_TOSS; + self->svflags |= SVF_DEADMONSTER; + gi.linkentity (self); + + M_FlyCheck (self); +} + +mframe_t mutant_frames_death1 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t mutant_move_death1 = {FRAME_death101, FRAME_death109, mutant_frames_death1, mutant_dead}; + +mframe_t mutant_frames_death2 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t mutant_move_death2 = {FRAME_death201, FRAME_death210, mutant_frames_death2, mutant_dead}; + +void mutant_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + int n; + + if (self->health <= self->gib_health) + { + gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0); + for (n= 0; n < 2; n++) + ThrowGib (self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC); + for (n= 0; n < 4; n++) + ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); + ThrowHead (self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC); + self->deadflag = DEAD_DEAD; + return; + } + + if (self->deadflag == DEAD_DEAD) + return; + + gi.sound (self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0); + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_YES; + self->s.skinnum = 1; + + if (random() < 0.5) + self->monsterinfo.currentmove = &mutant_move_death1; + else + self->monsterinfo.currentmove = &mutant_move_death2; +} + + + +//================ +//ROGUE +void mutant_jump_down (edict_t *self) +{ + vec3_t forward,up; + + AngleVectors (self->s.angles, forward, NULL, up); + VectorMA(self->velocity, 100, forward, self->velocity); + VectorMA(self->velocity, 300, up, self->velocity); +} + +void mutant_jump_up (edict_t *self) +{ + vec3_t forward,up; + + AngleVectors (self->s.angles, forward, NULL, up); + VectorMA(self->velocity, 200, forward, self->velocity); + VectorMA(self->velocity, 450, up, self->velocity); +} + +void mutant_jump_wait_land (edict_t *self) +{ + if(self->groundentity == NULL) + self->monsterinfo.nextframe = self->s.frame; + else + self->monsterinfo.nextframe = self->s.frame + 1; +} + +mframe_t mutant_frames_jump_up [] = +{ + ai_move, -8, NULL, + ai_move, -8, mutant_jump_up, + ai_move, 0, mutant_jump_wait_land, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t mutant_move_jump_up = { FRAME_jump01, FRAME_jump05, mutant_frames_jump_up, mutant_run }; + +mframe_t mutant_frames_jump_down [] = +{ + ai_move, 0, NULL, + ai_move, 0, mutant_jump_down, + ai_move, 0, mutant_jump_wait_land, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t mutant_move_jump_down = { FRAME_jump01, FRAME_jump05, mutant_frames_jump_down, mutant_run }; + +void mutant_jump_updown (edict_t *self) +{ + if(!self->enemy) + return; + + if(self->enemy->s.origin[2] > self->s.origin[2]) + self->monsterinfo.currentmove = &mutant_move_jump_up; + else + self->monsterinfo.currentmove = &mutant_move_jump_down; +} + +/* +=== +Blocked +=== +*/ +qboolean mutant_blocked (edict_t *self, float dist) +{ + if(blocked_checkjump (self, dist, 256, 68)) + { + mutant_jump_updown (self); + return true; + } + + if(blocked_checkplat (self, dist)) + return true; +} +//ROGUE +//================ + +// +// SPAWN +// + +/*QUAKED monster_mutant (1 .5 0) (-32 -32 -24) (32 32 32) Ambush Trigger_Spawn Sight +*/ +void SP_monster_mutant (edict_t *self) +{ + if (deathmatch->value) + { + G_FreeEdict (self); + return; + } + + sound_swing = gi.soundindex ("mutant/mutatck1.wav"); + sound_hit = gi.soundindex ("mutant/mutatck2.wav"); + sound_hit2 = gi.soundindex ("mutant/mutatck3.wav"); + sound_death = gi.soundindex ("mutant/mutdeth1.wav"); + sound_idle = gi.soundindex ("mutant/mutidle1.wav"); + sound_pain1 = gi.soundindex ("mutant/mutpain1.wav"); + sound_pain2 = gi.soundindex ("mutant/mutpain2.wav"); + sound_sight = gi.soundindex ("mutant/mutsght1.wav"); + sound_search = gi.soundindex ("mutant/mutsrch1.wav"); + sound_step1 = gi.soundindex ("mutant/step1.wav"); + sound_step2 = gi.soundindex ("mutant/step2.wav"); + sound_step3 = gi.soundindex ("mutant/step3.wav"); + sound_thud = gi.soundindex ("mutant/thud1.wav"); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex ("models/monsters/mutant/tris.md2"); + VectorSet (self->mins, -32, -32, -24); + VectorSet (self->maxs, 32, 32, 48); + + self->health = 300; + self->gib_health = -120; + self->mass = 300; + + self->pain = mutant_pain; + self->die = mutant_die; + + self->monsterinfo.stand = mutant_stand; + self->monsterinfo.walk = mutant_walk; + self->monsterinfo.run = mutant_run; + self->monsterinfo.dodge = NULL; + self->monsterinfo.attack = mutant_jump; + self->monsterinfo.melee = mutant_melee; + self->monsterinfo.sight = mutant_sight; + self->monsterinfo.search = mutant_search; + self->monsterinfo.idle = mutant_idle; + self->monsterinfo.checkattack = mutant_checkattack; + self->monsterinfo.blocked = mutant_blocked; // PGM + + gi.linkentity (self); + + self->monsterinfo.currentmove = &mutant_move_stand; + + self->monsterinfo.scale = MODEL_SCALE; + walkmonster_start (self); +} diff --git a/original/rogue/m_mutant.h b/original/rogue/m_mutant.h new file mode 100644 index 0000000..40ee872 --- /dev/null +++ b/original/rogue/m_mutant.h @@ -0,0 +1,163 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// G:\quake2\baseq2\models/monsters/mutant + +// This file generated by ModelGen - Do NOT Modify + +#define FRAME_attack01 0 +#define FRAME_attack02 1 +#define FRAME_attack03 2 +#define FRAME_attack04 3 +#define FRAME_attack05 4 +#define FRAME_attack06 5 +#define FRAME_attack07 6 +#define FRAME_attack08 7 +#define FRAME_attack09 8 +#define FRAME_attack10 9 +#define FRAME_attack11 10 +#define FRAME_attack12 11 +#define FRAME_attack13 12 +#define FRAME_attack14 13 +#define FRAME_attack15 14 +#define FRAME_death101 15 +#define FRAME_death102 16 +#define FRAME_death103 17 +#define FRAME_death104 18 +#define FRAME_death105 19 +#define FRAME_death106 20 +#define FRAME_death107 21 +#define FRAME_death108 22 +#define FRAME_death109 23 +#define FRAME_death201 24 +#define FRAME_death202 25 +#define FRAME_death203 26 +#define FRAME_death204 27 +#define FRAME_death205 28 +#define FRAME_death206 29 +#define FRAME_death207 30 +#define FRAME_death208 31 +#define FRAME_death209 32 +#define FRAME_death210 33 +#define FRAME_pain101 34 +#define FRAME_pain102 35 +#define FRAME_pain103 36 +#define FRAME_pain104 37 +#define FRAME_pain105 38 +#define FRAME_pain201 39 +#define FRAME_pain202 40 +#define FRAME_pain203 41 +#define FRAME_pain204 42 +#define FRAME_pain205 43 +#define FRAME_pain206 44 +#define FRAME_pain301 45 +#define FRAME_pain302 46 +#define FRAME_pain303 47 +#define FRAME_pain304 48 +#define FRAME_pain305 49 +#define FRAME_pain306 50 +#define FRAME_pain307 51 +#define FRAME_pain308 52 +#define FRAME_pain309 53 +#define FRAME_pain310 54 +#define FRAME_pain311 55 +#define FRAME_run03 56 +#define FRAME_run04 57 +#define FRAME_run05 58 +#define FRAME_run06 59 +#define FRAME_run07 60 +#define FRAME_run08 61 +#define FRAME_stand101 62 +#define FRAME_stand102 63 +#define FRAME_stand103 64 +#define FRAME_stand104 65 +#define FRAME_stand105 66 +#define FRAME_stand106 67 +#define FRAME_stand107 68 +#define FRAME_stand108 69 +#define FRAME_stand109 70 +#define FRAME_stand110 71 +#define FRAME_stand111 72 +#define FRAME_stand112 73 +#define FRAME_stand113 74 +#define FRAME_stand114 75 +#define FRAME_stand115 76 +#define FRAME_stand116 77 +#define FRAME_stand117 78 +#define FRAME_stand118 79 +#define FRAME_stand119 80 +#define FRAME_stand120 81 +#define FRAME_stand121 82 +#define FRAME_stand122 83 +#define FRAME_stand123 84 +#define FRAME_stand124 85 +#define FRAME_stand125 86 +#define FRAME_stand126 87 +#define FRAME_stand127 88 +#define FRAME_stand128 89 +#define FRAME_stand129 90 +#define FRAME_stand130 91 +#define FRAME_stand131 92 +#define FRAME_stand132 93 +#define FRAME_stand133 94 +#define FRAME_stand134 95 +#define FRAME_stand135 96 +#define FRAME_stand136 97 +#define FRAME_stand137 98 +#define FRAME_stand138 99 +#define FRAME_stand139 100 +#define FRAME_stand140 101 +#define FRAME_stand141 102 +#define FRAME_stand142 103 +#define FRAME_stand143 104 +#define FRAME_stand144 105 +#define FRAME_stand145 106 +#define FRAME_stand146 107 +#define FRAME_stand147 108 +#define FRAME_stand148 109 +#define FRAME_stand149 110 +#define FRAME_stand150 111 +#define FRAME_stand151 112 +#define FRAME_stand152 113 +#define FRAME_stand153 114 +#define FRAME_stand154 115 +#define FRAME_stand155 116 +#define FRAME_stand156 117 +#define FRAME_stand157 118 +#define FRAME_stand158 119 +#define FRAME_stand159 120 +#define FRAME_stand160 121 +#define FRAME_stand161 122 +#define FRAME_stand162 123 +#define FRAME_stand163 124 +#define FRAME_stand164 125 +#define FRAME_walk01 126 +#define FRAME_walk02 127 +#define FRAME_walk03 128 +#define FRAME_walk04 129 +#define FRAME_walk05 130 +#define FRAME_walk06 131 +#define FRAME_walk07 132 +#define FRAME_walk08 133 +#define FRAME_walk09 134 +#define FRAME_walk10 135 +#define FRAME_walk11 136 +#define FRAME_walk12 137 +#define FRAME_walk13 138 +#define FRAME_walk14 139 +#define FRAME_walk15 140 +#define FRAME_walk16 141 +#define FRAME_walk17 142 +#define FRAME_walk18 143 +#define FRAME_walk19 144 +#define FRAME_walk20 145 +#define FRAME_walk21 146 +#define FRAME_walk22 147 +#define FRAME_walk23 148 + +#define FRAME_jump01 149 +#define FRAME_jump02 150 +#define FRAME_jump03 151 +#define FRAME_jump04 152 +#define FRAME_jump05 153 + +#define MODEL_SCALE 1.000000 diff --git a/original/rogue/m_parasite.c b/original/rogue/m_parasite.c new file mode 100644 index 0000000..bb4ddb4 --- /dev/null +++ b/original/rogue/m_parasite.c @@ -0,0 +1,676 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +/* +============================================================================== + +parasite + +============================================================================== +*/ + +#include "g_local.h" +#include "m_parasite.h" + + +static int sound_pain1; +static int sound_pain2; +static int sound_die; +static int sound_launch; +static int sound_impact; +static int sound_suck; +static int sound_reelin; +static int sound_sight; +static int sound_tap; +static int sound_scratch; +static int sound_search; + + +void parasite_stand (edict_t *self); +void parasite_start_run (edict_t *self); +void parasite_run (edict_t *self); +void parasite_walk (edict_t *self); +void parasite_start_walk (edict_t *self); +void parasite_end_fidget (edict_t *self); +void parasite_do_fidget (edict_t *self); +void parasite_refidget (edict_t *self); + + +void parasite_launch (edict_t *self) +{ + gi.sound (self, CHAN_WEAPON, sound_launch, 1, ATTN_NORM, 0); +} + +void parasite_reel_in (edict_t *self) +{ + gi.sound (self, CHAN_WEAPON, sound_reelin, 1, ATTN_NORM, 0); +} + +void parasite_sight (edict_t *self, edict_t *other) +{ + gi.sound (self, CHAN_WEAPON, sound_sight, 1, ATTN_NORM, 0); +} + +void parasite_tap (edict_t *self) +{ + gi.sound (self, CHAN_WEAPON, sound_tap, 1, ATTN_IDLE, 0); +} + +void parasite_scratch (edict_t *self) +{ + gi.sound (self, CHAN_WEAPON, sound_scratch, 1, ATTN_IDLE, 0); +} + +void parasite_search (edict_t *self) +{ + gi.sound (self, CHAN_WEAPON, sound_search, 1, ATTN_IDLE, 0); +} + + +mframe_t parasite_frames_start_fidget [] = +{ + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL +}; +mmove_t parasite_move_start_fidget = {FRAME_stand18, FRAME_stand21, parasite_frames_start_fidget, parasite_do_fidget}; + +mframe_t parasite_frames_fidget [] = +{ + ai_stand, 0, parasite_scratch, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, parasite_scratch, + ai_stand, 0, NULL, + ai_stand, 0, NULL +}; +mmove_t parasite_move_fidget = {FRAME_stand22, FRAME_stand27, parasite_frames_fidget, parasite_refidget}; + +mframe_t parasite_frames_end_fidget [] = +{ + ai_stand, 0, parasite_scratch, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL +}; +mmove_t parasite_move_end_fidget = {FRAME_stand28, FRAME_stand35, parasite_frames_end_fidget, parasite_stand}; + +void parasite_end_fidget (edict_t *self) +{ + self->monsterinfo.currentmove = ¶site_move_end_fidget; +} + +void parasite_do_fidget (edict_t *self) +{ + self->monsterinfo.currentmove = ¶site_move_fidget; +} + +void parasite_refidget (edict_t *self) +{ + if (random() <= 0.8) + self->monsterinfo.currentmove = ¶site_move_fidget; + else + self->monsterinfo.currentmove = ¶site_move_end_fidget; +} + +void parasite_idle (edict_t *self) +{ + self->monsterinfo.currentmove = ¶site_move_start_fidget; +} + + +mframe_t parasite_frames_stand [] = +{ + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, parasite_tap, + ai_stand, 0, NULL, + ai_stand, 0, parasite_tap, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, parasite_tap, + ai_stand, 0, NULL, + ai_stand, 0, parasite_tap, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, parasite_tap, + ai_stand, 0, NULL, + ai_stand, 0, parasite_tap +}; +mmove_t parasite_move_stand = {FRAME_stand01, FRAME_stand17, parasite_frames_stand, parasite_stand}; + +void parasite_stand (edict_t *self) +{ + self->monsterinfo.currentmove = ¶site_move_stand; +} + + +mframe_t parasite_frames_run [] = +{ + ai_run, 30, NULL, + ai_run, 30, NULL, + ai_run, 22, NULL, + ai_run, 19, NULL, + ai_run, 24, NULL, + ai_run, 28, NULL, + ai_run, 25, NULL +}; +mmove_t parasite_move_run = {FRAME_run03, FRAME_run09, parasite_frames_run, NULL}; + +mframe_t parasite_frames_start_run [] = +{ + ai_run, 0, NULL, + ai_run, 30, NULL, +}; +mmove_t parasite_move_start_run = {FRAME_run01, FRAME_run02, parasite_frames_start_run, parasite_run}; + +mframe_t parasite_frames_stop_run [] = +{ + ai_run, 20, NULL, + ai_run, 20, NULL, + ai_run, 12, NULL, + ai_run, 10, NULL, + ai_run, 0, NULL, + ai_run, 0, NULL +}; +mmove_t parasite_move_stop_run = {FRAME_run10, FRAME_run15, parasite_frames_stop_run, NULL}; + +void parasite_start_run (edict_t *self) +{ + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + self->monsterinfo.currentmove = ¶site_move_stand; + else + self->monsterinfo.currentmove = ¶site_move_start_run; +} + +void parasite_run (edict_t *self) +{ + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + self->monsterinfo.currentmove = ¶site_move_stand; + else + self->monsterinfo.currentmove = ¶site_move_run; +} + + +mframe_t parasite_frames_walk [] = +{ + ai_walk, 30, NULL, + ai_walk, 30, NULL, + ai_walk, 22, NULL, + ai_walk, 19, NULL, + ai_walk, 24, NULL, + ai_walk, 28, NULL, + ai_walk, 25, NULL +}; +mmove_t parasite_move_walk = {FRAME_run03, FRAME_run09, parasite_frames_walk, parasite_walk}; + +mframe_t parasite_frames_start_walk [] = +{ + ai_walk, 0, NULL, + ai_walk, 30, parasite_walk +}; +mmove_t parasite_move_start_walk = {FRAME_run01, FRAME_run02, parasite_frames_start_walk, NULL}; + +mframe_t parasite_frames_stop_walk [] = +{ + ai_walk, 20, NULL, + ai_walk, 20, NULL, + ai_walk, 12, NULL, + ai_walk, 10, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL +}; +mmove_t parasite_move_stop_walk = {FRAME_run10, FRAME_run15, parasite_frames_stop_walk, NULL}; + +void parasite_start_walk (edict_t *self) +{ + self->monsterinfo.currentmove = ¶site_move_start_walk; +} + +void parasite_walk (edict_t *self) +{ + self->monsterinfo.currentmove = ¶site_move_walk; +} + + +mframe_t parasite_frames_pain1 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 6, NULL, + ai_move, 16, NULL, + ai_move, -6, NULL, + ai_move, -7, NULL, + ai_move, 0, NULL +}; +mmove_t parasite_move_pain1 = {FRAME_pain101, FRAME_pain111, parasite_frames_pain1, parasite_start_run}; + +void parasite_pain (edict_t *self, edict_t *other, float kick, int damage) +{ + if (self->health < (self->max_health / 2)) + self->s.skinnum = 1; + + if (level.time < self->pain_debounce_time) + return; + + self->pain_debounce_time = level.time + 3; + + if (skill->value == 3) + return; // no pain anims in nightmare + + if (random() < 0.5) + gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); + else + gi.sound (self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0); + + self->monsterinfo.currentmove = ¶site_move_pain1; +} + + +//static qboolean parasite_drain_attack_ok (vec3_t start, vec3_t end) +qboolean parasite_drain_attack_ok (vec3_t start, vec3_t end) +{ + vec3_t dir, angles; + + // check for max distance + VectorSubtract (start, end, dir); + if (VectorLength(dir) > 256) + return false; + + // check for min/max pitch + vectoangles (dir, angles); + if (angles[0] < -180) + angles[0] += 360; + if (fabs(angles[0]) > 30) + return false; + + return true; +} + +void parasite_drain_attack (edict_t *self) +{ + vec3_t offset, start, f, r, end, dir; + trace_t tr; + int damage; + + AngleVectors (self->s.angles, f, r, NULL); + VectorSet (offset, 24, 0, 6); + G_ProjectSource (self->s.origin, offset, f, r, start); + + VectorCopy (self->enemy->s.origin, end); + if (!parasite_drain_attack_ok(start, end)) + { + end[2] = self->enemy->s.origin[2] + self->enemy->maxs[2] - 8; + if (!parasite_drain_attack_ok(start, end)) + { + end[2] = self->enemy->s.origin[2] + self->enemy->mins[2] + 8; + if (!parasite_drain_attack_ok(start, end)) + return; + } + } + VectorCopy (self->enemy->s.origin, end); + + tr = gi.trace (start, NULL, NULL, end, self, MASK_SHOT); + if (tr.ent != self->enemy) + return; + + if (self->s.frame == FRAME_drain03) + { + damage = 5; + gi.sound (self->enemy, CHAN_AUTO, sound_impact, 1, ATTN_NORM, 0); + } + else + { + if (self->s.frame == FRAME_drain04) + gi.sound (self, CHAN_WEAPON, sound_suck, 1, ATTN_NORM, 0); + damage = 2; + } + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_PARASITE_ATTACK); + gi.WriteShort (self - g_edicts); + gi.WritePosition (start); + gi.WritePosition (end); + gi.multicast (self->s.origin, MULTICAST_PVS); + + VectorSubtract (start, end, dir); + T_Damage (self->enemy, self, self, dir, self->enemy->s.origin, vec3_origin, damage, 0, DAMAGE_NO_KNOCKBACK, MOD_UNKNOWN); +} + +mframe_t parasite_frames_drain [] = +{ + ai_charge, 0, parasite_launch, + ai_charge, 0, NULL, + ai_charge, 15, parasite_drain_attack, // Target hits + ai_charge, 0, parasite_drain_attack, // drain + ai_charge, 0, parasite_drain_attack, // drain + ai_charge, 0, parasite_drain_attack, // drain + ai_charge, 0, parasite_drain_attack, // drain + ai_charge, -2, parasite_drain_attack, // drain + ai_charge, -2, parasite_drain_attack, // drain + ai_charge, -3, parasite_drain_attack, // drain + ai_charge, -2, parasite_drain_attack, // drain + ai_charge, 0, parasite_drain_attack, // drain + ai_charge, -1, parasite_drain_attack, // drain + ai_charge, 0, parasite_reel_in, // let go + ai_charge, -2, NULL, + ai_charge, -2, NULL, + ai_charge, -3, NULL, + ai_charge, 0, NULL +}; +mmove_t parasite_move_drain = {FRAME_drain01, FRAME_drain18, parasite_frames_drain, parasite_start_run}; + + +mframe_t parasite_frames_break [] = +{ + ai_charge, 0, NULL, + ai_charge, -3, NULL, + ai_charge, 1, NULL, + ai_charge, 2, NULL, + ai_charge, -3, NULL, + ai_charge, 1, NULL, + ai_charge, 1, NULL, + ai_charge, 3, NULL, + ai_charge, 0, NULL, + ai_charge, -18, NULL, + ai_charge, 3, NULL, + ai_charge, 9, NULL, + ai_charge, 6, NULL, + ai_charge, 0, NULL, + ai_charge, -18, NULL, + ai_charge, 0, NULL, + ai_charge, 8, NULL, + ai_charge, 9, NULL, + ai_charge, 0, NULL, + ai_charge, -18, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, // airborne + ai_charge, 0, NULL, // airborne + ai_charge, 0, NULL, // slides + ai_charge, 0, NULL, // slides + ai_charge, 0, NULL, // slides + ai_charge, 0, NULL, // slides + ai_charge, 4, NULL, + ai_charge, 11, NULL, + ai_charge, -2, NULL, + ai_charge, -5, NULL, + ai_charge, 1, NULL +}; +mmove_t parasite_move_break = {FRAME_break01, FRAME_break32, parasite_frames_break, parasite_start_run}; + +/* +=== +Break Stuff Ends +=== +*/ + +void parasite_attack (edict_t *self) +{ +// if (random() <= 0.2) +// self->monsterinfo.currentmove = ¶site_move_break; +// else + self->monsterinfo.currentmove = ¶site_move_drain; +} + + +//================ +//ROGUE +void parasite_jump_down (edict_t *self) +{ + vec3_t forward,up; + + monster_jump_start (self); + + AngleVectors (self->s.angles, forward, NULL, up); + VectorMA(self->velocity, 100, forward, self->velocity); + VectorMA(self->velocity, 300, up, self->velocity); +} + +void parasite_jump_up (edict_t *self) +{ + vec3_t forward,up; + + monster_jump_start (self); + + AngleVectors (self->s.angles, forward, NULL, up); + VectorMA(self->velocity, 200, forward, self->velocity); + VectorMA(self->velocity, 450, up, self->velocity); +} + +void parasite_jump_wait_land (edict_t *self) +{ + if(self->groundentity == NULL) + { + self->monsterinfo.nextframe = self->s.frame; + + if(monster_jump_finished (self)) + self->monsterinfo.nextframe = self->s.frame + 1; + } + else + self->monsterinfo.nextframe = self->s.frame + 1; +} + +mframe_t parasite_frames_jump_up [] = +{ + ai_move, -8, NULL, + ai_move, -8, NULL, + ai_move, -8, NULL, + ai_move, -8, parasite_jump_up, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, parasite_jump_wait_land, + ai_move, 0, NULL +}; +mmove_t parasite_move_jump_up = { FRAME_jump01, FRAME_jump08, parasite_frames_jump_up, parasite_run }; + +mframe_t parasite_frames_jump_down [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, parasite_jump_down, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, parasite_jump_wait_land, + ai_move, 0, NULL +}; +mmove_t parasite_move_jump_down = { FRAME_jump01, FRAME_jump08, parasite_frames_jump_down, parasite_run }; + +void parasite_jump (edict_t *self) +{ + if(!self->enemy) + return; + + if(self->enemy->s.origin[2] > self->s.origin[2]) + self->monsterinfo.currentmove = ¶site_move_jump_up; + else + self->monsterinfo.currentmove = ¶site_move_jump_down; +} + +/* +=== +Blocked +=== +*/ +qboolean parasite_blocked (edict_t *self, float dist) +{ + if(blocked_checkshot (self, 0.25 + (0.05 * skill->value) )) + return true; + + if(blocked_checkjump (self, dist, 256, 68)) + { + parasite_jump (self); + return true; + } + + if(blocked_checkplat (self, dist)) + return true; +} +//ROGUE +//================ + + +qboolean parasite_checkattack (edict_t *self) +{ + vec3_t f, r, offset, start, end; + trace_t tr; + qboolean retval; + + retval = M_CheckAttack (self); + + if (!retval) + return false; + + AngleVectors (self->s.angles, f, r, NULL); + VectorSet (offset, 24, 0, 6); + G_ProjectSource (self->s.origin, offset, f, r, start); + + VectorCopy (self->enemy->s.origin, end); + if (!parasite_drain_attack_ok(start, end)) + { + end[2] = self->enemy->s.origin[2] + self->enemy->maxs[2] - 8; + if (!parasite_drain_attack_ok(start, end)) + { + end[2] = self->enemy->s.origin[2] + self->enemy->mins[2] + 8; + if (!parasite_drain_attack_ok(start, end)) + return false; + } + } + VectorCopy (self->enemy->s.origin, end); + + tr = gi.trace (start, NULL, NULL, end, self, MASK_SHOT); + if (tr.ent != self->enemy) + { + self->monsterinfo.aiflags |= AI_BLOCKED; + + if(self->monsterinfo.attack) + self->monsterinfo.attack(self); + + self->monsterinfo.aiflags &= ~AI_BLOCKED; + return true; + } +} + + +/* +=== +Death Stuff Starts +=== +*/ + +void parasite_dead (edict_t *self) +{ + VectorSet (self->mins, -16, -16, -24); + VectorSet (self->maxs, 16, 16, -8); + self->movetype = MOVETYPE_TOSS; + self->svflags |= SVF_DEADMONSTER; + self->nextthink = 0; + gi.linkentity (self); +} + +mframe_t parasite_frames_death [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t parasite_move_death = {FRAME_death101, FRAME_death107, parasite_frames_death, parasite_dead}; + +void parasite_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + int n; + +// check for gib + if (self->health <= self->gib_health) + { + gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0); + for (n= 0; n < 2; n++) + ThrowGib (self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC); + for (n= 0; n < 4; n++) + ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); + ThrowHead (self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC); + self->deadflag = DEAD_DEAD; + return; + } + + if (self->deadflag == DEAD_DEAD) + return; + +// regular death + gi.sound (self, CHAN_VOICE, sound_die, 1, ATTN_NORM, 0); + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_YES; + self->monsterinfo.currentmove = ¶site_move_death; +} + +/* +=== +End Death Stuff +=== +*/ + +/*QUAKED monster_parasite (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight +*/ +void SP_monster_parasite (edict_t *self) +{ + if (deathmatch->value) + { + G_FreeEdict (self); + return; + } + + sound_pain1 = gi.soundindex ("parasite/parpain1.wav"); + sound_pain2 = gi.soundindex ("parasite/parpain2.wav"); + sound_die = gi.soundindex ("parasite/pardeth1.wav"); + sound_launch = gi.soundindex("parasite/paratck1.wav"); + sound_impact = gi.soundindex("parasite/paratck2.wav"); + sound_suck = gi.soundindex("parasite/paratck3.wav"); + sound_reelin = gi.soundindex("parasite/paratck4.wav"); + sound_sight = gi.soundindex("parasite/parsght1.wav"); + sound_tap = gi.soundindex("parasite/paridle1.wav"); + sound_scratch = gi.soundindex("parasite/paridle2.wav"); + sound_search = gi.soundindex("parasite/parsrch1.wav"); + + self->s.modelindex = gi.modelindex ("models/monsters/parasite/tris.md2"); + VectorSet (self->mins, -16, -16, -24); + VectorSet (self->maxs, 16, 16, 24); + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + + self->health = 175; + self->gib_health = -50; + self->mass = 250; + + self->pain = parasite_pain; + self->die = parasite_die; + + self->monsterinfo.stand = parasite_stand; + self->monsterinfo.walk = parasite_start_walk; + self->monsterinfo.run = parasite_start_run; + self->monsterinfo.attack = parasite_attack; + self->monsterinfo.sight = parasite_sight; + self->monsterinfo.idle = parasite_idle; + self->monsterinfo.blocked = parasite_blocked; // PGM + self->monsterinfo.checkattack = parasite_checkattack; + + gi.linkentity (self); + + self->monsterinfo.currentmove = ¶site_move_stand; + self->monsterinfo.scale = MODEL_SCALE; + + walkmonster_start (self); +} diff --git a/original/rogue/m_parasite.h b/original/rogue/m_parasite.h new file mode 100644 index 0000000..2832eac --- /dev/null +++ b/original/rogue/m_parasite.h @@ -0,0 +1,135 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// G:\quake2\baseq2\models/monsters/parasite + +// This file generated by ModelGen - Do NOT Modify + +#define FRAME_break01 0 +#define FRAME_break02 1 +#define FRAME_break03 2 +#define FRAME_break04 3 +#define FRAME_break05 4 +#define FRAME_break06 5 +#define FRAME_break07 6 +#define FRAME_break08 7 +#define FRAME_break09 8 +#define FRAME_break10 9 +#define FRAME_break11 10 +#define FRAME_break12 11 +#define FRAME_break13 12 +#define FRAME_break14 13 +#define FRAME_break15 14 +#define FRAME_break16 15 +#define FRAME_break17 16 +#define FRAME_break18 17 +#define FRAME_break19 18 +#define FRAME_break20 19 +#define FRAME_break21 20 +#define FRAME_break22 21 +#define FRAME_break23 22 +#define FRAME_break24 23 +#define FRAME_break25 24 +#define FRAME_break26 25 +#define FRAME_break27 26 +#define FRAME_break28 27 +#define FRAME_break29 28 +#define FRAME_break30 29 +#define FRAME_break31 30 +#define FRAME_break32 31 +#define FRAME_death101 32 +#define FRAME_death102 33 +#define FRAME_death103 34 +#define FRAME_death104 35 +#define FRAME_death105 36 +#define FRAME_death106 37 +#define FRAME_death107 38 +#define FRAME_drain01 39 +#define FRAME_drain02 40 +#define FRAME_drain03 41 +#define FRAME_drain04 42 +#define FRAME_drain05 43 +#define FRAME_drain06 44 +#define FRAME_drain07 45 +#define FRAME_drain08 46 +#define FRAME_drain09 47 +#define FRAME_drain10 48 +#define FRAME_drain11 49 +#define FRAME_drain12 50 +#define FRAME_drain13 51 +#define FRAME_drain14 52 +#define FRAME_drain15 53 +#define FRAME_drain16 54 +#define FRAME_drain17 55 +#define FRAME_drain18 56 +#define FRAME_pain101 57 +#define FRAME_pain102 58 +#define FRAME_pain103 59 +#define FRAME_pain104 60 +#define FRAME_pain105 61 +#define FRAME_pain106 62 +#define FRAME_pain107 63 +#define FRAME_pain108 64 +#define FRAME_pain109 65 +#define FRAME_pain110 66 +#define FRAME_pain111 67 +#define FRAME_run01 68 +#define FRAME_run02 69 +#define FRAME_run03 70 +#define FRAME_run04 71 +#define FRAME_run05 72 +#define FRAME_run06 73 +#define FRAME_run07 74 +#define FRAME_run08 75 +#define FRAME_run09 76 +#define FRAME_run10 77 +#define FRAME_run11 78 +#define FRAME_run12 79 +#define FRAME_run13 80 +#define FRAME_run14 81 +#define FRAME_run15 82 +#define FRAME_stand01 83 +#define FRAME_stand02 84 +#define FRAME_stand03 85 +#define FRAME_stand04 86 +#define FRAME_stand05 87 +#define FRAME_stand06 88 +#define FRAME_stand07 89 +#define FRAME_stand08 90 +#define FRAME_stand09 91 +#define FRAME_stand10 92 +#define FRAME_stand11 93 +#define FRAME_stand12 94 +#define FRAME_stand13 95 +#define FRAME_stand14 96 +#define FRAME_stand15 97 +#define FRAME_stand16 98 +#define FRAME_stand17 99 +#define FRAME_stand18 100 +#define FRAME_stand19 101 +#define FRAME_stand20 102 +#define FRAME_stand21 103 +#define FRAME_stand22 104 +#define FRAME_stand23 105 +#define FRAME_stand24 106 +#define FRAME_stand25 107 +#define FRAME_stand26 108 +#define FRAME_stand27 109 +#define FRAME_stand28 110 +#define FRAME_stand29 111 +#define FRAME_stand30 112 +#define FRAME_stand31 113 +#define FRAME_stand32 114 +#define FRAME_stand33 115 +#define FRAME_stand34 116 +#define FRAME_stand35 117 + +#define FRAME_jump01 118 +#define FRAME_jump02 119 +#define FRAME_jump03 120 +#define FRAME_jump04 121 +#define FRAME_jump05 122 +#define FRAME_jump06 123 +#define FRAME_jump07 124 +#define FRAME_jump08 125 + +#define MODEL_SCALE 1.000000 diff --git a/original/rogue/m_player.h b/original/rogue/m_player.h new file mode 100644 index 0000000..990a100 --- /dev/null +++ b/original/rogue/m_player.h @@ -0,0 +1,207 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// G:\quake2\baseq2\models/player_x/frames + +// This file generated by qdata - Do NOT Modify + +#define FRAME_stand01 0 +#define FRAME_stand02 1 +#define FRAME_stand03 2 +#define FRAME_stand04 3 +#define FRAME_stand05 4 +#define FRAME_stand06 5 +#define FRAME_stand07 6 +#define FRAME_stand08 7 +#define FRAME_stand09 8 +#define FRAME_stand10 9 +#define FRAME_stand11 10 +#define FRAME_stand12 11 +#define FRAME_stand13 12 +#define FRAME_stand14 13 +#define FRAME_stand15 14 +#define FRAME_stand16 15 +#define FRAME_stand17 16 +#define FRAME_stand18 17 +#define FRAME_stand19 18 +#define FRAME_stand20 19 +#define FRAME_stand21 20 +#define FRAME_stand22 21 +#define FRAME_stand23 22 +#define FRAME_stand24 23 +#define FRAME_stand25 24 +#define FRAME_stand26 25 +#define FRAME_stand27 26 +#define FRAME_stand28 27 +#define FRAME_stand29 28 +#define FRAME_stand30 29 +#define FRAME_stand31 30 +#define FRAME_stand32 31 +#define FRAME_stand33 32 +#define FRAME_stand34 33 +#define FRAME_stand35 34 +#define FRAME_stand36 35 +#define FRAME_stand37 36 +#define FRAME_stand38 37 +#define FRAME_stand39 38 +#define FRAME_stand40 39 +#define FRAME_run1 40 +#define FRAME_run2 41 +#define FRAME_run3 42 +#define FRAME_run4 43 +#define FRAME_run5 44 +#define FRAME_run6 45 +#define FRAME_attack1 46 +#define FRAME_attack2 47 +#define FRAME_attack3 48 +#define FRAME_attack4 49 +#define FRAME_attack5 50 +#define FRAME_attack6 51 +#define FRAME_attack7 52 +#define FRAME_attack8 53 +#define FRAME_pain101 54 +#define FRAME_pain102 55 +#define FRAME_pain103 56 +#define FRAME_pain104 57 +#define FRAME_pain201 58 +#define FRAME_pain202 59 +#define FRAME_pain203 60 +#define FRAME_pain204 61 +#define FRAME_pain301 62 +#define FRAME_pain302 63 +#define FRAME_pain303 64 +#define FRAME_pain304 65 +#define FRAME_jump1 66 +#define FRAME_jump2 67 +#define FRAME_jump3 68 +#define FRAME_jump4 69 +#define FRAME_jump5 70 +#define FRAME_jump6 71 +#define FRAME_flip01 72 +#define FRAME_flip02 73 +#define FRAME_flip03 74 +#define FRAME_flip04 75 +#define FRAME_flip05 76 +#define FRAME_flip06 77 +#define FRAME_flip07 78 +#define FRAME_flip08 79 +#define FRAME_flip09 80 +#define FRAME_flip10 81 +#define FRAME_flip11 82 +#define FRAME_flip12 83 +#define FRAME_salute01 84 +#define FRAME_salute02 85 +#define FRAME_salute03 86 +#define FRAME_salute04 87 +#define FRAME_salute05 88 +#define FRAME_salute06 89 +#define FRAME_salute07 90 +#define FRAME_salute08 91 +#define FRAME_salute09 92 +#define FRAME_salute10 93 +#define FRAME_salute11 94 +#define FRAME_taunt01 95 +#define FRAME_taunt02 96 +#define FRAME_taunt03 97 +#define FRAME_taunt04 98 +#define FRAME_taunt05 99 +#define FRAME_taunt06 100 +#define FRAME_taunt07 101 +#define FRAME_taunt08 102 +#define FRAME_taunt09 103 +#define FRAME_taunt10 104 +#define FRAME_taunt11 105 +#define FRAME_taunt12 106 +#define FRAME_taunt13 107 +#define FRAME_taunt14 108 +#define FRAME_taunt15 109 +#define FRAME_taunt16 110 +#define FRAME_taunt17 111 +#define FRAME_wave01 112 +#define FRAME_wave02 113 +#define FRAME_wave03 114 +#define FRAME_wave04 115 +#define FRAME_wave05 116 +#define FRAME_wave06 117 +#define FRAME_wave07 118 +#define FRAME_wave08 119 +#define FRAME_wave09 120 +#define FRAME_wave10 121 +#define FRAME_wave11 122 +#define FRAME_point01 123 +#define FRAME_point02 124 +#define FRAME_point03 125 +#define FRAME_point04 126 +#define FRAME_point05 127 +#define FRAME_point06 128 +#define FRAME_point07 129 +#define FRAME_point08 130 +#define FRAME_point09 131 +#define FRAME_point10 132 +#define FRAME_point11 133 +#define FRAME_point12 134 +#define FRAME_crstnd01 135 +#define FRAME_crstnd02 136 +#define FRAME_crstnd03 137 +#define FRAME_crstnd04 138 +#define FRAME_crstnd05 139 +#define FRAME_crstnd06 140 +#define FRAME_crstnd07 141 +#define FRAME_crstnd08 142 +#define FRAME_crstnd09 143 +#define FRAME_crstnd10 144 +#define FRAME_crstnd11 145 +#define FRAME_crstnd12 146 +#define FRAME_crstnd13 147 +#define FRAME_crstnd14 148 +#define FRAME_crstnd15 149 +#define FRAME_crstnd16 150 +#define FRAME_crstnd17 151 +#define FRAME_crstnd18 152 +#define FRAME_crstnd19 153 +#define FRAME_crwalk1 154 +#define FRAME_crwalk2 155 +#define FRAME_crwalk3 156 +#define FRAME_crwalk4 157 +#define FRAME_crwalk5 158 +#define FRAME_crwalk6 159 +#define FRAME_crattak1 160 +#define FRAME_crattak2 161 +#define FRAME_crattak3 162 +#define FRAME_crattak4 163 +#define FRAME_crattak5 164 +#define FRAME_crattak6 165 +#define FRAME_crattak7 166 +#define FRAME_crattak8 167 +#define FRAME_crattak9 168 +#define FRAME_crpain1 169 +#define FRAME_crpain2 170 +#define FRAME_crpain3 171 +#define FRAME_crpain4 172 +#define FRAME_crdeath1 173 +#define FRAME_crdeath2 174 +#define FRAME_crdeath3 175 +#define FRAME_crdeath4 176 +#define FRAME_crdeath5 177 +#define FRAME_death101 178 +#define FRAME_death102 179 +#define FRAME_death103 180 +#define FRAME_death104 181 +#define FRAME_death105 182 +#define FRAME_death106 183 +#define FRAME_death201 184 +#define FRAME_death202 185 +#define FRAME_death203 186 +#define FRAME_death204 187 +#define FRAME_death205 188 +#define FRAME_death206 189 +#define FRAME_death301 190 +#define FRAME_death302 191 +#define FRAME_death303 192 +#define FRAME_death304 193 +#define FRAME_death305 194 +#define FRAME_death306 195 +#define FRAME_death307 196 +#define FRAME_death308 197 + +#define MODEL_SCALE 1.000000 + diff --git a/original/rogue/m_rider.h b/original/rogue/m_rider.h new file mode 100644 index 0000000..e7f3e7a --- /dev/null +++ b/original/rogue/m_rider.h @@ -0,0 +1,68 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// G:\quake2\baseq2\models/monsters/boss3/rider + +// This file generated by ModelGen - Do NOT Modify + +#define FRAME_stand201 0 +#define FRAME_stand202 1 +#define FRAME_stand203 2 +#define FRAME_stand204 3 +#define FRAME_stand205 4 +#define FRAME_stand206 5 +#define FRAME_stand207 6 +#define FRAME_stand208 7 +#define FRAME_stand209 8 +#define FRAME_stand210 9 +#define FRAME_stand211 10 +#define FRAME_stand212 11 +#define FRAME_stand213 12 +#define FRAME_stand214 13 +#define FRAME_stand215 14 +#define FRAME_stand216 15 +#define FRAME_stand217 16 +#define FRAME_stand218 17 +#define FRAME_stand219 18 +#define FRAME_stand220 19 +#define FRAME_stand221 20 +#define FRAME_stand222 21 +#define FRAME_stand223 22 +#define FRAME_stand224 23 +#define FRAME_stand225 24 +#define FRAME_stand226 25 +#define FRAME_stand227 26 +#define FRAME_stand228 27 +#define FRAME_stand229 28 +#define FRAME_stand230 29 +#define FRAME_stand231 30 +#define FRAME_stand232 31 +#define FRAME_stand233 32 +#define FRAME_stand234 33 +#define FRAME_stand235 34 +#define FRAME_stand236 35 +#define FRAME_stand237 36 +#define FRAME_stand238 37 +#define FRAME_stand239 38 +#define FRAME_stand240 39 +#define FRAME_stand241 40 +#define FRAME_stand242 41 +#define FRAME_stand243 42 +#define FRAME_stand244 43 +#define FRAME_stand245 44 +#define FRAME_stand246 45 +#define FRAME_stand247 46 +#define FRAME_stand248 47 +#define FRAME_stand249 48 +#define FRAME_stand250 49 +#define FRAME_stand251 50 +#define FRAME_stand252 51 +#define FRAME_stand253 52 +#define FRAME_stand254 53 +#define FRAME_stand255 54 +#define FRAME_stand256 55 +#define FRAME_stand257 56 +#define FRAME_stand258 57 +#define FRAME_stand259 58 +#define FRAME_stand260 59 + +#define MODEL_SCALE 1.000000 diff --git a/original/rogue/m_soldier.c b/original/rogue/m_soldier.c new file mode 100644 index 0000000..0d8f838 --- /dev/null +++ b/original/rogue/m_soldier.c @@ -0,0 +1,1783 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +/* +============================================================================== + +SOLDIER + +============================================================================== +*/ + +#include "g_local.h" +#include "m_soldier.h" + +//ROGUE +#define RUN_SHOOT 1 +#define CHECK_TARGET 1 +//ROGUE + +static int sound_idle; +static int sound_sight1; +static int sound_sight2; +static int sound_pain_light; +static int sound_pain; +static int sound_pain_ss; +static int sound_death_light; +static int sound_death; +static int sound_death_ss; +static int sound_cock; + +void soldier_duck_up (edict_t *self); + +void soldier_start_charge (edict_t *self) +{ + self->monsterinfo.aiflags |= AI_CHARGING; +} + +void soldier_stop_charge (edict_t *self) +{ + self->monsterinfo.aiflags &= ~AI_CHARGING; +} + +void soldier_idle (edict_t *self) +{ + if (random() > 0.8) + gi.sound (self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0); +} + +void soldier_cock (edict_t *self) +{ + if (self->s.frame == FRAME_stand322) + gi.sound (self, CHAN_WEAPON, sound_cock, 1, ATTN_IDLE, 0); + else + gi.sound (self, CHAN_WEAPON, sound_cock, 1, ATTN_NORM, 0); +} + + +// STAND + +void soldier_stand (edict_t *self); + +mframe_t soldier_frames_stand1 [] = +{ + ai_stand, 0, soldier_idle, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL +}; +mmove_t soldier_move_stand1 = {FRAME_stand101, FRAME_stand130, soldier_frames_stand1, soldier_stand}; + +mframe_t soldier_frames_stand3 [] = +{ + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + + ai_stand, 0, NULL, + ai_stand, 0, soldier_cock, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL +}; +mmove_t soldier_move_stand3 = {FRAME_stand301, FRAME_stand339, soldier_frames_stand3, soldier_stand}; + +#if 0 +mframe_t soldier_frames_stand4 [] = +{ + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 4, NULL, + ai_stand, 1, NULL, + ai_stand, -1, NULL, + ai_stand, -2, NULL, + + ai_stand, 0, NULL, + ai_stand, 0, NULL +}; +mmove_t soldier_move_stand4 = {FRAME_stand401, FRAME_stand452, soldier_frames_stand4, NULL}; +#endif + +void soldier_stand (edict_t *self) +{ + if ((self->monsterinfo.currentmove == &soldier_move_stand3) || (random() < 0.8)) + self->monsterinfo.currentmove = &soldier_move_stand1; + else + self->monsterinfo.currentmove = &soldier_move_stand3; +} + + +// +// WALK +// + +void soldier_walk1_random (edict_t *self) +{ + if (random() > 0.1) + self->monsterinfo.nextframe = FRAME_walk101; +} + +mframe_t soldier_frames_walk1 [] = +{ + ai_walk, 3, NULL, + ai_walk, 6, NULL, + ai_walk, 2, NULL, + ai_walk, 2, NULL, + ai_walk, 2, NULL, + ai_walk, 1, NULL, + ai_walk, 6, NULL, + ai_walk, 5, NULL, + ai_walk, 3, NULL, + ai_walk, -1, soldier_walk1_random, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL +}; +mmove_t soldier_move_walk1 = {FRAME_walk101, FRAME_walk133, soldier_frames_walk1, NULL}; + +mframe_t soldier_frames_walk2 [] = +{ + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 9, NULL, + ai_walk, 8, NULL, + ai_walk, 5, NULL, + ai_walk, 1, NULL, + ai_walk, 3, NULL, + ai_walk, 7, NULL, + ai_walk, 6, NULL, + ai_walk, 7, NULL +}; +mmove_t soldier_move_walk2 = {FRAME_walk209, FRAME_walk218, soldier_frames_walk2, NULL}; + +void soldier_walk (edict_t *self) +{ + if (random() < 0.5) + self->monsterinfo.currentmove = &soldier_move_walk1; + else + self->monsterinfo.currentmove = &soldier_move_walk2; +} + + +// +// RUN +// + +void soldier_run (edict_t *self); + +mframe_t soldier_frames_start_run [] = +{ + ai_run, 7, NULL, + ai_run, 5, NULL +}; +mmove_t soldier_move_start_run = {FRAME_run01, FRAME_run02, soldier_frames_start_run, soldier_run}; + +#ifdef RUN_SHOOT +void soldier_fire (edict_t *self, int); + +void soldier_fire_run (edict_t *self) { + if ((self->s.skinnum <= 1) && (self->enemy) && visible(self, self->enemy)) { + soldier_fire(self, 0); + } +} +#endif + +mframe_t soldier_frames_run [] = +{ + ai_run, 10, NULL, + ai_run, 11, monster_done_dodge, + ai_run, 11, NULL, + ai_run, 16, NULL, + ai_run, 10, NULL, + ai_run, 15, monster_done_dodge +}; + +mmove_t soldier_move_run = {FRAME_run03, FRAME_run08, soldier_frames_run, NULL}; + +void soldier_run (edict_t *self) +{ + monster_done_dodge (self); + + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + { + self->monsterinfo.currentmove = &soldier_move_stand1; + return; + } + + if (self->monsterinfo.currentmove == &soldier_move_walk1 || + self->monsterinfo.currentmove == &soldier_move_walk2 || + self->monsterinfo.currentmove == &soldier_move_start_run) + { + self->monsterinfo.currentmove = &soldier_move_run; + } + else + { + self->monsterinfo.currentmove = &soldier_move_start_run; + } +} + + +// +// PAIN +// + +mframe_t soldier_frames_pain1 [] = +{ + ai_move, -3, NULL, + ai_move, 4, NULL, + ai_move, 1, NULL, + ai_move, 1, NULL, + ai_move, 0, NULL +}; +mmove_t soldier_move_pain1 = {FRAME_pain101, FRAME_pain105, soldier_frames_pain1, soldier_run}; + +mframe_t soldier_frames_pain2 [] = +{ + ai_move, -13, NULL, + ai_move, -1, NULL, + ai_move, 2, NULL, + ai_move, 4, NULL, + ai_move, 2, NULL, + ai_move, 3, NULL, + ai_move, 2, NULL +}; +mmove_t soldier_move_pain2 = {FRAME_pain201, FRAME_pain207, soldier_frames_pain2, soldier_run}; + +mframe_t soldier_frames_pain3 [] = +{ + ai_move, -8, NULL, + ai_move, 10, NULL, + ai_move, -4, NULL, + ai_move, -1, NULL, + ai_move, -3, NULL, + ai_move, 0, NULL, + ai_move, 3, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 1, NULL, + ai_move, 0, NULL, + ai_move, 1, NULL, + ai_move, 2, NULL, + ai_move, 4, NULL, + ai_move, 3, NULL, + ai_move, 2, NULL +}; +mmove_t soldier_move_pain3 = {FRAME_pain301, FRAME_pain318, soldier_frames_pain3, soldier_run}; + +mframe_t soldier_frames_pain4 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, -10, NULL, + ai_move, -6, NULL, + ai_move, 8, NULL, + ai_move, 4, NULL, + ai_move, 1, NULL, + ai_move, 0, NULL, + ai_move, 2, NULL, + ai_move, 5, NULL, + ai_move, 2, NULL, + ai_move, -1, NULL, + ai_move, -1, NULL, + ai_move, 3, NULL, + ai_move, 2, NULL, + ai_move, 0, NULL +}; +mmove_t soldier_move_pain4 = {FRAME_pain401, FRAME_pain417, soldier_frames_pain4, soldier_run}; + + +void soldier_pain (edict_t *self, edict_t *other, float kick, int damage) +{ + float r; + int n; + + if (self->health < (self->max_health / 2)) + self->s.skinnum |= 1; + + monster_done_dodge (self); + soldier_stop_charge(self); + + // if we're blind firing, this needs to be turned off here + self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING; + + if (level.time < self->pain_debounce_time) + { + if ((self->velocity[2] > 100) && ( (self->monsterinfo.currentmove == &soldier_move_pain1) || (self->monsterinfo.currentmove == &soldier_move_pain2) || (self->monsterinfo.currentmove == &soldier_move_pain3))) + { + // PMM - clear duck flag + if (self->monsterinfo.aiflags & AI_DUCKED) + monster_duck_up(self); + self->monsterinfo.currentmove = &soldier_move_pain4; + } + return; + } + + self->pain_debounce_time = level.time + 3; + + n = self->s.skinnum | 1; + if (n == 1) + gi.sound (self, CHAN_VOICE, sound_pain_light, 1, ATTN_NORM, 0); + else if (n == 3) + gi.sound (self, CHAN_VOICE, sound_pain, 1, ATTN_NORM, 0); + else + gi.sound (self, CHAN_VOICE, sound_pain_ss, 1, ATTN_NORM, 0); + + if (self->velocity[2] > 100) + { + // PMM - clear duck flag + if (self->monsterinfo.aiflags & AI_DUCKED) + monster_duck_up(self); + self->monsterinfo.currentmove = &soldier_move_pain4; +// self->monsterinfo.pausetime = 0; + return; + } + + if (skill->value == 3) + return; // no pain anims in nightmare + + r = random(); + + if (r < 0.33) + self->monsterinfo.currentmove = &soldier_move_pain1; + else if (r < 0.66) + self->monsterinfo.currentmove = &soldier_move_pain2; + else + self->monsterinfo.currentmove = &soldier_move_pain3; + + // PMM - clear duck flag + if (self->monsterinfo.aiflags & AI_DUCKED) + monster_duck_up(self); +// self->monsterinfo.pausetime = 0; + +} + + +// +// ATTACK +// + +static int blaster_flash [] = {MZ2_SOLDIER_BLASTER_1, MZ2_SOLDIER_BLASTER_2, MZ2_SOLDIER_BLASTER_3, MZ2_SOLDIER_BLASTER_4, MZ2_SOLDIER_BLASTER_5, MZ2_SOLDIER_BLASTER_6, MZ2_SOLDIER_BLASTER_7, MZ2_SOLDIER_BLASTER_8}; +static int shotgun_flash [] = {MZ2_SOLDIER_SHOTGUN_1, MZ2_SOLDIER_SHOTGUN_2, MZ2_SOLDIER_SHOTGUN_3, MZ2_SOLDIER_SHOTGUN_4, MZ2_SOLDIER_SHOTGUN_5, MZ2_SOLDIER_SHOTGUN_6, MZ2_SOLDIER_SHOTGUN_7, MZ2_SOLDIER_SHOTGUN_8}; +static int machinegun_flash [] = {MZ2_SOLDIER_MACHINEGUN_1, MZ2_SOLDIER_MACHINEGUN_2, MZ2_SOLDIER_MACHINEGUN_3, MZ2_SOLDIER_MACHINEGUN_4, MZ2_SOLDIER_MACHINEGUN_5, MZ2_SOLDIER_MACHINEGUN_6, MZ2_SOLDIER_MACHINEGUN_7, MZ2_SOLDIER_MACHINEGUN_8}; + +//void soldier_fire (edict_t *self, int flash_number) PMM +void soldier_fire (edict_t *self, int in_flash_number) +{ + vec3_t start; + vec3_t forward, right, up; + vec3_t aim; + vec3_t dir; + vec3_t end; + float r, u; + int flash_index; + int flash_number; +#ifdef RUN_SHOOT + vec3_t aim_norm; + float angle; +#endif +#ifdef CHECK_TARGET + trace_t tr; + vec3_t aim_good; +#endif + + if ((!self->enemy) || (!self->enemy->inuse)) + { + self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; + return; + } + + if (in_flash_number < 0) + { + flash_number = -1 * in_flash_number; + } + else + flash_number = in_flash_number; + + if (self->s.skinnum < 2) + flash_index = blaster_flash[flash_number]; + else if (self->s.skinnum < 4) + flash_index = shotgun_flash[flash_number]; + else + flash_index = machinegun_flash[flash_number]; + + AngleVectors (self->s.angles, forward, right, NULL); + G_ProjectSource (self->s.origin, monster_flash_offset[flash_index], forward, right, start); + + if (flash_number == 5 || flash_number == 6) // he's dead + { + VectorCopy (forward, aim); + } + else + { + VectorCopy (self->enemy->s.origin, end); + end[2] += self->enemy->viewheight; + VectorSubtract (end, start, aim); +#ifdef CHECK_TARGET + VectorCopy (end, aim_good); +#endif +#ifdef RUN_SHOOT + //PMM + if (in_flash_number < 0) + { + VectorCopy (aim, aim_norm); + VectorNormalize (aim_norm); + angle = DotProduct (aim_norm, forward); + //gi.dprintf ("Dot Product: %f", DotProduct (aim_norm, forward)); + if (angle < 0.9) // ~25 degree angle + { +// if(g_showlogic && g_showlogic->value) +// gi.dprintf (" not firing due to bad dotprod %f\n", angle); + return; + } +// else +// { +// if(g_showlogic && g_showlogic->value) +// gi.dprintf (" firing: dotprod = %f\n", angle); +// } + } + //-PMM +#endif + vectoangles (aim, dir); + AngleVectors (dir, forward, right, up); + + if (skill->value < 2) + { + r = crandom()*1000; + u = crandom()*500; + } + else + { + r = crandom()*500; + u = crandom()*250; + } + VectorMA (start, 8192, forward, end); + VectorMA (end, r, right, end); + VectorMA (end, u, up, end); + + VectorSubtract (end, start, aim); + VectorNormalize (aim); + } +#ifdef CHECK_TARGET + if (!(flash_number == 5 || flash_number == 6)) // he's dead + { + tr = gi.trace (start, NULL, NULL, aim_good, self, MASK_SHOT); + if ((tr.ent != self->enemy) && (tr.ent != world)) + { +// if(g_showlogic && g_showlogic->value) +// gi.dprintf ("infantry shot aborted due to bad target\n"); + return; + } + } +#endif + if (self->s.skinnum <= 1) + { + monster_fire_blaster (self, start, aim, 5, 600, flash_index, EF_BLASTER); + } + else if (self->s.skinnum <= 3) + { + monster_fire_shotgun (self, start, aim, 2, 1, DEFAULT_SHOTGUN_HSPREAD, DEFAULT_SHOTGUN_VSPREAD, DEFAULT_SHOTGUN_COUNT, flash_index); + } + else + { + // PMM - changed to wait from pausetime to not interfere with dodge code + if (!(self->monsterinfo.aiflags & AI_HOLD_FRAME)) + self->wait = level.time + (3 + rand() % 8) * FRAMETIME; + + monster_fire_bullet (self, start, aim, 2, 4, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, flash_index); + + if (level.time >= self->wait) + self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; + else + self->monsterinfo.aiflags |= AI_HOLD_FRAME; + } +} + +// ATTACK1 (blaster/shotgun) + +void soldier_fire1 (edict_t *self) +{ + soldier_fire (self, 0); +} + +void soldier_attack1_refire1 (edict_t *self) +{ + // PMM - blindfire + if (self->monsterinfo.aiflags & AI_MANUAL_STEERING) + { + self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING; + return; + } + // pmm + + if (!self->enemy) + return; + + if (self->s.skinnum > 1) + return; + + if (self->enemy->health <= 0) + return; + + if ( ((skill->value == 3) && (random() < 0.5)) || (range(self, self->enemy) == RANGE_MELEE) ) + self->monsterinfo.nextframe = FRAME_attak102; + else + self->monsterinfo.nextframe = FRAME_attak110; +} + +void soldier_attack1_refire2 (edict_t *self) +{ + if (!self->enemy) + return; + + if (self->s.skinnum < 2) + return; + + if (self->enemy->health <= 0) + return; + + if ( ((skill->value == 3) && (random() < 0.5)) || (range(self, self->enemy) == RANGE_MELEE) ) + self->monsterinfo.nextframe = FRAME_attak102; +} + +mframe_t soldier_frames_attack1 [] = +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, soldier_fire1, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, soldier_attack1_refire1, + ai_charge, 0, NULL, + ai_charge, 0, soldier_cock, + ai_charge, 0, soldier_attack1_refire2, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL +}; +mmove_t soldier_move_attack1 = {FRAME_attak101, FRAME_attak112, soldier_frames_attack1, soldier_run}; + +// ATTACK2 (blaster/shotgun) + +void soldier_fire2 (edict_t *self) +{ + soldier_fire (self, 1); +} + +void soldier_attack2_refire1 (edict_t *self) +{ + if (!self->enemy) + return; + + if (self->s.skinnum > 1) + return; + + if (self->enemy->health <= 0) + return; + + if ( ((skill->value == 3) && (random() < 0.5)) || (range(self, self->enemy) == RANGE_MELEE) ) + self->monsterinfo.nextframe = FRAME_attak204; + else + self->monsterinfo.nextframe = FRAME_attak216; +} + +void soldier_attack2_refire2 (edict_t *self) +{ + if (!self->enemy) + return; + + if (self->s.skinnum < 2) + return; + + if (self->enemy->health <= 0) + return; + + if ( ((skill->value == 3) && (random() < 0.5)) || (range(self, self->enemy) == RANGE_MELEE) ) + self->monsterinfo.nextframe = FRAME_attak204; +} + +mframe_t soldier_frames_attack2 [] = +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, soldier_fire2, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, soldier_attack2_refire1, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, soldier_cock, + ai_charge, 0, NULL, + ai_charge, 0, soldier_attack2_refire2, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL +}; +mmove_t soldier_move_attack2 = {FRAME_attak201, FRAME_attak218, soldier_frames_attack2, soldier_run}; + +// ATTACK3 (duck and shoot) +/* +void soldier_duck_down (edict_t *self) +{ + if ((g_showlogic) && (g_showlogic->value)) + gi.dprintf ("duck down - %d!\n", self->s.frame); + + self->monsterinfo.aiflags |= AI_DUCKED; +// self->maxs[2] -= 32; + self->maxs[2] = self->monsterinfo.base_height - 32; + self->takedamage = DAMAGE_YES; + if (self->monsterinfo.duck_wait_time < level.time) + { + if ((g_showlogic) && (g_showlogic->value)) + gi.dprintf ("soldier duck with no time!\n"); + self->monsterinfo.duck_wait_time = level.time + 1; + } + gi.linkentity (self); +} + +void soldier_duck_up (edict_t *self) +{ + if ((g_showlogic) && (g_showlogic->value)) + gi.dprintf ("duck up - %d!\n", self->s.frame); + self->monsterinfo.aiflags &= ~AI_DUCKED; +// self->maxs[2] += 32; + self->maxs[2] = self->monsterinfo.base_height; + self->takedamage = DAMAGE_AIM; + gi.linkentity (self); +} +*/ +void soldier_fire3 (edict_t *self) +{ + monster_duck_down (self); + soldier_fire (self, 2); +} + +void soldier_attack3_refire (edict_t *self) +{ + if ((level.time + 0.4) < self->monsterinfo.duck_wait_time) + self->monsterinfo.nextframe = FRAME_attak303; +} + +mframe_t soldier_frames_attack3 [] = +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, soldier_fire3, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, soldier_attack3_refire, + ai_charge, 0, monster_duck_up, + ai_charge, 0, NULL, + ai_charge, 0, NULL +}; +mmove_t soldier_move_attack3 = {FRAME_attak301, FRAME_attak309, soldier_frames_attack3, soldier_run}; + +// ATTACK4 (machinegun) + +void soldier_fire4 (edict_t *self) +{ + soldier_fire (self, 3); +// +// if (self->enemy->health <= 0) +// return; +// +// if ( ((skill->value == 3) && (random() < 0.5)) || (range(self, self->enemy) == RANGE_MELEE) ) +// self->monsterinfo.nextframe = FRAME_attak402; +} + +mframe_t soldier_frames_attack4 [] = +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, soldier_fire4, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL +}; +mmove_t soldier_move_attack4 = {FRAME_attak401, FRAME_attak406, soldier_frames_attack4, soldier_run}; + +#if 0 +// ATTACK5 (prone) + +void soldier_fire5 (edict_t *self) +{ + soldier_fire (self, 4); +} + +void soldier_attack5_refire (edict_t *self) +{ + if (!self->enemy) + return; + + if (self->enemy->health <= 0) + return; + + if ( ((skill->value == 3) && (random() < 0.5)) || (range(self, self->enemy) == RANGE_MELEE) ) + self->monsterinfo.nextframe = FRAME_attak505; +} + +mframe_t soldier_frames_attack5 [] = +{ + ai_charge, 8, NULL, + ai_charge, 8, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, soldier_fire5, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, soldier_attack5_refire +}; +mmove_t soldier_move_attack5 = {FRAME_attak501, FRAME_attak508, soldier_frames_attack5, soldier_run}; +#endif + +// ATTACK6 (run & shoot) + +void soldier_fire8 (edict_t *self) +{ + soldier_fire (self, -7); +// self->monsterinfo.aiflags |= AI_HOLD_FRAME; +// self->monsterinfo.pausetime = level.time + 1000000; +} + +void soldier_attack6_refire (edict_t *self) +{ + // PMM - make sure dodge & charge bits are cleared + monster_done_dodge (self); + soldier_stop_charge (self); + + if (!self->enemy) + return; + + if (self->enemy->health <= 0) + return; + +// if (range(self, self->enemy) < RANGE_MID) + if (range(self, self->enemy) < RANGE_NEAR) + return; + + if ((skill->value == 3) || ((random() < (0.25*((float)skill->value))))) + self->monsterinfo.nextframe = FRAME_runs03; +} + +mframe_t soldier_frames_attack6 [] = +{ +// PMM +// ai_run, 10, NULL, + ai_run, 10, soldier_start_charge, + ai_run, 4, NULL, + ai_run, 12, soldier_fire8, + ai_run, 11, NULL, + ai_run, 13, monster_done_dodge, + ai_run, 18, NULL, + ai_run, 15, NULL, + ai_run, 14, NULL, + ai_run, 11, NULL, + ai_run, 8, NULL, + ai_run, 11, NULL, + ai_run, 12, NULL, + ai_run, 12, NULL, + ai_run, 17, soldier_attack6_refire +}; +mmove_t soldier_move_attack6 = {FRAME_runs01, FRAME_runs14, soldier_frames_attack6, soldier_run}; + +void soldier_attack(edict_t *self) +{ + float r, chance; + + monster_done_dodge (self); + + // PMM - blindfire! + if (self->monsterinfo.attack_state == AS_BLIND) + { + // setup shot probabilities + if (self->monsterinfo.blind_fire_delay < 1.0) + chance = 1.0; + else if (self->monsterinfo.blind_fire_delay < 7.5) + chance = 0.4; + else + chance = 0.1; + + r = random(); + + // minimum of 2 seconds, plus 0-3, after the shots are done + self->monsterinfo.blind_fire_delay += 2.1 + 2.0 + random()*3.0; + + // don't shoot at the origin + if (VectorCompare (self->monsterinfo.blind_fire_target, vec3_origin)) + return; + + // don't shoot if the dice say not to + if (r > chance) + { +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("blindfire - NO SHOT\n"); + return; + } + + // turn on manual steering to signal both manual steering and blindfire + self->monsterinfo.aiflags |= AI_MANUAL_STEERING; + self->monsterinfo.currentmove = &soldier_move_attack1; + self->monsterinfo.attack_finished = level.time + 1.5 + random(); + return; + } + // pmm + +// PMM - added this so the soldiers now run toward you and shoot instead of just stopping and shooting +// if ((range(self, self->enemy) >= RANGE_MID) && (r < (skill->value*0.25) && (self->s.skinnum <= 3))) + + r = random(); + + if ((!(self->monsterinfo.aiflags & (AI_BLOCKED|AI_STAND_GROUND))) && + (range(self, self->enemy) >= RANGE_NEAR) && + (r < (skill->value*0.25) && + (self->s.skinnum <= 3))) + { + self->monsterinfo.currentmove = &soldier_move_attack6; + } + else + { + if (self->s.skinnum < 4) + { + if (random() < 0.5) + self->monsterinfo.currentmove = &soldier_move_attack1; + else + self->monsterinfo.currentmove = &soldier_move_attack2; + } + else + { + self->monsterinfo.currentmove = &soldier_move_attack4; + } + } +} + + +// +// SIGHT +// + +void soldier_sight(edict_t *self, edict_t *other) +{ + if (random() < 0.5) + gi.sound (self, CHAN_VOICE, sound_sight1, 1, ATTN_NORM, 0); + else + gi.sound (self, CHAN_VOICE, sound_sight2, 1, ATTN_NORM, 0); + +// if ((skill->value > 0) && (self->enemy) && (range(self, self->enemy) >= RANGE_MID)) + if ((skill->value > 0) && (self->enemy) && (range(self, self->enemy) >= RANGE_NEAR)) + { +// PMM - don't let machinegunners run & shoot + if ((random() > 0.75) && (self->s.skinnum <= 3)) + self->monsterinfo.currentmove = &soldier_move_attack6; + } +} + +// +// DUCK +// +/* +void soldier_duck_hold (edict_t *self) +{ + if (level.time >= self->monsterinfo.duck_wait_time) + self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; + else + self->monsterinfo.aiflags |= AI_HOLD_FRAME; +} +*/ +mframe_t soldier_frames_duck [] = +{ + ai_move, 5, monster_duck_down, + ai_move, -1, monster_duck_hold, + ai_move, 1, NULL, + ai_move, 0, monster_duck_up, + ai_move, 5, NULL +}; +mmove_t soldier_move_duck = {FRAME_duck01, FRAME_duck05, soldier_frames_duck, soldier_run}; + +/* +void soldier_dodge (edict_t *self, edict_t *attacker, float eta, trace_t *tr) +{ +//=========== +//PMM - rogue rewrite of dodge code. +// lots o' changes in here. Basically, they now check the tr and see if ducking would help, +// and if it doesn't, they dodge like mad + float r = random(); + float height; + + if ((g_showlogic) && (g_showlogic->value)) + { + if (self->monsterinfo.aiflags & AI_DODGING) + gi.dprintf ("dodging - "); + if (self->monsterinfo.aiflags & AI_DUCKED) + gi.dprintf ("ducked - "); + } + if (!self->enemy) + { + self->enemy = attacker; + FoundTarget (self); + } + + // PMM - don't bother if it's going to hit anyway; fix for weird in-your-face etas (I was + // seeing numbers like 13 and 14) + if ((eta < 0.1) || (eta > 5)) + { + if ((g_showlogic) && (g_showlogic->value)) + gi.dprintf ("timeout\n"); + return; + } + + // skill level determination.. + if (r > (0.25*((skill->value)+1))) + { + if ((g_showlogic) && (g_showlogic->value)) + gi.dprintf ("skillout\n"); + return; + } + + // stop charging, since we're going to dodge (somehow) instead + soldier_stop_charge (self); + + height = self->absmax[2]-32-1; // the -1 is because the absmax is s.origin + maxs + 1 + + // if we're ducking already, or the shot is at our knees + if ((tr->endpos[2] <= height) || (self->monsterinfo.aiflags & AI_DUCKED)) + { + vec3_t right, diff; + + // if we're already dodging, just finish the sequence, i.e. don't do anything else + if (self->monsterinfo.aiflags & AI_DODGING) + { + if ((g_showlogic) && (g_showlogic->value)) + gi.dprintf ("already dodging\n"); + return; + } + + AngleVectors (self->s.angles, NULL, right, NULL); + VectorSubtract (tr->endpos, self->s.origin, diff); + + if (DotProduct (right, diff) < 0) + { + self->monsterinfo.lefty = 1; +// gi.dprintf ("left\n"); + } else { +// gi.dprintf ("right\n"); + } + // if it doesn't sense to duck, try to strafe and shoot + // we don't want the machine gun guys running & shooting (looks bad) + + // if we are currently ducked, unduck + if (self->monsterinfo.aiflags & AI_DUCKED) + { + if ((g_showlogic) && (g_showlogic->value)) + gi.dprintf ("unducking - "); + soldier_duck_up(self); + } + + self->monsterinfo.aiflags |= AI_DODGING; + self->monsterinfo.attack_state = AS_SLIDING; + + if (self->s.skinnum <= 3) + { + if ((g_showlogic) && (g_showlogic->value)) + gi.dprintf ("shooting back!\n"); + self->monsterinfo.currentmove = &soldier_move_attack6; + } + else + { + if ((g_showlogic) && (g_showlogic->value)) + gi.dprintf ("strafing away!\n"); + self->monsterinfo.currentmove = &soldier_move_start_run; + } + return; + } + + // if we're here, we're ducking, so clear the dodge bit if it's set + + if ((g_showlogic) && (g_showlogic->value)) + gi.dprintf ("ducking!\n"); + if (skill->value == 0) + { + // set this prematurely; it doesn't hurt, and prevents extra iterations + self->monsterinfo.aiflags |= AI_DUCKED; + monster_done_dodge (self); + self->monsterinfo.currentmove = &soldier_move_duck; + // PMM - stupid dodge + self->monsterinfo.duck_wait_time = level.time + eta + 1; + return; + } +// PMM - since we're only ducking some of the time, this needs to be moved down below +// self->monsterinfo.duck_wait_time = level.time + eta + 0.3; + + r = random(); + + // set this prematurely; it doesn't hurt, and prevents extra iterations + self->monsterinfo.aiflags |= AI_DUCKED; + monster_done_dodge (self); + + if (r > (skill->value * 0.33)) + { + self->monsterinfo.currentmove = &soldier_move_duck; + self->monsterinfo.duck_wait_time = level.time + eta + (0.1 * (3 - skill->value)); + // has to be done immediately otherwise he can get stuck + soldier_duck_down(self); + } + else + { + // has to be done immediately otherwise he can get stuck + soldier_duck_down(self); + self->monsterinfo.duck_wait_time = level.time + eta + 1; + self->monsterinfo.currentmove = &soldier_move_attack3; + self->monsterinfo.nextframe = FRAME_attak301; + } + return; +//PMM +//=========== + +} +*/ +// pmm - blocking code + +qboolean soldier_blocked (edict_t *self, float dist) +{ + // don't do anything if you're dodging + if ((self->monsterinfo.aiflags & AI_DODGING) || (self->monsterinfo.aiflags & AI_DUCKED)) + return false; + + if(blocked_checkshot (self, 0.25 + (0.05 * skill->value) )) + return true; + +// if(blocked_checkjump (self, dist, 192, 40)) +// { +// soldier_jump(self); +// return true; +// } + + if(blocked_checkplat (self, dist)) + return true; + + return false; +} + +// +// DEATH +// + +void soldier_fire6 (edict_t *self) +{ + soldier_fire (self, 5); +} + +void soldier_fire7 (edict_t *self) +{ + soldier_fire (self, 6); +} + +void soldier_dead (edict_t *self) +{ + VectorSet (self->mins, -16, -16, -24); + VectorSet (self->maxs, 16, 16, -8); + self->movetype = MOVETYPE_TOSS; + self->svflags |= SVF_DEADMONSTER; + self->nextthink = 0; + gi.linkentity (self); +} + +// pmm - this quickie does a location trace to try to grow the bounding box +// +// this is because the frames are off; the origin is at the guy's feet. +void soldier_dead2 (edict_t *self) +{ + vec3_t tempmins, tempmaxs, temporg; + trace_t tr; + + VectorCopy (self->s.origin, temporg); + // this is because location traces done at the floor are guaranteed to hit the floor + // (inside the sv_trace code it grows the bbox by 1 in all directions) + temporg[2] += 1; + + VectorSet (tempmins, -32, -32, -24); + VectorSet (tempmaxs, 32, 32, -8); + + tr = gi.trace (temporg, tempmins, tempmaxs, temporg, self, MASK_SOLID); + if (tr.startsolid || tr.allsolid) + { + VectorSet (self->mins, -16, -16, -24); + VectorSet (self->maxs, 16, 16, -8); + } + else + { + VectorCopy (tempmins, self->mins); + VectorCopy (tempmaxs, self->maxs); + } + self->movetype = MOVETYPE_TOSS; + self->svflags |= SVF_DEADMONSTER; + self->nextthink = 0; + gi.linkentity (self); +} + +mframe_t soldier_frames_death1 [] = +{ + ai_move, 0, NULL, + ai_move, -10, NULL, + ai_move, -10, NULL, + ai_move, -10, NULL, + ai_move, -5, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 0, NULL, + ai_move, 0, soldier_fire6, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, soldier_fire7, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t soldier_move_death1 = {FRAME_death101, FRAME_death136, soldier_frames_death1, soldier_dead}; + +mframe_t soldier_frames_death2 [] = +{ + ai_move, -5, NULL, + ai_move, -5, NULL, + ai_move, -5, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t soldier_move_death2 = {FRAME_death201, FRAME_death235, soldier_frames_death2, soldier_dead}; + +mframe_t soldier_frames_death3 [] = +{ + ai_move, -5, NULL, + ai_move, -5, NULL, + ai_move, -5, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, +}; +mmove_t soldier_move_death3 = {FRAME_death301, FRAME_death345, soldier_frames_death3, soldier_dead}; + +mframe_t soldier_frames_death4 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +// PMM -changed to soldier_dead2 to get a larger bounding box +mmove_t soldier_move_death4 = {FRAME_death401, FRAME_death453, soldier_frames_death4, soldier_dead2}; + +mframe_t soldier_frames_death5 [] = +{ + ai_move, -5, NULL, + ai_move, -5, NULL, + ai_move, -5, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t soldier_move_death5 = {FRAME_death501, FRAME_death524, soldier_frames_death5, soldier_dead}; + +mframe_t soldier_frames_death6 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t soldier_move_death6 = {FRAME_death601, FRAME_death610, soldier_frames_death6, soldier_dead}; + +void soldier_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + int n; + +// check for gib + if (self->health <= self->gib_health) + { + gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0); + for (n= 0; n < 3; n++) + ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); + ThrowGib (self, "models/objects/gibs/chest/tris.md2", damage, GIB_ORGANIC); + ThrowHead (self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC); + self->deadflag = DEAD_DEAD; + return; + } + + if (self->deadflag == DEAD_DEAD) + return; + +// regular death + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_YES; + self->s.skinnum |= 1; + + if (self->s.skinnum == 1) + gi.sound (self, CHAN_VOICE, sound_death_light, 1, ATTN_NORM, 0); + else if (self->s.skinnum == 3) + gi.sound (self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0); + else // (self->s.skinnum == 5) + gi.sound (self, CHAN_VOICE, sound_death_ss, 1, ATTN_NORM, 0); + + if (fabs((self->s.origin[2] + self->viewheight) - point[2]) <= 4) + { + // head shot + self->monsterinfo.currentmove = &soldier_move_death3; + return; + } + + n = rand() % 5; + if (n == 0) + self->monsterinfo.currentmove = &soldier_move_death1; + else if (n == 1) + self->monsterinfo.currentmove = &soldier_move_death2; + else if (n == 2) + self->monsterinfo.currentmove = &soldier_move_death4; + else if (n == 3) + self->monsterinfo.currentmove = &soldier_move_death5; + else + self->monsterinfo.currentmove = &soldier_move_death6; +} + +// +// NEW DODGE CODE +// + +void soldier_sidestep (edict_t *self) +{ + if (self->s.skinnum <= 3) + { +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("shooting back!\n"); + if (self->monsterinfo.currentmove != &soldier_move_attack6) + self->monsterinfo.currentmove = &soldier_move_attack6; + } + else + { +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("strafing away!\n"); + if (self->monsterinfo.currentmove != &soldier_move_start_run) + self->monsterinfo.currentmove = &soldier_move_start_run; + } +} + +void soldier_duck (edict_t *self, float eta) +{ + float r; + + // has to be done immediately otherwise he can get stuck + monster_duck_down(self); + + if (skill->value == 0) + { + // PMM - stupid dodge + self->monsterinfo.nextframe = FRAME_duck01; + self->monsterinfo.currentmove = &soldier_move_duck; + self->monsterinfo.duck_wait_time = level.time + eta + 1; + return; + } + + r = random(); + + if (r > (skill->value * 0.3)) + { + self->monsterinfo.nextframe = FRAME_duck01; + self->monsterinfo.currentmove = &soldier_move_duck; + self->monsterinfo.duck_wait_time = level.time + eta + (0.1 * (3 - skill->value)); + } + else + { + self->monsterinfo.nextframe = FRAME_attak301; + self->monsterinfo.currentmove = &soldier_move_attack3; + self->monsterinfo.duck_wait_time = level.time + eta + 1; + } + return; +} + +//========= +//ROGUE +void soldier_blind (edict_t *self); + +mframe_t soldier_frames_blind [] = +{ + ai_move, 0, soldier_idle, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t soldier_move_blind = {FRAME_stand101, FRAME_stand130, soldier_frames_blind, soldier_blind}; + +void soldier_blind (edict_t *self) +{ + self->monsterinfo.currentmove = &soldier_move_blind; +} +//ROGUE +//========= + +// +// SPAWN +// + +void SP_monster_soldier_x (edict_t *self) +{ + + self->s.modelindex = gi.modelindex ("models/monsters/soldier/tris.md2"); + //PMM +// self->s.effects |= EF_SPLATTER; + //PMM + self->monsterinfo.scale = MODEL_SCALE; + VectorSet (self->mins, -16, -16, -24); + VectorSet (self->maxs, 16, 16, 32); + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + + sound_idle = gi.soundindex ("soldier/solidle1.wav"); + sound_sight1 = gi.soundindex ("soldier/solsght1.wav"); + sound_sight2 = gi.soundindex ("soldier/solsrch1.wav"); + sound_cock = gi.soundindex ("infantry/infatck3.wav"); + + self->mass = 100; + + self->pain = soldier_pain; + self->die = soldier_die; + + self->monsterinfo.stand = soldier_stand; + self->monsterinfo.walk = soldier_walk; + self->monsterinfo.run = soldier_run; + self->monsterinfo.dodge = M_MonsterDodge; + self->monsterinfo.attack = soldier_attack; + self->monsterinfo.melee = NULL; + self->monsterinfo.sight = soldier_sight; + +//===== +//ROGUE + self->monsterinfo.blocked = soldier_blocked; + self->monsterinfo.duck = soldier_duck; + self->monsterinfo.unduck = monster_duck_up; + self->monsterinfo.sidestep = soldier_sidestep; + + if(self->spawnflags & 8) // blind + self->monsterinfo.stand = soldier_blind; +//ROGUE +//===== + + gi.linkentity (self); + + self->monsterinfo.stand (self); + + walkmonster_start (self); +} + + +/*QUAKED monster_soldier_light (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight Blind + +Blind - monster will just stand there until triggered +*/ +void SP_monster_soldier_light (edict_t *self) +{ + if (deathmatch->value) + { + G_FreeEdict (self); + return; + } + + SP_monster_soldier_x (self); + + sound_pain_light = gi.soundindex ("soldier/solpain2.wav"); + sound_death_light = gi.soundindex ("soldier/soldeth2.wav"); + gi.modelindex ("models/objects/laser/tris.md2"); + gi.soundindex ("misc/lasfly.wav"); + gi.soundindex ("soldier/solatck2.wav"); + + self->s.skinnum = 0; + self->health = 20; + self->gib_health = -30; + + // PMM - blindfire + self->monsterinfo.blindfire = true; +} + +/*QUAKED monster_soldier (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight Blind + +Blind - monster will just stand there until triggered +*/ +void SP_monster_soldier (edict_t *self) +{ + if (deathmatch->value) + { + G_FreeEdict (self); + return; + } + + SP_monster_soldier_x (self); + + sound_pain = gi.soundindex ("soldier/solpain1.wav"); + sound_death = gi.soundindex ("soldier/soldeth1.wav"); + gi.soundindex ("soldier/solatck1.wav"); + + self->s.skinnum = 2; + self->health = 30; + self->gib_health = -30; +} + +/*QUAKED monster_soldier_ss (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight Blind + +Blind - monster will just stand there until triggered +*/ +void SP_monster_soldier_ss (edict_t *self) +{ + if (deathmatch->value) + { + G_FreeEdict (self); + return; + } + + SP_monster_soldier_x (self); + + sound_pain_ss = gi.soundindex ("soldier/solpain3.wav"); + sound_death_ss = gi.soundindex ("soldier/soldeth3.wav"); + gi.soundindex ("soldier/solatck3.wav"); + + self->s.skinnum = 4; + self->health = 40; + self->gib_health = -30; +} diff --git a/original/rogue/m_soldier.h b/original/rogue/m_soldier.h new file mode 100644 index 0000000..86d2567 --- /dev/null +++ b/original/rogue/m_soldier.h @@ -0,0 +1,483 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// G:\quake2\baseq2\models/monsters/soldier + +// This file generated by ModelGen - Do NOT Modify + +#define FRAME_attak101 0 +#define FRAME_attak102 1 +#define FRAME_attak103 2 +#define FRAME_attak104 3 +#define FRAME_attak105 4 +#define FRAME_attak106 5 +#define FRAME_attak107 6 +#define FRAME_attak108 7 +#define FRAME_attak109 8 +#define FRAME_attak110 9 +#define FRAME_attak111 10 +#define FRAME_attak112 11 +#define FRAME_attak201 12 +#define FRAME_attak202 13 +#define FRAME_attak203 14 +#define FRAME_attak204 15 +#define FRAME_attak205 16 +#define FRAME_attak206 17 +#define FRAME_attak207 18 +#define FRAME_attak208 19 +#define FRAME_attak209 20 +#define FRAME_attak210 21 +#define FRAME_attak211 22 +#define FRAME_attak212 23 +#define FRAME_attak213 24 +#define FRAME_attak214 25 +#define FRAME_attak215 26 +#define FRAME_attak216 27 +#define FRAME_attak217 28 +#define FRAME_attak218 29 +#define FRAME_attak301 30 +#define FRAME_attak302 31 +#define FRAME_attak303 32 +#define FRAME_attak304 33 +#define FRAME_attak305 34 +#define FRAME_attak306 35 +#define FRAME_attak307 36 +#define FRAME_attak308 37 +#define FRAME_attak309 38 +#define FRAME_attak401 39 +#define FRAME_attak402 40 +#define FRAME_attak403 41 +#define FRAME_attak404 42 +#define FRAME_attak405 43 +#define FRAME_attak406 44 +#define FRAME_duck01 45 +#define FRAME_duck02 46 +#define FRAME_duck03 47 +#define FRAME_duck04 48 +#define FRAME_duck05 49 +#define FRAME_pain101 50 +#define FRAME_pain102 51 +#define FRAME_pain103 52 +#define FRAME_pain104 53 +#define FRAME_pain105 54 +#define FRAME_pain201 55 +#define FRAME_pain202 56 +#define FRAME_pain203 57 +#define FRAME_pain204 58 +#define FRAME_pain205 59 +#define FRAME_pain206 60 +#define FRAME_pain207 61 +#define FRAME_pain301 62 +#define FRAME_pain302 63 +#define FRAME_pain303 64 +#define FRAME_pain304 65 +#define FRAME_pain305 66 +#define FRAME_pain306 67 +#define FRAME_pain307 68 +#define FRAME_pain308 69 +#define FRAME_pain309 70 +#define FRAME_pain310 71 +#define FRAME_pain311 72 +#define FRAME_pain312 73 +#define FRAME_pain313 74 +#define FRAME_pain314 75 +#define FRAME_pain315 76 +#define FRAME_pain316 77 +#define FRAME_pain317 78 +#define FRAME_pain318 79 +#define FRAME_pain401 80 +#define FRAME_pain402 81 +#define FRAME_pain403 82 +#define FRAME_pain404 83 +#define FRAME_pain405 84 +#define FRAME_pain406 85 +#define FRAME_pain407 86 +#define FRAME_pain408 87 +#define FRAME_pain409 88 +#define FRAME_pain410 89 +#define FRAME_pain411 90 +#define FRAME_pain412 91 +#define FRAME_pain413 92 +#define FRAME_pain414 93 +#define FRAME_pain415 94 +#define FRAME_pain416 95 +#define FRAME_pain417 96 +#define FRAME_run01 97 +#define FRAME_run02 98 +#define FRAME_run03 99 +#define FRAME_run04 100 +#define FRAME_run05 101 +#define FRAME_run06 102 +#define FRAME_run07 103 +#define FRAME_run08 104 +#define FRAME_run09 105 +#define FRAME_run10 106 +#define FRAME_run11 107 +#define FRAME_run12 108 +#define FRAME_runs01 109 +#define FRAME_runs02 110 +#define FRAME_runs03 111 +#define FRAME_runs04 112 +#define FRAME_runs05 113 +#define FRAME_runs06 114 +#define FRAME_runs07 115 +#define FRAME_runs08 116 +#define FRAME_runs09 117 +#define FRAME_runs10 118 +#define FRAME_runs11 119 +#define FRAME_runs12 120 +#define FRAME_runs13 121 +#define FRAME_runs14 122 +#define FRAME_runs15 123 +#define FRAME_runs16 124 +#define FRAME_runs17 125 +#define FRAME_runs18 126 +#define FRAME_runt01 127 +#define FRAME_runt02 128 +#define FRAME_runt03 129 +#define FRAME_runt04 130 +#define FRAME_runt05 131 +#define FRAME_runt06 132 +#define FRAME_runt07 133 +#define FRAME_runt08 134 +#define FRAME_runt09 135 +#define FRAME_runt10 136 +#define FRAME_runt11 137 +#define FRAME_runt12 138 +#define FRAME_runt13 139 +#define FRAME_runt14 140 +#define FRAME_runt15 141 +#define FRAME_runt16 142 +#define FRAME_runt17 143 +#define FRAME_runt18 144 +#define FRAME_runt19 145 +#define FRAME_stand101 146 +#define FRAME_stand102 147 +#define FRAME_stand103 148 +#define FRAME_stand104 149 +#define FRAME_stand105 150 +#define FRAME_stand106 151 +#define FRAME_stand107 152 +#define FRAME_stand108 153 +#define FRAME_stand109 154 +#define FRAME_stand110 155 +#define FRAME_stand111 156 +#define FRAME_stand112 157 +#define FRAME_stand113 158 +#define FRAME_stand114 159 +#define FRAME_stand115 160 +#define FRAME_stand116 161 +#define FRAME_stand117 162 +#define FRAME_stand118 163 +#define FRAME_stand119 164 +#define FRAME_stand120 165 +#define FRAME_stand121 166 +#define FRAME_stand122 167 +#define FRAME_stand123 168 +#define FRAME_stand124 169 +#define FRAME_stand125 170 +#define FRAME_stand126 171 +#define FRAME_stand127 172 +#define FRAME_stand128 173 +#define FRAME_stand129 174 +#define FRAME_stand130 175 +#define FRAME_stand301 176 +#define FRAME_stand302 177 +#define FRAME_stand303 178 +#define FRAME_stand304 179 +#define FRAME_stand305 180 +#define FRAME_stand306 181 +#define FRAME_stand307 182 +#define FRAME_stand308 183 +#define FRAME_stand309 184 +#define FRAME_stand310 185 +#define FRAME_stand311 186 +#define FRAME_stand312 187 +#define FRAME_stand313 188 +#define FRAME_stand314 189 +#define FRAME_stand315 190 +#define FRAME_stand316 191 +#define FRAME_stand317 192 +#define FRAME_stand318 193 +#define FRAME_stand319 194 +#define FRAME_stand320 195 +#define FRAME_stand321 196 +#define FRAME_stand322 197 +#define FRAME_stand323 198 +#define FRAME_stand324 199 +#define FRAME_stand325 200 +#define FRAME_stand326 201 +#define FRAME_stand327 202 +#define FRAME_stand328 203 +#define FRAME_stand329 204 +#define FRAME_stand330 205 +#define FRAME_stand331 206 +#define FRAME_stand332 207 +#define FRAME_stand333 208 +#define FRAME_stand334 209 +#define FRAME_stand335 210 +#define FRAME_stand336 211 +#define FRAME_stand337 212 +#define FRAME_stand338 213 +#define FRAME_stand339 214 +#define FRAME_walk101 215 +#define FRAME_walk102 216 +#define FRAME_walk103 217 +#define FRAME_walk104 218 +#define FRAME_walk105 219 +#define FRAME_walk106 220 +#define FRAME_walk107 221 +#define FRAME_walk108 222 +#define FRAME_walk109 223 +#define FRAME_walk110 224 +#define FRAME_walk111 225 +#define FRAME_walk112 226 +#define FRAME_walk113 227 +#define FRAME_walk114 228 +#define FRAME_walk115 229 +#define FRAME_walk116 230 +#define FRAME_walk117 231 +#define FRAME_walk118 232 +#define FRAME_walk119 233 +#define FRAME_walk120 234 +#define FRAME_walk121 235 +#define FRAME_walk122 236 +#define FRAME_walk123 237 +#define FRAME_walk124 238 +#define FRAME_walk125 239 +#define FRAME_walk126 240 +#define FRAME_walk127 241 +#define FRAME_walk128 242 +#define FRAME_walk129 243 +#define FRAME_walk130 244 +#define FRAME_walk131 245 +#define FRAME_walk132 246 +#define FRAME_walk133 247 +#define FRAME_walk201 248 +#define FRAME_walk202 249 +#define FRAME_walk203 250 +#define FRAME_walk204 251 +#define FRAME_walk205 252 +#define FRAME_walk206 253 +#define FRAME_walk207 254 +#define FRAME_walk208 255 +#define FRAME_walk209 256 +#define FRAME_walk210 257 +#define FRAME_walk211 258 +#define FRAME_walk212 259 +#define FRAME_walk213 260 +#define FRAME_walk214 261 +#define FRAME_walk215 262 +#define FRAME_walk216 263 +#define FRAME_walk217 264 +#define FRAME_walk218 265 +#define FRAME_walk219 266 +#define FRAME_walk220 267 +#define FRAME_walk221 268 +#define FRAME_walk222 269 +#define FRAME_walk223 270 +#define FRAME_walk224 271 +#define FRAME_death101 272 +#define FRAME_death102 273 +#define FRAME_death103 274 +#define FRAME_death104 275 +#define FRAME_death105 276 +#define FRAME_death106 277 +#define FRAME_death107 278 +#define FRAME_death108 279 +#define FRAME_death109 280 +#define FRAME_death110 281 +#define FRAME_death111 282 +#define FRAME_death112 283 +#define FRAME_death113 284 +#define FRAME_death114 285 +#define FRAME_death115 286 +#define FRAME_death116 287 +#define FRAME_death117 288 +#define FRAME_death118 289 +#define FRAME_death119 290 +#define FRAME_death120 291 +#define FRAME_death121 292 +#define FRAME_death122 293 +#define FRAME_death123 294 +#define FRAME_death124 295 +#define FRAME_death125 296 +#define FRAME_death126 297 +#define FRAME_death127 298 +#define FRAME_death128 299 +#define FRAME_death129 300 +#define FRAME_death130 301 +#define FRAME_death131 302 +#define FRAME_death132 303 +#define FRAME_death133 304 +#define FRAME_death134 305 +#define FRAME_death135 306 +#define FRAME_death136 307 +#define FRAME_death201 308 +#define FRAME_death202 309 +#define FRAME_death203 310 +#define FRAME_death204 311 +#define FRAME_death205 312 +#define FRAME_death206 313 +#define FRAME_death207 314 +#define FRAME_death208 315 +#define FRAME_death209 316 +#define FRAME_death210 317 +#define FRAME_death211 318 +#define FRAME_death212 319 +#define FRAME_death213 320 +#define FRAME_death214 321 +#define FRAME_death215 322 +#define FRAME_death216 323 +#define FRAME_death217 324 +#define FRAME_death218 325 +#define FRAME_death219 326 +#define FRAME_death220 327 +#define FRAME_death221 328 +#define FRAME_death222 329 +#define FRAME_death223 330 +#define FRAME_death224 331 +#define FRAME_death225 332 +#define FRAME_death226 333 +#define FRAME_death227 334 +#define FRAME_death228 335 +#define FRAME_death229 336 +#define FRAME_death230 337 +#define FRAME_death231 338 +#define FRAME_death232 339 +#define FRAME_death233 340 +#define FRAME_death234 341 +#define FRAME_death235 342 +#define FRAME_death301 343 +#define FRAME_death302 344 +#define FRAME_death303 345 +#define FRAME_death304 346 +#define FRAME_death305 347 +#define FRAME_death306 348 +#define FRAME_death307 349 +#define FRAME_death308 350 +#define FRAME_death309 351 +#define FRAME_death310 352 +#define FRAME_death311 353 +#define FRAME_death312 354 +#define FRAME_death313 355 +#define FRAME_death314 356 +#define FRAME_death315 357 +#define FRAME_death316 358 +#define FRAME_death317 359 +#define FRAME_death318 360 +#define FRAME_death319 361 +#define FRAME_death320 362 +#define FRAME_death321 363 +#define FRAME_death322 364 +#define FRAME_death323 365 +#define FRAME_death324 366 +#define FRAME_death325 367 +#define FRAME_death326 368 +#define FRAME_death327 369 +#define FRAME_death328 370 +#define FRAME_death329 371 +#define FRAME_death330 372 +#define FRAME_death331 373 +#define FRAME_death332 374 +#define FRAME_death333 375 +#define FRAME_death334 376 +#define FRAME_death335 377 +#define FRAME_death336 378 +#define FRAME_death337 379 +#define FRAME_death338 380 +#define FRAME_death339 381 +#define FRAME_death340 382 +#define FRAME_death341 383 +#define FRAME_death342 384 +#define FRAME_death343 385 +#define FRAME_death344 386 +#define FRAME_death345 387 +#define FRAME_death401 388 +#define FRAME_death402 389 +#define FRAME_death403 390 +#define FRAME_death404 391 +#define FRAME_death405 392 +#define FRAME_death406 393 +#define FRAME_death407 394 +#define FRAME_death408 395 +#define FRAME_death409 396 +#define FRAME_death410 397 +#define FRAME_death411 398 +#define FRAME_death412 399 +#define FRAME_death413 400 +#define FRAME_death414 401 +#define FRAME_death415 402 +#define FRAME_death416 403 +#define FRAME_death417 404 +#define FRAME_death418 405 +#define FRAME_death419 406 +#define FRAME_death420 407 +#define FRAME_death421 408 +#define FRAME_death422 409 +#define FRAME_death423 410 +#define FRAME_death424 411 +#define FRAME_death425 412 +#define FRAME_death426 413 +#define FRAME_death427 414 +#define FRAME_death428 415 +#define FRAME_death429 416 +#define FRAME_death430 417 +#define FRAME_death431 418 +#define FRAME_death432 419 +#define FRAME_death433 420 +#define FRAME_death434 421 +#define FRAME_death435 422 +#define FRAME_death436 423 +#define FRAME_death437 424 +#define FRAME_death438 425 +#define FRAME_death439 426 +#define FRAME_death440 427 +#define FRAME_death441 428 +#define FRAME_death442 429 +#define FRAME_death443 430 +#define FRAME_death444 431 +#define FRAME_death445 432 +#define FRAME_death446 433 +#define FRAME_death447 434 +#define FRAME_death448 435 +#define FRAME_death449 436 +#define FRAME_death450 437 +#define FRAME_death451 438 +#define FRAME_death452 439 +#define FRAME_death453 440 +#define FRAME_death501 441 +#define FRAME_death502 442 +#define FRAME_death503 443 +#define FRAME_death504 444 +#define FRAME_death505 445 +#define FRAME_death506 446 +#define FRAME_death507 447 +#define FRAME_death508 448 +#define FRAME_death509 449 +#define FRAME_death510 450 +#define FRAME_death511 451 +#define FRAME_death512 452 +#define FRAME_death513 453 +#define FRAME_death514 454 +#define FRAME_death515 455 +#define FRAME_death516 456 +#define FRAME_death517 457 +#define FRAME_death518 458 +#define FRAME_death519 459 +#define FRAME_death520 460 +#define FRAME_death521 461 +#define FRAME_death522 462 +#define FRAME_death523 463 +#define FRAME_death524 464 +#define FRAME_death601 465 +#define FRAME_death602 466 +#define FRAME_death603 467 +#define FRAME_death604 468 +#define FRAME_death605 469 +#define FRAME_death606 470 +#define FRAME_death607 471 +#define FRAME_death608 472 +#define FRAME_death609 473 +#define FRAME_death610 474 + +#define MODEL_SCALE 1.200000 diff --git a/original/rogue/m_stalker.c b/original/rogue/m_stalker.c new file mode 100644 index 0000000..6895728 --- /dev/null +++ b/original/rogue/m_stalker.c @@ -0,0 +1,1216 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +/* +============================================================================== + +stalker + +============================================================================== +*/ + +#include "g_local.h" +#include "m_stalker.h" +#include + +static int sound_pain; +static int sound_die; +static int sound_sight; +static int sound_punch_hit1; +static int sound_punch_hit2; +static int sound_idle; + +int stalker_do_pounce(edict_t *self, vec3_t dest); +void stalker_stand (edict_t *self); +void stalker_run (edict_t *self); +void stalker_walk (edict_t *self); +void stalker_jump (edict_t *self); +void stalker_dodge_jump (edict_t *self); +void stalker_swing_check_l (edict_t *self); +void stalker_swing_check_r (edict_t *self); +void stalker_swing_attack (edict_t *self); +void stalker_jump_straightup (edict_t *self); +void stalker_jump_wait_land (edict_t *self); +void stalker_false_death (edict_t *self); +void stalker_false_death_start (edict_t *self); +qboolean stalker_ok_to_transition (edict_t *self); + +#define STALKER_ON_CEILING(ent) ( ent->gravityVector[2] > 0 ? 1 : 0 ) + +//extern qboolean SV_StepDirection (edict_t *ent, float yaw, float dist); +extern qboolean SV_PointCloseEnough (edict_t *ent, vec3_t goal, float dist); +extern void drawbbox(edict_t *self); + +//========================= +//========================= +qboolean stalker_ok_to_transition (edict_t *self) +{ + trace_t trace; + vec3_t pt, start; + float max_dist; + float margin; + float end_height; + + if(STALKER_ON_CEILING(self)) + { + max_dist = -384; + margin = self->mins[2] - 8; + } + else + { + // her stalkers are just better + if (self->monsterinfo.aiflags & AI_SPAWNED_WIDOW) + max_dist = 256; + else + max_dist = 180; + margin = self->maxs[2] + 8; + } + + VectorCopy(self->s.origin, pt); + pt[2] += max_dist; + trace = gi.trace (self->s.origin, self->mins, self->maxs, pt, self, MASK_MONSTERSOLID); + + if(trace.fraction == 1.0 || + !(trace.contents & CONTENTS_SOLID) || + (trace.ent != world)) + { + if(STALKER_ON_CEILING(self)) + { + if(trace.plane.normal[2] < 0.9) + return false; + } + else + { + if(trace.plane.normal[2] > -0.9) + return false; + } + } +// gi.dprintf("stalker_check_pt: main check ok\n"); + + end_height = trace.endpos[2]; + + // check the four corners, tracing only to the endpoint of the center trace (vertically). + pt[0] = self->absmin[0]; + pt[1] = self->absmin[1]; + pt[2] = trace.endpos[2] + margin; // give a little margin of error to allow slight inclines + VectorCopy(pt, start); + start[2] = self->s.origin[2]; + trace = gi.trace( start, vec3_origin, vec3_origin, pt, self, MASK_MONSTERSOLID); + if(trace.fraction == 1.0 || !(trace.contents & CONTENTS_SOLID) || (trace.ent != world)) + { +// gi.dprintf("stalker_check_pt: absmin/absmin failed\n"); + return false; + } + if(abs(end_height + margin - trace.endpos[2]) > 8) + return false; + + pt[0] = self->absmax[0]; + pt[1] = self->absmin[1]; + VectorCopy(pt, start); + start[2] = self->s.origin[2]; + trace = gi.trace( start, vec3_origin, vec3_origin, pt, self, MASK_MONSTERSOLID); + if(trace.fraction == 1.0 || !(trace.contents & CONTENTS_SOLID) || (trace.ent != world)) + { +// gi.dprintf("stalker_check_pt: absmax/absmin failed\n"); + return false; + } + if(abs(end_height + margin - trace.endpos[2]) > 8) + return false; + + pt[0] = self->absmax[0]; + pt[1] = self->absmax[1]; + VectorCopy(pt, start); + start[2] = self->s.origin[2]; + trace = gi.trace( start, vec3_origin, vec3_origin, pt, self, MASK_MONSTERSOLID); + if(trace.fraction == 1.0 || !(trace.contents & CONTENTS_SOLID) || (trace.ent != world)) + { +// gi.dprintf("stalker_check_pt: absmax/absmax failed\n"); + return false; + } + if(abs(end_height + margin - trace.endpos[2]) > 8) + return false; + + pt[0] = self->absmin[0]; + pt[1] = self->absmax[1]; + VectorCopy(pt, start); + start[2] = self->s.origin[2]; + trace = gi.trace( start, vec3_origin, vec3_origin, pt, self, MASK_MONSTERSOLID); + if(trace.fraction == 1.0 || !(trace.contents & CONTENTS_SOLID) || (trace.ent != world)) + { +// gi.dprintf("stalker_check_pt: absmin/absmax failed\n"); + return false; + } + if(abs(end_height + margin - trace.endpos[2]) > 8) + return false; + + return true; +} + +//========================= +//========================= +void stalker_sight (edict_t *self, edict_t *other) +{ + gi.sound (self, CHAN_WEAPON, sound_sight, 1, ATTN_NORM, 0); +} + +// ****************** +// IDLE +// ****************** + +void stalker_idle_noise (edict_t *self) +{ + gi.sound (self, CHAN_WEAPON, sound_idle, 0.5, ATTN_IDLE, 0); +} + +mframe_t stalker_frames_idle [] = +{ + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + + ai_stand, 0, NULL, + ai_stand, 0, stalker_idle_noise, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + + ai_stand, 0, NULL +}; +mmove_t stalker_move_idle = {FRAME_idle01, FRAME_idle21, stalker_frames_idle, stalker_stand}; + +mframe_t stalker_frames_idle2 [] = +{ + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL +}; +mmove_t stalker_move_idle2 = {FRAME_idle201, FRAME_idle213, stalker_frames_idle2, stalker_stand}; + +void stalker_idle (edict_t *self) +{ + if (random() < 0.35) + self->monsterinfo.currentmove = &stalker_move_idle; + else + self->monsterinfo.currentmove = &stalker_move_idle2; +} + +// ****************** +// STAND +// ****************** + +mframe_t stalker_frames_stand [] = +{ + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + + ai_stand, 0, NULL, + ai_stand, 0, stalker_idle_noise, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + + ai_stand, 0, NULL +}; +mmove_t stalker_move_stand = {FRAME_idle01, FRAME_idle21, stalker_frames_stand, stalker_stand}; + +void stalker_stand (edict_t *self) +{ + if (random() < 0.25) + self->monsterinfo.currentmove = &stalker_move_stand; + else + self->monsterinfo.currentmove = &stalker_move_idle2; +} + +// ****************** +// RUN +// ****************** + +mframe_t stalker_frames_run [] = +{ + ai_run, 13, NULL, + ai_run, 17, NULL, + ai_run, 21, NULL, + ai_run, 18, NULL + +/* ai_run, 15, NULL, + ai_run, 20, NULL, + ai_run, 18, NULL, + ai_run, 14, NULL*/ +}; +mmove_t stalker_move_run = {FRAME_run01, FRAME_run04, stalker_frames_run, NULL}; + +void stalker_run (edict_t *self) +{ +// gi.dprintf("stalker_run %5.1f\n", level.time); + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + self->monsterinfo.currentmove = &stalker_move_stand; + else + self->monsterinfo.currentmove = &stalker_move_run; +} + +// ****************** +// WALK +// ****************** + +mframe_t stalker_frames_walk [] = +{ + ai_walk, 4, NULL, + ai_walk, 6, NULL, + ai_walk, 8, NULL, + ai_walk, 5, NULL, + + ai_walk, 4, NULL, + ai_walk, 6, NULL, + ai_walk, 8, NULL, + ai_walk, 4, NULL +}; +mmove_t stalker_move_walk = {FRAME_walk01, FRAME_walk08, stalker_frames_walk, stalker_walk}; + +void stalker_walk (edict_t *self) +{ +// gi.dprintf("stalker_walk\n"); + self->monsterinfo.currentmove = &stalker_move_walk; +} + +// ****************** +// false death +// ****************** +mframe_t stalker_frames_reactivate [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t stalker_move_false_death_end = { FRAME_reactive01, FRAME_reactive04, stalker_frames_reactivate, stalker_run }; + +void stalker_reactivate (edict_t *self) +{ + self->monsterinfo.aiflags &= ~AI_STAND_GROUND; + self->monsterinfo.currentmove = &stalker_move_false_death_end; +} + +void stalker_heal (edict_t *self) +{ + if(skill->value == 2) + self->health+=2; + else if(skill->value == 3) + self->health+=3; + else + self->health++; + +// gi.dprintf("stalker_heal: %d\n", self->health); + + if(self->health > (self->max_health/2)) + self->s.skinnum = 0; + + if(self->health >= self->max_health) + { + self->health = self->max_health; + stalker_reactivate(self); + } +} + +mframe_t stalker_frames_false_death [] = +{ + ai_move, 0, stalker_heal, + ai_move, 0, stalker_heal, + ai_move, 0, stalker_heal, + ai_move, 0, stalker_heal, + ai_move, 0, stalker_heal, + + ai_move, 0, stalker_heal, + ai_move, 0, stalker_heal, + ai_move, 0, stalker_heal, + ai_move, 0, stalker_heal, + ai_move, 0, stalker_heal +}; +mmove_t stalker_move_false_death = {FRAME_twitch01, FRAME_twitch10, stalker_frames_false_death, stalker_false_death}; + +void stalker_false_death (edict_t *self) +{ + self->monsterinfo.currentmove = &stalker_move_false_death; +} + +mframe_t stalker_frames_false_death_start [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, +}; +mmove_t stalker_move_false_death_start = {FRAME_death01, FRAME_death09, stalker_frames_false_death_start, stalker_false_death}; + +void stalker_false_death_start (edict_t *self) +{ + self->s.angles[2] = 0; + VectorSet(self->gravityVector, 0, 0, -1); + + self->monsterinfo.aiflags |= AI_STAND_GROUND; + self->monsterinfo.currentmove = &stalker_move_false_death_start; +} + + +// ****************** +// PAIN +// ****************** + +mframe_t stalker_frames_pain [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t stalker_move_pain = {FRAME_pain01, FRAME_pain04, stalker_frames_pain, stalker_run}; + +void stalker_pain (edict_t *self, edict_t *other, float kick, int damage) +{ + if (self->deadflag == DEAD_DEAD) + return; + + if (self->health < (self->max_health / 2)) + { + self->s.skinnum = 1; + } + + if (skill->value == 3) + return; // no pain anims in nightmare + +// if (self->monsterinfo.aiflags & AI_DODGING) +// monster_done_dodge (self); + + if (self->groundentity == NULL) + return; + + // if we're reactivating or false dying, ignore the pain. + if (self->monsterinfo.currentmove == &stalker_move_false_death_end || + self->monsterinfo.currentmove == &stalker_move_false_death_start ) + return; + + if (self->monsterinfo.currentmove == &stalker_move_false_death) + { + stalker_reactivate(self); + return; + } + + if ((self->health > 0) && (self->health < (self->max_health / 4))) + { + if(random() < (0.2 * skill->value)) + { + if( !STALKER_ON_CEILING(self) || stalker_ok_to_transition(self) ) + { +// gi.dprintf("starting false death sequence\n"); + stalker_false_death_start(self); + return; + } + } + } + + if (level.time < self->pain_debounce_time) + return; + + self->pain_debounce_time = level.time + 3; + +// gi.dprintf("stalker_pain\n"); + if (damage > 10) // don't react unless the damage was significant + { + // stalker should dodge jump periodically to help avoid damage. + if(self->groundentity && (random() < 0.5)) + stalker_dodge_jump(self); + else + self->monsterinfo.currentmove = &stalker_move_pain; + + gi.sound (self, CHAN_WEAPON, sound_pain, 1, ATTN_NORM, 0); + } +} + + +// ****************** +// STALKER ATTACK +// ****************** + +//extern qboolean infront (edict_t *self, edict_t *other); + +void stalker_shoot_attack (edict_t *self) +{ + vec3_t offset, start, f, r, dir; + vec3_t end; + float time, dist; + trace_t trace; + + if(!has_valid_enemy(self)) + return; + + if(self->groundentity && random() < 0.33) + { + VectorSubtract (self->enemy->s.origin, self->s.origin, dir); + dist = VectorLength (dir); + + if((dist > 256) || (random() < 0.5)) + stalker_do_pounce(self, self->enemy->s.origin); + else + stalker_jump_straightup (self); + } + + // FIXME -- keep this but use a custom one +// if (!infront(self, self->enemy)) +// return; + + AngleVectors (self->s.angles, f, r, NULL); + VectorSet (offset, 24, 0, 6); + G_ProjectSource (self->s.origin, offset, f, r, start); + + VectorSubtract(self->enemy->s.origin, start, dir); + if(random() < (0.20 + 0.1 * skill->value)) + { + dist = VectorLength(dir); + time = dist / 1000; + VectorMA(self->enemy->s.origin, time, self->enemy->velocity, end); + VectorSubtract(end, start, dir); + } + else + VectorCopy(self->enemy->s.origin, end); + + trace = gi.trace(start, vec3_origin, vec3_origin, end, self, MASK_SHOT); + if(trace.ent == self->enemy || trace.ent == world) + monster_fire_blaster2(self, start, dir, 15, 800, MZ2_STALKER_BLASTER, EF_BLASTER); +// else +// gi.dprintf("blocked by entity %s\n", trace.ent->classname); +} + +void stalker_shoot_attack2 (edict_t *self) +{ +// if (random() < (0.4+(float)skill->value)) +// stalker_shoot_attack (self); + + if (random() < (0.4 + (0.1 * (float)skill->value))) + stalker_shoot_attack (self); +} + +mframe_t stalker_frames_shoot [] = +{ + ai_charge, 13, NULL, + ai_charge, 17, stalker_shoot_attack, + ai_charge, 21, NULL, + ai_charge, 18, stalker_shoot_attack2 +}; +mmove_t stalker_move_shoot = {FRAME_run01, FRAME_run04, stalker_frames_shoot, stalker_run}; + +void stalker_attack_ranged (edict_t *self) +{ + if(!has_valid_enemy(self)) + return; + + // PMM - circle strafe stuff + if (random() > (1.0 - (0.5/(float)(skill->value)))) + { + self->monsterinfo.attack_state = AS_STRAIGHT; + } + else + { + if (random () <= 0.5) // switch directions + self->monsterinfo.lefty = 1 - self->monsterinfo.lefty; + self->monsterinfo.attack_state = AS_SLIDING; + } + self->monsterinfo.currentmove = &stalker_move_shoot; +} + +// ****************** +// close combat +// ****************** + +void stalker_swing_attack (edict_t *self) +{ + vec3_t aim; + + VectorSet (aim, MELEE_DISTANCE, 0, 0); + if (fire_hit (self, aim, (5 + (rand() % 5)), 50)) + if (self->s.frame < FRAME_attack08) + gi.sound (self, CHAN_WEAPON, sound_punch_hit2, 1, ATTN_NORM, 0); + else + gi.sound (self, CHAN_WEAPON, sound_punch_hit1, 1, ATTN_NORM, 0); +} + +mframe_t stalker_frames_swing_l [] = +{ + ai_charge, 2, NULL, + ai_charge, 4, NULL, + ai_charge, 6, NULL, + ai_charge, 10, NULL, + + ai_charge, 5, stalker_swing_attack, + ai_charge, 5, NULL, + ai_charge, 5, NULL, + ai_charge, 5, NULL // stalker_swing_check_l +}; +mmove_t stalker_move_swing_l = {FRAME_attack01, FRAME_attack08, stalker_frames_swing_l, stalker_run}; + +mframe_t stalker_frames_swing_r [] = +{ + ai_charge, 4, NULL, + ai_charge, 6, NULL, + ai_charge, 6, stalker_swing_attack, + ai_charge, 10, NULL, + ai_charge, 5, NULL // stalker_swing_check_r +}; +mmove_t stalker_move_swing_r = {FRAME_attack11, FRAME_attack15, stalker_frames_swing_r, stalker_run}; + +void stalker_attack_melee (edict_t *self) +{ + if(!has_valid_enemy(self)) + return; + + if(random() < 0.5) + { + self->monsterinfo.currentmove = &stalker_move_swing_l; + } + else + { + self->monsterinfo.currentmove = &stalker_move_swing_r; + } +} + + +// ****************** +// POUNCE +// ****************** + +#define PI 3.14159 +#define RAD2DEG(x) (x * (float)180.0 / (float)PI) +#define DEG2RAD(x) (x * (float)PI / (float)180.0) +#define FAUX_GRAVITY 800.0 + +// ==================== +// ==================== +void calcJumpAngle(vec3_t start, vec3_t end, float velocity, vec3_t angles) +{ + float distV, distH; + float one, cosU; + float l, U; + vec3_t dist; + + VectorSubtract(end, start, dist); + distH = (float)sqrt(dist[0]*dist[0] + dist[1]*dist[1]); + distV = dist[2]; + if(distV < 0) + distV = 0 - distV; + + if(distV) + { + l = (float) sqrt(distH*distH + distV*distV); + U = (float) atan(distV / distH); + if(dist[2] > 0) + U = (float)0.0 - U; + + angles[2] = 0.0; + + cosU = (float)cos(U); + one = l * FAUX_GRAVITY * (cosU * cosU); + one = one / (velocity * velocity); + one = one - (float)sin(U); + // one = ((l * FAUX_GRAVITY * (cosU * cosU)) / (velocity * velocity)) - (float)sin(U); + angles[0] = (float)asin(one); + if(_isnan(angles[0])) + angles[2] = 1.0; + angles[1] = (float)PI - angles[0]; + if(_isnan(angles[1])) + angles[2] = 1.0; + + angles[0] = RAD2DEG ( (angles[0] - U) / 2.0 ); + angles[1] = RAD2DEG ( (angles[1] - U) / 2.0 ); + } + else + { + l = (float) sqrt(distH*distH + distV*distV); + + angles[2] = 0.0; + + one = l * FAUX_GRAVITY; + one = one / (velocity * velocity); + angles[0] = (float)asin(one); + if(_isnan(angles[0])) + angles[2] = 1.0; + angles[1] = (float)PI - angles[0]; + if(_isnan(angles[1])) + angles[2] = 1.0; + + angles[0] = RAD2DEG ( (angles[0]) / 2.0 ); + angles[1] = RAD2DEG ( (angles[1]) / 2.0 ); + } +} + +// ==================== +// ==================== +int stalker_check_lz (edict_t *self, edict_t *target, vec3_t dest) +{ + vec3_t jumpLZ; + + if( (gi.pointcontents (dest) & MASK_WATER) || (target->waterlevel)) + { +// gi.dprintf ("you won't make me jump in water!\n"); + return false; + } + + if( !target->groundentity ) + { +// gi.dprintf( "I'll wait until you land..\n"); + return false; + } + + // check under the player's four corners + // if they're not solid, bail. + jumpLZ[0] = self->enemy->mins[0]; + jumpLZ[1] = self->enemy->mins[1]; + jumpLZ[2] = self->enemy->mins[2] - 0.25; + if( !(gi.pointcontents (jumpLZ) & MASK_SOLID) ) + return false; + + jumpLZ[0] = self->enemy->maxs[0]; + jumpLZ[1] = self->enemy->mins[1]; + if( !(gi.pointcontents (jumpLZ) & MASK_SOLID) ) + return false; + + jumpLZ[0] = self->enemy->maxs[0]; + jumpLZ[1] = self->enemy->maxs[1]; + if( !(gi.pointcontents (jumpLZ) & MASK_SOLID) ) + return false; + + jumpLZ[0] = self->enemy->mins[0]; + jumpLZ[1] = self->enemy->maxs[1]; + if( !(gi.pointcontents (jumpLZ) & MASK_SOLID) ) + return false; + + return true; +} + +// ==================== +// ==================== +int stalker_do_pounce(edict_t *self, vec3_t dest) +{ + vec3_t forward, right; + vec3_t dist; + vec_t length; + vec3_t jumpAngles; + vec3_t jumpLZ; + float velocity = 400.1; + trace_t trace; + int preferHighJump; + + // don't pounce when we're on the ceiling + if(STALKER_ON_CEILING(self)) + return false; + + if(!stalker_check_lz (self, self->enemy, dest)) + return false; + + VectorSubtract(dest, self->s.origin, dist); + + // make sure we're pointing in that direction 15deg margin of error. + vectoangles2 (dist, jumpAngles); + if(abs(jumpAngles[YAW] - self->s.angles[YAW]) > 45) + return false; // not facing the player... + + self->ideal_yaw = jumpAngles[YAW]; + M_ChangeYaw(self); + + length = VectorLength(dist); + if(length > 450) + return false; // can't jump that far... + + VectorCopy(dest, jumpLZ); + + preferHighJump = 0; + + // if we're having to jump up a distance, jump a little too high to compensate. + if(dist[2] >= 32.0) + { + preferHighJump = 1; + jumpLZ[2] += 32; + } + + trace = gi.trace (self->s.origin, vec3_origin, vec3_origin, dest, self, MASK_MONSTERSOLID); + if((trace.fraction < 1) && (trace.ent != self->enemy)) + { +// gi.dprintf("prefer high jump angle\n"); + preferHighJump = 1; + } + + // find a valid angle/velocity combination + while(velocity <= 800) + { + calcJumpAngle(self->s.origin, jumpLZ, velocity, jumpAngles); + if((!_isnan(jumpAngles[0])) || (!_isnan(jumpAngles[1]))) + break; + + velocity+=200; + }; + + if(!preferHighJump && (!_isnan(jumpAngles[0])) ) + { + AngleVectors (self->s.angles, forward, right, NULL); + VectorNormalize ( forward ) ; + + VectorScale( forward, velocity * cos(DEG2RAD(jumpAngles[0])), self->velocity); + self->velocity[2] = velocity * sin(DEG2RAD(jumpAngles[0])) + (0.5 * sv_gravity->value * FRAMETIME); +// gi.dprintf(" pouncing! %0.1f,%0.1f (%0.1f) --> %0.1f, %0.1f, %0.1f\n", +// jumpAngles[0], jumpAngles[1], jumpAngles[0], +// self->velocity[0], self->velocity[1], self->velocity[2]); + return 1; + } + + if(!_isnan(jumpAngles[1])) + { + AngleVectors (self->s.angles, forward, right, NULL); + VectorNormalize ( forward ) ; + + VectorScale( forward, velocity * cos(DEG2RAD(jumpAngles[1])), self->velocity); + self->velocity[2] = velocity * sin(DEG2RAD(jumpAngles[1])) + (0.5 * sv_gravity->value * FRAMETIME); +// gi.dprintf(" pouncing! %0.1f,%0.1f (%0.1f) --> %0.1f, %0.1f, %0.1f\n", +// jumpAngles[0], jumpAngles[1], jumpAngles[1], +// self->velocity[0], self->velocity[1], self->velocity[2]); + return 1; + } + +// gi.dprintf(" nan\n"); + return 0; +} + +// ****************** +// DODGE +// ****************** + +//=================== +// stalker_jump_straightup +//=================== +void stalker_jump_straightup (edict_t *self) +{ + if (self->deadflag == DEAD_DEAD) + return; + + if(STALKER_ON_CEILING(self)) + { + if(stalker_ok_to_transition(self)) + { +// gi.dprintf("falling off ceiling %d\n", self->health); + self->gravityVector[2] = -1; + self->s.angles[2] += 180.0; + if(self->s.angles[2] > 360.0) + self->s.angles[2] -= 360.0; + self->groundentity = NULL; + } + } + else if(self->groundentity) // make sure we're standing on SOMETHING... + { + self->velocity[0] += ((random() * 10) - 5); + self->velocity[1] += ((random() * 10) - 5); + self->velocity[2] += -400 * self->gravityVector[2]; + if(stalker_ok_to_transition(self)) + { +// gi.dprintf("falling TO ceiling %d\n", self->health); + self->gravityVector[2] = 1; + self->s.angles[2] = 180.0; + self->groundentity = NULL; + } + } +} + +mframe_t stalker_frames_jump_straightup [] = +{ + ai_move, 1, stalker_jump_straightup, + ai_move, 1, stalker_jump_wait_land, + ai_move, -1, NULL, + ai_move, -1, NULL +}; + +mmove_t stalker_move_jump_straightup = {FRAME_jump04, FRAME_jump07, stalker_frames_jump_straightup, stalker_run}; + +//=================== +// stalker_dodge_jump - abstraction so pain function can trigger a dodge jump too without +// faking the inputs to stalker_dodge +//=================== +void stalker_dodge_jump (edict_t *self) +{ + self->monsterinfo.currentmove = &stalker_move_jump_straightup; +} + +mframe_t stalker_frames_dodge_run [] = +{ + ai_run, 13, NULL, + ai_run, 17, NULL, + ai_run, 21, NULL, + ai_run, 18, monster_done_dodge +}; +mmove_t stalker_move_dodge_run = {FRAME_run01, FRAME_run04, stalker_frames_dodge_run, NULL}; + +void stalker_dodge (edict_t *self, edict_t *attacker, float eta, trace_t *tr) +{ + if (!self->groundentity || self->health <= 0) + return; + + if (!self->enemy) + { + self->enemy = attacker; + FoundTarget(self); + return; + } + + // PMM - don't bother if it's going to hit anyway; fix for weird in-your-face etas (I was + // seeing numbers like 13 and 14) + if ((eta < 0.1) || (eta > 5)) + return; + + // this will override the foundtarget call of stalker_run + stalker_dodge_jump(self); +} + + +// ****************** +// Jump onto / off of things +// ****************** + +//=================== +//=================== +void stalker_jump_down (edict_t *self) +{ + vec3_t forward,up; + + monster_jump_start (self); + + AngleVectors (self->s.angles, forward, NULL, up); + VectorMA(self->velocity, 100, forward, self->velocity); + VectorMA(self->velocity, 300, up, self->velocity); +} + +//=================== +//=================== +void stalker_jump_up (edict_t *self) +{ + vec3_t forward,up; + + monster_jump_start (self); + + AngleVectors (self->s.angles, forward, NULL, up); + VectorMA(self->velocity, 200, forward, self->velocity); + VectorMA(self->velocity, 450, up, self->velocity); +} + +//=================== +//=================== +void stalker_jump_wait_land (edict_t *self) +{ + if ((random() < (0.3 + (0.1*(float)(skill->value)))) && (level.time >= self->monsterinfo.attack_finished)) + { + self->monsterinfo.attack_finished = level.time + 0.3; + stalker_shoot_attack(self); + } + + if(self->groundentity == NULL) + { + self->gravity = 1.3; + self->monsterinfo.nextframe = self->s.frame; + + if(monster_jump_finished (self)) + { + self->gravity = 1; + self->monsterinfo.nextframe = self->s.frame + 1; + } + } + else + { + self->gravity = 1; + self->monsterinfo.nextframe = self->s.frame + 1; + } +} + +mframe_t stalker_frames_jump_up [] = +{ + ai_move, -8, NULL, + ai_move, -8, NULL, + ai_move, -8, NULL, + ai_move, -8, NULL, + + ai_move, 0, stalker_jump_up, + ai_move, 0, stalker_jump_wait_land, + ai_move, 0, NULL +}; +mmove_t stalker_move_jump_up = { FRAME_jump01, FRAME_jump07, stalker_frames_jump_up, stalker_run }; + +mframe_t stalker_frames_jump_down [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 0, stalker_jump_down, + ai_move, 0, stalker_jump_wait_land, + ai_move, 0, NULL +}; +mmove_t stalker_move_jump_down = { FRAME_jump01, FRAME_jump07, stalker_frames_jump_down, stalker_run }; + +//============ +// stalker_jump - this is only used for jumping onto or off of things. for dodge jumping, +// use stalker_dodge_jump +//============ +void stalker_jump (edict_t *self) +{ + if(!self->enemy) + return; + + if(self->enemy->s.origin[2] >= self->s.origin[2]) + { +// gi.dprintf("stalker_jump_up\n"); + self->monsterinfo.currentmove = &stalker_move_jump_up; + } + else + { +// gi.dprintf("stalker_jump_down\n"); + self->monsterinfo.currentmove = &stalker_move_jump_down; + } +} + + +// ****************** +// Blocked +// ****************** + +qboolean stalker_blocked (edict_t *self, float dist) +{ + qboolean onCeiling; + +// gi.dprintf("stalker_blocked\n"); + if(!has_valid_enemy(self)) + return false; + + onCeiling = false; + if(self->gravityVector[2] > 0) + onCeiling = true; + + if(!onCeiling) + { + if(blocked_checkshot(self, 0.25 + (0.05 * skill->value) )) + { +// gi.dprintf("blocked: shooting\n"); + return true; + } + + if(visible (self, self->enemy)) + { +// gi.dprintf("blocked: jumping at player!\n"); + stalker_do_pounce(self, self->enemy->s.origin); + return true; + } + + if(blocked_checkjump (self, dist, 256, 68)) + { +// gi.dprintf("blocked: jumping up/down\n"); + stalker_jump (self); + return true; + } + + if(blocked_checkplat (self, dist)) + return true; + } + else + { + if(blocked_checkshot(self, 0.25 + (0.05 * skill->value) )) + { +// gi.dprintf("blocked: shooting\n"); + return true; + } + else if(stalker_ok_to_transition(self)) + { + self->gravityVector[2] = -1; + self->s.angles[2] += 180.0; + if(self->s.angles[2] > 360.0) + self->s.angles[2] -= 360.0; + self->groundentity = NULL; + +// gi.dprintf("falling off ceiling\n"); + return true; + } +// else +// gi.dprintf("Not OK to fall!\n"); + } + + return false; +} + +// ****************** +// Death +// ****************** + +void stalker_dead (edict_t *self) +{ + VectorSet (self->mins, -28, -28, -18); + VectorSet (self->maxs, 28, 28, -4); + self->movetype = MOVETYPE_TOSS; + self->svflags |= SVF_DEADMONSTER; + self->nextthink = 0; + gi.linkentity (self); +// drawbbox(self); +} + +mframe_t stalker_frames_death [] = +{ + ai_move, 0, NULL, + ai_move, -5, NULL, + ai_move, -10, NULL, + ai_move, -20, NULL, + + ai_move, -10, NULL, + ai_move, -10, NULL, + ai_move, -5, NULL, + ai_move, -5, NULL, + + ai_move, 0, NULL +}; +mmove_t stalker_move_death = {FRAME_death01, FRAME_death09, stalker_frames_death, stalker_dead}; + +void stalker_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + int n; + +// gi.dprintf("stalker_die: %d\n", self->health); + +// dude bit it, make him fall! + self->movetype = MOVETYPE_TOSS; + self->s.angles[2] = 0; + VectorSet(self->gravityVector, 0, 0, -1); + +// check for gib + if (self->health <= self->gib_health) + { + gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0); + for (n= 0; n < 2; n++) + ThrowGib (self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC); + for (n= 0; n < 4; n++) + ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); + ThrowHead (self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC); + self->deadflag = DEAD_DEAD; + return; + } + + if (self->deadflag == DEAD_DEAD) + return; + +// regular death + gi.sound (self, CHAN_VOICE, sound_die, 1, ATTN_NORM, 0); + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_YES; + self->monsterinfo.currentmove = &stalker_move_death; +} + + +// ****************** +// SPAWN +// ****************** + +/*QUAKED monster_stalker (1 .5 0) (-28 -28 -18) (28 28 18) Ambush Trigger_Spawn Sight OnRoof +Spider Monster + + ONROOF - Monster starts sticking to the roof. +*/ +void SP_monster_stalker (edict_t *self) +{ + if (deathmatch->value) + { + G_FreeEdict (self); + return; + } + + sound_pain = gi.soundindex ("stalker/pain.wav"); + sound_die = gi.soundindex ("stalker/death.wav"); + sound_sight = gi.soundindex("stalker/sight.wav"); + sound_punch_hit1 = gi.soundindex ("stalker/melee1.wav"); + sound_punch_hit2 = gi.soundindex ("stalker/melee2.wav"); + sound_idle = gi.soundindex ("stalker/idle.wav"); + + // PMM - precache bolt2 + gi.modelindex ("models/proj/laser2/tris.md2"); + + self->s.modelindex = gi.modelindex ("models/monsters/stalker/tris.md2"); + VectorSet (self->mins, -28, -28, -18); + VectorSet (self->maxs, 28, 28, 18); + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + + self->health = 250; + self->gib_health = -50; // FIXME + self->mass = 250; + + self->pain = stalker_pain; + self->die = stalker_die; + + self->monsterinfo.stand = stalker_stand; + self->monsterinfo.walk = stalker_walk; + self->monsterinfo.run = stalker_run; + self->monsterinfo.attack = stalker_attack_ranged; + self->monsterinfo.sight = stalker_sight; + self->monsterinfo.idle = stalker_idle; + self->monsterinfo.dodge = stalker_dodge; + self->monsterinfo.blocked = stalker_blocked; + self->monsterinfo.melee = stalker_attack_melee; + + gi.linkentity (self); + + self->monsterinfo.currentmove = &stalker_move_stand; + self->monsterinfo.scale = MODEL_SCALE; + + self->monsterinfo.aiflags |= AI_WALK_WALLS; + + if(self->spawnflags & 8) + { + self->s.angles[2] = 180; + self->gravityVector[2] = 1; + } + + walkmonster_start (self); +} diff --git a/original/rogue/m_stalker.h b/original/rogue/m_stalker.h new file mode 100644 index 0000000..21a8324 --- /dev/null +++ b/original/rogue/m_stalker.h @@ -0,0 +1,101 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// /expanse/quake2/xpack/models/monsters/stalker + +// This file generated by qdata - Do NOT Modify + +#define FRAME_idle01 0 +#define FRAME_idle02 1 +#define FRAME_idle03 2 +#define FRAME_idle04 3 +#define FRAME_idle05 4 +#define FRAME_idle06 5 +#define FRAME_idle07 6 +#define FRAME_idle08 7 +#define FRAME_idle09 8 +#define FRAME_idle10 9 +#define FRAME_idle11 10 +#define FRAME_idle12 11 +#define FRAME_idle13 12 +#define FRAME_idle14 13 +#define FRAME_idle15 14 +#define FRAME_idle16 15 +#define FRAME_idle17 16 +#define FRAME_idle18 17 +#define FRAME_idle19 18 +#define FRAME_idle20 19 +#define FRAME_idle21 20 +#define FRAME_idle201 21 +#define FRAME_idle202 22 +#define FRAME_idle203 23 +#define FRAME_idle204 24 +#define FRAME_idle205 25 +#define FRAME_idle206 26 +#define FRAME_idle207 27 +#define FRAME_idle208 28 +#define FRAME_idle209 29 +#define FRAME_idle210 30 +#define FRAME_idle211 31 +#define FRAME_idle212 32 +#define FRAME_idle213 33 +#define FRAME_walk01 34 +#define FRAME_walk02 35 +#define FRAME_walk03 36 +#define FRAME_walk04 37 +#define FRAME_walk05 38 +#define FRAME_walk06 39 +#define FRAME_walk07 40 +#define FRAME_walk08 41 +#define FRAME_jump01 42 +#define FRAME_jump02 43 +#define FRAME_jump03 44 +#define FRAME_jump04 45 +#define FRAME_jump05 46 +#define FRAME_jump06 47 +#define FRAME_jump07 48 +#define FRAME_run01 49 +#define FRAME_run02 50 +#define FRAME_run03 51 +#define FRAME_run04 52 +#define FRAME_attack01 53 +#define FRAME_attack02 54 +#define FRAME_attack03 55 +#define FRAME_attack04 56 +#define FRAME_attack05 57 +#define FRAME_attack06 58 +#define FRAME_attack07 59 +#define FRAME_attack08 60 +#define FRAME_attack11 61 +#define FRAME_attack12 62 +#define FRAME_attack13 63 +#define FRAME_attack14 64 +#define FRAME_attack15 65 +#define FRAME_pain01 66 +#define FRAME_pain02 67 +#define FRAME_pain03 68 +#define FRAME_pain04 69 +#define FRAME_death01 70 +#define FRAME_death02 71 +#define FRAME_death03 72 +#define FRAME_death04 73 +#define FRAME_death05 74 +#define FRAME_death06 75 +#define FRAME_death07 76 +#define FRAME_death08 77 +#define FRAME_death09 78 +#define FRAME_twitch01 79 +#define FRAME_twitch02 80 +#define FRAME_twitch03 81 +#define FRAME_twitch04 82 +#define FRAME_twitch05 83 +#define FRAME_twitch06 84 +#define FRAME_twitch07 85 +#define FRAME_twitch08 86 +#define FRAME_twitch09 87 +#define FRAME_twitch10 88 +#define FRAME_reactive01 89 +#define FRAME_reactive02 90 +#define FRAME_reactive03 91 +#define FRAME_reactive04 92 + +#define MODEL_SCALE 1.000000 diff --git a/original/rogue/m_supertank.c b/original/rogue/m_supertank.c new file mode 100644 index 0000000..3adaf0f --- /dev/null +++ b/original/rogue/m_supertank.c @@ -0,0 +1,727 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +/* +============================================================================== + +SUPERTANK + +============================================================================== +*/ + +#include "g_local.h" +#include "m_supertank.h" + +qboolean visible (edict_t *self, edict_t *other); + +static int sound_pain1; +static int sound_pain2; +static int sound_pain3; +static int sound_death; +static int sound_search1; +static int sound_search2; + +static int tread_sound; + +void BossExplode (edict_t *self); + +void TreadSound (edict_t *self) +{ + gi.sound (self, CHAN_VOICE, tread_sound, 1, ATTN_NORM, 0); +} + +void supertank_search (edict_t *self) +{ + if (random() < 0.5) + gi.sound (self, CHAN_VOICE, sound_search1, 1, ATTN_NORM, 0); + else + gi.sound (self, CHAN_VOICE, sound_search2, 1, ATTN_NORM, 0); +} + + +void supertank_dead (edict_t *self); +void supertankRocket (edict_t *self); +void supertankMachineGun (edict_t *self); +void supertank_reattack1(edict_t *self); + + +// +// stand +// + +mframe_t supertank_frames_stand []= +{ + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL +}; +mmove_t supertank_move_stand = {FRAME_stand_1, FRAME_stand_60, supertank_frames_stand, NULL}; + +void supertank_stand (edict_t *self) +{ + self->monsterinfo.currentmove = &supertank_move_stand; +} + + +mframe_t supertank_frames_run [] = +{ + ai_run, 12, TreadSound, + ai_run, 12, NULL, + ai_run, 12, NULL, + ai_run, 12, NULL, + ai_run, 12, NULL, + ai_run, 12, NULL, + ai_run, 12, NULL, + ai_run, 12, NULL, + ai_run, 12, NULL, + ai_run, 12, NULL, + ai_run, 12, NULL, + ai_run, 12, NULL, + ai_run, 12, NULL, + ai_run, 12, NULL, + ai_run, 12, NULL, + ai_run, 12, NULL, + ai_run, 12, NULL, + ai_run, 12, NULL +}; +mmove_t supertank_move_run = {FRAME_forwrd_1, FRAME_forwrd_18, supertank_frames_run, NULL}; + +// +// walk +// + + +mframe_t supertank_frames_forward [] = +{ + ai_walk, 4, TreadSound, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL +}; +mmove_t supertank_move_forward = {FRAME_forwrd_1, FRAME_forwrd_18, supertank_frames_forward, NULL}; + +void supertank_forward (edict_t *self) +{ + self->monsterinfo.currentmove = &supertank_move_forward; +} + +void supertank_walk (edict_t *self) +{ + self->monsterinfo.currentmove = &supertank_move_forward; +} + +void supertank_run (edict_t *self) +{ + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + self->monsterinfo.currentmove = &supertank_move_stand; + else + self->monsterinfo.currentmove = &supertank_move_run; +} + +mframe_t supertank_frames_turn_right [] = +{ + ai_move, 0, TreadSound, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t supertank_move_turn_right = {FRAME_right_1, FRAME_right_18, supertank_frames_turn_right, supertank_run}; + +mframe_t supertank_frames_turn_left [] = +{ + ai_move, 0, TreadSound, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t supertank_move_turn_left = {FRAME_left_1, FRAME_left_18, supertank_frames_turn_left, supertank_run}; + + +mframe_t supertank_frames_pain3 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t supertank_move_pain3 = {FRAME_pain3_9, FRAME_pain3_12, supertank_frames_pain3, supertank_run}; + +mframe_t supertank_frames_pain2 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t supertank_move_pain2 = {FRAME_pain2_5, FRAME_pain2_8, supertank_frames_pain2, supertank_run}; + +mframe_t supertank_frames_pain1 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t supertank_move_pain1 = {FRAME_pain1_1, FRAME_pain1_4, supertank_frames_pain1, supertank_run}; + +mframe_t supertank_frames_death1 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, BossExplode +}; +mmove_t supertank_move_death = {FRAME_death_1, FRAME_death_24, supertank_frames_death1, supertank_dead}; + +mframe_t supertank_frames_backward[] = +{ + ai_walk, 0, TreadSound, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL +}; +mmove_t supertank_move_backward = {FRAME_backwd_1, FRAME_backwd_18, supertank_frames_backward, NULL}; + +mframe_t supertank_frames_attack4[]= +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t supertank_move_attack4 = {FRAME_attak4_1, FRAME_attak4_6, supertank_frames_attack4, supertank_run}; + +mframe_t supertank_frames_attack3[]= +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t supertank_move_attack3 = {FRAME_attak3_1, FRAME_attak3_27, supertank_frames_attack3, supertank_run}; + +mframe_t supertank_frames_attack2[]= +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, supertankRocket, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, supertankRocket, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, supertankRocket, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t supertank_move_attack2 = {FRAME_attak2_1, FRAME_attak2_27, supertank_frames_attack2, supertank_run}; + +mframe_t supertank_frames_attack1[]= +{ + ai_charge, 0, supertankMachineGun, + ai_charge, 0, supertankMachineGun, + ai_charge, 0, supertankMachineGun, + ai_charge, 0, supertankMachineGun, + ai_charge, 0, supertankMachineGun, + ai_charge, 0, supertankMachineGun, + +}; +mmove_t supertank_move_attack1 = {FRAME_attak1_1, FRAME_attak1_6, supertank_frames_attack1, supertank_reattack1}; + +mframe_t supertank_frames_end_attack1[]= +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t supertank_move_end_attack1 = {FRAME_attak1_7, FRAME_attak1_20, supertank_frames_end_attack1, supertank_run}; + + +void supertank_reattack1(edict_t *self) +{ + if (visible(self, self->enemy)) + if (random() < 0.9) + self->monsterinfo.currentmove = &supertank_move_attack1; + else + self->monsterinfo.currentmove = &supertank_move_end_attack1; + else + self->monsterinfo.currentmove = &supertank_move_end_attack1; +} + +void supertank_pain (edict_t *self, edict_t *other, float kick, int damage) +{ + + if (self->health < (self->max_health / 2)) + self->s.skinnum = 1; + + if (level.time < self->pain_debounce_time) + return; + + // Lessen the chance of him going into his pain frames + if (damage <=25) + if (random()<0.2) + return; + + // Don't go into pain if he's firing his rockets + if (skill->value >= 2) + if ( (self->s.frame >= FRAME_attak2_1) && (self->s.frame <= FRAME_attak2_14) ) + return; + + self->pain_debounce_time = level.time + 3; + + if (skill->value == 3) + return; // no pain anims in nightmare + + if (damage <= 10) + { + gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM,0); + self->monsterinfo.currentmove = &supertank_move_pain1; + } + else if (damage <= 25) + { + gi.sound (self, CHAN_VOICE, sound_pain3, 1, ATTN_NORM,0); + self->monsterinfo.currentmove = &supertank_move_pain2; + } + else + { + gi.sound (self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM,0); + self->monsterinfo.currentmove = &supertank_move_pain3; + } +}; + + +void supertankRocket (edict_t *self) +{ + vec3_t forward, right; + vec3_t start; + vec3_t dir; + vec3_t vec; + int flash_number; + + if(!self->enemy || !self->enemy->inuse) //PGM + return; //PGM + + if (self->s.frame == FRAME_attak2_8) + flash_number = MZ2_SUPERTANK_ROCKET_1; + else if (self->s.frame == FRAME_attak2_11) + flash_number = MZ2_SUPERTANK_ROCKET_2; + else // (self->s.frame == FRAME_attak2_14) + flash_number = MZ2_SUPERTANK_ROCKET_3; + + AngleVectors (self->s.angles, forward, right, NULL); + G_ProjectSource (self->s.origin, monster_flash_offset[flash_number], forward, right, start); + + VectorCopy (self->enemy->s.origin, vec); + vec[2] += self->enemy->viewheight; + VectorSubtract (vec, start, dir); + VectorNormalize (dir); + + monster_fire_rocket (self, start, dir, 50, 500, flash_number); +} + +void supertankMachineGun (edict_t *self) +{ + vec3_t dir; + vec3_t vec; + vec3_t start; + vec3_t forward, right; + int flash_number; + + if(!self->enemy || !self->enemy->inuse) //PGM + return; //PGM + + flash_number = MZ2_SUPERTANK_MACHINEGUN_1 + (self->s.frame - FRAME_attak1_1); + + //FIXME!!! + dir[0] = 0; + dir[1] = self->s.angles[1]; + dir[2] = 0; + + AngleVectors (dir, forward, right, NULL); + G_ProjectSource (self->s.origin, monster_flash_offset[flash_number], forward, right, start); + + if (self->enemy) + { + VectorCopy (self->enemy->s.origin, vec); + VectorMA (vec, 0, self->enemy->velocity, vec); + vec[2] += self->enemy->viewheight; + VectorSubtract (vec, start, forward); + VectorNormalize (forward); + } + + monster_fire_bullet (self, start, forward, 6, 4, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, flash_number); +} + + +void supertank_attack(edict_t *self) +{ + vec3_t vec; + float range; + //float r; + + VectorSubtract (self->enemy->s.origin, self->s.origin, vec); + range = VectorLength (vec); + + //r = random(); + + // Attack 1 == Chaingun + // Attack 2 == Rocket Launcher + + if (range <= 160) + { + self->monsterinfo.currentmove = &supertank_move_attack1; + } + else + { // fire rockets more often at distance + if (random() < 0.3) + self->monsterinfo.currentmove = &supertank_move_attack1; + else + self->monsterinfo.currentmove = &supertank_move_attack2; + } +} + + +// +// death +// + +void supertank_dead (edict_t *self) +{ + VectorSet (self->mins, -60, -60, 0); + VectorSet (self->maxs, 60, 60, 72); + self->movetype = MOVETYPE_TOSS; + self->svflags |= SVF_DEADMONSTER; + self->nextthink = 0; + gi.linkentity (self); +} + + +void BossExplode (edict_t *self) +{ + vec3_t org; + int n; + + self->think = BossExplode; + VectorCopy (self->s.origin, org); + org[2] += 24 + (rand()&15); + switch (self->count++) + { + case 0: + org[0] -= 24; + org[1] -= 24; + break; + case 1: + org[0] += 24; + org[1] += 24; + break; + case 2: + org[0] += 24; + org[1] -= 24; + break; + case 3: + org[0] -= 24; + org[1] += 24; + break; + case 4: + org[0] -= 48; + org[1] -= 48; + break; + case 5: + org[0] += 48; + org[1] += 48; + break; + case 6: + org[0] -= 48; + org[1] += 48; + break; + case 7: + org[0] += 48; + org[1] -= 48; + break; + case 8: + self->s.sound = 0; + for (n= 0; n < 4; n++) + ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", 500, GIB_ORGANIC); + for (n= 0; n < 8; n++) + ThrowGib (self, "models/objects/gibs/sm_metal/tris.md2", 500, GIB_METALLIC); + ThrowGib (self, "models/objects/gibs/chest/tris.md2", 500, GIB_ORGANIC); + ThrowHead (self, "models/objects/gibs/gear/tris.md2", 500, GIB_METALLIC); + self->deadflag = DEAD_DEAD; + return; + } + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_EXPLOSION1); + gi.WritePosition (org); + gi.multicast (self->s.origin, MULTICAST_PVS); + + self->nextthink = level.time + 0.1; +} + + +void supertank_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + gi.sound (self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0); + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_NO; + self->count = 0; + self->monsterinfo.currentmove = &supertank_move_death; +} + + +//=========== +//PGM +qboolean supertank_blocked (edict_t *self, float dist) +{ + if(blocked_checkshot (self, 0.25 + (0.05 * skill->value) )) + return true; + + if(blocked_checkplat (self, dist)) + return true; + + return false; +} +//PGM +//=========== + +// +// monster_supertank +// + +/*QUAKED monster_supertank (1 .5 0) (-64 -64 0) (64 64 72) Ambush Trigger_Spawn Sight +*/ +void SP_monster_supertank (edict_t *self) +{ + if (deathmatch->value) + { + G_FreeEdict (self); + return; + } + + sound_pain1 = gi.soundindex ("bosstank/btkpain1.wav"); + sound_pain2 = gi.soundindex ("bosstank/btkpain2.wav"); + sound_pain3 = gi.soundindex ("bosstank/btkpain3.wav"); + sound_death = gi.soundindex ("bosstank/btkdeth1.wav"); + sound_search1 = gi.soundindex ("bosstank/btkunqv1.wav"); + sound_search2 = gi.soundindex ("bosstank/btkunqv2.wav"); + +// self->s.sound = gi.soundindex ("bosstank/btkengn1.wav"); + tread_sound = gi.soundindex ("bosstank/btkengn1.wav"); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex ("models/monsters/boss1/tris.md2"); + VectorSet (self->mins, -64, -64, 0); + VectorSet (self->maxs, 64, 64, 112); + + self->health = 1500; + self->gib_health = -500; + self->mass = 800; + + self->pain = supertank_pain; + self->die = supertank_die; + self->monsterinfo.stand = supertank_stand; + self->monsterinfo.walk = supertank_walk; + self->monsterinfo.run = supertank_run; + self->monsterinfo.dodge = NULL; + self->monsterinfo.attack = supertank_attack; + self->monsterinfo.search = supertank_search; + self->monsterinfo.melee = NULL; + self->monsterinfo.sight = NULL; + self->monsterinfo.blocked = supertank_blocked; //PGM + + gi.linkentity (self); + + self->monsterinfo.currentmove = &supertank_move_stand; + self->monsterinfo.scale = MODEL_SCALE; + + walkmonster_start(self); + + //PMM + self->monsterinfo.aiflags |= AI_IGNORE_SHOTS; + //pmm +} diff --git a/original/rogue/m_supertank.h b/original/rogue/m_supertank.h new file mode 100644 index 0000000..c745d70 --- /dev/null +++ b/original/rogue/m_supertank.h @@ -0,0 +1,262 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// G:\quake2\baseq2\models/monsters/boss1/backup + +// This file generated by ModelGen - Do NOT Modify + +#define FRAME_attak1_1 0 +#define FRAME_attak1_2 1 +#define FRAME_attak1_3 2 +#define FRAME_attak1_4 3 +#define FRAME_attak1_5 4 +#define FRAME_attak1_6 5 +#define FRAME_attak1_7 6 +#define FRAME_attak1_8 7 +#define FRAME_attak1_9 8 +#define FRAME_attak1_10 9 +#define FRAME_attak1_11 10 +#define FRAME_attak1_12 11 +#define FRAME_attak1_13 12 +#define FRAME_attak1_14 13 +#define FRAME_attak1_15 14 +#define FRAME_attak1_16 15 +#define FRAME_attak1_17 16 +#define FRAME_attak1_18 17 +#define FRAME_attak1_19 18 +#define FRAME_attak1_20 19 +#define FRAME_attak2_1 20 +#define FRAME_attak2_2 21 +#define FRAME_attak2_3 22 +#define FRAME_attak2_4 23 +#define FRAME_attak2_5 24 +#define FRAME_attak2_6 25 +#define FRAME_attak2_7 26 +#define FRAME_attak2_8 27 +#define FRAME_attak2_9 28 +#define FRAME_attak2_10 29 +#define FRAME_attak2_11 30 +#define FRAME_attak2_12 31 +#define FRAME_attak2_13 32 +#define FRAME_attak2_14 33 +#define FRAME_attak2_15 34 +#define FRAME_attak2_16 35 +#define FRAME_attak2_17 36 +#define FRAME_attak2_18 37 +#define FRAME_attak2_19 38 +#define FRAME_attak2_20 39 +#define FRAME_attak2_21 40 +#define FRAME_attak2_22 41 +#define FRAME_attak2_23 42 +#define FRAME_attak2_24 43 +#define FRAME_attak2_25 44 +#define FRAME_attak2_26 45 +#define FRAME_attak2_27 46 +#define FRAME_attak3_1 47 +#define FRAME_attak3_2 48 +#define FRAME_attak3_3 49 +#define FRAME_attak3_4 50 +#define FRAME_attak3_5 51 +#define FRAME_attak3_6 52 +#define FRAME_attak3_7 53 +#define FRAME_attak3_8 54 +#define FRAME_attak3_9 55 +#define FRAME_attak3_10 56 +#define FRAME_attak3_11 57 +#define FRAME_attak3_12 58 +#define FRAME_attak3_13 59 +#define FRAME_attak3_14 60 +#define FRAME_attak3_15 61 +#define FRAME_attak3_16 62 +#define FRAME_attak3_17 63 +#define FRAME_attak3_18 64 +#define FRAME_attak3_19 65 +#define FRAME_attak3_20 66 +#define FRAME_attak3_21 67 +#define FRAME_attak3_22 68 +#define FRAME_attak3_23 69 +#define FRAME_attak3_24 70 +#define FRAME_attak3_25 71 +#define FRAME_attak3_26 72 +#define FRAME_attak3_27 73 +#define FRAME_attak4_1 74 +#define FRAME_attak4_2 75 +#define FRAME_attak4_3 76 +#define FRAME_attak4_4 77 +#define FRAME_attak4_5 78 +#define FRAME_attak4_6 79 +#define FRAME_backwd_1 80 +#define FRAME_backwd_2 81 +#define FRAME_backwd_3 82 +#define FRAME_backwd_4 83 +#define FRAME_backwd_5 84 +#define FRAME_backwd_6 85 +#define FRAME_backwd_7 86 +#define FRAME_backwd_8 87 +#define FRAME_backwd_9 88 +#define FRAME_backwd_10 89 +#define FRAME_backwd_11 90 +#define FRAME_backwd_12 91 +#define FRAME_backwd_13 92 +#define FRAME_backwd_14 93 +#define FRAME_backwd_15 94 +#define FRAME_backwd_16 95 +#define FRAME_backwd_17 96 +#define FRAME_backwd_18 97 +#define FRAME_death_1 98 +#define FRAME_death_2 99 +#define FRAME_death_3 100 +#define FRAME_death_4 101 +#define FRAME_death_5 102 +#define FRAME_death_6 103 +#define FRAME_death_7 104 +#define FRAME_death_8 105 +#define FRAME_death_9 106 +#define FRAME_death_10 107 +#define FRAME_death_11 108 +#define FRAME_death_12 109 +#define FRAME_death_13 110 +#define FRAME_death_14 111 +#define FRAME_death_15 112 +#define FRAME_death_16 113 +#define FRAME_death_17 114 +#define FRAME_death_18 115 +#define FRAME_death_19 116 +#define FRAME_death_20 117 +#define FRAME_death_21 118 +#define FRAME_death_22 119 +#define FRAME_death_23 120 +#define FRAME_death_24 121 +#define FRAME_death_31 122 +#define FRAME_death_32 123 +#define FRAME_death_33 124 +#define FRAME_death_45 125 +#define FRAME_death_46 126 +#define FRAME_death_47 127 +#define FRAME_forwrd_1 128 +#define FRAME_forwrd_2 129 +#define FRAME_forwrd_3 130 +#define FRAME_forwrd_4 131 +#define FRAME_forwrd_5 132 +#define FRAME_forwrd_6 133 +#define FRAME_forwrd_7 134 +#define FRAME_forwrd_8 135 +#define FRAME_forwrd_9 136 +#define FRAME_forwrd_10 137 +#define FRAME_forwrd_11 138 +#define FRAME_forwrd_12 139 +#define FRAME_forwrd_13 140 +#define FRAME_forwrd_14 141 +#define FRAME_forwrd_15 142 +#define FRAME_forwrd_16 143 +#define FRAME_forwrd_17 144 +#define FRAME_forwrd_18 145 +#define FRAME_left_1 146 +#define FRAME_left_2 147 +#define FRAME_left_3 148 +#define FRAME_left_4 149 +#define FRAME_left_5 150 +#define FRAME_left_6 151 +#define FRAME_left_7 152 +#define FRAME_left_8 153 +#define FRAME_left_9 154 +#define FRAME_left_10 155 +#define FRAME_left_11 156 +#define FRAME_left_12 157 +#define FRAME_left_13 158 +#define FRAME_left_14 159 +#define FRAME_left_15 160 +#define FRAME_left_16 161 +#define FRAME_left_17 162 +#define FRAME_left_18 163 +#define FRAME_pain1_1 164 +#define FRAME_pain1_2 165 +#define FRAME_pain1_3 166 +#define FRAME_pain1_4 167 +#define FRAME_pain2_5 168 +#define FRAME_pain2_6 169 +#define FRAME_pain2_7 170 +#define FRAME_pain2_8 171 +#define FRAME_pain3_9 172 +#define FRAME_pain3_10 173 +#define FRAME_pain3_11 174 +#define FRAME_pain3_12 175 +#define FRAME_right_1 176 +#define FRAME_right_2 177 +#define FRAME_right_3 178 +#define FRAME_right_4 179 +#define FRAME_right_5 180 +#define FRAME_right_6 181 +#define FRAME_right_7 182 +#define FRAME_right_8 183 +#define FRAME_right_9 184 +#define FRAME_right_10 185 +#define FRAME_right_11 186 +#define FRAME_right_12 187 +#define FRAME_right_13 188 +#define FRAME_right_14 189 +#define FRAME_right_15 190 +#define FRAME_right_16 191 +#define FRAME_right_17 192 +#define FRAME_right_18 193 +#define FRAME_stand_1 194 +#define FRAME_stand_2 195 +#define FRAME_stand_3 196 +#define FRAME_stand_4 197 +#define FRAME_stand_5 198 +#define FRAME_stand_6 199 +#define FRAME_stand_7 200 +#define FRAME_stand_8 201 +#define FRAME_stand_9 202 +#define FRAME_stand_10 203 +#define FRAME_stand_11 204 +#define FRAME_stand_12 205 +#define FRAME_stand_13 206 +#define FRAME_stand_14 207 +#define FRAME_stand_15 208 +#define FRAME_stand_16 209 +#define FRAME_stand_17 210 +#define FRAME_stand_18 211 +#define FRAME_stand_19 212 +#define FRAME_stand_20 213 +#define FRAME_stand_21 214 +#define FRAME_stand_22 215 +#define FRAME_stand_23 216 +#define FRAME_stand_24 217 +#define FRAME_stand_25 218 +#define FRAME_stand_26 219 +#define FRAME_stand_27 220 +#define FRAME_stand_28 221 +#define FRAME_stand_29 222 +#define FRAME_stand_30 223 +#define FRAME_stand_31 224 +#define FRAME_stand_32 225 +#define FRAME_stand_33 226 +#define FRAME_stand_34 227 +#define FRAME_stand_35 228 +#define FRAME_stand_36 229 +#define FRAME_stand_37 230 +#define FRAME_stand_38 231 +#define FRAME_stand_39 232 +#define FRAME_stand_40 233 +#define FRAME_stand_41 234 +#define FRAME_stand_42 235 +#define FRAME_stand_43 236 +#define FRAME_stand_44 237 +#define FRAME_stand_45 238 +#define FRAME_stand_46 239 +#define FRAME_stand_47 240 +#define FRAME_stand_48 241 +#define FRAME_stand_49 242 +#define FRAME_stand_50 243 +#define FRAME_stand_51 244 +#define FRAME_stand_52 245 +#define FRAME_stand_53 246 +#define FRAME_stand_54 247 +#define FRAME_stand_55 248 +#define FRAME_stand_56 249 +#define FRAME_stand_57 250 +#define FRAME_stand_58 251 +#define FRAME_stand_59 252 +#define FRAME_stand_60 253 + +#define MODEL_SCALE 1.000000 diff --git a/original/rogue/m_tank.c b/original/rogue/m_tank.c new file mode 100644 index 0000000..6f750e8 --- /dev/null +++ b/original/rogue/m_tank.c @@ -0,0 +1,1035 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +/* +============================================================================== + +TANK + +============================================================================== +*/ + +#include "g_local.h" +#include "m_tank.h" + + +void tank_refire_rocket (edict_t *self); +void tank_doattack_rocket (edict_t *self); +void tank_reattack_blaster (edict_t *self); + +static int sound_thud; +static int sound_pain; +static int sound_idle; +static int sound_die; +static int sound_step; +static int sound_sight; +static int sound_windup; +static int sound_strike; + +// +// misc +// + +void tank_sight (edict_t *self, edict_t *other) +{ + gi.sound (self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); +} + + +void tank_footstep (edict_t *self) +{ + gi.sound (self, CHAN_BODY, sound_step, 1, ATTN_NORM, 0); +} + +void tank_thud (edict_t *self) +{ + gi.sound (self, CHAN_BODY, sound_thud, 1, ATTN_NORM, 0); +} + +void tank_windup (edict_t *self) +{ + gi.sound (self, CHAN_WEAPON, sound_windup, 1, ATTN_NORM, 0); +} + +void tank_idle (edict_t *self) +{ + gi.sound (self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0); +} + + +// +// stand +// + +mframe_t tank_frames_stand []= +{ + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL +}; +mmove_t tank_move_stand = {FRAME_stand01, FRAME_stand30, tank_frames_stand, NULL}; + +void tank_stand (edict_t *self) +{ + self->monsterinfo.currentmove = &tank_move_stand; +} + + +// +// walk +// + +void tank_walk (edict_t *self); + +mframe_t tank_frames_start_walk [] = +{ + ai_walk, 0, NULL, + ai_walk, 6, NULL, + ai_walk, 6, NULL, + ai_walk, 11, tank_footstep +}; +mmove_t tank_move_start_walk = {FRAME_walk01, FRAME_walk04, tank_frames_start_walk, tank_walk}; + +mframe_t tank_frames_walk [] = +{ + ai_walk, 4, NULL, + ai_walk, 5, NULL, + ai_walk, 3, NULL, + ai_walk, 2, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 4, NULL, + ai_walk, 4, tank_footstep, + ai_walk, 3, NULL, + ai_walk, 5, NULL, + ai_walk, 4, NULL, + ai_walk, 5, NULL, + ai_walk, 7, NULL, + ai_walk, 7, NULL, + ai_walk, 6, NULL, + ai_walk, 6, tank_footstep +}; +mmove_t tank_move_walk = {FRAME_walk05, FRAME_walk20, tank_frames_walk, NULL}; + +mframe_t tank_frames_stop_walk [] = +{ + ai_walk, 3, NULL, + ai_walk, 3, NULL, + ai_walk, 2, NULL, + ai_walk, 2, NULL, + ai_walk, 4, tank_footstep +}; +mmove_t tank_move_stop_walk = {FRAME_walk21, FRAME_walk25, tank_frames_stop_walk, tank_stand}; + +void tank_walk (edict_t *self) +{ + self->monsterinfo.currentmove = &tank_move_walk; +} + + +// +// run +// + +void tank_run (edict_t *self); + +mframe_t tank_frames_start_run [] = +{ + ai_run, 0, NULL, + ai_run, 6, NULL, + ai_run, 6, NULL, + ai_run, 11, tank_footstep +}; +mmove_t tank_move_start_run = {FRAME_walk01, FRAME_walk04, tank_frames_start_run, tank_run}; + +mframe_t tank_frames_run [] = +{ + ai_run, 4, NULL, + ai_run, 5, NULL, + ai_run, 3, NULL, + ai_run, 2, NULL, + ai_run, 5, NULL, + ai_run, 5, NULL, + ai_run, 4, NULL, + ai_run, 4, tank_footstep, + ai_run, 3, NULL, + ai_run, 5, NULL, + ai_run, 4, NULL, + ai_run, 5, NULL, + ai_run, 7, NULL, + ai_run, 7, NULL, + ai_run, 6, NULL, + ai_run, 6, tank_footstep +}; +mmove_t tank_move_run = {FRAME_walk05, FRAME_walk20, tank_frames_run, NULL}; + +mframe_t tank_frames_stop_run [] = +{ + ai_run, 3, NULL, + ai_run, 3, NULL, + ai_run, 2, NULL, + ai_run, 2, NULL, + ai_run, 4, tank_footstep +}; +mmove_t tank_move_stop_run = {FRAME_walk21, FRAME_walk25, tank_frames_stop_run, tank_walk}; + +void tank_run (edict_t *self) +{ + if (self->enemy && self->enemy->client) + self->monsterinfo.aiflags |= AI_BRUTAL; + else + self->monsterinfo.aiflags &= ~AI_BRUTAL; + + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + { + self->monsterinfo.currentmove = &tank_move_stand; + return; + } + + if (self->monsterinfo.currentmove == &tank_move_walk || + self->monsterinfo.currentmove == &tank_move_start_run) + { + self->monsterinfo.currentmove = &tank_move_run; + } + else + { + self->monsterinfo.currentmove = &tank_move_start_run; + } +} + +// +// pain +// + +mframe_t tank_frames_pain1 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t tank_move_pain1 = {FRAME_pain101, FRAME_pain104, tank_frames_pain1, tank_run}; + +mframe_t tank_frames_pain2 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t tank_move_pain2 = {FRAME_pain201, FRAME_pain205, tank_frames_pain2, tank_run}; + +mframe_t tank_frames_pain3 [] = +{ + ai_move, -7, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 2, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 3, NULL, + ai_move, 0, NULL, + ai_move, 2, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, tank_footstep +}; +mmove_t tank_move_pain3 = {FRAME_pain301, FRAME_pain316, tank_frames_pain3, tank_run}; + + +void tank_pain (edict_t *self, edict_t *other, float kick, int damage) +{ + if (self->health < (self->max_health / 2)) + self->s.skinnum |= 1; + + if (damage <= 10) + return; + + if (level.time < self->pain_debounce_time) + return; + + if (damage <= 30) + if (random() > 0.2) + return; + + // If hard or nightmare, don't go into pain while attacking + if ( skill->value >= 2) + { + if ( (self->s.frame >= FRAME_attak301) && (self->s.frame <= FRAME_attak330) ) + return; + if ( (self->s.frame >= FRAME_attak101) && (self->s.frame <= FRAME_attak116) ) + return; + } + + self->pain_debounce_time = level.time + 3; + gi.sound (self, CHAN_VOICE, sound_pain, 1, ATTN_NORM, 0); + + if (skill->value == 3) + return; // no pain anims in nightmare + + // PMM - blindfire cleanup + self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING; + // pmm + + if (damage <= 30) + self->monsterinfo.currentmove = &tank_move_pain1; + else if (damage <= 60) + self->monsterinfo.currentmove = &tank_move_pain2; + else + self->monsterinfo.currentmove = &tank_move_pain3; +}; + + +// +// attacks +// + +void TankBlaster (edict_t *self) +{ + vec3_t forward, right; + vec3_t start; + vec3_t end; + vec3_t dir; + int flash_number; + + if(!self->enemy || !self->enemy->inuse) //PGM + return; //PGM + + if (self->s.frame == FRAME_attak110) + flash_number = MZ2_TANK_BLASTER_1; + else if (self->s.frame == FRAME_attak113) + flash_number = MZ2_TANK_BLASTER_2; + else // (self->s.frame == FRAME_attak116) + flash_number = MZ2_TANK_BLASTER_3; + + AngleVectors (self->s.angles, forward, right, NULL); + G_ProjectSource (self->s.origin, monster_flash_offset[flash_number], forward, right, start); + + VectorCopy (self->enemy->s.origin, end); + end[2] += self->enemy->viewheight; + VectorSubtract (end, start, dir); + + monster_fire_blaster (self, start, dir, 30, 800, flash_number, EF_BLASTER); +} + +void TankStrike (edict_t *self) +{ + gi.sound (self, CHAN_WEAPON, sound_strike, 1, ATTN_NORM, 0); +} + +void TankRocket (edict_t *self) +{ + vec3_t forward, right; + vec3_t start; + vec3_t dir; + vec3_t vec; + int flash_number; + trace_t trace; // PGM + int rocketSpeed; // PGM + // pmm - blindfire support + vec3_t target; + qboolean blindfire = false; + + if(!self->enemy || !self->enemy->inuse) //PGM + return; //PGM + + // pmm - blindfire check + if (self->monsterinfo.aiflags & AI_MANUAL_STEERING) + blindfire = true; + else + blindfire = false; + + if (self->s.frame == FRAME_attak324) + flash_number = MZ2_TANK_ROCKET_1; + else if (self->s.frame == FRAME_attak327) + flash_number = MZ2_TANK_ROCKET_2; + else // (self->s.frame == FRAME_attak330) + flash_number = MZ2_TANK_ROCKET_3; + + AngleVectors (self->s.angles, forward, right, NULL); + G_ProjectSource (self->s.origin, monster_flash_offset[flash_number], forward, right, start); + + rocketSpeed = 500 + (100 * skill->value); // PGM rock & roll.... :) + + // PMM + if (blindfire) + VectorCopy (self->monsterinfo.blind_fire_target, target); + else + VectorCopy (self->enemy->s.origin, target); + // pmm + +// VectorCopy (self->enemy->s.origin, vec); +// vec[2] += self->enemy->viewheight; +// VectorSubtract (vec, start, dir); + +//PGM + // PMM - blindfire shooting + if (blindfire) + { + VectorCopy (target, vec); + VectorSubtract (vec, start, dir); + } + // pmm + // don't shoot at feet if they're above me. + else if(random() < 0.66 || (start[2] < self->enemy->absmin[2])) + { +// gi.dprintf("normal shot\n"); + VectorCopy (self->enemy->s.origin, vec); + vec[2] += self->enemy->viewheight; + VectorSubtract (vec, start, dir); + } + else + { +// gi.dprintf("shooting at feet!\n"); + VectorCopy (self->enemy->s.origin, vec); + vec[2] = self->enemy->absmin[2]; + VectorSubtract (vec, start, dir); + } +//PGM + +//====== +//PMM - lead target (not when blindfiring) + // 20, 35, 50, 65 chance of leading + if((!blindfire) && ((random() < (0.2 + ((3 - skill->value) * 0.15))))) + { + float dist; + float time; + +// gi.dprintf ("leading target\n"); + dist = VectorLength (dir); + time = dist/rocketSpeed; + VectorMA(vec, time, self->enemy->velocity, vec); + VectorSubtract(vec, start, dir); + } +//PMM - lead target +//====== + + VectorNormalize (dir); + +// gi.WriteByte (svc_temp_entity); +// gi.WriteByte (TE_DEBUGTRAIL); +// gi.WritePosition (start); +// gi.WritePosition (vec); +// gi.multicast (start, MULTICAST_ALL); + + // pmm blindfire doesn't check target (done in checkattack) + // paranoia, make sure we're not shooting a target right next to us + trace = gi.trace(start, vec3_origin, vec3_origin, vec, self, MASK_SHOT); + if (blindfire) + { + // blindfire has different fail criteria for the trace + if (!(trace.startsolid || trace.allsolid || (trace.fraction < 0.5))) + monster_fire_rocket (self, start, dir, 50, rocketSpeed, flash_number); + else + { + // try shifting the target to the left a little (to help counter large offset) + VectorCopy (target, vec); + VectorMA (vec, -20, right, vec); + VectorSubtract(vec, start, dir); + VectorNormalize (dir); + trace = gi.trace(start, vec3_origin, vec3_origin, vec, self, MASK_SHOT); + if (!(trace.startsolid || trace.allsolid || (trace.fraction < 0.5))) + monster_fire_rocket (self, start, dir, 50, rocketSpeed, flash_number); + else + { + // ok, that failed. try to the right + VectorCopy (target, vec); + VectorMA (vec, 20, right, vec); + VectorSubtract(vec, start, dir); + VectorNormalize (dir); + trace = gi.trace(start, vec3_origin, vec3_origin, vec, self, MASK_SHOT); + if (!(trace.startsolid || trace.allsolid || (trace.fraction < 0.5))) + monster_fire_rocket (self, start, dir, 50, rocketSpeed, flash_number); + else if ((g_showlogic) && (g_showlogic->value)) + // ok, I give up + gi.dprintf ("tank avoiding blindfire shot\n"); + } + } + } + else + { + trace = gi.trace(start, vec3_origin, vec3_origin, vec, self, MASK_SHOT); + if(trace.ent == self->enemy || trace.ent == world) + { + if(trace.fraction > 0.5 || (trace.ent && trace.ent->client)) + monster_fire_rocket (self, start, dir, 50, rocketSpeed, MZ2_CHICK_ROCKET_1); + // else + // gi.dprintf("didn't make it halfway to target...aborting\n"); + } + } +} + +void TankMachineGun (edict_t *self) +{ + vec3_t dir; + vec3_t vec; + vec3_t start; + vec3_t forward, right; + int flash_number; + + if(!self->enemy || !self->enemy->inuse) //PGM + return; //PGM + + flash_number = MZ2_TANK_MACHINEGUN_1 + (self->s.frame - FRAME_attak406); + + AngleVectors (self->s.angles, forward, right, NULL); + G_ProjectSource (self->s.origin, monster_flash_offset[flash_number], forward, right, start); + + if (self->enemy) + { + VectorCopy (self->enemy->s.origin, vec); + vec[2] += self->enemy->viewheight; + VectorSubtract (vec, start, vec); + vectoangles (vec, vec); + dir[0] = vec[0]; + } + else + { + dir[0] = 0; + } + if (self->s.frame <= FRAME_attak415) + dir[1] = self->s.angles[1] - 8 * (self->s.frame - FRAME_attak411); + else + dir[1] = self->s.angles[1] + 8 * (self->s.frame - FRAME_attak419); + dir[2] = 0; + + AngleVectors (dir, forward, NULL, NULL); + + monster_fire_bullet (self, start, forward, 20, 4, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, flash_number); +} + + +mframe_t tank_frames_attack_blast [] = +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, -1, NULL, + ai_charge, -2, NULL, + ai_charge, -1, NULL, + ai_charge, -1, NULL, + ai_charge, 0, NULL, + ai_charge, 0, TankBlaster, // 10 + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, TankBlaster, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, TankBlaster // 16 +}; +mmove_t tank_move_attack_blast = {FRAME_attak101, FRAME_attak116, tank_frames_attack_blast, tank_reattack_blaster}; + +mframe_t tank_frames_reattack_blast [] = +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, TankBlaster, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, TankBlaster // 16 +}; +mmove_t tank_move_reattack_blast = {FRAME_attak111, FRAME_attak116, tank_frames_reattack_blast, tank_reattack_blaster}; + +mframe_t tank_frames_attack_post_blast [] = +{ + ai_move, 0, NULL, // 17 + ai_move, 0, NULL, + ai_move, 2, NULL, + ai_move, 3, NULL, + ai_move, 2, NULL, + ai_move, -2, tank_footstep // 22 +}; +mmove_t tank_move_attack_post_blast = {FRAME_attak117, FRAME_attak122, tank_frames_attack_post_blast, tank_run}; + +void tank_reattack_blaster (edict_t *self) +{ + if (skill->value >= 2) + if (visible (self, self->enemy)) + if (self->enemy->health > 0) + if (random() <= 0.6) + { + self->monsterinfo.currentmove = &tank_move_reattack_blast; + return; + } + self->monsterinfo.currentmove = &tank_move_attack_post_blast; +} + + +void tank_poststrike (edict_t *self) +{ + self->enemy = NULL; + tank_run (self); +} + +mframe_t tank_frames_attack_strike [] = +{ + ai_move, 3, NULL, + ai_move, 2, NULL, + ai_move, 2, NULL, + ai_move, 1, NULL, + ai_move, 6, NULL, + ai_move, 7, NULL, + ai_move, 9, tank_footstep, + ai_move, 2, NULL, + ai_move, 1, NULL, + ai_move, 2, NULL, + ai_move, 2, tank_footstep, + ai_move, 2, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, -2, NULL, + ai_move, -2, NULL, + ai_move, 0, tank_windup, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, TankStrike, + ai_move, 0, NULL, + ai_move, -1, NULL, + ai_move, -1, NULL, + ai_move, -1, NULL, + ai_move, -1, NULL, + ai_move, -1, NULL, + ai_move, -3, NULL, + ai_move, -10, NULL, + ai_move, -10, NULL, + ai_move, -2, NULL, + ai_move, -3, NULL, + ai_move, -2, tank_footstep +}; +mmove_t tank_move_attack_strike = {FRAME_attak201, FRAME_attak238, tank_frames_attack_strike, tank_poststrike}; + +mframe_t tank_frames_attack_pre_rocket [] = +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, // 10 + + ai_charge, 0, NULL, + ai_charge, 1, NULL, + ai_charge, 2, NULL, + ai_charge, 7, NULL, + ai_charge, 7, NULL, + ai_charge, 7, tank_footstep, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, // 20 + + ai_charge, -3, NULL +}; +mmove_t tank_move_attack_pre_rocket = {FRAME_attak301, FRAME_attak321, tank_frames_attack_pre_rocket, tank_doattack_rocket}; + +mframe_t tank_frames_attack_fire_rocket [] = +{ + ai_charge, -3, NULL, // Loop Start 22 + ai_charge, 0, NULL, + ai_charge, 0, TankRocket, // 24 + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, TankRocket, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, -1, TankRocket // 30 Loop End +}; +mmove_t tank_move_attack_fire_rocket = {FRAME_attak322, FRAME_attak330, tank_frames_attack_fire_rocket, tank_refire_rocket}; + +mframe_t tank_frames_attack_post_rocket [] = +{ + ai_charge, 0, NULL, // 31 + ai_charge, -1, NULL, + ai_charge, -1, NULL, + ai_charge, 0, NULL, + ai_charge, 2, NULL, + ai_charge, 3, NULL, + ai_charge, 4, NULL, + ai_charge, 2, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, // 40 + + ai_charge, 0, NULL, + ai_charge, -9, NULL, + ai_charge, -8, NULL, + ai_charge, -7, NULL, + ai_charge, -1, NULL, + ai_charge, -1, tank_footstep, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, // 50 + + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL +}; +mmove_t tank_move_attack_post_rocket = {FRAME_attak331, FRAME_attak353, tank_frames_attack_post_rocket, tank_run}; + +mframe_t tank_frames_attack_chain [] = +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + NULL, 0, TankMachineGun, + NULL, 0, TankMachineGun, + NULL, 0, TankMachineGun, + NULL, 0, TankMachineGun, + NULL, 0, TankMachineGun, + NULL, 0, TankMachineGun, + NULL, 0, TankMachineGun, + NULL, 0, TankMachineGun, + NULL, 0, TankMachineGun, + NULL, 0, TankMachineGun, + NULL, 0, TankMachineGun, + NULL, 0, TankMachineGun, + NULL, 0, TankMachineGun, + NULL, 0, TankMachineGun, + NULL, 0, TankMachineGun, + NULL, 0, TankMachineGun, + NULL, 0, TankMachineGun, + NULL, 0, TankMachineGun, + NULL, 0, TankMachineGun, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL +}; +mmove_t tank_move_attack_chain = {FRAME_attak401, FRAME_attak429, tank_frames_attack_chain, tank_run}; + +void tank_refire_rocket (edict_t *self) +{ + // PMM - blindfire cleanup + if (self->monsterinfo.aiflags & AI_MANUAL_STEERING) + { + self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING; + self->monsterinfo.currentmove = &tank_move_attack_post_rocket; + return; + } + // pmm + + // Only on hard or nightmare + if ( skill->value >= 2 ) + if (self->enemy->health > 0) + if (visible(self, self->enemy) ) + if (random() <= 0.4) + { + self->monsterinfo.currentmove = &tank_move_attack_fire_rocket; + return; + } + self->monsterinfo.currentmove = &tank_move_attack_post_rocket; +} + +void tank_doattack_rocket (edict_t *self) +{ + self->monsterinfo.currentmove = &tank_move_attack_fire_rocket; +} + +void tank_attack(edict_t *self) +{ + vec3_t vec; + float range; + float r; + // PMM + float chance; + + // PMM + if (!self->enemy || !self->enemy->inuse) + return; + + if (self->enemy->health < 0) + { + self->monsterinfo.currentmove = &tank_move_attack_strike; + self->monsterinfo.aiflags &= ~AI_BRUTAL; + return; + } + + // PMM + if (self->monsterinfo.attack_state == AS_BLIND) + { + // setup shot probabilities + if (self->monsterinfo.blind_fire_delay < 1.0) + chance = 1.0; + else if (self->monsterinfo.blind_fire_delay < 7.5) + chance = 0.4; + else + chance = 0.1; + + r = random(); + + self->monsterinfo.blind_fire_delay += 3.2 + 2.0 + random()*3.0; + + // don't shoot at the origin + if (VectorCompare (self->monsterinfo.blind_fire_target, vec3_origin)) + return; + + // don't shoot if the dice say not to + if (r > chance) + { +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("blindfire - NO SHOT\n"); + return; + } + + // turn on manual steering to signal both manual steering and blindfire + self->monsterinfo.aiflags |= AI_MANUAL_STEERING; + self->monsterinfo.currentmove = &tank_move_attack_fire_rocket; + self->monsterinfo.attack_finished = level.time + 3.0 + 2*random(); + self->pain_debounce_time = level.time + 5.0; // no pain for a while + return; + } + // pmm + + VectorSubtract (self->enemy->s.origin, self->s.origin, vec); + range = VectorLength (vec); + + r = random(); + + if (range <= 125) + { + if (r < 0.4) + self->monsterinfo.currentmove = &tank_move_attack_chain; + else + self->monsterinfo.currentmove = &tank_move_attack_blast; + } + else if (range <= 250) + { + if (r < 0.5) + self->monsterinfo.currentmove = &tank_move_attack_chain; + else + self->monsterinfo.currentmove = &tank_move_attack_blast; + } + else + { + if (r < 0.33) + self->monsterinfo.currentmove = &tank_move_attack_chain; + else if (r < 0.66) + { + self->monsterinfo.currentmove = &tank_move_attack_pre_rocket; + self->pain_debounce_time = level.time + 5.0; // no pain for a while + } + else + self->monsterinfo.currentmove = &tank_move_attack_blast; + } +} + + +// +// death +// + +void tank_dead (edict_t *self) +{ + VectorSet (self->mins, -16, -16, -16); + VectorSet (self->maxs, 16, 16, -0); + self->movetype = MOVETYPE_TOSS; + self->svflags |= SVF_DEADMONSTER; + self->nextthink = 0; + gi.linkentity (self); +} + +mframe_t tank_frames_death1 [] = +{ + ai_move, -7, NULL, + ai_move, -2, NULL, + ai_move, -2, NULL, + ai_move, 1, NULL, + ai_move, 3, NULL, + ai_move, 6, NULL, + ai_move, 1, NULL, + ai_move, 1, NULL, + ai_move, 2, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, -2, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, -3, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, -4, NULL, + ai_move, -6, NULL, + ai_move, -4, NULL, + ai_move, -5, NULL, + ai_move, -7, NULL, + ai_move, -15, tank_thud, + ai_move, -5, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t tank_move_death = {FRAME_death101, FRAME_death132, tank_frames_death1, tank_dead}; + +void tank_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + int n; + +// check for gib + if (self->health <= self->gib_health) + { + gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0); + for (n= 0; n < 1 /*4*/; n++) + ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); + for (n= 0; n < 4; n++) + ThrowGib (self, "models/objects/gibs/sm_metal/tris.md2", damage, GIB_METALLIC); + ThrowGib (self, "models/objects/gibs/chest/tris.md2", damage, GIB_ORGANIC); + ThrowHead (self, "models/objects/gibs/gear/tris.md2", damage, GIB_METALLIC); + self->deadflag = DEAD_DEAD; + return; + } + + if (self->deadflag == DEAD_DEAD) + return; + +// regular death + gi.sound (self, CHAN_VOICE, sound_die, 1, ATTN_NORM, 0); + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_YES; + + self->monsterinfo.currentmove = &tank_move_death; + +} + +//=========== +//PGM +qboolean tank_blocked (edict_t *self, float dist) +{ + if(blocked_checkshot (self, 0.25 + (0.05 * skill->value) )) + return true; + + if(blocked_checkplat (self, dist)) + return true; + + return false; +} +//PGM +//=========== + +// +// monster_tank +// + +/*QUAKED monster_tank (1 .5 0) (-32 -32 -16) (32 32 72) Ambush Trigger_Spawn Sight +*/ +/*QUAKED monster_tank_commander (1 .5 0) (-32 -32 -16) (32 32 72) Ambush Trigger_Spawn Sight +*/ +void SP_monster_tank (edict_t *self) +{ + if (deathmatch->value) + { + G_FreeEdict (self); + return; + } + + self->s.modelindex = gi.modelindex ("models/monsters/tank/tris.md2"); + VectorSet (self->mins, -32, -32, -16); + VectorSet (self->maxs, 32, 32, 72); + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + + sound_pain = gi.soundindex ("tank/tnkpain2.wav"); + sound_thud = gi.soundindex ("tank/tnkdeth2.wav"); + sound_idle = gi.soundindex ("tank/tnkidle1.wav"); + sound_die = gi.soundindex ("tank/death.wav"); + sound_step = gi.soundindex ("tank/step.wav"); + sound_windup = gi.soundindex ("tank/tnkatck4.wav"); + sound_strike = gi.soundindex ("tank/tnkatck5.wav"); + sound_sight = gi.soundindex ("tank/sight1.wav"); + + gi.soundindex ("tank/tnkatck1.wav"); + gi.soundindex ("tank/tnkatk2a.wav"); + gi.soundindex ("tank/tnkatk2b.wav"); + gi.soundindex ("tank/tnkatk2c.wav"); + gi.soundindex ("tank/tnkatk2d.wav"); + gi.soundindex ("tank/tnkatk2e.wav"); + gi.soundindex ("tank/tnkatck3.wav"); + + if (strcmp(self->classname, "monster_tank_commander") == 0) + { + self->health = 1000; + self->gib_health = -225; + } + else + { + self->health = 750; + self->gib_health = -200; + } + + self->mass = 500; + + self->pain = tank_pain; + self->die = tank_die; + self->monsterinfo.stand = tank_stand; + self->monsterinfo.walk = tank_walk; + self->monsterinfo.run = tank_run; + self->monsterinfo.dodge = NULL; + self->monsterinfo.attack = tank_attack; + self->monsterinfo.melee = NULL; + self->monsterinfo.sight = tank_sight; + self->monsterinfo.idle = tank_idle; + self->monsterinfo.blocked = tank_blocked; // PGM + + gi.linkentity (self); + + self->monsterinfo.currentmove = &tank_move_stand; + self->monsterinfo.scale = MODEL_SCALE; + + walkmonster_start(self); + + // PMM + self->monsterinfo.aiflags |= AI_IGNORE_SHOTS; + self->monsterinfo.blindfire = true; + //pmm + if (strcmp(self->classname, "monster_tank_commander") == 0) + self->s.skinnum = 2; +} diff --git a/original/rogue/m_tank.h b/original/rogue/m_tank.h new file mode 100644 index 0000000..ea7bc07 --- /dev/null +++ b/original/rogue/m_tank.h @@ -0,0 +1,302 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// G:\quake2\baseq2\models/monsters/tank + +// This file generated by qdata - Do NOT Modify + +#define FRAME_stand01 0 +#define FRAME_stand02 1 +#define FRAME_stand03 2 +#define FRAME_stand04 3 +#define FRAME_stand05 4 +#define FRAME_stand06 5 +#define FRAME_stand07 6 +#define FRAME_stand08 7 +#define FRAME_stand09 8 +#define FRAME_stand10 9 +#define FRAME_stand11 10 +#define FRAME_stand12 11 +#define FRAME_stand13 12 +#define FRAME_stand14 13 +#define FRAME_stand15 14 +#define FRAME_stand16 15 +#define FRAME_stand17 16 +#define FRAME_stand18 17 +#define FRAME_stand19 18 +#define FRAME_stand20 19 +#define FRAME_stand21 20 +#define FRAME_stand22 21 +#define FRAME_stand23 22 +#define FRAME_stand24 23 +#define FRAME_stand25 24 +#define FRAME_stand26 25 +#define FRAME_stand27 26 +#define FRAME_stand28 27 +#define FRAME_stand29 28 +#define FRAME_stand30 29 +#define FRAME_walk01 30 +#define FRAME_walk02 31 +#define FRAME_walk03 32 +#define FRAME_walk04 33 +#define FRAME_walk05 34 +#define FRAME_walk06 35 +#define FRAME_walk07 36 +#define FRAME_walk08 37 +#define FRAME_walk09 38 +#define FRAME_walk10 39 +#define FRAME_walk11 40 +#define FRAME_walk12 41 +#define FRAME_walk13 42 +#define FRAME_walk14 43 +#define FRAME_walk15 44 +#define FRAME_walk16 45 +#define FRAME_walk17 46 +#define FRAME_walk18 47 +#define FRAME_walk19 48 +#define FRAME_walk20 49 +#define FRAME_walk21 50 +#define FRAME_walk22 51 +#define FRAME_walk23 52 +#define FRAME_walk24 53 +#define FRAME_walk25 54 +#define FRAME_attak101 55 +#define FRAME_attak102 56 +#define FRAME_attak103 57 +#define FRAME_attak104 58 +#define FRAME_attak105 59 +#define FRAME_attak106 60 +#define FRAME_attak107 61 +#define FRAME_attak108 62 +#define FRAME_attak109 63 +#define FRAME_attak110 64 +#define FRAME_attak111 65 +#define FRAME_attak112 66 +#define FRAME_attak113 67 +#define FRAME_attak114 68 +#define FRAME_attak115 69 +#define FRAME_attak116 70 +#define FRAME_attak117 71 +#define FRAME_attak118 72 +#define FRAME_attak119 73 +#define FRAME_attak120 74 +#define FRAME_attak121 75 +#define FRAME_attak122 76 +#define FRAME_attak201 77 +#define FRAME_attak202 78 +#define FRAME_attak203 79 +#define FRAME_attak204 80 +#define FRAME_attak205 81 +#define FRAME_attak206 82 +#define FRAME_attak207 83 +#define FRAME_attak208 84 +#define FRAME_attak209 85 +#define FRAME_attak210 86 +#define FRAME_attak211 87 +#define FRAME_attak212 88 +#define FRAME_attak213 89 +#define FRAME_attak214 90 +#define FRAME_attak215 91 +#define FRAME_attak216 92 +#define FRAME_attak217 93 +#define FRAME_attak218 94 +#define FRAME_attak219 95 +#define FRAME_attak220 96 +#define FRAME_attak221 97 +#define FRAME_attak222 98 +#define FRAME_attak223 99 +#define FRAME_attak224 100 +#define FRAME_attak225 101 +#define FRAME_attak226 102 +#define FRAME_attak227 103 +#define FRAME_attak228 104 +#define FRAME_attak229 105 +#define FRAME_attak230 106 +#define FRAME_attak231 107 +#define FRAME_attak232 108 +#define FRAME_attak233 109 +#define FRAME_attak234 110 +#define FRAME_attak235 111 +#define FRAME_attak236 112 +#define FRAME_attak237 113 +#define FRAME_attak238 114 +#define FRAME_attak301 115 +#define FRAME_attak302 116 +#define FRAME_attak303 117 +#define FRAME_attak304 118 +#define FRAME_attak305 119 +#define FRAME_attak306 120 +#define FRAME_attak307 121 +#define FRAME_attak308 122 +#define FRAME_attak309 123 +#define FRAME_attak310 124 +#define FRAME_attak311 125 +#define FRAME_attak312 126 +#define FRAME_attak313 127 +#define FRAME_attak314 128 +#define FRAME_attak315 129 +#define FRAME_attak316 130 +#define FRAME_attak317 131 +#define FRAME_attak318 132 +#define FRAME_attak319 133 +#define FRAME_attak320 134 +#define FRAME_attak321 135 +#define FRAME_attak322 136 +#define FRAME_attak323 137 +#define FRAME_attak324 138 +#define FRAME_attak325 139 +#define FRAME_attak326 140 +#define FRAME_attak327 141 +#define FRAME_attak328 142 +#define FRAME_attak329 143 +#define FRAME_attak330 144 +#define FRAME_attak331 145 +#define FRAME_attak332 146 +#define FRAME_attak333 147 +#define FRAME_attak334 148 +#define FRAME_attak335 149 +#define FRAME_attak336 150 +#define FRAME_attak337 151 +#define FRAME_attak338 152 +#define FRAME_attak339 153 +#define FRAME_attak340 154 +#define FRAME_attak341 155 +#define FRAME_attak342 156 +#define FRAME_attak343 157 +#define FRAME_attak344 158 +#define FRAME_attak345 159 +#define FRAME_attak346 160 +#define FRAME_attak347 161 +#define FRAME_attak348 162 +#define FRAME_attak349 163 +#define FRAME_attak350 164 +#define FRAME_attak351 165 +#define FRAME_attak352 166 +#define FRAME_attak353 167 +#define FRAME_attak401 168 +#define FRAME_attak402 169 +#define FRAME_attak403 170 +#define FRAME_attak404 171 +#define FRAME_attak405 172 +#define FRAME_attak406 173 +#define FRAME_attak407 174 +#define FRAME_attak408 175 +#define FRAME_attak409 176 +#define FRAME_attak410 177 +#define FRAME_attak411 178 +#define FRAME_attak412 179 +#define FRAME_attak413 180 +#define FRAME_attak414 181 +#define FRAME_attak415 182 +#define FRAME_attak416 183 +#define FRAME_attak417 184 +#define FRAME_attak418 185 +#define FRAME_attak419 186 +#define FRAME_attak420 187 +#define FRAME_attak421 188 +#define FRAME_attak422 189 +#define FRAME_attak423 190 +#define FRAME_attak424 191 +#define FRAME_attak425 192 +#define FRAME_attak426 193 +#define FRAME_attak427 194 +#define FRAME_attak428 195 +#define FRAME_attak429 196 +#define FRAME_pain101 197 +#define FRAME_pain102 198 +#define FRAME_pain103 199 +#define FRAME_pain104 200 +#define FRAME_pain201 201 +#define FRAME_pain202 202 +#define FRAME_pain203 203 +#define FRAME_pain204 204 +#define FRAME_pain205 205 +#define FRAME_pain301 206 +#define FRAME_pain302 207 +#define FRAME_pain303 208 +#define FRAME_pain304 209 +#define FRAME_pain305 210 +#define FRAME_pain306 211 +#define FRAME_pain307 212 +#define FRAME_pain308 213 +#define FRAME_pain309 214 +#define FRAME_pain310 215 +#define FRAME_pain311 216 +#define FRAME_pain312 217 +#define FRAME_pain313 218 +#define FRAME_pain314 219 +#define FRAME_pain315 220 +#define FRAME_pain316 221 +#define FRAME_death101 222 +#define FRAME_death102 223 +#define FRAME_death103 224 +#define FRAME_death104 225 +#define FRAME_death105 226 +#define FRAME_death106 227 +#define FRAME_death107 228 +#define FRAME_death108 229 +#define FRAME_death109 230 +#define FRAME_death110 231 +#define FRAME_death111 232 +#define FRAME_death112 233 +#define FRAME_death113 234 +#define FRAME_death114 235 +#define FRAME_death115 236 +#define FRAME_death116 237 +#define FRAME_death117 238 +#define FRAME_death118 239 +#define FRAME_death119 240 +#define FRAME_death120 241 +#define FRAME_death121 242 +#define FRAME_death122 243 +#define FRAME_death123 244 +#define FRAME_death124 245 +#define FRAME_death125 246 +#define FRAME_death126 247 +#define FRAME_death127 248 +#define FRAME_death128 249 +#define FRAME_death129 250 +#define FRAME_death130 251 +#define FRAME_death131 252 +#define FRAME_death132 253 +#define FRAME_recln101 254 +#define FRAME_recln102 255 +#define FRAME_recln103 256 +#define FRAME_recln104 257 +#define FRAME_recln105 258 +#define FRAME_recln106 259 +#define FRAME_recln107 260 +#define FRAME_recln108 261 +#define FRAME_recln109 262 +#define FRAME_recln110 263 +#define FRAME_recln111 264 +#define FRAME_recln112 265 +#define FRAME_recln113 266 +#define FRAME_recln114 267 +#define FRAME_recln115 268 +#define FRAME_recln116 269 +#define FRAME_recln117 270 +#define FRAME_recln118 271 +#define FRAME_recln119 272 +#define FRAME_recln120 273 +#define FRAME_recln121 274 +#define FRAME_recln122 275 +#define FRAME_recln123 276 +#define FRAME_recln124 277 +#define FRAME_recln125 278 +#define FRAME_recln126 279 +#define FRAME_recln127 280 +#define FRAME_recln128 281 +#define FRAME_recln129 282 +#define FRAME_recln130 283 +#define FRAME_recln131 284 +#define FRAME_recln132 285 +#define FRAME_recln133 286 +#define FRAME_recln134 287 +#define FRAME_recln135 288 +#define FRAME_recln136 289 +#define FRAME_recln137 290 +#define FRAME_recln138 291 +#define FRAME_recln139 292 +#define FRAME_recln140 293 + +#define MODEL_SCALE 1.000000 diff --git a/original/rogue/m_turret.c b/original/rogue/m_turret.c new file mode 100644 index 0000000..d534d48 --- /dev/null +++ b/original/rogue/m_turret.c @@ -0,0 +1,1040 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +/* +============================================================================== + +TURRET + +============================================================================== +*/ + +#include "g_local.h" +#include "m_turret.h" + +#define SPAWN_BLASTER 0x0008 +#define SPAWN_MACHINEGUN 0x0010 +#define SPAWN_ROCKET 0x0020 +#define SPAWN_HEATBEAM 0x0040 +#define SPAWN_WEAPONCHOICE 0x0078 +#define SPAWN_INSTANT_WEAPON 0x0050 +#define SPAWN_WALL_UNIT 0x0080 + +extern qboolean FindTarget (edict_t *self); + +void turret_run (edict_t *self); +void TurretAim (edict_t *self); +void turret_sight (edict_t *self, edict_t *other); +void turret_search (edict_t *self); +void turret_stand (edict_t *self); +void turret_wake (edict_t *self); +void turret_ready_gun (edict_t *self); +void turret_run (edict_t *self); + +void turret_attack (edict_t *self); +mmove_t turret_move_fire; +mmove_t turret_move_fire_blind; + + +void TurretAim(edict_t *self) +{ + vec3_t end, dir; + vec3_t ang; + float move, idealPitch, idealYaw, current, speed; + int orientation; + +// gi.dprintf("turret_aim: %d %d\n", self->s.frame, self->monsterinfo.nextframe); + + if(!self->enemy || self->enemy == world) + { + if(!FindTarget (self)) + return; + } + + // if turret is still in inactive mode, ready the gun, but don't aim + if(self->s.frame < FRAME_active01) + { + turret_ready_gun(self); + return; + } + // if turret is still readying, don't aim. + if(self->s.frame < FRAME_run01) + return; + + // PMM - blindfire aiming here + if (self->monsterinfo.currentmove == &turret_move_fire_blind) + { + VectorCopy(self->monsterinfo.blind_fire_target, end); + if (self->enemy->s.origin[2] < self->monsterinfo.blind_fire_target[2]) + end[2] += self->enemy->viewheight + 10; + else + end[2] += self->enemy->mins[2] - 10; + } + else + { + VectorCopy(self->enemy->s.origin, end); + if (self->enemy->client) + end[2] += self->enemy->viewheight; + } + + VectorSubtract(end, self->s.origin, dir); + vectoangles2(dir, ang); + + // + // Clamp first + // + + idealPitch = ang[PITCH]; + idealYaw = ang[YAW]; + + orientation = self->offset[1]; + switch(orientation) + { + case -1: // up pitch: 0 to 90 + if(idealPitch < -90) + idealPitch += 360; + if(idealPitch > -5) + idealPitch = -5; + break; + case -2: // down pitch: -180 to -360 + if(idealPitch > -90) + idealPitch -= 360; + if(idealPitch < -355) + idealPitch = -355; + else if(idealPitch > -185) + idealPitch = -185; + break; + case 0: // +X pitch: 0 to -90, -270 to -360 (or 0 to 90) +//gi.dprintf("idealpitch %0.1f idealyaw %0.1f\n", idealPitch, idealYaw); + if(idealPitch < -180) + idealPitch += 360; + + if(idealPitch > 85) + idealPitch = 85; + else if(idealPitch < -85) + idealPitch = -85; + +//gi.dprintf("idealpitch %0.1f idealyaw %0.1f\n", idealPitch, idealYaw); + // yaw: 270 to 360, 0 to 90 + // yaw: -90 to 90 (270-360 == -90-0) + if(idealYaw > 180) + idealYaw -= 360; + if(idealYaw > 85) + idealYaw = 85; + else if(idealYaw < -85) + idealYaw = -85; +//gi.dprintf("idealpitch %0.1f idealyaw %0.1f\n", idealPitch, idealYaw); + break; + case 90: // +Y pitch: 0 to 90, -270 to -360 (or 0 to 90) + if(idealPitch < -180) + idealPitch += 360; + + if(idealPitch > 85) + idealPitch = 85; + else if(idealPitch < -85) + idealPitch = -85; + + // yaw: 0 to 180 + if(idealYaw > 270) + idealYaw -= 360; + if(idealYaw > 175) idealYaw = 175; + else if(idealYaw < 5) idealYaw = 5; + + break; + case 180: // -X pitch: 0 to 90, -270 to -360 (or 0 to 90) + if(idealPitch < -180) + idealPitch += 360; + + if(idealPitch > 85) + idealPitch = 85; + else if(idealPitch < -85) + idealPitch = -85; + + // yaw: 90 to 270 + if(idealYaw > 265) idealYaw = 265; + else if(idealYaw < 95) idealYaw = 95; + + break; + case 270: // -Y pitch: 0 to 90, -270 to -360 (or 0 to 90) + if(idealPitch < -180) + idealPitch += 360; + + if(idealPitch > 85) + idealPitch = 85; + else if(idealPitch < -85) + idealPitch = -85; + + // yaw: 180 to 360 + if(idealYaw < 90) + idealYaw += 360; + if(idealYaw > 355) idealYaw = 355; + else if(idealYaw < 185) idealYaw = 185; + break; + } + + // + // adjust pitch + // + current = self->s.angles[PITCH]; + speed = self->yaw_speed; + + if(idealPitch != current) + { + move = idealPitch - current; + + while(move >= 360) + move -= 360; + if (move >= 90) + { + move = move - 360; + } + + while(move <= -360) + move += 360; + if (move <= -90) + { + move = move + 360; + } + + if (move > 0) + { + if (move > speed) + move = speed; + } + else + { + if (move < -speed) + move = -speed; + } + + self->s.angles[PITCH] = anglemod (current + move); + } + + // + // adjust yaw + // + current = self->s.angles[YAW]; + speed = self->yaw_speed; + + if(idealYaw != current) + { + move = idealYaw - current; + +// while(move >= 360) +// move -= 360; + if (move >= 180) + { + move = move - 360; + } + +// while(move <= -360) +// move += 360; + if (move <= -180) + { + move = move + 360; + } + + if (move > 0) + { + if (move > speed) + move = speed; + } + else + { + if (move < -speed) + move = -speed; + } + + self->s.angles[YAW] = anglemod (current + move); + } + +} + +void turret_sight (edict_t *self, edict_t *other) +{ +} + +void turret_search (edict_t *self) +{ +} + +mframe_t turret_frames_stand [] = +{ + ai_stand, 0, NULL, + ai_stand, 0, NULL +}; +mmove_t turret_move_stand = {FRAME_stand01, FRAME_stand02, turret_frames_stand, NULL}; + +void turret_stand (edict_t *self) +{ +//gi.dprintf("turret_stand\n"); + self->monsterinfo.currentmove = &turret_move_stand; +} + +mframe_t turret_frames_ready_gun [] = +{ + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + + ai_stand, 0, NULL +}; +mmove_t turret_move_ready_gun = { FRAME_active01, FRAME_run01, turret_frames_ready_gun, turret_run }; + +void turret_ready_gun (edict_t *self) +{ + self->monsterinfo.currentmove = &turret_move_ready_gun; +} + +mframe_t turret_frames_seek [] = +{ + ai_walk, 0, TurretAim, + ai_walk, 0, TurretAim +}; +mmove_t turret_move_seek = {FRAME_run01, FRAME_run02, turret_frames_seek, NULL}; + +void turret_walk (edict_t *self) +{ + if(self->s.frame < FRAME_run01) + turret_ready_gun(self); + else + self->monsterinfo.currentmove = &turret_move_seek; +} + + +mframe_t turret_frames_run [] = +{ + ai_run, 0, TurretAim, + ai_run, 0, TurretAim +}; +mmove_t turret_move_run = {FRAME_run01, FRAME_run02, turret_frames_run, turret_run}; + +void turret_run (edict_t *self) +{ + if(self->s.frame < FRAME_run01) + turret_ready_gun(self); + else + self->monsterinfo.currentmove = &turret_move_run; +} + +// ********************** +// ATTACK +// ********************** + +#define TURRET_BULLET_DAMAGE 4 +#define TURRET_HEAT_DAMAGE 4 + +void TurretFire (edict_t *self) +{ + vec3_t forward; + vec3_t start, end, dir; + float time, dist, chance; + trace_t trace; + int rocketSpeed; + + TurretAim(self); + + if(!self->enemy || !self->enemy->inuse) + return; + + VectorSubtract(self->enemy->s.origin, self->s.origin, dir); + VectorNormalize(dir); + AngleVectors(self->s.angles, forward, NULL, NULL); + chance = DotProduct(dir, forward); + if(chance < 0.98) + { +// gi.dprintf("off-angle\n"); + return; + } + + chance = random(); + + // rockets fire less often than the others do. + if (self->spawnflags & SPAWN_ROCKET) + { + chance = chance * 3; + + rocketSpeed = 550; + if (skill->value == 2) + { + rocketSpeed += 200 * random(); + } + else if (skill->value == 3) + { + rocketSpeed += 100 + (200 * random()); + } + } + else if (self->spawnflags & SPAWN_BLASTER) + { + if (skill->value == 0) + rocketSpeed = 600; + else if (skill->value == 1) + rocketSpeed = 800; + else + rocketSpeed = 1000; + chance = chance * 2; + } + + // up the fire chance 20% per skill level. + chance = chance - (0.2 * skill->value); + + if(/*chance < 0.5 && */visible(self, self->enemy)) + { + VectorCopy(self->s.origin, start); + VectorCopy(self->enemy->s.origin, end); + + // aim for the head. + if ((self->enemy) && (self->enemy->client)) + end[2]+=self->enemy->viewheight; + else + end[2]+=22; + + VectorSubtract(end, start, dir); + dist = VectorLength(dir); + + // check for predictive fire if distance less than 512 + if(!(self->spawnflags & SPAWN_INSTANT_WEAPON) && (dist<512)) + { + chance = random(); + // ramp chance. easy - 50%, avg - 60%, hard - 70%, nightmare - 80% + chance += (3 - skill->value) * 0.1; + if(chance < 0.8) + { + // lead the target.... + time = dist / 1000; + VectorMA(end, time, self->enemy->velocity, end); + VectorSubtract(end, start, dir); + } + } + + VectorNormalize(dir); + trace = gi.trace(start, vec3_origin, vec3_origin, end, self, MASK_SHOT); + if(trace.ent == self->enemy || trace.ent == world) + { + if(self->spawnflags & SPAWN_BLASTER) + monster_fire_blaster(self, start, dir, 20, rocketSpeed, MZ2_TURRET_BLASTER, EF_BLASTER); + else if(self->spawnflags & SPAWN_MACHINEGUN) + monster_fire_bullet (self, start, dir, TURRET_BULLET_DAMAGE, 0, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, MZ2_TURRET_MACHINEGUN); + else if(self->spawnflags & SPAWN_ROCKET) + { + if(dist * trace.fraction > 72) + monster_fire_rocket (self, start, dir, 50, rocketSpeed, MZ2_TURRET_ROCKET); + } + } + } +} + +// PMM +void TurretFireBlind (edict_t *self) +{ + vec3_t forward; + vec3_t start, end, dir; + float dist, chance; + int rocketSpeed; + + TurretAim(self); + + if(!self->enemy || !self->enemy->inuse) + return; + + VectorSubtract(self->monsterinfo.blind_fire_target, self->s.origin, dir); + VectorNormalize(dir); + AngleVectors(self->s.angles, forward, NULL, NULL); + chance = DotProduct(dir, forward); + if(chance < 0.98) + { +// gi.dprintf("off-angle\n"); + return; + } + + if (self->spawnflags & SPAWN_ROCKET) + { + rocketSpeed = 550; + if (skill->value == 2) + { + rocketSpeed += 200 * random(); + } + else if (skill->value == 3) + { + rocketSpeed += 100 + (200 * random()); + } + } + + VectorCopy(self->s.origin, start); + VectorCopy(self->monsterinfo.blind_fire_target, end); + + if (self->enemy->s.origin[2] < self->monsterinfo.blind_fire_target[2]) + end[2] += self->enemy->viewheight + 10; + else + end[2] += self->enemy->mins[2] - 10; + + VectorSubtract(end, start, dir); + dist = VectorLength(dir); + + VectorNormalize(dir); + + if(self->spawnflags & SPAWN_BLASTER) + monster_fire_blaster(self, start, dir, 20, 1000, MZ2_TURRET_BLASTER, EF_BLASTER); + else if(self->spawnflags & SPAWN_ROCKET) + monster_fire_rocket (self, start, dir, 50, rocketSpeed, MZ2_TURRET_ROCKET); +} +//pmm + +mframe_t turret_frames_fire [] = +{ + ai_run, 0, TurretFire, + ai_run, 0, TurretAim, + ai_run, 0, TurretAim, + ai_run, 0, TurretAim +}; +mmove_t turret_move_fire = {FRAME_pow01, FRAME_pow04, turret_frames_fire, turret_run}; + +//PMM + +// the blind frames need to aim first +mframe_t turret_frames_fire_blind [] = +{ + ai_run, 0, TurretAim, + ai_run, 0, TurretAim, + ai_run, 0, TurretAim, + ai_run, 0, TurretFireBlind +}; +mmove_t turret_move_fire_blind = {FRAME_pow01, FRAME_pow04, turret_frames_fire_blind, turret_run}; +//pmm + +void turret_attack(edict_t *self) +{ + float r, chance; + + if(self->s.frame < FRAME_run01) + turret_ready_gun(self); + // PMM + else if (self->monsterinfo.attack_state != AS_BLIND) + { + self->monsterinfo.nextframe = FRAME_pow01; + self->monsterinfo.currentmove = &turret_move_fire; + } + else + { + // setup shot probabilities + if (self->monsterinfo.blind_fire_delay < 1.0) + chance = 1.0; + else if (self->monsterinfo.blind_fire_delay < 7.5) + chance = 0.4; + else + chance = 0.1; + + r = random(); + + // minimum of 3 seconds, plus 0-4, after the shots are done - total time should be max less than 7.5 + self->monsterinfo.blind_fire_delay += 0.4 + 3.0 + random()*4.0; + // don't shoot at the origin + if (VectorCompare (self->monsterinfo.blind_fire_target, vec3_origin)) + return; + + // don't shoot if the dice say not to + if (r > chance) + return; + + self->monsterinfo.nextframe = FRAME_pow01; + self->monsterinfo.currentmove = &turret_move_fire_blind; + } + // pmm +} + +// ********************** +// PAIN +// ********************** + +void turret_pain (edict_t *self, edict_t *other, float kick, int damage) +{ + return; +} + +// ********************** +// DEATH +// ********************** + +void turret_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + vec3_t forward; + vec3_t start; + edict_t *base; + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_PLAIN_EXPLOSION); + gi.WritePosition (self->s.origin); + gi.multicast (self->s.origin, MULTICAST_PHS); + + AngleVectors(self->s.angles, forward, NULL, NULL); + VectorMA(self->s.origin, 1, forward, start); + + ThrowDebris (self, "models/objects/debris1/tris.md2", 1, start); + ThrowDebris (self, "models/objects/debris1/tris.md2", 2, start); + ThrowDebris (self, "models/objects/debris1/tris.md2", 1, start); + ThrowDebris (self, "models/objects/debris1/tris.md2", 2, start); + + if(self->teamchain) + { + base = self->teamchain; + base->solid = SOLID_BBOX; + base->takedamage = DAMAGE_NO; + base->movetype = MOVETYPE_NONE; + gi.linkentity (base); + } + + if(self->target) + { + if(self->enemy && self->enemy->inuse) + G_UseTargets (self, self->enemy); + else + G_UseTargets (self, self); + } + + G_FreeEdict(self); +} + +// ********************** +// WALL SPAWN +// ********************** + +void turret_wall_spawn (edict_t *turret) +{ + edict_t *ent; + int angle; + + ent = G_Spawn(); + VectorCopy(turret->s.origin, ent->s.origin); + VectorCopy(turret->s.angles, ent->s.angles); + + angle = ent->s.angles[1]; + if(ent->s.angles[0] == 90) + angle = -1; + else if(ent->s.angles[0] == 270) + angle = -2; + switch (angle) + { + case -1: + VectorSet(ent->mins, -16, -16, -8); + VectorSet(ent->maxs, 16, 16, 0); + break; + case -2: + VectorSet(ent->mins, -16, -16, 0); + VectorSet(ent->maxs, 16, 16, 8); + break; + case 0: + VectorSet(ent->mins, -8, -16, -16); + VectorSet(ent->maxs, 0, 16, 16); + break; + case 90: + VectorSet(ent->mins, -16, -8, -16); + VectorSet(ent->maxs, 16, 0, 16); + break; + case 180: + VectorSet(ent->mins, 0, -16, -16); + VectorSet(ent->maxs, 8, 16, 16); + break; + case 270: + VectorSet(ent->mins, -16, 0, -16); + VectorSet(ent->maxs, 16, 8, 16); + break; + + } + + ent->movetype = MOVETYPE_PUSH; + ent->solid = SOLID_NOT; + + ent->teammaster = turret; + turret->teammaster = turret; + turret->teamchain = ent; + ent->teamchain = NULL; + ent->flags |= FL_TEAMSLAVE; + ent->owner = turret; + + ent->s.modelindex = gi.modelindex("models/monsters/turretbase/tris.md2"); + + gi.linkentity (ent); +} + +void turret_wake (edict_t *self) +{ + // the wall section will call this when it stops moving. + // just return without doing anything. easiest way to have a null function. + if(self->flags & FL_TEAMSLAVE) + { + return; + } + + self->monsterinfo.stand = turret_stand; + self->monsterinfo.walk = turret_walk; + self->monsterinfo.run = turret_run; + self->monsterinfo.dodge = NULL; + self->monsterinfo.attack = turret_attack; + self->monsterinfo.melee = NULL; + self->monsterinfo.sight = turret_sight; + self->monsterinfo.search = turret_search; + self->monsterinfo.currentmove = &turret_move_stand; + self->takedamage = DAMAGE_AIM; + self->movetype = MOVETYPE_NONE; + // prevent counting twice + self->monsterinfo.aiflags |= AI_DO_NOT_COUNT; + + gi.linkentity (self); + + stationarymonster_start (self); + + if(self->spawnflags & SPAWN_MACHINEGUN) + { + self->s.skinnum = 1; + } + else if(self->spawnflags & SPAWN_ROCKET) + { + self->s.skinnum = 2; + } + + // but we do want the death to count + self->monsterinfo.aiflags &= ~AI_DO_NOT_COUNT; +} + +extern void Move_Calc (edict_t *ent, vec3_t dest, void(*func)(edict_t*)); + +void turret_activate (edict_t *self, edict_t *other, edict_t *activator) +{ + vec3_t endpos; + vec3_t forward; + edict_t *base; + + self->movetype = MOVETYPE_PUSH; + if(!self->speed) + self->speed = 15; + self->moveinfo.speed = self->speed; + self->moveinfo.accel = self->speed; + self->moveinfo.decel = self->speed; + + if(self->s.angles[0] == 270) + { + VectorSet (forward, 0,0,1); + } + else if(self->s.angles[0] == 90) + { + VectorSet (forward, 0,0,-1); + } + else if(self->s.angles[1] == 0) + { + VectorSet (forward, 1,0,0); + } + else if(self->s.angles[1] == 90) + { + VectorSet (forward, 0,1,0); + } + else if(self->s.angles[1] == 180) + { + VectorSet (forward, -1,0,0); + } + else if(self->s.angles[1] == 270) + { + VectorSet (forward, 0,-1,0); + } + + // start up the turret + VectorMA(self->s.origin, 32, forward, endpos); + Move_Calc(self, endpos, turret_wake); + + base = self->teamchain; + if(base) + { + base->movetype = MOVETYPE_PUSH; + base->speed = self->speed; + base->moveinfo.speed = base->speed; + base->moveinfo.accel = base->speed; + base->moveinfo.decel = base->speed; + + // start up the wall section + VectorMA(self->teamchain->s.origin, 32, forward, endpos); + Move_Calc(self->teamchain, endpos, turret_wake); + } + + gi.sound (self, CHAN_VOICE, gi.soundindex ("world/dr_short.wav"), 1, ATTN_NORM, 0); +} + +// PMM +// checkattack .. ignore range, just attack if available +qboolean turret_checkattack (edict_t *self) +{ + vec3_t spot1, spot2; + float chance, nexttime; + trace_t tr; + int enemy_range; + + if (self->enemy->health > 0) + { + // see if any entities are in the way of the shot + VectorCopy (self->s.origin, spot1); + spot1[2] += self->viewheight; + VectorCopy (self->enemy->s.origin, spot2); + spot2[2] += self->enemy->viewheight; + + tr = gi.trace (spot1, NULL, NULL, spot2, self, CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_SLIME|CONTENTS_LAVA|CONTENTS_WINDOW); + + // do we have a clear shot? + if (tr.ent != self->enemy) + { + // PGM - we want them to go ahead and shoot at info_notnulls if they can. + if(self->enemy->solid != SOLID_NOT || tr.fraction < 1.0) //PGM + { + // PMM - if we can't see our target, and we're not blocked by a monster, go into blind fire if available + if ((!(tr.ent->svflags & SVF_MONSTER)) && (!visible(self, self->enemy))) + { + if ((self->monsterinfo.blindfire) && (self->monsterinfo.blind_fire_delay <= 10.0)) + { + if (level.time < self->monsterinfo.attack_finished) + { + return false; + } + if (level.time < (self->monsterinfo.trail_time + self->monsterinfo.blind_fire_delay)) + { + // wait for our time + return false; + } + else + { + // make sure we're not going to shoot something we don't want to shoot + tr = gi.trace (spot1, NULL, NULL, self->monsterinfo.blind_fire_target, self, CONTENTS_MONSTER); + if (tr.allsolid || tr.startsolid || ((tr.fraction < 1.0) && (tr.ent != self->enemy))) + { + return false; + } + + self->monsterinfo.attack_state = AS_BLIND; + self->monsterinfo.attack_finished = level.time + 0.5 + 2*random(); + return true; + } + } + } + // pmm + return false; + } + } + } + + if (level.time < self->monsterinfo.attack_finished) + return false; + + enemy_range = range(self, self->enemy); + + if (enemy_range == RANGE_MELEE) + { + // don't always melee in easy mode + if (skill->value == 0 && (rand()&3) ) + return false; + self->monsterinfo.attack_state = AS_MISSILE; + return true; + } + + if (self->spawnflags & SPAWN_ROCKET) + { + chance = 0.10; + nexttime = (1.8 - (0.2 * skill->value)); + } + else if(self->spawnflags & SPAWN_BLASTER) + { + chance = 0.35; + nexttime = (1.2 - (0.2 * skill->value)); + } + else + { + chance = 0.50; + nexttime = (0.8 - (0.1 * skill->value)); + } + + if (skill->value == 0) + chance *= 0.5; + else if (skill->value > 1) + chance *= 2; + + // PGM - go ahead and shoot every time if it's a info_notnull + // PMM - added visibility check + if ( ((random () < chance) && (visible(self, self->enemy))) || (self->enemy->solid == SOLID_NOT)) + { + self->monsterinfo.attack_state = AS_MISSILE; +// self->monsterinfo.attack_finished = level.time + 0.3 + 2*random(); + self->monsterinfo.attack_finished = level.time + nexttime; + return true; + } + + self->monsterinfo.attack_state = AS_STRAIGHT; + + return false; +} + + +// ********************** +// SPAWN +// ********************** + +/*QUAKED monster_turret (1 .5 0) (-16 -16 -16) (16 16 16) Ambush Trigger_Spawn Sight Blaster MachineGun Rocket Heatbeam WallUnit + +The automated defense turret that mounts on walls. +Check the weapon you want it to use: blaster, machinegun, rocket, heatbeam. +Default weapon is blaster. +When activated, wall units move 32 units in the direction they're facing. +*/ +void SP_monster_turret (edict_t *self) +{ + int angle; + + if (deathmatch->value) + { + G_FreeEdict (self); + return; + } + + // VERSIONING +// if (g_showlogic && g_showlogic->value) +// gi.dprintf ("%s\n", ROGUE_VERSION_STRING); + +// self->plat2flags = ROGUE_VERSION_ID; + // versions + + // pre-caches + gi.soundindex ("world/dr_short.wav"); + gi.modelindex ("models/objects/debris1/tris.md2"); + + self->s.modelindex = gi.modelindex("models/monsters/turret/tris.md2"); + + VectorSet (self->mins, -12, -12, -12); + VectorSet (self->maxs, 12, 12, 12); + self->movetype = MOVETYPE_NONE; + self->solid = SOLID_BBOX; + + self->health = 240; + self->gib_health = -100; + self->mass = 250; + self->yaw_speed = 45; + + self->flags |= FL_MECHANICAL; + + self->pain = turret_pain; + self->die = turret_die; + + // map designer didn't specify weapon type. set it now. + if(!(self->spawnflags & SPAWN_WEAPONCHOICE)) + { + self->spawnflags |= SPAWN_BLASTER; +// self->spawnflags |= SPAWN_MACHINEGUN; +// self->spawnflags |= SPAWN_ROCKET; +// self->spawnflags |= SPAWN_HEATBEAM; + } + + if(self->spawnflags & SPAWN_HEATBEAM) + { + self->spawnflags &= ~SPAWN_HEATBEAM; + self->spawnflags |= SPAWN_BLASTER; + } + + if(!(self->spawnflags & SPAWN_WALL_UNIT)) + { + self->monsterinfo.stand = turret_stand; + self->monsterinfo.walk = turret_walk; + self->monsterinfo.run = turret_run; + self->monsterinfo.dodge = NULL; + self->monsterinfo.attack = turret_attack; + self->monsterinfo.melee = NULL; + self->monsterinfo.sight = turret_sight; + self->monsterinfo.search = turret_search; + self->monsterinfo.currentmove = &turret_move_stand; + } + + // PMM + self->monsterinfo.checkattack = turret_checkattack; + + self->monsterinfo.aiflags |= AI_MANUAL_STEERING; + self->monsterinfo.scale = MODEL_SCALE; + self->gravity = 0; + + VectorCopy(self->s.angles, self->offset); + angle=(int)self->s.angles[1]; + switch(angle) + { + case -1: // up + self->s.angles[0] = 270; + self->s.angles[1] = 0; + self->s.origin[2] += 2; + break; + case -2: // down + self->s.angles[0] = 90; + self->s.angles[1] = 0; + self->s.origin[2] -= 2; + break; + case 0: + self->s.origin[0] += 2; + break; + case 90: + self->s.origin[1] += 2; + break; + case 180: + self->s.origin[0] -= 2; + break; + case 270: + self->s.origin[1] -= 2; + break; + default: + break; + } + + gi.linkentity (self); + + + if(self->spawnflags & SPAWN_WALL_UNIT) + { + if(!self->targetname) + { +// gi.dprintf("Wall Unit Turret without targetname! %s\n", vtos(self->s.origin)); + G_FreeEdict(self); + return; + } + + self->takedamage = DAMAGE_NO; + self->use = turret_activate; + turret_wall_spawn(self); + if ((!(self->monsterinfo.aiflags & AI_GOOD_GUY)) && (!(self->monsterinfo.aiflags & AI_DO_NOT_COUNT))) + level.total_monsters++; + + } + else + { + stationarymonster_start (self); + } + + if(self->spawnflags & SPAWN_MACHINEGUN) + { + gi.soundindex ("infantry/infatck1.wav"); + self->s.skinnum = 1; + } + else if(self->spawnflags & SPAWN_ROCKET) + { + gi.soundindex ("weapons/rockfly.wav"); + gi.modelindex ("models/objects/rocket/tris.md2"); + gi.soundindex ("chick/chkatck2.wav"); + self->s.skinnum = 2; + } + else + { + if (!(self->spawnflags & SPAWN_BLASTER)) + { + self->spawnflags |= SPAWN_BLASTER; + } + gi.modelindex ("models/objects/laser/tris.md2"); + gi.soundindex ("misc/lasfly.wav"); + gi.soundindex ("soldier/solatck2.wav"); + } + + // PMM - turrets don't get mad at monsters, and visa versa + self->monsterinfo.aiflags |= AI_IGNORE_SHOTS; + // PMM - blindfire + if(self->spawnflags & (SPAWN_ROCKET|SPAWN_BLASTER)) + self->monsterinfo.blindfire = true; +} diff --git a/original/rogue/m_turret.h b/original/rogue/m_turret.h new file mode 100644 index 0000000..94a18b9 --- /dev/null +++ b/original/rogue/m_turret.h @@ -0,0 +1,24 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// G:\quake2\xpack\models/monsters/turret + +// This file generated by qdata - Do NOT Modify + +#define FRAME_stand01 0 +#define FRAME_stand02 1 +#define FRAME_active01 2 +#define FRAME_active02 3 +#define FRAME_active03 4 +#define FRAME_active04 5 +#define FRAME_active05 6 +#define FRAME_active06 7 +#define FRAME_run01 8 +#define FRAME_run02 9 +#define FRAME_pow01 10 +#define FRAME_pow02 11 +#define FRAME_pow03 12 +#define FRAME_pow04 13 +#define FRAME_death01 14 +#define FRAME_death02 15 + +#define MODEL_SCALE 3.500000 diff --git a/original/rogue/m_widow.c b/original/rogue/m_widow.c new file mode 100644 index 0000000..46e319d --- /dev/null +++ b/original/rogue/m_widow.c @@ -0,0 +1,1716 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +/* +============================================================================== + +black widow + +============================================================================== +*/ + +// self->timestamp used to prevent rapid fire of railgun +// self->plat2flags used for fire count (flashes) +// self->monsterinfo.pausetime used for timing of blaster shots + +#include "g_local.h" +#include "m_widow.h" + +#define NUM_STALKERS_SPAWNED 6 // max # of stalkers she can spawn + +#define RAIL_TIME 3 +#define BLASTER_TIME 2 +#define BLASTER2_DAMAGE 10 +#define WIDOW_RAIL_DAMAGE 50 + +#define DRAWBBOX NULL +#define SHOWME NULL // showme + +void BossExplode (edict_t *self); + +qboolean infront (edict_t *self, edict_t *other); + +static int sound_pain1; +static int sound_pain2; +static int sound_pain3; +static int sound_search1; +static int sound_rail; +static int sound_sight; + +static unsigned long shotsfired; + +static vec3_t spawnpoints[] = { + {30, 100, 16}, + {30, -100, 16} +}; + +static vec3_t beameffects[] = { + {12.58, -43.71, 68.88}, + {3.43, 58.72, 68.41} +}; + +static float sweep_angles[] = { +// 32.0, 26.0, 20.0, 11.5, 3.0, -8.0, -13.0, -27.0, -41.0 + 32.0, 26.0, 20.0, 10.0, 0.0, -6.5, -13.0, -27.0, -41.0 +}; + +vec3_t stalker_mins = {-28, -28, -18}; +vec3_t stalker_maxs = {28, 28, 18}; + +unsigned int widow_damage_multiplier; + +void widow_run (edict_t *self); +void widow_stand (edict_t *self); +void widow_dead (edict_t *self); +void widow_attack (edict_t *self); +void widow_attack_blaster (edict_t *self); +void widow_reattack_blaster (edict_t *self); +void widow_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point); + +void widow_start_spawn (edict_t *self); +void widow_done_spawn (edict_t *self); +void widow_spawn_check (edict_t *self); +void widow_prep_spawn (edict_t *self); +void widow_attack_rail (edict_t *self); + +void widow_start_run_5 (edict_t *self); +void widow_start_run_10 (edict_t *self); +void widow_start_run_12 (edict_t *self); + +void WidowCalcSlots (edict_t *self); + +void drawbbox (edict_t *self); + +void showme (edict_t *self) +{ + gi.dprintf ("frame %d\n", self->s.frame); +} + +void widow_search (edict_t *self) +{ +// if (random() < 0.5) +// gi.sound (self, CHAN_VOICE, sound_search1, 1, ATTN_NONE, 0); +} + +void widow_sight (edict_t *self, edict_t *other) +{ + self->monsterinfo.pausetime = 0; +// gi.sound (self, CHAN_WEAPON, sound_sight, 1, ATTN_NORM, 0); + +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("widow: found target!\n"); +} + +mmove_t widow_move_attack_post_blaster; +mmove_t widow_move_attack_post_blaster_r; +mmove_t widow_move_attack_post_blaster_l; +mmove_t widow_move_attack_blaster; + +float target_angle (edict_t *self) +{ + vec3_t target; + float enemy_yaw; + + VectorSubtract (self->s.origin, self->enemy->s.origin, target); + enemy_yaw = self->s.angles[YAW] - vectoyaw2(target); + if (enemy_yaw < 0) + enemy_yaw += 360.0; + + // this gets me 0 degrees = forward + enemy_yaw -= 180.0; + // positive is to right, negative to left + + return enemy_yaw; +} + +int WidowTorso (edict_t *self) +{ + float enemy_yaw; + + enemy_yaw = target_angle (self); + +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("%2.2f -> ", enemy_yaw); + + if (enemy_yaw >= 105) + { + self->monsterinfo.currentmove = &widow_move_attack_post_blaster_r; + self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING; + return 0; + } + + if (enemy_yaw <= -75.0) + { + self->monsterinfo.currentmove = &widow_move_attack_post_blaster_l; + self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING; + return 0; + } + + if (enemy_yaw >= 95) + return FRAME_fired03; + else if (enemy_yaw >= 85) + return FRAME_fired04; + else if (enemy_yaw >= 75) + return FRAME_fired05; + else if (enemy_yaw >= 65) + return FRAME_fired06; + else if (enemy_yaw >= 55) + return FRAME_fired07; + else if (enemy_yaw >= 45) + return FRAME_fired08; + else if (enemy_yaw >= 35) + return FRAME_fired09; + else if (enemy_yaw >= 25) + return FRAME_fired10; + else if (enemy_yaw >= 15) + return FRAME_fired11; + else if (enemy_yaw >= 5) + return FRAME_fired12; + else if (enemy_yaw >= -5) + return FRAME_fired13; + else if (enemy_yaw >= -15) + return FRAME_fired14; + else if (enemy_yaw >= -25) + return FRAME_fired15; + else if (enemy_yaw >= -35) + return FRAME_fired16; + else if (enemy_yaw >= -45) + return FRAME_fired17; + else if (enemy_yaw >= -55) + return FRAME_fired18; + else if (enemy_yaw >= -65) + return FRAME_fired19; + else if (enemy_yaw >= -75) + return FRAME_fired20; +/* + if (fabs(enemy_yaw) < 11.25) + return FRAME_fired03; + else if (fabs(enemy_yaw) > 56.25) + { + self->monsterinfo.currentmove = &widow_move_attack_post_blaster; + self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING; + return; + } + else if ((enemy_yaw >= 11.25) && (enemy_yaw < 33.75)) + return FRAME_fired04; + else if (enemy_yaw >= 33.75) + return FRAME_fired05; + else if ((enemy_yaw <= -11.25) && (enemy_yaw > -33.75)) + return FRAME_fired06; + else if (enemy_yaw <= -33.75) + return FRAME_fired07; +*/ +} + +#define VARIANCE 15.0 + +void WidowBlaster (edict_t *self) +{ + vec3_t forward, right, target, vec, targ_angles; + vec3_t start; + int flashnum; + int effect; + + if (!self->enemy) + return; + + shotsfired++; + if (!(shotsfired % 4)) + effect = EF_BLASTER; + else + effect = 0; + + AngleVectors (self->s.angles, forward, right, NULL); + if ((self->s.frame >= FRAME_spawn05) && (self->s.frame <= FRAME_spawn13)) + { + // sweep + flashnum = MZ2_WIDOW_BLASTER_SWEEP1 + self->s.frame - FRAME_spawn05; + G_ProjectSource (self->s.origin, monster_flash_offset[flashnum], forward, right, start); + VectorSubtract (self->enemy->s.origin, start, target); + vectoangles2 (target, targ_angles); + + VectorCopy (self->s.angles, vec); + + vec[PITCH] += targ_angles[PITCH]; + vec[YAW] -= sweep_angles[flashnum-MZ2_WIDOW_BLASTER_SWEEP1]; + + AngleVectors (vec, forward, NULL, NULL); + monster_fire_blaster2 (self, start, forward, BLASTER2_DAMAGE*widow_damage_multiplier, 1000, flashnum, effect); + +/* if (self->s.frame == FRAME_spawn13) + { + VectorMA (start, 1024, forward, debugend); + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_DEBUGTRAIL); + gi.WritePosition (start); + gi.WritePosition (debugend); + gi.multicast (start, MULTICAST_ALL); + + drawbbox (self); + self->monsterinfo.aiflags |= AI_HOLD_FRAME|AI_MANUAL_STEERING; + } +*/ + } + else if ((self->s.frame >= FRAME_fired02a) && (self->s.frame <= FRAME_fired20)) + { + vec3_t angles; + float aim_angle, target_angle; + float error; + + self->monsterinfo.aiflags |= AI_MANUAL_STEERING; + + self->monsterinfo.nextframe = WidowTorso (self); + + if (!self->monsterinfo.nextframe) + self->monsterinfo.nextframe = self->s.frame; + +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("%d\n", self->monsterinfo.nextframe); + + if (self->s.frame == FRAME_fired02a) + flashnum = MZ2_WIDOW_BLASTER_0; + else + flashnum = MZ2_WIDOW_BLASTER_100 + self->s.frame - FRAME_fired03; + + G_ProjectSource (self->s.origin, monster_flash_offset[flashnum], forward, right, start); + + PredictAim (self->enemy, start, 1000, true, ((random()*0.1)-0.05), forward, NULL); + + // clamp it to within 10 degrees of the aiming angle (where she's facing) + vectoangles2 (forward, angles); + // give me 100 -> -70 + aim_angle = 100 - (10*(flashnum-MZ2_WIDOW_BLASTER_100)); + if (aim_angle <= 0) + aim_angle += 360; + target_angle = self->s.angles[YAW] - angles[YAW]; + if (target_angle <= 0) + target_angle += 360; + + error = aim_angle - target_angle; + + // positive error is to entity's left, aka positive direction in engine + // unfortunately, I decided that for the aim_angle, positive was right. *sigh* + if (error > VARIANCE) + { +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("angle %2.2f (really %2.2f) (%2.2f off of %2.2f) corrected to", target_angle, angles[YAW], error, aim_angle); + angles[YAW] = (self->s.angles[YAW] - aim_angle) + VARIANCE; +// if ((g_showlogic) && (g_showlogic->value)) +// { +// if (angles[YAW] <= 0) +// angles[YAW] += 360; +// gi.dprintf (" %2.2f\n", angles[YAW]); +// } + AngleVectors (angles, forward, NULL, NULL); + } + else if (error < -VARIANCE) + { +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("angle %2.2f (really %2.2f) (%2.2f off of %2.2f) corrected to", target_angle, angles[YAW], error, aim_angle); + angles[YAW] = (self->s.angles[YAW] - aim_angle) - VARIANCE; +// if ((g_showlogic) && (g_showlogic->value)) +// { +// if (angles[YAW] <= 0) +// angles[YAW] += 360; +// gi.dprintf (" %2.2f\n", angles[YAW]); +// } + AngleVectors (angles, forward, NULL, NULL); + } +// gi.dprintf ("%2.2f - %2.2f - %2.2f - %2.2f\n", aim_angle, self->s.angles[YAW] - angles[YAW], target_angle, error); +// gi.dprintf ("%2.2f - %2.2f - %2.2f\n", angles[YAW], aim_angle, self->s.angles[YAW]); + +/* + if (self->s.frame == FRAME_fired20) + { + VectorMA (start, 512, forward, debugend); + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_DEBUGTRAIL); + gi.WritePosition (start); + gi.WritePosition (forward); + gi.multicast (start, MULTICAST_ALL); + + drawbbox (self); + self->monsterinfo.aiflags |= AI_HOLD_FRAME; + self->monsterinfo.nextframe = FRAME_fired20; + self->monsterinfo.aiflags |= AI_MANUAL_STEERING; + } +*/ +/* + if (!(self->plat2flags % 3)) + effect = EF_HYPERBLASTER; + else + effect = 0; + self->plat2flags ++; +*/ + monster_fire_blaster2 (self, start, forward, BLASTER2_DAMAGE*widow_damage_multiplier, 1000, flashnum, effect); + } + else if ((self->s.frame >= FRAME_run01) && (self->s.frame <= FRAME_run08)) + { + flashnum = MZ2_WIDOW_RUN_1 + self->s.frame - FRAME_run01; + G_ProjectSource (self->s.origin, monster_flash_offset[flashnum], forward, right, start); + + VectorSubtract (self->enemy->s.origin, start, target); + target[2] += self->enemy->viewheight; + + monster_fire_blaster2 (self, start, target, BLASTER2_DAMAGE*widow_damage_multiplier, 1000, flashnum, effect); + } +// else +// { +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("widow: firing on non-fire frame!\n"); +// } +} + +void WidowSpawn (edict_t *self) +{ + vec3_t f, r, u, offset, startpoint, spawnpoint; + edict_t *ent, *designated_enemy; + int i; + + AngleVectors (self->s.angles, f, r, u); + + for (i=0; i < 2; i++) + { + VectorCopy (spawnpoints[i], offset); + + G_ProjectSource2 (self->s.origin, offset, f, r, u, startpoint); + + if (FindSpawnPoint (startpoint, stalker_mins, stalker_maxs, spawnpoint, 64)) + { + ent = CreateGroundMonster (spawnpoint, self->s.angles, stalker_mins, stalker_maxs, "monster_stalker", 256); + + if (!ent) + continue; + + self->monsterinfo.monster_used++; + ent->monsterinfo.commander = self; +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("widow: post-spawn : %d slots left out of %d\n", SELF_SLOTS_LEFT, self->monsterinfo.monster_slots); + + ent->nextthink = level.time; + ent->think (ent); + + ent->monsterinfo.aiflags |= AI_SPAWNED_WIDOW|AI_DO_NOT_COUNT|AI_IGNORE_SHOTS; + + if (!(coop && coop->value)) + { + designated_enemy = self->enemy; + } + else + { + designated_enemy = PickCoopTarget(ent); + if (designated_enemy) + { + // try to avoid using my enemy + if (designated_enemy == self->enemy) + { + designated_enemy = PickCoopTarget(ent); + if (designated_enemy) + { +// if ((g_showlogic) && (g_showlogic->value)) +// { +// gi.dprintf ("PickCoopTarget returned a %s - ", designated_enemy->classname); +// if (designated_enemy->client) +// gi.dprintf ("with name %s\n", designated_enemy->client->pers.netname); +// else +// gi.dprintf ("NOT A CLIENT\n"); +// } + } + else + { +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("pick coop failed, using my current enemy\n"); + designated_enemy = self->enemy; + } + } + } + else + { +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("pick coop failed, using my current enemy\n"); + designated_enemy = self->enemy; + } + } + + if ((designated_enemy->inuse) && (designated_enemy->health > 0)) + { + ent->enemy = designated_enemy; + FoundTarget (ent); + ent->monsterinfo.attack(ent); + } + } + } +} + +void widow_spawn_check (edict_t *self) +{ + WidowBlaster(self); + WidowSpawn (self); +} + +void widow_ready_spawn (edict_t *self) +{ + vec3_t f, r, u, offset, startpoint, spawnpoint; + int i; + + WidowBlaster(self); + AngleVectors (self->s.angles, f, r, u); + + for (i=0; i < 2; i++) + { + VectorCopy (spawnpoints[i], offset); + G_ProjectSource2 (self->s.origin, offset, f, r, u, startpoint); + if (FindSpawnPoint (startpoint, stalker_mins, stalker_maxs, spawnpoint, 64)) + { + SpawnGrow_Spawn (spawnpoint, 1); + } + } +} + +void widow_step (edict_t *self) +{ + gi.sound (self, CHAN_BODY, gi.soundindex("widow/bwstep3.wav"), 1, ATTN_NORM, 0); +} + +mframe_t widow_frames_stand [] = +{ + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL +}; +mmove_t widow_move_stand = {FRAME_idle01, FRAME_idle11, widow_frames_stand, NULL}; + +mframe_t widow_frames_walk [] = +{ + // hand generated numbers +/* + ai_run, 6, NULL, + ai_run, 3, NULL, + ai_run, 3, NULL, + ai_run, 3, NULL, + ai_run, 4, NULL, //5 + ai_run, 4, NULL, + ai_run, 4, NULL, + ai_run, 4.5, NULL, + ai_run, 3, NULL, + ai_run, 5, NULL, //10 + ai_run, 8, NULL, + ai_run, 8, NULL, + ai_run, 6.5, NULL +*/ + // auto generated numbers + ai_walk, 2.79, widow_step, + ai_walk, 2.77, NULL, + ai_walk, 3.53, NULL, + ai_walk, 3.97, NULL, + ai_walk, 4.13, NULL, //5 + ai_walk, 4.09, NULL, + ai_walk, 3.84, NULL, + ai_walk, 3.62, widow_step, + ai_walk, 3.29, NULL, + ai_walk, 6.08, NULL, //10 + ai_walk, 6.94, NULL, + ai_walk, 5.73, NULL, + ai_walk, 2.85, NULL +}; +mmove_t widow_move_walk = {FRAME_walk01, FRAME_walk13, widow_frames_walk, NULL}; + + +mframe_t widow_frames_run [] = +{ + ai_run, 2.79, widow_step, + ai_run, 2.77, NULL, + ai_run, 3.53, NULL, + ai_run, 3.97, NULL, + ai_run, 4.13, NULL, //5 + ai_run, 4.09, NULL, + ai_run, 3.84, NULL, + ai_run, 3.62, widow_step, + ai_run, 3.29, NULL, + ai_run, 6.08, NULL, //10 + ai_run, 6.94, NULL, + ai_run, 5.73, NULL, + ai_run, 2.85, NULL +}; +mmove_t widow_move_run = {FRAME_walk01, FRAME_walk13, widow_frames_run, NULL}; + +void widow_stepshoot (edict_t *self) +{ + gi.sound (self, CHAN_BODY, gi.soundindex("widow/bwstep2.wav"), 1, ATTN_NORM,0); + WidowBlaster (self); +} + +mframe_t widow_frames_run_attack [] = +{ + ai_charge, 13, widow_stepshoot, + ai_charge, 11.72, WidowBlaster, + ai_charge, 18.04, WidowBlaster, + ai_charge, 14.58, WidowBlaster, + ai_charge, 13, widow_stepshoot, //5 + ai_charge, 12.12, WidowBlaster, + ai_charge, 19.63, WidowBlaster, + ai_charge, 11.37, WidowBlaster +}; +mmove_t widow_move_run_attack = {FRAME_run01, FRAME_run08, widow_frames_run_attack, widow_run}; + + +// +// These three allow specific entry into the run sequence +// + +void widow_start_run_5 (edict_t *self) +{ + self->monsterinfo.currentmove = &widow_move_run; + self->monsterinfo.nextframe = FRAME_walk05; +} + +void widow_start_run_10 (edict_t *self) +{ + self->monsterinfo.currentmove = &widow_move_run; + self->monsterinfo.nextframe = FRAME_walk10; +} + +void widow_start_run_12 (edict_t *self) +{ + self->monsterinfo.currentmove = &widow_move_run; + self->monsterinfo.nextframe = FRAME_walk12; +} + + +mframe_t widow_frames_attack_pre_blaster [] = +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, widow_attack_blaster +}; +mmove_t widow_move_attack_pre_blaster = {FRAME_fired01, FRAME_fired02a, widow_frames_attack_pre_blaster, NULL}; + +// Loop this +mframe_t widow_frames_attack_blaster [] = +{ + ai_charge, 0, widow_reattack_blaster, // straight ahead + ai_charge, 0, widow_reattack_blaster, // 100 degrees right + ai_charge, 0, widow_reattack_blaster, + ai_charge, 0, widow_reattack_blaster, + ai_charge, 0, widow_reattack_blaster, + ai_charge, 0, widow_reattack_blaster, + ai_charge, 0, widow_reattack_blaster, // 50 degrees right + ai_charge, 0, widow_reattack_blaster, + ai_charge, 0, widow_reattack_blaster, + ai_charge, 0, widow_reattack_blaster, + ai_charge, 0, widow_reattack_blaster, + ai_charge, 0, widow_reattack_blaster, // straight + ai_charge, 0, widow_reattack_blaster, + ai_charge, 0, widow_reattack_blaster, + ai_charge, 0, widow_reattack_blaster, + ai_charge, 0, widow_reattack_blaster, + ai_charge, 0, widow_reattack_blaster, // 50 degrees left + ai_charge, 0, widow_reattack_blaster, + ai_charge, 0, widow_reattack_blaster // 70 degrees left +}; +mmove_t widow_move_attack_blaster = {FRAME_fired02a, FRAME_fired20, widow_frames_attack_blaster, NULL}; + +mframe_t widow_frames_attack_post_blaster [] = +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL +}; +mmove_t widow_move_attack_post_blaster = {FRAME_fired21, FRAME_fired22, widow_frames_attack_post_blaster, widow_run}; + +mframe_t widow_frames_attack_post_blaster_r [] = +{ + ai_charge, -2, NULL, + ai_charge, -10, NULL, + ai_charge, -2, NULL, + ai_charge, 0, NULL, + ai_charge, 0, widow_start_run_12 +}; +mmove_t widow_move_attack_post_blaster_r = {FRAME_transa01, FRAME_transa05, widow_frames_attack_post_blaster_r, NULL}; + +mframe_t widow_frames_attack_post_blaster_l [] = +{ + ai_charge, 0, NULL, + ai_charge, 14, NULL, + ai_charge, -2, NULL, + ai_charge, 10, NULL, + ai_charge, 10, widow_start_run_12 +}; +mmove_t widow_move_attack_post_blaster_l = {FRAME_transb01, FRAME_transb05, widow_frames_attack_post_blaster_l, NULL}; + +mmove_t widow_move_attack_rail; +mmove_t widow_move_attack_rail_l; +mmove_t widow_move_attack_rail_r; + +void WidowRail (edict_t *self) +{ + vec3_t start; + vec3_t dir; + vec3_t forward, right; + int flash; + +// gi.dprintf ("railing!\n"); + AngleVectors (self->s.angles, forward, right, NULL); + + if (self->monsterinfo.currentmove == &widow_move_attack_rail) + flash = MZ2_WIDOW_RAIL; + else if (self->monsterinfo.currentmove == &widow_move_attack_rail_l) + { + flash = MZ2_WIDOW_RAIL_LEFT; + } + else if (self->monsterinfo.currentmove == &widow_move_attack_rail_r) + { + flash = MZ2_WIDOW_RAIL_RIGHT; + } + + G_ProjectSource (self->s.origin, monster_flash_offset[flash], forward, right, start); + + // calc direction to where we targeted + VectorSubtract (self->pos1, start, dir); + VectorNormalize (dir); + + monster_fire_railgun (self, start, dir, WIDOW_RAIL_DAMAGE*widow_damage_multiplier, 100, flash); + self->timestamp = level.time + RAIL_TIME; +} + +void WidowSaveLoc (edict_t *self) +{ + VectorCopy (self->enemy->s.origin, self->pos1); //save for aiming the shot + self->pos1[2] += self->enemy->viewheight; +}; + +void widow_start_rail (edict_t *self) +{ + self->monsterinfo.aiflags |= AI_MANUAL_STEERING; +} + +void widow_rail_done (edict_t *self) +{ + self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING; +} + +mframe_t widow_frames_attack_pre_rail [] = +{ + ai_charge, 0, widow_start_rail, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, widow_attack_rail +}; +mmove_t widow_move_attack_pre_rail = {FRAME_transc01, FRAME_transc04, widow_frames_attack_pre_rail, NULL}; + +mframe_t widow_frames_attack_rail [] = +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, WidowSaveLoc, + ai_charge, -10, WidowRail, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, widow_rail_done +}; +mmove_t widow_move_attack_rail = {FRAME_firea01, FRAME_firea09, widow_frames_attack_rail, widow_run}; + +mframe_t widow_frames_attack_rail_r [] = +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, WidowSaveLoc, + ai_charge, -10, WidowRail, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, widow_rail_done +}; +mmove_t widow_move_attack_rail_r = {FRAME_fireb01, FRAME_fireb09, widow_frames_attack_rail_r, widow_run}; + +mframe_t widow_frames_attack_rail_l [] = +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, WidowSaveLoc, + ai_charge, -10, WidowRail, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, widow_rail_done +}; +mmove_t widow_move_attack_rail_l = {FRAME_firec01, FRAME_firec09, widow_frames_attack_rail_l, widow_run}; + +void widow_attack_rail (edict_t *self) +{ + float enemy_angle; +// gi.dprintf ("going to the rail!\n"); + + enemy_angle = target_angle (self); + + if (enemy_angle < -15) + self->monsterinfo.currentmove = &widow_move_attack_rail_l; + else if (enemy_angle > 15) + self->monsterinfo.currentmove = &widow_move_attack_rail_r; + else + self->monsterinfo.currentmove = &widow_move_attack_rail; +} + +void widow_start_spawn (edict_t *self) +{ + self->monsterinfo.aiflags |= AI_MANUAL_STEERING; +} + +void widow_done_spawn (edict_t *self) +{ + self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING; +} + +mframe_t widow_frames_spawn [] = +{ + ai_charge, 0, NULL, //1 + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, widow_start_spawn, + ai_charge, 0, NULL, //5 + ai_charge, 0, WidowBlaster, //6 + ai_charge, 0, widow_ready_spawn, //7 + ai_charge, 0, WidowBlaster, + ai_charge, 0, WidowBlaster, //9 + ai_charge, 0, widow_spawn_check, + ai_charge, 0, WidowBlaster, //11 + ai_charge, 0, WidowBlaster, + ai_charge, 0, WidowBlaster, //13 + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, widow_done_spawn +}; +mmove_t widow_move_spawn = {FRAME_spawn01, FRAME_spawn18, widow_frames_spawn, widow_run}; + +mframe_t widow_frames_pain_heavy [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t widow_move_pain_heavy = {FRAME_pain01, FRAME_pain13, widow_frames_pain_heavy, widow_run}; + +mframe_t widow_frames_pain_light [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t widow_move_pain_light = {FRAME_pain201, FRAME_pain203, widow_frames_pain_light, widow_run}; + +void spawn_out_start (edict_t *self) +{ + vec3_t startpoint,f,r,u; + self->wait = level.time + 2.0; + +// gi.sound (self, CHAN_VOICE, sound_death, 1, ATTN_NONE, 0); + AngleVectors (self->s.angles, f, r, u); + + G_ProjectSource2 (self->s.origin, beameffects[0], f, r, u, startpoint); + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_WIDOWBEAMOUT); + gi.WriteShort (20001); + gi.WritePosition (startpoint); + gi.multicast (startpoint, MULTICAST_ALL); + + G_ProjectSource2 (self->s.origin, beameffects[1], f, r, u, startpoint); + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_WIDOWBEAMOUT); + gi.WriteShort (20002); + gi.WritePosition (startpoint); + gi.multicast (startpoint, MULTICAST_ALL); + + gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/bwidowbeamout.wav"), 1, ATTN_NORM, 0); +} + +void spawn_out_do (edict_t *self) +{ + vec3_t startpoint,f,r,u; + + AngleVectors (self->s.angles, f, r, u); + G_ProjectSource2 (self->s.origin, beameffects[0], f, r, u, startpoint); + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_WIDOWSPLASH); + gi.WritePosition (startpoint); + gi.multicast (startpoint, MULTICAST_ALL); + + G_ProjectSource2 (self->s.origin, beameffects[1], f, r, u, startpoint); + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_WIDOWSPLASH); + gi.WritePosition (startpoint); + gi.multicast (startpoint, MULTICAST_ALL); + + VectorCopy (self->s.origin, startpoint); + startpoint[2] += 36; + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_BOSSTPORT); + gi.WritePosition (startpoint); + gi.multicast (startpoint, MULTICAST_PVS); + + Widowlegs_Spawn (self->s.origin, self->s.angles); + + G_FreeEdict (self); +} + +mframe_t widow_frames_death [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, //5 + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, spawn_out_start, //10 + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, //15 + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, //20 + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, //25 + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, //30 + ai_move, 0, spawn_out_do +}; +mmove_t widow_move_death = {FRAME_death01, FRAME_death31, widow_frames_death, NULL}; + +void widow_attack_kick (edict_t *self) +{ + vec3_t aim; + +// VectorSet (aim, MELEE_DISTANCE, 0, 4); + VectorSet (aim, 100, 0, 4); + if (self->enemy->groundentity) + fire_hit (self, aim, (50 + (rand() % 6)), 500); + else // not as much kick if they're in the air .. makes it harder to land on her head + fire_hit (self, aim, (50 + (rand() % 6)), 250); + +} + +mframe_t widow_frames_attack_kick [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, widow_attack_kick, + ai_move, 0, NULL, // 5 + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; + +mmove_t widow_move_attack_kick = {FRAME_kick01, FRAME_kick08, widow_frames_attack_kick, widow_run}; + +void widow_stand (edict_t *self) +{ +// gi.dprintf ("widow stand\n"); + gi.sound (self, CHAN_WEAPON, gi.soundindex ("widow/laugh.wav"), 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &widow_move_stand; +} + +void widow_run (edict_t *self) +{ + self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; + + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + self->monsterinfo.currentmove = &widow_move_stand; + else + self->monsterinfo.currentmove = &widow_move_run; +} + +void widow_walk (edict_t *self) +{ + self->monsterinfo.currentmove = &widow_move_walk; +} + +void widow_attack (edict_t *self) +{ + float luck; + qboolean rail_frames = false, blaster_frames = false, blocked = false, anger = false; + + self->movetarget = NULL; + + if (self->monsterinfo.aiflags & AI_BLOCKED) + { + blocked = true; + self->monsterinfo.aiflags &= ~AI_BLOCKED; + } + + if (self->monsterinfo.aiflags & AI_TARGET_ANGER) + { + anger = true; + self->monsterinfo.aiflags &= ~AI_TARGET_ANGER; + } + + if ((!self->enemy) || (!self->enemy->inuse)) + return; + + if (self->bad_area) + { + if ((random() < 0.1) || (level.time < self->timestamp)) + self->monsterinfo.currentmove = &widow_move_attack_pre_blaster; + else + { + gi.sound (self, CHAN_WEAPON, sound_rail, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &widow_move_attack_pre_rail; + } + return; + } + + // frames FRAME_walk13, FRAME_walk01, FRAME_walk02, FRAME_walk03 are rail gun start frames + // frames FRAME_walk09, FRAME_walk10, FRAME_walk11, FRAME_walk12 are spawn & blaster start frames + + if ((self->s.frame == FRAME_walk13) || ((self->s.frame >= FRAME_walk01) && (self->s.frame <= FRAME_walk03))) + rail_frames = true; + + if ((self->s.frame >= FRAME_walk09) && (self->s.frame <= FRAME_walk12)) + blaster_frames = true; + + WidowCalcSlots(self); + + // if we can't see the target, spawn stuff regardless of frame + if ((self->monsterinfo.attack_state == AS_BLIND) && (SELF_SLOTS_LEFT >= 2)) + { +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("attacking blind!\n"); + self->monsterinfo.currentmove = &widow_move_spawn; + return; + } + + // accept bias towards spawning regardless of frame + if (blocked && (SELF_SLOTS_LEFT >= 2)) + { + self->monsterinfo.currentmove = &widow_move_spawn; + return; + } + + if ((realrange(self, self->enemy) > 300) && (!anger) && (random() < 0.5) && (!blocked)) + { + self->monsterinfo.currentmove = &widow_move_run_attack; + return; + } + + if (blaster_frames) + { +// gi.dprintf ("blaster frame %2.2f <= %2.2f\n", self->monsterinfo.pausetime + BLASTER_TIME, level.time); + if (SELF_SLOTS_LEFT >= 2) + { + self->monsterinfo.currentmove = &widow_move_spawn; + return; + } + else if (self->monsterinfo.pausetime + BLASTER_TIME <= level.time) + { + self->monsterinfo.currentmove = &widow_move_attack_pre_blaster; + return; + } + } + + if (rail_frames) + { +// gi.dprintf ("rail frame %2.2f - %2.2f\n", level.time, self->timestamp); + if (!(level.time < self->timestamp)) + { + gi.sound (self, CHAN_WEAPON, sound_rail, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &widow_move_attack_pre_rail; + } + } + + if ((rail_frames) || (blaster_frames)) + return; + +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("widow: unknown start frame, picking randomly\n"); + + luck = random(); + if (SELF_SLOTS_LEFT >= 2) + { + if ((luck <= 0.40) && (self->monsterinfo.pausetime + BLASTER_TIME <= level.time)) + self->monsterinfo.currentmove = &widow_move_attack_pre_blaster; + else if ((luck <= 0.7) && !(level.time < self->timestamp)) + { + gi.sound (self, CHAN_WEAPON, sound_rail, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &widow_move_attack_pre_rail; + } + else + self->monsterinfo.currentmove = &widow_move_spawn; + } + else + { + if (level.time < self->timestamp) + self->monsterinfo.currentmove = &widow_move_attack_pre_blaster; + else if ((luck <= 0.50) || (level.time + BLASTER_TIME >= self->monsterinfo.pausetime)) + { + gi.sound (self, CHAN_WEAPON, sound_rail, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &widow_move_attack_pre_rail; + } + else // holdout to blaster + self->monsterinfo.currentmove = &widow_move_attack_pre_blaster; + } +} +/* +void widow_attack (edict_t *self) +{ + float range, luck; + +// gi.dprintf ("widow attack\n"); + + if ((!self->enemy) || (!self->enemy->inuse)) + return; + + if (self->bad_area) + { + if ((random() < 0.1) || (level.time < self->timestamp)) + self->monsterinfo.currentmove = &widow_move_attack_pre_blaster; + else + { + gi.sound (self, CHAN_WEAPON, sound_rail, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &widow_move_attack_pre_rail; + } + return; + } + + // if we can't see the target, spawn stuff + if ((self->monsterinfo.attack_state == AS_BLIND) && (blaster_frames)) + { + self->monsterinfo.currentmove = &widow_move_spawn; + return; + } + + range = realrange (self, self->enemy); + + if (range < 600) + { + luck = random(); + if (SLOTS_LEFT >= 2) + { + if (luck <= 0.40) + self->monsterinfo.currentmove = &widow_move_attack_pre_blaster; + else if ((luck <= 0.7) && !(level.time < self->timestamp)) + { + gi.sound (self, CHAN_WEAPON, sound_rail, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &widow_move_attack_pre_rail; + } + else + self->monsterinfo.currentmove = &widow_move_spawn; + } + else + { + if ((luck <= 0.50) || (level.time < self->timestamp)) + self->monsterinfo.currentmove = &widow_move_attack_pre_blaster; + else + { + gi.sound (self, CHAN_WEAPON, sound_rail, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &widow_move_attack_pre_rail; + } + } + } + else + { + luck = random(); + if (SLOTS_LEFT >= 2) + { + if (luck < 0.3) + self->monsterinfo.currentmove = &widow_move_attack_pre_blaster; + else if ((luck < 0.65) || (level.time < self->timestamp)) + self->monsterinfo.currentmove = &widow_move_spawn; + else + { + gi.sound (self, CHAN_WEAPON, sound_rail, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &widow_move_attack_pre_rail; + } + } + else + { + if ((luck < 0.45) || (level.time < self->timestamp)) + self->monsterinfo.currentmove = &widow_move_attack_pre_blaster; + else + { + gi.sound (self, CHAN_WEAPON, sound_rail, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &widow_move_attack_pre_rail; + } + } + } +} +*/ +void widow_attack_blaster (edict_t *self) +{ + self->monsterinfo.pausetime = level.time + 1.0 + (2.0*random()); +// self->monsterinfo.pausetime = level.time + 100; +// self->plat2flags = 0; + self->monsterinfo.currentmove = &widow_move_attack_blaster; + self->monsterinfo.nextframe = WidowTorso (self); +} + +void widow_reattack_blaster (edict_t *self) +{ + WidowBlaster(self); + +// if ((g_showlogic) && (g_showlogic->value)) +// { +// if (self->monsterinfo.currentmove == &widow_move_attack_post_blaster_l) +// gi.dprintf ("pulling left!\n"); +// if (self->monsterinfo.currentmove == &widow_move_attack_post_blaster_r) +// gi.dprintf ("pulling right!\n"); +// } + +// self->monsterinfo.currentmove = &widow_move_attack_blaster; +// self->monsterinfo.aiflags |= AI_MANUAL_STEERING; +// return; + // if WidowBlaster bailed us out of the frames, just bail + if ((self->monsterinfo.currentmove == &widow_move_attack_post_blaster_l) || + (self->monsterinfo.currentmove == &widow_move_attack_post_blaster_r)) + return; + + // if we're not done with the attack, don't leave the sequence + if (self->monsterinfo.pausetime >= level.time) + return; + + self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING; + + self->monsterinfo.currentmove = &widow_move_attack_post_blaster; +} +/* + if ( infront(self, self->enemy) ) + if (random() <= 0.5) + if ((random() < 0.7) || (SLOTS_LEFT <= 1)) + self->monsterinfo.currentmove = &widow_move_attack_blaster; + else + self->monsterinfo.currentmove = &widow_move_spawn; + else + self->monsterinfo.currentmove = &widow_move_attack_post_blaster; + else + self->monsterinfo.currentmove = &widow_move_attack_post_blaster; +} +*/ + + +void widow_pain (edict_t *self, edict_t *other, float kick, int damage) +{ + if (self->health < (self->max_health / 2)) + self->s.skinnum = 1; + + if (skill->value == 3) + return; // no pain anims in nightmare + + if (level.time < self->pain_debounce_time) + return; + + if (self->monsterinfo.pausetime == 100000000) + self->monsterinfo.pausetime = 0; + + self->pain_debounce_time = level.time + 5; + + if (damage < 15) + { + gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NONE, 0); + } + else if (damage < 75) + { + if ((skill->value < 3) && (random() < (0.6 - (0.2*((float)skill->value))))) + { + self->monsterinfo.currentmove = &widow_move_pain_light; + self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING; + } + gi.sound (self, CHAN_VOICE, sound_pain2, 1, ATTN_NONE, 0); + } + else + { + if ((skill->value < 3) && (random() < (0.75 - (0.1*((float)skill->value))))) + { + self->monsterinfo.currentmove = &widow_move_pain_heavy; + self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING; + } + gi.sound (self, CHAN_VOICE, sound_pain3, 1, ATTN_NONE, 0); + } +} + +void widow_dead (edict_t *self) +{ + VectorSet (self->mins, -56, -56, 0); + VectorSet (self->maxs, 56, 56, 80); + self->movetype = MOVETYPE_TOSS; + self->svflags |= SVF_DEADMONSTER; + self->nextthink = 0; + gi.linkentity (self); +} + +void widow_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_NO; + self->count = 0; + self->monsterinfo.quad_framenum = 0; + self->monsterinfo.double_framenum = 0; + self->monsterinfo.invincible_framenum = 0; + self->monsterinfo.currentmove = &widow_move_death; +} + +void widow_melee (edict_t *self) +{ +// monster_done_dodge (self); + self->monsterinfo.currentmove = &widow_move_attack_kick; +} + +void WidowGoinQuad (edict_t *self, float framenum) +{ + self->monsterinfo.quad_framenum = framenum; + widow_damage_multiplier = 4; +} + +void WidowDouble (edict_t *self, float framenum) +{ + self->monsterinfo.double_framenum = framenum; + widow_damage_multiplier = 2; +} + +void WidowPent (edict_t *self, float framenum) +{ + self->monsterinfo.invincible_framenum = framenum; +} + +void WidowPowerArmor (edict_t *self) +{ + self->monsterinfo.power_armor_type = POWER_ARMOR_SHIELD; + // I don't like this, but it works + if (self->monsterinfo.power_armor_power <= 0) + self->monsterinfo.power_armor_power += 250 * skill->value; +} + +void WidowRespondPowerup (edict_t *self, edict_t *other) +{ + if (other->s.effects & EF_QUAD) + { + if (skill->value == 1) + WidowDouble (self, other->client->quad_framenum); + else if (skill->value == 2) + WidowGoinQuad (self, other->client->quad_framenum); + else if (skill->value == 3) + { + WidowGoinQuad (self, other->client->quad_framenum); + WidowPowerArmor (self); + } + } + else if (other->s.effects & EF_DOUBLE) + { + if (skill->value == 2) + WidowDouble (self, other->client->double_framenum); + else if (skill->value == 3) + { + WidowDouble (self, other->client->double_framenum); + WidowPowerArmor (self); + } + } + else + widow_damage_multiplier = 1; + + if (other->s.effects & EF_PENT) + { + if (skill->value == 1) + WidowPowerArmor (self); + else if (skill->value == 2) + WidowPent (self, other->client->invincible_framenum); + else if (skill->value == 3) + { + WidowPent (self, other->client->invincible_framenum); + WidowPowerArmor (self); + } + } +} + +void WidowPowerups (edict_t *self) +{ + int player; + edict_t *ent; + + if (!(coop && coop->value)) + { + WidowRespondPowerup (self, self->enemy); + } + else + { + // in coop, check for pents, then quads, then doubles + for (player = 1; player <= game.maxclients; player++) + { + ent = &g_edicts[player]; + if (!ent->inuse) + continue; + if (!ent->client) + continue; + if (ent->s.effects & EF_PENT) + { + WidowRespondPowerup (self, ent); + return; + } + } + + for (player = 1; player <= game.maxclients; player++) + { + ent = &g_edicts[player]; + if (!ent->inuse) + continue; + if (!ent->client) + continue; + if (ent->s.effects & EF_QUAD) + { + WidowRespondPowerup (self, ent); + return; + } + } + + for (player = 1; player <= game.maxclients; player++) + { + ent = &g_edicts[player]; + if (!ent->inuse) + continue; + if (!ent->client) + continue; + if (ent->s.effects & EF_DOUBLE) + { + WidowRespondPowerup (self, ent); + return; + } + } + } +} + +qboolean Widow_CheckAttack (edict_t *self) +{ + vec3_t spot1, spot2; + vec3_t temp; + float chance; + trace_t tr; + qboolean enemy_infront; + int enemy_range; + float enemy_yaw; + float real_enemy_range; + + if (!self->enemy) + return false; + + WidowPowerups(self); + + if (self->monsterinfo.currentmove == &widow_move_run) + { + // if we're in run, make sure we're in a good frame for attacking before doing anything else + // frames 1,2,3,9,10,11,13 good to fire + switch (self->s.frame) + { + case FRAME_walk04: + case FRAME_walk05: + case FRAME_walk06: + case FRAME_walk07: + case FRAME_walk08: + case FRAME_walk12: + { +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("Not in good walk frame (%d), not attacking\n", (self->s.frame - FRAME_walk01+1)); + return false; + } + default: + break; + } + } + + // give a LARGE bias to spawning things when we have room + // use AI_BLOCKED as a signal to attack to spawn + if ((random() < 0.8) && (SELF_SLOTS_LEFT >= 2) && (realrange(self, self->enemy) > 150)) + { + self->monsterinfo.aiflags |= AI_BLOCKED; + self->monsterinfo.attack_state = AS_MISSILE; + return true; + } + + if (self->enemy->health > 0) + { + // see if any entities are in the way of the shot + VectorCopy (self->s.origin, spot1); + spot1[2] += self->viewheight; + VectorCopy (self->enemy->s.origin, spot2); + spot2[2] += self->enemy->viewheight; + + tr = gi.trace (spot1, NULL, NULL, spot2, self, CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_SLIME|CONTENTS_LAVA); + + // do we have a clear shot? + if (tr.ent != self->enemy) + { + // go ahead and spawn stuff if we're mad a a client + if (self->enemy->client && SELF_SLOTS_LEFT >= 2) + { + self->monsterinfo.attack_state = AS_BLIND; + return true; + } + + // PGM - we want them to go ahead and shoot at info_notnulls if they can. + if(self->enemy->solid != SOLID_NOT || tr.fraction < 1.0) //PGM + return false; + } + } + + enemy_infront = infront(self, self->enemy); + + enemy_range = range(self, self->enemy); + VectorSubtract (self->enemy->s.origin, self->s.origin, temp); + enemy_yaw = vectoyaw2(temp); + + self->ideal_yaw = enemy_yaw; + + real_enemy_range = realrange (self, self->enemy); + +// if (g_showlogic->value) +// gi.dprintf ("range = %2.2f\n", real_enemy_range); + + // melee attack +// if (enemy_range == RANGE_MELEE) + if (real_enemy_range <= (MELEE_DISTANCE+20)) + { + // don't always melee in easy mode + if (skill->value == 0 && (rand()&3) ) + return false; + if (self->monsterinfo.melee) + self->monsterinfo.attack_state = AS_MELEE; + else + self->monsterinfo.attack_state = AS_MISSILE; + return true; + } + + if (level.time < self->monsterinfo.attack_finished) + return false; + + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + { + chance = 0.4; + } + else if (enemy_range == RANGE_MELEE) + { + chance = 0.8; + } + else if (enemy_range == RANGE_NEAR) + { + chance = 0.7; + } + else if (enemy_range == RANGE_MID) + { + chance = 0.6; + } + else if (enemy_range == RANGE_FAR) + { + chance = 0.5; + } + + // PGM - go ahead and shoot every time if it's a info_notnull + if ((random () < chance) || (self->enemy->solid == SOLID_NOT)) + { + self->monsterinfo.attack_state = AS_MISSILE; + return true; + } + + return false; +} + +qboolean widow_blocked (edict_t *self, float dist) +{ + // if we get blocked while we're in our run/attack mode, turn on a meaningless (in this context)AI flag, + // and call attack to get a new attack sequence. make sure to turn it off when we're done. + // + // I'm using AI_TARGET_ANGER for this purpose + + if (self->monsterinfo.currentmove == &widow_move_run_attack) + { + self->monsterinfo.aiflags |= AI_TARGET_ANGER; + if (self->monsterinfo.checkattack(self)) + self->monsterinfo.attack(self); + else + self->monsterinfo.run(self); + return true; + } + + if(blocked_checkshot (self, 0.25 + (0.05 * skill->value) )) + return true; + +/* + if(blocked_checkjump (self, dist, 192, 40)) + { + infantry_jump(self); + return true; + } + + if(blocked_checkplat (self, dist)) + return true; +*/ + return false; +} + +void WidowCalcSlots (edict_t *self) +{ + int old_slots; + + old_slots = self->monsterinfo.monster_slots; + + switch ((int)skill->value) + { + case 0: + case 1: + self->monsterinfo.monster_slots = 3; + break; + case 2: + self->monsterinfo.monster_slots = 4; + break; + case 3: + self->monsterinfo.monster_slots = 6; + break; + default: + self->monsterinfo.monster_slots = 3; + break; + } + if (coop->value) + { + self->monsterinfo.monster_slots = min (6, self->monsterinfo.monster_slots + ((skill->value)*(CountPlayers()-1))); + } +// if ((g_showlogic) && (g_showlogic->value) && (old_slots != self->monsterinfo.monster_slots)) +// gi.dprintf ("number of slots changed from %d to %d\n", old_slots, self->monsterinfo.monster_slots); +} + +void WidowPrecache () +{ + // cache in all of the stalker stuff, widow stuff, spawngro stuff, gibs + gi.soundindex ("stalker/pain.wav"); + gi.soundindex ("stalker/death.wav"); + gi.soundindex ("stalker/sight.wav"); + gi.soundindex ("stalker/melee1.wav"); + gi.soundindex ("stalker/melee2.wav"); + gi.soundindex ("stalker/idle.wav"); + + gi.soundindex ("tank/tnkatck3.wav"); + gi.modelindex ("models/proj/laser2/tris.md2"); + + gi.modelindex ("models/monsters/stalker/tris.md2"); + gi.modelindex ("models/items/spawngro2/tris.md2"); + gi.modelindex ("models/objects/gibs/sm_metal/tris.md2"); + gi.modelindex ("models/objects/gibs/gear/tris.md2"); + gi.modelindex ("models/monsters/blackwidow/gib1/tris.md2"); + gi.modelindex ("models/monsters/blackwidow/gib2/tris.md2"); + gi.modelindex ("models/monsters/blackwidow/gib3/tris.md2"); + gi.modelindex ("models/monsters/blackwidow/gib4/tris.md2"); + gi.modelindex ("models/monsters/blackwidow2/gib1/tris.md2"); + gi.modelindex ("models/monsters/blackwidow2/gib2/tris.md2"); + gi.modelindex ("models/monsters/blackwidow2/gib3/tris.md2"); + gi.modelindex ("models/monsters/blackwidow2/gib4/tris.md2"); + gi.modelindex ("models/monsters/legs/tris.md2"); + gi.soundindex ("misc/bwidowbeamout.wav"); + + gi.soundindex ("misc/bigtele.wav"); + gi.soundindex ("widow/bwstep3.wav"); + gi.soundindex ("widow/bwstep2.wav"); +} + + +/*QUAKED monster_widow (1 .5 0) (-40 -40 0) (40 40 144) Ambush Trigger_Spawn Sight +*/ +void SP_monster_widow (edict_t *self) +{ + if (deathmatch->value) + { + G_FreeEdict (self); + return; + } + + sound_pain1 = gi.soundindex ("widow/bw1pain1.wav"); + sound_pain2 = gi.soundindex ("widow/bw1pain2.wav"); + sound_pain3 = gi.soundindex ("widow/bw1pain3.wav"); + sound_search1 = gi.soundindex ("bosshovr/bhvunqv1.wav"); +// sound_sight = gi.soundindex ("widow/sight.wav"); + sound_rail = gi.soundindex ("gladiator/railgun.wav"); + +// self->s.sound = gi.soundindex ("bosshovr/bhvengn1.wav"); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex ("models/monsters/blackwidow/tris.md2"); + VectorSet (self->mins, -40, -40, 0); + VectorSet (self->maxs, 40, 40, 144); + + self->health = 2000 + 1000*(skill->value); + if (coop->value) + self->health += 500*(skill->value); +// self->health = 1; + self->gib_health = -5000; + self->mass = 1500; +/* + if (skill->value == 2) + { + self->monsterinfo.power_armor_type = POWER_ARMOR_SHIELD; + self->monsterinfo.power_armor_power = 250; + } + else */if (skill->value == 3) + { + self->monsterinfo.power_armor_type = POWER_ARMOR_SHIELD; + self->monsterinfo.power_armor_power = 500; + } + + self->yaw_speed = 30; + + self->flags |= FL_IMMUNE_LASER; + self->monsterinfo.aiflags |= AI_IGNORE_SHOTS; + + self->pain = widow_pain; + self->die = widow_die; + + self->monsterinfo.melee = widow_melee; + self->monsterinfo.stand = widow_stand; + self->monsterinfo.walk = widow_walk; + self->monsterinfo.run = widow_run; + self->monsterinfo.attack = widow_attack; + self->monsterinfo.search = widow_search; + self->monsterinfo.checkattack = Widow_CheckAttack; + self->monsterinfo.sight = widow_sight; + + self->monsterinfo.blocked = widow_blocked; + + gi.linkentity (self); + + self->monsterinfo.currentmove = &widow_move_stand; + self->monsterinfo.scale = MODEL_SCALE; + + WidowPrecache(); + WidowCalcSlots(self); + widow_damage_multiplier = 1; + + walkmonster_start (self); +} \ No newline at end of file diff --git a/original/rogue/m_widow.h b/original/rogue/m_widow.h new file mode 100644 index 0000000..8627269 --- /dev/null +++ b/original/rogue/m_widow.h @@ -0,0 +1,177 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// G:\quake2\xpack\models/monsters/blackwidow + +// This file generated by qdata - Do NOT Modify + +#define FRAME_idle01 0 +#define FRAME_idle02 1 +#define FRAME_idle03 2 +#define FRAME_idle04 3 +#define FRAME_idle05 4 +#define FRAME_idle06 5 +#define FRAME_idle07 6 +#define FRAME_idle08 7 +#define FRAME_idle09 8 +#define FRAME_idle10 9 +#define FRAME_idle11 10 +#define FRAME_walk01 11 +#define FRAME_walk02 12 +#define FRAME_walk03 13 +#define FRAME_walk04 14 +#define FRAME_walk05 15 +#define FRAME_walk06 16 +#define FRAME_walk07 17 +#define FRAME_walk08 18 +#define FRAME_walk09 19 +#define FRAME_walk10 20 +#define FRAME_walk11 21 +#define FRAME_walk12 22 +#define FRAME_walk13 23 +#define FRAME_run01 24 +#define FRAME_run02 25 +#define FRAME_run03 26 +#define FRAME_run04 27 +#define FRAME_run05 28 +#define FRAME_run06 29 +#define FRAME_run07 30 +#define FRAME_run08 31 +#define FRAME_firea01 32 +#define FRAME_firea02 33 +#define FRAME_firea03 34 +#define FRAME_firea04 35 +#define FRAME_firea05 36 +#define FRAME_firea06 37 +#define FRAME_firea07 38 +#define FRAME_firea08 39 +#define FRAME_firea09 40 +#define FRAME_fireb01 41 +#define FRAME_fireb02 42 +#define FRAME_fireb03 43 +#define FRAME_fireb04 44 +#define FRAME_fireb05 45 +#define FRAME_fireb06 46 +#define FRAME_fireb07 47 +#define FRAME_fireb08 48 +#define FRAME_fireb09 49 +#define FRAME_firec01 50 +#define FRAME_firec02 51 +#define FRAME_firec03 52 +#define FRAME_firec04 53 +#define FRAME_firec05 54 +#define FRAME_firec06 55 +#define FRAME_firec07 56 +#define FRAME_firec08 57 +#define FRAME_firec09 58 +#define FRAME_fired01 59 +#define FRAME_fired02 60 +#define FRAME_fired02a 61 +#define FRAME_fired03 62 +#define FRAME_fired04 63 +#define FRAME_fired05 64 +#define FRAME_fired06 65 +#define FRAME_fired07 66 +#define FRAME_fired08 67 +#define FRAME_fired09 68 +#define FRAME_fired10 69 +#define FRAME_fired11 70 +#define FRAME_fired12 71 +#define FRAME_fired13 72 +#define FRAME_fired14 73 +#define FRAME_fired15 74 +#define FRAME_fired16 75 +#define FRAME_fired17 76 +#define FRAME_fired18 77 +#define FRAME_fired19 78 +#define FRAME_fired20 79 +#define FRAME_fired21 80 +#define FRAME_fired22 81 +#define FRAME_spawn01 82 +#define FRAME_spawn02 83 +#define FRAME_spawn03 84 +#define FRAME_spawn04 85 +#define FRAME_spawn05 86 +#define FRAME_spawn06 87 +#define FRAME_spawn07 88 +#define FRAME_spawn08 89 +#define FRAME_spawn09 90 +#define FRAME_spawn10 91 +#define FRAME_spawn11 92 +#define FRAME_spawn12 93 +#define FRAME_spawn13 94 +#define FRAME_spawn14 95 +#define FRAME_spawn15 96 +#define FRAME_spawn16 97 +#define FRAME_spawn17 98 +#define FRAME_spawn18 99 +#define FRAME_pain01 100 +#define FRAME_pain02 101 +#define FRAME_pain03 102 +#define FRAME_pain04 103 +#define FRAME_pain05 104 +#define FRAME_pain06 105 +#define FRAME_pain07 106 +#define FRAME_pain08 107 +#define FRAME_pain09 108 +#define FRAME_pain10 109 +#define FRAME_pain11 110 +#define FRAME_pain12 111 +#define FRAME_pain13 112 +#define FRAME_pain201 113 +#define FRAME_pain202 114 +#define FRAME_pain203 115 +#define FRAME_transa01 116 +#define FRAME_transa02 117 +#define FRAME_transa03 118 +#define FRAME_transa04 119 +#define FRAME_transa05 120 +#define FRAME_transb01 121 +#define FRAME_transb02 122 +#define FRAME_transb03 123 +#define FRAME_transb04 124 +#define FRAME_transb05 125 +#define FRAME_transc01 126 +#define FRAME_transc02 127 +#define FRAME_transc03 128 +#define FRAME_transc04 129 +#define FRAME_death01 130 +#define FRAME_death02 131 +#define FRAME_death03 132 +#define FRAME_death04 133 +#define FRAME_death05 134 +#define FRAME_death06 135 +#define FRAME_death07 136 +#define FRAME_death08 137 +#define FRAME_death09 138 +#define FRAME_death10 139 +#define FRAME_death11 140 +#define FRAME_death12 141 +#define FRAME_death13 142 +#define FRAME_death14 143 +#define FRAME_death15 144 +#define FRAME_death16 145 +#define FRAME_death17 146 +#define FRAME_death18 147 +#define FRAME_death19 148 +#define FRAME_death20 149 +#define FRAME_death21 150 +#define FRAME_death22 151 +#define FRAME_death23 152 +#define FRAME_death24 153 +#define FRAME_death25 154 +#define FRAME_death26 155 +#define FRAME_death27 156 +#define FRAME_death28 157 +#define FRAME_death29 158 +#define FRAME_death30 159 +#define FRAME_death31 160 +#define FRAME_kick01 161 +#define FRAME_kick02 162 +#define FRAME_kick03 163 +#define FRAME_kick04 164 +#define FRAME_kick05 165 +#define FRAME_kick06 166 +#define FRAME_kick07 167 +#define FRAME_kick08 168 + +#define MODEL_SCALE 2.000000 diff --git a/original/rogue/m_widow2.c b/original/rogue/m_widow2.c new file mode 100644 index 0000000..a52ec0d --- /dev/null +++ b/original/rogue/m_widow2.c @@ -0,0 +1,1794 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +/* +============================================================================== + +black widow, part 2 + +============================================================================== +*/ + +// timestamp used to prevent rapid fire of melee attack + +#include "g_local.h" +#include "m_widow2.h" + +#define NUM_STALKERS_SPAWNED 6 // max # of stalkers she can spawn + +#define DISRUPT_TIME 3 + +static int sound_pain1; +static int sound_pain2; +static int sound_pain3; +static int sound_death; +static int sound_search1; +static int sound_disrupt; +static int sound_tentacles_retract; + +// sqrt(64*64*2) + sqrt(28*28*2) => 130.1 +static vec3_t spawnpoints[] = { + {30, 135, 0}, + {30, -135, 0} +}; + +static float sweep_angles[] = { + -40.0, -32.0, -24.0, -16.0, -8.0, 0.0, 8.0, 16.0, 24.0, 32.0, 40.0 +}; + +extern vec3_t stalker_mins, stalker_maxs; + +qboolean infront (edict_t *self, edict_t *other); +void WidowCalcSlots (edict_t *self); +void WidowPowerups (edict_t *self); + +void widow2_run (edict_t *self); +void widow2_stand (edict_t *self); +void widow2_dead (edict_t *self); +void widow2_attack (edict_t *self); +void widow2_attack_beam (edict_t *self); +void widow2_reattack_beam (edict_t *self); +void widow2_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point); +void widow_start_spawn (edict_t *self); +void widow_done_spawn (edict_t *self); +void widow2_spawn_check (edict_t *self); +void widow2_prep_spawn (edict_t *self); +void Widow2SaveBeamTarget(edict_t *self); + +// death stuff +void WidowExplode (edict_t *self); +void gib_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point); +void gib_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf); +void ThrowWidowGibReal (edict_t *self, char *gibname, int damage, int type, vec3_t startpos, qboolean large, int hitsound, qboolean fade); +void ThrowWidowGibSized (edict_t *self, char *gibname, int damage, int type, vec3_t startpos, int hitsound, qboolean fade); +void ThrowWidowGibLoc (edict_t *self, char *gibname, int damage, int type, vec3_t startpos, qboolean fade); +void WidowExplosion1 (edict_t *self); +void WidowExplosion2 (edict_t *self); +void WidowExplosion3 (edict_t *self); +void WidowExplosion4 (edict_t *self); +void WidowExplosion5 (edict_t *self); +void WidowExplosion6 (edict_t *self); +void WidowExplosion7 (edict_t *self); +void WidowExplosionLeg (edict_t *self); +void ThrowArm1 (edict_t *self); +void ThrowArm2 (edict_t *self); +void ClipGibVelocity (edict_t *ent); +// end of death stuff + +// these offsets used by the tongue +static vec3_t offsets[] = { + {17.48, 0.10, 68.92}, + {17.47, 0.29, 68.91}, + {17.45, 0.53, 68.87}, + {17.42, 0.78, 68.81}, + {17.39, 1.02, 68.75}, + {17.37, 1.20, 68.70}, + {17.36, 1.24, 68.71}, + {17.37, 1.21, 68.72}, +}; + +void showme (edict_t *self); + +void pauseme (edict_t *self) +{ + self->monsterinfo.aiflags |= AI_HOLD_FRAME; +} + +void widow2_search (edict_t *self) +{ + if (random() < 0.5) + gi.sound (self, CHAN_VOICE, sound_search1, 1, ATTN_NONE, 0); +} + +void Widow2Beam (edict_t *self) +{ + vec3_t forward, right, target; + vec3_t start, targ_angles, vec; + int flashnum; + + if ((!self->enemy) || (!self->enemy->inuse)) + return; + + AngleVectors (self->s.angles, forward, right, NULL); + + if ((self->s.frame >= FRAME_fireb05) && (self->s.frame <= FRAME_fireb09)) + { + // regular beam attack + Widow2SaveBeamTarget(self); + flashnum = MZ2_WIDOW2_BEAMER_1 + self->s.frame - FRAME_fireb05; + G_ProjectSource (self->s.origin, monster_flash_offset[flashnum], forward, right, start); + VectorCopy (self->pos2, target); + target[2] += self->enemy->viewheight-10; + VectorSubtract (target, start, forward); + VectorNormalize (forward); + monster_fire_heat (self, start, forward, vec3_origin, 10, 50, flashnum); + } + else if ((self->s.frame >= FRAME_spawn04) && (self->s.frame <= FRAME_spawn14)) + { + // sweep + flashnum = MZ2_WIDOW2_BEAM_SWEEP_1 + self->s.frame - FRAME_spawn04; + G_ProjectSource (self->s.origin, monster_flash_offset[flashnum], forward, right, start); + VectorSubtract (self->enemy->s.origin, start, target); + vectoangles2 (target, targ_angles); + + VectorCopy (self->s.angles, vec); + + vec[PITCH] += targ_angles[PITCH]; + vec[YAW] -= sweep_angles[flashnum-MZ2_WIDOW2_BEAM_SWEEP_1]; + + AngleVectors (vec, forward, NULL, NULL); + monster_fire_heat (self, start, forward, vec3_origin, 10, 50, flashnum); +/* + if (self->s.frame == FRAME_spawn04) + { + VectorMA (start, 1024, forward, debugend); + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_DEBUGTRAIL); + gi.WritePosition (start); + gi.WritePosition (debugend); + gi.multicast (start, MULTICAST_ALL); + + drawbbox (self); + self->monsterinfo.aiflags |= AI_HOLD_FRAME|AI_MANUAL_STEERING; + } +*/ + } + else + { +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("bad fire frame for widow2 beam -- tell me you saw this!\n"); + + Widow2SaveBeamTarget(self); + G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_WIDOW2_BEAMER_1], forward, right, start); + + VectorCopy (self->pos2, target); + target[2] += self->enemy->viewheight-10; + + VectorSubtract (target, start, forward); + VectorNormalize (forward); + + monster_fire_heat (self, start, forward, vec3_origin, 10, 50, 0); + } +} + +void Widow2Spawn (edict_t *self) +{ + vec3_t f, r, u, offset, startpoint, spawnpoint; + edict_t *ent, *designated_enemy; + int i; + + AngleVectors (self->s.angles, f, r, u); + + for (i=0; i < 2; i++) + { + VectorCopy (spawnpoints[i], offset); + + G_ProjectSource2 (self->s.origin, offset, f, r, u, startpoint); + + if (FindSpawnPoint (startpoint, stalker_mins, stalker_maxs, spawnpoint, 64)) + { + ent = CreateGroundMonster (spawnpoint, self->s.angles, stalker_mins, stalker_maxs, "monster_stalker", 256); + + if (!ent) + continue; + + self->monsterinfo.monster_used++; + ent->monsterinfo.commander = self; +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("widow: post-spawn : %d slots left\n", SELF_SLOTS_LEFT); + + ent->nextthink = level.time; + ent->think (ent); + + ent->monsterinfo.aiflags |= AI_SPAWNED_WIDOW|AI_DO_NOT_COUNT|AI_IGNORE_SHOTS; + + if (!(coop && coop->value)) + { + designated_enemy = self->enemy; + } + else + { + designated_enemy = PickCoopTarget(ent); + if (designated_enemy) + { + // try to avoid using my enemy + if (designated_enemy == self->enemy) + { + designated_enemy = PickCoopTarget(ent); + if (designated_enemy) + { +// if ((g_showlogic) && (g_showlogic->value)) +// { +// gi.dprintf ("PickCoopTarget returned a %s - ", designated_enemy->classname); +// if (designated_enemy->client) +// gi.dprintf ("with name %s\n", designated_enemy->client->pers.netname); +// else +// gi.dprintf ("NOT A CLIENT\n"); +// } + } + else + { +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("pick coop failed, using my current enemy\n"); + designated_enemy = self->enemy; + } + } + } + else + { +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("pick coop failed, using my current enemy\n"); + designated_enemy = self->enemy; + } + } + + if ((designated_enemy->inuse) && (designated_enemy->health > 0)) + { + ent->enemy = designated_enemy; + FoundTarget (ent); + ent->monsterinfo.attack(ent); + } + } + } +} + +void widow2_spawn_check (edict_t *self) +{ + Widow2Beam(self); + Widow2Spawn (self); +} + +void widow2_ready_spawn (edict_t *self) +{ + vec3_t f, r, u, offset, startpoint, spawnpoint; + int i; + + Widow2Beam(self); + AngleVectors (self->s.angles, f, r, u); + + for (i=0; i < 2; i++) + { + VectorCopy (spawnpoints[i], offset); + G_ProjectSource2 (self->s.origin, offset, f, r, u, startpoint); + if (FindSpawnPoint (startpoint, stalker_mins, stalker_maxs, spawnpoint, 64)) + { + SpawnGrow_Spawn (spawnpoint, 1); + } + } +} + +mframe_t widow2_frames_stand [] = +{ +// ai_stand, 0, drawbbox + ai_stand, 0, NULL +}; +mmove_t widow2_move_stand = {FRAME_blackwidow3, FRAME_blackwidow3, widow2_frames_stand, NULL}; + +mframe_t widow2_frames_walk [] = +{ +// ai_walk, 9.01, drawbbox, + ai_walk, 9.01, NULL, + ai_walk, 7.55, NULL, + ai_walk, 7.01, NULL, + ai_walk, 6.66, NULL, + ai_walk, 6.20, NULL, + ai_walk, 5.78, NULL, + ai_walk, 7.25, NULL, + ai_walk, 8.37, NULL, + ai_walk, 10.41, NULL +}; +mmove_t widow2_move_walk = {FRAME_walk01, FRAME_walk09, widow2_frames_walk, NULL}; + + +mframe_t widow2_frames_run [] = +{ +// ai_run, 9.01, drawbbox, + ai_run, 9.01, NULL, + ai_run, 7.55, NULL, + ai_run, 7.01, NULL, + ai_run, 6.66, NULL, + ai_run, 6.20, NULL, + ai_run, 5.78, NULL, + ai_run, 7.25, NULL, + ai_run, 8.37, NULL, + ai_run, 10.41, NULL +}; +mmove_t widow2_move_run = {FRAME_walk01, FRAME_walk09, widow2_frames_run, NULL}; + +mframe_t widow2_frames_attack_pre_beam [] = +{ + ai_charge, 4, NULL, + ai_charge, 4, NULL, + ai_charge, 4, NULL, + ai_charge, 4, widow2_attack_beam +}; +mmove_t widow2_move_attack_pre_beam = {FRAME_fireb01, FRAME_fireb04, widow2_frames_attack_pre_beam, NULL}; + + +// Loop this +mframe_t widow2_frames_attack_beam [] = +{ + ai_charge, 0, Widow2Beam, + ai_charge, 0, Widow2Beam, + ai_charge, 0, Widow2Beam, + ai_charge, 0, Widow2Beam, + ai_charge, 0, widow2_reattack_beam +}; +mmove_t widow2_move_attack_beam = {FRAME_fireb05, FRAME_fireb09, widow2_frames_attack_beam, NULL}; + +mframe_t widow2_frames_attack_post_beam [] = +{ + ai_charge, 4, NULL, + ai_charge, 4, NULL, + ai_charge, 4, NULL +}; +mmove_t widow2_move_attack_post_beam = {FRAME_fireb06, FRAME_fireb07, widow2_frames_attack_post_beam, widow2_run}; + + +void WidowDisrupt (edict_t *self) +{ + vec3_t start; + vec3_t dir; + vec3_t forward, right; + float len; + + AngleVectors (self->s.angles, forward, right, NULL); + G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_WIDOW_DISRUPTOR], forward, right, start); + + VectorSubtract (self->pos1, self->enemy->s.origin, dir); + len = VectorLength (dir); + + if (len < 30) + { +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("target locked - dist %2.2f\n", len); + // calc direction to where we targeted + VectorSubtract (self->pos1, start, dir); + VectorNormalize (dir); + + monster_fire_tracker(self, start, dir, 20, 500, self->enemy, MZ2_WIDOW_DISRUPTOR); + } + else + { +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("target missed - dist %2.2f\n", len); + + PredictAim (self->enemy, start, 1200, true, 0, dir, NULL); + +// VectorSubtract (self->enemy->s.origin, start, dir); +// VectorNormalize (dir); + monster_fire_tracker(self, start, dir, 20, 1200, NULL, MZ2_WIDOW_DISRUPTOR); + } +} + +void Widow2SaveDisruptLoc (edict_t *self) +{ + if (self->enemy && self->enemy->inuse) + { + VectorCopy (self->enemy->s.origin, self->pos1); //save for aiming the shot + self->pos1[2] += self->enemy->viewheight; + } + else + VectorCopy (vec3_origin, self->pos1); +}; + +void widow2_disrupt_reattack (edict_t *self) +{ + float luck; + + luck = random(); + + if (luck < (0.25 + ((float)(skill->value))*0.15)) + self->monsterinfo.nextframe = FRAME_firea01; +} + +mframe_t widow2_frames_attack_disrupt [] = +{ + ai_charge, 2, NULL, + ai_charge, 2, NULL, + ai_charge, 2, Widow2SaveDisruptLoc, + ai_charge, -20, WidowDisrupt, + ai_charge, 2, NULL, + ai_charge, 2, NULL, + ai_charge, 2, widow2_disrupt_reattack +}; +mmove_t widow2_move_attack_disrupt = {FRAME_firea01, FRAME_firea07, widow2_frames_attack_disrupt, widow2_run}; + +void Widow2SaveBeamTarget (edict_t *self) +{ + if (self->enemy && self->enemy->inuse) + { + VectorCopy (self->pos1, self->pos2); + VectorCopy (self->enemy->s.origin, self->pos1); //save for aiming the shot + } + else + { + VectorCopy (vec3_origin, self->pos1); + VectorCopy (vec3_origin, self->pos2); + } +} + +void Widow2BeamTargetRemove (edict_t *self) +{ + VectorCopy (vec3_origin, self->pos1); + VectorCopy (vec3_origin, self->pos2); +} + +void Widow2StartSweep (edict_t *self) +{ + Widow2SaveBeamTarget (self); +} + +mframe_t widow2_frames_spawn [] = +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, widow_start_spawn, + ai_charge, 0, Widow2Beam, + ai_charge, 0, Widow2Beam, //5 + ai_charge, 0, Widow2Beam, + ai_charge, 0, Widow2Beam, + ai_charge, 0, Widow2Beam, + ai_charge, 0, Widow2Beam, + ai_charge, 0, widow2_ready_spawn, //10 + ai_charge, 0, Widow2Beam, + ai_charge, 0, Widow2Beam, + ai_charge, 0, Widow2Beam, + ai_charge, 0, widow2_spawn_check, + ai_charge, 0, NULL, //15 + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, widow2_reattack_beam +}; +mmove_t widow2_move_spawn = {FRAME_spawn01, FRAME_spawn18, widow2_frames_spawn, NULL}; + +static qboolean widow2_tongue_attack_ok (vec3_t start, vec3_t end, float range) +{ + vec3_t dir, angles; + + // check for max distance + VectorSubtract (start, end, dir); + if (VectorLength(dir) > range) + return false; + + // check for min/max pitch + vectoangles (dir, angles); + if (angles[0] < -180) + angles[0] += 360; + if (fabs(angles[0]) > 30) + return false; + + return true; +} + +void Widow2Tongue (edict_t *self) +{ + vec3_t f, r, u; + vec3_t start, end, dir; + trace_t tr; + + AngleVectors (self->s.angles, f, r, u); + G_ProjectSource2 (self->s.origin, offsets[self->s.frame - FRAME_tongs01], f, r, u, start); + VectorCopy (self->enemy->s.origin, end); + if (!widow2_tongue_attack_ok(start, end, 256)) + { + end[2] = self->enemy->s.origin[2] + self->enemy->maxs[2] - 8; + if (!widow2_tongue_attack_ok(start, end, 256)) + { + end[2] = self->enemy->s.origin[2] + self->enemy->mins[2] + 8; + if (!widow2_tongue_attack_ok(start, end, 256)) + return; + } + } + VectorCopy (self->enemy->s.origin, end); + + tr = gi.trace (start, NULL, NULL, end, self, MASK_SHOT); + if (tr.ent != self->enemy) + return; + + gi.sound (self, CHAN_WEAPON, sound_tentacles_retract, 1, ATTN_NORM, 0); + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_PARASITE_ATTACK); + gi.WriteShort (self - g_edicts); + gi.WritePosition (start); + gi.WritePosition (end); + gi.multicast (self->s.origin, MULTICAST_PVS); + + VectorSubtract (start, end, dir); + T_Damage (self->enemy, self, self, dir, self->enemy->s.origin, vec3_origin, 2, 0, DAMAGE_NO_KNOCKBACK, MOD_UNKNOWN); +} + +void Widow2TonguePull (edict_t *self) +{ + vec3_t vec; + float len; + vec3_t f, r, u; + vec3_t start, end; + + if ((!self->enemy) || (!self->enemy->inuse)) + { + self->monsterinfo.run (self); + return; + } + + AngleVectors (self->s.angles, f, r, u); + G_ProjectSource2 (self->s.origin, offsets[self->s.frame - FRAME_tongs01], f, r, u, start); + VectorCopy (self->enemy->s.origin, end); + + if (!widow2_tongue_attack_ok(start, end, 256)) + { + return; + } + + if (self->enemy->groundentity) + { + self->enemy->s.origin[2] += 1; + self->enemy->groundentity = NULL; + // interesting, you don't have to relink the player + } + + VectorSubtract (self->s.origin, self->enemy->s.origin, vec); + len = VectorLength (vec); + if (self->enemy->client) + { + VectorNormalize (vec); + VectorMA (self->enemy->velocity, 1000, vec, self->enemy->velocity); + } + else + { + self->enemy->ideal_yaw = vectoyaw(vec); + M_ChangeYaw (self->enemy); + VectorScale (f, 1000, self->enemy->velocity); + } +} + +void Widow2Crunch (edict_t *self) +{ + vec3_t aim; + + if ((!self->enemy) || (!self->enemy->inuse)) + { + self->monsterinfo.run (self); + return; + } + + Widow2TonguePull (self); + + // 70 + 32 + VectorSet (aim, 150, 0, 4); + if (self->s.frame != FRAME_tongs07) + fire_hit (self, aim, 20 + (rand() % 6), 0); + else + { + if (self->enemy->groundentity) + fire_hit (self, aim, (20 + (rand() % 6)), 500); + else // not as much kick if they're in the air .. makes it harder to land on her head + fire_hit (self, aim, (20 + (rand() % 6)), 250); + } +} + +void Widow2Toss (edict_t *self) +{ + self->timestamp = level.time + 3; + return; +} + +mframe_t widow2_frames_tongs [] = +{ + ai_charge, 0, Widow2Tongue, + ai_charge, 0, Widow2Tongue, + ai_charge, 0, Widow2Tongue, + ai_charge, 0, Widow2TonguePull, + ai_charge, 0, Widow2TonguePull, //5 + ai_charge, 0, Widow2TonguePull, + ai_charge, 0, Widow2Crunch, + ai_charge, 0, Widow2Toss +}; +mmove_t widow2_move_tongs = {FRAME_tongs01, FRAME_tongs08, widow2_frames_tongs, widow2_run}; + +mframe_t widow2_frames_pain [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t widow2_move_pain = {FRAME_pain01, FRAME_pain05, widow2_frames_pain, widow2_run}; + +mframe_t widow2_frames_death [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, WidowExplosion1, // 3 boom + ai_move, 0, NULL, + ai_move, 0, NULL, // 5 + + ai_move, 0, WidowExplosion2, // 6 boom + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, // 10 + + ai_move, 0, NULL, + ai_move, 0, NULL, // 12 + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, // 15 + + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, WidowExplosion3, // 18 + ai_move, 0, NULL, // 19 + ai_move, 0, NULL, // 20 + + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, WidowExplosion4, // 25 + + ai_move, 0, NULL, // 26 + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, WidowExplosion5, + ai_move, 0, WidowExplosionLeg, // 30 + + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, WidowExplosion6, + ai_move, 0, NULL, // 35 + + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, WidowExplosion7, + ai_move, 0, NULL, + ai_move, 0, NULL, // 40 + + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, WidowExplode // 44 +}; +mmove_t widow2_move_death = {FRAME_death01, FRAME_death44, widow2_frames_death, NULL}; + +void widow2_start_searching (edict_t *self); +void widow2_keep_searching (edict_t *self); +void widow2_finaldeath (edict_t *self); + +mframe_t widow2_frames_dead [] = +{ + ai_move, 0, widow2_start_searching, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, widow2_keep_searching +}; +mmove_t widow2_move_dead = {FRAME_dthsrh01, FRAME_dthsrh15, widow2_frames_dead, NULL}; + +mframe_t widow2_frames_really_dead [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 0, NULL, + ai_move, 0, widow2_finaldeath +}; +mmove_t widow2_move_really_dead = {FRAME_dthsrh16, FRAME_dthsrh22, widow2_frames_really_dead, NULL}; + +void widow2_start_searching (edict_t *self) +{ + self->count = 0; +} + +void widow2_keep_searching (edict_t *self) +{ + if (self->count <= 2) + { + self->monsterinfo.currentmove = &widow2_move_dead; + self->s.frame = FRAME_dthsrh01; + self->count++; + return; + } + + self->monsterinfo.currentmove = &widow2_move_really_dead; +} + +void widow2_finaldeath (edict_t *self) +{ + VectorSet (self->mins, -70, -70, 0); + VectorSet (self->maxs, 70, 70, 80); + self->movetype = MOVETYPE_TOSS; +// self->svflags |= SVF_DEADMONSTER; + self->takedamage = DAMAGE_YES; + self->nextthink = 0; + gi.linkentity (self); +} + +void widow2_stand (edict_t *self) +{ +// gi.dprintf ("widow2 stand\n"); + self->monsterinfo.currentmove = &widow2_move_stand; +} + +void widow2_run (edict_t *self) +{ + +// gi.dprintf ("widow2 run - %2.2f - %s \n", level.time, self->enemy->classname); + self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; + + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + self->monsterinfo.currentmove = &widow2_move_stand; + else + self->monsterinfo.currentmove = &widow2_move_run; +} + +void widow2_walk (edict_t *self) +{ + self->monsterinfo.currentmove = &widow2_move_walk; +} + +void widow2_melee (edict_t *self) +{ + self->monsterinfo.currentmove = &widow2_move_tongs; +} + +void widow2_attack (edict_t *self) +{ + float range, luck; + qboolean blocked = false; + + if (self->monsterinfo.aiflags & AI_BLOCKED) + { + blocked = true; + self->monsterinfo.aiflags &= ~AI_BLOCKED; + } + +// gi.dprintf ("widow2 attack\n"); + + if (!self->enemy) + return; + + if (self->bad_area) + { + if ((random() < 0.75) || (level.time < self->monsterinfo.attack_finished)) + self->monsterinfo.currentmove = &widow2_move_attack_pre_beam; + else + { + self->monsterinfo.currentmove = &widow2_move_attack_disrupt; + } + return; + } + + WidowCalcSlots(self); + + // if we can't see the target, spawn stuff + if ((self->monsterinfo.attack_state == AS_BLIND) && (SELF_SLOTS_LEFT >= 2)) + { + self->monsterinfo.currentmove = &widow2_move_spawn; + return; + } + + // accept bias towards spawning + if (blocked && (SELF_SLOTS_LEFT >= 2)) + { + self->monsterinfo.currentmove = &widow2_move_spawn; + return; + } + + range = realrange (self, self->enemy); + + if (range < 600) + { + luck = random(); + if (SELF_SLOTS_LEFT >= 2) + { + if (luck <= 0.40) + self->monsterinfo.currentmove = &widow2_move_attack_pre_beam; + else if ((luck <= 0.7) && !(level.time < self->monsterinfo.attack_finished)) + { +// gi.sound (self, CHAN_WEAPON, sound_disrupt, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &widow2_move_attack_disrupt; + } + else + self->monsterinfo.currentmove = &widow2_move_spawn; + } + else + { + if ((luck <= 0.50) || (level.time < self->monsterinfo.attack_finished)) + self->monsterinfo.currentmove = &widow2_move_attack_pre_beam; + else + { +// gi.sound (self, CHAN_WEAPON, sound_disrupt, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &widow2_move_attack_disrupt; + } + } + } + else + { + luck = random(); + if (SELF_SLOTS_LEFT >= 2) + { + if (luck < 0.3) + self->monsterinfo.currentmove = &widow2_move_attack_pre_beam; + else if ((luck < 0.65) || (level.time < self->monsterinfo.attack_finished)) + self->monsterinfo.currentmove = &widow2_move_spawn; + else + { +// gi.sound (self, CHAN_WEAPON, sound_disrupt, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &widow2_move_attack_disrupt; + } + } + else + { + if ((luck < 0.45) || (level.time < self->monsterinfo.attack_finished)) + self->monsterinfo.currentmove = &widow2_move_attack_pre_beam; + else + { +// gi.sound (self, CHAN_WEAPON, sound_disrupt, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &widow2_move_attack_disrupt; + } + } + } +} + +void widow2_attack_beam (edict_t *self) +{ + self->monsterinfo.currentmove = &widow2_move_attack_beam; +} + +void widow2_reattack_beam (edict_t *self) +{ + self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING; + + if ( infront(self, self->enemy) ) + if (random() <= 0.5) + if ((random() < 0.7) || (SELF_SLOTS_LEFT < 2)) + self->monsterinfo.currentmove = &widow2_move_attack_beam; + else + self->monsterinfo.currentmove = &widow2_move_spawn; + else + self->monsterinfo.currentmove = &widow2_move_attack_post_beam; + else + self->monsterinfo.currentmove = &widow2_move_attack_post_beam; +} + + + +void widow2_pain (edict_t *self, edict_t *other, float kick, int damage) +{ + if (self->health < (self->max_health / 2)) + self->s.skinnum = 1; + + if (skill->value == 3) + return; // no pain anims in nightmare + +// gi.dprintf ("widow2 pain\n"); + if (level.time < self->pain_debounce_time) + return; + + self->pain_debounce_time = level.time + 5; + + if (damage < 15) + { + gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NONE, 0); + } + else if (damage < 75) + { + gi.sound (self, CHAN_VOICE, sound_pain2, 1, ATTN_NONE, 0); + if ((skill->value < 3) && (random() < (0.6 - (0.2*((float)skill->value))))) + { + self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING; + self->monsterinfo.currentmove = &widow2_move_pain; + } + } + else + { + gi.sound (self, CHAN_VOICE, sound_pain3, 1, ATTN_NONE, 0); + if ((skill->value < 3) && (random() < (0.75 - (0.1*((float)skill->value))))) + { + self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING; + self->monsterinfo.currentmove = &widow2_move_pain; + } + } +} + +void widow2_dead (edict_t *self) +{ +} + +void KillChildren (edict_t *self) +{ + edict_t *ent; + int field; + + ent = NULL; + field = FOFS(classname); + while (1) + { + ent = G_Find (ent, field, "monster_stalker"); + if(!ent) + return; + + // FIXME - may need to stagger + if ((ent->inuse) && (ent->health > 0)) + T_Damage (ent, self, self, vec3_origin, self->enemy->s.origin, vec3_origin, (ent->health + 1), 0, DAMAGE_NO_KNOCKBACK, MOD_UNKNOWN); + } +} + +void widow2_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + int n; + int clipped; + +// check for gib + if (self->health <= self->gib_health) + { + clipped = min (damage, 100); + + gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0); + for (n= 0; n < 2; n++) + ThrowWidowGibLoc (self, "models/objects/gibs/bone/tris.md2", clipped, GIB_ORGANIC, NULL, false); + for (n= 0; n < 3; n++) + ThrowWidowGibLoc (self, "models/objects/gibs/sm_meat/tris.md2", clipped, GIB_ORGANIC, NULL, false); + for (n= 0; n < 3; n++) + { + ThrowWidowGibSized (self, "models/monsters/blackwidow2/gib1/tris.md2", clipped, GIB_METALLIC, NULL, + 0, false); + ThrowWidowGibSized (self, "models/monsters/blackwidow2/gib2/tris.md2", clipped, GIB_METALLIC, NULL, + gi.soundindex ("misc/fhit3.wav"), false); + } + for (n= 0; n < 2; n++) + { + ThrowWidowGibSized (self, "models/monsters/blackwidow2/gib3/tris.md2", clipped, GIB_METALLIC, NULL, + 0, false); + ThrowWidowGibSized (self, "models/monsters/blackwidow/gib3/tris.md2", clipped, GIB_METALLIC, NULL, + 0, false); + } + ThrowGib (self, "models/objects/gibs/chest/tris.md2", clipped, GIB_ORGANIC); + ThrowHead (self, "models/objects/gibs/head2/tris.md2", clipped, GIB_ORGANIC); + self->deadflag = DEAD_DEAD; + return; + } + + if (self->deadflag == DEAD_DEAD) + return; + + gi.sound (self, CHAN_VOICE, sound_death, 1, ATTN_NONE, 0); + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_NO; + self->count = 0; + KillChildren (self); + self->monsterinfo.quad_framenum = 0; + self->monsterinfo.double_framenum = 0; + self->monsterinfo.invincible_framenum = 0; + self->monsterinfo.currentmove = &widow2_move_death; +} + +qboolean Widow2_CheckAttack (edict_t *self) +{ + vec3_t spot1, spot2; + vec3_t temp; + float chance; + trace_t tr; + qboolean enemy_infront; + int enemy_range; + float enemy_yaw; + float real_enemy_range; + vec3_t f, r, u; + + if (!self->enemy) + return false; + + WidowPowerups(self); + + if ((random() < 0.8) && (SELF_SLOTS_LEFT >= 2) && (realrange(self, self->enemy) > 150)) + { + self->monsterinfo.aiflags |= AI_BLOCKED; + self->monsterinfo.attack_state = AS_MISSILE; + return true; + } + + if (self->enemy->health > 0) + { + // see if any entities are in the way of the shot + VectorCopy (self->s.origin, spot1); + spot1[2] += self->viewheight; + VectorCopy (self->enemy->s.origin, spot2); + spot2[2] += self->enemy->viewheight; + + tr = gi.trace (spot1, NULL, NULL, spot2, self, CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_SLIME|CONTENTS_LAVA); + + // do we have a clear shot? + if (tr.ent != self->enemy) + { + // go ahead and spawn stuff if we're mad a a client + if (self->enemy->client && SELF_SLOTS_LEFT >= 2) + { + self->monsterinfo.attack_state = AS_BLIND; + return true; + } + + // PGM - we want them to go ahead and shoot at info_notnulls if they can. + if(self->enemy->solid != SOLID_NOT || tr.fraction < 1.0) //PGM + return false; + } + } + + enemy_infront = infront(self, self->enemy); + + enemy_range = range(self, self->enemy); + VectorSubtract (self->enemy->s.origin, self->s.origin, temp); + enemy_yaw = vectoyaw2(temp); + + self->ideal_yaw = enemy_yaw; + + // melee attack + if (self->timestamp < level.time) + { + real_enemy_range = realrange (self, self->enemy); + if (real_enemy_range < 300) + { + AngleVectors (self->s.angles, f, r, u); + G_ProjectSource2 (self->s.origin, offsets[0], f, r, u, spot1); + VectorCopy (self->enemy->s.origin, spot2); + if (widow2_tongue_attack_ok(spot1, spot2, 256)) + { + // melee attack ok + + // be nice in easy mode + if (skill->value == 0 && (rand()&3) ) + return false; + + if (self->monsterinfo.melee) + self->monsterinfo.attack_state = AS_MELEE; + else + self->monsterinfo.attack_state = AS_MISSILE; + return true; + } + } + } + + if (level.time < self->monsterinfo.attack_finished) + return false; + + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + { + chance = 0.4; + } + else if (enemy_range == RANGE_NEAR) + { + chance = 0.8; + } + else if (enemy_range == RANGE_MID) + { + chance = 0.8; + } + else if (enemy_range == RANGE_FAR) + { + chance = 0.5; + } + + // PGM - go ahead and shoot every time if it's a info_notnull + if ((random () < chance) || (self->enemy->solid == SOLID_NOT)) + { + self->monsterinfo.attack_state = AS_MISSILE; +// self->monsterinfo.attack_finished = level.time + 1.0 + 2*random(); + return true; + } + + return false; +} + +void Widow2Precache () +{ + // cache in all of the stalker stuff, widow stuff, spawngro stuff, gibs + gi.soundindex ("parasite/parpain1.wav"); + gi.soundindex ("parasite/parpain2.wav"); + gi.soundindex ("parasite/pardeth1.wav"); + gi.soundindex ("parasite/paratck1.wav"); + gi.soundindex ("parasite/parsght1.wav"); + gi.soundindex ("infantry/melee2.wav"); + gi.soundindex ("misc/fhit3.wav"); + + gi.soundindex ("tank/tnkatck3.wav"); + gi.soundindex ("weapons/disrupt.wav"); + gi.soundindex ("weapons/disint2.wav"); + + gi.modelindex ("models/monsters/stalker/tris.md2"); + gi.modelindex ("models/items/spawngro2/tris.md2"); + gi.modelindex ("models/objects/gibs/sm_metal/tris.md2"); + gi.modelindex ("models/proj/laser2/tris.md2"); + gi.modelindex ("models/proj/disintegrator/tris.md2"); + + gi.modelindex ("models/monsters/blackwidow/gib1/tris.md2"); + gi.modelindex ("models/monsters/blackwidow/gib2/tris.md2"); + gi.modelindex ("models/monsters/blackwidow/gib3/tris.md2"); + gi.modelindex ("models/monsters/blackwidow/gib4/tris.md2"); + gi.modelindex ("models/monsters/blackwidow2/gib1/tris.md2"); + gi.modelindex ("models/monsters/blackwidow2/gib2/tris.md2"); + gi.modelindex ("models/monsters/blackwidow2/gib3/tris.md2"); + gi.modelindex ("models/monsters/blackwidow2/gib4/tris.md2"); +} + +/*QUAKED monster_widow2 (1 .5 0) (-70 -70 0) (70 70 144) Ambush Trigger_Spawn Sight +*/ +void SP_monster_widow2 (edict_t *self) +{ + if (deathmatch->value) + { + G_FreeEdict (self); + return; + } + + sound_pain1 = gi.soundindex ("widow/bw2pain1.wav"); + sound_pain2 = gi.soundindex ("widow/bw2pain2.wav"); + sound_pain3 = gi.soundindex ("widow/bw2pain3.wav"); + sound_death = gi.soundindex ("widow/death.wav"); + sound_search1 = gi.soundindex ("bosshovr/bhvunqv1.wav"); +// sound_disrupt = gi.soundindex ("gladiator/railgun.wav"); + sound_tentacles_retract = gi.soundindex ("brain/brnatck3.wav"); + +// self->s.sound = gi.soundindex ("bosshovr/bhvengn1.wav"); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex ("models/monsters/blackwidow2/tris.md2"); + VectorSet (self->mins, -70, -70, 0); + VectorSet (self->maxs, 70, 70, 144); + + self->health = 2000 + 800 + 1000*(skill->value); + if (coop->value) + self->health += 500*(skill->value); +// self->health = 1; + self->gib_health = -900; + self->mass = 2500; + +/* if (skill->value == 2) + { + self->monsterinfo.power_armor_type = POWER_ARMOR_SHIELD; + self->monsterinfo.power_armor_power = 500; + } + else */if (skill->value == 3) + { + self->monsterinfo.power_armor_type = POWER_ARMOR_SHIELD; + self->monsterinfo.power_armor_power = 750; + } + + self->yaw_speed = 30; + + self->flags |= FL_IMMUNE_LASER; + self->monsterinfo.aiflags |= AI_IGNORE_SHOTS; + + self->pain = widow2_pain; + self->die = widow2_die; + + self->monsterinfo.melee = widow2_melee; + self->monsterinfo.stand = widow2_stand; + self->monsterinfo.walk = widow2_walk; + self->monsterinfo.run = widow2_run; + self->monsterinfo.attack = widow2_attack; + self->monsterinfo.search = widow2_search; + self->monsterinfo.checkattack = Widow2_CheckAttack; + gi.linkentity (self); + + self->monsterinfo.currentmove = &widow2_move_stand; + self->monsterinfo.scale = MODEL_SCALE; + + Widow2Precache(); + WidowCalcSlots(self); + walkmonster_start (self); +} + +// +// Death sequence stuff +// + +void WidowVelocityForDamage (int damage, vec3_t v) +{ + v[0] = damage * crandom(); + v[1] = damage * crandom(); + v[2] = damage * crandom() + 200.0; +} + +void widow_gib_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + + self->solid = SOLID_NOT; + self->touch = NULL; + self->s.angles[PITCH] = 0; + self->s.angles[ROLL] = 0; + VectorClear (self->avelocity); + + if (self->plat2flags) + gi.sound (self, CHAN_VOICE, self->plat2flags, 1, ATTN_NORM, 0); +/* + if (plane) + { + if (plane->normal[2] < -0.8) + { + gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/fhit3.wav"), 1, ATTN_NORM, 0); + } + + //vectoangles (plane->normal, normal_angles); + //AngleVectors (normal_angles, NULL, right, NULL); + //vectoangles (right, self->s.angles); + //VectorClear (self->avelocity); + } +*/ +} + +void ThrowWidowGib (edict_t *self, char *gibname, int damage, int type) +{ + ThrowWidowGibReal (self, gibname, damage, type, NULL, false, 0, true); +} + +void ThrowWidowGibLoc (edict_t *self, char *gibname, int damage, int type, vec3_t startpos, qboolean fade) +{ + ThrowWidowGibReal (self, gibname, damage, type, startpos, false, 0, fade); +} + +void ThrowWidowGibSized (edict_t *self, char *gibname, int damage, int type, vec3_t startpos, int hitsound, qboolean fade) +{ + ThrowWidowGibReal (self, gibname, damage, type, startpos, true, hitsound, fade); +} + +void ThrowWidowGibReal (edict_t *self, char *gibname, int damage, int type, vec3_t startpos, qboolean sized, int hitsound, qboolean fade) +{ + edict_t *gib; + vec3_t vd; + vec3_t origin; + vec3_t size; + float vscale; + + if (!gibname) + return; + + gib = G_Spawn(); + + if (startpos) + VectorCopy (startpos, gib->s.origin); + else + { + VectorScale (self->size, 0.5, size); + VectorAdd (self->absmin, size, origin); + gib->s.origin[0] = origin[0] + crandom() * size[0]; + gib->s.origin[1] = origin[1] + crandom() * size[1]; + gib->s.origin[2] = origin[2] + crandom() * size[2]; + } + + gib->solid = SOLID_NOT; + gib->s.effects |= EF_GIB; + gib->flags |= FL_NO_KNOCKBACK; + gib->takedamage = DAMAGE_YES; + gib->die = gib_die; + gib->s.renderfx |= RF_IR_VISIBLE; + + if (fade) + { + gib->think = G_FreeEdict; + // sized gibs last longer + if (sized) + gib->nextthink = level.time + 20 + random()*15; + else + gib->nextthink = level.time + 5 + random()*10; + } + else + { + gib->think = G_FreeEdict; + // sized gibs last longer + if (sized) + gib->nextthink = level.time + 60 + random()*15; + else + gib->nextthink = level.time + 25 + random()*10; + } + + if (type == GIB_ORGANIC) + { + gib->movetype = MOVETYPE_TOSS; + gib->touch = gib_touch; + vscale = 0.5; + } + else + { + gib->movetype = MOVETYPE_BOUNCE; + vscale = 1.0; + } + + WidowVelocityForDamage (damage, vd); + VectorMA (self->velocity, vscale, vd, gib->velocity); + ClipGibVelocity (gib); + + gi.setmodel (gib, gibname); + + if (sized) + { + gib->plat2flags = hitsound; + gib->solid = SOLID_BBOX; + gib->avelocity[0] = random()*400; + gib->avelocity[1] = random()*400; + gib->avelocity[2] = random()*200; + if (gib->velocity[2] < 0) + gib->velocity[2] *= -1; + gib->velocity[0] *= 2; + gib->velocity[1] *= 2; + ClipGibVelocity (gib); + gib->velocity[2] = max((350 + (random()*100.0)), gib->velocity[2]); + gib->gravity = 0.25; + gib->touch = widow_gib_touch; + gib->owner = self; + if (gib->s.modelindex == gi.modelindex ("models/monsters/blackwidow2/gib2/tris.md2")) + { + VectorSet (gib->mins, -10, -10, 0); + VectorSet (gib->maxs, 10, 10, 10); + } + else + { + VectorSet (gib->mins, -5, -5, 0); + VectorSet (gib->maxs, 5, 5, 5); + } + } + else + { + gib->velocity[0] *= 2; + gib->velocity[1] *= 2; + gib->avelocity[0] = random()*600; + gib->avelocity[1] = random()*600; + gib->avelocity[2] = random()*600; + } + +// gib->think = G_FreeEdict; +// gib->nextthink = level.time + 10 + random()*10; + + gi.linkentity (gib); +} + +void BloodFountain (edict_t *self, int number, vec3_t startpos, int damage) +{ + int n; + vec3_t vd; + vec3_t origin, size, velocity; + + return; + + for (n= 0; n < number; n++) + { + if (startpos) + VectorCopy (startpos, origin); + else + { + VectorScale (self->size, 0.5, size); + VectorAdd (self->absmin, size, origin); + origin[0] = origin[0] + crandom() * size[0]; + origin[1] = origin[1] + crandom() * size[1]; + origin[2] = origin[2] + crandom() * size[2]; + } + + WidowVelocityForDamage (damage, vd); + VectorMA (self->velocity, 1.0, vd, velocity); + velocity[0] *= 2; + velocity[1] *= 2; + +// gi.WriteByte (svc_temp_entity); +// gi.WriteByte (TE_BLOOD_FOUNTAIN); +// gi.WritePosition (origin); +// gi.WritePosition (velocity); +// gi.WriteShort (50); +// gi.multicast (self->s.origin, MULTICAST_ALL); + } +} + +void ThrowSmallStuff (edict_t *self, vec3_t point) +{ + int n; + + for (n= 0; n < 2; n++) + ThrowWidowGibLoc (self, "models/objects/gibs/sm_meat/tris.md2", 300, GIB_ORGANIC, point, false); + ThrowWidowGibLoc (self, "models/objects/gibs/sm_metal/tris.md2", 300, GIB_METALLIC, point, false); + ThrowWidowGibLoc (self, "models/objects/gibs/sm_metal/tris.md2", 100, GIB_METALLIC, point, false); + +} + +void ThrowMoreStuff (edict_t *self, vec3_t point) +{ + int n; + + if (coop && coop->value) + { + ThrowSmallStuff (self, point); + return; + } + + for (n= 0; n < 1; n++) + ThrowWidowGibLoc (self, "models/objects/gibs/sm_meat/tris.md2", 300, GIB_ORGANIC, point, false); + for (n= 0; n < 2; n++) + ThrowWidowGibLoc (self, "models/objects/gibs/sm_metal/tris.md2", 300, GIB_METALLIC, point, false); + for (n= 0; n < 3; n++) + ThrowWidowGibLoc (self, "models/objects/gibs/sm_metal/tris.md2", 100, GIB_METALLIC, point, false); + +} + +void WidowExplode (edict_t *self) +{ + vec3_t org; + int n; + + self->think = WidowExplode; +// gi.dprintf ("count = %d\n"); + +//redo: + VectorCopy (self->s.origin, org); + org[2] += 24 + (rand()&15); + if (self->count < 8) + org[2] += 24 + (rand()&31); + switch (self->count) + { + case 0: + org[0] -= 24; + org[1] -= 24; + break; + case 1: + org[0] += 24; + org[1] += 24; + ThrowSmallStuff(self, org); + break; + case 2: + org[0] += 24; + org[1] -= 24; + break; + case 3: + org[0] -= 24; + org[1] += 24; + ThrowMoreStuff(self, org); + break; + case 4: + org[0] -= 48; + org[1] -= 48; + break; + case 5: + org[0] += 48; + org[1] += 48; + ThrowArm1 (self); + break; + case 6: + org[0] -= 48; + org[1] += 48; + ThrowArm2 (self); + break; + case 7: + org[0] += 48; + org[1] -= 48; + ThrowSmallStuff(self, org); + break; + case 8: + org[0] += 18; + org[1] += 18; + org[2] = self->s.origin[2] + 48; + ThrowMoreStuff(self, org); + break; + case 9: + org[0] -= 18; + org[1] += 18; + org[2] = self->s.origin[2] + 48; + break; + case 10: + org[0] += 18; + org[1] -= 18; + org[2] = self->s.origin[2] + 48; + break; + case 11: + org[0] -= 18; + org[1] -= 18; + org[2] = self->s.origin[2] + 48; + break; + case 12: + self->s.sound = 0; + for (n= 0; n < 1; n++) + ThrowWidowGib (self, "models/objects/gibs/sm_meat/tris.md2", 400, GIB_ORGANIC); + for (n= 0; n < 2; n++) + ThrowWidowGib (self, "models/objects/gibs/sm_metal/tris.md2", 100, GIB_METALLIC); + for (n= 0; n < 2; n++) + ThrowWidowGib (self, "models/objects/gibs/sm_metal/tris.md2", 400, GIB_METALLIC); +// ThrowGib (self, "models/objects/gibs/chest/tris.md2", 1000, GIB_ORGANIC); +// ThrowHead (self, "models/objects/gibs/gear/tris.md2", 1000, GIB_METALLIC); + self->deadflag = DEAD_DEAD; + self->think = monster_think; + self->nextthink = level.time + 0.1; + self->monsterinfo.currentmove = &widow2_move_dead; + return; + } + + self->count++; + if (self->count >=9 && self->count <=12) + { + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_EXPLOSION1_BIG); + gi.WritePosition (org); + gi.multicast (self->s.origin, MULTICAST_ALL); +// goto redo; + } + else + { + // else + gi.WriteByte (svc_temp_entity); + if (self->count %2) + gi.WriteByte (TE_EXPLOSION1); + else + gi.WriteByte (TE_EXPLOSION1_NP); + gi.WritePosition (org); + gi.multicast (self->s.origin, MULTICAST_ALL); + } + + self->nextthink = level.time + 0.1; +} + +void WidowExplosion1 (edict_t *self) +{ + int n; + vec3_t f,r,u, startpoint; + vec3_t offset = {23.74, -37.67, 76.96}; + +// gi.dprintf ("1\n"); + AngleVectors (self->s.angles, f, r, u); + G_ProjectSource2 (self->s.origin, offset, f, r, u, startpoint); + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_EXPLOSION1); + gi.WritePosition (startpoint); + gi.multicast (self->s.origin, MULTICAST_ALL); + + for (n= 0; n < 1; n++) + ThrowWidowGibLoc (self, "models/objects/gibs/sm_meat/tris.md2", 300, GIB_ORGANIC, startpoint, false); + for (n= 0; n < 1; n++) + ThrowWidowGibLoc (self, "models/objects/gibs/sm_metal/tris.md2", 100, GIB_METALLIC, startpoint, false); + for (n= 0; n < 2; n++) + ThrowWidowGibLoc (self, "models/objects/gibs/sm_metal/tris.md2", 300, GIB_METALLIC, startpoint, false); +} + +void WidowExplosion2 (edict_t *self) +{ + int n; + vec3_t f,r,u, startpoint; + vec3_t offset = {-20.49, 36.92, 73.52}; + +// gi.dprintf ("2\n"); + + AngleVectors (self->s.angles, f, r, u); + G_ProjectSource2 (self->s.origin, offset, f, r, u, startpoint); + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_EXPLOSION1); + gi.WritePosition (startpoint); + gi.multicast (self->s.origin, MULTICAST_ALL); + + for (n= 0; n < 1; n++) + ThrowWidowGibLoc (self, "models/objects/gibs/sm_meat/tris.md2", 300, GIB_ORGANIC, startpoint, false); + for (n= 0; n < 1; n++) + ThrowWidowGibLoc (self, "models/objects/gibs/sm_metal/tris.md2", 100, GIB_METALLIC, startpoint, false); + for (n= 0; n < 2; n++) + ThrowWidowGibLoc (self, "models/objects/gibs/sm_metal/tris.md2", 300, GIB_METALLIC, startpoint, false); +} + +void WidowExplosion3 (edict_t *self) +{ + int n; + vec3_t f,r,u, startpoint; + vec3_t offset = {2.11, 0.05, 92.20}; + +// gi.dprintf ("3\n"); + + AngleVectors (self->s.angles, f, r, u); + G_ProjectSource2 (self->s.origin, offset, f, r, u, startpoint); + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_EXPLOSION1); + gi.WritePosition (startpoint); + gi.multicast (self->s.origin, MULTICAST_ALL); + + for (n= 0; n < 1; n++) + ThrowWidowGibLoc (self, "models/objects/gibs/sm_meat/tris.md2", 300, GIB_ORGANIC, startpoint, false); + for (n= 0; n < 1; n++) + ThrowWidowGibLoc (self, "models/objects/gibs/sm_metal/tris.md2", 100, GIB_METALLIC, startpoint, false); + for (n= 0; n < 2; n++) + ThrowWidowGibLoc (self, "models/objects/gibs/sm_metal/tris.md2", 300, GIB_METALLIC, startpoint, false); +} + +void WidowExplosion4 (edict_t *self) +{ + int n; + vec3_t f,r,u, startpoint; + vec3_t offset = {-28.04, -35.57, -77.56}; + +// gi.dprintf ("4\n"); + + AngleVectors (self->s.angles, f, r, u); + G_ProjectSource2 (self->s.origin, offset, f, r, u, startpoint); + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_EXPLOSION1); + gi.WritePosition (startpoint); + gi.multicast (self->s.origin, MULTICAST_ALL); + + for (n= 0; n < 1; n++) + ThrowWidowGibLoc (self, "models/objects/gibs/sm_meat/tris.md2", 300, GIB_ORGANIC, startpoint, false); + for (n= 0; n < 1; n++) + ThrowWidowGibLoc (self, "models/objects/gibs/sm_metal/tris.md2", 100, GIB_METALLIC, startpoint, false); + for (n= 0; n < 2; n++) + ThrowWidowGibLoc (self, "models/objects/gibs/sm_metal/tris.md2", 300, GIB_METALLIC, startpoint, false); +} + +void WidowExplosion5 (edict_t *self) +{ + int n; + vec3_t f,r,u, startpoint; + vec3_t offset = {-20.11, -1.11, 40.76}; + +// gi.dprintf ("5\n"); + + AngleVectors (self->s.angles, f, r, u); + G_ProjectSource2 (self->s.origin, offset, f, r, u, startpoint); + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_EXPLOSION1); + gi.WritePosition (startpoint); + gi.multicast (self->s.origin, MULTICAST_ALL); + + for (n= 0; n < 1; n++) + ThrowWidowGibLoc (self, "models/objects/gibs/sm_meat/tris.md2", 300, GIB_ORGANIC, startpoint, false); + for (n= 0; n < 1; n++) + ThrowWidowGibLoc (self, "models/objects/gibs/sm_metal/tris.md2", 100, GIB_METALLIC, startpoint, false); + for (n= 0; n < 2; n++) + ThrowWidowGibLoc (self, "models/objects/gibs/sm_metal/tris.md2", 300, GIB_METALLIC, startpoint, false); +} + +void WidowExplosion6 (edict_t *self) +{ + int n; + vec3_t f,r,u, startpoint; + vec3_t offset = {-20.11, -1.11, 40.76}; + + //gi.dprintf ("6\n"); + + AngleVectors (self->s.angles, f, r, u); + G_ProjectSource2 (self->s.origin, offset, f, r, u, startpoint); + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_EXPLOSION1); + gi.WritePosition (startpoint); + gi.multicast (self->s.origin, MULTICAST_ALL); + + for (n= 0; n < 1; n++) + ThrowWidowGibLoc (self, "models/objects/gibs/sm_meat/tris.md2", 300, GIB_ORGANIC, startpoint, false); + for (n= 0; n < 1; n++) + ThrowWidowGibLoc (self, "models/objects/gibs/sm_metal/tris.md2", 100, GIB_METALLIC, startpoint, false); + for (n= 0; n < 2; n++) + ThrowWidowGibLoc (self, "models/objects/gibs/sm_metal/tris.md2", 300, GIB_METALLIC, startpoint, false); +} + +void WidowExplosion7 (edict_t *self) +{ + int n; + vec3_t f,r,u, startpoint; + vec3_t offset = {-20.11, -1.11, 40.76}; + + //gi.dprintf ("7\n"); + + AngleVectors (self->s.angles, f, r, u); + G_ProjectSource2 (self->s.origin, offset, f, r, u, startpoint); + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_EXPLOSION1); + gi.WritePosition (startpoint); + gi.multicast (self->s.origin, MULTICAST_ALL); + + for (n= 0; n < 1; n++) + ThrowWidowGibLoc (self, "models/objects/gibs/sm_meat/tris.md2", 300, GIB_ORGANIC, startpoint, false); + for (n= 0; n < 1; n++) + ThrowWidowGibLoc (self, "models/objects/gibs/sm_metal/tris.md2", 100, GIB_METALLIC, startpoint, false); + for (n= 0; n < 2; n++) + ThrowWidowGibLoc (self, "models/objects/gibs/sm_metal/tris.md2", 300, GIB_METALLIC, startpoint, false); +} + +void WidowExplosionLeg (edict_t *self) +{ +// int n; + vec3_t f,r,u, startpoint; + vec3_t offset1 = {-31.89, -47.86, 67.02}; + vec3_t offset2 = {-44.9, -82.14, 54.72}; + + //gi.dprintf ("Leg\n"); + + AngleVectors (self->s.angles, f, r, u); + G_ProjectSource2 (self->s.origin, offset1, f, r, u, startpoint); + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_EXPLOSION1_BIG); + gi.WritePosition (startpoint); + gi.multicast (self->s.origin, MULTICAST_ALL); + + ThrowWidowGibSized (self, "models/monsters/blackwidow2/gib2/tris.md2", 200, GIB_METALLIC, startpoint, + gi.soundindex ("misc/fhit3.wav"), false); + ThrowWidowGibLoc (self, "models/objects/gibs/sm_meat/tris.md2", 300, GIB_ORGANIC, startpoint, false); + ThrowWidowGibLoc (self, "models/objects/gibs/sm_metal/tris.md2", 100, GIB_METALLIC, startpoint, false); + + G_ProjectSource2 (self->s.origin, offset2, f, r, u, startpoint); + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_EXPLOSION1); + gi.WritePosition (startpoint); + gi.multicast (self->s.origin, MULTICAST_ALL); + + ThrowWidowGibSized (self, "models/monsters/blackwidow2/gib1/tris.md2", 300, GIB_METALLIC, startpoint, + gi.soundindex ("misc/fhit3.wav"), false); + ThrowWidowGibLoc (self, "models/objects/gibs/sm_meat/tris.md2", 300, GIB_ORGANIC, startpoint, false); + ThrowWidowGibLoc (self, "models/objects/gibs/sm_metal/tris.md2", 100, GIB_METALLIC, startpoint, false); +} + +void ThrowArm1 (edict_t *self) +{ + int n; + vec3_t f,r,u, startpoint; + vec3_t offset1 = {65.76, 17.52, 7.56}; + + AngleVectors (self->s.angles, f, r, u); + G_ProjectSource2 (self->s.origin, offset1, f, r, u, startpoint); + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_EXPLOSION1_BIG); + gi.WritePosition (startpoint); + gi.multicast (self->s.origin, MULTICAST_ALL); + + for (n= 0; n < 2; n++) + ThrowWidowGibLoc (self, "models/objects/gibs/sm_metal/tris.md2", 100, GIB_METALLIC, startpoint, false); +} + +void ThrowArm2 (edict_t *self) +{ +// int n; + vec3_t f,r,u, startpoint; + vec3_t offset1 = {65.76, 17.52, 7.56}; + + AngleVectors (self->s.angles, f, r, u); + G_ProjectSource2 (self->s.origin, offset1, f, r, u, startpoint); + + ThrowWidowGibSized (self, "models/monsters/blackwidow2/gib4/tris.md2", 200, GIB_METALLIC, startpoint, + gi.soundindex ("misc/fhit3.wav"), false); + ThrowWidowGibLoc (self, "models/objects/gibs/sm_meat/tris.md2", 300, GIB_ORGANIC, startpoint, false); +} diff --git a/original/rogue/m_widow2.h b/original/rogue/m_widow2.h new file mode 100644 index 0000000..fcecfa2 --- /dev/null +++ b/original/rogue/m_widow2.h @@ -0,0 +1,134 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// G:\quake2\xpack\models/monsters/blackwidow2 + +// This file generated by qdata - Do NOT Modify + +#define FRAME_blackwidow3 0 +#define FRAME_walk01 1 +#define FRAME_walk02 2 +#define FRAME_walk03 3 +#define FRAME_walk04 4 +#define FRAME_walk05 5 +#define FRAME_walk06 6 +#define FRAME_walk07 7 +#define FRAME_walk08 8 +#define FRAME_walk09 9 +#define FRAME_spawn01 10 +#define FRAME_spawn02 11 +#define FRAME_spawn03 12 +#define FRAME_spawn04 13 +#define FRAME_spawn05 14 +#define FRAME_spawn06 15 +#define FRAME_spawn07 16 +#define FRAME_spawn08 17 +#define FRAME_spawn09 18 +#define FRAME_spawn10 19 +#define FRAME_spawn11 20 +#define FRAME_spawn12 21 +#define FRAME_spawn13 22 +#define FRAME_spawn14 23 +#define FRAME_spawn15 24 +#define FRAME_spawn16 25 +#define FRAME_spawn17 26 +#define FRAME_spawn18 27 +#define FRAME_firea01 28 +#define FRAME_firea02 29 +#define FRAME_firea03 30 +#define FRAME_firea04 31 +#define FRAME_firea05 32 +#define FRAME_firea06 33 +#define FRAME_firea07 34 +#define FRAME_fireb01 35 +#define FRAME_fireb02 36 +#define FRAME_fireb03 37 +#define FRAME_fireb04 38 +#define FRAME_fireb05 39 +#define FRAME_fireb06 40 +#define FRAME_fireb07 41 +#define FRAME_fireb08 42 +#define FRAME_fireb09 43 +#define FRAME_fireb10 44 +#define FRAME_fireb11 45 +#define FRAME_fireb12 46 +#define FRAME_tongs01 47 +#define FRAME_tongs02 48 +#define FRAME_tongs03 49 +#define FRAME_tongs04 50 +#define FRAME_tongs05 51 +#define FRAME_tongs06 52 +#define FRAME_tongs07 53 +#define FRAME_tongs08 54 +#define FRAME_pain01 55 +#define FRAME_pain02 56 +#define FRAME_pain03 57 +#define FRAME_pain04 58 +#define FRAME_pain05 59 +#define FRAME_death01 60 +#define FRAME_death02 61 +#define FRAME_death03 62 +#define FRAME_death04 63 +#define FRAME_death05 64 +#define FRAME_death06 65 +#define FRAME_death07 66 +#define FRAME_death08 67 +#define FRAME_death09 68 +#define FRAME_death10 69 +#define FRAME_death11 70 +#define FRAME_death12 71 +#define FRAME_death13 72 +#define FRAME_death14 73 +#define FRAME_death15 74 +#define FRAME_death16 75 +#define FRAME_death17 76 +#define FRAME_death18 77 +#define FRAME_death19 78 +#define FRAME_death20 79 +#define FRAME_death21 80 +#define FRAME_death22 81 +#define FRAME_death23 82 +#define FRAME_death24 83 +#define FRAME_death25 84 +#define FRAME_death26 85 +#define FRAME_death27 86 +#define FRAME_death28 87 +#define FRAME_death29 88 +#define FRAME_death30 89 +#define FRAME_death31 90 +#define FRAME_death32 91 +#define FRAME_death33 92 +#define FRAME_death34 93 +#define FRAME_death35 94 +#define FRAME_death36 95 +#define FRAME_death37 96 +#define FRAME_death38 97 +#define FRAME_death39 98 +#define FRAME_death40 99 +#define FRAME_death41 100 +#define FRAME_death42 101 +#define FRAME_death43 102 +#define FRAME_death44 103 +#define FRAME_dthsrh01 104 +#define FRAME_dthsrh02 105 +#define FRAME_dthsrh03 106 +#define FRAME_dthsrh04 107 +#define FRAME_dthsrh05 108 +#define FRAME_dthsrh06 109 +#define FRAME_dthsrh07 110 +#define FRAME_dthsrh08 111 +#define FRAME_dthsrh09 112 +#define FRAME_dthsrh10 113 +#define FRAME_dthsrh11 114 +#define FRAME_dthsrh12 115 +#define FRAME_dthsrh13 116 +#define FRAME_dthsrh14 117 +#define FRAME_dthsrh15 118 +#define FRAME_dthsrh16 119 +#define FRAME_dthsrh17 120 +#define FRAME_dthsrh18 121 +#define FRAME_dthsrh19 122 +#define FRAME_dthsrh20 123 +#define FRAME_dthsrh21 124 +#define FRAME_dthsrh22 125 + +#define MODEL_SCALE 2.000000 diff --git a/original/rogue/p_client.c b/original/rogue/p_client.c new file mode 100644 index 0000000..48ea5ee --- /dev/null +++ b/original/rogue/p_client.c @@ -0,0 +1,2199 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +#include "g_local.h" +#include "m_player.h" + +void ClientUserinfoChanged (edict_t *ent, char *userinfo); + +void SP_misc_teleporter_dest (edict_t *ent); + +// +// Gross, ugly, disgustuing hack section +// + +// this function is an ugly as hell hack to fix some map flaws +// +// the coop spawn spots on some maps are SNAFU. There are coop spots +// with the wrong targetname as well as spots with no name at all +// +// we use carnal knowledge of the maps to fix the coop spot targetnames to match +// that of the nearest named single player spot + +static void SP_FixCoopSpots (edict_t *self) +{ + edict_t *spot; + vec3_t d; + + spot = NULL; + + while(1) + { + spot = G_Find(spot, FOFS(classname), "info_player_start"); + if (!spot) + return; + if (!spot->targetname) + continue; + VectorSubtract(self->s.origin, spot->s.origin, d); + if (VectorLength(d) < 384) + { + if ((!self->targetname) || Q_stricmp(self->targetname, spot->targetname) != 0) + { +// gi.dprintf("FixCoopSpots changed %s at %s targetname from %s to %s\n", self->classname, vtos(self->s.origin), self->targetname, spot->targetname); + self->targetname = spot->targetname; + } + return; + } + } +} + +// now if that one wasn't ugly enough for you then try this one on for size +// some maps don't have any coop spots at all, so we need to create them +// where they should have been + +static void SP_CreateCoopSpots (edict_t *self) +{ + edict_t *spot; + + if(Q_stricmp(level.mapname, "security") == 0) + { + spot = G_Spawn(); + spot->classname = "info_player_coop"; + spot->s.origin[0] = 188 - 64; + spot->s.origin[1] = -164; + spot->s.origin[2] = 80; + spot->targetname = "jail3"; + spot->s.angles[1] = 90; + + spot = G_Spawn(); + spot->classname = "info_player_coop"; + spot->s.origin[0] = 188 + 64; + spot->s.origin[1] = -164; + spot->s.origin[2] = 80; + spot->targetname = "jail3"; + spot->s.angles[1] = 90; + + spot = G_Spawn(); + spot->classname = "info_player_coop"; + spot->s.origin[0] = 188 + 128; + spot->s.origin[1] = -164; + spot->s.origin[2] = 80; + spot->targetname = "jail3"; + spot->s.angles[1] = 90; + + return; + } +} + + +/*QUAKED info_player_start (1 0 0) (-16 -16 -24) (16 16 32) +The normal starting point for a level. +*/ +void SP_info_player_start(edict_t *self) +{ + if (!coop->value) + return; + if(Q_stricmp(level.mapname, "security") == 0) + { + // invoke one of our gross, ugly, disgusting hacks + self->think = SP_CreateCoopSpots; + self->nextthink = level.time + FRAMETIME; + } +} + +/*QUAKED info_player_deathmatch (1 0 1) (-16 -16 -24) (16 16 32) +potential spawning position for deathmatch games +*/ +void SP_info_player_deathmatch(edict_t *self) +{ + if (!deathmatch->value) + { + G_FreeEdict (self); + return; + } + SP_misc_teleporter_dest (self); +} + +/*QUAKED info_player_coop (1 0 1) (-16 -16 -24) (16 16 32) +potential spawning position for coop games +*/ + +void SP_info_player_coop(edict_t *self) +{ + if (!coop->value) + { + G_FreeEdict (self); + return; + } + + if((Q_stricmp(level.mapname, "jail2") == 0) || + (Q_stricmp(level.mapname, "jail4") == 0) || + (Q_stricmp(level.mapname, "mine1") == 0) || + (Q_stricmp(level.mapname, "mine2") == 0) || + (Q_stricmp(level.mapname, "mine3") == 0) || + (Q_stricmp(level.mapname, "mine4") == 0) || + (Q_stricmp(level.mapname, "lab") == 0) || + (Q_stricmp(level.mapname, "boss1") == 0) || + (Q_stricmp(level.mapname, "fact3") == 0) || + (Q_stricmp(level.mapname, "biggun") == 0) || + (Q_stricmp(level.mapname, "space") == 0) || + (Q_stricmp(level.mapname, "command") == 0) || + (Q_stricmp(level.mapname, "power2") == 0) || + (Q_stricmp(level.mapname, "strike") == 0)) + { + // invoke one of our gross, ugly, disgusting hacks + self->think = SP_FixCoopSpots; + self->nextthink = level.time + FRAMETIME; + } +} + +/*QUAKED info_player_coop_lava (1 0 1) (-16 -16 -24) (16 16 32) +potential spawning position for coop games on rmine2 where lava level +needs to be checked +*/ +void SP_info_player_coop_lava(edict_t *self) +{ + if (!coop->value) + { + G_FreeEdict (self); + return; + } +} + +/*QUAKED info_player_intermission (1 0 1) (-16 -16 -24) (16 16 32) +The deathmatch intermission point will be at one of these +Use 'angles' instead of 'angle', so you can set pitch or roll as well as yaw. 'pitch yaw roll' +*/ +void SP_info_player_intermission(void) +{ +} + + +//======================================================================= + + +void player_pain (edict_t *self, edict_t *other, float kick, int damage) +{ + // player pain is handled at the end of the frame in P_DamageFeedback +} + + +qboolean IsFemale (edict_t *ent) +{ + char *info; + + if (!ent->client) + return false; + + info = Info_ValueForKey (ent->client->pers.userinfo, "gender"); + if (info[0] == 'f' || info[0] == 'F') + return true; + return false; +} + +qboolean IsNeutral (edict_t *ent) +{ + char *info; + + if (!ent->client) + return false; + + info = Info_ValueForKey (ent->client->pers.userinfo, "gender"); + if (info[0] != 'f' && info[0] != 'F' && info[0] != 'm' && info[0] != 'M') + return true; + return false; +} + +void ClientObituary (edict_t *self, edict_t *inflictor, edict_t *attacker) +{ + int mod; + char *message; + char *message2; + qboolean ff; + + if (coop->value && attacker->client) + meansOfDeath |= MOD_FRIENDLY_FIRE; + + if (deathmatch->value || coop->value) + { + ff = meansOfDeath & MOD_FRIENDLY_FIRE; + mod = meansOfDeath & ~MOD_FRIENDLY_FIRE; + message = NULL; + message2 = ""; + + switch (mod) + { + case MOD_SUICIDE: + message = "suicides"; + break; + case MOD_FALLING: + message = "cratered"; + break; + case MOD_CRUSH: + message = "was squished"; + break; + case MOD_WATER: + message = "sank like a rock"; + break; + case MOD_SLIME: + message = "melted"; + break; + case MOD_LAVA: + message = "does a back flip into the lava"; + break; + case MOD_EXPLOSIVE: + case MOD_BARREL: + message = "blew up"; + break; + case MOD_EXIT: + message = "found a way out"; + break; + case MOD_TARGET_LASER: + message = "saw the light"; + break; + case MOD_TARGET_BLASTER: + message = "got blasted"; + break; + case MOD_BOMB: + case MOD_SPLASH: + case MOD_TRIGGER_HURT: + message = "was in the wrong place"; + break; + } + if (attacker == self) + { + switch (mod) + { + case MOD_HELD_GRENADE: + message = "tried to put the pin back in"; + break; + case MOD_HG_SPLASH: + case MOD_G_SPLASH: + if (IsNeutral(self)) + message = "tripped on its own grenade"; + else if (IsFemale(self)) + message = "tripped on her own grenade"; + else + message = "tripped on his own grenade"; + break; + case MOD_R_SPLASH: + if (IsNeutral(self)) + message = "blew itself up"; + else if (IsFemale(self)) + message = "blew herself up"; + else + message = "blew himself up"; + break; + case MOD_BFG_BLAST: + message = "should have used a smaller gun"; + break; +//ROGUE + case MOD_DOPPLE_EXPLODE: + if (IsNeutral(self)) + message = "got caught in it's own trap"; + else if (IsFemale(self)) + message = "got caught in her own trap"; + else + message = "got caught in his own trap"; + break; +//ROGUE + default: + if (IsNeutral(self)) + message = "killed itself"; + else if (IsFemale(self)) + message = "killed herself"; + else + message = "killed himself"; + break; + } + } + if (message) + { + gi.bprintf (PRINT_MEDIUM, "%s %s.\n", self->client->pers.netname, message); + if (deathmatch->value) + self->client->resp.score--; + self->enemy = NULL; + return; + } + + self->enemy = attacker; + if (attacker && attacker->client) + { + switch (mod) + { + case MOD_BLASTER: + message = "was blasted by"; + break; + case MOD_SHOTGUN: + message = "was gunned down by"; + break; + case MOD_SSHOTGUN: + message = "was blown away by"; + message2 = "'s super shotgun"; + break; + case MOD_MACHINEGUN: + message = "was machinegunned by"; + break; + case MOD_CHAINGUN: + message = "was cut in half by"; + message2 = "'s chaingun"; + break; + case MOD_GRENADE: + message = "was popped by"; + message2 = "'s grenade"; + break; + case MOD_G_SPLASH: + message = "was shredded by"; + message2 = "'s shrapnel"; + break; + case MOD_ROCKET: + message = "ate"; + message2 = "'s rocket"; + break; + case MOD_R_SPLASH: + message = "almost dodged"; + message2 = "'s rocket"; + break; + case MOD_HYPERBLASTER: + message = "was melted by"; + message2 = "'s hyperblaster"; + break; + case MOD_RAILGUN: + message = "was railed by"; + break; + case MOD_BFG_LASER: + message = "saw the pretty lights from"; + message2 = "'s BFG"; + break; + case MOD_BFG_BLAST: + message = "was disintegrated by"; + message2 = "'s BFG blast"; + break; + case MOD_BFG_EFFECT: + message = "couldn't hide from"; + message2 = "'s BFG"; + break; + case MOD_HANDGRENADE: + message = "caught"; + message2 = "'s handgrenade"; + break; + case MOD_HG_SPLASH: + message = "didn't see"; + message2 = "'s handgrenade"; + break; + case MOD_HELD_GRENADE: + message = "feels"; + message2 = "'s pain"; + break; + case MOD_TELEFRAG: + message = "tried to invade"; + message2 = "'s personal space"; + break; + +//=============== +//ROGUE + case MOD_CHAINFIST: + message = "was shredded by"; + message2 = "'s ripsaw"; + break; + case MOD_DISINTEGRATOR: + message = "lost his grip courtesy of"; + message2 = "'s disintegrator"; + break; + case MOD_ETF_RIFLE: + message = "was perforated by"; + break; + case MOD_HEATBEAM: + message = "was scorched by"; + message2 = "'s plasma beam"; + break; + case MOD_TESLA: + message = "was enlightened by"; + message2 = "'s tesla mine"; + break; + case MOD_PROX: + message = "got too close to"; + message2 = "'s proximity mine"; + break; + case MOD_NUKE: + message = "was nuked by"; + message2 = "'s antimatter bomb"; + break; + case MOD_VENGEANCE_SPHERE: + message = "was purged by"; + message2 = "'s vengeance sphere"; + break; + case MOD_DEFENDER_SPHERE: + message = "had a blast with"; + message2 = "'s defender sphere"; + break; + case MOD_HUNTER_SPHERE: + message = "was killed like a dog by"; + message2 = "'s hunter sphere"; + break; + case MOD_TRACKER: + message = "was annihilated by"; + message2 = "'s disruptor"; + break; + case MOD_DOPPLE_EXPLODE: + message = "was blown up by"; + message2 = "'s doppleganger"; + break; + case MOD_DOPPLE_VENGEANCE: + message = "was purged by"; + message2 = "'s doppleganger"; + break; + case MOD_DOPPLE_HUNTER: + message = "was hunted down by"; + message2 = "'s doppleganger"; + break; +//ROGUE +//=============== + } + if (message) + { + gi.bprintf (PRINT_MEDIUM,"%s %s %s%s\n", self->client->pers.netname, message, attacker->client->pers.netname, message2); +//ROGUE + if (gamerules && gamerules->value) + { + if(DMGame.Score) + { + if(ff) + DMGame.Score(attacker, self, -1); + else + DMGame.Score(attacker, self, 1); + } + return; + } +//ROGUE + + if (deathmatch->value) + { + if (ff) + attacker->client->resp.score--; + else + attacker->client->resp.score++; + } + return; + } + } + } + + gi.bprintf (PRINT_MEDIUM,"%s died.\n", self->client->pers.netname); + +//ROGUE +// if (g_showlogic && g_showlogic->value) +// { +// if (mod == MOD_UNKNOWN) +// gi.dprintf ("Player killed by MOD_UNKNOWN\n"); +// else +// gi.dprintf ("Player killed by undefined mod %d\n", mod); +// } +//ROGUE + + if (deathmatch->value) +//ROGUE + { + if (gamerules && gamerules->value) + { + if(DMGame.Score) + { + DMGame.Score(self, self, -1); + } + return; + } + else + self->client->resp.score--; + } +//ROGUE + +} + + +void Touch_Item (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf); + +void TossClientWeapon (edict_t *self) +{ + gitem_t *item; + edict_t *drop; + qboolean quad; + float spread; + + if (!deathmatch->value) + return; + + item = self->client->pers.weapon; + if (! self->client->pers.inventory[self->client->ammo_index] ) + item = NULL; + if (item && (strcmp (item->pickup_name, "Blaster") == 0)) + item = NULL; + + if (!((int)(dmflags->value) & DF_QUAD_DROP)) + quad = false; + else + quad = (self->client->quad_framenum > (level.framenum + 10)); + + if (item && quad) + spread = 22.5; + else + spread = 0.0; + + if (item) + { + self->client->v_angle[YAW] -= spread; + drop = Drop_Item (self, item); + self->client->v_angle[YAW] += spread; + drop->spawnflags = DROPPED_PLAYER_ITEM; + } + + if (quad) + { + self->client->v_angle[YAW] += spread; + drop = Drop_Item (self, FindItemByClassname ("item_quad")); + self->client->v_angle[YAW] -= spread; + drop->spawnflags |= DROPPED_PLAYER_ITEM; + + drop->touch = Touch_Item; + drop->nextthink = level.time + (self->client->quad_framenum - level.framenum) * FRAMETIME; + drop->think = G_FreeEdict; + } +} + + +/* +================== +LookAtKiller +================== +*/ +void LookAtKiller (edict_t *self, edict_t *inflictor, edict_t *attacker) +{ + vec3_t dir; + + if (attacker && attacker != world && attacker != self) + { + VectorSubtract (attacker->s.origin, self->s.origin, dir); + } + else if (inflictor && inflictor != world && inflictor != self) + { + VectorSubtract (inflictor->s.origin, self->s.origin, dir); + } + else + { + self->client->killer_yaw = self->s.angles[YAW]; + return; + } + // PMM - fixed to correct for pitch of 0 + if (dir[0]) + self->client->killer_yaw = 180/M_PI*atan2(dir[1], dir[0]); + else if (dir[1] > 0) + self->client->killer_yaw = 90; + else if (dir[1] < 0) + self->client->killer_yaw = 270; + else + self->client->killer_yaw = 0; +} + +/* +================== +player_die +================== +*/ +void player_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + int n; + + VectorClear (self->avelocity); + + self->takedamage = DAMAGE_YES; + self->movetype = MOVETYPE_TOSS; + + self->s.modelindex2 = 0; // remove linked weapon model + + self->s.angles[0] = 0; + self->s.angles[2] = 0; + + self->s.sound = 0; + self->client->weapon_sound = 0; + + self->maxs[2] = -8; + +// self->solid = SOLID_NOT; + self->svflags |= SVF_DEADMONSTER; + + if (!self->deadflag) + { + self->client->respawn_time = level.time + 1.0; + LookAtKiller (self, inflictor, attacker); + self->client->ps.pmove.pm_type = PM_DEAD; + ClientObituary (self, inflictor, attacker); + TossClientWeapon (self); + if (deathmatch->value) + Cmd_Help_f (self); // show scores + + // clear inventory + // this is kind of ugly, but it's how we want to handle keys in coop + for (n = 0; n < game.num_items; n++) + { + if (coop->value && itemlist[n].flags & IT_KEY) + self->client->resp.coop_respawn.inventory[n] = self->client->pers.inventory[n]; + self->client->pers.inventory[n] = 0; + } + } + + if(gamerules && gamerules->value) // if we're in a dm game, alert the game + { + if(DMGame.PlayerDeath) + DMGame.PlayerDeath(self, inflictor, attacker); + } + + // remove powerups + self->client->quad_framenum = 0; + self->client->invincible_framenum = 0; + self->client->breather_framenum = 0; + self->client->enviro_framenum = 0; + self->flags &= ~FL_POWER_ARMOR; + +//============== +// ROGUE stuff + self->client->double_framenum = 0; + + // if there's a sphere around, let it know the player died. + // vengeance and hunter will die if they're not attacking, + // defender should always die + if(self->client->owned_sphere) + { + edict_t *sphere; + + sphere = self->client->owned_sphere; + sphere->die(sphere, self, self, 0, vec3_origin); + } + + // if we've been killed by the tracker, GIB! + if((meansOfDeath & ~MOD_FRIENDLY_FIRE) == MOD_TRACKER) + { + self->health = -100; + damage = 400; + } + + // make sure no trackers are still hurting us. + if(self->client->tracker_pain_framenum) + { + RemoveAttackingPainDaemons (self); + } + + // if we got obliterated by the nuke, don't gib + if ((self->health < -80) && (meansOfDeath == MOD_NUKE)) + self->flags |= FL_NOGIB; + +// ROGUE +//============== + + if (self->health < -40) + { + // PMM + // don't toss gibs if we got vaped by the nuke + if (!(self->flags & FL_NOGIB)) + { + // pmm + // gib + gi.sound (self, CHAN_BODY, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0); + + // more meaty gibs for your dollar! + if((deathmatch->value) && (self->health < -80)) + { + for (n= 0; n < 4; n++) + ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); + } + + for (n= 0; n < 4; n++) + ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); + // PMM + } + self->flags &= ~FL_NOGIB; + // pmm + + ThrowClientHead (self, damage); + + self->takedamage = DAMAGE_NO; + } + else + { // normal death + if (!self->deadflag) + { + static int i; + + i = (i+1)%3; + // start a death animation + self->client->anim_priority = ANIM_DEATH; + if (self->client->ps.pmove.pm_flags & PMF_DUCKED) + { + self->s.frame = FRAME_crdeath1-1; + self->client->anim_end = FRAME_crdeath5; + } + else switch (i) + { + case 0: + self->s.frame = FRAME_death101-1; + self->client->anim_end = FRAME_death106; + break; + case 1: + self->s.frame = FRAME_death201-1; + self->client->anim_end = FRAME_death206; + break; + case 2: + self->s.frame = FRAME_death301-1; + self->client->anim_end = FRAME_death308; + break; + } + gi.sound (self, CHAN_VOICE, gi.soundindex(va("*death%i.wav", (rand()%4)+1)), 1, ATTN_NORM, 0); + } + } + + self->deadflag = DEAD_DEAD; + + gi.linkentity (self); +} + +//======================================================================= + +/* +============== +InitClientPersistant + +This is only called when the game first initializes in single player, +but is called after each death and level change in deathmatch +============== +*/ +void InitClientPersistant (gclient_t *client) +{ + gitem_t *item; + + memset (&client->pers, 0, sizeof(client->pers)); + + item = FindItem("Blaster"); + client->pers.selected_item = ITEM_INDEX(item); + client->pers.inventory[client->pers.selected_item] = 1; + + client->pers.weapon = item; + + client->pers.health = 100; + client->pers.max_health = 100; + + client->pers.max_bullets = 200; + client->pers.max_shells = 100; + client->pers.max_rockets = 50; + client->pers.max_grenades = 50; + client->pers.max_cells = 200; + client->pers.max_slugs = 50; + +//ROGUE + // FIXME - give these real numbers.... + client->pers.max_prox = 50; + client->pers.max_tesla = 50; + client->pers.max_flechettes = 200; +#ifndef KILL_DISRUPTOR + client->pers.max_rounds = 100; +#endif +//ROGUE + + client->pers.connected = true; +} + + +void InitClientResp (gclient_t *client) +{ + memset (&client->resp, 0, sizeof(client->resp)); + client->resp.enterframe = level.framenum; + client->resp.coop_respawn = client->pers; +} + +/* +================== +SaveClientData + +Some information that should be persistant, like health, +is still stored in the edict structure, so it needs to +be mirrored out to the client structure before all the +edicts are wiped. +================== +*/ +void SaveClientData (void) +{ + int i; + edict_t *ent; + + for (i=0 ; iinuse) + continue; + game.clients[i].pers.health = ent->health; + game.clients[i].pers.max_health = ent->max_health; + game.clients[i].pers.savedFlags = (ent->flags & (FL_GODMODE|FL_NOTARGET|FL_POWER_ARMOR)); + if (coop->value) + game.clients[i].pers.score = ent->client->resp.score; + } +} + +void FetchClientEntData (edict_t *ent) +{ + ent->health = ent->client->pers.health; + ent->max_health = ent->client->pers.max_health; + ent->flags |= ent->client->pers.savedFlags; + if (coop->value) + ent->client->resp.score = ent->client->pers.score; +} + + + +/* +======================================================================= + + SelectSpawnPoint + +======================================================================= +*/ + +/* +================ +PlayersRangeFromSpot + +Returns the distance to the nearest player from the given spot +================ +*/ +float PlayersRangeFromSpot (edict_t *spot) +{ + edict_t *player; + float bestplayerdistance; + vec3_t v; + int n; + float playerdistance; + + + bestplayerdistance = 9999999; + + for (n = 1; n <= maxclients->value; n++) + { + player = &g_edicts[n]; + + if (!player->inuse) + continue; + + if (player->health <= 0) + continue; + + VectorSubtract (spot->s.origin, player->s.origin, v); + playerdistance = VectorLength (v); + + if (playerdistance < bestplayerdistance) + bestplayerdistance = playerdistance; + } + + return bestplayerdistance; +} + +/* +================ +SelectRandomDeathmatchSpawnPoint + +go to a random point, but NOT the two points closest +to other players +================ +*/ +edict_t *SelectRandomDeathmatchSpawnPoint (void) +{ + edict_t *spot, *spot1, *spot2; + int count = 0; + int selection; + float range, range1, range2; + + spot = NULL; + range1 = range2 = 99999; + spot1 = spot2 = NULL; + + while ((spot = G_Find (spot, FOFS(classname), "info_player_deathmatch")) != NULL) + { + count++; + range = PlayersRangeFromSpot(spot); + if (range < range1) + { + range1 = range; + spot1 = spot; + } + else if (range < range2) + { + range2 = range; + spot2 = spot; + } + } + + if (!count) + return NULL; + + if (count <= 2) + { + spot1 = spot2 = NULL; + } + else + count -= 2; + + selection = rand() % count; + + spot = NULL; + do + { + spot = G_Find (spot, FOFS(classname), "info_player_deathmatch"); + if (spot == spot1 || spot == spot2) + selection++; + } while(selection--); + + return spot; +} + +/* +================ +SelectFarthestDeathmatchSpawnPoint + +================ +*/ +edict_t *SelectFarthestDeathmatchSpawnPoint (void) +{ + edict_t *bestspot; + float bestdistance, bestplayerdistance; + edict_t *spot; + + + spot = NULL; + bestspot = NULL; + bestdistance = 0; + while ((spot = G_Find (spot, FOFS(classname), "info_player_deathmatch")) != NULL) + { + bestplayerdistance = PlayersRangeFromSpot (spot); + + if (bestplayerdistance > bestdistance) + { + bestspot = spot; + bestdistance = bestplayerdistance; + } + } + + if (bestspot) + { + return bestspot; + } + + // if there is a player just spawned on each and every start spot + // we have no choice to turn one into a telefrag meltdown + spot = G_Find (NULL, FOFS(classname), "info_player_deathmatch"); + + return spot; +} + +edict_t *SelectDeathmatchSpawnPoint (void) +{ + if ( (int)(dmflags->value) & DF_SPAWN_FARTHEST) + return SelectFarthestDeathmatchSpawnPoint (); + else + return SelectRandomDeathmatchSpawnPoint (); +} + +//=============== +//ROGUE +edict_t *SelectLavaCoopSpawnPoint (edict_t *ent) +{ + int index; + edict_t *spot = NULL; + float lavatop; + edict_t *lava; + edict_t *pointWithLeastLava; + float lowest; + edict_t *spawnPoints [64]; + vec3_t center; + int numPoints; + edict_t *highestlava; + + lavatop = -99999; + highestlava = NULL; + + // first, find the highest lava + // remember that some will stop moving when they've filled their + // areas... + lava = NULL; + while (1) + { + lava = G_Find (lava, FOFS(classname), "func_door"); + if(!lava) + break; + + VectorAdd (lava->absmax, lava->absmin, center); + VectorScale (center, 0.5, center); + + if(lava->spawnflags & 2 && (gi.pointcontents(center) & MASK_WATER)) + { + if (lava->absmax[2] > lavatop) + { + lavatop = lava->absmax[2]; + highestlava = lava; + } + } + } + + // if we didn't find ANY lava, then return NULL + if (!highestlava) + return NULL; + + // find the top of the lava and include a small margin of error (plus bbox size) + lavatop = highestlava->absmax[2] + 64; + + // find all the lava spawn points and store them in spawnPoints[] + spot = NULL; + numPoints = 0; + while(spot = G_Find (spot, FOFS(classname), "info_player_coop_lava")) + { + if(numPoints == 64) + break; + + spawnPoints[numPoints++] = spot; + } + + if(numPoints < 1) + return NULL; + + // walk up the sorted list and return the lowest, open, non-lava spawn point + spot = NULL; + lowest = 999999; + pointWithLeastLava = NULL; + for (index = 0; index < numPoints; index++) + { + if(spawnPoints[index]->s.origin[2] < lavatop) + continue; + + if(PlayersRangeFromSpot(spawnPoints[index]) > 32) + { + if(spawnPoints[index]->s.origin[2] < lowest) + { + // save the last point + pointWithLeastLava = spawnPoints[index]; + lowest = spawnPoints[index]->s.origin[2]; + } + } + } + + // FIXME - better solution???? + // well, we may telefrag someone, but oh well... + if(pointWithLeastLava) + return pointWithLeastLava; + + return NULL; +} +//ROGUE +//=============== + +edict_t *SelectCoopSpawnPoint (edict_t *ent) +{ + int index; + edict_t *spot = NULL; + char *target; + +//ROGUE + // rogue hack, but not too gross... + if (!Q_stricmp(level.mapname, "rmine2p") || !Q_stricmp(level.mapname, "rmine2")) + return SelectLavaCoopSpawnPoint (ent); +//ROGUE + + index = ent->client - game.clients; + + // player 0 starts in normal player spawn point + if (!index) + return NULL; + + spot = NULL; + + // assume there are four coop spots at each spawnpoint + while (1) + { + spot = G_Find (spot, FOFS(classname), "info_player_coop"); + if (!spot) + return NULL; // we didn't have enough... + + target = spot->targetname; + if (!target) + target = ""; + if ( Q_stricmp(game.spawnpoint, target) == 0 ) + { // this is a coop spawn point for one of the clients here + index--; + if (!index) + return spot; // this is it + } + } + + + return spot; +} + + +/* +=========== +SelectSpawnPoint + +Chooses a player start, deathmatch start, coop start, etc +============ +*/ +void SelectSpawnPoint (edict_t *ent, vec3_t origin, vec3_t angles) +{ + edict_t *spot = NULL; + + if (deathmatch->value) + spot = SelectDeathmatchSpawnPoint (); + else if (coop->value) + spot = SelectCoopSpawnPoint (ent); + + // find a single player start spot + if (!spot) + { + while ((spot = G_Find (spot, FOFS(classname), "info_player_start")) != NULL) + { + if (!game.spawnpoint[0] && !spot->targetname) + break; + + if (!game.spawnpoint[0] || !spot->targetname) + continue; + + if (Q_stricmp(game.spawnpoint, spot->targetname) == 0) + break; + } + + if (!spot) + { + if (!game.spawnpoint[0]) + { // there wasn't a spawnpoint without a target, so use any + spot = G_Find (spot, FOFS(classname), "info_player_start"); + } + if (!spot) + gi.error ("Couldn't find spawn point %s\n", game.spawnpoint); + } + } + + VectorCopy (spot->s.origin, origin); + origin[2] += 9; + VectorCopy (spot->s.angles, angles); +} + +//====================================================================== + + +void InitBodyQue (void) +{ + int i; + edict_t *ent; + + level.body_que = 0; + for (i=0; iclassname = "bodyque"; + } +} + +void body_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + int n; + + if (self->health < -40) + { + gi.sound (self, CHAN_BODY, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0); + for (n= 0; n < 4; n++) + ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); + self->s.origin[2] -= 48; + ThrowClientHead (self, damage); + self->takedamage = DAMAGE_NO; + } +} + +void CopyToBodyQue (edict_t *ent) +{ + edict_t *body; + + // grab a body que and cycle to the next one + body = &g_edicts[(int)maxclients->value + level.body_que + 1]; + level.body_que = (level.body_que + 1) % BODY_QUEUE_SIZE; + + // FIXME: send an effect on the removed body + + gi.unlinkentity (ent); + + gi.unlinkentity (body); + body->s = ent->s; + body->s.number = body - g_edicts; + + body->svflags = ent->svflags; + VectorCopy (ent->mins, body->mins); + VectorCopy (ent->maxs, body->maxs); + VectorCopy (ent->absmin, body->absmin); + VectorCopy (ent->absmax, body->absmax); + VectorCopy (ent->size, body->size); + body->solid = ent->solid; + body->clipmask = ent->clipmask; + body->owner = ent->owner; + body->movetype = ent->movetype; + + body->die = body_die; + body->takedamage = DAMAGE_YES; + + gi.linkentity (body); +} + + +void respawn (edict_t *self) +{ + if (deathmatch->value || coop->value) + { + // spectators don't leave bodies + if (self->movetype != MOVETYPE_NOCLIP) + CopyToBodyQue (self); + self->svflags &= ~SVF_NOCLIENT; + PutClientInServer (self); + + // add a teleportation effect + self->s.event = EV_PLAYER_TELEPORT; + + // hold in place briefly + self->client->ps.pmove.pm_flags = PMF_TIME_TELEPORT; + self->client->ps.pmove.pm_time = 14; + + self->client->respawn_time = level.time; + + return; + } + + // restart the entire server + gi.AddCommandString ("menu_loadgame\n"); +} + +/* + * only called when pers.spectator changes + * note that resp.spectator should be the opposite of pers.spectator here + */ +void spectator_respawn (edict_t *ent) +{ + int i, numspec; + + // if the user wants to become a spectator, make sure he doesn't + // exceed max_spectators + + if (ent->client->pers.spectator) + { + char *value = Info_ValueForKey (ent->client->pers.userinfo, "spectator"); + if (*spectator_password->string && + strcmp(spectator_password->string, "none") && + strcmp(spectator_password->string, value)) + { + gi.cprintf(ent, PRINT_HIGH, "Spectator password incorrect.\n"); + ent->client->pers.spectator = false; + gi.WriteByte (svc_stufftext); + gi.WriteString ("spectator 0\n"); + gi.unicast(ent, true); + return; + } + + // count spectators + for (i = 1, numspec = 0; i <= maxclients->value; i++) + { + if (g_edicts[i].inuse && g_edicts[i].client->pers.spectator) + numspec++; + } + + if (numspec >= maxspectators->value) + { + gi.cprintf(ent, PRINT_HIGH, "Server spectator limit is full."); + ent->client->pers.spectator = false; + // reset his spectator var + gi.WriteByte (svc_stufftext); + gi.WriteString ("spectator 0\n"); + gi.unicast(ent, true); + return; + } + } + else + { + // he was a spectator and wants to join the game + // he must have the right password + char *value = Info_ValueForKey (ent->client->pers.userinfo, "password"); + if (*password->string && strcmp(password->string, "none") && + strcmp(password->string, value)) + { + gi.cprintf(ent, PRINT_HIGH, "Password incorrect.\n"); + ent->client->pers.spectator = true; + gi.WriteByte (svc_stufftext); + gi.WriteString ("spectator 1\n"); + gi.unicast(ent, true); + return; + } + } + + // clear score on respawn + ent->client->pers.score = ent->client->resp.score = 0; + + ent->svflags &= ~SVF_NOCLIENT; + PutClientInServer (ent); + + // add a teleportation effect + if (!ent->client->pers.spectator) + { + // send effect + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + gi.WriteByte (MZ_LOGIN); + gi.multicast (ent->s.origin, MULTICAST_PVS); + + // hold in place briefly + ent->client->ps.pmove.pm_flags = PMF_TIME_TELEPORT; + ent->client->ps.pmove.pm_time = 14; + } + + ent->client->respawn_time = level.time; + + if (ent->client->pers.spectator) + gi.bprintf (PRINT_HIGH, "%s has moved to the sidelines\n", ent->client->pers.netname); + else + gi.bprintf (PRINT_HIGH, "%s joined the game\n", ent->client->pers.netname); +} + +//============================================================== + + +/* +=========== +PutClientInServer + +Called when a player connects to a server or respawns in +a deathmatch. +============ +*/ +void PutClientInServer (edict_t *ent) +{ + vec3_t mins = {-16, -16, -24}; + vec3_t maxs = {16, 16, 32}; + int index; + vec3_t spawn_origin, spawn_angles; + gclient_t *client; + int i; + client_persistant_t saved; + client_respawn_t resp; + + // find a spawn point + // do it before setting health back up, so farthest + // ranging doesn't count this client + if(gamerules && gamerules->value && DMGame.SelectSpawnPoint) // PGM + DMGame.SelectSpawnPoint (ent, spawn_origin, spawn_angles); // PGM + else // PGM + SelectSpawnPoint (ent, spawn_origin, spawn_angles); + + index = ent-g_edicts-1; + client = ent->client; + + // deathmatch wipes most client data every spawn + if (deathmatch->value) + { + char userinfo[MAX_INFO_STRING]; + + resp = client->resp; + memcpy (userinfo, client->pers.userinfo, sizeof(userinfo)); + InitClientPersistant (client); + ClientUserinfoChanged (ent, userinfo); + } + else if (coop->value) + { +// int n; + char userinfo[MAX_INFO_STRING]; + + resp = client->resp; + memcpy (userinfo, client->pers.userinfo, sizeof(userinfo)); + // this is kind of ugly, but it's how we want to handle keys in coop +// for (n = 0; n < game.num_items; n++) +// { +// if (itemlist[n].flags & IT_KEY) +// resp.coop_respawn.inventory[n] = client->pers.inventory[n]; +// } + resp.coop_respawn.game_helpchanged = client->pers.game_helpchanged; + resp.coop_respawn.helpchanged = client->pers.helpchanged; + client->pers = resp.coop_respawn; + ClientUserinfoChanged (ent, userinfo); + if (resp.score > client->pers.score) + client->pers.score = resp.score; + } + else + { + memset (&resp, 0, sizeof(resp)); + } + + // clear everything but the persistant data + saved = client->pers; + memset (client, 0, sizeof(*client)); + client->pers = saved; + if (client->pers.health <= 0) + InitClientPersistant(client); + client->resp = resp; + + // copy some data from the client to the entity + FetchClientEntData (ent); + + // clear entity values + ent->groundentity = NULL; + ent->client = &game.clients[index]; + ent->takedamage = DAMAGE_AIM; + ent->movetype = MOVETYPE_WALK; + ent->viewheight = 22; + ent->inuse = true; + ent->classname = "player"; + ent->mass = 200; + ent->solid = SOLID_BBOX; + ent->deadflag = DEAD_NO; + ent->air_finished = level.time + 12; + ent->clipmask = MASK_PLAYERSOLID; + ent->model = "players/male/tris.md2"; + ent->pain = player_pain; + ent->die = player_die; + ent->waterlevel = 0; + ent->watertype = 0; + ent->flags &= ~FL_NO_KNOCKBACK; + ent->svflags &= ~SVF_DEADMONSTER; + + ent->flags &= ~FL_SAM_RAIMI; // PGM - turn off sam raimi flag + + VectorCopy (mins, ent->mins); + VectorCopy (maxs, ent->maxs); + VectorClear (ent->velocity); + + // clear playerstate values + memset (&ent->client->ps, 0, sizeof(client->ps)); + + client->ps.pmove.origin[0] = spawn_origin[0]*8; + client->ps.pmove.origin[1] = spawn_origin[1]*8; + client->ps.pmove.origin[2] = spawn_origin[2]*8; + + if (deathmatch->value && ((int)dmflags->value & DF_FIXED_FOV)) + { + client->ps.fov = 90; + } + else + { + client->ps.fov = atoi(Info_ValueForKey(client->pers.userinfo, "fov")); + if (client->ps.fov < 1) + client->ps.fov = 90; + else if (client->ps.fov > 160) + client->ps.fov = 160; + } + +//PGM + if (client->pers.weapon) + client->ps.gunindex = gi.modelindex(client->pers.weapon->view_model); + else + client->ps.gunindex = 0; +//PGM + + // clear entity state values + ent->s.effects = 0; + ent->s.modelindex = 255; // will use the skin specified model + ent->s.modelindex2 = 255; // custom gun model + // sknum is player num and weapon number + // weapon number will be added in changeweapon + ent->s.skinnum = ent - g_edicts - 1; + + ent->s.frame = 0; + VectorCopy (spawn_origin, ent->s.origin); + ent->s.origin[2] += 1; // make sure off ground + VectorCopy (ent->s.origin, ent->s.old_origin); + + // set the delta angle + for (i=0 ; i<3 ; i++) + client->ps.pmove.delta_angles[i] = ANGLE2SHORT(spawn_angles[i] - client->resp.cmd_angles[i]); + + ent->s.angles[PITCH] = 0; + ent->s.angles[YAW] = spawn_angles[YAW]; + ent->s.angles[ROLL] = 0; + VectorCopy (ent->s.angles, client->ps.viewangles); + VectorCopy (ent->s.angles, client->v_angle); + + // spawn a spectator + if (client->pers.spectator) + { + client->chase_target = NULL; + + client->resp.spectator = true; + + ent->movetype = MOVETYPE_NOCLIP; + ent->solid = SOLID_NOT; + ent->svflags |= SVF_NOCLIENT; + ent->client->ps.gunindex = 0; + gi.linkentity (ent); + return; + } + else + client->resp.spectator = false; + + if (!KillBox (ent)) + { // could't spawn in? + } + + gi.linkentity (ent); + + // my tribute to cash's level-specific hacks. I hope I live + // up to his trailblazing cheese. + if(Q_stricmp(level.mapname, "rboss") == 0) + { + // if you get on to rboss in single player or coop, ensure + // the player has the nuke key. (not in DM) + if(!(deathmatch->value)) + { + gitem_t *item; + + item = FindItem("Antimatter Bomb"); + client->pers.selected_item = ITEM_INDEX(item); + client->pers.inventory[client->pers.selected_item] = 1; + } + } + + // force the current weapon up + client->newweapon = client->pers.weapon; + ChangeWeapon (ent); +} + +/* +===================== +ClientBeginDeathmatch + +A client has just connected to the server in +deathmatch mode, so clear everything out before starting them. +===================== +*/ +void ClientBeginDeathmatch (edict_t *ent) +{ + G_InitEdict (ent); + + InitClientResp (ent->client); + + //PGM + if(gamerules && gamerules->value && DMGame.ClientBegin) + { + DMGame.ClientBegin (ent); + } + //PGM + + // locate ent at a spawn point + PutClientInServer (ent); + + if (level.intermissiontime) + { + MoveClientToIntermission (ent); + } + else + { + // send effect + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + gi.WriteByte (MZ_LOGIN); + gi.multicast (ent->s.origin, MULTICAST_PVS); + } + + gi.bprintf (PRINT_HIGH, "%s entered the game\n", ent->client->pers.netname); + + // make sure all view stuff is valid + ClientEndServerFrame (ent); +} + + +/* +=========== +ClientBegin + +called when a client has finished connecting, and is ready +to be placed into the game. This will happen every level load. +============ +*/ +void ClientBegin (edict_t *ent) +{ + int i; + + ent->client = game.clients + (ent - g_edicts - 1); + + if (deathmatch->value) + { + ClientBeginDeathmatch (ent); + return; + } + + // if there is already a body waiting for us (a loadgame), just + // take it, otherwise spawn one from scratch + if (ent->inuse == true) + { + // the client has cleared the client side viewangles upon + // connecting to the server, which is different than the + // state when the game is saved, so we need to compensate + // with deltaangles + for (i=0 ; i<3 ; i++) + ent->client->ps.pmove.delta_angles[i] = ANGLE2SHORT(ent->client->ps.viewangles[i]); + } + else + { + // a spawn point will completely reinitialize the entity + // except for the persistant data that was initialized at + // ClientConnect() time + G_InitEdict (ent); + ent->classname = "player"; + InitClientResp (ent->client); + PutClientInServer (ent); + } + + if (level.intermissiontime) + { + MoveClientToIntermission (ent); + } + else + { + // send effect if in a multiplayer game + if (game.maxclients > 1) + { + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + gi.WriteByte (MZ_LOGIN); + gi.multicast (ent->s.origin, MULTICAST_PVS); + + gi.bprintf (PRINT_HIGH, "%s entered the game\n", ent->client->pers.netname); + } + } + + // make sure all view stuff is valid + ClientEndServerFrame (ent); +} + +/* +=========== +ClientUserInfoChanged + +called whenever the player updates a userinfo variable. + +The game can override any of the settings in place +(forcing skins or names, etc) before copying it off. +============ +*/ +void ClientUserinfoChanged (edict_t *ent, char *userinfo) +{ + char *s; + int playernum; + + // check for malformed or illegal info strings + if (!Info_Validate(userinfo)) + { + strcpy (userinfo, "\\name\\badinfo\\skin\\male/grunt"); + } + + // set name + s = Info_ValueForKey (userinfo, "name"); + strncpy (ent->client->pers.netname, s, sizeof(ent->client->pers.netname)-1); + + // set spectator + s = Info_ValueForKey (userinfo, "spectator"); + // spectators are only supported in deathmatch + // if (deathmatch->value && strcmp(s, "0")) + if (deathmatch->value && *s && strcmp(s, "0")) + ent->client->pers.spectator = true; + else + ent->client->pers.spectator = false; + + // set skin + s = Info_ValueForKey (userinfo, "skin"); + + playernum = ent-g_edicts-1; + + // combine name and skin into a configstring + gi.configstring (CS_PLAYERSKINS+playernum, va("%s\\%s", ent->client->pers.netname, s) ); + + // fov + if (deathmatch->value && ((int)dmflags->value & DF_FIXED_FOV)) + { + ent->client->ps.fov = 90; + } + else + { + ent->client->ps.fov = atoi(Info_ValueForKey(userinfo, "fov")); + if (ent->client->ps.fov < 1) + ent->client->ps.fov = 90; + else if (ent->client->ps.fov > 160) + ent->client->ps.fov = 160; + } + + // handedness + s = Info_ValueForKey (userinfo, "hand"); + if (strlen(s)) + { + ent->client->pers.hand = atoi(s); + } + + // save off the userinfo in case we want to check something later + strncpy (ent->client->pers.userinfo, userinfo, sizeof(ent->client->pers.userinfo)-1); +} + + +/* +=========== +ClientConnect + +Called when a player begins connecting to the server. +The game can refuse entrance to a client by returning false. +If the client is allowed, the connection process will continue +and eventually get to ClientBegin() +Changing levels will NOT cause this to be called again, but +loadgames will. +============ +*/ +qboolean ClientConnect (edict_t *ent, char *userinfo) +{ + char *value; + + // check to see if they are on the banned IP list + value = Info_ValueForKey (userinfo, "ip"); + if (SV_FilterPacket(value)) + { + Info_SetValueForKey(userinfo, "rejmsg", "Banned."); + return false; + } + + // check for a spectator + value = Info_ValueForKey (userinfo, "spectator"); +// if (deathmatch->value && strcmp(value, "0")) + if (deathmatch->value && *value && strcmp(value, "0")) + { + int i, numspec; + + if (*spectator_password->string && + strcmp(spectator_password->string, "none") && + strcmp(spectator_password->string, value)) + { + Info_SetValueForKey(userinfo, "rejmsg", "Spectator password required or incorrect."); + return false; + } + + // count spectators + for (i = numspec = 0; i < maxclients->value; i++) + { + if (g_edicts[i+1].inuse && g_edicts[i+1].client->pers.spectator) + numspec++; + } + + if (numspec >= maxspectators->value) + { + Info_SetValueForKey(userinfo, "rejmsg", "Server spectator limit is full."); + return false; + } + } + else + { + // check for a password + value = Info_ValueForKey (userinfo, "password"); + if (*password->string && strcmp(password->string, "none") && + strcmp(password->string, value)) + { + Info_SetValueForKey(userinfo, "rejmsg", "Password required or incorrect."); + return false; + } + } + + + // they can connect + ent->client = game.clients + (ent - g_edicts - 1); + + // if there is already a body waiting for us (a loadgame), just + // take it, otherwise spawn one from scratch + if (ent->inuse == false) + { + // clear the respawning variables + InitClientResp (ent->client); + if (!game.autosaved || !ent->client->pers.weapon) + InitClientPersistant (ent->client); + } + + ClientUserinfoChanged (ent, userinfo); + + if (game.maxclients > 1) + gi.dprintf ("%s connected\n", ent->client->pers.netname); + + ent->svflags = 0; // make sure we start with known default + ent->client->pers.connected = true; + return true; +} + +/* +=========== +ClientDisconnect + +Called when a player drops from the server. +Will not be called between levels. +============ +*/ +void ClientDisconnect (edict_t *ent) +{ + int playernum; + + if (!ent->client) + return; + + gi.bprintf (PRINT_HIGH, "%s disconnected\n", ent->client->pers.netname); + +//============ +//ROGUE + // make sure no trackers are still hurting us. + if(ent->client->tracker_pain_framenum) + RemoveAttackingPainDaemons (ent); + + if (ent->client->owned_sphere) + { + if(ent->client->owned_sphere->inuse) + G_FreeEdict (ent->client->owned_sphere); + ent->client->owned_sphere = NULL; + } + + if (gamerules && gamerules->value) + { + if(DMGame.PlayerDisconnect) + DMGame.PlayerDisconnect(ent); + } +//ROGUE +//============ + + // send effect + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + gi.WriteByte (MZ_LOGOUT); + gi.multicast (ent->s.origin, MULTICAST_PVS); + + gi.unlinkentity (ent); + ent->s.modelindex = 0; + ent->solid = SOLID_NOT; + ent->inuse = false; + ent->classname = "disconnected"; + ent->client->pers.connected = false; + + playernum = ent-g_edicts-1; + gi.configstring (CS_PLAYERSKINS+playernum, ""); +} + + +//============================================================== + + +edict_t *pm_passent; + +// pmove doesn't need to know about passent and contentmask +trace_t PM_trace (vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end) +{ + if (pm_passent->health > 0) + return gi.trace (start, mins, maxs, end, pm_passent, MASK_PLAYERSOLID); + else + return gi.trace (start, mins, maxs, end, pm_passent, MASK_DEADSOLID); +} + +unsigned CheckBlock (void *b, int c) +{ + int v,i; + v = 0; + for (i=0 ; is, sizeof(pm->s)); + c2 = CheckBlock (&pm->cmd, sizeof(pm->cmd)); + Com_Printf ("sv %3i:%i %i\n", pm->cmd.impulse, c1, c2); +} + +/* +============== +ClientThink + +This will be called once for each client frame, which will +usually be a couple times for each server frame. +============== +*/ +void ClientThink (edict_t *ent, usercmd_t *ucmd) +{ + gclient_t *client; + edict_t *other; + int i, j; + pmove_t pm; + + level.current_entity = ent; + client = ent->client; + + if (level.intermissiontime) + { + client->ps.pmove.pm_type = PM_FREEZE; + // can exit intermission after five seconds + if (level.time > level.intermissiontime + 5.0 + && (ucmd->buttons & BUTTON_ANY) ) + level.exitintermission = true; + return; + } + + pm_passent = ent; + + if (ent->client->chase_target) + { + client->resp.cmd_angles[0] = SHORT2ANGLE(ucmd->angles[0]); + client->resp.cmd_angles[1] = SHORT2ANGLE(ucmd->angles[1]); + client->resp.cmd_angles[2] = SHORT2ANGLE(ucmd->angles[2]); + } + else + { + // set up for pmove + memset (&pm, 0, sizeof(pm)); + + if (ent->movetype == MOVETYPE_NOCLIP) + client->ps.pmove.pm_type = PM_SPECTATOR; + else if (ent->s.modelindex != 255) + client->ps.pmove.pm_type = PM_GIB; + else if (ent->deadflag) + client->ps.pmove.pm_type = PM_DEAD; + else + client->ps.pmove.pm_type = PM_NORMAL; + + //PGM trigger_gravity support + // client->ps.pmove.gravity = sv_gravity->value; + client->ps.pmove.gravity = sv_gravity->value * ent->gravity; + //PGM + pm.s = client->ps.pmove; + + for (i=0 ; i<3 ; i++) + { + pm.s.origin[i] = ent->s.origin[i]*8; + pm.s.velocity[i] = ent->velocity[i]*8; + } + + if (memcmp(&client->old_pmove, &pm.s, sizeof(pm.s))) + { + pm.snapinitial = true; + // gi.dprintf ("pmove changed!\n"); + } + + pm.cmd = *ucmd; + + pm.trace = PM_trace; // adds default parms + pm.pointcontents = gi.pointcontents; + + // perform a pmove + gi.Pmove (&pm); + + // save results of pmove + client->ps.pmove = pm.s; + client->old_pmove = pm.s; + + for (i=0 ; i<3 ; i++) + { + ent->s.origin[i] = pm.s.origin[i]*0.125; + ent->velocity[i] = pm.s.velocity[i]*0.125; + } + + VectorCopy (pm.mins, ent->mins); + VectorCopy (pm.maxs, ent->maxs); + + client->resp.cmd_angles[0] = SHORT2ANGLE(ucmd->angles[0]); + client->resp.cmd_angles[1] = SHORT2ANGLE(ucmd->angles[1]); + client->resp.cmd_angles[2] = SHORT2ANGLE(ucmd->angles[2]); + + if (ent->groundentity && !pm.groundentity && (pm.cmd.upmove >= 10) && (pm.waterlevel == 0)) + { + gi.sound(ent, CHAN_VOICE, gi.soundindex("*jump1.wav"), 1, ATTN_NORM, 0); + PlayerNoise(ent, ent->s.origin, PNOISE_SELF); + } + + //ROGUE sam raimi cam support + if(ent->flags & FL_SAM_RAIMI) + ent->viewheight = 8; + else + ent->viewheight = pm.viewheight; + //ROGUE + + ent->waterlevel = pm.waterlevel; + ent->watertype = pm.watertype; + ent->groundentity = pm.groundentity; + if (pm.groundentity) + ent->groundentity_linkcount = pm.groundentity->linkcount; + + if (ent->deadflag) + { + client->ps.viewangles[ROLL] = 40; + client->ps.viewangles[PITCH] = -15; + client->ps.viewangles[YAW] = client->killer_yaw; + } + else + { + VectorCopy (pm.viewangles, client->v_angle); + VectorCopy (pm.viewangles, client->ps.viewangles); + } + + gi.linkentity (ent); + + //PGM trigger_gravity support + ent->gravity = 1.0; + //PGM + if (ent->movetype != MOVETYPE_NOCLIP) + G_TouchTriggers (ent); + + // touch other objects + for (i=0 ; itouch) + continue; + other->touch (other, ent, NULL, NULL); + } + } + + client->oldbuttons = client->buttons; + client->buttons = ucmd->buttons; + client->latched_buttons |= client->buttons & ~client->oldbuttons; + + // save light level the player is standing on for + // monster sighting AI + ent->light_level = ucmd->lightlevel; + + // fire weapon from final position if needed + if (client->latched_buttons & BUTTON_ATTACK) + { + if (client->resp.spectator) + { + client->latched_buttons = 0; + + if (client->chase_target) + { + client->chase_target = NULL; + client->ps.pmove.pm_flags &= ~PMF_NO_PREDICTION; + } + else + GetChaseTarget(ent); + } + else if (!client->weapon_thunk) + { + client->weapon_thunk = true; + Think_Weapon (ent); + } + } + + if (client->resp.spectator) + { + if (ucmd->upmove >= 10) + { + if (!(client->ps.pmove.pm_flags & PMF_JUMP_HELD)) + { + client->ps.pmove.pm_flags |= PMF_JUMP_HELD; + if (client->chase_target) + ChaseNext(ent); + else + GetChaseTarget(ent); + } + } + else + client->ps.pmove.pm_flags &= ~PMF_JUMP_HELD; + } + + // update chase cam if being followed + for (i = 1; i <= maxclients->value; i++) + { + other = g_edicts + i; + if (other->inuse && other->client->chase_target == ent) + UpdateChaseCam(other); + } +} + + +/* +============== +ClientBeginServerFrame + +This will be called once for each server frame, before running +any other entities in the world. +============== +*/ +void ClientBeginServerFrame (edict_t *ent) +{ + gclient_t *client; + int buttonMask; + + if (level.intermissiontime) + return; + + client = ent->client; + + if (deathmatch->value && + client->pers.spectator != client->resp.spectator && + (level.time - client->respawn_time) >= 5) + { + spectator_respawn(ent); + return; + } + + // run weapon animations if it hasn't been done by a ucmd_t + if (!client->weapon_thunk && !client->resp.spectator) + Think_Weapon (ent); + else + client->weapon_thunk = false; + + if (ent->deadflag) + { + // wait for any button just going down + if ( level.time > client->respawn_time) + { + // in deathmatch, only wait for attack button + if (deathmatch->value) + buttonMask = BUTTON_ATTACK; + else + buttonMask = -1; + + if ( ( client->latched_buttons & buttonMask ) || + (deathmatch->value && ((int)dmflags->value & DF_FORCE_RESPAWN) ) ) + { + respawn(ent); + client->latched_buttons = 0; + } + } + return; + } + + // add player trail so monsters can follow + if (!deathmatch->value) + if (!visible (ent, PlayerTrail_LastSpot() ) ) + PlayerTrail_Add (ent->s.old_origin); + + client->latched_buttons = 0; +} + +/* +============== +RemoveAttackingPainDaemons + +This is called to clean up the pain daemons that the disruptor attaches +to clients to damage them. +============== +*/ +void RemoveAttackingPainDaemons (edict_t *self) +{ + edict_t *tracker; + + tracker = G_Find (NULL, FOFS(classname), "pain daemon"); + while(tracker) + { + if(tracker->enemy == self) + G_FreeEdict(tracker); + tracker = G_Find (tracker, FOFS(classname), "pain daemon"); + } + + if(self->client) + self->client->tracker_pain_framenum = 0; +} diff --git a/original/rogue/p_hud.c b/original/rogue/p_hud.c new file mode 100644 index 0000000..49b9153 --- /dev/null +++ b/original/rogue/p_hud.c @@ -0,0 +1,601 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +#include "g_local.h" + + + +/* +====================================================================== + +INTERMISSION + +====================================================================== +*/ + +void MoveClientToIntermission (edict_t *ent) +{ + if (deathmatch->value || coop->value) + ent->client->showscores = true; + VectorCopy (level.intermission_origin, ent->s.origin); + ent->client->ps.pmove.origin[0] = level.intermission_origin[0]*8; + ent->client->ps.pmove.origin[1] = level.intermission_origin[1]*8; + ent->client->ps.pmove.origin[2] = level.intermission_origin[2]*8; + VectorCopy (level.intermission_angle, ent->client->ps.viewangles); + ent->client->ps.pmove.pm_type = PM_FREEZE; + ent->client->ps.gunindex = 0; + ent->client->ps.blend[3] = 0; + ent->client->ps.rdflags &= ~RDF_UNDERWATER; + + // clean up powerup info + ent->client->quad_framenum = 0; + ent->client->invincible_framenum = 0; + ent->client->breather_framenum = 0; + ent->client->enviro_framenum = 0; + ent->client->grenade_blew_up = false; + ent->client->grenade_time = 0; + + ent->client->ps.rdflags &= ~RDF_IRGOGGLES; // PGM + ent->client->ir_framenum = 0; // PGM + ent->client->nuke_framenum = 0; // PMM + ent->client->double_framenum = 0; // PMM + + ent->viewheight = 0; + ent->s.modelindex = 0; + ent->s.modelindex2 = 0; + ent->s.modelindex3 = 0; + ent->s.modelindex = 0; + ent->s.effects = 0; + ent->s.sound = 0; + ent->solid = SOLID_NOT; + + // add the layout + + if (deathmatch->value || coop->value) + { + DeathmatchScoreboardMessage (ent, NULL); + gi.unicast (ent, true); + } + +} + +void BeginIntermission (edict_t *targ) +{ + int i, n; + edict_t *ent, *client; + + if (level.intermissiontime) + return; // already activated + + game.autosaved = false; + + // respawn any dead clients + for (i=0 ; ivalue ; i++) + { + client = g_edicts + 1 + i; + if (!client->inuse) + continue; + if (client->health <= 0) + respawn(client); + } + + level.intermissiontime = level.time; + level.changemap = targ->map; + + if (strstr(level.changemap, "*")) + { + if (coop->value) + { + for (i=0 ; ivalue ; i++) + { + client = g_edicts + 1 + i; + if (!client->inuse) + continue; + // strip players of all keys between units + for (n = 0; n < MAX_ITEMS; n++) + { + if (itemlist[n].flags & IT_KEY) + client->client->pers.inventory[n] = 0; + } + } + } + } + else + { + if (!deathmatch->value) + { + level.exitintermission = 1; // go immediately to the next level + return; + } + } + + level.exitintermission = 0; + + // find an intermission spot + ent = G_Find (NULL, FOFS(classname), "info_player_intermission"); + if (!ent) + { // the map creator forgot to put in an intermission point... + ent = G_Find (NULL, FOFS(classname), "info_player_start"); + if (!ent) + ent = G_Find (NULL, FOFS(classname), "info_player_deathmatch"); + } + else + { // chose one of four spots + i = rand() & 3; + while (i--) + { + ent = G_Find (ent, FOFS(classname), "info_player_intermission"); + if (!ent) // wrap around the list + ent = G_Find (ent, FOFS(classname), "info_player_intermission"); + } + } + + VectorCopy (ent->s.origin, level.intermission_origin); + VectorCopy (ent->s.angles, level.intermission_angle); + + // move all clients to the intermission point + for (i=0 ; ivalue ; i++) + { + client = g_edicts + 1 + i; + if (!client->inuse) + continue; + MoveClientToIntermission (client); + } +} + + +/* +================== +DeathmatchScoreboardMessage + +================== +*/ +void DeathmatchScoreboardMessage (edict_t *ent, edict_t *killer) +{ + char entry[1024]; + char string[1400]; + int stringlength; + int i, j, k; + int sorted[MAX_CLIENTS]; + int sortedscores[MAX_CLIENTS]; + int score, total; + int picnum; + int x, y; + gclient_t *cl; + edict_t *cl_ent; + char *tag; + + // sort the clients by score + total = 0; + for (i=0 ; iinuse || game.clients[i].resp.spectator) + continue; + score = game.clients[i].resp.score; + for (j=0 ; j sortedscores[j]) + break; + } + for (k=total ; k>j ; k--) + { + sorted[k] = sorted[k-1]; + sortedscores[k] = sortedscores[k-1]; + } + sorted[j] = i; + sortedscores[j] = score; + total++; + } + + // print level name and exit rules + string[0] = 0; + + stringlength = strlen(string); + + // add the clients in sorted order + if (total > 12) + total = 12; + + for (i=0 ; i=6) ? 160 : 0; + y = 32 + 32 * (i%6); + + // add a dogtag + if (cl_ent == ent) + tag = "tag1"; + else if (cl_ent == killer) + tag = "tag2"; + else + tag = NULL; + +//=============== +//ROGUE + // allow new DM games to override the tag picture + if (gamerules && gamerules->value) + { + if(DMGame.DogTag) + DMGame.DogTag(cl_ent, killer, &tag); + } +//ROGUE +//=============== + + if (tag) + { + Com_sprintf (entry, sizeof(entry), + "xv %i yv %i picn %s ",x+32, y, tag); + j = strlen(entry); + if (stringlength + j > 1024) + break; + strcpy (string + stringlength, entry); + stringlength += j; + } + + // send the layout + Com_sprintf (entry, sizeof(entry), + "client %i %i %i %i %i %i ", + x, y, sorted[i], cl->resp.score, cl->ping, (level.framenum - cl->resp.enterframe)/600); + j = strlen(entry); + if (stringlength + j > 1024) + break; + strcpy (string + stringlength, entry); + stringlength += j; + } + + gi.WriteByte (svc_layout); + gi.WriteString (string); +} + + +/* +================== +DeathmatchScoreboard + +Draw instead of help message. +Note that it isn't that hard to overflow the 1400 byte message limit! +================== +*/ +void DeathmatchScoreboard (edict_t *ent) +{ + DeathmatchScoreboardMessage (ent, ent->enemy); + gi.unicast (ent, true); +} + + +/* +================== +Cmd_Score_f + +Display the scoreboard +================== +*/ +void Cmd_Score_f (edict_t *ent) +{ + ent->client->showinventory = false; + ent->client->showhelp = false; + + if (!deathmatch->value && !coop->value) + return; + + if (ent->client->showscores) + { + ent->client->showscores = false; + return; + } + + ent->client->showscores = true; + DeathmatchScoreboard (ent); +} + + +/* +================== +HelpComputer + +Draw help computer. +================== +*/ +void HelpComputer (edict_t *ent) +{ + char string[1024]; + char *sk; + + if (skill->value == 0) + sk = "easy"; + else if (skill->value == 1) + sk = "medium"; + else if (skill->value == 2) + sk = "hard"; + else + sk = "hard+"; + + // send the layout + Com_sprintf (string, sizeof(string), + "xv 32 yv 8 picn help " // background + "xv 202 yv 12 string2 \"%s\" " // skill + "xv 0 yv 24 cstring2 \"%s\" " // level name + "xv 0 yv 54 cstring2 \"%s\" " // help 1 + "xv 0 yv 110 cstring2 \"%s\" " // help 2 + "xv 50 yv 164 string2 \" kills goals secrets\" " + "xv 50 yv 172 string2 \"%3i/%3i %i/%i %i/%i\" ", + sk, + level.level_name, + game.helpmessage1, + game.helpmessage2, + level.killed_monsters, level.total_monsters, + level.found_goals, level.total_goals, + level.found_secrets, level.total_secrets); + + gi.WriteByte (svc_layout); + gi.WriteString (string); + gi.unicast (ent, true); +} + + +/* +================== +Cmd_Help_f + +Display the current help message +================== +*/ +void Cmd_Help_f (edict_t *ent) +{ + // this is for backwards compatability + if (deathmatch->value) + { + Cmd_Score_f (ent); + return; + } + + ent->client->showinventory = false; + ent->client->showscores = false; + + if (ent->client->showhelp && (ent->client->pers.game_helpchanged == game.helpchanged)) + { + ent->client->showhelp = false; + return; + } + + ent->client->showhelp = true; + ent->client->pers.helpchanged = 0; + HelpComputer (ent); +} + + +//======================================================================= + +/* +=============== +G_SetStats +=============== +*/ +void G_SetStats (edict_t *ent) +{ + gitem_t *item; + int index, cells; + int power_armor_type; + + // + // health + // + ent->client->ps.stats[STAT_HEALTH_ICON] = level.pic_health; + ent->client->ps.stats[STAT_HEALTH] = ent->health; + + // + // ammo + // + if (!ent->client->ammo_index /* || !ent->client->pers.inventory[ent->client->ammo_index] */) + { + ent->client->ps.stats[STAT_AMMO_ICON] = 0; + ent->client->ps.stats[STAT_AMMO] = 0; + } + else + { + item = &itemlist[ent->client->ammo_index]; + ent->client->ps.stats[STAT_AMMO_ICON] = gi.imageindex (item->icon); + ent->client->ps.stats[STAT_AMMO] = ent->client->pers.inventory[ent->client->ammo_index]; + } + + // + // armor + // + power_armor_type = PowerArmorType (ent); + if (power_armor_type) + { + cells = ent->client->pers.inventory[ITEM_INDEX(FindItem ("cells"))]; + if (cells == 0) + { // ran out of cells for power armor + ent->flags &= ~FL_POWER_ARMOR; + gi.sound(ent, CHAN_ITEM, gi.soundindex("misc/power2.wav"), 1, ATTN_NORM, 0); + power_armor_type = 0;; + } + } + + index = ArmorIndex (ent); + if (power_armor_type && (!index || (level.framenum & 8) ) ) + { // flash between power armor and other armor icon + ent->client->ps.stats[STAT_ARMOR_ICON] = gi.imageindex ("i_powershield"); + ent->client->ps.stats[STAT_ARMOR] = cells; + } + else if (index) + { + item = GetItemByIndex (index); + ent->client->ps.stats[STAT_ARMOR_ICON] = gi.imageindex (item->icon); + ent->client->ps.stats[STAT_ARMOR] = ent->client->pers.inventory[index]; + } + else + { + ent->client->ps.stats[STAT_ARMOR_ICON] = 0; + ent->client->ps.stats[STAT_ARMOR] = 0; + } + + // + // pickup message + // + if (level.time > ent->client->pickup_msg_time) + { + ent->client->ps.stats[STAT_PICKUP_ICON] = 0; + ent->client->ps.stats[STAT_PICKUP_STRING] = 0; + } + + // + // timers + // + if (ent->client->quad_framenum > level.framenum) + { + ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex ("p_quad"); + ent->client->ps.stats[STAT_TIMER] = (ent->client->quad_framenum - level.framenum)/10; + } +// PMM + else if (ent->client->double_framenum > level.framenum) + { + ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex ("p_double"); + ent->client->ps.stats[STAT_TIMER] = (ent->client->double_framenum - level.framenum)/10; + } +// PMM + else if (ent->client->invincible_framenum > level.framenum) + { + ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex ("p_invulnerability"); + ent->client->ps.stats[STAT_TIMER] = (ent->client->invincible_framenum - level.framenum)/10; + } + else if (ent->client->enviro_framenum > level.framenum) + { + ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex ("p_envirosuit"); + ent->client->ps.stats[STAT_TIMER] = (ent->client->enviro_framenum - level.framenum)/10; + } + else if (ent->client->breather_framenum > level.framenum) + { + ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex ("p_rebreather"); + ent->client->ps.stats[STAT_TIMER] = (ent->client->breather_framenum - level.framenum)/10; + } +// PGM + else if (ent->client->owned_sphere) + { + if(ent->client->owned_sphere->spawnflags == 1) // defender + ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex ("p_defender"); + else if(ent->client->owned_sphere->spawnflags == 2) // hunter + ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex ("p_hunter"); + else if(ent->client->owned_sphere->spawnflags == 4) // vengeance + ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex ("p_vengeance"); + else // error case + ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex ("i_fixme"); + + ent->client->ps.stats[STAT_TIMER] = (int)(ent->client->owned_sphere->wait - level.time); + } + else if (ent->client->ir_framenum > level.framenum) + { + ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex ("p_ir"); + ent->client->ps.stats[STAT_TIMER] = (ent->client->ir_framenum - level.framenum)/10; + } +// PGM + else + { + ent->client->ps.stats[STAT_TIMER_ICON] = 0; + ent->client->ps.stats[STAT_TIMER] = 0; + } + + // + // selected item + // + if (ent->client->pers.selected_item == -1) + ent->client->ps.stats[STAT_SELECTED_ICON] = 0; + else + ent->client->ps.stats[STAT_SELECTED_ICON] = gi.imageindex (itemlist[ent->client->pers.selected_item].icon); + + ent->client->ps.stats[STAT_SELECTED_ITEM] = ent->client->pers.selected_item; + + // + // layouts + // + ent->client->ps.stats[STAT_LAYOUTS] = 0; + + if (deathmatch->value) + { + if (ent->client->pers.health <= 0 || level.intermissiontime + || ent->client->showscores) + ent->client->ps.stats[STAT_LAYOUTS] |= 1; + if (ent->client->showinventory && ent->client->pers.health > 0) + ent->client->ps.stats[STAT_LAYOUTS] |= 2; + } + else + { + if (ent->client->showscores || ent->client->showhelp) + ent->client->ps.stats[STAT_LAYOUTS] |= 1; + if (ent->client->showinventory && ent->client->pers.health > 0) + ent->client->ps.stats[STAT_LAYOUTS] |= 2; + } + + // + // frags + // + ent->client->ps.stats[STAT_FRAGS] = ent->client->resp.score; + + // + // help icon / current weapon if not shown + // + if (ent->client->pers.helpchanged && (level.framenum&8) ) + ent->client->ps.stats[STAT_HELPICON] = gi.imageindex ("i_help"); + else if ( (ent->client->pers.hand == CENTER_HANDED || ent->client->ps.fov > 91) + && ent->client->pers.weapon) + ent->client->ps.stats[STAT_HELPICON] = gi.imageindex (ent->client->pers.weapon->icon); + else + ent->client->ps.stats[STAT_HELPICON] = 0; + + ent->client->ps.stats[STAT_SPECTATOR] = 0; +} + +/* +=============== +G_CheckChaseStats +=============== +*/ +void G_CheckChaseStats (edict_t *ent) +{ + int i; + gclient_t *cl; + + for (i = 1; i <= maxclients->value; i++) + { + cl = g_edicts[i].client; + if (!g_edicts[i].inuse || cl->chase_target != ent) + continue; + memcpy(cl->ps.stats, ent->client->ps.stats, sizeof(cl->ps.stats)); + G_SetSpectatorStats(g_edicts + i); + } +} + +/* +=============== +G_SetSpectatorStats +=============== +*/ +void G_SetSpectatorStats (edict_t *ent) +{ + gclient_t *cl = ent->client; + + if (!cl->chase_target) + G_SetStats (ent); + + cl->ps.stats[STAT_SPECTATOR] = 1; + + // layouts are independant in spectator + cl->ps.stats[STAT_LAYOUTS] = 0; + if (cl->pers.health <= 0 || level.intermissiontime || cl->showscores) + cl->ps.stats[STAT_LAYOUTS] |= 1; + if (cl->showinventory && cl->pers.health > 0) + cl->ps.stats[STAT_LAYOUTS] |= 2; + + if (cl->chase_target && cl->chase_target->inuse) + { + cl->ps.stats[STAT_CHASE] = CS_PLAYERSKINS + + (cl->chase_target - g_edicts) - 1; + } + else + cl->ps.stats[STAT_CHASE] = 0; +} + diff --git a/original/rogue/p_trail.c b/original/rogue/p_trail.c new file mode 100644 index 0000000..abb108c --- /dev/null +++ b/original/rogue/p_trail.c @@ -0,0 +1,129 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +#include "g_local.h" + + +/* +============================================================================== + +PLAYER TRAIL + +============================================================================== + +This is a circular list containing the a list of points of where +the player has been recently. It is used by monsters for pursuit. + +.origin the spot +.owner forward link +.aiment backward link +*/ + + +#define TRAIL_LENGTH 8 + +edict_t *trail[TRAIL_LENGTH]; +int trail_head; +qboolean trail_active = false; + +#define NEXT(n) (((n) + 1) & (TRAIL_LENGTH - 1)) +#define PREV(n) (((n) - 1) & (TRAIL_LENGTH - 1)) + + +void PlayerTrail_Init (void) +{ + int n; + + if (deathmatch->value /* FIXME || coop */) + return; + + for (n = 0; n < TRAIL_LENGTH; n++) + { + trail[n] = G_Spawn(); + trail[n]->classname = "player_trail"; + } + + trail_head = 0; + trail_active = true; +} + + +void PlayerTrail_Add (vec3_t spot) +{ + vec3_t temp; + + if (!trail_active) + return; + + VectorCopy (spot, trail[trail_head]->s.origin); + + trail[trail_head]->timestamp = level.time; + + VectorSubtract (spot, trail[PREV(trail_head)]->s.origin, temp); + trail[trail_head]->s.angles[1] = vectoyaw (temp); + + trail_head = NEXT(trail_head); +} + + +void PlayerTrail_New (vec3_t spot) +{ + if (!trail_active) + return; + + PlayerTrail_Init (); + PlayerTrail_Add (spot); +} + + +edict_t *PlayerTrail_PickFirst (edict_t *self) +{ + int marker; + int n; + + if (!trail_active) + return NULL; + + for (marker = trail_head, n = TRAIL_LENGTH; n; n--) + { + if(trail[marker]->timestamp <= self->monsterinfo.trail_time) + marker = NEXT(marker); + else + break; + } + + if (visible(self, trail[marker])) + { + return trail[marker]; + } + + if (visible(self, trail[PREV(marker)])) + { + return trail[PREV(marker)]; + } + + return trail[marker]; +} + +edict_t *PlayerTrail_PickNext (edict_t *self) +{ + int marker; + int n; + + if (!trail_active) + return NULL; + + for (marker = trail_head, n = TRAIL_LENGTH; n; n--) + { + if(trail[marker]->timestamp <= self->monsterinfo.trail_time) + marker = NEXT(marker); + else + break; + } + + return trail[marker]; +} + +edict_t *PlayerTrail_LastSpot (void) +{ + return trail[PREV(trail_head)]; +} diff --git a/original/rogue/p_view.c b/original/rogue/p_view.c new file mode 100644 index 0000000..cc1ae2f --- /dev/null +++ b/original/rogue/p_view.c @@ -0,0 +1,1178 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. + +#include "g_local.h" +#include "m_player.h" + + + +static edict_t *current_player; +static gclient_t *current_client; + +static vec3_t forward, right, up; +float xyspeed; + +float bobmove; +int bobcycle; // odd cycles are right foot going forward +float bobfracsin; // sin(bobfrac*M_PI) + +/* +=============== +SV_CalcRoll + +=============== +*/ +float SV_CalcRoll (vec3_t angles, vec3_t velocity) +{ + float sign; + float side; + float value; + + side = DotProduct (velocity, right); + sign = side < 0 ? -1 : 1; + side = fabs(side); + + value = sv_rollangle->value; + + if (side < sv_rollspeed->value) + side = side * value / sv_rollspeed->value; + else + side = value; + + return side*sign; + +} + + +/* +=============== +P_DamageFeedback + +Handles color blends and view kicks +=============== +*/ +void P_DamageFeedback (edict_t *player) +{ + gclient_t *client; + float side; + float realcount, count, kick; + vec3_t v; + int r, l; + static vec3_t power_color = {0.0, 1.0, 0.0}; + static vec3_t acolor = {1.0, 1.0, 1.0}; + static vec3_t bcolor = {1.0, 0.0, 0.0}; + + client = player->client; + + // flash the backgrounds behind the status numbers + client->ps.stats[STAT_FLASHES] = 0; + if (client->damage_blood) + client->ps.stats[STAT_FLASHES] |= 1; + if (client->damage_armor && !(player->flags & FL_GODMODE) && (client->invincible_framenum <= level.framenum)) + client->ps.stats[STAT_FLASHES] |= 2; + + // total points of damage shot at the player this frame + count = (client->damage_blood + client->damage_armor + client->damage_parmor); + if (count == 0) + return; // didn't take any damage + + // start a pain animation if still in the player model + if (client->anim_priority < ANIM_PAIN && player->s.modelindex == 255) + { + static int i; + + client->anim_priority = ANIM_PAIN; + if (client->ps.pmove.pm_flags & PMF_DUCKED) + { + player->s.frame = FRAME_crpain1-1; + client->anim_end = FRAME_crpain4; + } + else + { + i = (i+1)%3; + switch (i) + { + case 0: + player->s.frame = FRAME_pain101-1; + client->anim_end = FRAME_pain104; + break; + case 1: + player->s.frame = FRAME_pain201-1; + client->anim_end = FRAME_pain204; + break; + case 2: + player->s.frame = FRAME_pain301-1; + client->anim_end = FRAME_pain304; + break; + } + } + } + + realcount = count; + if (count < 10) + count = 10; // always make a visible effect + + // play an apropriate pain sound + if ((level.time > player->pain_debounce_time) && !(player->flags & FL_GODMODE) && (client->invincible_framenum <= level.framenum)) + { + r = 1 + (rand()&1); + player->pain_debounce_time = level.time + 0.7; + if (player->health < 25) + l = 25; + else if (player->health < 50) + l = 50; + else if (player->health < 75) + l = 75; + else + l = 100; + gi.sound (player, CHAN_VOICE, gi.soundindex(va("*pain%i_%i.wav", l, r)), 1, ATTN_NORM, 0); + } + + // the total alpha of the blend is always proportional to count + if (client->damage_alpha < 0) + client->damage_alpha = 0; + client->damage_alpha += count*0.01; + if (client->damage_alpha < 0.2) + client->damage_alpha = 0.2; + if (client->damage_alpha > 0.6) + client->damage_alpha = 0.6; // don't go too saturated + + // the color of the blend will vary based on how much was absorbed + // by different armors + VectorClear (v); + if (client->damage_parmor) + VectorMA (v, (float)client->damage_parmor/realcount, power_color, v); + if (client->damage_armor) + VectorMA (v, (float)client->damage_armor/realcount, acolor, v); + if (client->damage_blood) + VectorMA (v, (float)client->damage_blood/realcount, bcolor, v); + VectorCopy (v, client->damage_blend); + + + // + // calculate view angle kicks + // + kick = abs(client->damage_knockback); + if (kick && player->health > 0) // kick of 0 means no view adjust at all + { + kick = kick * 100 / player->health; + + if (kick < count*0.5) + kick = count*0.5; + if (kick > 50) + kick = 50; + + VectorSubtract (client->damage_from, player->s.origin, v); + VectorNormalize (v); + + side = DotProduct (v, right); + client->v_dmg_roll = kick*side*0.3; + + side = -DotProduct (v, forward); + client->v_dmg_pitch = kick*side*0.3; + + client->v_dmg_time = level.time + DAMAGE_TIME; + } + + // + // clear totals + // + client->damage_blood = 0; + client->damage_armor = 0; + client->damage_parmor = 0; + client->damage_knockback = 0; +} + + + + +/* +=============== +SV_CalcViewOffset + +Auto pitching on slopes? + + fall from 128: 400 = 160000 + fall from 256: 580 = 336400 + fall from 384: 720 = 518400 + fall from 512: 800 = 640000 + fall from 640: 960 = + + damage = deltavelocity*deltavelocity * 0.0001 + +=============== +*/ +void SV_CalcViewOffset (edict_t *ent) +{ + float *angles; + float bob; + float ratio; + float delta; + vec3_t v; + + +//=================================== + + // base angles + angles = ent->client->ps.kick_angles; + + // if dead, fix the angle and don't add any kick + if (ent->deadflag) + { + VectorClear (angles); + + if(ent->flags & FL_SAM_RAIMI) + { + ent->client->ps.viewangles[ROLL] = 0; + ent->client->ps.viewangles[PITCH] = 0; + } + else + { + ent->client->ps.viewangles[ROLL] = 40; + ent->client->ps.viewangles[PITCH] = -15; + } + ent->client->ps.viewangles[YAW] = ent->client->killer_yaw; + } + else + { + // add angles based on weapon kick + + VectorCopy (ent->client->kick_angles, angles); + + // add angles based on damage kick + + ratio = (ent->client->v_dmg_time - level.time) / DAMAGE_TIME; + if (ratio < 0) + { + ratio = 0; + ent->client->v_dmg_pitch = 0; + ent->client->v_dmg_roll = 0; + } + angles[PITCH] += ratio * ent->client->v_dmg_pitch; + angles[ROLL] += ratio * ent->client->v_dmg_roll; + + // add pitch based on fall kick + + ratio = (ent->client->fall_time - level.time) / FALL_TIME; + if (ratio < 0) + ratio = 0; + angles[PITCH] += ratio * ent->client->fall_value; + + // add angles based on velocity + + delta = DotProduct (ent->velocity, forward); + angles[PITCH] += delta*run_pitch->value; + + delta = DotProduct (ent->velocity, right); + angles[ROLL] += delta*run_roll->value; + + // add angles based on bob + + delta = bobfracsin * bob_pitch->value * xyspeed; + if (ent->client->ps.pmove.pm_flags & PMF_DUCKED) + delta *= 6; // crouching + angles[PITCH] += delta; + delta = bobfracsin * bob_roll->value * xyspeed; + if (ent->client->ps.pmove.pm_flags & PMF_DUCKED) + delta *= 6; // crouching + if (bobcycle & 1) + delta = -delta; + angles[ROLL] += delta; + } + +//=================================== + + // base origin + + VectorClear (v); + + // add view height + + v[2] += ent->viewheight; + + // add fall height + + ratio = (ent->client->fall_time - level.time) / FALL_TIME; + if (ratio < 0) + ratio = 0; + v[2] -= ratio * ent->client->fall_value * 0.4; + + // add bob height + + bob = bobfracsin * xyspeed * bob_up->value; + if (bob > 6) + bob = 6; + //gi.DebugGraph (bob *2, 255); + v[2] += bob; + + // add kick offset + + VectorAdd (v, ent->client->kick_origin, v); + + // absolutely bound offsets + // so the view can never be outside the player box + + if (v[0] < -14) + v[0] = -14; + else if (v[0] > 14) + v[0] = 14; + if (v[1] < -14) + v[1] = -14; + else if (v[1] > 14) + v[1] = 14; + if (v[2] < -22) + v[2] = -22; + else if (v[2] > 30) + v[2] = 30; + + VectorCopy (v, ent->client->ps.viewoffset); +} + +/* +============== +SV_CalcGunOffset +============== +*/ +void SV_CalcGunOffset (edict_t *ent) +{ + int i; + float delta; + //ROGUE + static gitem_t *heatbeam; + + if (!heatbeam) + heatbeam = FindItemByClassname ("weapon_plasmabeam"); + + //ROGUE - heatbeam shouldn't bob so the beam looks right + if (ent->client->pers.weapon != heatbeam) + { + // ROGUE + // gun angles from bobbing + ent->client->ps.gunangles[ROLL] = xyspeed * bobfracsin * 0.005; + ent->client->ps.gunangles[YAW] = xyspeed * bobfracsin * 0.01; + if (bobcycle & 1) + { + ent->client->ps.gunangles[ROLL] = -ent->client->ps.gunangles[ROLL]; + ent->client->ps.gunangles[YAW] = -ent->client->ps.gunangles[YAW]; + } + + ent->client->ps.gunangles[PITCH] = xyspeed * bobfracsin * 0.005; + + // gun angles from delta movement + for (i=0 ; i<3 ; i++) + { + delta = ent->client->oldviewangles[i] - ent->client->ps.viewangles[i]; + if (delta > 180) + delta -= 360; + if (delta < -180) + delta += 360; + if (delta > 45) + delta = 45; + if (delta < -45) + delta = -45; + if (i == YAW) + ent->client->ps.gunangles[ROLL] += 0.1*delta; + ent->client->ps.gunangles[i] += 0.2 * delta; + } + } + // ROGUE + else + { + for (i=0; i<3; i++) + ent->client->ps.gunangles[i] = 0; + } + //ROGUE + + // gun height + VectorClear (ent->client->ps.gunoffset); +// ent->ps->gunorigin[2] += bob; + + // gun_x / gun_y / gun_z are development tools + for (i=0 ; i<3 ; i++) + { + ent->client->ps.gunoffset[i] += forward[i]*(gun_y->value); + ent->client->ps.gunoffset[i] += right[i]*gun_x->value; + ent->client->ps.gunoffset[i] += up[i]* (-gun_z->value); + } +} + + +/* +============= +SV_AddBlend +============= +*/ +void SV_AddBlend (float r, float g, float b, float a, float *v_blend) +{ + float a2, a3; + + if (a <= 0) + return; + a2 = v_blend[3] + (1-v_blend[3])*a; // new total alpha + a3 = v_blend[3]/a2; // fraction of color from old + + v_blend[0] = v_blend[0]*a3 + r*(1-a3); + v_blend[1] = v_blend[1]*a3 + g*(1-a3); + v_blend[2] = v_blend[2]*a3 + b*(1-a3); + v_blend[3] = a2; +} + + +/* +============= +SV_CalcBlend +============= +*/ +void SV_CalcBlend (edict_t *ent) +{ + int contents; + vec3_t vieworg; + int remaining; + + ent->client->ps.blend[0] = ent->client->ps.blend[1] = + ent->client->ps.blend[2] = ent->client->ps.blend[3] = 0; + + // add for contents + VectorAdd (ent->s.origin, ent->client->ps.viewoffset, vieworg); + contents = gi.pointcontents (vieworg); + if (contents & (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER) ) + ent->client->ps.rdflags |= RDF_UNDERWATER; + else + ent->client->ps.rdflags &= ~RDF_UNDERWATER; + + if (contents & (CONTENTS_SOLID|CONTENTS_LAVA)) + SV_AddBlend (1.0, 0.3, 0.0, 0.6, ent->client->ps.blend); + else if (contents & CONTENTS_SLIME) + SV_AddBlend (0.0, 0.1, 0.05, 0.6, ent->client->ps.blend); + else if (contents & CONTENTS_WATER) + SV_AddBlend (0.5, 0.3, 0.2, 0.4, ent->client->ps.blend); + + // add for powerups + if (ent->client->quad_framenum > level.framenum) + { + remaining = ent->client->quad_framenum - level.framenum; + if (remaining == 30) // beginning to fade + gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage2.wav"), 1, ATTN_NORM, 0); + if (remaining > 30 || (remaining & 4) ) + SV_AddBlend (0, 0, 1, 0.08, ent->client->ps.blend); + } + // PMM - double damage + else if (ent->client->double_framenum > level.framenum) + { + remaining = ent->client->double_framenum - level.framenum; + if (remaining == 30) // beginning to fade + gi.sound(ent, CHAN_ITEM, gi.soundindex("misc/ddamage2.wav"), 1, ATTN_NORM, 0); + if (remaining > 30 || (remaining & 4) ) + SV_AddBlend (0.9, 0.7, 0, 0.08, ent->client->ps.blend); + } + // PMM + else if (ent->client->invincible_framenum > level.framenum) + { + remaining = ent->client->invincible_framenum - level.framenum; + if (remaining == 30) // beginning to fade + gi.sound(ent, CHAN_ITEM, gi.soundindex("items/protect2.wav"), 1, ATTN_NORM, 0); + if (remaining > 30 || (remaining & 4) ) + SV_AddBlend (1, 1, 0, 0.08, ent->client->ps.blend); + } + else if (ent->client->enviro_framenum > level.framenum) + { + remaining = ent->client->enviro_framenum - level.framenum; + if (remaining == 30) // beginning to fade + gi.sound(ent, CHAN_ITEM, gi.soundindex("items/airout.wav"), 1, ATTN_NORM, 0); + if (remaining > 30 || (remaining & 4) ) + SV_AddBlend (0, 1, 0, 0.08, ent->client->ps.blend); + } + else if (ent->client->breather_framenum > level.framenum) + { + remaining = ent->client->breather_framenum - level.framenum; + if (remaining == 30) // beginning to fade + gi.sound(ent, CHAN_ITEM, gi.soundindex("items/airout.wav"), 1, ATTN_NORM, 0); + if (remaining > 30 || (remaining & 4) ) + SV_AddBlend (0.4, 1, 0.4, 0.04, ent->client->ps.blend); + } + +//PGM + if(ent->client->nuke_framenum > level.framenum) + { + float brightness; + brightness = (ent->client->nuke_framenum - level.framenum) / 20.0; + SV_AddBlend (1, 1, 1, brightness, ent->client->ps.blend); + } + if (ent->client->ir_framenum > level.framenum) + { + remaining = ent->client->ir_framenum - level.framenum; + if(remaining > 30 || (remaining & 4)) + { + ent->client->ps.rdflags |= RDF_IRGOGGLES; + SV_AddBlend (1, 0, 0, 0.2, ent->client->ps.blend); + } + else + ent->client->ps.rdflags &= ~RDF_IRGOGGLES; + } + else + { + ent->client->ps.rdflags &= ~RDF_IRGOGGLES; + } +//PGM + + // add for damage + if (ent->client->damage_alpha > 0) + SV_AddBlend (ent->client->damage_blend[0],ent->client->damage_blend[1] + ,ent->client->damage_blend[2], ent->client->damage_alpha, ent->client->ps.blend); + + if (ent->client->bonus_alpha > 0) + SV_AddBlend (0.85, 0.7, 0.3, ent->client->bonus_alpha, ent->client->ps.blend); + + // drop the damage value + ent->client->damage_alpha -= 0.06; + if (ent->client->damage_alpha < 0) + ent->client->damage_alpha = 0; + + // drop the bonus value + ent->client->bonus_alpha -= 0.1; + if (ent->client->bonus_alpha < 0) + ent->client->bonus_alpha = 0; +} + + +/* +================= +P_FallingDamage +================= +*/ +void P_FallingDamage (edict_t *ent) +{ + float delta; + int damage; + vec3_t dir; + + if (ent->s.modelindex != 255) + return; // not in the player model + + if (ent->movetype == MOVETYPE_NOCLIP) + return; + + if ((ent->client->oldvelocity[2] < 0) && (ent->velocity[2] > ent->client->oldvelocity[2]) && (!ent->groundentity)) + { + delta = ent->client->oldvelocity[2]; + } + else + { + if (!ent->groundentity) + return; + delta = ent->velocity[2] - ent->client->oldvelocity[2]; + } + delta = delta*delta * 0.0001; + + // never take falling damage if completely underwater + if (ent->waterlevel == 3) + return; + if (ent->waterlevel == 2) + delta *= 0.25; + if (ent->waterlevel == 1) + delta *= 0.5; + + if (delta < 1) + return; + + if (delta < 15) + { + ent->s.event = EV_FOOTSTEP; + return; + } + + ent->client->fall_value = delta*0.5; + if (ent->client->fall_value > 40) + ent->client->fall_value = 40; + ent->client->fall_time = level.time + FALL_TIME; + + if (delta > 30) + { + if (ent->health > 0) + { + if (delta >= 55) + ent->s.event = EV_FALLFAR; + else + ent->s.event = EV_FALL; + } + ent->pain_debounce_time = level.time; // no normal pain sound + damage = (delta-30)/2; + if (damage < 1) + damage = 1; + VectorSet (dir, 0, 0, 1); + + if (!deathmatch->value || !((int)dmflags->value & DF_NO_FALLING) ) + T_Damage (ent, world, world, dir, ent->s.origin, vec3_origin, damage, 0, 0, MOD_FALLING); + } + else + { + ent->s.event = EV_FALLSHORT; + return; + } +} + + + +/* +============= +P_WorldEffects +============= +*/ +void P_WorldEffects (void) +{ + qboolean breather; + qboolean envirosuit; + int waterlevel, old_waterlevel; + + if (current_player->movetype == MOVETYPE_NOCLIP) + { + current_player->air_finished = level.time + 12; // don't need air + return; + } + + waterlevel = current_player->waterlevel; + old_waterlevel = current_client->old_waterlevel; + current_client->old_waterlevel = waterlevel; + + breather = current_client->breather_framenum > level.framenum; + envirosuit = current_client->enviro_framenum > level.framenum; + + // + // if just entered a water volume, play a sound + // + if (!old_waterlevel && waterlevel) + { + PlayerNoise(current_player, current_player->s.origin, PNOISE_SELF); + if (current_player->watertype & CONTENTS_LAVA) + gi.sound (current_player, CHAN_BODY, gi.soundindex("player/lava_in.wav"), 1, ATTN_NORM, 0); + else if (current_player->watertype & CONTENTS_SLIME) + gi.sound (current_player, CHAN_BODY, gi.soundindex("player/watr_in.wav"), 1, ATTN_NORM, 0); + else if (current_player->watertype & CONTENTS_WATER) + gi.sound (current_player, CHAN_BODY, gi.soundindex("player/watr_in.wav"), 1, ATTN_NORM, 0); + current_player->flags |= FL_INWATER; + + // clear damage_debounce, so the pain sound will play immediately + current_player->damage_debounce_time = level.time - 1; + } + + // + // if just completely exited a water volume, play a sound + // + if (old_waterlevel && ! waterlevel) + { + PlayerNoise(current_player, current_player->s.origin, PNOISE_SELF); + gi.sound (current_player, CHAN_BODY, gi.soundindex("player/watr_out.wav"), 1, ATTN_NORM, 0); + current_player->flags &= ~FL_INWATER; + } + + // + // check for head just going under water + // + if (old_waterlevel != 3 && waterlevel == 3) + { + gi.sound (current_player, CHAN_BODY, gi.soundindex("player/watr_un.wav"), 1, ATTN_NORM, 0); + } + + // + // check for head just coming out of water + // + if (old_waterlevel == 3 && waterlevel != 3) + { + if (current_player->air_finished < level.time) + { // gasp for air + gi.sound (current_player, CHAN_VOICE, gi.soundindex("player/gasp1.wav"), 1, ATTN_NORM, 0); + PlayerNoise(current_player, current_player->s.origin, PNOISE_SELF); + } + else if (current_player->air_finished < level.time + 11) + { // just break surface + gi.sound (current_player, CHAN_VOICE, gi.soundindex("player/gasp2.wav"), 1, ATTN_NORM, 0); + } + } + + // + // check for drowning + // + if (waterlevel == 3) + { + // breather or envirosuit give air + if (breather || envirosuit) + { + current_player->air_finished = level.time + 10; + + if (((int)(current_client->breather_framenum - level.framenum) % 25) == 0) + { + if (!current_client->breather_sound) + gi.sound (current_player, CHAN_AUTO, gi.soundindex("player/u_breath1.wav"), 1, ATTN_NORM, 0); + else + gi.sound (current_player, CHAN_AUTO, gi.soundindex("player/u_breath2.wav"), 1, ATTN_NORM, 0); + current_client->breather_sound ^= 1; + PlayerNoise(current_player, current_player->s.origin, PNOISE_SELF); + //FIXME: release a bubble? + } + } + + // if out of air, start drowning + if (current_player->air_finished < level.time) + { // drown! + if (current_player->client->next_drown_time < level.time + && current_player->health > 0) + { + current_player->client->next_drown_time = level.time + 1; + + // take more damage the longer underwater + current_player->dmg += 2; + if (current_player->dmg > 15) + current_player->dmg = 15; + + // play a gurp sound instead of a normal pain sound + if (current_player->health <= current_player->dmg) + gi.sound (current_player, CHAN_VOICE, gi.soundindex("player/drown1.wav"), 1, ATTN_NORM, 0); + else if (rand()&1) + gi.sound (current_player, CHAN_VOICE, gi.soundindex("*gurp1.wav"), 1, ATTN_NORM, 0); + else + gi.sound (current_player, CHAN_VOICE, gi.soundindex("*gurp2.wav"), 1, ATTN_NORM, 0); + + current_player->pain_debounce_time = level.time; + + T_Damage (current_player, world, world, vec3_origin, current_player->s.origin, vec3_origin, current_player->dmg, 0, DAMAGE_NO_ARMOR, MOD_WATER); + } + } + } + else + { + current_player->air_finished = level.time + 12; + current_player->dmg = 2; + } + + // + // check for sizzle damage + // + if (waterlevel && (current_player->watertype&(CONTENTS_LAVA|CONTENTS_SLIME)) ) + { + if (current_player->watertype & CONTENTS_LAVA) + { + if (current_player->health > 0 + && current_player->pain_debounce_time <= level.time + && current_client->invincible_framenum < level.framenum) + { + if (rand()&1) + gi.sound (current_player, CHAN_VOICE, gi.soundindex("player/burn1.wav"), 1, ATTN_NORM, 0); + else + gi.sound (current_player, CHAN_VOICE, gi.soundindex("player/burn2.wav"), 1, ATTN_NORM, 0); + current_player->pain_debounce_time = level.time + 1; + } + + if (envirosuit) // take 1/3 damage with envirosuit + T_Damage (current_player, world, world, vec3_origin, current_player->s.origin, vec3_origin, 1*waterlevel, 0, 0, MOD_LAVA); + else + T_Damage (current_player, world, world, vec3_origin, current_player->s.origin, vec3_origin, 3*waterlevel, 0, 0, MOD_LAVA); + } + + if (current_player->watertype & CONTENTS_SLIME) + { + if (!envirosuit) + { // no damage from slime with envirosuit + T_Damage (current_player, world, world, vec3_origin, current_player->s.origin, vec3_origin, 1*waterlevel, 0, 0, MOD_SLIME); + } + } + } +} + + +/* +=============== +G_SetClientEffects +=============== +*/ +void G_SetClientEffects (edict_t *ent) +{ + int pa_type; + int remaining; + + ent->s.effects = 0; +// ent->s.renderfx = 0; + + // PGM - player is always ir visible, even dead. + ent->s.renderfx = RF_IR_VISIBLE; + + if (ent->health <= 0 || level.intermissiontime) + return; + +//========= +//PGM + if(ent->flags & FL_DISGUISED) + ent->s.renderfx |= RF_USE_DISGUISE; + + if (gamerules && gamerules->value) + { + if(DMGame.PlayerEffects) + DMGame.PlayerEffects(ent); + } +//PGM +//========= + + if (ent->powerarmor_time > level.time) + { + pa_type = PowerArmorType (ent); + if (pa_type == POWER_ARMOR_SCREEN) + { + ent->s.effects |= EF_POWERSCREEN; + } + else if (pa_type == POWER_ARMOR_SHIELD) + { + ent->s.effects |= EF_COLOR_SHELL; + ent->s.renderfx |= RF_SHELL_GREEN; + } + } + + if (ent->client->quad_framenum > level.framenum) + { + remaining = ent->client->quad_framenum - level.framenum; + if (remaining > 30 || (remaining & 4) ) + ent->s.effects |= EF_QUAD; + } + +//======= +//ROGUE + if (ent->client->double_framenum > level.framenum) + { + remaining = ent->client->double_framenum - level.framenum; + if (remaining > 30 || (remaining & 4) ) + ent->s.effects |= EF_DOUBLE; + } + if ((ent->client->owned_sphere) && (ent->client->owned_sphere->spawnflags == 1)) + { + ent->s.effects |= EF_HALF_DAMAGE; + } + if (ent->client->tracker_pain_framenum > level.framenum) + { + ent->s.effects |= EF_TRACKERTRAIL; + } +//ROGUE +//======= + + if (ent->client->invincible_framenum > level.framenum) + { + remaining = ent->client->invincible_framenum - level.framenum; + if (remaining > 30 || (remaining & 4) ) + ent->s.effects |= EF_PENT; + } + + // show cheaters!!! + if (ent->flags & FL_GODMODE) + { + ent->s.effects |= EF_COLOR_SHELL; + ent->s.renderfx |= (RF_SHELL_RED|RF_SHELL_GREEN|RF_SHELL_BLUE); + } + +//PGM +/* + if (ent->client->torch_framenum > level.framenum) + { + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_FLASHLIGHT); + gi.WritePosition (ent->s.origin); + gi.WriteShort (ent - g_edicts); + gi.multicast (ent->s.origin, MULTICAST_PVS); + } +*/ +//PGM +} + + +/* +=============== +G_SetClientEvent +=============== +*/ +void G_SetClientEvent (edict_t *ent) +{ + if (ent->s.event) + return; + + if ( ent->groundentity && xyspeed > 225) + { + if ( (int)(current_client->bobtime+bobmove) != bobcycle ) + ent->s.event = EV_FOOTSTEP; + } +} + +/* +=============== +G_SetClientSound +=============== +*/ +void G_SetClientSound (edict_t *ent) +{ + char *weap; + + if (ent->client->pers.game_helpchanged != game.helpchanged) + { + ent->client->pers.game_helpchanged = game.helpchanged; + ent->client->pers.helpchanged = 1; + } + + // help beep (no more than three times) + if (ent->client->pers.helpchanged && ent->client->pers.helpchanged <= 3 && !(level.framenum&63) ) + { + ent->client->pers.helpchanged++; + gi.sound (ent, CHAN_VOICE, gi.soundindex ("misc/pc_up.wav"), 1, ATTN_STATIC, 0); + } + + + if (ent->client->pers.weapon) + weap = ent->client->pers.weapon->classname; + else + weap = ""; + + if (ent->waterlevel && (ent->watertype&(CONTENTS_LAVA|CONTENTS_SLIME)) ) + ent->s.sound = snd_fry; + else if (strcmp(weap, "weapon_railgun") == 0) + ent->s.sound = gi.soundindex("weapons/rg_hum.wav"); + else if (strcmp(weap, "weapon_bfg") == 0) + ent->s.sound = gi.soundindex("weapons/bfg_hum.wav"); + else if (ent->client->weapon_sound) + ent->s.sound = ent->client->weapon_sound; + else + ent->s.sound = 0; +} + +/* +=============== +G_SetClientFrame +=============== +*/ +void G_SetClientFrame (edict_t *ent) +{ + gclient_t *client; + qboolean duck, run; + + if (ent->s.modelindex != 255) + return; // not in the player model + + client = ent->client; + + if (client->ps.pmove.pm_flags & PMF_DUCKED) + duck = true; + else + duck = false; + if (xyspeed) + run = true; + else + run = false; + + // check for stand/duck and stop/go transitions + if (duck != client->anim_duck && client->anim_priority < ANIM_DEATH) + goto newanim; + if (run != client->anim_run && client->anim_priority == ANIM_BASIC) + goto newanim; + if (!ent->groundentity && client->anim_priority <= ANIM_WAVE) + goto newanim; + + if(client->anim_priority == ANIM_REVERSE) + { + if(ent->s.frame > client->anim_end) + { + ent->s.frame--; + return; + } + } + else if (ent->s.frame < client->anim_end) + { // continue an animation + ent->s.frame++; + return; + } + + if (client->anim_priority == ANIM_DEATH) + return; // stay there + if (client->anim_priority == ANIM_JUMP) + { + if (!ent->groundentity) + return; // stay there + ent->client->anim_priority = ANIM_WAVE; + ent->s.frame = FRAME_jump3; + ent->client->anim_end = FRAME_jump6; + return; + } + +newanim: + // return to either a running or standing frame + client->anim_priority = ANIM_BASIC; + client->anim_duck = duck; + client->anim_run = run; + + if (!ent->groundentity) + { + client->anim_priority = ANIM_JUMP; + if (ent->s.frame != FRAME_jump2) + ent->s.frame = FRAME_jump1; + client->anim_end = FRAME_jump2; + } + else if (run) + { // running + if (duck) + { + ent->s.frame = FRAME_crwalk1; + client->anim_end = FRAME_crwalk6; + } + else + { + ent->s.frame = FRAME_run1; + client->anim_end = FRAME_run6; + } + } + else + { // standing + if (duck) + { + ent->s.frame = FRAME_crstnd01; + client->anim_end = FRAME_crstnd19; + } + else + { + ent->s.frame = FRAME_stand01; + client->anim_end = FRAME_stand40; + } + } +} + + +/* +================= +ClientEndServerFrame + +Called for each player at the end of the server frame +and right after spawning +================= +*/ +void ClientEndServerFrame (edict_t *ent) +{ + float bobtime; + int i; + + current_player = ent; + current_client = ent->client; + + // + // If the origin or velocity have changed since ClientThink(), + // update the pmove values. This will happen when the client + // is pushed by a bmodel or kicked by an explosion. + // + // If it wasn't updated here, the view position would lag a frame + // behind the body position when pushed -- "sinking into plats" + // + for (i=0 ; i<3 ; i++) + { + current_client->ps.pmove.origin[i] = ent->s.origin[i]*8.0; + current_client->ps.pmove.velocity[i] = ent->velocity[i]*8.0; + } + + // + // If the end of unit layout is displayed, don't give + // the player any normal movement attributes + // + if (level.intermissiontime) + { + // FIXME: add view drifting here? + current_client->ps.blend[3] = 0; + current_client->ps.fov = 90; + G_SetStats (ent); + return; + } + + AngleVectors (ent->client->v_angle, forward, right, up); + + // burn from lava, etc + P_WorldEffects (); + + // + // set model angles from view angles so other things in + // the world can tell which direction you are looking + // + if (ent->client->v_angle[PITCH] > 180) + ent->s.angles[PITCH] = (-360 + ent->client->v_angle[PITCH])/3; + else + ent->s.angles[PITCH] = ent->client->v_angle[PITCH]/3; + ent->s.angles[YAW] = ent->client->v_angle[YAW]; + ent->s.angles[ROLL] = 0; + ent->s.angles[ROLL] = SV_CalcRoll (ent->s.angles, ent->velocity)*4; + + // + // calculate speed and cycle to be used for + // all cyclic walking effects + // + xyspeed = sqrt(ent->velocity[0]*ent->velocity[0] + ent->velocity[1]*ent->velocity[1]); + + if (xyspeed < 5) + { + bobmove = 0; + current_client->bobtime = 0; // start at beginning of cycle again + } + else if (ent->groundentity) + { // so bobbing only cycles when on ground + if (xyspeed > 210) + bobmove = 0.25; + else if (xyspeed > 100) + bobmove = 0.125; + else + bobmove = 0.0625; + } + + bobtime = (current_client->bobtime += bobmove); + + if (current_client->ps.pmove.pm_flags & PMF_DUCKED) + bobtime *= 4; + + bobcycle = (int)bobtime; + bobfracsin = fabs(sin(bobtime*M_PI)); + + // detect hitting the floor + P_FallingDamage (ent); + + // apply all the damage taken this frame + P_DamageFeedback (ent); + + // determine the view offsets + SV_CalcViewOffset (ent); + + // determine the gun offsets + SV_CalcGunOffset (ent); + + // determine the full screen color blend + // must be after viewoffset, so eye contents can be + // accurately determined + // FIXME: with client prediction, the contents + // should be determined by the client + SV_CalcBlend (ent); + + // chase cam stuff + if (ent->client->resp.spectator) + G_SetSpectatorStats(ent); + else + G_SetStats (ent); + + G_CheckChaseStats(ent); + + G_SetClientEvent (ent); + + G_SetClientEffects (ent); + + G_SetClientSound (ent); + + G_SetClientFrame (ent); + + VectorCopy (ent->velocity, ent->client->oldvelocity); + VectorCopy (ent->client->ps.viewangles, ent->client->oldviewangles); + + // clear weapon kicks + VectorClear (ent->client->kick_origin); + VectorClear (ent->client->kick_angles); + + // if the scoreboard is up, update it + if (ent->client->showscores && !(level.framenum & 31) ) + { + DeathmatchScoreboardMessage (ent, ent->enemy); + gi.unicast (ent, false); + } +} + diff --git a/original/rogue/p_weapon.c b/original/rogue/p_weapon.c new file mode 100644 index 0000000..be23fe6 --- /dev/null +++ b/original/rogue/p_weapon.c @@ -0,0 +1,2273 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// g_weapon.c + +#include "g_local.h" +#include "m_player.h" + + +static qboolean is_quad; +static byte is_silenced; + +//PGM +static byte damage_multiplier; +//PGM + +void weapon_grenade_fire (edict_t *ent, qboolean held); + +//======== +//ROGUE +byte P_DamageModifier(edict_t *ent) +{ + is_quad = 0; + damage_multiplier = 1; + + if(ent->client->quad_framenum > level.framenum) + { + damage_multiplier *= 4; + is_quad = 1; + + // if we're quad and DF_NO_STACK_DOUBLE is on, return now. + if(((int)(dmflags->value) & DF_NO_STACK_DOUBLE)) + return damage_multiplier; + } + if(ent->client->double_framenum > level.framenum) + { + if ((deathmatch->value) || (damage_multiplier == 1)) + { + damage_multiplier *= 2; + is_quad = 1; + } + } + + return damage_multiplier; +} +//ROGUE +//======== + +static void P_ProjectSource (gclient_t *client, vec3_t point, vec3_t distance, vec3_t forward, vec3_t right, vec3_t result) +{ + vec3_t _distance; + + VectorCopy (distance, _distance); + if (client->pers.hand == LEFT_HANDED) + _distance[1] *= -1; + else if (client->pers.hand == CENTER_HANDED) + _distance[1] = 0; + G_ProjectSource (point, _distance, forward, right, result); +} + +static void P_ProjectSource2 (gclient_t *client, vec3_t point, vec3_t distance, vec3_t forward, + vec3_t right, vec3_t up, vec3_t result) +{ + vec3_t _distance; + + VectorCopy (distance, _distance); + if (client->pers.hand == LEFT_HANDED) + _distance[1] *= -1; + else if (client->pers.hand == CENTER_HANDED) + _distance[1] = 0; + G_ProjectSource2 (point, _distance, forward, right, up, result); +} + +/* +=============== +PlayerNoise + +Each player can have two noise objects associated with it: +a personal noise (jumping, pain, weapon firing), and a weapon +target noise (bullet wall impacts) + +Monsters that don't directly see the player can move +to a noise in hopes of seeing the player from there. +=============== +*/ +void PlayerNoise(edict_t *who, vec3_t where, int type) +{ + edict_t *noise; + + if (type == PNOISE_WEAPON) + { + if (who->client->silencer_shots) + { + who->client->silencer_shots--; + return; + } + } + + if (deathmatch->value) + return; + + if (who->flags & FL_NOTARGET) + return; + + if (who->flags & FL_DISGUISED) + { + if (type == PNOISE_WEAPON) + { + level.disguise_violator = who; + level.disguise_violation_framenum = level.framenum + 5; + } + else + return; + } + + if (!who->mynoise) + { + noise = G_Spawn(); + noise->classname = "player_noise"; + VectorSet (noise->mins, -8, -8, -8); + VectorSet (noise->maxs, 8, 8, 8); + noise->owner = who; + noise->svflags = SVF_NOCLIENT; + who->mynoise = noise; + + noise = G_Spawn(); + noise->classname = "player_noise"; + VectorSet (noise->mins, -8, -8, -8); + VectorSet (noise->maxs, 8, 8, 8); + noise->owner = who; + noise->svflags = SVF_NOCLIENT; + who->mynoise2 = noise; + } + + if (type == PNOISE_SELF || type == PNOISE_WEAPON) + { + noise = who->mynoise; + level.sound_entity = noise; + level.sound_entity_framenum = level.framenum; + } + else // type == PNOISE_IMPACT + { + noise = who->mynoise2; + level.sound2_entity = noise; + level.sound2_entity_framenum = level.framenum; + } + + VectorCopy (where, noise->s.origin); + VectorSubtract (where, noise->maxs, noise->absmin); + VectorAdd (where, noise->maxs, noise->absmax); + noise->teleport_time = level.time; + gi.linkentity (noise); +} + + +qboolean Pickup_Weapon (edict_t *ent, edict_t *other) +{ + int index; + gitem_t *ammo; + + index = ITEM_INDEX(ent->item); + + if ( ( ((int)(dmflags->value) & DF_WEAPONS_STAY) || coop->value) + && other->client->pers.inventory[index]) + { + if (!(ent->spawnflags & (DROPPED_ITEM | DROPPED_PLAYER_ITEM) ) ) + return false; // leave the weapon for others to pickup + } + + other->client->pers.inventory[index]++; + + if (!(ent->spawnflags & DROPPED_ITEM) ) + { + // give them some ammo with it + // PGM -- IF APPROPRIATE! + if(ent->item->ammo) //PGM + { + ammo = FindItem (ent->item->ammo); + if ( (int)dmflags->value & DF_INFINITE_AMMO ) + Add_Ammo (other, ammo, 1000); + else + Add_Ammo (other, ammo, ammo->quantity); + } + + if (! (ent->spawnflags & DROPPED_PLAYER_ITEM) ) + { + if (deathmatch->value) + { + if ((int)(dmflags->value) & DF_WEAPONS_STAY) + ent->flags |= FL_RESPAWN; + else + SetRespawn (ent, 30); + } + if (coop->value) + ent->flags |= FL_RESPAWN; + } + } + + if (other->client->pers.weapon != ent->item && + (other->client->pers.inventory[index] == 1) && + ( !deathmatch->value || other->client->pers.weapon == FindItem("blaster") ) ) + other->client->newweapon = ent->item; + + return true; +} + + +/* +=============== +ChangeWeapon + +The old weapon has been dropped all the way, so make the new one +current +=============== +*/ +void ChangeWeapon (edict_t *ent) +{ + int i; + + if (ent->client->grenade_time) + { + ent->client->grenade_time = level.time; + ent->client->weapon_sound = 0; + weapon_grenade_fire (ent, false); + ent->client->grenade_time = 0; + } + + ent->client->pers.lastweapon = ent->client->pers.weapon; + ent->client->pers.weapon = ent->client->newweapon; + ent->client->newweapon = NULL; + ent->client->machinegun_shots = 0; + + // set visible model + if (ent->s.modelindex == 255) + { + if (ent->client->pers.weapon) + i = ((ent->client->pers.weapon->weapmodel & 0xff) << 8); + else + i = 0; + ent->s.skinnum = (ent - g_edicts - 1) | i; + } + + if (ent->client->pers.weapon && ent->client->pers.weapon->ammo) + ent->client->ammo_index = ITEM_INDEX(FindItem(ent->client->pers.weapon->ammo)); + else + ent->client->ammo_index = 0; + + if (!ent->client->pers.weapon) + { // dead + ent->client->ps.gunindex = 0; + return; + } + + ent->client->weaponstate = WEAPON_ACTIVATING; + ent->client->ps.gunframe = 0; + ent->client->ps.gunindex = gi.modelindex(ent->client->pers.weapon->view_model); + + ent->client->anim_priority = ANIM_PAIN; + if(ent->client->ps.pmove.pm_flags & PMF_DUCKED) + { + ent->s.frame = FRAME_crpain1; + ent->client->anim_end = FRAME_crpain4; + } + else + { + ent->s.frame = FRAME_pain301; + ent->client->anim_end = FRAME_pain304; + + } +} + +/* +================= +NoAmmoWeaponChange +================= +*/ + +// PMM - added rogue weapons to the list + +void NoAmmoWeaponChange (edict_t *ent) +{ + if ( ent->client->pers.inventory[ITEM_INDEX(FindItem("slugs"))] + && ent->client->pers.inventory[ITEM_INDEX(FindItem("railgun"))] ) + { + ent->client->newweapon = FindItem ("railgun"); + return; + } + // ROGUE + if ( (ent->client->pers.inventory[ITEM_INDEX(FindItem("cells"))] >= 2) + && ent->client->pers.inventory[ITEM_INDEX(FindItem("Plasma Beam"))] ) + { + ent->client->newweapon = FindItem ("Plasma Beam"); + return; + } + // -ROGUE + /* + if ( ent->client->pers.inventory[ITEM_INDEX(FindItem("cells"))] + && ent->client->pers.inventory[ITEM_INDEX(FindItem("hyperblaster"))] ) + { + ent->client->newweapon = FindItem ("hyperblaster"); + return; + } + */ + // ROGUE + if ( ent->client->pers.inventory[ITEM_INDEX(FindItem("flechettes"))] + && ent->client->pers.inventory[ITEM_INDEX(FindItem("etf rifle"))] ) + { + ent->client->newweapon = FindItem ("etf rifle"); + return; + } + // -ROGUE + if ( ent->client->pers.inventory[ITEM_INDEX(FindItem("bullets"))] + && ent->client->pers.inventory[ITEM_INDEX(FindItem("chaingun"))] ) + { + ent->client->newweapon = FindItem ("chaingun"); + return; + } + if ( ent->client->pers.inventory[ITEM_INDEX(FindItem("bullets"))] + && ent->client->pers.inventory[ITEM_INDEX(FindItem("machinegun"))] ) + { + ent->client->newweapon = FindItem ("machinegun"); + return; + } + if ( ent->client->pers.inventory[ITEM_INDEX(FindItem("shells"))] > 1 + && ent->client->pers.inventory[ITEM_INDEX(FindItem("super shotgun"))] ) + { + ent->client->newweapon = FindItem ("super shotgun"); + return; + } + if ( ent->client->pers.inventory[ITEM_INDEX(FindItem("shells"))] + && ent->client->pers.inventory[ITEM_INDEX(FindItem("shotgun"))] ) + { + ent->client->newweapon = FindItem ("shotgun"); + return; + } + ent->client->newweapon = FindItem ("blaster"); +} + +/* +================= +Think_Weapon + +Called by ClientBeginServerFrame and ClientThink +================= +*/ +void Think_Weapon (edict_t *ent) +{ + // if just died, put the weapon away + if (ent->health < 1) + { + ent->client->newweapon = NULL; + ChangeWeapon (ent); + } + + // call active weapon think routine + if (ent->client->pers.weapon && ent->client->pers.weapon->weaponthink) + { +//PGM + P_DamageModifier(ent); +// is_quad = (ent->client->quad_framenum > level.framenum); +//PGM + if (ent->client->silencer_shots) + is_silenced = MZ_SILENCED; + else + is_silenced = 0; + ent->client->pers.weapon->weaponthink (ent); + } +} + + +/* +================ +Use_Weapon + +Make the weapon ready if there is ammo +================ +*/ +void Use_Weapon (edict_t *ent, gitem_t *item) +{ + int ammo_index; + gitem_t *ammo_item; + + // see if we're already using it + if (item == ent->client->pers.weapon) + return; + + if (item->ammo && !g_select_empty->value && !(item->flags & IT_AMMO)) + { + ammo_item = FindItem(item->ammo); + ammo_index = ITEM_INDEX(ammo_item); + + if (!ent->client->pers.inventory[ammo_index]) + { + gi.cprintf (ent, PRINT_HIGH, "No %s for %s.\n", ammo_item->pickup_name, item->pickup_name); + return; + } + + if (ent->client->pers.inventory[ammo_index] < item->quantity) + { + gi.cprintf (ent, PRINT_HIGH, "Not enough %s for %s.\n", ammo_item->pickup_name, item->pickup_name); + return; + } + } + + // change to this weapon when down + ent->client->newweapon = item; +} + + + +/* +================ +Drop_Weapon +================ +*/ +void Drop_Weapon (edict_t *ent, gitem_t *item) +{ + int index; + + if ((int)(dmflags->value) & DF_WEAPONS_STAY) + return; + + index = ITEM_INDEX(item); + // see if we're already using it + if ( ((item == ent->client->pers.weapon) || (item == ent->client->newweapon))&& (ent->client->pers.inventory[index] == 1) ) + { + gi.cprintf (ent, PRINT_HIGH, "Can't drop current weapon\n"); + return; + } + + Drop_Item (ent, item); + ent->client->pers.inventory[index]--; +} + + +/* +================ +Weapon_Generic + +A generic function to handle the basics of weapon thinking +================ +*/ +#define FRAME_FIRE_FIRST (FRAME_ACTIVATE_LAST + 1) +#define FRAME_IDLE_FIRST (FRAME_FIRE_LAST + 1) +#define FRAME_DEACTIVATE_FIRST (FRAME_IDLE_LAST + 1) + +void Weapon_Generic (edict_t *ent, int FRAME_ACTIVATE_LAST, int FRAME_FIRE_LAST, int FRAME_IDLE_LAST, int FRAME_DEACTIVATE_LAST, int *pause_frames, int *fire_frames, void (*fire)(edict_t *ent)) +{ + int n; + + if(ent->deadflag || ent->s.modelindex != 255) // VWep animations screw up corpses + { + return; + } + + if (ent->client->weaponstate == WEAPON_DROPPING) + { + if (ent->client->ps.gunframe == FRAME_DEACTIVATE_LAST) + { + ChangeWeapon (ent); + return; + } + else if ((FRAME_DEACTIVATE_LAST - ent->client->ps.gunframe) == 4) + { + ent->client->anim_priority = ANIM_REVERSE; + if(ent->client->ps.pmove.pm_flags & PMF_DUCKED) + { + ent->s.frame = FRAME_crpain4+1; + ent->client->anim_end = FRAME_crpain1; + } + else + { + ent->s.frame = FRAME_pain304+1; + ent->client->anim_end = FRAME_pain301; + + } + } + + ent->client->ps.gunframe++; + return; + } + + if (ent->client->weaponstate == WEAPON_ACTIVATING) + { + if (ent->client->ps.gunframe == FRAME_ACTIVATE_LAST) + { + ent->client->weaponstate = WEAPON_READY; + ent->client->ps.gunframe = FRAME_IDLE_FIRST; + return; + } + + ent->client->ps.gunframe++; + return; + } + + if ((ent->client->newweapon) && (ent->client->weaponstate != WEAPON_FIRING)) + { + ent->client->weaponstate = WEAPON_DROPPING; + ent->client->ps.gunframe = FRAME_DEACTIVATE_FIRST; + + if ((FRAME_DEACTIVATE_LAST - FRAME_DEACTIVATE_FIRST) < 4) + { + ent->client->anim_priority = ANIM_REVERSE; + if(ent->client->ps.pmove.pm_flags & PMF_DUCKED) + { + ent->s.frame = FRAME_crpain4+1; + ent->client->anim_end = FRAME_crpain1; + } + else + { + ent->s.frame = FRAME_pain304+1; + ent->client->anim_end = FRAME_pain301; + + } + } + return; + } + + if (ent->client->weaponstate == WEAPON_READY) + { + if ( ((ent->client->latched_buttons|ent->client->buttons) & BUTTON_ATTACK) ) + { + ent->client->latched_buttons &= ~BUTTON_ATTACK; + if ((!ent->client->ammo_index) || + ( ent->client->pers.inventory[ent->client->ammo_index] >= ent->client->pers.weapon->quantity)) + { + ent->client->ps.gunframe = FRAME_FIRE_FIRST; + ent->client->weaponstate = WEAPON_FIRING; + + // start the animation + ent->client->anim_priority = ANIM_ATTACK; + if (ent->client->ps.pmove.pm_flags & PMF_DUCKED) + { + ent->s.frame = FRAME_crattak1-1; + ent->client->anim_end = FRAME_crattak9; + } + else + { + ent->s.frame = FRAME_attack1-1; + ent->client->anim_end = FRAME_attack8; + } + } + else + { + if (level.time >= ent->pain_debounce_time) + { + gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/noammo.wav"), 1, ATTN_NORM, 0); + ent->pain_debounce_time = level.time + 1; + } + NoAmmoWeaponChange (ent); + } + } + else + { + if (ent->client->ps.gunframe == FRAME_IDLE_LAST) + { + ent->client->ps.gunframe = FRAME_IDLE_FIRST; + return; + } + + if (pause_frames) + { + for (n = 0; pause_frames[n]; n++) + { + if (ent->client->ps.gunframe == pause_frames[n]) + { + if (rand()&15) + return; + } + } + } + + ent->client->ps.gunframe++; + return; + } + } + + if (ent->client->weaponstate == WEAPON_FIRING) + { + for (n = 0; fire_frames[n]; n++) + { + if (ent->client->ps.gunframe == fire_frames[n]) + { + // FIXME - double should use different sound + if (ent->client->quad_framenum > level.framenum) + gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage3.wav"), 1, ATTN_NORM, 0); + else if (ent->client->double_framenum > level.framenum) + gi.sound(ent, CHAN_ITEM, gi.soundindex("misc/ddamage3.wav"), 1, ATTN_NORM, 0); + + fire (ent); + break; + } + } + + if (!fire_frames[n]) + ent->client->ps.gunframe++; + + if (ent->client->ps.gunframe == FRAME_IDLE_FIRST+1) + ent->client->weaponstate = WEAPON_READY; + } +} + + +/* +====================================================================== + +GRENADE + +====================================================================== +*/ + +#define GRENADE_TIMER 3.0 +#define GRENADE_MINSPEED 400 +#define GRENADE_MAXSPEED 800 + +void weapon_grenade_fire (edict_t *ent, qboolean held) +{ + vec3_t offset; + vec3_t forward, right, up; + vec3_t start; + int damage = 125; + float timer; + int speed; + float radius; + + radius = damage+40; + if (is_quad) +// damage *= 4; + damage *= damage_multiplier; // PGM + + AngleVectors (ent->client->v_angle, forward, right, up); + if (ent->client->pers.weapon->tag == AMMO_TESLA) + { +// VectorSet(offset, 0, -12, ent->viewheight-26); + VectorSet(offset, 0, -4, ent->viewheight-22); + } + else + { +// VectorSet(offset, 8, 8, ent->viewheight-8); + VectorSet(offset, 2, 6, ent->viewheight-14); + } + P_ProjectSource2 (ent->client, ent->s.origin, offset, forward, right, up, start); + + timer = ent->client->grenade_time - level.time; + speed = GRENADE_MINSPEED + (GRENADE_TIMER - timer) * ((GRENADE_MAXSPEED - GRENADE_MINSPEED) / GRENADE_TIMER); + if (speed > GRENADE_MAXSPEED) + speed = GRENADE_MAXSPEED; + +// fire_grenade2 (ent, start, forward, damage, speed, timer, radius, held); + +// ============ +// PGM + switch(ent->client->pers.weapon->tag) + { + case AMMO_GRENADES: + fire_grenade2 (ent, start, forward, damage, speed, timer, radius, held); + break; + case AMMO_TESLA: + fire_tesla (ent, start, forward, damage_multiplier, speed); + break; + default: + fire_prox (ent, start, forward, damage_multiplier, speed); + break; + } +// PGM +// ============ + + if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) ) + ent->client->pers.inventory[ent->client->ammo_index]--; + + ent->client->grenade_time = level.time + 1.0; + + if(ent->deadflag || ent->s.modelindex != 255) // VWep animations screw up corpses + { + return; + } + + if (ent->health <= 0) + return; + + if (ent->client->ps.pmove.pm_flags & PMF_DUCKED) + { + ent->client->anim_priority = ANIM_ATTACK; + ent->s.frame = FRAME_crattak1-1; + ent->client->anim_end = FRAME_crattak3; + } + else + { + ent->client->anim_priority = ANIM_REVERSE; + ent->s.frame = FRAME_wave08; + ent->client->anim_end = FRAME_wave01; + } +} +/* +void Weapon_Grenade (edict_t *ent) +{ + if ((ent->client->newweapon) && (ent->client->weaponstate == WEAPON_READY)) + { + ChangeWeapon (ent); + return; + } + + if (ent->client->weaponstate == WEAPON_ACTIVATING) + { + ent->client->weaponstate = WEAPON_READY; + ent->client->ps.gunframe = 16; + return; + } + + if (ent->client->weaponstate == WEAPON_READY) + { + if ( ((ent->client->latched_buttons|ent->client->buttons) & BUTTON_ATTACK) ) + { + ent->client->latched_buttons &= ~BUTTON_ATTACK; + if (ent->client->pers.inventory[ent->client->ammo_index]) + { + ent->client->ps.gunframe = 1; + ent->client->weaponstate = WEAPON_FIRING; + ent->client->grenade_time = 0; + } + else + { + if (level.time >= ent->pain_debounce_time) + { + gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/noammo.wav"), 1, ATTN_NORM, 0); + ent->pain_debounce_time = level.time + 1; + } + NoAmmoWeaponChange (ent); + } + return; + } + + if ((ent->client->ps.gunframe == 29) || (ent->client->ps.gunframe == 34) || (ent->client->ps.gunframe == 39) || (ent->client->ps.gunframe == 48)) + { + if (rand()&15) + return; + } + + if (++ent->client->ps.gunframe > 48) + ent->client->ps.gunframe = 16; + return; + } + + if (ent->client->weaponstate == WEAPON_FIRING) + { + if (ent->client->ps.gunframe == 5) + gi.sound(ent, CHAN_WEAPON, gi.soundindex("weapons/hgrena1b.wav"), 1, ATTN_NORM, 0); + + if (ent->client->ps.gunframe == 11) + { + if (!ent->client->grenade_time) + { + ent->client->grenade_time = level.time + GRENADE_TIMER + 0.2; + ent->client->weapon_sound = gi.soundindex("weapons/hgrenc1b.wav"); + } + + // they waited too long, detonate it in their hand + if (!ent->client->grenade_blew_up && level.time >= ent->client->grenade_time) + { + ent->client->weapon_sound = 0; + weapon_grenade_fire (ent, true); + ent->client->grenade_blew_up = true; + } + + if (ent->client->buttons & BUTTON_ATTACK) + return; + + if (ent->client->grenade_blew_up) + { + if (level.time >= ent->client->grenade_time) + { + ent->client->ps.gunframe = 15; + ent->client->grenade_blew_up = false; + } + else + { + return; + } + } + } + + if (ent->client->ps.gunframe == 12) + { + ent->client->weapon_sound = 0; + weapon_grenade_fire (ent, false); + } + + if ((ent->client->ps.gunframe == 15) && (level.time < ent->client->grenade_time)) + return; + + ent->client->ps.gunframe++; + + if (ent->client->ps.gunframe == 16) + { + ent->client->grenade_time = 0; + ent->client->weaponstate = WEAPON_READY; + } + } +} +*/ + +#define FRAME_IDLE_FIRST (FRAME_FIRE_LAST + 1) + +//void Weapon_Generic (edict_t *ent, int FRAME_ACTIVATE_LAST, int FRAME_FIRE_LAST, int FRAME_IDLE_LAST, int FRAME_DEACTIVATE_LAST, int *pause_frames, int *fire_frames, void (*fire)(edict_t *ent)) +// 15 48 5 11 12 29,34,39,48 +void Throw_Generic (edict_t *ent, int FRAME_FIRE_LAST, int FRAME_IDLE_LAST, int FRAME_THROW_SOUND, + int FRAME_THROW_HOLD, int FRAME_THROW_FIRE, int *pause_frames, int EXPLODE, + void (*fire)(edict_t *ent, qboolean held)) +{ + int n; + + if ((ent->client->newweapon) && (ent->client->weaponstate == WEAPON_READY)) + { + ChangeWeapon (ent); + return; + } + + if (ent->client->weaponstate == WEAPON_ACTIVATING) + { + ent->client->weaponstate = WEAPON_READY; + ent->client->ps.gunframe = FRAME_IDLE_FIRST; + return; + } + + if (ent->client->weaponstate == WEAPON_READY) + { + if ( ((ent->client->latched_buttons|ent->client->buttons) & BUTTON_ATTACK) ) + { + ent->client->latched_buttons &= ~BUTTON_ATTACK; + if (ent->client->pers.inventory[ent->client->ammo_index]) + { + ent->client->ps.gunframe = 1; + ent->client->weaponstate = WEAPON_FIRING; + ent->client->grenade_time = 0; + } + else + { + if (level.time >= ent->pain_debounce_time) + { + gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/noammo.wav"), 1, ATTN_NORM, 0); + ent->pain_debounce_time = level.time + 1; + } + NoAmmoWeaponChange (ent); + } + return; + } + + if (ent->client->ps.gunframe == FRAME_IDLE_LAST) + { + ent->client->ps.gunframe = FRAME_IDLE_FIRST; + return; + } + + if (pause_frames) + { + for (n = 0; pause_frames[n]; n++) + { + if (ent->client->ps.gunframe == pause_frames[n]) + { + if (rand()&15) + return; + } + } + } + + ent->client->ps.gunframe++; + return; + } + + if (ent->client->weaponstate == WEAPON_FIRING) + { + if (ent->client->ps.gunframe == FRAME_THROW_SOUND) + gi.sound(ent, CHAN_WEAPON, gi.soundindex("weapons/hgrena1b.wav"), 1, ATTN_NORM, 0); + + if (ent->client->ps.gunframe == FRAME_THROW_HOLD) + { + if (!ent->client->grenade_time) + { + ent->client->grenade_time = level.time + GRENADE_TIMER + 0.2; + switch(ent->client->pers.weapon->tag) + { + case AMMO_GRENADES: + ent->client->weapon_sound = gi.soundindex("weapons/hgrenc1b.wav"); + break; + } + } + + // they waited too long, detonate it in their hand + if (EXPLODE && !ent->client->grenade_blew_up && level.time >= ent->client->grenade_time) + { + ent->client->weapon_sound = 0; + fire (ent, true); + ent->client->grenade_blew_up = true; + } + + if (ent->client->buttons & BUTTON_ATTACK) + return; + + if (ent->client->grenade_blew_up) + { + if (level.time >= ent->client->grenade_time) + { + ent->client->ps.gunframe = FRAME_FIRE_LAST; + ent->client->grenade_blew_up = false; + } + else + { + return; + } + } + } + + if (ent->client->ps.gunframe == FRAME_THROW_FIRE) + { + ent->client->weapon_sound = 0; + fire (ent, true); + } + + if ((ent->client->ps.gunframe == FRAME_FIRE_LAST) && (level.time < ent->client->grenade_time)) + return; + + ent->client->ps.gunframe++; + + if (ent->client->ps.gunframe == FRAME_IDLE_FIRST) + { + ent->client->grenade_time = 0; + ent->client->weaponstate = WEAPON_READY; + } + } +} + +//void Throw_Generic (edict_t *ent, int FRAME_FIRE_LAST, int FRAME_IDLE_LAST, int FRAME_THROW_SOUND, +// int FRAME_THROW_HOLD, int FRAME_THROW_FIRE, int *pause_frames, +// int EXPLOSION_TIME, void (*fire)(edict_t *ent)) + +void Weapon_Grenade (edict_t *ent) +{ + static int pause_frames[] = {29,34,39,48,0}; + + Throw_Generic (ent, 15, 48, 5, 11, 12, pause_frames, GRENADE_TIMER, weapon_grenade_fire); +} + +void Weapon_Prox (edict_t *ent) +{ + static int pause_frames[] = {22, 29, 0}; + + Throw_Generic (ent, 7, 27, 99, 2, 4, pause_frames, 0, weapon_grenade_fire); +} + +void Weapon_Tesla (edict_t *ent) +{ + static int pause_frames[] = {21, 0}; + + if ((ent->client->ps.gunframe > 1) && (ent->client->ps.gunframe < 9)) + { + ent->client->ps.gunindex = gi.modelindex ("models/weapons/v_tesla2/tris.md2"); + } + else + { + ent->client->ps.gunindex = gi.modelindex ("models/weapons/v_tesla/tris.md2"); + } + + Throw_Generic (ent, 8, 32, 99, 1, 2, pause_frames, 0, weapon_grenade_fire); +} + + + +/* +====================================================================== + +GRENADE LAUNCHER + +====================================================================== +*/ + +void weapon_grenadelauncher_fire (edict_t *ent) +{ + vec3_t offset; + vec3_t forward, right; + vec3_t start; +// int damage = 120; + int damage; // PGM + float radius; + +// ===== +// PGM + switch(ent->client->pers.weapon->tag) + { + case AMMO_PROX: + damage = 90; + break; + default: + damage = 120; + break; + } +// PGM +// ===== + + radius = damage+40; + if (is_quad) +// damage *= 4; + damage *= damage_multiplier; //pgm + + VectorSet(offset, 8, 8, ent->viewheight-8); + AngleVectors (ent->client->v_angle, forward, right, NULL); + P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start); + + VectorScale (forward, -2, ent->client->kick_origin); + ent->client->kick_angles[0] = -1; + +// fire_grenade (ent, start, forward, damage, 600, 2.5, radius); +// ===== +// PGM + switch(ent->client->pers.weapon->tag) + { + case AMMO_PROX: + fire_prox (ent, start, forward, damage_multiplier, 600); + break; + default: + fire_grenade (ent, start, forward, damage, 600, 2.5, radius); + break; + } +// PGM +// ===== + + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + gi.WriteByte (MZ_GRENADE | is_silenced); + gi.multicast (ent->s.origin, MULTICAST_PVS); + + ent->client->ps.gunframe++; + + PlayerNoise(ent, start, PNOISE_WEAPON); + + if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) ) + ent->client->pers.inventory[ent->client->ammo_index]--; +} + +void Weapon_GrenadeLauncher (edict_t *ent) +{ + static int pause_frames[] = {34, 51, 59, 0}; + static int fire_frames[] = {6, 0}; + + Weapon_Generic (ent, 5, 16, 59, 64, pause_frames, fire_frames, weapon_grenadelauncher_fire); +} + +//========== +//PGM +void Weapon_ProxLauncher (edict_t *ent) +{ + static int pause_frames[] = {34, 51, 59, 0}; + static int fire_frames[] = {6, 0}; + + Weapon_Generic (ent, 5, 16, 59, 64, pause_frames, fire_frames, weapon_grenadelauncher_fire); +} +//PGM +//========== + +/* +====================================================================== + +ROCKET + +====================================================================== +*/ + +void Weapon_RocketLauncher_Fire (edict_t *ent) +{ + vec3_t offset, start; + vec3_t forward, right; + int damage; + float damage_radius; + int radius_damage; + + damage = 100 + (int)(random() * 20.0); + radius_damage = 120; + damage_radius = 120; + if (is_quad) + { +//PGM +// damage *= 4; + damage *= damage_multiplier; +// radius_damage *= 4; + radius_damage *= damage_multiplier; +//PGM + } + + AngleVectors (ent->client->v_angle, forward, right, NULL); + + VectorScale (forward, -2, ent->client->kick_origin); + ent->client->kick_angles[0] = -1; + + VectorSet(offset, 8, 8, ent->viewheight-8); + P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start); + fire_rocket (ent, start, forward, damage, 650, damage_radius, radius_damage); + + // send muzzle flash + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + gi.WriteByte (MZ_ROCKET | is_silenced); + gi.multicast (ent->s.origin, MULTICAST_PVS); + + ent->client->ps.gunframe++; + + PlayerNoise(ent, start, PNOISE_WEAPON); + + if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) ) + ent->client->pers.inventory[ent->client->ammo_index]--; +} + +void Weapon_RocketLauncher (edict_t *ent) +{ + static int pause_frames[] = {25, 33, 42, 50, 0}; + static int fire_frames[] = {5, 0}; + + Weapon_Generic (ent, 4, 12, 50, 54, pause_frames, fire_frames, Weapon_RocketLauncher_Fire); +} + + +/* +====================================================================== + +BLASTER / HYPERBLASTER + +====================================================================== +*/ + +void Blaster_Fire (edict_t *ent, vec3_t g_offset, int damage, qboolean hyper, int effect) +{ + vec3_t forward, right; + vec3_t start; + vec3_t offset; + + if (is_quad) +// damage *= 4; + damage *= damage_multiplier; //pgm + + AngleVectors (ent->client->v_angle, forward, right, NULL); + VectorSet(offset, 24, 8, ent->viewheight-8); + VectorAdd (offset, g_offset, offset); + P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start); + + VectorScale (forward, -2, ent->client->kick_origin); + ent->client->kick_angles[0] = -1; + + fire_blaster (ent, start, forward, damage, 1000, effect, hyper); + + // send muzzle flash + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + if (hyper) + gi.WriteByte (MZ_HYPERBLASTER | is_silenced); + else + gi.WriteByte (MZ_BLASTER | is_silenced); + gi.multicast (ent->s.origin, MULTICAST_PVS); + + PlayerNoise(ent, start, PNOISE_WEAPON); +} + + +void Weapon_Blaster_Fire (edict_t *ent) +{ + int damage; + + if (deathmatch->value) + damage = 15; + else + damage = 10; + Blaster_Fire (ent, vec3_origin, damage, false, EF_BLASTER); + ent->client->ps.gunframe++; +} + +void Weapon_Blaster (edict_t *ent) +{ + static int pause_frames[] = {19, 32, 0}; + static int fire_frames[] = {5, 0}; + + Weapon_Generic (ent, 4, 8, 52, 55, pause_frames, fire_frames, Weapon_Blaster_Fire); +} + + +void Weapon_HyperBlaster_Fire (edict_t *ent) +{ + float rotation; + vec3_t offset; + int effect; + int damage; + + ent->client->weapon_sound = gi.soundindex("weapons/hyprbl1a.wav"); + + if (!(ent->client->buttons & BUTTON_ATTACK)) + { + ent->client->ps.gunframe++; + } + else + { + if (! ent->client->pers.inventory[ent->client->ammo_index] ) + { + if (level.time >= ent->pain_debounce_time) + { + gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/noammo.wav"), 1, ATTN_NORM, 0); + ent->pain_debounce_time = level.time + 1; + } + NoAmmoWeaponChange (ent); + } + else + { + rotation = (ent->client->ps.gunframe - 5) * 2*M_PI/6; + offset[0] = -4 * sin(rotation); + offset[1] = 0; + offset[2] = 4 * cos(rotation); + + if ((ent->client->ps.gunframe == 6) || (ent->client->ps.gunframe == 9)) + effect = EF_HYPERBLASTER; + else + effect = 0; + if (deathmatch->value) + damage = 15; + else + damage = 20; + Blaster_Fire (ent, offset, damage, true, effect); + if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) ) + ent->client->pers.inventory[ent->client->ammo_index]--; + + ent->client->anim_priority = ANIM_ATTACK; + if (ent->client->ps.pmove.pm_flags & PMF_DUCKED) + { + ent->s.frame = FRAME_crattak1 - 1; + ent->client->anim_end = FRAME_crattak9; + } + else + { + ent->s.frame = FRAME_attack1 - 1; + ent->client->anim_end = FRAME_attack8; + } + } + + ent->client->ps.gunframe++; + if (ent->client->ps.gunframe == 12 && ent->client->pers.inventory[ent->client->ammo_index]) + ent->client->ps.gunframe = 6; + } + + if (ent->client->ps.gunframe == 12) + { + gi.sound(ent, CHAN_AUTO, gi.soundindex("weapons/hyprbd1a.wav"), 1, ATTN_NORM, 0); + ent->client->weapon_sound = 0; + } + +} + +void Weapon_HyperBlaster (edict_t *ent) +{ + static int pause_frames[] = {0}; + static int fire_frames[] = {6, 7, 8, 9, 10, 11, 0}; + + Weapon_Generic (ent, 5, 20, 49, 53, pause_frames, fire_frames, Weapon_HyperBlaster_Fire); +} + +/* +====================================================================== + +MACHINEGUN / CHAINGUN + +====================================================================== +*/ + +void Machinegun_Fire (edict_t *ent) +{ + int i; + vec3_t start; + vec3_t forward, right; + vec3_t angles; + int damage = 8; + int kick = 2; + vec3_t offset; + + if (!(ent->client->buttons & BUTTON_ATTACK)) + { + ent->client->machinegun_shots = 0; + ent->client->ps.gunframe++; + return; + } + + if (ent->client->ps.gunframe == 5) + ent->client->ps.gunframe = 4; + else + ent->client->ps.gunframe = 5; + + if (ent->client->pers.inventory[ent->client->ammo_index] < 1) + { + ent->client->ps.gunframe = 6; + if (level.time >= ent->pain_debounce_time) + { + gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/noammo.wav"), 1, ATTN_NORM, 0); + ent->pain_debounce_time = level.time + 1; + } + NoAmmoWeaponChange (ent); + return; + } + + if (is_quad) + { +//PGM +// damage *= 4; + damage *= damage_multiplier; +// kick *= 4; + kick *= damage_multiplier; +//PGM + } + + for (i=1 ; i<3 ; i++) + { + ent->client->kick_origin[i] = crandom() * 0.35; + ent->client->kick_angles[i] = crandom() * 0.7; + } + ent->client->kick_origin[0] = crandom() * 0.35; + ent->client->kick_angles[0] = ent->client->machinegun_shots * -1.5; + + // raise the gun as it is firing + if (!deathmatch->value) + { + ent->client->machinegun_shots++; + if (ent->client->machinegun_shots > 9) + ent->client->machinegun_shots = 9; + } + + // get start / end positions + VectorAdd (ent->client->v_angle, ent->client->kick_angles, angles); + AngleVectors (angles, forward, right, NULL); + VectorSet(offset, 0, 8, ent->viewheight-8); + P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start); + fire_bullet (ent, start, forward, damage, kick, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, MOD_MACHINEGUN); + + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + gi.WriteByte (MZ_MACHINEGUN | is_silenced); + gi.multicast (ent->s.origin, MULTICAST_PVS); + + PlayerNoise(ent, start, PNOISE_WEAPON); + + if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) ) + ent->client->pers.inventory[ent->client->ammo_index]--; + + ent->client->anim_priority = ANIM_ATTACK; + if (ent->client->ps.pmove.pm_flags & PMF_DUCKED) + { + ent->s.frame = FRAME_crattak1 - (int) (random()+0.25); + ent->client->anim_end = FRAME_crattak9; + } + else + { + ent->s.frame = FRAME_attack1 - (int) (random()+0.25); + ent->client->anim_end = FRAME_attack8; + } +} + +void Weapon_Machinegun (edict_t *ent) +{ + static int pause_frames[] = {23, 45, 0}; + static int fire_frames[] = {4, 5, 0}; + + Weapon_Generic (ent, 3, 5, 45, 49, pause_frames, fire_frames, Machinegun_Fire); +} + +void Chaingun_Fire (edict_t *ent) +{ + int i; + int shots; + vec3_t start; + vec3_t forward, right, up; + float r, u; + vec3_t offset; + int damage; + int kick = 2; + + if (deathmatch->value) + damage = 6; + else + damage = 8; + + if (ent->client->ps.gunframe == 5) + gi.sound(ent, CHAN_AUTO, gi.soundindex("weapons/chngnu1a.wav"), 1, ATTN_IDLE, 0); + + if ((ent->client->ps.gunframe == 14) && !(ent->client->buttons & BUTTON_ATTACK)) + { + ent->client->ps.gunframe = 32; + ent->client->weapon_sound = 0; + return; + } + else if ((ent->client->ps.gunframe == 21) && (ent->client->buttons & BUTTON_ATTACK) + && ent->client->pers.inventory[ent->client->ammo_index]) + { + ent->client->ps.gunframe = 15; + } + else + { + ent->client->ps.gunframe++; + } + + if (ent->client->ps.gunframe == 22) + { + ent->client->weapon_sound = 0; + gi.sound(ent, CHAN_AUTO, gi.soundindex("weapons/chngnd1a.wav"), 1, ATTN_IDLE, 0); + } + else + { + ent->client->weapon_sound = gi.soundindex("weapons/chngnl1a.wav"); + } + + ent->client->anim_priority = ANIM_ATTACK; + if (ent->client->ps.pmove.pm_flags & PMF_DUCKED) + { + ent->s.frame = FRAME_crattak1 - (ent->client->ps.gunframe & 1); + ent->client->anim_end = FRAME_crattak9; + } + else + { + ent->s.frame = FRAME_attack1 - (ent->client->ps.gunframe & 1); + ent->client->anim_end = FRAME_attack8; + } + + if (ent->client->ps.gunframe <= 9) + shots = 1; + else if (ent->client->ps.gunframe <= 14) + { + if (ent->client->buttons & BUTTON_ATTACK) + shots = 2; + else + shots = 1; + } + else + shots = 3; + + if (ent->client->pers.inventory[ent->client->ammo_index] < shots) + shots = ent->client->pers.inventory[ent->client->ammo_index]; + + if (!shots) + { + if (level.time >= ent->pain_debounce_time) + { + gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/noammo.wav"), 1, ATTN_NORM, 0); + ent->pain_debounce_time = level.time + 1; + } + NoAmmoWeaponChange (ent); + return; + } + + if (is_quad) + { +//PGM +// damage *= 4; + damage *= damage_multiplier; +// kick *= 4; + kick *= damage_multiplier; +//PGM + } + + for (i=0 ; i<3 ; i++) + { + ent->client->kick_origin[i] = crandom() * 0.35; + ent->client->kick_angles[i] = crandom() * 0.7; + } + + for (i=0 ; iclient->v_angle, forward, right, up); + r = 7 + crandom()*4; + u = crandom()*4; + VectorSet(offset, 0, r, u + ent->viewheight-8); + P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start); + + fire_bullet (ent, start, forward, damage, kick, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, MOD_CHAINGUN); + } + + // send muzzle flash + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + gi.WriteByte ((MZ_CHAINGUN1 + shots - 1) | is_silenced); + gi.multicast (ent->s.origin, MULTICAST_PVS); + + PlayerNoise(ent, start, PNOISE_WEAPON); + + if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) ) + ent->client->pers.inventory[ent->client->ammo_index] -= shots; +} + + +void Weapon_Chaingun (edict_t *ent) +{ + static int pause_frames[] = {38, 43, 51, 61, 0}; + static int fire_frames[] = {5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 0}; + + Weapon_Generic (ent, 4, 31, 61, 64, pause_frames, fire_frames, Chaingun_Fire); +} + + +/* +====================================================================== + +SHOTGUN / SUPERSHOTGUN + +====================================================================== +*/ + +void weapon_shotgun_fire (edict_t *ent) +{ + vec3_t start; + vec3_t forward, right; + vec3_t offset; + int damage = 4; + int kick = 8; + + if (ent->client->ps.gunframe == 9) + { + ent->client->ps.gunframe++; + return; + } + + AngleVectors (ent->client->v_angle, forward, right, NULL); + + VectorScale (forward, -2, ent->client->kick_origin); + ent->client->kick_angles[0] = -2; + + VectorSet(offset, 0, 8, ent->viewheight-8); + P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start); + + if (is_quad) + { +//PGM +// damage *= 4; + damage *= damage_multiplier; +// kick *= 4; + kick *= damage_multiplier; +//PGM + } + + if (deathmatch->value) + fire_shotgun (ent, start, forward, damage, kick, 500, 500, DEFAULT_DEATHMATCH_SHOTGUN_COUNT, MOD_SHOTGUN); + else + fire_shotgun (ent, start, forward, damage, kick, 500, 500, DEFAULT_SHOTGUN_COUNT, MOD_SHOTGUN); + + // send muzzle flash + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + gi.WriteByte (MZ_SHOTGUN | is_silenced); + gi.multicast (ent->s.origin, MULTICAST_PVS); + + ent->client->ps.gunframe++; + PlayerNoise(ent, start, PNOISE_WEAPON); + + if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) ) + ent->client->pers.inventory[ent->client->ammo_index]--; +} + +void Weapon_Shotgun (edict_t *ent) +{ + static int pause_frames[] = {22, 28, 34, 0}; + static int fire_frames[] = {8, 9, 0}; + + Weapon_Generic (ent, 7, 18, 36, 39, pause_frames, fire_frames, weapon_shotgun_fire); +} + + +void weapon_supershotgun_fire (edict_t *ent) +{ + vec3_t start; + vec3_t forward, right; + vec3_t offset; + vec3_t v; + int damage = 6; + int kick = 12; + + AngleVectors (ent->client->v_angle, forward, right, NULL); + + VectorScale (forward, -2, ent->client->kick_origin); + ent->client->kick_angles[0] = -2; + + VectorSet(offset, 0, 8, ent->viewheight-8); + P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start); + + if (is_quad) + { +//PGM +// damage *= 4; + damage *= damage_multiplier; +// kick *= 4; + kick *= damage_multiplier; +//PGM + } + + v[PITCH] = ent->client->v_angle[PITCH]; + v[YAW] = ent->client->v_angle[YAW] - 5; + v[ROLL] = ent->client->v_angle[ROLL]; + AngleVectors (v, forward, NULL, NULL); + fire_shotgun (ent, start, forward, damage, kick, DEFAULT_SHOTGUN_HSPREAD, DEFAULT_SHOTGUN_VSPREAD, DEFAULT_SSHOTGUN_COUNT/2, MOD_SSHOTGUN); + v[YAW] = ent->client->v_angle[YAW] + 5; + AngleVectors (v, forward, NULL, NULL); + fire_shotgun (ent, start, forward, damage, kick, DEFAULT_SHOTGUN_HSPREAD, DEFAULT_SHOTGUN_VSPREAD, DEFAULT_SSHOTGUN_COUNT/2, MOD_SSHOTGUN); + + // send muzzle flash + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + gi.WriteByte (MZ_SSHOTGUN | is_silenced); + gi.multicast (ent->s.origin, MULTICAST_PVS); + + ent->client->ps.gunframe++; + PlayerNoise(ent, start, PNOISE_WEAPON); + + if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) ) + ent->client->pers.inventory[ent->client->ammo_index] -= 2; +} + +void Weapon_SuperShotgun (edict_t *ent) +{ + static int pause_frames[] = {29, 42, 57, 0}; + static int fire_frames[] = {7, 0}; + + Weapon_Generic (ent, 6, 17, 57, 61, pause_frames, fire_frames, weapon_supershotgun_fire); +} + + + +/* +====================================================================== + +RAILGUN + +====================================================================== +*/ + +void weapon_railgun_fire (edict_t *ent) +{ + vec3_t start; + vec3_t forward, right; + vec3_t offset; + int damage; + int kick; + + if (deathmatch->value) + { // normal damage is too extreme in dm + damage = 100; + kick = 200; + } + else + { + damage = 150; + kick = 250; + } + + if (is_quad) + { +//PGM +// damage *= 4; + damage *= damage_multiplier; +// kick *= 4; + kick *= damage_multiplier; +//PGM + } + + AngleVectors (ent->client->v_angle, forward, right, NULL); + + VectorScale (forward, -3, ent->client->kick_origin); + ent->client->kick_angles[0] = -3; + + VectorSet(offset, 0, 7, ent->viewheight-8); + P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start); + fire_rail (ent, start, forward, damage, kick); + + // send muzzle flash + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + gi.WriteByte (MZ_RAILGUN | is_silenced); + gi.multicast (ent->s.origin, MULTICAST_PVS); + + ent->client->ps.gunframe++; + PlayerNoise(ent, start, PNOISE_WEAPON); + + if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) ) + ent->client->pers.inventory[ent->client->ammo_index]--; +} + + +void Weapon_Railgun (edict_t *ent) +{ + static int pause_frames[] = {56, 0}; + static int fire_frames[] = {4, 0}; + + Weapon_Generic (ent, 3, 18, 56, 61, pause_frames, fire_frames, weapon_railgun_fire); +} + + +/* +====================================================================== + +BFG10K + +====================================================================== +*/ + +void weapon_bfg_fire (edict_t *ent) +{ + vec3_t offset, start; + vec3_t forward, right; + int damage; + float damage_radius = 1000; + + if (deathmatch->value) + damage = 200; + else + damage = 500; + + if (ent->client->ps.gunframe == 9) + { + // send muzzle flash + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + gi.WriteByte (MZ_BFG | is_silenced); + gi.multicast (ent->s.origin, MULTICAST_PVS); + + ent->client->ps.gunframe++; + + PlayerNoise(ent, start, PNOISE_WEAPON); + return; + } + + // cells can go down during windup (from power armor hits), so + // check again and abort firing if we don't have enough now + if (ent->client->pers.inventory[ent->client->ammo_index] < 50) + { + ent->client->ps.gunframe++; + return; + } + + if (is_quad) +//PGM +// damage *= 4; + damage *= damage_multiplier; +//PGM + + AngleVectors (ent->client->v_angle, forward, right, NULL); + + VectorScale (forward, -2, ent->client->kick_origin); + + // make a big pitch kick with an inverse fall + ent->client->v_dmg_pitch = -40; + ent->client->v_dmg_roll = crandom()*8; + ent->client->v_dmg_time = level.time + DAMAGE_TIME; + + VectorSet(offset, 8, 8, ent->viewheight-8); + P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start); + fire_bfg (ent, start, forward, damage, 400, damage_radius); + + ent->client->ps.gunframe++; + + PlayerNoise(ent, start, PNOISE_WEAPON); + + if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) ) + ent->client->pers.inventory[ent->client->ammo_index] -= 50; +} + +void Weapon_BFG (edict_t *ent) +{ + static int pause_frames[] = {39, 45, 50, 55, 0}; + static int fire_frames[] = {9, 17, 0}; + + Weapon_Generic (ent, 8, 32, 55, 58, pause_frames, fire_frames, weapon_bfg_fire); +} + + +//====================================================================== +// ROGUE MODS BELOW +//====================================================================== + + +// +// CHAINFIST +// +#define CHAINFIST_REACH 64 + +void weapon_chainfist_fire (edict_t *ent) +{ + vec3_t offset; + vec3_t forward, right, up; + vec3_t start; + int damage; + + damage = 15; + if(deathmatch->value) + damage = 30; + + if (is_quad) + damage *= damage_multiplier; + + AngleVectors (ent->client->v_angle, forward, right, up); + + // kick back + VectorScale (forward, -2, ent->client->kick_origin); + ent->client->kick_angles[0] = -1; + + // set start point + VectorSet(offset, 0, 8, ent->viewheight-4); + P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start); + + fire_player_melee (ent, start, forward, CHAINFIST_REACH, damage, 100, 1, MOD_CHAINFIST); + + PlayerNoise(ent, start, PNOISE_WEAPON); + + ent->client->ps.gunframe++; + ent->client->pers.inventory[ent->client->ammo_index] -= ent->client->pers.weapon->quantity; +} + +// this spits out some smoke from the motor. it's a two-stroke, you know. +void chainfist_smoke (edict_t *ent) +{ + vec3_t tempVec, forward, right, up; + vec3_t offset; + + AngleVectors(ent->client->v_angle, forward, right, up); + VectorSet(offset, 8, 8, ent->viewheight -4); + P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, tempVec); + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_CHAINFIST_SMOKE); + gi.WritePosition (tempVec); + gi.unicast (ent, 0); +// gi.multicast (tempVec, MULTICAST_PVS); +} + +#define HOLD_FRAMES 0 + +void Weapon_ChainFist (edict_t *ent) +{ + static int pause_frames[] = {0}; + static int fire_frames[] = {8, 9, 16, 17, 18, 30, 31, 0}; + + // these are caches for the sound index. there's probably a better way to do this. +// static int idle_index; +// static int attack_index; + float chance; + int last_sequence; + + last_sequence = 0; + + // load chainsaw sounds and store the indexes for later use. +// if(!idle_index && !attack_index) +// { +// idle_index = gi.soundindex("weapons/sawidle.wav"); +// attack_index = gi.soundindex("weapons/sawhit.wav"); +// } + + if(ent->client->ps.gunframe == 13 || + ent->client->ps.gunframe == 23) // end of attack, go idle + ent->client->ps.gunframe = 32; + +#if HOLD_FRAMES + else if(ent->client->ps.gunframe == 9 && ((ent->client->buttons) & BUTTON_ATTACK)) + ent->client->ps.gunframe = 7; + else if(ent->client->ps.gunframe == 18 && ((ent->client->buttons) & BUTTON_ATTACK)) + ent->client->ps.gunframe = 16; +#endif + + // holds for idle sequence + else if(ent->client->ps.gunframe == 42 && (rand()&7)) + { + if((ent->client->pers.hand != CENTER_HANDED) && random() < 0.4) + chainfist_smoke(ent); +// ent->client->ps.gunframe = 40; + } + else if(ent->client->ps.gunframe == 51 && (rand()&7)) + { + if((ent->client->pers.hand != CENTER_HANDED) && random() < 0.4) + chainfist_smoke(ent); +// ent->client->ps.gunframe = 49; + } + + // set the appropriate weapon sound. + if(ent->client->weaponstate == WEAPON_FIRING) +// ent->client->weapon_sound = attack_index; + ent->client->weapon_sound = gi.soundindex("weapons/sawhit.wav"); + else if(ent->client->weaponstate == WEAPON_DROPPING) + ent->client->weapon_sound = 0; + else +// ent->client->weapon_sound = idle_index; + ent->client->weapon_sound = gi.soundindex("weapons/sawidle.wav"); + + Weapon_Generic (ent, 4, 32, 57, 60, pause_frames, fire_frames, weapon_chainfist_fire); + +// gi.dprintf("chainfist %d\n", ent->client->ps.gunframe); + if((ent->client->buttons) & BUTTON_ATTACK) + { + if(ent->client->ps.gunframe == 13 || + ent->client->ps.gunframe == 23 || + ent->client->ps.gunframe == 32) + { + last_sequence = ent->client->ps.gunframe; + ent->client->ps.gunframe = 6; + } + } + + if (ent->client->ps.gunframe == 6) + { + chance = random(); + if(last_sequence == 13) // if we just did sequence 1, do 2 or 3. + chance -= 0.34; + else if(last_sequence == 23) // if we just did sequence 2, do 1 or 3 + chance += 0.33; + else if(last_sequence == 32) // if we just did sequence 3, do 1 or 2 + { + if(chance >= 0.33) + chance += 0.34; + } + + if(chance < 0.33) + ent->client->ps.gunframe = 14; + else if(chance < 0.66) + ent->client->ps.gunframe = 24; + } + +} + +// +// Disintegrator +// + +void weapon_tracker_fire (edict_t *self) +{ + vec3_t forward, right; + vec3_t start; + vec3_t end; + vec3_t offset; + edict_t *enemy; + trace_t tr; + int damage; + vec3_t mins, maxs; + + // PMM - felt a little high at 25 + if(deathmatch->value) + damage = 30; + else + damage = 45; + + if (is_quad) + damage *= damage_multiplier; //pgm + + VectorSet(mins, -16, -16, -16); + VectorSet(maxs, 16, 16, 16); + AngleVectors (self->client->v_angle, forward, right, NULL); + VectorSet(offset, 24, 8, self->viewheight-8); + P_ProjectSource (self->client, self->s.origin, offset, forward, right, start); + + // FIXME - can we shorten this? do we need to? + VectorMA (start, 8192, forward, end); + enemy = NULL; + //PMM - doing two traces .. one point and one box. + tr = gi.trace (start, vec3_origin, vec3_origin, end, self, MASK_SHOT); + if(tr.ent != world) + { + if(tr.ent->svflags & SVF_MONSTER || tr.ent->client || tr.ent->svflags & SVF_DAMAGEABLE) + { + if(tr.ent->health > 0) + enemy = tr.ent; + } + } + else + { + tr = gi.trace (start, mins, maxs, end, self, MASK_SHOT); + if(tr.ent != world) + { + if(tr.ent->svflags & SVF_MONSTER || tr.ent->client || tr.ent->svflags & SVF_DAMAGEABLE) + { + if(tr.ent->health > 0) + enemy = tr.ent; + } + } + } + + VectorScale (forward, -2, self->client->kick_origin); + self->client->kick_angles[0] = -1; + + fire_tracker (self, start, forward, damage, 1000, enemy); + + // send muzzle flash + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (self-g_edicts); + gi.WriteByte (MZ_TRACKER); + gi.multicast (self->s.origin, MULTICAST_PVS); + + PlayerNoise(self, start, PNOISE_WEAPON); + + self->client->ps.gunframe++; + self->client->pers.inventory[self->client->ammo_index] -= self->client->pers.weapon->quantity; +} + +void Weapon_Disintegrator (edict_t *ent) +{ + static int pause_frames[] = {14, 19, 23, 0}; +// static int fire_frames[] = {7, 0}; + static int fire_frames[] = {5, 0}; + + Weapon_Generic (ent, 4, 9, 29, 34, pause_frames, fire_frames, weapon_tracker_fire); +} + +/* +====================================================================== + +ETF RIFLE + +====================================================================== +*/ +void weapon_etf_rifle_fire (edict_t *ent) +{ + vec3_t forward, right, up; + vec3_t start, tempPt; + int damage; + int kick = 3; + int i; + vec3_t angles; + vec3_t offset; + + if(deathmatch->value) + damage = 10; + else + damage = 10; + + // PGM - adjusted to use the quantity entry in the weapon structure. + if(ent->client->pers.inventory[ent->client->ammo_index] < ent->client->pers.weapon->quantity) + { + VectorClear (ent->client->kick_origin); + VectorClear (ent->client->kick_angles); + ent->client->ps.gunframe = 8; + + if (level.time >= ent->pain_debounce_time) + { + gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/noammo.wav"), 1, ATTN_NORM, 0); + ent->pain_debounce_time = level.time + 1; + } + NoAmmoWeaponChange (ent); + return; + } + + if (is_quad) + { + damage *= damage_multiplier; + kick *= damage_multiplier; + } + + for(i=0;i<3;i++) + { + ent->client->kick_origin[i] = crandom() * 0.85; + ent->client->kick_angles[i] = crandom() * 0.85; + } + + // get start / end positions + VectorAdd (ent->client->v_angle, ent->client->kick_angles, angles); +// AngleVectors (angles, forward, right, NULL); +// gi.dprintf("v_angle: %s\n", vtos(ent->client->v_angle)); + AngleVectors (ent->client->v_angle, forward, right, up); + + // FIXME - set correct frames for different offsets. + + if(ent->client->ps.gunframe == 6) // right barrel + { +// gi.dprintf("right\n"); + VectorSet(offset, 15, 8, -8); + } + else // left barrel + { +// gi.dprintf("left\n"); + VectorSet(offset, 15, 6, -8); + } + + VectorCopy (ent->s.origin, tempPt); + tempPt[2] += ent->viewheight; + P_ProjectSource2 (ent->client, tempPt, offset, forward, right, up, start); +// gi.dprintf("start: %s\n", vtos(start)); + fire_flechette (ent, start, forward, damage, 750, kick); + + // send muzzle flash + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + gi.WriteByte (MZ_ETF_RIFLE); + gi.multicast (ent->s.origin, MULTICAST_PVS); + + PlayerNoise(ent, start, PNOISE_WEAPON); + + ent->client->ps.gunframe++; + ent->client->pers.inventory[ent->client->ammo_index] -= ent->client->pers.weapon->quantity; + + ent->client->anim_priority = ANIM_ATTACK; + if (ent->client->ps.pmove.pm_flags & PMF_DUCKED) + { + ent->s.frame = FRAME_crattak1 - 1; + ent->client->anim_end = FRAME_crattak9; + } + else + { + ent->s.frame = FRAME_attack1 - 1; + ent->client->anim_end = FRAME_attack8; + } + +} + +void Weapon_ETF_Rifle (edict_t *ent) +{ + static int pause_frames[] = {18, 28, 0}; + static int fire_frames[] = {6, 7, 0}; +// static int idle_seq; + + // note - if you change the fire frame number, fix the offset in weapon_etf_rifle_fire. + +// if (!(ent->client->buttons & BUTTON_ATTACK)) +// ent->client->machinegun_shots = 0; + + if (ent->client->weaponstate == WEAPON_FIRING) + { + if (ent->client->pers.inventory[ent->client->ammo_index] <= 0) + ent->client->ps.gunframe = 8; + } + + Weapon_Generic (ent, 4, 7, 37, 41, pause_frames, fire_frames, weapon_etf_rifle_fire); + + if(ent->client->ps.gunframe == 8 && (ent->client->buttons & BUTTON_ATTACK)) + ent->client->ps.gunframe = 6; + +// gi.dprintf("etf rifle %d\n", ent->client->ps.gunframe); +} + +// pgm - this now uses ent->client->pers.weapon->quantity like all the other weapons +//#define HEATBEAM_AMMO_USE 2 +#define HEATBEAM_DM_DMG 15 +#define HEATBEAM_SP_DMG 15 + +void Heatbeam_Fire (edict_t *ent) +{ + vec3_t start; + vec3_t forward, right, up; + vec3_t offset; + int damage; + int kick; + + // for comparison, the hyperblaster is 15/20 + // jim requested more damage, so try 15/15 --- PGM 07/23/98 + if (deathmatch->value) + damage = HEATBEAM_DM_DMG; + else + damage = HEATBEAM_SP_DMG; + + if (deathmatch->value) // really knock 'em around in deathmatch + kick = 75; + else + kick = 30; + +// if(ent->client->pers.inventory[ent->client->ammo_index] < HEATBEAM_AMMO_USE) +// { +// NoAmmoWeaponChange (ent); +// return; +// } + + ent->client->ps.gunframe++; + ent->client->ps.gunindex = gi.modelindex ("models/weapons/v_beamer2/tris.md2"); + + if (is_quad) + { + damage *= damage_multiplier; + kick *= damage_multiplier; + } + + VectorClear (ent->client->kick_origin); + VectorClear (ent->client->kick_angles); + + // get start / end positions + AngleVectors (ent->client->v_angle, forward, right, up); + +// This offset is the "view" offset for the beam start (used by trace) + + VectorSet(offset, 7, 2, ent->viewheight-3); + P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start); + + // This offset is the entity offset + VectorSet(offset, 2, 7, -3); + + fire_heat (ent, start, forward, offset, damage, kick, false); + + // send muzzle flash + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + gi.WriteByte (MZ_HEATBEAM | is_silenced); + gi.multicast (ent->s.origin, MULTICAST_PVS); + + PlayerNoise(ent, start, PNOISE_WEAPON); + + if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) ) + ent->client->pers.inventory[ent->client->ammo_index] -= ent->client->pers.weapon->quantity; + + ent->client->anim_priority = ANIM_ATTACK; + if (ent->client->ps.pmove.pm_flags & PMF_DUCKED) + { + ent->s.frame = FRAME_crattak1 - 1; + ent->client->anim_end = FRAME_crattak9; + } + else + { + ent->s.frame = FRAME_attack1 - 1; + ent->client->anim_end = FRAME_attack8; + } + +} + +void Weapon_Heatbeam (edict_t *ent) +{ +// static int pause_frames[] = {38, 43, 51, 61, 0}; +// static int fire_frames[] = {5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 0}; + static int pause_frames[] = {35, 0}; +// static int fire_frames[] = {9, 0}; + static int fire_frames[] = {9, 10, 11, 12, 0}; +// static int attack_index; +// static int off_model, on_model; + +// if ((g_showlogic) && (g_showlogic->value)) { +// gi.dprintf ("Frame %d, skin %d\n", ent->client->ps.gunframe, ent->client->ps.gunskin); +// } + +// if (!attack_index) +// { +// attack_index = gi.soundindex ("weapons/bfg__l1a.wav"); +// off_model = gi.modelindex ("models/weapons/v_beamer/tris.md2"); +// on_model = gi.modelindex ("models/weapons/v_beamer2/tris.md2"); + //ent->client->ps.gunindex = gi.modelindex(ent->client->pers.weapon->view_model); +// } + + if (ent->client->weaponstate == WEAPON_FIRING) + { +// ent->client->weapon_sound = attack_index; + ent->client->weapon_sound = gi.soundindex ("weapons/bfg__l1a.wav"); + if ((ent->client->pers.inventory[ent->client->ammo_index] >= 2) && ((ent->client->buttons) & BUTTON_ATTACK)) + { +// if(ent->client->ps.gunframe >= 9 && ((ent->client->buttons) & BUTTON_ATTACK)) +// if(ent->client->ps.gunframe >= 12 && ((ent->client->buttons) & BUTTON_ATTACK)) + if(ent->client->ps.gunframe >= 13) + { + ent->client->ps.gunframe = 9; +// ent->client->ps.gunframe = 8; +// ent->client->ps.gunskin = 0; +// ent->client->ps.gunindex = on_model; + ent->client->ps.gunindex = gi.modelindex ("models/weapons/v_beamer2/tris.md2"); + } + else + { +// ent->client->ps.gunskin = 1; +// ent->client->ps.gunindex = on_model; + ent->client->ps.gunindex = gi.modelindex ("models/weapons/v_beamer2/tris.md2"); + } + } + else + { +// ent->client->ps.gunframe = 10; + ent->client->ps.gunframe = 13; +// ent->client->ps.gunskin = 1; +// ent->client->ps.gunindex = off_model; + ent->client->ps.gunindex = gi.modelindex ("models/weapons/v_beamer/tris.md2"); + } + } + else + { +// ent->client->ps.gunskin = 1; +// ent->client->ps.gunindex = off_model; + ent->client->ps.gunindex = gi.modelindex ("models/weapons/v_beamer/tris.md2"); + ent->client->weapon_sound = 0; + } + +// Weapon_Generic (ent, 8, 9, 39, 44, pause_frames, fire_frames, Heatbeam_Fire); + Weapon_Generic (ent, 8, 12, 39, 44, pause_frames, fire_frames, Heatbeam_Fire); +} diff --git a/original/rogue/q_shared.c b/original/rogue/q_shared.c new file mode 100644 index 0000000..5749036 --- /dev/null +++ b/original/rogue/q_shared.c @@ -0,0 +1,1404 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +#include "q_shared.h" + +#define DEG2RAD( a ) ( a * M_PI ) / 180.0F + +vec3_t vec3_origin = {0,0,0}; +// ROGUE VERSIONING +//int rogueid = ROGUE_VERSION_ID; +// ROGUE + +//============================================================================ + +#ifdef _WIN32 +#pragma optimize( "", off ) +#endif + +void RotatePointAroundVector( vec3_t dst, const vec3_t dir, const vec3_t point, float degrees ) +{ + float m[3][3]; + float im[3][3]; + float zrot[3][3]; + float tmpmat[3][3]; + float rot[3][3]; + int i; + vec3_t vr, vup, vf; + + vf[0] = dir[0]; + vf[1] = dir[1]; + vf[2] = dir[2]; + + PerpendicularVector( vr, dir ); + CrossProduct( vr, vf, vup ); + + m[0][0] = vr[0]; + m[1][0] = vr[1]; + m[2][0] = vr[2]; + + m[0][1] = vup[0]; + m[1][1] = vup[1]; + m[2][1] = vup[2]; + + m[0][2] = vf[0]; + m[1][2] = vf[1]; + m[2][2] = vf[2]; + + memcpy( im, m, sizeof( im ) ); + + im[0][1] = m[1][0]; + im[0][2] = m[2][0]; + im[1][0] = m[0][1]; + im[1][2] = m[2][1]; + im[2][0] = m[0][2]; + im[2][1] = m[1][2]; + + memset( zrot, 0, sizeof( zrot ) ); + zrot[0][0] = zrot[1][1] = zrot[2][2] = 1.0F; + + zrot[0][0] = cos( DEG2RAD( degrees ) ); + zrot[0][1] = sin( DEG2RAD( degrees ) ); + zrot[1][0] = -sin( DEG2RAD( degrees ) ); + zrot[1][1] = cos( DEG2RAD( degrees ) ); + + R_ConcatRotations( m, zrot, tmpmat ); + R_ConcatRotations( tmpmat, im, rot ); + + for ( i = 0; i < 3; i++ ) + { + dst[i] = rot[i][0] * point[0] + rot[i][1] * point[1] + rot[i][2] * point[2]; + } +} + +#ifdef _WIN32 +#pragma optimize( "", on ) +#endif + + + +void AngleVectors (vec3_t angles, vec3_t forward, vec3_t right, vec3_t up) +{ + float angle; + static float sr, sp, sy, cr, cp, cy; + // static to help MS compiler fp bugs + + angle = angles[YAW] * (M_PI*2 / 360); + sy = sin(angle); + cy = cos(angle); + angle = angles[PITCH] * (M_PI*2 / 360); + sp = sin(angle); + cp = cos(angle); + angle = angles[ROLL] * (M_PI*2 / 360); + sr = sin(angle); + cr = cos(angle); + + if (forward) + { + forward[0] = cp*cy; + forward[1] = cp*sy; + forward[2] = -sp; + } + if (right) + { + right[0] = (-1*sr*sp*cy+-1*cr*-sy); + right[1] = (-1*sr*sp*sy+-1*cr*cy); + right[2] = -1*sr*cp; + } + if (up) + { + up[0] = (cr*sp*cy+-sr*-sy); + up[1] = (cr*sp*sy+-sr*cy); + up[2] = cr*cp; + } +} + + +void ProjectPointOnPlane( vec3_t dst, const vec3_t p, const vec3_t normal ) +{ + float d; + vec3_t n; + float inv_denom; + + inv_denom = 1.0F / DotProduct( normal, normal ); + + d = DotProduct( normal, p ) * inv_denom; + + n[0] = normal[0] * inv_denom; + n[1] = normal[1] * inv_denom; + n[2] = normal[2] * inv_denom; + + dst[0] = p[0] - d * n[0]; + dst[1] = p[1] - d * n[1]; + dst[2] = p[2] - d * n[2]; +} + +/* +** assumes "src" is normalized +*/ +void PerpendicularVector( vec3_t dst, const vec3_t src ) +{ + int pos; + int i; + float minelem = 1.0F; + vec3_t tempvec; + + /* + ** find the smallest magnitude axially aligned vector + */ + for ( pos = 0, i = 0; i < 3; i++ ) + { + if ( fabs( src[i] ) < minelem ) + { + pos = i; + minelem = fabs( src[i] ); + } + } + tempvec[0] = tempvec[1] = tempvec[2] = 0.0F; + tempvec[pos] = 1.0F; + + /* + ** project the point onto the plane defined by src + */ + ProjectPointOnPlane( dst, tempvec, src ); + + /* + ** normalize the result + */ + VectorNormalize( dst ); +} + + + +/* +================ +R_ConcatRotations +================ +*/ +void R_ConcatRotations (float in1[3][3], float in2[3][3], float out[3][3]) +{ + out[0][0] = in1[0][0] * in2[0][0] + in1[0][1] * in2[1][0] + + in1[0][2] * in2[2][0]; + out[0][1] = in1[0][0] * in2[0][1] + in1[0][1] * in2[1][1] + + in1[0][2] * in2[2][1]; + out[0][2] = in1[0][0] * in2[0][2] + in1[0][1] * in2[1][2] + + in1[0][2] * in2[2][2]; + out[1][0] = in1[1][0] * in2[0][0] + in1[1][1] * in2[1][0] + + in1[1][2] * in2[2][0]; + out[1][1] = in1[1][0] * in2[0][1] + in1[1][1] * in2[1][1] + + in1[1][2] * in2[2][1]; + out[1][2] = in1[1][0] * in2[0][2] + in1[1][1] * in2[1][2] + + in1[1][2] * in2[2][2]; + out[2][0] = in1[2][0] * in2[0][0] + in1[2][1] * in2[1][0] + + in1[2][2] * in2[2][0]; + out[2][1] = in1[2][0] * in2[0][1] + in1[2][1] * in2[1][1] + + in1[2][2] * in2[2][1]; + out[2][2] = in1[2][0] * in2[0][2] + in1[2][1] * in2[1][2] + + in1[2][2] * in2[2][2]; +} + + +/* +================ +R_ConcatTransforms +================ +*/ +void R_ConcatTransforms (float in1[3][4], float in2[3][4], float out[3][4]) +{ + out[0][0] = in1[0][0] * in2[0][0] + in1[0][1] * in2[1][0] + + in1[0][2] * in2[2][0]; + out[0][1] = in1[0][0] * in2[0][1] + in1[0][1] * in2[1][1] + + in1[0][2] * in2[2][1]; + out[0][2] = in1[0][0] * in2[0][2] + in1[0][1] * in2[1][2] + + in1[0][2] * in2[2][2]; + out[0][3] = in1[0][0] * in2[0][3] + in1[0][1] * in2[1][3] + + in1[0][2] * in2[2][3] + in1[0][3]; + out[1][0] = in1[1][0] * in2[0][0] + in1[1][1] * in2[1][0] + + in1[1][2] * in2[2][0]; + out[1][1] = in1[1][0] * in2[0][1] + in1[1][1] * in2[1][1] + + in1[1][2] * in2[2][1]; + out[1][2] = in1[1][0] * in2[0][2] + in1[1][1] * in2[1][2] + + in1[1][2] * in2[2][2]; + out[1][3] = in1[1][0] * in2[0][3] + in1[1][1] * in2[1][3] + + in1[1][2] * in2[2][3] + in1[1][3]; + out[2][0] = in1[2][0] * in2[0][0] + in1[2][1] * in2[1][0] + + in1[2][2] * in2[2][0]; + out[2][1] = in1[2][0] * in2[0][1] + in1[2][1] * in2[1][1] + + in1[2][2] * in2[2][1]; + out[2][2] = in1[2][0] * in2[0][2] + in1[2][1] * in2[1][2] + + in1[2][2] * in2[2][2]; + out[2][3] = in1[2][0] * in2[0][3] + in1[2][1] * in2[1][3] + + in1[2][2] * in2[2][3] + in1[2][3]; +} + + +//============================================================================ + + +float Q_fabs (float f) +{ +#if 0 + if (f >= 0) + return f; + return -f; +#else + int tmp = * ( int * ) &f; + tmp &= 0x7FFFFFFF; + return * ( float * ) &tmp; +#endif +} + +#if defined _M_IX86 && !defined C_ONLY +#pragma warning (disable:4035) +__declspec( naked ) long Q_ftol( float f ) +{ + static int tmp; + __asm fld dword ptr [esp+4] + __asm fistp tmp + __asm mov eax, tmp + __asm ret +} +#pragma warning (default:4035) +#endif + +/* +=============== +LerpAngle + +=============== +*/ +float LerpAngle (float a2, float a1, float frac) +{ + if (a1 - a2 > 180) + a1 -= 360; + if (a1 - a2 < -180) + a1 += 360; + return a2 + frac * (a1 - a2); +} + + +float anglemod(float a) +{ +#if 0 + if (a >= 0) + a -= 360*(int)(a/360); + else + a += 360*( 1 + (int)(-a/360) ); +#endif + a = (360.0/65536) * ((int)(a*(65536/360.0)) & 65535); + return a; +} + + int i; + vec3_t corners[2]; + + +// this is the slow, general version +int BoxOnPlaneSide2 (vec3_t emins, vec3_t emaxs, struct cplane_s *p) +{ + int i; + float dist1, dist2; + int sides; + vec3_t corners[2]; + + for (i=0 ; i<3 ; i++) + { + if (p->normal[i] < 0) + { + corners[0][i] = emins[i]; + corners[1][i] = emaxs[i]; + } + else + { + corners[1][i] = emins[i]; + corners[0][i] = emaxs[i]; + } + } + dist1 = DotProduct (p->normal, corners[0]) - p->dist; + dist2 = DotProduct (p->normal, corners[1]) - p->dist; + sides = 0; + if (dist1 >= 0) + sides = 1; + if (dist2 < 0) + sides |= 2; + + return sides; +} + +/* +================== +BoxOnPlaneSide + +Returns 1, 2, or 1 + 2 +================== +*/ +#if !id386 || defined __linux__ +int BoxOnPlaneSide (vec3_t emins, vec3_t emaxs, struct cplane_s *p) +{ + float dist1, dist2; + int sides; + +// fast axial cases + if (p->type < 3) + { + if (p->dist <= emins[p->type]) + return 1; + if (p->dist >= emaxs[p->type]) + return 2; + return 3; + } + +// general case + switch (p->signbits) + { + case 0: +dist1 = p->normal[0]*emaxs[0] + p->normal[1]*emaxs[1] + p->normal[2]*emaxs[2]; +dist2 = p->normal[0]*emins[0] + p->normal[1]*emins[1] + p->normal[2]*emins[2]; + break; + case 1: +dist1 = p->normal[0]*emins[0] + p->normal[1]*emaxs[1] + p->normal[2]*emaxs[2]; +dist2 = p->normal[0]*emaxs[0] + p->normal[1]*emins[1] + p->normal[2]*emins[2]; + break; + case 2: +dist1 = p->normal[0]*emaxs[0] + p->normal[1]*emins[1] + p->normal[2]*emaxs[2]; +dist2 = p->normal[0]*emins[0] + p->normal[1]*emaxs[1] + p->normal[2]*emins[2]; + break; + case 3: +dist1 = p->normal[0]*emins[0] + p->normal[1]*emins[1] + p->normal[2]*emaxs[2]; +dist2 = p->normal[0]*emaxs[0] + p->normal[1]*emaxs[1] + p->normal[2]*emins[2]; + break; + case 4: +dist1 = p->normal[0]*emaxs[0] + p->normal[1]*emaxs[1] + p->normal[2]*emins[2]; +dist2 = p->normal[0]*emins[0] + p->normal[1]*emins[1] + p->normal[2]*emaxs[2]; + break; + case 5: +dist1 = p->normal[0]*emins[0] + p->normal[1]*emaxs[1] + p->normal[2]*emins[2]; +dist2 = p->normal[0]*emaxs[0] + p->normal[1]*emins[1] + p->normal[2]*emaxs[2]; + break; + case 6: +dist1 = p->normal[0]*emaxs[0] + p->normal[1]*emins[1] + p->normal[2]*emins[2]; +dist2 = p->normal[0]*emins[0] + p->normal[1]*emaxs[1] + p->normal[2]*emaxs[2]; + break; + case 7: +dist1 = p->normal[0]*emins[0] + p->normal[1]*emins[1] + p->normal[2]*emins[2]; +dist2 = p->normal[0]*emaxs[0] + p->normal[1]*emaxs[1] + p->normal[2]*emaxs[2]; + break; + default: + dist1 = dist2 = 0; // shut up compiler + assert( 0 ); + break; + } + + sides = 0; + if (dist1 >= p->dist) + sides = 1; + if (dist2 < p->dist) + sides |= 2; + + assert( sides != 0 ); + + return sides; +} +#else +#pragma warning( disable: 4035 ) + +__declspec( naked ) int BoxOnPlaneSide (vec3_t emins, vec3_t emaxs, struct cplane_s *p) +{ + static int bops_initialized; + static int Ljmptab[8]; + + __asm { + + push ebx + + cmp bops_initialized, 1 + je initialized + mov bops_initialized, 1 + + mov Ljmptab[0*4], offset Lcase0 + mov Ljmptab[1*4], offset Lcase1 + mov Ljmptab[2*4], offset Lcase2 + mov Ljmptab[3*4], offset Lcase3 + mov Ljmptab[4*4], offset Lcase4 + mov Ljmptab[5*4], offset Lcase5 + mov Ljmptab[6*4], offset Lcase6 + mov Ljmptab[7*4], offset Lcase7 + +initialized: + + mov edx,ds:dword ptr[4+12+esp] + mov ecx,ds:dword ptr[4+4+esp] + xor eax,eax + mov ebx,ds:dword ptr[4+8+esp] + mov al,ds:byte ptr[17+edx] + cmp al,8 + jge Lerror + fld ds:dword ptr[0+edx] + fld st(0) + jmp dword ptr[Ljmptab+eax*4] +Lcase0: + fmul ds:dword ptr[ebx] + fld ds:dword ptr[0+4+edx] + fxch st(2) + fmul ds:dword ptr[ecx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[4+ebx] + fld ds:dword ptr[0+8+edx] + fxch st(2) + fmul ds:dword ptr[4+ecx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[8+ebx] + fxch st(5) + faddp st(3),st(0) + fmul ds:dword ptr[8+ecx] + fxch st(1) + faddp st(3),st(0) + fxch st(3) + faddp st(2),st(0) + jmp LSetSides +Lcase1: + fmul ds:dword ptr[ecx] + fld ds:dword ptr[0+4+edx] + fxch st(2) + fmul ds:dword ptr[ebx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[4+ebx] + fld ds:dword ptr[0+8+edx] + fxch st(2) + fmul ds:dword ptr[4+ecx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[8+ebx] + fxch st(5) + faddp st(3),st(0) + fmul ds:dword ptr[8+ecx] + fxch st(1) + faddp st(3),st(0) + fxch st(3) + faddp st(2),st(0) + jmp LSetSides +Lcase2: + fmul ds:dword ptr[ebx] + fld ds:dword ptr[0+4+edx] + fxch st(2) + fmul ds:dword ptr[ecx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[4+ecx] + fld ds:dword ptr[0+8+edx] + fxch st(2) + fmul ds:dword ptr[4+ebx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[8+ebx] + fxch st(5) + faddp st(3),st(0) + fmul ds:dword ptr[8+ecx] + fxch st(1) + faddp st(3),st(0) + fxch st(3) + faddp st(2),st(0) + jmp LSetSides +Lcase3: + fmul ds:dword ptr[ecx] + fld ds:dword ptr[0+4+edx] + fxch st(2) + fmul ds:dword ptr[ebx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[4+ecx] + fld ds:dword ptr[0+8+edx] + fxch st(2) + fmul ds:dword ptr[4+ebx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[8+ebx] + fxch st(5) + faddp st(3),st(0) + fmul ds:dword ptr[8+ecx] + fxch st(1) + faddp st(3),st(0) + fxch st(3) + faddp st(2),st(0) + jmp LSetSides +Lcase4: + fmul ds:dword ptr[ebx] + fld ds:dword ptr[0+4+edx] + fxch st(2) + fmul ds:dword ptr[ecx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[4+ebx] + fld ds:dword ptr[0+8+edx] + fxch st(2) + fmul ds:dword ptr[4+ecx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[8+ecx] + fxch st(5) + faddp st(3),st(0) + fmul ds:dword ptr[8+ebx] + fxch st(1) + faddp st(3),st(0) + fxch st(3) + faddp st(2),st(0) + jmp LSetSides +Lcase5: + fmul ds:dword ptr[ecx] + fld ds:dword ptr[0+4+edx] + fxch st(2) + fmul ds:dword ptr[ebx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[4+ebx] + fld ds:dword ptr[0+8+edx] + fxch st(2) + fmul ds:dword ptr[4+ecx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[8+ecx] + fxch st(5) + faddp st(3),st(0) + fmul ds:dword ptr[8+ebx] + fxch st(1) + faddp st(3),st(0) + fxch st(3) + faddp st(2),st(0) + jmp LSetSides +Lcase6: + fmul ds:dword ptr[ebx] + fld ds:dword ptr[0+4+edx] + fxch st(2) + fmul ds:dword ptr[ecx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[4+ecx] + fld ds:dword ptr[0+8+edx] + fxch st(2) + fmul ds:dword ptr[4+ebx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[8+ecx] + fxch st(5) + faddp st(3),st(0) + fmul ds:dword ptr[8+ebx] + fxch st(1) + faddp st(3),st(0) + fxch st(3) + faddp st(2),st(0) + jmp LSetSides +Lcase7: + fmul ds:dword ptr[ecx] + fld ds:dword ptr[0+4+edx] + fxch st(2) + fmul ds:dword ptr[ebx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[4+ecx] + fld ds:dword ptr[0+8+edx] + fxch st(2) + fmul ds:dword ptr[4+ebx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[8+ecx] + fxch st(5) + faddp st(3),st(0) + fmul ds:dword ptr[8+ebx] + fxch st(1) + faddp st(3),st(0) + fxch st(3) + faddp st(2),st(0) +LSetSides: + faddp st(2),st(0) + fcomp ds:dword ptr[12+edx] + xor ecx,ecx + fnstsw ax + fcomp ds:dword ptr[12+edx] + and ah,1 + xor ah,1 + add cl,ah + fnstsw ax + and ah,1 + add ah,ah + add cl,ah + pop ebx + mov eax,ecx + ret +Lerror: + int 3 + } +} +#pragma warning( default: 4035 ) +#endif + +void ClearBounds (vec3_t mins, vec3_t maxs) +{ + mins[0] = mins[1] = mins[2] = 99999; + maxs[0] = maxs[1] = maxs[2] = -99999; +} + +void AddPointToBounds (vec3_t v, vec3_t mins, vec3_t maxs) +{ + int i; + vec_t val; + + for (i=0 ; i<3 ; i++) + { + val = v[i]; + if (val < mins[i]) + mins[i] = val; + if (val > maxs[i]) + maxs[i] = val; + } +} + + +int VectorCompare (vec3_t v1, vec3_t v2) +{ + if (v1[0] != v2[0] || v1[1] != v2[1] || v1[2] != v2[2]) + return 0; + + return 1; +} + + +vec_t VectorNormalize (vec3_t v) +{ + float length, ilength; + + length = v[0]*v[0] + v[1]*v[1] + v[2]*v[2]; + length = sqrt (length); // FIXME + + if (length) + { + ilength = 1/length; + v[0] *= ilength; + v[1] *= ilength; + v[2] *= ilength; + } + + return length; + +} + +vec_t VectorNormalize2 (vec3_t v, vec3_t out) +{ + float length, ilength; + + length = v[0]*v[0] + v[1]*v[1] + v[2]*v[2]; + length = sqrt (length); // FIXME + + if (length) + { + ilength = 1/length; + out[0] = v[0]*ilength; + out[1] = v[1]*ilength; + out[2] = v[2]*ilength; + } + + return length; + +} + +void VectorMA (vec3_t veca, float scale, vec3_t vecb, vec3_t vecc) +{ + vecc[0] = veca[0] + scale*vecb[0]; + vecc[1] = veca[1] + scale*vecb[1]; + vecc[2] = veca[2] + scale*vecb[2]; +} + + +vec_t _DotProduct (vec3_t v1, vec3_t v2) +{ + return v1[0]*v2[0] + v1[1]*v2[1] + v1[2]*v2[2]; +} + +void _VectorSubtract (vec3_t veca, vec3_t vecb, vec3_t out) +{ + out[0] = veca[0]-vecb[0]; + out[1] = veca[1]-vecb[1]; + out[2] = veca[2]-vecb[2]; +} + +void _VectorAdd (vec3_t veca, vec3_t vecb, vec3_t out) +{ + out[0] = veca[0]+vecb[0]; + out[1] = veca[1]+vecb[1]; + out[2] = veca[2]+vecb[2]; +} + +void _VectorCopy (vec3_t in, vec3_t out) +{ + out[0] = in[0]; + out[1] = in[1]; + out[2] = in[2]; +} + +void CrossProduct (vec3_t v1, vec3_t v2, vec3_t cross) +{ + cross[0] = v1[1]*v2[2] - v1[2]*v2[1]; + cross[1] = v1[2]*v2[0] - v1[0]*v2[2]; + cross[2] = v1[0]*v2[1] - v1[1]*v2[0]; +} + +double sqrt(double x); + +vec_t VectorLength(vec3_t v) +{ + int i; + float length; + + length = 0; + for (i=0 ; i< 3 ; i++) + length += v[i]*v[i]; + length = sqrt (length); // FIXME + + return length; +} + +void VectorInverse (vec3_t v) +{ + v[0] = -v[0]; + v[1] = -v[1]; + v[2] = -v[2]; +} + +void VectorScale (vec3_t in, vec_t scale, vec3_t out) +{ + out[0] = in[0]*scale; + out[1] = in[1]*scale; + out[2] = in[2]*scale; +} + + +int Q_log2(int val) +{ + int answer=0; + while (val>>=1) + answer++; + return answer; +} + + + +//==================================================================================== + +/* +============ +COM_SkipPath +============ +*/ +char *COM_SkipPath (char *pathname) +{ + char *last; + + last = pathname; + while (*pathname) + { + if (*pathname=='/') + last = pathname+1; + pathname++; + } + return last; +} + +/* +============ +COM_StripExtension +============ +*/ +void COM_StripExtension (char *in, char *out) +{ + while (*in && *in != '.') + *out++ = *in++; + *out = 0; +} + +/* +============ +COM_FileExtension +============ +*/ +char *COM_FileExtension (char *in) +{ + static char exten[8]; + int i; + + while (*in && *in != '.') + in++; + if (!*in) + return ""; + in++; + for (i=0 ; i<7 && *in ; i++,in++) + exten[i] = *in; + exten[i] = 0; + return exten; +} + +/* +============ +COM_FileBase +============ +*/ +void COM_FileBase (char *in, char *out) +{ + char *s, *s2; + + s = in + strlen(in) - 1; + + while (s != in && *s != '.') + s--; + + for (s2 = s ; s2 != in && *s2 != '/' ; s2--) + ; + + if (s-s2 < 2) + out[0] = 0; + else + { + s--; + strncpy (out,s2+1, s-s2); + out[s-s2] = 0; + } +} + +/* +============ +COM_FilePath + +Returns the path up to, but not including the last / +============ +*/ +void COM_FilePath (char *in, char *out) +{ + char *s; + + s = in + strlen(in) - 1; + + while (s != in && *s != '/') + s--; + + strncpy (out,in, s-in); + out[s-in] = 0; +} + + +/* +================== +COM_DefaultExtension +================== +*/ +void COM_DefaultExtension (char *path, char *extension) +{ + char *src; +// +// if path doesn't have a .EXT, append extension +// (extension should include the .) +// + src = path + strlen(path) - 1; + + while (*src != '/' && src != path) + { + if (*src == '.') + return; // it has an extension + src--; + } + + strcat (path, extension); +} + +/* +============================================================================ + + BYTE ORDER FUNCTIONS + +============================================================================ +*/ + +qboolean bigendien; + +// can't just use function pointers, or dll linkage can +// mess up when qcommon is included in multiple places +short (*_BigShort) (short l); +short (*_LittleShort) (short l); +int (*_BigLong) (int l); +int (*_LittleLong) (int l); +float (*_BigFloat) (float l); +float (*_LittleFloat) (float l); + +short BigShort(short l){return _BigShort(l);} +short LittleShort(short l) {return _LittleShort(l);} +int BigLong (int l) {return _BigLong(l);} +int LittleLong (int l) {return _LittleLong(l);} +float BigFloat (float l) {return _BigFloat(l);} +float LittleFloat (float l) {return _LittleFloat(l);} + +short ShortSwap (short l) +{ + byte b1,b2; + + b1 = l&255; + b2 = (l>>8)&255; + + return (b1<<8) + b2; +} + +short ShortNoSwap (short l) +{ + return l; +} + +int LongSwap (int l) +{ + byte b1,b2,b3,b4; + + b1 = l&255; + b2 = (l>>8)&255; + b3 = (l>>16)&255; + b4 = (l>>24)&255; + + return ((int)b1<<24) + ((int)b2<<16) + ((int)b3<<8) + b4; +} + +int LongNoSwap (int l) +{ + return l; +} + +float FloatSwap (float f) +{ + union + { + float f; + byte b[4]; + } dat1, dat2; + + + dat1.f = f; + dat2.b[0] = dat1.b[3]; + dat2.b[1] = dat1.b[2]; + dat2.b[2] = dat1.b[1]; + dat2.b[3] = dat1.b[0]; + return dat2.f; +} + +float FloatNoSwap (float f) +{ + return f; +} + +/* +================ +Swap_Init +================ +*/ +void Swap_Init (void) +{ + byte swaptest[2] = {1,0}; + +// set the byte swapping variables in a portable manner + if ( *(short *)swaptest == 1) + { + bigendien = false; + _BigShort = ShortSwap; + _LittleShort = ShortNoSwap; + _BigLong = LongSwap; + _LittleLong = LongNoSwap; + _BigFloat = FloatSwap; + _LittleFloat = FloatNoSwap; + } + else + { + bigendien = true; + _BigShort = ShortNoSwap; + _LittleShort = ShortSwap; + _BigLong = LongNoSwap; + _LittleLong = LongSwap; + _BigFloat = FloatNoSwap; + _LittleFloat = FloatSwap; + } + +} + + + +/* +============ +va + +does a varargs printf into a temp buffer, so I don't need to have +varargs versions of all text functions. +FIXME: make this buffer size safe someday +============ +*/ +char *va(char *format, ...) +{ + va_list argptr; + static char string[1024]; + + va_start (argptr, format); + vsprintf (string, format,argptr); + va_end (argptr); + + return string; +} + + +char com_token[MAX_TOKEN_CHARS]; + +/* +============== +COM_Parse + +Parse a token out of a string +============== +*/ +char *COM_Parse (char **data_p) +{ + int c; + int len; + char *data; + + data = *data_p; + len = 0; + com_token[0] = 0; + + if (!data) + { + *data_p = NULL; + return ""; + } + +// skip whitespace +skipwhite: + while ( (c = *data) <= ' ') + { + if (c == 0) + { + *data_p = NULL; + return ""; + } + data++; + } + +// skip // comments + if (c=='/' && data[1] == '/') + { + while (*data && *data != '\n') + data++; + goto skipwhite; + } + +// handle quoted strings specially + if (c == '\"') + { + data++; + while (1) + { + c = *data++; + if (c=='\"' || !c) + { + com_token[len] = 0; + *data_p = data; + return com_token; + } + if (len < MAX_TOKEN_CHARS) + { + com_token[len] = c; + len++; + } + } + } + +// parse a regular word + do + { + if (len < MAX_TOKEN_CHARS) + { + com_token[len] = c; + len++; + } + data++; + c = *data; + } while (c>32); + + if (len == MAX_TOKEN_CHARS) + { +// Com_Printf ("Token exceeded %i chars, discarded.\n", MAX_TOKEN_CHARS); + len = 0; + } + com_token[len] = 0; + + *data_p = data; + return com_token; +} + + +/* +=============== +Com_PageInMemory + +=============== +*/ +int paged_total; + +void Com_PageInMemory (byte *buffer, int size) +{ + int i; + + for (i=size-1 ; i>0 ; i-=4096) + paged_total += buffer[i]; +} + + + +/* +============================================================================ + + LIBRARY REPLACEMENT FUNCTIONS + +============================================================================ +*/ + +// FIXME: replace all Q_stricmp with Q_strcasecmp +int Q_stricmp (char *s1, char *s2) +{ +#if defined(WIN32) + return _stricmp (s1, s2); +#else + return strcasecmp (s1, s2); +#endif +} + + +int Q_strncasecmp (char *s1, char *s2, int n) +{ + int c1, c2; + + do + { + c1 = *s1++; + c2 = *s2++; + + if (!n--) + return 0; // strings are equal until end point + + if (c1 != c2) + { + if (c1 >= 'a' && c1 <= 'z') + c1 -= ('a' - 'A'); + if (c2 >= 'a' && c2 <= 'z') + c2 -= ('a' - 'A'); + if (c1 != c2) + return -1; // strings not equal + } + } while (c1); + + return 0; // strings are equal +} + +int Q_strcasecmp (char *s1, char *s2) +{ + return Q_strncasecmp (s1, s2, 99999); +} + + + +void Com_sprintf (char *dest, int size, char *fmt, ...) +{ + int len; + va_list argptr; + char bigbuffer[0x10000]; + + va_start (argptr,fmt); + len = vsprintf (bigbuffer,fmt,argptr); + va_end (argptr); + if (len >= size) + Com_Printf ("Com_sprintf: overflow of %i in %i\n", len, size); + strncpy (dest, bigbuffer, size-1); +} + +/* +===================================================================== + + INFO STRINGS + +===================================================================== +*/ + +/* +=============== +Info_ValueForKey + +Searches the string for the given +key and returns the associated value, or an empty string. +=============== +*/ +char *Info_ValueForKey (char *s, char *key) +{ + char pkey[512]; + static char value[2][512]; // use two buffers so compares + // work without stomping on each other + static int valueindex; + char *o; + + valueindex ^= 1; + if (*s == '\\') + s++; + while (1) + { + o = pkey; + while (*s != '\\') + { + if (!*s) + return ""; + *o++ = *s++; + } + *o = 0; + s++; + + o = value[valueindex]; + + while (*s != '\\' && *s) + { + if (!*s) + return ""; + *o++ = *s++; + } + *o = 0; + + if (!strcmp (key, pkey) ) + return value[valueindex]; + + if (!*s) + return ""; + s++; + } +} + +void Info_RemoveKey (char *s, char *key) +{ + char *start; + char pkey[512]; + char value[512]; + char *o; + + if (strstr (key, "\\")) + { +// Com_Printf ("Can't use a key with a \\\n"); + return; + } + + while (1) + { + start = s; + if (*s == '\\') + s++; + o = pkey; + while (*s != '\\') + { + if (!*s) + return; + *o++ = *s++; + } + *o = 0; + s++; + + o = value; + while (*s != '\\' && *s) + { + if (!*s) + return; + *o++ = *s++; + } + *o = 0; + + if (!strcmp (key, pkey) ) + { + strcpy (start, s); // remove this part + return; + } + + if (!*s) + return; + } + +} + + +/* +================== +Info_Validate + +Some characters are illegal in info strings because they +can mess up the server's parsing +================== +*/ +qboolean Info_Validate (char *s) +{ + if (strstr (s, "\"")) + return false; + if (strstr (s, ";")) + return false; + return true; +} + +void Info_SetValueForKey (char *s, char *key, char *value) +{ + char newi[MAX_INFO_STRING], *v; + int c; + int maxsize = MAX_INFO_STRING; + + if (strstr (key, "\\") || strstr (value, "\\") ) + { + Com_Printf ("Can't use keys or values with a \\\n"); + return; + } + + if (strstr (key, ";") ) + { + Com_Printf ("Can't use keys or values with a semicolon\n"); + return; + } + + if (strstr (key, "\"") || strstr (value, "\"") ) + { + Com_Printf ("Can't use keys or values with a \"\n"); + return; + } + + if (strlen(key) > MAX_INFO_KEY-1 || strlen(value) > MAX_INFO_KEY-1) + { + Com_Printf ("Keys and values must be < 64 characters.\n"); + return; + } + Info_RemoveKey (s, key); + if (!value || !strlen(value)) + return; + + Com_sprintf (newi, sizeof(newi), "\\%s\\%s", key, value); + + if (strlen(newi) + strlen(s) > maxsize) + { + Com_Printf ("Info string length exceeded\n"); + return; + } + + // only copy ascii values + s += strlen(s); + v = newi; + while (*v) + { + c = *v++; + c &= 127; // strip high bits + if (c >= 32 && c < 127) + *s++ = c; + } + *s = 0; +} + +//==================================================================== + + diff --git a/original/rogue/q_shared.h b/original/rogue/q_shared.h new file mode 100644 index 0000000..6cfb6cd --- /dev/null +++ b/original/rogue/q_shared.h @@ -0,0 +1,1190 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. + +// q_shared.h -- included first by ALL program modules + +#ifdef _WIN32 +// unknown pragmas are SUPPOSED to be ignored, but.... +#pragma warning(disable : 4244) // MIPS +#pragma warning(disable : 4136) // X86 +#pragma warning(disable : 4051) // ALPHA + +#pragma warning(disable : 4018) // signed/unsigned mismatch +#pragma warning(disable : 4305) // truncation from const double to float + +#endif + +#include +#include +#include +#include +#include +#include +#include + +#if (defined _M_IX86 || defined __i386__) && !defined C_ONLY && !defined __sun__ +#define id386 1 +#else +#define id386 0 +#endif + +#if defined _M_ALPHA && !defined C_ONLY +#define idaxp 1 +#else +#define idaxp 0 +#endif + +typedef unsigned char byte; +typedef enum {false, true} qboolean; + + +#ifndef NULL +#define NULL ((void *)0) +#endif + + +// angle indexes +#define PITCH 0 // up / down +#define YAW 1 // left / right +#define ROLL 2 // fall over + +#define MAX_STRING_CHARS 1024 // max length of a string passed to Cmd_TokenizeString +#define MAX_STRING_TOKENS 80 // max tokens resulting from Cmd_TokenizeString +#define MAX_TOKEN_CHARS 128 // max length of an individual token + +#define MAX_QPATH 64 // max length of a quake game pathname +#define MAX_OSPATH 128 // max length of a filesystem pathname + +// +// per-level limits +// +#define MAX_CLIENTS 256 // absolute limit +#define MAX_EDICTS 1024 // must change protocol to increase more +#define MAX_LIGHTSTYLES 256 +#define MAX_MODELS 256 // these are sent over the net as bytes +#define MAX_SOUNDS 256 // so they cannot be blindly increased +#define MAX_IMAGES 256 +#define MAX_ITEMS 256 +#define MAX_GENERAL (MAX_CLIENTS*2) // general config strings + + +// game print flags +#define PRINT_LOW 0 // pickup messages +#define PRINT_MEDIUM 1 // death messages +#define PRINT_HIGH 2 // critical messages +#define PRINT_CHAT 3 // chat messages + + + +#define ERR_FATAL 0 // exit the entire game with a popup window +#define ERR_DROP 1 // print to console and disconnect from game +#define ERR_DISCONNECT 2 // don't kill server + +#define PRINT_ALL 0 +#define PRINT_DEVELOPER 1 // only print when "developer 1" +#define PRINT_ALERT 2 + + +// destination class for gi.multicast() +typedef enum +{ +MULTICAST_ALL, +MULTICAST_PHS, +MULTICAST_PVS, +MULTICAST_ALL_R, +MULTICAST_PHS_R, +MULTICAST_PVS_R +} multicast_t; + + +/* +============================================================== + +MATHLIB + +============================================================== +*/ + +typedef float vec_t; +typedef vec_t vec3_t[3]; +typedef vec_t vec5_t[5]; + +typedef int fixed4_t; +typedef int fixed8_t; +typedef int fixed16_t; + +#ifndef M_PI +#define M_PI 3.14159265358979323846 // matches value in gcc v2 math.h +#endif + +struct cplane_s; + +extern vec3_t vec3_origin; + +#define nanmask (255<<23) + +#define IS_NAN(x) (((*(int *)&x)&nanmask)==nanmask) + +// microsoft's fabs seems to be ungodly slow... +//float Q_fabs (float f); +//#define fabs(f) Q_fabs(f) +#if !defined C_ONLY && !defined __linux__ && !defined __sgi +extern long Q_ftol( float f ); +#else +#define Q_ftol( f ) ( long ) (f) +#endif + +#define DotProduct(x,y) (x[0]*y[0]+x[1]*y[1]+x[2]*y[2]) +#define VectorSubtract(a,b,c) (c[0]=a[0]-b[0],c[1]=a[1]-b[1],c[2]=a[2]-b[2]) +#define VectorAdd(a,b,c) (c[0]=a[0]+b[0],c[1]=a[1]+b[1],c[2]=a[2]+b[2]) +#define VectorCopy(a,b) (b[0]=a[0],b[1]=a[1],b[2]=a[2]) +#define VectorClear(a) (a[0]=a[1]=a[2]=0) +#define VectorNegate(a,b) (b[0]=-a[0],b[1]=-a[1],b[2]=-a[2]) +#define VectorSet(v, x, y, z) (v[0]=(x), v[1]=(y), v[2]=(z)) + +void VectorMA (vec3_t veca, float scale, vec3_t vecb, vec3_t vecc); + +// just in case you do't want to use the macros +vec_t _DotProduct (vec3_t v1, vec3_t v2); +void _VectorSubtract (vec3_t veca, vec3_t vecb, vec3_t out); +void _VectorAdd (vec3_t veca, vec3_t vecb, vec3_t out); +void _VectorCopy (vec3_t in, vec3_t out); + +void ClearBounds (vec3_t mins, vec3_t maxs); +void AddPointToBounds (vec3_t v, vec3_t mins, vec3_t maxs); +int VectorCompare (vec3_t v1, vec3_t v2); +vec_t VectorLength (vec3_t v); +void CrossProduct (vec3_t v1, vec3_t v2, vec3_t cross); +vec_t VectorNormalize (vec3_t v); // returns vector length +vec_t VectorNormalize2 (vec3_t v, vec3_t out); +void VectorInverse (vec3_t v); +void VectorScale (vec3_t in, vec_t scale, vec3_t out); +int Q_log2(int val); + +void R_ConcatRotations (float in1[3][3], float in2[3][3], float out[3][3]); +void R_ConcatTransforms (float in1[3][4], float in2[3][4], float out[3][4]); + +void AngleVectors (vec3_t angles, vec3_t forward, vec3_t right, vec3_t up); +int BoxOnPlaneSide (vec3_t emins, vec3_t emaxs, struct cplane_s *plane); +float anglemod(float a); +float LerpAngle (float a1, float a2, float frac); + +#define BOX_ON_PLANE_SIDE(emins, emaxs, p) \ + (((p)->type < 3)? \ + ( \ + ((p)->dist <= (emins)[(p)->type])? \ + 1 \ + : \ + ( \ + ((p)->dist >= (emaxs)[(p)->type])?\ + 2 \ + : \ + 3 \ + ) \ + ) \ + : \ + BoxOnPlaneSide( (emins), (emaxs), (p))) + +void ProjectPointOnPlane( vec3_t dst, const vec3_t p, const vec3_t normal ); +void PerpendicularVector( vec3_t dst, const vec3_t src ); +void RotatePointAroundVector( vec3_t dst, const vec3_t dir, const vec3_t point, float degrees ); + + +//============================================= + +char *COM_SkipPath (char *pathname); +void COM_StripExtension (char *in, char *out); +void COM_FileBase (char *in, char *out); +void COM_FilePath (char *in, char *out); +void COM_DefaultExtension (char *path, char *extension); + +char *COM_Parse (char **data_p); +// data is an in/out parm, returns a parsed out token + +void Com_sprintf (char *dest, int size, char *fmt, ...); + +void Com_PageInMemory (byte *buffer, int size); + +//============================================= + +// portable case insensitive compare +int Q_stricmp (char *s1, char *s2); +int Q_strcasecmp (char *s1, char *s2); +int Q_strncasecmp (char *s1, char *s2, int n); + +//============================================= + +short BigShort(short l); +short LittleShort(short l); +int BigLong (int l); +int LittleLong (int l); +float BigFloat (float l); +float LittleFloat (float l); + +void Swap_Init (void); +char *va(char *format, ...); + +//============================================= + +// +// key / value info strings +// +#define MAX_INFO_KEY 64 +#define MAX_INFO_VALUE 64 +#define MAX_INFO_STRING 512 + +char *Info_ValueForKey (char *s, char *key); +void Info_RemoveKey (char *s, char *key); +void Info_SetValueForKey (char *s, char *key, char *value); +qboolean Info_Validate (char *s); + +/* +============================================================== + +SYSTEM SPECIFIC + +============================================================== +*/ + +extern int curtime; // time returned by last Sys_Milliseconds + +int Sys_Milliseconds (void); +void Sys_Mkdir (char *path); + +// large block stack allocation routines +void *Hunk_Begin (int maxsize); +void *Hunk_Alloc (int size); +void Hunk_Free (void *buf); +int Hunk_End (void); + +// directory searching +#define SFF_ARCH 0x01 +#define SFF_HIDDEN 0x02 +#define SFF_RDONLY 0x04 +#define SFF_SUBDIR 0x08 +#define SFF_SYSTEM 0x10 + +/* +** pass in an attribute mask of things you wish to REJECT +*/ +char *Sys_FindFirst (char *path, unsigned musthave, unsigned canthave ); +char *Sys_FindNext ( unsigned musthave, unsigned canthave ); +void Sys_FindClose (void); + + +// this is only here so the functions in q_shared.c and q_shwin.c can link +void Sys_Error (char *error, ...); +void Com_Printf (char *msg, ...); + + +/* +========================================================== + +CVARS (console variables) + +========================================================== +*/ + +#ifndef CVAR +#define CVAR + +#define CVAR_ARCHIVE 1 // set to cause it to be saved to vars.rc +#define CVAR_USERINFO 2 // added to userinfo when changed +#define CVAR_SERVERINFO 4 // added to serverinfo when changed +#define CVAR_NOSET 8 // don't allow change from console at all, + // but can be set from the command line +#define CVAR_LATCH 16 // save changes until server restart + +// nothing outside the Cvar_*() functions should modify these fields! +typedef struct cvar_s +{ + char *name; + char *string; + char *latched_string; // for CVAR_LATCH vars + int flags; + qboolean modified; // set each time the cvar is changed + float value; + struct cvar_s *next; +} cvar_t; + +#endif // CVAR + +/* +============================================================== + +COLLISION DETECTION + +============================================================== +*/ + +// lower bits are stronger, and will eat weaker brushes completely +#define CONTENTS_SOLID 1 // an eye is never valid in a solid +#define CONTENTS_WINDOW 2 // translucent, but not watery +#define CONTENTS_AUX 4 +#define CONTENTS_LAVA 8 +#define CONTENTS_SLIME 16 +#define CONTENTS_WATER 32 +#define CONTENTS_MIST 64 +#define LAST_VISIBLE_CONTENTS 64 + +// remaining contents are non-visible, and don't eat brushes + +#define CONTENTS_AREAPORTAL 0x8000 + +#define CONTENTS_PLAYERCLIP 0x10000 +#define CONTENTS_MONSTERCLIP 0x20000 + +// currents can be added to any other contents, and may be mixed +#define CONTENTS_CURRENT_0 0x40000 +#define CONTENTS_CURRENT_90 0x80000 +#define CONTENTS_CURRENT_180 0x100000 +#define CONTENTS_CURRENT_270 0x200000 +#define CONTENTS_CURRENT_UP 0x400000 +#define CONTENTS_CURRENT_DOWN 0x800000 + +#define CONTENTS_ORIGIN 0x1000000 // removed before bsping an entity + +#define CONTENTS_MONSTER 0x2000000 // should never be on a brush, only in game +#define CONTENTS_DEADMONSTER 0x4000000 +#define CONTENTS_DETAIL 0x8000000 // brushes to be added after vis leafs +#define CONTENTS_TRANSLUCENT 0x10000000 // auto set if any surface has trans +#define CONTENTS_LADDER 0x20000000 + + + +#define SURF_LIGHT 0x1 // value will hold the light strength + +#define SURF_SLICK 0x2 // effects game physics + +#define SURF_SKY 0x4 // don't draw, but add to skybox +#define SURF_WARP 0x8 // turbulent water warp +#define SURF_TRANS33 0x10 +#define SURF_TRANS66 0x20 +#define SURF_FLOWING 0x40 // scroll towards angle +#define SURF_NODRAW 0x80 // don't bother referencing the texture + + + +// content masks +#define MASK_ALL (-1) +#define MASK_SOLID (CONTENTS_SOLID|CONTENTS_WINDOW) +#define MASK_PLAYERSOLID (CONTENTS_SOLID|CONTENTS_PLAYERCLIP|CONTENTS_WINDOW|CONTENTS_MONSTER) +#define MASK_DEADSOLID (CONTENTS_SOLID|CONTENTS_PLAYERCLIP|CONTENTS_WINDOW) +#define MASK_MONSTERSOLID (CONTENTS_SOLID|CONTENTS_MONSTERCLIP|CONTENTS_WINDOW|CONTENTS_MONSTER) +#define MASK_WATER (CONTENTS_WATER|CONTENTS_LAVA|CONTENTS_SLIME) +#define MASK_OPAQUE (CONTENTS_SOLID|CONTENTS_SLIME|CONTENTS_LAVA) +#define MASK_SHOT (CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_WINDOW|CONTENTS_DEADMONSTER) +#define MASK_CURRENT (CONTENTS_CURRENT_0|CONTENTS_CURRENT_90|CONTENTS_CURRENT_180|CONTENTS_CURRENT_270|CONTENTS_CURRENT_UP|CONTENTS_CURRENT_DOWN) + + +// gi.BoxEdicts() can return a list of either solid or trigger entities +// FIXME: eliminate AREA_ distinction? +#define AREA_SOLID 1 +#define AREA_TRIGGERS 2 + + +// plane_t structure +// !!! if this is changed, it must be changed in asm code too !!! +typedef struct cplane_s +{ + vec3_t normal; + float dist; + byte type; // for fast side tests + byte signbits; // signx + (signy<<1) + (signz<<1) + byte pad[2]; +} cplane_t; + +// structure offset for asm code +#define CPLANE_NORMAL_X 0 +#define CPLANE_NORMAL_Y 4 +#define CPLANE_NORMAL_Z 8 +#define CPLANE_DIST 12 +#define CPLANE_TYPE 16 +#define CPLANE_SIGNBITS 17 +#define CPLANE_PAD0 18 +#define CPLANE_PAD1 19 + +typedef struct cmodel_s +{ + vec3_t mins, maxs; + vec3_t origin; // for sounds or lights + int headnode; +} cmodel_t; + +typedef struct csurface_s +{ + char name[16]; + int flags; + int value; +} csurface_t; + +typedef struct mapsurface_s // used internally due to name len probs //ZOID +{ + csurface_t c; + char rname[32]; +} mapsurface_t; + +// a trace is returned when a box is swept through the world +typedef struct +{ + qboolean allsolid; // if true, plane is not valid + qboolean startsolid; // if true, the initial point was in a solid area + float fraction; // time completed, 1.0 = didn't hit anything + vec3_t endpos; // final position + cplane_t plane; // surface normal at impact + csurface_t *surface; // surface hit + int contents; // contents on other side of surface hit + struct edict_s *ent; // not set by CM_*() functions +} trace_t; + + + +// pmove_state_t is the information necessary for client side movement +// prediction +typedef enum +{ + // can accelerate and turn + PM_NORMAL, + PM_SPECTATOR, + // no acceleration or turning + PM_DEAD, + PM_GIB, // different bounding box + PM_FREEZE +} pmtype_t; + +// pmove->pm_flags +#define PMF_DUCKED 1 +#define PMF_JUMP_HELD 2 +#define PMF_ON_GROUND 4 +#define PMF_TIME_WATERJUMP 8 // pm_time is waterjump +#define PMF_TIME_LAND 16 // pm_time is time before rejump +#define PMF_TIME_TELEPORT 32 // pm_time is non-moving time +#define PMF_NO_PREDICTION 64 // temporarily disables prediction (used for grappling hook) + +// this structure needs to be communicated bit-accurate +// from the server to the client to guarantee that +// prediction stays in sync, so no floats are used. +// if any part of the game code modifies this struct, it +// will result in a prediction error of some degree. +typedef struct +{ + pmtype_t pm_type; + + short origin[3]; // 12.3 + short velocity[3]; // 12.3 + byte pm_flags; // ducked, jump_held, etc + byte pm_time; // each unit = 8 ms + short gravity; + short delta_angles[3]; // add to command angles to get view direction + // changed by spawns, rotating objects, and teleporters +} pmove_state_t; + + +// +// button bits +// +#define BUTTON_ATTACK 1 +#define BUTTON_USE 2 +#define BUTTON_ANY 128 // any key whatsoever + + +// usercmd_t is sent to the server each client frame +typedef struct usercmd_s +{ + byte msec; + byte buttons; + short angles[3]; + short forwardmove, sidemove, upmove; + byte impulse; // remove? + byte lightlevel; // light level the player is standing on +} usercmd_t; + + +#define MAXTOUCH 32 +typedef struct +{ + // state (in / out) + pmove_state_t s; + + // command (in) + usercmd_t cmd; + qboolean snapinitial; // if s has been changed outside pmove + + // results (out) + int numtouch; + struct edict_s *touchents[MAXTOUCH]; + + vec3_t viewangles; // clamped + float viewheight; + + vec3_t mins, maxs; // bounding box size + + struct edict_s *groundentity; + int watertype; + int waterlevel; + + // callbacks to test the world + trace_t (*trace) (vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end); + int (*pointcontents) (vec3_t point); +} pmove_t; + + +// entity_state_t->effects +// Effects are things handled on the client side (lights, particles, frame animations) +// that happen constantly on the given entity. +// An entity that has effects will be sent to the client +// even if it has a zero index model. +#define EF_ROTATE 0x00000001 // rotate (bonus items) +#define EF_GIB 0x00000002 // leave a trail +#define EF_BLASTER 0x00000008 // redlight + trail +#define EF_ROCKET 0x00000010 // redlight + trail +#define EF_GRENADE 0x00000020 +#define EF_HYPERBLASTER 0x00000040 +#define EF_BFG 0x00000080 +#define EF_COLOR_SHELL 0x00000100 +#define EF_POWERSCREEN 0x00000200 +#define EF_ANIM01 0x00000400 // automatically cycle between frames 0 and 1 at 2 hz +#define EF_ANIM23 0x00000800 // automatically cycle between frames 2 and 3 at 2 hz +#define EF_ANIM_ALL 0x00001000 // automatically cycle through all frames at 2hz +#define EF_ANIM_ALLFAST 0x00002000 // automatically cycle through all frames at 10hz +#define EF_FLIES 0x00004000 +#define EF_QUAD 0x00008000 +#define EF_PENT 0x00010000 +#define EF_TELEPORTER 0x00020000 // particle fountain +#define EF_FLAG1 0x00040000 +#define EF_FLAG2 0x00080000 +// RAFAEL +#define EF_IONRIPPER 0x00100000 +#define EF_GREENGIB 0x00200000 +#define EF_BLUEHYPERBLASTER 0x00400000 +#define EF_SPINNINGLIGHTS 0x00800000 +#define EF_PLASMA 0x01000000 +#define EF_TRAP 0x02000000 + +//ROGUE +#define EF_TRACKER 0x04000000 +#define EF_DOUBLE 0x08000000 +#define EF_SPHERETRANS 0x10000000 +#define EF_TAGTRAIL 0x20000000 +#define EF_HALF_DAMAGE 0x40000000 +#define EF_TRACKERTRAIL 0x80000000 +//ROGUE + +// entity_state_t->renderfx flags +#define RF_MINLIGHT 1 // allways have some light (viewmodel) +#define RF_VIEWERMODEL 2 // don't draw through eyes, only mirrors +#define RF_WEAPONMODEL 4 // only draw through eyes +#define RF_FULLBRIGHT 8 // allways draw full intensity +#define RF_DEPTHHACK 16 // for view weapon Z crunching +#define RF_TRANSLUCENT 32 +#define RF_FRAMELERP 64 +#define RF_BEAM 128 +#define RF_CUSTOMSKIN 256 // skin is an index in image_precache +#define RF_GLOW 512 // pulse lighting for bonus items +#define RF_SHELL_RED 1024 +#define RF_SHELL_GREEN 2048 +#define RF_SHELL_BLUE 4096 + +//ROGUE +#define RF_IR_VISIBLE 0x00008000 // 32768 +#define RF_SHELL_DOUBLE 0x00010000 // 65536 +#define RF_SHELL_HALF_DAM 0x00020000 +#define RF_USE_DISGUISE 0x00040000 +//ROGUE + +// player_state_t->refdef flags +#define RDF_UNDERWATER 1 // warp the screen as apropriate +#define RDF_NOWORLDMODEL 2 // used for player configuration screen + +//ROGUE +#define RDF_IRGOGGLES 4 +#define RDF_UVGOGGLES 8 +//ROGUE + +// +// muzzle flashes / player effects +// +#define MZ_BLASTER 0 +#define MZ_MACHINEGUN 1 +#define MZ_SHOTGUN 2 +#define MZ_CHAINGUN1 3 +#define MZ_CHAINGUN2 4 +#define MZ_CHAINGUN3 5 +#define MZ_RAILGUN 6 +#define MZ_ROCKET 7 +#define MZ_GRENADE 8 +#define MZ_LOGIN 9 +#define MZ_LOGOUT 10 +#define MZ_RESPAWN 11 +#define MZ_BFG 12 +#define MZ_SSHOTGUN 13 +#define MZ_HYPERBLASTER 14 +#define MZ_ITEMRESPAWN 15 +// RAFAEL +#define MZ_IONRIPPER 16 +#define MZ_BLUEHYPERBLASTER 17 +#define MZ_PHALANX 18 +#define MZ_SILENCED 128 // bit flag ORed with one of the above numbers + +//ROGUE +#define MZ_ETF_RIFLE 30 +#define MZ_UNUSED 31 +#define MZ_SHOTGUN2 32 +#define MZ_HEATBEAM 33 +#define MZ_BLASTER2 34 +#define MZ_TRACKER 35 +#define MZ_NUKE1 36 +#define MZ_NUKE2 37 +#define MZ_NUKE4 38 +#define MZ_NUKE8 39 +//ROGUE + +// +// monster muzzle flashes +// +#define MZ2_TANK_BLASTER_1 1 +#define MZ2_TANK_BLASTER_2 2 +#define MZ2_TANK_BLASTER_3 3 +#define MZ2_TANK_MACHINEGUN_1 4 +#define MZ2_TANK_MACHINEGUN_2 5 +#define MZ2_TANK_MACHINEGUN_3 6 +#define MZ2_TANK_MACHINEGUN_4 7 +#define MZ2_TANK_MACHINEGUN_5 8 +#define MZ2_TANK_MACHINEGUN_6 9 +#define MZ2_TANK_MACHINEGUN_7 10 +#define MZ2_TANK_MACHINEGUN_8 11 +#define MZ2_TANK_MACHINEGUN_9 12 +#define MZ2_TANK_MACHINEGUN_10 13 +#define MZ2_TANK_MACHINEGUN_11 14 +#define MZ2_TANK_MACHINEGUN_12 15 +#define MZ2_TANK_MACHINEGUN_13 16 +#define MZ2_TANK_MACHINEGUN_14 17 +#define MZ2_TANK_MACHINEGUN_15 18 +#define MZ2_TANK_MACHINEGUN_16 19 +#define MZ2_TANK_MACHINEGUN_17 20 +#define MZ2_TANK_MACHINEGUN_18 21 +#define MZ2_TANK_MACHINEGUN_19 22 +#define MZ2_TANK_ROCKET_1 23 +#define MZ2_TANK_ROCKET_2 24 +#define MZ2_TANK_ROCKET_3 25 + +#define MZ2_INFANTRY_MACHINEGUN_1 26 +#define MZ2_INFANTRY_MACHINEGUN_2 27 +#define MZ2_INFANTRY_MACHINEGUN_3 28 +#define MZ2_INFANTRY_MACHINEGUN_4 29 +#define MZ2_INFANTRY_MACHINEGUN_5 30 +#define MZ2_INFANTRY_MACHINEGUN_6 31 +#define MZ2_INFANTRY_MACHINEGUN_7 32 +#define MZ2_INFANTRY_MACHINEGUN_8 33 +#define MZ2_INFANTRY_MACHINEGUN_9 34 +#define MZ2_INFANTRY_MACHINEGUN_10 35 +#define MZ2_INFANTRY_MACHINEGUN_11 36 +#define MZ2_INFANTRY_MACHINEGUN_12 37 +#define MZ2_INFANTRY_MACHINEGUN_13 38 + +#define MZ2_SOLDIER_BLASTER_1 39 +#define MZ2_SOLDIER_BLASTER_2 40 +#define MZ2_SOLDIER_SHOTGUN_1 41 +#define MZ2_SOLDIER_SHOTGUN_2 42 +#define MZ2_SOLDIER_MACHINEGUN_1 43 +#define MZ2_SOLDIER_MACHINEGUN_2 44 + +#define MZ2_GUNNER_MACHINEGUN_1 45 +#define MZ2_GUNNER_MACHINEGUN_2 46 +#define MZ2_GUNNER_MACHINEGUN_3 47 +#define MZ2_GUNNER_MACHINEGUN_4 48 +#define MZ2_GUNNER_MACHINEGUN_5 49 +#define MZ2_GUNNER_MACHINEGUN_6 50 +#define MZ2_GUNNER_MACHINEGUN_7 51 +#define MZ2_GUNNER_MACHINEGUN_8 52 +#define MZ2_GUNNER_GRENADE_1 53 +#define MZ2_GUNNER_GRENADE_2 54 +#define MZ2_GUNNER_GRENADE_3 55 +#define MZ2_GUNNER_GRENADE_4 56 + +#define MZ2_CHICK_ROCKET_1 57 + +#define MZ2_FLYER_BLASTER_1 58 +#define MZ2_FLYER_BLASTER_2 59 + +#define MZ2_MEDIC_BLASTER_1 60 + +#define MZ2_GLADIATOR_RAILGUN_1 61 + +#define MZ2_HOVER_BLASTER_1 62 + +#define MZ2_ACTOR_MACHINEGUN_1 63 + +#define MZ2_SUPERTANK_MACHINEGUN_1 64 +#define MZ2_SUPERTANK_MACHINEGUN_2 65 +#define MZ2_SUPERTANK_MACHINEGUN_3 66 +#define MZ2_SUPERTANK_MACHINEGUN_4 67 +#define MZ2_SUPERTANK_MACHINEGUN_5 68 +#define MZ2_SUPERTANK_MACHINEGUN_6 69 +#define MZ2_SUPERTANK_ROCKET_1 70 +#define MZ2_SUPERTANK_ROCKET_2 71 +#define MZ2_SUPERTANK_ROCKET_3 72 + +#define MZ2_BOSS2_MACHINEGUN_L1 73 +#define MZ2_BOSS2_MACHINEGUN_L2 74 +#define MZ2_BOSS2_MACHINEGUN_L3 75 +#define MZ2_BOSS2_MACHINEGUN_L4 76 +#define MZ2_BOSS2_MACHINEGUN_L5 77 +#define MZ2_BOSS2_ROCKET_1 78 +#define MZ2_BOSS2_ROCKET_2 79 +#define MZ2_BOSS2_ROCKET_3 80 +#define MZ2_BOSS2_ROCKET_4 81 + +#define MZ2_FLOAT_BLASTER_1 82 + +#define MZ2_SOLDIER_BLASTER_3 83 +#define MZ2_SOLDIER_SHOTGUN_3 84 +#define MZ2_SOLDIER_MACHINEGUN_3 85 +#define MZ2_SOLDIER_BLASTER_4 86 +#define MZ2_SOLDIER_SHOTGUN_4 87 +#define MZ2_SOLDIER_MACHINEGUN_4 88 +#define MZ2_SOLDIER_BLASTER_5 89 +#define MZ2_SOLDIER_SHOTGUN_5 90 +#define MZ2_SOLDIER_MACHINEGUN_5 91 +#define MZ2_SOLDIER_BLASTER_6 92 +#define MZ2_SOLDIER_SHOTGUN_6 93 +#define MZ2_SOLDIER_MACHINEGUN_6 94 +#define MZ2_SOLDIER_BLASTER_7 95 +#define MZ2_SOLDIER_SHOTGUN_7 96 +#define MZ2_SOLDIER_MACHINEGUN_7 97 +#define MZ2_SOLDIER_BLASTER_8 98 +#define MZ2_SOLDIER_SHOTGUN_8 99 +#define MZ2_SOLDIER_MACHINEGUN_8 100 + +// --- Xian shit below --- +#define MZ2_MAKRON_BFG 101 +#define MZ2_MAKRON_BLASTER_1 102 +#define MZ2_MAKRON_BLASTER_2 103 +#define MZ2_MAKRON_BLASTER_3 104 +#define MZ2_MAKRON_BLASTER_4 105 +#define MZ2_MAKRON_BLASTER_5 106 +#define MZ2_MAKRON_BLASTER_6 107 +#define MZ2_MAKRON_BLASTER_7 108 +#define MZ2_MAKRON_BLASTER_8 109 +#define MZ2_MAKRON_BLASTER_9 110 +#define MZ2_MAKRON_BLASTER_10 111 +#define MZ2_MAKRON_BLASTER_11 112 +#define MZ2_MAKRON_BLASTER_12 113 +#define MZ2_MAKRON_BLASTER_13 114 +#define MZ2_MAKRON_BLASTER_14 115 +#define MZ2_MAKRON_BLASTER_15 116 +#define MZ2_MAKRON_BLASTER_16 117 +#define MZ2_MAKRON_BLASTER_17 118 +#define MZ2_MAKRON_RAILGUN_1 119 +#define MZ2_JORG_MACHINEGUN_L1 120 +#define MZ2_JORG_MACHINEGUN_L2 121 +#define MZ2_JORG_MACHINEGUN_L3 122 +#define MZ2_JORG_MACHINEGUN_L4 123 +#define MZ2_JORG_MACHINEGUN_L5 124 +#define MZ2_JORG_MACHINEGUN_L6 125 +#define MZ2_JORG_MACHINEGUN_R1 126 +#define MZ2_JORG_MACHINEGUN_R2 127 +#define MZ2_JORG_MACHINEGUN_R3 128 +#define MZ2_JORG_MACHINEGUN_R4 129 +#define MZ2_JORG_MACHINEGUN_R5 130 +#define MZ2_JORG_MACHINEGUN_R6 131 +#define MZ2_JORG_BFG_1 132 +#define MZ2_BOSS2_MACHINEGUN_R1 133 +#define MZ2_BOSS2_MACHINEGUN_R2 134 +#define MZ2_BOSS2_MACHINEGUN_R3 135 +#define MZ2_BOSS2_MACHINEGUN_R4 136 +#define MZ2_BOSS2_MACHINEGUN_R5 137 + +//ROGUE +#define MZ2_CARRIER_MACHINEGUN_L1 138 +#define MZ2_CARRIER_MACHINEGUN_R1 139 +#define MZ2_CARRIER_GRENADE 140 +#define MZ2_TURRET_MACHINEGUN 141 +#define MZ2_TURRET_ROCKET 142 +#define MZ2_TURRET_BLASTER 143 +#define MZ2_STALKER_BLASTER 144 +#define MZ2_DAEDALUS_BLASTER 145 +#define MZ2_MEDIC_BLASTER_2 146 +#define MZ2_CARRIER_RAILGUN 147 +#define MZ2_WIDOW_DISRUPTOR 148 +#define MZ2_WIDOW_BLASTER 149 +#define MZ2_WIDOW_RAIL 150 +#define MZ2_WIDOW_PLASMABEAM 151 // PMM - not used +#define MZ2_CARRIER_MACHINEGUN_L2 152 +#define MZ2_CARRIER_MACHINEGUN_R2 153 +#define MZ2_WIDOW_RAIL_LEFT 154 +#define MZ2_WIDOW_RAIL_RIGHT 155 +#define MZ2_WIDOW_BLASTER_SWEEP1 156 +#define MZ2_WIDOW_BLASTER_SWEEP2 157 +#define MZ2_WIDOW_BLASTER_SWEEP3 158 +#define MZ2_WIDOW_BLASTER_SWEEP4 159 +#define MZ2_WIDOW_BLASTER_SWEEP5 160 +#define MZ2_WIDOW_BLASTER_SWEEP6 161 +#define MZ2_WIDOW_BLASTER_SWEEP7 162 +#define MZ2_WIDOW_BLASTER_SWEEP8 163 +#define MZ2_WIDOW_BLASTER_SWEEP9 164 +#define MZ2_WIDOW_BLASTER_100 165 +#define MZ2_WIDOW_BLASTER_90 166 +#define MZ2_WIDOW_BLASTER_80 167 +#define MZ2_WIDOW_BLASTER_70 168 +#define MZ2_WIDOW_BLASTER_60 169 +#define MZ2_WIDOW_BLASTER_50 170 +#define MZ2_WIDOW_BLASTER_40 171 +#define MZ2_WIDOW_BLASTER_30 172 +#define MZ2_WIDOW_BLASTER_20 173 +#define MZ2_WIDOW_BLASTER_10 174 +#define MZ2_WIDOW_BLASTER_0 175 +#define MZ2_WIDOW_BLASTER_10L 176 +#define MZ2_WIDOW_BLASTER_20L 177 +#define MZ2_WIDOW_BLASTER_30L 178 +#define MZ2_WIDOW_BLASTER_40L 179 +#define MZ2_WIDOW_BLASTER_50L 180 +#define MZ2_WIDOW_BLASTER_60L 181 +#define MZ2_WIDOW_BLASTER_70L 182 +#define MZ2_WIDOW_RUN_1 183 +#define MZ2_WIDOW_RUN_2 184 +#define MZ2_WIDOW_RUN_3 185 +#define MZ2_WIDOW_RUN_4 186 +#define MZ2_WIDOW_RUN_5 187 +#define MZ2_WIDOW_RUN_6 188 +#define MZ2_WIDOW_RUN_7 189 +#define MZ2_WIDOW_RUN_8 190 +#define MZ2_CARRIER_ROCKET_1 191 +#define MZ2_CARRIER_ROCKET_2 192 +#define MZ2_CARRIER_ROCKET_3 193 +#define MZ2_CARRIER_ROCKET_4 194 +#define MZ2_WIDOW2_BEAMER_1 195 +#define MZ2_WIDOW2_BEAMER_2 196 +#define MZ2_WIDOW2_BEAMER_3 197 +#define MZ2_WIDOW2_BEAMER_4 198 +#define MZ2_WIDOW2_BEAMER_5 199 +#define MZ2_WIDOW2_BEAM_SWEEP_1 200 +#define MZ2_WIDOW2_BEAM_SWEEP_2 201 +#define MZ2_WIDOW2_BEAM_SWEEP_3 202 +#define MZ2_WIDOW2_BEAM_SWEEP_4 203 +#define MZ2_WIDOW2_BEAM_SWEEP_5 204 +#define MZ2_WIDOW2_BEAM_SWEEP_6 205 +#define MZ2_WIDOW2_BEAM_SWEEP_7 206 +#define MZ2_WIDOW2_BEAM_SWEEP_8 207 +#define MZ2_WIDOW2_BEAM_SWEEP_9 208 +#define MZ2_WIDOW2_BEAM_SWEEP_10 209 +#define MZ2_WIDOW2_BEAM_SWEEP_11 210 + +// ROGUE + +extern vec3_t monster_flash_offset []; + + +// temp entity events +// +// Temp entity events are for things that happen +// at a location seperate from any existing entity. +// Temporary entity messages are explicitly constructed +// and broadcast. +typedef enum +{ + TE_GUNSHOT, + TE_BLOOD, + TE_BLASTER, + TE_RAILTRAIL, + TE_SHOTGUN, + TE_EXPLOSION1, + TE_EXPLOSION2, + TE_ROCKET_EXPLOSION, + TE_GRENADE_EXPLOSION, + TE_SPARKS, + TE_SPLASH, + TE_BUBBLETRAIL, + TE_SCREEN_SPARKS, + TE_SHIELD_SPARKS, + TE_BULLET_SPARKS, + TE_LASER_SPARKS, + TE_PARASITE_ATTACK, + TE_ROCKET_EXPLOSION_WATER, + TE_GRENADE_EXPLOSION_WATER, + TE_MEDIC_CABLE_ATTACK, + TE_BFG_EXPLOSION, + TE_BFG_BIGEXPLOSION, + TE_BOSSTPORT, // used as '22' in a map, so DON'T RENUMBER!!! + TE_BFG_LASER, + TE_GRAPPLE_CABLE, + TE_WELDING_SPARKS, + TE_GREENBLOOD, + TE_BLUEHYPERBLASTER, + TE_PLASMA_EXPLOSION, + TE_TUNNEL_SPARKS, +//ROGUE + TE_BLASTER2, + TE_RAILTRAIL2, + TE_FLAME, + TE_LIGHTNING, + TE_DEBUGTRAIL, + TE_PLAIN_EXPLOSION, + TE_FLASHLIGHT, + TE_FORCEWALL, + TE_HEATBEAM, + TE_MONSTER_HEATBEAM, + TE_STEAM, + TE_BUBBLETRAIL2, + TE_MOREBLOOD, + TE_HEATBEAM_SPARKS, + TE_HEATBEAM_STEAM, + TE_CHAINFIST_SMOKE, + TE_ELECTRIC_SPARKS, + TE_TRACKER_EXPLOSION, + TE_TELEPORT_EFFECT, + TE_DBALL_GOAL, + TE_WIDOWBEAMOUT, + TE_NUKEBLAST, + TE_WIDOWSPLASH, + TE_EXPLOSION1_BIG, + TE_EXPLOSION1_NP, + TE_FLECHETTE +//ROGUE +} temp_event_t; + +#define SPLASH_UNKNOWN 0 +#define SPLASH_SPARKS 1 +#define SPLASH_BLUE_WATER 2 +#define SPLASH_BROWN_WATER 3 +#define SPLASH_SLIME 4 +#define SPLASH_LAVA 5 +#define SPLASH_BLOOD 6 + + +// sound channels +// channel 0 never willingly overrides +// other channels (1-7) allways override a playing sound on that channel +#define CHAN_AUTO 0 +#define CHAN_WEAPON 1 +#define CHAN_VOICE 2 +#define CHAN_ITEM 3 +#define CHAN_BODY 4 +// modifier flags +#define CHAN_NO_PHS_ADD 8 // send to all clients, not just ones in PHS (ATTN 0 will also do this) +#define CHAN_RELIABLE 16 // send by reliable message, not datagram + + +// sound attenuation values +#define ATTN_NONE 0 // full volume the entire level +#define ATTN_NORM 1 +#define ATTN_IDLE 2 +#define ATTN_STATIC 3 // diminish very rapidly with distance + + +// player_state->stats[] indexes +#define STAT_HEALTH_ICON 0 +#define STAT_HEALTH 1 +#define STAT_AMMO_ICON 2 +#define STAT_AMMO 3 +#define STAT_ARMOR_ICON 4 +#define STAT_ARMOR 5 +#define STAT_SELECTED_ICON 6 +#define STAT_PICKUP_ICON 7 +#define STAT_PICKUP_STRING 8 +#define STAT_TIMER_ICON 9 +#define STAT_TIMER 10 +#define STAT_HELPICON 11 +#define STAT_SELECTED_ITEM 12 +#define STAT_LAYOUTS 13 +#define STAT_FRAGS 14 +#define STAT_FLASHES 15 // cleared each frame, 1 = health, 2 = armor +#define STAT_CHASE 16 +#define STAT_SPECTATOR 17 + +#define MAX_STATS 32 + + +// dmflags->value flags +#define DF_NO_HEALTH 0x00000001 // 1 +#define DF_NO_ITEMS 0x00000002 // 2 +#define DF_WEAPONS_STAY 0x00000004 // 4 +#define DF_NO_FALLING 0x00000008 // 8 +#define DF_INSTANT_ITEMS 0x00000010 // 16 +#define DF_SAME_LEVEL 0x00000020 // 32 +#define DF_SKINTEAMS 0x00000040 // 64 +#define DF_MODELTEAMS 0x00000080 // 128 +#define DF_NO_FRIENDLY_FIRE 0x00000100 // 256 +#define DF_SPAWN_FARTHEST 0x00000200 // 512 +#define DF_FORCE_RESPAWN 0x00000400 // 1024 +#define DF_NO_ARMOR 0x00000800 // 2048 +#define DF_ALLOW_EXIT 0x00001000 // 4096 +#define DF_INFINITE_AMMO 0x00002000 // 8192 +#define DF_QUAD_DROP 0x00004000 // 16384 +#define DF_FIXED_FOV 0x00008000 // 32768 + +// RAFAEL +#define DF_QUADFIRE_DROP 0x00010000 // 65536 + +//ROGUE +#define DF_NO_MINES 0x00020000 +#define DF_NO_STACK_DOUBLE 0x00040000 +#define DF_NO_NUKES 0x00080000 +#define DF_NO_SPHERES 0x00100000 +//ROGUE + +/* +ROGUE - VERSIONS +1234 08/13/1998 Activision +1235 08/14/1998 Id Software +1236 08/15/1998 Steve Tietze +1237 08/15/1998 Phil Dobranski +1238 08/15/1998 John Sheley +1239 08/17/1998 Barrett Alexander +1230 08/17/1998 Brandon Fish +1245 08/17/1998 Don MacAskill +1246 08/17/1998 David "Zoid" Kirsch +1247 08/17/1998 Manu Smith +1248 08/17/1998 Geoff Scully +1249 08/17/1998 Andy Van Fossen +1240 08/20/1998 Activision Build 2 +1256 08/20/1998 Ranger Clan +1257 08/20/1998 Ensemble Studios +1258 08/21/1998 Robert Duffy +1259 08/21/1998 Stephen Seachord +1250 08/21/1998 Stephen Heaslip +1267 08/21/1998 Samir Sandesara +1268 08/21/1998 Oliver Wyman +1269 08/21/1998 Steven Marchegiano +1260 08/21/1998 Build #2 for Nihilistic +1278 08/21/1998 Build #2 for Ensemble +1279 08/26/1998 Build for Ron Solo - DEFUNCT +1270 08/26/1998 Build #3 for Activision +1289 08/26/1998 Build for Don MacAskill +1280 08/26/1998 Build for Robert Duffy +1290 08/26/1998 Build #2 for Rangers +1345 08/28/1998 Build #4 for Activision +2345 08/26/1998 Build for Zoid + +9999 08/20/1998 Internal Use +*/ +//#define ROGUE_VERSION_ID 1345 + +//#define ROGUE_VERSION_STRING "08/29/1998 Beta 4 for Activision" + +// ROGUE +/* +========================================================== + + ELEMENTS COMMUNICATED ACROSS THE NET + +========================================================== +*/ + +#define ANGLE2SHORT(x) ((int)((x)*65536/360) & 65535) +#define SHORT2ANGLE(x) ((x)*(360.0/65536)) + + +// +// config strings are a general means of communication from +// the server to all connected clients. +// Each config string can be at most MAX_QPATH characters. +// +#define CS_NAME 0 +#define CS_CDTRACK 1 +#define CS_SKY 2 +#define CS_SKYAXIS 3 // %f %f %f format +#define CS_SKYROTATE 4 +#define CS_STATUSBAR 5 // display program string + +#define CS_AIRACCEL 29 // air acceleration control +#define CS_MAXCLIENTS 30 +#define CS_MAPCHECKSUM 31 // for catching cheater maps + +#define CS_MODELS 32 +#define CS_SOUNDS (CS_MODELS+MAX_MODELS) +#define CS_IMAGES (CS_SOUNDS+MAX_SOUNDS) +#define CS_LIGHTS (CS_IMAGES+MAX_IMAGES) +#define CS_ITEMS (CS_LIGHTS+MAX_LIGHTSTYLES) +#define CS_PLAYERSKINS (CS_ITEMS+MAX_ITEMS) +#define CS_GENERAL (CS_PLAYERSKINS+MAX_CLIENTS) +#define MAX_CONFIGSTRINGS (CS_GENERAL+MAX_GENERAL) + + +//============================================== + + +// entity_state_t->event values +// ertity events are for effects that take place reletive +// to an existing entities origin. Very network efficient. +// All muzzle flashes really should be converted to events... +typedef enum +{ + EV_NONE, + EV_ITEM_RESPAWN, + EV_FOOTSTEP, + EV_FALLSHORT, + EV_FALL, + EV_FALLFAR, + EV_PLAYER_TELEPORT, + EV_OTHER_TELEPORT +} entity_event_t; + + +// entity_state_t is the information conveyed from the server +// in an update message about entities that the client will +// need to render in some way +typedef struct entity_state_s +{ + int number; // edict index + + vec3_t origin; + vec3_t angles; + vec3_t old_origin; // for lerping + int modelindex; + int modelindex2, modelindex3, modelindex4; // weapons, CTF flags, etc + int frame; + int skinnum; + unsigned int effects; // PGM - we're filling it, so it needs to be unsigned + int renderfx; + int solid; // for client side prediction, 8*(bits 0-4) is x/y radius + // 8*(bits 5-9) is z down distance, 8(bits10-15) is z up + // gi.linkentity sets this properly + int sound; // for looping sounds, to guarantee shutoff + int event; // impulse events -- muzzle flashes, footsteps, etc + // events only go out for a single frame, they + // are automatically cleared each frame +} entity_state_t; + +//============================================== + + +// player_state_t is the information needed in addition to pmove_state_t +// to rendered a view. There will only be 10 player_state_t sent each second, +// but the number of pmove_state_t changes will be reletive to client +// frame rates +typedef struct +{ + pmove_state_t pmove; // for prediction + + // these fields do not need to be communicated bit-precise + + vec3_t viewangles; // for fixed views + vec3_t viewoffset; // add to pmovestate->origin + vec3_t kick_angles; // add to view direction to get render angles + // set by weapon kicks, pain effects, etc + + vec3_t gunangles; + vec3_t gunoffset; + int gunindex; + int gunframe; + + float blend[4]; // rgba full screen effect + + float fov; // horizontal field of view + + int rdflags; // refdef flags + + short stats[MAX_STATS]; // fast status bar updates +} player_state_t; + + +// ================== +// PGM +#define VIDREF_GL 1 +#define VIDREF_SOFT 2 +#define VIDREF_OTHER 3 + +extern int vidref_val; +// PGM +// ================== diff --git a/original/rogue/rogue.def b/original/rogue/rogue.def new file mode 100644 index 0000000..4461243 --- /dev/null +++ b/original/rogue/rogue.def @@ -0,0 +1,2 @@ +EXPORTS + GetGameAPI diff --git a/original/rogue/rogue.dsp b/original/rogue/rogue.dsp new file mode 100644 index 0000000..e64a8b0 --- /dev/null +++ b/original/rogue/rogue.dsp @@ -0,0 +1,2023 @@ +# Microsoft Developer Studio Project File - Name="rogue" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 5.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102 +# TARGTYPE "Win32 (ALPHA) Dynamic-Link Library" 0x0602 + +CFG=rogue - Win32 Debug Alpha +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "rogue.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "rogue.mak" CFG="rogue - Win32 Debug Alpha" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "rogue - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "rogue - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "rogue - Win32 Debug Alpha" (based on\ + "Win32 (ALPHA) Dynamic-Link Library") +!MESSAGE "rogue - Win32 Release Alpha" (based on\ + "Win32 (ALPHA) Dynamic-Link Library") +!MESSAGE + +# Begin Project +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +# PROP WCE_Configuration "H/PC Ver. 2.00" + +!IF "$(CFG)" == "rogue - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir ".\release" +# PROP Intermediate_Dir ".\release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +CPP=cl.exe +# ADD BASE CPP /nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /YX /FD /c +# ADD CPP /nologo /MT /W1 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /YX /FD /c +MTL=midl.exe +# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /o NUL /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /o NUL /win32 +RSC=rc.exe +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /dll /machine:I386 +# ADD LINK32 kernel32.lib user32.lib winmm.lib /nologo /subsystem:windows /dll /machine:I386 /out:".\release\gamex86.dll" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir ".\debug" +# PROP Intermediate_Dir ".\debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +CPP=cl.exe +# ADD BASE CPP /nologo /MTd /W3 /Gm /GX /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /YX /FD /c +# ADD CPP /nologo /MTd /W3 /Gm /GX /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /YX /FD /c +MTL=midl.exe +# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /o NUL /win32 +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /o NUL /win32 +RSC=rc.exe +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /dll /debug /machine:I386 /pdbtype:sept +# ADD LINK32 kernel32.lib user32.lib winmm.lib /nologo /subsystem:windows /dll /incremental:no /map /debug /machine:I386 /out:".\debug\gamex86.dll" /pdbtype:sept + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug Alpha" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "rogue___" +# PROP BASE Intermediate_Dir "rogue___" +# PROP BASE Ignore_Export_Lib 0 +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "..\DebugAXP" +# PROP Intermediate_Dir ".\DebugAXP" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +MTL=midl.exe +# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /o NUL /win32 +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /o NUL /win32 +CPP=cl.exe +# ADD BASE CPP /nologo /Gt0 /W3 /GX /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /YX /FD /MTd /c +# ADD CPP /nologo /Gt0 /W3 /GX /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "C_ONLY" /YX /FD /MTd /c +RSC=rc.exe +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 winmm.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib /nologo /subsystem:windows /dll /map /debug /machine:ALPHA /out:".\debug\gamex86.dll" /pdbtype:sept +# ADD LINK32 winmm.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib /nologo /subsystem:windows /dll /map /debug /machine:ALPHA /out:".\debugAXP\gameaxp.dll" /pdbtype:sept + +!ELSEIF "$(CFG)" == "rogue - Win32 Release Alpha" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "rogue__0" +# PROP BASE Intermediate_Dir "rogue__0" +# PROP BASE Ignore_Export_Lib 0 +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "..\ReleaseAXP" +# PROP Intermediate_Dir ".\ReleaseAXP" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +MTL=midl.exe +# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /o NUL /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /o NUL /win32 +CPP=cl.exe +# ADD BASE CPP /nologo /MT /Gt0 /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /YX /FD /c +# ADD CPP /nologo /MT /Gt0 /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "C_ONLY" /YX /FD /c +RSC=rc.exe +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 winmm.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib /nologo /subsystem:windows /dll /machine:ALPHA /out:".\release\gamex86.dll" +# ADD LINK32 winmm.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib /nologo /subsystem:windows /dll /machine:ALPHA /out:".\ReleaseAXP\gameaxp.dll" + +!ENDIF + +# Begin Target + +# Name "rogue - Win32 Release" +# Name "rogue - Win32 Debug" +# Name "rogue - Win32 Debug Alpha" +# Name "rogue - Win32 Release Alpha" +# Begin Group "Source Files" + +# PROP Default_Filter "*.c" +# Begin Source File + +SOURCE=.\dm_ball.c + +!IF "$(CFG)" == "rogue - Win32 Release" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug Alpha" + +DEP_CPP_DM_BA=\ + ".\g_local.h"\ + ".\game.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "rogue - Win32 Release Alpha" + +DEP_CPP_DM_BA=\ + ".\g_local.h"\ + ".\game.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\dm_tag.c + +!IF "$(CFG)" == "rogue - Win32 Release" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug Alpha" + +DEP_CPP_DM_TA=\ + ".\g_local.h"\ + ".\game.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "rogue - Win32 Release Alpha" + +DEP_CPP_DM_TA=\ + ".\g_local.h"\ + ".\game.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\g_ai.c + +!IF "$(CFG)" == "rogue - Win32 Release" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug Alpha" + +DEP_CPP_G_AI_=\ + ".\g_local.h"\ + ".\game.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "rogue - Win32 Release Alpha" + +DEP_CPP_G_AI_=\ + ".\g_local.h"\ + ".\game.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\g_chase.c + +!IF "$(CFG)" == "rogue - Win32 Release" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug Alpha" + +DEP_CPP_G_CHA=\ + ".\g_local.h"\ + ".\game.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "rogue - Win32 Release Alpha" + +DEP_CPP_G_CHA=\ + ".\g_local.h"\ + ".\game.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\g_cmds.c + +!IF "$(CFG)" == "rogue - Win32 Release" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug Alpha" + +DEP_CPP_G_CMD=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_player.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "rogue - Win32 Release Alpha" + +DEP_CPP_G_CMD=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_player.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\g_combat.c + +!IF "$(CFG)" == "rogue - Win32 Release" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug Alpha" + +DEP_CPP_G_COM=\ + ".\g_local.h"\ + ".\game.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "rogue - Win32 Release Alpha" + +DEP_CPP_G_COM=\ + ".\g_local.h"\ + ".\game.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\g_func.c + +!IF "$(CFG)" == "rogue - Win32 Release" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug Alpha" + +DEP_CPP_G_FUN=\ + ".\g_local.h"\ + ".\game.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "rogue - Win32 Release Alpha" + +DEP_CPP_G_FUN=\ + ".\g_local.h"\ + ".\game.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\g_items.c + +!IF "$(CFG)" == "rogue - Win32 Release" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug Alpha" + +DEP_CPP_G_ITE=\ + ".\g_local.h"\ + ".\game.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "rogue - Win32 Release Alpha" + +DEP_CPP_G_ITE=\ + ".\g_local.h"\ + ".\game.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\g_main.c + +!IF "$(CFG)" == "rogue - Win32 Release" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug Alpha" + +DEP_CPP_G_MAI=\ + ".\g_local.h"\ + ".\game.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "rogue - Win32 Release Alpha" + +DEP_CPP_G_MAI=\ + ".\g_local.h"\ + ".\game.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\g_misc.c + +!IF "$(CFG)" == "rogue - Win32 Release" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug Alpha" + +DEP_CPP_G_MIS=\ + ".\g_local.h"\ + ".\game.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "rogue - Win32 Release Alpha" + +DEP_CPP_G_MIS=\ + ".\g_local.h"\ + ".\game.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\g_monster.c + +!IF "$(CFG)" == "rogue - Win32 Release" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug Alpha" + +DEP_CPP_G_MON=\ + ".\g_local.h"\ + ".\game.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "rogue - Win32 Release Alpha" + +DEP_CPP_G_MON=\ + ".\g_local.h"\ + ".\game.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\g_newai.c + +!IF "$(CFG)" == "rogue - Win32 Release" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug Alpha" + +DEP_CPP_G_NEW=\ + ".\g_local.h"\ + ".\game.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "rogue - Win32 Release Alpha" + +DEP_CPP_G_NEW=\ + ".\g_local.h"\ + ".\game.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\g_newdm.c + +!IF "$(CFG)" == "rogue - Win32 Release" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug Alpha" + +DEP_CPP_G_NEWD=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_player.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "rogue - Win32 Release Alpha" + +DEP_CPP_G_NEWD=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_player.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\g_newfnc.c + +!IF "$(CFG)" == "rogue - Win32 Release" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug Alpha" + +DEP_CPP_G_NEWF=\ + ".\g_local.h"\ + ".\game.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "rogue - Win32 Release Alpha" + +DEP_CPP_G_NEWF=\ + ".\g_local.h"\ + ".\game.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\g_newtarg.c + +!IF "$(CFG)" == "rogue - Win32 Release" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug Alpha" + +DEP_CPP_G_NEWT=\ + ".\g_local.h"\ + ".\game.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "rogue - Win32 Release Alpha" + +DEP_CPP_G_NEWT=\ + ".\g_local.h"\ + ".\game.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\g_newtrig.c + +!IF "$(CFG)" == "rogue - Win32 Release" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug Alpha" + +DEP_CPP_G_NEWTR=\ + ".\g_local.h"\ + ".\game.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "rogue - Win32 Release Alpha" + +DEP_CPP_G_NEWTR=\ + ".\g_local.h"\ + ".\game.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\g_newweap.c + +!IF "$(CFG)" == "rogue - Win32 Release" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug Alpha" + +DEP_CPP_G_NEWW=\ + ".\g_local.h"\ + ".\game.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "rogue - Win32 Release Alpha" + +DEP_CPP_G_NEWW=\ + ".\g_local.h"\ + ".\game.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\g_phys.c + +!IF "$(CFG)" == "rogue - Win32 Release" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug Alpha" + +DEP_CPP_G_PHY=\ + ".\g_local.h"\ + ".\game.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "rogue - Win32 Release Alpha" + +DEP_CPP_G_PHY=\ + ".\g_local.h"\ + ".\game.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\g_save.c + +!IF "$(CFG)" == "rogue - Win32 Release" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug Alpha" + +DEP_CPP_G_SAV=\ + ".\g_local.h"\ + ".\game.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "rogue - Win32 Release Alpha" + +DEP_CPP_G_SAV=\ + ".\g_local.h"\ + ".\game.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\g_spawn.c + +!IF "$(CFG)" == "rogue - Win32 Release" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug Alpha" + +DEP_CPP_G_SPA=\ + ".\g_local.h"\ + ".\game.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "rogue - Win32 Release Alpha" + +DEP_CPP_G_SPA=\ + ".\g_local.h"\ + ".\game.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\g_sphere.c + +!IF "$(CFG)" == "rogue - Win32 Release" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug Alpha" + +DEP_CPP_G_SPH=\ + ".\g_local.h"\ + ".\game.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "rogue - Win32 Release Alpha" + +DEP_CPP_G_SPH=\ + ".\g_local.h"\ + ".\game.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\g_svcmds.c + +!IF "$(CFG)" == "rogue - Win32 Release" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug Alpha" + +DEP_CPP_G_SVC=\ + ".\g_local.h"\ + ".\game.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "rogue - Win32 Release Alpha" + +DEP_CPP_G_SVC=\ + ".\g_local.h"\ + ".\game.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\g_target.c + +!IF "$(CFG)" == "rogue - Win32 Release" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug Alpha" + +DEP_CPP_G_TAR=\ + ".\g_local.h"\ + ".\game.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "rogue - Win32 Release Alpha" + +DEP_CPP_G_TAR=\ + ".\g_local.h"\ + ".\game.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\g_trigger.c + +!IF "$(CFG)" == "rogue - Win32 Release" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug Alpha" + +DEP_CPP_G_TRI=\ + ".\g_local.h"\ + ".\game.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "rogue - Win32 Release Alpha" + +DEP_CPP_G_TRI=\ + ".\g_local.h"\ + ".\game.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\g_turret.c + +!IF "$(CFG)" == "rogue - Win32 Release" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug Alpha" + +DEP_CPP_G_TUR=\ + ".\g_local.h"\ + ".\game.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "rogue - Win32 Release Alpha" + +DEP_CPP_G_TUR=\ + ".\g_local.h"\ + ".\game.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\g_utils.c + +!IF "$(CFG)" == "rogue - Win32 Release" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug Alpha" + +DEP_CPP_G_UTI=\ + ".\g_local.h"\ + ".\game.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "rogue - Win32 Release Alpha" + +DEP_CPP_G_UTI=\ + ".\g_local.h"\ + ".\game.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\g_weapon.c + +!IF "$(CFG)" == "rogue - Win32 Release" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug Alpha" + +DEP_CPP_G_WEA=\ + ".\g_local.h"\ + ".\game.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "rogue - Win32 Release Alpha" + +DEP_CPP_G_WEA=\ + ".\g_local.h"\ + ".\game.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\m_actor.c + +!IF "$(CFG)" == "rogue - Win32 Release" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug Alpha" + +DEP_CPP_M_ACT=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_actor.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "rogue - Win32 Release Alpha" + +DEP_CPP_M_ACT=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_actor.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\m_berserk.c + +!IF "$(CFG)" == "rogue - Win32 Release" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug Alpha" + +DEP_CPP_M_BER=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_berserk.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "rogue - Win32 Release Alpha" + +DEP_CPP_M_BER=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_berserk.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\m_boss2.c + +!IF "$(CFG)" == "rogue - Win32 Release" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug Alpha" + +DEP_CPP_M_BOS=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_boss2.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "rogue - Win32 Release Alpha" + +DEP_CPP_M_BOS=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_boss2.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\m_boss3.c + +!IF "$(CFG)" == "rogue - Win32 Release" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug Alpha" + +DEP_CPP_M_BOSS=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_boss32.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "rogue - Win32 Release Alpha" + +DEP_CPP_M_BOSS=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_boss32.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\m_boss31.c + +!IF "$(CFG)" == "rogue - Win32 Release" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug Alpha" + +DEP_CPP_M_BOSS3=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_boss31.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "rogue - Win32 Release Alpha" + +DEP_CPP_M_BOSS3=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_boss31.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\m_boss32.c + +!IF "$(CFG)" == "rogue - Win32 Release" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug Alpha" + +DEP_CPP_M_BOSS32=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_boss32.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "rogue - Win32 Release Alpha" + +DEP_CPP_M_BOSS32=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_boss32.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\m_brain.c + +!IF "$(CFG)" == "rogue - Win32 Release" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug Alpha" + +DEP_CPP_M_BRA=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_brain.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "rogue - Win32 Release Alpha" + +DEP_CPP_M_BRA=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_brain.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\m_carrier.c + +!IF "$(CFG)" == "rogue - Win32 Release" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug Alpha" + +DEP_CPP_M_CAR=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_carrier.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "rogue - Win32 Release Alpha" + +DEP_CPP_M_CAR=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_carrier.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\m_chick.c + +!IF "$(CFG)" == "rogue - Win32 Release" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug Alpha" + +DEP_CPP_M_CHI=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_chick.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "rogue - Win32 Release Alpha" + +DEP_CPP_M_CHI=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_chick.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\m_flash.c + +!IF "$(CFG)" == "rogue - Win32 Release" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug Alpha" + +DEP_CPP_M_FLA=\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "rogue - Win32 Release Alpha" + +DEP_CPP_M_FLA=\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\m_flipper.c + +!IF "$(CFG)" == "rogue - Win32 Release" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug Alpha" + +DEP_CPP_M_FLI=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_flipper.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "rogue - Win32 Release Alpha" + +DEP_CPP_M_FLI=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_flipper.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\m_float.c + +!IF "$(CFG)" == "rogue - Win32 Release" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug Alpha" + +DEP_CPP_M_FLO=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_float.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "rogue - Win32 Release Alpha" + +DEP_CPP_M_FLO=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_float.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\m_flyer.c + +!IF "$(CFG)" == "rogue - Win32 Release" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug Alpha" + +DEP_CPP_M_FLY=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_flyer.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "rogue - Win32 Release Alpha" + +DEP_CPP_M_FLY=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_flyer.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\m_gladiator.c + +!IF "$(CFG)" == "rogue - Win32 Release" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug Alpha" + +DEP_CPP_M_GLA=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_gladiator.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "rogue - Win32 Release Alpha" + +DEP_CPP_M_GLA=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_gladiator.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\m_gunner.c + +!IF "$(CFG)" == "rogue - Win32 Release" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug Alpha" + +DEP_CPP_M_GUN=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_gunner.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "rogue - Win32 Release Alpha" + +DEP_CPP_M_GUN=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_gunner.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\m_hover.c + +!IF "$(CFG)" == "rogue - Win32 Release" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug Alpha" + +DEP_CPP_M_HOV=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_hover.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "rogue - Win32 Release Alpha" + +DEP_CPP_M_HOV=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_hover.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\m_infantry.c + +!IF "$(CFG)" == "rogue - Win32 Release" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug Alpha" + +DEP_CPP_M_INF=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_infantry.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "rogue - Win32 Release Alpha" + +DEP_CPP_M_INF=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_infantry.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\m_insane.c + +!IF "$(CFG)" == "rogue - Win32 Release" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug Alpha" + +DEP_CPP_M_INS=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_insane.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "rogue - Win32 Release Alpha" + +DEP_CPP_M_INS=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_insane.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\m_medic.c + +!IF "$(CFG)" == "rogue - Win32 Release" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug Alpha" + +DEP_CPP_M_MED=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_medic.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "rogue - Win32 Release Alpha" + +DEP_CPP_M_MED=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_medic.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\m_move.c + +!IF "$(CFG)" == "rogue - Win32 Release" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug Alpha" + +DEP_CPP_M_MOV=\ + ".\g_local.h"\ + ".\game.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "rogue - Win32 Release Alpha" + +DEP_CPP_M_MOV=\ + ".\g_local.h"\ + ".\game.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\m_mutant.c + +!IF "$(CFG)" == "rogue - Win32 Release" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug Alpha" + +DEP_CPP_M_MUT=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_mutant.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "rogue - Win32 Release Alpha" + +DEP_CPP_M_MUT=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_mutant.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\m_parasite.c + +!IF "$(CFG)" == "rogue - Win32 Release" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug Alpha" + +DEP_CPP_M_PAR=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_parasite.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "rogue - Win32 Release Alpha" + +DEP_CPP_M_PAR=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_parasite.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\m_soldier.c + +!IF "$(CFG)" == "rogue - Win32 Release" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug Alpha" + +DEP_CPP_M_SOL=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_soldier.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "rogue - Win32 Release Alpha" + +DEP_CPP_M_SOL=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_soldier.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\m_stalker.c + +!IF "$(CFG)" == "rogue - Win32 Release" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug Alpha" + +DEP_CPP_M_STA=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_stalker.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "rogue - Win32 Release Alpha" + +DEP_CPP_M_STA=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_stalker.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\m_supertank.c + +!IF "$(CFG)" == "rogue - Win32 Release" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug Alpha" + +DEP_CPP_M_SUP=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_supertank.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "rogue - Win32 Release Alpha" + +DEP_CPP_M_SUP=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_supertank.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\m_tank.c + +!IF "$(CFG)" == "rogue - Win32 Release" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug Alpha" + +DEP_CPP_M_TAN=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_tank.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "rogue - Win32 Release Alpha" + +DEP_CPP_M_TAN=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_tank.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\m_turret.c + +!IF "$(CFG)" == "rogue - Win32 Release" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug Alpha" + +DEP_CPP_M_TUR=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_turret.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "rogue - Win32 Release Alpha" + +DEP_CPP_M_TUR=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_turret.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\m_widow.c + +!IF "$(CFG)" == "rogue - Win32 Release" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug Alpha" + +DEP_CPP_M_WID=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_widow.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "rogue - Win32 Release Alpha" + +DEP_CPP_M_WID=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_widow.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\m_widow2.c + +!IF "$(CFG)" == "rogue - Win32 Release" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug Alpha" + +DEP_CPP_M_WIDO=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_widow2.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "rogue - Win32 Release Alpha" + +DEP_CPP_M_WIDO=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_widow2.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\p_client.c + +!IF "$(CFG)" == "rogue - Win32 Release" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug Alpha" + +DEP_CPP_P_CLI=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_player.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "rogue - Win32 Release Alpha" + +DEP_CPP_P_CLI=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_player.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\p_hud.c + +!IF "$(CFG)" == "rogue - Win32 Release" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug Alpha" + +DEP_CPP_P_HUD=\ + ".\g_local.h"\ + ".\game.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "rogue - Win32 Release Alpha" + +DEP_CPP_P_HUD=\ + ".\g_local.h"\ + ".\game.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\p_trail.c + +!IF "$(CFG)" == "rogue - Win32 Release" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug Alpha" + +DEP_CPP_P_TRA=\ + ".\g_local.h"\ + ".\game.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "rogue - Win32 Release Alpha" + +DEP_CPP_P_TRA=\ + ".\g_local.h"\ + ".\game.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\p_view.c + +!IF "$(CFG)" == "rogue - Win32 Release" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug Alpha" + +DEP_CPP_P_VIE=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_player.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "rogue - Win32 Release Alpha" + +DEP_CPP_P_VIE=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_player.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\p_weapon.c + +!IF "$(CFG)" == "rogue - Win32 Release" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug Alpha" + +DEP_CPP_P_WEA=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_player.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "rogue - Win32 Release Alpha" + +DEP_CPP_P_WEA=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_player.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\q_shared.c + +!IF "$(CFG)" == "rogue - Win32 Release" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug" + +!ELSEIF "$(CFG)" == "rogue - Win32 Debug Alpha" + +DEP_CPP_Q_SHA=\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "rogue - Win32 Release Alpha" + +DEP_CPP_Q_SHA=\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "*.h" +# Begin Source File + +SOURCE=.\g_local.h +# End Source File +# Begin Source File + +SOURCE=.\game.h +# End Source File +# Begin Source File + +SOURCE=.\m_actor.h +# End Source File +# Begin Source File + +SOURCE=.\m_berserk.h +# End Source File +# Begin Source File + +SOURCE=.\m_boss2.h +# End Source File +# Begin Source File + +SOURCE=.\m_boss31.h +# End Source File +# Begin Source File + +SOURCE=.\m_boss32.h +# End Source File +# Begin Source File + +SOURCE=.\m_brain.h +# End Source File +# Begin Source File + +SOURCE=.\m_chick.h +# End Source File +# Begin Source File + +SOURCE=.\m_fixbot.h +# End Source File +# Begin Source File + +SOURCE=.\m_flipper.h +# End Source File +# Begin Source File + +SOURCE=.\m_float.h +# End Source File +# Begin Source File + +SOURCE=.\m_flyer.h +# End Source File +# Begin Source File + +SOURCE=.\m_gekk.h +# End Source File +# Begin Source File + +SOURCE=.\m_gladiator.h +# End Source File +# Begin Source File + +SOURCE=.\m_gunner.h +# End Source File +# Begin Source File + +SOURCE=.\m_hover.h +# End Source File +# Begin Source File + +SOURCE=.\m_infantry.h +# End Source File +# Begin Source File + +SOURCE=.\m_insane.h +# End Source File +# Begin Source File + +SOURCE=.\m_medic.h +# End Source File +# Begin Source File + +SOURCE=.\m_mutant.h +# End Source File +# Begin Source File + +SOURCE=.\m_parasite.h +# End Source File +# Begin Source File + +SOURCE=.\m_player.h +# End Source File +# Begin Source File + +SOURCE=.\m_rider.h +# End Source File +# Begin Source File + +SOURCE=.\m_soldier.h +# End Source File +# Begin Source File + +SOURCE=.\m_soldierh.h +# End Source File +# Begin Source File + +SOURCE=.\m_supertank.h +# End Source File +# Begin Source File + +SOURCE=.\m_tank.h +# End Source File +# Begin Source File + +SOURCE=.\q_shared.h +# End Source File +# End Group +# Begin Group "Resource Files" + +# PROP Default_Filter "*.def,*.res" +# Begin Source File + +SOURCE=.\rogue.def +# End Source File +# End Group +# End Target +# End Project diff --git a/original/xatrix/g_ai.c b/original/xatrix/g_ai.c new file mode 100644 index 0000000..c2f98d1 --- /dev/null +++ b/original/xatrix/g_ai.c @@ -0,0 +1,1100 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// g_ai.c + +#include "g_local.h" + +qboolean FindTarget (edict_t *self); +extern cvar_t *maxclients; + +qboolean ai_checkattack (edict_t *self, float dist); + +qboolean enemy_vis; +qboolean enemy_infront; +int enemy_range; +float enemy_yaw; + +//============================================================================ + + +/* +================= +AI_SetSightClient + +Called once each frame to set level.sight_client to the +player to be checked for in findtarget. + +If all clients are either dead or in notarget, sight_client +will be null. + +In coop games, sight_client will cycle between the clients. +================= +*/ +void AI_SetSightClient (void) +{ + edict_t *ent; + int start, check; + + if (level.sight_client == NULL) + start = 1; + else + start = level.sight_client - g_edicts; + + check = start; + while (1) + { + check++; + if (check > game.maxclients) + check = 1; + ent = &g_edicts[check]; + if (ent->inuse + && ent->health > 0 + && !(ent->flags & FL_NOTARGET) ) + { + level.sight_client = ent; + return; // got one + } + if (check == start) + { + level.sight_client = NULL; + return; // nobody to see + } + } +} + +//============================================================================ + +/* +============= +ai_move + +Move the specified distance at current facing. +This replaces the QC functions: ai_forward, ai_back, ai_pain, and ai_painforward +============== +*/ +void ai_move (edict_t *self, float dist) +{ + M_walkmove (self, self->s.angles[YAW], dist); +} + + +/* +============= +ai_stand + +Used for standing around and looking for players +Distance is for slight position adjustments needed by the animations +============== +*/ +void ai_stand (edict_t *self, float dist) +{ + vec3_t v; + + if (dist) + M_walkmove (self, self->s.angles[YAW], dist); + + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + { + if (self->enemy) + { + VectorSubtract (self->enemy->s.origin, self->s.origin, v); + self->ideal_yaw = vectoyaw(v); + if (self->s.angles[YAW] != self->ideal_yaw && self->monsterinfo.aiflags & AI_TEMP_STAND_GROUND) + { + self->monsterinfo.aiflags &= ~(AI_STAND_GROUND | AI_TEMP_STAND_GROUND); + self->monsterinfo.run (self); + } + M_ChangeYaw (self); + ai_checkattack (self, 0); + } + else + FindTarget (self); + return; + } + + if (FindTarget (self)) + return; + + if (level.time > self->monsterinfo.pausetime) + { + self->monsterinfo.walk (self); + return; + } + + if (!(self->spawnflags & 1) && (self->monsterinfo.idle) && (level.time > self->monsterinfo.idle_time)) + { + if (self->monsterinfo.idle_time) + { + self->monsterinfo.idle (self); + self->monsterinfo.idle_time = level.time + 15 + random() * 15; + } + else + { + self->monsterinfo.idle_time = level.time + random() * 15; + } + } +} + + +/* +============= +ai_walk + +The monster is walking it's beat +============= +*/ +void ai_walk (edict_t *self, float dist) +{ + M_MoveToGoal (self, dist); + + // check for noticing a player + if (FindTarget (self)) + return; + + if ((self->monsterinfo.search) && (level.time > self->monsterinfo.idle_time)) + { + if (self->monsterinfo.idle_time) + { + self->monsterinfo.search (self); + self->monsterinfo.idle_time = level.time + 15 + random() * 15; + } + else + { + self->monsterinfo.idle_time = level.time + random() * 15; + } + } +} + + +/* +============= +ai_charge + +Turns towards target and advances +Use this call with a distnace of 0 to replace ai_face +============== +*/ +void ai_charge (edict_t *self, float dist) +{ + vec3_t v; + + VectorSubtract (self->enemy->s.origin, self->s.origin, v); + self->ideal_yaw = vectoyaw(v); + M_ChangeYaw (self); + + if (dist) + M_walkmove (self, self->s.angles[YAW], dist); +} + + +/* +============= +ai_turn + +don't move, but turn towards ideal_yaw +Distance is for slight position adjustments needed by the animations +============= +*/ +void ai_turn (edict_t *self, float dist) +{ + if (dist) + M_walkmove (self, self->s.angles[YAW], dist); + + if (FindTarget (self)) + return; + + M_ChangeYaw (self); +} + + +/* + +.enemy +Will be world if not currently angry at anyone. + +.movetarget +The next path spot to walk toward. If .enemy, ignore .movetarget. +When an enemy is killed, the monster will try to return to it's path. + +.hunt_time +Set to time + something when the player is in sight, but movement straight for +him is blocked. This causes the monster to use wall following code for +movement direction instead of sighting on the player. + +.ideal_yaw +A yaw angle of the intended direction, which will be turned towards at up +to 45 deg / state. If the enemy is in view and hunt_time is not active, +this will be the exact line towards the enemy. + +.pausetime +A monster will leave it's stand state and head towards it's .movetarget when +time > .pausetime. + +walkmove(angle, speed) primitive is all or nothing +*/ + +/* +============= +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 +============= +*/ +int range (edict_t *self, edict_t *other) +{ + vec3_t v; + float len; + + VectorSubtract (self->s.origin, other->s.origin, v); + len = VectorLength (v); + if (len < MELEE_DISTANCE) + return RANGE_MELEE; + if (len < 500) + return RANGE_NEAR; + if (len < 1000) + return RANGE_MID; + return RANGE_FAR; +} + +/* +============= +visible + +returns 1 if the entity is visible to self, even if not infront () +============= +*/ +qboolean visible (edict_t *self, edict_t *other) +{ + vec3_t spot1; + vec3_t spot2; + trace_t trace; + + VectorCopy (self->s.origin, spot1); + spot1[2] += self->viewheight; + VectorCopy (other->s.origin, spot2); + spot2[2] += other->viewheight; + trace = gi.trace (spot1, vec3_origin, vec3_origin, spot2, self, MASK_OPAQUE); + + if (trace.fraction == 1.0) + return true; + return false; +} + + +/* +============= +infront + +returns 1 if the entity is in front (in sight) of self +============= +*/ +qboolean infront (edict_t *self, edict_t *other) +{ + vec3_t vec; + float dot; + vec3_t forward; + + AngleVectors (self->s.angles, forward, NULL, NULL); + VectorSubtract (other->s.origin, self->s.origin, vec); + VectorNormalize (vec); + dot = DotProduct (vec, forward); + + if (dot > 0.3) + return true; + return false; +} + + +//============================================================================ + +void HuntTarget (edict_t *self) +{ + vec3_t vec; + + self->goalentity = self->enemy; + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + self->monsterinfo.stand (self); + else + self->monsterinfo.run (self); + VectorSubtract (self->enemy->s.origin, self->s.origin, vec); + self->ideal_yaw = vectoyaw(vec); + // wait a while before first attack + if (!(self->monsterinfo.aiflags & AI_STAND_GROUND)) + AttackFinished (self, 1); +} + +void FoundTarget (edict_t *self) +{ + // let other monsters see this monster for a while + if (self->enemy->client) + { + level.sight_entity = self; + level.sight_entity_framenum = level.framenum; + level.sight_entity->light_level = 128; + } + + self->show_hostile = level.time + 1; // wake up other monsters + + VectorCopy(self->enemy->s.origin, self->monsterinfo.last_sighting); + self->monsterinfo.trail_time = level.time; + + if (!self->combattarget) + { + HuntTarget (self); + return; + } + + self->goalentity = self->movetarget = G_PickTarget(self->combattarget); + if (!self->movetarget) + { + self->goalentity = self->movetarget = self->enemy; + HuntTarget (self); + gi.dprintf("%s at %s, combattarget %s not found\n", self->classname, vtos(self->s.origin), self->combattarget); + return; + } + + // clear out our combattarget, these are a one shot deal + self->combattarget = NULL; + self->monsterinfo.aiflags |= AI_COMBAT_POINT; + + // clear the targetname, that point is ours! + self->movetarget->targetname = NULL; + self->monsterinfo.pausetime = 0; + + // run for it + self->monsterinfo.run (self); +} + + +/* +=========== +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. +============ +*/ +qboolean FindTarget (edict_t *self) +{ + edict_t *client; + qboolean heardit; + int r; + + if (self->monsterinfo.aiflags & AI_GOOD_GUY) + { + if (self->goalentity && self->goalentity->inuse && self->goalentity->classname) + { + if (strcmp(self->goalentity->classname, "target_actor") == 0) + return false; + } + + //FIXME look for monsters? + return false; + } + + // if we're going to a combat point, just proceed + if (self->monsterinfo.aiflags & AI_COMBAT_POINT) + return false; + +// if the first spawnflag bit is set, the monster will only wake up on +// really seeing the player, not another monster getting angry or hearing +// something + +// revised behavior so they will wake up if they "see" a player make a noise +// but not weapon impact/explosion noises + + heardit = false; + if ((level.sight_entity_framenum >= (level.framenum - 1)) && !(self->spawnflags & 1) ) + { + client = level.sight_entity; + if (client->enemy == self->enemy) + { + return false; + } + } + else if (level.sound_entity_framenum >= (level.framenum - 1)) + { + client = level.sound_entity; + heardit = true; + } + else if (!(self->enemy) && (level.sound2_entity_framenum >= (level.framenum - 1)) && !(self->spawnflags & 1) ) + { + client = level.sound2_entity; + heardit = true; + } + else + { + client = level.sight_client; + if (!client) + return false; // no clients to get mad at + } + + // if the entity went away, forget it + if (!client->inuse) + return false; + + if (client == self->enemy) + return true; // JDC false; + + if (client->client) + { + if (client->flags & FL_NOTARGET) + return false; + } + else if (client->svflags & SVF_MONSTER) + { + if (!client->enemy) + return false; + if (client->enemy->flags & FL_NOTARGET) + return false; + } + else if (heardit) + { + if (client->owner->flags & FL_NOTARGET) + return false; + } + else + return false; + + if (!heardit) + { + r = range (self, client); + + if (r == RANGE_FAR) + return false; + +// this is where we would check invisibility + + // is client in an spot too dark to be seen? + if (client->light_level <= 5) + return false; + + if (!visible (self, client)) + { + return false; + } + + if (r == RANGE_NEAR) + { + if (client->show_hostile < level.time && !infront (self, client)) + { + return false; + } + } + else if (r == RANGE_MID) + { + if (!infront (self, client)) + { + return false; + } + } + + self->enemy = client; + + if (strcmp(self->enemy->classname, "player_noise") != 0) + { + self->monsterinfo.aiflags &= ~AI_SOUND_TARGET; + + if (!self->enemy->client) + { + self->enemy = self->enemy->enemy; + if (!self->enemy->client) + { + self->enemy = NULL; + return false; + } + } + } + } + else // heardit + { + vec3_t temp; + + if (self->spawnflags & 1) + { + if (!visible (self, client)) + return false; + } + else + { + if (!gi.inPHS(self->s.origin, client->s.origin)) + return false; + } + + VectorSubtract (client->s.origin, self->s.origin, temp); + + if (VectorLength(temp) > 1000) // too far to hear + { + return false; + } + + // check area portals - if they are different and not connected then we can't hear it + if (client->areanum != self->areanum) + if (!gi.AreasConnected(self->areanum, client->areanum)) + return false; + + self->ideal_yaw = vectoyaw(temp); + M_ChangeYaw (self); + + // hunt the sound for a bit; hopefully find the real player + self->monsterinfo.aiflags |= AI_SOUND_TARGET; + self->enemy = client; + } + +// +// got one +// + FoundTarget (self); + + if (!(self->monsterinfo.aiflags & AI_SOUND_TARGET) && (self->monsterinfo.sight)) + self->monsterinfo.sight (self, self->enemy); + + return true; +} + + +//============================================================================= + +/* +============ +FacingIdeal + +============ +*/ +qboolean FacingIdeal(edict_t *self) +{ + float delta; + + delta = anglemod(self->s.angles[YAW] - self->ideal_yaw); + if (delta > 45 && delta < 315) + return false; + return true; +} + + +//============================================================================= + +qboolean M_CheckAttack (edict_t *self) +{ + vec3_t spot1, spot2; + float chance; + trace_t tr; + + if (self->enemy->health > 0) + { + // see if any entities are in the way of the shot + VectorCopy (self->s.origin, spot1); + spot1[2] += self->viewheight; + VectorCopy (self->enemy->s.origin, spot2); + spot2[2] += self->enemy->viewheight; + + tr = gi.trace (spot1, NULL, NULL, spot2, self, CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_SLIME|CONTENTS_LAVA|CONTENTS_WINDOW); + + // do we have a clear shot? + if (tr.ent != self->enemy) + return false; + } + + // melee attack + if (enemy_range == RANGE_MELEE) + { + // don't always melee in easy mode + if (skill->value == 0 && (rand()&3) ) + return false; + if (self->monsterinfo.melee) + self->monsterinfo.attack_state = AS_MELEE; + else + self->monsterinfo.attack_state = AS_MISSILE; + return true; + } + +// missile attack + if (!self->monsterinfo.attack) + return false; + + if (level.time < self->monsterinfo.attack_finished) + return false; + + if (enemy_range == RANGE_FAR) + return false; + + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + { + chance = 0.4; + } + else if (enemy_range == RANGE_MELEE) + { + chance = 0.2; + } + else if (enemy_range == RANGE_NEAR) + { + chance = 0.1; + } + else if (enemy_range == RANGE_MID) + { + chance = 0.02; + } + else + { + return false; + } + + if (skill->value == 0) + chance *= 0.5; + else if (skill->value >= 2) + chance *= 2; + + if (random () < chance) + { + self->monsterinfo.attack_state = AS_MISSILE; + self->monsterinfo.attack_finished = level.time + 2*random(); + return true; + } + + if (self->flags & FL_FLY) + { + if (random() < 0.3) + self->monsterinfo.attack_state = AS_SLIDING; + else + self->monsterinfo.attack_state = AS_STRAIGHT; + } + + return false; +} + + +/* +============= +ai_run_melee + +Turn and close until within an angle to launch a melee attack +============= +*/ +void ai_run_melee(edict_t *self) +{ + self->ideal_yaw = enemy_yaw; + M_ChangeYaw (self); + + if (FacingIdeal(self)) + { + self->monsterinfo.melee (self); + self->monsterinfo.attack_state = AS_STRAIGHT; + } +} + + +/* +============= +ai_run_missile + +Turn in place until within an angle to launch a missile attack +============= +*/ +void ai_run_missile(edict_t *self) +{ + self->ideal_yaw = enemy_yaw; + M_ChangeYaw (self); + + if (FacingIdeal(self)) + { + self->monsterinfo.attack (self); + self->monsterinfo.attack_state = AS_STRAIGHT; + } +}; + + +/* +============= +ai_run_slide + +Strafe sideways, but stay at aproximately the same range +============= +*/ +void ai_run_slide(edict_t *self, float distance) +{ + float ofs; + + self->ideal_yaw = enemy_yaw; + M_ChangeYaw (self); + + if (self->monsterinfo.lefty) + ofs = 90; + else + ofs = -90; + + if (M_walkmove (self, self->ideal_yaw + ofs, distance)) + return; + + self->monsterinfo.lefty = 1 - self->monsterinfo.lefty; + M_walkmove (self, self->ideal_yaw - ofs, distance); +} + + +/* +============= +ai_checkattack + +Decides if we're going to attack or do something else +used by ai_run and ai_stand +============= +*/ +qboolean ai_checkattack (edict_t *self, float dist) +{ + vec3_t temp; + qboolean hesDeadJim; + +// this causes monsters to run blindly to the combat point w/o firing + if (self->goalentity) + { + if (self->monsterinfo.aiflags & AI_COMBAT_POINT) + return false; + + if (self->monsterinfo.aiflags & AI_SOUND_TARGET) + { + if ((level.time - self->enemy->teleport_time) > 5.0) + { + if (self->goalentity == self->enemy) + if (self->movetarget) + self->goalentity = self->movetarget; + else + self->goalentity = NULL; + self->monsterinfo.aiflags &= ~AI_SOUND_TARGET; + if (self->monsterinfo.aiflags & AI_TEMP_STAND_GROUND) + self->monsterinfo.aiflags &= ~(AI_STAND_GROUND | AI_TEMP_STAND_GROUND); + } + else + { + self->show_hostile = level.time + 1; + return false; + } + } + } + + enemy_vis = false; + +// see if the enemy is dead + hesDeadJim = false; + if ((!self->enemy) || (!self->enemy->inuse)) + { + hesDeadJim = true; + } + else if (self->monsterinfo.aiflags & AI_MEDIC) + { + if (self->enemy->health > 0) + { + hesDeadJim = true; + self->monsterinfo.aiflags &= ~AI_MEDIC; + } + } + else + { + if (self->monsterinfo.aiflags & AI_BRUTAL) + { + if (self->enemy->health <= -80) + hesDeadJim = true; + } + else + { + if (self->enemy->health <= 0) + hesDeadJim = true; + } + } + + if (hesDeadJim) + { + self->enemy = NULL; + // FIXME: look all around for other targets + if (self->oldenemy && self->oldenemy->health > 0) + { + self->enemy = self->oldenemy; + self->oldenemy = NULL; + HuntTarget (self); + } + else + { + if (self->movetarget) + { + self->goalentity = self->movetarget; + self->monsterinfo.walk (self); + } + else + { + // we need the pausetime otherwise the stand code + // will just revert to walking with no target and + // the monsters will wonder around aimlessly trying + // to hunt the world entity + self->monsterinfo.pausetime = level.time + 100000000; + self->monsterinfo.stand (self); + } + return true; + } + } + + self->show_hostile = level.time + 1; // wake up other monsters + +// check knowledge of enemy + enemy_vis = visible(self, self->enemy); + if (enemy_vis) + { + self->monsterinfo.search_time = level.time + 5; + VectorCopy (self->enemy->s.origin, self->monsterinfo.last_sighting); + } + +// look for other coop players here +// if (coop && self->monsterinfo.search_time < level.time) +// { +// if (FindTarget (self)) +// return true; +// } + + enemy_infront = infront(self, self->enemy); + enemy_range = range(self, self->enemy); + VectorSubtract (self->enemy->s.origin, self->s.origin, temp); + enemy_yaw = vectoyaw(temp); + + + // JDC self->ideal_yaw = enemy_yaw; + + if (self->monsterinfo.attack_state == AS_MISSILE) + { + ai_run_missile (self); + return true; + } + if (self->monsterinfo.attack_state == AS_MELEE) + { + ai_run_melee (self); + return true; + } + + // if enemy is not currently visible, we will never attack + if (!enemy_vis) + return false; + + return self->monsterinfo.checkattack (self); +} + + +/* +============= +ai_run + +The monster has an enemy it is trying to kill +============= +*/ +void ai_run (edict_t *self, float dist) +{ + vec3_t v; + edict_t *tempgoal; + edict_t *save; + qboolean new; + edict_t *marker; + float d1, d2; + trace_t tr; + vec3_t v_forward, v_right; + float left, center, right; + vec3_t left_target, right_target; + + // if we're going to a combat point, just proceed + if (self->monsterinfo.aiflags & AI_COMBAT_POINT) + { + M_MoveToGoal (self, dist); + return; + } + + if (self->monsterinfo.aiflags & AI_SOUND_TARGET) + { + VectorSubtract (self->s.origin, self->enemy->s.origin, v); + if (VectorLength(v) < 64) + { + self->monsterinfo.aiflags |= (AI_STAND_GROUND | AI_TEMP_STAND_GROUND); + self->monsterinfo.stand (self); + return; + } + + M_MoveToGoal (self, dist); + + if (!FindTarget (self)) + return; + } + + if (ai_checkattack (self, dist)) + return; + + if (self->monsterinfo.attack_state == AS_SLIDING) + { + ai_run_slide (self, dist); + return; + } + + if (enemy_vis) + { +// if (self.aiflags & AI_LOST_SIGHT) +// dprint("regained sight\n"); + M_MoveToGoal (self, dist); + self->monsterinfo.aiflags &= ~AI_LOST_SIGHT; + VectorCopy (self->enemy->s.origin, self->monsterinfo.last_sighting); + self->monsterinfo.trail_time = level.time; + return; + } + + // coop will change to another enemy if visible + if (coop->value) + { // FIXME: insane guys get mad with this, which causes crashes! + if (FindTarget (self)) + return; + } + + if ((self->monsterinfo.search_time) && (level.time > (self->monsterinfo.search_time + 20))) + { + M_MoveToGoal (self, dist); + self->monsterinfo.search_time = 0; +// dprint("search timeout\n"); + return; + } + + save = self->goalentity; + tempgoal = G_Spawn(); + self->goalentity = tempgoal; + + new = false; + + if (!(self->monsterinfo.aiflags & AI_LOST_SIGHT)) + { + // just lost sight of the player, decide where to go first +// dprint("lost sight of player, last seen at "); dprint(vtos(self.last_sighting)); dprint("\n"); + self->monsterinfo.aiflags |= (AI_LOST_SIGHT | AI_PURSUIT_LAST_SEEN); + self->monsterinfo.aiflags &= ~(AI_PURSUE_NEXT | AI_PURSUE_TEMP); + new = true; + } + + if (self->monsterinfo.aiflags & AI_PURSUE_NEXT) + { + self->monsterinfo.aiflags &= ~AI_PURSUE_NEXT; +// dprint("reached current goal: "); dprint(vtos(self.origin)); dprint(" "); dprint(vtos(self.last_sighting)); dprint(" "); dprint(ftos(vlen(self.origin - self.last_sighting))); dprint("\n"); + + // give ourself more time since we got this far + self->monsterinfo.search_time = level.time + 5; + + if (self->monsterinfo.aiflags & AI_PURSUE_TEMP) + { +// dprint("was temp goal; retrying original\n"); + self->monsterinfo.aiflags &= ~AI_PURSUE_TEMP; + marker = NULL; + VectorCopy (self->monsterinfo.saved_goal, self->monsterinfo.last_sighting); + new = true; + } + else if (self->monsterinfo.aiflags & AI_PURSUIT_LAST_SEEN) + { + self->monsterinfo.aiflags &= ~AI_PURSUIT_LAST_SEEN; + marker = PlayerTrail_PickFirst (self); + } + else + { + marker = PlayerTrail_PickNext (self); + } + + if (marker) + { + VectorCopy (marker->s.origin, self->monsterinfo.last_sighting); + self->monsterinfo.trail_time = marker->timestamp; + self->s.angles[YAW] = self->ideal_yaw = marker->s.angles[YAW]; +// dprint("heading is "); dprint(ftos(self.ideal_yaw)); dprint("\n"); + +// debug_drawline(self.origin, self.last_sighting, 52); + new = true; + } + } + + VectorSubtract (self->s.origin, self->monsterinfo.last_sighting, v); + d1 = VectorLength(v); + if (d1 <= dist) + { + self->monsterinfo.aiflags |= AI_PURSUE_NEXT; + dist = d1; + } + + VectorCopy (self->monsterinfo.last_sighting, self->goalentity->s.origin); + + if (new) + { +// gi.dprintf("checking for course correction\n"); + + tr = gi.trace(self->s.origin, self->mins, self->maxs, self->monsterinfo.last_sighting, self, MASK_PLAYERSOLID); + if (tr.fraction < 1) + { + VectorSubtract (self->goalentity->s.origin, self->s.origin, v); + d1 = VectorLength(v); + center = tr.fraction; + d2 = d1 * ((center+1)/2); + self->s.angles[YAW] = self->ideal_yaw = vectoyaw(v); + AngleVectors(self->s.angles, v_forward, v_right, NULL); + + VectorSet(v, d2, -16, 0); + G_ProjectSource (self->s.origin, v, v_forward, v_right, left_target); + tr = gi.trace(self->s.origin, self->mins, self->maxs, left_target, self, MASK_PLAYERSOLID); + left = tr.fraction; + + VectorSet(v, d2, 16, 0); + G_ProjectSource (self->s.origin, v, v_forward, v_right, right_target); + tr = gi.trace(self->s.origin, self->mins, self->maxs, right_target, self, MASK_PLAYERSOLID); + right = tr.fraction; + + center = (d1*center)/d2; + if (left >= center && left > right) + { + if (left < 1) + { + VectorSet(v, d2 * left * 0.5, -16, 0); + G_ProjectSource (self->s.origin, v, v_forward, v_right, left_target); +// gi.dprintf("incomplete path, go part way and adjust again\n"); + } + VectorCopy (self->monsterinfo.last_sighting, self->monsterinfo.saved_goal); + self->monsterinfo.aiflags |= AI_PURSUE_TEMP; + VectorCopy (left_target, self->goalentity->s.origin); + VectorCopy (left_target, self->monsterinfo.last_sighting); + VectorSubtract (self->goalentity->s.origin, self->s.origin, v); + self->s.angles[YAW] = self->ideal_yaw = vectoyaw(v); +// gi.dprintf("adjusted left\n"); +// debug_drawline(self.origin, self.last_sighting, 152); + } + else if (right >= center && right > left) + { + if (right < 1) + { + VectorSet(v, d2 * right * 0.5, 16, 0); + G_ProjectSource (self->s.origin, v, v_forward, v_right, right_target); +// gi.dprintf("incomplete path, go part way and adjust again\n"); + } + VectorCopy (self->monsterinfo.last_sighting, self->monsterinfo.saved_goal); + self->monsterinfo.aiflags |= AI_PURSUE_TEMP; + VectorCopy (right_target, self->goalentity->s.origin); + VectorCopy (right_target, self->monsterinfo.last_sighting); + VectorSubtract (self->goalentity->s.origin, self->s.origin, v); + self->s.angles[YAW] = self->ideal_yaw = vectoyaw(v); +// gi.dprintf("adjusted right\n"); +// debug_drawline(self.origin, self.last_sighting, 152); + } + } +// else gi.dprintf("course was fine\n"); + } + + M_MoveToGoal (self, dist); + + G_FreeEdict(tempgoal); + + if (self) + self->goalentity = save; +} diff --git a/original/xatrix/g_chase.c b/original/xatrix/g_chase.c new file mode 100644 index 0000000..accf8e1 --- /dev/null +++ b/original/xatrix/g_chase.c @@ -0,0 +1,158 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +#include "g_local.h" + +void UpdateChaseCam(edict_t *ent) +{ + vec3_t o, ownerv, goal; + edict_t *targ; + vec3_t forward, right; + trace_t trace; + int i; + vec3_t oldgoal; + vec3_t angles; + + // is our chase target gone? + if (!ent->client->chase_target->inuse + || ent->client->chase_target->client->resp.spectator) { + edict_t *old = ent->client->chase_target; + ChaseNext(ent); + if (ent->client->chase_target == old) { + ent->client->chase_target = NULL; + ent->client->ps.pmove.pm_flags &= ~PMF_NO_PREDICTION; + return; + } + } + + targ = ent->client->chase_target; + + VectorCopy(targ->s.origin, ownerv); + VectorCopy(ent->s.origin, oldgoal); + + ownerv[2] += targ->viewheight; + + VectorCopy(targ->client->v_angle, angles); + if (angles[PITCH] > 56) + angles[PITCH] = 56; + AngleVectors (angles, forward, right, NULL); + VectorNormalize(forward); + VectorMA(ownerv, -30, forward, o); + + if (o[2] < targ->s.origin[2] + 20) + o[2] = targ->s.origin[2] + 20; + + // jump animation lifts + if (!targ->groundentity) + o[2] += 16; + + trace = gi.trace(ownerv, vec3_origin, vec3_origin, o, targ, MASK_SOLID); + + VectorCopy(trace.endpos, goal); + + VectorMA(goal, 2, forward, goal); + + // pad for floors and ceilings + VectorCopy(goal, o); + o[2] += 6; + trace = gi.trace(goal, vec3_origin, vec3_origin, o, targ, MASK_SOLID); + if (trace.fraction < 1) { + VectorCopy(trace.endpos, goal); + goal[2] -= 6; + } + + VectorCopy(goal, o); + o[2] -= 6; + trace = gi.trace(goal, vec3_origin, vec3_origin, o, targ, MASK_SOLID); + if (trace.fraction < 1) { + VectorCopy(trace.endpos, goal); + goal[2] += 6; + } + + if (targ->deadflag) + ent->client->ps.pmove.pm_type = PM_DEAD; + else + ent->client->ps.pmove.pm_type = PM_FREEZE; + + VectorCopy(goal, ent->s.origin); + for (i=0 ; i<3 ; i++) + ent->client->ps.pmove.delta_angles[i] = ANGLE2SHORT(targ->client->v_angle[i] - ent->client->resp.cmd_angles[i]); + + if (targ->deadflag) { + ent->client->ps.viewangles[ROLL] = 40; + ent->client->ps.viewangles[PITCH] = -15; + ent->client->ps.viewangles[YAW] = targ->client->killer_yaw; + } else { + VectorCopy(targ->client->v_angle, ent->client->ps.viewangles); + VectorCopy(targ->client->v_angle, ent->client->v_angle); + } + + ent->viewheight = 0; + ent->client->ps.pmove.pm_flags |= PMF_NO_PREDICTION; + gi.linkentity(ent); +} + +void ChaseNext(edict_t *ent) +{ + int i; + edict_t *e; + + if (!ent->client->chase_target) + return; + + i = ent->client->chase_target - g_edicts; + do { + i++; + if (i > maxclients->value) + i = 1; + e = g_edicts + i; + if (!e->inuse) + continue; + if (!e->client->resp.spectator) + break; + } while (e != ent->client->chase_target); + + ent->client->chase_target = e; + ent->client->update_chase = true; +} + +void ChasePrev(edict_t *ent) +{ + int i; + edict_t *e; + + if (!ent->client->chase_target) + return; + + i = ent->client->chase_target - g_edicts; + do { + i--; + if (i < 1) + i = maxclients->value; + e = g_edicts + i; + if (!e->inuse) + continue; + if (!e->client->resp.spectator) + break; + } while (e != ent->client->chase_target); + + ent->client->chase_target = e; + ent->client->update_chase = true; +} + +void GetChaseTarget(edict_t *ent) +{ + int i; + edict_t *other; + + for (i = 1; i <= maxclients->value; i++) { + other = g_edicts + i; + if (other->inuse && !other->client->resp.spectator) { + ent->client->chase_target = other; + ent->client->update_chase = true; + UpdateChaseCam(ent); + return; + } + } + gi.centerprintf(ent, "No other players to chase."); +} + diff --git a/original/xatrix/g_cmds.c b/original/xatrix/g_cmds.c new file mode 100644 index 0000000..5b18131 --- /dev/null +++ b/original/xatrix/g_cmds.c @@ -0,0 +1,1062 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +#include "g_local.h" +#include "m_player.h" + + +char *ClientTeam (edict_t *ent) +{ + char *p; + static char value[512]; + + value[0] = 0; + + if (!ent->client) + return value; + + strcpy(value, Info_ValueForKey (ent->client->pers.userinfo, "skin")); + p = strchr(value, '/'); + if (!p) + return value; + + if ((int)(dmflags->value) & DF_MODELTEAMS) + { + *p = 0; + return value; + } + + // if ((int)(dmflags->value) & DF_SKINTEAMS) + return ++p; +} + +qboolean OnSameTeam (edict_t *ent1, edict_t *ent2) +{ + char ent1Team [512]; + char ent2Team [512]; + + if (!((int)(dmflags->value) & (DF_MODELTEAMS | DF_SKINTEAMS))) + return false; + + strcpy (ent1Team, ClientTeam (ent1)); + strcpy (ent2Team, ClientTeam (ent2)); + + if (strcmp(ent1Team, ent2Team) == 0) + return true; + return false; +} + + +void SelectNextItem (edict_t *ent, int itflags) +{ + gclient_t *cl; + int i, index; + gitem_t *it; + + cl = ent->client; + + if (cl->chase_target) { + ChaseNext(ent); + return; + } + + // scan for the next valid one + for (i=1 ; i<=MAX_ITEMS ; i++) + { + index = (cl->pers.selected_item + i)%MAX_ITEMS; + if (!cl->pers.inventory[index]) + continue; + it = &itemlist[index]; + if (!it->use) + continue; + if (!(it->flags & itflags)) + continue; + + cl->pers.selected_item = index; + return; + } + + cl->pers.selected_item = -1; +} + +void SelectPrevItem (edict_t *ent, int itflags) +{ + gclient_t *cl; + int i, index; + gitem_t *it; + + cl = ent->client; + + if (cl->chase_target) { + ChasePrev(ent); + return; + } + + // scan for the next valid one + for (i=1 ; i<=MAX_ITEMS ; i++) + { + index = (cl->pers.selected_item + MAX_ITEMS - i)%MAX_ITEMS; + if (!cl->pers.inventory[index]) + continue; + it = &itemlist[index]; + if (!it->use) + continue; + if (!(it->flags & itflags)) + continue; + + cl->pers.selected_item = index; + return; + } + + cl->pers.selected_item = -1; +} + +void ValidateSelectedItem (edict_t *ent) +{ + gclient_t *cl; + + cl = ent->client; + + if (cl->pers.inventory[cl->pers.selected_item]) + return; // valid + + SelectNextItem (ent, -1); +} + + +//================================================================================= + +/* +================== +Cmd_Give_f + +Give items to a client +================== +*/ +void Cmd_Give_f (edict_t *ent) +{ + char *name; + gitem_t *it; + int index; + int i; + qboolean give_all; + edict_t *it_ent; + + if (deathmatch->value && !sv_cheats->value) + { + gi.cprintf (ent, PRINT_HIGH, "You must run the server with '+set cheats 1' to enable this command.\n"); + return; + } + + name = gi.args(); + + if (Q_stricmp(name, "all") == 0) + give_all = true; + else + give_all = false; + + if (give_all || Q_stricmp(gi.argv(1), "health") == 0) + { + if (gi.argc() == 3) + ent->health = atoi(gi.argv(2)); + else + ent->health = ent->max_health; + if (!give_all) + return; + } + + if (give_all || Q_stricmp(name, "weapons") == 0) + { + for (i=0 ; ipickup) + continue; + if (!(it->flags & IT_WEAPON)) + continue; + ent->client->pers.inventory[i] += 1; + } + if (!give_all) + return; + } + + if (give_all || Q_stricmp(name, "ammo") == 0) + { + for (i=0 ; ipickup) + continue; + if (!(it->flags & IT_AMMO)) + continue; + Add_Ammo (ent, it, 1000); + } + if (!give_all) + return; + } + + if (give_all || Q_stricmp(name, "armor") == 0) + { + gitem_armor_t *info; + + it = FindItem("Jacket Armor"); + ent->client->pers.inventory[ITEM_INDEX(it)] = 0; + + it = FindItem("Combat Armor"); + ent->client->pers.inventory[ITEM_INDEX(it)] = 0; + + it = FindItem("Body Armor"); + info = (gitem_armor_t *)it->info; + ent->client->pers.inventory[ITEM_INDEX(it)] = info->max_count; + + if (!give_all) + return; + } + + if (give_all || Q_stricmp(name, "Power Shield") == 0) + { + it = FindItem("Power Shield"); + it_ent = G_Spawn(); + it_ent->classname = it->classname; + SpawnItem (it_ent, it); + Touch_Item (it_ent, ent, NULL, NULL); + if (it_ent->inuse) + G_FreeEdict(it_ent); + + if (!give_all) + return; + } + + if (give_all) + { + for (i=0 ; ipickup) + continue; + if (it->flags & (IT_ARMOR|IT_WEAPON|IT_AMMO)) + continue; + ent->client->pers.inventory[i] = 1; + } + return; + } + + it = FindItem (name); + if (!it) + { + name = gi.argv(1); + it = FindItem (name); + if (!it) + { + gi.cprintf (ent, PRINT_HIGH, "unknown item\n"); + return; + } + } + + if (!it->pickup) + { + gi.cprintf (ent, PRINT_HIGH, "non-pickup item\n"); + return; + } + + index = ITEM_INDEX(it); + + if (it->flags & IT_AMMO) + { + if (gi.argc() == 3) + ent->client->pers.inventory[index] = atoi(gi.argv(2)); + else + ent->client->pers.inventory[index] += it->quantity; + } + else + { + it_ent = G_Spawn(); + it_ent->classname = it->classname; + SpawnItem (it_ent, it); + Touch_Item (it_ent, ent, NULL, NULL); + if (it_ent->inuse) + G_FreeEdict(it_ent); + } +} + + +/* +================== +Cmd_God_f + +Sets client to godmode + +argv(0) god +================== +*/ +void Cmd_God_f (edict_t *ent) +{ + char *msg; + + if (deathmatch->value && !sv_cheats->value) + { + gi.cprintf (ent, PRINT_HIGH, "You must run the server with '+set cheats 1' to enable this command.\n"); + return; + } + + ent->flags ^= FL_GODMODE; + if (!(ent->flags & FL_GODMODE) ) + msg = "godmode OFF\n"; + else + msg = "godmode ON\n"; + + gi.cprintf (ent, PRINT_HIGH, msg); +} + + +/* +================== +Cmd_Notarget_f + +Sets client to notarget + +argv(0) notarget +================== +*/ +void Cmd_Notarget_f (edict_t *ent) +{ + char *msg; + + if (deathmatch->value && !sv_cheats->value) + { + gi.cprintf (ent, PRINT_HIGH, "You must run the server with '+set cheats 1' to enable this command.\n"); + return; + } + + ent->flags ^= FL_NOTARGET; + if (!(ent->flags & FL_NOTARGET) ) + msg = "notarget OFF\n"; + else + msg = "notarget ON\n"; + + gi.cprintf (ent, PRINT_HIGH, msg); +} + + +/* +================== +Cmd_Noclip_f + +argv(0) noclip +================== +*/ +void Cmd_Noclip_f (edict_t *ent) +{ + char *msg; + + if (deathmatch->value && !sv_cheats->value) + { + gi.cprintf (ent, PRINT_HIGH, "You must run the server with '+set cheats 1' to enable this command.\n"); + return; + } + + if (ent->movetype == MOVETYPE_NOCLIP) + { + ent->movetype = MOVETYPE_WALK; + msg = "noclip OFF\n"; + } + else + { + ent->movetype = MOVETYPE_NOCLIP; + msg = "noclip ON\n"; + } + + gi.cprintf (ent, PRINT_HIGH, msg); +} + + +/* +================== +Cmd_Use_f + +Use an inventory item +================== +*/ + +void Cmd_Use_f (edict_t *ent) +{ + int index; + gitem_t *it; + char *s; + + s = gi.args(); + it = FindItem (s); + if (!it) + { + gi.cprintf (ent, PRINT_HIGH, "unknown item: %s\n", s); + return; + } + if (!it->use) + { + gi.cprintf (ent, PRINT_HIGH, "Item is not usable.\n"); + return; + } + + index = ITEM_INDEX(it); + if (!ent->client->pers.inventory[index]) + { + // RAFAEL + if (strcmp (it->pickup_name, "HyperBlaster") == 0) + { + it = FindItem ("Ionripper"); + index = ITEM_INDEX (it); + if (!ent->client->pers.inventory[index]) + { + gi.cprintf (ent, PRINT_HIGH, "Out of item: %s\n", s); + return; + } + } + // RAFAEL + else if (strcmp (it->pickup_name, "Railgun") == 0) + { + it = FindItem ("Phalanx"); + index = ITEM_INDEX (it); + if (!ent->client->pers.inventory[index]) + { + gi.cprintf (ent, PRINT_HIGH, "Out of item: %s\n", s); + return; + } + } + else + { + gi.cprintf (ent, PRINT_HIGH, "Out of item: %s\n", s); + return; + } + } + + it->use (ent, it); +} + + +/* +================== +Cmd_Drop_f + +Drop an inventory item +================== +*/ +void Cmd_Drop_f (edict_t *ent) +{ + int index; + gitem_t *it; + char *s; + + s = gi.args(); + it = FindItem (s); + if (!it) + { + gi.cprintf (ent, PRINT_HIGH, "unknown item: %s\n", s); + return; + } + if (!it->drop) + { + gi.cprintf (ent, PRINT_HIGH, "Item is not dropable.\n"); + return; + } + + index = ITEM_INDEX(it); + if (!ent->client->pers.inventory[index]) + { + // RAFAEL + if (strcmp (it->pickup_name, "HyperBlaster") == 0) + { + it = FindItem ("Ionripper"); + index = ITEM_INDEX (it); + if (!ent->client->pers.inventory[index]) + { + gi.cprintf (ent, PRINT_HIGH, "Out of item: %s\n", s); + return; + } + } + // RAFAEL + else if (strcmp (it->pickup_name, "Railgun") == 0) + { + it = FindItem ("Phalanx"); + index = ITEM_INDEX (it); + if (!ent->client->pers.inventory[index]) + { + gi.cprintf (ent, PRINT_HIGH, "Out of item: %s\n", s); + return; + } + } + else + { + gi.cprintf (ent, PRINT_HIGH, "Out of item: %s\n", s); + return; + } + } + + it->drop (ent, it); +} + + +/* +================= +Cmd_Inven_f +================= +*/ +void Cmd_Inven_f (edict_t *ent) +{ + int i; + gclient_t *cl; + + cl = ent->client; + + cl->showscores = false; + cl->showhelp = false; + + if (cl->showinventory) + { + cl->showinventory = false; + return; + } + + cl->showinventory = true; + + gi.WriteByte (svc_inventory); + for (i=0 ; ipers.inventory[i]); + } + gi.unicast (ent, true); +} + +/* +================= +Cmd_InvUse_f +================= +*/ +void Cmd_InvUse_f (edict_t *ent) +{ + gitem_t *it; + + ValidateSelectedItem (ent); + + if (ent->client->pers.selected_item == -1) + { + gi.cprintf (ent, PRINT_HIGH, "No item to use.\n"); + return; + } + + it = &itemlist[ent->client->pers.selected_item]; + if (!it->use) + { + gi.cprintf (ent, PRINT_HIGH, "Item is not usable.\n"); + return; + } + it->use (ent, it); +} + +/* +================= +Cmd_WeapPrev_f +================= +*/ + +void Cmd_WeapPrev_f (edict_t *ent) +{ + gclient_t *cl; + int i, index; + gitem_t *it; + int selected_weapon; + + cl = ent->client; + + if (!cl->pers.weapon) + return; + + selected_weapon = ITEM_INDEX(cl->pers.weapon); + + // scan for the next valid one + for (i=1 ; i<=MAX_ITEMS ; i++) + { + index = (selected_weapon + i)%MAX_ITEMS; + if (!cl->pers.inventory[index]) + continue; + it = &itemlist[index]; + if (!it->use) + continue; + if (! (it->flags & IT_WEAPON) ) + continue; + it->use (ent, it); + if (cl->pers.weapon == it) + return; // successful + } +} + +/* +================= +Cmd_WeapNext_f +================= +*/ +#if 0 +void Cmd_WeapNext_f (edict_t *ent) +{ + gclient_t *cl; + int i, index; + gitem_t *it; + int selected_weapon; + + cl = ent->client; + + if (!cl->pers.weapon) + return; + + selected_weapon = ITEM_INDEX(cl->pers.weapon); + + // scan for the next valid one + for (i=1 ; i<=MAX_ITEMS ; i++) + { + index = (selected_weapon + MAX_ITEMS - i)%MAX_ITEMS; + if (!cl->pers.inventory[index]) + continue; + it = &itemlist[index]; + if (!it->use) + continue; + if (! (it->flags & IT_WEAPON) ) + continue; + it->use (ent, it); + if (cl->pers.weapon == it) + return; // successful + } +} +#endif +void Cmd_WeapNext_f (edict_t *ent) +{ + gclient_t *cl; + int i, index; + gitem_t *it; + int selected_weapon; + + cl = ent->client; + + if (!cl->pers.weapon) + return; + + selected_weapon = ITEM_INDEX(cl->pers.weapon); + + // scan for the next valid one + for (i=1 ; i<=MAX_ITEMS ; i++) + { + index = (selected_weapon + MAX_ITEMS - i)%MAX_ITEMS; + + if (!cl->pers.inventory[index]) + continue; + it = &itemlist[index]; + if (!it->use) + continue; + if (! (it->flags & IT_WEAPON) ) + continue; + it->use (ent, it); + if (cl->pers.weapon == it) + return; // successful + } +} + +/* +================= +Cmd_WeapLast_f +================= +*/ +void Cmd_WeapLast_f (edict_t *ent) +{ + gclient_t *cl; + int index; + gitem_t *it; + + cl = ent->client; + + if (!cl->pers.weapon || !cl->pers.lastweapon) + return; + + index = ITEM_INDEX(cl->pers.lastweapon); + if (!cl->pers.inventory[index]) + return; + it = &itemlist[index]; + if (!it->use) + return; + if (! (it->flags & IT_WEAPON) ) + return; + it->use (ent, it); +} + +/* +================= +Cmd_InvDrop_f +================= +*/ +void Cmd_InvDrop_f (edict_t *ent) +{ + gitem_t *it; + + ValidateSelectedItem (ent); + + if (ent->client->pers.selected_item == -1) + { + gi.cprintf (ent, PRINT_HIGH, "No item to drop.\n"); + return; + } + + it = &itemlist[ent->client->pers.selected_item]; + if (!it->drop) + { + gi.cprintf (ent, PRINT_HIGH, "Item is not dropable.\n"); + return; + } + it->drop (ent, it); +} + +/* +================= +Cmd_Kill_f +================= +*/ +void Cmd_Kill_f (edict_t *ent) +{ + if((level.time - ent->client->respawn_time) < 5) + return; + ent->flags &= ~FL_GODMODE; + ent->health = 0; + meansOfDeath = MOD_SUICIDE; + player_die (ent, ent, ent, 100000, vec3_origin); +} + +/* +================= +Cmd_PutAway_f +================= +*/ +void Cmd_PutAway_f (edict_t *ent) +{ + ent->client->showscores = false; + ent->client->showhelp = false; + ent->client->showinventory = false; +} + + +int PlayerSort (void const *a, void const *b) +{ + int anum, bnum; + + anum = *(int *)a; + bnum = *(int *)b; + + anum = game.clients[anum].ps.stats[STAT_FRAGS]; + bnum = game.clients[bnum].ps.stats[STAT_FRAGS]; + + if (anum < bnum) + return -1; + if (anum > bnum) + return 1; + return 0; +} + +/* +================= +Cmd_Players_f +================= +*/ +void Cmd_Players_f (edict_t *ent) +{ + int i; + int count; + char small[64]; + char large[1280]; + int index[256]; + + count = 0; + for (i = 0 ; i < maxclients->value ; i++) + if (game.clients[i].pers.connected) + { + index[count] = i; + count++; + } + + // sort by frags + qsort (index, count, sizeof(index[0]), PlayerSort); + + // print information + large[0] = 0; + + for (i = 0 ; i < count ; i++) + { + Com_sprintf (small, sizeof(small), "%3i %s\n", + game.clients[index[i]].ps.stats[STAT_FRAGS], + game.clients[index[i]].pers.netname); + if (strlen (small) + strlen(large) > sizeof(large) - 100 ) + { // can't print all of them in one packet + strcat (large, "...\n"); + break; + } + strcat (large, small); + } + + gi.cprintf (ent, PRINT_HIGH, "%s\n%i players\n", large, count); +} + +/* +================= +Cmd_Wave_f +================= +*/ +void Cmd_Wave_f (edict_t *ent) +{ + int i; + + i = atoi (gi.argv(1)); + + // can't wave when ducked + if (ent->client->ps.pmove.pm_flags & PMF_DUCKED) + return; + + if (ent->client->anim_priority > ANIM_WAVE) + return; + + ent->client->anim_priority = ANIM_WAVE; + + switch (i) + { + case 0: + gi.cprintf (ent, PRINT_HIGH, "flipoff\n"); + ent->s.frame = FRAME_flip01-1; + ent->client->anim_end = FRAME_flip12; + break; + case 1: + gi.cprintf (ent, PRINT_HIGH, "salute\n"); + ent->s.frame = FRAME_salute01-1; + ent->client->anim_end = FRAME_salute11; + break; + case 2: + gi.cprintf (ent, PRINT_HIGH, "taunt\n"); + ent->s.frame = FRAME_taunt01-1; + ent->client->anim_end = FRAME_taunt17; + break; + case 3: + gi.cprintf (ent, PRINT_HIGH, "wave\n"); + ent->s.frame = FRAME_wave01-1; + ent->client->anim_end = FRAME_wave11; + break; + case 4: + default: + gi.cprintf (ent, PRINT_HIGH, "point\n"); + ent->s.frame = FRAME_point01-1; + ent->client->anim_end = FRAME_point12; + break; + } +} + +/* +================== +Cmd_Say_f +================== +*/ +void Cmd_Say_f (edict_t *ent, qboolean team, qboolean arg0) +{ + int i, j; + edict_t *other; + char *p; + char text[2048]; + gclient_t *cl; + + if (gi.argc () < 2 && !arg0) + return; + + if (!((int)(dmflags->value) & (DF_MODELTEAMS | DF_SKINTEAMS))) + team = false; + + if (team) + Com_sprintf (text, sizeof(text), "(%s): ", ent->client->pers.netname); + else + Com_sprintf (text, sizeof(text), "%s: ", ent->client->pers.netname); + + if (arg0) + { + strcat (text, gi.argv(0)); + strcat (text, " "); + strcat (text, gi.args()); + } + else + { + p = gi.args(); + + if (*p == '"') + { + p++; + p[strlen(p)-1] = 0; + } + strcat(text, p); + } + + // don't let text be too long for malicious reasons + if (strlen(text) > 150) + text[150] = 0; + + strcat(text, "\n"); + + if (flood_msgs->value) { + cl = ent->client; + + if (level.time < cl->flood_locktill) { + gi.cprintf(ent, PRINT_HIGH, "You can't talk for %d more seconds\n", + (int)(cl->flood_locktill - level.time)); + return; + } + i = cl->flood_whenhead - flood_msgs->value + 1; + if (i < 0) + i = (sizeof(cl->flood_when)/sizeof(cl->flood_when[0])) + i; + if (cl->flood_when[i] && + level.time - cl->flood_when[i] < flood_persecond->value) { + cl->flood_locktill = level.time + flood_waitdelay->value; + gi.cprintf(ent, PRINT_CHAT, "Flood protection: You can't talk for %d seconds.\n", + (int)flood_waitdelay->value); + return; + } + cl->flood_whenhead = (cl->flood_whenhead + 1) % + (sizeof(cl->flood_when)/sizeof(cl->flood_when[0])); + cl->flood_when[cl->flood_whenhead] = level.time; + } + + if (dedicated->value) + gi.cprintf(NULL, PRINT_CHAT, "%s", text); + + for (j = 1; j <= game.maxclients; j++) + { + other = &g_edicts[j]; + if (!other->inuse) + continue; + if (!other->client) + continue; + if (team) + { + if (!OnSameTeam(ent, other)) + continue; + } + gi.cprintf(other, PRINT_CHAT, "%s", text); + } +} + +void Cmd_PlayerList_f(edict_t *ent) +{ + int i; + char st[80]; + char text[1400]; + edict_t *e2; + + // connect time, ping, score, name + *text = 0; + for (i = 0, e2 = g_edicts + 1; i < maxclients->value; i++, e2++) { + if (!e2->inuse) + continue; + + Com_sprintf(st, sizeof(st), "%02d:%02d %4d %3d %s%s\n", + (level.framenum - e2->client->resp.enterframe) / 600, + ((level.framenum - e2->client->resp.enterframe) % 600)/10, + e2->client->ping, + e2->client->resp.score, + e2->client->pers.netname, + e2->client->pers.spectator ? " (spectator)" : ""); + if (strlen(text) + strlen(st) > sizeof(text) - 50) { + sprintf(text+strlen(text), "And more...\n"); + gi.cprintf(ent, PRINT_HIGH, "%s", text); + return; + } + strcat(text, st); + } + gi.cprintf(ent, PRINT_HIGH, "%s", text); +} + + +/* +================= +ClientCommand +================= +*/ +void ClientCommand (edict_t *ent) +{ + char *cmd; + + if (!ent->client) + return; // not fully in game yet + + cmd = gi.argv(0); + + if (Q_stricmp (cmd, "players") == 0) + { + Cmd_Players_f (ent); + return; + } + if (Q_stricmp (cmd, "say") == 0) + { + Cmd_Say_f (ent, false, false); + return; + } + if (Q_stricmp (cmd, "say_team") == 0) + { + Cmd_Say_f (ent, true, false); + return; + } + if (Q_stricmp (cmd, "score") == 0) + { + Cmd_Score_f (ent); + return; + } + if (Q_stricmp (cmd, "help") == 0) + { + Cmd_Help_f (ent); + return; + } + + if (level.intermissiontime) + return; + + if (Q_stricmp (cmd, "use") == 0) + Cmd_Use_f (ent); + else if (Q_stricmp (cmd, "drop") == 0) + Cmd_Drop_f (ent); + else if (Q_stricmp (cmd, "give") == 0) + Cmd_Give_f (ent); + else if (Q_stricmp (cmd, "god") == 0) + Cmd_God_f (ent); + else if (Q_stricmp (cmd, "notarget") == 0) + Cmd_Notarget_f (ent); + else if (Q_stricmp (cmd, "noclip") == 0) + Cmd_Noclip_f (ent); + else if (Q_stricmp (cmd, "inven") == 0) + Cmd_Inven_f (ent); + else if (Q_stricmp (cmd, "invnext") == 0) + SelectNextItem (ent, -1); + else if (Q_stricmp (cmd, "invprev") == 0) + SelectPrevItem (ent, -1); + else if (Q_stricmp (cmd, "invnextw") == 0) + SelectNextItem (ent, IT_WEAPON); + else if (Q_stricmp (cmd, "invprevw") == 0) + SelectPrevItem (ent, IT_WEAPON); + else if (Q_stricmp (cmd, "invnextp") == 0) + SelectNextItem (ent, IT_POWERUP); + else if (Q_stricmp (cmd, "invprevp") == 0) + SelectPrevItem (ent, IT_POWERUP); + else if (Q_stricmp (cmd, "invuse") == 0) + Cmd_InvUse_f (ent); + else if (Q_stricmp (cmd, "invdrop") == 0) + Cmd_InvDrop_f (ent); + else if (Q_stricmp (cmd, "weapprev") == 0) + Cmd_WeapPrev_f (ent); + else if (Q_stricmp (cmd, "weapnext") == 0) + Cmd_WeapNext_f (ent); + else if (Q_stricmp (cmd, "weaplast") == 0) + Cmd_WeapLast_f (ent); + else if (Q_stricmp (cmd, "kill") == 0) + Cmd_Kill_f (ent); + else if (Q_stricmp (cmd, "putaway") == 0) + Cmd_PutAway_f (ent); + else if (Q_stricmp (cmd, "wave") == 0) + Cmd_Wave_f (ent); + else if (Q_stricmp(cmd, "playerlist") == 0) + Cmd_PlayerList_f(ent); + else // anything that doesn't match a command will be a chat + Cmd_Say_f (ent, false, true); +} diff --git a/original/xatrix/g_combat.c b/original/xatrix/g_combat.c new file mode 100644 index 0000000..f6cb6ca --- /dev/null +++ b/original/xatrix/g_combat.c @@ -0,0 +1,562 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// g_combat.c + +#include "g_local.h" + +/* +============ +CanDamage + +Returns true if the inflictor can directly damage the target. Used for +explosions and melee attacks. +============ +*/ +qboolean CanDamage (edict_t *targ, edict_t *inflictor) +{ + vec3_t dest; + trace_t trace; + +// bmodels need special checking because their origin is 0,0,0 + if (targ->movetype == MOVETYPE_PUSH) + { + VectorAdd (targ->absmin, targ->absmax, dest); + VectorScale (dest, 0.5, dest); + trace = gi.trace (inflictor->s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID); + if (trace.fraction == 1.0) + return true; + if (trace.ent == targ) + return true; + return false; + } + + trace = gi.trace (inflictor->s.origin, vec3_origin, vec3_origin, targ->s.origin, inflictor, MASK_SOLID); + if (trace.fraction == 1.0) + return true; + + VectorCopy (targ->s.origin, dest); + dest[0] += 15.0; + dest[1] += 15.0; + trace = gi.trace (inflictor->s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID); + if (trace.fraction == 1.0) + return true; + + VectorCopy (targ->s.origin, dest); + dest[0] += 15.0; + dest[1] -= 15.0; + trace = gi.trace (inflictor->s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID); + if (trace.fraction == 1.0) + return true; + + VectorCopy (targ->s.origin, dest); + dest[0] -= 15.0; + dest[1] += 15.0; + trace = gi.trace (inflictor->s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID); + if (trace.fraction == 1.0) + return true; + + VectorCopy (targ->s.origin, dest); + dest[0] -= 15.0; + dest[1] -= 15.0; + trace = gi.trace (inflictor->s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID); + if (trace.fraction == 1.0) + return true; + + + return false; +} + + +/* +============ +Killed +============ +*/ +void Killed (edict_t *targ, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + if (targ->health < -999) + targ->health = -999; + + targ->enemy = attacker; + + if ((targ->svflags & SVF_MONSTER) && (targ->deadflag != DEAD_DEAD)) + { +// targ->svflags |= SVF_DEADMONSTER; // now treat as a different content type + if (!(targ->monsterinfo.aiflags & AI_GOOD_GUY)) + { + level.killed_monsters++; + if (coop->value && attacker->client) + attacker->client->resp.score++; + // medics won't heal monsters that they kill themselves + if (strcmp(attacker->classname, "monster_medic") == 0) + targ->owner = attacker; + } + } + + if (targ->movetype == MOVETYPE_PUSH || targ->movetype == MOVETYPE_STOP || targ->movetype == MOVETYPE_NONE) + { // doors, triggers, etc + targ->die (targ, inflictor, attacker, damage, point); + return; + } + + if ((targ->svflags & SVF_MONSTER) && (targ->deadflag != DEAD_DEAD)) + { + targ->touch = NULL; + monster_death_use (targ); + } + + targ->die (targ, inflictor, attacker, damage, point); +} + + +/* +================ +SpawnDamage +================ +*/ +void SpawnDamage (int type, vec3_t origin, vec3_t normal, int damage) +{ + if (damage > 255) + damage = 255; + gi.WriteByte (svc_temp_entity); + gi.WriteByte (type); +// gi.WriteByte (damage); + gi.WritePosition (origin); + gi.WriteDir (normal); + gi.multicast (origin, MULTICAST_PVS); +} + + +/* +============ +T_Damage + +targ entity that is being damaged +inflictor entity that is causing the damage +attacker entity that caused the inflictor to damage targ + example: targ=monster, inflictor=rocket, attacker=player + +dir direction of the attack +point point at which the damage is being inflicted +normal normal vector from that point +damage amount of damage being inflicted +knockback force to be applied against targ as a result of the damage + +dflags these flags are used to control how T_Damage works + DAMAGE_RADIUS damage was indirect (from a nearby explosion) + DAMAGE_NO_ARMOR armor does not protect from this damage + DAMAGE_ENERGY damage is from an energy based weapon + DAMAGE_NO_KNOCKBACK do not affect velocity, just view angles + DAMAGE_BULLET damage is from a bullet (used for ricochets) + DAMAGE_NO_PROTECTION kills godmode, armor, everything +============ +*/ +static int CheckPowerArmor (edict_t *ent, vec3_t point, vec3_t normal, int damage, int dflags) +{ + gclient_t *client; + int save; + int power_armor_type; + int index; + int damagePerCell; + int pa_te_type; + int power; + int power_used; + + if (!damage) + return 0; + + client = ent->client; + + if (dflags & DAMAGE_NO_ARMOR) + return 0; + + if (client) + { + power_armor_type = PowerArmorType (ent); + if (power_armor_type != POWER_ARMOR_NONE) + { + index = ITEM_INDEX(FindItem("Cells")); + power = client->pers.inventory[index]; + } + } + else if (ent->svflags & SVF_MONSTER) + { + power_armor_type = ent->monsterinfo.power_armor_type; + power = ent->monsterinfo.power_armor_power; + } + else + return 0; + + if (power_armor_type == POWER_ARMOR_NONE) + return 0; + if (!power) + return 0; + + if (power_armor_type == POWER_ARMOR_SCREEN) + { + vec3_t vec; + float dot; + vec3_t forward; + + // only works if damage point is in front + AngleVectors (ent->s.angles, forward, NULL, NULL); + VectorSubtract (point, ent->s.origin, vec); + VectorNormalize (vec); + dot = DotProduct (vec, forward); + if (dot <= 0.3) + return 0; + + damagePerCell = 1; + pa_te_type = TE_SCREEN_SPARKS; + damage = damage / 3; + } + else + { + damagePerCell = 2; + pa_te_type = TE_SHIELD_SPARKS; + damage = (2 * damage) / 3; + } + + save = power * damagePerCell; + if (!save) + return 0; + if (save > damage) + save = damage; + + SpawnDamage (pa_te_type, point, normal, save); + ent->powerarmor_time = level.time + 0.2; + + power_used = save / damagePerCell; + + if (client) + client->pers.inventory[index] -= power_used; + else + ent->monsterinfo.power_armor_power -= power_used; + return save; +} + +static int CheckArmor (edict_t *ent, vec3_t point, vec3_t normal, int damage, int te_sparks, int dflags) +{ + gclient_t *client; + int save; + int index; + gitem_t *armor; + + if (!damage) + return 0; + + client = ent->client; + + if (!client) + return 0; + + if (dflags & DAMAGE_NO_ARMOR) + return 0; + + index = ArmorIndex (ent); + if (!index) + return 0; + + armor = GetItemByIndex (index); + + if (dflags & DAMAGE_ENERGY) + save = ceil(((gitem_armor_t *)armor->info)->energy_protection*damage); + else + save = ceil(((gitem_armor_t *)armor->info)->normal_protection*damage); + if (save >= client->pers.inventory[index]) + save = client->pers.inventory[index]; + + if (!save) + return 0; + + client->pers.inventory[index] -= save; + SpawnDamage (te_sparks, point, normal, save); + + return save; +} + +void M_ReactToDamage (edict_t *targ, edict_t *attacker) +{ + if (!(attacker->client) && !(attacker->svflags & SVF_MONSTER)) + return; + + if (attacker == targ || attacker == targ->enemy) + return; + + // if we are a good guy monster and our attacker is a player + // or another good guy, do not get mad at them + if (targ->monsterinfo.aiflags & AI_GOOD_GUY) + { + if (attacker->client || (attacker->monsterinfo.aiflags & AI_GOOD_GUY)) + return; + } + + // we now know that we are not both good guys + + // if attacker is a client, get mad at them because he's good and we're not + if (attacker->client) + { + targ->monsterinfo.aiflags &= ~AI_SOUND_TARGET; + + // this can only happen in coop (both new and old enemies are clients) + // only switch if can't see the current enemy + if (targ->enemy && targ->enemy->client) + { + if (visible(targ, targ->enemy)) + { + targ->oldenemy = attacker; + return; + } + targ->oldenemy = targ->enemy; + } + targ->enemy = attacker; + if (!(targ->monsterinfo.aiflags & AI_DUCKED)) + FoundTarget (targ); + return; + } + + // it's the same base (walk/swim/fly) type and a different classname and it's not a tank + // (they spray too much), get mad at them + if (((targ->flags & (FL_FLY|FL_SWIM)) == (attacker->flags & (FL_FLY|FL_SWIM))) && + (strcmp (targ->classname, attacker->classname) != 0) && + (strcmp(attacker->classname, "monster_tank") != 0) && + (strcmp(attacker->classname, "monster_supertank") != 0) && + (strcmp(attacker->classname, "monster_makron") != 0) && + (strcmp(attacker->classname, "monster_jorg") != 0) ) + { + if (targ->enemy && targ->enemy->client) + targ->oldenemy = targ->enemy; + targ->enemy = attacker; + if (!(targ->monsterinfo.aiflags & AI_DUCKED)) + FoundTarget (targ); + } + // if they *meant* to shoot us, then shoot back + else if (attacker->enemy == targ) + { + if (targ->enemy && targ->enemy->client) + targ->oldenemy = targ->enemy; + targ->enemy = attacker; + if (!(targ->monsterinfo.aiflags & AI_DUCKED)) + FoundTarget (targ); + } + // otherwise get mad at whoever they are mad at (help our buddy) unless it is us! + else if (attacker->enemy && attacker->enemy != targ) + { + if (targ->enemy && targ->enemy->client) + targ->oldenemy = targ->enemy; + targ->enemy = attacker->enemy; + if (!(targ->monsterinfo.aiflags & AI_DUCKED)) + FoundTarget (targ); + } +} + +qboolean CheckTeamDamage (edict_t *targ, edict_t *attacker) +{ + //FIXME make the next line real and uncomment this block + // if ((ability to damage a teammate == OFF) && (targ's team == attacker's team)) + return false; +} + +void T_Damage (edict_t *targ, edict_t *inflictor, edict_t *attacker, vec3_t dir, vec3_t point, vec3_t normal, int damage, int knockback, int dflags, int mod) +{ + gclient_t *client; + int take; + int save; + int asave; + int psave; + int te_sparks; + + if (!targ->takedamage) + return; + + // friendly fire avoidance + // if enabled you can't hurt teammates (but you can hurt yourself) + // knockback still occurs + if ((targ != attacker) && ((deathmatch->value && ((int)(dmflags->value) & (DF_MODELTEAMS | DF_SKINTEAMS))) || coop->value)) + { + if (OnSameTeam (targ, attacker)) + { + if ((int)(dmflags->value) & DF_NO_FRIENDLY_FIRE) + damage = 0; + else + mod |= MOD_FRIENDLY_FIRE; + } + } + meansOfDeath = mod; + + // easy mode takes half damage + if (skill->value == 0 && deathmatch->value == 0 && targ->client) + { + damage *= 0.5; + if (!damage) + damage = 1; + } + + client = targ->client; + + if (dflags & DAMAGE_BULLET) + te_sparks = TE_BULLET_SPARKS; + else + te_sparks = TE_SPARKS; + + VectorNormalize(dir); + +// bonus damage for suprising a monster + if (!(dflags & DAMAGE_RADIUS) && (targ->svflags & SVF_MONSTER) && (attacker->client) && (!targ->enemy) && (targ->health > 0)) + damage *= 2; + + if (targ->flags & FL_NO_KNOCKBACK) + knockback = 0; + +// figure momentum add + if (!(dflags & DAMAGE_NO_KNOCKBACK)) + { + if ((knockback) && (targ->movetype != MOVETYPE_NONE) && (targ->movetype != MOVETYPE_BOUNCE) && (targ->movetype != MOVETYPE_PUSH) && (targ->movetype != MOVETYPE_STOP)) + { + vec3_t kvel; + float mass; + + if (targ->mass < 50) + mass = 50; + else + mass = targ->mass; + + if (targ->client && attacker == targ) + VectorScale (dir, 1600.0 * (float)knockback / mass, kvel); // the rocket jump hack... + else + VectorScale (dir, 500.0 * (float)knockback / mass, kvel); + + VectorAdd (targ->velocity, kvel, targ->velocity); + } + } + + take = damage; + save = 0; + + // check for godmode + if ( (targ->flags & FL_GODMODE) && !(dflags & DAMAGE_NO_PROTECTION) ) + { + take = 0; + save = damage; + SpawnDamage (te_sparks, point, normal, save); + } + + // check for invincibility + if ((client && client->invincible_framenum > level.framenum ) && !(dflags & DAMAGE_NO_PROTECTION) && mod != MOD_TRAP) + { + if (targ->pain_debounce_time < level.time) + { + gi.sound(targ, CHAN_ITEM, gi.soundindex("items/protect4.wav"), 1, ATTN_NORM, 0); + targ->pain_debounce_time = level.time + 2; + } + take = 0; + save = damage; + } + + psave = CheckPowerArmor (targ, point, normal, take, dflags); + take -= psave; + + asave = CheckArmor (targ, point, normal, take, te_sparks, dflags); + take -= asave; + + //treat cheat/powerup savings the same as armor + asave += save; + + // team damage avoidance + if (!(dflags & DAMAGE_NO_PROTECTION) && CheckTeamDamage (targ, attacker)) + return; + +// do the damage + if (take) + { + if ((targ->svflags & SVF_MONSTER) || (client)) + if (strcmp (targ->classname, "monster_gekk") == 0) + SpawnDamage (TE_GREENBLOOD, point, normal, take); + else + SpawnDamage (TE_BLOOD, point, normal, take); + else + SpawnDamage (te_sparks, point, normal, take); + + + targ->health = targ->health - take; + + if (targ->health <= 0) + { + if ((targ->svflags & SVF_MONSTER) || (client)) + targ->flags |= FL_NO_KNOCKBACK; + Killed (targ, inflictor, attacker, take, point); + return; + } + } + + if (targ->svflags & SVF_MONSTER) + { + M_ReactToDamage (targ, attacker); + if (!(targ->monsterinfo.aiflags & AI_DUCKED) && (take)) + { + targ->pain (targ, attacker, knockback, take); + // nightmare mode monsters don't go into pain frames often + if (skill->value == 3) + targ->pain_debounce_time = level.time + 5; + } + } + else if (client) + { + if (!(targ->flags & FL_GODMODE) && (take)) + targ->pain (targ, attacker, knockback, take); + } + else if (take) + { + if (targ->pain) + targ->pain (targ, attacker, knockback, take); + } + + // add to the damage inflicted on a player this frame + // the total will be turned into screen blends and view angle kicks + // at the end of the frame + if (client) + { + client->damage_parmor += psave; + client->damage_armor += asave; + client->damage_blood += take; + client->damage_knockback += knockback; + VectorCopy (point, client->damage_from); + } +} + + +/* +============ +T_RadiusDamage +============ +*/ +void T_RadiusDamage (edict_t *inflictor, edict_t *attacker, float damage, edict_t *ignore, float radius, int mod) +{ + float points; + edict_t *ent = NULL; + vec3_t v; + vec3_t dir; + + while ((ent = findradius(ent, inflictor->s.origin, radius)) != NULL) + { + if (ent == ignore) + continue; + if (!ent->takedamage) + continue; + + VectorAdd (ent->mins, ent->maxs, v); + VectorMA (ent->s.origin, 0.5, v, v); + VectorSubtract (inflictor->s.origin, v, v); + points = damage - 0.5 * VectorLength (v); + if (ent == attacker) + points = points * 0.5; + if (points > 0) + { + if (CanDamage (ent, inflictor)) + { + VectorSubtract (ent->s.origin, inflictor->s.origin, dir); + T_Damage (ent, inflictor, attacker, dir, inflictor->s.origin, vec3_origin, (int)points, (int)points, DAMAGE_RADIUS, mod); + } + } + } +} diff --git a/original/xatrix/g_func.c b/original/xatrix/g_func.c new file mode 100644 index 0000000..b157b58 --- /dev/null +++ b/original/xatrix/g_func.c @@ -0,0 +1,2218 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +#include "g_local.h" + +/* +========================================================= + + PLATS + + movement options: + + linear + smooth start, hard stop + smooth start, smooth stop + + start + end + acceleration + speed + deceleration + begin sound + end sound + target fired when reaching end + wait at end + + object characteristics that use move segments + --------------------------------------------- + movetype_push, or movetype_stop + action when touched + action when blocked + action when used + disabled? + auto trigger spawning + + +========================================================= +*/ + +#define PLAT_LOW_TRIGGER 1 + +#define STATE_TOP 0 +#define STATE_BOTTOM 1 +#define STATE_UP 2 +#define STATE_DOWN 3 + +#define DOOR_START_OPEN 1 +#define DOOR_REVERSE 2 +#define DOOR_CRUSHER 4 +#define DOOR_NOMONSTER 8 +#define DOOR_TOGGLE 32 +#define DOOR_X_AXIS 64 +#define DOOR_Y_AXIS 128 + + +// +// Support routines for movement (changes in origin using velocity) +// + +void Move_Done (edict_t *ent) +{ + VectorClear (ent->velocity); + ent->moveinfo.endfunc (ent); +} + +void Move_Final (edict_t *ent) +{ + if (ent->moveinfo.remaining_distance == 0) + { + Move_Done (ent); + return; + } + + VectorScale (ent->moveinfo.dir, ent->moveinfo.remaining_distance / FRAMETIME, ent->velocity); + + ent->think = Move_Done; + ent->nextthink = level.time + FRAMETIME; +} + +void Move_Begin (edict_t *ent) +{ + float frames; + + if ((ent->moveinfo.speed * FRAMETIME) >= ent->moveinfo.remaining_distance) + { + Move_Final (ent); + return; + } + VectorScale (ent->moveinfo.dir, ent->moveinfo.speed, ent->velocity); + frames = floor((ent->moveinfo.remaining_distance / ent->moveinfo.speed) / FRAMETIME); + ent->moveinfo.remaining_distance -= frames * ent->moveinfo.speed * FRAMETIME; + ent->nextthink = level.time + (frames * FRAMETIME); + ent->think = Move_Final; +} + +void Think_AccelMove (edict_t *ent); + +void Move_Calc (edict_t *ent, vec3_t dest, void(*func)(edict_t*)) +{ + VectorClear (ent->velocity); + VectorSubtract (dest, ent->s.origin, ent->moveinfo.dir); + ent->moveinfo.remaining_distance = VectorNormalize (ent->moveinfo.dir); + ent->moveinfo.endfunc = func; + + if (ent->moveinfo.speed == ent->moveinfo.accel && ent->moveinfo.speed == ent->moveinfo.decel) + { + if (level.current_entity == ((ent->flags & FL_TEAMSLAVE) ? ent->teammaster : ent)) + { + Move_Begin (ent); + } + else + { + ent->nextthink = level.time + FRAMETIME; + ent->think = Move_Begin; + } + } + else + { + // accelerative + ent->moveinfo.current_speed = 0; + ent->think = Think_AccelMove; + ent->nextthink = level.time + FRAMETIME; + } +} + + +// +// Support routines for angular movement (changes in angle using avelocity) +// + +void AngleMove_Done (edict_t *ent) +{ + VectorClear (ent->avelocity); + ent->moveinfo.endfunc (ent); +} + +void AngleMove_Final (edict_t *ent) +{ + vec3_t move; + + if (ent->moveinfo.state == STATE_UP) + VectorSubtract (ent->moveinfo.end_angles, ent->s.angles, move); + else + VectorSubtract (ent->moveinfo.start_angles, ent->s.angles, move); + + if (VectorCompare (move, vec3_origin)) + { + AngleMove_Done (ent); + return; + } + + VectorScale (move, 1.0/FRAMETIME, ent->avelocity); + + ent->think = AngleMove_Done; + ent->nextthink = level.time + FRAMETIME; +} + +void AngleMove_Begin (edict_t *ent) +{ + vec3_t destdelta; + float len; + float traveltime; + float frames; + + // set destdelta to the vector needed to move + if (ent->moveinfo.state == STATE_UP) + VectorSubtract (ent->moveinfo.end_angles, ent->s.angles, destdelta); + else + VectorSubtract (ent->moveinfo.start_angles, ent->s.angles, destdelta); + + // calculate length of vector + len = VectorLength (destdelta); + + // divide by speed to get time to reach dest + traveltime = len / ent->moveinfo.speed; + + if (traveltime < FRAMETIME) + { + AngleMove_Final (ent); + return; + } + + frames = floor(traveltime / FRAMETIME); + + // scale the destdelta vector by the time spent traveling to get velocity + VectorScale (destdelta, 1.0 / traveltime, ent->avelocity); + + // set nextthink to trigger a think when dest is reached + ent->nextthink = level.time + frames * FRAMETIME; + ent->think = AngleMove_Final; +} + +void AngleMove_Calc (edict_t *ent, void(*func)(edict_t*)) +{ + VectorClear (ent->avelocity); + ent->moveinfo.endfunc = func; + if (level.current_entity == ((ent->flags & FL_TEAMSLAVE) ? ent->teammaster : ent)) + { + AngleMove_Begin (ent); + } + else + { + ent->nextthink = level.time + FRAMETIME; + ent->think = AngleMove_Begin; + } +} + + +/* +============== +Think_AccelMove + +The team has completed a frame of movement, so +change the speed for the next frame +============== +*/ +#define AccelerationDistance(target, rate) (target * ((target / rate) + 1) / 2) + +void plat_CalcAcceleratedMove(moveinfo_t *moveinfo) +{ + float accel_dist; + float decel_dist; + + moveinfo->move_speed = moveinfo->speed; + + if (moveinfo->remaining_distance < moveinfo->accel) + { + moveinfo->current_speed = moveinfo->remaining_distance; + return; + } + + accel_dist = AccelerationDistance (moveinfo->speed, moveinfo->accel); + decel_dist = AccelerationDistance (moveinfo->speed, moveinfo->decel); + + if ((moveinfo->remaining_distance - accel_dist - decel_dist) < 0) + { + float f; + + f = (moveinfo->accel + moveinfo->decel) / (moveinfo->accel * moveinfo->decel); + moveinfo->move_speed = (-2 + sqrt(4 - 4 * f * (-2 * moveinfo->remaining_distance))) / (2 * f); + decel_dist = AccelerationDistance (moveinfo->move_speed, moveinfo->decel); + } + + moveinfo->decel_distance = decel_dist; +}; + +void plat_Accelerate (moveinfo_t *moveinfo) +{ + // are we decelerating? + if (moveinfo->remaining_distance <= moveinfo->decel_distance) + { + if (moveinfo->remaining_distance < moveinfo->decel_distance) + { + if (moveinfo->next_speed) + { + moveinfo->current_speed = moveinfo->next_speed; + moveinfo->next_speed = 0; + return; + } + if (moveinfo->current_speed > moveinfo->decel) + moveinfo->current_speed -= moveinfo->decel; + } + return; + } + + // are we at full speed and need to start decelerating during this move? + if (moveinfo->current_speed == moveinfo->move_speed) + if ((moveinfo->remaining_distance - moveinfo->current_speed) < moveinfo->decel_distance) + { + float p1_distance; + float p2_distance; + float distance; + + p1_distance = moveinfo->remaining_distance - moveinfo->decel_distance; + p2_distance = moveinfo->move_speed * (1.0 - (p1_distance / moveinfo->move_speed)); + distance = p1_distance + p2_distance; + moveinfo->current_speed = moveinfo->move_speed; + moveinfo->next_speed = moveinfo->move_speed - moveinfo->decel * (p2_distance / distance); + return; + } + + // are we accelerating? + if (moveinfo->current_speed < moveinfo->speed) + { + float old_speed; + float p1_distance; + float p1_speed; + float p2_distance; + float distance; + + old_speed = moveinfo->current_speed; + + // figure simple acceleration up to move_speed + moveinfo->current_speed += moveinfo->accel; + if (moveinfo->current_speed > moveinfo->speed) + moveinfo->current_speed = moveinfo->speed; + + // are we accelerating throughout this entire move? + if ((moveinfo->remaining_distance - moveinfo->current_speed) >= moveinfo->decel_distance) + return; + + // during this move we will accelrate from current_speed to move_speed + // and cross over the decel_distance; figure the average speed for the + // entire move + p1_distance = moveinfo->remaining_distance - moveinfo->decel_distance; + p1_speed = (old_speed + moveinfo->move_speed) / 2.0; + p2_distance = moveinfo->move_speed * (1.0 - (p1_distance / p1_speed)); + distance = p1_distance + p2_distance; + moveinfo->current_speed = (p1_speed * (p1_distance / distance)) + (moveinfo->move_speed * (p2_distance / distance)); + moveinfo->next_speed = moveinfo->move_speed - moveinfo->decel * (p2_distance / distance); + return; + } + + // we are at constant velocity (move_speed) + return; +}; + +void Think_AccelMove (edict_t *ent) +{ + ent->moveinfo.remaining_distance -= ent->moveinfo.current_speed; + + if (ent->moveinfo.current_speed == 0) // starting or blocked + plat_CalcAcceleratedMove(&ent->moveinfo); + + plat_Accelerate (&ent->moveinfo); + + // will the entire move complete on next frame? + if (ent->moveinfo.remaining_distance <= ent->moveinfo.current_speed) + { + Move_Final (ent); + return; + } + + VectorScale (ent->moveinfo.dir, ent->moveinfo.current_speed*10, ent->velocity); + ent->nextthink = level.time + FRAMETIME; + ent->think = Think_AccelMove; +} + + +void plat_go_down (edict_t *ent); + +void plat_hit_top (edict_t *ent) +{ + if (!(ent->flags & FL_TEAMSLAVE)) + { + if (ent->moveinfo.sound_end) + gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, ent->moveinfo.sound_end, 1, ATTN_STATIC, 0); + ent->s.sound = 0; + } + ent->moveinfo.state = STATE_TOP; + + ent->think = plat_go_down; + ent->nextthink = level.time + 3; +} + +void plat_hit_bottom (edict_t *ent) +{ + if (!(ent->flags & FL_TEAMSLAVE)) + { + if (ent->moveinfo.sound_end) + gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, ent->moveinfo.sound_end, 1, ATTN_STATIC, 0); + ent->s.sound = 0; + } + ent->moveinfo.state = STATE_BOTTOM; +} + +void plat_go_down (edict_t *ent) +{ + if (!(ent->flags & FL_TEAMSLAVE)) + { + if (ent->moveinfo.sound_start) + gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, ent->moveinfo.sound_start, 1, ATTN_STATIC, 0); + ent->s.sound = ent->moveinfo.sound_middle; + } + ent->moveinfo.state = STATE_DOWN; + Move_Calc (ent, ent->moveinfo.end_origin, plat_hit_bottom); +} + +void plat_go_up (edict_t *ent) +{ + if (!(ent->flags & FL_TEAMSLAVE)) + { + if (ent->moveinfo.sound_start) + gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, ent->moveinfo.sound_start, 1, ATTN_STATIC, 0); + ent->s.sound = ent->moveinfo.sound_middle; + } + ent->moveinfo.state = STATE_UP; + Move_Calc (ent, ent->moveinfo.start_origin, plat_hit_top); +} + +void plat_blocked (edict_t *self, edict_t *other) +{ + if (!(other->svflags & SVF_MONSTER) && (!other->client) ) + { + // give it a chance to go away on it's own terms (like gibs) + T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, 100000, 1, 0, MOD_CRUSH); + // if it's still there, nuke it + if (other) + BecomeExplosion1 (other); + return; + } + + T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH); + + if (self->moveinfo.state == STATE_UP) + plat_go_down (self); + else if (self->moveinfo.state == STATE_DOWN) + plat_go_up (self); +} + + +void Use_Plat (edict_t *ent, edict_t *other, edict_t *activator) +{ + if (ent->think) + return; // already down + plat_go_down (ent); +} + + +void Touch_Plat_Center (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (!other->client) + return; + + if (other->health <= 0) + return; + + ent = ent->enemy; // now point at the plat, not the trigger + if (ent->moveinfo.state == STATE_BOTTOM) + plat_go_up (ent); + else if (ent->moveinfo.state == STATE_TOP) + ent->nextthink = level.time + 1; // the player is still on the plat, so delay going down +} + +void plat_spawn_inside_trigger (edict_t *ent) +{ + edict_t *trigger; + vec3_t tmin, tmax; + +// +// middle trigger +// + trigger = G_Spawn(); + trigger->touch = Touch_Plat_Center; + trigger->movetype = MOVETYPE_NONE; + trigger->solid = SOLID_TRIGGER; + trigger->enemy = ent; + + tmin[0] = ent->mins[0] + 25; + tmin[1] = ent->mins[1] + 25; + tmin[2] = ent->mins[2]; + + tmax[0] = ent->maxs[0] - 25; + tmax[1] = ent->maxs[1] - 25; + tmax[2] = ent->maxs[2] + 8; + + tmin[2] = tmax[2] - (ent->pos1[2] - ent->pos2[2] + st.lip); + + if (ent->spawnflags & PLAT_LOW_TRIGGER) + tmax[2] = tmin[2] + 8; + + if (tmax[0] - tmin[0] <= 0) + { + tmin[0] = (ent->mins[0] + ent->maxs[0]) *0.5; + tmax[0] = tmin[0] + 1; + } + if (tmax[1] - tmin[1] <= 0) + { + tmin[1] = (ent->mins[1] + ent->maxs[1]) *0.5; + tmax[1] = tmin[1] + 1; + } + + VectorCopy (tmin, trigger->mins); + VectorCopy (tmax, trigger->maxs); + + gi.linkentity (trigger); +} + + +/*QUAKED func_plat (0 .5 .8) ? PLAT_LOW_TRIGGER +speed default 150 + +Plats are always drawn in the extended position, so they will light correctly. + +If the plat is the target of another trigger or button, it will start out disabled in the extended position until it is trigger, when it will lower and become a normal plat. + +"speed" overrides default 200. +"accel" overrides default 500 +"lip" overrides default 8 pixel lip + +If the "height" key is set, that will determine the amount the plat moves, instead of being implicitly determoveinfoned by the model's height. + +Set "sounds" to one of the following: +1) base fast +2) chain slow +*/ +void SP_func_plat (edict_t *ent) +{ + VectorClear (ent->s.angles); + ent->solid = SOLID_BSP; + ent->movetype = MOVETYPE_PUSH; + + gi.setmodel (ent, ent->model); + + ent->blocked = plat_blocked; + + if (!ent->speed) + ent->speed = 20; + else + ent->speed *= 0.1; + + if (!ent->accel) + ent->accel = 5; + else + ent->accel *= 0.1; + + if (!ent->decel) + ent->decel = 5; + else + ent->decel *= 0.1; + + if (!ent->dmg) + ent->dmg = 2; + + if (!st.lip) + st.lip = 8; + + // pos1 is the top position, pos2 is the bottom + VectorCopy (ent->s.origin, ent->pos1); + VectorCopy (ent->s.origin, ent->pos2); + if (st.height) + ent->pos2[2] -= st.height; + else + ent->pos2[2] -= (ent->maxs[2] - ent->mins[2]) - st.lip; + + ent->use = Use_Plat; + + plat_spawn_inside_trigger (ent); // the "start moving" trigger + + if (ent->targetname) + { + ent->moveinfo.state = STATE_UP; + } + else + { + VectorCopy (ent->pos2, ent->s.origin); + gi.linkentity (ent); + ent->moveinfo.state = STATE_BOTTOM; + } + + ent->moveinfo.speed = ent->speed; + ent->moveinfo.accel = ent->accel; + ent->moveinfo.decel = ent->decel; + ent->moveinfo.wait = ent->wait; + VectorCopy (ent->pos1, ent->moveinfo.start_origin); + VectorCopy (ent->s.angles, ent->moveinfo.start_angles); + VectorCopy (ent->pos2, ent->moveinfo.end_origin); + VectorCopy (ent->s.angles, ent->moveinfo.end_angles); + + ent->moveinfo.sound_start = gi.soundindex ("plats/pt1_strt.wav"); + ent->moveinfo.sound_middle = gi.soundindex ("plats/pt1_mid.wav"); + ent->moveinfo.sound_end = gi.soundindex ("plats/pt1_end.wav"); +} + +//==================================================================== + +/*QUAKED func_rotating (0 .5 .8) ? START_ON REVERSE X_AXIS Y_AXIS TOUCH_PAIN STOP ANIMATED ANIMATED_FAST +You need to have an origin brush as part of this entity. The center of that brush will be +the point around which it is rotated. It will rotate around the Z axis by default. You can +check either the X_AXIS or Y_AXIS box to change that. + +"speed" determines how fast it moves; default value is 100. +"dmg" damage to inflict when blocked (2 default) + +REVERSE will cause the it to rotate in the opposite direction. +STOP mean it will stop moving instead of pushing entities +*/ + +void rotating_blocked (edict_t *self, edict_t *other) +{ + T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH); +} + +void rotating_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (self->avelocity[0] || self->avelocity[1] || self->avelocity[2]) + T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH); +} + +void rotating_use (edict_t *self, edict_t *other, edict_t *activator) +{ + if (!VectorCompare (self->avelocity, vec3_origin)) + { + self->s.sound = 0; + VectorClear (self->avelocity); + self->touch = NULL; + } + else + { + self->s.sound = self->moveinfo.sound_middle; + VectorScale (self->movedir, self->speed, self->avelocity); + if (self->spawnflags & 16) + self->touch = rotating_touch; + } +} + +void SP_func_rotating (edict_t *ent) +{ + ent->solid = SOLID_BSP; + if (ent->spawnflags & 32) + ent->movetype = MOVETYPE_STOP; + else + ent->movetype = MOVETYPE_PUSH; + + // set the axis of rotation + VectorClear(ent->movedir); + if (ent->spawnflags & 4) + ent->movedir[2] = 1.0; + else if (ent->spawnflags & 8) + ent->movedir[0] = 1.0; + else // Z_AXIS + ent->movedir[1] = 1.0; + + // check for reverse rotation + if (ent->spawnflags & 2) + VectorNegate (ent->movedir, ent->movedir); + + if (!ent->speed) + ent->speed = 100; + if (!ent->dmg) + ent->dmg = 2; + +// ent->moveinfo.sound_middle = "doors/hydro1.wav"; + + ent->use = rotating_use; + if (ent->dmg) + ent->blocked = rotating_blocked; + + if (ent->spawnflags & 1) + ent->use (ent, NULL, NULL); + + if (ent->spawnflags & 64) + ent->s.effects |= EF_ANIM_ALL; + if (ent->spawnflags & 128) + ent->s.effects |= EF_ANIM_ALLFAST; + + gi.setmodel (ent, ent->model); + gi.linkentity (ent); +} + +/* +====================================================================== + +BUTTONS + +====================================================================== +*/ + +/*QUAKED func_button (0 .5 .8) ? +When a button is touched, it moves some distance in the direction of it's angle, triggers all of it's targets, waits some time, then returns to it's original position where it can be triggered again. + +"angle" determines the opening direction +"target" all entities with a matching targetname will be used +"speed" override the default 40 speed +"wait" override the default 1 second wait (-1 = never return) +"lip" override the default 4 pixel lip remaining at end of move +"health" if set, the button must be killed instead of touched +"sounds" +1) silent +2) steam metal +3) wooden clunk +4) metallic click +5) in-out +*/ + +void button_done (edict_t *self) +{ + self->moveinfo.state = STATE_BOTTOM; + self->s.effects &= ~EF_ANIM23; + self->s.effects |= EF_ANIM01; +} + +void button_return (edict_t *self) +{ + self->moveinfo.state = STATE_DOWN; + + Move_Calc (self, self->moveinfo.start_origin, button_done); + + self->s.frame = 0; + + if (self->health) + self->takedamage = DAMAGE_YES; +} + +void button_wait (edict_t *self) +{ + self->moveinfo.state = STATE_TOP; + self->s.effects &= ~EF_ANIM01; + self->s.effects |= EF_ANIM23; + + G_UseTargets (self, self->activator); + self->s.frame = 1; + if (self->moveinfo.wait >= 0) + { + self->nextthink = level.time + self->moveinfo.wait; + self->think = button_return; + } +} + +void button_fire (edict_t *self) +{ + if (self->moveinfo.state == STATE_UP || self->moveinfo.state == STATE_TOP) + return; + + self->moveinfo.state = STATE_UP; + if (self->moveinfo.sound_start && !(self->flags & FL_TEAMSLAVE)) + gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_start, 1, ATTN_STATIC, 0); + Move_Calc (self, self->moveinfo.end_origin, button_wait); +} + +void button_use (edict_t *self, edict_t *other, edict_t *activator) +{ + self->activator = activator; + button_fire (self); +} + +void button_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (!other->client) + return; + + if (other->health <= 0) + return; + + self->activator = other; + button_fire (self); +} + +void button_killed (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + self->activator = attacker; + self->health = self->max_health; + self->takedamage = DAMAGE_NO; + button_fire (self); +} + +void SP_func_button (edict_t *ent) +{ + vec3_t abs_movedir; + float dist; + + G_SetMovedir (ent->s.angles, ent->movedir); + ent->movetype = MOVETYPE_STOP; + ent->solid = SOLID_BSP; + gi.setmodel (ent, ent->model); + + if (ent->sounds != 1) + ent->moveinfo.sound_start = gi.soundindex ("switches/butn2.wav"); + + if (!ent->speed) + ent->speed = 40; + if (!ent->accel) + ent->accel = ent->speed; + if (!ent->decel) + ent->decel = ent->speed; + + if (!ent->wait) + ent->wait = 3; + if (!st.lip) + st.lip = 4; + + VectorCopy (ent->s.origin, ent->pos1); + abs_movedir[0] = fabs(ent->movedir[0]); + abs_movedir[1] = fabs(ent->movedir[1]); + abs_movedir[2] = fabs(ent->movedir[2]); + dist = abs_movedir[0] * ent->size[0] + abs_movedir[1] * ent->size[1] + abs_movedir[2] * ent->size[2] - st.lip; + VectorMA (ent->pos1, dist, ent->movedir, ent->pos2); + + ent->use = button_use; + ent->s.effects |= EF_ANIM01; + + if (ent->health) + { + ent->max_health = ent->health; + ent->die = button_killed; + ent->takedamage = DAMAGE_YES; + } + else if (! ent->targetname) + ent->touch = button_touch; + + ent->moveinfo.state = STATE_BOTTOM; + + ent->moveinfo.speed = ent->speed; + ent->moveinfo.accel = ent->accel; + ent->moveinfo.decel = ent->decel; + ent->moveinfo.wait = ent->wait; + VectorCopy (ent->pos1, ent->moveinfo.start_origin); + VectorCopy (ent->s.angles, ent->moveinfo.start_angles); + VectorCopy (ent->pos2, ent->moveinfo.end_origin); + VectorCopy (ent->s.angles, ent->moveinfo.end_angles); + + gi.linkentity (ent); +} + +/* +====================================================================== + +DOORS + + spawn a trigger surrounding the entire team unless it is + already targeted by another + +====================================================================== +*/ + +/*QUAKED func_door (0 .5 .8) ? START_OPEN x CRUSHER NOMONSTER ANIMATED TOGGLE ANIMATED_FAST +TOGGLE wait in both the start and end states for a trigger event. +START_OPEN the door to moves to its destination when spawned, and operate in reverse. It is used to temporarily or permanently close off an area when triggered (not useful for touch or takedamage doors). +NOMONSTER monsters will not trigger this door + +"message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet +"angle" determines the opening direction +"targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door. +"health" if set, door must be shot open +"speed" movement speed (100 default) +"wait" wait before returning (3 default, -1 = never return) +"lip" lip remaining at end of move (8 default) +"dmg" damage to inflict when blocked (2 default) +"sounds" +1) silent +2) light +3) medium +4) heavy +*/ + +void door_use_areaportals (edict_t *self, qboolean open) +{ + edict_t *t = NULL; + + if (!self->target) + return; + + while ((t = G_Find (t, FOFS(targetname), self->target))) + { + if (Q_stricmp(t->classname, "func_areaportal") == 0) + { + gi.SetAreaPortalState (t->style, open); + } + } +} + +void door_go_down (edict_t *self); + +void door_hit_top (edict_t *self) +{ + if (!(self->flags & FL_TEAMSLAVE)) + { + if (self->moveinfo.sound_end) + gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_end, 1, ATTN_STATIC, 0); + self->s.sound = 0; + } + self->moveinfo.state = STATE_TOP; + if (self->spawnflags & DOOR_TOGGLE) + return; + if (self->moveinfo.wait >= 0) + { + self->think = door_go_down; + self->nextthink = level.time + self->moveinfo.wait; + } +} + +void door_hit_bottom (edict_t *self) +{ + if (!(self->flags & FL_TEAMSLAVE)) + { + if (self->moveinfo.sound_end) + gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_end, 1, ATTN_STATIC, 0); + self->s.sound = 0; + } + self->moveinfo.state = STATE_BOTTOM; + door_use_areaportals (self, false); +} + +void door_go_down (edict_t *self) +{ + if (!(self->flags & FL_TEAMSLAVE)) + { + if (self->moveinfo.sound_start) + gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_start, 1, ATTN_STATIC, 0); + self->s.sound = self->moveinfo.sound_middle; + } + if (self->max_health) + { + self->takedamage = DAMAGE_YES; + self->health = self->max_health; + } + + self->moveinfo.state = STATE_DOWN; + if (strcmp(self->classname, "func_door") == 0) + Move_Calc (self, self->moveinfo.start_origin, door_hit_bottom); + else if (strcmp(self->classname, "func_door_rotating") == 0) + AngleMove_Calc (self, door_hit_bottom); +} + +void door_go_up (edict_t *self, edict_t *activator) +{ + if (self->moveinfo.state == STATE_UP) + return; // already going up + + if (self->moveinfo.state == STATE_TOP) + { // reset top wait time + if (self->moveinfo.wait >= 0) + self->nextthink = level.time + self->moveinfo.wait; + return; + } + + if (!(self->flags & FL_TEAMSLAVE)) + { + if (self->moveinfo.sound_start) + gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_start, 1, ATTN_STATIC, 0); + self->s.sound = self->moveinfo.sound_middle; + } + self->moveinfo.state = STATE_UP; + if (strcmp(self->classname, "func_door") == 0) + Move_Calc (self, self->moveinfo.end_origin, door_hit_top); + else if (strcmp(self->classname, "func_door_rotating") == 0) + AngleMove_Calc (self, door_hit_top); + + G_UseTargets (self, activator); + door_use_areaportals (self, true); +} + +void door_use (edict_t *self, edict_t *other, edict_t *activator) +{ + edict_t *ent; + + if (self->flags & FL_TEAMSLAVE) + return; + + if (self->spawnflags & DOOR_TOGGLE) + { + if (self->moveinfo.state == STATE_UP || self->moveinfo.state == STATE_TOP) + { + // trigger all paired doors + for (ent = self ; ent ; ent = ent->teamchain) + { + ent->message = NULL; + ent->touch = NULL; + door_go_down (ent); + } + return; + } + } + + // trigger all paired doors + for (ent = self ; ent ; ent = ent->teamchain) + { + ent->message = NULL; + ent->touch = NULL; + door_go_up (ent, activator); + } +}; + +void Touch_DoorTrigger (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (other->health <= 0) + return; + + if (!(other->svflags & SVF_MONSTER) && (!other->client)) + return; + + if ((self->owner->spawnflags & DOOR_NOMONSTER) && (other->svflags & SVF_MONSTER)) + return; + + if (level.time < self->touch_debounce_time) + return; + self->touch_debounce_time = level.time + 1.0; + + door_use (self->owner, other, other); +} + +void Think_CalcMoveSpeed (edict_t *self) +{ + edict_t *ent; + float min; + float time; + float newspeed; + float ratio; + float dist; + + if (self->flags & FL_TEAMSLAVE) + return; // only the team master does this + + // find the smallest distance any member of the team will be moving + min = fabs(self->moveinfo.distance); + for (ent = self->teamchain; ent; ent = ent->teamchain) + { + dist = fabs(ent->moveinfo.distance); + if (dist < min) + min = dist; + } + + time = min / self->moveinfo.speed; + + // adjust speeds so they will all complete at the same time + for (ent = self; ent; ent = ent->teamchain) + { + newspeed = fabs(ent->moveinfo.distance) / time; + ratio = newspeed / ent->moveinfo.speed; + if (ent->moveinfo.accel == ent->moveinfo.speed) + ent->moveinfo.accel = newspeed; + else + ent->moveinfo.accel *= ratio; + if (ent->moveinfo.decel == ent->moveinfo.speed) + ent->moveinfo.decel = newspeed; + else + ent->moveinfo.decel *= ratio; + ent->moveinfo.speed = newspeed; + } +} + +void Think_SpawnDoorTrigger (edict_t *ent) +{ + edict_t *other; + vec3_t mins, maxs; + + if (ent->flags & FL_TEAMSLAVE) + return; // only the team leader spawns a trigger + + VectorCopy (ent->absmin, mins); + VectorCopy (ent->absmax, maxs); + + for (other = ent->teamchain ; other ; other=other->teamchain) + { + AddPointToBounds (other->absmin, mins, maxs); + AddPointToBounds (other->absmax, mins, maxs); + } + + // expand + mins[0] -= 60; + mins[1] -= 60; + maxs[0] += 60; + maxs[1] += 60; + + other = G_Spawn (); + VectorCopy (mins, other->mins); + VectorCopy (maxs, other->maxs); + other->owner = ent; + other->solid = SOLID_TRIGGER; + other->movetype = MOVETYPE_NONE; + other->touch = Touch_DoorTrigger; + gi.linkentity (other); + + if (ent->spawnflags & DOOR_START_OPEN) + door_use_areaportals (ent, true); + + Think_CalcMoveSpeed (ent); +} + +void door_blocked (edict_t *self, edict_t *other) +{ + edict_t *ent; + + if (!(other->svflags & SVF_MONSTER) && (!other->client) ) + { + // give it a chance to go away on it's own terms (like gibs) + T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, 100000, 1, 0, MOD_CRUSH); + // if it's still there, nuke it + if (other) + BecomeExplosion1 (other); + return; + } + + T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH); + + if (self->spawnflags & DOOR_CRUSHER) + return; + + +// if a door has a negative wait, it would never come back if blocked, +// so let it just squash the object to death real fast + if (self->moveinfo.wait >= 0) + { + if (self->moveinfo.state == STATE_DOWN) + { + for (ent = self->teammaster ; ent ; ent = ent->teamchain) + door_go_up (ent, ent->activator); + } + else + { + for (ent = self->teammaster ; ent ; ent = ent->teamchain) + door_go_down (ent); + } + } +} + +void door_killed (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + edict_t *ent; + + for (ent = self->teammaster ; ent ; ent = ent->teamchain) + { + ent->health = ent->max_health; + ent->takedamage = DAMAGE_NO; + } + door_use (self->teammaster, attacker, attacker); +} + +void door_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (!other->client) + return; + + if (level.time < self->touch_debounce_time) + return; + self->touch_debounce_time = level.time + 5.0; + + gi.centerprintf (other, "%s", self->message); + gi.sound (other, CHAN_AUTO, gi.soundindex ("misc/talk1.wav"), 1, ATTN_NORM, 0); +} + +void SP_func_door (edict_t *ent) +{ + vec3_t abs_movedir; + + if (ent->sounds != 1) + { + ent->moveinfo.sound_start = gi.soundindex ("doors/dr1_strt.wav"); + ent->moveinfo.sound_middle = gi.soundindex ("doors/dr1_mid.wav"); + ent->moveinfo.sound_end = gi.soundindex ("doors/dr1_end.wav"); + } + + G_SetMovedir (ent->s.angles, ent->movedir); + ent->movetype = MOVETYPE_PUSH; + ent->solid = SOLID_BSP; + gi.setmodel (ent, ent->model); + + ent->blocked = door_blocked; + ent->use = door_use; + + if (!ent->speed) + ent->speed = 100; + if (deathmatch->value) + ent->speed *= 2; + + if (!ent->accel) + ent->accel = ent->speed; + if (!ent->decel) + ent->decel = ent->speed; + + if (!ent->wait) + ent->wait = 3; + if (!st.lip) + st.lip = 8; + if (!ent->dmg) + ent->dmg = 2; + + // calculate second position + VectorCopy (ent->s.origin, ent->pos1); + abs_movedir[0] = fabs(ent->movedir[0]); + abs_movedir[1] = fabs(ent->movedir[1]); + abs_movedir[2] = fabs(ent->movedir[2]); + ent->moveinfo.distance = abs_movedir[0] * ent->size[0] + abs_movedir[1] * ent->size[1] + abs_movedir[2] * ent->size[2] - st.lip; + VectorMA (ent->pos1, ent->moveinfo.distance, ent->movedir, ent->pos2); + + // if it starts open, switch the positions + if (ent->spawnflags & DOOR_START_OPEN) + { + VectorCopy (ent->pos2, ent->s.origin); + VectorCopy (ent->pos1, ent->pos2); + VectorCopy (ent->s.origin, ent->pos1); + } + + ent->moveinfo.state = STATE_BOTTOM; + + if (ent->health) + { + ent->takedamage = DAMAGE_YES; + ent->die = door_killed; + ent->max_health = ent->health; + } + else if (ent->targetname && ent->message) + { + gi.soundindex ("misc/talk.wav"); + ent->touch = door_touch; + } + + ent->moveinfo.speed = ent->speed; + ent->moveinfo.accel = ent->accel; + ent->moveinfo.decel = ent->decel; + ent->moveinfo.wait = ent->wait; + VectorCopy (ent->pos1, ent->moveinfo.start_origin); + VectorCopy (ent->s.angles, ent->moveinfo.start_angles); + VectorCopy (ent->pos2, ent->moveinfo.end_origin); + VectorCopy (ent->s.angles, ent->moveinfo.end_angles); + + if (ent->spawnflags & 16) + ent->s.effects |= EF_ANIM_ALL; + if (ent->spawnflags & 64) + ent->s.effects |= EF_ANIM_ALLFAST; + + // to simplify logic elsewhere, make non-teamed doors into a team of one + if (!ent->team) + ent->teammaster = ent; + + gi.linkentity (ent); + + ent->nextthink = level.time + FRAMETIME; + if (ent->health || ent->targetname) + ent->think = Think_CalcMoveSpeed; + else + ent->think = Think_SpawnDoorTrigger; +} + + +/*QUAKED func_door_rotating (0 .5 .8) ? START_OPEN REVERSE CRUSHER NOMONSTER ANIMATED TOGGLE X_AXIS Y_AXIS +TOGGLE causes the door to wait in both the start and end states for a trigger event. + +START_OPEN the door to moves to its destination when spawned, and operate in reverse. It is used to temporarily or permanently close off an area when triggered (not useful for touch or takedamage doors). +NOMONSTER monsters will not trigger this door + +You need to have an origin brush as part of this entity. The center of that brush will be +the point around which it is rotated. It will rotate around the Z axis by default. You can +check either the X_AXIS or Y_AXIS box to change that. + +"distance" is how many degrees the door will be rotated. +"speed" determines how fast the door moves; default value is 100. + +REVERSE will cause the door to rotate in the opposite direction. + +"message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet +"angle" determines the opening direction +"targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door. +"health" if set, door must be shot open +"speed" movement speed (100 default) +"wait" wait before returning (3 default, -1 = never return) +"dmg" damage to inflict when blocked (2 default) +"sounds" +1) silent +2) light +3) medium +4) heavy +*/ + +void SP_func_door_rotating (edict_t *ent) +{ + VectorClear (ent->s.angles); + + // set the axis of rotation + VectorClear(ent->movedir); + if (ent->spawnflags & DOOR_X_AXIS) + ent->movedir[2] = 1.0; + else if (ent->spawnflags & DOOR_Y_AXIS) + ent->movedir[0] = 1.0; + else // Z_AXIS + ent->movedir[1] = 1.0; + + // check for reverse rotation + if (ent->spawnflags & DOOR_REVERSE) + VectorNegate (ent->movedir, ent->movedir); + + if (!st.distance) + { + gi.dprintf("%s at %s with no distance set\n", ent->classname, vtos(ent->s.origin)); + st.distance = 90; + } + + VectorCopy (ent->s.angles, ent->pos1); + VectorMA (ent->s.angles, st.distance, ent->movedir, ent->pos2); + ent->moveinfo.distance = st.distance; + + ent->movetype = MOVETYPE_PUSH; + ent->solid = SOLID_BSP; + gi.setmodel (ent, ent->model); + + ent->blocked = door_blocked; + ent->use = door_use; + + if (!ent->speed) + ent->speed = 100; + if (!ent->accel) + ent->accel = ent->speed; + if (!ent->decel) + ent->decel = ent->speed; + + if (!ent->wait) + ent->wait = 3; + if (!ent->dmg) + ent->dmg = 2; + + if (ent->sounds != 1) + { + ent->moveinfo.sound_start = gi.soundindex ("doors/dr1_strt.wav"); + ent->moveinfo.sound_middle = gi.soundindex ("doors/dr1_mid.wav"); + ent->moveinfo.sound_end = gi.soundindex ("doors/dr1_end.wav"); + } + + // if it starts open, switch the positions + if (ent->spawnflags & DOOR_START_OPEN) + { + VectorCopy (ent->pos2, ent->s.angles); + VectorCopy (ent->pos1, ent->pos2); + VectorCopy (ent->s.angles, ent->pos1); + VectorNegate (ent->movedir, ent->movedir); + } + + if (ent->health) + { + ent->takedamage = DAMAGE_YES; + ent->die = door_killed; + ent->max_health = ent->health; + } + + if (ent->targetname && ent->message) + { + gi.soundindex ("misc/talk.wav"); + ent->touch = door_touch; + } + + ent->moveinfo.state = STATE_BOTTOM; + ent->moveinfo.speed = ent->speed; + ent->moveinfo.accel = ent->accel; + ent->moveinfo.decel = ent->decel; + ent->moveinfo.wait = ent->wait; + VectorCopy (ent->s.origin, ent->moveinfo.start_origin); + VectorCopy (ent->pos1, ent->moveinfo.start_angles); + VectorCopy (ent->s.origin, ent->moveinfo.end_origin); + VectorCopy (ent->pos2, ent->moveinfo.end_angles); + + if (ent->spawnflags & 16) + ent->s.effects |= EF_ANIM_ALL; + + // to simplify logic elsewhere, make non-teamed doors into a team of one + if (!ent->team) + ent->teammaster = ent; + + gi.linkentity (ent); + + ent->nextthink = level.time + FRAMETIME; + if (ent->health || ent->targetname) + ent->think = Think_CalcMoveSpeed; + else + ent->think = Think_SpawnDoorTrigger; +} + + +/*QUAKED func_water (0 .5 .8) ? START_OPEN +func_water is a moveable water brush. It must be targeted to operate. Use a non-water texture at your own risk. + +START_OPEN causes the water to move to its destination when spawned and operate in reverse. + +"angle" determines the opening direction (up or down only) +"speed" movement speed (25 default) +"wait" wait before returning (-1 default, -1 = TOGGLE) +"lip" lip remaining at end of move (0 default) +"sounds" (yes, these need to be changed) +0) no sound +1) water +2) lava +*/ + +void SP_func_water (edict_t *self) +{ + vec3_t abs_movedir; + + G_SetMovedir (self->s.angles, self->movedir); + self->movetype = MOVETYPE_PUSH; + self->solid = SOLID_BSP; + gi.setmodel (self, self->model); + + switch (self->sounds) + { + default: + break; + + case 1: // water + self->moveinfo.sound_start = gi.soundindex ("world/mov_watr.wav"); + self->moveinfo.sound_end = gi.soundindex ("world/stp_watr.wav"); + break; + + case 2: // lava + self->moveinfo.sound_start = gi.soundindex ("world/mov_watr.wav"); + self->moveinfo.sound_end = gi.soundindex ("world/stp_watr.wav"); + break; + } + + // calculate second position + VectorCopy (self->s.origin, self->pos1); + abs_movedir[0] = fabs(self->movedir[0]); + abs_movedir[1] = fabs(self->movedir[1]); + abs_movedir[2] = fabs(self->movedir[2]); + self->moveinfo.distance = abs_movedir[0] * self->size[0] + abs_movedir[1] * self->size[1] + abs_movedir[2] * self->size[2] - st.lip; + VectorMA (self->pos1, self->moveinfo.distance, self->movedir, self->pos2); + + // if it starts open, switch the positions + if (self->spawnflags & DOOR_START_OPEN) + { + VectorCopy (self->pos2, self->s.origin); + VectorCopy (self->pos1, self->pos2); + VectorCopy (self->s.origin, self->pos1); + } + + VectorCopy (self->pos1, self->moveinfo.start_origin); + VectorCopy (self->s.angles, self->moveinfo.start_angles); + VectorCopy (self->pos2, self->moveinfo.end_origin); + VectorCopy (self->s.angles, self->moveinfo.end_angles); + + self->moveinfo.state = STATE_BOTTOM; + + if (!self->speed) + self->speed = 25; + self->moveinfo.accel = self->moveinfo.decel = self->moveinfo.speed = self->speed; + + if (!self->wait) + self->wait = -1; + self->moveinfo.wait = self->wait; + + self->use = door_use; + + if (self->wait == -1) + self->spawnflags |= DOOR_TOGGLE; + + self->classname = "func_door"; + + gi.linkentity (self); +} + + +#define TRAIN_START_ON 1 +#define TRAIN_TOGGLE 2 +#define TRAIN_BLOCK_STOPS 4 + +/*QUAKED func_train (0 .5 .8) ? START_ON TOGGLE BLOCK_STOPS +Trains are moving platforms that players can ride. +The targets origin specifies the min point of the train at each corner. +The train spawns at the first target it is pointing at. +If the train is the target of a button or trigger, it will not begin moving until activated. +speed default 100 +dmg default 2 +noise looping sound to play when the train is in motion + +*/ +void train_next (edict_t *self); + +void train_blocked (edict_t *self, edict_t *other) +{ + if (!(other->svflags & SVF_MONSTER) && (!other->client) ) + { + // give it a chance to go away on it's own terms (like gibs) + T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, 100000, 1, 0, MOD_CRUSH); + // if it's still there, nuke it + if (other) + BecomeExplosion1 (other); + return; + } + + if (level.time < self->touch_debounce_time) + return; + + if (!self->dmg) + return; + self->touch_debounce_time = level.time + 0.5; + T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH); +} + +void train_wait (edict_t *self) +{ + if (self->target_ent->pathtarget) + { + char *savetarget; + edict_t *ent; + + ent = self->target_ent; + savetarget = ent->target; + ent->target = ent->pathtarget; + G_UseTargets (ent, self->activator); + ent->target = savetarget; + + // make sure we didn't get killed by a killtarget + if (!self->inuse) + return; + } + + if (self->moveinfo.wait) + { + if (self->moveinfo.wait > 0) + { + self->nextthink = level.time + self->moveinfo.wait; + self->think = train_next; + } + else if (self->spawnflags & TRAIN_TOGGLE) // && wait < 0 + { + train_next (self); + self->spawnflags &= ~TRAIN_START_ON; + VectorClear (self->velocity); + self->nextthink = 0; + } + + if (!(self->flags & FL_TEAMSLAVE)) + { + if (self->moveinfo.sound_end) + gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_end, 1, ATTN_STATIC, 0); + self->s.sound = 0; + } + } + else + { + train_next (self); + } + +} + +void train_next (edict_t *self) +{ + edict_t *ent; + vec3_t dest; + qboolean first; + + first = true; +again: + if (!self->target) + { +// gi.dprintf ("train_next: no next target\n"); + return; + } + + ent = G_PickTarget (self->target); + if (!ent) + { + gi.dprintf ("train_next: bad target %s\n", self->target); + return; + } + + self->target = ent->target; + + // check for a teleport path_corner + if (ent->spawnflags & 1) + { + if (!first) + { + gi.dprintf ("connected teleport path_corners, see %s at %s\n", ent->classname, vtos(ent->s.origin)); + return; + } + first = false; + VectorSubtract (ent->s.origin, self->mins, self->s.origin); + VectorCopy (self->s.origin, self->s.old_origin); + self->s.event = EV_OTHER_TELEPORT; + gi.linkentity (self); + goto again; + } + + self->moveinfo.wait = ent->wait; + self->target_ent = ent; + + if (!(self->flags & FL_TEAMSLAVE)) + { + if (self->moveinfo.sound_start) + gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_start, 1, ATTN_STATIC, 0); + self->s.sound = self->moveinfo.sound_middle; + } + + VectorSubtract (ent->s.origin, self->mins, dest); + self->moveinfo.state = STATE_TOP; + VectorCopy (self->s.origin, self->moveinfo.start_origin); + VectorCopy (dest, self->moveinfo.end_origin); + Move_Calc (self, dest, train_wait); + self->spawnflags |= TRAIN_START_ON; +} + +void train_resume (edict_t *self) +{ + edict_t *ent; + vec3_t dest; + + ent = self->target_ent; + + VectorSubtract (ent->s.origin, self->mins, dest); + self->moveinfo.state = STATE_TOP; + VectorCopy (self->s.origin, self->moveinfo.start_origin); + VectorCopy (dest, self->moveinfo.end_origin); + Move_Calc (self, dest, train_wait); + self->spawnflags |= TRAIN_START_ON; +} + +void func_train_find (edict_t *self) +{ + edict_t *ent; + + if (!self->target) + { + gi.dprintf ("train_find: no target\n"); + return; + } + ent = G_PickTarget (self->target); + if (!ent) + { + gi.dprintf ("train_find: target %s not found\n", self->target); + return; + } + self->target = ent->target; + + VectorSubtract (ent->s.origin, self->mins, self->s.origin); + gi.linkentity (self); + + // if not triggered, start immediately + if (!self->targetname) + self->spawnflags |= TRAIN_START_ON; + + if (self->spawnflags & TRAIN_START_ON) + { + self->nextthink = level.time + FRAMETIME; + self->think = train_next; + self->activator = self; + } +} + +void train_use (edict_t *self, edict_t *other, edict_t *activator) +{ + self->activator = activator; + + if (self->spawnflags & TRAIN_START_ON) + { + if (!(self->spawnflags & TRAIN_TOGGLE)) + return; + self->spawnflags &= ~TRAIN_START_ON; + VectorClear (self->velocity); + self->nextthink = 0; + } + else + { + if (self->target_ent) + train_resume(self); + else + train_next(self); + } +} + +void SP_func_train (edict_t *self) +{ + self->movetype = MOVETYPE_PUSH; + + VectorClear (self->s.angles); + self->blocked = train_blocked; + if (self->spawnflags & TRAIN_BLOCK_STOPS) + self->dmg = 0; + else + { + if (!self->dmg) + self->dmg = 100; + } + self->solid = SOLID_BSP; + gi.setmodel (self, self->model); + + if (st.noise) + self->moveinfo.sound_middle = gi.soundindex (st.noise); + + if (!self->speed) + self->speed = 100; + + self->moveinfo.speed = self->speed; + self->moveinfo.accel = self->moveinfo.decel = self->moveinfo.speed; + + self->use = train_use; + + gi.linkentity (self); + + if (self->target) + { + // start trains on the second frame, to make sure their targets have had + // a chance to spawn + self->nextthink = level.time + FRAMETIME; + self->think = func_train_find; + } + else + { + gi.dprintf ("func_train without a target at %s\n", vtos(self->absmin)); + } +} + + +/*QUAKED trigger_elevator (0.3 0.1 0.6) (-8 -8 -8) (8 8 8) +*/ +void trigger_elevator_use (edict_t *self, edict_t *other, edict_t *activator) +{ + edict_t *target; + + if (self->movetarget->nextthink) + { +// gi.dprintf("elevator busy\n"); + return; + } + + if (!other->pathtarget) + { + gi.dprintf("elevator used with no pathtarget\n"); + return; + } + + target = G_PickTarget (other->pathtarget); + if (!target) + { + gi.dprintf("elevator used with bad pathtarget: %s\n", other->pathtarget); + return; + } + + self->movetarget->target_ent = target; + train_resume (self->movetarget); +} + +void trigger_elevator_init (edict_t *self) +{ + if (!self->target) + { + gi.dprintf("trigger_elevator has no target\n"); + return; + } + self->movetarget = G_PickTarget (self->target); + if (!self->movetarget) + { + gi.dprintf("trigger_elevator unable to find target %s\n", self->target); + return; + } + if (strcmp(self->movetarget->classname, "func_train") != 0) + { + gi.dprintf("trigger_elevator target %s is not a train\n", self->target); + return; + } + + self->use = trigger_elevator_use; + self->svflags = SVF_NOCLIENT; + +} + +void SP_trigger_elevator (edict_t *self) +{ + self->think = trigger_elevator_init; + self->nextthink = level.time + FRAMETIME; +} + + +/*QUAKED func_timer (0.3 0.1 0.6) (-8 -8 -8) (8 8 8) START_ON +"wait" base time between triggering all targets, default is 1 +"random" wait variance, default is 0 + +so, the basic time between firing is a random time between +(wait - random) and (wait + random) + +"delay" delay before first firing when turned on, default is 0 + +"pausetime" additional delay used only the very first time + and only if spawned with START_ON + +These can used but not touched. +*/ +void func_timer_think (edict_t *self) +{ + G_UseTargets (self, self->activator); + self->nextthink = level.time + self->wait + crandom() * self->random; +} + +void func_timer_use (edict_t *self, edict_t *other, edict_t *activator) +{ + self->activator = activator; + + // if on, turn it off + if (self->nextthink) + { + self->nextthink = 0; + return; + } + + // turn it on + if (self->delay) + self->nextthink = level.time + self->delay; + else + func_timer_think (self); +} + +void SP_func_timer (edict_t *self) +{ + if (!self->wait) + self->wait = 1.0; + + self->use = func_timer_use; + self->think = func_timer_think; + + if (self->random >= self->wait) + { + self->random = self->wait - FRAMETIME; + gi.dprintf("func_timer at %s has random >= wait\n", vtos(self->s.origin)); + } + + if (self->spawnflags & 1) + { + self->nextthink = level.time + 1.0 + st.pausetime + self->delay + self->wait + crandom() * self->random; + self->activator = self; + } + + self->svflags = SVF_NOCLIENT; +} + + +/*QUAKED func_conveyor (0 .5 .8) ? START_ON TOGGLE +Conveyors are stationary brushes that move what's on them. +The brush should be have a surface with at least one current content enabled. +speed default 100 +*/ + +void func_conveyor_use (edict_t *self, edict_t *other, edict_t *activator) +{ + if (self->spawnflags & 1) + { + self->speed = 0; + self->spawnflags &= ~1; + } + else + { + self->speed = self->count; + self->spawnflags |= 1; + } + + if (!(self->spawnflags & 2)) + self->count = 0; +} + +void SP_func_conveyor (edict_t *self) +{ + if (!self->speed) + self->speed = 100; + + if (!(self->spawnflags & 1)) + { + self->count = self->speed; + self->speed = 0; + } + + self->use = func_conveyor_use; + + gi.setmodel (self, self->model); + self->solid = SOLID_BSP; + gi.linkentity (self); +} + + +/*QUAKED func_door_secret (0 .5 .8) ? always_shoot 1st_left 1st_down +A secret door. Slide back and then to the side. + +open_once doors never closes +1st_left 1st move is left of arrow +1st_down 1st move is down from arrow +always_shoot door is shootebale even if targeted + +"angle" determines the direction +"dmg" damage to inflic when blocked (default 2) +"wait" how long to hold in the open position (default 5, -1 means hold) +*/ + +#define SECRET_ALWAYS_SHOOT 1 +#define SECRET_1ST_LEFT 2 +#define SECRET_1ST_DOWN 4 + +void door_secret_move1 (edict_t *self); +void door_secret_move2 (edict_t *self); +void door_secret_move3 (edict_t *self); +void door_secret_move4 (edict_t *self); +void door_secret_move5 (edict_t *self); +void door_secret_move6 (edict_t *self); +void door_secret_done (edict_t *self); + +void door_secret_use (edict_t *self, edict_t *other, edict_t *activator) +{ + // make sure we're not already moving + if (!VectorCompare(self->s.origin, vec3_origin)) + return; + + Move_Calc (self, self->pos1, door_secret_move1); + door_use_areaportals (self, true); +} + +void door_secret_move1 (edict_t *self) +{ + self->nextthink = level.time + 1.0; + self->think = door_secret_move2; +} + +void door_secret_move2 (edict_t *self) +{ + Move_Calc (self, self->pos2, door_secret_move3); +} + +void door_secret_move3 (edict_t *self) +{ + if (self->wait == -1) + return; + self->nextthink = level.time + self->wait; + self->think = door_secret_move4; +} + +void door_secret_move4 (edict_t *self) +{ + Move_Calc (self, self->pos1, door_secret_move5); +} + +void door_secret_move5 (edict_t *self) +{ + self->nextthink = level.time + 1.0; + self->think = door_secret_move6; +} + +void door_secret_move6 (edict_t *self) +{ + Move_Calc (self, vec3_origin, door_secret_done); +} + +void door_secret_done (edict_t *self) +{ + if (!(self->targetname) || (self->spawnflags & SECRET_ALWAYS_SHOOT)) + { + self->health = 0; + self->takedamage = DAMAGE_YES; + } + door_use_areaportals (self, false); +} + +void door_secret_blocked (edict_t *self, edict_t *other) +{ + if (!(other->svflags & SVF_MONSTER) && (!other->client) ) + { + // give it a chance to go away on it's own terms (like gibs) + T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, 100000, 1, 0, MOD_CRUSH); + // if it's still there, nuke it + if (other) + BecomeExplosion1 (other); + return; + } + + if (level.time < self->touch_debounce_time) + return; + self->touch_debounce_time = level.time + 0.5; + + T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH); +} + +void door_secret_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + self->takedamage = DAMAGE_NO; + door_secret_use (self, attacker, attacker); +} + +void SP_func_door_secret (edict_t *ent) +{ + vec3_t forward, right, up; + float side; + float width; + float length; + + ent->moveinfo.sound_start = gi.soundindex ("doors/dr1_strt.wav"); + ent->moveinfo.sound_middle = gi.soundindex ("doors/dr1_mid.wav"); + ent->moveinfo.sound_end = gi.soundindex ("doors/dr1_end.wav"); + + ent->movetype = MOVETYPE_PUSH; + ent->solid = SOLID_BSP; + gi.setmodel (ent, ent->model); + + ent->blocked = door_secret_blocked; + ent->use = door_secret_use; + + if (!(ent->targetname) || (ent->spawnflags & SECRET_ALWAYS_SHOOT)) + { + ent->health = 0; + ent->takedamage = DAMAGE_YES; + ent->die = door_secret_die; + } + + if (!ent->dmg) + ent->dmg = 2; + + if (!ent->wait) + ent->wait = 5; + + ent->moveinfo.accel = + ent->moveinfo.decel = + ent->moveinfo.speed = 50; + + // calculate positions + AngleVectors (ent->s.angles, forward, right, up); + VectorClear (ent->s.angles); + side = 1.0 - (ent->spawnflags & SECRET_1ST_LEFT); + if (ent->spawnflags & SECRET_1ST_DOWN) + width = fabs(DotProduct(up, ent->size)); + else + width = fabs(DotProduct(right, ent->size)); + length = fabs(DotProduct(forward, ent->size)); + if (ent->spawnflags & SECRET_1ST_DOWN) + VectorMA (ent->s.origin, -1 * width, up, ent->pos1); + else + VectorMA (ent->s.origin, side * width, right, ent->pos1); + VectorMA (ent->pos1, length, forward, ent->pos2); + + if (ent->health) + { + ent->takedamage = DAMAGE_YES; + ent->die = door_killed; + ent->max_health = ent->health; + } + else if (ent->targetname && ent->message) + { + gi.soundindex ("misc/talk.wav"); + ent->touch = door_touch; + } + + ent->classname = "func_door"; + + gi.linkentity (ent); +} + + +/*QUAKED func_killbox (1 0 0) ? +Kills everything inside when fired, irrespective of protection. +*/ +void use_killbox (edict_t *self, edict_t *other, edict_t *activator) +{ + KillBox (self); +} + +void SP_func_killbox (edict_t *ent) +{ + gi.setmodel (ent, ent->model); + ent->use = use_killbox; + ent->svflags = SVF_NOCLIENT; +} + + +/*QUAKED rotating_light (0 .5 .8) (-8 -8 -8) (8 8 8) START_OFF ALARM +"health" if set, the light may be killed. +*/ + +// RAFAEL +// note to self +// the lights will take damage from explosions +// this could leave a player in total darkness very bad + +#define START_OFF 1 + +void rotating_light_alarm (edict_t *self) +{ + if (self->spawnflags & START_OFF) + { + self->think = NULL; + self->nextthink = 0; + } + else + { + gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_start, 1, ATTN_STATIC, 0); + self->nextthink = level.time + 1; + } +} + +void rotating_light_killed (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_WELDING_SPARKS); + gi.WriteByte (30); + gi.WritePosition (self->s.origin); + gi.WriteDir (vec3_origin); + gi.WriteByte (0xe0 + (rand()&7)); + gi.multicast (self->s.origin, MULTICAST_PVS); + + self->s.effects &= ~EF_SPINNINGLIGHTS; + self->use = NULL; + + self->think = G_FreeEdict; + self->nextthink = level.time + 0.1; + +} + +static void rotating_light_use (edict_t *self, edict_t *other, edict_t *activator) +{ + if (self->spawnflags & START_OFF) + { + self->spawnflags &= ~START_OFF; + self->s.effects |= EF_SPINNINGLIGHTS; + + if (self->spawnflags & 2) + { + self->think = rotating_light_alarm; + self->nextthink = level.time + 0.1; + } + } + else + { + self->spawnflags |= START_OFF; + self->s.effects &= ~EF_SPINNINGLIGHTS; + } +} + + +void SP_rotating_light (edict_t *self) +{ + + self->movetype = MOVETYPE_STOP; + self->solid = SOLID_BBOX; + + self->s.modelindex = gi.modelindex ("models/objects/light/tris.md2"); + + self->s.frame = 0; + + self->use = rotating_light_use; + + if (self->spawnflags & START_OFF) + self->s.effects &= ~EF_SPINNINGLIGHTS; + else + { + self->s.effects |= EF_SPINNINGLIGHTS; + } + + if (!self->speed) + self->speed = 32; + // this is a real cheap way + // to set the radius of the light + // self->s.frame = self->speed; + + if (!self->health) + { + self->health = 10; + self->max_health = self->health; + self->die = rotating_light_killed; + self->takedamage = DAMAGE_YES; + } + else + { + self->max_health = self->health; + self->die = rotating_light_killed; + self->takedamage = DAMAGE_YES; + } + + if (self->spawnflags & 2) + { + self->moveinfo.sound_start = gi.soundindex ("misc/alarm.wav"); + } + + gi.linkentity (self); + +} + + +/*QUAKED func_object_repair (1 .5 0) (-8 -8 -8) (8 8 8) +object to be repaired. +The default delay is 1 second +"delay" the delay in seconds for spark to occur +*/ + +void object_repair_fx (edict_t *ent) +{ + + + ent->nextthink = level.time + ent->delay; + + if (ent->health <= 100) + ent->health++; + else + { + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_WELDING_SPARKS); + gi.WriteByte (10); + gi.WritePosition (ent->s.origin); + gi.WriteDir (vec3_origin); + gi.WriteByte (0xe0 + (rand()&7)); + gi.multicast (ent->s.origin, MULTICAST_PVS); + } + +} + + +void object_repair_dead (edict_t *ent) +{ + G_UseTargets (ent, ent); + ent->nextthink = level.time + 0.1; + ent->think = object_repair_fx; +} + +void object_repair_sparks (edict_t *ent) +{ + + if (ent->health < 0) + { + ent->nextthink = level.time + 0.1; + ent->think = object_repair_dead; + return; + } + + ent->nextthink = level.time + ent->delay; + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_WELDING_SPARKS); + gi.WriteByte (10); + gi.WritePosition (ent->s.origin); + gi.WriteDir (vec3_origin); + gi.WriteByte (0xe0 + (rand()&7)); + gi.multicast (ent->s.origin, MULTICAST_PVS); + +} + +void SP_object_repair (edict_t *ent) +{ + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_BBOX; + ent->classname = "object_repair"; + VectorSet (ent->mins, -8, -8, 8); + VectorSet (ent->maxs, 8, 8, 8); + ent->think = object_repair_sparks; + ent->nextthink = level.time + 1.0; + ent->health = 100; + if (!ent->delay) + ent->delay = 1.0; + +} + diff --git a/original/xatrix/g_items.c b/original/xatrix/g_items.c new file mode 100644 index 0000000..06bf23a --- /dev/null +++ b/original/xatrix/g_items.c @@ -0,0 +1,2448 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +#include "g_local.h" + + +qboolean Pickup_Weapon (edict_t *ent, edict_t *other); +void Use_Weapon (edict_t *ent, gitem_t *inv); +void Use_Weapon2 (edict_t *ent, gitem_t *inv); +void Drop_Weapon (edict_t *ent, gitem_t *inv); + +void Weapon_Blaster (edict_t *ent); +void Weapon_Shotgun (edict_t *ent); +void Weapon_SuperShotgun (edict_t *ent); +void Weapon_Machinegun (edict_t *ent); +void Weapon_Chaingun (edict_t *ent); +void Weapon_HyperBlaster (edict_t *ent); +void Weapon_RocketLauncher (edict_t *ent); +void Weapon_Grenade (edict_t *ent); +void Weapon_GrenadeLauncher (edict_t *ent); +void Weapon_Railgun (edict_t *ent); +void Weapon_BFG (edict_t *ent); +// RAFAEL +void Weapon_Ionripper (edict_t *ent); +void Weapon_Phalanx (edict_t *ent); +void Weapon_Trap (edict_t *ent); + +gitem_armor_t jacketarmor_info = { 25, 50, .30, .00, ARMOR_JACKET}; +gitem_armor_t combatarmor_info = { 50, 100, .60, .30, ARMOR_COMBAT}; +gitem_armor_t bodyarmor_info = {100, 200, .80, .60, ARMOR_BODY}; + +static int jacket_armor_index; +static int combat_armor_index; +static int body_armor_index; +static int power_screen_index; +static int power_shield_index; + +#define HEALTH_IGNORE_MAX 1 +#define HEALTH_TIMED 2 + +void Use_Quad (edict_t *ent, gitem_t *item); +// RAFAEL +void Use_QuadFire (edict_t *ent, gitem_t *item); + +static int quad_drop_timeout_hack; +// RAFAEL +static int quad_fire_drop_timeout_hack; + +//====================================================================== + +/* +=============== +GetItemByIndex +=============== +*/ +gitem_t *GetItemByIndex (int index) +{ + if (index == 0 || index >= game.num_items) + return NULL; + + return &itemlist[index]; +} + + +/* +=============== +FindItemByClassname + +=============== +*/ +gitem_t *FindItemByClassname (char *classname) +{ + int i; + gitem_t *it; + + it = itemlist; + for (i=0 ; iclassname) + continue; + if (!Q_stricmp(it->classname, classname)) + return it; + } + + return NULL; +} + +/* +=============== +FindItem + +=============== +*/ +gitem_t *FindItem (char *pickup_name) +{ + int i; + gitem_t *it; + + it = itemlist; + for (i=0 ; ipickup_name) + continue; + if (!Q_stricmp(it->pickup_name, pickup_name)) + return it; + } + + return NULL; +} + +//====================================================================== + +void DoRespawn (edict_t *ent) +{ + if (ent->team) + { + edict_t *master; + int count; + int choice; + + master = ent->teammaster; + + for (count = 0, ent = master; ent; ent = ent->chain, count++) + ; + + choice = rand() % count; + + for (count = 0, ent = master; count < choice; ent = ent->chain, count++) + ; + } + + ent->svflags &= ~SVF_NOCLIENT; + ent->solid = SOLID_TRIGGER; + gi.linkentity (ent); + + // send an effect + ent->s.event = EV_ITEM_RESPAWN; +} + +void SetRespawn (edict_t *ent, float delay) +{ + ent->flags |= FL_RESPAWN; + ent->svflags |= SVF_NOCLIENT; + ent->solid = SOLID_NOT; + ent->nextthink = level.time + delay; + ent->think = DoRespawn; + gi.linkentity (ent); +} + + +//====================================================================== + +qboolean Pickup_Powerup (edict_t *ent, edict_t *other) +{ + int quantity; + + quantity = other->client->pers.inventory[ITEM_INDEX(ent->item)]; + if ((skill->value == 1 && quantity >= 2) || (skill->value >= 2 && quantity >= 1)) + return false; + + if ((coop->value) && (ent->item->flags & IT_STAY_COOP) && (quantity > 0)) + return false; + + other->client->pers.inventory[ITEM_INDEX(ent->item)]++; + + if (deathmatch->value) + { + if (!(ent->spawnflags & DROPPED_ITEM) ) + SetRespawn (ent, ent->item->quantity); + if (((int)dmflags->value & DF_INSTANT_ITEMS) || ((ent->item->use == Use_Quad) && (ent->spawnflags & DROPPED_PLAYER_ITEM))) + { + if ((ent->item->use == Use_Quad) && (ent->spawnflags & DROPPED_PLAYER_ITEM)) + quad_drop_timeout_hack = (ent->nextthink - level.time) / FRAMETIME; + ent->item->use (other, ent->item); + } + // RAFAEL + else if (((int)dmflags->value & DF_INSTANT_ITEMS) || ((ent->item->use == Use_QuadFire) && (ent->spawnflags & DROPPED_PLAYER_ITEM))) + { + if ((ent->item->use == Use_QuadFire) && (ent->spawnflags & DROPPED_PLAYER_ITEM)) + quad_fire_drop_timeout_hack = (ent->nextthink - level.time) / FRAMETIME; + ent->item->use (other, ent->item); + } + } + + return true; +} + +void Drop_General (edict_t *ent, gitem_t *item) +{ + Drop_Item (ent, item); + ent->client->pers.inventory[ITEM_INDEX(item)]--; + ValidateSelectedItem (ent); +} + + +//====================================================================== + +qboolean Pickup_Adrenaline (edict_t *ent, edict_t *other) +{ + if (!deathmatch->value) + other->max_health += 1; + + if (other->health < other->max_health) + other->health = other->max_health; + + if (!(ent->spawnflags & DROPPED_ITEM) && (deathmatch->value)) + SetRespawn (ent, ent->item->quantity); + + return true; +} + +qboolean Pickup_AncientHead (edict_t *ent, edict_t *other) +{ + other->max_health += 2; + + if (!(ent->spawnflags & DROPPED_ITEM) && (deathmatch->value)) + SetRespawn (ent, ent->item->quantity); + + return true; +} + +qboolean Pickup_Bandolier (edict_t *ent, edict_t *other) +{ + gitem_t *item; + int index; + + if (other->client->pers.max_bullets < 250) + other->client->pers.max_bullets = 250; + if (other->client->pers.max_shells < 150) + other->client->pers.max_shells = 150; + if (other->client->pers.max_cells < 250) + other->client->pers.max_cells = 250; + if (other->client->pers.max_slugs < 75) + other->client->pers.max_slugs = 75; + // RAFAEL + if (other->client->pers.max_magslug < 75) + other->client->pers.max_magslug = 75; + + item = FindItem("Bullets"); + if (item) + { + index = ITEM_INDEX(item); + other->client->pers.inventory[index] += item->quantity; + if (other->client->pers.inventory[index] > other->client->pers.max_bullets) + other->client->pers.inventory[index] = other->client->pers.max_bullets; + } + + item = FindItem("Shells"); + if (item) + { + index = ITEM_INDEX(item); + other->client->pers.inventory[index] += item->quantity; + if (other->client->pers.inventory[index] > other->client->pers.max_shells) + other->client->pers.inventory[index] = other->client->pers.max_shells; + } + + if (!(ent->spawnflags & DROPPED_ITEM) && (deathmatch->value)) + SetRespawn (ent, ent->item->quantity); + + return true; +} + +qboolean Pickup_Pack (edict_t *ent, edict_t *other) +{ + gitem_t *item; + int index; + + if (other->client->pers.max_bullets < 300) + other->client->pers.max_bullets = 300; + if (other->client->pers.max_shells < 200) + other->client->pers.max_shells = 200; + if (other->client->pers.max_rockets < 100) + other->client->pers.max_rockets = 100; + if (other->client->pers.max_grenades < 100) + other->client->pers.max_grenades = 100; + if (other->client->pers.max_cells < 300) + other->client->pers.max_cells = 300; + if (other->client->pers.max_slugs < 100) + other->client->pers.max_slugs = 100; + // RAFAEL + if (other->client->pers.max_magslug < 100) + other->client->pers.max_magslug = 100; + + item = FindItem("Bullets"); + if (item) + { + index = ITEM_INDEX(item); + other->client->pers.inventory[index] += item->quantity; + if (other->client->pers.inventory[index] > other->client->pers.max_bullets) + other->client->pers.inventory[index] = other->client->pers.max_bullets; + } + + item = FindItem("Shells"); + if (item) + { + index = ITEM_INDEX(item); + other->client->pers.inventory[index] += item->quantity; + if (other->client->pers.inventory[index] > other->client->pers.max_shells) + other->client->pers.inventory[index] = other->client->pers.max_shells; + } + + item = FindItem("Cells"); + if (item) + { + index = ITEM_INDEX(item); + other->client->pers.inventory[index] += item->quantity; + if (other->client->pers.inventory[index] > other->client->pers.max_cells) + other->client->pers.inventory[index] = other->client->pers.max_cells; + } + + item = FindItem("Grenades"); + if (item) + { + index = ITEM_INDEX(item); + other->client->pers.inventory[index] += item->quantity; + if (other->client->pers.inventory[index] > other->client->pers.max_grenades) + other->client->pers.inventory[index] = other->client->pers.max_grenades; + } + + item = FindItem("Rockets"); + if (item) + { + index = ITEM_INDEX(item); + other->client->pers.inventory[index] += item->quantity; + if (other->client->pers.inventory[index] > other->client->pers.max_rockets) + other->client->pers.inventory[index] = other->client->pers.max_rockets; + } + + item = FindItem("Slugs"); + if (item) + { + index = ITEM_INDEX(item); + other->client->pers.inventory[index] += item->quantity; + if (other->client->pers.inventory[index] > other->client->pers.max_slugs) + other->client->pers.inventory[index] = other->client->pers.max_slugs; + } + + // RAFAEL + item = FindItem ("Mag Slug"); + if (item) + { + index = ITEM_INDEX(item); + other->client->pers.inventory[index] += item->quantity; + if (other->client->pers.inventory[index] > other->client->pers.max_magslug) + other->client->pers.inventory[index] = other->client->pers.max_magslug; + } + + if (!(ent->spawnflags & DROPPED_ITEM) && (deathmatch->value)) + SetRespawn (ent, ent->item->quantity); + + return true; +} + +//====================================================================== + +void Use_Quad (edict_t *ent, gitem_t *item) +{ + int timeout; + + ent->client->pers.inventory[ITEM_INDEX(item)]--; + ValidateSelectedItem (ent); + + if (quad_drop_timeout_hack) + { + timeout = quad_drop_timeout_hack; + quad_drop_timeout_hack = 0; + } + else + { + timeout = 300; + } + + if (ent->client->quad_framenum > level.framenum) + ent->client->quad_framenum += timeout; + else + ent->client->quad_framenum = level.framenum + timeout; + + gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage.wav"), 1, ATTN_NORM, 0); +} + + +// ===================================================================== + +// RAFAEL +void Use_QuadFire (edict_t *ent, gitem_t *item) +{ + int timeout; + + ent->client->pers.inventory[ITEM_INDEX(item)]--; + ValidateSelectedItem (ent); + + if (quad_fire_drop_timeout_hack) + { + timeout = quad_fire_drop_timeout_hack; + quad_fire_drop_timeout_hack = 0; + } + else + { + timeout = 300; + } + + if (ent->client->quadfire_framenum > level.framenum) + ent->client->quadfire_framenum += timeout; + else + ent->client->quadfire_framenum = level.framenum + timeout; + + gi.sound(ent, CHAN_ITEM, gi.soundindex("items/quadfire1.wav"), 1, ATTN_NORM, 0); +} + + +//====================================================================== + +void Use_Breather (edict_t *ent, gitem_t *item) +{ + ent->client->pers.inventory[ITEM_INDEX(item)]--; + ValidateSelectedItem (ent); + + if (ent->client->breather_framenum > level.framenum) + ent->client->breather_framenum += 300; + else + ent->client->breather_framenum = level.framenum + 300; + +// gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage.wav"), 1, ATTN_NORM, 0); +} + +//====================================================================== + +void Use_Envirosuit (edict_t *ent, gitem_t *item) +{ + ent->client->pers.inventory[ITEM_INDEX(item)]--; + ValidateSelectedItem (ent); + + if (ent->client->enviro_framenum > level.framenum) + ent->client->enviro_framenum += 300; + else + ent->client->enviro_framenum = level.framenum + 300; + +// gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage.wav"), 1, ATTN_NORM, 0); +} + +//====================================================================== + +void Use_Invulnerability (edict_t *ent, gitem_t *item) +{ + ent->client->pers.inventory[ITEM_INDEX(item)]--; + ValidateSelectedItem (ent); + + if (ent->client->invincible_framenum > level.framenum) + ent->client->invincible_framenum += 300; + else + ent->client->invincible_framenum = level.framenum + 300; + + gi.sound(ent, CHAN_ITEM, gi.soundindex("items/protect.wav"), 1, ATTN_NORM, 0); +} + +//====================================================================== + +void Use_Silencer (edict_t *ent, gitem_t *item) +{ + ent->client->pers.inventory[ITEM_INDEX(item)]--; + ValidateSelectedItem (ent); + ent->client->silencer_shots += 30; + +// gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage.wav"), 1, ATTN_NORM, 0); +} + +//====================================================================== + +qboolean Pickup_Key (edict_t *ent, edict_t *other) +{ + if (coop->value) + { + if (strcmp(ent->classname, "key_power_cube") == 0) + { + if (other->client->pers.power_cubes & ((ent->spawnflags & 0x0000ff00)>> 8)) + return false; + other->client->pers.inventory[ITEM_INDEX(ent->item)]++; + other->client->pers.power_cubes |= ((ent->spawnflags & 0x0000ff00) >> 8); + } + else + { + if (other->client->pers.inventory[ITEM_INDEX(ent->item)]) + return false; + other->client->pers.inventory[ITEM_INDEX(ent->item)] = 1; + } + return true; + } + other->client->pers.inventory[ITEM_INDEX(ent->item)]++; + return true; +} + +//====================================================================== + +qboolean Add_Ammo (edict_t *ent, gitem_t *item, int count) +{ + int index; + int max; + + if (!ent->client) + return false; + + if (item->tag == AMMO_BULLETS) + max = ent->client->pers.max_bullets; + else if (item->tag == AMMO_SHELLS) + max = ent->client->pers.max_shells; + else if (item->tag == AMMO_ROCKETS) + max = ent->client->pers.max_rockets; + else if (item->tag == AMMO_GRENADES) + max = ent->client->pers.max_grenades; + else if (item->tag == AMMO_CELLS) + max = ent->client->pers.max_cells; + else if (item->tag == AMMO_SLUGS) + max = ent->client->pers.max_slugs; + // RAFAEL + else if (item->tag == AMMO_MAGSLUG) + max = ent->client->pers.max_magslug; + // RAFAEL + else if (item->tag == AMMO_TRAP) + max = ent->client->pers.max_trap; + else + return false; + + index = ITEM_INDEX(item); + + if (ent->client->pers.inventory[index] == max) + return false; + + ent->client->pers.inventory[index] += count; + + if (ent->client->pers.inventory[index] > max) + ent->client->pers.inventory[index] = max; + + return true; +} + +qboolean Pickup_Ammo (edict_t *ent, edict_t *other) +{ + int oldcount; + int count; + qboolean weapon; + + weapon = (ent->item->flags & IT_WEAPON); + if ( (weapon) && ( (int)dmflags->value & DF_INFINITE_AMMO ) ) + count = 1000; + else if (ent->count) + count = ent->count; + else + count = ent->item->quantity; + + oldcount = other->client->pers.inventory[ITEM_INDEX(ent->item)]; + + if (!Add_Ammo (other, ent->item, count)) + return false; + + if (weapon && !oldcount) + { + if (other->client->pers.weapon != ent->item && ( !deathmatch->value || other->client->pers.weapon == FindItem("blaster") ) ) + other->client->newweapon = ent->item; + } + + if (!(ent->spawnflags & (DROPPED_ITEM | DROPPED_PLAYER_ITEM)) && (deathmatch->value)) + SetRespawn (ent, 30); + return true; +} + +void Drop_Ammo (edict_t *ent, gitem_t *item) +{ + edict_t *dropped; + int index; + + index = ITEM_INDEX(item); + dropped = Drop_Item (ent, item); + if (ent->client->pers.inventory[index] >= item->quantity) + dropped->count = item->quantity; + else + dropped->count = ent->client->pers.inventory[index]; + + if (ent->client->pers.weapon && + ent->client->pers.weapon->tag == AMMO_GRENADES && + item->tag == AMMO_GRENADES && + ent->client->pers.inventory[index] - dropped->count <= 0) { + gi.cprintf (ent, PRINT_HIGH, "Can't drop current weapon\n"); + G_FreeEdict(dropped); + return; + } + + ent->client->pers.inventory[index] -= dropped->count; + ValidateSelectedItem (ent); +} + + +//====================================================================== + +void MegaHealth_think (edict_t *self) +{ + if (self->owner->health > self->owner->max_health) + { + self->nextthink = level.time + 1; + self->owner->health -= 1; + return; + } + + if (!(self->spawnflags & DROPPED_ITEM) && (deathmatch->value)) + SetRespawn (self, 20); + else + G_FreeEdict (self); +} + +qboolean Pickup_Health (edict_t *ent, edict_t *other) +{ + if (!(ent->style & HEALTH_IGNORE_MAX)) + if (other->health >= other->max_health) + return false; + + other->health += ent->count; + + if (!(ent->style & HEALTH_IGNORE_MAX)) + { + if (other->health > other->max_health) + other->health = other->max_health; + } + + if (ent->style & HEALTH_TIMED) + { + ent->think = MegaHealth_think; + ent->nextthink = level.time + 5; + ent->owner = other; + ent->flags |= FL_RESPAWN; + ent->svflags |= SVF_NOCLIENT; + ent->solid = SOLID_NOT; + } + else + { + if (!(ent->spawnflags & DROPPED_ITEM) && (deathmatch->value)) + SetRespawn (ent, 30); + } + + return true; +} + +//====================================================================== + +int ArmorIndex (edict_t *ent) +{ + if (!ent->client) + return 0; + + if (ent->client->pers.inventory[jacket_armor_index] > 0) + return jacket_armor_index; + + if (ent->client->pers.inventory[combat_armor_index] > 0) + return combat_armor_index; + + if (ent->client->pers.inventory[body_armor_index] > 0) + return body_armor_index; + + return 0; +} + +qboolean Pickup_Armor (edict_t *ent, edict_t *other) +{ + int old_armor_index; + gitem_armor_t *oldinfo; + gitem_armor_t *newinfo; + int newcount; + float salvage; + int salvagecount; + + // get info on new armor + newinfo = (gitem_armor_t *)ent->item->info; + + old_armor_index = ArmorIndex (other); + + // handle armor shards specially + if (ent->item->tag == ARMOR_SHARD) + { + if (!old_armor_index) + other->client->pers.inventory[jacket_armor_index] = 2; + else + other->client->pers.inventory[old_armor_index] += 2; + } + + // if player has no armor, just use it + else if (!old_armor_index) + { + other->client->pers.inventory[ITEM_INDEX(ent->item)] = newinfo->base_count; + } + + // use the better armor + else + { + // get info on old armor + if (old_armor_index == jacket_armor_index) + oldinfo = &jacketarmor_info; + else if (old_armor_index == combat_armor_index) + oldinfo = &combatarmor_info; + else // (old_armor_index == body_armor_index) + oldinfo = &bodyarmor_info; + + if (newinfo->normal_protection > oldinfo->normal_protection) + { + // calc new armor values + salvage = oldinfo->normal_protection / newinfo->normal_protection; + salvagecount = salvage * other->client->pers.inventory[old_armor_index]; + newcount = newinfo->base_count + salvagecount; + if (newcount > newinfo->max_count) + newcount = newinfo->max_count; + + // zero count of old armor so it goes away + other->client->pers.inventory[old_armor_index] = 0; + + // change armor to new item with computed value + other->client->pers.inventory[ITEM_INDEX(ent->item)] = newcount; + } + else + { + // calc new armor values + salvage = newinfo->normal_protection / oldinfo->normal_protection; + salvagecount = salvage * newinfo->base_count; + newcount = other->client->pers.inventory[old_armor_index] + salvagecount; + if (newcount > oldinfo->max_count) + newcount = oldinfo->max_count; + + // if we're already maxed out then we don't need the new armor + if (other->client->pers.inventory[old_armor_index] >= newcount) + return false; + + // update current armor value + other->client->pers.inventory[old_armor_index] = newcount; + } + } + + if (!(ent->spawnflags & DROPPED_ITEM) && (deathmatch->value)) + SetRespawn (ent, 20); + + return true; +} + +//====================================================================== + +int PowerArmorType (edict_t *ent) +{ + if (!ent->client) + return POWER_ARMOR_NONE; + + if (!(ent->flags & FL_POWER_ARMOR)) + return POWER_ARMOR_NONE; + + if (ent->client->pers.inventory[power_shield_index] > 0) + return POWER_ARMOR_SHIELD; + + if (ent->client->pers.inventory[power_screen_index] > 0) + return POWER_ARMOR_SCREEN; + + return POWER_ARMOR_NONE; +} + +void Use_PowerArmor (edict_t *ent, gitem_t *item) +{ + int index; + + if (ent->flags & FL_POWER_ARMOR) + { + ent->flags &= ~FL_POWER_ARMOR; + gi.sound(ent, CHAN_AUTO, gi.soundindex("misc/power2.wav"), 1, ATTN_NORM, 0); + } + else + { + index = ITEM_INDEX(FindItem("cells")); + if (!ent->client->pers.inventory[index]) + { + gi.cprintf (ent, PRINT_HIGH, "No cells for power armor.\n"); + return; + } + ent->flags |= FL_POWER_ARMOR; + gi.sound(ent, CHAN_AUTO, gi.soundindex("misc/power1.wav"), 1, ATTN_NORM, 0); + } +} + +qboolean Pickup_PowerArmor (edict_t *ent, edict_t *other) +{ + int quantity; + + quantity = other->client->pers.inventory[ITEM_INDEX(ent->item)]; + + other->client->pers.inventory[ITEM_INDEX(ent->item)]++; + + if (deathmatch->value) + { + if (!(ent->spawnflags & DROPPED_ITEM) ) + SetRespawn (ent, ent->item->quantity); + // auto-use for DM only if we didn't already have one + if (!quantity) + ent->item->use (other, ent->item); + } + + return true; +} + +void Drop_PowerArmor (edict_t *ent, gitem_t *item) +{ + if ((ent->flags & FL_POWER_ARMOR) && (ent->client->pers.inventory[ITEM_INDEX(item)] == 1)) + Use_PowerArmor (ent, item); + Drop_General (ent, item); +} + +//====================================================================== + +/* +=============== +Touch_Item +=============== +*/ +void Touch_Item (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + qboolean taken; + + if (!other->client) + return; + if (other->health < 1) + return; // dead people can't pickup + if (!ent->item->pickup) + return; // not a grabbable item? + + taken = ent->item->pickup(ent, other); + + if (taken) + { + // flash the screen + other->client->bonus_alpha = 0.25; + + // show icon and name on status bar + other->client->ps.stats[STAT_PICKUP_ICON] = gi.imageindex(ent->item->icon); + other->client->ps.stats[STAT_PICKUP_STRING] = CS_ITEMS+ITEM_INDEX(ent->item); + other->client->pickup_msg_time = level.time + 3.0; + + // change selected item + if (ent->item->use) + other->client->pers.selected_item = other->client->ps.stats[STAT_SELECTED_ITEM] = ITEM_INDEX(ent->item); + + if (ent->item->pickup == Pickup_Health) + { + if (ent->count == 2) + gi.sound(other, CHAN_ITEM, gi.soundindex("items/s_health.wav"), 1, ATTN_NORM, 0); + else if (ent->count == 10) + gi.sound(other, CHAN_ITEM, gi.soundindex("items/n_health.wav"), 1, ATTN_NORM, 0); + else if (ent->count == 25) + gi.sound(other, CHAN_ITEM, gi.soundindex("items/l_health.wav"), 1, ATTN_NORM, 0); + else // (ent->count == 100) + gi.sound(other, CHAN_ITEM, gi.soundindex("items/m_health.wav"), 1, ATTN_NORM, 0); + } + else if (ent->item->pickup_sound) + { + gi.sound(other, CHAN_ITEM, gi.soundindex(ent->item->pickup_sound), 1, ATTN_NORM, 0); + } + } + + if (!(ent->spawnflags & ITEM_TARGETS_USED)) + { + G_UseTargets (ent, other); + ent->spawnflags |= ITEM_TARGETS_USED; + } + + if (!taken) + return; + + if (!((coop->value) && (ent->item->flags & IT_STAY_COOP)) || (ent->spawnflags & (DROPPED_ITEM | DROPPED_PLAYER_ITEM))) + { + if (ent->flags & FL_RESPAWN) + ent->flags &= ~FL_RESPAWN; + else + G_FreeEdict (ent); + } +} + +//====================================================================== + +static void drop_temp_touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (other == ent->owner) + return; + + Touch_Item (ent, other, plane, surf); +} + +static void drop_make_touchable (edict_t *ent) +{ + ent->touch = Touch_Item; + if (deathmatch->value) + { + ent->nextthink = level.time + 29; + ent->think = G_FreeEdict; + } +} + +edict_t *Drop_Item (edict_t *ent, gitem_t *item) +{ + edict_t *dropped; + vec3_t forward, right; + vec3_t offset; + + dropped = G_Spawn(); + + dropped->classname = item->classname; + dropped->item = item; + dropped->spawnflags = DROPPED_ITEM; + dropped->s.effects = item->world_model_flags; + dropped->s.renderfx = RF_GLOW; + VectorSet (dropped->mins, -15, -15, -15); + VectorSet (dropped->maxs, 15, 15, 15); + gi.setmodel (dropped, dropped->item->world_model); + dropped->solid = SOLID_TRIGGER; + dropped->movetype = MOVETYPE_TOSS; + dropped->touch = drop_temp_touch; + dropped->owner = ent; + + if (ent->client) + { + trace_t trace; + + AngleVectors (ent->client->v_angle, forward, right, NULL); + VectorSet(offset, 24, 0, -16); + G_ProjectSource (ent->s.origin, offset, forward, right, dropped->s.origin); + trace = gi.trace (ent->s.origin, dropped->mins, dropped->maxs, + dropped->s.origin, ent, CONTENTS_SOLID); + VectorCopy (trace.endpos, dropped->s.origin); + } + else + { + AngleVectors (ent->s.angles, forward, right, NULL); + VectorCopy (ent->s.origin, dropped->s.origin); + } + + VectorScale (forward, 100, dropped->velocity); + dropped->velocity[2] = 300; + + dropped->think = drop_make_touchable; + dropped->nextthink = level.time + 1; + + gi.linkentity (dropped); + + return dropped; +} + +void Use_Item (edict_t *ent, edict_t *other, edict_t *activator) +{ + ent->svflags &= ~SVF_NOCLIENT; + ent->use = NULL; + + if (ent->spawnflags & ITEM_NO_TOUCH) + { + ent->solid = SOLID_BBOX; + ent->touch = NULL; + } + else + { + ent->solid = SOLID_TRIGGER; + ent->touch = Touch_Item; + } + + gi.linkentity (ent); +} + +//====================================================================== + +/* +================ +droptofloor +================ +*/ +void droptofloor (edict_t *ent) +{ + trace_t tr; + vec3_t dest; + float *v; + + v = tv(-15,-15,-15); + VectorCopy (v, ent->mins); + v = tv(15,15,15); + VectorCopy (v, ent->maxs); + + if (ent->model) + gi.setmodel (ent, ent->model); + else + gi.setmodel (ent, ent->item->world_model); + ent->solid = SOLID_TRIGGER; + ent->movetype = MOVETYPE_TOSS; + ent->touch = Touch_Item; + + v = tv(0,0,-128); + VectorAdd (ent->s.origin, v, dest); + + tr = gi.trace (ent->s.origin, ent->mins, ent->maxs, dest, ent, MASK_SOLID); + if (tr.startsolid) + { + // RAFAEL + if (strcmp (ent->classname, "foodcube") == 0) + { + VectorCopy (ent->s.origin, tr.endpos); + ent->velocity[2] = 0; + } + else + { + gi.dprintf ("droptofloor: %s startsolid at %s\n", ent->classname, vtos(ent->s.origin)); + G_FreeEdict (ent); + return; + } + } + + VectorCopy (tr.endpos, ent->s.origin); + + if (ent->team) + { + ent->flags &= ~FL_TEAMSLAVE; + ent->chain = ent->teamchain; + ent->teamchain = NULL; + + ent->svflags |= SVF_NOCLIENT; + ent->solid = SOLID_NOT; + if (ent == ent->teammaster) + { + ent->nextthink = level.time + FRAMETIME; + ent->think = DoRespawn; + } + } + + if (ent->spawnflags & ITEM_NO_TOUCH) + { + ent->solid = SOLID_BBOX; + ent->touch = NULL; + ent->s.effects &= ~EF_ROTATE; + ent->s.renderfx &= ~RF_GLOW; + } + + if (ent->spawnflags & ITEM_TRIGGER_SPAWN) + { + ent->svflags |= SVF_NOCLIENT; + ent->solid = SOLID_NOT; + ent->use = Use_Item; + } + + gi.linkentity (ent); +} + + +/* +=============== +PrecacheItem + +Precaches all data needed for a given item. +This will be called for each item spawned in a level, +and for each item in each client's inventory. +=============== +*/ +void PrecacheItem (gitem_t *it) +{ + char *s, *start; + char data[MAX_QPATH]; + int len; + gitem_t *ammo; + + if (!it) + return; + + if (it->pickup_sound) + gi.soundindex (it->pickup_sound); + if (it->world_model) + gi.modelindex (it->world_model); + if (it->view_model) + gi.modelindex (it->view_model); + if (it->icon) + gi.imageindex (it->icon); + + // parse everything for its ammo + if (it->ammo && it->ammo[0]) + { + ammo = FindItem (it->ammo); + if (ammo != it) + PrecacheItem (ammo); + } + + // parse the space seperated precache string for other items + s = it->precaches; + if (!s || !s[0]) + return; + + while (*s) + { + start = s; + while (*s && *s != ' ') + s++; + + len = s-start; + if (len >= MAX_QPATH || len < 5) + gi.error ("PrecacheItem: %s has bad precache string", it->classname); + memcpy (data, start, len); + data[len] = 0; + if (*s) + s++; + + // determine type based on extension + if (!strcmp(data+len-3, "md2")) + gi.modelindex (data); + else if (!strcmp(data+len-3, "sp2")) + gi.modelindex (data); + else if (!strcmp(data+len-3, "wav")) + gi.soundindex (data); + if (!strcmp(data+len-3, "pcx")) + gi.imageindex (data); + } +} + +/* +============ +SpawnItem + +Sets the clipping size and plants the object on the floor. + +Items can't be immediately dropped to floor, because they might +be on an entity that hasn't spawned yet. +============ +*/ +void SpawnItem (edict_t *ent, gitem_t *item) +{ + PrecacheItem (item); + + if (ent->spawnflags) + { + if (strcmp(ent->classname, "key_power_cube") != 0) + { + ent->spawnflags = 0; + gi.dprintf("%s at %s has invalid spawnflags set\n", ent->classname, vtos(ent->s.origin)); + } + } + + // some items will be prevented in deathmatch + if (deathmatch->value) + { + if ( (int)dmflags->value & DF_NO_ARMOR ) + { + if (item->pickup == Pickup_Armor || item->pickup == Pickup_PowerArmor) + { + G_FreeEdict (ent); + return; + } + } + if ( (int)dmflags->value & DF_NO_ITEMS ) + { + if (item->pickup == Pickup_Powerup) + { + G_FreeEdict (ent); + return; + } + } + if ( (int)dmflags->value & DF_NO_HEALTH ) + { + if (item->pickup == Pickup_Health || item->pickup == Pickup_Adrenaline || item->pickup == Pickup_AncientHead) + { + G_FreeEdict (ent); + return; + } + } + if ( (int)dmflags->value & DF_INFINITE_AMMO ) + { + if ( (item->flags == IT_AMMO) || (strcmp(ent->classname, "weapon_bfg") == 0) ) + { + G_FreeEdict (ent); + return; + } + } + } + + if (coop->value && (strcmp(ent->classname, "key_power_cube") == 0)) + { + ent->spawnflags |= (1 << (8 + level.power_cubes)); + level.power_cubes++; + } + + // don't let them drop items that stay in a coop game + if ((coop->value) && (item->flags & IT_STAY_COOP)) + { + item->drop = NULL; + } + + ent->item = item; + ent->nextthink = level.time + 2 * FRAMETIME; // items start after other solids + ent->think = droptofloor; + ent->s.effects = item->world_model_flags; + ent->s.renderfx = RF_GLOW; + if (ent->model) + gi.modelindex (ent->model); +} + +//====================================================================== + +gitem_t itemlist[] = +{ + { + NULL + }, // leave index 0 alone + + // + // ARMOR + // + +/*QUAKED item_armor_body (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "item_armor_body", + Pickup_Armor, + NULL, + NULL, + NULL, + "misc/ar1_pkup.wav", + "models/items/armor/body/tris.md2", EF_ROTATE, + NULL, +/* icon */ "i_bodyarmor", +/* pickup */ "Body Armor", +/* width */ 3, + 0, + NULL, + IT_ARMOR, + 0, + &bodyarmor_info, + ARMOR_BODY, +/* precache */ "" + }, + +/*QUAKED item_armor_combat (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "item_armor_combat", + Pickup_Armor, + NULL, + NULL, + NULL, + "misc/ar1_pkup.wav", + "models/items/armor/combat/tris.md2", EF_ROTATE, + NULL, +/* icon */ "i_combatarmor", +/* pickup */ "Combat Armor", +/* width */ 3, + 0, + NULL, + IT_ARMOR, + 0, + &combatarmor_info, + ARMOR_COMBAT, +/* precache */ "" + }, + +/*QUAKED item_armor_jacket (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "item_armor_jacket", + Pickup_Armor, + NULL, + NULL, + NULL, + "misc/ar1_pkup.wav", + "models/items/armor/jacket/tris.md2", EF_ROTATE, + NULL, +/* icon */ "i_jacketarmor", +/* pickup */ "Jacket Armor", +/* width */ 3, + 0, + NULL, + IT_ARMOR, + 0, + &jacketarmor_info, + ARMOR_JACKET, +/* precache */ "" + }, + +/*QUAKED item_armor_shard (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "item_armor_shard", + Pickup_Armor, + NULL, + NULL, + NULL, + "misc/ar2_pkup.wav", + "models/items/armor/shard/tris.md2", EF_ROTATE, + NULL, +/* icon */ "i_jacketarmor", +/* pickup */ "Armor Shard", +/* width */ 3, + 0, + NULL, + IT_ARMOR, + 0, + NULL, + ARMOR_SHARD, +/* precache */ "" + }, + + +/*QUAKED item_power_screen (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "item_power_screen", + Pickup_PowerArmor, + Use_PowerArmor, + Drop_PowerArmor, + NULL, + "misc/ar3_pkup.wav", + "models/items/armor/screen/tris.md2", EF_ROTATE, + NULL, +/* icon */ "i_powerscreen", +/* pickup */ "Power Screen", +/* width */ 0, + 60, + NULL, + IT_ARMOR, + 0, + NULL, + 0, +/* precache */ "" + }, + +/*QUAKED item_power_shield (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "item_power_shield", + Pickup_PowerArmor, + Use_PowerArmor, + Drop_PowerArmor, + NULL, + "misc/ar3_pkup.wav", + "models/items/armor/shield/tris.md2", EF_ROTATE, + NULL, +/* icon */ "i_powershield", +/* pickup */ "Power Shield", +/* width */ 0, + 60, + NULL, + IT_ARMOR, + 0, + NULL, + 0, +/* precache */ "misc/power2.wav misc/power1.wav" + }, + + + // + // WEAPONS + // + +/* weapon_blaster (.3 .3 1) (-16 -16 -16) (16 16 16) +always owned, never in the world +*/ + { + "weapon_blaster", + NULL, + Use_Weapon, + NULL, + Weapon_Blaster, + "misc/w_pkup.wav", + NULL, 0, + "models/weapons/v_blast/tris.md2", +/* icon */ "w_blaster", +/* pickup */ "Blaster", + 0, + 0, + NULL, + IT_WEAPON|IT_STAY_COOP, + WEAP_BLASTER, + NULL, + 0, +/* precache */ "weapons/blastf1a.wav misc/lasfly.wav" + }, + +/*QUAKED weapon_shotgun (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "weapon_shotgun", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_Shotgun, + "misc/w_pkup.wav", + "models/weapons/g_shotg/tris.md2", EF_ROTATE, + "models/weapons/v_shotg/tris.md2", +/* icon */ "w_shotgun", +/* pickup */ "Shotgun", + 0, + 1, + "Shells", + IT_WEAPON|IT_STAY_COOP, + WEAP_SHOTGUN, + NULL, + 0, +/* precache */ "weapons/shotgf1b.wav weapons/shotgr1b.wav" + }, + +/*QUAKED weapon_supershotgun (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "weapon_supershotgun", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_SuperShotgun, + "misc/w_pkup.wav", + "models/weapons/g_shotg2/tris.md2", EF_ROTATE, + "models/weapons/v_shotg2/tris.md2", +/* icon */ "w_sshotgun", +/* pickup */ "Super Shotgun", + 0, + 2, + "Shells", + IT_WEAPON|IT_STAY_COOP, + WEAP_SUPERSHOTGUN, + NULL, + 0, +/* precache */ "weapons/sshotf1b.wav" + }, + +/*QUAKED weapon_machinegun (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "weapon_machinegun", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_Machinegun, + "misc/w_pkup.wav", + "models/weapons/g_machn/tris.md2", EF_ROTATE, + "models/weapons/v_machn/tris.md2", +/* icon */ "w_machinegun", +/* pickup */ "Machinegun", + 0, + 1, + "Bullets", + IT_WEAPON|IT_STAY_COOP, + WEAP_MACHINEGUN, + NULL, + 0, +/* precache */ "weapons/machgf1b.wav weapons/machgf2b.wav weapons/machgf3b.wav weapons/machgf4b.wav weapons/machgf5b.wav" + }, + +/*QUAKED weapon_chaingun (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "weapon_chaingun", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_Chaingun, + "misc/w_pkup.wav", + "models/weapons/g_chain/tris.md2", EF_ROTATE, + "models/weapons/v_chain/tris.md2", +/* icon */ "w_chaingun", +/* pickup */ "Chaingun", + 0, + 1, + "Bullets", + IT_WEAPON|IT_STAY_COOP, + WEAP_CHAINGUN, + NULL, + 0, +/* precache */ "weapons/chngnu1a.wav weapons/chngnl1a.wav weapons/machgf3b.wav` weapons/chngnd1a.wav" + }, + +/*QUAKED ammo_grenades (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "ammo_grenades", + Pickup_Ammo, + Use_Weapon, + Drop_Ammo, + Weapon_Grenade, + "misc/am_pkup.wav", + "models/items/ammo/grenades/medium/tris.md2", 0, + "models/weapons/v_handgr/tris.md2", +/* icon */ "a_grenades", +/* pickup */ "Grenades", +/* width */ 3, + 5, + "grenades", + IT_AMMO|IT_WEAPON, + WEAP_GRENADES, + NULL, + AMMO_GRENADES, +/* precache */ "weapons/hgrent1a.wav weapons/hgrena1b.wav weapons/hgrenc1b.wav weapons/hgrenb1a.wav weapons/hgrenb2a.wav " + }, + +/*QUAKED ammo_trap (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "ammo_trap", + Pickup_Ammo, + Use_Weapon, + Drop_Ammo, + Weapon_Trap, + "misc/am_pkup.wav", + "models/weapons/g_trap/tris.md2", EF_ROTATE, + "models/weapons/v_trap/tris.md2", +/* icon */ "a_trap", +/* pickup */ "Trap", +/* width */ 3, + 1, + "trap", + IT_AMMO|IT_WEAPON, + 0, + NULL, + AMMO_TRAP, +/* precache */ "weapons/trapcock.wav weapons/traploop.wav weapons/trapsuck.wav weapons/trapdown.wav" +// "weapons/hgrent1a.wav weapons/hgrena1b.wav weapons/hgrenc1b.wav weapons/hgrenb1a.wav weapons/hgrenb2a.wav " + }, + + +/*QUAKED weapon_grenadelauncher (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "weapon_grenadelauncher", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_GrenadeLauncher, + "misc/w_pkup.wav", + "models/weapons/g_launch/tris.md2", EF_ROTATE, + "models/weapons/v_launch/tris.md2", +/* icon */ "w_glauncher", +/* pickup */ "Grenade Launcher", + 0, + 1, + "Grenades", + IT_WEAPON|IT_STAY_COOP, + WEAP_GRENADELAUNCHER, + NULL, + 0, +/* precache */ "models/objects/grenade/tris.md2 weapons/grenlf1a.wav weapons/grenlr1b.wav weapons/grenlb1b.wav" + }, + +/*QUAKED weapon_rocketlauncher (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "weapon_rocketlauncher", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_RocketLauncher, + "misc/w_pkup.wav", + "models/weapons/g_rocket/tris.md2", EF_ROTATE, + "models/weapons/v_rocket/tris.md2", +/* icon */ "w_rlauncher", +/* pickup */ "Rocket Launcher", + 0, + 1, + "Rockets", + IT_WEAPON|IT_STAY_COOP, + WEAP_ROCKETLAUNCHER, + NULL, + 0, +/* precache */ "models/objects/rocket/tris.md2 weapons/rockfly.wav weapons/rocklf1a.wav weapons/rocklr1b.wav models/objects/debris2/tris.md2" + }, + +/*QUAKED weapon_hyperblaster (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "weapon_hyperblaster", + Pickup_Weapon, + // RAFAEL + Use_Weapon2, + Drop_Weapon, + Weapon_HyperBlaster, + "misc/w_pkup.wav", + "models/weapons/g_hyperb/tris.md2", EF_ROTATE, + "models/weapons/v_hyperb/tris.md2", +/* icon */ "w_hyperblaster", +/* pickup */ "HyperBlaster", + 0, + 1, + "Cells", + IT_WEAPON|IT_STAY_COOP, + WEAP_HYPERBLASTER, + NULL, + 0, +/* precache */ "weapons/hyprbu1a.wav weapons/hyprbl1a.wav weapons/hyprbf1a.wav weapons/hyprbd1a.wav misc/lasfly.wav" + }, + +/*QUAKED weapon_boomer (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + + { + "weapon_boomer", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_Ionripper, + "misc/w_pkup.wav", + "models/weapons/g_boom/tris.md2", EF_ROTATE, + "models/weapons/v_boomer/tris.md2", +/* icon */ "w_ripper", +/* pickup */ "Ionripper", + 0, + 2, + "Cells", + IT_WEAPON, + WEAP_BOOMER, + NULL, + 0, +/* precache */ "weapons/rg_hum.wav weapons/rippfire.wav" + }, +// END 14-APR-98 + + +/*QUAKED weapon_railgun (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "weapon_railgun", + Pickup_Weapon, + // RAFAEL + Use_Weapon2, + Drop_Weapon, + Weapon_Railgun, + "misc/w_pkup.wav", + "models/weapons/g_rail/tris.md2", EF_ROTATE, + "models/weapons/v_rail/tris.md2", +/* icon */ "w_railgun", +/* pickup */ "Railgun", + 0, + 1, + "Slugs", + IT_WEAPON|IT_STAY_COOP, + WEAP_RAILGUN, + NULL, + 0, +/* precache */ "weapons/rg_hum.wav" + }, + +// RAFAEL 14-APR-98 +/*QUAKED weapon_phalanx (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + + { + "weapon_phalanx", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_Phalanx, + "misc/w_pkup.wav", + "models/weapons/g_shotx/tris.md2", EF_ROTATE, + "models/weapons/v_shotx/tris.md2", +/* icon */ "w_phallanx", +/* pickup */ "Phalanx", + 0, + 1, + "Mag Slug", + IT_WEAPON, + WEAP_PHALANX, + NULL, + 0, +/* precache */ "weapons/plasshot.wav" + }, + + + +/*QUAKED weapon_bfg (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "weapon_bfg", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_BFG, + "misc/w_pkup.wav", + "models/weapons/g_bfg/tris.md2", EF_ROTATE, + "models/weapons/v_bfg/tris.md2", +/* icon */ "w_bfg", +/* pickup */ "BFG10K", + 0, + 50, + "Cells", + IT_WEAPON|IT_STAY_COOP, + WEAP_BFG, + NULL, + 0, +/* precache */ "sprites/s_bfg1.sp2 sprites/s_bfg2.sp2 sprites/s_bfg3.sp2 weapons/bfg__f1y.wav weapons/bfg__l1a.wav weapons/bfg__x1b.wav weapons/bfg_hum.wav" + }, + + // + // AMMO ITEMS + // + +/*QUAKED ammo_shells (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "ammo_shells", + Pickup_Ammo, + NULL, + Drop_Ammo, + NULL, + "misc/am_pkup.wav", + "models/items/ammo/shells/medium/tris.md2", 0, + NULL, +/* icon */ "a_shells", +/* pickup */ "Shells", +/* width */ 3, + 10, + NULL, + IT_AMMO, + 0, + NULL, + AMMO_SHELLS, +/* precache */ "" + }, + +/*QUAKED ammo_bullets (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "ammo_bullets", + Pickup_Ammo, + NULL, + Drop_Ammo, + NULL, + "misc/am_pkup.wav", + "models/items/ammo/bullets/medium/tris.md2", 0, + NULL, +/* icon */ "a_bullets", +/* pickup */ "Bullets", +/* width */ 3, + 50, + NULL, + IT_AMMO, + 0, + NULL, + AMMO_BULLETS, +/* precache */ "" + }, + +/*QUAKED ammo_cells (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "ammo_cells", + Pickup_Ammo, + NULL, + Drop_Ammo, + NULL, + "misc/am_pkup.wav", + "models/items/ammo/cells/medium/tris.md2", 0, + NULL, +/* icon */ "a_cells", +/* pickup */ "Cells", +/* width */ 3, + 50, + NULL, + IT_AMMO, + 0, + NULL, + AMMO_CELLS, +/* precache */ "" + }, + +/*QUAKED ammo_rockets (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "ammo_rockets", + Pickup_Ammo, + NULL, + Drop_Ammo, + NULL, + "misc/am_pkup.wav", + "models/items/ammo/rockets/medium/tris.md2", 0, + NULL, +/* icon */ "a_rockets", +/* pickup */ "Rockets", +/* width */ 3, + 5, + NULL, + IT_AMMO, + 0, + NULL, + AMMO_ROCKETS, +/* precache */ "" + }, + +/*QUAKED ammo_slugs (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "ammo_slugs", + Pickup_Ammo, + NULL, + Drop_Ammo, + NULL, + "misc/am_pkup.wav", + "models/items/ammo/slugs/medium/tris.md2", 0, + NULL, +/* icon */ "a_slugs", +/* pickup */ "Slugs", +/* width */ 3, + 10, + NULL, + IT_AMMO, + 0, + NULL, + AMMO_SLUGS, +/* precache */ "" + }, + + +/*QUAKED ammo_magslug (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "ammo_magslug", + Pickup_Ammo, + NULL, + Drop_Ammo, + NULL, + "misc/am_pkup.wav", + "models/objects/ammo/tris.md2", 0, + NULL, +/* icon */ "a_mslugs", +/* pickup */ "Mag Slug", +/* width */ 3, + 10, + NULL, + IT_AMMO, + 0, + NULL, + AMMO_MAGSLUG, +/* precache */ "" + }, + + // + // POWERUP ITEMS + // +/*QUAKED item_quad (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "item_quad", + Pickup_Powerup, + Use_Quad, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/quaddama/tris.md2", EF_ROTATE, + NULL, +/* icon */ "p_quad", +/* pickup */ "Quad Damage", +/* width */ 2, + 60, + NULL, + IT_POWERUP, + 0, + NULL, + 0, +/* precache */ "items/damage.wav items/damage2.wav items/damage3.wav" + }, + +/*QUAKED item_quadfire (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "item_quadfire", + Pickup_Powerup, + Use_QuadFire, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/quadfire/tris.md2", EF_ROTATE, + NULL, +/* icon */ "p_quadfire", + +/* pickup */ "DualFire Damage", +/* width */ 2, + 60, + NULL, + IT_POWERUP, + 0, + NULL, + 0, +/* precache */ "items/quadfire1.wav items/quadfire2.wav items/quadfire3.wav" + }, + + +/*QUAKED item_invulnerability (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "item_invulnerability", + Pickup_Powerup, + Use_Invulnerability, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/invulner/tris.md2", EF_ROTATE, + NULL, +/* icon */ "p_invulnerability", +/* pickup */ "Invulnerability", +/* width */ 2, + 300, + NULL, + IT_POWERUP, + 0, + NULL, + 0, +/* precache */ "items/protect.wav items/protect2.wav items/protect4.wav" + }, + +/*QUAKED item_silencer (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "item_silencer", + Pickup_Powerup, + Use_Silencer, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/silencer/tris.md2", EF_ROTATE, + NULL, +/* icon */ "p_silencer", +/* pickup */ "Silencer", +/* width */ 2, + 60, + NULL, + IT_POWERUP, + 0, + NULL, + 0, +/* precache */ "" + }, + +/*QUAKED item_breather (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "item_breather", + Pickup_Powerup, + Use_Breather, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/breather/tris.md2", EF_ROTATE, + NULL, +/* icon */ "p_rebreather", +/* pickup */ "Rebreather", +/* width */ 2, + 60, + NULL, + IT_STAY_COOP|IT_POWERUP, + 0, + NULL, + 0, +/* precache */ "items/airout.wav" + }, + +/*QUAKED item_enviro (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "item_enviro", + Pickup_Powerup, + Use_Envirosuit, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/enviro/tris.md2", EF_ROTATE, + NULL, +/* icon */ "p_envirosuit", +/* pickup */ "Environment Suit", +/* width */ 2, + 60, + NULL, + IT_STAY_COOP|IT_POWERUP, + 0, + NULL, + 0, +/* precache */ "items/airout.wav" + }, + +/*QUAKED item_ancient_head (.3 .3 1) (-16 -16 -16) (16 16 16) +Special item that gives +2 to maximum health +*/ + { + "item_ancient_head", + Pickup_AncientHead, + NULL, + NULL, + NULL, + "items/pkup.wav", + "models/items/c_head/tris.md2", EF_ROTATE, + NULL, +/* icon */ "i_fixme", +/* pickup */ "Ancient Head", +/* width */ 2, + 60, + NULL, + 0, + 0, + NULL, + 0, +/* precache */ "" + }, + +/*QUAKED item_adrenaline (.3 .3 1) (-16 -16 -16) (16 16 16) +gives +1 to maximum health +*/ + { + "item_adrenaline", + Pickup_Adrenaline, + NULL, + NULL, + NULL, + "items/pkup.wav", + "models/items/adrenal/tris.md2", EF_ROTATE, + NULL, +/* icon */ "p_adrenaline", +/* pickup */ "Adrenaline", +/* width */ 2, + 60, + NULL, + 0, + 0, + NULL, + 0, +/* precache */ "" + }, + +/*QUAKED item_bandolier (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "item_bandolier", + Pickup_Bandolier, + NULL, + NULL, + NULL, + "items/pkup.wav", + "models/items/band/tris.md2", EF_ROTATE, + NULL, +/* icon */ "p_bandolier", +/* pickup */ "Bandolier", +/* width */ 2, + 60, + NULL, + 0, + 0, + NULL, + 0, +/* precache */ "" + }, + +/*QUAKED item_pack (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "item_pack", + Pickup_Pack, + NULL, + NULL, + NULL, + "items/pkup.wav", + "models/items/pack/tris.md2", EF_ROTATE, + NULL, +/* icon */ "i_pack", +/* pickup */ "Ammo Pack", +/* width */ 2, + 180, + NULL, + 0, + 0, + NULL, + 0, +/* precache */ "" + }, + + // + // KEYS + // +/*QUAKED key_data_cd (0 .5 .8) (-16 -16 -16) (16 16 16) +key for computer centers +*/ + { + "key_data_cd", + Pickup_Key, + NULL, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/keys/data_cd/tris.md2", EF_ROTATE, + NULL, + "k_datacd", + "Data CD", + 2, + 0, + NULL, + IT_STAY_COOP|IT_KEY, + 0, + NULL, + 0, +/* precache */ "" + }, + +/*QUAKED key_power_cube (0 .5 .8) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN NO_TOUCH +warehouse circuits +*/ + { + "key_power_cube", + Pickup_Key, + NULL, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/keys/power/tris.md2", EF_ROTATE, + NULL, + "k_powercube", + "Power Cube", + 2, + 0, + NULL, + IT_STAY_COOP|IT_KEY, + 0, + NULL, + 0, +/* precache */ "" + }, + +/*QUAKED key_pyramid (0 .5 .8) (-16 -16 -16) (16 16 16) +key for the entrance of jail3 +*/ + { + "key_pyramid", + Pickup_Key, + NULL, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/keys/pyramid/tris.md2", EF_ROTATE, + NULL, + "k_pyramid", + "Pyramid Key", + 2, + 0, + NULL, + IT_STAY_COOP|IT_KEY, + 0, + NULL, + 0, +/* precache */ "" + }, + +/*QUAKED key_data_spinner (0 .5 .8) (-16 -16 -16) (16 16 16) +key for the city computer +*/ + { + "key_data_spinner", + Pickup_Key, + NULL, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/keys/spinner/tris.md2", EF_ROTATE, + NULL, + "k_dataspin", + "Data Spinner", + 2, + 0, + NULL, + IT_STAY_COOP|IT_KEY, + 0, + NULL, + 0, +/* precache */ "" + }, + +/*QUAKED key_pass (0 .5 .8) (-16 -16 -16) (16 16 16) +security pass for the security level +*/ + { + "key_pass", + Pickup_Key, + NULL, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/keys/pass/tris.md2", EF_ROTATE, + NULL, + "k_security", + "Security Pass", + 2, + 0, + NULL, + IT_STAY_COOP|IT_KEY, + 0, + NULL, + 0, +/* precache */ "" + }, + +/*QUAKED key_blue_key (0 .5 .8) (-16 -16 -16) (16 16 16) +normal door key - blue +*/ + { + "key_blue_key", + Pickup_Key, + NULL, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/keys/key/tris.md2", EF_ROTATE, + NULL, + "k_bluekey", + "Blue Key", + 2, + 0, + NULL, + IT_STAY_COOP|IT_KEY, + 0, + NULL, + 0, +/* precache */ "" + }, + +/*QUAKED key_red_key (0 .5 .8) (-16 -16 -16) (16 16 16) +normal door key - red +*/ + { + "key_red_key", + Pickup_Key, + NULL, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/keys/red_key/tris.md2", EF_ROTATE, + NULL, + "k_redkey", + "Red Key", + 2, + 0, + NULL, + IT_STAY_COOP|IT_KEY, + 0, + NULL, + 0, +/* precache */ "" + }, + + +// RAFAEL +/*QUAKED key_green_key (0 .5 .8) (-16 -16 -16) (16 16 16) +normal door key - blue +*/ + { + "key_green_key", + Pickup_Key, + NULL, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/keys/green_key/tris.md2", EF_ROTATE, + NULL, + "k_green", + "Green Key", + 2, + 0, + NULL, + IT_STAY_COOP|IT_KEY, + 0, + NULL, + 0, +/* precache */ "" + }, + +/*QUAKED key_commander_head (0 .5 .8) (-16 -16 -16) (16 16 16) +tank commander's head +*/ + { + "key_commander_head", + Pickup_Key, + NULL, + Drop_General, + NULL, + "items/pkup.wav", + "models/monsters/commandr/head/tris.md2", EF_GIB, + NULL, +/* icon */ "k_comhead", +/* pickup */ "Commander's Head", +/* width */ 2, + 0, + NULL, + IT_STAY_COOP|IT_KEY, + 0, + NULL, + 0, +/* precache */ "" + }, + +/*QUAKED key_airstrike_target (0 .5 .8) (-16 -16 -16) (16 16 16) +tank commander's head +*/ + { + "key_airstrike_target", + Pickup_Key, + NULL, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/keys/target/tris.md2", EF_ROTATE, + NULL, +/* icon */ "i_airstrike", +/* pickup */ "Airstrike Marker", +/* width */ 2, + 0, + NULL, + IT_STAY_COOP|IT_KEY, + 0, + NULL, + 0, +/* precache */ "" + }, + + { + NULL, + Pickup_Health, + NULL, + NULL, + NULL, + "items/pkup.wav", + NULL, 0, + NULL, +/* icon */ "i_health", +/* pickup */ "Health", +/* width */ 3, + 0, + NULL, + 0, + 0, + NULL, + 0, +/* precache */ "items/s_health.wav items/n_health.wav items/l_health.wav items/m_health.wav" + }, + + // end of list marker + {NULL} +}; + + +/*QUAKED item_health (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ +void SP_item_health (edict_t *self) +{ + if ( deathmatch->value && ((int)dmflags->value & DF_NO_HEALTH) ) + { + G_FreeEdict (self); + return; + } + + self->model = "models/items/healing/medium/tris.md2"; + self->count = 10; + SpawnItem (self, FindItem ("Health")); + gi.soundindex ("items/n_health.wav"); +} + +/*QUAKED item_health_small (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ +void SP_item_health_small (edict_t *self) +{ + if ( deathmatch->value && ((int)dmflags->value & DF_NO_HEALTH) ) + { + G_FreeEdict (self); + return; + } + + self->model = "models/items/healing/stimpack/tris.md2"; + self->count = 2; + SpawnItem (self, FindItem ("Health")); + self->style = HEALTH_IGNORE_MAX; + gi.soundindex ("items/s_health.wav"); +} + +/*QUAKED item_health_large (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ +void SP_item_health_large (edict_t *self) +{ + if ( deathmatch->value && ((int)dmflags->value & DF_NO_HEALTH) ) + { + G_FreeEdict (self); + return; + } + + self->model = "models/items/healing/large/tris.md2"; + self->count = 25; + SpawnItem (self, FindItem ("Health")); + gi.soundindex ("items/l_health.wav"); +} + +/*QUAKED item_health_mega (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ +void SP_item_health_mega (edict_t *self) +{ + if ( deathmatch->value && ((int)dmflags->value & DF_NO_HEALTH) ) + { + G_FreeEdict (self); + return; + } + + self->model = "models/items/mega_h/tris.md2"; + self->count = 100; + SpawnItem (self, FindItem ("Health")); + gi.soundindex ("items/m_health.wav"); + self->style = HEALTH_IGNORE_MAX|HEALTH_TIMED; +} + +// RAFAEL +void SP_item_foodcube (edict_t *self) +{ + if ( deathmatch->value && ((int)dmflags->value & DF_NO_HEALTH) ) + { + G_FreeEdict (self); + return; + } + + self->model = "models/objects/trapfx/tris.md2"; + SpawnItem (self, FindItem ("Health")); + self->spawnflags |= DROPPED_ITEM; + self->style = HEALTH_IGNORE_MAX; + gi.soundindex ("items/s_health.wav"); + self->classname = "foodcube"; +} + + +void InitItems (void) +{ + game.num_items = sizeof(itemlist)/sizeof(itemlist[0]) - 1; +} + + + +/* +=============== +SetItemNames + +Called by worldspawn +=============== +*/ +void SetItemNames (void) +{ + int i; + gitem_t *it; + + for (i=0 ; ipickup_name); + } + + jacket_armor_index = ITEM_INDEX(FindItem("Jacket Armor")); + combat_armor_index = ITEM_INDEX(FindItem("Combat Armor")); + body_armor_index = ITEM_INDEX(FindItem("Body Armor")); + power_screen_index = ITEM_INDEX(FindItem("Power Screen")); + power_shield_index = ITEM_INDEX(FindItem("Power Shield")); +} diff --git a/original/xatrix/g_local.h b/original/xatrix/g_local.h new file mode 100644 index 0000000..5fab874 --- /dev/null +++ b/original/xatrix/g_local.h @@ -0,0 +1,1138 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// g_local.h -- local definitions for game module + +#include "q_shared.h" + +// define GAME_INCLUDE so that game.h does not define the +// short, server-visible gclient_t and edict_t structures, +// because we define the full size ones in this file +#define GAME_INCLUDE +#include "game.h" + +// the "gameversion" client command will print this plus compile date +#define GAMEVERSION "baseq2" + +// protocol bytes that can be directly added to messages +#define svc_muzzleflash 1 +#define svc_muzzleflash2 2 +#define svc_temp_entity 3 +#define svc_layout 4 +#define svc_inventory 5 +#define svc_stufftext 11 + +//================================================================== + +// view pitching times +#define DAMAGE_TIME 0.5 +#define FALL_TIME 0.3 + + +// edict->spawnflags +// these are set with checkboxes on each entity in the map editor +#define SPAWNFLAG_NOT_EASY 0x00000100 +#define SPAWNFLAG_NOT_MEDIUM 0x00000200 +#define SPAWNFLAG_NOT_HARD 0x00000400 +#define SPAWNFLAG_NOT_DEATHMATCH 0x00000800 +#define SPAWNFLAG_NOT_COOP 0x00001000 + +// edict->flags +#define FL_FLY 0x00000001 +#define FL_SWIM 0x00000002 // implied immunity to drowining +#define FL_IMMUNE_LASER 0x00000004 +#define FL_INWATER 0x00000008 +#define FL_GODMODE 0x00000010 +#define FL_NOTARGET 0x00000020 +#define FL_IMMUNE_SLIME 0x00000040 +#define FL_IMMUNE_LAVA 0x00000080 +#define FL_PARTIALGROUND 0x00000100 // not all corners are valid +#define FL_WATERJUMP 0x00000200 // player jumping out of water +#define FL_TEAMSLAVE 0x00000400 // not the first on the team +#define FL_NO_KNOCKBACK 0x00000800 +#define FL_POWER_ARMOR 0x00001000 // power armor (if any) is active +#define FL_RESPAWN 0x80000000 // used for item respawning + + +#define FRAMETIME 0.1 + +// memory tags to allow dynamic memory to be cleaned up +#define TAG_GAME 765 // clear when unloading the dll +#define TAG_LEVEL 766 // clear when loading a new level + + +#define MELEE_DISTANCE 80 + +#define BODY_QUEUE_SIZE 8 + +typedef enum +{ + DAMAGE_NO, + DAMAGE_YES, // will take damage if hit + DAMAGE_AIM // auto targeting recognizes this +} damage_t; + +typedef enum +{ + WEAPON_READY, + WEAPON_ACTIVATING, + WEAPON_DROPPING, + WEAPON_FIRING +} weaponstate_t; + +typedef enum +{ + AMMO_BULLETS, + AMMO_SHELLS, + AMMO_ROCKETS, + AMMO_GRENADES, + AMMO_CELLS, + AMMO_SLUGS, + // RAFAEL + AMMO_MAGSLUG, + AMMO_TRAP +} ammo_t; + + +//deadflag +#define DEAD_NO 0 +#define DEAD_DYING 1 +#define DEAD_DEAD 2 +#define DEAD_RESPAWNABLE 3 + +//range +#define RANGE_MELEE 0 +#define RANGE_NEAR 1 +#define RANGE_MID 2 +#define RANGE_FAR 3 + +//gib types +#define GIB_ORGANIC 0 +#define GIB_METALLIC 1 + +//monster ai flags +#define AI_STAND_GROUND 0x00000001 +#define AI_TEMP_STAND_GROUND 0x00000002 +#define AI_SOUND_TARGET 0x00000004 +#define AI_LOST_SIGHT 0x00000008 +#define AI_PURSUIT_LAST_SEEN 0x00000010 +#define AI_PURSUE_NEXT 0x00000020 +#define AI_PURSUE_TEMP 0x00000040 +#define AI_HOLD_FRAME 0x00000080 +#define AI_GOOD_GUY 0x00000100 +#define AI_BRUTAL 0x00000200 +#define AI_NOSTEP 0x00000400 +#define AI_DUCKED 0x00000800 +#define AI_COMBAT_POINT 0x00001000 +#define AI_MEDIC 0x00002000 +#define AI_RESURRECTING 0x00004000 + +//monster attack state +#define AS_STRAIGHT 1 +#define AS_SLIDING 2 +#define AS_MELEE 3 +#define AS_MISSILE 4 + +// armor types +#define ARMOR_NONE 0 +#define ARMOR_JACKET 1 +#define ARMOR_COMBAT 2 +#define ARMOR_BODY 3 +#define ARMOR_SHARD 4 + +// power armor types +#define POWER_ARMOR_NONE 0 +#define POWER_ARMOR_SCREEN 1 +#define POWER_ARMOR_SHIELD 2 + +// handedness values +#define RIGHT_HANDED 0 +#define LEFT_HANDED 1 +#define CENTER_HANDED 2 + + +// game.serverflags values +#define SFL_CROSS_TRIGGER_1 0x00000001 +#define SFL_CROSS_TRIGGER_2 0x00000002 +#define SFL_CROSS_TRIGGER_3 0x00000004 +#define SFL_CROSS_TRIGGER_4 0x00000008 +#define SFL_CROSS_TRIGGER_5 0x00000010 +#define SFL_CROSS_TRIGGER_6 0x00000020 +#define SFL_CROSS_TRIGGER_7 0x00000040 +#define SFL_CROSS_TRIGGER_8 0x00000080 +#define SFL_CROSS_TRIGGER_MASK 0x000000ff + + +// noise types for PlayerNoise +#define PNOISE_SELF 0 +#define PNOISE_WEAPON 1 +#define PNOISE_IMPACT 2 + + +// edict->movetype values +typedef enum +{ +MOVETYPE_NONE, // never moves +MOVETYPE_NOCLIP, // origin and angles change with no interaction +MOVETYPE_PUSH, // no clip to world, push on box contact +MOVETYPE_STOP, // no clip to world, stops on box contact + +MOVETYPE_WALK, // gravity +MOVETYPE_STEP, // gravity, special edge handling +MOVETYPE_FLY, +MOVETYPE_TOSS, // gravity +MOVETYPE_FLYMISSILE, // extra size to monsters +MOVETYPE_BOUNCE, // added this (the comma at the end of line) +// RAFAEL +MOVETYPE_WALLBOUNCE +} movetype_t; + + + +typedef struct +{ + int base_count; + int max_count; + float normal_protection; + float energy_protection; + int armor; +} gitem_armor_t; + + +// gitem_t->flags +#define IT_WEAPON 1 // use makes active weapon +#define IT_AMMO 2 +#define IT_ARMOR 4 +#define IT_STAY_COOP 8 +#define IT_KEY 16 +#define IT_POWERUP 32 + +// gitem_t->weapmodel for weapons indicates model index +#define WEAP_BLASTER 1 +#define WEAP_SHOTGUN 2 +#define WEAP_SUPERSHOTGUN 3 +#define WEAP_MACHINEGUN 4 +#define WEAP_CHAINGUN 5 +#define WEAP_GRENADES 6 +#define WEAP_GRENADELAUNCHER 7 +#define WEAP_ROCKETLAUNCHER 8 +#define WEAP_HYPERBLASTER 9 +#define WEAP_RAILGUN 10 +#define WEAP_BFG 11 +#define WEAP_PHALANX 12 +#define WEAP_BOOMER 13 + +typedef struct gitem_s +{ + char *classname; // spawning name + qboolean (*pickup)(struct edict_s *ent, struct edict_s *other); + void (*use)(struct edict_s *ent, struct gitem_s *item); + void (*drop)(struct edict_s *ent, struct gitem_s *item); + void (*weaponthink)(struct edict_s *ent); + char *pickup_sound; + char *world_model; + int world_model_flags; + char *view_model; + + // client side info + char *icon; + char *pickup_name; // for printing on pickup + int count_width; // number of digits to display by icon + + int quantity; // for ammo how much, for weapons how much is used per shot + char *ammo; // for weapons + int flags; // IT_* flags + + int weapmodel; // weapon model index (for weapons) + + void *info; + int tag; + + char *precaches; // string of all models, sounds, and images this item will use +} gitem_t; + + + +// +// this structure is left intact through an entire game +// it should be initialized at dll load time, and read/written to +// the server.ssv file for savegames +// +typedef struct +{ + char helpmessage1[512]; + char helpmessage2[512]; + int helpchanged; // flash F1 icon if non 0, play sound + // and increment only if 1, 2, or 3 + + gclient_t *clients; // [maxclients] + + // can't store spawnpoint in level, because + // it would get overwritten by the savegame restore + char spawnpoint[512]; // needed for coop respawns + + // store latched cvars here that we want to get at often + int maxclients; + int maxentities; + + // cross level triggers + int serverflags; + + // items + int num_items; + + qboolean autosaved; +} game_locals_t; + + +// +// this structure is cleared as each map is entered +// it is read/written to the level.sav file for savegames +// +typedef struct +{ + int framenum; + float time; + + char level_name[MAX_QPATH]; // the descriptive name (Outer Base, etc) + char mapname[MAX_QPATH]; // the server name (base1, etc) + char nextmap[MAX_QPATH]; // go here when fraglimit is hit + + // intermission state + float intermissiontime; // time the intermission was started + char *changemap; + int exitintermission; + vec3_t intermission_origin; + vec3_t intermission_angle; + + edict_t *sight_client; // changed once each frame for coop games + + edict_t *sight_entity; + int sight_entity_framenum; + edict_t *sound_entity; + int sound_entity_framenum; + edict_t *sound2_entity; + int sound2_entity_framenum; + + int pic_health; + + int total_secrets; + int found_secrets; + + int total_goals; + int found_goals; + + int total_monsters; + int killed_monsters; + + edict_t *current_entity; // entity running from G_RunFrame + int body_que; // dead bodies + + int power_cubes; // ugly necessity for coop +} level_locals_t; + + +// spawn_temp_t is only used to hold entity field values that +// can be set from the editor, but aren't actualy present +// in edict_t during gameplay +typedef struct +{ + // world vars + char *sky; + float skyrotate; + vec3_t skyaxis; + char *nextmap; + + int lip; + int distance; + int height; + char *noise; + float pausetime; + char *item; + char *gravity; + + float minyaw; + float maxyaw; + float minpitch; + float maxpitch; +} spawn_temp_t; + + +typedef struct +{ + // fixed data + vec3_t start_origin; + vec3_t start_angles; + vec3_t end_origin; + vec3_t end_angles; + + int sound_start; + int sound_middle; + int sound_end; + + float accel; + float speed; + float decel; + float distance; + + float wait; + + // state data + int state; + vec3_t dir; + float current_speed; + float move_speed; + float next_speed; + float remaining_distance; + float decel_distance; + void (*endfunc)(edict_t *); +} moveinfo_t; + + +typedef struct +{ + void (*aifunc)(edict_t *self, float dist); + float dist; + void (*thinkfunc)(edict_t *self); +} mframe_t; + +typedef struct +{ + int firstframe; + int lastframe; + mframe_t *frame; + void (*endfunc)(edict_t *self); +} mmove_t; + +typedef struct +{ + mmove_t *currentmove; + int aiflags; + int nextframe; + float scale; + + void (*stand)(edict_t *self); + void (*idle)(edict_t *self); + void (*search)(edict_t *self); + void (*walk)(edict_t *self); + void (*run)(edict_t *self); + void (*dodge)(edict_t *self, edict_t *other, float eta); + void (*attack)(edict_t *self); + void (*melee)(edict_t *self); + void (*sight)(edict_t *self, edict_t *other); + qboolean (*checkattack)(edict_t *self); + + float pausetime; + float attack_finished; + + vec3_t saved_goal; + float search_time; + float trail_time; + vec3_t last_sighting; + int attack_state; + int lefty; + float idle_time; + int linkcount; + + int power_armor_type; + int power_armor_power; +} monsterinfo_t; + + + +extern game_locals_t game; +extern level_locals_t level; +extern game_import_t gi; +extern game_export_t globals; +extern spawn_temp_t st; + +extern int sm_meat_index; +extern int snd_fry; + +extern int jacket_armor_index; +extern int combat_armor_index; +extern int body_armor_index; + + +// means of death +#define MOD_UNKNOWN 0 +#define MOD_BLASTER 1 +#define MOD_SHOTGUN 2 +#define MOD_SSHOTGUN 3 +#define MOD_MACHINEGUN 4 +#define MOD_CHAINGUN 5 +#define MOD_GRENADE 6 +#define MOD_G_SPLASH 7 +#define MOD_ROCKET 8 +#define MOD_R_SPLASH 9 +#define MOD_HYPERBLASTER 10 +#define MOD_RAILGUN 11 +#define MOD_BFG_LASER 12 +#define MOD_BFG_BLAST 13 +#define MOD_BFG_EFFECT 14 +#define MOD_HANDGRENADE 15 +#define MOD_HG_SPLASH 16 +#define MOD_WATER 17 +#define MOD_SLIME 18 +#define MOD_LAVA 19 +#define MOD_CRUSH 20 +#define MOD_TELEFRAG 21 +#define MOD_FALLING 22 +#define MOD_SUICIDE 23 +#define MOD_HELD_GRENADE 24 +#define MOD_EXPLOSIVE 25 +#define MOD_BARREL 26 +#define MOD_BOMB 27 +#define MOD_EXIT 28 +#define MOD_SPLASH 29 +#define MOD_TARGET_LASER 30 +#define MOD_TRIGGER_HURT 31 +#define MOD_HIT 32 +#define MOD_TARGET_BLASTER 33 +// RAFAEL 14-APR-98 +#define MOD_RIPPER 34 +#define MOD_PHALANX 35 +#define MOD_BRAINTENTACLE 36 +#define MOD_BLASTOFF 37 +#define MOD_GEKK 38 +#define MOD_TRAP 39 +// END 14-APR-98 +#define MOD_FRIENDLY_FIRE 0x8000000 + +extern int meansOfDeath; + + +extern edict_t *g_edicts; + +#define FOFS(x) (int)&(((edict_t *)0)->x) +#define STOFS(x) (int)&(((spawn_temp_t *)0)->x) +#define LLOFS(x) (int)&(((level_locals_t *)0)->x) +#define CLOFS(x) (int)&(((gclient_t *)0)->x) + +#define random() ((rand () & 0x7fff) / ((float)0x7fff)) +#define crandom() (2.0 * (random() - 0.5)) + +extern cvar_t *maxentities; +extern cvar_t *deathmatch; +extern cvar_t *coop; +extern cvar_t *dmflags; +extern cvar_t *skill; +extern cvar_t *fraglimit; +extern cvar_t *timelimit; +extern cvar_t *password; +extern cvar_t *spectator_password; +extern cvar_t *g_select_empty; +extern cvar_t *dedicated; + +extern cvar_t *filterban; + +extern cvar_t *sv_gravity; +extern cvar_t *sv_maxvelocity; + +extern cvar_t *gun_x, *gun_y, *gun_z; +extern cvar_t *sv_rollspeed; +extern cvar_t *sv_rollangle; + +extern cvar_t *run_pitch; +extern cvar_t *run_roll; +extern cvar_t *bob_up; +extern cvar_t *bob_pitch; +extern cvar_t *bob_roll; + +extern cvar_t *sv_cheats; +extern cvar_t *maxclients; +extern cvar_t *maxspectators; + +extern cvar_t *flood_msgs; +extern cvar_t *flood_persecond; +extern cvar_t *flood_waitdelay; + +extern cvar_t *sv_maplist; + +#define world (&g_edicts[0]) + +// item spawnflags +#define ITEM_TRIGGER_SPAWN 0x00000001 +#define ITEM_NO_TOUCH 0x00000002 +// 6 bits reserved for editor flags +// 8 bits used as power cube id bits for coop games +#define DROPPED_ITEM 0x00010000 +#define DROPPED_PLAYER_ITEM 0x00020000 +#define ITEM_TARGETS_USED 0x00040000 + +// +// fields are needed for spawning from the entity string +// and saving / loading games +// +#define FFL_SPAWNTEMP 1 +#define FFL_NOSPAWN 2 + +typedef enum { + F_INT, + F_FLOAT, + F_LSTRING, // string on disk, pointer in memory, TAG_LEVEL + F_GSTRING, // string on disk, pointer in memory, TAG_GAME + F_VECTOR, + F_ANGLEHACK, + F_EDICT, // index on disk, pointer in memory + F_ITEM, // index on disk, pointer in memory + F_CLIENT, // index on disk, pointer in memory + F_FUNCTION, + F_MMOVE, + F_IGNORE +} fieldtype_t; + +typedef struct +{ + char *name; + int ofs; + fieldtype_t type; + int flags; +} field_t; + + +extern field_t fields[]; +extern gitem_t itemlist[]; + + +// +// g_cmds.c +// +void Cmd_Help_f (edict_t *ent); +void Cmd_Score_f (edict_t *ent); + +// +// g_items.c +// +void PrecacheItem (gitem_t *it); +void InitItems (void); +void SetItemNames (void); +gitem_t *FindItem (char *pickup_name); +gitem_t *FindItemByClassname (char *classname); +#define ITEM_INDEX(x) ((x)-itemlist) +edict_t *Drop_Item (edict_t *ent, gitem_t *item); +void SetRespawn (edict_t *ent, float delay); +void ChangeWeapon (edict_t *ent); +void SpawnItem (edict_t *ent, gitem_t *item); +void Think_Weapon (edict_t *ent); +int ArmorIndex (edict_t *ent); +int PowerArmorType (edict_t *ent); +gitem_t *GetItemByIndex (int index); +qboolean Add_Ammo (edict_t *ent, gitem_t *item, int count); +void Touch_Item (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf); + +// +// g_utils.c +// +qboolean KillBox (edict_t *ent); +void G_ProjectSource (vec3_t point, vec3_t distance, vec3_t forward, vec3_t right, vec3_t result); +edict_t *G_Find (edict_t *from, int fieldofs, char *match); +edict_t *findradius (edict_t *from, vec3_t org, float rad); +edict_t *G_PickTarget (char *targetname); +void G_UseTargets (edict_t *ent, edict_t *activator); +void G_SetMovedir (vec3_t angles, vec3_t movedir); + +void G_InitEdict (edict_t *e); +edict_t *G_Spawn (void); +void G_FreeEdict (edict_t *e); + +void G_TouchTriggers (edict_t *ent); +void G_TouchSolids (edict_t *ent); + +char *G_CopyString (char *in); + +float *tv (float x, float y, float z); +char *vtos (vec3_t v); + +float vectoyaw (vec3_t vec); +void vectoangles (vec3_t vec, vec3_t angles); + +// +// g_combat.c +// +qboolean OnSameTeam (edict_t *ent1, edict_t *ent2); +qboolean CanDamage (edict_t *targ, edict_t *inflictor); +void T_Damage (edict_t *targ, edict_t *inflictor, edict_t *attacker, vec3_t dir, vec3_t point, vec3_t normal, int damage, int knockback, int dflags, int mod); +void T_RadiusDamage (edict_t *inflictor, edict_t *attacker, float damage, edict_t *ignore, float radius, int mod); + +// damage flags +#define DAMAGE_RADIUS 0x00000001 // damage was indirect +#define DAMAGE_NO_ARMOR 0x00000002 // armour does not protect from this damage +#define DAMAGE_ENERGY 0x00000004 // damage is from an energy based weapon +#define DAMAGE_NO_KNOCKBACK 0x00000008 // do not affect velocity, just view angles +#define DAMAGE_BULLET 0x00000010 // damage is from a bullet (used for ricochets) +#define DAMAGE_NO_PROTECTION 0x00000020 // armor, shields, invulnerability, and godmode have no effect + +#define DEFAULT_BULLET_HSPREAD 300 +#define DEFAULT_BULLET_VSPREAD 500 +#define DEFAULT_SHOTGUN_HSPREAD 1000 +#define DEFAULT_SHOTGUN_VSPREAD 500 +#define DEFAULT_DEATHMATCH_SHOTGUN_COUNT 12 +#define DEFAULT_SHOTGUN_COUNT 12 +#define DEFAULT_SSHOTGUN_COUNT 20 + +// +// g_monster.c +// +void monster_fire_bullet (edict_t *self, vec3_t start, vec3_t dir, int damage, int kick, int hspread, int vspread, int flashtype); +void monster_fire_shotgun (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int hspread, int vspread, int count, int flashtype); +void monster_fire_blaster (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int flashtype, int effect); +void monster_fire_grenade (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, int flashtype); +void monster_fire_rocket (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int flashtype); +void monster_fire_railgun (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int flashtype); +void monster_fire_bfg (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, int kick, float damage_radius, int flashtype); +// RAFAEL +void monster_fire_ionripper (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int flashtype, int effect); +void monster_fire_heat (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int flashtype); +void monster_dabeam (edict_t *self); +void monster_fire_blueblaster (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int flashtype, int effect); + + +void M_droptofloor (edict_t *ent); +void monster_think (edict_t *self); +void walkmonster_start (edict_t *self); +void swimmonster_start (edict_t *self); +void flymonster_start (edict_t *self); +void AttackFinished (edict_t *self, float time); +void monster_death_use (edict_t *self); +void M_CatagorizePosition (edict_t *ent); +qboolean M_CheckAttack (edict_t *self); +void M_FlyCheck (edict_t *self); +void M_CheckGround (edict_t *ent); + +// +// g_misc.c +// +void ThrowHead (edict_t *self, char *gibname, int damage, int type); +void ThrowClientHead (edict_t *self, int damage); +void ThrowGib (edict_t *self, char *gibname, int damage, int type); +void BecomeExplosion1(edict_t *self); +// RAFAEL +void ThrowHeadACID (edict_t *self, char *gibname, int damage, int type); +void ThrowGibACID (edict_t *self, char *gibname, int damage, int type); + +// +// g_ai.c +// +void AI_SetSightClient (void); + +void ai_stand (edict_t *self, float dist); +void ai_move (edict_t *self, float dist); +void ai_walk (edict_t *self, float dist); +void ai_turn (edict_t *self, float dist); +void ai_run (edict_t *self, float dist); +void ai_charge (edict_t *self, float dist); +int range (edict_t *self, edict_t *other); + +void FoundTarget (edict_t *self); +qboolean infront (edict_t *self, edict_t *other); +qboolean visible (edict_t *self, edict_t *other); +qboolean FacingIdeal(edict_t *self); + +// +// g_weapon.c +// +void ThrowDebris (edict_t *self, char *modelname, float speed, vec3_t origin); +qboolean fire_hit (edict_t *self, vec3_t aim, int damage, int kick); +void fire_bullet (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int hspread, int vspread, int mod); +void fire_shotgun (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int hspread, int vspread, int count, int mod); +void fire_blaster (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, int effect, qboolean hyper); +void fire_grenade (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, float timer, float damage_radius); +void fire_grenade2 (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, float timer, float damage_radius, qboolean held); +void fire_rocket (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, float damage_radius, int radius_damage); +void fire_rail (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick); +void fire_bfg (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, float damage_radius); +// RAFAEL +void fire_ionripper (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, int effect); +void fire_heat (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, float damage_radius, int radius_damage); +void fire_blueblaster (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, int effect); +void fire_plasma (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, float damage_radius, int radius_damage); +void fire_trap (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, float timer, float damage_radius, qboolean held); + +// +// g_ptrail.c +// +void PlayerTrail_Init (void); +void PlayerTrail_Add (vec3_t spot); +void PlayerTrail_New (vec3_t spot); +edict_t *PlayerTrail_PickFirst (edict_t *self); +edict_t *PlayerTrail_PickNext (edict_t *self); +edict_t *PlayerTrail_LastSpot (void); + +// +// g_client.c +// +void respawn (edict_t *ent); +void BeginIntermission (edict_t *targ); +void PutClientInServer (edict_t *ent); +void InitClientPersistant (gclient_t *client); +void InitClientResp (gclient_t *client); +void InitBodyQue (void); +void ClientBeginServerFrame (edict_t *ent); + +// +// g_player.c +// +void player_pain (edict_t *self, edict_t *other, float kick, int damage); +void player_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point); + +// +// g_svcmds.c +// +void ServerCommand (void); +qboolean SV_FilterPacket (char *from); + +// +// p_view.c +// +void ClientEndServerFrame (edict_t *ent); + +// +// p_hud.c +// +void MoveClientToIntermission (edict_t *client); +void G_SetStats (edict_t *ent); +void G_SetSpectatorStats (edict_t *ent); +void G_CheckChaseStats (edict_t *ent); +void ValidateSelectedItem (edict_t *ent); +void DeathmatchScoreboardMessage (edict_t *client, edict_t *killer); + +// +// g_pweapon.c +// +void PlayerNoise(edict_t *who, vec3_t where, int type); + +// +// m_move.c +// +qboolean M_CheckBottom (edict_t *ent); +qboolean M_walkmove (edict_t *ent, float yaw, float dist); +void M_MoveToGoal (edict_t *ent, float dist); +void M_ChangeYaw (edict_t *ent); + +// +// g_phys.c +// +void G_RunEntity (edict_t *ent); + +// +// g_main.c +// +void SaveClientData (void); +void FetchClientEntData (edict_t *ent); + +// +// g_chase.c +// +void UpdateChaseCam(edict_t *ent); +void ChaseNext(edict_t *ent); +void ChasePrev(edict_t *ent); +void GetChaseTarget(edict_t *ent); + +//============================================================================ + +// client_t->anim_priority +#define ANIM_BASIC 0 // stand / run +#define ANIM_WAVE 1 +#define ANIM_JUMP 2 +#define ANIM_PAIN 3 +#define ANIM_ATTACK 4 +#define ANIM_DEATH 5 +#define ANIM_REVERSE 6 + + +// client data that stays across multiple level loads +typedef struct +{ + char userinfo[MAX_INFO_STRING]; + char netname[16]; + int hand; + + qboolean connected; // a loadgame will leave valid entities that + // just don't have a connection yet + + // values saved and restored from edicts when changing levels + int health; + int max_health; + int savedFlags; + + int selected_item; + int inventory[MAX_ITEMS]; + + // ammo capacities + int max_bullets; + int max_shells; + int max_rockets; + int max_grenades; + int max_cells; + int max_slugs; + // RAFAEL + int max_magslug; + int max_trap; + + gitem_t *weapon; + gitem_t *lastweapon; + + int power_cubes; // used for tracking the cubes in coop games + int score; // for calculating total unit score in coop games + + int game_helpchanged; + int helpchanged; + + qboolean spectator; // client is a spectator +} client_persistant_t; + +// client data that stays across deathmatch respawns +typedef struct +{ + client_persistant_t coop_respawn; // what to set client->pers to on a respawn + int enterframe; // level.framenum the client entered the game + int score; // frags, etc + vec3_t cmd_angles; // angles sent over in the last command + + qboolean spectator; // client is a spectator +} client_respawn_t; + +// this structure is cleared on each PutClientInServer(), +// except for 'client->pers' +struct gclient_s +{ + // known to server + player_state_t ps; // communicated by server to clients + int ping; + + // private to game + client_persistant_t pers; + client_respawn_t resp; + pmove_state_t old_pmove; // for detecting out-of-pmove changes + + qboolean showscores; // set layout stat + qboolean showinventory; // set layout stat + qboolean showhelp; + qboolean showhelpicon; + + int ammo_index; + + int buttons; + int oldbuttons; + int latched_buttons; + + qboolean weapon_thunk; + + gitem_t *newweapon; + + // sum up damage over an entire frame, so + // shotgun blasts give a single big kick + int damage_armor; // damage absorbed by armor + int damage_parmor; // damage absorbed by power armor + int damage_blood; // damage taken out of health + int damage_knockback; // impact damage + vec3_t damage_from; // origin for vector calculation + + float killer_yaw; // when dead, look at killer + + weaponstate_t weaponstate; + vec3_t kick_angles; // weapon kicks + vec3_t kick_origin; + float v_dmg_roll, v_dmg_pitch, v_dmg_time; // damage kicks + float fall_time, fall_value; // for view drop on fall + float damage_alpha; + float bonus_alpha; + vec3_t damage_blend; + vec3_t v_angle; // aiming direction + float bobtime; // so off-ground doesn't change it + vec3_t oldviewangles; + vec3_t oldvelocity; + + float next_drown_time; + int old_waterlevel; + int breather_sound; + + int machinegun_shots; // for weapon raising + + // animation vars + int anim_end; + int anim_priority; + qboolean anim_duck; + qboolean anim_run; + + // powerup timers + float quad_framenum; + float invincible_framenum; + float breather_framenum; + float enviro_framenum; + + qboolean grenade_blew_up; + float grenade_time; + // RAFAEL + float quadfire_framenum; + qboolean trap_blew_up; + float trap_time; + + int silencer_shots; + int weapon_sound; + + float pickup_msg_time; + + float flood_locktill; // locked from talking + float flood_when[10]; // when messages were said + int flood_whenhead; // head pointer for when said + + float respawn_time; // can respawn when time > this + + edict_t *chase_target; // player we are chasing + qboolean update_chase; // need to update chase info? +}; + + +struct edict_s +{ + entity_state_t s; + struct gclient_s *client; // NULL if not a player + // the server expects the first part + // of gclient_s to be a player_state_t + // but the rest of it is opaque + + qboolean inuse; + int linkcount; + + // FIXME: move these fields to a server private sv_entity_t + link_t area; // linked to a division node or leaf + + int num_clusters; // if -1, use headnode instead + int clusternums[MAX_ENT_CLUSTERS]; + int headnode; // unused if num_clusters != -1 + int areanum, areanum2; + + //================================ + + int svflags; + vec3_t mins, maxs; + vec3_t absmin, absmax, size; + solid_t solid; + int clipmask; + edict_t *owner; + + + // DO NOT MODIFY ANYTHING ABOVE THIS, THE SERVER + // EXPECTS THE FIELDS IN THAT ORDER! + + //================================ + int movetype; + int flags; + + char *model; + float freetime; // sv.time when the object was freed + + // + // only used locally in game, not by server + // + char *message; + char *classname; + int spawnflags; + + float timestamp; + + float angle; // set in qe3, -1 = up, -2 = down + char *target; + char *targetname; + char *killtarget; + char *team; + char *pathtarget; + char *deathtarget; + char *combattarget; + edict_t *target_ent; + + float speed, accel, decel; + vec3_t movedir; + vec3_t pos1, pos2; + + vec3_t velocity; + vec3_t avelocity; + int mass; + float air_finished; + float gravity; // per entity gravity multiplier (1.0 is normal) + // use for lowgrav artifact, flares + + edict_t *goalentity; + edict_t *movetarget; + float yaw_speed; + float ideal_yaw; + + float nextthink; + void (*prethink) (edict_t *ent); + void (*think)(edict_t *self); + void (*blocked)(edict_t *self, edict_t *other); //move to moveinfo? + void (*touch)(edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf); + void (*use)(edict_t *self, edict_t *other, edict_t *activator); + void (*pain)(edict_t *self, edict_t *other, float kick, int damage); + void (*die)(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point); + + float touch_debounce_time; // are all these legit? do we need more/less of them? + float pain_debounce_time; + float damage_debounce_time; + float fly_sound_debounce_time; //move to clientinfo + float last_move_time; + + int health; + int max_health; + int gib_health; + int deadflag; + qboolean show_hostile; + + float powerarmor_time; + + char *map; // target_changelevel + + int viewheight; // height above origin where eyesight is determined + int takedamage; + int dmg; + int radius_dmg; + float dmg_radius; + int sounds; //make this a spawntemp var? + int count; + + edict_t *chain; + edict_t *enemy; + edict_t *oldenemy; + edict_t *activator; + edict_t *groundentity; + int groundentity_linkcount; + edict_t *teamchain; + edict_t *teammaster; + + edict_t *mynoise; // can go in client only + edict_t *mynoise2; + + int noise_index; + int noise_index2; + float volume; + float attenuation; + + // timing variables + float wait; + float delay; // before firing targets + float random; + + float teleport_time; + + int watertype; + int waterlevel; + + vec3_t move_origin; + vec3_t move_angles; + + // move this to clientinfo? + int light_level; + + int style; // also used as areaportal number + + gitem_t *item; // for bonus items + + // common data blocks + moveinfo_t moveinfo; + monsterinfo_t monsterinfo; + + // RAFAEL + int orders; +}; + diff --git a/original/xatrix/g_main.c b/original/xatrix/g_main.c new file mode 100644 index 0000000..202f68d --- /dev/null +++ b/original/xatrix/g_main.c @@ -0,0 +1,394 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. + +#include "g_local.h" + +game_locals_t game; +level_locals_t level; +game_import_t gi; +game_export_t globals; +spawn_temp_t st; + +int sm_meat_index; +int snd_fry; +int meansOfDeath; + +edict_t *g_edicts; + +cvar_t *deathmatch; +cvar_t *coop; +cvar_t *dmflags; +cvar_t *skill; +cvar_t *fraglimit; +cvar_t *timelimit; +cvar_t *password; +cvar_t *spectator_password; +cvar_t *maxclients; +cvar_t *maxspectators; +cvar_t *maxentities; +cvar_t *g_select_empty; +cvar_t *dedicated; + +cvar_t *filterban; + +cvar_t *sv_maxvelocity; +cvar_t *sv_gravity; + +cvar_t *sv_rollspeed; +cvar_t *sv_rollangle; +cvar_t *gun_x; +cvar_t *gun_y; +cvar_t *gun_z; + +cvar_t *run_pitch; +cvar_t *run_roll; +cvar_t *bob_up; +cvar_t *bob_pitch; +cvar_t *bob_roll; + +cvar_t *sv_cheats; + +cvar_t *flood_msgs; +cvar_t *flood_persecond; +cvar_t *flood_waitdelay; + +cvar_t *sv_maplist; + +void SpawnEntities (char *mapname, char *entities, char *spawnpoint); +void ClientThink (edict_t *ent, usercmd_t *cmd); +qboolean ClientConnect (edict_t *ent, char *userinfo); +void ClientUserinfoChanged (edict_t *ent, char *userinfo); +void ClientDisconnect (edict_t *ent); +void ClientBegin (edict_t *ent); +void ClientCommand (edict_t *ent); +void RunEntity (edict_t *ent); +void WriteGame (char *filename, qboolean autosave); +void ReadGame (char *filename); +void WriteLevel (char *filename); +void ReadLevel (char *filename); +void InitGame (void); +void G_RunFrame (void); + + +//=================================================================== + + +void ShutdownGame (void) +{ + gi.dprintf ("==== ShutdownGame ====\n"); + + gi.FreeTags (TAG_LEVEL); + gi.FreeTags (TAG_GAME); +} + + +/* +================= +GetGameAPI + +Returns a pointer to the structure with all entry points +and global variables +================= +*/ +game_export_t *GetGameAPI (game_import_t *import) +{ + gi = *import; + + globals.apiversion = GAME_API_VERSION; + globals.Init = InitGame; + globals.Shutdown = ShutdownGame; + globals.SpawnEntities = SpawnEntities; + + globals.WriteGame = WriteGame; + globals.ReadGame = ReadGame; + globals.WriteLevel = WriteLevel; + globals.ReadLevel = ReadLevel; + + globals.ClientThink = ClientThink; + globals.ClientConnect = ClientConnect; + globals.ClientUserinfoChanged = ClientUserinfoChanged; + globals.ClientDisconnect = ClientDisconnect; + globals.ClientBegin = ClientBegin; + globals.ClientCommand = ClientCommand; + + globals.RunFrame = G_RunFrame; + + globals.ServerCommand = ServerCommand; + + globals.edict_size = sizeof(edict_t); + + return &globals; +} + +#ifndef GAME_HARD_LINKED +// this is only here so the functions in q_shared.c and q_shwin.c can link +void Sys_Error (char *error, ...) +{ + va_list argptr; + char text[1024]; + + va_start (argptr, error); + vsprintf (text, error, argptr); + va_end (argptr); + + gi.error (ERR_FATAL, "%s", text); +} + +void Com_Printf (char *msg, ...) +{ + va_list argptr; + char text[1024]; + + va_start (argptr, msg); + vsprintf (text, msg, argptr); + va_end (argptr); + + gi.dprintf ("%s", text); +} + +#endif + +//====================================================================== + + +/* +================= +ClientEndServerFrames +================= +*/ +void ClientEndServerFrames (void) +{ + int i; + edict_t *ent; + + // calc the player views now that all pushing + // and damage has been added + for (i=0 ; ivalue ; i++) + { + ent = g_edicts + 1 + i; + if (!ent->inuse || !ent->client) + continue; + ClientEndServerFrame (ent); + } + +} + +/* +================= +CreateTargetChangeLevel + +Returns the created target changelevel +================= +*/ +edict_t *CreateTargetChangeLevel(char *map) +{ + edict_t *ent; + + ent = G_Spawn (); + ent->classname = "target_changelevel"; + Com_sprintf(level.nextmap, sizeof(level.nextmap), "%s", map); + ent->map = level.nextmap; + return ent; +} + +/* +================= +EndDMLevel + +The timelimit or fraglimit has been exceeded +================= +*/ +void EndDMLevel (void) +{ + edict_t *ent; + char *s, *t, *f; + static const char *seps = " ,\n\r"; + + // stay on same level flag + if ((int)dmflags->value & DF_SAME_LEVEL) + { + BeginIntermission (CreateTargetChangeLevel (level.mapname) ); + return; + } + + // see if it's in the map list + if (*sv_maplist->string) { + s = strdup(sv_maplist->string); + f = NULL; + t = strtok(s, seps); + while (t != NULL) { + if (Q_stricmp(t, level.mapname) == 0) { + // it's in the list, go to the next one + t = strtok(NULL, seps); + if (t == NULL) { // end of list, go to first one + if (f == NULL) // there isn't a first one, same level + BeginIntermission (CreateTargetChangeLevel (level.mapname) ); + else + BeginIntermission (CreateTargetChangeLevel (f) ); + } else + BeginIntermission (CreateTargetChangeLevel (t) ); + free(s); + return; + } + if (!f) + f = t; + t = strtok(NULL, seps); + } + free(s); + } + + if (level.nextmap[0]) // go to a specific map + BeginIntermission (CreateTargetChangeLevel (level.nextmap) ); + else { // search for a changelevel + ent = G_Find (NULL, FOFS(classname), "target_changelevel"); + if (!ent) + { // the map designer didn't include a changelevel, + // so create a fake ent that goes back to the same level + BeginIntermission (CreateTargetChangeLevel (level.mapname) ); + return; + } + BeginIntermission (ent); + } +} + +/* +================= +CheckDMRules +================= +*/ +void CheckDMRules (void) +{ + int i; + gclient_t *cl; + + if (level.intermissiontime) + return; + + if (!deathmatch->value) + return; + + if (timelimit->value) + { + if (level.time >= timelimit->value*60) + { + gi.bprintf (PRINT_HIGH, "Timelimit hit.\n"); + EndDMLevel (); + return; + } + } + + if (fraglimit->value) + { + for (i=0 ; ivalue ; i++) + { + cl = game.clients + i; + if (!g_edicts[i+1].inuse) + continue; + + if (cl->resp.score >= fraglimit->value) + { + gi.bprintf (PRINT_HIGH, "Fraglimit hit.\n"); + EndDMLevel (); + return; + } + } + } +} + + +/* +============= +ExitLevel +============= +*/ +void ExitLevel (void) +{ + int i; + edict_t *ent; + char command [256]; + + Com_sprintf (command, sizeof(command), "gamemap \"%s\"\n", level.changemap); + gi.AddCommandString (command); + level.changemap = NULL; + level.exitintermission = 0; + level.intermissiontime = 0; + ClientEndServerFrames (); + + // clear some things before going to next level + for (i=0 ; ivalue ; i++) + { + ent = g_edicts + 1 + i; + if (!ent->inuse) + continue; + if (ent->health > ent->client->pers.max_health) + ent->health = ent->client->pers.max_health; + } + +} + +/* +================ +G_RunFrame + +Advances the world by 0.1 seconds +================ +*/ +void G_RunFrame (void) +{ + int i; + edict_t *ent; + + level.framenum++; + level.time = level.framenum*FRAMETIME; + + // choose a client for monsters to target this frame + AI_SetSightClient (); + + // exit intermissions + + if (level.exitintermission) + { + ExitLevel (); + return; + } + + // + // treat each object in turn + // even the world gets a chance to think + // + ent = &g_edicts[0]; + for (i=0 ; iinuse) + continue; + + level.current_entity = ent; + + VectorCopy (ent->s.origin, ent->s.old_origin); + + // if the ground entity moved, make sure we are still on it + if ((ent->groundentity) && (ent->groundentity->linkcount != ent->groundentity_linkcount)) + { + ent->groundentity = NULL; + if ( !(ent->flags & (FL_SWIM|FL_FLY)) && (ent->svflags & SVF_MONSTER) ) + { + M_CheckGround (ent); + } + } + + if (i > 0 && i <= maxclients->value) + { + ClientBeginServerFrame (ent); + continue; + } + + G_RunEntity (ent); + } + + // see if it is time to end a deathmatch + CheckDMRules (); + + // build the playerstate_t structures for all players + ClientEndServerFrames (); +} + diff --git a/original/xatrix/g_misc.c b/original/xatrix/g_misc.c new file mode 100644 index 0000000..94bbe75 --- /dev/null +++ b/original/xatrix/g_misc.c @@ -0,0 +1,2179 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// g_misc.c + +#include "g_local.h" + + +/*QUAKED func_group (0 0 0) ? +Used to group brushes together just for editor convenience. +*/ + +//===================================================== + +void Use_Areaportal (edict_t *ent, edict_t *other, edict_t *activator) +{ + ent->count ^= 1; // toggle state +// gi.dprintf ("portalstate: %i = %i\n", ent->style, ent->count); + gi.SetAreaPortalState (ent->style, ent->count); +} + +/*QUAKED func_areaportal (0 0 0) ? + +This is a non-visible object that divides the world into +areas that are seperated when this portal is not activated. +Usually enclosed in the middle of a door. +*/ +void SP_func_areaportal (edict_t *ent) +{ + ent->use = Use_Areaportal; + ent->count = 0; // always start closed; +} + +//===================================================== + + +/* +================= +Misc functions +================= +*/ +void VelocityForDamage (int damage, vec3_t v) +{ + v[0] = 100.0 * crandom(); + v[1] = 100.0 * crandom(); + v[2] = 200.0 + 100.0 * random(); + + if (damage < 50) + VectorScale (v, 0.7, v); + else + VectorScale (v, 1.2, v); +} + +void ClipGibVelocity (edict_t *ent) +{ + if (ent->velocity[0] < -300) + ent->velocity[0] = -300; + else if (ent->velocity[0] > 300) + ent->velocity[0] = 300; + if (ent->velocity[1] < -300) + ent->velocity[1] = -300; + else if (ent->velocity[1] > 300) + ent->velocity[1] = 300; + if (ent->velocity[2] < 200) + ent->velocity[2] = 200; // always some upwards + else if (ent->velocity[2] > 500) + ent->velocity[2] = 500; +} + + +/* +================= +gibs +================= +*/ +void gib_think (edict_t *self) +{ + self->s.frame++; + self->nextthink = level.time + FRAMETIME; + + if (self->s.frame == 10) + { + self->think = G_FreeEdict; + self->nextthink = level.time + 8 + random()*10; + } +} + +void gib_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + vec3_t normal_angles, right; + + if (!self->groundentity) + return; + + self->touch = NULL; + + if (plane) + { + gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/fhit3.wav"), 1, ATTN_NORM, 0); + + vectoangles (plane->normal, normal_angles); + AngleVectors (normal_angles, NULL, right, NULL); + vectoangles (right, self->s.angles); + + if (self->s.modelindex == sm_meat_index) + { + self->s.frame++; + self->think = gib_think; + self->nextthink = level.time + FRAMETIME; + } + } +} + +// RAFAEL 24-APR-98 +// removed acid damage +#if 0 +// RAFAEL +void gib_touchacid (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + vec3_t normal_angles, right; + + if (other->takedamage) + { + T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH); + G_FreeEdict (self); + } + + if (!self->groundentity) + return; + + if (plane) + { + gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/fhit3.wav"), 1, ATTN_NORM, 0); + + vectoangles (plane->normal, normal_angles); + AngleVectors (normal_angles, NULL, right, NULL); + vectoangles (right, self->s.angles); + + if (self->s.modelindex == sm_meat_index) + { + self->s.frame++; + self->think = gib_think; + self->nextthink = level.time + FRAMETIME; + } + } +} +#endif + +void gib_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + G_FreeEdict (self); +} + +void ThrowGib (edict_t *self, char *gibname, int damage, int type) +{ + edict_t *gib; + vec3_t vd; + vec3_t origin; + vec3_t size; + float vscale; + + gib = G_Spawn(); + + VectorScale (self->size, 0.5, size); + VectorAdd (self->absmin, size, origin); + gib->s.origin[0] = origin[0] + crandom() * size[0]; + gib->s.origin[1] = origin[1] + crandom() * size[1]; + gib->s.origin[2] = origin[2] + crandom() * size[2]; + + gi.setmodel (gib, gibname); + gib->solid = SOLID_NOT; + gib->s.effects |= EF_GIB; + gib->flags |= FL_NO_KNOCKBACK; + gib->takedamage = DAMAGE_YES; + gib->die = gib_die; + + if (type == GIB_ORGANIC) + { + gib->movetype = MOVETYPE_TOSS; + gib->touch = gib_touch; + vscale = 0.5; + } + else + { + gib->movetype = MOVETYPE_BOUNCE; + vscale = 1.0; + } + + VelocityForDamage (damage, vd); + VectorMA (self->velocity, vscale, vd, gib->velocity); + ClipGibVelocity (gib); + gib->avelocity[0] = random()*600; + gib->avelocity[1] = random()*600; + gib->avelocity[2] = random()*600; + + gib->think = G_FreeEdict; + gib->nextthink = level.time + 10 + random()*10; + + gi.linkentity (gib); +} + + + +void ThrowHead (edict_t *self, char *gibname, int damage, int type) +{ + vec3_t vd; + float vscale; + + self->s.skinnum = 0; + self->s.frame = 0; + VectorClear (self->mins); + VectorClear (self->maxs); + + self->s.modelindex2 = 0; + gi.setmodel (self, gibname); + self->solid = SOLID_NOT; + self->s.effects |= EF_GIB; + self->s.effects &= ~EF_FLIES; + self->s.sound = 0; + self->flags |= FL_NO_KNOCKBACK; + self->svflags &= ~SVF_MONSTER; + self->takedamage = DAMAGE_YES; + self->die = gib_die; + + if (type == GIB_ORGANIC) + { + self->movetype = MOVETYPE_TOSS; + self->touch = gib_touch; + vscale = 0.5; + } + else + { + self->movetype = MOVETYPE_BOUNCE; + vscale = 1.0; + } + + VelocityForDamage (damage, vd); + VectorMA (self->velocity, vscale, vd, self->velocity); + ClipGibVelocity (self); + + self->avelocity[YAW] = crandom()*600; + + self->think = G_FreeEdict; + self->nextthink = level.time + 10 + random()*10; + + gi.linkentity (self); +} + + +// RAFAEL +void ThrowGibACID (edict_t *self, char *gibname, int damage, int type) +{ + edict_t *gib; + vec3_t vd; + vec3_t origin; + vec3_t size; + float vscale; + + gib = G_Spawn(); + + VectorScale (self->size, 0.5, size); + VectorAdd (self->absmin, size, origin); + gib->s.origin[0] = origin[0] + crandom() * size[0]; + gib->s.origin[1] = origin[1] + crandom() * size[1]; + gib->s.origin[2] = origin[2] + crandom() * size[2]; + + // gi.setmodel (gib, gibname); + gib->s.modelindex = gi.modelindex (gibname); + + gib->clipmask = MASK_SHOT; + gib->solid = SOLID_BBOX; + + gib->s.effects |= EF_GREENGIB; + // note to self check this + gib->s.renderfx |= RF_FULLBRIGHT; + gib->flags |= FL_NO_KNOCKBACK; + gib->takedamage = DAMAGE_YES; + gib->die = gib_die; + gib->dmg = 2; + + if (type == GIB_ORGANIC) + { + gib->movetype = MOVETYPE_TOSS; + // RAFAEL 24-APR-98 + // removed acid damage + //gib->touch = gib_touchacid; + vscale = 3.0; + } + else + { + gib->movetype = MOVETYPE_BOUNCE; + vscale = 1.0; + } + + VelocityForDamage (damage, vd); + VectorMA (self->velocity, vscale, vd, gib->velocity); + ClipGibVelocity (gib); + gib->avelocity[0] = random()*600; + gib->avelocity[1] = random()*600; + gib->avelocity[2] = random()*600; + + gib->think = G_FreeEdict; + gib->nextthink = level.time + 10 + random()*10; + + gi.linkentity (gib); +} + +// RAFAEL +void ThrowHeadACID (edict_t *self, char *gibname, int damage, int type) +{ + vec3_t vd; + float vscale; + + self->s.skinnum = 0; + self->s.frame = 0; + VectorClear (self->mins); + VectorClear (self->maxs); + + self->s.modelindex2 = 0; + gi.setmodel (self, gibname); + + self->clipmask = MASK_SHOT; + self->solid = SOLID_BBOX; + + self->s.effects |= EF_GREENGIB; + self->s.effects &= ~EF_FLIES; + self->s.effects |= RF_FULLBRIGHT; + self->s.sound = 0; + self->flags |= FL_NO_KNOCKBACK; + self->svflags &= ~SVF_MONSTER; + self->takedamage = DAMAGE_YES; + self->die = gib_die; + self->dmg = 2; + + if (type == GIB_ORGANIC) + { + self->movetype = MOVETYPE_TOSS; + // RAFAEL 24-APR-98 + // removed acid damage + // self->touch = gib_touchacid; + vscale = 0.5; + } + else + { + self->movetype = MOVETYPE_BOUNCE; + vscale = 1.0; + } + + VelocityForDamage (damage, vd); + VectorMA (self->velocity, vscale, vd, self->velocity); + ClipGibVelocity (self); + + self->avelocity[YAW] = crandom()*600; + + self->think = G_FreeEdict; + self->nextthink = level.time + 10 + random()*10; + + gi.linkentity (self); +} + + +void ThrowClientHead (edict_t *self, int damage) +{ + vec3_t vd; + char *gibname; + + if (rand()&1) + { + gibname = "models/objects/gibs/head2/tris.md2"; + self->s.skinnum = 1; // second skin is player + } + else + { + gibname = "models/objects/gibs/skull/tris.md2"; + self->s.skinnum = 0; + } + + self->s.origin[2] += 32; + self->s.frame = 0; + gi.setmodel (self, gibname); + VectorSet (self->mins, -16, -16, 0); + VectorSet (self->maxs, 16, 16, 16); + + self->takedamage = DAMAGE_NO; + self->solid = SOLID_NOT; + self->s.effects = EF_GIB; + self->s.sound = 0; + self->flags |= FL_NO_KNOCKBACK; + + self->movetype = MOVETYPE_BOUNCE; + VelocityForDamage (damage, vd); + VectorAdd (self->velocity, vd, self->velocity); + + if (self->client) // bodies in the queue don't have a client anymore + { + self->client->anim_priority = ANIM_DEATH; + self->client->anim_end = self->s.frame; + } + else + { + self->think = NULL; + self->nextthink = 0; + } + + gi.linkentity (self); +} + + +/* +================= +debris +================= +*/ +void debris_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + G_FreeEdict (self); +} + +void ThrowDebris (edict_t *self, char *modelname, float speed, vec3_t origin) +{ + edict_t *chunk; + vec3_t v; + + chunk = G_Spawn(); + VectorCopy (origin, chunk->s.origin); + gi.setmodel (chunk, modelname); + v[0] = 100 * crandom(); + v[1] = 100 * crandom(); + v[2] = 100 + 100 * crandom(); + VectorMA (self->velocity, speed, v, chunk->velocity); + chunk->movetype = MOVETYPE_BOUNCE; + chunk->solid = SOLID_NOT; + chunk->avelocity[0] = random()*600; + chunk->avelocity[1] = random()*600; + chunk->avelocity[2] = random()*600; + chunk->think = G_FreeEdict; + chunk->nextthink = level.time + 5 + random()*5; + chunk->s.frame = 0; + chunk->flags = 0; + chunk->classname = "debris"; + chunk->takedamage = DAMAGE_YES; + chunk->die = debris_die; + gi.linkentity (chunk); +} + + +void BecomeExplosion1 (edict_t *self) +{ + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_EXPLOSION1); + gi.WritePosition (self->s.origin); + gi.multicast (self->s.origin, MULTICAST_PVS); + + G_FreeEdict (self); +} + + +void BecomeExplosion2 (edict_t *self) +{ + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_EXPLOSION2); + gi.WritePosition (self->s.origin); + gi.multicast (self->s.origin, MULTICAST_PVS); + + G_FreeEdict (self); +} + + +/*QUAKED path_corner (.5 .3 0) (-8 -8 -8) (8 8 8) TELEPORT +Target: next path corner +Pathtarget: gets used when an entity that has + this path_corner targeted touches it +*/ + +void path_corner_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + vec3_t v; + edict_t *next; + + if (other->movetarget != self) + return; + + if (other->enemy) + return; + + if (self->pathtarget) + { + char *savetarget; + + savetarget = self->target; + self->target = self->pathtarget; + G_UseTargets (self, other); + self->target = savetarget; + } + + if (self->target) + next = G_PickTarget(self->target); + else + next = NULL; + + if ((next) && (next->spawnflags & 1)) + { + VectorCopy (next->s.origin, v); + v[2] += next->mins[2]; + v[2] -= other->mins[2]; + VectorCopy (v, other->s.origin); + next = G_PickTarget(next->target); + other->s.event = EV_OTHER_TELEPORT; + } + + other->goalentity = other->movetarget = next; + + if (self->wait) + { + other->monsterinfo.pausetime = level.time + self->wait; + other->monsterinfo.stand (other); + return; + } + + if (!other->movetarget) + { + other->monsterinfo.pausetime = level.time + 100000000; + other->monsterinfo.stand (other); + } + else + { + VectorSubtract (other->goalentity->s.origin, other->s.origin, v); + other->ideal_yaw = vectoyaw (v); + } +} + +void SP_path_corner (edict_t *self) +{ + if (!self->targetname) + { + gi.dprintf ("path_corner with no targetname at %s\n", vtos(self->s.origin)); + G_FreeEdict (self); + return; + } + + self->solid = SOLID_TRIGGER; + self->touch = path_corner_touch; + VectorSet (self->mins, -8, -8, -8); + VectorSet (self->maxs, 8, 8, 8); + self->svflags |= SVF_NOCLIENT; + gi.linkentity (self); +} + + +/*QUAKED point_combat (0.5 0.3 0) (-8 -8 -8) (8 8 8) Hold +Makes this the target of a monster and it will head here +when first activated before going after the activator. If +hold is selected, it will stay here. +*/ +void point_combat_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + edict_t *activator; + + if (other->movetarget != self) + return; + + if (self->target) + { + other->target = self->target; + other->goalentity = other->movetarget = G_PickTarget(other->target); + if (!other->goalentity) + { + gi.dprintf("%s at %s target %s does not exist\n", self->classname, vtos(self->s.origin), self->target); + other->movetarget = self; + } + self->target = NULL; + } + else if ((self->spawnflags & 1) && !(other->flags & (FL_SWIM|FL_FLY))) + { + other->monsterinfo.pausetime = level.time + 100000000; + other->monsterinfo.aiflags |= AI_STAND_GROUND; + other->monsterinfo.stand (other); + } + + if (other->movetarget == self) + { + other->target = NULL; + other->movetarget = NULL; + other->goalentity = other->enemy; + other->monsterinfo.aiflags &= ~AI_COMBAT_POINT; + } + + if (self->pathtarget) + { + char *savetarget; + + savetarget = self->target; + self->target = self->pathtarget; + if (other->enemy && other->enemy->client) + activator = other->enemy; + else if (other->oldenemy && other->oldenemy->client) + activator = other->oldenemy; + else if (other->activator && other->activator->client) + activator = other->activator; + else + activator = other; + G_UseTargets (self, activator); + self->target = savetarget; + } +} + +void SP_point_combat (edict_t *self) +{ + if (deathmatch->value) + { + G_FreeEdict (self); + return; + } + self->solid = SOLID_TRIGGER; + self->touch = point_combat_touch; + VectorSet (self->mins, -8, -8, -16); + VectorSet (self->maxs, 8, 8, 16); + self->svflags = SVF_NOCLIENT; + gi.linkentity (self); +}; + + +/*QUAKED viewthing (0 .5 .8) (-8 -8 -8) (8 8 8) +Just for the debugging level. Don't use +*/ +void TH_viewthing(edict_t *ent) +{ + ent->s.frame = (ent->s.frame + 1) % 7; + ent->nextthink = level.time + FRAMETIME; +} + +void SP_viewthing(edict_t *ent) +{ + gi.dprintf ("viewthing spawned\n"); + + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_BBOX; + ent->s.renderfx = RF_FRAMELERP; + VectorSet (ent->mins, -16, -16, -24); + VectorSet (ent->maxs, 16, 16, 32); + ent->s.modelindex = gi.modelindex ("models/objects/banner/tris.md2"); + gi.linkentity (ent); + ent->nextthink = level.time + 0.5; + ent->think = TH_viewthing; + return; +} + + +/*QUAKED info_null (0 0.5 0) (-4 -4 -4) (4 4 4) +Used as a positional target for spotlights, etc. +*/ +void SP_info_null (edict_t *self) +{ + G_FreeEdict (self); +}; + + +/*QUAKED info_notnull (0 0.5 0) (-4 -4 -4) (4 4 4) +Used as a positional target for lightning. +*/ +void SP_info_notnull (edict_t *self) +{ + VectorCopy (self->s.origin, self->absmin); + VectorCopy (self->s.origin, self->absmax); +}; + + +/*QUAKED light (0 1 0) (-8 -8 -8) (8 8 8) START_OFF +Non-displayed light. +Default light value is 300. +Default style is 0. +If targeted, will toggle between on and off. +Default _cone value is 10 (used to set size of light for spotlights) +*/ + +#define START_OFF 1 + +static void light_use (edict_t *self, edict_t *other, edict_t *activator) +{ + if (self->spawnflags & START_OFF) + { + gi.configstring (CS_LIGHTS+self->style, "m"); + self->spawnflags &= ~START_OFF; + } + else + { + gi.configstring (CS_LIGHTS+self->style, "a"); + self->spawnflags |= START_OFF; + } +} + +void SP_light (edict_t *self) +{ + // no targeted lights in deathmatch, because they cause global messages + if (!self->targetname || deathmatch->value) + { + G_FreeEdict (self); + return; + } + + if (self->style >= 32) + { + self->use = light_use; + if (self->spawnflags & START_OFF) + gi.configstring (CS_LIGHTS+self->style, "a"); + else + gi.configstring (CS_LIGHTS+self->style, "m"); + } +} + + +/*QUAKED func_wall (0 .5 .8) ? TRIGGER_SPAWN TOGGLE START_ON ANIMATED ANIMATED_FAST +This is just a solid wall if not inhibited + +TRIGGER_SPAWN the wall will not be present until triggered + it will then blink in to existance; it will + kill anything that was in it's way + +TOGGLE only valid for TRIGGER_SPAWN walls + this allows the wall to be turned on and off + +START_ON only valid for TRIGGER_SPAWN walls + the wall will initially be present +*/ + +void func_wall_use (edict_t *self, edict_t *other, edict_t *activator) +{ + if (self->solid == SOLID_NOT) + { + self->solid = SOLID_BSP; + self->svflags &= ~SVF_NOCLIENT; + KillBox (self); + } + else + { + self->solid = SOLID_NOT; + self->svflags |= SVF_NOCLIENT; + } + gi.linkentity (self); + + if (!(self->spawnflags & 2)) + self->use = NULL; +} + +void SP_func_wall (edict_t *self) +{ + self->movetype = MOVETYPE_PUSH; + gi.setmodel (self, self->model); + + if (self->spawnflags & 8) + self->s.effects |= EF_ANIM_ALL; + if (self->spawnflags & 16) + self->s.effects |= EF_ANIM_ALLFAST; + + // just a wall + if ((self->spawnflags & 7) == 0) + { + self->solid = SOLID_BSP; + gi.linkentity (self); + return; + } + + // it must be TRIGGER_SPAWN + if (!(self->spawnflags & 1)) + { +// gi.dprintf("func_wall missing TRIGGER_SPAWN\n"); + self->spawnflags |= 1; + } + + // yell if the spawnflags are odd + if (self->spawnflags & 4) + { + if (!(self->spawnflags & 2)) + { + gi.dprintf("func_wall START_ON without TOGGLE\n"); + self->spawnflags |= 2; + } + } + + self->use = func_wall_use; + if (self->spawnflags & 4) + { + self->solid = SOLID_BSP; + } + else + { + self->solid = SOLID_NOT; + self->svflags |= SVF_NOCLIENT; + } + gi.linkentity (self); +} + + +/*QUAKED func_object (0 .5 .8) ? TRIGGER_SPAWN ANIMATED ANIMATED_FAST +This is solid bmodel that will fall if it's support it removed. +*/ + +void func_object_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + // only squash thing we fall on top of + if (!plane) + return; + if (plane->normal[2] < 1.0) + return; + if (other->takedamage == DAMAGE_NO) + return; + T_Damage (other, self, self, vec3_origin, self->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH); +} + +void func_object_release (edict_t *self) +{ + self->movetype = MOVETYPE_TOSS; + self->touch = func_object_touch; +} + +void func_object_use (edict_t *self, edict_t *other, edict_t *activator) +{ + self->solid = SOLID_BSP; + self->svflags &= ~SVF_NOCLIENT; + self->use = NULL; + KillBox (self); + func_object_release (self); +} + +void SP_func_object (edict_t *self) +{ + gi.setmodel (self, self->model); + + self->mins[0] += 1; + self->mins[1] += 1; + self->mins[2] += 1; + self->maxs[0] -= 1; + self->maxs[1] -= 1; + self->maxs[2] -= 1; + + if (!self->dmg) + self->dmg = 100; + + if (self->spawnflags == 0) + { + self->solid = SOLID_BSP; + self->movetype = MOVETYPE_PUSH; + self->think = func_object_release; + self->nextthink = level.time + 2 * FRAMETIME; + } + else + { + self->solid = SOLID_NOT; + self->movetype = MOVETYPE_PUSH; + self->use = func_object_use; + self->svflags |= SVF_NOCLIENT; + } + + if (self->spawnflags & 2) + self->s.effects |= EF_ANIM_ALL; + if (self->spawnflags & 4) + self->s.effects |= EF_ANIM_ALLFAST; + + self->clipmask = MASK_MONSTERSOLID; + + gi.linkentity (self); +} + + +/*QUAKED func_explosive (0 .5 .8) ? Trigger_Spawn ANIMATED ANIMATED_FAST +Any brush that you want to explode or break apart. If you want an +ex0plosion, set dmg and it will do a radius explosion of that amount +at the center of the bursh. + +If targeted it will not be shootable. + +health defaults to 100. + +mass defaults to 75. This determines how much debris is emitted when +it explodes. You get one large chunk per 100 of mass (up to 8) and +one small chunk per 25 of mass (up to 16). So 800 gives the most. +*/ +void func_explosive_explode (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + vec3_t origin; + vec3_t chunkorigin; + vec3_t size; + int count; + int mass; + + // bmodel origins are (0 0 0), we need to adjust that here + VectorScale (self->size, 0.5, size); + VectorAdd (self->absmin, size, origin); + VectorCopy (origin, self->s.origin); + + self->takedamage = DAMAGE_NO; + + if (self->dmg) + T_RadiusDamage (self, attacker, self->dmg, NULL, self->dmg+40, MOD_EXPLOSIVE); + + VectorSubtract (self->s.origin, inflictor->s.origin, self->velocity); + VectorNormalize (self->velocity); + VectorScale (self->velocity, 150, self->velocity); + + // start chunks towards the center + VectorScale (size, 0.5, size); + + mass = self->mass; + if (!mass) + mass = 75; + + // big chunks + if (mass >= 100) + { + count = mass / 100; + if (count > 8) + count = 8; + while(count--) + { + chunkorigin[0] = origin[0] + crandom() * size[0]; + chunkorigin[1] = origin[1] + crandom() * size[1]; + chunkorigin[2] = origin[2] + crandom() * size[2]; + ThrowDebris (self, "models/objects/debris1/tris.md2", 1, chunkorigin); + } + } + + // small chunks + count = mass / 25; + if (count > 16) + count = 16; + while(count--) + { + chunkorigin[0] = origin[0] + crandom() * size[0]; + chunkorigin[1] = origin[1] + crandom() * size[1]; + chunkorigin[2] = origin[2] + crandom() * size[2]; + ThrowDebris (self, "models/objects/debris2/tris.md2", 2, chunkorigin); + } + + G_UseTargets (self, attacker); + + if (self->dmg) + BecomeExplosion1 (self); + else + G_FreeEdict (self); +} + +void func_explosive_use(edict_t *self, edict_t *other, edict_t *activator) +{ + func_explosive_explode (self, self, other, self->health, vec3_origin); +} + +void func_explosive_spawn (edict_t *self, edict_t *other, edict_t *activator) +{ + self->solid = SOLID_BSP; + self->svflags &= ~SVF_NOCLIENT; + self->use = NULL; + KillBox (self); + gi.linkentity (self); +} + +void SP_func_explosive (edict_t *self) +{ + if (deathmatch->value) + { // auto-remove for deathmatch + G_FreeEdict (self); + return; + } + + self->movetype = MOVETYPE_PUSH; + + gi.modelindex ("models/objects/debris1/tris.md2"); + gi.modelindex ("models/objects/debris2/tris.md2"); + + gi.setmodel (self, self->model); + + if (self->spawnflags & 1) + { + self->svflags |= SVF_NOCLIENT; + self->solid = SOLID_NOT; + self->use = func_explosive_spawn; + } + else + { + self->solid = SOLID_BSP; + if (self->targetname) + self->use = func_explosive_use; + } + + if (self->spawnflags & 2) + self->s.effects |= EF_ANIM_ALL; + if (self->spawnflags & 4) + self->s.effects |= EF_ANIM_ALLFAST; + + if (self->use != func_explosive_use) + { + if (!self->health) + self->health = 100; + self->die = func_explosive_explode; + self->takedamage = DAMAGE_YES; + } + + gi.linkentity (self); +} + + +/*QUAKED misc_explobox (0 .5 .8) (-16 -16 0) (16 16 40) +Large exploding box. You can override its mass (100), +health (80), and dmg (150). +*/ + +void barrel_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) + +{ + float ratio; + vec3_t v; + + if ((!other->groundentity) || (other->groundentity == self)) + return; + + ratio = (float)other->mass / (float)self->mass; + VectorSubtract (self->s.origin, other->s.origin, v); + M_walkmove (self, vectoyaw(v), 20 * ratio * FRAMETIME); +} + +void barrel_explode (edict_t *self) +{ + vec3_t org; + float spd; + vec3_t save; + + T_RadiusDamage (self, self->activator, self->dmg, NULL, self->dmg+40, MOD_BARREL); + + VectorCopy (self->s.origin, save); + VectorMA (self->absmin, 0.5, self->size, self->s.origin); + + // a few big chunks + spd = 1.5 * (float)self->dmg / 200.0; + org[0] = self->s.origin[0] + crandom() * self->size[0]; + org[1] = self->s.origin[1] + crandom() * self->size[1]; + org[2] = self->s.origin[2] + crandom() * self->size[2]; + ThrowDebris (self, "models/objects/debris1/tris.md2", spd, org); + org[0] = self->s.origin[0] + crandom() * self->size[0]; + org[1] = self->s.origin[1] + crandom() * self->size[1]; + org[2] = self->s.origin[2] + crandom() * self->size[2]; + ThrowDebris (self, "models/objects/debris1/tris.md2", spd, org); + + // bottom corners + spd = 1.75 * (float)self->dmg / 200.0; + VectorCopy (self->absmin, org); + ThrowDebris (self, "models/objects/debris3/tris.md2", spd, org); + VectorCopy (self->absmin, org); + org[0] += self->size[0]; + ThrowDebris (self, "models/objects/debris3/tris.md2", spd, org); + VectorCopy (self->absmin, org); + org[1] += self->size[1]; + ThrowDebris (self, "models/objects/debris3/tris.md2", spd, org); + VectorCopy (self->absmin, org); + org[0] += self->size[0]; + org[1] += self->size[1]; + ThrowDebris (self, "models/objects/debris3/tris.md2", spd, org); + + // a bunch of little chunks + spd = 2 * self->dmg / 200; + org[0] = self->s.origin[0] + crandom() * self->size[0]; + org[1] = self->s.origin[1] + crandom() * self->size[1]; + org[2] = self->s.origin[2] + crandom() * self->size[2]; + ThrowDebris (self, "models/objects/debris2/tris.md2", spd, org); + org[0] = self->s.origin[0] + crandom() * self->size[0]; + org[1] = self->s.origin[1] + crandom() * self->size[1]; + org[2] = self->s.origin[2] + crandom() * self->size[2]; + ThrowDebris (self, "models/objects/debris2/tris.md2", spd, org); + org[0] = self->s.origin[0] + crandom() * self->size[0]; + org[1] = self->s.origin[1] + crandom() * self->size[1]; + org[2] = self->s.origin[2] + crandom() * self->size[2]; + ThrowDebris (self, "models/objects/debris2/tris.md2", spd, org); + org[0] = self->s.origin[0] + crandom() * self->size[0]; + org[1] = self->s.origin[1] + crandom() * self->size[1]; + org[2] = self->s.origin[2] + crandom() * self->size[2]; + ThrowDebris (self, "models/objects/debris2/tris.md2", spd, org); + org[0] = self->s.origin[0] + crandom() * self->size[0]; + org[1] = self->s.origin[1] + crandom() * self->size[1]; + org[2] = self->s.origin[2] + crandom() * self->size[2]; + ThrowDebris (self, "models/objects/debris2/tris.md2", spd, org); + org[0] = self->s.origin[0] + crandom() * self->size[0]; + org[1] = self->s.origin[1] + crandom() * self->size[1]; + org[2] = self->s.origin[2] + crandom() * self->size[2]; + ThrowDebris (self, "models/objects/debris2/tris.md2", spd, org); + org[0] = self->s.origin[0] + crandom() * self->size[0]; + org[1] = self->s.origin[1] + crandom() * self->size[1]; + org[2] = self->s.origin[2] + crandom() * self->size[2]; + ThrowDebris (self, "models/objects/debris2/tris.md2", spd, org); + org[0] = self->s.origin[0] + crandom() * self->size[0]; + org[1] = self->s.origin[1] + crandom() * self->size[1]; + org[2] = self->s.origin[2] + crandom() * self->size[2]; + ThrowDebris (self, "models/objects/debris2/tris.md2", spd, org); + + VectorCopy (save, self->s.origin); + if (self->groundentity) + BecomeExplosion2 (self); + else + BecomeExplosion1 (self); +} + +void barrel_delay (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + self->takedamage = DAMAGE_NO; + self->nextthink = level.time + 2 * FRAMETIME; + self->think = barrel_explode; + self->activator = attacker; +} + +void SP_misc_explobox (edict_t *self) +{ + if (deathmatch->value) + { // auto-remove for deathmatch + G_FreeEdict (self); + return; + } + + gi.modelindex ("models/objects/debris1/tris.md2"); + gi.modelindex ("models/objects/debris2/tris.md2"); + gi.modelindex ("models/objects/debris3/tris.md2"); + + self->solid = SOLID_BBOX; + self->movetype = MOVETYPE_STEP; + + self->model = "models/objects/barrels/tris.md2"; + self->s.modelindex = gi.modelindex (self->model); + VectorSet (self->mins, -16, -16, 0); + VectorSet (self->maxs, 16, 16, 40); + + if (!self->mass) + self->mass = 400; + if (!self->health) + self->health = 10; + if (!self->dmg) + self->dmg = 150; + + self->die = barrel_delay; + self->takedamage = DAMAGE_YES; + self->monsterinfo.aiflags = AI_NOSTEP; + + self->touch = barrel_touch; + + self->think = M_droptofloor; + self->nextthink = level.time + 2 * FRAMETIME; + + gi.linkentity (self); +} + + +// +// miscellaneous specialty items +// + +/*QUAKED misc_blackhole (1 .5 0) (-8 -8 -8) (8 8 8) +*/ + +void misc_blackhole_use (edict_t *ent, edict_t *other, edict_t *activator) +{ + /* + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_BOSSTPORT); + gi.WritePosition (ent->s.origin); + gi.multicast (ent->s.origin, MULTICAST_PVS); + */ + G_FreeEdict (ent); +} + +void misc_blackhole_think (edict_t *self) +{ + if (++self->s.frame < 19) + self->nextthink = level.time + FRAMETIME; + else + { + self->s.frame = 0; + self->nextthink = level.time + FRAMETIME; + } +} + +void SP_misc_blackhole (edict_t *ent) +{ + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_NOT; + VectorSet (ent->mins, -64, -64, 0); + VectorSet (ent->maxs, 64, 64, 8); + ent->s.modelindex = gi.modelindex ("models/objects/black/tris.md2"); + ent->s.renderfx = RF_TRANSLUCENT; + ent->use = misc_blackhole_use; + ent->think = misc_blackhole_think; + ent->nextthink = level.time + 2 * FRAMETIME; + gi.linkentity (ent); +} + +/*QUAKED misc_eastertank (1 .5 0) (-32 -32 -16) (32 32 32) +*/ + +void misc_eastertank_think (edict_t *self) +{ + if (++self->s.frame < 293) + self->nextthink = level.time + FRAMETIME; + else + { + self->s.frame = 254; + self->nextthink = level.time + FRAMETIME; + } +} + +void SP_misc_eastertank (edict_t *ent) +{ + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_BBOX; + VectorSet (ent->mins, -32, -32, -16); + VectorSet (ent->maxs, 32, 32, 32); + ent->s.modelindex = gi.modelindex ("models/monsters/tank/tris.md2"); + ent->s.frame = 254; + ent->think = misc_eastertank_think; + ent->nextthink = level.time + 2 * FRAMETIME; + gi.linkentity (ent); +} + +/*QUAKED misc_easterchick (1 .5 0) (-32 -32 0) (32 32 32) +*/ + + +void misc_easterchick_think (edict_t *self) +{ + if (++self->s.frame < 247) + self->nextthink = level.time + FRAMETIME; + else + { + self->s.frame = 208; + self->nextthink = level.time + FRAMETIME; + } +} + +void SP_misc_easterchick (edict_t *ent) +{ + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_BBOX; + VectorSet (ent->mins, -32, -32, 0); + VectorSet (ent->maxs, 32, 32, 32); + ent->s.modelindex = gi.modelindex ("models/monsters/bitch/tris.md2"); + ent->s.frame = 208; + ent->think = misc_easterchick_think; + ent->nextthink = level.time + 2 * FRAMETIME; + gi.linkentity (ent); +} + +/*QUAKED misc_easterchick2 (1 .5 0) (-32 -32 0) (32 32 32) +*/ + + +void misc_easterchick2_think (edict_t *self) +{ + if (++self->s.frame < 287) + self->nextthink = level.time + FRAMETIME; + else + { + self->s.frame = 248; + self->nextthink = level.time + FRAMETIME; + } +} + +void SP_misc_easterchick2 (edict_t *ent) +{ + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_BBOX; + VectorSet (ent->mins, -32, -32, 0); + VectorSet (ent->maxs, 32, 32, 32); + ent->s.modelindex = gi.modelindex ("models/monsters/bitch/tris.md2"); + ent->s.frame = 248; + ent->think = misc_easterchick2_think; + ent->nextthink = level.time + 2 * FRAMETIME; + gi.linkentity (ent); +} + + +/*QUAKED monster_commander_body (1 .5 0) (-32 -32 0) (32 32 48) +Not really a monster, this is the Tank Commander's decapitated body. +There should be a item_commander_head that has this as it's target. +*/ + +void commander_body_think (edict_t *self) +{ + if (++self->s.frame < 24) + self->nextthink = level.time + FRAMETIME; + else + self->nextthink = 0; + + if (self->s.frame == 22) + gi.sound (self, CHAN_BODY, gi.soundindex ("tank/thud.wav"), 1, ATTN_NORM, 0); +} + +void commander_body_use (edict_t *self, edict_t *other, edict_t *activator) +{ + self->think = commander_body_think; + self->nextthink = level.time + FRAMETIME; + gi.sound (self, CHAN_BODY, gi.soundindex ("tank/pain.wav"), 1, ATTN_NORM, 0); +} + +void commander_body_drop (edict_t *self) +{ + self->movetype = MOVETYPE_TOSS; + self->s.origin[2] += 2; +} + +void SP_monster_commander_body (edict_t *self) +{ + self->movetype = MOVETYPE_NONE; + self->solid = SOLID_BBOX; + self->model = "models/monsters/commandr/tris.md2"; + self->s.modelindex = gi.modelindex (self->model); + VectorSet (self->mins, -32, -32, 0); + VectorSet (self->maxs, 32, 32, 48); + self->use = commander_body_use; + self->takedamage = DAMAGE_YES; + self->flags = FL_GODMODE; + self->s.renderfx |= RF_FRAMELERP; + gi.linkentity (self); + + gi.soundindex ("tank/thud.wav"); + gi.soundindex ("tank/pain.wav"); + + self->think = commander_body_drop; + self->nextthink = level.time + 5 * FRAMETIME; +} + + +/*QUAKED misc_banner (1 .5 0) (-4 -4 -4) (4 4 4) +The origin is the bottom of the banner. +The banner is 128 tall. +*/ +void misc_banner_think (edict_t *ent) +{ + ent->s.frame = (ent->s.frame + 1) % 16; + ent->nextthink = level.time + FRAMETIME; +} + +void SP_misc_banner (edict_t *ent) +{ + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_NOT; + ent->s.modelindex = gi.modelindex ("models/objects/banner/tris.md2"); + ent->s.frame = rand() % 16; + gi.linkentity (ent); + + ent->think = misc_banner_think; + ent->nextthink = level.time + FRAMETIME; +} + +/*QUAKED misc_deadsoldier (1 .5 0) (-16 -16 0) (16 16 16) ON_BACK ON_STOMACH BACK_DECAP FETAL_POS SIT_DECAP IMPALED +This is the dead player model. Comes in 6 exciting different poses! +*/ +void misc_deadsoldier_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + int n; + + if (self->health > -80) + return; + + gi.sound (self, CHAN_BODY, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0); + for (n= 0; n < 4; n++) + ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); + ThrowHead (self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC); +} + +void SP_misc_deadsoldier (edict_t *ent) +{ + if (deathmatch->value) + { // auto-remove for deathmatch + G_FreeEdict (ent); + return; + } + + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_BBOX; + ent->s.modelindex=gi.modelindex ("models/deadbods/dude/tris.md2"); + + // Defaults to frame 0 + if (ent->spawnflags & 2) + ent->s.frame = 1; + else if (ent->spawnflags & 4) + ent->s.frame = 2; + else if (ent->spawnflags & 8) + ent->s.frame = 3; + else if (ent->spawnflags & 16) + ent->s.frame = 4; + else if (ent->spawnflags & 32) + ent->s.frame = 5; + else + ent->s.frame = 0; + + VectorSet (ent->mins, -16, -16, 0); + VectorSet (ent->maxs, 16, 16, 16); + ent->deadflag = DEAD_DEAD; + ent->takedamage = DAMAGE_YES; + ent->svflags |= SVF_MONSTER|SVF_DEADMONSTER; + ent->die = misc_deadsoldier_die; + ent->monsterinfo.aiflags |= AI_GOOD_GUY; + + gi.linkentity (ent); +} + +/*QUAKED misc_viper (1 .5 0) (-16 -16 0) (16 16 32) +This is the Viper for the flyby bombing. +It is trigger_spawned, so you must have something use it for it to show up. +There must be a path for it to follow once it is activated. + +"speed" How fast the Viper should fly +*/ + +extern void train_use (edict_t *self, edict_t *other, edict_t *activator); +extern void func_train_find (edict_t *self); + +void misc_viper_use (edict_t *self, edict_t *other, edict_t *activator) +{ + self->svflags &= ~SVF_NOCLIENT; + self->use = train_use; + train_use (self, other, activator); +} + +void SP_misc_viper (edict_t *ent) +{ + if (!ent->target) + { + gi.dprintf ("misc_viper without a target at %s\n", vtos(ent->absmin)); + G_FreeEdict (ent); + return; + } + + if (!ent->speed) + ent->speed = 300; + + ent->movetype = MOVETYPE_PUSH; + ent->solid = SOLID_NOT; + ent->s.modelindex = gi.modelindex ("models/ships/viper/tris.md2"); + VectorSet (ent->mins, -16, -16, 0); + VectorSet (ent->maxs, 16, 16, 32); + + ent->think = func_train_find; + ent->nextthink = level.time + FRAMETIME; + ent->use = misc_viper_use; + ent->svflags |= SVF_NOCLIENT; + ent->moveinfo.accel = ent->moveinfo.decel = ent->moveinfo.speed = ent->speed; + + gi.linkentity (ent); +} + +/*QUAKED misc_crashviper (1 .5 0) (-176 -120 -24) (176 120 72) +This is a large viper about to crash +*/ +void SP_misc_crashviper (edict_t *ent) +{ + if (!ent->target) + { + gi.dprintf ("misc_viper without a target at %s\n", vtos(ent->absmin)); + G_FreeEdict (ent); + return; + } + + if (!ent->speed) + ent->speed = 300; + + ent->movetype = MOVETYPE_PUSH; + ent->solid = SOLID_NOT; + ent->s.modelindex = gi.modelindex ("models/ships/bigviper/tris.md2"); + VectorSet (ent->mins, -16, -16, 0); + VectorSet (ent->maxs, 16, 16, 32); + + + ent->think = func_train_find; + ent->nextthink = level.time + FRAMETIME; + ent->use = misc_viper_use; + ent->svflags |= SVF_NOCLIENT; + ent->moveinfo.accel = ent->moveinfo.decel = ent->moveinfo.speed = ent->speed; + + gi.linkentity (ent); +} + + +/*QUAKED misc_bigviper (1 .5 0) (-176 -120 -24) (176 120 72) +This is a large stationary viper as seen in Paul's intro +*/ +void SP_misc_bigviper (edict_t *ent) +{ + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_BBOX; + VectorSet (ent->mins, -176, -120, -24); + VectorSet (ent->maxs, 176, 120, 72); + ent->s.modelindex = gi.modelindex ("models/ships/bigviper/tris.md2"); + gi.linkentity (ent); +} + + +/*QUAKED misc_viper_bomb (1 0 0) (-8 -8 -8) (8 8 8) +"dmg" how much boom should the bomb make? +*/ +void misc_viper_bomb_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + G_UseTargets (self, self->activator); + + self->s.origin[2] = self->absmin[2] + 1; + T_RadiusDamage (self, self, self->dmg, NULL, self->dmg+40, MOD_BOMB); + BecomeExplosion2 (self); +} + +void misc_viper_bomb_prethink (edict_t *self) +{ + vec3_t v; + float diff; + + self->groundentity = NULL; + + diff = self->timestamp - level.time; + if (diff < -1.0) + diff = -1.0; + + VectorScale (self->moveinfo.dir, 1.0 + diff, v); + v[2] = diff; + + diff = self->s.angles[2]; + vectoangles (v, self->s.angles); + self->s.angles[2] = diff + 10; +} + +void misc_viper_bomb_use (edict_t *self, edict_t *other, edict_t *activator) +{ + edict_t *viper; + + self->solid = SOLID_BBOX; + self->svflags &= ~SVF_NOCLIENT; + self->s.effects |= EF_ROCKET; + self->use = NULL; + self->movetype = MOVETYPE_TOSS; + self->prethink = misc_viper_bomb_prethink; + self->touch = misc_viper_bomb_touch; + self->activator = activator; + + viper = G_Find (NULL, FOFS(classname), "misc_viper"); + VectorScale (viper->moveinfo.dir, viper->moveinfo.speed, self->velocity); + + self->timestamp = level.time; + VectorCopy (viper->moveinfo.dir, self->moveinfo.dir); +} + + +void SP_misc_viper_bomb (edict_t *self) +{ + self->movetype = MOVETYPE_NONE; + self->solid = SOLID_NOT; + VectorSet (self->mins, -8, -8, -8); + VectorSet (self->maxs, 8, 8, 8); + + self->s.modelindex = gi.modelindex ("models/objects/bomb/tris.md2"); + + if (!self->dmg) + self->dmg = 1000; + + self->use = misc_viper_bomb_use; + self->svflags |= SVF_NOCLIENT; + + gi.linkentity (self); +} + + +// RAFAEL +/*QUAKED misc_viper_missile (1 0 0) (-8 -8 -8) (8 8 8) +"dmg" how much boom should the bomb make? the default value is 250 +*/ + +void misc_viper_missile_use (edict_t *self, edict_t *other, edict_t *activator) +{ + + vec3_t forward, right, up; + vec3_t start, dir; + vec3_t vec; + + AngleVectors (self->s.angles, forward, right, up); + + self->enemy = G_Find (NULL, FOFS(targetname), self->target); + + VectorCopy (self->enemy->s.origin, vec); + vec[2] + 16; + + VectorCopy (self->s.origin, start); + VectorSubtract (vec, start, dir); + VectorNormalize (dir); + + monster_fire_rocket (self, start, dir, self->dmg, 500, MZ2_CHICK_ROCKET_1); + + self->nextthink = level.time + 0.1; + self->think = G_FreeEdict; + + +} + + +void SP_misc_viper_missile (edict_t *self) +{ + self->movetype = MOVETYPE_NONE; + self->solid = SOLID_NOT; + VectorSet (self->mins, -8, -8, -8); + VectorSet (self->maxs, 8, 8, 8); + + if (!self->dmg) + self->dmg = 250; + + self->s.modelindex = gi.modelindex ("models/objects/bomb/tris.md2"); + + self->use = misc_viper_missile_use; + self->svflags |= SVF_NOCLIENT; + + gi.linkentity (self); +} + + +/*QUAKED misc_strogg_ship (1 .5 0) (-16 -16 0) (16 16 32) +This is a Storgg ship for the flybys. +It is trigger_spawned, so you must have something use it for it to show up. +There must be a path for it to follow once it is activated. + +"speed" How fast it should fly +*/ + +extern void train_use (edict_t *self, edict_t *other, edict_t *activator); +extern void func_train_find (edict_t *self); + +void misc_strogg_ship_use (edict_t *self, edict_t *other, edict_t *activator) +{ + self->svflags &= ~SVF_NOCLIENT; + self->use = train_use; + train_use (self, other, activator); +} + +void SP_misc_strogg_ship (edict_t *ent) +{ + if (!ent->target) + { + gi.dprintf ("%s without a target at %s\n", ent->classname, vtos(ent->absmin)); + G_FreeEdict (ent); + return; + } + + if (!ent->speed) + ent->speed = 300; + + ent->movetype = MOVETYPE_PUSH; + ent->solid = SOLID_NOT; + ent->s.modelindex = gi.modelindex ("models/ships/strogg1/tris.md2"); + VectorSet (ent->mins, -16, -16, 0); + VectorSet (ent->maxs, 16, 16, 32); + + ent->think = func_train_find; + ent->nextthink = level.time + FRAMETIME; + ent->use = misc_strogg_ship_use; + ent->svflags |= SVF_NOCLIENT; + ent->moveinfo.accel = ent->moveinfo.decel = ent->moveinfo.speed = ent->speed; + + gi.linkentity (ent); +} + + +// RAFAEL 17-APR-98 +/*QUAKED misc_transport (1 0 0) (-8 -8 -8) (8 8 8) TRIGGER_SPAWN +Maxx's transport at end of game +*/ +void SP_misc_transport (edict_t *ent) +{ + + if (!ent->target) + { + gi.dprintf ("%s without a target at %s\n", ent->classname, vtos(ent->absmin)); + G_FreeEdict (ent); + return; + } + + if (!ent->speed) + ent->speed = 300; + + ent->movetype = MOVETYPE_PUSH; + ent->solid = SOLID_NOT; + ent->s.modelindex = gi.modelindex ("models/objects/ship/tris.md2"); + + VectorSet (ent->mins, -16, -16, 0); + VectorSet (ent->maxs, 16, 16, 32); + + ent->think = func_train_find; + ent->nextthink = level.time + FRAMETIME; + ent->use = misc_strogg_ship_use; + ent->svflags |= SVF_NOCLIENT; + ent->moveinfo.accel = ent->moveinfo.decel = ent->moveinfo.speed = ent->speed; + + if (!(ent->spawnflags & 1)) + { + ent->spawnflags |= 1; + } + + gi.linkentity (ent); +} +// END 17-APR-98 + + +/*QUAKED misc_satellite_dish (1 .5 0) (-64 -64 0) (64 64 128) +*/ +void misc_satellite_dish_think (edict_t *self) +{ + self->s.frame++; + if (self->s.frame < 38) + self->nextthink = level.time + FRAMETIME; +} + +void misc_satellite_dish_use (edict_t *self, edict_t *other, edict_t *activator) +{ + self->s.frame = 0; + self->think = misc_satellite_dish_think; + self->nextthink = level.time + FRAMETIME; +} + +void SP_misc_satellite_dish (edict_t *ent) +{ + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_BBOX; + VectorSet (ent->mins, -64, -64, 0); + VectorSet (ent->maxs, 64, 64, 128); + ent->s.modelindex = gi.modelindex ("models/objects/satellite/tris.md2"); + ent->use = misc_satellite_dish_use; + gi.linkentity (ent); +} + + +/*QUAKED light_mine1 (0 1 0) (-2 -2 -12) (2 2 12) +*/ +void SP_light_mine1 (edict_t *ent) +{ + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_BBOX; + ent->s.modelindex = gi.modelindex ("models/objects/minelite/light1/tris.md2"); + gi.linkentity (ent); +} + + +/*QUAKED light_mine2 (0 1 0) (-2 -2 -12) (2 2 12) +*/ +void SP_light_mine2 (edict_t *ent) +{ + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_BBOX; + ent->s.modelindex = gi.modelindex ("models/objects/minelite/light2/tris.md2"); + gi.linkentity (ent); +} + + +/*QUAKED misc_gib_arm (1 0 0) (-8 -8 -8) (8 8 8) +Intended for use with the target_spawner +*/ +void SP_misc_gib_arm (edict_t *ent) +{ + gi.setmodel (ent, "models/objects/gibs/arm/tris.md2"); + ent->solid = SOLID_NOT; + ent->s.effects |= EF_GIB; + ent->takedamage = DAMAGE_YES; + ent->die = gib_die; + ent->movetype = MOVETYPE_TOSS; + ent->svflags |= SVF_MONSTER; + ent->deadflag = DEAD_DEAD; + ent->avelocity[0] = random()*200; + ent->avelocity[1] = random()*200; + ent->avelocity[2] = random()*200; + ent->think = G_FreeEdict; + ent->nextthink = level.time + 30; + gi.linkentity (ent); +} + +/*QUAKED misc_gib_leg (1 0 0) (-8 -8 -8) (8 8 8) +Intended for use with the target_spawner +*/ +void SP_misc_gib_leg (edict_t *ent) +{ + gi.setmodel (ent, "models/objects/gibs/leg/tris.md2"); + ent->solid = SOLID_NOT; + ent->s.effects |= EF_GIB; + ent->takedamage = DAMAGE_YES; + ent->die = gib_die; + ent->movetype = MOVETYPE_TOSS; + ent->svflags |= SVF_MONSTER; + ent->deadflag = DEAD_DEAD; + ent->avelocity[0] = random()*200; + ent->avelocity[1] = random()*200; + ent->avelocity[2] = random()*200; + ent->think = G_FreeEdict; + ent->nextthink = level.time + 30; + gi.linkentity (ent); +} + +/*QUAKED misc_gib_head (1 0 0) (-8 -8 -8) (8 8 8) +Intended for use with the target_spawner +*/ +void SP_misc_gib_head (edict_t *ent) +{ + gi.setmodel (ent, "models/objects/gibs/head/tris.md2"); + ent->solid = SOLID_NOT; + ent->s.effects |= EF_GIB; + ent->takedamage = DAMAGE_YES; + ent->die = gib_die; + ent->movetype = MOVETYPE_TOSS; + ent->svflags |= SVF_MONSTER; + ent->deadflag = DEAD_DEAD; + ent->avelocity[0] = random()*200; + ent->avelocity[1] = random()*200; + ent->avelocity[2] = random()*200; + ent->think = G_FreeEdict; + ent->nextthink = level.time + 30; + gi.linkentity (ent); +} + +//===================================================== + +/*QUAKED target_character (0 0 1) ? +used with target_string (must be on same "team") +"count" is position in the string (starts at 1) +*/ + +void SP_target_character (edict_t *self) +{ + self->movetype = MOVETYPE_PUSH; + gi.setmodel (self, self->model); + self->solid = SOLID_BSP; + self->s.frame = 12; + gi.linkentity (self); + return; +} + + +/*QUAKED target_string (0 0 1) (-8 -8 -8) (8 8 8) +*/ + +void target_string_use (edict_t *self, edict_t *other, edict_t *activator) +{ + edict_t *e; + int n, l; + char c; + + l = strlen(self->message); + for (e = self->teammaster; e; e = e->teamchain) + { + if (!e->count) + continue; + n = e->count - 1; + if (n > l) + { + e->s.frame = 12; + continue; + } + + c = self->message[n]; + if (c >= '0' && c <= '9') + e->s.frame = c - '0'; + else if (c == '-') + e->s.frame = 10; + else if (c == ':') + e->s.frame = 11; + else + e->s.frame = 12; + } +} + +void SP_target_string (edict_t *self) +{ + if (!self->message) + self->message = ""; + self->use = target_string_use; +} + + +/*QUAKED func_clock (0 0 1) (-8 -8 -8) (8 8 8) TIMER_UP TIMER_DOWN START_OFF MULTI_USE +target a target_string with this + +The default is to be a time of day clock + +TIMER_UP and TIMER_DOWN run for "count" seconds and the fire "pathtarget" +If START_OFF, this entity must be used before it starts + +"style" 0 "xx" + 1 "xx:xx" + 2 "xx:xx:xx" +*/ + +#define CLOCK_MESSAGE_SIZE 16 + +// don't let field width of any clock messages change, or it +// could cause an overwrite after a game load + +static void func_clock_reset (edict_t *self) +{ + self->activator = NULL; + if (self->spawnflags & 1) + { + self->health = 0; + self->wait = self->count; + } + else if (self->spawnflags & 2) + { + self->health = self->count; + self->wait = 0; + } +} + +static void func_clock_format_countdown (edict_t *self) +{ + if (self->style == 0) + { + Com_sprintf (self->message, CLOCK_MESSAGE_SIZE, "%2i", self->health); + return; + } + + if (self->style == 1) + { + Com_sprintf(self->message, CLOCK_MESSAGE_SIZE, "%2i:%2i", self->health / 60, self->health % 60); + if (self->message[3] == ' ') + self->message[3] = '0'; + return; + } + + if (self->style == 2) + { + Com_sprintf(self->message, CLOCK_MESSAGE_SIZE, "%2i:%2i:%2i", self->health / 3600, (self->health - (self->health / 3600) * 3600) / 60, self->health % 60); + if (self->message[3] == ' ') + self->message[3] = '0'; + if (self->message[6] == ' ') + self->message[6] = '0'; + return; + } +} + +void func_clock_think (edict_t *self) +{ + if (!self->enemy) + { + self->enemy = G_Find (NULL, FOFS(targetname), self->target); + if (!self->enemy) + return; + } + + if (self->spawnflags & 1) + { + func_clock_format_countdown (self); + self->health++; + } + else if (self->spawnflags & 2) + { + func_clock_format_countdown (self); + self->health--; + } + else + { + struct tm *ltime; + time_t gmtime; + + time(&gmtime); + ltime = localtime(&gmtime); + Com_sprintf (self->message, CLOCK_MESSAGE_SIZE, "%2i:%2i:%2i", ltime->tm_hour, ltime->tm_min, ltime->tm_sec); + if (self->message[3] == ' ') + self->message[3] = '0'; + if (self->message[6] == ' ') + self->message[6] = '0'; + } + + self->enemy->message = self->message; + self->enemy->use (self->enemy, self, self); + + if (((self->spawnflags & 1) && (self->health > self->wait)) || + ((self->spawnflags & 2) && (self->health < self->wait))) + { + if (self->pathtarget) + { + char *savetarget; + char *savemessage; + + savetarget = self->target; + savemessage = self->message; + self->target = self->pathtarget; + self->message = NULL; + G_UseTargets (self, self->activator); + self->target = savetarget; + self->message = savemessage; + } + + if (!(self->spawnflags & 8)) + return; + + func_clock_reset (self); + + if (self->spawnflags & 4) + return; + } + + self->nextthink = level.time + 1; +} + +void func_clock_use (edict_t *self, edict_t *other, edict_t *activator) +{ + if (!(self->spawnflags & 8)) + self->use = NULL; + if (self->activator) + return; + self->activator = activator; + self->think (self); +} + +void SP_func_clock (edict_t *self) +{ + if (!self->target) + { + gi.dprintf("%s with no target at %s\n", self->classname, vtos(self->s.origin)); + G_FreeEdict (self); + return; + } + + if ((self->spawnflags & 2) && (!self->count)) + { + gi.dprintf("%s with no count at %s\n", self->classname, vtos(self->s.origin)); + G_FreeEdict (self); + return; + } + + if ((self->spawnflags & 1) && (!self->count)) + self->count = 60*60;; + + func_clock_reset (self); + + self->message = gi.TagMalloc (CLOCK_MESSAGE_SIZE, TAG_LEVEL); + + self->think = func_clock_think; + + if (self->spawnflags & 4) + self->use = func_clock_use; + else + self->nextthink = level.time + 1; +} + +//================================================================================= + +void teleporter_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + edict_t *dest; + int i; + + if (!other->client) + return; + dest = G_Find (NULL, FOFS(targetname), self->target); + if (!dest) + { + gi.dprintf ("Couldn't find destination\n"); + return; + } + + // unlink to make sure it can't possibly interfere with KillBox + gi.unlinkentity (other); + + VectorCopy (dest->s.origin, other->s.origin); + VectorCopy (dest->s.origin, other->s.old_origin); + other->s.origin[2] += 10; + + // clear the velocity and hold them in place briefly + VectorClear (other->velocity); + other->client->ps.pmove.pm_time = 160>>3; // hold time + other->client->ps.pmove.pm_flags |= PMF_TIME_TELEPORT; + + // draw the teleport splash at source and on the player + self->owner->s.event = EV_PLAYER_TELEPORT; + other->s.event = EV_PLAYER_TELEPORT; + + // set angles + for (i=0 ; i<3 ; i++) + other->client->ps.pmove.delta_angles[i] = ANGLE2SHORT(dest->s.angles[i] - other->client->resp.cmd_angles[i]); + + VectorClear (other->s.angles); + VectorClear (other->client->ps.viewangles); + VectorClear (other->client->v_angle); + + // kill anything at the destination + KillBox (other); + + gi.linkentity (other); +} + +/*QUAKED misc_teleporter (1 0 0) (-32 -32 -24) (32 32 -16) +Stepping onto this disc will teleport players to the targeted misc_teleporter_dest object. +*/ +void SP_misc_teleporter (edict_t *ent) +{ + edict_t *trig; + + if (!ent->target) + { + gi.dprintf ("teleporter without a target.\n"); + G_FreeEdict (ent); + return; + } + + gi.setmodel (ent, "models/objects/dmspot/tris.md2"); + ent->s.skinnum = 1; + ent->s.effects = EF_TELEPORTER; + ent->s.sound = gi.soundindex ("world/amb10.wav"); + ent->solid = SOLID_BBOX; + + VectorSet (ent->mins, -32, -32, -24); + VectorSet (ent->maxs, 32, 32, -16); + gi.linkentity (ent); + + trig = G_Spawn (); + trig->touch = teleporter_touch; + trig->solid = SOLID_TRIGGER; + trig->target = ent->target; + trig->owner = ent; + VectorCopy (ent->s.origin, trig->s.origin); + VectorSet (trig->mins, -8, -8, 8); + VectorSet (trig->maxs, 8, 8, 24); + gi.linkentity (trig); + +} + +/*QUAKED misc_teleporter_dest (1 0 0) (-32 -32 -24) (32 32 -16) +Point teleporters at these. +*/ +void SP_misc_teleporter_dest (edict_t *ent) +{ + gi.setmodel (ent, "models/objects/dmspot/tris.md2"); + ent->s.skinnum = 0; + ent->solid = SOLID_BBOX; +// ent->s.effects |= EF_FLIES; + VectorSet (ent->mins, -32, -32, -24); + VectorSet (ent->maxs, 32, 32, -16); + gi.linkentity (ent); +} + + +/*QUAKED misc_amb4 (1 0 0) (-16 -16 -16) (16 16 16) +Mal's amb4 loop entity +*/ +static int amb4sound; + +void amb4_think (edict_t *ent) +{ + ent->nextthink = level.time + 2.7; + gi.sound(ent, CHAN_VOICE, amb4sound, 1, ATTN_NONE, 0); +} + +void SP_misc_amb4 (edict_t *ent) +{ + ent->think = amb4_think; + ent->nextthink = level.time + 1; + amb4sound = gi.soundindex ("world/amb4.wav"); + gi.linkentity (ent); +} + +/*QUAKED misc_nuke (1 0 0) (-16 -16 -16) (16 16 16) +*/ + +void use_nuke (edict_t *self, edict_t *other, edict_t *activator) +{ + edict_t *from = g_edicts; + + for ( ; from < &g_edicts[globals.num_edicts]; from++) + { + if (from == self) + continue; + if (from->client) + { + T_Damage (from, self, self, vec3_origin, from->s.origin, vec3_origin, 100000, 1, 0, MOD_TRAP); + } + else if (from->svflags & SVF_MONSTER) + { + G_FreeEdict (from); + } + } + + self->use = NULL; +} + +void SP_misc_nuke (edict_t *ent) +{ + ent->use = use_nuke; +} \ No newline at end of file diff --git a/original/xatrix/g_monster.c b/original/xatrix/g_monster.c new file mode 100644 index 0000000..b0dc6b9 --- /dev/null +++ b/original/xatrix/g_monster.c @@ -0,0 +1,875 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +#include "g_local.h" + + +// +// monster weapons +// + +//FIXME mosnters should call these with a totally accurate direction +// and we can mess it up based on skill. Spread should be for normal +// and we can tighten or loosen based on skill. We could muck with +// the damages too, but I'm not sure that's such a good idea. +void monster_fire_bullet (edict_t *self, vec3_t start, vec3_t dir, int damage, int kick, int hspread, int vspread, int flashtype) +{ + fire_bullet (self, start, dir, damage, kick, hspread, vspread, MOD_UNKNOWN); + + gi.WriteByte (svc_muzzleflash2); + gi.WriteShort (self - g_edicts); + gi.WriteByte (flashtype); + gi.multicast (start, MULTICAST_PVS); +} + +void monster_fire_shotgun (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int hspread, int vspread, int count, int flashtype) +{ + fire_shotgun (self, start, aimdir, damage, kick, hspread, vspread, count, MOD_UNKNOWN); + + gi.WriteByte (svc_muzzleflash2); + gi.WriteShort (self - g_edicts); + gi.WriteByte (flashtype); + gi.multicast (start, MULTICAST_PVS); +} + +void monster_fire_blaster (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int flashtype, int effect) +{ + fire_blaster (self, start, dir, damage, speed, effect, false); + + gi.WriteByte (svc_muzzleflash2); + gi.WriteShort (self - g_edicts); + gi.WriteByte (flashtype); + gi.multicast (start, MULTICAST_PVS); +} + +// RAFAEL +void monster_fire_blueblaster (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int flashtype, int effect) +{ + fire_blueblaster (self, start, dir, damage, speed, effect); + + gi.WriteByte (svc_muzzleflash2); + gi.WriteShort (self - g_edicts); + gi.WriteByte (MZ_BLUEHYPERBLASTER); + gi.multicast (start, MULTICAST_PVS); +} + +// RAFAEL +void monster_fire_ionripper (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int flashtype, int effect) +{ + fire_ionripper (self, start, dir, damage, speed, effect); + + gi.WriteByte (svc_muzzleflash2); + gi.WriteShort (self - g_edicts); + gi.WriteByte (flashtype); + gi.multicast (start, MULTICAST_PVS); +} + +// RAFAEL +void monster_fire_heat (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int flashtype) +{ + fire_heat (self, start, dir, damage, speed, damage, damage); + + gi.WriteByte (svc_muzzleflash2); + gi.WriteShort (self - g_edicts); + gi.WriteByte (flashtype); + gi.multicast (start, MULTICAST_PVS); +} + +// RAFAEL +void dabeam_hit (edict_t *self) +{ + edict_t *ignore; + vec3_t start; + vec3_t end; + trace_t tr; + int count; + static vec3_t lmins = {-4, -4, -4}; + static vec3_t lmaxs = {4, 4, 4}; + + if (self->spawnflags & 0x80000000) + count = 8; + else + count = 4; + + ignore = self; + VectorCopy (self->s.origin, start); + VectorMA (start, 2048, self->movedir, end); + + while(1) + { + tr = gi.trace (start, NULL, NULL, end, ignore, CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_DEADMONSTER); + if (!tr.ent) + break; + + // hurt it if we can + if ((tr.ent->takedamage) && !(tr.ent->flags & FL_IMMUNE_LASER) && (tr.ent != self->owner)) + T_Damage (tr.ent, self, self->owner, self->movedir, tr.endpos, vec3_origin, self->dmg, skill->value, DAMAGE_ENERGY, MOD_TARGET_LASER); + + if (self->dmg < 0) // healer ray + { + // when player is at 100 health + // just undo health fix + // keeping fx + if (tr.ent->client && tr.ent->health > 100) + tr.ent->health += self->dmg; + } + + // if we hit something that's not a monster or player or is immune to lasers, we're done + if (!(tr.ent->svflags & SVF_MONSTER) && (!tr.ent->client)) + { + if (self->spawnflags & 0x80000000) + { + self->spawnflags &= ~0x80000000; + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_LASER_SPARKS); + gi.WriteByte (10); + gi.WritePosition (tr.endpos); + gi.WriteDir (tr.plane.normal); + gi.WriteByte (self->s.skinnum); + gi.multicast (tr.endpos, MULTICAST_PVS); + } + break; + } + + ignore = tr.ent; + VectorCopy (tr.endpos, start); + } + + + VectorCopy (tr.endpos, self->s.old_origin); + self->nextthink = level.time + 0.1; + self->think = G_FreeEdict; + +} + +// RAFAEL +void monster_dabeam (edict_t *self) +{ + vec3_t last_movedir; + vec3_t point; + + self->movetype = MOVETYPE_NONE; + self->solid = SOLID_NOT; + self->s.renderfx |= RF_BEAM|RF_TRANSLUCENT; + self->s.modelindex = 1; + + self->s.frame = 2; + + if (self->owner->monsterinfo.aiflags & AI_MEDIC) + self->s.skinnum = 0xf3f3f1f1; + else + self->s.skinnum = 0xf2f2f0f0; + + if (self->enemy) + { + VectorCopy (self->movedir, last_movedir); + VectorMA (self->enemy->absmin, 0.5, self->enemy->size, point); + if (self->owner->monsterinfo.aiflags & AI_MEDIC) + point[0] += sin (level.time) * 8; + VectorSubtract (point, self->s.origin, self->movedir); + VectorNormalize (self->movedir); + if (!VectorCompare(self->movedir, last_movedir)) + self->spawnflags |= 0x80000000; + } + else + G_SetMovedir (self->s.angles, self->movedir); + + self->think = dabeam_hit; + self->nextthink = level.time + 0.1; + VectorSet (self->mins, -8, -8, -8); + VectorSet (self->maxs, 8, 8, 8); + gi.linkentity (self); + + self->spawnflags |= 0x80000001; + self->svflags &= ~SVF_NOCLIENT; +} + +void monster_fire_grenade (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, int flashtype) +{ + fire_grenade (self, start, aimdir, damage, speed, 2.5, damage+40); + + gi.WriteByte (svc_muzzleflash2); + gi.WriteShort (self - g_edicts); + gi.WriteByte (flashtype); + gi.multicast (start, MULTICAST_PVS); +} + +void monster_fire_rocket (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int flashtype) +{ + fire_rocket (self, start, dir, damage, speed, damage+20, damage); + + gi.WriteByte (svc_muzzleflash2); + gi.WriteShort (self - g_edicts); + gi.WriteByte (flashtype); + gi.multicast (start, MULTICAST_PVS); +} + +void monster_fire_railgun (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int flashtype) +{ + fire_rail (self, start, aimdir, damage, kick); + + gi.WriteByte (svc_muzzleflash2); + gi.WriteShort (self - g_edicts); + gi.WriteByte (flashtype); + gi.multicast (start, MULTICAST_PVS); +} + +void monster_fire_bfg (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, int kick, float damage_radius, int flashtype) +{ + fire_bfg (self, start, aimdir, damage, speed, damage_radius); + + gi.WriteByte (svc_muzzleflash2); + gi.WriteShort (self - g_edicts); + gi.WriteByte (flashtype); + gi.multicast (start, MULTICAST_PVS); +} + + + +// +// Monster utility functions +// + +static void M_FliesOff (edict_t *self) +{ + self->s.effects &= ~EF_FLIES; + self->s.sound = 0; +} + +static void M_FliesOn (edict_t *self) +{ + if (self->waterlevel) + return; + self->s.effects |= EF_FLIES; + self->s.sound = gi.soundindex ("infantry/inflies1.wav"); + self->think = M_FliesOff; + self->nextthink = level.time + 60; +} + +void M_FlyCheck (edict_t *self) +{ + if (self->waterlevel) + return; + + if (random() > 0.5) + return; + + self->think = M_FliesOn; + self->nextthink = level.time + 5 + 10 * random(); +} + +void AttackFinished (edict_t *self, float time) +{ + self->monsterinfo.attack_finished = level.time + time; +} + + +void M_CheckGround (edict_t *ent) +{ + vec3_t point; + trace_t trace; + + if (ent->flags & (FL_SWIM|FL_FLY)) + return; + + if (ent->velocity[2] > 100) + { + ent->groundentity = NULL; + return; + } + +// if the hull point one-quarter unit down is solid the entity is on ground + point[0] = ent->s.origin[0]; + point[1] = ent->s.origin[1]; + point[2] = ent->s.origin[2] - 0.25; + + trace = gi.trace (ent->s.origin, ent->mins, ent->maxs, point, ent, MASK_MONSTERSOLID); + + // check steepness + if ( trace.plane.normal[2] < 0.7 && !trace.startsolid) + { + ent->groundentity = NULL; + return; + } + +// ent->groundentity = trace.ent; +// ent->groundentity_linkcount = trace.ent->linkcount; +// if (!trace.startsolid && !trace.allsolid) +// VectorCopy (trace.endpos, ent->s.origin); + if (!trace.startsolid && !trace.allsolid) + { + VectorCopy (trace.endpos, ent->s.origin); + ent->groundentity = trace.ent; + ent->groundentity_linkcount = trace.ent->linkcount; + ent->velocity[2] = 0; + } +} + + +void M_CatagorizePosition (edict_t *ent) +{ + vec3_t point; + int cont; + +// +// get waterlevel +// + point[0] = ent->s.origin[0]; + point[1] = ent->s.origin[1]; + point[2] = ent->s.origin[2] + ent->mins[2] + 1; + cont = gi.pointcontents (point); + + if (!(cont & MASK_WATER)) + { + ent->waterlevel = 0; + ent->watertype = 0; + return; + } + + ent->watertype = cont; + ent->waterlevel = 1; + point[2] += 26; + cont = gi.pointcontents (point); + if (!(cont & MASK_WATER)) + return; + + ent->waterlevel = 2; + point[2] += 22; + cont = gi.pointcontents (point); + if (cont & MASK_WATER) + ent->waterlevel = 3; +} + + +void M_WorldEffects (edict_t *ent) +{ + int dmg; + + if (ent->health > 0) + { + if (!(ent->flags & FL_SWIM)) + { + if (ent->waterlevel < 3) + { + ent->air_finished = level.time + 12; + } + else if (ent->air_finished < level.time) + { // drown! + if (ent->pain_debounce_time < level.time) + { + dmg = 2 + 2 * floor(level.time - ent->air_finished); + if (dmg > 15) + dmg = 15; + T_Damage (ent, world, world, vec3_origin, ent->s.origin, vec3_origin, dmg, 0, DAMAGE_NO_ARMOR, MOD_WATER); + ent->pain_debounce_time = level.time + 1; + } + } + } + else + { + if (ent->waterlevel > 0) + { + ent->air_finished = level.time + 9; + } + else if (ent->air_finished < level.time) + { // suffocate! + if (ent->pain_debounce_time < level.time) + { + dmg = 2 + 2 * floor(level.time - ent->air_finished); + if (dmg > 15) + dmg = 15; + T_Damage (ent, world, world, vec3_origin, ent->s.origin, vec3_origin, dmg, 0, DAMAGE_NO_ARMOR, MOD_WATER); + ent->pain_debounce_time = level.time + 1; + } + } + } + } + + if (ent->waterlevel == 0) + { + if (ent->flags & FL_INWATER) + { + gi.sound (ent, CHAN_BODY, gi.soundindex("player/watr_out.wav"), 1, ATTN_NORM, 0); + ent->flags &= ~FL_INWATER; + } + return; + } + + if ((ent->watertype & CONTENTS_LAVA) && !(ent->flags & FL_IMMUNE_LAVA)) + { + if (ent->damage_debounce_time < level.time) + { + ent->damage_debounce_time = level.time + 0.2; + T_Damage (ent, world, world, vec3_origin, ent->s.origin, vec3_origin, 10*ent->waterlevel, 0, 0, MOD_LAVA); + } + } + if ((ent->watertype & CONTENTS_SLIME) && !(ent->flags & FL_IMMUNE_SLIME)) + { + if (ent->damage_debounce_time < level.time) + { + ent->damage_debounce_time = level.time + 1; + T_Damage (ent, world, world, vec3_origin, ent->s.origin, vec3_origin, 4*ent->waterlevel, 0, 0, MOD_SLIME); + } + } + + if ( !(ent->flags & FL_INWATER) ) + { + if (!(ent->svflags & SVF_DEADMONSTER)) + { + if (ent->watertype & CONTENTS_LAVA) + if (random() <= 0.5) + gi.sound (ent, CHAN_BODY, gi.soundindex("player/lava1.wav"), 1, ATTN_NORM, 0); + else + gi.sound (ent, CHAN_BODY, gi.soundindex("player/lava2.wav"), 1, ATTN_NORM, 0); + else if (ent->watertype & CONTENTS_SLIME) + gi.sound (ent, CHAN_BODY, gi.soundindex("player/watr_in.wav"), 1, ATTN_NORM, 0); + else if (ent->watertype & CONTENTS_WATER) + gi.sound (ent, CHAN_BODY, gi.soundindex("player/watr_in.wav"), 1, ATTN_NORM, 0); + } + + ent->flags |= FL_INWATER; + ent->damage_debounce_time = 0; + } +} + + +void M_droptofloor (edict_t *ent) +{ + vec3_t end; + trace_t trace; + + ent->s.origin[2] += 1; + VectorCopy (ent->s.origin, end); + end[2] -= 256; + + trace = gi.trace (ent->s.origin, ent->mins, ent->maxs, end, ent, MASK_MONSTERSOLID); + + if (trace.fraction == 1 || trace.allsolid) + return; + + VectorCopy (trace.endpos, ent->s.origin); + + gi.linkentity (ent); + M_CheckGround (ent); + M_CatagorizePosition (ent); +} + + +void M_SetEffects (edict_t *ent) +{ + ent->s.effects &= ~(EF_COLOR_SHELL|EF_POWERSCREEN); + ent->s.renderfx &= ~(RF_SHELL_RED|RF_SHELL_GREEN|RF_SHELL_BLUE); + + if (ent->monsterinfo.aiflags & AI_RESURRECTING) + { + ent->s.effects |= EF_COLOR_SHELL; + ent->s.renderfx |= RF_SHELL_RED; + } + + if (ent->health <= 0) + return; + + if (ent->powerarmor_time > level.time) + { + if (ent->monsterinfo.power_armor_type == POWER_ARMOR_SCREEN) + { + ent->s.effects |= EF_POWERSCREEN; + } + else if (ent->monsterinfo.power_armor_type == POWER_ARMOR_SHIELD) + { + ent->s.effects |= EF_COLOR_SHELL; + ent->s.renderfx |= RF_SHELL_GREEN; + } + } +} + + +void M_MoveFrame (edict_t *self) +{ + mmove_t *move; + int index; + + move = self->monsterinfo.currentmove; + self->nextthink = level.time + FRAMETIME; + + if ((self->monsterinfo.nextframe) && (self->monsterinfo.nextframe >= move->firstframe) && (self->monsterinfo.nextframe <= move->lastframe)) + { + self->s.frame = self->monsterinfo.nextframe; + self->monsterinfo.nextframe = 0; + } + else + { + if (self->s.frame == move->lastframe) + { + if (move->endfunc) + { + move->endfunc (self); + + // regrab move, endfunc is very likely to change it + move = self->monsterinfo.currentmove; + + // check for death + if (self->svflags & SVF_DEADMONSTER) + return; + } + } + + if (self->s.frame < move->firstframe || self->s.frame > move->lastframe) + { + self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; + self->s.frame = move->firstframe; + } + else + { + if (!(self->monsterinfo.aiflags & AI_HOLD_FRAME)) + { + self->s.frame++; + if (self->s.frame > move->lastframe) + self->s.frame = move->firstframe; + } + } + } + + index = self->s.frame - move->firstframe; + if (move->frame[index].aifunc) + if (!(self->monsterinfo.aiflags & AI_HOLD_FRAME)) + move->frame[index].aifunc (self, move->frame[index].dist * self->monsterinfo.scale); + else + move->frame[index].aifunc (self, 0); + + if (move->frame[index].thinkfunc) + move->frame[index].thinkfunc (self); +} + + +void monster_think (edict_t *self) +{ + M_MoveFrame (self); + if (self->linkcount != self->monsterinfo.linkcount) + { + self->monsterinfo.linkcount = self->linkcount; + M_CheckGround (self); + } + M_CatagorizePosition (self); + M_WorldEffects (self); + M_SetEffects (self); +} + + +/* +================ +monster_use + +Using a monster makes it angry at the current activator +================ +*/ +void monster_use (edict_t *self, edict_t *other, edict_t *activator) +{ + if (self->enemy) + return; + if (self->health <= 0) + return; + if (activator->flags & FL_NOTARGET) + return; + if (!(activator->client) && !(activator->monsterinfo.aiflags & AI_GOOD_GUY)) + return; + +// delay reaction so if the monster is teleported, its sound is still heard + self->enemy = activator; + FoundTarget (self); +} + + +void monster_start_go (edict_t *self); + + +void monster_triggered_spawn (edict_t *self) +{ + self->s.origin[2] += 1; + KillBox (self); + + self->solid = SOLID_BBOX; + self->movetype = MOVETYPE_STEP; + self->svflags &= ~SVF_NOCLIENT; + self->air_finished = level.time + 12; + gi.linkentity (self); + + monster_start_go (self); + + // RAFAEL + if (strcmp (self->classname, "monster_fixbot") == 0) + { + if (self->spawnflags &16 || self->spawnflags &8 || self->spawnflags &4) + { + self->enemy = NULL; + return; + } + } + + if (self->enemy && !(self->spawnflags & 1) && !(self->enemy->flags & FL_NOTARGET)) + { + FoundTarget (self); + } + else + { + self->enemy = NULL; + } +} + +void monster_triggered_spawn_use (edict_t *self, edict_t *other, edict_t *activator) +{ + // we have a one frame delay here so we don't telefrag the guy who activated us + self->think = monster_triggered_spawn; + self->nextthink = level.time + FRAMETIME; + if (activator->client) + self->enemy = activator; + self->use = monster_use; +} + +void monster_triggered_start (edict_t *self) +{ + self->solid = SOLID_NOT; + self->movetype = MOVETYPE_NONE; + self->svflags |= SVF_NOCLIENT; + self->nextthink = 0; + self->use = monster_triggered_spawn_use; +} + + +/* +================ +monster_death_use + +When a monster dies, it fires all of its targets with the current +enemy as activator. +================ +*/ +void monster_death_use (edict_t *self) +{ + self->flags &= ~(FL_FLY|FL_SWIM); + self->monsterinfo.aiflags &= AI_GOOD_GUY; + + if (self->item) + { + Drop_Item (self, self->item); + self->item = NULL; + } + + if (self->deathtarget) + self->target = self->deathtarget; + + if (!self->target) + return; + + G_UseTargets (self, self->enemy); +} + + +//============================================================================ + +qboolean monster_start (edict_t *self) +{ + if (deathmatch->value) + { + G_FreeEdict (self); + return false; + } + + if ((self->spawnflags & 4) && !(self->monsterinfo.aiflags & AI_GOOD_GUY)) + { + self->spawnflags &= ~4; + self->spawnflags |= 1; +// gi.dprintf("fixed spawnflags on %s at %s\n", self->classname, vtos(self->s.origin)); + } + + if (!(self->monsterinfo.aiflags & AI_GOOD_GUY)) + level.total_monsters++; + + self->nextthink = level.time + FRAMETIME; + self->svflags |= SVF_MONSTER; + self->s.renderfx |= RF_FRAMELERP; + self->takedamage = DAMAGE_AIM; + self->air_finished = level.time + 12; + self->use = monster_use; + self->max_health = self->health; + self->clipmask = MASK_MONSTERSOLID; + + self->s.skinnum = 0; + self->deadflag = DEAD_NO; + self->svflags &= ~SVF_DEADMONSTER; + + if (!self->monsterinfo.checkattack) + self->monsterinfo.checkattack = M_CheckAttack; + VectorCopy (self->s.origin, self->s.old_origin); + + if (st.item) + { + self->item = FindItemByClassname (st.item); + if (!self->item) + gi.dprintf("%s at %s has bad item: %s\n", self->classname, vtos(self->s.origin), st.item); + } + + // randomize what frame they start on + if (self->monsterinfo.currentmove) + self->s.frame = self->monsterinfo.currentmove->firstframe + (rand() % (self->monsterinfo.currentmove->lastframe - self->monsterinfo.currentmove->firstframe + 1)); + + return true; +} + +void monster_start_go (edict_t *self) +{ + vec3_t v; + + if (self->health <= 0) + return; + + // check for target to combat_point and change to combattarget + if (self->target) + { + qboolean notcombat; + qboolean fixup; + edict_t *target; + + target = NULL; + notcombat = false; + fixup = false; + while ((target = G_Find (target, FOFS(targetname), self->target)) != NULL) + { + if (strcmp(target->classname, "point_combat") == 0) + { + self->combattarget = self->target; + fixup = true; + } + else + { + notcombat = true; + } + } + if (notcombat && self->combattarget) + gi.dprintf("%s at %s has target with mixed types\n", self->classname, vtos(self->s.origin)); + if (fixup) + self->target = NULL; + } + + // validate combattarget + if (self->combattarget) + { + edict_t *target; + + target = NULL; + while ((target = G_Find (target, FOFS(targetname), self->combattarget)) != NULL) + { + if (strcmp(target->classname, "point_combat") != 0) + { + gi.dprintf("%s at (%i %i %i) has a bad combattarget %s : %s at (%i %i %i)\n", + self->classname, (int)self->s.origin[0], (int)self->s.origin[1], (int)self->s.origin[2], + self->combattarget, target->classname, (int)target->s.origin[0], (int)target->s.origin[1], + (int)target->s.origin[2]); + } + } + } + + if (self->target) + { + self->goalentity = self->movetarget = G_PickTarget(self->target); + if (!self->movetarget) + { + gi.dprintf ("%s can't find target %s at %s\n", self->classname, self->target, vtos(self->s.origin)); + self->target = NULL; + self->monsterinfo.pausetime = 100000000; + self->monsterinfo.stand (self); + } + else if (strcmp (self->movetarget->classname, "path_corner") == 0) + { + VectorSubtract (self->goalentity->s.origin, self->s.origin, v); + self->ideal_yaw = self->s.angles[YAW] = vectoyaw(v); + self->monsterinfo.walk (self); + self->target = NULL; + } + else + { + self->goalentity = self->movetarget = NULL; + self->monsterinfo.pausetime = 100000000; + self->monsterinfo.stand (self); + } + } + else + { + self->monsterinfo.pausetime = 100000000; + self->monsterinfo.stand (self); + } + + self->think = monster_think; + self->nextthink = level.time + FRAMETIME; +} + + +void walkmonster_start_go (edict_t *self) +{ + if (!(self->spawnflags & 2) && level.time < 1) + { + M_droptofloor (self); + + if (self->groundentity) + if (!M_walkmove (self, 0, 0)) + gi.dprintf ("%s in solid at %s\n", self->classname, vtos(self->s.origin)); + } + + if (!self->yaw_speed) + self->yaw_speed = 20; + self->viewheight = 25; + + monster_start_go (self); + + if (self->spawnflags & 2) + monster_triggered_start (self); +} + +void walkmonster_start (edict_t *self) +{ + self->think = walkmonster_start_go; + monster_start (self); +} + + +void flymonster_start_go (edict_t *self) +{ + if (!M_walkmove (self, 0, 0)) + gi.dprintf ("%s in solid at %s\n", self->classname, vtos(self->s.origin)); + + if (!self->yaw_speed) + self->yaw_speed = 10; + self->viewheight = 25; + + monster_start_go (self); + + if (self->spawnflags & 2) + monster_triggered_start (self); +} + + +void flymonster_start (edict_t *self) +{ + self->flags |= FL_FLY; + self->think = flymonster_start_go; + monster_start (self); +} + + +void swimmonster_start_go (edict_t *self) +{ + if (!self->yaw_speed) + self->yaw_speed = 10; + self->viewheight = 10; + + monster_start_go (self); + + if (self->spawnflags & 2) + monster_triggered_start (self); +} + +void swimmonster_start (edict_t *self) +{ + self->flags |= FL_SWIM; + self->think = swimmonster_start_go; + monster_start (self); +} diff --git a/original/xatrix/g_phys.c b/original/xatrix/g_phys.c new file mode 100644 index 0000000..6509af1 --- /dev/null +++ b/original/xatrix/g_phys.c @@ -0,0 +1,958 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// g_phys.c + +#include "g_local.h" + +/* + + +pushmove objects do not obey gravity, and do not interact with each other or trigger fields, but block normal movement and push normal objects when they move. + +onground is set for toss objects when they come to a complete rest. it is set for steping or walking objects + +doors, plats, etc are SOLID_BSP, and MOVETYPE_PUSH +bonus items are SOLID_TRIGGER touch, and MOVETYPE_TOSS +corpses are SOLID_NOT and MOVETYPE_TOSS +crates are SOLID_BBOX and MOVETYPE_TOSS +walking monsters are SOLID_SLIDEBOX and MOVETYPE_STEP +flying/floating monsters are SOLID_SLIDEBOX and MOVETYPE_FLY + +solid_edge items only clip against bsp models. + +*/ + + +/* +============ +SV_TestEntityPosition + +============ +*/ +edict_t *SV_TestEntityPosition (edict_t *ent) +{ + trace_t trace; + int mask; + + if (ent->clipmask) + mask = ent->clipmask; + else + mask = MASK_SOLID; + trace = gi.trace (ent->s.origin, ent->mins, ent->maxs, ent->s.origin, ent, mask); + + if (trace.startsolid) + return g_edicts; + + return NULL; +} + + +/* +================ +SV_CheckVelocity +================ +*/ +void SV_CheckVelocity (edict_t *ent) +{ + int i; + +// +// bound velocity +// + for (i=0 ; i<3 ; i++) + { + if (ent->velocity[i] > sv_maxvelocity->value) + ent->velocity[i] = sv_maxvelocity->value; + else if (ent->velocity[i] < -sv_maxvelocity->value) + ent->velocity[i] = -sv_maxvelocity->value; + } +} + +/* +============= +SV_RunThink + +Runs thinking code for this frame if necessary +============= +*/ +qboolean SV_RunThink (edict_t *ent) +{ + float thinktime; + + thinktime = ent->nextthink; + if (thinktime <= 0) + return true; + if (thinktime > level.time+0.001) + return true; + + ent->nextthink = 0; + if (!ent->think) + gi.error ("NULL ent->think"); + ent->think (ent); + + return false; +} + +/* +================== +SV_Impact + +Two entities have touched, so run their touch functions +================== +*/ +void SV_Impact (edict_t *e1, trace_t *trace) +{ + edict_t *e2; +// cplane_t backplane; + + e2 = trace->ent; + + if (e1->touch && e1->solid != SOLID_NOT) + e1->touch (e1, e2, &trace->plane, trace->surface); + + if (e2->touch && e2->solid != SOLID_NOT) + e2->touch (e2, e1, NULL, NULL); +} + + +/* +================== +ClipVelocity + +Slide off of the impacting object +returns the blocked flags (1 = floor, 2 = step / wall) +================== +*/ +#define STOP_EPSILON 0.1 + +int ClipVelocity (vec3_t in, vec3_t normal, vec3_t out, float overbounce) +{ + float backoff; + float change; + int i, blocked; + + blocked = 0; + if (normal[2] > 0) + blocked |= 1; // floor + if (!normal[2]) + blocked |= 2; // step + + backoff = DotProduct (in, normal) * overbounce; + + for (i=0 ; i<3 ; i++) + { + change = normal[i]*backoff; + out[i] = in[i] - change; + if (out[i] > -STOP_EPSILON && out[i] < STOP_EPSILON) + out[i] = 0; + } + + return blocked; +} + + +/* +============ +SV_FlyMove + +The basic solid body movement clip that slides along multiple planes +Returns the clipflags if the velocity was modified (hit something solid) +1 = floor +2 = wall / step +4 = dead stop +============ +*/ +#define MAX_CLIP_PLANES 5 +int SV_FlyMove (edict_t *ent, float time, int mask) +{ + edict_t *hit; + int bumpcount, numbumps; + vec3_t dir; + float d; + int numplanes; + vec3_t planes[MAX_CLIP_PLANES]; + vec3_t primal_velocity, original_velocity, new_velocity; + int i, j; + trace_t trace; + vec3_t end; + float time_left; + int blocked; + + numbumps = 4; + + blocked = 0; + VectorCopy (ent->velocity, original_velocity); + VectorCopy (ent->velocity, primal_velocity); + numplanes = 0; + + time_left = time; + + ent->groundentity = NULL; + for (bumpcount=0 ; bumpcounts.origin[i] + time_left * ent->velocity[i]; + + trace = gi.trace (ent->s.origin, ent->mins, ent->maxs, end, ent, mask); + + if (trace.allsolid) + { // entity is trapped in another solid + VectorCopy (vec3_origin, ent->velocity); + return 3; + } + + if (trace.fraction > 0) + { // actually covered some distance + VectorCopy (trace.endpos, ent->s.origin); + VectorCopy (ent->velocity, original_velocity); + numplanes = 0; + } + + if (trace.fraction == 1) + break; // moved the entire distance + + hit = trace.ent; + + if (trace.plane.normal[2] > 0.7) + { + blocked |= 1; // floor + if ( hit->solid == SOLID_BSP) + { + ent->groundentity = hit; + ent->groundentity_linkcount = hit->linkcount; + } + } + if (!trace.plane.normal[2]) + { + blocked |= 2; // step + } + +// +// run the impact function +// + SV_Impact (ent, &trace); + if (!ent->inuse) + break; // removed by the impact function + + + time_left -= time_left * trace.fraction; + + // cliped to another plane + if (numplanes >= MAX_CLIP_PLANES) + { // this shouldn't really happen + VectorCopy (vec3_origin, ent->velocity); + return 3; + } + + VectorCopy (trace.plane.normal, planes[numplanes]); + numplanes++; + +// +// modify original_velocity so it parallels all of the clip planes +// + for (i=0 ; ivelocity); + } + else + { // go along the crease + if (numplanes != 2) + { +// gi.dprintf ("clip velocity, numplanes == %i\n",numplanes); + VectorCopy (vec3_origin, ent->velocity); + return 7; + } + CrossProduct (planes[0], planes[1], dir); + d = DotProduct (dir, ent->velocity); + VectorScale (dir, d, ent->velocity); + } + +// +// if original velocity is against the original velocity, stop dead +// to avoid tiny occilations in sloping corners +// + if (DotProduct (ent->velocity, primal_velocity) <= 0) + { + VectorCopy (vec3_origin, ent->velocity); + return blocked; + } + } + + return blocked; +} + + +/* +============ +SV_AddGravity + +============ +*/ +void SV_AddGravity (edict_t *ent) +{ + ent->velocity[2] -= ent->gravity * sv_gravity->value * FRAMETIME; +} + +/* +=============================================================================== + +PUSHMOVE + +=============================================================================== +*/ + +/* +============ +SV_PushEntity + +Does not change the entities velocity at all +============ +*/ +trace_t SV_PushEntity (edict_t *ent, vec3_t push) +{ + trace_t trace; + vec3_t start; + vec3_t end; + int mask; + + VectorCopy (ent->s.origin, start); + VectorAdd (start, push, end); + +retry: + if (ent->clipmask) + mask = ent->clipmask; + else + mask = MASK_SOLID; + + trace = gi.trace (start, ent->mins, ent->maxs, end, ent, mask); + + VectorCopy (trace.endpos, ent->s.origin); + gi.linkentity (ent); + + if (trace.fraction != 1.0) + { + SV_Impact (ent, &trace); + + // if the pushed entity went away and the pusher is still there + if (!trace.ent->inuse && ent->inuse) + { + // move the pusher back and try again + VectorCopy (start, ent->s.origin); + gi.linkentity (ent); + goto retry; + } + } + + if (ent->inuse) + G_TouchTriggers (ent); + + return trace; +} + + +typedef struct +{ + edict_t *ent; + vec3_t origin; + vec3_t angles; + float deltayaw; +} pushed_t; +pushed_t pushed[MAX_EDICTS], *pushed_p; + +edict_t *obstacle; + +/* +============ +SV_Push + +Objects need to be moved back on a failed push, +otherwise riders would continue to slide. +============ +*/ +qboolean SV_Push (edict_t *pusher, vec3_t move, vec3_t amove) +{ + int i, e; + edict_t *check, *block; + vec3_t mins, maxs; + pushed_t *p; + vec3_t org, org2, move2, forward, right, up; + + // clamp the move to 1/8 units, so the position will + // be accurate for client side prediction + for (i=0 ; i<3 ; i++) + { + float temp; + temp = move[i]*8.0; + if (temp > 0.0) + temp += 0.5; + else + temp -= 0.5; + move[i] = 0.125 * (int)temp; + } + + // find the bounding box + for (i=0 ; i<3 ; i++) + { + mins[i] = pusher->absmin[i] + move[i]; + maxs[i] = pusher->absmax[i] + move[i]; + } + +// we need this for pushing things later + VectorSubtract (vec3_origin, amove, org); + AngleVectors (org, forward, right, up); + +// save the pusher's original position + pushed_p->ent = pusher; + VectorCopy (pusher->s.origin, pushed_p->origin); + VectorCopy (pusher->s.angles, pushed_p->angles); + if (pusher->client) + pushed_p->deltayaw = pusher->client->ps.pmove.delta_angles[YAW]; + pushed_p++; + +// move the pusher to it's final position + VectorAdd (pusher->s.origin, move, pusher->s.origin); + VectorAdd (pusher->s.angles, amove, pusher->s.angles); + gi.linkentity (pusher); + +// see if any solid entities are inside the final position + check = g_edicts+1; + for (e = 1; e < globals.num_edicts; e++, check++) + { + if (!check->inuse) + continue; + if (check->movetype == MOVETYPE_PUSH + || check->movetype == MOVETYPE_STOP + || check->movetype == MOVETYPE_NONE + || check->movetype == MOVETYPE_NOCLIP) + continue; + + if (!check->area.prev) + continue; // not linked in anywhere + + // if the entity is standing on the pusher, it will definitely be moved + if (check->groundentity != pusher) + { + // see if the ent needs to be tested + if ( check->absmin[0] >= maxs[0] + || check->absmin[1] >= maxs[1] + || check->absmin[2] >= maxs[2] + || check->absmax[0] <= mins[0] + || check->absmax[1] <= mins[1] + || check->absmax[2] <= mins[2] ) + continue; + + // see if the ent's bbox is inside the pusher's final position + if (!SV_TestEntityPosition (check)) + continue; + } + + if ((pusher->movetype == MOVETYPE_PUSH) || (check->groundentity == pusher)) + { + // move this entity + pushed_p->ent = check; + VectorCopy (check->s.origin, pushed_p->origin); + VectorCopy (check->s.angles, pushed_p->angles); + pushed_p++; + + // try moving the contacted entity + VectorAdd (check->s.origin, move, check->s.origin); + if (check->client) + { // FIXME: doesn't rotate monsters? + check->client->ps.pmove.delta_angles[YAW] += amove[YAW]; + } + + // figure movement due to the pusher's amove + VectorSubtract (check->s.origin, pusher->s.origin, org); + org2[0] = DotProduct (org, forward); + org2[1] = -DotProduct (org, right); + org2[2] = DotProduct (org, up); + VectorSubtract (org2, org, move2); + VectorAdd (check->s.origin, move2, check->s.origin); + + // may have pushed them off an edge + if (check->groundentity != pusher) + check->groundentity = NULL; + + block = SV_TestEntityPosition (check); + if (!block) + { // pushed ok + gi.linkentity (check); + // impact? + continue; + } + + // if it is ok to leave in the old position, do it + // this is only relevent for riding entities, not pushed + // FIXME: this doesn't acount for rotation + VectorSubtract (check->s.origin, move, check->s.origin); + block = SV_TestEntityPosition (check); + if (!block) + { + pushed_p--; + continue; + } + } + + // save off the obstacle so we can call the block function + obstacle = check; + + // move back any entities we already moved + // go backwards, so if the same entity was pushed + // twice, it goes back to the original position + for (p=pushed_p-1 ; p>=pushed ; p--) + { + VectorCopy (p->origin, p->ent->s.origin); + VectorCopy (p->angles, p->ent->s.angles); + if (p->ent->client) + { + p->ent->client->ps.pmove.delta_angles[YAW] = p->deltayaw; + } + gi.linkentity (p->ent); + } + return false; + } + +//FIXME: is there a better way to handle this? + // see if anything we moved has touched a trigger + for (p=pushed_p-1 ; p>=pushed ; p--) + G_TouchTriggers (p->ent); + + return true; +} + +/* +================ +SV_Physics_Pusher + +Bmodel objects don't interact with each other, but +push all box objects +================ +*/ +void SV_Physics_Pusher (edict_t *ent) +{ + vec3_t move, amove; + edict_t *part, *mv; + + // if not a team captain, so movement will be handled elsewhere + if ( ent->flags & FL_TEAMSLAVE) + return; + + // make sure all team slaves can move before commiting + // any moves or calling any think functions + // if the move is blocked, all moved objects will be backed out +//retry: + pushed_p = pushed; + for (part = ent ; part ; part=part->teamchain) + { + if (part->velocity[0] || part->velocity[1] || part->velocity[2] || + part->avelocity[0] || part->avelocity[1] || part->avelocity[2] + ) + { // object is moving + VectorScale (part->velocity, FRAMETIME, move); + VectorScale (part->avelocity, FRAMETIME, amove); + + if (!SV_Push (part, move, amove)) + break; // move was blocked + } + } + if (pushed_p > &pushed[MAX_EDICTS]) + gi.error (ERR_FATAL, "pushed_p > &pushed[MAX_EDICTS], memory corrupted"); + + if (part) + { + // the move failed, bump all nextthink times and back out moves + for (mv = ent ; mv ; mv=mv->teamchain) + { + if (mv->nextthink > 0) + mv->nextthink += FRAMETIME; + } + + // if the pusher has a "blocked" function, call it + // otherwise, just stay in place until the obstacle is gone + if (part->blocked) + part->blocked (part, obstacle); +#if 0 + // if the pushed entity went away and the pusher is still there + if (!obstacle->inuse && part->inuse) + goto retry; +#endif + } + else + { + // the move succeeded, so call all think functions + for (part = ent ; part ; part=part->teamchain) + { + SV_RunThink (part); + } + } +} + +//================================================================== + +/* +============= +SV_Physics_None + +Non moving objects can only think +============= +*/ +void SV_Physics_None (edict_t *ent) +{ +// regular thinking + SV_RunThink (ent); +} + +/* +============= +SV_Physics_Noclip + +A moving object that doesn't obey physics +============= +*/ +void SV_Physics_Noclip (edict_t *ent) +{ +// regular thinking + if (!SV_RunThink (ent)) + return; + + VectorMA (ent->s.angles, FRAMETIME, ent->avelocity, ent->s.angles); + VectorMA (ent->s.origin, FRAMETIME, ent->velocity, ent->s.origin); + + gi.linkentity (ent); +} + +/* +============================================================================== + +TOSS / BOUNCE + +============================================================================== +*/ + +/* +============= +SV_Physics_Toss + +Toss, bounce, and fly movement. When onground, do nothing. +============= +*/ +void SV_Physics_Toss (edict_t *ent) +{ + trace_t trace; + vec3_t move; + float backoff; + edict_t *slave; + qboolean wasinwater; + qboolean isinwater; + vec3_t old_origin; + +// regular thinking + SV_RunThink (ent); + + // if not a team captain, so movement will be handled elsewhere + if ( ent->flags & FL_TEAMSLAVE) + return; + + if (ent->velocity[2] > 0) + ent->groundentity = NULL; + +// check for the groundentity going away + if (ent->groundentity) + if (!ent->groundentity->inuse) + ent->groundentity = NULL; + +// if onground, return without moving + if ( ent->groundentity ) + return; + + VectorCopy (ent->s.origin, old_origin); + + SV_CheckVelocity (ent); + +// add gravity + if (ent->movetype != MOVETYPE_FLY + && ent->movetype != MOVETYPE_FLYMISSILE + // RAFAEL + // move type for rippergun projectile + && ent->movetype != MOVETYPE_WALLBOUNCE) + SV_AddGravity (ent); + +// move angles + VectorMA (ent->s.angles, FRAMETIME, ent->avelocity, ent->s.angles); + +// move origin + VectorScale (ent->velocity, FRAMETIME, move); + trace = SV_PushEntity (ent, move); + if (!ent->inuse) + return; + + if (trace.fraction < 1) + { + // RAFAEL + if (ent->movetype == MOVETYPE_WALLBOUNCE) + backoff = 2.0; + // RAFAEL ( else ) + else if (ent->movetype == MOVETYPE_BOUNCE) + backoff = 1.5; + else + backoff = 1; + + ClipVelocity (ent->velocity, trace.plane.normal, ent->velocity, backoff); + + // RAFAEL + if (ent->movetype == MOVETYPE_WALLBOUNCE) + vectoangles (ent->velocity, ent->s.angles); + + // stop if on ground + // RAFAEL + if (trace.plane.normal[2] > 0.7 && ent->movetype != MOVETYPE_WALLBOUNCE) + { + if (ent->velocity[2] < 60 || ent->movetype != MOVETYPE_BOUNCE ) + { + ent->groundentity = trace.ent; + ent->groundentity_linkcount = trace.ent->linkcount; + VectorCopy (vec3_origin, ent->velocity); + VectorCopy (vec3_origin, ent->avelocity); + } + } + +// if (ent->touch) +// ent->touch (ent, trace.ent, &trace.plane, trace.surface); + } + +// check for water transition + wasinwater = (ent->watertype & MASK_WATER); + ent->watertype = gi.pointcontents (ent->s.origin); + isinwater = ent->watertype & MASK_WATER; + + if (isinwater) + ent->waterlevel = 1; + else + ent->waterlevel = 0; + + if (!wasinwater && isinwater) + gi.positioned_sound (old_origin, g_edicts, CHAN_AUTO, gi.soundindex("misc/h2ohit1.wav"), 1, 1, 0); + else if (wasinwater && !isinwater) + gi.positioned_sound (ent->s.origin, g_edicts, CHAN_AUTO, gi.soundindex("misc/h2ohit1.wav"), 1, 1, 0); + +// move teamslaves + for (slave = ent->teamchain; slave; slave = slave->teamchain) + { + VectorCopy (ent->s.origin, slave->s.origin); + gi.linkentity (slave); + } +} + +/* +=============================================================================== + +STEPPING MOVEMENT + +=============================================================================== +*/ + +/* +============= +SV_Physics_Step + +Monsters freefall when they don't have a ground entity, otherwise +all movement is done with discrete steps. + +This is also used for objects that have become still on the ground, but +will fall if the floor is pulled out from under them. +FIXME: is this true? +============= +*/ + +//FIXME: hacked in for E3 demo +#define sv_stopspeed 100 +#define sv_friction 6 +#define sv_waterfriction 1 + +void SV_AddRotationalFriction (edict_t *ent) +{ + int n; + float adjustment; + + VectorMA (ent->s.angles, FRAMETIME, ent->avelocity, ent->s.angles); + adjustment = FRAMETIME * sv_stopspeed * sv_friction; + for (n = 0; n < 3; n++) + { + if (ent->avelocity[n] > 0) + { + ent->avelocity[n] -= adjustment; + if (ent->avelocity[n] < 0) + ent->avelocity[n] = 0; + } + else + { + ent->avelocity[n] += adjustment; + if (ent->avelocity[n] > 0) + ent->avelocity[n] = 0; + } + } +} + +void SV_Physics_Step (edict_t *ent) +{ + qboolean wasonground; + qboolean hitsound = false; + float *vel; + float speed, newspeed, control; + float friction; + edict_t *groundentity; + int mask; + + // airborn monsters should always check for ground + if (!ent->groundentity) + M_CheckGround (ent); + + groundentity = ent->groundentity; + + SV_CheckVelocity (ent); + + if (groundentity) + wasonground = true; + else + wasonground = false; + + if (ent->avelocity[0] || ent->avelocity[1] || ent->avelocity[2]) + SV_AddRotationalFriction (ent); + + // add gravity except: + // flying monsters + // swimming monsters who are in the water + if (! wasonground) + if (!(ent->flags & FL_FLY)) + if (!((ent->flags & FL_SWIM) && (ent->waterlevel > 2))) + { + if (ent->velocity[2] < sv_gravity->value*-0.1) + hitsound = true; + if (ent->waterlevel == 0) + SV_AddGravity (ent); + } + + // friction for flying monsters that have been given vertical velocity + if ((ent->flags & FL_FLY) && (ent->velocity[2] != 0)) + { + speed = fabs(ent->velocity[2]); + control = speed < sv_stopspeed ? sv_stopspeed : speed; + friction = sv_friction/3; + newspeed = speed - (FRAMETIME * control * friction); + if (newspeed < 0) + newspeed = 0; + newspeed /= speed; + ent->velocity[2] *= newspeed; + } + + // friction for flying monsters that have been given vertical velocity + if ((ent->flags & FL_SWIM) && (ent->velocity[2] != 0)) + { + speed = fabs(ent->velocity[2]); + control = speed < sv_stopspeed ? sv_stopspeed : speed; + newspeed = speed - (FRAMETIME * control * sv_waterfriction * ent->waterlevel); + if (newspeed < 0) + newspeed = 0; + newspeed /= speed; + ent->velocity[2] *= newspeed; + } + + if (ent->velocity[2] || ent->velocity[1] || ent->velocity[0]) + { + // apply friction + // let dead monsters who aren't completely onground slide + if ((wasonground) || (ent->flags & (FL_SWIM|FL_FLY))) + if (!(ent->health <= 0.0 && !M_CheckBottom(ent))) + { + vel = ent->velocity; + speed = sqrt(vel[0]*vel[0] +vel[1]*vel[1]); + if (speed) + { + friction = sv_friction; + + control = speed < sv_stopspeed ? sv_stopspeed : speed; + newspeed = speed - FRAMETIME*control*friction; + + if (newspeed < 0) + newspeed = 0; + newspeed /= speed; + + vel[0] *= newspeed; + vel[1] *= newspeed; + } + } + + if (ent->svflags & SVF_MONSTER) + mask = MASK_MONSTERSOLID; + else + mask = MASK_SOLID; + SV_FlyMove (ent, FRAMETIME, mask); + + gi.linkentity (ent); + G_TouchTriggers (ent); + if (!ent->inuse) + return; + + if (ent->groundentity) + if (!wasonground) + if (hitsound) + gi.sound (ent, 0, gi.soundindex("world/land.wav"), 1, 1, 0); + } + +// regular thinking + SV_RunThink (ent); +} + +//============================================================================ +/* +================ +G_RunEntity + +================ +*/ +void G_RunEntity (edict_t *ent) +{ + if (ent->prethink) + ent->prethink (ent); + + switch ( (int)ent->movetype) + { + case MOVETYPE_PUSH: + case MOVETYPE_STOP: + SV_Physics_Pusher (ent); + break; + case MOVETYPE_NONE: + SV_Physics_None (ent); + break; + case MOVETYPE_NOCLIP: + SV_Physics_Noclip (ent); + break; + case MOVETYPE_STEP: + SV_Physics_Step (ent); + break; + case MOVETYPE_TOSS: + case MOVETYPE_BOUNCE: + case MOVETYPE_FLY: + case MOVETYPE_FLYMISSILE: + // RAFAEL + case MOVETYPE_WALLBOUNCE: + SV_Physics_Toss (ent); + break; + default: + gi.error ("SV_Physics: bad movetype %i", (int)ent->movetype); + } +} diff --git a/original/xatrix/g_save.c b/original/xatrix/g_save.c new file mode 100644 index 0000000..c3cb564 --- /dev/null +++ b/original/xatrix/g_save.c @@ -0,0 +1,752 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. + +#include "g_local.h" + +#define Function(f) {#f, f} + +mmove_t mmove_reloc; + +field_t fields[] = { + {"classname", FOFS(classname), F_LSTRING}, + {"model", FOFS(model), F_LSTRING}, + {"spawnflags", FOFS(spawnflags), F_INT}, + {"speed", FOFS(speed), F_FLOAT}, + {"accel", FOFS(accel), F_FLOAT}, + {"decel", FOFS(decel), F_FLOAT}, + {"target", FOFS(target), F_LSTRING}, + {"targetname", FOFS(targetname), F_LSTRING}, + {"pathtarget", FOFS(pathtarget), F_LSTRING}, + {"deathtarget", FOFS(deathtarget), F_LSTRING}, + {"killtarget", FOFS(killtarget), F_LSTRING}, + {"combattarget", FOFS(combattarget), F_LSTRING}, + {"message", FOFS(message), F_LSTRING}, + {"team", FOFS(team), F_LSTRING}, + {"wait", FOFS(wait), F_FLOAT}, + {"delay", FOFS(delay), F_FLOAT}, + {"random", FOFS(random), F_FLOAT}, + {"move_origin", FOFS(move_origin), F_VECTOR}, + {"move_angles", FOFS(move_angles), F_VECTOR}, + {"style", FOFS(style), F_INT}, + {"count", FOFS(count), F_INT}, + {"health", FOFS(health), F_INT}, + {"sounds", FOFS(sounds), F_INT}, + {"light", 0, F_IGNORE}, + {"dmg", FOFS(dmg), F_INT}, + {"mass", FOFS(mass), F_INT}, + {"volume", FOFS(volume), F_FLOAT}, + {"attenuation", FOFS(attenuation), F_FLOAT}, + {"map", FOFS(map), F_LSTRING}, + {"origin", FOFS(s.origin), F_VECTOR}, + {"angles", FOFS(s.angles), F_VECTOR}, + {"angle", FOFS(s.angles), F_ANGLEHACK}, + + {"goalentity", FOFS(goalentity), F_EDICT, FFL_NOSPAWN}, + {"movetarget", FOFS(movetarget), F_EDICT, FFL_NOSPAWN}, + {"enemy", FOFS(enemy), F_EDICT, FFL_NOSPAWN}, + {"oldenemy", FOFS(oldenemy), F_EDICT, FFL_NOSPAWN}, + {"activator", FOFS(activator), F_EDICT, FFL_NOSPAWN}, + {"groundentity", FOFS(groundentity), F_EDICT, FFL_NOSPAWN}, + {"teamchain", FOFS(teamchain), F_EDICT, FFL_NOSPAWN}, + {"teammaster", FOFS(teammaster), F_EDICT, FFL_NOSPAWN}, + {"owner", FOFS(owner), F_EDICT, FFL_NOSPAWN}, + {"mynoise", FOFS(mynoise), F_EDICT, FFL_NOSPAWN}, + {"mynoise2", FOFS(mynoise2), F_EDICT, FFL_NOSPAWN}, + {"target_ent", FOFS(target_ent), F_EDICT, FFL_NOSPAWN}, + {"chain", FOFS(chain), F_EDICT, FFL_NOSPAWN}, + + {"prethink", FOFS(prethink), F_FUNCTION, FFL_NOSPAWN}, + {"think", FOFS(think), F_FUNCTION, FFL_NOSPAWN}, + {"blocked", FOFS(blocked), F_FUNCTION, FFL_NOSPAWN}, + {"touch", FOFS(touch), F_FUNCTION, FFL_NOSPAWN}, + {"use", FOFS(use), F_FUNCTION, FFL_NOSPAWN}, + {"pain", FOFS(pain), F_FUNCTION, FFL_NOSPAWN}, + {"die", FOFS(die), F_FUNCTION, FFL_NOSPAWN}, + + {"stand", FOFS(monsterinfo.stand), F_FUNCTION, FFL_NOSPAWN}, + {"idle", FOFS(monsterinfo.idle), F_FUNCTION, FFL_NOSPAWN}, + {"search", FOFS(monsterinfo.search), F_FUNCTION, FFL_NOSPAWN}, + {"walk", FOFS(monsterinfo.walk), F_FUNCTION, FFL_NOSPAWN}, + {"run", FOFS(monsterinfo.run), F_FUNCTION, FFL_NOSPAWN}, + {"dodge", FOFS(monsterinfo.dodge), F_FUNCTION, FFL_NOSPAWN}, + {"attack", FOFS(monsterinfo.attack), F_FUNCTION, FFL_NOSPAWN}, + {"melee", FOFS(monsterinfo.melee), F_FUNCTION, FFL_NOSPAWN}, + {"sight", FOFS(monsterinfo.sight), F_FUNCTION, FFL_NOSPAWN}, + {"checkattack", FOFS(monsterinfo.checkattack), F_FUNCTION, FFL_NOSPAWN}, + {"currentmove", FOFS(monsterinfo.currentmove), F_MMOVE, FFL_NOSPAWN}, + + {"endfunc", FOFS(moveinfo.endfunc), F_FUNCTION, FFL_NOSPAWN}, + + // temp spawn vars -- only valid when the spawn function is called + {"lip", STOFS(lip), F_INT, FFL_SPAWNTEMP}, + {"distance", STOFS(distance), F_INT, FFL_SPAWNTEMP}, + {"height", STOFS(height), F_INT, FFL_SPAWNTEMP}, + {"noise", STOFS(noise), F_LSTRING, FFL_SPAWNTEMP}, + {"pausetime", STOFS(pausetime), F_FLOAT, FFL_SPAWNTEMP}, + {"item", STOFS(item), F_LSTRING, FFL_SPAWNTEMP}, + +//need for item field in edict struct, FFL_SPAWNTEMP item will be skipped on saves + {"item", FOFS(item), F_ITEM}, + + {"gravity", STOFS(gravity), F_LSTRING, FFL_SPAWNTEMP}, + {"sky", STOFS(sky), F_LSTRING, FFL_SPAWNTEMP}, + {"skyrotate", STOFS(skyrotate), F_FLOAT, FFL_SPAWNTEMP}, + {"skyaxis", STOFS(skyaxis), F_VECTOR, FFL_SPAWNTEMP}, + {"minyaw", STOFS(minyaw), F_FLOAT, FFL_SPAWNTEMP}, + {"maxyaw", STOFS(maxyaw), F_FLOAT, FFL_SPAWNTEMP}, + {"minpitch", STOFS(minpitch), F_FLOAT, FFL_SPAWNTEMP}, + {"maxpitch", STOFS(maxpitch), F_FLOAT, FFL_SPAWNTEMP}, + {"nextmap", STOFS(nextmap), F_LSTRING, FFL_SPAWNTEMP}, + + {0, 0, 0, 0} + +}; + +field_t levelfields[] = +{ + {"changemap", LLOFS(changemap), F_LSTRING}, + + {"sight_client", LLOFS(sight_client), F_EDICT}, + {"sight_entity", LLOFS(sight_entity), F_EDICT}, + {"sound_entity", LLOFS(sound_entity), F_EDICT}, + {"sound2_entity", LLOFS(sound2_entity), F_EDICT}, + + {NULL, 0, F_INT} +}; + +field_t clientfields[] = +{ + {"pers.weapon", CLOFS(pers.weapon), F_ITEM}, + {"pers.lastweapon", CLOFS(pers.lastweapon), F_ITEM}, + {"newweapon", CLOFS(newweapon), F_ITEM}, + + {NULL, 0, F_INT} +}; + +/* +============ +InitGame + +This will be called when the dll is first loaded, which +only happens when a new game is started or a save game +is loaded. +============ +*/ +void InitGame (void) +{ + gi.dprintf ("==== InitGame ====\n"); + + gun_x = gi.cvar ("gun_x", "0", 0); + gun_y = gi.cvar ("gun_y", "0", 0); + gun_z = gi.cvar ("gun_z", "0", 0); + + //FIXME: sv_ prefix is wrong for these + sv_rollspeed = gi.cvar ("sv_rollspeed", "200", 0); + sv_rollangle = gi.cvar ("sv_rollangle", "2", 0); + sv_maxvelocity = gi.cvar ("sv_maxvelocity", "2000", 0); + sv_gravity = gi.cvar ("sv_gravity", "800", 0); + + // noset vars + dedicated = gi.cvar ("dedicated", "0", CVAR_NOSET); + + // latched vars + sv_cheats = gi.cvar ("cheats", "0", CVAR_SERVERINFO|CVAR_LATCH); + gi.cvar ("gamename", GAMEVERSION , CVAR_SERVERINFO | CVAR_LATCH); + gi.cvar ("gamedate", __DATE__ , CVAR_SERVERINFO | CVAR_LATCH); + + maxclients = gi.cvar ("maxclients", "4", CVAR_SERVERINFO | CVAR_LATCH); + maxspectators = gi.cvar ("maxspectators", "4", CVAR_SERVERINFO); + deathmatch = gi.cvar ("deathmatch", "0", CVAR_LATCH); + coop = gi.cvar ("coop", "0", CVAR_LATCH); + skill = gi.cvar ("skill", "1", CVAR_LATCH); + maxentities = gi.cvar ("maxentities", "1024", CVAR_LATCH); + + // change anytime vars + dmflags = gi.cvar ("dmflags", "0", CVAR_SERVERINFO); + fraglimit = gi.cvar ("fraglimit", "0", CVAR_SERVERINFO); + timelimit = gi.cvar ("timelimit", "0", CVAR_SERVERINFO); + password = gi.cvar ("password", "", CVAR_USERINFO); + spectator_password = gi.cvar ("spectator_password", "", 0); + filterban = gi.cvar ("filterban", "1", 0); + + g_select_empty = gi.cvar ("g_select_empty", "0", CVAR_ARCHIVE); + + run_pitch = gi.cvar ("run_pitch", "0.002", 0); + run_roll = gi.cvar ("run_roll", "0.005", 0); + bob_up = gi.cvar ("bob_up", "0.005", 0); + bob_pitch = gi.cvar ("bob_pitch", "0.002", 0); + bob_roll = gi.cvar ("bob_roll", "0.002", 0); + + // flood control + flood_msgs = gi.cvar ("flood_msgs", "4", 0); + flood_persecond = gi.cvar ("flood_persecond", "4", 0); + flood_waitdelay = gi.cvar ("flood_waitdelay", "10", 0); + + // dm map list + sv_maplist = gi.cvar ("sv_maplist", "", 0); + + // items + InitItems (); + + Com_sprintf (game.helpmessage1, sizeof(game.helpmessage1), ""); + + Com_sprintf (game.helpmessage2, sizeof(game.helpmessage2), ""); + + // initialize all entities for this game + game.maxentities = maxentities->value; + g_edicts = gi.TagMalloc (game.maxentities * sizeof(g_edicts[0]), TAG_GAME); + globals.edicts = g_edicts; + globals.max_edicts = game.maxentities; + + // initialize all clients for this game + game.maxclients = maxclients->value; + game.clients = gi.TagMalloc (game.maxclients * sizeof(game.clients[0]), TAG_GAME); + globals.num_edicts = game.maxclients+1; +} + +//========================================================= + +void WriteField1 (FILE *f, field_t *field, byte *base) +{ + void *p; + int len; + int index; + + if (field->flags & FFL_SPAWNTEMP) + return; + + p = (void *)(base + field->ofs); + switch (field->type) + { + case F_INT: + case F_FLOAT: + case F_ANGLEHACK: + case F_VECTOR: + case F_IGNORE: + break; + + case F_LSTRING: + case F_GSTRING: + if ( *(char **)p ) + len = strlen(*(char **)p) + 1; + else + len = 0; + *(int *)p = len; + break; + case F_EDICT: + if ( *(edict_t **)p == NULL) + index = -1; + else + index = *(edict_t **)p - g_edicts; + *(int *)p = index; + break; + case F_CLIENT: + if ( *(gclient_t **)p == NULL) + index = -1; + else + index = *(gclient_t **)p - game.clients; + *(int *)p = index; + break; + case F_ITEM: + if ( *(edict_t **)p == NULL) + index = -1; + else + index = *(gitem_t **)p - itemlist; + *(int *)p = index; + break; + + //relative to code segment + case F_FUNCTION: + if (*(byte **)p == NULL) + index = 0; + else + index = *(byte **)p - ((byte *)InitGame); + *(int *)p = index; + break; + + //relative to data segment + case F_MMOVE: + if (*(byte **)p == NULL) + index = 0; + else + index = *(byte **)p - (byte *)&mmove_reloc; + *(int *)p = index; + break; + + default: + gi.error ("WriteEdict: unknown field type"); + } +} + + +void WriteField2 (FILE *f, field_t *field, byte *base) +{ + int len; + void *p; + + if (field->flags & FFL_SPAWNTEMP) + return; + + p = (void *)(base + field->ofs); + switch (field->type) + { + case F_LSTRING: + if ( *(char **)p ) + { + len = strlen(*(char **)p) + 1; + fwrite (*(char **)p, len, 1, f); + } + break; + } +} + +void ReadField (FILE *f, field_t *field, byte *base) +{ + void *p; + int len; + int index; + + if (field->flags & FFL_SPAWNTEMP) + return; + + p = (void *)(base + field->ofs); + switch (field->type) + { + case F_INT: + case F_FLOAT: + case F_ANGLEHACK: + case F_VECTOR: + case F_IGNORE: + break; + + case F_LSTRING: + len = *(int *)p; + if (!len) + *(char **)p = NULL; + else + { + *(char **)p = gi.TagMalloc (len, TAG_LEVEL); + fread (*(char **)p, len, 1, f); + } + break; + case F_EDICT: + index = *(int *)p; + if ( index == -1 ) + *(edict_t **)p = NULL; + else + *(edict_t **)p = &g_edicts[index]; + break; + case F_CLIENT: + index = *(int *)p; + if ( index == -1 ) + *(gclient_t **)p = NULL; + else + *(gclient_t **)p = &game.clients[index]; + break; + case F_ITEM: + index = *(int *)p; + if ( index == -1 ) + *(gitem_t **)p = NULL; + else + *(gitem_t **)p = &itemlist[index]; + break; + + //relative to code segment + case F_FUNCTION: + index = *(int *)p; + if ( index == 0 ) + *(byte **)p = NULL; + else + *(byte **)p = ((byte *)InitGame) + index; + break; + + //relative to data segment + case F_MMOVE: + index = *(int *)p; + if (index == 0) + *(byte **)p = NULL; + else + *(byte **)p = (byte *)&mmove_reloc + index; + break; + + default: + gi.error ("ReadEdict: unknown field type"); + } +} + +//========================================================= + +/* +============== +WriteClient + +All pointer variables (except function pointers) must be handled specially. +============== +*/ +void WriteClient (FILE *f, gclient_t *client) +{ + field_t *field; + gclient_t temp; + + // all of the ints, floats, and vectors stay as they are + temp = *client; + + // change the pointers to lengths or indexes + for (field=clientfields ; field->name ; field++) + { + WriteField1 (f, field, (byte *)&temp); + } + + // write the block + fwrite (&temp, sizeof(temp), 1, f); + + // now write any allocated data following the edict + for (field=clientfields ; field->name ; field++) + { + WriteField2 (f, field, (byte *)client); + } +} + +/* +============== +ReadClient + +All pointer variables (except function pointers) must be handled specially. +============== +*/ +void ReadClient (FILE *f, gclient_t *client) +{ + field_t *field; + + fread (client, sizeof(*client), 1, f); + + for (field=clientfields ; field->name ; field++) + { + ReadField (f, field, (byte *)client); + } +} + +/* +============ +WriteGame + +This will be called whenever the game goes to a new level, +and when the user explicitly saves the game. + +Game information include cross level data, like multi level +triggers, help computer info, and all client states. + +A single player death will automatically restore from the +last save position. +============ +*/ +void WriteGame (char *filename, qboolean autosave) +{ + FILE *f; + int i; + char str[16]; + + if (!autosave) + SaveClientData (); + + f = fopen (filename, "wb"); + if (!f) + gi.error ("Couldn't open %s", filename); + + memset (str, 0, sizeof(str)); + strcpy (str, __DATE__); + fwrite (str, sizeof(str), 1, f); + + game.autosaved = autosave; + fwrite (&game, sizeof(game), 1, f); + game.autosaved = false; + + for (i=0 ; iname ; field++) + { + WriteField1 (f, field, (byte *)&temp); + } + + // write the block + fwrite (&temp, sizeof(temp), 1, f); + + // now write any allocated data following the edict + for (field=fields ; field->name ; field++) + { + WriteField2 (f, field, (byte *)ent); + } + +} + +/* +============== +WriteLevelLocals + +All pointer variables (except function pointers) must be handled specially. +============== +*/ +void WriteLevelLocals (FILE *f) +{ + field_t *field; + level_locals_t temp; + + // all of the ints, floats, and vectors stay as they are + temp = level; + + // change the pointers to lengths or indexes + for (field=levelfields ; field->name ; field++) + { + WriteField1 (f, field, (byte *)&temp); + } + + // write the block + fwrite (&temp, sizeof(temp), 1, f); + + // now write any allocated data following the edict + for (field=levelfields ; field->name ; field++) + { + WriteField2 (f, field, (byte *)&level); + } +} + + +/* +============== +ReadEdict + +All pointer variables (except function pointers) must be handled specially. +============== +*/ +void ReadEdict (FILE *f, edict_t *ent) +{ + field_t *field; + + fread (ent, sizeof(*ent), 1, f); + + for (field=fields ; field->name ; field++) + { + ReadField (f, field, (byte *)ent); + } +} + +/* +============== +ReadLevelLocals + +All pointer variables (except function pointers) must be handled specially. +============== +*/ +void ReadLevelLocals (FILE *f) +{ + field_t *field; + + fread (&level, sizeof(level), 1, f); + + for (field=levelfields ; field->name ; field++) + { + ReadField (f, field, (byte *)&level); + } +} + +/* +================= +WriteLevel + +================= +*/ +void WriteLevel (char *filename) +{ + int i; + edict_t *ent; + FILE *f; + void *base; + + f = fopen (filename, "wb"); + if (!f) + gi.error ("Couldn't open %s", filename); + + // write out edict size for checking + i = sizeof(edict_t); + fwrite (&i, sizeof(i), 1, f); + + // write out a function pointer for checking + base = (void *)InitGame; + fwrite (&base, sizeof(base), 1, f); + + // write out level_locals_t + WriteLevelLocals (f); + + // write out all the entities + for (i=0 ; iinuse) + continue; + fwrite (&i, sizeof(i), 1, f); + WriteEdict (f, ent); + } + i = -1; + fwrite (&i, sizeof(i), 1, f); + + fclose (f); +} + + +/* +================= +ReadLevel + +SpawnEntities will allready have been called on the +level the same way it was when the level was saved. + +That is necessary to get the baselines +set up identically. + +The server will have cleared all of the world links before +calling ReadLevel. + +No clients are connected yet. +================= +*/ +void ReadLevel (char *filename) +{ + int entnum; + FILE *f; + int i; + void *base; + edict_t *ent; + + f = fopen (filename, "rb"); + if (!f) + gi.error ("Couldn't open %s", filename); + + // free any dynamic memory allocated by loading the level + // base state + gi.FreeTags (TAG_LEVEL); + + // wipe all the entities + memset (g_edicts, 0, game.maxentities*sizeof(g_edicts[0])); + globals.num_edicts = maxclients->value+1; + + // check edict size + fread (&i, sizeof(i), 1, f); + if (i != sizeof(edict_t)) + { + fclose (f); + gi.error ("ReadLevel: mismatched edict size"); + } + + // check function pointer base address + fread (&base, sizeof(base), 1, f); +#ifdef _WIN32 + if (base != (void *)InitGame) + { + fclose (f); + gi.error ("ReadLevel: function pointers have moved"); + } +#else + gi.dprintf("Function offsets %d\n", ((byte *)base) - ((byte *)InitGame)); +#endif + + // load the level locals + ReadLevelLocals (f); + + // load all the entities + while (1) + { + if (fread (&entnum, sizeof(entnum), 1, f) != 1) + { + fclose (f); + gi.error ("ReadLevel: failed to read entnum"); + } + if (entnum == -1) + break; + if (entnum >= globals.num_edicts) + globals.num_edicts = entnum+1; + + ent = &g_edicts[entnum]; + ReadEdict (f, ent); + + // let the server rebuild world links for this ent + memset (&ent->area, 0, sizeof(ent->area)); + gi.linkentity (ent); + } + + fclose (f); + + // mark all clients as unconnected + for (i=0 ; ivalue ; i++) + { + ent = &g_edicts[i+1]; + ent->client = game.clients + i; + ent->client->pers.connected = false; + } + + // do any load time things at this point + for (i=0 ; iinuse) + continue; + + // fire any cross-level triggers + if (ent->classname) + if (strcmp(ent->classname, "target_crosslevel_target") == 0) + ent->nextthink = level.time + ent->delay; + } +} diff --git a/original/xatrix/g_spawn.c b/original/xatrix/g_spawn.c new file mode 100644 index 0000000..50250b0 --- /dev/null +++ b/original/xatrix/g_spawn.c @@ -0,0 +1,1022 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. + +#include "g_local.h" + +typedef struct +{ + char *name; + void (*spawn)(edict_t *ent); +} spawn_t; + + +void SP_item_health (edict_t *self); +void SP_item_health_small (edict_t *self); +void SP_item_health_large (edict_t *self); +void SP_item_health_mega (edict_t *self); + +void SP_info_player_start (edict_t *ent); +void SP_info_player_deathmatch (edict_t *ent); +void SP_info_player_coop (edict_t *ent); +void SP_info_player_intermission (edict_t *ent); + +void SP_func_plat (edict_t *ent); +void SP_func_rotating (edict_t *ent); +void SP_func_button (edict_t *ent); +void SP_func_door (edict_t *ent); +void SP_func_door_secret (edict_t *ent); +void SP_func_door_rotating (edict_t *ent); +void SP_func_water (edict_t *ent); +void SP_func_train (edict_t *ent); +void SP_func_conveyor (edict_t *self); +void SP_func_wall (edict_t *self); +void SP_func_object (edict_t *self); +void SP_func_explosive (edict_t *self); +void SP_func_timer (edict_t *self); +void SP_func_areaportal (edict_t *ent); +void SP_func_clock (edict_t *ent); +void SP_func_killbox (edict_t *ent); + +void SP_trigger_always (edict_t *ent); +void SP_trigger_once (edict_t *ent); +void SP_trigger_multiple (edict_t *ent); +void SP_trigger_relay (edict_t *ent); +void SP_trigger_push (edict_t *ent); +void SP_trigger_hurt (edict_t *ent); +void SP_trigger_key (edict_t *ent); +void SP_trigger_counter (edict_t *ent); +void SP_trigger_elevator (edict_t *ent); +void SP_trigger_gravity (edict_t *ent); +void SP_trigger_monsterjump (edict_t *ent); + +void SP_target_temp_entity (edict_t *ent); +void SP_target_speaker (edict_t *ent); +void SP_target_explosion (edict_t *ent); +void SP_target_changelevel (edict_t *ent); +void SP_target_secret (edict_t *ent); +void SP_target_goal (edict_t *ent); +void SP_target_splash (edict_t *ent); +void SP_target_spawner (edict_t *ent); +void SP_target_blaster (edict_t *ent); +void SP_target_crosslevel_trigger (edict_t *ent); +void SP_target_crosslevel_target (edict_t *ent); +void SP_target_laser (edict_t *self); +void SP_target_help (edict_t *ent); +void SP_target_actor (edict_t *ent); +void SP_target_lightramp (edict_t *self); +void SP_target_earthquake (edict_t *ent); +void SP_target_character (edict_t *ent); +void SP_target_string (edict_t *ent); + +void SP_worldspawn (edict_t *ent); +void SP_viewthing (edict_t *ent); + +void SP_light (edict_t *self); +void SP_light_mine1 (edict_t *ent); +void SP_light_mine2 (edict_t *ent); +void SP_info_null (edict_t *self); +void SP_info_notnull (edict_t *self); +void SP_path_corner (edict_t *self); +void SP_point_combat (edict_t *self); + +void SP_misc_explobox (edict_t *self); +void SP_misc_banner (edict_t *self); +void SP_misc_satellite_dish (edict_t *self); +void SP_misc_actor (edict_t *self); +void SP_misc_gib_arm (edict_t *self); +void SP_misc_gib_leg (edict_t *self); +void SP_misc_gib_head (edict_t *self); +void SP_misc_insane (edict_t *self); +void SP_misc_deadsoldier (edict_t *self); +void SP_misc_viper (edict_t *self); +void SP_misc_viper_bomb (edict_t *self); +void SP_misc_bigviper (edict_t *self); +void SP_misc_strogg_ship (edict_t *self); +void SP_misc_teleporter (edict_t *self); +void SP_misc_teleporter_dest (edict_t *self); +void SP_misc_blackhole (edict_t *self); +void SP_misc_eastertank (edict_t *self); +void SP_misc_easterchick (edict_t *self); +void SP_misc_easterchick2 (edict_t *self); + +void SP_monster_berserk (edict_t *self); +void SP_monster_gladiator (edict_t *self); +void SP_monster_gunner (edict_t *self); +void SP_monster_infantry (edict_t *self); +void SP_monster_soldier_light (edict_t *self); +void SP_monster_soldier (edict_t *self); +void SP_monster_soldier_ss (edict_t *self); +void SP_monster_tank (edict_t *self); +void SP_monster_medic (edict_t *self); +void SP_monster_flipper (edict_t *self); +void SP_monster_chick (edict_t *self); +void SP_monster_parasite (edict_t *self); +void SP_monster_flyer (edict_t *self); +void SP_monster_brain (edict_t *self); +void SP_monster_floater (edict_t *self); +void SP_monster_hover (edict_t *self); +void SP_monster_mutant (edict_t *self); +void SP_monster_supertank (edict_t *self); +void SP_monster_boss2 (edict_t *self); +void SP_monster_jorg (edict_t *self); +void SP_monster_boss3_stand (edict_t *self); + +void SP_monster_commander_body (edict_t *self); + +void SP_turret_breach (edict_t *self); +void SP_turret_base (edict_t *self); +void SP_turret_driver (edict_t *self); + +// RAFAEL 14-APR-98 +void SP_monster_soldier_hypergun (edict_t *self); +void SP_monster_soldier_lasergun (edict_t *self); +void SP_monster_soldier_ripper (edict_t *self); +void SP_monster_fixbot (edict_t *self); +void SP_monster_gekk (edict_t *self); +void SP_monster_chick_heat (edict_t *self); +void SP_monster_gladb (edict_t *self); +void SP_monster_boss5 (edict_t *self); +void SP_rotating_light (edict_t *self); +void SP_object_repair (edict_t *self); +void SP_misc_crashviper (edict_t *ent); +void SP_misc_viper_missile (edict_t *self); +void SP_misc_amb4 (edict_t *ent); +void SP_target_mal_laser (edict_t *ent); +void SP_misc_transport (edict_t *ent); +// END 14-APR-98 + +void SP_misc_nuke (edict_t *ent); + + +spawn_t spawns[] = { + {"item_health", SP_item_health}, + {"item_health_small", SP_item_health_small}, + {"item_health_large", SP_item_health_large}, + {"item_health_mega", SP_item_health_mega}, + + {"info_player_start", SP_info_player_start}, + {"info_player_deathmatch", SP_info_player_deathmatch}, + {"info_player_coop", SP_info_player_coop}, + {"info_player_intermission", SP_info_player_intermission}, + + {"func_plat", SP_func_plat}, + {"func_button", SP_func_button}, + {"func_door", SP_func_door}, + {"func_door_secret", SP_func_door_secret}, + {"func_door_rotating", SP_func_door_rotating}, + {"func_rotating", SP_func_rotating}, + {"func_train", SP_func_train}, + {"func_water", SP_func_water}, + {"func_conveyor", SP_func_conveyor}, + {"func_areaportal", SP_func_areaportal}, + {"func_clock", SP_func_clock}, + {"func_wall", SP_func_wall}, + {"func_object", SP_func_object}, + {"func_timer", SP_func_timer}, + {"func_explosive", SP_func_explosive}, + {"func_killbox", SP_func_killbox}, + + // RAFAEL + {"func_object_repair", SP_object_repair}, + {"rotating_light", SP_rotating_light}, + + {"trigger_always", SP_trigger_always}, + {"trigger_once", SP_trigger_once}, + {"trigger_multiple", SP_trigger_multiple}, + {"trigger_relay", SP_trigger_relay}, + {"trigger_push", SP_trigger_push}, + {"trigger_hurt", SP_trigger_hurt}, + {"trigger_key", SP_trigger_key}, + {"trigger_counter", SP_trigger_counter}, + {"trigger_elevator", SP_trigger_elevator}, + {"trigger_gravity", SP_trigger_gravity}, + {"trigger_monsterjump", SP_trigger_monsterjump}, + + {"target_temp_entity", SP_target_temp_entity}, + {"target_speaker", SP_target_speaker}, + {"target_explosion", SP_target_explosion}, + {"target_changelevel", SP_target_changelevel}, + {"target_secret", SP_target_secret}, + {"target_goal", SP_target_goal}, + {"target_splash", SP_target_splash}, + {"target_spawner", SP_target_spawner}, + {"target_blaster", SP_target_blaster}, + {"target_crosslevel_trigger", SP_target_crosslevel_trigger}, + {"target_crosslevel_target", SP_target_crosslevel_target}, + {"target_laser", SP_target_laser}, + {"target_help", SP_target_help}, + {"target_actor", SP_target_actor}, + {"target_lightramp", SP_target_lightramp}, + {"target_earthquake", SP_target_earthquake}, + {"target_character", SP_target_character}, + {"target_string", SP_target_string}, + + // RAFAEL 15-APR-98 + {"target_mal_laser", SP_target_mal_laser}, + + + {"worldspawn", SP_worldspawn}, + {"viewthing", SP_viewthing}, + + {"light", SP_light}, + {"light_mine1", SP_light_mine1}, + {"light_mine2", SP_light_mine2}, + {"info_null", SP_info_null}, + {"func_group", SP_info_null}, + {"info_notnull", SP_info_notnull}, + {"path_corner", SP_path_corner}, + {"point_combat", SP_point_combat}, + + {"misc_explobox", SP_misc_explobox}, + {"misc_banner", SP_misc_banner}, + {"misc_satellite_dish", SP_misc_satellite_dish}, + {"misc_actor", SP_misc_actor}, + {"misc_gib_arm", SP_misc_gib_arm}, + {"misc_gib_leg", SP_misc_gib_leg}, + {"misc_gib_head", SP_misc_gib_head}, + {"misc_insane", SP_misc_insane}, + {"misc_deadsoldier", SP_misc_deadsoldier}, + {"misc_viper", SP_misc_viper}, + {"misc_viper_bomb", SP_misc_viper_bomb}, + {"misc_bigviper", SP_misc_bigviper}, + {"misc_strogg_ship", SP_misc_strogg_ship}, + {"misc_teleporter", SP_misc_teleporter}, + {"misc_teleporter_dest", SP_misc_teleporter_dest}, + {"misc_blackhole", SP_misc_blackhole}, + {"misc_eastertank", SP_misc_eastertank}, + {"misc_easterchick", SP_misc_easterchick}, + {"misc_easterchick2", SP_misc_easterchick2}, + // RAFAEL + {"misc_crashviper", SP_misc_crashviper}, + {"misc_viper_missile", SP_misc_viper_missile}, + {"misc_amb4", SP_misc_amb4}, + // RAFAEL 17-APR-98 + {"misc_transport", SP_misc_transport}, + // END 17-APR-98 + // RAFAEL 12-MAY-98 + {"misc_nuke", SP_misc_nuke}, + + {"monster_berserk", SP_monster_berserk}, + {"monster_gladiator", SP_monster_gladiator}, + {"monster_gunner", SP_monster_gunner}, + {"monster_infantry", SP_monster_infantry}, + {"monster_soldier_light", SP_monster_soldier_light}, + {"monster_soldier", SP_monster_soldier}, + {"monster_soldier_ss", SP_monster_soldier_ss}, + {"monster_tank", SP_monster_tank}, + {"monster_tank_commander", SP_monster_tank}, + {"monster_medic", SP_monster_medic}, + {"monster_flipper", SP_monster_flipper}, + {"monster_chick", SP_monster_chick}, + {"monster_parasite", SP_monster_parasite}, + {"monster_flyer", SP_monster_flyer}, + {"monster_brain", SP_monster_brain}, + {"monster_floater", SP_monster_floater}, + {"monster_hover", SP_monster_hover}, + {"monster_mutant", SP_monster_mutant}, + {"monster_supertank", SP_monster_supertank}, + {"monster_boss2", SP_monster_boss2}, + {"monster_boss3_stand", SP_monster_boss3_stand}, + {"monster_jorg", SP_monster_jorg}, + + {"monster_commander_body", SP_monster_commander_body}, + + // RAFAEL 14-APR-98 + {"monster_soldier_hypergun", SP_monster_soldier_hypergun}, + {"monster_soldier_lasergun", SP_monster_soldier_lasergun}, + {"monster_soldier_ripper", SP_monster_soldier_ripper}, + {"monster_fixbot", SP_monster_fixbot}, + {"monster_gekk", SP_monster_gekk}, + {"monster_chick_heat", SP_monster_chick_heat}, + {"monster_gladb", SP_monster_gladb}, + {"monster_boss5", SP_monster_boss5}, + // END 14-APR-98 + + {"turret_breach", SP_turret_breach}, + {"turret_base", SP_turret_base}, + {"turret_driver", SP_turret_driver}, + + {NULL, NULL} +}; + +/* +=============== +ED_CallSpawn + +Finds the spawn function for the entity and calls it +=============== +*/ +void ED_CallSpawn (edict_t *ent) +{ + spawn_t *s; + gitem_t *item; + int i; + + if (!ent->classname) + { + gi.dprintf ("ED_CallSpawn: NULL classname\n"); + return; + } + + // check item spawn functions + for (i=0,item=itemlist ; iclassname) + continue; + if (!strcmp(item->classname, ent->classname)) + { // found it + SpawnItem (ent, item); + return; + } + } + + // check normal spawn functions + for (s=spawns ; s->name ; s++) + { + if (!strcmp(s->name, ent->classname)) + { // found it + s->spawn (ent); + return; + } + } + gi.dprintf ("%s doesn't have a spawn function\n", ent->classname); +} + +/* +============= +ED_NewString +============= +*/ +char *ED_NewString (char *string) +{ + char *newb, *new_p; + int i,l; + + l = strlen(string) + 1; + + newb = gi.TagMalloc (l, TAG_LEVEL); + + new_p = newb; + + for (i=0 ; i< l ; i++) + { + if (string[i] == '\\' && i < l-1) + { + i++; + if (string[i] == 'n') + *new_p++ = '\n'; + else + *new_p++ = '\\'; + } + else + *new_p++ = string[i]; + } + + return newb; +} + + + + +/* +=============== +ED_ParseField + +Takes a key/value pair and sets the binary values +in an edict +=============== +*/ +void ED_ParseField (char *key, char *value, edict_t *ent) +{ + field_t *f; + byte *b; + float v; + vec3_t vec; + + for (f=fields ; f->name ; f++) + { + if (!(f->flags & FFL_NOSPAWN) && !Q_stricmp(f->name, key)) + { // found it + if (f->flags & FFL_SPAWNTEMP) + b = (byte *)&st; + else + b = (byte *)ent; + + switch (f->type) + { + case F_LSTRING: + *(char **)(b+f->ofs) = ED_NewString (value); + break; + case F_VECTOR: + sscanf (value, "%f %f %f", &vec[0], &vec[1], &vec[2]); + ((float *)(b+f->ofs))[0] = vec[0]; + ((float *)(b+f->ofs))[1] = vec[1]; + ((float *)(b+f->ofs))[2] = vec[2]; + break; + case F_INT: + *(int *)(b+f->ofs) = atoi(value); + break; + case F_FLOAT: + *(float *)(b+f->ofs) = atof(value); + break; + case F_ANGLEHACK: + v = atof(value); + ((float *)(b+f->ofs))[0] = 0; + ((float *)(b+f->ofs))[1] = v; + ((float *)(b+f->ofs))[2] = 0; + break; + case F_IGNORE: + break; + } + return; + } + } + gi.dprintf ("%s is not a field\n", key); +} + +/* +==================== +ED_ParseEdict + +Parses an edict out of the given string, returning the new position +ed should be a properly initialized empty edict. +==================== +*/ +char *ED_ParseEdict (char *data, edict_t *ent) +{ + qboolean init; + char keyname[256]; + char *com_token; + + init = false; + memset (&st, 0, sizeof(st)); + +// go through all the dictionary pairs + while (1) + { + // parse key + com_token = COM_Parse (&data); + if (com_token[0] == '}') + break; + if (!data) + gi.error ("ED_ParseEntity: EOF without closing brace"); + + strncpy (keyname, com_token, sizeof(keyname)-1); + + // parse value + com_token = COM_Parse (&data); + if (!data) + gi.error ("ED_ParseEntity: EOF without closing brace"); + + if (com_token[0] == '}') + gi.error ("ED_ParseEntity: closing brace without data"); + + init = true; + + // keynames with a leading underscore are used for utility comments, + // and are immediately discarded by quake + if (keyname[0] == '_') + continue; + + ED_ParseField (keyname, com_token, ent); + } + + if (!init) + memset (ent, 0, sizeof(*ent)); + + return data; +} + + +/* +================ +G_FindTeams + +Chain together all entities with a matching team field. + +All but the first will have the FL_TEAMSLAVE flag set. +All but the last will have the teamchain field set to the next one +================ +*/ +void G_FindTeams (void) +{ + edict_t *e, *e2, *chain; + int i, j; + int c, c2; + + c = 0; + c2 = 0; + for (i=1, e=g_edicts+i ; i < globals.num_edicts ; i++,e++) + { + if (!e->inuse) + continue; + if (!e->team) + continue; + if (e->flags & FL_TEAMSLAVE) + continue; + chain = e; + e->teammaster = e; + c++; + c2++; + for (j=i+1, e2=e+1 ; j < globals.num_edicts ; j++,e2++) + { + if (!e2->inuse) + continue; + if (!e2->team) + continue; + if (e2->flags & FL_TEAMSLAVE) + continue; + if (!strcmp(e->team, e2->team)) + { + c2++; + chain->teamchain = e2; + e2->teammaster = e; + chain = e2; + e2->flags |= FL_TEAMSLAVE; + } + } + } + + gi.dprintf ("%i teams with %i entities\n", c, c2); +} + +/* +============== +SpawnEntities + +Creates a server's entity / program execution context by +parsing textual entity definitions out of an ent file. +============== +*/ +void SpawnEntities (char *mapname, char *entities, char *spawnpoint) +{ + edict_t *ent; + int inhibit; + char *com_token; + int i; + float skill_level; + + skill_level = floor (skill->value); + if (skill_level < 0) + skill_level = 0; + if (skill_level > 3) + skill_level = 3; + if (skill->value != skill_level) + gi.cvar_forceset("skill", va("%f", skill_level)); + + SaveClientData (); + + gi.FreeTags (TAG_LEVEL); + + memset (&level, 0, sizeof(level)); + memset (g_edicts, 0, game.maxentities * sizeof (g_edicts[0])); + + strncpy (level.mapname, mapname, sizeof(level.mapname)-1); + strncpy (game.spawnpoint, spawnpoint, sizeof(game.spawnpoint)-1); + + // set client fields on player ents + for (i=0 ; iclassname, "trigger_once") && !stricmp(ent->model, "*27")) + ent->spawnflags &= ~SPAWNFLAG_NOT_HARD; + + // remove things (except the world) from different skill levels or deathmatch + if (ent != g_edicts) + { + if (deathmatch->value) + { + if ( ent->spawnflags & SPAWNFLAG_NOT_DEATHMATCH ) + { + G_FreeEdict (ent); + inhibit++; + continue; + } + } + else + { + if ( /* ((coop->value) && (ent->spawnflags & SPAWNFLAG_NOT_COOP)) || */ + ((skill->value == 0) && (ent->spawnflags & SPAWNFLAG_NOT_EASY)) || + ((skill->value == 1) && (ent->spawnflags & SPAWNFLAG_NOT_MEDIUM)) || + (((skill->value == 2) || (skill->value == 3)) && (ent->spawnflags & SPAWNFLAG_NOT_HARD)) + ) + { + G_FreeEdict (ent); + inhibit++; + continue; + } + } + + ent->spawnflags &= ~(SPAWNFLAG_NOT_EASY|SPAWNFLAG_NOT_MEDIUM|SPAWNFLAG_NOT_HARD|SPAWNFLAG_NOT_COOP|SPAWNFLAG_NOT_DEATHMATCH); + } + + ED_CallSpawn (ent); + } + + gi.dprintf ("%i entities inhibited\n", inhibit); + +#ifdef DEBUG + i = 1; + ent = EDICT_NUM(i); + while (i < globals.num_edicts) { + if (ent->inuse != 0 || ent->inuse != 1) + Com_DPrintf("Invalid entity %d\n", i); + i++, ent++; + } +#endif + + G_FindTeams (); + + PlayerTrail_Init (); +} + + +//=================================================================== + +#if 0 + // cursor positioning + xl + xr + yb + yt + xv + yv + + // drawing + statpic + pic + num + string + + // control + if + ifeq + ifbit + endif + +#endif + +char *single_statusbar = +"yb -24 " + +// health +"xv 0 " +"hnum " +"xv 50 " +"pic 0 " + +// ammo +"if 2 " +" xv 100 " +" anum " +" xv 150 " +" pic 2 " +"endif " + +// armor +"if 4 " +" xv 200 " +" rnum " +" xv 250 " +" pic 4 " +"endif " + +// selected item +"if 6 " +" xv 296 " +" pic 6 " +"endif " + +"yb -50 " + +// picked up item +"if 7 " +" xv 0 " +" pic 7 " +" xv 26 " +" yb -42 " +" stat_string 8 " +" yb -50 " +"endif " + +// timer +"if 9 " +" xv 262 " +" num 2 10 " +" xv 296 " +" pic 9 " +"endif " + +// help / weapon icon +"if 11 " +" xv 148 " +" pic 11 " +"endif " +; + +char *dm_statusbar = +"yb -24 " + +// health +"xv 0 " +"hnum " +"xv 50 " +"pic 0 " + +// ammo +"if 2 " +" xv 100 " +" anum " +" xv 150 " +" pic 2 " +"endif " + +// armor +"if 4 " +" xv 200 " +" rnum " +" xv 250 " +" pic 4 " +"endif " + +// selected item +"if 6 " +" xv 296 " +" pic 6 " +"endif " + +"yb -50 " + +// picked up item +"if 7 " +" xv 0 " +" pic 7 " +" xv 26 " +" yb -42 " +" stat_string 8 " +" yb -50 " +"endif " + +// timer +"if 9 " +" xv 246 " +" num 2 10 " +" xv 296 " +" pic 9 " +"endif " + +// help / weapon icon +"if 11 " +" xv 148 " +" pic 11 " +"endif " + +// frags +"xr -50 " +"yt 2 " +"num 3 14 " + +// spectator +"if 17 " + "xv 0 " + "yb -58 " + "string2 \"SPECTATOR MODE\" " +"endif " + +// chase camera +"if 16 " + "xv 0 " + "yb -68 " + "string \"Chasing\" " + "xv 64 " + "stat_string 16 " +"endif " +; + + +/*QUAKED worldspawn (0 0 0) ? + +Only used for the world. +"sky" environment map name +"skyaxis" vector axis for rotating sky +"skyrotate" speed of rotation in degrees/second +"sounds" music cd track number +"gravity" 800 is default gravity +"message" text to print at user logon +*/ +void SP_worldspawn (edict_t *ent) +{ + ent->movetype = MOVETYPE_PUSH; + ent->solid = SOLID_BSP; + ent->inuse = true; // since the world doesn't use G_Spawn() + ent->s.modelindex = 1; // world model is always index 1 + + //--------------- + + // reserve some spots for dead player bodies for coop / deathmatch + InitBodyQue (); + + // set configstrings for items + SetItemNames (); + + if (st.nextmap) + strcpy (level.nextmap, st.nextmap); + + // make some data visible to the server + + if (ent->message && ent->message[0]) + { + gi.configstring (CS_NAME, ent->message); + strncpy (level.level_name, ent->message, sizeof(level.level_name)); + } + else + strncpy (level.level_name, level.mapname, sizeof(level.level_name)); + + if (st.sky && st.sky[0]) + gi.configstring (CS_SKY, st.sky); + else + gi.configstring (CS_SKY, "unit1_"); + + gi.configstring (CS_SKYROTATE, va("%f", st.skyrotate) ); + + gi.configstring (CS_SKYAXIS, va("%f %f %f", + st.skyaxis[0], st.skyaxis[1], st.skyaxis[2]) ); + + gi.configstring (CS_CDTRACK, va("%i", ent->sounds) ); + + gi.configstring (CS_MAXCLIENTS, va("%i", (int)(maxclients->value) ) ); + + // status bar program + if (deathmatch->value) + gi.configstring (CS_STATUSBAR, dm_statusbar); + else + gi.configstring (CS_STATUSBAR, single_statusbar); + + //--------------- + + + // help icon for statusbar + gi.imageindex ("i_help"); + level.pic_health = gi.imageindex ("i_health"); + gi.imageindex ("help"); + gi.imageindex ("field_3"); + + if (!st.gravity) + gi.cvar_set("sv_gravity", "800"); + else + gi.cvar_set("sv_gravity", st.gravity); + + snd_fry = gi.soundindex ("player/fry.wav"); // standing in lava / slime + + PrecacheItem (FindItem ("Blaster")); + + gi.soundindex ("player/lava1.wav"); + gi.soundindex ("player/lava2.wav"); + + gi.soundindex ("misc/pc_up.wav"); + gi.soundindex ("misc/talk1.wav"); + + gi.soundindex ("misc/udeath.wav"); + + // gibs + gi.soundindex ("items/respawn1.wav"); + + // sexed sounds + gi.soundindex ("*death1.wav"); + gi.soundindex ("*death2.wav"); + gi.soundindex ("*death3.wav"); + gi.soundindex ("*death4.wav"); + gi.soundindex ("*fall1.wav"); + gi.soundindex ("*fall2.wav"); + gi.soundindex ("*gurp1.wav"); // drowning damage + gi.soundindex ("*gurp2.wav"); + gi.soundindex ("*jump1.wav"); // player jump + gi.soundindex ("*pain25_1.wav"); + gi.soundindex ("*pain25_2.wav"); + gi.soundindex ("*pain50_1.wav"); + gi.soundindex ("*pain50_2.wav"); + gi.soundindex ("*pain75_1.wav"); + gi.soundindex ("*pain75_2.wav"); + gi.soundindex ("*pain100_1.wav"); + gi.soundindex ("*pain100_2.wav"); + + // sexed models + // THIS ORDER MUST MATCH THE DEFINES IN g_local.h + // you can add more, max 19 (pete change) + // these models are only loaded in coop or deathmatch. not singleplayer. + if (coop->value || deathmatch->value) + { + gi.modelindex ("#w_blaster.md2"); + gi.modelindex ("#w_shotgun.md2"); + gi.modelindex ("#w_sshotgun.md2"); + gi.modelindex ("#w_machinegun.md2"); + gi.modelindex ("#w_chaingun.md2"); + gi.modelindex ("#a_grenades.md2"); + gi.modelindex ("#w_glauncher.md2"); + gi.modelindex ("#w_rlauncher.md2"); + gi.modelindex ("#w_hyperblaster.md2"); + gi.modelindex ("#w_railgun.md2"); + gi.modelindex ("#w_bfg.md2"); + + gi.modelindex ("#w_phalanx.md2"); + gi.modelindex ("#w_ripper.md2"); + } + + //------------------- + + gi.soundindex ("player/gasp1.wav"); // gasping for air + gi.soundindex ("player/gasp2.wav"); // head breaking surface, not gasping + + gi.soundindex ("player/watr_in.wav"); // feet hitting water + gi.soundindex ("player/watr_out.wav"); // feet leaving water + + gi.soundindex ("player/watr_un.wav"); // head going underwater + + gi.soundindex ("player/u_breath1.wav"); + gi.soundindex ("player/u_breath2.wav"); + + gi.soundindex ("items/pkup.wav"); // bonus item pickup + gi.soundindex ("world/land.wav"); // landing thud + gi.soundindex ("misc/h2ohit1.wav"); // landing splash + + gi.soundindex ("items/damage.wav"); + gi.soundindex ("items/protect.wav"); + gi.soundindex ("items/protect4.wav"); + gi.soundindex ("weapons/noammo.wav"); + + gi.soundindex ("infantry/inflies1.wav"); + + sm_meat_index = gi.modelindex ("models/objects/gibs/sm_meat/tris.md2"); + gi.modelindex ("models/objects/gibs/arm/tris.md2"); + gi.modelindex ("models/objects/gibs/bone/tris.md2"); + gi.modelindex ("models/objects/gibs/bone2/tris.md2"); + gi.modelindex ("models/objects/gibs/chest/tris.md2"); + gi.modelindex ("models/objects/gibs/skull/tris.md2"); + gi.modelindex ("models/objects/gibs/head2/tris.md2"); + +// +// Setup light animation tables. 'a' is total darkness, 'z' is doublebright. +// + + // 0 normal + gi.configstring(CS_LIGHTS+0, "m"); + + // 1 FLICKER (first variety) + gi.configstring(CS_LIGHTS+1, "mmnmmommommnonmmonqnmmo"); + + // 2 SLOW STRONG PULSE + gi.configstring(CS_LIGHTS+2, "abcdefghijklmnopqrstuvwxyzyxwvutsrqponmlkjihgfedcba"); + + // 3 CANDLE (first variety) + gi.configstring(CS_LIGHTS+3, "mmmmmaaaaammmmmaaaaaabcdefgabcdefg"); + + // 4 FAST STROBE + gi.configstring(CS_LIGHTS+4, "mamamamamama"); + + // 5 GENTLE PULSE 1 + gi.configstring(CS_LIGHTS+5,"jklmnopqrstuvwxyzyxwvutsrqponmlkj"); + + // 6 FLICKER (second variety) + gi.configstring(CS_LIGHTS+6, "nmonqnmomnmomomno"); + + // 7 CANDLE (second variety) + gi.configstring(CS_LIGHTS+7, "mmmaaaabcdefgmmmmaaaammmaamm"); + + // 8 CANDLE (third variety) + gi.configstring(CS_LIGHTS+8, "mmmaaammmaaammmabcdefaaaammmmabcdefmmmaaaa"); + + // 9 SLOW STROBE (fourth variety) + gi.configstring(CS_LIGHTS+9, "aaaaaaaazzzzzzzz"); + + // 10 FLUORESCENT FLICKER + gi.configstring(CS_LIGHTS+10, "mmamammmmammamamaaamammma"); + + // 11 SLOW PULSE NOT FADE TO BLACK + gi.configstring(CS_LIGHTS+11, "abcdefghijklmnopqrrqponmlkjihgfedcba"); + + // styles 32-62 are assigned by the light program for switchable lights + + // 63 testing + gi.configstring(CS_LIGHTS+63, "a"); +} + diff --git a/original/xatrix/g_svcmds.c b/original/xatrix/g_svcmds.c new file mode 100644 index 0000000..b20d25a --- /dev/null +++ b/original/xatrix/g_svcmds.c @@ -0,0 +1,283 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. + +#include "g_local.h" + + +void Svcmd_Test_f (void) +{ + gi.cprintf (NULL, PRINT_HIGH, "Svcmd_Test_f()\n"); +} + +/* +============================================================================== + +PACKET FILTERING + + +You can add or remove addresses from the filter list with: + +addip +removeip + +The ip address is specified in dot format, and any unspecified digits will match any value, so you can specify an entire class C network with "addip 192.246.40". + +Removeip will only remove an address specified exactly the same way. You cannot addip a subnet, then removeip a single host. + +listip +Prints the current list of filters. + +writeip +Dumps "addip " commands to listip.cfg so it can be execed at a later date. The filter lists are not saved and restored by default, because I beleive it would cause too much confusion. + +filterban <0 or 1> + +If 1 (the default), then ip addresses matching the current list will be prohibited from entering the game. This is the default setting. + +If 0, then only addresses matching the list will be allowed. This lets you easily set up a private game, or a game that only allows players from your local network. + + +============================================================================== +*/ + +typedef struct +{ + unsigned mask; + unsigned compare; +} ipfilter_t; + +#define MAX_IPFILTERS 1024 + +ipfilter_t ipfilters[MAX_IPFILTERS]; +int numipfilters; + +/* +================= +StringToFilter +================= +*/ +static qboolean StringToFilter (char *s, ipfilter_t *f) +{ + char num[128]; + int i, j; + byte b[4]; + byte m[4]; + + for (i=0 ; i<4 ; i++) + { + b[i] = 0; + m[i] = 0; + } + + for (i=0 ; i<4 ; i++) + { + if (*s < '0' || *s > '9') + { + gi.cprintf(NULL, PRINT_HIGH, "Bad filter address: %s\n", s); + return false; + } + + j = 0; + while (*s >= '0' && *s <= '9') + { + num[j++] = *s++; + } + num[j] = 0; + b[i] = atoi(num); + if (b[i] != 0) + m[i] = 255; + + if (!*s) + break; + s++; + } + + f->mask = *(unsigned *)m; + f->compare = *(unsigned *)b; + + return true; +} + +/* +================= +SV_FilterPacket +================= +*/ +qboolean SV_FilterPacket (char *from) +{ + int i; + unsigned in; + byte m[4]; + char *p; + + i = 0; + p = from; + while (*p && i < 4) { + m[i] = 0; + while (*p >= '0' && *p <= '9') { + m[i] = m[i]*10 + (*p - '0'); + p++; + } + if (!*p || *p == ':') + break; + i++, p++; + } + + in = *(unsigned *)m; + + for (i=0 ; ivalue; + + return (int)!filterban->value; +} + + +/* +================= +SV_AddIP_f +================= +*/ +void SVCmd_AddIP_f (void) +{ + int i; + + if (gi.argc() < 3) { + gi.cprintf(NULL, PRINT_HIGH, "Usage: addip \n"); + return; + } + + for (i=0 ; i\n"); + return; + } + + if (!StringToFilter (gi.argv(2), &f)) + return; + + for (i=0 ; istring) + sprintf (name, "%s/listip.cfg", GAMEVERSION); + else + sprintf (name, "%s/listip.cfg", game->string); + + gi.cprintf (NULL, PRINT_HIGH, "Writing %s.\n", name); + + f = fopen (name, "wb"); + if (!f) + { + gi.cprintf (NULL, PRINT_HIGH, "Couldn't open %s\n", name); + return; + } + + fprintf(f, "set filterban %d\n", (int)filterban->value); + + for (i=0 ; istyle); + gi.WritePosition (ent->s.origin); + gi.multicast (ent->s.origin, MULTICAST_PVS); +} + +void SP_target_temp_entity (edict_t *ent) +{ + ent->use = Use_Target_Tent; +} + + +//========================================================== + +//========================================================== + +/*QUAKED target_speaker (1 0 0) (-8 -8 -8) (8 8 8) looped-on looped-off reliable +"noise" wav file to play +"attenuation" +-1 = none, send to whole level +1 = normal fighting sounds +2 = idle sound level +3 = ambient sound level +"volume" 0.0 to 1.0 + +Normal sounds play each time the target is used. The reliable flag can be set for crucial voiceovers. + +Looped sounds are always atten 3 / vol 1, and the use function toggles it on/off. +Multiple identical looping sounds will just increase volume without any speed cost. +*/ +void Use_Target_Speaker (edict_t *ent, edict_t *other, edict_t *activator) +{ + int chan; + + if (ent->spawnflags & 3) + { // looping sound toggles + if (ent->s.sound) + ent->s.sound = 0; // turn it off + else + ent->s.sound = ent->noise_index; // start it + } + else + { // normal sound + if (ent->spawnflags & 4) + chan = CHAN_VOICE|CHAN_RELIABLE; + else + chan = CHAN_VOICE; + // use a positioned_sound, because this entity won't normally be + // sent to any clients because it is invisible + gi.positioned_sound (ent->s.origin, ent, chan, ent->noise_index, ent->volume, ent->attenuation, 0); + } +} + +void SP_target_speaker (edict_t *ent) +{ + char buffer[MAX_QPATH]; + + if(!st.noise) + { + gi.dprintf("target_speaker with no noise set at %s\n", vtos(ent->s.origin)); + return; + } + if (!strstr (st.noise, ".wav")) + Com_sprintf (buffer, sizeof(buffer), "%s.wav", st.noise); + else + strncpy (buffer, st.noise, sizeof(buffer)); + ent->noise_index = gi.soundindex (buffer); + + if (!ent->volume) + ent->volume = 1.0; + + if (!ent->attenuation) + ent->attenuation = 1.0; + else if (ent->attenuation == -1) // use -1 so 0 defaults to 1 + ent->attenuation = 0; + + // check for prestarted looping sound + if (ent->spawnflags & 1) + ent->s.sound = ent->noise_index; + + ent->use = Use_Target_Speaker; + + // must link the entity so we get areas and clusters so + // the server can determine who to send updates to + gi.linkentity (ent); +} + + +//========================================================== + +void Use_Target_Help (edict_t *ent, edict_t *other, edict_t *activator) +{ + if (ent->spawnflags & 1) + strncpy (game.helpmessage1, ent->message, sizeof(game.helpmessage2)-1); + else + strncpy (game.helpmessage2, ent->message, sizeof(game.helpmessage1)-1); + + game.helpchanged++; +} + +/*QUAKED target_help (1 0 1) (-16 -16 -24) (16 16 24) help1 +When fired, the "message" key becomes the current personal computer string, and the message light will be set on all clients status bars. +*/ +void SP_target_help(edict_t *ent) +{ + if (deathmatch->value) + { // auto-remove for deathmatch + G_FreeEdict (ent); + return; + } + + if (!ent->message) + { + gi.dprintf ("%s with no message at %s\n", ent->classname, vtos(ent->s.origin)); + G_FreeEdict (ent); + return; + } + ent->use = Use_Target_Help; +} + +//========================================================== + +/*QUAKED target_secret (1 0 1) (-8 -8 -8) (8 8 8) +Counts a secret found. +These are single use targets. +*/ +void use_target_secret (edict_t *ent, edict_t *other, edict_t *activator) +{ + gi.sound (ent, CHAN_VOICE, ent->noise_index, 1, ATTN_NORM, 0); + + level.found_secrets++; + + G_UseTargets (ent, activator); + G_FreeEdict (ent); +} + +void SP_target_secret (edict_t *ent) +{ + if (deathmatch->value) + { // auto-remove for deathmatch + G_FreeEdict (ent); + return; + } + + ent->use = use_target_secret; + if (!st.noise) + st.noise = "misc/secret.wav"; + ent->noise_index = gi.soundindex (st.noise); + ent->svflags = SVF_NOCLIENT; + level.total_secrets++; + // map bug hack + if (!stricmp(level.mapname, "mine3") && ent->s.origin[0] == 280 && ent->s.origin[1] == -2048 && ent->s.origin[2] == -624) + ent->message = "You have found a secret area."; +} + +//========================================================== + +/*QUAKED target_goal (1 0 1) (-8 -8 -8) (8 8 8) +Counts a goal completed. +These are single use targets. +*/ +void use_target_goal (edict_t *ent, edict_t *other, edict_t *activator) +{ + gi.sound (ent, CHAN_VOICE, ent->noise_index, 1, ATTN_NORM, 0); + + level.found_goals++; + + if (level.found_goals == level.total_goals) + gi.configstring (CS_CDTRACK, "0"); + + G_UseTargets (ent, activator); + G_FreeEdict (ent); +} + +void SP_target_goal (edict_t *ent) +{ + if (deathmatch->value) + { // auto-remove for deathmatch + G_FreeEdict (ent); + return; + } + + ent->use = use_target_goal; + if (!st.noise) + st.noise = "misc/secret.wav"; + ent->noise_index = gi.soundindex (st.noise); + ent->svflags = SVF_NOCLIENT; + level.total_goals++; +} + +//========================================================== + + +/*QUAKED target_explosion (1 0 0) (-8 -8 -8) (8 8 8) +Spawns an explosion temporary entity when used. + +"delay" wait this long before going off +"dmg" how much radius damage should be done, defaults to 0 +*/ +void target_explosion_explode (edict_t *self) +{ + float save; + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_EXPLOSION1); + gi.WritePosition (self->s.origin); + gi.multicast (self->s.origin, MULTICAST_PHS); + + T_RadiusDamage (self, self->activator, self->dmg, NULL, self->dmg+40, MOD_EXPLOSIVE); + + save = self->delay; + self->delay = 0; + G_UseTargets (self, self->activator); + self->delay = save; +} + +void use_target_explosion (edict_t *self, edict_t *other, edict_t *activator) +{ + self->activator = activator; + + if (!self->delay) + { + target_explosion_explode (self); + return; + } + + self->think = target_explosion_explode; + self->nextthink = level.time + self->delay; +} + +void SP_target_explosion (edict_t *ent) +{ + ent->use = use_target_explosion; + ent->svflags = SVF_NOCLIENT; +} + + +//========================================================== + +/*QUAKED target_changelevel (1 0 0) (-8 -8 -8) (8 8 8) +Changes level to "map" when fired +*/ +void use_target_changelevel (edict_t *self, edict_t *other, edict_t *activator) +{ + if (level.intermissiontime) + return; // already activated + + if (!deathmatch->value && !coop->value) + { + if (g_edicts[1].health <= 0) + return; + } + + // if noexit, do a ton of damage to other + if (deathmatch->value && !( (int)dmflags->value & DF_ALLOW_EXIT) && other != world) + { + T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, 10 * other->max_health, 1000, 0, MOD_EXIT); + return; + } + + // if multiplayer, let everyone know who hit the exit + if (deathmatch->value) + { + if (activator && activator->client) + gi.bprintf (PRINT_HIGH, "%s exited the level.\n", activator->client->pers.netname); + } + + // if going to a new unit, clear cross triggers + if (strstr(self->map, "*")) + game.serverflags &= ~(SFL_CROSS_TRIGGER_MASK); + + BeginIntermission (self); +} + +void SP_target_changelevel (edict_t *ent) +{ + if (!ent->map) + { + gi.dprintf("target_changelevel with no map at %s\n", vtos(ent->s.origin)); + G_FreeEdict (ent); + return; + } + + // ugly hack because *SOMEBODY* screwed up their map + if((stricmp(level.mapname, "fact1") == 0) && (stricmp(ent->map, "fact3") == 0)) + ent->map = "fact3$secret1"; + + ent->use = use_target_changelevel; + ent->svflags = SVF_NOCLIENT; +} + + +//========================================================== + +/*QUAKED target_splash (1 0 0) (-8 -8 -8) (8 8 8) +Creates a particle splash effect when used. + +Set "sounds" to one of the following: + 1) sparks + 2) blue water + 3) brown water + 4) slime + 5) lava + 6) blood + +"count" how many pixels in the splash +"dmg" if set, does a radius damage at this location when it splashes + useful for lava/sparks +*/ + +void use_target_splash (edict_t *self, edict_t *other, edict_t *activator) +{ + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_SPLASH); + gi.WriteByte (self->count); + gi.WritePosition (self->s.origin); + gi.WriteDir (self->movedir); + gi.WriteByte (self->sounds); + gi.multicast (self->s.origin, MULTICAST_PVS); + + if (self->dmg) + T_RadiusDamage (self, activator, self->dmg, NULL, self->dmg+40, MOD_SPLASH); +} + +void SP_target_splash (edict_t *self) +{ + self->use = use_target_splash; + G_SetMovedir (self->s.angles, self->movedir); + + if (!self->count) + self->count = 32; + + self->svflags = SVF_NOCLIENT; +} + + +//========================================================== + +/*QUAKED target_spawner (1 0 0) (-8 -8 -8) (8 8 8) 1 2 3 4 5 6 +Set target to the type of entity you want spawned. +Useful for spawning monsters and gibs in the factory levels. + +For monsters: + Set direction to the facing you want it to have. + +For gibs: + Set direction if you want it moving and + speed how fast it should be moving otherwise it + will just be dropped +*/ +void ED_CallSpawn (edict_t *ent); + +void use_target_spawner (edict_t *self, edict_t *other, edict_t *activator) +{ + edict_t *ent; + + ent = G_Spawn(); + ent->classname = self->target; + ent->flags = self->flags; + VectorCopy (self->s.origin, ent->s.origin); + VectorCopy (self->s.angles, ent->s.angles); + ED_CallSpawn (ent); + gi.unlinkentity (ent); + KillBox (ent); + gi.linkentity (ent); + if (self->speed) + VectorCopy (self->movedir, ent->velocity); +} + +void SP_target_spawner (edict_t *self) +{ + self->use = use_target_spawner; + self->svflags = SVF_NOCLIENT; + if (self->speed) + { + G_SetMovedir (self->s.angles, self->movedir); + VectorScale (self->movedir, self->speed, self->movedir); + } +} + +//========================================================== + +/*QUAKED target_blaster (1 0 0) (-8 -8 -8) (8 8 8) NOTRAIL NOEFFECTS +Fires a blaster bolt in the set direction when triggered. + +dmg default is 15 +speed default is 1000 +*/ + +void use_target_blaster (edict_t *self, edict_t *other, edict_t *activator) +{ + int effect; + + if (self->spawnflags & 2) + effect = 0; + else if (self->spawnflags & 1) + effect = EF_HYPERBLASTER; + else + effect = EF_BLASTER; + + fire_blaster (self, self->s.origin, self->movedir, self->dmg, self->speed, EF_BLASTER, MOD_TARGET_BLASTER); + gi.sound (self, CHAN_VOICE, self->noise_index, 1, ATTN_NORM, 0); +} + +void SP_target_blaster (edict_t *self) +{ + self->use = use_target_blaster; + G_SetMovedir (self->s.angles, self->movedir); + self->noise_index = gi.soundindex ("weapons/laser2.wav"); + + if (!self->dmg) + self->dmg = 15; + if (!self->speed) + self->speed = 1000; + + self->svflags = SVF_NOCLIENT; +} + + +//========================================================== + +/*QUAKED target_crosslevel_trigger (.5 .5 .5) (-8 -8 -8) (8 8 8) trigger1 trigger2 trigger3 trigger4 trigger5 trigger6 trigger7 trigger8 +Once this trigger is touched/used, any trigger_crosslevel_target with the same trigger number is automatically used when a level is started within the same unit. It is OK to check multiple triggers. Message, delay, target, and killtarget also work. +*/ +void trigger_crosslevel_trigger_use (edict_t *self, edict_t *other, edict_t *activator) +{ + game.serverflags |= self->spawnflags; + G_FreeEdict (self); +} + +void SP_target_crosslevel_trigger (edict_t *self) +{ + self->svflags = SVF_NOCLIENT; + self->use = trigger_crosslevel_trigger_use; +} + +/*QUAKED target_crosslevel_target (.5 .5 .5) (-8 -8 -8) (8 8 8) trigger1 trigger2 trigger3 trigger4 trigger5 trigger6 trigger7 trigger8 +Triggered by a trigger_crosslevel elsewhere within a unit. If multiple triggers are checked, all must be true. Delay, target and +killtarget also work. + +"delay" delay before using targets if the trigger has been activated (default 1) +*/ +void target_crosslevel_target_think (edict_t *self) +{ + if (self->spawnflags == (game.serverflags & SFL_CROSS_TRIGGER_MASK & self->spawnflags)) + { + G_UseTargets (self, self); + G_FreeEdict (self); + } +} + +void SP_target_crosslevel_target (edict_t *self) +{ + if (! self->delay) + self->delay = 1; + self->svflags = SVF_NOCLIENT; + + self->think = target_crosslevel_target_think; + self->nextthink = level.time + self->delay; +} + +//========================================================== + +/*QUAKED target_laser (0 .5 .8) (-8 -8 -8) (8 8 8) START_ON RED GREEN BLUE YELLOW ORANGE FAT +When triggered, fires a laser. You can either set a target +or a direction. +*/ + +void target_laser_think (edict_t *self) +{ + edict_t *ignore; + vec3_t start; + vec3_t end; + trace_t tr; + vec3_t point; + vec3_t last_movedir; + int count; + + if (self->spawnflags & 0x80000000) + count = 8; + else + count = 4; + + if (self->enemy) + { + VectorCopy (self->movedir, last_movedir); + VectorMA (self->enemy->absmin, 0.5, self->enemy->size, point); + VectorSubtract (point, self->s.origin, self->movedir); + VectorNormalize (self->movedir); + if (!VectorCompare(self->movedir, last_movedir)) + self->spawnflags |= 0x80000000; + } + + ignore = self; + VectorCopy (self->s.origin, start); + VectorMA (start, 2048, self->movedir, end); + while(1) + { + tr = gi.trace (start, NULL, NULL, end, ignore, CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_DEADMONSTER); + + if (!tr.ent) + break; + + // hurt it if we can + if ((tr.ent->takedamage) && !(tr.ent->flags & FL_IMMUNE_LASER)) + T_Damage (tr.ent, self, self->activator, self->movedir, tr.endpos, vec3_origin, self->dmg, 1, DAMAGE_ENERGY, MOD_TARGET_LASER); + + // if we hit something that's not a monster or player or is immune to lasers, we're done + if (!(tr.ent->svflags & SVF_MONSTER) && (!tr.ent->client)) + { + if (self->spawnflags & 0x80000000) + { + self->spawnflags &= ~0x80000000; + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_LASER_SPARKS); + gi.WriteByte (count); + gi.WritePosition (tr.endpos); + gi.WriteDir (tr.plane.normal); + gi.WriteByte (self->s.skinnum); + gi.multicast (tr.endpos, MULTICAST_PVS); + } + break; + } + + ignore = tr.ent; + VectorCopy (tr.endpos, start); + } + + VectorCopy (tr.endpos, self->s.old_origin); + + self->nextthink = level.time + FRAMETIME; +} + +void target_laser_on (edict_t *self) +{ + if (!self->activator) + self->activator = self; + self->spawnflags |= 0x80000001; + self->svflags &= ~SVF_NOCLIENT; + target_laser_think (self); +} + +void target_laser_off (edict_t *self) +{ + self->spawnflags &= ~1; + self->svflags |= SVF_NOCLIENT; + self->nextthink = 0; +} + +void target_laser_use (edict_t *self, edict_t *other, edict_t *activator) +{ + self->activator = activator; + if (self->spawnflags & 1) + target_laser_off (self); + else + target_laser_on (self); +} + +void target_laser_start (edict_t *self) +{ + edict_t *ent; + + self->movetype = MOVETYPE_NONE; + self->solid = SOLID_NOT; + self->s.renderfx |= RF_BEAM|RF_TRANSLUCENT; + self->s.modelindex = 1; // must be non-zero + + // set the beam diameter + if (self->spawnflags & 64) + self->s.frame = 16; + else + self->s.frame = 4; + + // set the color + if (self->spawnflags & 2) + self->s.skinnum = 0xf2f2f0f0; + else if (self->spawnflags & 4) + self->s.skinnum = 0xd0d1d2d3; + else if (self->spawnflags & 8) + self->s.skinnum = 0xf3f3f1f1; + else if (self->spawnflags & 16) + self->s.skinnum = 0xdcdddedf; + else if (self->spawnflags & 32) + self->s.skinnum = 0xe0e1e2e3; + + if (!self->enemy) + { + if (self->target) + { + ent = G_Find (NULL, FOFS(targetname), self->target); + if (!ent) + gi.dprintf ("%s at %s: %s is a bad target\n", self->classname, vtos(self->s.origin), self->target); + self->enemy = ent; + } + else + { + G_SetMovedir (self->s.angles, self->movedir); + } + } + self->use = target_laser_use; + self->think = target_laser_think; + + if (!self->dmg) + self->dmg = 1; + + VectorSet (self->mins, -8, -8, -8); + VectorSet (self->maxs, 8, 8, 8); + gi.linkentity (self); + + if (self->spawnflags & 1) + target_laser_on (self); + else + target_laser_off (self); +} + +void SP_target_laser (edict_t *self) +{ + // let everything else get spawned before we start firing + self->think = target_laser_start; + self->nextthink = level.time + 1; +} + + +// RAFAEL 15-APR-98 +/*QUAKED target_mal_laser (1 0 0) (-4 -4 -4) (4 4 4) START_ON RED GREEN BLUE YELLOW ORANGE FAT +Mal's laser +*/ +void target_mal_laser_on (edict_t *self) +{ + if (!self->activator) + self->activator = self; + self->spawnflags |= 0x80000001; + self->svflags &= ~SVF_NOCLIENT; + // target_laser_think (self); + self->nextthink = level.time + self->wait + self->delay; +} + +void target_mal_laser_off (edict_t *self) +{ + self->spawnflags &= ~1; + self->svflags |= SVF_NOCLIENT; + self->nextthink = 0; +} + +void target_mal_laser_use (edict_t *self, edict_t *other, edict_t *activator) +{ + self->activator = activator; + if (self->spawnflags & 1) + target_mal_laser_off (self); + else + target_mal_laser_on (self); +} + +void mal_laser_think (edict_t *self) +{ + target_laser_think (self); + self->nextthink = level.time + self->wait + 0.1; + self->spawnflags |= 0x80000000; +} + +void SP_target_mal_laser (edict_t *self) +{ + self->movetype = MOVETYPE_NONE; + self->solid = SOLID_NOT; + self->s.renderfx |= RF_BEAM|RF_TRANSLUCENT; + self->s.modelindex = 1; // must be non-zero + + // set the beam diameter + if (self->spawnflags & 64) + self->s.frame = 16; + else + self->s.frame = 4; + + // set the color + if (self->spawnflags & 2) + self->s.skinnum = 0xf2f2f0f0; + else if (self->spawnflags & 4) + self->s.skinnum = 0xd0d1d2d3; + else if (self->spawnflags & 8) + self->s.skinnum = 0xf3f3f1f1; + else if (self->spawnflags & 16) + self->s.skinnum = 0xdcdddedf; + else if (self->spawnflags & 32) + self->s.skinnum = 0xe0e1e2e3; + + G_SetMovedir (self->s.angles, self->movedir); + + if (!self->delay) + self->delay = 0.1; + + if (!self->wait) + self->wait = 0.1; + + if (!self->dmg) + self->dmg = 5; + + VectorSet (self->mins, -8, -8, -8); + VectorSet (self->maxs, 8, 8, 8); + + self->nextthink = level.time + self->delay; + self->think = mal_laser_think; + + self->use = target_mal_laser_use; + + gi.linkentity (self); + + if (self->spawnflags & 1) + target_mal_laser_on (self); + else + target_mal_laser_off (self); +} +// END 15-APR-98 + +//========================================================== + +/*QUAKED target_lightramp (0 .5 .8) (-8 -8 -8) (8 8 8) TOGGLE +speed How many seconds the ramping will take +message two letters; starting lightlevel and ending lightlevel +*/ + +void target_lightramp_think (edict_t *self) +{ + char style[2]; + + style[0] = 'a' + self->movedir[0] + (level.time - self->timestamp) / FRAMETIME * self->movedir[2]; + style[1] = 0; + gi.configstring (CS_LIGHTS+self->enemy->style, style); + + if ((level.time - self->timestamp) < self->speed) + { + self->nextthink = level.time + FRAMETIME; + } + else if (self->spawnflags & 1) + { + char temp; + + temp = self->movedir[0]; + self->movedir[0] = self->movedir[1]; + self->movedir[1] = temp; + self->movedir[2] *= -1; + } +} + +void target_lightramp_use (edict_t *self, edict_t *other, edict_t *activator) +{ + if (!self->enemy) + { + edict_t *e; + + // check all the targets + e = NULL; + while (1) + { + e = G_Find (e, FOFS(targetname), self->target); + if (!e) + break; + if (strcmp(e->classname, "light") != 0) + { + gi.dprintf("%s at %s ", self->classname, vtos(self->s.origin)); + gi.dprintf("target %s (%s at %s) is not a light\n", self->target, e->classname, vtos(e->s.origin)); + } + else + { + self->enemy = e; + } + } + + if (!self->enemy) + { + gi.dprintf("%s target %s not found at %s\n", self->classname, self->target, vtos(self->s.origin)); + G_FreeEdict (self); + return; + } + } + + self->timestamp = level.time; + target_lightramp_think (self); +} + +void SP_target_lightramp (edict_t *self) +{ + if (!self->message || strlen(self->message) != 2 || self->message[0] < 'a' || self->message[0] > 'z' || self->message[1] < 'a' || self->message[1] > 'z' || self->message[0] == self->message[1]) + { + gi.dprintf("target_lightramp has bad ramp (%s) at %s\n", self->message, vtos(self->s.origin)); + G_FreeEdict (self); + return; + } + + if (deathmatch->value) + { + G_FreeEdict (self); + return; + } + + if (!self->target) + { + gi.dprintf("%s with no target at %s\n", self->classname, vtos(self->s.origin)); + G_FreeEdict (self); + return; + } + + self->svflags |= SVF_NOCLIENT; + self->use = target_lightramp_use; + self->think = target_lightramp_think; + + self->movedir[0] = self->message[0] - 'a'; + self->movedir[1] = self->message[1] - 'a'; + self->movedir[2] = (self->movedir[1] - self->movedir[0]) / (self->speed / FRAMETIME); +} + +//========================================================== + +/*QUAKED target_earthquake (1 0 0) (-8 -8 -8) (8 8 8) +When triggered, this initiates a level-wide earthquake. +All players and monsters are affected. +"speed" severity of the quake (default:200) +"count" duration of the quake (default:5) +*/ + +void target_earthquake_think (edict_t *self) +{ + int i; + edict_t *e; + + if (self->last_move_time < level.time) + { + gi.positioned_sound (self->s.origin, self, CHAN_AUTO, self->noise_index, 1.0, ATTN_NONE, 0); + self->last_move_time = level.time + 0.5; + } + + for (i=1, e=g_edicts+i; i < globals.num_edicts; i++,e++) + { + if (!e->inuse) + continue; + if (!e->client) + continue; + if (!e->groundentity) + continue; + + e->groundentity = NULL; + e->velocity[0] += crandom()* 150; + e->velocity[1] += crandom()* 150; + e->velocity[2] = self->speed * (100.0 / e->mass); + } + + if (level.time < self->timestamp) + self->nextthink = level.time + FRAMETIME; +} + +void target_earthquake_use (edict_t *self, edict_t *other, edict_t *activator) +{ + self->timestamp = level.time + self->count; + self->nextthink = level.time + FRAMETIME; + self->activator = activator; + self->last_move_time = 0; +} + +void SP_target_earthquake (edict_t *self) +{ + if (!self->targetname) + gi.dprintf("untargeted %s at %s\n", self->classname, vtos(self->s.origin)); + + if (!self->count) + self->count = 5; + + if (!self->speed) + self->speed = 200; + + self->svflags |= SVF_NOCLIENT; + self->think = target_earthquake_think; + self->use = target_earthquake_use; + + self->noise_index = gi.soundindex ("world/quake.wav"); +} diff --git a/original/xatrix/g_trigger.c b/original/xatrix/g_trigger.c new file mode 100644 index 0000000..60ce7f4 --- /dev/null +++ b/original/xatrix/g_trigger.c @@ -0,0 +1,692 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +#include "g_local.h" + + +void InitTrigger (edict_t *self) +{ + if (!VectorCompare (self->s.angles, vec3_origin)) + G_SetMovedir (self->s.angles, self->movedir); + + self->solid = SOLID_TRIGGER; + self->movetype = MOVETYPE_NONE; + gi.setmodel (self, self->model); + self->svflags = SVF_NOCLIENT; +} + + +// the wait time has passed, so set back up for another activation +void multi_wait (edict_t *ent) +{ + ent->nextthink = 0; +} + + +// the trigger was just activated +// ent->activator should be set to the activator so it can be held through a delay +// so wait for the delay time before firing +void multi_trigger (edict_t *ent) +{ + if (ent->nextthink) + return; // already been triggered + + G_UseTargets (ent, ent->activator); + + if (ent->wait > 0) + { + ent->think = multi_wait; + ent->nextthink = level.time + ent->wait; + } + else + { // we can't just remove (self) here, because this is a touch function + // called while looping through area links... + ent->touch = NULL; + ent->nextthink = level.time + FRAMETIME; + ent->think = G_FreeEdict; + } +} + +void Use_Multi (edict_t *ent, edict_t *other, edict_t *activator) +{ + ent->activator = activator; + multi_trigger (ent); +} + +void Touch_Multi (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if(other->client) + { + if (self->spawnflags & 2) + return; + } + else if (other->svflags & SVF_MONSTER) + { + if (!(self->spawnflags & 1)) + return; + } + else + return; + + if (!VectorCompare(self->movedir, vec3_origin)) + { + vec3_t forward; + + AngleVectors(other->s.angles, forward, NULL, NULL); + if (_DotProduct(forward, self->movedir) < 0) + return; + } + + self->activator = other; + multi_trigger (self); +} + +/*QUAKED trigger_multiple (.5 .5 .5) ? MONSTER NOT_PLAYER TRIGGERED +Variable sized repeatable trigger. Must be targeted at one or more entities. +If "delay" is set, the trigger waits some time after activating before firing. +"wait" : Seconds between triggerings. (.2 default) +sounds +1) secret +2) beep beep +3) large switch +4) +set "message" to text string +*/ +void trigger_enable (edict_t *self, edict_t *other, edict_t *activator) +{ + self->solid = SOLID_TRIGGER; + self->use = Use_Multi; + gi.linkentity (self); +} + +void SP_trigger_multiple (edict_t *ent) +{ + if (ent->sounds == 1) + ent->noise_index = gi.soundindex ("misc/secret.wav"); + else if (ent->sounds == 2) + ent->noise_index = gi.soundindex ("misc/talk.wav"); + else if (ent->sounds == 3) + ent->noise_index = gi.soundindex ("misc/trigger1.wav"); + + if (!ent->wait) + ent->wait = 0.2; + ent->touch = Touch_Multi; + ent->movetype = MOVETYPE_NONE; + ent->svflags |= SVF_NOCLIENT; + + + if (ent->spawnflags & 4) + { + ent->solid = SOLID_NOT; + ent->use = trigger_enable; + } + else + { + ent->solid = SOLID_TRIGGER; + ent->use = Use_Multi; + } + + if (!VectorCompare(ent->s.angles, vec3_origin)) + G_SetMovedir (ent->s.angles, ent->movedir); + + gi.setmodel (ent, ent->model); + gi.linkentity (ent); +} + + +/*QUAKED trigger_once (.5 .5 .5) ? x x TRIGGERED +Triggers once, then removes itself. +You must set the key "target" to the name of another object in the level that has a matching "targetname". + +If TRIGGERED, this trigger must be triggered before it is live. + +sounds + 1) secret + 2) beep beep + 3) large switch + 4) + +"message" string to be displayed when triggered +*/ + +void SP_trigger_once(edict_t *ent) +{ + // make old maps work because I messed up on flag assignments here + // triggered was on bit 1 when it should have been on bit 4 + if (ent->spawnflags & 1) + { + vec3_t v; + + VectorMA (ent->mins, 0.5, ent->size, v); + ent->spawnflags &= ~1; + ent->spawnflags |= 4; + gi.dprintf("fixed TRIGGERED flag on %s at %s\n", ent->classname, vtos(v)); + } + + ent->wait = -1; + SP_trigger_multiple (ent); +} + +/*QUAKED trigger_relay (.5 .5 .5) (-8 -8 -8) (8 8 8) +This fixed size trigger cannot be touched, it can only be fired by other events. +*/ +void trigger_relay_use (edict_t *self, edict_t *other, edict_t *activator) +{ + G_UseTargets (self, activator); +} + +void SP_trigger_relay (edict_t *self) +{ + self->use = trigger_relay_use; +} + + +/* +============================================================================== + +trigger_key + +============================================================================== +*/ + +/*QUAKED trigger_key (.5 .5 .5) (-8 -8 -8) (8 8 8) +A relay trigger that only fires it's targets if player has the proper key. +Use "item" to specify the required key, for example "key_data_cd" +*/ +void trigger_key_use (edict_t *self, edict_t *other, edict_t *activator) +{ + int index; + + if (!self->item) + return; + if (!activator->client) + return; + + index = ITEM_INDEX(self->item); + if (!activator->client->pers.inventory[index]) + { + if (level.time < self->touch_debounce_time) + return; + self->touch_debounce_time = level.time + 5.0; + gi.centerprintf (activator, "You need the %s", self->item->pickup_name); + gi.sound (activator, CHAN_AUTO, gi.soundindex ("misc/keytry.wav"), 1, ATTN_NORM, 0); + return; + } + + gi.sound (activator, CHAN_AUTO, gi.soundindex ("misc/keyuse.wav"), 1, ATTN_NORM, 0); + if (coop->value) + { + int player; + edict_t *ent; + + if (strcmp(self->item->classname, "key_power_cube") == 0) + { + int cube; + + for (cube = 0; cube < 8; cube++) + if (activator->client->pers.power_cubes & (1 << cube)) + break; + for (player = 1; player <= game.maxclients; player++) + { + ent = &g_edicts[player]; + if (!ent->inuse) + continue; + if (!ent->client) + continue; + if (ent->client->pers.power_cubes & (1 << cube)) + { + ent->client->pers.inventory[index]--; + ent->client->pers.power_cubes &= ~(1 << cube); + } + } + } + else + { + for (player = 1; player <= game.maxclients; player++) + { + ent = &g_edicts[player]; + if (!ent->inuse) + continue; + if (!ent->client) + continue; + ent->client->pers.inventory[index] = 0; + } + } + } + else + { + activator->client->pers.inventory[index]--; + } + + G_UseTargets (self, activator); + + self->use = NULL; +} + +void SP_trigger_key (edict_t *self) +{ + if (!st.item) + { + gi.dprintf("no key item for trigger_key at %s\n", vtos(self->s.origin)); + return; + } + self->item = FindItemByClassname (st.item); + + if (!self->item) + { + gi.dprintf("item %s not found for trigger_key at %s\n", st.item, vtos(self->s.origin)); + return; + } + + if (!self->target) + { + gi.dprintf("%s at %s has no target\n", self->classname, vtos(self->s.origin)); + return; + } + + gi.soundindex ("misc/keytry.wav"); + gi.soundindex ("misc/keyuse.wav"); + + self->use = trigger_key_use; +} + + +/* +============================================================================== + +trigger_counter + +============================================================================== +*/ + +/*QUAKED trigger_counter (.5 .5 .5) ? nomessage +Acts as an intermediary for an action that takes multiple inputs. + +If nomessage is not set, t will print "1 more.. " etc when triggered and "sequence complete" when finished. + +After the counter has been triggered "count" times (default 2), it will fire all of it's targets and remove itself. +*/ + +void trigger_counter_use(edict_t *self, edict_t *other, edict_t *activator) +{ + if (self->count == 0) + return; + + self->count--; + + if (self->count) + { + if (! (self->spawnflags & 1)) + { + gi.centerprintf(activator, "%i more to go...", self->count); + gi.sound (activator, CHAN_AUTO, gi.soundindex ("misc/talk1.wav"), 1, ATTN_NORM, 0); + } + return; + } + + if (! (self->spawnflags & 1)) + { + gi.centerprintf(activator, "Sequence completed!"); + gi.sound (activator, CHAN_AUTO, gi.soundindex ("misc/talk1.wav"), 1, ATTN_NORM, 0); + } + self->activator = activator; + multi_trigger (self); +} + +void SP_trigger_counter (edict_t *self) +{ + self->wait = -1; + if (!self->count) + self->count = 2; + + self->use = trigger_counter_use; +} + + +/* +============================================================================== + +trigger_always + +============================================================================== +*/ + +/*QUAKED trigger_always (.5 .5 .5) (-8 -8 -8) (8 8 8) +This trigger will always fire. It is activated by the world. +*/ +void SP_trigger_always (edict_t *ent) +{ + // we must have some delay to make sure our use targets are present + if (ent->delay < 0.2) + ent->delay = 0.2; + G_UseTargets(ent, ent); +} + + +/* +============================================================================== + +trigger_push + +============================================================================== +*/ +#if 0 +#define PUSH_ONCE 1 + +static int windsound; + +void trigger_push_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (strcmp(other->classname, "grenade") == 0) + { + VectorScale (self->movedir, self->speed * 10, other->velocity); + } + else if (other->health > 0) + { + VectorScale (self->movedir, self->speed * 10, other->velocity); + + if (other->client) + { + // don't take falling damage immediately from this + VectorCopy (other->velocity, other->client->oldvelocity); + if (other->fly_sound_debounce_time < level.time) + { + other->fly_sound_debounce_time = level.time + 1.5; + gi.sound (other, CHAN_AUTO, windsound, 1, ATTN_NORM, 0); + } + } + } + if (self->spawnflags & PUSH_ONCE) + G_FreeEdict (self); +} + +void SP_trigger_push (edict_t *self) +{ + InitTrigger (self); + windsound = gi.soundindex ("misc/windfly.wav"); + self->touch = trigger_push_touch; + if (!self->speed) + self->speed = 1000; + gi.linkentity (self); +} +#endif + +// RAFAEL +#define PUSH_ONCE 1 + +static int windsound; + +void trigger_push_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (strcmp(other->classname, "grenade") == 0) + { + VectorScale (self->movedir, self->speed * 10, other->velocity); + } + else if (other->health > 0) + { + VectorScale (self->movedir, self->speed * 10, other->velocity); + + if (other->client) + { + // don't take falling damage immediately from this + VectorCopy (other->velocity, other->client->oldvelocity); + if (other->fly_sound_debounce_time < level.time) + { + other->fly_sound_debounce_time = level.time + 1.5; + gi.sound (other, CHAN_AUTO, windsound, 1, ATTN_NORM, 0); + } + } + } + if (self->spawnflags & PUSH_ONCE) + G_FreeEdict (self); +} + + +/*QUAKED trigger_push (.5 .5 .5) ? PUSH_ONCE PUSH_PLUS PUSH_RAMP +Pushes the player +"speed" defaults to 1000 +"wait" defaults to 10 must use PUSH_PLUS used for on +*/ + +void trigger_push_active (edict_t *self); + +void trigger_effect (edict_t *self) +{ + vec3_t origin; + vec3_t size; + int i; + + VectorScale (self->size, 0.5, size); + VectorAdd (self->absmin, size, origin); + + for (i=0; i<10; i++) + { + origin[2] += (self->speed * 0.01) * (i + random()); + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_TUNNEL_SPARKS); + gi.WriteByte (1); + gi.WritePosition (origin); + gi.WriteDir (vec3_origin); + gi.WriteByte (0x74 + (rand()&7)); + gi.multicast (self->s.origin, MULTICAST_PVS); + } + +} + +void trigger_push_inactive (edict_t *self) +{ + if (self->delay > level.time) + { + self->nextthink = level.time + 0.1; + } + else + { + self->touch = trigger_push_touch; + self->think = trigger_push_active; + self->nextthink = level.time + 0.1; + self->delay = self->nextthink + self->wait; + } +} + +void trigger_push_active (edict_t *self) +{ + if (self->delay > level.time) + { + self->nextthink = level.time + 0.1; + trigger_effect (self); + } + else + { + self->touch = NULL; + self->think = trigger_push_inactive; + self->nextthink = level.time + 0.1; + self->delay = self->nextthink + self->wait; + } +} + +void SP_trigger_push (edict_t *self) +{ + InitTrigger (self); + windsound = gi.soundindex ("misc/windfly.wav"); + self->touch = trigger_push_touch; + + if (self->spawnflags & 2) + { + if (!self->wait) + self->wait = 10; + + self->think = trigger_push_active; + self->nextthink = level.time + 0.1; + self->delay = self->nextthink + self->wait; + } + + if (!self->speed) + self->speed = 1000; + + gi.linkentity (self); + +} + +/* +============================================================================== + +trigger_hurt + +============================================================================== +*/ + +/*QUAKED trigger_hurt (.5 .5 .5) ? START_OFF TOGGLE SILENT NO_PROTECTION SLOW +Any entity that touches this will be hurt. + +It does dmg points of damage each server frame + +SILENT supresses playing the sound +SLOW changes the damage rate to once per second +NO_PROTECTION *nothing* stops the damage + +"dmg" default 5 (whole numbers only) + +*/ +void hurt_use (edict_t *self, edict_t *other, edict_t *activator) +{ + if (self->solid == SOLID_NOT) + self->solid = SOLID_TRIGGER; + else + self->solid = SOLID_NOT; + gi.linkentity (self); + + if (!(self->spawnflags & 2)) + self->use = NULL; +} + + +void hurt_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + int dflags; + + if (!other->takedamage) + return; + + if (self->timestamp > level.time) + return; + + if (self->spawnflags & 16) + self->timestamp = level.time + 1; + else + self->timestamp = level.time + FRAMETIME; + + if (!(self->spawnflags & 4)) + { + if ((level.framenum % 10) == 0) + gi.sound (other, CHAN_AUTO, self->noise_index, 1, ATTN_NORM, 0); + } + + if (self->spawnflags & 8) + dflags = DAMAGE_NO_PROTECTION; + else + dflags = 0; + T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, self->dmg, dflags, MOD_TRIGGER_HURT); +} + +void SP_trigger_hurt (edict_t *self) +{ + InitTrigger (self); + + self->noise_index = gi.soundindex ("world/electro.wav"); + self->touch = hurt_touch; + + if (!self->dmg) + self->dmg = 5; + + if (self->spawnflags & 1) + self->solid = SOLID_NOT; + else + self->solid = SOLID_TRIGGER; + + if (self->spawnflags & 2) + self->use = hurt_use; + + gi.linkentity (self); +} + + +/* +============================================================================== + +trigger_gravity + +============================================================================== +*/ + +/*QUAKED trigger_gravity (.5 .5 .5) ? +Changes the touching entites gravity to +the value of "gravity". 1.0 is standard +gravity for the level. +*/ + +void trigger_gravity_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + other->gravity = self->gravity; +} + +void SP_trigger_gravity (edict_t *self) +{ + if (st.gravity == 0) + { + gi.dprintf("trigger_gravity without gravity set at %s\n", vtos(self->s.origin)); + G_FreeEdict (self); + return; + } + + InitTrigger (self); + self->gravity = atoi(st.gravity); + self->touch = trigger_gravity_touch; +} + + +/* +============================================================================== + +trigger_monsterjump + +============================================================================== +*/ + +/*QUAKED trigger_monsterjump (.5 .5 .5) ? +Walking monsters that touch this will jump in the direction of the trigger's angle +"speed" default to 200, the speed thrown forward +"height" default to 200, the speed thrown upwards +*/ + +void trigger_monsterjump_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (other->flags & (FL_FLY | FL_SWIM) ) + return; + if (other->svflags & SVF_DEADMONSTER) + return; + if ( !(other->svflags & SVF_MONSTER)) + return; + +// set XY even if not on ground, so the jump will clear lips + other->velocity[0] = self->movedir[0] * self->speed; + other->velocity[1] = self->movedir[1] * self->speed; + + if (!other->groundentity) + return; + + other->groundentity = NULL; + other->velocity[2] = self->movedir[2]; +} + +void SP_trigger_monsterjump (edict_t *self) +{ + if (!self->speed) + self->speed = 200; + if (!st.height) + st.height = 200; + if (self->s.angles[YAW] == 0) + self->s.angles[YAW] = 360; + InitTrigger (self); + self->touch = trigger_monsterjump_touch; + self->movedir[2] = st.height; +} + diff --git a/original/xatrix/g_turret.c b/original/xatrix/g_turret.c new file mode 100644 index 0000000..f5d90e8 --- /dev/null +++ b/original/xatrix/g_turret.c @@ -0,0 +1,415 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// g_turret.c + +#include "g_local.h" + + +void AnglesNormalize(vec3_t vec) +{ + while(vec[0] > 360) + vec[0] -= 360; + while(vec[0] < 0) + vec[0] += 360; + while(vec[1] > 360) + vec[1] -= 360; + while(vec[1] < 0) + vec[1] += 360; +} + +float SnapToEights(float x) +{ + x *= 8.0; + if (x > 0.0) + x += 0.5; + else + x -= 0.5; + return 0.125 * (int)x; +} + + +void turret_blocked(edict_t *self, edict_t *other) +{ + edict_t *attacker; + + if (other->takedamage) + { + if (self->teammaster->owner) + attacker = self->teammaster->owner; + else + attacker = self->teammaster; + T_Damage (other, self, attacker, vec3_origin, other->s.origin, vec3_origin, self->teammaster->dmg, 10, 0, MOD_CRUSH); + } +} + +/*QUAKED turret_breach (0 0 0) ? +This portion of the turret can change both pitch and yaw. +The model should be made with a flat pitch. +It (and the associated base) need to be oriented towards 0. +Use "angle" to set the starting angle. + +"speed" default 50 +"dmg" default 10 +"angle" point this forward +"target" point this at an info_notnull at the muzzle tip +"minpitch" min acceptable pitch angle : default -30 +"maxpitch" max acceptable pitch angle : default 30 +"minyaw" min acceptable yaw angle : default 0 +"maxyaw" max acceptable yaw angle : default 360 +*/ + +void turret_breach_fire (edict_t *self) +{ + vec3_t f, r, u; + vec3_t start; + int damage; + int speed; + + AngleVectors (self->s.angles, f, r, u); + VectorMA (self->s.origin, self->move_origin[0], f, start); + VectorMA (start, self->move_origin[1], r, start); + VectorMA (start, self->move_origin[2], u, start); + + damage = 100 + random() * 50; + speed = 550 + 50 * skill->value; + fire_rocket (self->teammaster->owner, start, f, damage, speed, 150, damage); + gi.positioned_sound (start, self, CHAN_WEAPON, gi.soundindex("weapons/rocklf1a.wav"), 1, ATTN_NORM, 0); +} + +void turret_breach_think (edict_t *self) +{ + edict_t *ent; + vec3_t current_angles; + vec3_t delta; + + VectorCopy (self->s.angles, current_angles); + AnglesNormalize(current_angles); + + AnglesNormalize(self->move_angles); + if (self->move_angles[PITCH] > 180) + self->move_angles[PITCH] -= 360; + + // clamp angles to mins & maxs + if (self->move_angles[PITCH] > self->pos1[PITCH]) + self->move_angles[PITCH] = self->pos1[PITCH]; + else if (self->move_angles[PITCH] < self->pos2[PITCH]) + self->move_angles[PITCH] = self->pos2[PITCH]; + + if ((self->move_angles[YAW] < self->pos1[YAW]) || (self->move_angles[YAW] > self->pos2[YAW])) + { + float dmin, dmax; + + dmin = fabs(self->pos1[YAW] - self->move_angles[YAW]); + if (dmin < -180) + dmin += 360; + else if (dmin > 180) + dmin -= 360; + dmax = fabs(self->pos2[YAW] - self->move_angles[YAW]); + if (dmax < -180) + dmax += 360; + else if (dmax > 180) + dmax -= 360; + if (fabs(dmin) < fabs(dmax)) + self->move_angles[YAW] = self->pos1[YAW]; + else + self->move_angles[YAW] = self->pos2[YAW]; + } + + VectorSubtract (self->move_angles, current_angles, delta); + if (delta[0] < -180) + delta[0] += 360; + else if (delta[0] > 180) + delta[0] -= 360; + if (delta[1] < -180) + delta[1] += 360; + else if (delta[1] > 180) + delta[1] -= 360; + delta[2] = 0; + + if (delta[0] > self->speed * FRAMETIME) + delta[0] = self->speed * FRAMETIME; + if (delta[0] < -1 * self->speed * FRAMETIME) + delta[0] = -1 * self->speed * FRAMETIME; + if (delta[1] > self->speed * FRAMETIME) + delta[1] = self->speed * FRAMETIME; + if (delta[1] < -1 * self->speed * FRAMETIME) + delta[1] = -1 * self->speed * FRAMETIME; + + VectorScale (delta, 1.0/FRAMETIME, self->avelocity); + + self->nextthink = level.time + FRAMETIME; + + for (ent = self->teammaster; ent; ent = ent->teamchain) + ent->avelocity[1] = self->avelocity[1]; + + // if we have adriver, adjust his velocities + if (self->owner) + { + float angle; + float target_z; + float diff; + vec3_t target; + vec3_t dir; + + // angular is easy, just copy ours + self->owner->avelocity[0] = self->avelocity[0]; + self->owner->avelocity[1] = self->avelocity[1]; + + // x & y + angle = self->s.angles[1] + self->owner->move_origin[1]; + angle *= (M_PI*2 / 360); + target[0] = SnapToEights(self->s.origin[0] + cos(angle) * self->owner->move_origin[0]); + target[1] = SnapToEights(self->s.origin[1] + sin(angle) * self->owner->move_origin[0]); + target[2] = self->owner->s.origin[2]; + + VectorSubtract (target, self->owner->s.origin, dir); + self->owner->velocity[0] = dir[0] * 1.0 / FRAMETIME; + self->owner->velocity[1] = dir[1] * 1.0 / FRAMETIME; + + // z + angle = self->s.angles[PITCH] * (M_PI*2 / 360); + target_z = SnapToEights(self->s.origin[2] + self->owner->move_origin[0] * tan(angle) + self->owner->move_origin[2]); + + diff = target_z - self->owner->s.origin[2]; + self->owner->velocity[2] = diff * 1.0 / FRAMETIME; + + if (self->spawnflags & 65536) + { + turret_breach_fire (self); + self->spawnflags &= ~65536; + } + } +} + +void turret_breach_finish_init (edict_t *self) +{ + // get and save info for muzzle location + if (!self->target) + { + gi.dprintf("%s at %s needs a target\n", self->classname, vtos(self->s.origin)); + } + else + { + self->target_ent = G_PickTarget (self->target); + VectorSubtract (self->target_ent->s.origin, self->s.origin, self->move_origin); + G_FreeEdict(self->target_ent); + } + + self->teammaster->dmg = self->dmg; + self->think = turret_breach_think; + self->think (self); +} + +void SP_turret_breach (edict_t *self) +{ + self->solid = SOLID_BSP; + self->movetype = MOVETYPE_PUSH; + gi.setmodel (self, self->model); + + if (!self->speed) + self->speed = 50; + if (!self->dmg) + self->dmg = 10; + + if (!st.minpitch) + st.minpitch = -30; + if (!st.maxpitch) + st.maxpitch = 30; + if (!st.maxyaw) + st.maxyaw = 360; + + self->pos1[PITCH] = -1 * st.minpitch; + self->pos1[YAW] = st.minyaw; + self->pos2[PITCH] = -1 * st.maxpitch; + self->pos2[YAW] = st.maxyaw; + + self->ideal_yaw = self->s.angles[YAW]; + self->move_angles[YAW] = self->ideal_yaw; + + self->blocked = turret_blocked; + + self->think = turret_breach_finish_init; + self->nextthink = level.time + FRAMETIME; + gi.linkentity (self); +} + + +/*QUAKED turret_base (0 0 0) ? +This portion of the turret changes yaw only. +MUST be teamed with a turret_breach. +*/ + +void SP_turret_base (edict_t *self) +{ + self->solid = SOLID_BSP; + self->movetype = MOVETYPE_PUSH; + gi.setmodel (self, self->model); + self->blocked = turret_blocked; + gi.linkentity (self); +} + + +/*QUAKED turret_driver (1 .5 0) (-16 -16 -24) (16 16 32) +Must NOT be on the team with the rest of the turret parts. +Instead it must target the turret_breach. +*/ + +void infantry_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage); +void infantry_stand (edict_t *self); +void monster_use (edict_t *self, edict_t *other, edict_t *activator); + +void turret_driver_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + edict_t *ent; + + // level the gun + self->target_ent->move_angles[0] = 0; + + // remove the driver from the end of them team chain + for (ent = self->target_ent->teammaster; ent->teamchain != self; ent = ent->teamchain) + ; + ent->teamchain = NULL; + self->teammaster = NULL; + self->flags &= ~FL_TEAMSLAVE; + + self->target_ent->owner = NULL; + self->target_ent->teammaster->owner = NULL; + + infantry_die (self, inflictor, attacker, damage); +} + +qboolean FindTarget (edict_t *self); + +void turret_driver_think (edict_t *self) +{ + vec3_t target; + vec3_t dir; + float reaction_time; + + self->nextthink = level.time + FRAMETIME; + + if (self->enemy && (!self->enemy->inuse || self->enemy->health <= 0)) + self->enemy = NULL; + + if (!self->enemy) + { + if (!FindTarget (self)) + return; + self->monsterinfo.trail_time = level.time; + self->monsterinfo.aiflags &= ~AI_LOST_SIGHT; + } + else + { + if (visible (self, self->enemy)) + { + if (self->monsterinfo.aiflags & AI_LOST_SIGHT) + { + self->monsterinfo.trail_time = level.time; + self->monsterinfo.aiflags &= ~AI_LOST_SIGHT; + } + } + else + { + self->monsterinfo.aiflags |= AI_LOST_SIGHT; + return; + } + } + + // let the turret know where we want it to aim + VectorCopy (self->enemy->s.origin, target); + target[2] += self->enemy->viewheight; + VectorSubtract (target, self->target_ent->s.origin, dir); + vectoangles (dir, self->target_ent->move_angles); + + // decide if we should shoot + if (level.time < self->monsterinfo.attack_finished) + return; + + reaction_time = (3 - skill->value) * 1.0; + if ((level.time - self->monsterinfo.trail_time) < reaction_time) + return; + + self->monsterinfo.attack_finished = level.time + reaction_time + 1.0; + //FIXME how do we really want to pass this along? + self->target_ent->spawnflags |= 65536; +} + +void turret_driver_link (edict_t *self) +{ + vec3_t vec; + edict_t *ent; + + self->think = turret_driver_think; + self->nextthink = level.time + FRAMETIME; + + self->target_ent = G_PickTarget (self->target); + self->target_ent->owner = self; + self->target_ent->teammaster->owner = self; + VectorCopy (self->target_ent->s.angles, self->s.angles); + + vec[0] = self->target_ent->s.origin[0] - self->s.origin[0]; + vec[1] = self->target_ent->s.origin[1] - self->s.origin[1]; + vec[2] = 0; + self->move_origin[0] = VectorLength(vec); + + VectorSubtract (self->s.origin, self->target_ent->s.origin, vec); + vectoangles (vec, vec); + AnglesNormalize(vec); + self->move_origin[1] = vec[1]; + + self->move_origin[2] = self->s.origin[2] - self->target_ent->s.origin[2]; + + // add the driver to the end of them team chain + for (ent = self->target_ent->teammaster; ent->teamchain; ent = ent->teamchain) + ; + ent->teamchain = self; + self->teammaster = self->target_ent->teammaster; + self->flags |= FL_TEAMSLAVE; +} + +void SP_turret_driver (edict_t *self) +{ + if (deathmatch->value) + { + G_FreeEdict (self); + return; + } + + self->movetype = MOVETYPE_PUSH; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex("models/monsters/infantry/tris.md2"); + VectorSet (self->mins, -16, -16, -24); + VectorSet (self->maxs, 16, 16, 32); + + self->health = 100; + self->gib_health = 0; + self->mass = 200; + self->viewheight = 24; + + self->die = turret_driver_die; + self->monsterinfo.stand = infantry_stand; + + self->flags |= FL_NO_KNOCKBACK; + + level.total_monsters++; + + self->svflags |= SVF_MONSTER; + self->s.renderfx |= RF_FRAMELERP; + self->takedamage = DAMAGE_AIM; + self->use = monster_use; + self->clipmask = MASK_MONSTERSOLID; + VectorCopy (self->s.origin, self->s.old_origin); + self->monsterinfo.aiflags |= AI_STAND_GROUND|AI_DUCKED; + + if (st.item) + { + self->item = FindItemByClassname (st.item); + if (!self->item) + gi.dprintf("%s at %s has bad item: %s\n", self->classname, vtos(self->s.origin), st.item); + } + + self->think = turret_driver_link; + self->nextthink = level.time + FRAMETIME; + + gi.linkentity (self); +} diff --git a/original/xatrix/g_utils.c b/original/xatrix/g_utils.c new file mode 100644 index 0000000..6a577a1 --- /dev/null +++ b/original/xatrix/g_utils.c @@ -0,0 +1,551 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// g_utils.c -- misc utility functions for game module + +#include "g_local.h" + + +void G_ProjectSource (vec3_t point, vec3_t distance, vec3_t forward, vec3_t right, vec3_t result) +{ + result[0] = point[0] + forward[0] * distance[0] + right[0] * distance[1]; + result[1] = point[1] + forward[1] * distance[0] + right[1] * distance[1]; + result[2] = point[2] + forward[2] * distance[0] + right[2] * distance[1] + distance[2]; +} + + +/* +============= +G_Find + +Searches all active entities for the next one that holds +the matching string at fieldofs (use the FOFS() macro) in the structure. + +Searches beginning at the edict after from, or the beginning if NULL +NULL will be returned if the end of the list is reached. + +============= +*/ +edict_t *G_Find (edict_t *from, int fieldofs, char *match) +{ + char *s; + + if (!from) + from = g_edicts; + else + from++; + + for ( ; from < &g_edicts[globals.num_edicts] ; from++) + { + if (!from->inuse) + continue; + s = *(char **) ((byte *)from + fieldofs); + if (!s) + continue; + if (!Q_stricmp (s, match)) + return from; + } + + return NULL; +} + + +/* +================= +findradius + +Returns entities that have origins within a spherical area + +findradius (origin, radius) +================= +*/ +edict_t *findradius (edict_t *from, vec3_t org, float rad) +{ + vec3_t eorg; + int j; + + if (!from) + from = g_edicts; + else + from++; + for ( ; from < &g_edicts[globals.num_edicts]; from++) + { + if (!from->inuse) + continue; + if (from->solid == SOLID_NOT) + continue; + for (j=0 ; j<3 ; j++) + eorg[j] = org[j] - (from->s.origin[j] + (from->mins[j] + from->maxs[j])*0.5); + if (VectorLength(eorg) > rad) + continue; + return from; + } + + return NULL; +} + + +/* +============= +G_PickTarget + +Searches all active entities for the next one that holds +the matching string at fieldofs (use the FOFS() macro) in the structure. + +Searches beginning at the edict after from, or the beginning if NULL +NULL will be returned if the end of the list is reached. + +============= +*/ +#define MAXCHOICES 8 + +edict_t *G_PickTarget (char *targetname) +{ + edict_t *ent = NULL; + int num_choices = 0; + edict_t *choice[MAXCHOICES]; + + if (!targetname) + { + gi.dprintf("G_PickTarget called with NULL targetname\n"); + return NULL; + } + + while(1) + { + ent = G_Find (ent, FOFS(targetname), targetname); + if (!ent) + break; + choice[num_choices++] = ent; + if (num_choices == MAXCHOICES) + break; + } + + if (!num_choices) + { + gi.dprintf("G_PickTarget: target %s not found\n", targetname); + return NULL; + } + + return choice[rand() % num_choices]; +} + + + +void Think_Delay (edict_t *ent) +{ + G_UseTargets (ent, ent->activator); + G_FreeEdict (ent); +} + +/* +============================== +G_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. + +Search for (string)targetname in all entities that +match (string)self.target and call their .use function + +============================== +*/ +void G_UseTargets (edict_t *ent, edict_t *activator) +{ + edict_t *t; + +// +// check for a delay +// + if (ent->delay) + { + // create a temp object to fire at a later time + t = G_Spawn(); + t->classname = "DelayedUse"; + t->nextthink = level.time + ent->delay; + t->think = Think_Delay; + t->activator = activator; + if (!activator) + gi.dprintf ("Think_Delay with no activator\n"); + t->message = ent->message; + t->target = ent->target; + t->killtarget = ent->killtarget; + return; + } + + +// +// print the message +// + if ((ent->message) && !(activator->svflags & SVF_MONSTER)) + { + gi.centerprintf (activator, "%s", ent->message); + if (ent->noise_index) + gi.sound (activator, CHAN_AUTO, ent->noise_index, 1, ATTN_NORM, 0); + else + gi.sound (activator, CHAN_AUTO, gi.soundindex ("misc/talk1.wav"), 1, ATTN_NORM, 0); + } + +// +// kill killtargets +// + if (ent->killtarget) + { + t = NULL; + while ((t = G_Find (t, FOFS(targetname), ent->killtarget))) + { + G_FreeEdict (t); + if (!ent->inuse) + { + gi.dprintf("entity was removed while using killtargets\n"); + return; + } + } + } + +// +// fire targets +// + if (ent->target) + { + t = NULL; + while ((t = G_Find (t, FOFS(targetname), ent->target))) + { + // doors fire area portals in a specific way + if (!Q_stricmp(t->classname, "func_areaportal") && + (!Q_stricmp(ent->classname, "func_door") || !Q_stricmp(ent->classname, "func_door_rotating"))) + continue; + + if (t == ent) + { + gi.dprintf ("WARNING: Entity used itself.\n"); + } + else + { + if (t->use) + t->use (t, ent, activator); + } + if (!ent->inuse) + { + gi.dprintf("entity was removed while using targets\n"); + return; + } + } + } +} + + +/* +============= +TempVector + +This is just a convenience function +for making temporary vectors for function calls +============= +*/ +float *tv (float x, float y, float z) +{ + static int index; + static vec3_t vecs[8]; + float *v; + + // use an array so that multiple tempvectors won't collide + // for a while + v = vecs[index]; + index = (index + 1)&7; + + v[0] = x; + v[1] = y; + v[2] = z; + + return v; +} + + +/* +============= +VectorToString + +This is just a convenience function +for printing vectors +============= +*/ +char *vtos (vec3_t v) +{ + static int index; + static char str[8][32]; + char *s; + + // use an array so that multiple vtos won't collide + s = str[index]; + index = (index + 1)&7; + + Com_sprintf (s, 32, "(%i %i %i)", (int)v[0], (int)v[1], (int)v[2]); + + return s; +} + + +vec3_t VEC_UP = {0, -1, 0}; +vec3_t MOVEDIR_UP = {0, 0, 1}; +vec3_t VEC_DOWN = {0, -2, 0}; +vec3_t MOVEDIR_DOWN = {0, 0, -1}; + +void G_SetMovedir (vec3_t angles, vec3_t movedir) +{ + if (VectorCompare (angles, VEC_UP)) + { + VectorCopy (MOVEDIR_UP, movedir); + } + else if (VectorCompare (angles, VEC_DOWN)) + { + VectorCopy (MOVEDIR_DOWN, movedir); + } + else + { + AngleVectors (angles, movedir, NULL, NULL); + } + + VectorClear (angles); +} + + +float vectoyaw (vec3_t vec) +{ + float yaw; + + if (/*vec[YAW] == 0 &&*/ vec[PITCH] == 0) + { + yaw = 0; + if (vec[YAW] > 0) + yaw = 90; + else if (vec[YAW] < 0) + yaw = -90; + } + else + { + yaw = (int) (atan2(vec[YAW], vec[PITCH]) * 180 / M_PI); + if (yaw < 0) + yaw += 360; + } + + return yaw; +} + + +void vectoangles (vec3_t value1, vec3_t angles) +{ + float forward; + float yaw, pitch; + + if (value1[1] == 0 && value1[0] == 0) + { + yaw = 0; + if (value1[2] > 0) + pitch = 90; + else + pitch = 270; + } + else + { + if (value1[0]) + yaw = (int) (atan2(value1[1], value1[0]) * 180 / M_PI); + else if (value1[1] > 0) + yaw = 90; + else + yaw = -90; + if (yaw < 0) + yaw += 360; + + forward = sqrt (value1[0]*value1[0] + value1[1]*value1[1]); + pitch = (int) (atan2(value1[2], forward) * 180 / M_PI); + if (pitch < 0) + pitch += 360; + } + + angles[PITCH] = -pitch; + angles[YAW] = yaw; + angles[ROLL] = 0; +} + +char *G_CopyString (char *in) +{ + char *out; + + out = gi.TagMalloc (strlen(in)+1, TAG_LEVEL); + strcpy (out, in); + return out; +} + + +void G_InitEdict (edict_t *e) +{ + e->inuse = true; + e->classname = "noclass"; + e->gravity = 1.0; + e->s.number = e - g_edicts; +} + +/* +================= +G_Spawn + +Either finds a free edict, or allocates a new one. +Try to avoid reusing an entity that was recently freed, because it +can cause the client to think the entity morphed into something else +instead of being removed and recreated, which can cause interpolated +angles and bad trails. +================= +*/ +edict_t *G_Spawn (void) +{ + int i; + edict_t *e; + + e = &g_edicts[(int)maxclients->value+1]; + for ( i=maxclients->value+1 ; iinuse && ( e->freetime < 2 || level.time - e->freetime > 0.5 ) ) + { + G_InitEdict (e); + return e; + } + } + + if (i == game.maxentities) + gi.error ("ED_Alloc: no free edicts"); + + globals.num_edicts++; + G_InitEdict (e); + return e; +} + +/* +================= +G_FreeEdict + +Marks the edict as free +================= +*/ +void G_FreeEdict (edict_t *ed) +{ + gi.unlinkentity (ed); // unlink from world + + if ((ed - g_edicts) <= (maxclients->value + BODY_QUEUE_SIZE)) + { +// gi.dprintf("tried to free special edict\n"); + return; + } + + memset (ed, 0, sizeof(*ed)); + ed->classname = "freed"; + ed->freetime = level.time; + ed->inuse = false; +} + + +/* +============ +G_TouchTriggers + +============ +*/ +void G_TouchTriggers (edict_t *ent) +{ + int i, num; + edict_t *touch[MAX_EDICTS], *hit; + + // dead things don't activate triggers! + if ((ent->client || (ent->svflags & SVF_MONSTER)) && (ent->health <= 0)) + return; + + num = gi.BoxEdicts (ent->absmin, ent->absmax, touch + , MAX_EDICTS, AREA_TRIGGERS); + + // be careful, it is possible to have an entity in this + // list removed before we get to it (killtriggered) + for (i=0 ; iinuse) + continue; + if (!hit->touch) + continue; + hit->touch (hit, ent, NULL, NULL); + } +} + +/* +============ +G_TouchSolids + +Call after linking a new trigger in during gameplay +to force all entities it covers to immediately touch it +============ +*/ +void G_TouchSolids (edict_t *ent) +{ + int i, num; + edict_t *touch[MAX_EDICTS], *hit; + + num = gi.BoxEdicts (ent->absmin, ent->absmax, touch + , MAX_EDICTS, AREA_SOLID); + + // be careful, it is possible to have an entity in this + // list removed before we get to it (killtriggered) + for (i=0 ; iinuse) + continue; + if (ent->touch) + ent->touch (hit, ent, NULL, NULL); + if (!ent->inuse) + break; + } +} + + + + +/* +============================================================================== + +Kill box + +============================================================================== +*/ + +/* +================= +KillBox + +Kills all entities that would touch the proposed new positioning +of ent. Ent should be unlinked before calling this! +================= +*/ +qboolean KillBox (edict_t *ent) +{ + trace_t tr; + + while (1) + { + tr = gi.trace (ent->s.origin, ent->mins, ent->maxs, ent->s.origin, NULL, MASK_PLAYERSOLID); + if (!tr.ent) + break; + + // nail it + T_Damage (tr.ent, ent, ent, vec3_origin, ent->s.origin, vec3_origin, 100000, 0, DAMAGE_NO_PROTECTION, MOD_TELEFRAG); + + // if we didn't kill it, fail + if (tr.ent->solid) + return false; + } + + return true; // all clear +} diff --git a/original/xatrix/g_weapon.c b/original/xatrix/g_weapon.c new file mode 100644 index 0000000..366e59c --- /dev/null +++ b/original/xatrix/g_weapon.c @@ -0,0 +1,1491 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +#include "g_local.h" + + +/* +================= +check_dodge + +This is a support routine used when a client is firing +a non-instant attack weapon. It checks to see if a +monster's dodge function should be called. +================= +*/ +static void check_dodge (edict_t *self, vec3_t start, vec3_t dir, int speed) +{ + vec3_t end; + vec3_t v; + trace_t tr; + float eta; + + // easy mode only ducks one quarter the time + if (skill->value == 0) + { + if (random() > 0.25) + return; + } + VectorMA (start, 8192, dir, end); + tr = gi.trace (start, NULL, NULL, end, self, MASK_SHOT); + if ((tr.ent) && (tr.ent->svflags & SVF_MONSTER) && (tr.ent->health > 0) && (tr.ent->monsterinfo.dodge) && infront(tr.ent, self)) + { + VectorSubtract (tr.endpos, start, v); + eta = (VectorLength(v) - tr.ent->maxs[0]) / speed; + tr.ent->monsterinfo.dodge (tr.ent, self, eta); + } +} + + +/* +================= +fire_hit + +Used for all impact (hit/punch/slash) attacks +================= +*/ +qboolean fire_hit (edict_t *self, vec3_t aim, int damage, int kick) +{ + trace_t tr; + vec3_t forward, right, up; + vec3_t v; + vec3_t point; + float range; + vec3_t dir; + + //see if enemy is in range + VectorSubtract (self->enemy->s.origin, self->s.origin, dir); + range = VectorLength(dir); + if (range > aim[0]) + return false; + + if (aim[1] > self->mins[0] && aim[1] < self->maxs[0]) + { + // the hit is straight on so back the range up to the edge of their bbox + range -= self->enemy->maxs[0]; + } + else + { + // this is a side hit so adjust the "right" value out to the edge of their bbox + if (aim[1] < 0) + aim[1] = self->enemy->mins[0]; + else + aim[1] = self->enemy->maxs[0]; + } + + VectorMA (self->s.origin, range, dir, point); + + tr = gi.trace (self->s.origin, NULL, NULL, point, self, MASK_SHOT); + if (tr.fraction < 1) + { + if (!tr.ent->takedamage) + return false; + // if it will hit any client/monster then hit the one we wanted to hit + if ((tr.ent->svflags & SVF_MONSTER) || (tr.ent->client)) + tr.ent = self->enemy; + } + + AngleVectors(self->s.angles, forward, right, up); + VectorMA (self->s.origin, range, forward, point); + VectorMA (point, aim[1], right, point); + VectorMA (point, aim[2], up, point); + VectorSubtract (point, self->enemy->s.origin, dir); + + // do the damage + T_Damage (tr.ent, self, self, dir, point, vec3_origin, damage, kick/2, DAMAGE_NO_KNOCKBACK, MOD_HIT); + + if (!(tr.ent->svflags & SVF_MONSTER) && (!tr.ent->client)) + return false; + + // do our special form of knockback here + VectorMA (self->enemy->absmin, 0.5, self->enemy->size, v); + VectorSubtract (v, point, v); + VectorNormalize (v); + VectorMA (self->enemy->velocity, kick, v, self->enemy->velocity); + if (self->enemy->velocity[2] > 0) + self->enemy->groundentity = NULL; + return true; +} + + +/* +================= +fire_lead + +This is an internal support routine used for bullet/pellet based weapons. +================= +*/ +static void fire_lead (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int te_impact, int hspread, int vspread, int mod) +{ + trace_t tr; + vec3_t dir; + vec3_t forward, right, up; + vec3_t end; + float r; + float u; + vec3_t water_start; + qboolean water = false; + int content_mask = MASK_SHOT | MASK_WATER; + + tr = gi.trace (self->s.origin, NULL, NULL, start, self, MASK_SHOT); + if (!(tr.fraction < 1.0)) + { + vectoangles (aimdir, dir); + AngleVectors (dir, forward, right, up); + + r = crandom()*hspread; + u = crandom()*vspread; + VectorMA (start, 8192, forward, end); + VectorMA (end, r, right, end); + VectorMA (end, u, up, end); + + if (gi.pointcontents (start) & MASK_WATER) + { + water = true; + VectorCopy (start, water_start); + content_mask &= ~MASK_WATER; + } + + tr = gi.trace (start, NULL, NULL, end, self, content_mask); + + // see if we hit water + if (tr.contents & MASK_WATER) + { + int color; + + water = true; + VectorCopy (tr.endpos, water_start); + + if (!VectorCompare (start, tr.endpos)) + { + if (tr.contents & CONTENTS_WATER) + { + if (strcmp(tr.surface->name, "*brwater") == 0) + color = SPLASH_BROWN_WATER; + else + color = SPLASH_BLUE_WATER; + } + else if (tr.contents & CONTENTS_SLIME) + color = SPLASH_SLIME; + else if (tr.contents & CONTENTS_LAVA) + color = SPLASH_LAVA; + else + color = SPLASH_UNKNOWN; + + if (color != SPLASH_UNKNOWN) + { + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_SPLASH); + gi.WriteByte (8); + gi.WritePosition (tr.endpos); + gi.WriteDir (tr.plane.normal); + gi.WriteByte (color); + gi.multicast (tr.endpos, MULTICAST_PVS); + } + + // change bullet's course when it enters water + VectorSubtract (end, start, dir); + vectoangles (dir, dir); + AngleVectors (dir, forward, right, up); + r = crandom()*hspread*2; + u = crandom()*vspread*2; + VectorMA (water_start, 8192, forward, end); + VectorMA (end, r, right, end); + VectorMA (end, u, up, end); + } + + // re-trace ignoring water this time + tr = gi.trace (water_start, NULL, NULL, end, self, MASK_SHOT); + } + } + + // send gun puff / flash + if (!((tr.surface) && (tr.surface->flags & SURF_SKY))) + { + if (tr.fraction < 1.0) + { + if (tr.ent->takedamage) + { + T_Damage (tr.ent, self, self, aimdir, tr.endpos, tr.plane.normal, damage, kick, DAMAGE_BULLET, mod); + } + else + { + if (strncmp (tr.surface->name, "sky", 3) != 0) + { + gi.WriteByte (svc_temp_entity); + gi.WriteByte (te_impact); + gi.WritePosition (tr.endpos); + gi.WriteDir (tr.plane.normal); + gi.multicast (tr.endpos, MULTICAST_PVS); + + if (self->client) + PlayerNoise(self, tr.endpos, PNOISE_IMPACT); + } + } + } + } + + // if went through water, determine where the end and make a bubble trail + if (water) + { + vec3_t pos; + + VectorSubtract (tr.endpos, water_start, dir); + VectorNormalize (dir); + VectorMA (tr.endpos, -2, dir, pos); + if (gi.pointcontents (pos) & MASK_WATER) + VectorCopy (pos, tr.endpos); + else + tr = gi.trace (pos, NULL, NULL, water_start, tr.ent, MASK_WATER); + + VectorAdd (water_start, tr.endpos, pos); + VectorScale (pos, 0.5, pos); + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_BUBBLETRAIL); + gi.WritePosition (water_start); + gi.WritePosition (tr.endpos); + gi.multicast (pos, MULTICAST_PVS); + } +} + + +/* +================= +fire_bullet + +Fires a single round. Used for machinegun and chaingun. Would be fine for +pistols, rifles, etc.... +================= +*/ +void fire_bullet (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int hspread, int vspread, int mod) +{ + fire_lead (self, start, aimdir, damage, kick, TE_GUNSHOT, hspread, vspread, mod); +} + + +/* +================= +fire_shotgun + +Shoots shotgun pellets. Used by shotgun and super shotgun. +================= +*/ +void fire_shotgun (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int hspread, int vspread, int count, int mod) +{ + int i; + + for (i = 0; i < count; i++) + fire_lead (self, start, aimdir, damage, kick, TE_SHOTGUN, hspread, vspread, mod); +} + + +/* +================= +fire_blaster + +Fires a single blaster bolt. Used by the blaster and hyper blaster. +================= +*/ +void blaster_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + int mod; + + if (other == self->owner) + return; + + if (surf && (surf->flags & SURF_SKY)) + { + G_FreeEdict (self); + return; + } + + if (self->owner->client) + PlayerNoise(self->owner, self->s.origin, PNOISE_IMPACT); + + if (other->takedamage) + { + if (self->spawnflags & 1) + mod = MOD_HYPERBLASTER; + else + mod = MOD_BLASTER; + T_Damage (other, self, self->owner, self->velocity, self->s.origin, plane->normal, self->dmg, 1, DAMAGE_ENERGY, mod); + } + else + { + gi.WriteByte (svc_temp_entity); + // RAFAEL + //if (self->s.effects & TE_BLUEHYPERBLASTER) + // gi.WriteByte (TE_BLUEHYPERBLASTER); + //else + gi.WriteByte (TE_BLASTER); + gi.WritePosition (self->s.origin); + if (!plane) + gi.WriteDir (vec3_origin); + else + gi.WriteDir (plane->normal); + gi.multicast (self->s.origin, MULTICAST_PVS); + } + + G_FreeEdict (self); +} + +void fire_blaster (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int effect, qboolean hyper) +{ + edict_t *bolt; + trace_t tr; + + VectorNormalize (dir); + + bolt = G_Spawn(); + bolt->svflags = SVF_DEADMONSTER; + // yes, I know it looks weird that projectiles are deadmonsters + // what this means is that when prediction is used against the object + // (blaster/hyperblaster shots), the player won't be solid clipped against + // the object. Right now trying to run into a firing hyperblaster + // is very jerky since you are predicted 'against' the shots. + VectorCopy (start, bolt->s.origin); + VectorCopy (start, bolt->s.old_origin); + vectoangles (dir, bolt->s.angles); + VectorScale (dir, speed, bolt->velocity); + bolt->movetype = MOVETYPE_FLYMISSILE; + bolt->clipmask = MASK_SHOT; + bolt->solid = SOLID_BBOX; + bolt->s.effects |= effect; + VectorClear (bolt->mins); + VectorClear (bolt->maxs); + bolt->s.modelindex = gi.modelindex ("models/objects/laser/tris.md2"); + bolt->s.sound = gi.soundindex ("misc/lasfly.wav"); + bolt->owner = self; + bolt->touch = blaster_touch; + bolt->nextthink = level.time + 2; + bolt->think = G_FreeEdict; + bolt->dmg = damage; + bolt->classname = "bolt"; + if (hyper) + bolt->spawnflags = 1; + gi.linkentity (bolt); + + if (self->client) + check_dodge (self, bolt->s.origin, dir, speed); + + tr = gi.trace (self->s.origin, NULL, NULL, bolt->s.origin, bolt, MASK_SHOT); + if (tr.fraction < 1.0) + { + VectorMA (bolt->s.origin, -10, dir, bolt->s.origin); + bolt->touch (bolt, tr.ent, NULL, NULL); + } +} + +// RAFAEL +void fire_blueblaster (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int effect) +{ + edict_t *bolt; + trace_t tr; + + VectorNormalize (dir); + + bolt = G_Spawn (); + VectorCopy (start, bolt->s.origin); + VectorCopy (start, bolt->s.old_origin); + vectoangles (dir, bolt->s.angles); + VectorScale (dir, speed, bolt->velocity); + bolt->movetype = MOVETYPE_FLYMISSILE; + bolt->clipmask = MASK_SHOT; + bolt->solid = SOLID_BBOX; + bolt->s.effects |= effect; + VectorClear (bolt->mins); + VectorClear (bolt->maxs); + + bolt->s.modelindex = gi.modelindex ("models/objects/blaser/tris.md2"); + bolt->s.sound = gi.soundindex ("misc/lasfly.wav"); + bolt->owner = self; + bolt->touch = blaster_touch; + bolt->nextthink = level.time + 2; + bolt->think = G_FreeEdict; + bolt->dmg = damage; + bolt->classname = "bolt"; + gi.linkentity (bolt); + + if (self->client) + check_dodge (self, bolt->s.origin, dir, speed); + + tr = gi.trace (self->s.origin, NULL, NULL, bolt->s.origin, bolt, MASK_SHOT); + + if (tr.fraction < 1.0) + { + VectorMA (bolt->s.origin, -10, dir, bolt->s.origin); + bolt->touch (bolt, tr.ent, NULL, NULL); + } + +} + +/* +================= +fire_grenade +================= +*/ +static void Grenade_Explode (edict_t *ent) +{ + vec3_t origin; + int mod; + + if (ent->owner->client) + PlayerNoise(ent->owner, ent->s.origin, PNOISE_IMPACT); + + //FIXME: if we are onground then raise our Z just a bit since we are a point? + if (ent->enemy) + { + float points; + vec3_t v; + vec3_t dir; + + VectorAdd (ent->enemy->mins, ent->enemy->maxs, v); + VectorMA (ent->enemy->s.origin, 0.5, v, v); + VectorSubtract (ent->s.origin, v, v); + points = ent->dmg - 0.5 * VectorLength (v); + VectorSubtract (ent->enemy->s.origin, ent->s.origin, dir); + if (ent->spawnflags & 1) + mod = MOD_HANDGRENADE; + else + mod = MOD_GRENADE; + T_Damage (ent->enemy, ent, ent->owner, dir, ent->s.origin, vec3_origin, (int)points, (int)points, DAMAGE_RADIUS, mod); + } + + if (ent->spawnflags & 2) + mod = MOD_HELD_GRENADE; + else if (ent->spawnflags & 1) + mod = MOD_HG_SPLASH; + else + mod = MOD_G_SPLASH; + T_RadiusDamage(ent, ent->owner, ent->dmg, ent->enemy, ent->dmg_radius, mod); + + VectorMA (ent->s.origin, -0.02, ent->velocity, origin); + gi.WriteByte (svc_temp_entity); + if (ent->waterlevel) + { + if (ent->groundentity) + gi.WriteByte (TE_GRENADE_EXPLOSION_WATER); + else + gi.WriteByte (TE_ROCKET_EXPLOSION_WATER); + } + else + { + if (ent->groundentity) + gi.WriteByte (TE_GRENADE_EXPLOSION); + else + gi.WriteByte (TE_ROCKET_EXPLOSION); + } + gi.WritePosition (origin); + gi.multicast (ent->s.origin, MULTICAST_PHS); + + G_FreeEdict (ent); +} + +static void Grenade_Touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (other == ent->owner) + return; + + if (surf && (surf->flags & SURF_SKY)) + { + G_FreeEdict (ent); + return; + } + + if (!other->takedamage) + { + if (ent->spawnflags & 1) + { + if (random() > 0.5) + gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/hgrenb1a.wav"), 1, ATTN_NORM, 0); + else + gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/hgrenb2a.wav"), 1, ATTN_NORM, 0); + } + else + { + gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/grenlb1b.wav"), 1, ATTN_NORM, 0); + } + return; + } + + ent->enemy = other; + Grenade_Explode (ent); +} + +void fire_grenade (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, float timer, float damage_radius) +{ + edict_t *grenade; + vec3_t dir; + vec3_t forward, right, up; + + vectoangles (aimdir, dir); + AngleVectors (dir, forward, right, up); + + grenade = G_Spawn(); + VectorCopy (start, grenade->s.origin); + VectorScale (aimdir, speed, grenade->velocity); + VectorMA (grenade->velocity, 200 + crandom() * 10.0, up, grenade->velocity); + VectorMA (grenade->velocity, crandom() * 10.0, right, grenade->velocity); + VectorSet (grenade->avelocity, 300, 300, 300); + grenade->movetype = MOVETYPE_BOUNCE; + grenade->clipmask = MASK_SHOT; + grenade->solid = SOLID_BBOX; + grenade->s.effects |= EF_GRENADE; + VectorClear (grenade->mins); + VectorClear (grenade->maxs); + grenade->s.modelindex = gi.modelindex ("models/objects/grenade/tris.md2"); + grenade->owner = self; + grenade->touch = Grenade_Touch; + grenade->nextthink = level.time + timer; + grenade->think = Grenade_Explode; + grenade->dmg = damage; + grenade->dmg_radius = damage_radius; + grenade->classname = "grenade"; + + gi.linkentity (grenade); +} + +void fire_grenade2 (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, float timer, float damage_radius, qboolean held) +{ + edict_t *grenade; + vec3_t dir; + vec3_t forward, right, up; + + vectoangles (aimdir, dir); + AngleVectors (dir, forward, right, up); + + grenade = G_Spawn(); + VectorCopy (start, grenade->s.origin); + VectorScale (aimdir, speed, grenade->velocity); + VectorMA (grenade->velocity, 200 + crandom() * 10.0, up, grenade->velocity); + VectorMA (grenade->velocity, crandom() * 10.0, right, grenade->velocity); + VectorSet (grenade->avelocity, 300, 300, 300); + grenade->movetype = MOVETYPE_BOUNCE; + grenade->clipmask = MASK_SHOT; + grenade->solid = SOLID_BBOX; + grenade->s.effects |= EF_GRENADE; + VectorClear (grenade->mins); + VectorClear (grenade->maxs); + grenade->s.modelindex = gi.modelindex ("models/objects/grenade2/tris.md2"); + grenade->owner = self; + grenade->touch = Grenade_Touch; + grenade->nextthink = level.time + timer; + grenade->think = Grenade_Explode; + grenade->dmg = damage; + grenade->dmg_radius = damage_radius; + grenade->classname = "hgrenade"; + if (held) + grenade->spawnflags = 3; + else + grenade->spawnflags = 1; + grenade->s.sound = gi.soundindex("weapons/hgrenc1b.wav"); + + if (timer <= 0.0) + Grenade_Explode (grenade); + else + { + gi.sound (self, CHAN_WEAPON, gi.soundindex ("weapons/hgrent1a.wav"), 1, ATTN_NORM, 0); + gi.linkentity (grenade); + } +} + + +/* +================= +fire_rocket +================= +*/ +void rocket_touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + vec3_t origin; + int n; + + if (other == ent->owner) + return; + + if (surf && (surf->flags & SURF_SKY)) + { + G_FreeEdict (ent); + return; + } + + if (ent->owner->client) + PlayerNoise(ent->owner, ent->s.origin, PNOISE_IMPACT); + + // calculate position for the explosion entity + VectorMA (ent->s.origin, -0.02, ent->velocity, origin); + + if (other->takedamage) + { + T_Damage (other, ent, ent->owner, ent->velocity, ent->s.origin, plane->normal, ent->dmg, 0, 0, MOD_ROCKET); + } + else + { + // don't throw any debris in net games + if (!deathmatch->value && !coop->value) + { + if ((surf) && !(surf->flags & (SURF_WARP|SURF_TRANS33|SURF_TRANS66|SURF_FLOWING))) + { + n = rand() % 5; + while(n--) + ThrowDebris (ent, "models/objects/debris2/tris.md2", 2, ent->s.origin); + } + } + } + + T_RadiusDamage(ent, ent->owner, ent->radius_dmg, other, ent->dmg_radius, MOD_R_SPLASH); + + gi.WriteByte (svc_temp_entity); + if (ent->waterlevel) + gi.WriteByte (TE_ROCKET_EXPLOSION_WATER); + else + gi.WriteByte (TE_ROCKET_EXPLOSION); + gi.WritePosition (origin); + gi.multicast (ent->s.origin, MULTICAST_PHS); + + G_FreeEdict (ent); +} + +void fire_rocket (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, float damage_radius, int radius_damage) +{ + edict_t *rocket; + + rocket = G_Spawn(); + VectorCopy (start, rocket->s.origin); + VectorCopy (dir, rocket->movedir); + vectoangles (dir, rocket->s.angles); + VectorScale (dir, speed, rocket->velocity); + rocket->movetype = MOVETYPE_FLYMISSILE; + rocket->clipmask = MASK_SHOT; + rocket->solid = SOLID_BBOX; + rocket->s.effects |= EF_ROCKET; + VectorClear (rocket->mins); + VectorClear (rocket->maxs); + rocket->s.modelindex = gi.modelindex ("models/objects/rocket/tris.md2"); + rocket->owner = self; + rocket->touch = rocket_touch; + rocket->nextthink = level.time + 8000/speed; + rocket->think = G_FreeEdict; + rocket->dmg = damage; + rocket->radius_dmg = radius_damage; + rocket->dmg_radius = damage_radius; + rocket->s.sound = gi.soundindex ("weapons/rockfly.wav"); + rocket->classname = "rocket"; + + if (self->client) + check_dodge (self, rocket->s.origin, dir, speed); + + gi.linkentity (rocket); +} + + +/* +================= +fire_rail +================= +*/ +void fire_rail (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick) +{ + vec3_t from; + vec3_t end; + trace_t tr; + edict_t *ignore; + int mask; + qboolean water; + + VectorMA (start, 8192, aimdir, end); + VectorCopy (start, from); + ignore = self; + water = false; + mask = MASK_SHOT|CONTENTS_SLIME|CONTENTS_LAVA; + while (ignore) + { + tr = gi.trace (from, NULL, NULL, end, ignore, mask); + + if (tr.contents & (CONTENTS_SLIME|CONTENTS_LAVA)) + { + mask &= ~(CONTENTS_SLIME|CONTENTS_LAVA); + water = true; + } + else + { + //ZOID--added so rail goes through SOLID_BBOX entities (gibs, etc) + if ((tr.ent->svflags & SVF_MONSTER) || (tr.ent->client) || + (tr.ent->solid == SOLID_BBOX)) + ignore = tr.ent; + else + ignore = NULL; + + if ((tr.ent != self) && (tr.ent->takedamage)) + T_Damage (tr.ent, self, self, aimdir, tr.endpos, tr.plane.normal, damage, kick, 0, MOD_RAILGUN); + } + + VectorCopy (tr.endpos, from); + } + + // send gun puff / flash + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_RAILTRAIL); + gi.WritePosition (start); + gi.WritePosition (tr.endpos); + gi.multicast (self->s.origin, MULTICAST_PHS); +// gi.multicast (start, MULTICAST_PHS); + if (water) + { + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_RAILTRAIL); + gi.WritePosition (start); + gi.WritePosition (tr.endpos); + gi.multicast (tr.endpos, MULTICAST_PHS); + } + + if (self->client) + PlayerNoise(self, tr.endpos, PNOISE_IMPACT); +} + + +/* +================= +fire_bfg +================= +*/ +void bfg_explode (edict_t *self) +{ + edict_t *ent; + float points; + vec3_t v; + float dist; + + if (self->s.frame == 0) + { + // the BFG effect + ent = NULL; + while ((ent = findradius(ent, self->s.origin, self->dmg_radius)) != NULL) + { + if (!ent->takedamage) + continue; + if (ent == self->owner) + continue; + if (!CanDamage (ent, self)) + continue; + if (!CanDamage (ent, self->owner)) + continue; + + VectorAdd (ent->mins, ent->maxs, v); + VectorMA (ent->s.origin, 0.5, v, v); + VectorSubtract (self->s.origin, v, v); + dist = VectorLength(v); + points = self->radius_dmg * (1.0 - sqrt(dist/self->dmg_radius)); + if (ent == self->owner) + points = points * 0.5; + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_BFG_EXPLOSION); + gi.WritePosition (ent->s.origin); + gi.multicast (ent->s.origin, MULTICAST_PHS); + T_Damage (ent, self, self->owner, self->velocity, ent->s.origin, vec3_origin, (int)points, 0, DAMAGE_ENERGY, MOD_BFG_EFFECT); + } + } + + self->nextthink = level.time + FRAMETIME; + self->s.frame++; + if (self->s.frame == 5) + self->think = G_FreeEdict; +} + +void bfg_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (other == self->owner) + return; + + if (surf && (surf->flags & SURF_SKY)) + { + G_FreeEdict (self); + return; + } + + if (self->owner->client) + PlayerNoise(self->owner, self->s.origin, PNOISE_IMPACT); + + // core explosion - prevents firing it into the wall/floor + if (other->takedamage) + T_Damage (other, self, self->owner, self->velocity, self->s.origin, plane->normal, 200, 0, 0, MOD_BFG_BLAST); + T_RadiusDamage(self, self->owner, 200, other, 100, MOD_BFG_BLAST); + + gi.sound (self, CHAN_VOICE, gi.soundindex ("weapons/bfg__x1b.wav"), 1, ATTN_NORM, 0); + self->solid = SOLID_NOT; + self->touch = NULL; + VectorMA (self->s.origin, -1 * FRAMETIME, self->velocity, self->s.origin); + VectorClear (self->velocity); + self->s.modelindex = gi.modelindex ("sprites/s_bfg3.sp2"); + self->s.frame = 0; + self->s.sound = 0; + self->s.effects &= ~EF_ANIM_ALLFAST; + self->think = bfg_explode; + self->nextthink = level.time + FRAMETIME; + self->enemy = other; + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_BFG_BIGEXPLOSION); + gi.WritePosition (self->s.origin); + gi.multicast (self->s.origin, MULTICAST_PVS); +} + + +void bfg_think (edict_t *self) +{ + edict_t *ent; + edict_t *ignore; + vec3_t point; + vec3_t dir; + vec3_t start; + vec3_t end; + int dmg; + trace_t tr; + + if (deathmatch->value) + dmg = 5; + else + dmg = 10; + + ent = NULL; + while ((ent = findradius(ent, self->s.origin, 256)) != NULL) + { + if (ent == self) + continue; + + if (ent == self->owner) + continue; + + if (!ent->takedamage) + continue; + + if (!(ent->svflags & SVF_MONSTER) && (!ent->client) && (strcmp(ent->classname, "misc_explobox") != 0)) + continue; + + VectorMA (ent->absmin, 0.5, ent->size, point); + + VectorSubtract (point, self->s.origin, dir); + VectorNormalize (dir); + + ignore = self; + VectorCopy (self->s.origin, start); + VectorMA (start, 2048, dir, end); + while(1) + { + tr = gi.trace (start, NULL, NULL, end, ignore, CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_DEADMONSTER); + + if (!tr.ent) + break; + + // hurt it if we can + if ((tr.ent->takedamage) && !(tr.ent->flags & FL_IMMUNE_LASER) && (tr.ent != self->owner)) + T_Damage (tr.ent, self, self->owner, dir, tr.endpos, vec3_origin, dmg, 1, DAMAGE_ENERGY, MOD_BFG_LASER); + + // if we hit something that's not a monster or player we're done + if (!(tr.ent->svflags & SVF_MONSTER) && (!tr.ent->client)) + { + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_LASER_SPARKS); + gi.WriteByte (4); + gi.WritePosition (tr.endpos); + gi.WriteDir (tr.plane.normal); + gi.WriteByte (self->s.skinnum); + gi.multicast (tr.endpos, MULTICAST_PVS); + break; + } + + ignore = tr.ent; + VectorCopy (tr.endpos, start); + } + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_BFG_LASER); + gi.WritePosition (self->s.origin); + gi.WritePosition (tr.endpos); + gi.multicast (self->s.origin, MULTICAST_PHS); + } + + self->nextthink = level.time + FRAMETIME; +} + + +void fire_bfg (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, float damage_radius) +{ + edict_t *bfg; + + bfg = G_Spawn(); + VectorCopy (start, bfg->s.origin); + VectorCopy (dir, bfg->movedir); + vectoangles (dir, bfg->s.angles); + VectorScale (dir, speed, bfg->velocity); + bfg->movetype = MOVETYPE_FLYMISSILE; + bfg->clipmask = MASK_SHOT; + bfg->solid = SOLID_BBOX; + bfg->s.effects |= EF_BFG | EF_ANIM_ALLFAST; + VectorClear (bfg->mins); + VectorClear (bfg->maxs); + bfg->s.modelindex = gi.modelindex ("sprites/s_bfg1.sp2"); + bfg->owner = self; + bfg->touch = bfg_touch; + bfg->nextthink = level.time + 8000/speed; + bfg->think = G_FreeEdict; + bfg->radius_dmg = damage; + bfg->dmg_radius = damage_radius; + bfg->classname = "bfg blast"; + bfg->s.sound = gi.soundindex ("weapons/bfg__l1a.wav"); + + bfg->think = bfg_think; + bfg->nextthink = level.time + FRAMETIME; + bfg->teammaster = bfg; + bfg->teamchain = NULL; + + if (self->client) + check_dodge (self, bfg->s.origin, dir, speed); + + gi.linkentity (bfg); +} + + +// RAFAEL + +/* + fire_ionripper +*/ + +void ionripper_sparks (edict_t *self) +{ + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_WELDING_SPARKS); + gi.WriteByte (0); + gi.WritePosition (self->s.origin); + gi.WriteDir (vec3_origin); + gi.WriteByte (0xe4 + (rand()&3)); + gi.multicast (self->s.origin, MULTICAST_PVS); + + G_FreeEdict (self); +} + +// RAFAEL +void ionripper_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (other == self->owner) + return; + + if (surf && (surf->flags & SURF_SKY)) + { + G_FreeEdict (self); + return; + } + + if (self->owner->client) + PlayerNoise (self->owner, self->s.origin, PNOISE_IMPACT); + + if (other->takedamage) + { + T_Damage (other, self, self->owner, self->velocity, self->s.origin, plane->normal, self->dmg, 1, DAMAGE_ENERGY, MOD_RIPPER); + + } + else + { + return; + } + + G_FreeEdict (self); +} + + +// RAFAEL +void fire_ionripper (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int effect) +{ + edict_t *ion; + trace_t tr; + + VectorNormalize (dir); + + ion = G_Spawn (); + VectorCopy (start, ion->s.origin); + VectorCopy (start, ion->s.old_origin); + vectoangles (dir, ion->s.angles); + VectorScale (dir, speed, ion->velocity); + + ion->movetype = MOVETYPE_WALLBOUNCE; + ion->clipmask = MASK_SHOT; + ion->solid = SOLID_BBOX; + ion->s.effects |= effect; + + ion->s.renderfx |= RF_FULLBRIGHT; + + VectorClear (ion->mins); + VectorClear (ion->maxs); + ion->s.modelindex = gi.modelindex ("models/objects/boomrang/tris.md2"); + ion->s.sound = gi.soundindex ("misc/lasfly.wav"); + ion->owner = self; + ion->touch = ionripper_touch; + ion->nextthink = level.time + 3; + ion->think = ionripper_sparks; + ion->dmg = damage; + ion->dmg_radius = 100; + gi.linkentity (ion); + + if (self->client) + check_dodge (self, ion->s.origin, dir, speed); + + tr = gi.trace (self->s.origin, NULL, NULL, ion->s.origin, ion, MASK_SHOT); + if (tr.fraction < 1.0) + { + VectorMA (ion->s.origin, -10, dir, ion->s.origin); + ion->touch (ion, tr.ent, NULL, NULL); + } + +} + + +// RAFAEL +/* +fire_heat +*/ + + +void heat_think (edict_t *self) +{ + edict_t *target = NULL; + edict_t *aquire = NULL; + vec3_t vec; + vec3_t oldang; + int len; + int oldlen = 0; + + VectorClear (vec); + + // aquire new target + while (( target = findradius (target, self->s.origin, 1024)) != NULL) + { + + if (self->owner == target) + continue; + if (!target->svflags & SVF_MONSTER) + continue; + if (!target->client) + continue; + if (target->health <= 0) + continue; + if (!visible (self, target)) + continue; + + // if we need to reduce the tracking cone + /* + { + vec3_t vec; + float dot; + vec3_t forward; + + AngleVectors (self->s.angles, forward, NULL, NULL); + VectorSubtract (target->s.origin, self->s.origin, vec); + VectorNormalize (vec); + dot = DotProduct (vec, forward); + + if (dot > 0.6) + continue; + } + */ + + if (!infront (self, target)) + continue; + + VectorSubtract (self->s.origin, target->s.origin, vec); + len = VectorLength (vec); + + if (aquire == NULL || len < oldlen) + { + aquire = target; + self->target_ent = aquire; + oldlen = len; + } + } + + if (aquire != NULL) + { + VectorCopy (self->s.angles, oldang); + VectorSubtract (aquire->s.origin, self->s.origin, vec); + + vectoangles (vec, self->s.angles); + + VectorNormalize (vec); + VectorCopy (vec, self->movedir); + VectorScale (vec, 500, self->velocity); + } + + self->nextthink = level.time + 0.1; +} + +// RAFAEL +void fire_heat (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, float damage_radius, int radius_damage) +{ + edict_t *heat; + + heat = G_Spawn(); + VectorCopy (start, heat->s.origin); + VectorCopy (dir, heat->movedir); + vectoangles (dir, heat->s.angles); + VectorScale (dir, speed, heat->velocity); + heat->movetype = MOVETYPE_FLYMISSILE; + heat->clipmask = MASK_SHOT; + heat->solid = SOLID_BBOX; + heat->s.effects |= EF_ROCKET; + VectorClear (heat->mins); + VectorClear (heat->maxs); + heat->s.modelindex = gi.modelindex ("models/objects/rocket/tris.md2"); + heat->owner = self; + heat->touch = rocket_touch; + + heat->nextthink = level.time + 0.1; + heat->think = heat_think; + + heat->dmg = damage; + heat->radius_dmg = radius_damage; + heat->dmg_radius = damage_radius; + heat->s.sound = gi.soundindex ("weapons/rockfly.wav"); + + if (self->client) + check_dodge (self, heat->s.origin, dir, speed); + + gi.linkentity (heat); +} + + + +// RAFAEL + +/* + fire_plasma +*/ + +void plasma_touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + vec3_t origin; + + if (other == ent->owner) + return; + + if (surf && (surf->flags & SURF_SKY)) + { + G_FreeEdict (ent); + return; + } + + if (ent->owner->client) + PlayerNoise(ent->owner, ent->s.origin, PNOISE_IMPACT); + + // calculate position for the explosion entity + VectorMA (ent->s.origin, -0.02, ent->velocity, origin); + + if (other->takedamage) + { + T_Damage (other, ent, ent->owner, ent->velocity, ent->s.origin, plane->normal, ent->dmg, 0, 0, MOD_PHALANX); + } + + T_RadiusDamage(ent, ent->owner, ent->radius_dmg, other, ent->dmg_radius, MOD_PHALANX); + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_PLASMA_EXPLOSION); + gi.WritePosition (origin); + gi.multicast (ent->s.origin, MULTICAST_PVS); + + G_FreeEdict (ent); +} + + +// RAFAEL +void fire_plasma (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, float damage_radius, int radius_damage) +{ + edict_t *plasma; + + plasma = G_Spawn(); + VectorCopy (start, plasma->s.origin); + VectorCopy (dir, plasma->movedir); + vectoangles (dir, plasma->s.angles); + VectorScale (dir, speed, plasma->velocity); + plasma->movetype = MOVETYPE_FLYMISSILE; + plasma->clipmask = MASK_SHOT; + plasma->solid = SOLID_BBOX; + + VectorClear (plasma->mins); + VectorClear (plasma->maxs); + + plasma->owner = self; + plasma->touch = plasma_touch; + plasma->nextthink = level.time + 8000/speed; + plasma->think = G_FreeEdict; + plasma->dmg = damage; + plasma->radius_dmg = radius_damage; + plasma->dmg_radius = damage_radius; + plasma->s.sound = gi.soundindex ("weapons/rockfly.wav"); + + plasma->s.modelindex = gi.modelindex ("sprites/s_photon.sp2"); + plasma->s.effects |= EF_PLASMA | EF_ANIM_ALLFAST; + + if (self->client) + check_dodge (self, plasma->s.origin, dir, speed); + + gi.linkentity (plasma); + + +} + +// RAFAEL +extern void SP_item_foodcube (edict_t *best); +// RAFAEL +static void Trap_Think (edict_t *ent) +{ + edict_t *target = NULL; + edict_t *best = NULL; + vec3_t vec; + int len, i; + int oldlen = 8000; + vec3_t forward, right, up; + + if (ent->timestamp < level.time) + { + BecomeExplosion1(ent); + // note to self + // cause explosion damage??? + return; + } + + ent->nextthink = level.time + 0.1; + + if (!ent->groundentity) + return; + + // ok lets do the blood effect + if (ent->s.frame > 4) + { + if (ent->s.frame == 5) + { + if (ent->wait == 64) + gi.sound(ent, CHAN_VOICE, gi.soundindex ("weapons/trapdown.wav"), 1, ATTN_IDLE, 0); + + ent->wait -= 2; + ent->delay += level.time; + + for (i=0; i<3; i++) + { + + best = G_Spawn(); + + if (strcmp (ent->enemy->classname, "monster_gekk") == 0) + { + best->s.modelindex = gi.modelindex ("models/objects/gekkgib/torso/tris.md2"); + best->s.effects |= TE_GREENBLOOD; + } + else if (ent->mass > 200) + { + best->s.modelindex = gi.modelindex ("models/objects/gibs/chest/tris.md2"); + best->s.effects |= TE_BLOOD; + } + else + { + best->s.modelindex = gi.modelindex ("models/objects/gibs/sm_meat/tris.md2"); + best->s.effects |= TE_BLOOD; + } + + AngleVectors (ent->s.angles, forward, right, up); + + RotatePointAroundVector( vec, up, right, ((360.0/3)* i)+ent->delay); + VectorMA (vec, ent->wait/2, vec, vec); + VectorAdd(vec, ent->s.origin, vec); + VectorAdd(vec, forward, best->s.origin); + + best->s.origin[2] = ent->s.origin[2] + ent->wait; + + VectorCopy (ent->s.angles, best->s.angles); + + best->solid = SOLID_NOT; + best->s.effects |= EF_GIB; + best->takedamage = DAMAGE_YES; + + best->movetype = MOVETYPE_TOSS; + best->svflags |= SVF_MONSTER; + best->deadflag = DEAD_DEAD; + + VectorClear (best->mins); + VectorClear (best->maxs); + + best->watertype = gi.pointcontents(best->s.origin); + if (best->watertype & MASK_WATER) + best->waterlevel = 1; + + best->nextthink = level.time + 0.1; + best->think = G_FreeEdict; + gi.linkentity (best); + } + + if (ent->wait < 19) + ent->s.frame ++; + + return; + } + ent->s.frame ++; + if (ent->s.frame == 8) + { + ent->nextthink = level.time + 1.0; + ent->think = G_FreeEdict; + + best = G_Spawn (); + SP_item_foodcube (best); + VectorCopy (ent->s.origin, best->s.origin); + best->s.origin[2]+= 16; + best->velocity[2] = 400; + best->count = ent->mass; + gi.linkentity (best); + return; + } + return; + } + + ent->s.effects &= ~EF_TRAP; + if (ent->s.frame >= 4) + { + ent->s.effects |= EF_TRAP; + VectorClear (ent->mins); + VectorClear (ent->maxs); + + } + + if (ent->s.frame < 4) + ent->s.frame++; + + while ((target = findradius(target, ent->s.origin, 256)) != NULL) + { + if (target == ent) + continue; + if (!(target->svflags & SVF_MONSTER) && !target->client) + continue; + // if (target == ent->owner) + // continue; + if (target->health <= 0) + continue; + if (!visible (ent, target)) + continue; + if (!best) + { + best = target; + continue; + } + VectorSubtract (ent->s.origin, target->s.origin, vec); + len = VectorLength (vec); + if (len < oldlen) + { + oldlen = len; + best = target; + } + } + + // pull the enemy in + if (best) + { + vec3_t forward; + + if (best->groundentity) + { + best->s.origin[2] += 1; + best->groundentity = NULL; + } + VectorSubtract (ent->s.origin, best->s.origin, vec); + len = VectorLength (vec); + if (best->client) + { + VectorNormalize (vec); + VectorMA (best->velocity, 250, vec, best->velocity); + } + else + { + best->ideal_yaw = vectoyaw(vec); + M_ChangeYaw (best); + AngleVectors (best->s.angles, forward, NULL, NULL); + VectorScale (forward, 256, best->velocity); + } + + gi.sound(ent, CHAN_VOICE, gi.soundindex ("weapons/trapsuck.wav"), 1, ATTN_IDLE, 0); + + if (len < 32) + { + if (best->mass < 400) + { + T_Damage (best, ent, ent->owner, vec3_origin, best->s.origin, vec3_origin, 100000, 1, 0, MOD_TRAP); + ent->enemy = best; + ent->wait = 64; + VectorCopy (ent->s.origin, ent->s.old_origin); + ent->timestamp = level.time + 30; + if (deathmatch->value) + ent->mass = best->mass/4; + else + ent->mass = best->mass/10; + // ok spawn the food cube + ent->s.frame = 5; + } + else + { + BecomeExplosion1(ent); + // note to self + // cause explosion damage??? + return; + } + + } + } + + +} + + +// RAFAEL +void fire_trap (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, float timer, float damage_radius, qboolean held) +{ + edict_t *trap; + vec3_t dir; + vec3_t forward, right, up; + + vectoangles (aimdir, dir); + AngleVectors (dir, forward, right, up); + + trap = G_Spawn(); + VectorCopy (start, trap->s.origin); + VectorScale (aimdir, speed, trap->velocity); + VectorMA (trap->velocity, 200 + crandom() * 10.0, up, trap->velocity); + VectorMA (trap->velocity, crandom() * 10.0, right, trap->velocity); + VectorSet (trap->avelocity, 0, 300, 0); + trap->movetype = MOVETYPE_BOUNCE; + trap->clipmask = MASK_SHOT; + trap->solid = SOLID_BBOX; +// VectorClear (trap->mins); +// VectorClear (trap->maxs); + VectorSet (trap->mins, -4, -4, 0); + VectorSet (trap->maxs, 4, 4, 8); + trap->s.modelindex = gi.modelindex ("models/weapons/z_trap/tris.md2"); + trap->owner = self; + trap->nextthink = level.time + 1.0; + trap->think = Trap_Think; + trap->dmg = damage; + trap->dmg_radius = damage_radius; + trap->classname = "htrap"; + // RAFAEL 16-APR-98 + trap->s.sound = gi.soundindex ("weapons/traploop.wav"); + // END 16-APR-98 + if (held) + trap->spawnflags = 3; + else + trap->spawnflags = 1; + + if (timer <= 0.0) + Grenade_Explode (trap); + else + { + // gi.sound (self, CHAN_WEAPON, gi.soundindex ("weapons/trapdown.wav"), 1, ATTN_NORM, 0); + gi.linkentity (trap); + } + + trap->timestamp = level.time + 30; + +} diff --git a/original/xatrix/game.h b/original/xatrix/game.h new file mode 100644 index 0000000..54f3d29 --- /dev/null +++ b/original/xatrix/game.h @@ -0,0 +1,218 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. + +// game.h -- game dll information visible to server + +#define GAME_API_VERSION 3 + +// edict->svflags + +#define SVF_NOCLIENT 0x00000001 // don't send entity to clients, even if it has effects +#define SVF_DEADMONSTER 0x00000002 // treat as CONTENTS_DEADMONSTER for collision +#define SVF_MONSTER 0x00000004 // treat as CONTENTS_MONSTER for collision + +// edict->solid values + +typedef enum +{ +SOLID_NOT, // no interaction with other objects +SOLID_TRIGGER, // only touch when inside, after moving +SOLID_BBOX, // touch on edge +SOLID_BSP // bsp clip, touch on edge +} solid_t; + +//=============================================================== + +// link_t is only used for entity area links now +typedef struct link_s +{ + struct link_s *prev, *next; +} link_t; + +#define MAX_ENT_CLUSTERS 16 + + +typedef struct edict_s edict_t; +typedef struct gclient_s gclient_t; + + +#ifndef GAME_INCLUDE + +struct gclient_s +{ + player_state_t ps; // communicated by server to clients + int ping; + // the game dll can add anything it wants after + // this point in the structure +}; + + +struct edict_s +{ + entity_state_t s; + struct gclient_s *client; + qboolean inuse; + int linkcount; + + // FIXME: move these fields to a server private sv_entity_t + link_t area; // linked to a division node or leaf + + int num_clusters; // if -1, use headnode instead + int clusternums[MAX_ENT_CLUSTERS]; + int headnode; // unused if num_clusters != -1 + int areanum, areanum2; + + //================================ + + int svflags; // SVF_NOCLIENT, SVF_DEADMONSTER, SVF_MONSTER, etc + vec3_t mins, maxs; + vec3_t absmin, absmax, size; + solid_t solid; + int clipmask; + edict_t *owner; + + // the game dll can add anything it wants after + // this point in the structure +}; + +#endif // GAME_INCLUDE + +//=============================================================== + +// +// functions provided by the main engine +// +typedef struct +{ + // special messages + void (*bprintf) (int printlevel, char *fmt, ...); + void (*dprintf) (char *fmt, ...); + void (*cprintf) (edict_t *ent, int printlevel, char *fmt, ...); + void (*centerprintf) (edict_t *ent, char *fmt, ...); + void (*sound) (edict_t *ent, int channel, int soundindex, float volume, float attenuation, float timeofs); + void (*positioned_sound) (vec3_t origin, edict_t *ent, int channel, int soundinedex, float volume, float attenuation, float timeofs); + + // config strings hold all the index strings, the lightstyles, + // and misc data like the sky definition and cdtrack. + // All of the current configstrings are sent to clients when + // they connect, and changes are sent to all connected clients. + void (*configstring) (int num, char *string); + + void (*error) (char *fmt, ...); + + // the *index functions create configstrings and some internal server state + int (*modelindex) (char *name); + int (*soundindex) (char *name); + int (*imageindex) (char *name); + + void (*setmodel) (edict_t *ent, char *name); + + // collision detection + trace_t (*trace) (vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, edict_t *passent, int contentmask); + int (*pointcontents) (vec3_t point); + qboolean (*inPVS) (vec3_t p1, vec3_t p2); + qboolean (*inPHS) (vec3_t p1, vec3_t p2); + void (*SetAreaPortalState) (int portalnum, qboolean open); + qboolean (*AreasConnected) (int area1, int area2); + + // an entity will never be sent to a client or used for collision + // if it is not passed to linkentity. If the size, position, or + // solidity changes, it must be relinked. + void (*linkentity) (edict_t *ent); + void (*unlinkentity) (edict_t *ent); // call before removing an interactive edict + int (*BoxEdicts) (vec3_t mins, vec3_t maxs, edict_t **list, int maxcount, int areatype); + void (*Pmove) (pmove_t *pmove); // player movement code common with client prediction + + // network messaging + void (*multicast) (vec3_t origin, multicast_t to); + void (*unicast) (edict_t *ent, qboolean reliable); + void (*WriteChar) (int c); + void (*WriteByte) (int c); + void (*WriteShort) (int c); + void (*WriteLong) (int c); + void (*WriteFloat) (float f); + void (*WriteString) (char *s); + void (*WritePosition) (vec3_t pos); // some fractional bits + void (*WriteDir) (vec3_t pos); // single byte encoded, very coarse + void (*WriteAngle) (float f); + + // managed memory allocation + void *(*TagMalloc) (int size, int tag); + void (*TagFree) (void *block); + void (*FreeTags) (int tag); + + // console variable interaction + cvar_t *(*cvar) (char *var_name, char *value, int flags); + cvar_t *(*cvar_set) (char *var_name, char *value); + cvar_t *(*cvar_forceset) (char *var_name, char *value); + + // ClientCommand and ServerCommand parameter access + int (*argc) (void); + char *(*argv) (int n); + char *(*args) (void); // concatenation of all argv >= 1 + + // add commands to the server console as if they were typed in + // for map changing, etc + void (*AddCommandString) (char *text); + + void (*DebugGraph) (float value, int color); +} game_import_t; + +// +// functions exported by the game subsystem +// +typedef struct +{ + int apiversion; + + // the init function will only be called when a game starts, + // not each time a level is loaded. Persistant data for clients + // and the server can be allocated in init + void (*Init) (void); + void (*Shutdown) (void); + + // each new level entered will cause a call to SpawnEntities + void (*SpawnEntities) (char *mapname, char *entstring, char *spawnpoint); + + // Read/Write Game is for storing persistant cross level information + // about the world state and the clients. + // WriteGame is called every time a level is exited. + // ReadGame is called on a loadgame. + void (*WriteGame) (char *filename, qboolean autosave); + void (*ReadGame) (char *filename); + + // ReadLevel is called after the default map information has been + // loaded with SpawnEntities + void (*WriteLevel) (char *filename); + void (*ReadLevel) (char *filename); + + qboolean (*ClientConnect) (edict_t *ent, char *userinfo); + void (*ClientBegin) (edict_t *ent); + void (*ClientUserinfoChanged) (edict_t *ent, char *userinfo); + void (*ClientDisconnect) (edict_t *ent); + void (*ClientCommand) (edict_t *ent); + void (*ClientThink) (edict_t *ent, usercmd_t *cmd); + + void (*RunFrame) (void); + + // ServerCommand will be called when an "sv " command is issued on the + // server console. + // The game can issue gi.argc() / gi.argv() commands to get the rest + // of the parameters + void (*ServerCommand) (void); + + // + // global variables shared between game and server + // + + // The edict array is allocated in the game dll so it + // can vary in size from one game to another. + // + // The size will be fixed when ge->Init() is called + struct edict_s *edicts; + int edict_size; + int num_edicts; // current number, <= max_edicts + int max_edicts; +} game_export_t; + +game_export_t *GetGameApi (game_import_t *import); diff --git a/original/xatrix/m_actor.c b/original/xatrix/m_actor.c new file mode 100644 index 0000000..9f6cbd6 --- /dev/null +++ b/original/xatrix/m_actor.c @@ -0,0 +1,592 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// g_actor.c + +#include "g_local.h" +#include "m_actor.h" + +#define MAX_ACTOR_NAMES 8 +char *actor_names[MAX_ACTOR_NAMES] = +{ + "Hellrot", + "Tokay", + "Killme", + "Disruptor", + "Adrianator", + "Rambear", + "Titus", + "Bitterman" +}; + + +mframe_t actor_frames_stand [] = +{ + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL +}; +mmove_t actor_move_stand = {FRAME_stand101, FRAME_stand140, actor_frames_stand, NULL}; + +void actor_stand (edict_t *self) +{ + self->monsterinfo.currentmove = &actor_move_stand; + + // randomize on startup + if (level.time < 1.0) + self->s.frame = self->monsterinfo.currentmove->firstframe + (rand() % (self->monsterinfo.currentmove->lastframe - self->monsterinfo.currentmove->firstframe + 1)); +} + + +mframe_t actor_frames_walk [] = +{ + ai_walk, 0, NULL, + ai_walk, 6, NULL, + ai_walk, 10, NULL, + ai_walk, 3, NULL, + ai_walk, 2, NULL, + ai_walk, 7, NULL, + ai_walk, 10, NULL, + ai_walk, 1, NULL, + ai_walk, 4, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL +}; +mmove_t actor_move_walk = {FRAME_walk01, FRAME_walk08, actor_frames_walk, NULL}; + +void actor_walk (edict_t *self) +{ + self->monsterinfo.currentmove = &actor_move_walk; +} + + +mframe_t actor_frames_run [] = +{ + ai_run, 4, NULL, + ai_run, 15, NULL, + ai_run, 15, NULL, + ai_run, 8, NULL, + ai_run, 20, NULL, + ai_run, 15, NULL, + ai_run, 8, NULL, + ai_run, 17, NULL, + ai_run, 12, NULL, + ai_run, -2, NULL, + ai_run, -2, NULL, + ai_run, -1, NULL +}; +mmove_t actor_move_run = {FRAME_run02, FRAME_run07, actor_frames_run, NULL}; + +void actor_run (edict_t *self) +{ + if ((level.time < self->pain_debounce_time) && (!self->enemy)) + { + if (self->movetarget) + actor_walk(self); + else + actor_stand(self); + return; + } + + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + { + actor_stand(self); + return; + } + + self->monsterinfo.currentmove = &actor_move_run; +} + + +mframe_t actor_frames_pain1 [] = +{ + ai_move, -5, NULL, + ai_move, 4, NULL, + ai_move, 1, NULL +}; +mmove_t actor_move_pain1 = {FRAME_pain101, FRAME_pain103, actor_frames_pain1, actor_run}; + +mframe_t actor_frames_pain2 [] = +{ + ai_move, -4, NULL, + ai_move, 4, NULL, + ai_move, 0, NULL +}; +mmove_t actor_move_pain2 = {FRAME_pain201, FRAME_pain203, actor_frames_pain2, actor_run}; + +mframe_t actor_frames_pain3 [] = +{ + ai_move, -1, NULL, + ai_move, 1, NULL, + ai_move, 0, NULL +}; +mmove_t actor_move_pain3 = {FRAME_pain301, FRAME_pain303, actor_frames_pain3, actor_run}; + +mframe_t actor_frames_flipoff [] = +{ + ai_turn, 0, NULL, + ai_turn, 0, NULL, + ai_turn, 0, NULL, + ai_turn, 0, NULL, + ai_turn, 0, NULL, + ai_turn, 0, NULL, + ai_turn, 0, NULL, + ai_turn, 0, NULL, + ai_turn, 0, NULL, + ai_turn, 0, NULL, + ai_turn, 0, NULL, + ai_turn, 0, NULL, + ai_turn, 0, NULL, + ai_turn, 0, NULL +}; +mmove_t actor_move_flipoff = {FRAME_flip01, FRAME_flip14, actor_frames_flipoff, actor_run}; + +mframe_t actor_frames_taunt [] = +{ + ai_turn, 0, NULL, + ai_turn, 0, NULL, + ai_turn, 0, NULL, + ai_turn, 0, NULL, + ai_turn, 0, NULL, + ai_turn, 0, NULL, + ai_turn, 0, NULL, + ai_turn, 0, NULL, + ai_turn, 0, NULL, + ai_turn, 0, NULL, + ai_turn, 0, NULL, + ai_turn, 0, NULL, + ai_turn, 0, NULL, + ai_turn, 0, NULL, + ai_turn, 0, NULL, + ai_turn, 0, NULL, + ai_turn, 0, NULL +}; +mmove_t actor_move_taunt = {FRAME_taunt01, FRAME_taunt17, actor_frames_taunt, actor_run}; + +char *messages[] = +{ + "Watch it", + "#$@*&", + "Idiot", + "Check your targets" +}; + +void actor_pain (edict_t *self, edict_t *other, float kick, int damage) +{ + int n; + + if (self->health < (self->max_health / 2)) + self->s.skinnum = 1; + + if (level.time < self->pain_debounce_time) + return; + + self->pain_debounce_time = level.time + 3; +// gi.sound (self, CHAN_VOICE, actor.sound_pain, 1, ATTN_NORM, 0); + + if ((other->client) && (random() < 0.4)) + { + vec3_t v; + char *name; + + VectorSubtract (other->s.origin, self->s.origin, v); + self->ideal_yaw = vectoyaw (v); + if (random() < 0.5) + self->monsterinfo.currentmove = &actor_move_flipoff; + else + self->monsterinfo.currentmove = &actor_move_taunt; + name = actor_names[(self - g_edicts)%MAX_ACTOR_NAMES]; + gi.cprintf (other, PRINT_CHAT, "%s: %s!\n", name, messages[rand()%3]); + return; + } + + n = rand() % 3; + if (n == 0) + self->monsterinfo.currentmove = &actor_move_pain1; + else if (n == 1) + self->monsterinfo.currentmove = &actor_move_pain2; + else + self->monsterinfo.currentmove = &actor_move_pain3; +} + + +void actorMachineGun (edict_t *self) +{ + vec3_t start, target; + vec3_t forward, right; + + AngleVectors (self->s.angles, forward, right, NULL); + G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_ACTOR_MACHINEGUN_1], forward, right, start); + if (self->enemy) + { + if (self->enemy->health > 0) + { + VectorMA (self->enemy->s.origin, -0.2, self->enemy->velocity, target); + target[2] += self->enemy->viewheight; + } + else + { + VectorCopy (self->enemy->absmin, target); + target[2] += (self->enemy->size[2] / 2); + } + VectorSubtract (target, start, forward); + VectorNormalize (forward); + } + else + { + AngleVectors (self->s.angles, forward, NULL, NULL); + } + monster_fire_bullet (self, start, forward, 3, 4, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, MZ2_ACTOR_MACHINEGUN_1); +} + + +void actor_dead (edict_t *self) +{ + VectorSet (self->mins, -16, -16, -24); + VectorSet (self->maxs, 16, 16, -8); + self->movetype = MOVETYPE_TOSS; + self->svflags |= SVF_DEADMONSTER; + self->nextthink = 0; + gi.linkentity (self); +} + +mframe_t actor_frames_death1 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, -13, NULL, + ai_move, 14, NULL, + ai_move, 3, NULL, + ai_move, -2, NULL, + ai_move, 1, NULL +}; +mmove_t actor_move_death1 = {FRAME_death101, FRAME_death107, actor_frames_death1, actor_dead}; + +mframe_t actor_frames_death2 [] = +{ + ai_move, 0, NULL, + ai_move, 7, NULL, + ai_move, -6, NULL, + ai_move, -5, NULL, + ai_move, 1, NULL, + ai_move, 0, NULL, + ai_move, -1, NULL, + ai_move, -2, NULL, + ai_move, -1, NULL, + ai_move, -9, NULL, + ai_move, -13, NULL, + ai_move, -13, NULL, + ai_move, 0, NULL +}; +mmove_t actor_move_death2 = {FRAME_death201, FRAME_death213, actor_frames_death2, actor_dead}; + +void actor_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + int n; + +// check for gib + if (self->health <= -80) + { +// gi.sound (self, CHAN_VOICE, actor.sound_gib, 1, ATTN_NORM, 0); + for (n= 0; n < 2; n++) + ThrowGib (self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC); + for (n= 0; n < 4; n++) + ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); + ThrowHead (self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC); + self->deadflag = DEAD_DEAD; + return; + } + + if (self->deadflag == DEAD_DEAD) + return; + +// regular death +// gi.sound (self, CHAN_VOICE, actor.sound_die, 1, ATTN_NORM, 0); + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_YES; + + n = rand() % 2; + if (n == 0) + self->monsterinfo.currentmove = &actor_move_death1; + else + self->monsterinfo.currentmove = &actor_move_death2; +} + + +void actor_fire (edict_t *self) +{ + actorMachineGun (self); + + if (level.time >= self->monsterinfo.pausetime) + self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; + else + self->monsterinfo.aiflags |= AI_HOLD_FRAME; +} + +mframe_t actor_frames_attack [] = +{ + ai_charge, -2, actor_fire, + ai_charge, -2, NULL, + ai_charge, 3, NULL, + ai_charge, 2, NULL +}; +mmove_t actor_move_attack = {FRAME_attak01, FRAME_attak04, actor_frames_attack, actor_run}; + +void actor_attack(edict_t *self) +{ + int n; + + self->monsterinfo.currentmove = &actor_move_attack; + n = (rand() & 15) + 3 + 7; + self->monsterinfo.pausetime = level.time + n * FRAMETIME; +} + + +void actor_use (edict_t *self, edict_t *other, edict_t *activator) +{ + vec3_t v; + + self->goalentity = self->movetarget = G_PickTarget(self->target); + if ((!self->movetarget) || (strcmp(self->movetarget->classname, "target_actor") != 0)) + { + gi.dprintf ("%s has bad target %s at %s\n", self->classname, self->target, vtos(self->s.origin)); + self->target = NULL; + self->monsterinfo.pausetime = 100000000; + self->monsterinfo.stand (self); + return; + } + + VectorSubtract (self->goalentity->s.origin, self->s.origin, v); + self->ideal_yaw = self->s.angles[YAW] = vectoyaw(v); + self->monsterinfo.walk (self); + self->target = NULL; +} + + +/*QUAKED misc_actor (1 .5 0) (-16 -16 -24) (16 16 32) +*/ + +void SP_misc_actor (edict_t *self) +{ + if (deathmatch->value) + { + G_FreeEdict (self); + return; + } + + if (!self->targetname) + { + gi.dprintf("untargeted %s at %s\n", self->classname, vtos(self->s.origin)); + G_FreeEdict (self); + return; + } + + if (!self->target) + { + gi.dprintf("%s with no target at %s\n", self->classname, vtos(self->s.origin)); + G_FreeEdict (self); + return; + } + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex("players/male/tris.md2"); + VectorSet (self->mins, -16, -16, -24); + VectorSet (self->maxs, 16, 16, 32); + + if (!self->health) + self->health = 100; + self->mass = 200; + + self->pain = actor_pain; + self->die = actor_die; + + self->monsterinfo.stand = actor_stand; + self->monsterinfo.walk = actor_walk; + self->monsterinfo.run = actor_run; + self->monsterinfo.attack = actor_attack; + self->monsterinfo.melee = NULL; + self->monsterinfo.sight = NULL; + + self->monsterinfo.aiflags |= AI_GOOD_GUY; + + gi.linkentity (self); + + self->monsterinfo.currentmove = &actor_move_stand; + self->monsterinfo.scale = MODEL_SCALE; + + walkmonster_start (self); + + // actors always start in a dormant state, they *must* be used to get going + self->use = actor_use; +} + + +/*QUAKED target_actor (.5 .3 0) (-8 -8 -8) (8 8 8) JUMP SHOOT ATTACK x HOLD BRUTAL +JUMP jump in set direction upon reaching this target +SHOOT take a single shot at the pathtarget +ATTACK attack pathtarget until it or actor is dead + +"target" next target_actor +"pathtarget" target of any action to be taken at this point +"wait" amount of time actor should pause at this point +"message" actor will "say" this to the player + +for JUMP only: +"speed" speed thrown forward (default 200) +"height" speed thrown upwards (default 200) +*/ + +void target_actor_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + vec3_t v; + + if (other->movetarget != self) + return; + + if (other->enemy) + return; + + other->goalentity = other->movetarget = NULL; + + if (self->message) + { + int n; + edict_t *ent; + + for (n = 1; n <= game.maxclients; n++) + { + ent = &g_edicts[n]; + if (!ent->inuse) + continue; + gi.cprintf (ent, PRINT_CHAT, "%s: %s\n", actor_names[(other - g_edicts)%MAX_ACTOR_NAMES], self->message); + } + } + + if (self->spawnflags & 1) //jump + { + other->velocity[0] = self->movedir[0] * self->speed; + other->velocity[1] = self->movedir[1] * self->speed; + + if (other->groundentity) + { + other->groundentity = NULL; + other->velocity[2] = self->movedir[2]; + gi.sound(other, CHAN_VOICE, gi.soundindex("player/male/jump1.wav"), 1, ATTN_NORM, 0); + } + } + + if (self->spawnflags & 2) //shoot + { + } + else if (self->spawnflags & 4) //attack + { + other->enemy = G_PickTarget(self->pathtarget); + if (other->enemy) + { + other->goalentity = other->enemy; + if (self->spawnflags & 32) + other->monsterinfo.aiflags |= AI_BRUTAL; + if (self->spawnflags & 16) + { + other->monsterinfo.aiflags |= AI_STAND_GROUND; + actor_stand (other); + } + else + { + actor_run (other); + } + } + } + + if (!(self->spawnflags & 6) && (self->pathtarget)) + { + char *savetarget; + + savetarget = self->target; + self->target = self->pathtarget; + G_UseTargets (self, other); + self->target = savetarget; + } + + other->movetarget = G_PickTarget(self->target); + + if (!other->goalentity) + other->goalentity = other->movetarget; + + if (!other->movetarget && !other->enemy) + { + other->monsterinfo.pausetime = level.time + 100000000; + other->monsterinfo.stand (other); + } + else if (other->movetarget == other->goalentity) + { + VectorSubtract (other->movetarget->s.origin, other->s.origin, v); + other->ideal_yaw = vectoyaw (v); + } +} + +void SP_target_actor (edict_t *self) +{ + if (!self->targetname) + gi.dprintf ("%s with no targetname at %s\n", self->classname, vtos(self->s.origin)); + + self->solid = SOLID_TRIGGER; + self->touch = target_actor_touch; + VectorSet (self->mins, -8, -8, -8); + VectorSet (self->maxs, 8, 8, 8); + self->svflags = SVF_NOCLIENT; + + if (self->spawnflags & 1) + { + if (!self->speed) + self->speed = 200; + if (!st.height) + st.height = 200; + if (self->s.angles[YAW] == 0) + self->s.angles[YAW] = 360; + G_SetMovedir (self->s.angles, self->movedir); + self->movedir[2] = st.height; + } + + gi.linkentity (self); +} diff --git a/original/xatrix/m_actor.h b/original/xatrix/m_actor.h new file mode 100644 index 0000000..41354a4 --- /dev/null +++ b/original/xatrix/m_actor.h @@ -0,0 +1,489 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// G:\quake2\baseq2\models/player_y + +// This file generated by ModelGen - Do NOT Modify + +#define FRAME_attak01 0 +#define FRAME_attak02 1 +#define FRAME_attak03 2 +#define FRAME_attak04 3 +#define FRAME_death101 4 +#define FRAME_death102 5 +#define FRAME_death103 6 +#define FRAME_death104 7 +#define FRAME_death105 8 +#define FRAME_death106 9 +#define FRAME_death107 10 +#define FRAME_death201 11 +#define FRAME_death202 12 +#define FRAME_death203 13 +#define FRAME_death204 14 +#define FRAME_death205 15 +#define FRAME_death206 16 +#define FRAME_death207 17 +#define FRAME_death208 18 +#define FRAME_death209 19 +#define FRAME_death210 20 +#define FRAME_death211 21 +#define FRAME_death212 22 +#define FRAME_death213 23 +#define FRAME_death301 24 +#define FRAME_death302 25 +#define FRAME_death303 26 +#define FRAME_death304 27 +#define FRAME_death305 28 +#define FRAME_death306 29 +#define FRAME_death307 30 +#define FRAME_death308 31 +#define FRAME_death309 32 +#define FRAME_death310 33 +#define FRAME_death311 34 +#define FRAME_death312 35 +#define FRAME_death313 36 +#define FRAME_death314 37 +#define FRAME_death315 38 +#define FRAME_flip01 39 +#define FRAME_flip02 40 +#define FRAME_flip03 41 +#define FRAME_flip04 42 +#define FRAME_flip05 43 +#define FRAME_flip06 44 +#define FRAME_flip07 45 +#define FRAME_flip08 46 +#define FRAME_flip09 47 +#define FRAME_flip10 48 +#define FRAME_flip11 49 +#define FRAME_flip12 50 +#define FRAME_flip13 51 +#define FRAME_flip14 52 +#define FRAME_grenad01 53 +#define FRAME_grenad02 54 +#define FRAME_grenad03 55 +#define FRAME_grenad04 56 +#define FRAME_grenad05 57 +#define FRAME_grenad06 58 +#define FRAME_grenad07 59 +#define FRAME_grenad08 60 +#define FRAME_grenad09 61 +#define FRAME_grenad10 62 +#define FRAME_grenad11 63 +#define FRAME_grenad12 64 +#define FRAME_grenad13 65 +#define FRAME_grenad14 66 +#define FRAME_grenad15 67 +#define FRAME_jump01 68 +#define FRAME_jump02 69 +#define FRAME_jump03 70 +#define FRAME_jump04 71 +#define FRAME_jump05 72 +#define FRAME_jump06 73 +#define FRAME_pain101 74 +#define FRAME_pain102 75 +#define FRAME_pain103 76 +#define FRAME_pain201 77 +#define FRAME_pain202 78 +#define FRAME_pain203 79 +#define FRAME_pain301 80 +#define FRAME_pain302 81 +#define FRAME_pain303 82 +#define FRAME_push01 83 +#define FRAME_push02 84 +#define FRAME_push03 85 +#define FRAME_push04 86 +#define FRAME_push05 87 +#define FRAME_push06 88 +#define FRAME_push07 89 +#define FRAME_push08 90 +#define FRAME_push09 91 +#define FRAME_run01 92 +#define FRAME_run02 93 +#define FRAME_run03 94 +#define FRAME_run04 95 +#define FRAME_run05 96 +#define FRAME_run06 97 +#define FRAME_run07 98 +#define FRAME_run08 99 +#define FRAME_run09 100 +#define FRAME_run10 101 +#define FRAME_run11 102 +#define FRAME_run12 103 +#define FRAME_runs01 104 +#define FRAME_runs02 105 +#define FRAME_runs03 106 +#define FRAME_runs04 107 +#define FRAME_runs05 108 +#define FRAME_runs06 109 +#define FRAME_runs07 110 +#define FRAME_runs08 111 +#define FRAME_runs09 112 +#define FRAME_runs10 113 +#define FRAME_runs11 114 +#define FRAME_runs12 115 +#define FRAME_salute01 116 +#define FRAME_salute02 117 +#define FRAME_salute03 118 +#define FRAME_salute04 119 +#define FRAME_salute05 120 +#define FRAME_salute06 121 +#define FRAME_salute07 122 +#define FRAME_salute08 123 +#define FRAME_salute09 124 +#define FRAME_salute10 125 +#define FRAME_salute11 126 +#define FRAME_salute12 127 +#define FRAME_stand101 128 +#define FRAME_stand102 129 +#define FRAME_stand103 130 +#define FRAME_stand104 131 +#define FRAME_stand105 132 +#define FRAME_stand106 133 +#define FRAME_stand107 134 +#define FRAME_stand108 135 +#define FRAME_stand109 136 +#define FRAME_stand110 137 +#define FRAME_stand111 138 +#define FRAME_stand112 139 +#define FRAME_stand113 140 +#define FRAME_stand114 141 +#define FRAME_stand115 142 +#define FRAME_stand116 143 +#define FRAME_stand117 144 +#define FRAME_stand118 145 +#define FRAME_stand119 146 +#define FRAME_stand120 147 +#define FRAME_stand121 148 +#define FRAME_stand122 149 +#define FRAME_stand123 150 +#define FRAME_stand124 151 +#define FRAME_stand125 152 +#define FRAME_stand126 153 +#define FRAME_stand127 154 +#define FRAME_stand128 155 +#define FRAME_stand129 156 +#define FRAME_stand130 157 +#define FRAME_stand131 158 +#define FRAME_stand132 159 +#define FRAME_stand133 160 +#define FRAME_stand134 161 +#define FRAME_stand135 162 +#define FRAME_stand136 163 +#define FRAME_stand137 164 +#define FRAME_stand138 165 +#define FRAME_stand139 166 +#define FRAME_stand140 167 +#define FRAME_stand201 168 +#define FRAME_stand202 169 +#define FRAME_stand203 170 +#define FRAME_stand204 171 +#define FRAME_stand205 172 +#define FRAME_stand206 173 +#define FRAME_stand207 174 +#define FRAME_stand208 175 +#define FRAME_stand209 176 +#define FRAME_stand210 177 +#define FRAME_stand211 178 +#define FRAME_stand212 179 +#define FRAME_stand213 180 +#define FRAME_stand214 181 +#define FRAME_stand215 182 +#define FRAME_stand216 183 +#define FRAME_stand217 184 +#define FRAME_stand218 185 +#define FRAME_stand219 186 +#define FRAME_stand220 187 +#define FRAME_stand221 188 +#define FRAME_stand222 189 +#define FRAME_stand223 190 +#define FRAME_swim01 191 +#define FRAME_swim02 192 +#define FRAME_swim03 193 +#define FRAME_swim04 194 +#define FRAME_swim05 195 +#define FRAME_swim06 196 +#define FRAME_swim07 197 +#define FRAME_swim08 198 +#define FRAME_swim09 199 +#define FRAME_swim10 200 +#define FRAME_swim11 201 +#define FRAME_swim12 202 +#define FRAME_sw_atk01 203 +#define FRAME_sw_atk02 204 +#define FRAME_sw_atk03 205 +#define FRAME_sw_atk04 206 +#define FRAME_sw_atk05 207 +#define FRAME_sw_atk06 208 +#define FRAME_sw_pan01 209 +#define FRAME_sw_pan02 210 +#define FRAME_sw_pan03 211 +#define FRAME_sw_pan04 212 +#define FRAME_sw_pan05 213 +#define FRAME_sw_std01 214 +#define FRAME_sw_std02 215 +#define FRAME_sw_std03 216 +#define FRAME_sw_std04 217 +#define FRAME_sw_std05 218 +#define FRAME_sw_std06 219 +#define FRAME_sw_std07 220 +#define FRAME_sw_std08 221 +#define FRAME_sw_std09 222 +#define FRAME_sw_std10 223 +#define FRAME_sw_std11 224 +#define FRAME_sw_std12 225 +#define FRAME_sw_std13 226 +#define FRAME_sw_std14 227 +#define FRAME_sw_std15 228 +#define FRAME_sw_std16 229 +#define FRAME_sw_std17 230 +#define FRAME_sw_std18 231 +#define FRAME_sw_std19 232 +#define FRAME_sw_std20 233 +#define FRAME_taunt01 234 +#define FRAME_taunt02 235 +#define FRAME_taunt03 236 +#define FRAME_taunt04 237 +#define FRAME_taunt05 238 +#define FRAME_taunt06 239 +#define FRAME_taunt07 240 +#define FRAME_taunt08 241 +#define FRAME_taunt09 242 +#define FRAME_taunt10 243 +#define FRAME_taunt11 244 +#define FRAME_taunt12 245 +#define FRAME_taunt13 246 +#define FRAME_taunt14 247 +#define FRAME_taunt15 248 +#define FRAME_taunt16 249 +#define FRAME_taunt17 250 +#define FRAME_walk01 251 +#define FRAME_walk02 252 +#define FRAME_walk03 253 +#define FRAME_walk04 254 +#define FRAME_walk05 255 +#define FRAME_walk06 256 +#define FRAME_walk07 257 +#define FRAME_walk08 258 +#define FRAME_walk09 259 +#define FRAME_walk10 260 +#define FRAME_walk11 261 +#define FRAME_wave01 262 +#define FRAME_wave02 263 +#define FRAME_wave03 264 +#define FRAME_wave04 265 +#define FRAME_wave05 266 +#define FRAME_wave06 267 +#define FRAME_wave07 268 +#define FRAME_wave08 269 +#define FRAME_wave09 270 +#define FRAME_wave10 271 +#define FRAME_wave11 272 +#define FRAME_wave12 273 +#define FRAME_wave13 274 +#define FRAME_wave14 275 +#define FRAME_wave15 276 +#define FRAME_wave16 277 +#define FRAME_wave17 278 +#define FRAME_wave18 279 +#define FRAME_wave19 280 +#define FRAME_wave20 281 +#define FRAME_wave21 282 +#define FRAME_bl_atk01 283 +#define FRAME_bl_atk02 284 +#define FRAME_bl_atk03 285 +#define FRAME_bl_atk04 286 +#define FRAME_bl_atk05 287 +#define FRAME_bl_atk06 288 +#define FRAME_bl_flp01 289 +#define FRAME_bl_flp02 290 +#define FRAME_bl_flp13 291 +#define FRAME_bl_flp14 292 +#define FRAME_bl_flp15 293 +#define FRAME_bl_jmp01 294 +#define FRAME_bl_jmp02 295 +#define FRAME_bl_jmp03 296 +#define FRAME_bl_jmp04 297 +#define FRAME_bl_jmp05 298 +#define FRAME_bl_jmp06 299 +#define FRAME_bl_pn101 300 +#define FRAME_bl_pn102 301 +#define FRAME_bl_pn103 302 +#define FRAME_bl_pn201 303 +#define FRAME_bl_pn202 304 +#define FRAME_bl_pn203 305 +#define FRAME_bl_pn301 306 +#define FRAME_bl_pn302 307 +#define FRAME_bl_pn303 308 +#define FRAME_bl_psh08 309 +#define FRAME_bl_psh09 310 +#define FRAME_bl_run01 311 +#define FRAME_bl_run02 312 +#define FRAME_bl_run03 313 +#define FRAME_bl_run04 314 +#define FRAME_bl_run05 315 +#define FRAME_bl_run06 316 +#define FRAME_bl_run07 317 +#define FRAME_bl_run08 318 +#define FRAME_bl_run09 319 +#define FRAME_bl_run10 320 +#define FRAME_bl_run11 321 +#define FRAME_bl_run12 322 +#define FRAME_bl_rns03 323 +#define FRAME_bl_rns04 324 +#define FRAME_bl_rns05 325 +#define FRAME_bl_rns06 326 +#define FRAME_bl_rns07 327 +#define FRAME_bl_rns08 328 +#define FRAME_bl_rns09 329 +#define FRAME_bl_sal10 330 +#define FRAME_bl_sal11 331 +#define FRAME_bl_sal12 332 +#define FRAME_bl_std01 333 +#define FRAME_bl_std02 334 +#define FRAME_bl_std03 335 +#define FRAME_bl_std04 336 +#define FRAME_bl_std05 337 +#define FRAME_bl_std06 338 +#define FRAME_bl_std07 339 +#define FRAME_bl_std08 340 +#define FRAME_bl_std09 341 +#define FRAME_bl_std10 342 +#define FRAME_bl_std11 343 +#define FRAME_bl_std12 344 +#define FRAME_bl_std13 345 +#define FRAME_bl_std14 346 +#define FRAME_bl_std15 347 +#define FRAME_bl_std16 348 +#define FRAME_bl_std17 349 +#define FRAME_bl_std18 350 +#define FRAME_bl_std19 351 +#define FRAME_bl_std20 352 +#define FRAME_bl_std21 353 +#define FRAME_bl_std22 354 +#define FRAME_bl_std23 355 +#define FRAME_bl_std24 356 +#define FRAME_bl_std25 357 +#define FRAME_bl_std26 358 +#define FRAME_bl_std27 359 +#define FRAME_bl_std28 360 +#define FRAME_bl_std29 361 +#define FRAME_bl_std30 362 +#define FRAME_bl_std31 363 +#define FRAME_bl_std32 364 +#define FRAME_bl_std33 365 +#define FRAME_bl_std34 366 +#define FRAME_bl_std35 367 +#define FRAME_bl_std36 368 +#define FRAME_bl_std37 369 +#define FRAME_bl_std38 370 +#define FRAME_bl_std39 371 +#define FRAME_bl_std40 372 +#define FRAME_bl_swm01 373 +#define FRAME_bl_swm02 374 +#define FRAME_bl_swm03 375 +#define FRAME_bl_swm04 376 +#define FRAME_bl_swm05 377 +#define FRAME_bl_swm06 378 +#define FRAME_bl_swm07 379 +#define FRAME_bl_swm08 380 +#define FRAME_bl_swm09 381 +#define FRAME_bl_swm10 382 +#define FRAME_bl_swm11 383 +#define FRAME_bl_swm12 384 +#define FRAME_bl_swk01 385 +#define FRAME_bl_swk02 386 +#define FRAME_bl_swk03 387 +#define FRAME_bl_swk04 388 +#define FRAME_bl_swk05 389 +#define FRAME_bl_swk06 390 +#define FRAME_bl_swp01 391 +#define FRAME_bl_swp02 392 +#define FRAME_bl_swp03 393 +#define FRAME_bl_swp04 394 +#define FRAME_bl_swp05 395 +#define FRAME_bl_sws01 396 +#define FRAME_bl_sws02 397 +#define FRAME_bl_sws03 398 +#define FRAME_bl_sws04 399 +#define FRAME_bl_sws05 400 +#define FRAME_bl_sws06 401 +#define FRAME_bl_sws07 402 +#define FRAME_bl_sws08 403 +#define FRAME_bl_sws09 404 +#define FRAME_bl_sws10 405 +#define FRAME_bl_sws11 406 +#define FRAME_bl_sws12 407 +#define FRAME_bl_sws13 408 +#define FRAME_bl_sws14 409 +#define FRAME_bl_tau14 410 +#define FRAME_bl_tau15 411 +#define FRAME_bl_tau16 412 +#define FRAME_bl_tau17 413 +#define FRAME_bl_wlk01 414 +#define FRAME_bl_wlk02 415 +#define FRAME_bl_wlk03 416 +#define FRAME_bl_wlk04 417 +#define FRAME_bl_wlk05 418 +#define FRAME_bl_wlk06 419 +#define FRAME_bl_wlk07 420 +#define FRAME_bl_wlk08 421 +#define FRAME_bl_wlk09 422 +#define FRAME_bl_wlk10 423 +#define FRAME_bl_wlk11 424 +#define FRAME_bl_wav19 425 +#define FRAME_bl_wav20 426 +#define FRAME_bl_wav21 427 +#define FRAME_cr_atk01 428 +#define FRAME_cr_atk02 429 +#define FRAME_cr_atk03 430 +#define FRAME_cr_atk04 431 +#define FRAME_cr_atk05 432 +#define FRAME_cr_atk06 433 +#define FRAME_cr_atk07 434 +#define FRAME_cr_atk08 435 +#define FRAME_cr_pan01 436 +#define FRAME_cr_pan02 437 +#define FRAME_cr_pan03 438 +#define FRAME_cr_pan04 439 +#define FRAME_cr_std01 440 +#define FRAME_cr_std02 441 +#define FRAME_cr_std03 442 +#define FRAME_cr_std04 443 +#define FRAME_cr_std05 444 +#define FRAME_cr_std06 445 +#define FRAME_cr_std07 446 +#define FRAME_cr_std08 447 +#define FRAME_cr_wlk01 448 +#define FRAME_cr_wlk02 449 +#define FRAME_cr_wlk03 450 +#define FRAME_cr_wlk04 451 +#define FRAME_cr_wlk05 452 +#define FRAME_cr_wlk06 453 +#define FRAME_cr_wlk07 454 +#define FRAME_crbl_a01 455 +#define FRAME_crbl_a02 456 +#define FRAME_crbl_a03 457 +#define FRAME_crbl_a04 458 +#define FRAME_crbl_a05 459 +#define FRAME_crbl_a06 460 +#define FRAME_crbl_a07 461 +#define FRAME_crbl_p01 462 +#define FRAME_crbl_p02 463 +#define FRAME_crbl_p03 464 +#define FRAME_crbl_p04 465 +#define FRAME_crbl_s01 466 +#define FRAME_crbl_s02 467 +#define FRAME_crbl_s03 468 +#define FRAME_crbl_s04 469 +#define FRAME_crbl_s05 470 +#define FRAME_crbl_s06 471 +#define FRAME_crbl_s07 472 +#define FRAME_crbl_s08 473 +#define FRAME_crbl_w01 474 +#define FRAME_crbl_w02 475 +#define FRAME_crbl_w03 476 +#define FRAME_crbl_w04 477 +#define FRAME_crbl_w05 478 +#define FRAME_crbl_w06 479 +#define FRAME_crbl_w07 480 + +#define MODEL_SCALE 1.000000 diff --git a/original/xatrix/m_berserk.c b/original/xatrix/m_berserk.c new file mode 100644 index 0000000..0ea43a4 --- /dev/null +++ b/original/xatrix/m_berserk.c @@ -0,0 +1,440 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +/* +============================================================================== + +BERSERK + +============================================================================== +*/ + +#include "g_local.h" +#include "m_berserk.h" + + +static int sound_pain; +static int sound_die; +static int sound_idle; +static int sound_punch; +static int sound_sight; +static int sound_search; + +void berserk_sight (edict_t *self, edict_t *other) +{ + gi.sound (self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); +} + +void berserk_search (edict_t *self) +{ + gi.sound (self, CHAN_VOICE, sound_search, 1, ATTN_NORM, 0); +} + + +void berserk_fidget (edict_t *self); +mframe_t berserk_frames_stand [] = +{ + ai_stand, 0, berserk_fidget, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL +}; +mmove_t berserk_move_stand = {FRAME_stand1, FRAME_stand5, berserk_frames_stand, NULL}; + +void berserk_stand (edict_t *self) +{ + self->monsterinfo.currentmove = &berserk_move_stand; +} + +mframe_t berserk_frames_stand_fidget [] = +{ + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL +}; +mmove_t berserk_move_stand_fidget = {FRAME_standb1, FRAME_standb20, berserk_frames_stand_fidget, berserk_stand}; + +void berserk_fidget (edict_t *self) +{ + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + return; + if (random() > 0.15) + return; + + self->monsterinfo.currentmove = &berserk_move_stand_fidget; + gi.sound (self, CHAN_WEAPON, sound_idle, 1, ATTN_IDLE, 0); +} + + +mframe_t berserk_frames_walk [] = +{ + ai_walk, 9.1, NULL, + ai_walk, 6.3, NULL, + ai_walk, 4.9, NULL, + ai_walk, 6.7, NULL, + ai_walk, 6.0, NULL, + ai_walk, 8.2, NULL, + ai_walk, 7.2, NULL, + ai_walk, 6.1, NULL, + ai_walk, 4.9, NULL, + ai_walk, 4.7, NULL, + ai_walk, 4.7, NULL, + ai_walk, 4.8, NULL +}; +mmove_t berserk_move_walk = {FRAME_walkc1, FRAME_walkc11, berserk_frames_walk, NULL}; + +void berserk_walk (edict_t *self) +{ + self->monsterinfo.currentmove = &berserk_move_walk; +} + +/* + + ***************************** + SKIPPED THIS FOR NOW! + ***************************** + + Running -> Arm raised in air + +void() berserk_runb1 =[ $r_att1 , berserk_runb2 ] {ai_run(21);}; +void() berserk_runb2 =[ $r_att2 , berserk_runb3 ] {ai_run(11);}; +void() berserk_runb3 =[ $r_att3 , berserk_runb4 ] {ai_run(21);}; +void() berserk_runb4 =[ $r_att4 , berserk_runb5 ] {ai_run(25);}; +void() berserk_runb5 =[ $r_att5 , berserk_runb6 ] {ai_run(18);}; +void() berserk_runb6 =[ $r_att6 , berserk_runb7 ] {ai_run(19);}; +// running with arm in air : start loop +void() berserk_runb7 =[ $r_att7 , berserk_runb8 ] {ai_run(21);}; +void() berserk_runb8 =[ $r_att8 , berserk_runb9 ] {ai_run(11);}; +void() berserk_runb9 =[ $r_att9 , berserk_runb10 ] {ai_run(21);}; +void() berserk_runb10 =[ $r_att10 , berserk_runb11 ] {ai_run(25);}; +void() berserk_runb11 =[ $r_att11 , berserk_runb12 ] {ai_run(18);}; +void() berserk_runb12 =[ $r_att12 , berserk_runb7 ] {ai_run(19);}; +// running with arm in air : end loop +*/ + + +mframe_t berserk_frames_run1 [] = +{ + ai_run, 21, NULL, + ai_run, 11, NULL, + ai_run, 21, NULL, + ai_run, 25, NULL, + ai_run, 18, NULL, + ai_run, 19, NULL +}; +mmove_t berserk_move_run1 = {FRAME_run1, FRAME_run6, berserk_frames_run1, NULL}; + +void berserk_run (edict_t *self) +{ + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + self->monsterinfo.currentmove = &berserk_move_stand; + else + self->monsterinfo.currentmove = &berserk_move_run1; +} + + +void berserk_attack_spike (edict_t *self) +{ + static vec3_t aim = {MELEE_DISTANCE, 0, -24}; + fire_hit (self, aim, (15 + (rand() % 6)), 400); // Faster attack -- upwards and backwards +} + + +void berserk_swing (edict_t *self) +{ + gi.sound (self, CHAN_WEAPON, sound_punch, 1, ATTN_NORM, 0); +} + +mframe_t berserk_frames_attack_spike [] = +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, berserk_swing, + ai_charge, 0, berserk_attack_spike, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL +}; +mmove_t berserk_move_attack_spike = {FRAME_att_c1, FRAME_att_c8, berserk_frames_attack_spike, berserk_run}; + + +void berserk_attack_club (edict_t *self) +{ + vec3_t aim; + + VectorSet (aim, MELEE_DISTANCE, self->mins[0], -4); + fire_hit (self, aim, (5 + (rand() % 6)), 400); // Slower attack +} + +mframe_t berserk_frames_attack_club [] = +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, berserk_swing, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, berserk_attack_club, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL +}; +mmove_t berserk_move_attack_club = {FRAME_att_c9, FRAME_att_c20, berserk_frames_attack_club, berserk_run}; + + +void berserk_strike (edict_t *self) +{ + //FIXME play impact sound +} + + +mframe_t berserk_frames_attack_strike [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, berserk_swing, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, berserk_strike, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 9.7, NULL, + ai_move, 13.6, NULL +}; + +mmove_t berserk_move_attack_strike = {FRAME_att_c21, FRAME_att_c34, berserk_frames_attack_strike, berserk_run}; + + +void berserk_melee (edict_t *self) +{ + if ((rand() % 2) == 0) + self->monsterinfo.currentmove = &berserk_move_attack_spike; + else + self->monsterinfo.currentmove = &berserk_move_attack_club; +} + + +/* +void() berserk_atke1 =[ $r_attb1, berserk_atke2 ] {ai_run(9);}; +void() berserk_atke2 =[ $r_attb2, berserk_atke3 ] {ai_run(6);}; +void() berserk_atke3 =[ $r_attb3, berserk_atke4 ] {ai_run(18.4);}; +void() berserk_atke4 =[ $r_attb4, berserk_atke5 ] {ai_run(25);}; +void() berserk_atke5 =[ $r_attb5, berserk_atke6 ] {ai_run(14);}; +void() berserk_atke6 =[ $r_attb6, berserk_atke7 ] {ai_run(20);}; +void() berserk_atke7 =[ $r_attb7, berserk_atke8 ] {ai_run(8.5);}; +void() berserk_atke8 =[ $r_attb8, berserk_atke9 ] {ai_run(3);}; +void() berserk_atke9 =[ $r_attb9, berserk_atke10 ] {ai_run(17.5);}; +void() berserk_atke10 =[ $r_attb10, berserk_atke11 ] {ai_run(17);}; +void() berserk_atke11 =[ $r_attb11, berserk_atke12 ] {ai_run(9);}; +void() berserk_atke12 =[ $r_attb12, berserk_atke13 ] {ai_run(25);}; +void() berserk_atke13 =[ $r_attb13, berserk_atke14 ] {ai_run(3.7);}; +void() berserk_atke14 =[ $r_attb14, berserk_atke15 ] {ai_run(2.6);}; +void() berserk_atke15 =[ $r_attb15, berserk_atke16 ] {ai_run(19);}; +void() berserk_atke16 =[ $r_attb16, berserk_atke17 ] {ai_run(25);}; +void() berserk_atke17 =[ $r_attb17, berserk_atke18 ] {ai_run(19.6);}; +void() berserk_atke18 =[ $r_attb18, berserk_run1 ] {ai_run(7.8);}; +*/ + + +mframe_t berserk_frames_pain1 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t berserk_move_pain1 = {FRAME_painc1, FRAME_painc4, berserk_frames_pain1, berserk_run}; + + +mframe_t berserk_frames_pain2 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t berserk_move_pain2 = {FRAME_painb1, FRAME_painb20, berserk_frames_pain2, berserk_run}; + +void berserk_pain (edict_t *self, edict_t *other, float kick, int damage) +{ + if (self->health < (self->max_health / 2)) + self->s.skinnum = 1; + + if (level.time < self->pain_debounce_time) + return; + + self->pain_debounce_time = level.time + 3; + gi.sound (self, CHAN_VOICE, sound_pain, 1, ATTN_NORM, 0); + + if (skill->value == 3) + return; // no pain anims in nightmare + + if ((damage < 20) || (random() < 0.5)) + self->monsterinfo.currentmove = &berserk_move_pain1; + else + self->monsterinfo.currentmove = &berserk_move_pain2; +} + + +void berserk_dead (edict_t *self) +{ + VectorSet (self->mins, -16, -16, -24); + VectorSet (self->maxs, 16, 16, -8); + self->movetype = MOVETYPE_TOSS; + self->svflags |= SVF_DEADMONSTER; + self->nextthink = 0; + gi.linkentity (self); +} + + +mframe_t berserk_frames_death1 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL + +}; +mmove_t berserk_move_death1 = {FRAME_death1, FRAME_death13, berserk_frames_death1, berserk_dead}; + + +mframe_t berserk_frames_death2 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t berserk_move_death2 = {FRAME_deathc1, FRAME_deathc8, berserk_frames_death2, berserk_dead}; + + +void berserk_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + int n; + + if (self->health <= self->gib_health) + { + gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0); + for (n= 0; n < 2; n++) + ThrowGib (self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC); + for (n= 0; n < 4; n++) + ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); + ThrowHead (self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC); + self->deadflag = DEAD_DEAD; + return; + } + + if (self->deadflag == DEAD_DEAD) + return; + + gi.sound (self, CHAN_VOICE, sound_die, 1, ATTN_NORM, 0); + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_YES; + + if (damage >= 50) + self->monsterinfo.currentmove = &berserk_move_death1; + else + self->monsterinfo.currentmove = &berserk_move_death2; +} + + +/*QUAKED monster_berserk (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight +*/ +void SP_monster_berserk (edict_t *self) +{ + if (deathmatch->value) + { + G_FreeEdict (self); + return; + } + + // pre-caches + sound_pain = gi.soundindex ("berserk/berpain2.wav"); + sound_die = gi.soundindex ("berserk/berdeth2.wav"); + sound_idle = gi.soundindex ("berserk/beridle1.wav"); + sound_punch = gi.soundindex ("berserk/attack.wav"); + sound_search = gi.soundindex ("berserk/bersrch1.wav"); + sound_sight = gi.soundindex ("berserk/sight.wav"); + + self->s.modelindex = gi.modelindex("models/monsters/berserk/tris.md2"); + VectorSet (self->mins, -16, -16, -24); + VectorSet (self->maxs, 16, 16, 32); + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + + self->health = 240; + self->gib_health = -60; + self->mass = 250; + + self->pain = berserk_pain; + self->die = berserk_die; + + self->monsterinfo.stand = berserk_stand; + self->monsterinfo.walk = berserk_walk; + self->monsterinfo.run = berserk_run; + self->monsterinfo.dodge = NULL; + self->monsterinfo.attack = NULL; + self->monsterinfo.melee = berserk_melee; + self->monsterinfo.sight = berserk_sight; + self->monsterinfo.search = berserk_search; + + self->monsterinfo.currentmove = &berserk_move_stand; + self->monsterinfo.scale = MODEL_SCALE; + + gi.linkentity (self); + + walkmonster_start (self); +} diff --git a/original/xatrix/m_berserk.h b/original/xatrix/m_berserk.h new file mode 100644 index 0000000..0f51a5e --- /dev/null +++ b/original/xatrix/m_berserk.h @@ -0,0 +1,252 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// G:\quake2\baseq2\models/monsters/berserk + +// This file generated by ModelGen - Do NOT Modify + +#define FRAME_stand1 0 +#define FRAME_stand2 1 +#define FRAME_stand3 2 +#define FRAME_stand4 3 +#define FRAME_stand5 4 +#define FRAME_standb1 5 +#define FRAME_standb2 6 +#define FRAME_standb3 7 +#define FRAME_standb4 8 +#define FRAME_standb5 9 +#define FRAME_standb6 10 +#define FRAME_standb7 11 +#define FRAME_standb8 12 +#define FRAME_standb9 13 +#define FRAME_standb10 14 +#define FRAME_standb11 15 +#define FRAME_standb12 16 +#define FRAME_standb13 17 +#define FRAME_standb14 18 +#define FRAME_standb15 19 +#define FRAME_standb16 20 +#define FRAME_standb17 21 +#define FRAME_standb18 22 +#define FRAME_standb19 23 +#define FRAME_standb20 24 +#define FRAME_walkc1 25 +#define FRAME_walkc2 26 +#define FRAME_walkc3 27 +#define FRAME_walkc4 28 +#define FRAME_walkc5 29 +#define FRAME_walkc6 30 +#define FRAME_walkc7 31 +#define FRAME_walkc8 32 +#define FRAME_walkc9 33 +#define FRAME_walkc10 34 +#define FRAME_walkc11 35 +#define FRAME_run1 36 +#define FRAME_run2 37 +#define FRAME_run3 38 +#define FRAME_run4 39 +#define FRAME_run5 40 +#define FRAME_run6 41 +#define FRAME_att_a1 42 +#define FRAME_att_a2 43 +#define FRAME_att_a3 44 +#define FRAME_att_a4 45 +#define FRAME_att_a5 46 +#define FRAME_att_a6 47 +#define FRAME_att_a7 48 +#define FRAME_att_a8 49 +#define FRAME_att_a9 50 +#define FRAME_att_a10 51 +#define FRAME_att_a11 52 +#define FRAME_att_a12 53 +#define FRAME_att_a13 54 +#define FRAME_att_b1 55 +#define FRAME_att_b2 56 +#define FRAME_att_b3 57 +#define FRAME_att_b4 58 +#define FRAME_att_b5 59 +#define FRAME_att_b6 60 +#define FRAME_att_b7 61 +#define FRAME_att_b8 62 +#define FRAME_att_b9 63 +#define FRAME_att_b10 64 +#define FRAME_att_b11 65 +#define FRAME_att_b12 66 +#define FRAME_att_b13 67 +#define FRAME_att_b14 68 +#define FRAME_att_b15 69 +#define FRAME_att_b16 70 +#define FRAME_att_b17 71 +#define FRAME_att_b18 72 +#define FRAME_att_b19 73 +#define FRAME_att_b20 74 +#define FRAME_att_b21 75 +#define FRAME_att_c1 76 +#define FRAME_att_c2 77 +#define FRAME_att_c3 78 +#define FRAME_att_c4 79 +#define FRAME_att_c5 80 +#define FRAME_att_c6 81 +#define FRAME_att_c7 82 +#define FRAME_att_c8 83 +#define FRAME_att_c9 84 +#define FRAME_att_c10 85 +#define FRAME_att_c11 86 +#define FRAME_att_c12 87 +#define FRAME_att_c13 88 +#define FRAME_att_c14 89 +#define FRAME_att_c15 90 +#define FRAME_att_c16 91 +#define FRAME_att_c17 92 +#define FRAME_att_c18 93 +#define FRAME_att_c19 94 +#define FRAME_att_c20 95 +#define FRAME_att_c21 96 +#define FRAME_att_c22 97 +#define FRAME_att_c23 98 +#define FRAME_att_c24 99 +#define FRAME_att_c25 100 +#define FRAME_att_c26 101 +#define FRAME_att_c27 102 +#define FRAME_att_c28 103 +#define FRAME_att_c29 104 +#define FRAME_att_c30 105 +#define FRAME_att_c31 106 +#define FRAME_att_c32 107 +#define FRAME_att_c33 108 +#define FRAME_att_c34 109 +#define FRAME_r_att1 110 +#define FRAME_r_att2 111 +#define FRAME_r_att3 112 +#define FRAME_r_att4 113 +#define FRAME_r_att5 114 +#define FRAME_r_att6 115 +#define FRAME_r_att7 116 +#define FRAME_r_att8 117 +#define FRAME_r_att9 118 +#define FRAME_r_att10 119 +#define FRAME_r_att11 120 +#define FRAME_r_att12 121 +#define FRAME_r_att13 122 +#define FRAME_r_att14 123 +#define FRAME_r_att15 124 +#define FRAME_r_att16 125 +#define FRAME_r_att17 126 +#define FRAME_r_att18 127 +#define FRAME_r_attb1 128 +#define FRAME_r_attb2 129 +#define FRAME_r_attb3 130 +#define FRAME_r_attb4 131 +#define FRAME_r_attb5 132 +#define FRAME_r_attb6 133 +#define FRAME_r_attb7 134 +#define FRAME_r_attb8 135 +#define FRAME_r_attb9 136 +#define FRAME_r_attb10 137 +#define FRAME_r_attb11 138 +#define FRAME_r_attb12 139 +#define FRAME_r_attb13 140 +#define FRAME_r_attb14 141 +#define FRAME_r_attb15 142 +#define FRAME_r_attb16 143 +#define FRAME_r_attb17 144 +#define FRAME_r_attb18 145 +#define FRAME_slam1 146 +#define FRAME_slam2 147 +#define FRAME_slam3 148 +#define FRAME_slam4 149 +#define FRAME_slam5 150 +#define FRAME_slam6 151 +#define FRAME_slam7 152 +#define FRAME_slam8 153 +#define FRAME_slam9 154 +#define FRAME_slam10 155 +#define FRAME_slam11 156 +#define FRAME_slam12 157 +#define FRAME_slam13 158 +#define FRAME_slam14 159 +#define FRAME_slam15 160 +#define FRAME_slam16 161 +#define FRAME_slam17 162 +#define FRAME_slam18 163 +#define FRAME_slam19 164 +#define FRAME_slam20 165 +#define FRAME_slam21 166 +#define FRAME_slam22 167 +#define FRAME_slam23 168 +#define FRAME_duck1 169 +#define FRAME_duck2 170 +#define FRAME_duck3 171 +#define FRAME_duck4 172 +#define FRAME_duck5 173 +#define FRAME_duck6 174 +#define FRAME_duck7 175 +#define FRAME_duck8 176 +#define FRAME_duck9 177 +#define FRAME_duck10 178 +#define FRAME_fall1 179 +#define FRAME_fall2 180 +#define FRAME_fall3 181 +#define FRAME_fall4 182 +#define FRAME_fall5 183 +#define FRAME_fall6 184 +#define FRAME_fall7 185 +#define FRAME_fall8 186 +#define FRAME_fall9 187 +#define FRAME_fall10 188 +#define FRAME_fall11 189 +#define FRAME_fall12 190 +#define FRAME_fall13 191 +#define FRAME_fall14 192 +#define FRAME_fall15 193 +#define FRAME_fall16 194 +#define FRAME_fall17 195 +#define FRAME_fall18 196 +#define FRAME_fall19 197 +#define FRAME_fall20 198 +#define FRAME_painc1 199 +#define FRAME_painc2 200 +#define FRAME_painc3 201 +#define FRAME_painc4 202 +#define FRAME_painb1 203 +#define FRAME_painb2 204 +#define FRAME_painb3 205 +#define FRAME_painb4 206 +#define FRAME_painb5 207 +#define FRAME_painb6 208 +#define FRAME_painb7 209 +#define FRAME_painb8 210 +#define FRAME_painb9 211 +#define FRAME_painb10 212 +#define FRAME_painb11 213 +#define FRAME_painb12 214 +#define FRAME_painb13 215 +#define FRAME_painb14 216 +#define FRAME_painb15 217 +#define FRAME_painb16 218 +#define FRAME_painb17 219 +#define FRAME_painb18 220 +#define FRAME_painb19 221 +#define FRAME_painb20 222 +#define FRAME_death1 223 +#define FRAME_death2 224 +#define FRAME_death3 225 +#define FRAME_death4 226 +#define FRAME_death5 227 +#define FRAME_death6 228 +#define FRAME_death7 229 +#define FRAME_death8 230 +#define FRAME_death9 231 +#define FRAME_death10 232 +#define FRAME_death11 233 +#define FRAME_death12 234 +#define FRAME_death13 235 +#define FRAME_deathc1 236 +#define FRAME_deathc2 237 +#define FRAME_deathc3 238 +#define FRAME_deathc4 239 +#define FRAME_deathc5 240 +#define FRAME_deathc6 241 +#define FRAME_deathc7 242 +#define FRAME_deathc8 243 + +#define MODEL_SCALE 1.000000 diff --git a/original/xatrix/m_boss2.c b/original/xatrix/m_boss2.c new file mode 100644 index 0000000..4a1ebc9 --- /dev/null +++ b/original/xatrix/m_boss2.c @@ -0,0 +1,662 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +/* +============================================================================== + +boss2 + +============================================================================== +*/ + +#include "g_local.h" +#include "m_boss2.h" + +void BossExplode (edict_t *self); + +qboolean infront (edict_t *self, edict_t *other); + +static int sound_pain1; +static int sound_pain2; +static int sound_pain3; +static int sound_death; +static int sound_search1; + +void boss2_search (edict_t *self) +{ + if (random() < 0.5) + gi.sound (self, CHAN_VOICE, sound_search1, 1, ATTN_NONE, 0); +} + +void boss2_run (edict_t *self); +void boss2_stand (edict_t *self); +void boss2_dead (edict_t *self); +void boss2_attack (edict_t *self); +void boss2_attack_mg (edict_t *self); +void boss2_reattack_mg (edict_t *self); +void boss2_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point); + +void Boss2Rocket (edict_t *self) +{ + vec3_t forward, right; + vec3_t start; + vec3_t dir; + vec3_t vec; + + AngleVectors (self->s.angles, forward, right, NULL); + +//1 + G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_BOSS2_ROCKET_1], forward, right, start); + VectorCopy (self->enemy->s.origin, vec); + vec[2] += self->enemy->viewheight; + VectorSubtract (vec, start, dir); + VectorNormalize (dir); + monster_fire_rocket (self, start, dir, 50, 500, MZ2_BOSS2_ROCKET_1); + +//2 + G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_BOSS2_ROCKET_2], forward, right, start); + VectorCopy (self->enemy->s.origin, vec); + vec[2] += self->enemy->viewheight; + VectorSubtract (vec, start, dir); + VectorNormalize (dir); + monster_fire_rocket (self, start, dir, 50, 500, MZ2_BOSS2_ROCKET_2); + +//3 + G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_BOSS2_ROCKET_3], forward, right, start); + VectorCopy (self->enemy->s.origin, vec); + vec[2] += self->enemy->viewheight; + VectorSubtract (vec, start, dir); + VectorNormalize (dir); + monster_fire_rocket (self, start, dir, 50, 500, MZ2_BOSS2_ROCKET_3); + +//4 + G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_BOSS2_ROCKET_4], forward, right, start); + VectorCopy (self->enemy->s.origin, vec); + vec[2] += self->enemy->viewheight; + VectorSubtract (vec, start, dir); + VectorNormalize (dir); + monster_fire_rocket (self, start, dir, 50, 500, MZ2_BOSS2_ROCKET_4); +} + +void boss2_firebullet_right (edict_t *self) +{ + vec3_t forward, right, target; + vec3_t start; + + AngleVectors (self->s.angles, forward, right, NULL); + G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_BOSS2_MACHINEGUN_R1], forward, right, start); + + VectorMA (self->enemy->s.origin, -0.2, self->enemy->velocity, target); + target[2] += self->enemy->viewheight; + VectorSubtract (target, start, forward); + VectorNormalize (forward); + + monster_fire_bullet (self, start, forward, 6, 4, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, MZ2_BOSS2_MACHINEGUN_R1); +} + +void boss2_firebullet_left (edict_t *self) +{ + vec3_t forward, right, target; + vec3_t start; + + AngleVectors (self->s.angles, forward, right, NULL); + G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_BOSS2_MACHINEGUN_L1], forward, right, start); + + VectorMA (self->enemy->s.origin, -0.2, self->enemy->velocity, target); + + target[2] += self->enemy->viewheight; + VectorSubtract (target, start, forward); + VectorNormalize (forward); + + monster_fire_bullet (self, start, forward, 6, 4, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, MZ2_BOSS2_MACHINEGUN_L1); +} + +void Boss2MachineGun (edict_t *self) +{ +/* vec3_t forward, right; + vec3_t start; + vec3_t dir; + vec3_t vec; + int flash_number; + + AngleVectors (self->s.angles, forward, right, NULL); + + flash_number = MZ2_BOSS2_MACHINEGUN_1 + (self->s.frame - FRAME_attack10); + G_ProjectSource (self->s.origin, monster_flash_offset[flash_number], forward, right, start); + + VectorCopy (self->enemy->s.origin, vec); + vec[2] += self->enemy->viewheight; + VectorSubtract (vec, start, dir); + VectorNormalize (dir); + monster_fire_bullet (self, start, dir, 3, 4, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, flash_number); +*/ + boss2_firebullet_left(self); + boss2_firebullet_right(self); +} + + +mframe_t boss2_frames_stand [] = +{ + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL +}; +mmove_t boss2_move_stand = {FRAME_stand30, FRAME_stand50, boss2_frames_stand, NULL}; + +mframe_t boss2_frames_fidget [] = +{ + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL +}; +mmove_t boss2_move_fidget = {FRAME_stand1, FRAME_stand30, boss2_frames_fidget, NULL}; + +mframe_t boss2_frames_walk [] = +{ + ai_walk, 8, NULL, + ai_walk, 8, NULL, + ai_walk, 8, NULL, + ai_walk, 8, NULL, + ai_walk, 8, NULL, + ai_walk, 8, NULL, + ai_walk, 8, NULL, + ai_walk, 8, NULL, + ai_walk, 8, NULL, + ai_walk, 8, NULL, + ai_walk, 8, NULL, + ai_walk, 8, NULL, + ai_walk, 8, NULL, + ai_walk, 8, NULL, + ai_walk, 8, NULL, + ai_walk, 8, NULL, + ai_walk, 8, NULL, + ai_walk, 8, NULL, + ai_walk, 8, NULL, + ai_walk, 8, NULL +}; +mmove_t boss2_move_walk = {FRAME_walk1, FRAME_walk20, boss2_frames_walk, NULL}; + + +mframe_t boss2_frames_run [] = +{ + ai_run, 8, NULL, + ai_run, 8, NULL, + ai_run, 8, NULL, + ai_run, 8, NULL, + ai_run, 8, NULL, + ai_run, 8, NULL, + ai_run, 8, NULL, + ai_run, 8, NULL, + ai_run, 8, NULL, + ai_run, 8, NULL, + ai_run, 8, NULL, + ai_run, 8, NULL, + ai_run, 8, NULL, + ai_run, 8, NULL, + ai_run, 8, NULL, + ai_run, 8, NULL, + ai_run, 8, NULL, + ai_run, 8, NULL, + ai_run, 8, NULL, + ai_run, 8, NULL +}; +mmove_t boss2_move_run = {FRAME_walk1, FRAME_walk20, boss2_frames_run, NULL}; + +mframe_t boss2_frames_attack_pre_mg [] = +{ + ai_charge, 1, NULL, + ai_charge, 1, NULL, + ai_charge, 1, NULL, + ai_charge, 1, NULL, + ai_charge, 1, NULL, + ai_charge, 1, NULL, + ai_charge, 1, NULL, + ai_charge, 1, NULL, + ai_charge, 1, boss2_attack_mg +}; +mmove_t boss2_move_attack_pre_mg = {FRAME_attack1, FRAME_attack9, boss2_frames_attack_pre_mg, NULL}; + + +// Loop this +mframe_t boss2_frames_attack_mg [] = +{ + ai_charge, 1, Boss2MachineGun, + ai_charge, 1, Boss2MachineGun, + ai_charge, 1, Boss2MachineGun, + ai_charge, 1, Boss2MachineGun, + ai_charge, 1, Boss2MachineGun, + ai_charge, 1, boss2_reattack_mg +}; +mmove_t boss2_move_attack_mg = {FRAME_attack10, FRAME_attack15, boss2_frames_attack_mg, NULL}; + +mframe_t boss2_frames_attack_post_mg [] = +{ + ai_charge, 1, NULL, + ai_charge, 1, NULL, + ai_charge, 1, NULL, + ai_charge, 1, NULL +}; +mmove_t boss2_move_attack_post_mg = {FRAME_attack16, FRAME_attack19, boss2_frames_attack_post_mg, boss2_run}; + +mframe_t boss2_frames_attack_rocket [] = +{ + ai_charge, 1, NULL, + ai_charge, 1, NULL, + ai_charge, 1, NULL, + ai_charge, 1, NULL, + ai_charge, 1, NULL, + ai_charge, 1, NULL, + ai_charge, 1, NULL, + ai_charge, 1, NULL, + ai_charge, 1, NULL, + ai_charge, 1, NULL, + ai_charge, 1, NULL, + ai_charge, 1, NULL, + ai_move, -20, Boss2Rocket, + ai_charge, 1, NULL, + ai_charge, 1, NULL, + ai_charge, 1, NULL, + ai_charge, 1, NULL, + ai_charge, 1, NULL, + ai_charge, 1, NULL, + ai_charge, 1, NULL, + ai_charge, 1, NULL +}; +mmove_t boss2_move_attack_rocket = {FRAME_attack20, FRAME_attack40, boss2_frames_attack_rocket, boss2_run}; + +mframe_t boss2_frames_pain_heavy [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t boss2_move_pain_heavy = {FRAME_pain2, FRAME_pain19, boss2_frames_pain_heavy, boss2_run}; + +mframe_t boss2_frames_pain_light [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t boss2_move_pain_light = {FRAME_pain20, FRAME_pain23, boss2_frames_pain_light, boss2_run}; + +mframe_t boss2_frames_death [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, BossExplode +}; +mmove_t boss2_move_death = {FRAME_death2, FRAME_death50, boss2_frames_death, boss2_dead}; + +void boss2_stand (edict_t *self) +{ + self->monsterinfo.currentmove = &boss2_move_stand; +} + +void boss2_run (edict_t *self) +{ + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + self->monsterinfo.currentmove = &boss2_move_stand; + else + self->monsterinfo.currentmove = &boss2_move_run; +} + +void boss2_walk (edict_t *self) +{ + self->monsterinfo.currentmove = &boss2_move_walk; +} + +void boss2_attack (edict_t *self) +{ + vec3_t vec; + float range; + + VectorSubtract (self->enemy->s.origin, self->s.origin, vec); + range = VectorLength (vec); + + if (range <= 125) + { + self->monsterinfo.currentmove = &boss2_move_attack_pre_mg; + } + else + { + if (random() <= 0.6) + self->monsterinfo.currentmove = &boss2_move_attack_pre_mg; + else + self->monsterinfo.currentmove = &boss2_move_attack_rocket; + } +} + +void boss2_attack_mg (edict_t *self) +{ + self->monsterinfo.currentmove = &boss2_move_attack_mg; +} + +void boss2_reattack_mg (edict_t *self) +{ + if ( infront(self, self->enemy) ) + if (random() <= 0.7) + self->monsterinfo.currentmove = &boss2_move_attack_mg; + else + self->monsterinfo.currentmove = &boss2_move_attack_post_mg; + else + self->monsterinfo.currentmove = &boss2_move_attack_post_mg; +} + + +void boss2_pain (edict_t *self, edict_t *other, float kick, int damage) +{ + if (self->health < (self->max_health / 2)) + self->s.skinnum = 1; + + if (level.time < self->pain_debounce_time) + return; + + self->pain_debounce_time = level.time + 3; +// American wanted these at no attenuation + if (damage < 10) + { + gi.sound (self, CHAN_VOICE, sound_pain3, 1, ATTN_NONE, 0); + self->monsterinfo.currentmove = &boss2_move_pain_light; + } + else if (damage < 30) + { + gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NONE, 0); + self->monsterinfo.currentmove = &boss2_move_pain_light; + } + else + { + gi.sound (self, CHAN_VOICE, sound_pain2, 1, ATTN_NONE, 0); + self->monsterinfo.currentmove = &boss2_move_pain_heavy; + } +} + +void boss2_dead (edict_t *self) +{ + VectorSet (self->mins, -56, -56, 0); + VectorSet (self->maxs, 56, 56, 80); + self->movetype = MOVETYPE_TOSS; + self->svflags |= SVF_DEADMONSTER; + self->nextthink = 0; + gi.linkentity (self); +} + +void boss2_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + gi.sound (self, CHAN_VOICE, sound_death, 1, ATTN_NONE, 0); + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_NO; + self->count = 0; + self->monsterinfo.currentmove = &boss2_move_death; +#if 0 + int n; + + self->s.sound = 0; + // check for gib + if (self->health <= self->gib_health) + { + gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0); + for (n= 0; n < 2; n++) + ThrowGib (self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC); + for (n= 0; n < 4; n++) + ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); + ThrowHead (self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC); + self->deadflag = DEAD_DEAD; + return; + } + + if (self->deadflag == DEAD_DEAD) + return; + + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_YES; + self->monsterinfo.currentmove = &boss2_move_death; +#endif +} + +qboolean Boss2_CheckAttack (edict_t *self) +{ + vec3_t spot1, spot2; + vec3_t temp; + float chance; + trace_t tr; + qboolean enemy_infront; + int enemy_range; + float enemy_yaw; + + if (self->enemy->health > 0) + { + // see if any entities are in the way of the shot + VectorCopy (self->s.origin, spot1); + spot1[2] += self->viewheight; + VectorCopy (self->enemy->s.origin, spot2); + spot2[2] += self->enemy->viewheight; + + tr = gi.trace (spot1, NULL, NULL, spot2, self, CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_SLIME|CONTENTS_LAVA); + + // do we have a clear shot? + if (tr.ent != self->enemy) + return false; + } + + enemy_infront = infront(self, self->enemy); + enemy_range = range(self, self->enemy); + VectorSubtract (self->enemy->s.origin, self->s.origin, temp); + enemy_yaw = vectoyaw(temp); + + self->ideal_yaw = enemy_yaw; + + + // melee attack + if (enemy_range == RANGE_MELEE) + { + if (self->monsterinfo.melee) + self->monsterinfo.attack_state = AS_MELEE; + else + self->monsterinfo.attack_state = AS_MISSILE; + return true; + } + +// missile attack + if (!self->monsterinfo.attack) + return false; + + if (level.time < self->monsterinfo.attack_finished) + return false; + + if (enemy_range == RANGE_FAR) + return false; + + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + { + chance = 0.4; + } + else if (enemy_range == RANGE_MELEE) + { + chance = 0.8; + } + else if (enemy_range == RANGE_NEAR) + { + chance = 0.8; + } + else if (enemy_range == RANGE_MID) + { + chance = 0.8; + } + else + { + return false; + } + + if (random () < chance) + { + self->monsterinfo.attack_state = AS_MISSILE; + self->monsterinfo.attack_finished = level.time + 2*random(); + return true; + } + + if (self->flags & FL_FLY) + { + if (random() < 0.3) + self->monsterinfo.attack_state = AS_SLIDING; + else + self->monsterinfo.attack_state = AS_STRAIGHT; + } + + return false; +} + + + +/*QUAKED monster_boss2 (1 .5 0) (-56 -56 0) (56 56 80) Ambush Trigger_Spawn Sight +*/ +void SP_monster_boss2 (edict_t *self) +{ + if (deathmatch->value) + { + G_FreeEdict (self); + return; + } + + sound_pain1 = gi.soundindex ("bosshovr/bhvpain1.wav"); + sound_pain2 = gi.soundindex ("bosshovr/bhvpain2.wav"); + sound_pain3 = gi.soundindex ("bosshovr/bhvpain3.wav"); + sound_death = gi.soundindex ("bosshovr/bhvdeth1.wav"); + sound_search1 = gi.soundindex ("bosshovr/bhvunqv1.wav"); + + self->s.sound = gi.soundindex ("bosshovr/bhvengn1.wav"); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex ("models/monsters/boss2/tris.md2"); + VectorSet (self->mins, -56, -56, 0); + VectorSet (self->maxs, 56, 56, 80); + + self->health = 2000; + self->gib_health = -200; + self->mass = 1000; + + self->flags |= FL_IMMUNE_LASER; + + self->pain = boss2_pain; + self->die = boss2_die; + + self->monsterinfo.stand = boss2_stand; + self->monsterinfo.walk = boss2_walk; + self->monsterinfo.run = boss2_run; + self->monsterinfo.attack = boss2_attack; + self->monsterinfo.search = boss2_search; + self->monsterinfo.checkattack = Boss2_CheckAttack; + gi.linkentity (self); + + self->monsterinfo.currentmove = &boss2_move_stand; + self->monsterinfo.scale = MODEL_SCALE; + + flymonster_start (self); +} diff --git a/original/xatrix/m_boss2.h b/original/xatrix/m_boss2.h new file mode 100644 index 0000000..9951203 --- /dev/null +++ b/original/xatrix/m_boss2.h @@ -0,0 +1,189 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// G:\quake2\baseq2\models/monsters/boss2 + +// This file generated by ModelGen - Do NOT Modify + +#define FRAME_stand30 0 +#define FRAME_stand31 1 +#define FRAME_stand32 2 +#define FRAME_stand33 3 +#define FRAME_stand34 4 +#define FRAME_stand35 5 +#define FRAME_stand36 6 +#define FRAME_stand37 7 +#define FRAME_stand38 8 +#define FRAME_stand39 9 +#define FRAME_stand40 10 +#define FRAME_stand41 11 +#define FRAME_stand42 12 +#define FRAME_stand43 13 +#define FRAME_stand44 14 +#define FRAME_stand45 15 +#define FRAME_stand46 16 +#define FRAME_stand47 17 +#define FRAME_stand48 18 +#define FRAME_stand49 19 +#define FRAME_stand50 20 +#define FRAME_stand1 21 +#define FRAME_stand2 22 +#define FRAME_stand3 23 +#define FRAME_stand4 24 +#define FRAME_stand5 25 +#define FRAME_stand6 26 +#define FRAME_stand7 27 +#define FRAME_stand8 28 +#define FRAME_stand9 29 +#define FRAME_stand10 30 +#define FRAME_stand11 31 +#define FRAME_stand12 32 +#define FRAME_stand13 33 +#define FRAME_stand14 34 +#define FRAME_stand15 35 +#define FRAME_stand16 36 +#define FRAME_stand17 37 +#define FRAME_stand18 38 +#define FRAME_stand19 39 +#define FRAME_stand20 40 +#define FRAME_stand21 41 +#define FRAME_stand22 42 +#define FRAME_stand23 43 +#define FRAME_stand24 44 +#define FRAME_stand25 45 +#define FRAME_stand26 46 +#define FRAME_stand27 47 +#define FRAME_stand28 48 +#define FRAME_stand29 49 +#define FRAME_walk1 50 +#define FRAME_walk2 51 +#define FRAME_walk3 52 +#define FRAME_walk4 53 +#define FRAME_walk5 54 +#define FRAME_walk6 55 +#define FRAME_walk7 56 +#define FRAME_walk8 57 +#define FRAME_walk9 58 +#define FRAME_walk10 59 +#define FRAME_walk11 60 +#define FRAME_walk12 61 +#define FRAME_walk13 62 +#define FRAME_walk14 63 +#define FRAME_walk15 64 +#define FRAME_walk16 65 +#define FRAME_walk17 66 +#define FRAME_walk18 67 +#define FRAME_walk19 68 +#define FRAME_walk20 69 +#define FRAME_attack1 70 +#define FRAME_attack2 71 +#define FRAME_attack3 72 +#define FRAME_attack4 73 +#define FRAME_attack5 74 +#define FRAME_attack6 75 +#define FRAME_attack7 76 +#define FRAME_attack8 77 +#define FRAME_attack9 78 +#define FRAME_attack10 79 +#define FRAME_attack11 80 +#define FRAME_attack12 81 +#define FRAME_attack13 82 +#define FRAME_attack14 83 +#define FRAME_attack15 84 +#define FRAME_attack16 85 +#define FRAME_attack17 86 +#define FRAME_attack18 87 +#define FRAME_attack19 88 +#define FRAME_attack20 89 +#define FRAME_attack21 90 +#define FRAME_attack22 91 +#define FRAME_attack23 92 +#define FRAME_attack24 93 +#define FRAME_attack25 94 +#define FRAME_attack26 95 +#define FRAME_attack27 96 +#define FRAME_attack28 97 +#define FRAME_attack29 98 +#define FRAME_attack30 99 +#define FRAME_attack31 100 +#define FRAME_attack32 101 +#define FRAME_attack33 102 +#define FRAME_attack34 103 +#define FRAME_attack35 104 +#define FRAME_attack36 105 +#define FRAME_attack37 106 +#define FRAME_attack38 107 +#define FRAME_attack39 108 +#define FRAME_attack40 109 +#define FRAME_pain2 110 +#define FRAME_pain3 111 +#define FRAME_pain4 112 +#define FRAME_pain5 113 +#define FRAME_pain6 114 +#define FRAME_pain7 115 +#define FRAME_pain8 116 +#define FRAME_pain9 117 +#define FRAME_pain10 118 +#define FRAME_pain11 119 +#define FRAME_pain12 120 +#define FRAME_pain13 121 +#define FRAME_pain14 122 +#define FRAME_pain15 123 +#define FRAME_pain16 124 +#define FRAME_pain17 125 +#define FRAME_pain18 126 +#define FRAME_pain19 127 +#define FRAME_pain20 128 +#define FRAME_pain21 129 +#define FRAME_pain22 130 +#define FRAME_pain23 131 +#define FRAME_death2 132 +#define FRAME_death3 133 +#define FRAME_death4 134 +#define FRAME_death5 135 +#define FRAME_death6 136 +#define FRAME_death7 137 +#define FRAME_death8 138 +#define FRAME_death9 139 +#define FRAME_death10 140 +#define FRAME_death11 141 +#define FRAME_death12 142 +#define FRAME_death13 143 +#define FRAME_death14 144 +#define FRAME_death15 145 +#define FRAME_death16 146 +#define FRAME_death17 147 +#define FRAME_death18 148 +#define FRAME_death19 149 +#define FRAME_death20 150 +#define FRAME_death21 151 +#define FRAME_death22 152 +#define FRAME_death23 153 +#define FRAME_death24 154 +#define FRAME_death25 155 +#define FRAME_death26 156 +#define FRAME_death27 157 +#define FRAME_death28 158 +#define FRAME_death29 159 +#define FRAME_death30 160 +#define FRAME_death31 161 +#define FRAME_death32 162 +#define FRAME_death33 163 +#define FRAME_death34 164 +#define FRAME_death35 165 +#define FRAME_death36 166 +#define FRAME_death37 167 +#define FRAME_death38 168 +#define FRAME_death39 169 +#define FRAME_death40 170 +#define FRAME_death41 171 +#define FRAME_death42 172 +#define FRAME_death43 173 +#define FRAME_death44 174 +#define FRAME_death45 175 +#define FRAME_death46 176 +#define FRAME_death47 177 +#define FRAME_death48 178 +#define FRAME_death49 179 +#define FRAME_death50 180 + +#define MODEL_SCALE 1.000000 diff --git a/original/xatrix/m_boss3.c b/original/xatrix/m_boss3.c new file mode 100644 index 0000000..22d2df1 --- /dev/null +++ b/original/xatrix/m_boss3.c @@ -0,0 +1,59 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +/* +============================================================================== + +boss3 + +============================================================================== +*/ + +#include "g_local.h" +#include "m_boss32.h" + +void Use_Boss3 (edict_t *ent, edict_t *other, edict_t *activator) +{ + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_BOSSTPORT); + gi.WritePosition (ent->s.origin); + gi.multicast (ent->s.origin, MULTICAST_PVS); + G_FreeEdict (ent); +} + +void Think_Boss3Stand (edict_t *ent) +{ + if (ent->s.frame == FRAME_stand260) + ent->s.frame = FRAME_stand201; + else + ent->s.frame++; + ent->nextthink = level.time + FRAMETIME; +} + +/*QUAKED monster_boss3_stand (1 .5 0) (-32 -32 0) (32 32 90) + +Just stands and cycles in one place until targeted, then teleports away. +*/ +void SP_monster_boss3_stand (edict_t *self) +{ + if (deathmatch->value) + { + G_FreeEdict (self); + return; + } + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->model = "models/monsters/boss3/rider/tris.md2"; + self->s.modelindex = gi.modelindex (self->model); + self->s.frame = FRAME_stand201; + + gi.soundindex ("misc/bigtele.wav"); + + VectorSet (self->mins, -32, -32, 0); + VectorSet (self->maxs, 32, 32, 90); + + self->use = Use_Boss3; + self->think = Think_Boss3Stand; + self->nextthink = level.time + FRAMETIME; + gi.linkentity (self); +} diff --git a/original/xatrix/m_boss31.c b/original/xatrix/m_boss31.c new file mode 100644 index 0000000..b908b50 --- /dev/null +++ b/original/xatrix/m_boss31.c @@ -0,0 +1,732 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +/* +============================================================================== + +jorg + +============================================================================== +*/ + +#include "g_local.h" +#include "m_boss31.h" + +extern SP_monster_makron (edict_t *self); +qboolean visible (edict_t *self, edict_t *other); + +static int sound_pain1; +static int sound_pain2; +static int sound_pain3; +static int sound_idle; +static int sound_death; +static int sound_search1; +static int sound_search2; +static int sound_search3; +static int sound_attack1; +static int sound_attack2; +static int sound_firegun; +static int sound_step_left; +static int sound_step_right; +static int sound_death_hit; + +void BossExplode (edict_t *self); +void MakronToss (edict_t *self); + + +void jorg_search (edict_t *self) +{ + float r; + + r = random(); + + if (r <= 0.3) + gi.sound (self, CHAN_VOICE, sound_search1, 1, ATTN_NORM, 0); + else if (r <= 0.6) + gi.sound (self, CHAN_VOICE, sound_search2, 1, ATTN_NORM, 0); + else + gi.sound (self, CHAN_VOICE, sound_search3, 1, ATTN_NORM, 0); +} + + +void jorg_dead (edict_t *self); +void jorgBFG (edict_t *self); +void jorgMachineGun (edict_t *self); +void jorg_firebullet (edict_t *self); +void jorg_reattack1(edict_t *self); +void jorg_attack1(edict_t *self); +void jorg_idle(edict_t *self); +void jorg_step_left(edict_t *self); +void jorg_step_right(edict_t *self); +void jorg_death_hit(edict_t *self); + +// +// stand +// + +mframe_t jorg_frames_stand []= +{ + ai_stand, 0, jorg_idle, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, // 10 + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, // 20 + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, // 30 + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 19, NULL, + ai_stand, 11, jorg_step_left, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 6, NULL, + ai_stand, 9, jorg_step_right, + ai_stand, 0, NULL, // 40 + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, -2, NULL, + ai_stand, -17, jorg_step_left, + ai_stand, 0, NULL, + ai_stand, -12, NULL, // 50 + ai_stand, -14, jorg_step_right // 51 +}; +mmove_t jorg_move_stand = {FRAME_stand01, FRAME_stand51, jorg_frames_stand, NULL}; + +void jorg_idle (edict_t *self) +{ + gi.sound (self, CHAN_VOICE, sound_idle, 1, ATTN_NORM,0); +} + +void jorg_death_hit (edict_t *self) +{ + gi.sound (self, CHAN_BODY, sound_death_hit, 1, ATTN_NORM,0); +} + + +void jorg_step_left (edict_t *self) +{ + gi.sound (self, CHAN_BODY, sound_step_left, 1, ATTN_NORM,0); +} + +void jorg_step_right (edict_t *self) +{ + gi.sound (self, CHAN_BODY, sound_step_right, 1, ATTN_NORM,0); +} + + +void jorg_stand (edict_t *self) +{ + self->monsterinfo.currentmove = &jorg_move_stand; +} + +mframe_t jorg_frames_run [] = +{ + ai_run, 17, jorg_step_left, + ai_run, 0, NULL, + ai_run, 0, NULL, + ai_run, 0, NULL, + ai_run, 12, NULL, + ai_run, 8, NULL, + ai_run, 10, NULL, + ai_run, 33, jorg_step_right, + ai_run, 0, NULL, + ai_run, 0, NULL, + ai_run, 0, NULL, + ai_run, 9, NULL, + ai_run, 9, NULL, + ai_run, 9, NULL +}; +mmove_t jorg_move_run = {FRAME_walk06, FRAME_walk19, jorg_frames_run, NULL}; + +// +// walk +// + +mframe_t jorg_frames_start_walk [] = +{ + ai_walk, 5, NULL, + ai_walk, 6, NULL, + ai_walk, 7, NULL, + ai_walk, 9, NULL, + ai_walk, 15, NULL +}; +mmove_t jorg_move_start_walk = {FRAME_walk01, FRAME_walk05, jorg_frames_start_walk, NULL}; + +mframe_t jorg_frames_walk [] = +{ + ai_walk, 17, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 12, NULL, + ai_walk, 8, NULL, + ai_walk, 10, NULL, + ai_walk, 33, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 9, NULL, + ai_walk, 9, NULL, + ai_walk, 9, NULL +}; +mmove_t jorg_move_walk = {FRAME_walk06, FRAME_walk19, jorg_frames_walk, NULL}; + +mframe_t jorg_frames_end_walk [] = +{ + ai_walk, 11, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 8, NULL, + ai_walk, -8, NULL +}; +mmove_t jorg_move_end_walk = {FRAME_walk20, FRAME_walk25, jorg_frames_end_walk, NULL}; + +void jorg_walk (edict_t *self) +{ + self->monsterinfo.currentmove = &jorg_move_walk; +} + +void jorg_run (edict_t *self) +{ + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + self->monsterinfo.currentmove = &jorg_move_stand; + else + self->monsterinfo.currentmove = &jorg_move_run; +} + +mframe_t jorg_frames_pain3 [] = +{ + ai_move, -28, NULL, + ai_move, -6, NULL, + ai_move, -3, jorg_step_left, + ai_move, -9, NULL, + ai_move, 0, jorg_step_right, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, -7, NULL, + ai_move, 1, NULL, + ai_move, -11, NULL, + ai_move, -4, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 10, NULL, + ai_move, 11, NULL, + ai_move, 0, NULL, + ai_move, 10, NULL, + ai_move, 3, NULL, + ai_move, 10, NULL, + ai_move, 7, jorg_step_left, + ai_move, 17, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, jorg_step_right +}; +mmove_t jorg_move_pain3 = {FRAME_pain301, FRAME_pain325, jorg_frames_pain3, jorg_run}; + +mframe_t jorg_frames_pain2 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t jorg_move_pain2 = {FRAME_pain201, FRAME_pain203, jorg_frames_pain2, jorg_run}; + +mframe_t jorg_frames_pain1 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t jorg_move_pain1 = {FRAME_pain101, FRAME_pain103, jorg_frames_pain1, jorg_run}; + +mframe_t jorg_frames_death1 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, // 10 + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, // 20 + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, // 30 + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, // 40 + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, MakronToss, + ai_move, 0, BossExplode // 50 +}; +mmove_t jorg_move_death = {FRAME_death01, FRAME_death50, jorg_frames_death1, jorg_dead}; + +mframe_t jorg_frames_attack2 []= +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, jorgBFG, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t jorg_move_attack2 = {FRAME_attak201, FRAME_attak213, jorg_frames_attack2, jorg_run}; + +mframe_t jorg_frames_start_attack1 [] = +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL +}; +mmove_t jorg_move_start_attack1 = {FRAME_attak101, FRAME_attak108, jorg_frames_start_attack1, jorg_attack1}; + +mframe_t jorg_frames_attack1[]= +{ + ai_charge, 0, jorg_firebullet, + ai_charge, 0, jorg_firebullet, + ai_charge, 0, jorg_firebullet, + ai_charge, 0, jorg_firebullet, + ai_charge, 0, jorg_firebullet, + ai_charge, 0, jorg_firebullet +}; +mmove_t jorg_move_attack1 = {FRAME_attak109, FRAME_attak114, jorg_frames_attack1, jorg_reattack1}; + +mframe_t jorg_frames_end_attack1[]= +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t jorg_move_end_attack1 = {FRAME_attak115, FRAME_attak118, jorg_frames_end_attack1, jorg_run}; + +void jorg_reattack1(edict_t *self) +{ + if (visible(self, self->enemy)) + if (random() < 0.9) + self->monsterinfo.currentmove = &jorg_move_attack1; + else + { + self->s.sound = 0; + self->monsterinfo.currentmove = &jorg_move_end_attack1; + } + else + { + self->s.sound = 0; + self->monsterinfo.currentmove = &jorg_move_end_attack1; + } +} + +void jorg_attack1(edict_t *self) +{ + self->monsterinfo.currentmove = &jorg_move_attack1; +} + +void jorg_pain (edict_t *self, edict_t *other, float kick, int damage) +{ + + if (self->health < (self->max_health / 2)) + self->s.skinnum = 1; + + self->s.sound = 0; + + if (level.time < self->pain_debounce_time) + return; + + // Lessen the chance of him going into his pain frames if he takes little damage + if (damage <= 40) + if (random()<=0.6) + return; + + /* + If he's entering his attack1 or using attack1, lessen the chance of him + going into pain + */ + + if ( (self->s.frame >= FRAME_attak101) && (self->s.frame <= FRAME_attak108) ) + if (random() <= 0.005) + return; + + if ( (self->s.frame >= FRAME_attak109) && (self->s.frame <= FRAME_attak114) ) + if (random() <= 0.00005) + return; + + + if ( (self->s.frame >= FRAME_attak201) && (self->s.frame <= FRAME_attak208) ) + if (random() <= 0.005) + return; + + + self->pain_debounce_time = level.time + 3; + if (skill->value == 3) + return; // no pain anims in nightmare + + if (damage <= 50) + { + gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM,0); + self->monsterinfo.currentmove = &jorg_move_pain1; + } + else if (damage <= 100) + { + gi.sound (self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM,0); + self->monsterinfo.currentmove = &jorg_move_pain2; + } + else + { + if (random() <= 0.3) + { + gi.sound (self, CHAN_VOICE, sound_pain3, 1, ATTN_NORM,0); + self->monsterinfo.currentmove = &jorg_move_pain3; + } + } +}; + +void jorgBFG (edict_t *self) +{ + vec3_t forward, right; + vec3_t start; + vec3_t dir; + vec3_t vec; + + AngleVectors (self->s.angles, forward, right, NULL); + G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_JORG_BFG_1], forward, right, start); + + VectorCopy (self->enemy->s.origin, vec); + vec[2] += self->enemy->viewheight; + VectorSubtract (vec, start, dir); + VectorNormalize (dir); + gi.sound (self, CHAN_VOICE, sound_attack2, 1, ATTN_NORM, 0); + /*void monster_fire_bfg (edict_t *self, + vec3_t start, + vec3_t aimdir, + int damage, + int speed, + int kick, + float damage_radius, + int flashtype)*/ + monster_fire_bfg (self, start, dir, 50, 300, 100, 200, MZ2_JORG_BFG_1); +} + +void jorg_firebullet_right (edict_t *self) +{ + vec3_t forward, right, target; + vec3_t start; + + AngleVectors (self->s.angles, forward, right, NULL); + G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_JORG_MACHINEGUN_R1], forward, right, start); + + VectorMA (self->enemy->s.origin, -0.2, self->enemy->velocity, target); + target[2] += self->enemy->viewheight; + VectorSubtract (target, start, forward); + VectorNormalize (forward); + + monster_fire_bullet (self, start, forward, 6, 4, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, MZ2_JORG_MACHINEGUN_R1); +} + +void jorg_firebullet_left (edict_t *self) +{ + vec3_t forward, right, target; + vec3_t start; + + AngleVectors (self->s.angles, forward, right, NULL); + G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_JORG_MACHINEGUN_L1], forward, right, start); + + VectorMA (self->enemy->s.origin, -0.2, self->enemy->velocity, target); + target[2] += self->enemy->viewheight; + VectorSubtract (target, start, forward); + VectorNormalize (forward); + + monster_fire_bullet (self, start, forward, 6, 4, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, MZ2_JORG_MACHINEGUN_L1); +} + +void jorg_firebullet (edict_t *self) +{ + jorg_firebullet_left(self); + jorg_firebullet_right(self); +}; + +void jorg_attack(edict_t *self) +{ + vec3_t vec; + float range; + + VectorSubtract (self->enemy->s.origin, self->s.origin, vec); + range = VectorLength (vec); + + if (random() <= 0.75) + { + gi.sound (self, CHAN_VOICE, sound_attack1, 1, ATTN_NORM,0); + self->s.sound = gi.soundindex ("boss3/w_loop.wav"); + self->monsterinfo.currentmove = &jorg_move_start_attack1; + } + else + { + gi.sound (self, CHAN_VOICE, sound_attack2, 1, ATTN_NORM,0); + self->monsterinfo.currentmove = &jorg_move_attack2; + } +} + +void jorg_dead (edict_t *self) +{ +#if 0 + edict_t *tempent; + /* + VectorSet (self->mins, -16, -16, -24); + VectorSet (self->maxs, 16, 16, -8); + */ + + // Jorg is on modelindex2. Do not clear him. + VectorSet (self->mins, -60, -60, 0); + VectorSet (self->maxs, 60, 60, 72); + self->movetype = MOVETYPE_TOSS; + self->nextthink = 0; + gi.linkentity (self); + + tempent = G_Spawn(); + VectorCopy (self->s.origin, tempent->s.origin); + VectorCopy (self->s.angles, tempent->s.angles); + tempent->killtarget = self->killtarget; + tempent->target = self->target; + tempent->activator = self->enemy; + self->killtarget = 0; + self->target = 0; + SP_monster_makron (tempent); +#endif +} + + +void jorg_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + gi.sound (self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0); + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_NO; + self->s.sound = 0; + self->count = 0; + self->monsterinfo.currentmove = &jorg_move_death; +} + +qboolean Jorg_CheckAttack (edict_t *self) +{ + vec3_t spot1, spot2; + vec3_t temp; + float chance; + trace_t tr; + qboolean enemy_infront; + int enemy_range; + float enemy_yaw; + + if (self->enemy->health > 0) + { + // see if any entities are in the way of the shot + VectorCopy (self->s.origin, spot1); + spot1[2] += self->viewheight; + VectorCopy (self->enemy->s.origin, spot2); + spot2[2] += self->enemy->viewheight; + + tr = gi.trace (spot1, NULL, NULL, spot2, self, CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_SLIME|CONTENTS_LAVA); + + // do we have a clear shot? + if (tr.ent != self->enemy) + return false; + } + + enemy_infront = infront(self, self->enemy); + enemy_range = range(self, self->enemy); + VectorSubtract (self->enemy->s.origin, self->s.origin, temp); + enemy_yaw = vectoyaw(temp); + + self->ideal_yaw = enemy_yaw; + + + // melee attack + if (enemy_range == RANGE_MELEE) + { + if (self->monsterinfo.melee) + self->monsterinfo.attack_state = AS_MELEE; + else + self->monsterinfo.attack_state = AS_MISSILE; + return true; + } + +// missile attack + if (!self->monsterinfo.attack) + return false; + + if (level.time < self->monsterinfo.attack_finished) + return false; + + if (enemy_range == RANGE_FAR) + return false; + + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + { + chance = 0.4; + } + else if (enemy_range == RANGE_MELEE) + { + chance = 0.8; + } + else if (enemy_range == RANGE_NEAR) + { + chance = 0.4; + } + else if (enemy_range == RANGE_MID) + { + chance = 0.2; + } + else + { + return false; + } + + if (random () < chance) + { + self->monsterinfo.attack_state = AS_MISSILE; + self->monsterinfo.attack_finished = level.time + 2*random(); + return true; + } + + if (self->flags & FL_FLY) + { + if (random() < 0.3) + self->monsterinfo.attack_state = AS_SLIDING; + else + self->monsterinfo.attack_state = AS_STRAIGHT; + } + + return false; +} + + +void MakronPrecache (void); + +/*QUAKED monster_jorg (1 .5 0) (-80 -80 0) (90 90 140) Ambush Trigger_Spawn Sight +*/ +void SP_monster_jorg (edict_t *self) +{ + if (deathmatch->value) + { + G_FreeEdict (self); + return; + } + + sound_pain1 = gi.soundindex ("boss3/bs3pain1.wav"); + sound_pain2 = gi.soundindex ("boss3/bs3pain2.wav"); + sound_pain3 = gi.soundindex ("boss3/bs3pain3.wav"); + sound_death = gi.soundindex ("boss3/bs3deth1.wav"); + sound_attack1 = gi.soundindex ("boss3/bs3atck1.wav"); + sound_attack2 = gi.soundindex ("boss3/bs3atck2.wav"); + sound_search1 = gi.soundindex ("boss3/bs3srch1.wav"); + sound_search2 = gi.soundindex ("boss3/bs3srch2.wav"); + sound_search3 = gi.soundindex ("boss3/bs3srch3.wav"); + sound_idle = gi.soundindex ("boss3/bs3idle1.wav"); + sound_step_left = gi.soundindex ("boss3/step1.wav"); + sound_step_right = gi.soundindex ("boss3/step2.wav"); + sound_firegun = gi.soundindex ("boss3/xfire.wav"); + sound_death_hit = gi.soundindex ("boss3/d_hit.wav"); + + MakronPrecache (); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex ("models/monsters/boss3/rider/tris.md2"); + self->s.modelindex2 = gi.modelindex ("models/monsters/boss3/jorg/tris.md2"); + VectorSet (self->mins, -80, -80, 0); + VectorSet (self->maxs, 80, 80, 140); + + self->health = 3000; + self->gib_health = -2000; + self->mass = 1000; + + self->pain = jorg_pain; + self->die = jorg_die; + self->monsterinfo.stand = jorg_stand; + self->monsterinfo.walk = jorg_walk; + self->monsterinfo.run = jorg_run; + self->monsterinfo.dodge = NULL; + self->monsterinfo.attack = jorg_attack; + self->monsterinfo.search = jorg_search; + self->monsterinfo.melee = NULL; + self->monsterinfo.sight = NULL; + self->monsterinfo.checkattack = Jorg_CheckAttack; + gi.linkentity (self); + + self->monsterinfo.currentmove = &jorg_move_stand; + self->monsterinfo.scale = MODEL_SCALE; + + walkmonster_start(self); +} diff --git a/original/xatrix/m_boss31.h b/original/xatrix/m_boss31.h new file mode 100644 index 0000000..e950987 --- /dev/null +++ b/original/xatrix/m_boss31.h @@ -0,0 +1,196 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// G:\quake2\baseq2\models/monsters/boss3/jorg + +// This file generated by ModelGen - Do NOT Modify + +#define FRAME_attak101 0 +#define FRAME_attak102 1 +#define FRAME_attak103 2 +#define FRAME_attak104 3 +#define FRAME_attak105 4 +#define FRAME_attak106 5 +#define FRAME_attak107 6 +#define FRAME_attak108 7 +#define FRAME_attak109 8 +#define FRAME_attak110 9 +#define FRAME_attak111 10 +#define FRAME_attak112 11 +#define FRAME_attak113 12 +#define FRAME_attak114 13 +#define FRAME_attak115 14 +#define FRAME_attak116 15 +#define FRAME_attak117 16 +#define FRAME_attak118 17 +#define FRAME_attak201 18 +#define FRAME_attak202 19 +#define FRAME_attak203 20 +#define FRAME_attak204 21 +#define FRAME_attak205 22 +#define FRAME_attak206 23 +#define FRAME_attak207 24 +#define FRAME_attak208 25 +#define FRAME_attak209 26 +#define FRAME_attak210 27 +#define FRAME_attak211 28 +#define FRAME_attak212 29 +#define FRAME_attak213 30 +#define FRAME_death01 31 +#define FRAME_death02 32 +#define FRAME_death03 33 +#define FRAME_death04 34 +#define FRAME_death05 35 +#define FRAME_death06 36 +#define FRAME_death07 37 +#define FRAME_death08 38 +#define FRAME_death09 39 +#define FRAME_death10 40 +#define FRAME_death11 41 +#define FRAME_death12 42 +#define FRAME_death13 43 +#define FRAME_death14 44 +#define FRAME_death15 45 +#define FRAME_death16 46 +#define FRAME_death17 47 +#define FRAME_death18 48 +#define FRAME_death19 49 +#define FRAME_death20 50 +#define FRAME_death21 51 +#define FRAME_death22 52 +#define FRAME_death23 53 +#define FRAME_death24 54 +#define FRAME_death25 55 +#define FRAME_death26 56 +#define FRAME_death27 57 +#define FRAME_death28 58 +#define FRAME_death29 59 +#define FRAME_death30 60 +#define FRAME_death31 61 +#define FRAME_death32 62 +#define FRAME_death33 63 +#define FRAME_death34 64 +#define FRAME_death35 65 +#define FRAME_death36 66 +#define FRAME_death37 67 +#define FRAME_death38 68 +#define FRAME_death39 69 +#define FRAME_death40 70 +#define FRAME_death41 71 +#define FRAME_death42 72 +#define FRAME_death43 73 +#define FRAME_death44 74 +#define FRAME_death45 75 +#define FRAME_death46 76 +#define FRAME_death47 77 +#define FRAME_death48 78 +#define FRAME_death49 79 +#define FRAME_death50 80 +#define FRAME_pain101 81 +#define FRAME_pain102 82 +#define FRAME_pain103 83 +#define FRAME_pain201 84 +#define FRAME_pain202 85 +#define FRAME_pain203 86 +#define FRAME_pain301 87 +#define FRAME_pain302 88 +#define FRAME_pain303 89 +#define FRAME_pain304 90 +#define FRAME_pain305 91 +#define FRAME_pain306 92 +#define FRAME_pain307 93 +#define FRAME_pain308 94 +#define FRAME_pain309 95 +#define FRAME_pain310 96 +#define FRAME_pain311 97 +#define FRAME_pain312 98 +#define FRAME_pain313 99 +#define FRAME_pain314 100 +#define FRAME_pain315 101 +#define FRAME_pain316 102 +#define FRAME_pain317 103 +#define FRAME_pain318 104 +#define FRAME_pain319 105 +#define FRAME_pain320 106 +#define FRAME_pain321 107 +#define FRAME_pain322 108 +#define FRAME_pain323 109 +#define FRAME_pain324 110 +#define FRAME_pain325 111 +#define FRAME_stand01 112 +#define FRAME_stand02 113 +#define FRAME_stand03 114 +#define FRAME_stand04 115 +#define FRAME_stand05 116 +#define FRAME_stand06 117 +#define FRAME_stand07 118 +#define FRAME_stand08 119 +#define FRAME_stand09 120 +#define FRAME_stand10 121 +#define FRAME_stand11 122 +#define FRAME_stand12 123 +#define FRAME_stand13 124 +#define FRAME_stand14 125 +#define FRAME_stand15 126 +#define FRAME_stand16 127 +#define FRAME_stand17 128 +#define FRAME_stand18 129 +#define FRAME_stand19 130 +#define FRAME_stand20 131 +#define FRAME_stand21 132 +#define FRAME_stand22 133 +#define FRAME_stand23 134 +#define FRAME_stand24 135 +#define FRAME_stand25 136 +#define FRAME_stand26 137 +#define FRAME_stand27 138 +#define FRAME_stand28 139 +#define FRAME_stand29 140 +#define FRAME_stand30 141 +#define FRAME_stand31 142 +#define FRAME_stand32 143 +#define FRAME_stand33 144 +#define FRAME_stand34 145 +#define FRAME_stand35 146 +#define FRAME_stand36 147 +#define FRAME_stand37 148 +#define FRAME_stand38 149 +#define FRAME_stand39 150 +#define FRAME_stand40 151 +#define FRAME_stand41 152 +#define FRAME_stand42 153 +#define FRAME_stand43 154 +#define FRAME_stand44 155 +#define FRAME_stand45 156 +#define FRAME_stand46 157 +#define FRAME_stand47 158 +#define FRAME_stand48 159 +#define FRAME_stand49 160 +#define FRAME_stand50 161 +#define FRAME_stand51 162 +#define FRAME_walk01 163 +#define FRAME_walk02 164 +#define FRAME_walk03 165 +#define FRAME_walk04 166 +#define FRAME_walk05 167 +#define FRAME_walk06 168 +#define FRAME_walk07 169 +#define FRAME_walk08 170 +#define FRAME_walk09 171 +#define FRAME_walk10 172 +#define FRAME_walk11 173 +#define FRAME_walk12 174 +#define FRAME_walk13 175 +#define FRAME_walk14 176 +#define FRAME_walk15 177 +#define FRAME_walk16 178 +#define FRAME_walk17 179 +#define FRAME_walk18 180 +#define FRAME_walk19 181 +#define FRAME_walk20 182 +#define FRAME_walk21 183 +#define FRAME_walk22 184 +#define FRAME_walk23 185 +#define FRAME_walk24 186 +#define FRAME_walk25 187 + +#define MODEL_SCALE 1.000000 diff --git a/original/xatrix/m_boss32.c b/original/xatrix/m_boss32.c new file mode 100644 index 0000000..a6c31c9 --- /dev/null +++ b/original/xatrix/m_boss32.c @@ -0,0 +1,896 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +/* +============================================================================== + +Makron -- Final Boss + +============================================================================== +*/ + +#include "g_local.h" +#include "m_boss32.h" + +qboolean visible (edict_t *self, edict_t *other); + +void MakronRailgun (edict_t *self); +void MakronSaveloc (edict_t *self); +void MakronHyperblaster (edict_t *self); +void makron_step_left (edict_t *self); +void makron_step_right (edict_t *self); +void makronBFG (edict_t *self); +void makron_dead (edict_t *self); + +static int sound_pain4; +static int sound_pain5; +static int sound_pain6; +static int sound_death; +static int sound_step_left; +static int sound_step_right; +static int sound_attack_bfg; +static int sound_brainsplorch; +static int sound_prerailgun; +static int sound_popup; +static int sound_taunt1; +static int sound_taunt2; +static int sound_taunt3; +static int sound_hit; + +void makron_taunt (edict_t *self) +{ + float r; + + r=random(); + if (r <= 0.3) + gi.sound (self, CHAN_AUTO, sound_taunt1, 1, ATTN_NONE, 0); + else if (r <= 0.6) + gi.sound (self, CHAN_AUTO, sound_taunt2, 1, ATTN_NONE, 0); + else + gi.sound (self, CHAN_AUTO, sound_taunt3, 1, ATTN_NONE, 0); +} + +// +// stand +// + +mframe_t makron_frames_stand []= +{ + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, // 10 + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, // 20 + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, // 30 + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, // 40 + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, // 50 + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL // 60 +}; +mmove_t makron_move_stand = {FRAME_stand201, FRAME_stand260, makron_frames_stand, NULL}; + +void makron_stand (edict_t *self) +{ + self->monsterinfo.currentmove = &makron_move_stand; +} + +mframe_t makron_frames_run [] = +{ + ai_run, 3, makron_step_left, + ai_run, 12, NULL, + ai_run, 8, NULL, + ai_run, 8, NULL, + ai_run, 8, makron_step_right, + ai_run, 6, NULL, + ai_run, 12, NULL, + ai_run, 9, NULL, + ai_run, 6, NULL, + ai_run, 12, NULL +}; +mmove_t makron_move_run = {FRAME_walk204, FRAME_walk213, makron_frames_run, NULL}; + +void makron_hit (edict_t *self) +{ + gi.sound (self, CHAN_AUTO, sound_hit, 1, ATTN_NONE,0); +} + +void makron_popup (edict_t *self) +{ + gi.sound (self, CHAN_BODY, sound_popup, 1, ATTN_NONE,0); +} + +void makron_step_left (edict_t *self) +{ + gi.sound (self, CHAN_BODY, sound_step_left, 1, ATTN_NORM,0); +} + +void makron_step_right (edict_t *self) +{ + gi.sound (self, CHAN_BODY, sound_step_right, 1, ATTN_NORM,0); +} + +void makron_brainsplorch (edict_t *self) +{ + gi.sound (self, CHAN_VOICE, sound_brainsplorch, 1, ATTN_NORM,0); +} + +void makron_prerailgun (edict_t *self) +{ + gi.sound (self, CHAN_WEAPON, sound_prerailgun, 1, ATTN_NORM,0); +} + + +mframe_t makron_frames_walk [] = +{ + ai_walk, 3, makron_step_left, + ai_walk, 12, NULL, + ai_walk, 8, NULL, + ai_walk, 8, NULL, + ai_walk, 8, makron_step_right, + ai_walk, 6, NULL, + ai_walk, 12, NULL, + ai_walk, 9, NULL, + ai_walk, 6, NULL, + ai_walk, 12, NULL +}; +mmove_t makron_move_walk = {FRAME_walk204, FRAME_walk213, makron_frames_run, NULL}; + +void makron_walk (edict_t *self) +{ + self->monsterinfo.currentmove = &makron_move_walk; +} + +void makron_run (edict_t *self) +{ + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + self->monsterinfo.currentmove = &makron_move_stand; + else + self->monsterinfo.currentmove = &makron_move_run; +} + +mframe_t makron_frames_pain6 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, // 10 + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, makron_popup, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, // 20 + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, makron_taunt, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t makron_move_pain6 = {FRAME_pain601, FRAME_pain627, makron_frames_pain6, makron_run}; + +mframe_t makron_frames_pain5 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t makron_move_pain5 = {FRAME_pain501, FRAME_pain504, makron_frames_pain5, makron_run}; + +mframe_t makron_frames_pain4 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t makron_move_pain4 = {FRAME_pain401, FRAME_pain404, makron_frames_pain4, makron_run}; + +mframe_t makron_frames_death2 [] = +{ + ai_move, -15, NULL, + ai_move, 3, NULL, + ai_move, -12, NULL, + ai_move, 0, makron_step_left, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, // 10 + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 11, NULL, + ai_move, 12, NULL, + ai_move, 11, makron_step_right, + ai_move, 0, NULL, + ai_move, 0, NULL, // 20 + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, // 30 + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 5, NULL, + ai_move, 7, NULL, + ai_move, 6, makron_step_left, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, -1, NULL, + ai_move, 2, NULL, // 40 + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, // 50 + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, -6, NULL, + ai_move, -4, NULL, + ai_move, -6, makron_step_right, + ai_move, -4, NULL, + ai_move, -4, makron_step_left, + ai_move, 0, NULL, + ai_move, 0, NULL, // 60 + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, -2, NULL, + ai_move, -5, NULL, + ai_move, -3, makron_step_right, + ai_move, -8, NULL, + ai_move, -3, makron_step_left, + ai_move, -7, NULL, + ai_move, -4, NULL, + ai_move, -4, makron_step_right, // 70 + ai_move, -6, NULL, + ai_move, -7, NULL, + ai_move, 0, makron_step_left, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, // 80 + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, -2, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 2, NULL, + ai_move, 0, NULL, // 90 + ai_move, 27, makron_hit, + ai_move, 26, NULL, + ai_move, 0, makron_brainsplorch, + ai_move, 0, NULL, + ai_move, 0, NULL // 95 +}; +mmove_t makron_move_death2 = {FRAME_death201, FRAME_death295, makron_frames_death2, makron_dead}; + +mframe_t makron_frames_death3 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t makron_move_death3 = {FRAME_death301, FRAME_death320, makron_frames_death3, NULL}; + +mframe_t makron_frames_sight [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t makron_move_sight= {FRAME_active01, FRAME_active13, makron_frames_sight, makron_run}; + +void makronBFG (edict_t *self) +{ + vec3_t forward, right; + vec3_t start; + vec3_t dir; + vec3_t vec; + + AngleVectors (self->s.angles, forward, right, NULL); + G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_MAKRON_BFG], forward, right, start); + + VectorCopy (self->enemy->s.origin, vec); + vec[2] += self->enemy->viewheight; + VectorSubtract (vec, start, dir); + VectorNormalize (dir); + gi.sound (self, CHAN_VOICE, sound_attack_bfg, 1, ATTN_NORM, 0); + monster_fire_bfg (self, start, dir, 50, 300, 100, 300, MZ2_MAKRON_BFG); +} + + +mframe_t makron_frames_attack3 []= +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, makronBFG, // FIXME: BFG Attack here + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t makron_move_attack3 = {FRAME_attak301, FRAME_attak308, makron_frames_attack3, makron_run}; + +mframe_t makron_frames_attack4[]= +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_move, 0, MakronHyperblaster, // fire + ai_move, 0, MakronHyperblaster, // fire + ai_move, 0, MakronHyperblaster, // fire + ai_move, 0, MakronHyperblaster, // fire + ai_move, 0, MakronHyperblaster, // fire + ai_move, 0, MakronHyperblaster, // fire + ai_move, 0, MakronHyperblaster, // fire + ai_move, 0, MakronHyperblaster, // fire + ai_move, 0, MakronHyperblaster, // fire + ai_move, 0, MakronHyperblaster, // fire + ai_move, 0, MakronHyperblaster, // fire + ai_move, 0, MakronHyperblaster, // fire + ai_move, 0, MakronHyperblaster, // fire + ai_move, 0, MakronHyperblaster, // fire + ai_move, 0, MakronHyperblaster, // fire + ai_move, 0, MakronHyperblaster, // fire + ai_move, 0, MakronHyperblaster, // fire + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t makron_move_attack4 = {FRAME_attak401, FRAME_attak426, makron_frames_attack4, makron_run}; + +mframe_t makron_frames_attack5[]= +{ + ai_charge, 0, makron_prerailgun, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, MakronSaveloc, + ai_move, 0, MakronRailgun, // Fire railgun + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t makron_move_attack5 = {FRAME_attak501, FRAME_attak516, makron_frames_attack5, makron_run}; + +void MakronSaveloc (edict_t *self) +{ + VectorCopy (self->enemy->s.origin, self->pos1); //save for aiming the shot + self->pos1[2] += self->enemy->viewheight; +}; + +// FIXME: He's not firing from the proper Z +void MakronRailgun (edict_t *self) +{ + vec3_t start; + vec3_t dir; + vec3_t forward, right; + + AngleVectors (self->s.angles, forward, right, NULL); + G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_MAKRON_RAILGUN_1], forward, right, start); + + // calc direction to where we targted + VectorSubtract (self->pos1, start, dir); + VectorNormalize (dir); + + monster_fire_railgun (self, start, dir, 50, 100, MZ2_MAKRON_RAILGUN_1); +} + +// FIXME: This is all wrong. He's not firing at the proper angles. +void MakronHyperblaster (edict_t *self) +{ + vec3_t dir; + vec3_t vec; + vec3_t start; + vec3_t forward, right; + int flash_number; + + flash_number = MZ2_MAKRON_BLASTER_1 + (self->s.frame - FRAME_attak405); + + AngleVectors (self->s.angles, forward, right, NULL); + G_ProjectSource (self->s.origin, monster_flash_offset[flash_number], forward, right, start); + + if (self->enemy) + { + VectorCopy (self->enemy->s.origin, vec); + vec[2] += self->enemy->viewheight; + VectorSubtract (vec, start, vec); + vectoangles (vec, vec); + dir[0] = vec[0]; + } + else + { + dir[0] = 0; + } + if (self->s.frame <= FRAME_attak413) + dir[1] = self->s.angles[1] - 10 * (self->s.frame - FRAME_attak413); + else + dir[1] = self->s.angles[1] + 10 * (self->s.frame - FRAME_attak421); + dir[2] = 0; + + AngleVectors (dir, forward, NULL, NULL); + + monster_fire_blaster (self, start, forward, 15, 1000, MZ2_MAKRON_BLASTER_1, EF_BLASTER); +} + + +void makron_pain (edict_t *self, edict_t *other, float kick, int damage) +{ + + if (self->health < (self->max_health / 2)) + self->s.skinnum = 1; + + if (level.time < self->pain_debounce_time) + return; + + // Lessen the chance of him going into his pain frames + if (damage <=25) + if (random()<0.2) + return; + + self->pain_debounce_time = level.time + 3; + if (skill->value == 3) + return; // no pain anims in nightmare + + + if (damage <= 40) + { + gi.sound (self, CHAN_VOICE, sound_pain4, 1, ATTN_NONE,0); + self->monsterinfo.currentmove = &makron_move_pain4; + } + else if (damage <= 110) + { + gi.sound (self, CHAN_VOICE, sound_pain5, 1, ATTN_NONE,0); + self->monsterinfo.currentmove = &makron_move_pain5; + } + else + { + if (damage <= 150) + if (random() <= 0.45) + { + gi.sound (self, CHAN_VOICE, sound_pain6, 1, ATTN_NONE,0); + self->monsterinfo.currentmove = &makron_move_pain6; + } + else + if (random() <= 0.35) + { + gi.sound (self, CHAN_VOICE, sound_pain6, 1, ATTN_NONE,0); + self->monsterinfo.currentmove = &makron_move_pain6; + } + } +}; + +void makron_sight(edict_t *self, edict_t *other) +{ + self->monsterinfo.currentmove = &makron_move_sight; +}; + +void makron_attack(edict_t *self) +{ + vec3_t vec; + float range; + float r; + + r = random(); + + VectorSubtract (self->enemy->s.origin, self->s.origin, vec); + range = VectorLength (vec); + + + if (r <= 0.3) + self->monsterinfo.currentmove = &makron_move_attack3; + else if (r <= 0.6) + self->monsterinfo.currentmove = &makron_move_attack4; + else + self->monsterinfo.currentmove = &makron_move_attack5; +} + +/* +--- +Makron Torso. This needs to be spawned in +--- +*/ + +void makron_torso_think (edict_t *self) +{ + if (++self->s.frame < 365) + self->nextthink = level.time + FRAMETIME; + else + { + self->s.frame = 346; + self->nextthink = level.time + FRAMETIME; + } +} + +void makron_torso (edict_t *ent) +{ + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_NOT; + VectorSet (ent->mins, -8, -8, 0); + VectorSet (ent->maxs, 8, 8, 8); + ent->s.frame = 346; + ent->s.modelindex = gi.modelindex ("models/monsters/boss3/rider/tris.md2"); + ent->think = makron_torso_think; + ent->nextthink = level.time + 2 * FRAMETIME; + ent->s.sound = gi.soundindex ("makron/spine.wav"); + gi.linkentity (ent); +} + + +// +// death +// + +void makron_dead (edict_t *self) +{ + VectorSet (self->mins, -60, -60, 0); + VectorSet (self->maxs, 60, 60, 72); + self->movetype = MOVETYPE_TOSS; + self->svflags |= SVF_DEADMONSTER; + self->nextthink = 0; + gi.linkentity (self); +} + + +void makron_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + edict_t *tempent; + + int n; + + self->s.sound = 0; + // check for gib + if (self->health <= self->gib_health) + { + gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0); + for (n= 0; n < 1 /*4*/; n++) + ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); + for (n= 0; n < 4; n++) + ThrowGib (self, "models/objects/gibs/sm_metal/tris.md2", damage, GIB_METALLIC); + ThrowHead (self, "models/objects/gibs/gear/tris.md2", damage, GIB_METALLIC); + self->deadflag = DEAD_DEAD; + return; + } + + if (self->deadflag == DEAD_DEAD) + return; + +// regular death + gi.sound (self, CHAN_VOICE, sound_death, 1, ATTN_NONE, 0); + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_YES; + + tempent = G_Spawn(); + VectorCopy (self->s.origin, tempent->s.origin); + VectorCopy (self->s.angles, tempent->s.angles); + tempent->s.origin[1] -= 84; + makron_torso (tempent); + + self->monsterinfo.currentmove = &makron_move_death2; + +} + +qboolean Makron_CheckAttack (edict_t *self) +{ + vec3_t spot1, spot2; + vec3_t temp; + float chance; + trace_t tr; + qboolean enemy_infront; + int enemy_range; + float enemy_yaw; + + if (self->enemy->health > 0) + { + // see if any entities are in the way of the shot + VectorCopy (self->s.origin, spot1); + spot1[2] += self->viewheight; + VectorCopy (self->enemy->s.origin, spot2); + spot2[2] += self->enemy->viewheight; + + tr = gi.trace (spot1, NULL, NULL, spot2, self, CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_SLIME|CONTENTS_LAVA); + + // do we have a clear shot? + if (tr.ent != self->enemy) + return false; + } + + enemy_infront = infront(self, self->enemy); + enemy_range = range(self, self->enemy); + VectorSubtract (self->enemy->s.origin, self->s.origin, temp); + enemy_yaw = vectoyaw(temp); + + self->ideal_yaw = enemy_yaw; + + + // melee attack + if (enemy_range == RANGE_MELEE) + { + if (self->monsterinfo.melee) + self->monsterinfo.attack_state = AS_MELEE; + else + self->monsterinfo.attack_state = AS_MISSILE; + return true; + } + +// missile attack + if (!self->monsterinfo.attack) + return false; + + if (level.time < self->monsterinfo.attack_finished) + return false; + + if (enemy_range == RANGE_FAR) + return false; + + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + { + chance = 0.4; + } + else if (enemy_range == RANGE_MELEE) + { + chance = 0.8; + } + else if (enemy_range == RANGE_NEAR) + { + chance = 0.4; + } + else if (enemy_range == RANGE_MID) + { + chance = 0.2; + } + else + { + return false; + } + + if (random () < chance) + { + self->monsterinfo.attack_state = AS_MISSILE; + self->monsterinfo.attack_finished = level.time + 2*random(); + return true; + } + + if (self->flags & FL_FLY) + { + if (random() < 0.3) + self->monsterinfo.attack_state = AS_SLIDING; + else + self->monsterinfo.attack_state = AS_STRAIGHT; + } + + return false; +} + + +// +// monster_makron +// + +void MakronPrecache (void) +{ + sound_pain4 = gi.soundindex ("makron/pain3.wav"); + sound_pain5 = gi.soundindex ("makron/pain2.wav"); + sound_pain6 = gi.soundindex ("makron/pain1.wav"); + sound_death = gi.soundindex ("makron/death.wav"); + sound_step_left = gi.soundindex ("makron/step1.wav"); + sound_step_right = gi.soundindex ("makron/step2.wav"); + sound_attack_bfg = gi.soundindex ("makron/bfg_fire.wav"); + sound_brainsplorch = gi.soundindex ("makron/brain1.wav"); + sound_prerailgun = gi.soundindex ("makron/rail_up.wav"); + sound_popup = gi.soundindex ("makron/popup.wav"); + sound_taunt1 = gi.soundindex ("makron/voice4.wav"); + sound_taunt2 = gi.soundindex ("makron/voice3.wav"); + sound_taunt3 = gi.soundindex ("makron/voice.wav"); + sound_hit = gi.soundindex ("makron/bhit.wav"); + + gi.modelindex ("models/monsters/boss3/rider/tris.md2"); +} + +/*QUAKED monster_makron (1 .5 0) (-30 -30 0) (30 30 90) Ambush Trigger_Spawn Sight +*/ +void SP_monster_makron (edict_t *self) +{ + if (deathmatch->value) + { + G_FreeEdict (self); + return; + } + + MakronPrecache (); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex ("models/monsters/boss3/rider/tris.md2"); + VectorSet (self->mins, -30, -30, 0); + VectorSet (self->maxs, 30, 30, 90); + + self->health = 3000; + self->gib_health = -2000; + self->mass = 500; + + self->pain = makron_pain; + self->die = makron_die; + self->monsterinfo.stand = makron_stand; + self->monsterinfo.walk = makron_walk; + self->monsterinfo.run = makron_run; + self->monsterinfo.dodge = NULL; + self->monsterinfo.attack = makron_attack; + self->monsterinfo.melee = NULL; + self->monsterinfo.sight = makron_sight; + self->monsterinfo.checkattack = Makron_CheckAttack; + + gi.linkentity (self); + +// self->monsterinfo.currentmove = &makron_move_stand; + self->monsterinfo.currentmove = &makron_move_sight; + self->monsterinfo.scale = MODEL_SCALE; + + walkmonster_start(self); +} + + +/* +================= +MakronSpawn + +================= +*/ +void MakronSpawn (edict_t *self) +{ + vec3_t vec; + edict_t *player; + + SP_monster_makron (self); + + // jump at player + player = level.sight_client; + if (!player) + return; + + VectorSubtract (player->s.origin, self->s.origin, vec); + self->s.angles[YAW] = vectoyaw(vec); + VectorNormalize (vec); + VectorMA (vec3_origin, 400, vec, self->velocity); + self->velocity[2] = 200; + self->groundentity = NULL; +} + +/* +================= +MakronToss + +Jorg is just about dead, so set up to launch Makron out +================= +*/ +void MakronToss (edict_t *self) +{ + edict_t *ent; + + ent = G_Spawn (); + ent->nextthink = level.time + 0.8; + ent->think = MakronSpawn; + ent->target = self->target; + VectorCopy (self->s.origin, ent->s.origin); +} diff --git a/original/xatrix/m_boss32.h b/original/xatrix/m_boss32.h new file mode 100644 index 0000000..097831a --- /dev/null +++ b/original/xatrix/m_boss32.h @@ -0,0 +1,499 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// G:\quake2\baseq2\models/monsters/boss3/rider + +// This file generated by ModelGen - Do NOT Modify + +#define FRAME_attak101 0 +#define FRAME_attak102 1 +#define FRAME_attak103 2 +#define FRAME_attak104 3 +#define FRAME_attak105 4 +#define FRAME_attak106 5 +#define FRAME_attak107 6 +#define FRAME_attak108 7 +#define FRAME_attak109 8 +#define FRAME_attak110 9 +#define FRAME_attak111 10 +#define FRAME_attak112 11 +#define FRAME_attak113 12 +#define FRAME_attak114 13 +#define FRAME_attak115 14 +#define FRAME_attak116 15 +#define FRAME_attak117 16 +#define FRAME_attak118 17 +#define FRAME_attak201 18 +#define FRAME_attak202 19 +#define FRAME_attak203 20 +#define FRAME_attak204 21 +#define FRAME_attak205 22 +#define FRAME_attak206 23 +#define FRAME_attak207 24 +#define FRAME_attak208 25 +#define FRAME_attak209 26 +#define FRAME_attak210 27 +#define FRAME_attak211 28 +#define FRAME_attak212 29 +#define FRAME_attak213 30 +#define FRAME_death01 31 +#define FRAME_death02 32 +#define FRAME_death03 33 +#define FRAME_death04 34 +#define FRAME_death05 35 +#define FRAME_death06 36 +#define FRAME_death07 37 +#define FRAME_death08 38 +#define FRAME_death09 39 +#define FRAME_death10 40 +#define FRAME_death11 41 +#define FRAME_death12 42 +#define FRAME_death13 43 +#define FRAME_death14 44 +#define FRAME_death15 45 +#define FRAME_death16 46 +#define FRAME_death17 47 +#define FRAME_death18 48 +#define FRAME_death19 49 +#define FRAME_death20 50 +#define FRAME_death21 51 +#define FRAME_death22 52 +#define FRAME_death23 53 +#define FRAME_death24 54 +#define FRAME_death25 55 +#define FRAME_death26 56 +#define FRAME_death27 57 +#define FRAME_death28 58 +#define FRAME_death29 59 +#define FRAME_death30 60 +#define FRAME_death31 61 +#define FRAME_death32 62 +#define FRAME_death33 63 +#define FRAME_death34 64 +#define FRAME_death35 65 +#define FRAME_death36 66 +#define FRAME_death37 67 +#define FRAME_death38 68 +#define FRAME_death39 69 +#define FRAME_death40 70 +#define FRAME_death41 71 +#define FRAME_death42 72 +#define FRAME_death43 73 +#define FRAME_death44 74 +#define FRAME_death45 75 +#define FRAME_death46 76 +#define FRAME_death47 77 +#define FRAME_death48 78 +#define FRAME_death49 79 +#define FRAME_death50 80 +#define FRAME_pain101 81 +#define FRAME_pain102 82 +#define FRAME_pain103 83 +#define FRAME_pain201 84 +#define FRAME_pain202 85 +#define FRAME_pain203 86 +#define FRAME_pain301 87 +#define FRAME_pain302 88 +#define FRAME_pain303 89 +#define FRAME_pain304 90 +#define FRAME_pain305 91 +#define FRAME_pain306 92 +#define FRAME_pain307 93 +#define FRAME_pain308 94 +#define FRAME_pain309 95 +#define FRAME_pain310 96 +#define FRAME_pain311 97 +#define FRAME_pain312 98 +#define FRAME_pain313 99 +#define FRAME_pain314 100 +#define FRAME_pain315 101 +#define FRAME_pain316 102 +#define FRAME_pain317 103 +#define FRAME_pain318 104 +#define FRAME_pain319 105 +#define FRAME_pain320 106 +#define FRAME_pain321 107 +#define FRAME_pain322 108 +#define FRAME_pain323 109 +#define FRAME_pain324 110 +#define FRAME_pain325 111 +#define FRAME_stand01 112 +#define FRAME_stand02 113 +#define FRAME_stand03 114 +#define FRAME_stand04 115 +#define FRAME_stand05 116 +#define FRAME_stand06 117 +#define FRAME_stand07 118 +#define FRAME_stand08 119 +#define FRAME_stand09 120 +#define FRAME_stand10 121 +#define FRAME_stand11 122 +#define FRAME_stand12 123 +#define FRAME_stand13 124 +#define FRAME_stand14 125 +#define FRAME_stand15 126 +#define FRAME_stand16 127 +#define FRAME_stand17 128 +#define FRAME_stand18 129 +#define FRAME_stand19 130 +#define FRAME_stand20 131 +#define FRAME_stand21 132 +#define FRAME_stand22 133 +#define FRAME_stand23 134 +#define FRAME_stand24 135 +#define FRAME_stand25 136 +#define FRAME_stand26 137 +#define FRAME_stand27 138 +#define FRAME_stand28 139 +#define FRAME_stand29 140 +#define FRAME_stand30 141 +#define FRAME_stand31 142 +#define FRAME_stand32 143 +#define FRAME_stand33 144 +#define FRAME_stand34 145 +#define FRAME_stand35 146 +#define FRAME_stand36 147 +#define FRAME_stand37 148 +#define FRAME_stand38 149 +#define FRAME_stand39 150 +#define FRAME_stand40 151 +#define FRAME_stand41 152 +#define FRAME_stand42 153 +#define FRAME_stand43 154 +#define FRAME_stand44 155 +#define FRAME_stand45 156 +#define FRAME_stand46 157 +#define FRAME_stand47 158 +#define FRAME_stand48 159 +#define FRAME_stand49 160 +#define FRAME_stand50 161 +#define FRAME_stand51 162 +#define FRAME_walk01 163 +#define FRAME_walk02 164 +#define FRAME_walk03 165 +#define FRAME_walk04 166 +#define FRAME_walk05 167 +#define FRAME_walk06 168 +#define FRAME_walk07 169 +#define FRAME_walk08 170 +#define FRAME_walk09 171 +#define FRAME_walk10 172 +#define FRAME_walk11 173 +#define FRAME_walk12 174 +#define FRAME_walk13 175 +#define FRAME_walk14 176 +#define FRAME_walk15 177 +#define FRAME_walk16 178 +#define FRAME_walk17 179 +#define FRAME_walk18 180 +#define FRAME_walk19 181 +#define FRAME_walk20 182 +#define FRAME_walk21 183 +#define FRAME_walk22 184 +#define FRAME_walk23 185 +#define FRAME_walk24 186 +#define FRAME_walk25 187 +#define FRAME_active01 188 +#define FRAME_active02 189 +#define FRAME_active03 190 +#define FRAME_active04 191 +#define FRAME_active05 192 +#define FRAME_active06 193 +#define FRAME_active07 194 +#define FRAME_active08 195 +#define FRAME_active09 196 +#define FRAME_active10 197 +#define FRAME_active11 198 +#define FRAME_active12 199 +#define FRAME_active13 200 +#define FRAME_attak301 201 +#define FRAME_attak302 202 +#define FRAME_attak303 203 +#define FRAME_attak304 204 +#define FRAME_attak305 205 +#define FRAME_attak306 206 +#define FRAME_attak307 207 +#define FRAME_attak308 208 +#define FRAME_attak401 209 +#define FRAME_attak402 210 +#define FRAME_attak403 211 +#define FRAME_attak404 212 +#define FRAME_attak405 213 +#define FRAME_attak406 214 +#define FRAME_attak407 215 +#define FRAME_attak408 216 +#define FRAME_attak409 217 +#define FRAME_attak410 218 +#define FRAME_attak411 219 +#define FRAME_attak412 220 +#define FRAME_attak413 221 +#define FRAME_attak414 222 +#define FRAME_attak415 223 +#define FRAME_attak416 224 +#define FRAME_attak417 225 +#define FRAME_attak418 226 +#define FRAME_attak419 227 +#define FRAME_attak420 228 +#define FRAME_attak421 229 +#define FRAME_attak422 230 +#define FRAME_attak423 231 +#define FRAME_attak424 232 +#define FRAME_attak425 233 +#define FRAME_attak426 234 +#define FRAME_attak501 235 +#define FRAME_attak502 236 +#define FRAME_attak503 237 +#define FRAME_attak504 238 +#define FRAME_attak505 239 +#define FRAME_attak506 240 +#define FRAME_attak507 241 +#define FRAME_attak508 242 +#define FRAME_attak509 243 +#define FRAME_attak510 244 +#define FRAME_attak511 245 +#define FRAME_attak512 246 +#define FRAME_attak513 247 +#define FRAME_attak514 248 +#define FRAME_attak515 249 +#define FRAME_attak516 250 +#define FRAME_death201 251 +#define FRAME_death202 252 +#define FRAME_death203 253 +#define FRAME_death204 254 +#define FRAME_death205 255 +#define FRAME_death206 256 +#define FRAME_death207 257 +#define FRAME_death208 258 +#define FRAME_death209 259 +#define FRAME_death210 260 +#define FRAME_death211 261 +#define FRAME_death212 262 +#define FRAME_death213 263 +#define FRAME_death214 264 +#define FRAME_death215 265 +#define FRAME_death216 266 +#define FRAME_death217 267 +#define FRAME_death218 268 +#define FRAME_death219 269 +#define FRAME_death220 270 +#define FRAME_death221 271 +#define FRAME_death222 272 +#define FRAME_death223 273 +#define FRAME_death224 274 +#define FRAME_death225 275 +#define FRAME_death226 276 +#define FRAME_death227 277 +#define FRAME_death228 278 +#define FRAME_death229 279 +#define FRAME_death230 280 +#define FRAME_death231 281 +#define FRAME_death232 282 +#define FRAME_death233 283 +#define FRAME_death234 284 +#define FRAME_death235 285 +#define FRAME_death236 286 +#define FRAME_death237 287 +#define FRAME_death238 288 +#define FRAME_death239 289 +#define FRAME_death240 290 +#define FRAME_death241 291 +#define FRAME_death242 292 +#define FRAME_death243 293 +#define FRAME_death244 294 +#define FRAME_death245 295 +#define FRAME_death246 296 +#define FRAME_death247 297 +#define FRAME_death248 298 +#define FRAME_death249 299 +#define FRAME_death250 300 +#define FRAME_death251 301 +#define FRAME_death252 302 +#define FRAME_death253 303 +#define FRAME_death254 304 +#define FRAME_death255 305 +#define FRAME_death256 306 +#define FRAME_death257 307 +#define FRAME_death258 308 +#define FRAME_death259 309 +#define FRAME_death260 310 +#define FRAME_death261 311 +#define FRAME_death262 312 +#define FRAME_death263 313 +#define FRAME_death264 314 +#define FRAME_death265 315 +#define FRAME_death266 316 +#define FRAME_death267 317 +#define FRAME_death268 318 +#define FRAME_death269 319 +#define FRAME_death270 320 +#define FRAME_death271 321 +#define FRAME_death272 322 +#define FRAME_death273 323 +#define FRAME_death274 324 +#define FRAME_death275 325 +#define FRAME_death276 326 +#define FRAME_death277 327 +#define FRAME_death278 328 +#define FRAME_death279 329 +#define FRAME_death280 330 +#define FRAME_death281 331 +#define FRAME_death282 332 +#define FRAME_death283 333 +#define FRAME_death284 334 +#define FRAME_death285 335 +#define FRAME_death286 336 +#define FRAME_death287 337 +#define FRAME_death288 338 +#define FRAME_death289 339 +#define FRAME_death290 340 +#define FRAME_death291 341 +#define FRAME_death292 342 +#define FRAME_death293 343 +#define FRAME_death294 344 +#define FRAME_death295 345 +#define FRAME_death301 346 +#define FRAME_death302 347 +#define FRAME_death303 348 +#define FRAME_death304 349 +#define FRAME_death305 350 +#define FRAME_death306 351 +#define FRAME_death307 352 +#define FRAME_death308 353 +#define FRAME_death309 354 +#define FRAME_death310 355 +#define FRAME_death311 356 +#define FRAME_death312 357 +#define FRAME_death313 358 +#define FRAME_death314 359 +#define FRAME_death315 360 +#define FRAME_death316 361 +#define FRAME_death317 362 +#define FRAME_death318 363 +#define FRAME_death319 364 +#define FRAME_death320 365 +#define FRAME_jump01 366 +#define FRAME_jump02 367 +#define FRAME_jump03 368 +#define FRAME_jump04 369 +#define FRAME_jump05 370 +#define FRAME_jump06 371 +#define FRAME_jump07 372 +#define FRAME_jump08 373 +#define FRAME_jump09 374 +#define FRAME_jump10 375 +#define FRAME_jump11 376 +#define FRAME_jump12 377 +#define FRAME_jump13 378 +#define FRAME_pain401 379 +#define FRAME_pain402 380 +#define FRAME_pain403 381 +#define FRAME_pain404 382 +#define FRAME_pain501 383 +#define FRAME_pain502 384 +#define FRAME_pain503 385 +#define FRAME_pain504 386 +#define FRAME_pain601 387 +#define FRAME_pain602 388 +#define FRAME_pain603 389 +#define FRAME_pain604 390 +#define FRAME_pain605 391 +#define FRAME_pain606 392 +#define FRAME_pain607 393 +#define FRAME_pain608 394 +#define FRAME_pain609 395 +#define FRAME_pain610 396 +#define FRAME_pain611 397 +#define FRAME_pain612 398 +#define FRAME_pain613 399 +#define FRAME_pain614 400 +#define FRAME_pain615 401 +#define FRAME_pain616 402 +#define FRAME_pain617 403 +#define FRAME_pain618 404 +#define FRAME_pain619 405 +#define FRAME_pain620 406 +#define FRAME_pain621 407 +#define FRAME_pain622 408 +#define FRAME_pain623 409 +#define FRAME_pain624 410 +#define FRAME_pain625 411 +#define FRAME_pain626 412 +#define FRAME_pain627 413 +#define FRAME_stand201 414 +#define FRAME_stand202 415 +#define FRAME_stand203 416 +#define FRAME_stand204 417 +#define FRAME_stand205 418 +#define FRAME_stand206 419 +#define FRAME_stand207 420 +#define FRAME_stand208 421 +#define FRAME_stand209 422 +#define FRAME_stand210 423 +#define FRAME_stand211 424 +#define FRAME_stand212 425 +#define FRAME_stand213 426 +#define FRAME_stand214 427 +#define FRAME_stand215 428 +#define FRAME_stand216 429 +#define FRAME_stand217 430 +#define FRAME_stand218 431 +#define FRAME_stand219 432 +#define FRAME_stand220 433 +#define FRAME_stand221 434 +#define FRAME_stand222 435 +#define FRAME_stand223 436 +#define FRAME_stand224 437 +#define FRAME_stand225 438 +#define FRAME_stand226 439 +#define FRAME_stand227 440 +#define FRAME_stand228 441 +#define FRAME_stand229 442 +#define FRAME_stand230 443 +#define FRAME_stand231 444 +#define FRAME_stand232 445 +#define FRAME_stand233 446 +#define FRAME_stand234 447 +#define FRAME_stand235 448 +#define FRAME_stand236 449 +#define FRAME_stand237 450 +#define FRAME_stand238 451 +#define FRAME_stand239 452 +#define FRAME_stand240 453 +#define FRAME_stand241 454 +#define FRAME_stand242 455 +#define FRAME_stand243 456 +#define FRAME_stand244 457 +#define FRAME_stand245 458 +#define FRAME_stand246 459 +#define FRAME_stand247 460 +#define FRAME_stand248 461 +#define FRAME_stand249 462 +#define FRAME_stand250 463 +#define FRAME_stand251 464 +#define FRAME_stand252 465 +#define FRAME_stand253 466 +#define FRAME_stand254 467 +#define FRAME_stand255 468 +#define FRAME_stand256 469 +#define FRAME_stand257 470 +#define FRAME_stand258 471 +#define FRAME_stand259 472 +#define FRAME_stand260 473 +#define FRAME_walk201 474 +#define FRAME_walk202 475 +#define FRAME_walk203 476 +#define FRAME_walk204 477 +#define FRAME_walk205 478 +#define FRAME_walk206 479 +#define FRAME_walk207 480 +#define FRAME_walk208 481 +#define FRAME_walk209 482 +#define FRAME_walk210 483 +#define FRAME_walk211 484 +#define FRAME_walk212 485 +#define FRAME_walk213 486 +#define FRAME_walk214 487 +#define FRAME_walk215 488 +#define FRAME_walk216 489 +#define FRAME_walk217 490 + +#define MODEL_SCALE 1.000000 diff --git a/original/xatrix/m_boss5.c b/original/xatrix/m_boss5.c new file mode 100644 index 0000000..9377c51 --- /dev/null +++ b/original/xatrix/m_boss5.c @@ -0,0 +1,707 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +/* +============================================================================== + +boss5 + +============================================================================== +*/ + +#include "g_local.h" +#include "m_supertank.h" + +qboolean visible (edict_t *self, edict_t *other); + +static int sound_pain1; +static int sound_pain2; +static int sound_pain3; +static int sound_death; +static int sound_search1; +static int sound_search2; + +static int tread_sound; + +void BossExplode2 (edict_t *self); + +void TreadSound2 (edict_t *self) +{ + gi.sound (self, CHAN_VOICE, tread_sound, 1, ATTN_NORM, 0); +} + +void boss5_search (edict_t *self) +{ + if (random() < 0.5) + gi.sound (self, CHAN_VOICE, sound_search1, 1, ATTN_NORM, 0); + else + gi.sound (self, CHAN_VOICE, sound_search2, 1, ATTN_NORM, 0); +} + + +void boss5_dead (edict_t *self); +void boss5Rocket (edict_t *self); +void boss5MachineGun (edict_t *self); +void boss5_reattack1(edict_t *self); + + +// +// stand +// + +mframe_t boss5_frames_stand []= +{ + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL +}; +mmove_t boss5_move_stand = {FRAME_stand_1, FRAME_stand_60, boss5_frames_stand, NULL}; + +void boss5_stand (edict_t *self) +{ + self->monsterinfo.currentmove = &boss5_move_stand; +} + + +mframe_t boss5_frames_run [] = +{ + ai_run, 12, TreadSound2, + ai_run, 12, NULL, + ai_run, 12, NULL, + ai_run, 12, NULL, + ai_run, 12, NULL, + ai_run, 12, NULL, + ai_run, 12, NULL, + ai_run, 12, NULL, + ai_run, 12, NULL, + ai_run, 12, NULL, + ai_run, 12, NULL, + ai_run, 12, NULL, + ai_run, 12, NULL, + ai_run, 12, NULL, + ai_run, 12, NULL, + ai_run, 12, NULL, + ai_run, 12, NULL, + ai_run, 12, NULL +}; +mmove_t boss5_move_run = {FRAME_forwrd_1, FRAME_forwrd_18, boss5_frames_run, NULL}; + +// +// walk +// + + +mframe_t boss5_frames_forward [] = +{ + ai_walk, 4, TreadSound2, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL +}; +mmove_t boss5_move_forward = {FRAME_forwrd_1, FRAME_forwrd_18, boss5_frames_forward, NULL}; + +void boss5_forward (edict_t *self) +{ + self->monsterinfo.currentmove = &boss5_move_forward; +} + +void boss5_walk (edict_t *self) +{ + self->monsterinfo.currentmove = &boss5_move_forward; +} + +void boss5_run (edict_t *self) +{ + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + self->monsterinfo.currentmove = &boss5_move_stand; + else + self->monsterinfo.currentmove = &boss5_move_run; +} + +mframe_t boss5_frames_turn_right [] = +{ + ai_move, 0, TreadSound2, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t boss5_move_turn_right = {FRAME_right_1, FRAME_right_18, boss5_frames_turn_right, boss5_run}; + +mframe_t boss5_frames_turn_left [] = +{ + ai_move, 0, TreadSound2, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t boss5_move_turn_left = {FRAME_left_1, FRAME_left_18, boss5_frames_turn_left, boss5_run}; + + +mframe_t boss5_frames_pain3 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t boss5_move_pain3 = {FRAME_pain3_9, FRAME_pain3_12, boss5_frames_pain3, boss5_run}; + +mframe_t boss5_frames_pain2 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t boss5_move_pain2 = {FRAME_pain2_5, FRAME_pain2_8, boss5_frames_pain2, boss5_run}; + +mframe_t boss5_frames_pain1 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t boss5_move_pain1 = {FRAME_pain1_1, FRAME_pain1_4, boss5_frames_pain1, boss5_run}; + +mframe_t boss5_frames_death1 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, BossExplode2 +}; +mmove_t boss5_move_death = {FRAME_death_1, FRAME_death_24, boss5_frames_death1, boss5_dead}; + +mframe_t boss5_frames_backward[] = +{ + ai_walk, 0, TreadSound2, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL +}; +mmove_t boss5_move_backward = {FRAME_backwd_1, FRAME_backwd_18, boss5_frames_backward, NULL}; + +mframe_t boss5_frames_attack4[]= +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t boss5_move_attack4 = {FRAME_attak4_1, FRAME_attak4_6, boss5_frames_attack4, boss5_run}; + +mframe_t boss5_frames_attack3[]= +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t boss5_move_attack3 = {FRAME_attak3_1, FRAME_attak3_27, boss5_frames_attack3, boss5_run}; + +mframe_t boss5_frames_attack2[]= +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, boss5Rocket, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, boss5Rocket, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, boss5Rocket, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t boss5_move_attack2 = {FRAME_attak2_1, FRAME_attak2_27, boss5_frames_attack2, boss5_run}; + +mframe_t boss5_frames_attack1[]= +{ + ai_charge, 0, boss5MachineGun, + ai_charge, 0, boss5MachineGun, + ai_charge, 0, boss5MachineGun, + ai_charge, 0, boss5MachineGun, + ai_charge, 0, boss5MachineGun, + ai_charge, 0, boss5MachineGun, + +}; +mmove_t boss5_move_attack1 = {FRAME_attak1_1, FRAME_attak1_6, boss5_frames_attack1, boss5_reattack1}; + +mframe_t boss5_frames_end_attack1[]= +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t boss5_move_end_attack1 = {FRAME_attak1_7, FRAME_attak1_20, boss5_frames_end_attack1, boss5_run}; + + +void boss5_reattack1(edict_t *self) +{ + if (visible(self, self->enemy)) + if (random() < 0.9) + self->monsterinfo.currentmove = &boss5_move_attack1; + else + self->monsterinfo.currentmove = &boss5_move_end_attack1; + else + self->monsterinfo.currentmove = &boss5_move_end_attack1; +} + +void boss5_pain (edict_t *self, edict_t *other, float kick, int damage) +{ + + if (self->health < (self->max_health / 2)) + self->s.skinnum = 1; + + if (level.time < self->pain_debounce_time) + return; + + // Lessen the chance of him going into his pain frames + if (damage <=25) + if (random()<0.2) + return; + + // Don't go into pain if he's firing his rockets + if (skill->value >= 2) + if ( (self->s.frame >= FRAME_attak2_1) && (self->s.frame <= FRAME_attak2_14) ) + return; + + self->pain_debounce_time = level.time + 3; + + if (damage <= 10) + { + gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM,0); + self->monsterinfo.currentmove = &boss5_move_pain1; + } + else if (damage <= 25) + { + gi.sound (self, CHAN_VOICE, sound_pain3, 1, ATTN_NORM,0); + self->monsterinfo.currentmove = &boss5_move_pain2; + } + else + { + gi.sound (self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM,0); + self->monsterinfo.currentmove = &boss5_move_pain3; + } +}; + + +void boss5Rocket (edict_t *self) +{ + vec3_t forward, right; + vec3_t start; + vec3_t dir; + vec3_t vec; + int flash_number; + + if (self->s.frame == FRAME_attak2_8) + flash_number = MZ2_SUPERTANK_ROCKET_1; + else if (self->s.frame == FRAME_attak2_11) + flash_number = MZ2_SUPERTANK_ROCKET_2; + else // (self->s.frame == FRAME_attak2_14) + flash_number = MZ2_SUPERTANK_ROCKET_3; + + AngleVectors (self->s.angles, forward, right, NULL); + G_ProjectSource (self->s.origin, monster_flash_offset[flash_number], forward, right, start); + + VectorCopy (self->enemy->s.origin, vec); + vec[2] += self->enemy->viewheight; + VectorSubtract (vec, start, dir); + VectorNormalize (dir); + + monster_fire_rocket (self, start, dir, 50, 500, flash_number); + +} + +void boss5MachineGun (edict_t *self) +{ + vec3_t dir; + vec3_t vec; + vec3_t start; + vec3_t forward, right; + int flash_number; + + flash_number = MZ2_SUPERTANK_MACHINEGUN_1 + (self->s.frame - FRAME_attak1_1); + + //FIXME!!! + dir[0] = 0; + dir[1] = self->s.angles[1]; + dir[2] = 0; + + AngleVectors (dir, forward, right, NULL); + G_ProjectSource (self->s.origin, monster_flash_offset[flash_number], forward, right, start); + + if (self->enemy) + { + VectorCopy (self->enemy->s.origin, vec); + VectorMA (vec, 0, self->enemy->velocity, vec); + vec[2] += self->enemy->viewheight; + VectorSubtract (vec, start, forward); + VectorNormalize (forward); + } + + monster_fire_bullet (self, start, forward, 6, 4, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, flash_number); +} + + +void boss5_attack(edict_t *self) +{ + vec3_t vec; + float range; + //float r; + + VectorSubtract (self->enemy->s.origin, self->s.origin, vec); + range = VectorLength (vec); + + //r = random(); + + // Attack 1 == Chaingun + // Attack 2 == Rocket Launcher + + if (range <= 160) + { + self->monsterinfo.currentmove = &boss5_move_attack1; + } + else + { // fire rockets more often at distance + if (random() < 0.3) + self->monsterinfo.currentmove = &boss5_move_attack1; + else + self->monsterinfo.currentmove = &boss5_move_attack2; + } +} + + +// +// death +// + +void boss5_dead (edict_t *self) +{ + /* + VectorSet (self->mins, -16, -16, -24); + VectorSet (self->maxs, 16, 16, -8); + */ + VectorSet (self->mins, -60, -60, 0); + VectorSet (self->maxs, 60, 60, 72); + self->movetype = MOVETYPE_TOSS; + self->svflags |= SVF_DEADMONSTER; + self->nextthink = 0; + gi.linkentity (self); +} + + +void BossExplode2 (edict_t *self) +{ + vec3_t org; + int n; + + self->think = BossExplode2; + VectorCopy (self->s.origin, org); + org[2] += 24 + (rand()&15); + switch (self->count++) + { + case 0: + org[0] -= 24; + org[1] -= 24; + break; + case 1: + org[0] += 24; + org[1] += 24; + break; + case 2: + org[0] += 24; + org[1] -= 24; + break; + case 3: + org[0] -= 24; + org[1] += 24; + break; + case 4: + org[0] -= 48; + org[1] -= 48; + break; + case 5: + org[0] += 48; + org[1] += 48; + break; + case 6: + org[0] -= 48; + org[1] += 48; + break; + case 7: + org[0] += 48; + org[1] -= 48; + break; + case 8: + self->s.sound = 0; + for (n= 0; n < 4; n++) + ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", 500, GIB_ORGANIC); + for (n= 0; n < 8; n++) + ThrowGib (self, "models/objects/gibs/sm_metal/tris.md2", 500, GIB_METALLIC); + ThrowGib (self, "models/objects/gibs/chest/tris.md2", 500, GIB_ORGANIC); + ThrowHead (self, "models/objects/gibs/gear/tris.md2", 500, GIB_METALLIC); + self->deadflag = DEAD_DEAD; + return; + } + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_EXPLOSION1); + gi.WritePosition (org); + gi.multicast (self->s.origin, MULTICAST_PVS); + + self->nextthink = level.time + 0.1; +} + + +void boss5_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + gi.sound (self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0); + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_NO; + self->count = 0; + self->monsterinfo.currentmove = &boss5_move_death; +} + +// +// monster_boss5 +// + +/*QUAKED monster_boss5 (1 .5 0) (-64 -64 0) (64 64 72) Ambush Trigger_Spawn Sight +*/ +void SP_monster_boss5 (edict_t *self) +{ + if (deathmatch->value) + { + G_FreeEdict (self); + return; + } + + sound_pain1 = gi.soundindex ("bosstank/btkpain1.wav"); + sound_pain2 = gi.soundindex ("bosstank/btkpain2.wav"); + sound_pain3 = gi.soundindex ("bosstank/btkpain3.wav"); + sound_death = gi.soundindex ("bosstank/btkdeth1.wav"); + sound_search1 = gi.soundindex ("bosstank/btkunqv1.wav"); + sound_search2 = gi.soundindex ("bosstank/btkunqv2.wav"); + +// self->s.sound = gi.soundindex ("bosstank/btkengn1.wav"); + tread_sound = gi.soundindex ("bosstank/btkengn1.wav"); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex ("models/monsters/boss5/tris.md2"); + VectorSet (self->mins, -64, -64, 0); + VectorSet (self->maxs, 64, 64, 112); + + self->health = 1500; + self->gib_health = -500; + self->mass = 800; + + self->pain = boss5_pain; + self->die = boss5_die; + self->monsterinfo.stand = boss5_stand; + self->monsterinfo.walk = boss5_walk; + self->monsterinfo.run = boss5_run; + self->monsterinfo.dodge = NULL; + self->monsterinfo.attack = boss5_attack; + self->monsterinfo.search = boss5_search; + self->monsterinfo.melee = NULL; + self->monsterinfo.sight = NULL; + + gi.linkentity (self); + + self->monsterinfo.currentmove = &boss5_move_stand; + self->monsterinfo.scale = MODEL_SCALE; + + // RAFAEL + self->monsterinfo.power_armor_type = POWER_ARMOR_SHIELD; + self->monsterinfo.power_armor_power = 400; + + + walkmonster_start(self); +} diff --git a/original/xatrix/m_brain.c b/original/xatrix/m_brain.c new file mode 100644 index 0000000..3496a8f --- /dev/null +++ b/original/xatrix/m_brain.c @@ -0,0 +1,895 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +/* +============================================================================== + +brain + +============================================================================== +*/ + +#include "g_local.h" +#include "m_brain.h" + + +static int sound_chest_open; +static int sound_tentacles_extend; +static int sound_tentacles_retract; +static int sound_death; +static int sound_idle1; +static int sound_idle2; +static int sound_idle3; +static int sound_pain1; +static int sound_pain2; +static int sound_sight; +static int sound_search; +static int sound_melee1; +static int sound_melee2; +static int sound_melee3; + + +void brain_sight (edict_t *self, edict_t *other) +{ + gi.sound (self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); +} + +void brain_search (edict_t *self) +{ + gi.sound (self, CHAN_VOICE, sound_search, 1, ATTN_NORM, 0); +} + + +void brain_run (edict_t *self); +void brain_dead (edict_t *self); + + +// +// STAND +// + +mframe_t brain_frames_stand [] = +{ + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL +}; +mmove_t brain_move_stand = {FRAME_stand01, FRAME_stand30, brain_frames_stand, NULL}; + +void brain_stand (edict_t *self) +{ + self->monsterinfo.currentmove = &brain_move_stand; +} + + +// +// IDLE +// + +mframe_t brain_frames_idle [] = +{ + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL +}; +mmove_t brain_move_idle = {FRAME_stand31, FRAME_stand60, brain_frames_idle, brain_stand}; + +void brain_idle (edict_t *self) +{ + gi.sound (self, CHAN_AUTO, sound_idle3, 1, ATTN_IDLE, 0); + self->monsterinfo.currentmove = &brain_move_idle; +} + + +// +// WALK +// +mframe_t brain_frames_walk1 [] = +{ + ai_walk, 7, NULL, + ai_walk, 2, NULL, + ai_walk, 3, NULL, + ai_walk, 3, NULL, + ai_walk, 1, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 9, NULL, + ai_walk, -4, NULL, + ai_walk, -1, NULL, + ai_walk, 2, NULL +}; +mmove_t brain_move_walk1 = {FRAME_walk101, FRAME_walk111, brain_frames_walk1, NULL}; + +// walk2 is FUBAR, do not use +#if 0 +void brain_walk2_cycle (edict_t *self) +{ + if (random() > 0.1) + self->monsterinfo.nextframe = FRAME_walk220; +} + +mframe_t brain_frames_walk2 [] = +{ + ai_walk, 3, NULL, + ai_walk, -2, NULL, + ai_walk, -4, NULL, + ai_walk, -3, NULL, + ai_walk, 0, NULL, + ai_walk, 1, NULL, + ai_walk, 12, NULL, + ai_walk, 0, NULL, + ai_walk, -3, NULL, + ai_walk, 0, NULL, + + ai_walk, -2, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 1, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 10, NULL, // Cycle Start + + ai_walk, -1, NULL, + ai_walk, 7, NULL, + ai_walk, 0, NULL, + ai_walk, 3, NULL, + ai_walk, -3, NULL, + ai_walk, 2, NULL, + ai_walk, 4, NULL, + ai_walk, -3, NULL, + ai_walk, 2, NULL, + ai_walk, 0, NULL, + + ai_walk, 4, brain_walk2_cycle, + ai_walk, -1, NULL, + ai_walk, -1, NULL, + ai_walk, -8, NULL, + ai_walk, 0, NULL, + ai_walk, 1, NULL, + ai_walk, 5, NULL, + ai_walk, 2, NULL, + ai_walk, -1, NULL, + ai_walk, -5, NULL +}; +mmove_t brain_move_walk2 = {FRAME_walk201, FRAME_walk240, brain_frames_walk2, NULL}; +#endif + +void brain_walk (edict_t *self) +{ +// if (random() <= 0.5) + self->monsterinfo.currentmove = &brain_move_walk1; +// else +// self->monsterinfo.currentmove = &brain_move_walk2; +} + + + +mframe_t brain_frames_defense [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t brain_move_defense = {FRAME_defens01, FRAME_defens08, brain_frames_defense, NULL}; + +mframe_t brain_frames_pain3 [] = +{ + ai_move, -2, NULL, + ai_move, 2, NULL, + ai_move, 1, NULL, + ai_move, 3, NULL, + ai_move, 0, NULL, + ai_move, -4, NULL +}; +mmove_t brain_move_pain3 = {FRAME_pain301, FRAME_pain306, brain_frames_pain3, brain_run}; + +mframe_t brain_frames_pain2 [] = +{ + ai_move, -2, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 3, NULL, + ai_move, 1, NULL, + ai_move, -2, NULL +}; +mmove_t brain_move_pain2 = {FRAME_pain201, FRAME_pain208, brain_frames_pain2, brain_run}; + +mframe_t brain_frames_pain1 [] = +{ + ai_move, -6, NULL, + ai_move, -2, NULL, + ai_move, -6, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 2, NULL, + ai_move, 0, NULL, + ai_move, 2, NULL, + ai_move, 1, NULL, + ai_move, 7, NULL, + ai_move, 0, NULL, + ai_move, 3, NULL, + ai_move, -1, NULL +}; +mmove_t brain_move_pain1 = {FRAME_pain101, FRAME_pain121, brain_frames_pain1, brain_run}; + + +// +// DUCK +// + +void brain_duck_down (edict_t *self) +{ + if (self->monsterinfo.aiflags & AI_DUCKED) + return; + self->monsterinfo.aiflags |= AI_DUCKED; + self->maxs[2] -= 32; + self->takedamage = DAMAGE_YES; + gi.linkentity (self); +} + +void brain_duck_hold (edict_t *self) +{ + if (level.time >= self->monsterinfo.pausetime) + self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; + else + self->monsterinfo.aiflags |= AI_HOLD_FRAME; +} + +void brain_duck_up (edict_t *self) +{ + self->monsterinfo.aiflags &= ~AI_DUCKED; + self->maxs[2] += 32; + self->takedamage = DAMAGE_AIM; + gi.linkentity (self); +} + +mframe_t brain_frames_duck [] = +{ + ai_move, 0, NULL, + ai_move, -2, brain_duck_down, + ai_move, 17, brain_duck_hold, + ai_move, -3, NULL, + ai_move, -1, brain_duck_up, + ai_move, -5, NULL, + ai_move, -6, NULL, + ai_move, -6, NULL +}; +mmove_t brain_move_duck = {FRAME_duck01, FRAME_duck08, brain_frames_duck, brain_run}; + +void brain_dodge (edict_t *self, edict_t *attacker, float eta) +{ + if (random() > 0.25) + return; + + if (!self->enemy) + self->enemy = attacker; + + self->monsterinfo.pausetime = level.time + eta + 0.5; + self->monsterinfo.currentmove = &brain_move_duck; +} + + +mframe_t brain_frames_death2 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 9, NULL, + ai_move, 0, NULL +}; +mmove_t brain_move_death2 = {FRAME_death201, FRAME_death205, brain_frames_death2, brain_dead}; + +mframe_t brain_frames_death1 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, -2, NULL, + ai_move, 9, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t brain_move_death1 = {FRAME_death101, FRAME_death118, brain_frames_death1, brain_dead}; + + +// +// MELEE +// + +void brain_swing_right (edict_t *self) +{ + gi.sound (self, CHAN_BODY, sound_melee1, 1, ATTN_NORM, 0); +} + +void brain_hit_right (edict_t *self) +{ + vec3_t aim; + + VectorSet (aim, MELEE_DISTANCE, self->maxs[0], 8); + if (fire_hit (self, aim, (15 + (rand() %5)), 40)) + gi.sound (self, CHAN_WEAPON, sound_melee3, 1, ATTN_NORM, 0); +} + +void brain_swing_left (edict_t *self) +{ + gi.sound (self, CHAN_BODY, sound_melee2, 1, ATTN_NORM, 0); +} + +void brain_hit_left (edict_t *self) +{ + vec3_t aim; + + VectorSet (aim, MELEE_DISTANCE, self->mins[0], 8); + if (fire_hit (self, aim, (15 + (rand() %5)), 40)) + gi.sound (self, CHAN_WEAPON, sound_melee3, 1, ATTN_NORM, 0); +} + +mframe_t brain_frames_attack1 [] = +{ + ai_charge, 8, NULL, + ai_charge, 3, NULL, + ai_charge, 5, NULL, + ai_charge, 0, NULL, + ai_charge, -3, brain_swing_right, + ai_charge, 0, NULL, + ai_charge, -5, NULL, + ai_charge, -7, brain_hit_right, + ai_charge, 0, NULL, + ai_charge, 6, brain_swing_left, + ai_charge, 1, NULL, + ai_charge, 2, brain_hit_left, + ai_charge, -3, NULL, + ai_charge, 6, NULL, + ai_charge, -1, NULL, + ai_charge, -3, NULL, + ai_charge, 2, NULL, + ai_charge, -11,NULL +}; +mmove_t brain_move_attack1 = {FRAME_attak101, FRAME_attak118, brain_frames_attack1, brain_run}; + +void brain_chest_open (edict_t *self) +{ + self->spawnflags &= ~65536; + self->monsterinfo.power_armor_type = POWER_ARMOR_NONE; + gi.sound (self, CHAN_BODY, sound_chest_open, 1, ATTN_NORM, 0); +} + +void brain_tentacle_attack (edict_t *self) +{ + vec3_t aim; + + VectorSet (aim, MELEE_DISTANCE, 0, 8); + if (fire_hit (self, aim, (10 + (rand() %5)), -600) && skill->value > 0) + self->spawnflags |= 65536; + gi.sound (self, CHAN_WEAPON, sound_tentacles_retract, 1, ATTN_NORM, 0); +} + +void brain_chest_closed (edict_t *self) +{ + self->monsterinfo.power_armor_type = POWER_ARMOR_SCREEN; + if (self->spawnflags & 65536) + { + self->spawnflags &= ~65536; + self->monsterinfo.currentmove = &brain_move_attack1; + } +} + +mframe_t brain_frames_attack2 [] = +{ + ai_charge, 5, NULL, + ai_charge, -4, NULL, + ai_charge, -4, NULL, + ai_charge, -3, NULL, + ai_charge, 0, brain_chest_open, + ai_charge, 0, NULL, + ai_charge, 13, brain_tentacle_attack, + ai_charge, 0, NULL, + ai_charge, 2, NULL, + ai_charge, 0, NULL, + ai_charge, -9, brain_chest_closed, + ai_charge, 0, NULL, + ai_charge, 4, NULL, + ai_charge, 3, NULL, + ai_charge, 2, NULL, + ai_charge, -3, NULL, + ai_charge, -6, NULL +}; +mmove_t brain_move_attack2 = {FRAME_attak201, FRAME_attak217, brain_frames_attack2, brain_run}; + +void brain_melee(edict_t *self) +{ + if (random() <= 0.5) + self->monsterinfo.currentmove = &brain_move_attack1; + else + self->monsterinfo.currentmove = &brain_move_attack2; +} + +static qboolean brain_tounge_attack_ok (vec3_t start, vec3_t end) +{ + vec3_t dir, angles; + + // check for max distance + VectorSubtract (start, end, dir); + if (VectorLength(dir) > 512) + return false; + + // check for min/max pitch + vectoangles (dir, angles); + if (angles[0] < -180) + angles[0] += 360; + if (fabs(angles[0]) > 30) + return false; + + return true; +} + +void brain_tounge_attack (edict_t *self) +{ + vec3_t offset, start, f, r, end, dir; + trace_t tr; + int damage; + + AngleVectors (self->s.angles, f, r, NULL); + // VectorSet (offset, 24, 0, 6); + VectorSet (offset, 24, 0, 16); + G_ProjectSource (self->s.origin, offset, f, r, start); + + VectorCopy (self->enemy->s.origin, end); + if (!brain_tounge_attack_ok(start, end)) + { + end[2] = self->enemy->s.origin[2] + self->enemy->maxs[2] - 8; + if (!brain_tounge_attack_ok(start, end)) + { + end[2] = self->enemy->s.origin[2] + self->enemy->mins[2] + 8; + if (!brain_tounge_attack_ok(start, end)) + return; + } + } + VectorCopy (self->enemy->s.origin, end); + + tr = gi.trace (start, NULL, NULL, end, self, MASK_SHOT); + if (tr.ent != self->enemy) + return; + + damage = 5; + gi.sound (self, CHAN_WEAPON, sound_tentacles_retract, 1, ATTN_NORM, 0); + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_PARASITE_ATTACK); + gi.WriteShort (self - g_edicts); + gi.WritePosition (start); + gi.WritePosition (end); + gi.multicast (self->s.origin, MULTICAST_PVS); + + VectorSubtract (start, end, dir); + T_Damage (self->enemy, self, self, dir, self->enemy->s.origin, vec3_origin, damage, 0, DAMAGE_NO_KNOCKBACK, MOD_BRAINTENTACLE); + + // pull the enemy in + { + vec3_t forward; + self->s.origin[2] += 1; + AngleVectors (self->s.angles, forward, NULL, NULL); + VectorScale (forward, -1200, self->enemy->velocity); + } + +} + +// Brian right eye center + +struct r_eyeball +{ + float x; + float y; + float z; +} brain_reye [11] = { + { 0.746700, 0.238370, 34.167690 }, + { -1.076390, 0.238370, 33.386372 }, + { -1.335500, 5.334300, 32.177170 }, + { -0.175360, 8.846370, 30.635479 }, + { -2.757590, 7.804610, 30.150860 }, + { -5.575090, 5.152840, 30.056160 }, + { -7.017550, 3.262470, 30.552521 }, + { -7.915740, 0.638800, 33.176189 }, + { -3.915390, 8.285730, 33.976349 }, + { -0.913540, 10.933030, 34.141811 }, + { -0.369900, 8.923900, 34.189079 } +}; + + + +// Brain left eye center +struct l_eyeball +{ + float x; + float y; + float z; +} brain_leye [11] = { + { -3.364710, 0.327750, 33.938381 }, + { -5.140450, 0.493480, 32.659851 }, + { -5.341980, 5.646980, 31.277901 }, + { -4.134480, 9.277440, 29.925621 }, + { -6.598340, 6.815090, 29.322620 }, + { -8.610840, 2.529650, 29.251591 }, + { -9.231360, 0.093280, 29.747959 }, + { -11.004110, 1.936930, 32.395260 }, + { -7.878310, 7.648190, 33.148151 }, + { -4.947370, 11.430050, 33.313610 }, + { -4.332820, 9.444570, 33.526340 } +}; + +// note to self +// need to get an x,y,z offset for +// each frame of the run cycle +void brain_laserbeam (edict_t *self) +{ + + vec3_t forward, right, up; + vec3_t tempang, start; + vec3_t dir, angles, end; + edict_t *ent; + + // RAFAEL + // cant call sound this frequent + if (random() > 0.8) + gi.sound(self, CHAN_AUTO, gi.soundindex("misc/lasfly.wav"), 1, ATTN_STATIC, 0); + + // check for max distance + + VectorCopy (self->s.origin, start); + VectorCopy (self->enemy->s.origin, end); + VectorSubtract (end, start, dir); + vectoangles (dir, angles); + + // dis is my right eye + ent = G_Spawn (); + VectorCopy (self->s.origin, ent->s.origin); + VectorCopy (angles, tempang); + AngleVectors (tempang, forward, right, up); + VectorCopy (tempang, ent->s.angles); + VectorCopy (ent->s.origin, start); + VectorMA (start, brain_reye[self->s.frame - FRAME_walk101].x, right, start); + VectorMA (start, brain_reye[self->s.frame - FRAME_walk101].y, forward, start); + VectorMA (start, brain_reye[self->s.frame - FRAME_walk101].z, up, start); + VectorCopy (start, ent->s.origin); + ent->enemy = self->enemy; + ent->owner = self; + ent->dmg = 1; + monster_dabeam (ent); + + // dis is me left eye + ent = G_Spawn (); + VectorCopy (self->s.origin, ent->s.origin); + VectorCopy (angles, tempang); + AngleVectors (tempang, forward, right, up); + VectorCopy (tempang, ent->s.angles); + VectorCopy (ent->s.origin, start); + VectorMA (start, brain_leye[self->s.frame - FRAME_walk101].x, right, start); + VectorMA (start, brain_leye[self->s.frame - FRAME_walk101].y, forward, start); + VectorMA (start, brain_leye[self->s.frame - FRAME_walk101].z, up, start); + VectorCopy (start, ent->s.origin); + ent->enemy = self->enemy; + ent->owner = self; + ent->dmg = 1; + monster_dabeam (ent); + +} + +void brain_laserbeam_reattack (edict_t *self) +{ + if (random() < 0.5) + if (visible (self, self->enemy)) + if (self->enemy->health > 0) + self->s.frame = FRAME_walk101; +} + + +mframe_t brain_frames_attack3 [] = +{ + ai_charge, 5, NULL, + ai_charge, -4, NULL, + ai_charge, -4, NULL, + ai_charge, -3, NULL, + ai_charge, 0, brain_chest_open, + ai_charge, 0, brain_tounge_attack, + ai_charge, 13, NULL, + ai_charge, 0, brain_tentacle_attack, + ai_charge, 2, NULL, + ai_charge, 0, brain_tounge_attack, + ai_charge, -9, brain_chest_closed, + ai_charge, 0, NULL, + ai_charge, 4, NULL, + ai_charge, 3, NULL, + ai_charge, 2, NULL, + ai_charge, -3, NULL, + ai_charge, -6, NULL +}; +mmove_t brain_move_attack3 = {FRAME_attak201, FRAME_attak217, brain_frames_attack3, brain_run}; + +mframe_t brain_frames_attack4 [] = +{ + ai_charge, 9, brain_laserbeam, + ai_charge, 2, brain_laserbeam, + ai_charge, 3, brain_laserbeam, + ai_charge, 3, brain_laserbeam, + ai_charge, 1, brain_laserbeam, + ai_charge, 0, brain_laserbeam, + ai_charge, 0, brain_laserbeam, + ai_charge, 10, brain_laserbeam, + ai_charge, -4, brain_laserbeam, + ai_charge, -1, brain_laserbeam, + ai_charge, 2, brain_laserbeam_reattack +}; +mmove_t brain_move_attack4 = {FRAME_walk101, FRAME_walk111, brain_frames_attack4, brain_run}; + + +// RAFAEL +void brain_attack (edict_t *self) +{ + int r; + + if (random() < 0.8) + { + r = range (self, self->enemy); + if (r == RANGE_NEAR) + { + if (random() < 0.5) + self->monsterinfo.currentmove = &brain_move_attack3; + else + self->monsterinfo.currentmove = &brain_move_attack4; + } + else if (r > RANGE_NEAR) + self->monsterinfo.currentmove = &brain_move_attack4; + } + +} + +// +// RUN +// + +mframe_t brain_frames_run [] = +{ + ai_run, 9, NULL, + ai_run, 2, NULL, + ai_run, 3, NULL, + ai_run, 3, NULL, + ai_run, 1, NULL, + ai_run, 0, NULL, + ai_run, 0, NULL, + ai_run, 10, NULL, + ai_run, -4, NULL, + ai_run, -1, NULL, + ai_run, 2, NULL +}; +mmove_t brain_move_run = {FRAME_walk101, FRAME_walk111, brain_frames_run, NULL}; + +void brain_run (edict_t *self) +{ + self->monsterinfo.power_armor_type = POWER_ARMOR_SCREEN; + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + self->monsterinfo.currentmove = &brain_move_stand; + else + self->monsterinfo.currentmove = &brain_move_run; +} + + +void brain_pain (edict_t *self, edict_t *other, float kick, int damage) +{ + float r; + + if (self->health < (self->max_health / 2)) + self->s.skinnum = 1; + + if (level.time < self->pain_debounce_time) + return; + + self->pain_debounce_time = level.time + 3; + + r = random(); + if (r < 0.33) + { + gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &brain_move_pain1; + } + else if (r < 0.66) + { + gi.sound (self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &brain_move_pain2; + } + else + { + gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &brain_move_pain3; + } +} + +void brain_dead (edict_t *self) +{ + VectorSet (self->mins, -16, -16, -24); + VectorSet (self->maxs, 16, 16, -8); + self->movetype = MOVETYPE_TOSS; + self->svflags |= SVF_DEADMONSTER; + self->nextthink = 0; + gi.linkentity (self); +} + + + +void brain_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + int n; + + self->s.effects = 0; + self->monsterinfo.power_armor_type = POWER_ARMOR_NONE; + +// check for gib + if (self->health <= self->gib_health) + { + gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0); + for (n= 0; n < 2; n++) + ThrowGib (self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC); + for (n= 0; n < 4; n++) + ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); + ThrowHead (self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC); + self->deadflag = DEAD_DEAD; + return; + } + + if (self->deadflag == DEAD_DEAD) + return; + +// regular death + gi.sound (self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0); + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_YES; + if (random() <= 0.5) + self->monsterinfo.currentmove = &brain_move_death1; + else + self->monsterinfo.currentmove = &brain_move_death2; +} + +/*QUAKED monster_brain (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight +*/ +void SP_monster_brain (edict_t *self) +{ + if (deathmatch->value) + { + G_FreeEdict (self); + return; + } + + sound_chest_open = gi.soundindex ("brain/brnatck1.wav"); + sound_tentacles_extend = gi.soundindex ("brain/brnatck2.wav"); + sound_tentacles_retract = gi.soundindex ("brain/brnatck3.wav"); + sound_death = gi.soundindex ("brain/brndeth1.wav"); + sound_idle1 = gi.soundindex ("brain/brnidle1.wav"); + sound_idle2 = gi.soundindex ("brain/brnidle2.wav"); + sound_idle3 = gi.soundindex ("brain/brnlens1.wav"); + sound_pain1 = gi.soundindex ("brain/brnpain1.wav"); + sound_pain2 = gi.soundindex ("brain/brnpain2.wav"); + sound_sight = gi.soundindex ("brain/brnsght1.wav"); + sound_search = gi.soundindex ("brain/brnsrch1.wav"); + sound_melee1 = gi.soundindex ("brain/melee1.wav"); + sound_melee2 = gi.soundindex ("brain/melee2.wav"); + sound_melee3 = gi.soundindex ("brain/melee3.wav"); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex ("models/monsters/brain/tris.md2"); + VectorSet (self->mins, -16, -16, -24); + VectorSet (self->maxs, 16, 16, 32); + + self->health = 300; + self->gib_health = -150; + self->mass = 400; + + self->pain = brain_pain; + self->die = brain_die; + + self->monsterinfo.stand = brain_stand; + self->monsterinfo.walk = brain_walk; + self->monsterinfo.run = brain_run; + self->monsterinfo.dodge = brain_dodge; + self->monsterinfo.attack = brain_attack; + self->monsterinfo.melee = brain_melee; + self->monsterinfo.sight = brain_sight; + self->monsterinfo.search = brain_search; + self->monsterinfo.idle = brain_idle; + + self->monsterinfo.power_armor_type = POWER_ARMOR_SCREEN; + self->monsterinfo.power_armor_power = 100; + + gi.linkentity (self); + + self->monsterinfo.currentmove = &brain_move_stand; + self->monsterinfo.scale = MODEL_SCALE; + + walkmonster_start (self); +} diff --git a/original/xatrix/m_brain.h b/original/xatrix/m_brain.h new file mode 100644 index 0000000..acdb7b3 --- /dev/null +++ b/original/xatrix/m_brain.h @@ -0,0 +1,230 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// G:\quake2\baseq2\models/monsters/brain + +// This file generated by ModelGen - Do NOT Modify + +#define FRAME_walk101 0 +#define FRAME_walk102 1 +#define FRAME_walk103 2 +#define FRAME_walk104 3 +#define FRAME_walk105 4 +#define FRAME_walk106 5 +#define FRAME_walk107 6 +#define FRAME_walk108 7 +#define FRAME_walk109 8 +#define FRAME_walk110 9 +#define FRAME_walk111 10 +#define FRAME_walk112 11 +#define FRAME_walk113 12 +#define FRAME_walk201 13 +#define FRAME_walk202 14 +#define FRAME_walk203 15 +#define FRAME_walk204 16 +#define FRAME_walk205 17 +#define FRAME_walk206 18 +#define FRAME_walk207 19 +#define FRAME_walk208 20 +#define FRAME_walk209 21 +#define FRAME_walk210 22 +#define FRAME_walk211 23 +#define FRAME_walk212 24 +#define FRAME_walk213 25 +#define FRAME_walk214 26 +#define FRAME_walk215 27 +#define FRAME_walk216 28 +#define FRAME_walk217 29 +#define FRAME_walk218 30 +#define FRAME_walk219 31 +#define FRAME_walk220 32 +#define FRAME_walk221 33 +#define FRAME_walk222 34 +#define FRAME_walk223 35 +#define FRAME_walk224 36 +#define FRAME_walk225 37 +#define FRAME_walk226 38 +#define FRAME_walk227 39 +#define FRAME_walk228 40 +#define FRAME_walk229 41 +#define FRAME_walk230 42 +#define FRAME_walk231 43 +#define FRAME_walk232 44 +#define FRAME_walk233 45 +#define FRAME_walk234 46 +#define FRAME_walk235 47 +#define FRAME_walk236 48 +#define FRAME_walk237 49 +#define FRAME_walk238 50 +#define FRAME_walk239 51 +#define FRAME_walk240 52 +#define FRAME_attak101 53 +#define FRAME_attak102 54 +#define FRAME_attak103 55 +#define FRAME_attak104 56 +#define FRAME_attak105 57 +#define FRAME_attak106 58 +#define FRAME_attak107 59 +#define FRAME_attak108 60 +#define FRAME_attak109 61 +#define FRAME_attak110 62 +#define FRAME_attak111 63 +#define FRAME_attak112 64 +#define FRAME_attak113 65 +#define FRAME_attak114 66 +#define FRAME_attak115 67 +#define FRAME_attak116 68 +#define FRAME_attak117 69 +#define FRAME_attak118 70 +#define FRAME_attak201 71 +#define FRAME_attak202 72 +#define FRAME_attak203 73 +#define FRAME_attak204 74 +#define FRAME_attak205 75 +#define FRAME_attak206 76 +#define FRAME_attak207 77 +#define FRAME_attak208 78 +#define FRAME_attak209 79 +#define FRAME_attak210 80 +#define FRAME_attak211 81 +#define FRAME_attak212 82 +#define FRAME_attak213 83 +#define FRAME_attak214 84 +#define FRAME_attak215 85 +#define FRAME_attak216 86 +#define FRAME_attak217 87 +#define FRAME_pain101 88 +#define FRAME_pain102 89 +#define FRAME_pain103 90 +#define FRAME_pain104 91 +#define FRAME_pain105 92 +#define FRAME_pain106 93 +#define FRAME_pain107 94 +#define FRAME_pain108 95 +#define FRAME_pain109 96 +#define FRAME_pain110 97 +#define FRAME_pain111 98 +#define FRAME_pain112 99 +#define FRAME_pain113 100 +#define FRAME_pain114 101 +#define FRAME_pain115 102 +#define FRAME_pain116 103 +#define FRAME_pain117 104 +#define FRAME_pain118 105 +#define FRAME_pain119 106 +#define FRAME_pain120 107 +#define FRAME_pain121 108 +#define FRAME_pain201 109 +#define FRAME_pain202 110 +#define FRAME_pain203 111 +#define FRAME_pain204 112 +#define FRAME_pain205 113 +#define FRAME_pain206 114 +#define FRAME_pain207 115 +#define FRAME_pain208 116 +#define FRAME_pain301 117 +#define FRAME_pain302 118 +#define FRAME_pain303 119 +#define FRAME_pain304 120 +#define FRAME_pain305 121 +#define FRAME_pain306 122 +#define FRAME_death101 123 +#define FRAME_death102 124 +#define FRAME_death103 125 +#define FRAME_death104 126 +#define FRAME_death105 127 +#define FRAME_death106 128 +#define FRAME_death107 129 +#define FRAME_death108 130 +#define FRAME_death109 131 +#define FRAME_death110 132 +#define FRAME_death111 133 +#define FRAME_death112 134 +#define FRAME_death113 135 +#define FRAME_death114 136 +#define FRAME_death115 137 +#define FRAME_death116 138 +#define FRAME_death117 139 +#define FRAME_death118 140 +#define FRAME_death201 141 +#define FRAME_death202 142 +#define FRAME_death203 143 +#define FRAME_death204 144 +#define FRAME_death205 145 +#define FRAME_duck01 146 +#define FRAME_duck02 147 +#define FRAME_duck03 148 +#define FRAME_duck04 149 +#define FRAME_duck05 150 +#define FRAME_duck06 151 +#define FRAME_duck07 152 +#define FRAME_duck08 153 +#define FRAME_defens01 154 +#define FRAME_defens02 155 +#define FRAME_defens03 156 +#define FRAME_defens04 157 +#define FRAME_defens05 158 +#define FRAME_defens06 159 +#define FRAME_defens07 160 +#define FRAME_defens08 161 +#define FRAME_stand01 162 +#define FRAME_stand02 163 +#define FRAME_stand03 164 +#define FRAME_stand04 165 +#define FRAME_stand05 166 +#define FRAME_stand06 167 +#define FRAME_stand07 168 +#define FRAME_stand08 169 +#define FRAME_stand09 170 +#define FRAME_stand10 171 +#define FRAME_stand11 172 +#define FRAME_stand12 173 +#define FRAME_stand13 174 +#define FRAME_stand14 175 +#define FRAME_stand15 176 +#define FRAME_stand16 177 +#define FRAME_stand17 178 +#define FRAME_stand18 179 +#define FRAME_stand19 180 +#define FRAME_stand20 181 +#define FRAME_stand21 182 +#define FRAME_stand22 183 +#define FRAME_stand23 184 +#define FRAME_stand24 185 +#define FRAME_stand25 186 +#define FRAME_stand26 187 +#define FRAME_stand27 188 +#define FRAME_stand28 189 +#define FRAME_stand29 190 +#define FRAME_stand30 191 +#define FRAME_stand31 192 +#define FRAME_stand32 193 +#define FRAME_stand33 194 +#define FRAME_stand34 195 +#define FRAME_stand35 196 +#define FRAME_stand36 197 +#define FRAME_stand37 198 +#define FRAME_stand38 199 +#define FRAME_stand39 200 +#define FRAME_stand40 201 +#define FRAME_stand41 202 +#define FRAME_stand42 203 +#define FRAME_stand43 204 +#define FRAME_stand44 205 +#define FRAME_stand45 206 +#define FRAME_stand46 207 +#define FRAME_stand47 208 +#define FRAME_stand48 209 +#define FRAME_stand49 210 +#define FRAME_stand50 211 +#define FRAME_stand51 212 +#define FRAME_stand52 213 +#define FRAME_stand53 214 +#define FRAME_stand54 215 +#define FRAME_stand55 216 +#define FRAME_stand56 217 +#define FRAME_stand57 218 +#define FRAME_stand58 219 +#define FRAME_stand59 220 +#define FRAME_stand60 221 + +#define MODEL_SCALE 1.000000 diff --git a/original/xatrix/m_chick.c b/original/xatrix/m_chick.c new file mode 100644 index 0000000..dd9b167 --- /dev/null +++ b/original/xatrix/m_chick.c @@ -0,0 +1,672 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +/* +============================================================================== + +chick + +============================================================================== +*/ + +#include "g_local.h" +#include "m_chick.h" + +qboolean visible (edict_t *self, edict_t *other); + +void chick_stand (edict_t *self); +void chick_run (edict_t *self); +void chick_reslash(edict_t *self); +void chick_rerocket(edict_t *self); +void chick_attack1(edict_t *self); + +static int sound_missile_prelaunch; +static int sound_missile_launch; +static int sound_melee_swing; +static int sound_melee_hit; +static int sound_missile_reload; +static int sound_death1; +static int sound_death2; +static int sound_fall_down; +static int sound_idle1; +static int sound_idle2; +static int sound_pain1; +static int sound_pain2; +static int sound_pain3; +static int sound_sight; +static int sound_search; + + +void ChickMoan (edict_t *self) +{ + if (random() < 0.5) + gi.sound (self, CHAN_VOICE, sound_idle1, 1, ATTN_IDLE, 0); + else + gi.sound (self, CHAN_VOICE, sound_idle2, 1, ATTN_IDLE, 0); +} + +mframe_t chick_frames_fidget [] = +{ + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, ChickMoan, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL +}; +mmove_t chick_move_fidget = {FRAME_stand201, FRAME_stand230, chick_frames_fidget, chick_stand}; + +void chick_fidget (edict_t *self) +{ + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + return; + if (random() <= 0.3) + self->monsterinfo.currentmove = &chick_move_fidget; +} + +mframe_t chick_frames_stand [] = +{ + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, chick_fidget, + +}; +mmove_t chick_move_stand = {FRAME_stand101, FRAME_stand130, chick_frames_stand, NULL}; + +void chick_stand (edict_t *self) +{ + self->monsterinfo.currentmove = &chick_move_stand; +} + +mframe_t chick_frames_start_run [] = +{ + ai_run, 1, NULL, + ai_run, 0, NULL, + ai_run, 0, NULL, + ai_run, -1, NULL, + ai_run, -1, NULL, + ai_run, 0, NULL, + ai_run, 1, NULL, + ai_run, 3, NULL, + ai_run, 6, NULL, + ai_run, 3, NULL +}; +mmove_t chick_move_start_run = {FRAME_walk01, FRAME_walk10, chick_frames_start_run, chick_run}; + +mframe_t chick_frames_run [] = +{ + ai_run, 6, NULL, + ai_run, 8, NULL, + ai_run, 13, NULL, + ai_run, 5, NULL, + ai_run, 7, NULL, + ai_run, 4, NULL, + ai_run, 11, NULL, + ai_run, 5, NULL, + ai_run, 9, NULL, + ai_run, 7, NULL + +}; + +mmove_t chick_move_run = {FRAME_walk11, FRAME_walk20, chick_frames_run, NULL}; + +mframe_t chick_frames_walk [] = +{ + ai_walk, 6, NULL, + ai_walk, 8, NULL, + ai_walk, 13, NULL, + ai_walk, 5, NULL, + ai_walk, 7, NULL, + ai_walk, 4, NULL, + ai_walk, 11, NULL, + ai_walk, 5, NULL, + ai_walk, 9, NULL, + ai_walk, 7, NULL +}; + +mmove_t chick_move_walk = {FRAME_walk11, FRAME_walk20, chick_frames_walk, NULL}; + +void chick_walk (edict_t *self) +{ + self->monsterinfo.currentmove = &chick_move_walk; +} + +void chick_run (edict_t *self) +{ + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + { + self->monsterinfo.currentmove = &chick_move_stand; + return; + } + + if (self->monsterinfo.currentmove == &chick_move_walk || + self->monsterinfo.currentmove == &chick_move_start_run) + { + self->monsterinfo.currentmove = &chick_move_run; + } + else + { + self->monsterinfo.currentmove = &chick_move_start_run; + } +} + +mframe_t chick_frames_pain1 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t chick_move_pain1 = {FRAME_pain101, FRAME_pain105, chick_frames_pain1, chick_run}; + +mframe_t chick_frames_pain2 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t chick_move_pain2 = {FRAME_pain201, FRAME_pain205, chick_frames_pain2, chick_run}; + +mframe_t chick_frames_pain3 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, -6, NULL, + ai_move, 3, NULL, + ai_move, 11, NULL, + ai_move, 3, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 4, NULL, + ai_move, 1, NULL, + ai_move, 0, NULL, + ai_move, -3, NULL, + ai_move, -4, NULL, + ai_move, 5, NULL, + ai_move, 7, NULL, + ai_move, -2, NULL, + ai_move, 3, NULL, + ai_move, -5, NULL, + ai_move, -2, NULL, + ai_move, -8, NULL, + ai_move, 2, NULL +}; +mmove_t chick_move_pain3 = {FRAME_pain301, FRAME_pain321, chick_frames_pain3, chick_run}; + +void chick_pain (edict_t *self, edict_t *other, float kick, int damage) +{ + float r; + + if (self->health < (self->max_health / 2)) + self->s.skinnum = 1; + + if (level.time < self->pain_debounce_time) + return; + + self->pain_debounce_time = level.time + 3; + + r = random(); + if (r < 0.33) + gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); + else if (r < 0.66) + gi.sound (self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0); + else + gi.sound (self, CHAN_VOICE, sound_pain3, 1, ATTN_NORM, 0); + + if (damage <= 10) + self->monsterinfo.currentmove = &chick_move_pain1; + else if (damage <= 25) + self->monsterinfo.currentmove = &chick_move_pain2; + else + self->monsterinfo.currentmove = &chick_move_pain3; +} + +void chick_dead (edict_t *self) +{ + VectorSet (self->mins, -16, -16, 0); + VectorSet (self->maxs, 16, 16, 16); + self->movetype = MOVETYPE_TOSS; + self->svflags |= SVF_DEADMONSTER; + self->nextthink = 0; + gi.linkentity (self); +} + +mframe_t chick_frames_death2 [] = +{ + ai_move, -6, NULL, + ai_move, 0, NULL, + ai_move, -1, NULL, + ai_move, -5, NULL, + ai_move, 0, NULL, + ai_move, -1, NULL, + ai_move, -2, NULL, + ai_move, 1, NULL, + ai_move, 10, NULL, + ai_move, 2, NULL, + ai_move, 3, NULL, + ai_move, 1, NULL, + ai_move, 2, NULL, + ai_move, 0, NULL, + ai_move, 3, NULL, + ai_move, 3, NULL, + ai_move, 1, NULL, + ai_move, -3, NULL, + ai_move, -5, NULL, + ai_move, 4, NULL, + ai_move, 15, NULL, + ai_move, 14, NULL, + ai_move, 1, NULL +}; +mmove_t chick_move_death2 = {FRAME_death201, FRAME_death223, chick_frames_death2, chick_dead}; + +mframe_t chick_frames_death1 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, -7, NULL, + ai_move, 4, NULL, + ai_move, 11, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL + +}; +mmove_t chick_move_death1 = {FRAME_death101, FRAME_death112, chick_frames_death1, chick_dead}; + +void chick_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + int n; + +// check for gib + if (self->health <= self->gib_health) + { + gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0); + for (n= 0; n < 2; n++) + ThrowGib (self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC); + for (n= 0; n < 4; n++) + ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); + ThrowHead (self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC); + self->deadflag = DEAD_DEAD; + return; + } + + if (self->deadflag == DEAD_DEAD) + return; + +// regular death + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_YES; + + n = rand() % 2; + if (n == 0) + { + self->monsterinfo.currentmove = &chick_move_death1; + gi.sound (self, CHAN_VOICE, sound_death1, 1, ATTN_NORM, 0); + } + else + { + self->monsterinfo.currentmove = &chick_move_death2; + gi.sound (self, CHAN_VOICE, sound_death2, 1, ATTN_NORM, 0); + } +} + + +void chick_duck_down (edict_t *self) +{ + if (self->monsterinfo.aiflags & AI_DUCKED) + return; + self->monsterinfo.aiflags |= AI_DUCKED; + self->maxs[2] -= 32; + self->takedamage = DAMAGE_YES; + self->monsterinfo.pausetime = level.time + 1; + gi.linkentity (self); +} + +void chick_duck_hold (edict_t *self) +{ + if (level.time >= self->monsterinfo.pausetime) + self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; + else + self->monsterinfo.aiflags |= AI_HOLD_FRAME; +} + +void chick_duck_up (edict_t *self) +{ + self->monsterinfo.aiflags &= ~AI_DUCKED; + self->maxs[2] += 32; + self->takedamage = DAMAGE_AIM; + gi.linkentity (self); +} + +mframe_t chick_frames_duck [] = +{ + ai_move, 0, chick_duck_down, + ai_move, 1, NULL, + ai_move, 4, chick_duck_hold, + ai_move, -4, NULL, + ai_move, -5, chick_duck_up, + ai_move, 3, NULL, + ai_move, 1, NULL +}; +mmove_t chick_move_duck = {FRAME_duck01, FRAME_duck07, chick_frames_duck, chick_run}; + +void chick_dodge (edict_t *self, edict_t *attacker, float eta) +{ + if (random() > 0.25) + return; + + if (!self->enemy) + self->enemy = attacker; + + self->monsterinfo.currentmove = &chick_move_duck; +} + +void ChickSlash (edict_t *self) +{ + vec3_t aim; + + VectorSet (aim, MELEE_DISTANCE, self->mins[0], 10); + gi.sound (self, CHAN_WEAPON, sound_melee_swing, 1, ATTN_NORM, 0); + fire_hit (self, aim, (10 + (rand() %6)), 100); +} + + +void ChickRocket (edict_t *self) +{ + vec3_t forward, right; + vec3_t start; + vec3_t dir; + vec3_t vec; + qboolean tone = true; + + AngleVectors (self->s.angles, forward, right, NULL); + G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_CHICK_ROCKET_1], forward, right, start); + + VectorCopy (self->enemy->s.origin, vec); + vec[2] += self->enemy->viewheight; + VectorSubtract (vec, start, dir); + VectorNormalize (dir); + + if (self->s.skinnum > 1) + monster_fire_heat (self, start, dir, 50, 500, MZ2_CHICK_ROCKET_1); + else + monster_fire_rocket (self, start, dir, 50, 500, MZ2_CHICK_ROCKET_1); + +} + +void Chick_PreAttack1 (edict_t *self) +{ + gi.sound (self, CHAN_VOICE, sound_missile_prelaunch, 1, ATTN_NORM, 0); +} + +void ChickReload (edict_t *self) +{ + gi.sound (self, CHAN_VOICE, sound_missile_reload, 1, ATTN_NORM, 0); +} + + +mframe_t chick_frames_start_attack1 [] = +{ + ai_charge, 0, Chick_PreAttack1, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 4, NULL, + ai_charge, 0, NULL, + ai_charge, -3, NULL, + ai_charge, 3, NULL, + ai_charge, 5, NULL, + ai_charge, 7, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, chick_attack1 +}; +mmove_t chick_move_start_attack1 = {FRAME_attak101, FRAME_attak113, chick_frames_start_attack1, NULL}; + + +mframe_t chick_frames_attack1 [] = +{ + ai_charge, 19, ChickRocket, + ai_charge, -6, NULL, + ai_charge, -5, NULL, + ai_charge, -2, NULL, + ai_charge, -7, NULL, + ai_charge, 0, NULL, + ai_charge, 1, NULL, + ai_charge, 10, ChickReload, + ai_charge, 4, NULL, + ai_charge, 5, NULL, + ai_charge, 6, NULL, + ai_charge, 6, NULL, + ai_charge, 4, NULL, + ai_charge, 3, chick_rerocket + +}; +mmove_t chick_move_attack1 = {FRAME_attak114, FRAME_attak127, chick_frames_attack1, NULL}; + +mframe_t chick_frames_end_attack1 [] = +{ + ai_charge, -3, NULL, + ai_charge, 0, NULL, + ai_charge, -6, NULL, + ai_charge, -4, NULL, + ai_charge, -2, NULL +}; +mmove_t chick_move_end_attack1 = {FRAME_attak128, FRAME_attak132, chick_frames_end_attack1, chick_run}; + +void chick_rerocket(edict_t *self) +{ + if (self->enemy->health > 0) + { + if (range (self, self->enemy) > RANGE_MELEE) + if ( visible (self, self->enemy) ) + if (random() <= 0.6) + { + self->monsterinfo.currentmove = &chick_move_attack1; + return; + } + } + self->monsterinfo.currentmove = &chick_move_end_attack1; +} + +void chick_attack1(edict_t *self) +{ + self->monsterinfo.currentmove = &chick_move_attack1; +} + +mframe_t chick_frames_slash [] = +{ + ai_charge, 1, NULL, + ai_charge, 7, ChickSlash, + ai_charge, -7, NULL, + ai_charge, 1, NULL, + ai_charge, -1, NULL, + ai_charge, 1, NULL, + ai_charge, 0, NULL, + ai_charge, 1, NULL, + ai_charge, -2, chick_reslash +}; +mmove_t chick_move_slash = {FRAME_attak204, FRAME_attak212, chick_frames_slash, NULL}; + +mframe_t chick_frames_end_slash [] = +{ + ai_charge, -6, NULL, + ai_charge, -1, NULL, + ai_charge, -6, NULL, + ai_charge, 0, NULL +}; +mmove_t chick_move_end_slash = {FRAME_attak213, FRAME_attak216, chick_frames_end_slash, chick_run}; + + +void chick_reslash(edict_t *self) +{ + if (self->enemy->health > 0) + { + if (range (self, self->enemy) == RANGE_MELEE) + if (random() <= 0.9) + { + self->monsterinfo.currentmove = &chick_move_slash; + return; + } + else + { + self->monsterinfo.currentmove = &chick_move_end_slash; + return; + } + } + self->monsterinfo.currentmove = &chick_move_end_slash; +} + +void chick_slash(edict_t *self) +{ + self->monsterinfo.currentmove = &chick_move_slash; +} + + +mframe_t chick_frames_start_slash [] = +{ + ai_charge, 1, NULL, + ai_charge, 8, NULL, + ai_charge, 3, NULL +}; +mmove_t chick_move_start_slash = {FRAME_attak201, FRAME_attak203, chick_frames_start_slash, chick_slash}; + + + +void chick_melee(edict_t *self) +{ + self->monsterinfo.currentmove = &chick_move_start_slash; +} + + +void chick_attack(edict_t *self) +{ + self->monsterinfo.currentmove = &chick_move_start_attack1; +} + +void chick_sight(edict_t *self, edict_t *other) +{ + gi.sound (self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); +} + + +/*QUAKED monster_chick (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight +*/ +void SP_monster_chick (edict_t *self) +{ + if (deathmatch->value) + { + G_FreeEdict (self); + return; + } + + sound_missile_prelaunch = gi.soundindex ("chick/chkatck1.wav"); + sound_missile_launch = gi.soundindex ("chick/chkatck2.wav"); + sound_melee_swing = gi.soundindex ("chick/chkatck3.wav"); + sound_melee_hit = gi.soundindex ("chick/chkatck4.wav"); + sound_missile_reload = gi.soundindex ("chick/chkatck5.wav"); + sound_death1 = gi.soundindex ("chick/chkdeth1.wav"); + sound_death2 = gi.soundindex ("chick/chkdeth2.wav"); + sound_fall_down = gi.soundindex ("chick/chkfall1.wav"); + sound_idle1 = gi.soundindex ("chick/chkidle1.wav"); + sound_idle2 = gi.soundindex ("chick/chkidle2.wav"); + sound_pain1 = gi.soundindex ("chick/chkpain1.wav"); + sound_pain2 = gi.soundindex ("chick/chkpain2.wav"); + sound_pain3 = gi.soundindex ("chick/chkpain3.wav"); + sound_sight = gi.soundindex ("chick/chksght1.wav"); + sound_search = gi.soundindex ("chick/chksrch1.wav"); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex ("models/monsters/bitch/tris.md2"); + VectorSet (self->mins, -16, -16, 0); + VectorSet (self->maxs, 16, 16, 56); + + self->health = 175; + self->gib_health = -70; + self->mass = 200; + + self->pain = chick_pain; + self->die = chick_die; + + self->monsterinfo.stand = chick_stand; + self->monsterinfo.walk = chick_walk; + self->monsterinfo.run = chick_run; + self->monsterinfo.dodge = chick_dodge; + self->monsterinfo.attack = chick_attack; + self->monsterinfo.melee = chick_melee; + self->monsterinfo.sight = chick_sight; + + gi.linkentity (self); + + self->monsterinfo.currentmove = &chick_move_stand; + self->monsterinfo.scale = MODEL_SCALE; + + walkmonster_start (self); +} + + +/*QUAKED monster_chick_heat (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight +*/ +void SP_monster_chick_heat (edict_t *self) +{ + SP_monster_chick (self); + self->s.skinnum = 3; +} diff --git a/original/xatrix/m_chick.h b/original/xatrix/m_chick.h new file mode 100644 index 0000000..fdf85d0 --- /dev/null +++ b/original/xatrix/m_chick.h @@ -0,0 +1,296 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// G:\quake2\baseq2\models/monsters/bitch + +// This file generated by qdata - Do NOT Modify + +#define FRAME_attak101 0 +#define FRAME_attak102 1 +#define FRAME_attak103 2 +#define FRAME_attak104 3 +#define FRAME_attak105 4 +#define FRAME_attak106 5 +#define FRAME_attak107 6 +#define FRAME_attak108 7 +#define FRAME_attak109 8 +#define FRAME_attak110 9 +#define FRAME_attak111 10 +#define FRAME_attak112 11 +#define FRAME_attak113 12 +#define FRAME_attak114 13 +#define FRAME_attak115 14 +#define FRAME_attak116 15 +#define FRAME_attak117 16 +#define FRAME_attak118 17 +#define FRAME_attak119 18 +#define FRAME_attak120 19 +#define FRAME_attak121 20 +#define FRAME_attak122 21 +#define FRAME_attak123 22 +#define FRAME_attak124 23 +#define FRAME_attak125 24 +#define FRAME_attak126 25 +#define FRAME_attak127 26 +#define FRAME_attak128 27 +#define FRAME_attak129 28 +#define FRAME_attak130 29 +#define FRAME_attak131 30 +#define FRAME_attak132 31 +#define FRAME_attak201 32 +#define FRAME_attak202 33 +#define FRAME_attak203 34 +#define FRAME_attak204 35 +#define FRAME_attak205 36 +#define FRAME_attak206 37 +#define FRAME_attak207 38 +#define FRAME_attak208 39 +#define FRAME_attak209 40 +#define FRAME_attak210 41 +#define FRAME_attak211 42 +#define FRAME_attak212 43 +#define FRAME_attak213 44 +#define FRAME_attak214 45 +#define FRAME_attak215 46 +#define FRAME_attak216 47 +#define FRAME_death101 48 +#define FRAME_death102 49 +#define FRAME_death103 50 +#define FRAME_death104 51 +#define FRAME_death105 52 +#define FRAME_death106 53 +#define FRAME_death107 54 +#define FRAME_death108 55 +#define FRAME_death109 56 +#define FRAME_death110 57 +#define FRAME_death111 58 +#define FRAME_death112 59 +#define FRAME_death201 60 +#define FRAME_death202 61 +#define FRAME_death203 62 +#define FRAME_death204 63 +#define FRAME_death205 64 +#define FRAME_death206 65 +#define FRAME_death207 66 +#define FRAME_death208 67 +#define FRAME_death209 68 +#define FRAME_death210 69 +#define FRAME_death211 70 +#define FRAME_death212 71 +#define FRAME_death213 72 +#define FRAME_death214 73 +#define FRAME_death215 74 +#define FRAME_death216 75 +#define FRAME_death217 76 +#define FRAME_death218 77 +#define FRAME_death219 78 +#define FRAME_death220 79 +#define FRAME_death221 80 +#define FRAME_death222 81 +#define FRAME_death223 82 +#define FRAME_duck01 83 +#define FRAME_duck02 84 +#define FRAME_duck03 85 +#define FRAME_duck04 86 +#define FRAME_duck05 87 +#define FRAME_duck06 88 +#define FRAME_duck07 89 +#define FRAME_pain101 90 +#define FRAME_pain102 91 +#define FRAME_pain103 92 +#define FRAME_pain104 93 +#define FRAME_pain105 94 +#define FRAME_pain201 95 +#define FRAME_pain202 96 +#define FRAME_pain203 97 +#define FRAME_pain204 98 +#define FRAME_pain205 99 +#define FRAME_pain301 100 +#define FRAME_pain302 101 +#define FRAME_pain303 102 +#define FRAME_pain304 103 +#define FRAME_pain305 104 +#define FRAME_pain306 105 +#define FRAME_pain307 106 +#define FRAME_pain308 107 +#define FRAME_pain309 108 +#define FRAME_pain310 109 +#define FRAME_pain311 110 +#define FRAME_pain312 111 +#define FRAME_pain313 112 +#define FRAME_pain314 113 +#define FRAME_pain315 114 +#define FRAME_pain316 115 +#define FRAME_pain317 116 +#define FRAME_pain318 117 +#define FRAME_pain319 118 +#define FRAME_pain320 119 +#define FRAME_pain321 120 +#define FRAME_stand101 121 +#define FRAME_stand102 122 +#define FRAME_stand103 123 +#define FRAME_stand104 124 +#define FRAME_stand105 125 +#define FRAME_stand106 126 +#define FRAME_stand107 127 +#define FRAME_stand108 128 +#define FRAME_stand109 129 +#define FRAME_stand110 130 +#define FRAME_stand111 131 +#define FRAME_stand112 132 +#define FRAME_stand113 133 +#define FRAME_stand114 134 +#define FRAME_stand115 135 +#define FRAME_stand116 136 +#define FRAME_stand117 137 +#define FRAME_stand118 138 +#define FRAME_stand119 139 +#define FRAME_stand120 140 +#define FRAME_stand121 141 +#define FRAME_stand122 142 +#define FRAME_stand123 143 +#define FRAME_stand124 144 +#define FRAME_stand125 145 +#define FRAME_stand126 146 +#define FRAME_stand127 147 +#define FRAME_stand128 148 +#define FRAME_stand129 149 +#define FRAME_stand130 150 +#define FRAME_stand201 151 +#define FRAME_stand202 152 +#define FRAME_stand203 153 +#define FRAME_stand204 154 +#define FRAME_stand205 155 +#define FRAME_stand206 156 +#define FRAME_stand207 157 +#define FRAME_stand208 158 +#define FRAME_stand209 159 +#define FRAME_stand210 160 +#define FRAME_stand211 161 +#define FRAME_stand212 162 +#define FRAME_stand213 163 +#define FRAME_stand214 164 +#define FRAME_stand215 165 +#define FRAME_stand216 166 +#define FRAME_stand217 167 +#define FRAME_stand218 168 +#define FRAME_stand219 169 +#define FRAME_stand220 170 +#define FRAME_stand221 171 +#define FRAME_stand222 172 +#define FRAME_stand223 173 +#define FRAME_stand224 174 +#define FRAME_stand225 175 +#define FRAME_stand226 176 +#define FRAME_stand227 177 +#define FRAME_stand228 178 +#define FRAME_stand229 179 +#define FRAME_stand230 180 +#define FRAME_walk01 181 +#define FRAME_walk02 182 +#define FRAME_walk03 183 +#define FRAME_walk04 184 +#define FRAME_walk05 185 +#define FRAME_walk06 186 +#define FRAME_walk07 187 +#define FRAME_walk08 188 +#define FRAME_walk09 189 +#define FRAME_walk10 190 +#define FRAME_walk11 191 +#define FRAME_walk12 192 +#define FRAME_walk13 193 +#define FRAME_walk14 194 +#define FRAME_walk15 195 +#define FRAME_walk16 196 +#define FRAME_walk17 197 +#define FRAME_walk18 198 +#define FRAME_walk19 199 +#define FRAME_walk20 200 +#define FRAME_walk21 201 +#define FRAME_walk22 202 +#define FRAME_walk23 203 +#define FRAME_walk24 204 +#define FRAME_walk25 205 +#define FRAME_walk26 206 +#define FRAME_walk27 207 +#define FRAME_recln201 208 +#define FRAME_recln202 209 +#define FRAME_recln203 210 +#define FRAME_recln204 211 +#define FRAME_recln205 212 +#define FRAME_recln206 213 +#define FRAME_recln207 214 +#define FRAME_recln208 215 +#define FRAME_recln209 216 +#define FRAME_recln210 217 +#define FRAME_recln211 218 +#define FRAME_recln212 219 +#define FRAME_recln213 220 +#define FRAME_recln214 221 +#define FRAME_recln215 222 +#define FRAME_recln216 223 +#define FRAME_recln217 224 +#define FRAME_recln218 225 +#define FRAME_recln219 226 +#define FRAME_recln220 227 +#define FRAME_recln221 228 +#define FRAME_recln222 229 +#define FRAME_recln223 230 +#define FRAME_recln224 231 +#define FRAME_recln225 232 +#define FRAME_recln226 233 +#define FRAME_recln227 234 +#define FRAME_recln228 235 +#define FRAME_recln229 236 +#define FRAME_recln230 237 +#define FRAME_recln231 238 +#define FRAME_recln232 239 +#define FRAME_recln233 240 +#define FRAME_recln234 241 +#define FRAME_recln235 242 +#define FRAME_recln236 243 +#define FRAME_recln237 244 +#define FRAME_recln238 245 +#define FRAME_recln239 246 +#define FRAME_recln240 247 +#define FRAME_recln101 248 +#define FRAME_recln102 249 +#define FRAME_recln103 250 +#define FRAME_recln104 251 +#define FRAME_recln105 252 +#define FRAME_recln106 253 +#define FRAME_recln107 254 +#define FRAME_recln108 255 +#define FRAME_recln109 256 +#define FRAME_recln110 257 +#define FRAME_recln111 258 +#define FRAME_recln112 259 +#define FRAME_recln113 260 +#define FRAME_recln114 261 +#define FRAME_recln115 262 +#define FRAME_recln116 263 +#define FRAME_recln117 264 +#define FRAME_recln118 265 +#define FRAME_recln119 266 +#define FRAME_recln120 267 +#define FRAME_recln121 268 +#define FRAME_recln122 269 +#define FRAME_recln123 270 +#define FRAME_recln124 271 +#define FRAME_recln125 272 +#define FRAME_recln126 273 +#define FRAME_recln127 274 +#define FRAME_recln128 275 +#define FRAME_recln129 276 +#define FRAME_recln130 277 +#define FRAME_recln131 278 +#define FRAME_recln132 279 +#define FRAME_recln133 280 +#define FRAME_recln134 281 +#define FRAME_recln135 282 +#define FRAME_recln136 283 +#define FRAME_recln137 284 +#define FRAME_recln138 285 +#define FRAME_recln139 286 +#define FRAME_recln140 287 + +#define MODEL_SCALE 1.000000 diff --git a/original/xatrix/m_fixbot.c b/original/xatrix/m_fixbot.c new file mode 100644 index 0000000..7c2a556 --- /dev/null +++ b/original/xatrix/m_fixbot.c @@ -0,0 +1,1330 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +/* + fixbot.c +*/ + + +#include "g_local.h" +#include "m_fixbot.h" + + +#define MZ2_fixbot_BLASTER_1 MZ2_HOVER_BLASTER_1 + +qboolean visible (edict_t *self, edict_t *other); +qboolean infront (edict_t *self, edict_t *other); + +static int sound_pain1; +static int sound_die; +static int sound_weld1; +static int sound_weld2; +static int sound_weld3; + +void fixbot_run (edict_t *self); +void fixbot_stand (edict_t *self); +void fixbot_dead (edict_t *self); +void fixbot_attack (edict_t *self); +void fixbot_fire_blaster (edict_t *self); +void fixbot_fire_welder (edict_t *self); +void fixbot_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point); + +void use_scanner (edict_t *self); +void change_to_roam (edict_t *self); +void fly_vertical (edict_t *self); + +extern mmove_t fixbot_move_forward; +extern mmove_t fixbot_move_stand; +extern mmove_t fixbot_move_stand2; +extern mmove_t fixbot_move_roamgoal; + +extern mmove_t fixbot_move_weld_start; +extern mmove_t fixbot_move_weld; +extern mmove_t fixbot_move_weld_end; +extern mmove_t fixbot_move_takeoff; +extern mmove_t fixbot_move_landing; +extern mmove_t fixbot_move_turn; + +extern void roam_goal (edict_t *self); +void ED_CallSpawn (edict_t *ent); + +float crand(void) +{ + return (rand()&32767)*(2.0/32767)-1; +}; + +edict_t *fixbot_FindDeadMonster (edict_t *self) +{ + edict_t *ent = NULL; + edict_t *best = NULL; + + while ((ent = findradius(ent, self->s.origin, 1024)) != NULL) + { + if (ent == self) + continue; + if (!(ent->svflags & SVF_MONSTER)) + continue; + if (ent->monsterinfo.aiflags & AI_GOOD_GUY) + continue; + if (ent->owner) + continue; + if (ent->health > 0) + continue; + if (ent->nextthink) + continue; + if (!visible(self, ent)) + continue; + if (!best) + { + best = ent; + continue; + } + if (ent->max_health <= best->max_health) + continue; + best = ent; + } + + return best; +} + + +int fixbot_search (edict_t *self) +{ + edict_t *ent; + + if (!self->goalentity) + { + ent = fixbot_FindDeadMonster(self); + if (ent) + { + self->oldenemy = self->enemy; + self->enemy = ent; + self->enemy->owner = self; + self->monsterinfo.aiflags |= AI_MEDIC; + FoundTarget (self); + return (1); + } + } + return (0); +} + +void landing_goal (edict_t *self) +{ + trace_t tr; + vec3_t forward, right, up; + vec3_t end; + edict_t *ent; + + ent = G_Spawn (); + ent->classname = "bot_goal"; + ent->solid = SOLID_BBOX; + ent->owner = self; + gi.linkentity (ent); + + VectorSet (ent->mins, -32, -32, -24); + VectorSet (ent->maxs, 32, 32, 24); + + AngleVectors (self->s.angles, forward, right, up); + VectorMA (self->s.origin, 32, forward, end); + VectorMA (self->s.origin, -8096, up, end); + + tr = gi.trace (self->s.origin, ent->mins, ent->maxs, end, self, MASK_MONSTERSOLID); + + VectorCopy (tr.endpos, ent->s.origin); + + self->goalentity = self->enemy = ent; + self->monsterinfo.currentmove = &fixbot_move_landing; + + +} + + +void takeoff_goal (edict_t *self) +{ + trace_t tr; + vec3_t forward, right, up; + vec3_t end; + edict_t *ent; + + ent = G_Spawn (); + ent->classname = "bot_goal"; + ent->solid = SOLID_BBOX; + ent->owner = self; + gi.linkentity (ent); + + VectorSet (ent->mins, -32, -32, -24); + VectorSet (ent->maxs, 32, 32, 24); + + + AngleVectors (self->s.angles, forward, right, up); + VectorMA (self->s.origin, 32, forward, end); + VectorMA (self->s.origin, 128, up, end); + + tr = gi.trace (self->s.origin, ent->mins, ent->maxs, end, self, MASK_MONSTERSOLID); + + VectorCopy (tr.endpos, ent->s.origin); + + self->goalentity = self->enemy = ent; + self->monsterinfo.currentmove = &fixbot_move_takeoff; + + +} + +void change_to_roam (edict_t *self) +{ + + if (fixbot_search(self)) + return; + + self->monsterinfo.currentmove = &fixbot_move_roamgoal; + + + if (self->spawnflags & 16) + { + landing_goal (self); + self->monsterinfo.currentmove = &fixbot_move_landing; + self->spawnflags &= ~16; + self->spawnflags = 32; + } + if (self->spawnflags & 8) + { + takeoff_goal (self); + self->monsterinfo.currentmove = &fixbot_move_takeoff; + self->spawnflags &= ~8; + self->spawnflags = 32; + } + if (self->spawnflags & 4) + { + self->monsterinfo.currentmove = &fixbot_move_roamgoal; + self->spawnflags &= ~4; + self->spawnflags = 32; + } + if (!self->spawnflags) + { + self->monsterinfo.currentmove = &fixbot_move_stand2; + } + +} + + +void roam_goal (edict_t *self) +{ + + trace_t tr; + vec3_t forward, right, up; + vec3_t end; + edict_t *ent; + vec3_t dang; + int len, oldlen, whichi, i; + vec3_t vec; + vec3_t whichvec; + + ent = G_Spawn (); + ent->classname = "bot_goal"; + ent->solid = SOLID_BBOX; + ent->owner = self; + gi.linkentity (ent); + + oldlen = 0; + whichi = 0; + for (i=0; i<12; i++) + { + + VectorCopy (self->s.angles, dang); + + if (i < 6) + dang[YAW] += 30 * i; + else + dang[YAW] -= 30 * (i-6); + + AngleVectors (dang, forward, right, up); + VectorMA (self->s.origin, 8192, forward, end); + + tr = gi.trace (self->s.origin, NULL, NULL, end, self, MASK_SHOT); + + VectorSubtract (self->s.origin, tr.endpos, vec); + len = VectorNormalize (vec); + + if (len > oldlen) + { + oldlen=len; + VectorCopy (tr.endpos, whichvec); + } + + } + + VectorCopy (whichvec, ent->s.origin); + self->goalentity = self->enemy = ent; + + self->monsterinfo.currentmove = &fixbot_move_turn; + + +} + +void use_scanner (edict_t *self) +{ + edict_t *ent = NULL; + + float radius = 1024; + vec3_t vec; + + int len; + int oldlen = 0x10000; + edict_t *tempent = NULL; + + + while ((ent = findradius(ent, self->s.origin, radius)) != NULL) + { + if (ent->health >= 100) + { + if (strcmp (ent->classname, "object_repair") == 0) + { + if (visible(self, ent)) + { + // remove the old one + if (strcmp (self->goalentity->classname, "bot_goal") == 0) + { + self->goalentity->nextthink = level.time + 0.1; + self->goalentity->think = G_FreeEdict; + } + + self->goalentity = self->enemy = ent; + + VectorSubtract (self->s.origin, self->goalentity->s.origin, vec); + len = VectorNormalize (vec); + + if (len < 32) + { + self->monsterinfo.currentmove = &fixbot_move_weld_start; + return; + } + return; + } + } + } + } + + VectorSubtract (self->s.origin, self->goalentity->s.origin, vec); + len = VectorLength (vec); + + if (len < 32) + { + if (strcmp (self->goalentity->classname, "object_repair") == 0) + { + self->monsterinfo.currentmove = &fixbot_move_weld_start; + } + else + { + self->goalentity->nextthink = level.time + 0.1; + self->goalentity->think = G_FreeEdict; + self->goalentity = self->enemy = NULL; + self->monsterinfo.currentmove = &fixbot_move_stand; + } + return; + } + + VectorSubtract (self->s.origin, self->s.old_origin, vec); + len = VectorLength (vec); + + /* + bot is stuck get new goalentity + */ + if (len == 0) + { + if (strcmp (self->goalentity->classname , "object_repair") == 0) + { + self->monsterinfo.currentmove = &fixbot_move_stand; + } + else + { + self->goalentity->nextthink = level.time + 0.1; + self->goalentity->think = G_FreeEdict; + self->goalentity = self->enemy = NULL; + self->monsterinfo.currentmove = &fixbot_move_stand; + } + } + +} + + +/* + when the bot has found a landing pad + it will proceed to its goalentity + just above the landing pad and + decend translated along the z the current + frames are at 10fps +*/ +void blastoff (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int te_impact, int hspread, int vspread) +{ + trace_t tr; + vec3_t dir; + vec3_t forward, right, up; + vec3_t end; + float r; + float u; + vec3_t water_start; + qboolean water = false; + int content_mask = MASK_SHOT | MASK_WATER; + + hspread+= (self->s.frame - FRAME_takeoff_01); + vspread+= (self->s.frame - FRAME_takeoff_01); + + tr = gi.trace (self->s.origin, NULL, NULL, start, self, MASK_SHOT); + if (!(tr.fraction < 1.0)) + { + vectoangles (aimdir, dir); + AngleVectors (dir, forward, right, up); + + r = crandom()*hspread; + u = crandom()*vspread; + VectorMA (start, 8192, forward, end); + VectorMA (end, r, right, end); + VectorMA (end, u, up, end); + + if (gi.pointcontents (start) & MASK_WATER) + { + water = true; + VectorCopy (start, water_start); + content_mask &= ~MASK_WATER; + } + + tr = gi.trace (start, NULL, NULL, end, self, content_mask); + + // see if we hit water + if (tr.contents & MASK_WATER) + { + int color; + + water = true; + VectorCopy (tr.endpos, water_start); + + if (!VectorCompare (start, tr.endpos)) + { + if (tr.contents & CONTENTS_WATER) + { + if (strcmp(tr.surface->name, "*brwater") == 0) + color = SPLASH_BROWN_WATER; + else + color = SPLASH_BLUE_WATER; + } + else if (tr.contents & CONTENTS_SLIME) + color = SPLASH_SLIME; + else if (tr.contents & CONTENTS_LAVA) + color = SPLASH_LAVA; + else + color = SPLASH_UNKNOWN; + + if (color != SPLASH_UNKNOWN) + { + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_SPLASH); + gi.WriteByte (8); + gi.WritePosition (tr.endpos); + gi.WriteDir (tr.plane.normal); + gi.WriteByte (color); + gi.multicast (tr.endpos, MULTICAST_PVS); + } + + // change bullet's course when it enters water + VectorSubtract (end, start, dir); + vectoangles (dir, dir); + AngleVectors (dir, forward, right, up); + r = crandom()*hspread*2; + u = crandom()*vspread*2; + VectorMA (water_start, 8192, forward, end); + VectorMA (end, r, right, end); + VectorMA (end, u, up, end); + } + + // re-trace ignoring water this time + tr = gi.trace (water_start, NULL, NULL, end, self, MASK_SHOT); + } + } + + // send gun puff / flash + if (!((tr.surface) && (tr.surface->flags & SURF_SKY))) + { + if (tr.fraction < 1.0) + { + if (tr.ent->takedamage) + { + T_Damage (tr.ent, self, self, aimdir, tr.endpos, tr.plane.normal, damage, kick, DAMAGE_BULLET, MOD_BLASTOFF); + } + else + { + if (strncmp (tr.surface->name, "sky", 3) != 0) + { + gi.WriteByte (svc_temp_entity); + gi.WriteByte (te_impact); + gi.WritePosition (tr.endpos); + gi.WriteDir (tr.plane.normal); + gi.multicast (tr.endpos, MULTICAST_PVS); + + if (self->client) + PlayerNoise(self, tr.endpos, PNOISE_IMPACT); + } + } + } + } + + // if went through water, determine where the end and make a bubble trail + if (water) + { + vec3_t pos; + + VectorSubtract (tr.endpos, water_start, dir); + VectorNormalize (dir); + VectorMA (tr.endpos, -2, dir, pos); + if (gi.pointcontents (pos) & MASK_WATER) + VectorCopy (pos, tr.endpos); + else + tr = gi.trace (pos, NULL, NULL, water_start, tr.ent, MASK_WATER); + + VectorAdd (water_start, tr.endpos, pos); + VectorScale (pos, 0.5, pos); + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_BUBBLETRAIL); + gi.WritePosition (water_start); + gi.WritePosition (tr.endpos); + gi.multicast (pos, MULTICAST_PVS); + } +} + + +void fly_vertical (edict_t *self) +{ + int i; + vec3_t v; + vec3_t forward, right, up; + vec3_t start; + vec3_t tempvec; + + VectorSubtract (self->goalentity->s.origin, self->s.origin, v); + self->ideal_yaw = vectoyaw(v); + M_ChangeYaw (self); + + if (self->s.frame == FRAME_landing_58 || self->s.frame == FRAME_takeoff_16) + { + self->goalentity->nextthink = level.time + 0.1; + self->goalentity->think = G_FreeEdict; + self->monsterinfo.currentmove = &fixbot_move_stand; + self->goalentity = self->enemy = NULL; + } + + // kick up some particles + VectorCopy (self->s.angles, tempvec); + tempvec[PITCH] += 90; + + AngleVectors (tempvec, forward, right, up); + VectorCopy (self->s.origin, start); + + for (i=0; i< 10; i++) + blastoff (self, start, forward, 2, 1, TE_SHOTGUN, DEFAULT_SHOTGUN_HSPREAD, DEFAULT_SHOTGUN_VSPREAD); + + // needs sound +} + + +void fly_vertical2 (edict_t *self) +{ + vec3_t v; + int len; + + VectorSubtract (self->goalentity->s.origin, self->s.origin, v); + len = VectorLength (v); + self->ideal_yaw = vectoyaw(v); + M_ChangeYaw (self); + + if (len < 32) + { + self->goalentity->nextthink = level.time + 0.1; + self->goalentity->think = G_FreeEdict; + self->monsterinfo.currentmove = &fixbot_move_stand; + self->goalentity = self->enemy = NULL; + } + + // needs sound +} + +mframe_t fixbot_frames_landing [] = +{ + ai_move, 0, NULL, + ai_move, 0, fly_vertical2, + ai_move, 0, fly_vertical2, + ai_move, 0, fly_vertical2, + ai_move, 0, fly_vertical2, + ai_move, 0, fly_vertical2, + ai_move, 0, fly_vertical2, + ai_move, 0, fly_vertical2, + ai_move, 0, fly_vertical2, + ai_move, 0, fly_vertical2, + + ai_move, 0, fly_vertical2, + ai_move, 0, fly_vertical2, + ai_move, 0, fly_vertical2, + ai_move, 0, fly_vertical2, + ai_move, 0, fly_vertical2, + ai_move, 0, fly_vertical2, + ai_move, 0, fly_vertical2, + ai_move, 0, fly_vertical2, + ai_move, 0, fly_vertical2, + ai_move, 0, fly_vertical2, + + ai_move, 0, fly_vertical2, + ai_move, 0, fly_vertical2, + ai_move, 0, fly_vertical2, + ai_move, 0, fly_vertical2, + ai_move, 0, fly_vertical2, + ai_move, 0, fly_vertical2, + ai_move, 0, fly_vertical2, + ai_move, 0, fly_vertical2, + ai_move, 0, fly_vertical2, + ai_move, 0, fly_vertical2, + + ai_move, 0, fly_vertical2, + ai_move, 0, fly_vertical2, + ai_move, 0, fly_vertical2, + ai_move, 0, fly_vertical2, + ai_move, 0, fly_vertical2, + ai_move, 0, fly_vertical2, + ai_move, 0, fly_vertical2, + ai_move, 0, fly_vertical2, + ai_move, 0, fly_vertical2, + ai_move, 0, fly_vertical2, + + ai_move, 0, fly_vertical2, + ai_move, 0, fly_vertical2, + ai_move, 0, fly_vertical2, + ai_move, 0, fly_vertical2, + ai_move, 0, fly_vertical2, + ai_move, 0, fly_vertical2, + ai_move, 0, fly_vertical2, + ai_move, 0, fly_vertical2, + ai_move, 0, fly_vertical2, + ai_move, 0, fly_vertical2, + + ai_move, 0, fly_vertical2, + ai_move, 0, fly_vertical2, + ai_move, 0, fly_vertical2, + ai_move, 0, fly_vertical2, + ai_move, 0, fly_vertical2, + ai_move, 0, fly_vertical2, + ai_move, 0, fly_vertical2, + ai_move, 0, fly_vertical2 +}; +mmove_t fixbot_move_landing = { FRAME_landing_01, FRAME_landing_58, fixbot_frames_landing, NULL }; + +/* + generic ambient stand +*/ +mframe_t fixbot_frames_stand [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, change_to_roam + +}; +mmove_t fixbot_move_stand = {FRAME_ambient_01, FRAME_ambient_19, fixbot_frames_stand, NULL}; + +mframe_t fixbot_frames_stand2 [] = +{ + + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL + +}; +mmove_t fixbot_move_stand2 = {FRAME_ambient_01, FRAME_ambient_19, fixbot_frames_stand2, NULL}; + + +/* + will need the pickup offset for the front pincers + object will need to stop forward of the object + and take the object with it ( this may require a variant of liftoff and landing ) +*/ +mframe_t fixbot_frames_pickup [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL + +}; +mmove_t fixbot_move_pickup = { FRAME_pickup_01, FRAME_pickup_27, fixbot_frames_pickup, NULL}; + +/* + generic frame to move bot +*/ +mframe_t fixbot_frames_roamgoal [] = +{ + ai_move, 0, roam_goal +}; +mmove_t fixbot_move_roamgoal = {FRAME_freeze_01, FRAME_freeze_01, fixbot_frames_roamgoal, NULL}; + + +void ai_facing (edict_t *self, float dist) +{ + vec3_t v; + + if (infront (self, self->goalentity)) + self->monsterinfo.currentmove = &fixbot_move_forward; + else + { + VectorSubtract (self->goalentity->s.origin, self->s.origin, v); + self->ideal_yaw = vectoyaw(v); + M_ChangeYaw (self); + } + +}; + +mframe_t fixbot_frames_turn [] = +{ + ai_facing, 0, NULL +}; +mmove_t fixbot_move_turn = {FRAME_freeze_01, FRAME_freeze_01, fixbot_frames_turn, NULL}; + + +void go_roam (edict_t *self) +{ + self->monsterinfo.currentmove = &fixbot_move_stand; +} + + +/* + takeoff +*/ +mframe_t fixbot_frames_takeoff [] = +{ + ai_move, 0.01, fly_vertical, + ai_move, 0.01, fly_vertical, + ai_move, 0.01, fly_vertical, + ai_move, 0.01, fly_vertical, + ai_move, 0.01, fly_vertical, + ai_move, 0.01, fly_vertical, + ai_move, 0.01, fly_vertical, + ai_move, 0.01, fly_vertical, + ai_move, 0.01, fly_vertical, + ai_move, 0.01, fly_vertical, + + ai_move, 0.01, fly_vertical, + ai_move, 0.01, fly_vertical, + ai_move, 0.01, fly_vertical, + ai_move, 0.01, fly_vertical, + ai_move, 0.01, fly_vertical, + ai_move, 0.01, fly_vertical +}; +mmove_t fixbot_move_takeoff = {FRAME_takeoff_01, FRAME_takeoff_16, fixbot_frames_takeoff, NULL}; + + +/* findout what this is */ +mframe_t fixbot_frames_paina [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t fixbot_move_paina = {FRAME_paina_01, FRAME_paina_06, fixbot_frames_paina, fixbot_run}; + +/* findout what this is */ +mframe_t fixbot_frames_painb [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t fixbot_move_painb = {FRAME_painb_01, FRAME_painb_08, fixbot_frames_painb, fixbot_run}; + +/* + backup from pain + call a generic painsound + some spark effects +*/ +mframe_t fixbot_frames_pain3 [] = +{ + ai_move, -1, NULL +}; +mmove_t fixbot_move_pain3 = {FRAME_freeze_01, FRAME_freeze_01, fixbot_frames_pain3, fixbot_run}; + +/* + bot has compleated landing + and is now on the grownd + ( may need second land if the bot is releasing jib into jib vat ) +*/ +mframe_t fixbot_frames_land [] = +{ + ai_move, 0, NULL +}; +mmove_t fixbot_move_land = {FRAME_freeze_01, FRAME_freeze_01, fixbot_frames_land, NULL}; + + +void M_MoveToGoal (edict_t *ent, float dist); + +void ai_movetogoal (edict_t *self, float dist) +{ + M_MoveToGoal (self, dist); +} +/* + +*/ +mframe_t fixbot_frames_forward [] = +{ + ai_movetogoal, 5, use_scanner +}; +mmove_t fixbot_move_forward = {FRAME_freeze_01, FRAME_freeze_01, fixbot_frames_forward, NULL}; + + +/* + +*/ +mframe_t fixbot_frames_walk [] = +{ + ai_walk, 5, NULL +}; +mmove_t fixbot_move_walk = {FRAME_freeze_01, FRAME_freeze_01, fixbot_frames_walk, NULL}; + + +/* + +*/ +mframe_t fixbot_frames_run [] = +{ + ai_run, 10, NULL +}; +mmove_t fixbot_move_run = {FRAME_freeze_01, FRAME_freeze_01, fixbot_frames_run, NULL}; + +/* + raf + note to self + they could have a timer that will cause + the bot to explode on countdown +*/ +mframe_t fixbot_frames_death1 [] = +{ + ai_move, 0, NULL +}; +mmove_t fixbot_move_death1 = {FRAME_freeze_01, FRAME_freeze_01, fixbot_frames_death1, fixbot_dead}; + +// +mframe_t fixbot_frames_backward [] = +{ + ai_move, 0, NULL +}; +mmove_t fixbot_move_backward = {FRAME_freeze_01, FRAME_freeze_01, fixbot_frames_backward, NULL}; + +// +mframe_t fixbot_frames_start_attack [] = +{ + ai_charge, 0, NULL +}; +mmove_t fixbot_move_start_attack = {FRAME_freeze_01, FRAME_freeze_01, fixbot_frames_start_attack, fixbot_attack}; + +/* + TBD: + need to get laser attack anim + attack with the laser blast +*/ +mframe_t fixbot_frames_attack1 [] = +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, -10, fixbot_fire_blaster +}; +mmove_t fixbot_move_attack1 = {FRAME_shoot_01, FRAME_shoot_06, fixbot_frames_attack1, NULL}; + + +int check_telefrag (edict_t *self) +{ + vec3_t start = { 0, 0, 0 }; + vec3_t forward, right, up; + trace_t tr; + + AngleVectors (self->enemy->s.angles, forward, right, up); + VectorMA (start, 48, up, start); + tr = gi.trace (self->enemy->s.origin, self->enemy->mins, self->enemy->maxs, start, self, MASK_MONSTERSOLID); + if (tr.ent->takedamage) + { + tr.ent->health = -1000; + return (0); + } + + return (1); +} + +void fixbot_fire_laser (edict_t *self) +{ + + vec3_t forward, right, up; + vec3_t tempang, start; + vec3_t dir, angles, end; + edict_t *ent; + + // critter dun got blown up while bein' fixed + if (self->enemy->health <= self->enemy->gib_health) + { + self->monsterinfo.currentmove = &fixbot_move_stand; + self->monsterinfo.aiflags &= ~AI_MEDIC; + return; + } + + gi.sound(self, CHAN_AUTO, gi.soundindex("misc/lasfly.wav"), 1, ATTN_STATIC, 0); + + VectorCopy (self->s.origin, start); + VectorCopy (self->enemy->s.origin, end); + VectorSubtract (end, start, dir); + vectoangles (dir, angles); + + ent = G_Spawn (); + VectorCopy (self->s.origin, ent->s.origin); + VectorCopy (angles, tempang); + AngleVectors (tempang, forward, right, up); + VectorCopy (tempang, ent->s.angles); + VectorCopy (ent->s.origin, start); + + VectorMA (start, 16, forward, start); + + VectorCopy (start, ent->s.origin); + ent->enemy = self->enemy; + ent->owner = self; + ent->dmg = -1; + monster_dabeam (ent); + + if (self->enemy->health > (self->enemy->mass/10)) + { + // sorry guys but had to fix the problem this way + // if it doesn't do this then two creatures can share the same space + // and its real bad. + if (check_telefrag (self)) + { + self->enemy->spawnflags = 0; + self->enemy->monsterinfo.aiflags = 0; + self->enemy->target = NULL; + self->enemy->targetname = NULL; + self->enemy->combattarget = NULL; + self->enemy->deathtarget = NULL; + self->enemy->owner = self; + ED_CallSpawn (self->enemy); + self->enemy->owner = NULL; + self->s.origin[2] += 1; + + self->enemy->monsterinfo.aiflags &= ~AI_RESURRECTING; + + self->monsterinfo.currentmove = &fixbot_move_stand; + self->monsterinfo.aiflags &= ~AI_MEDIC; + + } + } + else + self->enemy->monsterinfo.aiflags |= AI_RESURRECTING; +} + +mframe_t fixbot_frames_laserattack [] = +{ + ai_charge, 0, fixbot_fire_laser, + ai_charge, 0, fixbot_fire_laser, + ai_charge, 0, fixbot_fire_laser, + ai_charge, 0, fixbot_fire_laser, + ai_charge, 0, fixbot_fire_laser, + ai_charge, 0, fixbot_fire_laser +}; +mmove_t fixbot_move_laserattack = {FRAME_shoot_01, FRAME_shoot_06, fixbot_frames_laserattack, NULL}; + + +/* + need to get forward translation data + for the charge attack +*/ +mframe_t fixbot_frames_attack2 [] = +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + + ai_charge, -10, NULL, + ai_charge, -10, NULL, + ai_charge, -10, NULL, + ai_charge, -10, NULL, + ai_charge, -10, NULL, + ai_charge, -10, NULL, + ai_charge, -10, NULL, + ai_charge, -10, NULL, + ai_charge, -10, NULL, + ai_charge, -10, NULL, + + ai_charge, 0, fixbot_fire_blaster, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + + ai_charge, 0, NULL +}; +mmove_t fixbot_move_attack2 = {FRAME_charging_01, FRAME_charging_31, fixbot_frames_attack2, fixbot_run}; + +void weldstate (edict_t *self) +{ + if (self->s.frame == FRAME_weldstart_10) + self->monsterinfo.currentmove = &fixbot_move_weld; + else if (self->s.frame == FRAME_weldmiddle_07) + { + if (self->goalentity->health < 0) + { + self->enemy->owner = NULL; + self->monsterinfo.currentmove = &fixbot_move_weld_end; + } + else + self->goalentity->health-=10; + } + else + { + self->goalentity = self->enemy = NULL; + self->monsterinfo.currentmove = &fixbot_move_stand; + } +} + +void ai_move2 (edict_t *self, float dist) +{ + vec3_t v; + + if (dist) + M_walkmove (self, self->s.angles[YAW], dist); + + VectorSubtract (self->goalentity->s.origin, self->s.origin, v); + self->ideal_yaw = vectoyaw(v); + M_ChangeYaw (self); +}; + +mframe_t fixbot_frames_weld_start [] = +{ + ai_move2, 0, NULL, + ai_move2, 0, NULL, + ai_move2, 0, NULL, + ai_move2, 0, NULL, + ai_move2, 0, NULL, + ai_move2, 0, NULL, + ai_move2, 0, NULL, + ai_move2, 0, NULL, + ai_move2, 0, NULL, + ai_move2, 0, weldstate +}; +mmove_t fixbot_move_weld_start = {FRAME_weldstart_01, FRAME_weldstart_10, fixbot_frames_weld_start, NULL}; + +mframe_t fixbot_frames_weld [] = +{ + ai_move2, 0, fixbot_fire_welder, + ai_move2, 0, fixbot_fire_welder, + ai_move2, 0, fixbot_fire_welder, + ai_move2, 0, fixbot_fire_welder, + ai_move2, 0, fixbot_fire_welder, + ai_move2, 0, fixbot_fire_welder, + ai_move2, 0, weldstate +}; +mmove_t fixbot_move_weld = {FRAME_weldmiddle_01, FRAME_weldmiddle_07, fixbot_frames_weld, NULL}; + +mframe_t fixbot_frames_weld_end [] = +{ + ai_move2, -2, NULL, + ai_move2, -2, NULL, + ai_move2, -2, NULL, + ai_move2, -2, NULL, + ai_move2, -2, NULL, + ai_move2, -2, NULL, + ai_move2, -2, weldstate +}; +mmove_t fixbot_move_weld_end = {FRAME_weldend_01, FRAME_weldend_07, fixbot_frames_weld_end, NULL}; + + +void fixbot_fire_welder (edict_t *self) +{ + vec3_t start; + vec3_t forward, right, up; + vec3_t end; + vec3_t dir; + int count = 2; + vec3_t vec; + float r; + + if (!self->enemy) + return; + + + vec[0]=24.0; + vec[1]=-0.8; + vec[2]=-10.0; + + AngleVectors (self->s.angles, forward, right, up); + G_ProjectSource (self->s.origin, vec, forward, right, start); + + VectorCopy (self->enemy->s.origin, end); + + VectorSubtract (end, start, dir); + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_WELDING_SPARKS); + gi.WriteByte (10); + gi.WritePosition (start); + gi.WriteDir (vec3_origin); + gi.WriteByte (0xe0 + (rand()&7)); + gi.multicast (self->s.origin, MULTICAST_PVS); + + + if (random() > 0.8) + { + r = random(); + + if (r < 0.33) + gi.sound (self, CHAN_VOICE, sound_weld1, 1, ATTN_IDLE, 0); + else if (r < 0.66) + gi.sound (self, CHAN_VOICE, sound_weld2, 1, ATTN_IDLE, 0); + else + gi.sound (self, CHAN_VOICE, sound_weld3, 1, ATTN_IDLE, 0); + } +} + +void fixbot_fire_blaster (edict_t *self) +{ + vec3_t start; + vec3_t forward, right, up; + vec3_t end; + vec3_t dir; + + if (!visible(self, self->enemy)) + { + self->monsterinfo.currentmove = &fixbot_move_run; + } + + AngleVectors (self->s.angles, forward, right, up); + G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_fixbot_BLASTER_1], forward, right, start); + + VectorCopy (self->enemy->s.origin, end); + end[2] += self->enemy->viewheight; + VectorSubtract (end, start, dir); + + monster_fire_blaster (self, start, dir, 15, 1000, MZ2_fixbot_BLASTER_1, EF_BLASTER); + +} + +void fixbot_stand (edict_t *self) +{ + self->monsterinfo.currentmove = &fixbot_move_stand; +} + +void fixbot_run (edict_t *self) +{ + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + self->monsterinfo.currentmove = &fixbot_move_stand; + else + self->monsterinfo.currentmove = &fixbot_move_run; +} + +void fixbot_walk (edict_t *self) +{ + vec3_t vec; + int len; + + if (strcmp (self->goalentity->classname, "object_repair") == 0) + { + VectorSubtract (self->s.origin, self->goalentity->s.origin, vec); + len = VectorLength (vec); + if (len < 32) + { + self->monsterinfo.currentmove = &fixbot_move_weld_start; + return; + } + } + self->monsterinfo.currentmove = &fixbot_move_walk; +} + +void fixbot_start_attack (edict_t *self) +{ + self->monsterinfo.currentmove = &fixbot_move_start_attack; +} + +void fixbot_attack(edict_t *self) +{ + vec3_t vec; + int len; + + if (self->monsterinfo.aiflags & AI_MEDIC) + { + if (!visible (self, self->goalentity)) + return; + VectorSubtract (self->s.origin, self->enemy->s.origin, vec); + len = VectorLength(vec); + if (len > 128) + return; + else + self->monsterinfo.currentmove = &fixbot_move_laserattack; + } + else + self->monsterinfo.currentmove = &fixbot_move_attack2; + +} + + +void fixbot_pain (edict_t *self, edict_t *other, float kick, int damage) +{ +// if (self->health < (self->max_health / 2)) +// self->s.skinnum = 1; + // gi.dprintf("pain\n"); + + if (level.time < self->pain_debounce_time) + return; + + self->pain_debounce_time = level.time + 3; + gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); + + if (damage <= 10) + self->monsterinfo.currentmove = &fixbot_move_pain3; + else if (damage <= 25) + self->monsterinfo.currentmove = &fixbot_move_painb; + else + self->monsterinfo.currentmove = &fixbot_move_paina; +} + +void fixbot_dead (edict_t *self) +{ + VectorSet (self->mins, -16, -16, -24); + VectorSet (self->maxs, 16, 16, -8); + self->movetype = MOVETYPE_TOSS; + self->svflags |= SVF_DEADMONSTER; + self->nextthink = 0; + gi.linkentity (self); +} + +void fixbot_die(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + gi.sound (self, CHAN_VOICE, sound_die, 1, ATTN_NORM, 0); + BecomeExplosion1(self); + + // shards + +} + +/*QUAKED monster_fixbot (1 .5 0) (-32 -32 -24) (32 32 24) Ambush Trigger_Spawn Fixit Takeoff Landing +*/ +void SP_monster_fixbot (edict_t *self) +{ + if (deathmatch->value) + { + G_FreeEdict (self); + return; + } + + sound_pain1 = gi.soundindex ("flyer/flypain1.wav"); + sound_die = gi.soundindex ("flyer/flydeth1.wav"); + + sound_weld1 = gi.soundindex ("misc/welder1.wav"); + sound_weld2 = gi.soundindex ("misc/welder2.wav"); + sound_weld3 = gi.soundindex ("misc/welder3.wav"); + + self->s.modelindex = gi.modelindex ("models/monsters/fixbot/tris.md2"); + + VectorSet (self->mins, -32, -32, -24); + VectorSet (self->maxs, 32, 32, 24); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + + self->health = 150; + self->mass = 150; + + self->pain = fixbot_pain; + self->die = fixbot_die; + + self->monsterinfo.stand = fixbot_stand; + self->monsterinfo.walk = fixbot_walk; + self->monsterinfo.run = fixbot_run; + self->monsterinfo.attack = fixbot_attack; + + gi.linkentity (self); + + self->monsterinfo.currentmove = &fixbot_move_stand; + self->monsterinfo.scale = MODEL_SCALE; + + flymonster_start (self); + +} + + diff --git a/original/xatrix/m_fixbot.h b/original/xatrix/m_fixbot.h new file mode 100644 index 0000000..a694cf6 --- /dev/null +++ b/original/xatrix/m_fixbot.h @@ -0,0 +1,220 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// ./fixbot + +// This file generated by ModelGen - Do NOT Modify + +#define FRAME_charging_01 0 +#define FRAME_charging_02 1 +#define FRAME_charging_03 2 +#define FRAME_charging_04 3 +#define FRAME_charging_05 4 +#define FRAME_charging_06 5 +#define FRAME_charging_07 6 +#define FRAME_charging_08 7 +#define FRAME_charging_09 8 +#define FRAME_charging_10 9 +#define FRAME_charging_11 10 +#define FRAME_charging_12 11 +#define FRAME_charging_13 12 +#define FRAME_charging_14 13 +#define FRAME_charging_15 14 +#define FRAME_charging_16 15 +#define FRAME_charging_17 16 +#define FRAME_charging_18 17 +#define FRAME_charging_19 18 +#define FRAME_charging_20 19 +#define FRAME_charging_21 20 +#define FRAME_charging_22 21 +#define FRAME_charging_23 22 +#define FRAME_charging_24 23 +#define FRAME_charging_25 24 +#define FRAME_charging_26 25 +#define FRAME_charging_27 26 +#define FRAME_charging_28 27 +#define FRAME_charging_29 28 +#define FRAME_charging_30 29 +#define FRAME_charging_31 30 +#define FRAME_landing_01 31 +#define FRAME_landing_02 32 +#define FRAME_landing_03 33 +#define FRAME_landing_04 34 +#define FRAME_landing_05 35 +#define FRAME_landing_06 36 +#define FRAME_landing_07 37 +#define FRAME_landing_08 38 +#define FRAME_landing_09 39 +#define FRAME_landing_10 40 +#define FRAME_landing_11 41 +#define FRAME_landing_12 42 +#define FRAME_landing_13 43 +#define FRAME_landing_14 44 +#define FRAME_landing_15 45 +#define FRAME_landing_16 46 +#define FRAME_landing_17 47 +#define FRAME_landing_18 48 +#define FRAME_landing_19 49 +#define FRAME_landing_20 50 +#define FRAME_landing_21 51 +#define FRAME_landing_22 52 +#define FRAME_landing_23 53 +#define FRAME_landing_24 54 +#define FRAME_landing_25 55 +#define FRAME_landing_26 56 +#define FRAME_landing_27 57 +#define FRAME_landing_28 58 +#define FRAME_landing_29 59 +#define FRAME_landing_30 60 +#define FRAME_landing_31 61 +#define FRAME_landing_32 62 +#define FRAME_landing_33 63 +#define FRAME_landing_34 64 +#define FRAME_landing_35 65 +#define FRAME_landing_36 66 +#define FRAME_landing_37 67 +#define FRAME_landing_38 68 +#define FRAME_landing_39 69 +#define FRAME_landing_40 70 +#define FRAME_landing_41 71 +#define FRAME_landing_42 72 +#define FRAME_landing_43 73 +#define FRAME_landing_44 74 +#define FRAME_landing_45 75 +#define FRAME_landing_46 76 +#define FRAME_landing_47 77 +#define FRAME_landing_48 78 +#define FRAME_landing_49 79 +#define FRAME_landing_50 80 +#define FRAME_landing_51 81 +#define FRAME_landing_52 82 +#define FRAME_landing_53 83 +#define FRAME_landing_54 84 +#define FRAME_landing_55 85 +#define FRAME_landing_56 86 +#define FRAME_landing_57 87 +#define FRAME_landing_58 88 +#define FRAME_pushback_01 89 +#define FRAME_pushback_02 90 +#define FRAME_pushback_03 91 +#define FRAME_pushback_04 92 +#define FRAME_pushback_05 93 +#define FRAME_pushback_06 94 +#define FRAME_pushback_07 95 +#define FRAME_pushback_08 96 +#define FRAME_pushback_09 97 +#define FRAME_pushback_10 98 +#define FRAME_pushback_11 99 +#define FRAME_pushback_12 100 +#define FRAME_pushback_13 101 +#define FRAME_pushback_14 102 +#define FRAME_pushback_15 103 +#define FRAME_pushback_16 104 +#define FRAME_takeoff_01 105 +#define FRAME_takeoff_02 106 +#define FRAME_takeoff_03 107 +#define FRAME_takeoff_04 108 +#define FRAME_takeoff_05 109 +#define FRAME_takeoff_06 110 +#define FRAME_takeoff_07 111 +#define FRAME_takeoff_08 112 +#define FRAME_takeoff_09 113 +#define FRAME_takeoff_10 114 +#define FRAME_takeoff_11 115 +#define FRAME_takeoff_12 116 +#define FRAME_takeoff_13 117 +#define FRAME_takeoff_14 118 +#define FRAME_takeoff_15 119 +#define FRAME_takeoff_16 120 +#define FRAME_ambient_01 121 +#define FRAME_ambient_02 122 +#define FRAME_ambient_03 123 +#define FRAME_ambient_04 124 +#define FRAME_ambient_05 125 +#define FRAME_ambient_06 126 +#define FRAME_ambient_07 127 +#define FRAME_ambient_08 128 +#define FRAME_ambient_09 129 +#define FRAME_ambient_10 130 +#define FRAME_ambient_11 131 +#define FRAME_ambient_12 132 +#define FRAME_ambient_13 133 +#define FRAME_ambient_14 134 +#define FRAME_ambient_15 135 +#define FRAME_ambient_16 136 +#define FRAME_ambient_17 137 +#define FRAME_ambient_18 138 +#define FRAME_ambient_19 139 +#define FRAME_paina_01 140 +#define FRAME_paina_02 141 +#define FRAME_paina_03 142 +#define FRAME_paina_04 143 +#define FRAME_paina_05 144 +#define FRAME_paina_06 145 +#define FRAME_painb_01 146 +#define FRAME_painb_02 147 +#define FRAME_painb_03 148 +#define FRAME_painb_04 149 +#define FRAME_painb_05 150 +#define FRAME_painb_06 151 +#define FRAME_painb_07 152 +#define FRAME_painb_08 153 +#define FRAME_pickup_01 154 +#define FRAME_pickup_02 155 +#define FRAME_pickup_03 156 +#define FRAME_pickup_04 157 +#define FRAME_pickup_05 158 +#define FRAME_pickup_06 159 +#define FRAME_pickup_07 160 +#define FRAME_pickup_08 161 +#define FRAME_pickup_09 162 +#define FRAME_pickup_10 163 +#define FRAME_pickup_11 164 +#define FRAME_pickup_12 165 +#define FRAME_pickup_13 166 +#define FRAME_pickup_14 167 +#define FRAME_pickup_15 168 +#define FRAME_pickup_16 169 +#define FRAME_pickup_17 170 +#define FRAME_pickup_18 171 +#define FRAME_pickup_19 172 +#define FRAME_pickup_20 173 +#define FRAME_pickup_21 174 +#define FRAME_pickup_22 175 +#define FRAME_pickup_23 176 +#define FRAME_pickup_24 177 +#define FRAME_pickup_25 178 +#define FRAME_pickup_26 179 +#define FRAME_pickup_27 180 +#define FRAME_freeze_01 181 +#define FRAME_shoot_01 182 +#define FRAME_shoot_02 183 +#define FRAME_shoot_03 184 +#define FRAME_shoot_04 185 +#define FRAME_shoot_05 186 +#define FRAME_shoot_06 187 +#define FRAME_weldstart_01 188 +#define FRAME_weldstart_02 189 +#define FRAME_weldstart_03 190 +#define FRAME_weldstart_04 191 +#define FRAME_weldstart_05 192 +#define FRAME_weldstart_06 193 +#define FRAME_weldstart_07 194 +#define FRAME_weldstart_08 195 +#define FRAME_weldstart_09 196 +#define FRAME_weldstart_10 197 +#define FRAME_weldmiddle_01 198 +#define FRAME_weldmiddle_02 199 +#define FRAME_weldmiddle_03 200 +#define FRAME_weldmiddle_04 201 +#define FRAME_weldmiddle_05 202 +#define FRAME_weldmiddle_06 203 +#define FRAME_weldmiddle_07 204 +#define FRAME_weldend_01 205 +#define FRAME_weldend_02 206 +#define FRAME_weldend_03 207 +#define FRAME_weldend_04 208 +#define FRAME_weldend_05 209 +#define FRAME_weldend_06 210 +#define FRAME_weldend_07 211 + +#define MODEL_SCALE 1.000000 diff --git a/original/xatrix/m_flash.c b/original/xatrix/m_flash.c new file mode 100644 index 0000000..9dc5c4d --- /dev/null +++ b/original/xatrix/m_flash.c @@ -0,0 +1,471 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// m_flash.c + +#include "q_shared.h" + +// this file is included in both the game dll and quake2, +// the game needs it to source shot locations, the client +// needs it to position muzzle flashes +vec3_t monster_flash_offset [] = +{ +// flash 0 is not used + 0.0, 0.0, 0.0, + +// MZ2_TANK_BLASTER_1 1 + 20.7, -18.5, 28.7, +// MZ2_TANK_BLASTER_2 2 + 16.6, -21.5, 30.1, +// MZ2_TANK_BLASTER_3 3 + 11.8, -23.9, 32.1, +// MZ2_TANK_MACHINEGUN_1 4 + 22.9, -0.7, 25.3, +// MZ2_TANK_MACHINEGUN_2 5 + 22.2, 6.2, 22.3, +// MZ2_TANK_MACHINEGUN_3 6 + 19.4, 13.1, 18.6, +// MZ2_TANK_MACHINEGUN_4 7 + 19.4, 18.8, 18.6, +// MZ2_TANK_MACHINEGUN_5 8 + 17.9, 25.0, 18.6, +// MZ2_TANK_MACHINEGUN_6 9 + 14.1, 30.5, 20.6, +// MZ2_TANK_MACHINEGUN_7 10 + 9.3, 35.3, 22.1, +// MZ2_TANK_MACHINEGUN_8 11 + 4.7, 38.4, 22.1, +// MZ2_TANK_MACHINEGUN_9 12 + -1.1, 40.4, 24.1, +// MZ2_TANK_MACHINEGUN_10 13 + -6.5, 41.2, 24.1, +// MZ2_TANK_MACHINEGUN_11 14 + 3.2, 40.1, 24.7, +// MZ2_TANK_MACHINEGUN_12 15 + 11.7, 36.7, 26.0, +// MZ2_TANK_MACHINEGUN_13 16 + 18.9, 31.3, 26.0, +// MZ2_TANK_MACHINEGUN_14 17 + 24.4, 24.4, 26.4, +// MZ2_TANK_MACHINEGUN_15 18 + 27.1, 17.1, 27.2, +// MZ2_TANK_MACHINEGUN_16 19 + 28.5, 9.1, 28.0, +// MZ2_TANK_MACHINEGUN_17 20 + 27.1, 2.2, 28.0, +// MZ2_TANK_MACHINEGUN_18 21 + 24.9, -2.8, 28.0, +// MZ2_TANK_MACHINEGUN_19 22 + 21.6, -7.0, 26.4, +// MZ2_TANK_ROCKET_1 23 + 6.2, 29.1, 49.1, +// MZ2_TANK_ROCKET_2 24 + 6.9, 23.8, 49.1, +// MZ2_TANK_ROCKET_3 25 + 8.3, 17.8, 49.5, + +// MZ2_INFANTRY_MACHINEGUN_1 26 + 26.6, 7.1, 13.1, +// MZ2_INFANTRY_MACHINEGUN_2 27 + 18.2, 7.5, 15.4, +// MZ2_INFANTRY_MACHINEGUN_3 28 + 17.2, 10.3, 17.9, +// MZ2_INFANTRY_MACHINEGUN_4 29 + 17.0, 12.8, 20.1, +// MZ2_INFANTRY_MACHINEGUN_5 30 + 15.1, 14.1, 21.8, +// MZ2_INFANTRY_MACHINEGUN_6 31 + 11.8, 17.2, 23.1, +// MZ2_INFANTRY_MACHINEGUN_7 32 + 11.4, 20.2, 21.0, +// MZ2_INFANTRY_MACHINEGUN_8 33 + 9.0, 23.0, 18.9, +// MZ2_INFANTRY_MACHINEGUN_9 34 + 13.9, 18.6, 17.7, +// MZ2_INFANTRY_MACHINEGUN_10 35 + 15.4, 15.6, 15.8, +// MZ2_INFANTRY_MACHINEGUN_11 36 + 10.2, 15.2, 25.1, +// MZ2_INFANTRY_MACHINEGUN_12 37 + -1.9, 15.1, 28.2, +// MZ2_INFANTRY_MACHINEGUN_13 38 + -12.4, 13.0, 20.2, + +// MZ2_SOLDIER_BLASTER_1 39 + 10.6 * 1.2, 7.7 * 1.2, 7.8 * 1.2, +// MZ2_SOLDIER_BLASTER_2 40 + 21.1 * 1.2, 3.6 * 1.2, 19.0 * 1.2, +// MZ2_SOLDIER_SHOTGUN_1 41 + 10.6 * 1.2, 7.7 * 1.2, 7.8 * 1.2, +// MZ2_SOLDIER_SHOTGUN_2 42 + 21.1 * 1.2, 3.6 * 1.2, 19.0 * 1.2, +// MZ2_SOLDIER_MACHINEGUN_1 43 + 10.6 * 1.2, 7.7 * 1.2, 7.8 * 1.2, +// MZ2_SOLDIER_MACHINEGUN_2 44 + 21.1 * 1.2, 3.6 * 1.2, 19.0 * 1.2, + +// MZ2_GUNNER_MACHINEGUN_1 45 + 30.1 * 1.15, 3.9 * 1.15, 19.6 * 1.15, +// MZ2_GUNNER_MACHINEGUN_2 46 + 29.1 * 1.15, 2.5 * 1.15, 20.7 * 1.15, +// MZ2_GUNNER_MACHINEGUN_3 47 + 28.2 * 1.15, 2.5 * 1.15, 22.2 * 1.15, +// MZ2_GUNNER_MACHINEGUN_4 48 + 28.2 * 1.15, 3.6 * 1.15, 22.0 * 1.15, +// MZ2_GUNNER_MACHINEGUN_5 49 + 26.9 * 1.15, 2.0 * 1.15, 23.4 * 1.15, +// MZ2_GUNNER_MACHINEGUN_6 50 + 26.5 * 1.15, 0.6 * 1.15, 20.8 * 1.15, +// MZ2_GUNNER_MACHINEGUN_7 51 + 26.9 * 1.15, 0.5 * 1.15, 21.5 * 1.15, +// MZ2_GUNNER_MACHINEGUN_8 52 + 29.0 * 1.15, 2.4 * 1.15, 19.5 * 1.15, +// MZ2_GUNNER_GRENADE_1 53 + 4.6 * 1.15, -16.8 * 1.15, 7.3 * 1.15, +// MZ2_GUNNER_GRENADE_2 54 + 4.6 * 1.15, -16.8 * 1.15, 7.3 * 1.15, +// MZ2_GUNNER_GRENADE_3 55 + 4.6 * 1.15, -16.8 * 1.15, 7.3 * 1.15, +// MZ2_GUNNER_GRENADE_4 56 + 4.6 * 1.15, -16.8 * 1.15, 7.3 * 1.15, + +// MZ2_CHICK_ROCKET_1 57 +// -24.8, -9.0, 39.0, + 24.8, -9.0, 39.0, // PGM - this was incorrect in Q2 + +// MZ2_FLYER_BLASTER_1 58 + 12.1, 13.4, -14.5, +// MZ2_FLYER_BLASTER_2 59 + 12.1, -7.4, -14.5, + +// MZ2_MEDIC_BLASTER_1 60 + 12.1, 5.4, 16.5, + +// MZ2_GLADIATOR_RAILGUN_1 61 + 30.0, 18.0, 28.0, + +// MZ2_HOVER_BLASTER_1 62 + 32.5, -0.8, 10.0, + +// MZ2_ACTOR_MACHINEGUN_1 63 + 18.4, 7.4, 9.6, + +// MZ2_SUPERTANK_MACHINEGUN_1 64 + 30.0, 30.0, 88.5, +// MZ2_SUPERTANK_MACHINEGUN_2 65 + 30.0, 30.0, 88.5, +// MZ2_SUPERTANK_MACHINEGUN_3 66 + 30.0, 30.0, 88.5, +// MZ2_SUPERTANK_MACHINEGUN_4 67 + 30.0, 30.0, 88.5, +// MZ2_SUPERTANK_MACHINEGUN_5 68 + 30.0, 30.0, 88.5, +// MZ2_SUPERTANK_MACHINEGUN_6 69 + 30.0, 30.0, 88.5, +// MZ2_SUPERTANK_ROCKET_1 70 + 16.0, -22.5, 91.2, +// MZ2_SUPERTANK_ROCKET_2 71 + 16.0, -33.4, 86.7, +// MZ2_SUPERTANK_ROCKET_3 72 + 16.0, -42.8, 83.3, + +// --- Start Xian Stuff --- +// MZ2_BOSS2_MACHINEGUN_L1 73 + 32, -40, 70, +// MZ2_BOSS2_MACHINEGUN_L2 74 + 32, -40, 70, +// MZ2_BOSS2_MACHINEGUN_L3 75 + 32, -40, 70, +// MZ2_BOSS2_MACHINEGUN_L4 76 + 32, -40, 70, +// MZ2_BOSS2_MACHINEGUN_L5 77 + 32, -40, 70, +// --- End Xian Stuff + +// MZ2_BOSS2_ROCKET_1 78 + 22.0, 16.0, 10.0, +// MZ2_BOSS2_ROCKET_2 79 + 22.0, 8.0, 10.0, +// MZ2_BOSS2_ROCKET_3 80 + 22.0, -8.0, 10.0, +// MZ2_BOSS2_ROCKET_4 81 + 22.0, -16.0, 10.0, + +// MZ2_FLOAT_BLASTER_1 82 + 32.5, -0.8, 10, + +// MZ2_SOLDIER_BLASTER_3 83 + 20.8 * 1.2, 10.1 * 1.2, -2.7 * 1.2, +// MZ2_SOLDIER_SHOTGUN_3 84 + 20.8 * 1.2, 10.1 * 1.2, -2.7 * 1.2, +// MZ2_SOLDIER_MACHINEGUN_3 85 + 20.8 * 1.2, 10.1 * 1.2, -2.7 * 1.2, +// MZ2_SOLDIER_BLASTER_4 86 + 7.6 * 1.2, 9.3 * 1.2, 0.8 * 1.2, +// MZ2_SOLDIER_SHOTGUN_4 87 + 7.6 * 1.2, 9.3 * 1.2, 0.8 * 1.2, +// MZ2_SOLDIER_MACHINEGUN_4 88 + 7.6 * 1.2, 9.3 * 1.2, 0.8 * 1.2, +// MZ2_SOLDIER_BLASTER_5 89 + 30.5 * 1.2, 9.9 * 1.2, -18.7 * 1.2, +// MZ2_SOLDIER_SHOTGUN_5 90 + 30.5 * 1.2, 9.9 * 1.2, -18.7 * 1.2, +// MZ2_SOLDIER_MACHINEGUN_5 91 + 30.5 * 1.2, 9.9 * 1.2, -18.7 * 1.2, +// MZ2_SOLDIER_BLASTER_6 92 + 27.6 * 1.2, 3.4 * 1.2, -10.4 * 1.2, +// MZ2_SOLDIER_SHOTGUN_6 93 + 27.6 * 1.2, 3.4 * 1.2, -10.4 * 1.2, +// MZ2_SOLDIER_MACHINEGUN_6 94 + 27.6 * 1.2, 3.4 * 1.2, -10.4 * 1.2, +// MZ2_SOLDIER_BLASTER_7 95 + 28.9 * 1.2, 4.6 * 1.2, -8.1 * 1.2, +// MZ2_SOLDIER_SHOTGUN_7 96 + 28.9 * 1.2, 4.6 * 1.2, -8.1 * 1.2, +// MZ2_SOLDIER_MACHINEGUN_7 97 + 28.9 * 1.2, 4.6 * 1.2, -8.1 * 1.2, +// MZ2_SOLDIER_BLASTER_8 98 +// 34.5 * 1.2, 9.6 * 1.2, 6.1 * 1.2, + 31.5 * 1.2, 9.6 * 1.2, 10.1 * 1.2, +// MZ2_SOLDIER_SHOTGUN_8 99 + 34.5 * 1.2, 9.6 * 1.2, 6.1 * 1.2, +// MZ2_SOLDIER_MACHINEGUN_8 100 + 34.5 * 1.2, 9.6 * 1.2, 6.1 * 1.2, + +// --- Xian shit below --- +// MZ2_MAKRON_BFG 101 + 17, -19.5, 62.9, +// MZ2_MAKRON_BLASTER_1 102 + -3.6, -24.1, 59.5, +// MZ2_MAKRON_BLASTER_2 103 + -1.6, -19.3, 59.5, +// MZ2_MAKRON_BLASTER_3 104 + -0.1, -14.4, 59.5, +// MZ2_MAKRON_BLASTER_4 105 + 2.0, -7.6, 59.5, +// MZ2_MAKRON_BLASTER_5 106 + 3.4, 1.3, 59.5, +// MZ2_MAKRON_BLASTER_6 107 + 3.7, 11.1, 59.5, +// MZ2_MAKRON_BLASTER_7 108 + -0.3, 22.3, 59.5, +// MZ2_MAKRON_BLASTER_8 109 + -6, 33, 59.5, +// MZ2_MAKRON_BLASTER_9 110 + -9.3, 36.4, 59.5, +// MZ2_MAKRON_BLASTER_10 111 + -7, 35, 59.5, +// MZ2_MAKRON_BLASTER_11 112 + -2.1, 29, 59.5, +// MZ2_MAKRON_BLASTER_12 113 + 3.9, 17.3, 59.5, +// MZ2_MAKRON_BLASTER_13 114 + 6.1, 5.8, 59.5, +// MZ2_MAKRON_BLASTER_14 115 + 5.9, -4.4, 59.5, +// MZ2_MAKRON_BLASTER_15 116 + 4.2, -14.1, 59.5, +// MZ2_MAKRON_BLASTER_16 117 + 2.4, -18.8, 59.5, +// MZ2_MAKRON_BLASTER_17 118 + -1.8, -25.5, 59.5, +// MZ2_MAKRON_RAILGUN_1 119 + -17.3, 7.8, 72.4, + +// MZ2_JORG_MACHINEGUN_L1 120 + 78.5, -47.1, 96, +// MZ2_JORG_MACHINEGUN_L2 121 + 78.5, -47.1, 96, +// MZ2_JORG_MACHINEGUN_L3 122 + 78.5, -47.1, 96, +// MZ2_JORG_MACHINEGUN_L4 123 + 78.5, -47.1, 96, +// MZ2_JORG_MACHINEGUN_L5 124 + 78.5, -47.1, 96, +// MZ2_JORG_MACHINEGUN_L6 125 + 78.5, -47.1, 96, +// MZ2_JORG_MACHINEGUN_R1 126 + 78.5, 46.7, 96, +// MZ2_JORG_MACHINEGUN_R2 127 + 78.5, 46.7, 96, +// MZ2_JORG_MACHINEGUN_R3 128 + 78.5, 46.7, 96, +// MZ2_JORG_MACHINEGUN_R4 129 + 78.5, 46.7, 96, +// MZ2_JORG_MACHINEGUN_R5 130 + 78.5, 46.7, 96, +// MZ2_JORG_MACHINEGUN_R6 131 + 78.5, 46.7, 96, +// MZ2_JORG_BFG_1 132 + 6.3, -9, 111.2, + +// MZ2_BOSS2_MACHINEGUN_R1 73 + 32, 40, 70, +// MZ2_BOSS2_MACHINEGUN_R2 74 + 32, 40, 70, +// MZ2_BOSS2_MACHINEGUN_R3 75 + 32, 40, 70, +// MZ2_BOSS2_MACHINEGUN_R4 76 + 32, 40, 70, +// MZ2_BOSS2_MACHINEGUN_R5 77 + 32, 40, 70, + +// --- End Xian Shit --- + +// ROGUE +// note that the above really ends at 137 +// carrier machineguns +// MZ2_CARRIER_MACHINEGUN_L1 + 56, -32, 32, +// MZ2_CARRIER_MACHINEGUN_R1 + 56, 32, 32, +// MZ2_CARRIER_GRENADE + 42, 24, 50, +// MZ2_TURRET_MACHINEGUN 141 + 16, 0, 0, +// MZ2_TURRET_ROCKET 142 + 16, 0, 0, +// MZ2_TURRET_BLASTER 143 + 16, 0, 0, +// MZ2_STALKER_BLASTER 144 + 24, 0, 6, +// MZ2_DAEDALUS_BLASTER 145 + 32.5, -0.8, 10.0, +// MZ2_MEDIC_BLASTER_2 146 + 12.1, 5.4, 16.5, +// MZ2_CARRIER_RAILGUN 147 + 32, 0, 6, +// MZ2_WIDOW_DISRUPTOR 148 + 57.72, 14.50, 88.81, +// MZ2_WIDOW_BLASTER 149 + 56, 32, 32, +// MZ2_WIDOW_RAIL 150 + 62, -20, 84, +// MZ2_WIDOW_PLASMABEAM 151 // PMM - not used! + 32, 0, 6, +// MZ2_CARRIER_MACHINEGUN_L2 152 + 61, -32, 12, +// MZ2_CARRIER_MACHINEGUN_R2 153 + 61, 32, 12, +// MZ2_WIDOW_RAIL_LEFT 154 + 17, -62, 91, +// MZ2_WIDOW_RAIL_RIGHT 155 + 68, 12, 86, +// MZ2_WIDOW_BLASTER_SWEEP1 156 pmm - the sweeps need to be in sequential order + 47.5, 56, 89, +// MZ2_WIDOW_BLASTER_SWEEP2 157 + 54, 52, 91, +// MZ2_WIDOW_BLASTER_SWEEP3 158 + 58, 40, 91, +// MZ2_WIDOW_BLASTER_SWEEP4 159 + 68, 30, 88, +// MZ2_WIDOW_BLASTER_SWEEP5 160 + 74, 20, 88, +// MZ2_WIDOW_BLASTER_SWEEP6 161 + 73, 11, 87, +// MZ2_WIDOW_BLASTER_SWEEP7 162 + 73, 3, 87, +// MZ2_WIDOW_BLASTER_SWEEP8 163 + 70, -12, 87, +// MZ2_WIDOW_BLASTER_SWEEP9 164 + 67, -20, 90, +// MZ2_WIDOW_BLASTER_100 165 + -20, 76, 90, +// MZ2_WIDOW_BLASTER_90 166 + -8, 74, 90, +// MZ2_WIDOW_BLASTER_80 167 + 0, 72, 90, +// MZ2_WIDOW_BLASTER_70 168 d06 + 10, 71, 89, +// MZ2_WIDOW_BLASTER_60 169 d07 + 23, 70, 87, +// MZ2_WIDOW_BLASTER_50 170 d08 + 32, 64, 85, +// MZ2_WIDOW_BLASTER_40 171 + 40, 58, 84, +// MZ2_WIDOW_BLASTER_30 172 d10 + 48, 50, 83, +// MZ2_WIDOW_BLASTER_20 173 + 54, 42, 82, +// MZ2_WIDOW_BLASTER_10 174 d12 + 56, 34, 82, +// MZ2_WIDOW_BLASTER_0 175 + 58, 26, 82, +// MZ2_WIDOW_BLASTER_10L 176 d14 + 60, 16, 82, +// MZ2_WIDOW_BLASTER_20L 177 + 59, 6, 81, +// MZ2_WIDOW_BLASTER_30L 178 d16 + 58, -2, 80, +// MZ2_WIDOW_BLASTER_40L 179 + 57, -10, 79, +// MZ2_WIDOW_BLASTER_50L 180 d18 + 54, -18, 78, +// MZ2_WIDOW_BLASTER_60L 181 + 42, -32, 80, +// MZ2_WIDOW_BLASTER_70L 182 d20 + 36, -40, 78, +// MZ2_WIDOW_RUN_1 183 + 68.4, 10.88, 82.08, +// MZ2_WIDOW_RUN_2 184 + 68.51, 8.64, 85.14, +// MZ2_WIDOW_RUN_3 185 + 68.66, 6.38, 88.78, +// MZ2_WIDOW_RUN_4 186 + 68.73, 5.1, 84.47, +// MZ2_WIDOW_RUN_5 187 + 68.82, 4.79, 80.52, +// MZ2_WIDOW_RUN_6 188 + 68.77, 6.11, 85.37, +// MZ2_WIDOW_RUN_7 189 + 68.67, 7.99, 90.24, +// MZ2_WIDOW_RUN_8 190 + 68.55, 9.54, 87.36, +// MZ2_CARRIER_ROCKET_1 191 + 0, 0, -5, +// MZ2_CARRIER_ROCKET_2 192 + 0, 0, -5, +// MZ2_CARRIER_ROCKET_3 193 + 0, 0, -5, +// MZ2_CARRIER_ROCKET_4 194 + 0, 0, -5, +// MZ2_WIDOW2_BEAMER_1 195 +// 72.13, -17.63, 93.77, + 69.00, -17.63, 93.77, +// MZ2_WIDOW2_BEAMER_2 196 +// 71.46, -17.08, 89.82, + 69.00, -17.08, 89.82, +// MZ2_WIDOW2_BEAMER_3 197 +// 71.47, -18.40, 90.70, + 69.00, -18.40, 90.70, +// MZ2_WIDOW2_BEAMER_4 198 +// 71.96, -18.34, 94.32, + 69.00, -18.34, 94.32, +// MZ2_WIDOW2_BEAMER_5 199 +// 72.25, -18.30, 97.98, + 69.00, -18.30, 97.98, +// MZ2_WIDOW2_BEAM_SWEEP_1 200 + 45.04, -59.02, 92.24, +// MZ2_WIDOW2_BEAM_SWEEP_2 201 + 50.68, -54.70, 91.96, +// MZ2_WIDOW2_BEAM_SWEEP_3 202 + 56.57, -47.72, 91.65, +// MZ2_WIDOW2_BEAM_SWEEP_4 203 + 61.75, -38.75, 91.38, +// MZ2_WIDOW2_BEAM_SWEEP_5 204 + 65.55, -28.76, 91.24, +// MZ2_WIDOW2_BEAM_SWEEP_6 205 + 67.79, -18.90, 91.22, +// MZ2_WIDOW2_BEAM_SWEEP_7 206 + 68.60, -9.52, 91.23, +// MZ2_WIDOW2_BEAM_SWEEP_8 207 + 68.08, 0.18, 91.32, +// MZ2_WIDOW2_BEAM_SWEEP_9 208 + 66.14, 9.79, 91.44, +// MZ2_WIDOW2_BEAM_SWEEP_10 209 + 62.77, 18.91, 91.65, +// MZ2_WIDOW2_BEAM_SWEEP_11 210 + 58.29, 27.11, 92.00, + +// end of table + 0.0, 0.0, 0.0 +}; diff --git a/original/xatrix/m_flipper.c b/original/xatrix/m_flipper.c new file mode 100644 index 0000000..586b87f --- /dev/null +++ b/original/xatrix/m_flipper.c @@ -0,0 +1,386 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +/* +============================================================================== + +FLIPPER + +============================================================================== +*/ + +#include "g_local.h" +#include "m_flipper.h" + + +static int sound_chomp; +static int sound_attack; +static int sound_pain1; +static int sound_pain2; +static int sound_death; +static int sound_idle; +static int sound_search; +static int sound_sight; + + +void flipper_stand (edict_t *self); + +mframe_t flipper_frames_stand [] = +{ + ai_stand, 0, NULL +}; + +mmove_t flipper_move_stand = {FRAME_flphor01, FRAME_flphor01, flipper_frames_stand, NULL}; + +void flipper_stand (edict_t *self) +{ + self->monsterinfo.currentmove = &flipper_move_stand; +} + +#define FLIPPER_RUN_SPEED 24 + +mframe_t flipper_frames_run [] = +{ + ai_run, FLIPPER_RUN_SPEED, NULL, // 6 + ai_run, FLIPPER_RUN_SPEED, NULL, + ai_run, FLIPPER_RUN_SPEED, NULL, + ai_run, FLIPPER_RUN_SPEED, NULL, + ai_run, FLIPPER_RUN_SPEED, NULL, // 10 + + ai_run, FLIPPER_RUN_SPEED, NULL, + ai_run, FLIPPER_RUN_SPEED, NULL, + ai_run, FLIPPER_RUN_SPEED, NULL, + ai_run, FLIPPER_RUN_SPEED, NULL, + ai_run, FLIPPER_RUN_SPEED, NULL, + ai_run, FLIPPER_RUN_SPEED, NULL, + ai_run, FLIPPER_RUN_SPEED, NULL, + ai_run, FLIPPER_RUN_SPEED, NULL, + ai_run, FLIPPER_RUN_SPEED, NULL, + ai_run, FLIPPER_RUN_SPEED, NULL, // 20 + + ai_run, FLIPPER_RUN_SPEED, NULL, + ai_run, FLIPPER_RUN_SPEED, NULL, + ai_run, FLIPPER_RUN_SPEED, NULL, + ai_run, FLIPPER_RUN_SPEED, NULL, + ai_run, FLIPPER_RUN_SPEED, NULL, + ai_run, FLIPPER_RUN_SPEED, NULL, + ai_run, FLIPPER_RUN_SPEED, NULL, + ai_run, FLIPPER_RUN_SPEED, NULL, + ai_run, FLIPPER_RUN_SPEED, NULL // 29 +}; +mmove_t flipper_move_run_loop = {FRAME_flpver06, FRAME_flpver29, flipper_frames_run, NULL}; + +void flipper_run_loop (edict_t *self) +{ + self->monsterinfo.currentmove = &flipper_move_run_loop; +} + +mframe_t flipper_frames_run_start [] = +{ + ai_run, 8, NULL, + ai_run, 8, NULL, + ai_run, 8, NULL, + ai_run, 8, NULL, + ai_run, 8, NULL, + ai_run, 8, NULL +}; +mmove_t flipper_move_run_start = {FRAME_flpver01, FRAME_flpver06, flipper_frames_run_start, flipper_run_loop}; + +void flipper_run (edict_t *self) +{ + self->monsterinfo.currentmove = &flipper_move_run_start; +} + +/* Standard Swimming */ +mframe_t flipper_frames_walk [] = +{ + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL +}; +mmove_t flipper_move_walk = {FRAME_flphor01, FRAME_flphor24, flipper_frames_walk, NULL}; + +void flipper_walk (edict_t *self) +{ + self->monsterinfo.currentmove = &flipper_move_walk; +} + +mframe_t flipper_frames_start_run [] = +{ + ai_run, 8, NULL, + ai_run, 8, NULL, + ai_run, 8, NULL, + ai_run, 8, NULL, + ai_run, 8, flipper_run +}; +mmove_t flipper_move_start_run = {FRAME_flphor01, FRAME_flphor05, flipper_frames_start_run, NULL}; + +void flipper_start_run (edict_t *self) +{ + self->monsterinfo.currentmove = &flipper_move_start_run; +} + +mframe_t flipper_frames_pain2 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t flipper_move_pain2 = {FRAME_flppn101, FRAME_flppn105, flipper_frames_pain2, flipper_run}; + +mframe_t flipper_frames_pain1 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t flipper_move_pain1 = {FRAME_flppn201, FRAME_flppn205, flipper_frames_pain1, flipper_run}; + +void flipper_bite (edict_t *self) +{ + vec3_t aim; + + VectorSet (aim, MELEE_DISTANCE, 0, 0); + fire_hit (self, aim, 5, 0); +} + +void flipper_preattack (edict_t *self) +{ + gi.sound (self, CHAN_WEAPON, sound_chomp, 1, ATTN_NORM, 0); +} + +mframe_t flipper_frames_attack [] = +{ + ai_charge, 0, flipper_preattack, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, flipper_bite, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, flipper_bite, + ai_charge, 0, NULL +}; +mmove_t flipper_move_attack = {FRAME_flpbit01, FRAME_flpbit20, flipper_frames_attack, flipper_run}; + +void flipper_melee(edict_t *self) +{ + self->monsterinfo.currentmove = &flipper_move_attack; +} + +void flipper_pain (edict_t *self, edict_t *other, float kick, int damage) +{ + int n; + + if (self->health < (self->max_health / 2)) + self->s.skinnum = 1; + + if (level.time < self->pain_debounce_time) + return; + + self->pain_debounce_time = level.time + 3; + + if (skill->value == 3) + return; // no pain anims in nightmare + + n = (rand() + 1) % 2; + if (n == 0) + { + gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &flipper_move_pain1; + } + else + { + gi.sound (self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &flipper_move_pain2; + } +} + +void flipper_dead (edict_t *self) +{ + VectorSet (self->mins, -16, -16, -24); + VectorSet (self->maxs, 16, 16, -8); + self->movetype = MOVETYPE_TOSS; + self->svflags |= SVF_DEADMONSTER; + self->nextthink = 0; + gi.linkentity (self); +} + +mframe_t flipper_frames_death [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t flipper_move_death = {FRAME_flpdth01, FRAME_flpdth56, flipper_frames_death, flipper_dead}; + +void flipper_sight (edict_t *self, edict_t *other) +{ + gi.sound (self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); +} + +void flipper_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + int n; + +// check for gib + if (self->health <= self->gib_health) + { + gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0); + for (n= 0; n < 2; n++) + ThrowGib (self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC); + for (n= 0; n < 2; n++) + ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); + ThrowHead (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); + self->deadflag = DEAD_DEAD; + return; + } + + if (self->deadflag == DEAD_DEAD) + return; + +// regular death + gi.sound (self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0); + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_YES; + self->monsterinfo.currentmove = &flipper_move_death; +} + +/*QUAKED monster_flipper (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight +*/ +void SP_monster_flipper (edict_t *self) +{ + if (deathmatch->value) + { + G_FreeEdict (self); + return; + } + + sound_pain1 = gi.soundindex ("flipper/flppain1.wav"); + sound_pain2 = gi.soundindex ("flipper/flppain2.wav"); + sound_death = gi.soundindex ("flipper/flpdeth1.wav"); + sound_chomp = gi.soundindex ("flipper/flpatck1.wav"); + sound_attack = gi.soundindex ("flipper/flpatck2.wav"); + sound_idle = gi.soundindex ("flipper/flpidle1.wav"); + sound_search = gi.soundindex ("flipper/flpsrch1.wav"); + sound_sight = gi.soundindex ("flipper/flpsght1.wav"); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex ("models/monsters/flipper/tris.md2"); + VectorSet (self->mins, -16, -16, 0); + VectorSet (self->maxs, 16, 16, 32); + + self->health = 50; + self->gib_health = -30; + self->mass = 100; + + self->pain = flipper_pain; + self->die = flipper_die; + + self->monsterinfo.stand = flipper_stand; + self->monsterinfo.walk = flipper_walk; + self->monsterinfo.run = flipper_start_run; + self->monsterinfo.melee = flipper_melee; + self->monsterinfo.sight = flipper_sight; + + gi.linkentity (self); + + self->monsterinfo.currentmove = &flipper_move_stand; + self->monsterinfo.scale = MODEL_SCALE; + + swimmonster_start (self); +} diff --git a/original/xatrix/m_flipper.h b/original/xatrix/m_flipper.h new file mode 100644 index 0000000..c7afb96 --- /dev/null +++ b/original/xatrix/m_flipper.h @@ -0,0 +1,168 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// G:\quake2\baseq2\models/monsters/flipper + +// This file generated by ModelGen - Do NOT Modify + +#define FRAME_flpbit01 0 +#define FRAME_flpbit02 1 +#define FRAME_flpbit03 2 +#define FRAME_flpbit04 3 +#define FRAME_flpbit05 4 +#define FRAME_flpbit06 5 +#define FRAME_flpbit07 6 +#define FRAME_flpbit08 7 +#define FRAME_flpbit09 8 +#define FRAME_flpbit10 9 +#define FRAME_flpbit11 10 +#define FRAME_flpbit12 11 +#define FRAME_flpbit13 12 +#define FRAME_flpbit14 13 +#define FRAME_flpbit15 14 +#define FRAME_flpbit16 15 +#define FRAME_flpbit17 16 +#define FRAME_flpbit18 17 +#define FRAME_flpbit19 18 +#define FRAME_flpbit20 19 +#define FRAME_flptal01 20 +#define FRAME_flptal02 21 +#define FRAME_flptal03 22 +#define FRAME_flptal04 23 +#define FRAME_flptal05 24 +#define FRAME_flptal06 25 +#define FRAME_flptal07 26 +#define FRAME_flptal08 27 +#define FRAME_flptal09 28 +#define FRAME_flptal10 29 +#define FRAME_flptal11 30 +#define FRAME_flptal12 31 +#define FRAME_flptal13 32 +#define FRAME_flptal14 33 +#define FRAME_flptal15 34 +#define FRAME_flptal16 35 +#define FRAME_flptal17 36 +#define FRAME_flptal18 37 +#define FRAME_flptal19 38 +#define FRAME_flptal20 39 +#define FRAME_flptal21 40 +#define FRAME_flphor01 41 +#define FRAME_flphor02 42 +#define FRAME_flphor03 43 +#define FRAME_flphor04 44 +#define FRAME_flphor05 45 +#define FRAME_flphor06 46 +#define FRAME_flphor07 47 +#define FRAME_flphor08 48 +#define FRAME_flphor09 49 +#define FRAME_flphor10 50 +#define FRAME_flphor11 51 +#define FRAME_flphor12 52 +#define FRAME_flphor13 53 +#define FRAME_flphor14 54 +#define FRAME_flphor15 55 +#define FRAME_flphor16 56 +#define FRAME_flphor17 57 +#define FRAME_flphor18 58 +#define FRAME_flphor19 59 +#define FRAME_flphor20 60 +#define FRAME_flphor21 61 +#define FRAME_flphor22 62 +#define FRAME_flphor23 63 +#define FRAME_flphor24 64 +#define FRAME_flpver01 65 +#define FRAME_flpver02 66 +#define FRAME_flpver03 67 +#define FRAME_flpver04 68 +#define FRAME_flpver05 69 +#define FRAME_flpver06 70 +#define FRAME_flpver07 71 +#define FRAME_flpver08 72 +#define FRAME_flpver09 73 +#define FRAME_flpver10 74 +#define FRAME_flpver11 75 +#define FRAME_flpver12 76 +#define FRAME_flpver13 77 +#define FRAME_flpver14 78 +#define FRAME_flpver15 79 +#define FRAME_flpver16 80 +#define FRAME_flpver17 81 +#define FRAME_flpver18 82 +#define FRAME_flpver19 83 +#define FRAME_flpver20 84 +#define FRAME_flpver21 85 +#define FRAME_flpver22 86 +#define FRAME_flpver23 87 +#define FRAME_flpver24 88 +#define FRAME_flpver25 89 +#define FRAME_flpver26 90 +#define FRAME_flpver27 91 +#define FRAME_flpver28 92 +#define FRAME_flpver29 93 +#define FRAME_flppn101 94 +#define FRAME_flppn102 95 +#define FRAME_flppn103 96 +#define FRAME_flppn104 97 +#define FRAME_flppn105 98 +#define FRAME_flppn201 99 +#define FRAME_flppn202 100 +#define FRAME_flppn203 101 +#define FRAME_flppn204 102 +#define FRAME_flppn205 103 +#define FRAME_flpdth01 104 +#define FRAME_flpdth02 105 +#define FRAME_flpdth03 106 +#define FRAME_flpdth04 107 +#define FRAME_flpdth05 108 +#define FRAME_flpdth06 109 +#define FRAME_flpdth07 110 +#define FRAME_flpdth08 111 +#define FRAME_flpdth09 112 +#define FRAME_flpdth10 113 +#define FRAME_flpdth11 114 +#define FRAME_flpdth12 115 +#define FRAME_flpdth13 116 +#define FRAME_flpdth14 117 +#define FRAME_flpdth15 118 +#define FRAME_flpdth16 119 +#define FRAME_flpdth17 120 +#define FRAME_flpdth18 121 +#define FRAME_flpdth19 122 +#define FRAME_flpdth20 123 +#define FRAME_flpdth21 124 +#define FRAME_flpdth22 125 +#define FRAME_flpdth23 126 +#define FRAME_flpdth24 127 +#define FRAME_flpdth25 128 +#define FRAME_flpdth26 129 +#define FRAME_flpdth27 130 +#define FRAME_flpdth28 131 +#define FRAME_flpdth29 132 +#define FRAME_flpdth30 133 +#define FRAME_flpdth31 134 +#define FRAME_flpdth32 135 +#define FRAME_flpdth33 136 +#define FRAME_flpdth34 137 +#define FRAME_flpdth35 138 +#define FRAME_flpdth36 139 +#define FRAME_flpdth37 140 +#define FRAME_flpdth38 141 +#define FRAME_flpdth39 142 +#define FRAME_flpdth40 143 +#define FRAME_flpdth41 144 +#define FRAME_flpdth42 145 +#define FRAME_flpdth43 146 +#define FRAME_flpdth44 147 +#define FRAME_flpdth45 148 +#define FRAME_flpdth46 149 +#define FRAME_flpdth47 150 +#define FRAME_flpdth48 151 +#define FRAME_flpdth49 152 +#define FRAME_flpdth50 153 +#define FRAME_flpdth51 154 +#define FRAME_flpdth52 155 +#define FRAME_flpdth53 156 +#define FRAME_flpdth54 157 +#define FRAME_flpdth55 158 +#define FRAME_flpdth56 159 + +#define MODEL_SCALE 1.000000 diff --git a/original/xatrix/m_float.c b/original/xatrix/m_float.c new file mode 100644 index 0000000..0ab7aaf --- /dev/null +++ b/original/xatrix/m_float.c @@ -0,0 +1,646 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +/* +============================================================================== + +floater + +============================================================================== +*/ + +#include "g_local.h" +#include "m_float.h" + + +static int sound_attack2; +static int sound_attack3; +static int sound_death1; +static int sound_idle; +static int sound_pain1; +static int sound_pain2; +static int sound_sight; + + +void floater_sight (edict_t *self, edict_t *other) +{ + gi.sound (self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); +} + +void floater_idle (edict_t *self) +{ + gi.sound (self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0); +} + + +//void floater_stand1 (edict_t *self); +void floater_dead (edict_t *self); +void floater_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point); +void floater_run (edict_t *self); +void floater_wham (edict_t *self); +void floater_zap (edict_t *self); + + +void floater_fire_blaster (edict_t *self) +{ + vec3_t start; + vec3_t forward, right; + vec3_t end; + vec3_t dir; + int effect; + + if ((self->s.frame == FRAME_attak104) || (self->s.frame == FRAME_attak107)) + effect = EF_HYPERBLASTER; + else + effect = 0; + AngleVectors (self->s.angles, forward, right, NULL); + G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_FLOAT_BLASTER_1], forward, right, start); + + VectorCopy (self->enemy->s.origin, end); + end[2] += self->enemy->viewheight; + VectorSubtract (end, start, dir); + + monster_fire_blaster (self, start, dir, 1, 1000, MZ2_FLOAT_BLASTER_1, effect); +} + + +mframe_t floater_frames_stand1 [] = +{ + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL +}; +mmove_t floater_move_stand1 = {FRAME_stand101, FRAME_stand152, floater_frames_stand1, NULL}; + +mframe_t floater_frames_stand2 [] = +{ + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL +}; +mmove_t floater_move_stand2 = {FRAME_stand201, FRAME_stand252, floater_frames_stand2, NULL}; + +void floater_stand (edict_t *self) +{ + if (random() <= 0.5) + self->monsterinfo.currentmove = &floater_move_stand1; + else + self->monsterinfo.currentmove = &floater_move_stand2; +} + +mframe_t floater_frames_activate [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t floater_move_activate = {FRAME_actvat01, FRAME_actvat31, floater_frames_activate, NULL}; + +mframe_t floater_frames_attack1 [] = +{ + ai_charge, 0, NULL, // Blaster attack + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, floater_fire_blaster, // BOOM (0, -25.8, 32.5) -- LOOP Starts + ai_charge, 0, floater_fire_blaster, + ai_charge, 0, floater_fire_blaster, + ai_charge, 0, floater_fire_blaster, + ai_charge, 0, floater_fire_blaster, + ai_charge, 0, floater_fire_blaster, + ai_charge, 0, floater_fire_blaster, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL // -- LOOP Ends +}; +mmove_t floater_move_attack1 = {FRAME_attak101, FRAME_attak114, floater_frames_attack1, floater_run}; + +mframe_t floater_frames_attack2 [] = +{ + ai_charge, 0, NULL, // Claws + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, floater_wham, // WHAM (0, -45, 29.6) -- LOOP Starts + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, // -- LOOP Ends + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL +}; +mmove_t floater_move_attack2 = {FRAME_attak201, FRAME_attak225, floater_frames_attack2, floater_run}; + +mframe_t floater_frames_attack3 [] = +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, floater_zap, // -- LOOP Starts + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, // -- LOOP Ends + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL +}; +mmove_t floater_move_attack3 = {FRAME_attak301, FRAME_attak334, floater_frames_attack3, floater_run}; + +mframe_t floater_frames_death [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t floater_move_death = {FRAME_death01, FRAME_death13, floater_frames_death, floater_dead}; + +mframe_t floater_frames_pain1 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t floater_move_pain1 = {FRAME_pain101, FRAME_pain107, floater_frames_pain1, floater_run}; + +mframe_t floater_frames_pain2 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t floater_move_pain2 = {FRAME_pain201, FRAME_pain208, floater_frames_pain2, floater_run}; + +mframe_t floater_frames_pain3 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t floater_move_pain3 = {FRAME_pain301, FRAME_pain312, floater_frames_pain3, floater_run}; + +mframe_t floater_frames_walk [] = +{ + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL +}; +mmove_t floater_move_walk = {FRAME_stand101, FRAME_stand152, floater_frames_walk, NULL}; + +mframe_t floater_frames_run [] = +{ + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL, + ai_run, 13, NULL +}; +mmove_t floater_move_run = {FRAME_stand101, FRAME_stand152, floater_frames_run, NULL}; + +void floater_run (edict_t *self) +{ + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + self->monsterinfo.currentmove = &floater_move_stand1; + else + self->monsterinfo.currentmove = &floater_move_run; +} + +void floater_walk (edict_t *self) +{ + self->monsterinfo.currentmove = &floater_move_walk; +} + +void floater_wham (edict_t *self) +{ + static vec3_t aim = {MELEE_DISTANCE, 0, 0}; + gi.sound (self, CHAN_WEAPON, sound_attack3, 1, ATTN_NORM, 0); + fire_hit (self, aim, 5 + rand() % 6, -50); +} + +void floater_zap (edict_t *self) +{ + vec3_t forward, right; + vec3_t origin; + vec3_t dir; + vec3_t offset; + + VectorSubtract (self->enemy->s.origin, self->s.origin, dir); + + AngleVectors (self->s.angles, forward, right, NULL); + //FIXME use a flash and replace these two lines with the commented one + VectorSet (offset, 18.5, -0.9, 10); + G_ProjectSource (self->s.origin, offset, forward, right, origin); +// G_ProjectSource (self->s.origin, monster_flash_offset[flash_number], forward, right, origin); + + gi.sound (self, CHAN_WEAPON, sound_attack2, 1, ATTN_NORM, 0); + + //FIXME use the flash, Luke + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_SPLASH); + gi.WriteByte (32); + gi.WritePosition (origin); + gi.WriteDir (dir); + gi.WriteByte (1); //sparks + gi.multicast (origin, MULTICAST_PVS); + + T_Damage (self->enemy, self, self, dir, self->enemy->s.origin, vec3_origin, 5 + rand() % 6, -10, DAMAGE_ENERGY, MOD_UNKNOWN); +} + +void floater_attack(edict_t *self) +{ + self->monsterinfo.currentmove = &floater_move_attack1; +} + + +void floater_melee(edict_t *self) +{ + if (random() < 0.5) + self->monsterinfo.currentmove = &floater_move_attack3; + else + self->monsterinfo.currentmove = &floater_move_attack2; +} + + +void floater_pain (edict_t *self, edict_t *other, float kick, int damage) +{ + int n; + + if (self->health < (self->max_health / 2)) + self->s.skinnum = 1; + + if (level.time < self->pain_debounce_time) + return; + + self->pain_debounce_time = level.time + 3; + if (skill->value == 3) + return; // no pain anims in nightmare + + n = (rand() + 1) % 3; + if (n == 0) + { + gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &floater_move_pain1; + } + else + { + gi.sound (self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &floater_move_pain2; + } +} + +void floater_dead (edict_t *self) +{ + VectorSet (self->mins, -16, -16, -24); + VectorSet (self->maxs, 16, 16, -8); + self->movetype = MOVETYPE_TOSS; + self->svflags |= SVF_DEADMONSTER; + self->nextthink = 0; + gi.linkentity (self); +} + +void floater_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + gi.sound (self, CHAN_VOICE, sound_death1, 1, ATTN_NORM, 0); + BecomeExplosion1(self); +} + +/*QUAKED monster_floater (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight +*/ +void SP_monster_floater (edict_t *self) +{ + if (deathmatch->value) + { + G_FreeEdict (self); + return; + } + + sound_attack2 = gi.soundindex ("floater/fltatck2.wav"); + sound_attack3 = gi.soundindex ("floater/fltatck3.wav"); + sound_death1 = gi.soundindex ("floater/fltdeth1.wav"); + sound_idle = gi.soundindex ("floater/fltidle1.wav"); + sound_pain1 = gi.soundindex ("floater/fltpain1.wav"); + sound_pain2 = gi.soundindex ("floater/fltpain2.wav"); + sound_sight = gi.soundindex ("floater/fltsght1.wav"); + + gi.soundindex ("floater/fltatck1.wav"); + + self->s.sound = gi.soundindex ("floater/fltsrch1.wav"); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex ("models/monsters/float/tris.md2"); + VectorSet (self->mins, -24, -24, -24); + VectorSet (self->maxs, 24, 24, 32); + + self->health = 200; + self->gib_health = -80; + self->mass = 300; + + self->pain = floater_pain; + self->die = floater_die; + + self->monsterinfo.stand = floater_stand; + self->monsterinfo.walk = floater_walk; + self->monsterinfo.run = floater_run; +// self->monsterinfo.dodge = floater_dodge; + self->monsterinfo.attack = floater_attack; + self->monsterinfo.melee = floater_melee; + self->monsterinfo.sight = floater_sight; + self->monsterinfo.idle = floater_idle; + + gi.linkentity (self); + + if (random() <= 0.5) + self->monsterinfo.currentmove = &floater_move_stand1; + else + self->monsterinfo.currentmove = &floater_move_stand2; + + self->monsterinfo.scale = MODEL_SCALE; + + flymonster_start (self); +} diff --git a/original/xatrix/m_float.h b/original/xatrix/m_float.h new file mode 100644 index 0000000..3d53ff1 --- /dev/null +++ b/original/xatrix/m_float.h @@ -0,0 +1,256 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// G:\quake2\baseq2\models/monsters/float + +// This file generated by ModelGen - Do NOT Modify + +#define FRAME_actvat01 0 +#define FRAME_actvat02 1 +#define FRAME_actvat03 2 +#define FRAME_actvat04 3 +#define FRAME_actvat05 4 +#define FRAME_actvat06 5 +#define FRAME_actvat07 6 +#define FRAME_actvat08 7 +#define FRAME_actvat09 8 +#define FRAME_actvat10 9 +#define FRAME_actvat11 10 +#define FRAME_actvat12 11 +#define FRAME_actvat13 12 +#define FRAME_actvat14 13 +#define FRAME_actvat15 14 +#define FRAME_actvat16 15 +#define FRAME_actvat17 16 +#define FRAME_actvat18 17 +#define FRAME_actvat19 18 +#define FRAME_actvat20 19 +#define FRAME_actvat21 20 +#define FRAME_actvat22 21 +#define FRAME_actvat23 22 +#define FRAME_actvat24 23 +#define FRAME_actvat25 24 +#define FRAME_actvat26 25 +#define FRAME_actvat27 26 +#define FRAME_actvat28 27 +#define FRAME_actvat29 28 +#define FRAME_actvat30 29 +#define FRAME_actvat31 30 +#define FRAME_attak101 31 +#define FRAME_attak102 32 +#define FRAME_attak103 33 +#define FRAME_attak104 34 +#define FRAME_attak105 35 +#define FRAME_attak106 36 +#define FRAME_attak107 37 +#define FRAME_attak108 38 +#define FRAME_attak109 39 +#define FRAME_attak110 40 +#define FRAME_attak111 41 +#define FRAME_attak112 42 +#define FRAME_attak113 43 +#define FRAME_attak114 44 +#define FRAME_attak201 45 +#define FRAME_attak202 46 +#define FRAME_attak203 47 +#define FRAME_attak204 48 +#define FRAME_attak205 49 +#define FRAME_attak206 50 +#define FRAME_attak207 51 +#define FRAME_attak208 52 +#define FRAME_attak209 53 +#define FRAME_attak210 54 +#define FRAME_attak211 55 +#define FRAME_attak212 56 +#define FRAME_attak213 57 +#define FRAME_attak214 58 +#define FRAME_attak215 59 +#define FRAME_attak216 60 +#define FRAME_attak217 61 +#define FRAME_attak218 62 +#define FRAME_attak219 63 +#define FRAME_attak220 64 +#define FRAME_attak221 65 +#define FRAME_attak222 66 +#define FRAME_attak223 67 +#define FRAME_attak224 68 +#define FRAME_attak225 69 +#define FRAME_attak301 70 +#define FRAME_attak302 71 +#define FRAME_attak303 72 +#define FRAME_attak304 73 +#define FRAME_attak305 74 +#define FRAME_attak306 75 +#define FRAME_attak307 76 +#define FRAME_attak308 77 +#define FRAME_attak309 78 +#define FRAME_attak310 79 +#define FRAME_attak311 80 +#define FRAME_attak312 81 +#define FRAME_attak313 82 +#define FRAME_attak314 83 +#define FRAME_attak315 84 +#define FRAME_attak316 85 +#define FRAME_attak317 86 +#define FRAME_attak318 87 +#define FRAME_attak319 88 +#define FRAME_attak320 89 +#define FRAME_attak321 90 +#define FRAME_attak322 91 +#define FRAME_attak323 92 +#define FRAME_attak324 93 +#define FRAME_attak325 94 +#define FRAME_attak326 95 +#define FRAME_attak327 96 +#define FRAME_attak328 97 +#define FRAME_attak329 98 +#define FRAME_attak330 99 +#define FRAME_attak331 100 +#define FRAME_attak332 101 +#define FRAME_attak333 102 +#define FRAME_attak334 103 +#define FRAME_death01 104 +#define FRAME_death02 105 +#define FRAME_death03 106 +#define FRAME_death04 107 +#define FRAME_death05 108 +#define FRAME_death06 109 +#define FRAME_death07 110 +#define FRAME_death08 111 +#define FRAME_death09 112 +#define FRAME_death10 113 +#define FRAME_death11 114 +#define FRAME_death12 115 +#define FRAME_death13 116 +#define FRAME_pain101 117 +#define FRAME_pain102 118 +#define FRAME_pain103 119 +#define FRAME_pain104 120 +#define FRAME_pain105 121 +#define FRAME_pain106 122 +#define FRAME_pain107 123 +#define FRAME_pain201 124 +#define FRAME_pain202 125 +#define FRAME_pain203 126 +#define FRAME_pain204 127 +#define FRAME_pain205 128 +#define FRAME_pain206 129 +#define FRAME_pain207 130 +#define FRAME_pain208 131 +#define FRAME_pain301 132 +#define FRAME_pain302 133 +#define FRAME_pain303 134 +#define FRAME_pain304 135 +#define FRAME_pain305 136 +#define FRAME_pain306 137 +#define FRAME_pain307 138 +#define FRAME_pain308 139 +#define FRAME_pain309 140 +#define FRAME_pain310 141 +#define FRAME_pain311 142 +#define FRAME_pain312 143 +#define FRAME_stand101 144 +#define FRAME_stand102 145 +#define FRAME_stand103 146 +#define FRAME_stand104 147 +#define FRAME_stand105 148 +#define FRAME_stand106 149 +#define FRAME_stand107 150 +#define FRAME_stand108 151 +#define FRAME_stand109 152 +#define FRAME_stand110 153 +#define FRAME_stand111 154 +#define FRAME_stand112 155 +#define FRAME_stand113 156 +#define FRAME_stand114 157 +#define FRAME_stand115 158 +#define FRAME_stand116 159 +#define FRAME_stand117 160 +#define FRAME_stand118 161 +#define FRAME_stand119 162 +#define FRAME_stand120 163 +#define FRAME_stand121 164 +#define FRAME_stand122 165 +#define FRAME_stand123 166 +#define FRAME_stand124 167 +#define FRAME_stand125 168 +#define FRAME_stand126 169 +#define FRAME_stand127 170 +#define FRAME_stand128 171 +#define FRAME_stand129 172 +#define FRAME_stand130 173 +#define FRAME_stand131 174 +#define FRAME_stand132 175 +#define FRAME_stand133 176 +#define FRAME_stand134 177 +#define FRAME_stand135 178 +#define FRAME_stand136 179 +#define FRAME_stand137 180 +#define FRAME_stand138 181 +#define FRAME_stand139 182 +#define FRAME_stand140 183 +#define FRAME_stand141 184 +#define FRAME_stand142 185 +#define FRAME_stand143 186 +#define FRAME_stand144 187 +#define FRAME_stand145 188 +#define FRAME_stand146 189 +#define FRAME_stand147 190 +#define FRAME_stand148 191 +#define FRAME_stand149 192 +#define FRAME_stand150 193 +#define FRAME_stand151 194 +#define FRAME_stand152 195 +#define FRAME_stand201 196 +#define FRAME_stand202 197 +#define FRAME_stand203 198 +#define FRAME_stand204 199 +#define FRAME_stand205 200 +#define FRAME_stand206 201 +#define FRAME_stand207 202 +#define FRAME_stand208 203 +#define FRAME_stand209 204 +#define FRAME_stand210 205 +#define FRAME_stand211 206 +#define FRAME_stand212 207 +#define FRAME_stand213 208 +#define FRAME_stand214 209 +#define FRAME_stand215 210 +#define FRAME_stand216 211 +#define FRAME_stand217 212 +#define FRAME_stand218 213 +#define FRAME_stand219 214 +#define FRAME_stand220 215 +#define FRAME_stand221 216 +#define FRAME_stand222 217 +#define FRAME_stand223 218 +#define FRAME_stand224 219 +#define FRAME_stand225 220 +#define FRAME_stand226 221 +#define FRAME_stand227 222 +#define FRAME_stand228 223 +#define FRAME_stand229 224 +#define FRAME_stand230 225 +#define FRAME_stand231 226 +#define FRAME_stand232 227 +#define FRAME_stand233 228 +#define FRAME_stand234 229 +#define FRAME_stand235 230 +#define FRAME_stand236 231 +#define FRAME_stand237 232 +#define FRAME_stand238 233 +#define FRAME_stand239 234 +#define FRAME_stand240 235 +#define FRAME_stand241 236 +#define FRAME_stand242 237 +#define FRAME_stand243 238 +#define FRAME_stand244 239 +#define FRAME_stand245 240 +#define FRAME_stand246 241 +#define FRAME_stand247 242 +#define FRAME_stand248 243 +#define FRAME_stand249 244 +#define FRAME_stand250 245 +#define FRAME_stand251 246 +#define FRAME_stand252 247 + +#define MODEL_SCALE 1.000000 diff --git a/original/xatrix/m_flyer.c b/original/xatrix/m_flyer.c new file mode 100644 index 0000000..82ff2e0 --- /dev/null +++ b/original/xatrix/m_flyer.c @@ -0,0 +1,609 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +/* +============================================================================== + +flyer + +============================================================================== +*/ + +#include "g_local.h" +#include "m_flyer.h" + +qboolean visible (edict_t *self, edict_t *other); + +static int nextmove; // Used for start/stop frames + +static int sound_sight; +static int sound_idle; +static int sound_pain1; +static int sound_pain2; +static int sound_slash; +static int sound_sproing; +static int sound_die; + + +void flyer_check_melee(edict_t *self); +void flyer_loop_melee (edict_t *self); +void flyer_melee (edict_t *self); +void flyer_setstart (edict_t *self); +void flyer_stand (edict_t *self); +void flyer_nextmove (edict_t *self); + + +void flyer_sight (edict_t *self, edict_t *other) +{ + gi.sound (self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); +} + +void flyer_idle (edict_t *self) +{ + gi.sound (self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0); +} + +void flyer_pop_blades (edict_t *self) +{ + gi.sound (self, CHAN_VOICE, sound_sproing, 1, ATTN_NORM, 0); +} + + +mframe_t flyer_frames_stand [] = +{ + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL +}; +mmove_t flyer_move_stand = {FRAME_stand01, FRAME_stand45, flyer_frames_stand, NULL}; + + +mframe_t flyer_frames_walk [] = +{ + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL +}; +mmove_t flyer_move_walk = {FRAME_stand01, FRAME_stand45, flyer_frames_walk, NULL}; + +mframe_t flyer_frames_run [] = +{ + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL +}; +mmove_t flyer_move_run = {FRAME_stand01, FRAME_stand45, flyer_frames_run, NULL}; + +void flyer_run (edict_t *self) +{ + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + self->monsterinfo.currentmove = &flyer_move_stand; + else + self->monsterinfo.currentmove = &flyer_move_run; +} + +void flyer_walk (edict_t *self) +{ + self->monsterinfo.currentmove = &flyer_move_walk; +} + +void flyer_stand (edict_t *self) +{ + self->monsterinfo.currentmove = &flyer_move_stand; +} + +mframe_t flyer_frames_start [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, flyer_nextmove +}; +mmove_t flyer_move_start = {FRAME_start01, FRAME_start06, flyer_frames_start, NULL}; + +mframe_t flyer_frames_stop [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, flyer_nextmove +}; +mmove_t flyer_move_stop = {FRAME_stop01, FRAME_stop07, flyer_frames_stop, NULL}; + +void flyer_stop (edict_t *self) +{ + self->monsterinfo.currentmove = &flyer_move_stop; +} + +void flyer_start (edict_t *self) +{ + self->monsterinfo.currentmove = &flyer_move_start; +} + + +mframe_t flyer_frames_rollright [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t flyer_move_rollright = {FRAME_rollr01, FRAME_rollr09, flyer_frames_rollright, NULL}; + +mframe_t flyer_frames_rollleft [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t flyer_move_rollleft = {FRAME_rollf01, FRAME_rollf09, flyer_frames_rollleft, NULL}; + +mframe_t flyer_frames_pain3 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t flyer_move_pain3 = {FRAME_pain301, FRAME_pain304, flyer_frames_pain3, flyer_run}; + +mframe_t flyer_frames_pain2 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t flyer_move_pain2 = {FRAME_pain201, FRAME_pain204, flyer_frames_pain2, flyer_run}; + +mframe_t flyer_frames_pain1 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t flyer_move_pain1 = {FRAME_pain101, FRAME_pain109, flyer_frames_pain1, flyer_run}; + +mframe_t flyer_frames_defense [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, // Hold this frame + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t flyer_move_defense = {FRAME_defens01, FRAME_defens06, flyer_frames_defense, NULL}; + +mframe_t flyer_frames_bankright [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t flyer_move_bankright = {FRAME_bankr01, FRAME_bankr07, flyer_frames_bankright, NULL}; + +mframe_t flyer_frames_bankleft [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t flyer_move_bankleft = {FRAME_bankl01, FRAME_bankl07, flyer_frames_bankleft, NULL}; + + +void flyer_fire (edict_t *self, int flash_number) +{ + vec3_t start; + vec3_t forward, right; + vec3_t end; + vec3_t dir; + int effect; + + if ((self->s.frame == FRAME_attak204) || (self->s.frame == FRAME_attak207) || (self->s.frame == FRAME_attak210)) + effect = EF_HYPERBLASTER; + else + effect = 0; + AngleVectors (self->s.angles, forward, right, NULL); + G_ProjectSource (self->s.origin, monster_flash_offset[flash_number], forward, right, start); + + VectorCopy (self->enemy->s.origin, end); + end[2] += self->enemy->viewheight; + VectorSubtract (end, start, dir); + + monster_fire_blaster (self, start, dir, 1, 1000, flash_number, effect); +} + +void flyer_fireleft (edict_t *self) +{ + flyer_fire (self, MZ2_FLYER_BLASTER_1); +} + +void flyer_fireright (edict_t *self) +{ + flyer_fire (self, MZ2_FLYER_BLASTER_2); +} + + +mframe_t flyer_frames_attack2 [] = +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, -10, flyer_fireleft, // left gun + ai_charge, -10, flyer_fireright, // right gun + ai_charge, -10, flyer_fireleft, // left gun + ai_charge, -10, flyer_fireright, // right gun + ai_charge, -10, flyer_fireleft, // left gun + ai_charge, -10, flyer_fireright, // right gun + ai_charge, -10, flyer_fireleft, // left gun + ai_charge, -10, flyer_fireright, // right gun + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL +}; +mmove_t flyer_move_attack2 = {FRAME_attak201, FRAME_attak217, flyer_frames_attack2, flyer_run}; + + +void flyer_slash_left (edict_t *self) +{ + vec3_t aim; + + VectorSet (aim, MELEE_DISTANCE, self->mins[0], 0); + fire_hit (self, aim, 5, 0); + gi.sound (self, CHAN_WEAPON, sound_slash, 1, ATTN_NORM, 0); +} + +void flyer_slash_right (edict_t *self) +{ + vec3_t aim; + + VectorSet (aim, MELEE_DISTANCE, self->maxs[0], 0); + fire_hit (self, aim, 5, 0); + gi.sound (self, CHAN_WEAPON, sound_slash, 1, ATTN_NORM, 0); +} + +mframe_t flyer_frames_start_melee [] = +{ + ai_charge, 0, flyer_pop_blades, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL +}; +mmove_t flyer_move_start_melee = {FRAME_attak101, FRAME_attak106, flyer_frames_start_melee, flyer_loop_melee}; + +mframe_t flyer_frames_end_melee [] = +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL +}; +mmove_t flyer_move_end_melee = {FRAME_attak119, FRAME_attak121, flyer_frames_end_melee, flyer_run}; + + +mframe_t flyer_frames_loop_melee [] = +{ + ai_charge, 0, NULL, // Loop Start + ai_charge, 0, NULL, + ai_charge, 0, flyer_slash_left, // Left Wing Strike + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, flyer_slash_right, // Right Wing Strike + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL // Loop Ends + +}; +mmove_t flyer_move_loop_melee = {FRAME_attak107, FRAME_attak118, flyer_frames_loop_melee, flyer_check_melee}; + +void flyer_loop_melee (edict_t *self) +{ +/* if (random() <= 0.5) + self->monsterinfo.currentmove = &flyer_move_attack1; + else */ + self->monsterinfo.currentmove = &flyer_move_loop_melee; +} + + + +void flyer_attack (edict_t *self) +{ +/* if (random() <= 0.5) + self->monsterinfo.currentmove = &flyer_move_attack1; + else */ + self->monsterinfo.currentmove = &flyer_move_attack2; +} + +void flyer_setstart (edict_t *self) +{ + nextmove = ACTION_run; + self->monsterinfo.currentmove = &flyer_move_start; +} + +void flyer_nextmove (edict_t *self) +{ + if (nextmove == ACTION_attack1) + self->monsterinfo.currentmove = &flyer_move_start_melee; + else if (nextmove == ACTION_attack2) + self->monsterinfo.currentmove = &flyer_move_attack2; + else if (nextmove == ACTION_run) + self->monsterinfo.currentmove = &flyer_move_run; +} + +void flyer_melee (edict_t *self) +{ +// flyer.nextmove = ACTION_attack1; +// self->monsterinfo.currentmove = &flyer_move_stop; + self->monsterinfo.currentmove = &flyer_move_start_melee; +} + +void flyer_check_melee(edict_t *self) +{ + if (range (self, self->enemy) == RANGE_MELEE) + if (random() <= 0.8) + self->monsterinfo.currentmove = &flyer_move_loop_melee; + else + self->monsterinfo.currentmove = &flyer_move_end_melee; + else + self->monsterinfo.currentmove = &flyer_move_end_melee; +} + +void flyer_pain (edict_t *self, edict_t *other, float kick, int damage) +{ + int n; + + if (self->health < (self->max_health / 2)) + self->s.skinnum = 1; + + if (level.time < self->pain_debounce_time) + return; + + self->pain_debounce_time = level.time + 3; + if (skill->value == 3) + return; // no pain anims in nightmare + + n = rand() % 3; + if (n == 0) + { + gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &flyer_move_pain1; + } + else if (n == 1) + { + gi.sound (self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &flyer_move_pain2; + } + else + { + gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &flyer_move_pain3; + } +} + + +void flyer_die(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + gi.sound (self, CHAN_VOICE, sound_die, 1, ATTN_NORM, 0); + BecomeExplosion1(self); +} + + +/*QUAKED monster_flyer (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight +*/ +void SP_monster_flyer (edict_t *self) +{ + if (deathmatch->value) + { + G_FreeEdict (self); + return; + } + + // fix a map bug in jail5.bsp + if (!stricmp(level.mapname, "jail5") && (self->s.origin[2] == -104)) + { + self->targetname = self->target; + self->target = NULL; + } + + sound_sight = gi.soundindex ("flyer/flysght1.wav"); + sound_idle = gi.soundindex ("flyer/flysrch1.wav"); + sound_pain1 = gi.soundindex ("flyer/flypain1.wav"); + sound_pain2 = gi.soundindex ("flyer/flypain2.wav"); + sound_slash = gi.soundindex ("flyer/flyatck2.wav"); + sound_sproing = gi.soundindex ("flyer/flyatck1.wav"); + sound_die = gi.soundindex ("flyer/flydeth1.wav"); + + gi.soundindex ("flyer/flyatck3.wav"); + + self->s.modelindex = gi.modelindex ("models/monsters/flyer/tris.md2"); + VectorSet (self->mins, -16, -16, -24); + VectorSet (self->maxs, 16, 16, 32); + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + + self->s.sound = gi.soundindex ("flyer/flyidle1.wav"); + + self->health = 50; + self->mass = 50; + + self->pain = flyer_pain; + self->die = flyer_die; + + self->monsterinfo.stand = flyer_stand; + self->monsterinfo.walk = flyer_walk; + self->monsterinfo.run = flyer_run; + self->monsterinfo.attack = flyer_attack; + self->monsterinfo.melee = flyer_melee; + self->monsterinfo.sight = flyer_sight; + self->monsterinfo.idle = flyer_idle; + + gi.linkentity (self); + + self->monsterinfo.currentmove = &flyer_move_stand; + self->monsterinfo.scale = MODEL_SCALE; + + flymonster_start (self); +} diff --git a/original/xatrix/m_flyer.h b/original/xatrix/m_flyer.h new file mode 100644 index 0000000..47b2111 --- /dev/null +++ b/original/xatrix/m_flyer.h @@ -0,0 +1,165 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// G:\quake2\baseq2\models/monsters/flyer + +// This file generated by ModelGen - Do NOT Modify + +#define ACTION_nothing 0 +#define ACTION_attack1 1 +#define ACTION_attack2 2 +#define ACTION_run 3 +#define ACTION_walk 4 + +#define FRAME_start01 0 +#define FRAME_start02 1 +#define FRAME_start03 2 +#define FRAME_start04 3 +#define FRAME_start05 4 +#define FRAME_start06 5 +#define FRAME_stop01 6 +#define FRAME_stop02 7 +#define FRAME_stop03 8 +#define FRAME_stop04 9 +#define FRAME_stop05 10 +#define FRAME_stop06 11 +#define FRAME_stop07 12 +#define FRAME_stand01 13 +#define FRAME_stand02 14 +#define FRAME_stand03 15 +#define FRAME_stand04 16 +#define FRAME_stand05 17 +#define FRAME_stand06 18 +#define FRAME_stand07 19 +#define FRAME_stand08 20 +#define FRAME_stand09 21 +#define FRAME_stand10 22 +#define FRAME_stand11 23 +#define FRAME_stand12 24 +#define FRAME_stand13 25 +#define FRAME_stand14 26 +#define FRAME_stand15 27 +#define FRAME_stand16 28 +#define FRAME_stand17 29 +#define FRAME_stand18 30 +#define FRAME_stand19 31 +#define FRAME_stand20 32 +#define FRAME_stand21 33 +#define FRAME_stand22 34 +#define FRAME_stand23 35 +#define FRAME_stand24 36 +#define FRAME_stand25 37 +#define FRAME_stand26 38 +#define FRAME_stand27 39 +#define FRAME_stand28 40 +#define FRAME_stand29 41 +#define FRAME_stand30 42 +#define FRAME_stand31 43 +#define FRAME_stand32 44 +#define FRAME_stand33 45 +#define FRAME_stand34 46 +#define FRAME_stand35 47 +#define FRAME_stand36 48 +#define FRAME_stand37 49 +#define FRAME_stand38 50 +#define FRAME_stand39 51 +#define FRAME_stand40 52 +#define FRAME_stand41 53 +#define FRAME_stand42 54 +#define FRAME_stand43 55 +#define FRAME_stand44 56 +#define FRAME_stand45 57 +#define FRAME_attak101 58 +#define FRAME_attak102 59 +#define FRAME_attak103 60 +#define FRAME_attak104 61 +#define FRAME_attak105 62 +#define FRAME_attak106 63 +#define FRAME_attak107 64 +#define FRAME_attak108 65 +#define FRAME_attak109 66 +#define FRAME_attak110 67 +#define FRAME_attak111 68 +#define FRAME_attak112 69 +#define FRAME_attak113 70 +#define FRAME_attak114 71 +#define FRAME_attak115 72 +#define FRAME_attak116 73 +#define FRAME_attak117 74 +#define FRAME_attak118 75 +#define FRAME_attak119 76 +#define FRAME_attak120 77 +#define FRAME_attak121 78 +#define FRAME_attak201 79 +#define FRAME_attak202 80 +#define FRAME_attak203 81 +#define FRAME_attak204 82 +#define FRAME_attak205 83 +#define FRAME_attak206 84 +#define FRAME_attak207 85 +#define FRAME_attak208 86 +#define FRAME_attak209 87 +#define FRAME_attak210 88 +#define FRAME_attak211 89 +#define FRAME_attak212 90 +#define FRAME_attak213 91 +#define FRAME_attak214 92 +#define FRAME_attak215 93 +#define FRAME_attak216 94 +#define FRAME_attak217 95 +#define FRAME_bankl01 96 +#define FRAME_bankl02 97 +#define FRAME_bankl03 98 +#define FRAME_bankl04 99 +#define FRAME_bankl05 100 +#define FRAME_bankl06 101 +#define FRAME_bankl07 102 +#define FRAME_bankr01 103 +#define FRAME_bankr02 104 +#define FRAME_bankr03 105 +#define FRAME_bankr04 106 +#define FRAME_bankr05 107 +#define FRAME_bankr06 108 +#define FRAME_bankr07 109 +#define FRAME_rollf01 110 +#define FRAME_rollf02 111 +#define FRAME_rollf03 112 +#define FRAME_rollf04 113 +#define FRAME_rollf05 114 +#define FRAME_rollf06 115 +#define FRAME_rollf07 116 +#define FRAME_rollf08 117 +#define FRAME_rollf09 118 +#define FRAME_rollr01 119 +#define FRAME_rollr02 120 +#define FRAME_rollr03 121 +#define FRAME_rollr04 122 +#define FRAME_rollr05 123 +#define FRAME_rollr06 124 +#define FRAME_rollr07 125 +#define FRAME_rollr08 126 +#define FRAME_rollr09 127 +#define FRAME_defens01 128 +#define FRAME_defens02 129 +#define FRAME_defens03 130 +#define FRAME_defens04 131 +#define FRAME_defens05 132 +#define FRAME_defens06 133 +#define FRAME_pain101 134 +#define FRAME_pain102 135 +#define FRAME_pain103 136 +#define FRAME_pain104 137 +#define FRAME_pain105 138 +#define FRAME_pain106 139 +#define FRAME_pain107 140 +#define FRAME_pain108 141 +#define FRAME_pain109 142 +#define FRAME_pain201 143 +#define FRAME_pain202 144 +#define FRAME_pain203 145 +#define FRAME_pain204 146 +#define FRAME_pain301 147 +#define FRAME_pain302 148 +#define FRAME_pain303 149 +#define FRAME_pain304 150 + +#define MODEL_SCALE 1.000000 diff --git a/original/xatrix/m_gekk.c b/original/xatrix/m_gekk.c new file mode 100644 index 0000000..a17caf6 --- /dev/null +++ b/original/xatrix/m_gekk.c @@ -0,0 +1,1582 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +/* + xatrix + gekk.c +*/ + +#include "g_local.h" +#include "m_gekk.h" + +static int sound_swing; +static int sound_hit; +static int sound_hit2; +static int sound_death; +static int sound_pain1; +static int sound_sight; +static int sound_search; +static int sound_step1; +static int sound_step2; +static int sound_step3; +static int sound_thud; +static int sound_chantlow; +static int sound_chantmid; +static int sound_chanthigh; + +void gekk_swim (edict_t *self); + +extern void gekk_jump_takeoff (edict_t *self); +extern void gekk_jump_takeoff2 (edict_t *self); +extern void gekk_check_landing (edict_t *self); +extern void gekk_check_landing2 (edict_t *self); +extern void gekk_stop_skid (edict_t *self); + +extern void water_to_land (edict_t *self); +extern void land_to_water (edict_t *self); + +extern void gekk_check_underwater (edict_t *self); +extern void gekk_bite (edict_t *self); + +extern void gekk_hit_left (edict_t *self); +extern void gekk_hit_right (edict_t *self); +extern void gekk_run_start (edict_t *self); + +extern mmove_t gekk_move_attack1; +extern mmove_t gekk_move_attack2; +extern mmove_t gekk_move_chant; +extern mmove_t gekk_move_swim_start; +extern mmove_t gekk_move_swim_loop; +extern mmove_t gekk_move_spit; +extern mmove_t gekk_move_run_start; + +extern qboolean gekk_check_jump (edict_t *self); + +// +// CHECKATTACK +// + +qboolean gekk_check_melee (edict_t *self) +{ + if (!self->enemy && self->enemy->health <= 0) + return false; + + if (range (self, self->enemy) == RANGE_MELEE) + return true; + return false; +} + +qboolean gekk_check_jump (edict_t *self) +{ + vec3_t v; + float distance; + + if (self->absmin[2] > (self->enemy->absmin[2] + 0.75 * self->enemy->size[2])) + return false; + + if (self->absmax[2] < (self->enemy->absmin[2] + 0.25 * self->enemy->size[2])) + return false; + + v[0] = self->s.origin[0] - self->enemy->s.origin[0]; + v[1] = self->s.origin[1] - self->enemy->s.origin[1]; + v[2] = 0; + distance = VectorLength(v); + + if (distance < 100) + { + return false; + } + if (distance > 100) + { + if (random() < 0.9) + return false; + } + + return true; +} + +qboolean gekk_check_jump_close (edict_t *self) +{ + vec3_t v; + float distance; + + v[0] = self->s.origin[0] - self->enemy->s.origin[0]; + v[1] = self->s.origin[1] - self->enemy->s.origin[1]; + v[2] = 0; + + distance = VectorLength(v); + + if (distance < 100) + { + if (self->s.origin[2] < self->enemy->s.origin[2]) + return true; + else + return false; + } + + return true; +} + + +qboolean gekk_checkattack (edict_t *self) +{ + if (!self->enemy || self->enemy->health <= 0) + return false; + + if (gekk_check_melee(self)) + { + self->monsterinfo.attack_state = AS_MELEE; + return true; + } + + if (gekk_check_jump(self)) + { + self->monsterinfo.attack_state = AS_MISSILE; + return true; + } + + if (gekk_check_jump_close (self) && !self->waterlevel) + { + self->monsterinfo.attack_state = AS_MISSILE; + return true; + } + + return false; +} + + +// +// SOUNDS +// + +void gekk_step (edict_t *self) +{ + int n; + n = (rand() + 1) % 3; + if (n == 0) + gi.sound (self, CHAN_VOICE, sound_step1, 1, ATTN_NORM, 0); + else if (n == 1) + gi.sound (self, CHAN_VOICE, sound_step2, 1, ATTN_NORM, 0); + else + gi.sound (self, CHAN_VOICE, sound_step3, 1, ATTN_NORM, 0); +} + +void gekk_sight (edict_t *self, edict_t *other) +{ + gi.sound (self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); +} + +void gekk_search (edict_t *self) +{ + float r; + + if (self->spawnflags & 8) + { + r = random(); + if (r < 0.33) + gi.sound (self, CHAN_VOICE, sound_chantlow, 1, ATTN_NORM, 0); + else if (r < 0.66) + gi.sound (self, CHAN_VOICE, sound_chantmid, 1, ATTN_NORM, 0); + else + gi.sound (self, CHAN_VOICE, sound_chanthigh, 1, ATTN_NORM, 0); + } + else + gi.sound (self, CHAN_VOICE, sound_search, 1, ATTN_NORM, 0); + + + self->health += 10 + (10 * random()); + if (self->health > self->max_health) + self->health = self->max_health; + + if (self->health < (self->max_health /4)) + self->s.skinnum = 2; + else if (self->health < (self->max_health / 2)) + self->s.skinnum = 1; + else + self->s.skinnum = 0; +} + +void gekk_swing (edict_t *self) +{ + gi.sound (self, CHAN_VOICE, sound_swing, 1, ATTN_NORM, 0); +} + +extern mmove_t gekk_move_run; +void gekk_face (edict_t *self) +{ + self->monsterinfo.currentmove = &gekk_move_run; +} + +// +// STAND +// + +void ai_stand2 (edict_t *self, float dist) +{ + if (self->spawnflags & 8) + { + ai_move (self, dist); + if (!(self->spawnflags & 1) && (self->monsterinfo.idle) && (level.time > self->monsterinfo.idle_time)) + { + if (self->monsterinfo.idle_time) + { + self->monsterinfo.idle (self); + self->monsterinfo.idle_time = level.time + 15 + random() * 15; + } + else + { + self->monsterinfo.idle_time = level.time + random() * 15; + } + } + } + else + ai_stand (self, dist); +} + +mframe_t gekk_frames_stand [] = +{ + ai_stand2, 0, NULL, + ai_stand2, 0, NULL, + ai_stand2, 0, NULL, + ai_stand2, 0, NULL, + ai_stand2, 0, NULL, + ai_stand2, 0, NULL, + ai_stand2, 0, NULL, + ai_stand2, 0, NULL, + ai_stand2, 0, NULL, + ai_stand2, 0, NULL, // 10 + + ai_stand2, 0, NULL, + ai_stand2, 0, NULL, + ai_stand2, 0, NULL, + ai_stand2, 0, NULL, + ai_stand2, 0, NULL, + ai_stand2, 0, NULL, + ai_stand2, 0, NULL, + ai_stand2, 0, NULL, + ai_stand2, 0, NULL, + ai_stand2, 0, NULL, // 20 + + ai_stand2, 0, NULL, + ai_stand2, 0, NULL, + ai_stand2, 0, NULL, + ai_stand2, 0, NULL, + ai_stand2, 0, NULL, + ai_stand2, 0, NULL, + ai_stand2, 0, NULL, + ai_stand2, 0, NULL, + ai_stand2, 0, NULL, + ai_stand2, 0, NULL, // 30 + + ai_stand2, 0, NULL, + ai_stand2, 0, NULL, + ai_stand2, 0, NULL, + ai_stand2, 0, NULL, + ai_stand2, 0, NULL, + ai_stand2, 0, NULL, + ai_stand2, 0, NULL, + ai_stand2, 0, NULL, + + ai_stand2, 0, gekk_check_underwater, +}; +mmove_t gekk_move_stand = {FRAME_stand_01, FRAME_stand_39, gekk_frames_stand, NULL}; + +mframe_t gekk_frames_standunderwater[] = +{ + ai_stand2, 0, NULL, + ai_stand2, 0, NULL, + ai_stand2, 0, NULL, + + ai_stand2, 0, gekk_check_underwater +}; + +mmove_t gekk_move_standunderwater = {FRAME_amb_01, FRAME_amb_04, gekk_frames_standunderwater, NULL}; + + +void gekk_swim_loop (edict_t *self) +{ + self->flags |= FL_SWIM; + self->monsterinfo.currentmove = &gekk_move_swim_loop; +} + +mframe_t gekk_frames_swim [] = +{ + ai_run, 16, NULL, + ai_run, 16, NULL, + ai_run, 16, NULL, + + ai_run, 16, gekk_swim +}; +mmove_t gekk_move_swim_loop = {FRAME_amb_01, FRAME_amb_04, gekk_frames_swim, gekk_swim_loop}; + +mframe_t gekk_frames_swim_start [] = +{ + ai_run, 14, NULL, + ai_run, 14, NULL, + ai_run, 14, NULL, + ai_run, 14, NULL, + ai_run, 16, NULL, + ai_run, 16, NULL, + ai_run, 16, NULL, + ai_run, 18, NULL, + ai_run, 18, gekk_hit_left, + ai_run, 18, NULL, + + ai_run, 20, NULL, + ai_run, 20, NULL, + ai_run, 22, NULL, + ai_run, 22, NULL, + ai_run, 24, gekk_hit_right, + ai_run, 24, NULL, + ai_run, 26, NULL, + ai_run, 26, NULL, + ai_run, 24, NULL, + ai_run, 24, NULL, + + ai_run, 22, gekk_bite, + ai_run, 22, NULL, + ai_run, 22, NULL, + ai_run, 22, NULL, + ai_run, 22, NULL, + ai_run, 22, NULL, + ai_run, 22, NULL, + ai_run, 22, NULL, + ai_run, 18, NULL, + ai_run, 18, NULL, + + ai_run, 18, NULL, + ai_run, 18, NULL +}; +mmove_t gekk_move_swim_start = {FRAME_swim_01, FRAME_swim_32, gekk_frames_swim_start, gekk_swim_loop}; + +void gekk_swim (edict_t *self) +{ + + if (gekk_checkattack) + if (!self->enemy->waterlevel && random() > 0.7) + water_to_land (self); + else + self->monsterinfo.currentmove = &gekk_move_swim_start; +} + + +void gekk_stand (edict_t *self) +{ + if (self->waterlevel) + self->monsterinfo.currentmove = &gekk_move_standunderwater; + else + self->monsterinfo.currentmove = &gekk_move_stand; +} + +void gekk_chant (edict_t *self) +{ + self->monsterinfo.currentmove = &gekk_move_chant; +} + +// +// IDLE +// + +void gekk_idle_loop (edict_t *self) +{ + if (random() > 0.75 && self->health < self->max_health) + self->monsterinfo.nextframe = FRAME_idle_01; +} + +mframe_t gekk_frames_idle [] = +{ + ai_stand2, 0, gekk_search, + ai_stand2, 0, NULL, + ai_stand2, 0, NULL, + ai_stand2, 0, NULL, + ai_stand2, 0, NULL, + ai_stand2, 0, NULL, + ai_stand2, 0, NULL, + ai_stand2, 0, NULL, + ai_stand2, 0, NULL, + ai_stand2, 0, NULL, + + ai_stand2, 0, NULL, + ai_stand2, 0, NULL, + ai_stand2, 0, NULL, + ai_stand2, 0, NULL, + ai_stand2, 0, NULL, + ai_stand2, 0, NULL, + ai_stand2, 0, NULL, + ai_stand2, 0, NULL, + ai_stand2, 0, NULL, + ai_stand2, 0, NULL, + + ai_stand2, 0, NULL, + ai_stand2, 0, NULL, + ai_stand2, 0, NULL, + ai_stand2, 0, NULL, + ai_stand2, 0, NULL, + ai_stand2, 0, NULL, + ai_stand2, 0, NULL, + ai_stand2, 0, NULL, + ai_stand2, 0, NULL, + ai_stand2, 0, NULL, + + ai_stand2, 0, NULL, + ai_stand2, 0, gekk_idle_loop +}; +mmove_t gekk_move_idle = {FRAME_idle_01, FRAME_idle_32, gekk_frames_idle, gekk_stand}; +mmove_t gekk_move_idle2 = {FRAME_idle_01, FRAME_idle_32, gekk_frames_idle, gekk_face}; + +mframe_t gekk_frames_idle2 [] = +{ + ai_move, 0, gekk_search, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 0, NULL, + ai_move, 0, gekk_idle_loop +}; +mmove_t gekk_move_chant = {FRAME_idle_01, FRAME_idle_32, gekk_frames_idle2, gekk_chant}; + + +void gekk_idle (edict_t *self) +{ + if (!self->waterlevel) + self->monsterinfo.currentmove = &gekk_move_idle; + else + self->monsterinfo.currentmove = &gekk_move_swim_start; + // gi.sound (self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0); +} + + +// +// WALK +// + +void gekk_walk (edict_t *self); +mframe_t gekk_frames_walk[] = +{ + ai_walk, 3.849, gekk_check_underwater, // frame 0 + ai_walk, 19.606, NULL, // frame 1 + ai_walk, 25.583, NULL, // frame 2 + ai_walk, 34.625, gekk_step, // frame 3 + ai_walk, 27.365, NULL, // frame 4 + ai_walk, 28.480, NULL, // frame 5 +}; + +mmove_t gekk_move_walk = {FRAME_run_01, FRAME_run_06, gekk_frames_walk, NULL}; + +void gekk_walk (edict_t *self) +{ + self->monsterinfo.currentmove = &gekk_move_walk; +} + + +// +// RUN +// + +void gekk_run_start (edict_t *self) +{ + if (self->waterlevel) + { + self->monsterinfo.currentmove = &gekk_move_swim_start; + } + else + { + self->monsterinfo.currentmove = &gekk_move_run_start; + } +} + +void gekk_run (edict_t *self) +{ + + if (self->waterlevel) + { + self->monsterinfo.currentmove = &gekk_move_swim_start; + return; + } + else + { + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + self->monsterinfo.currentmove = &gekk_move_stand; + else + self->monsterinfo.currentmove = &gekk_move_run; + } + +} +mframe_t gekk_frames_run[] = +{ + ai_run, 3.849, gekk_check_underwater, // frame 0 + ai_run, 19.606, NULL, // frame 1 + ai_run, 25.583, NULL, // frame 2 + ai_run, 34.625, gekk_step, // frame 3 + ai_run, 27.365, NULL, // frame 4 + ai_run, 28.480, NULL, // frame 5 +}; +mmove_t gekk_move_run = {FRAME_run_01, FRAME_run_06, gekk_frames_run, NULL}; + +mframe_t gekk_frames_run_st[] = +{ + ai_run, 0.212, NULL, // frame 0 + ai_run, 19.753, NULL, // frame 1 +}; +mmove_t gekk_move_run_start = {FRAME_stand_01, FRAME_stand_02, gekk_frames_run_st, gekk_run}; + +// +// MELEE +// + +void gekk_hit_left (edict_t *self) +{ + vec3_t aim; + + VectorSet (aim, MELEE_DISTANCE, self->mins[0], 8); + if (fire_hit (self, aim, (15 + (rand() %5)), 100)) + gi.sound (self, CHAN_WEAPON, sound_hit, 1, ATTN_NORM, 0); + else + gi.sound (self, CHAN_WEAPON, sound_swing, 1, ATTN_NORM, 0); +} + +void gekk_hit_right (edict_t *self) +{ + vec3_t aim; + + VectorSet (aim, MELEE_DISTANCE, self->maxs[0], 8); + if (fire_hit (self, aim, (15 + (rand() %5)), 100)) + gi.sound (self, CHAN_WEAPON, sound_hit2, 1, ATTN_NORM, 0); + else + gi.sound (self, CHAN_WEAPON, sound_swing, 1, ATTN_NORM, 0); +} + +void gekk_check_refire (edict_t *self) +{ + if (!self->enemy || !self->enemy->inuse || self->enemy->health <= 0) + return; + + if (random() < (skill->value * 0.1)) + { + if (range (self, self->enemy) == RANGE_MELEE) + { + if (self->s.frame == FRAME_clawatk3_09) + self->monsterinfo.currentmove = &gekk_move_attack2; + else if (self->s.frame == FRAME_clawatk5_09) + self->monsterinfo.currentmove = &gekk_move_attack1; + } + } + +} + + +void loogie_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + + if (other == self->owner) + return; + + if (surf && (surf->flags & SURF_SKY)) + { + G_FreeEdict (self); + return; + } + + if (self->owner->client) + PlayerNoise(self->owner, self->s.origin, PNOISE_IMPACT); + + if (other->takedamage) + T_Damage (other, self, self->owner, self->velocity, self->s.origin, plane->normal, self->dmg, 1, DAMAGE_ENERGY, MOD_GEKK); + + G_FreeEdict (self); +}; + + + +void fire_loogie (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed) +{ + edict_t *loogie; + trace_t tr; + + VectorNormalize (dir); + + loogie = G_Spawn(); + VectorCopy (start, loogie->s.origin); + VectorCopy (start, loogie->s.old_origin); + vectoangles (dir, loogie->s.angles); + VectorScale (dir, speed, loogie->velocity); + loogie->movetype = MOVETYPE_FLYMISSILE; + loogie->clipmask = MASK_SHOT; + loogie->solid = SOLID_BBOX; + loogie->s.effects |= RF_FULLBRIGHT; + VectorClear (loogie->mins); + VectorClear (loogie->maxs); + + loogie->s.modelindex = gi.modelindex ("models/objects/loogy/tris.md2"); + loogie->owner = self; + loogie->touch = loogie_touch; + loogie->nextthink = level.time + 2; + loogie->think = G_FreeEdict; + loogie->dmg = damage; + gi.linkentity (loogie); + + tr = gi.trace (self->s.origin, NULL, NULL, loogie->s.origin, loogie, MASK_SHOT); + if (tr.fraction < 1.0) + { + VectorMA (loogie->s.origin, -10, dir, loogie->s.origin); + loogie->touch (loogie, tr.ent, NULL, NULL); + } + +} + +void loogie (edict_t *self) +{ + vec3_t start; + vec3_t forward, right, up; + vec3_t end; + vec3_t dir; + vec3_t gekkoffset; + + VectorSet (gekkoffset, -18, -0.8, 24); + + if (!self->enemy || self->enemy->health <= 0) + return; + + AngleVectors (self->s.angles, forward, right, up); + G_ProjectSource (self->s.origin, gekkoffset, forward, right, start); + + VectorMA (start, 2, up, start); + + VectorCopy (self->enemy->s.origin, end); + end[2] += self->enemy->viewheight; + VectorSubtract (end, start, dir); + + fire_loogie (self, start, dir, 5, 550); + +} + +void reloogie (edict_t *self) +{ + if (random() > 0.8 && self->health < self->max_health) + { + self->monsterinfo.currentmove = &gekk_move_idle2; + return; + } + + if (self->enemy->health >= 0) + if (random() > 0.7 && (range(self, self->enemy) == RANGE_NEAR)) + self->monsterinfo.currentmove = &gekk_move_spit; +} + + +mframe_t gekk_frames_spit [] = +{ + ai_charge, 0.000, NULL, + ai_charge, 0.000, NULL, + ai_charge, 0.000, NULL, + ai_charge, 0.000, NULL, + ai_charge, 0.000, NULL, + + ai_charge, 0.000, loogie, + ai_charge, 0.000, reloogie +}; +mmove_t gekk_move_spit = {FRAME_spit_01, FRAME_spit_07, gekk_frames_spit, gekk_run_start}; + + +mframe_t gekk_frames_attack1 [] = +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + + ai_charge, 0, gekk_hit_left, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, gekk_check_refire + +}; +mmove_t gekk_move_attack1 = {FRAME_clawatk3_01, FRAME_clawatk3_09, gekk_frames_attack1, gekk_run_start}; + +mframe_t gekk_frames_attack2[] = +{ + ai_charge, 0.000, NULL, + ai_charge, 0.000, NULL, + ai_charge, 0.000, gekk_hit_left, + + ai_charge, 0.000, NULL, + ai_charge, 0.000, NULL, + ai_charge, 0.000, gekk_hit_right, + + ai_charge, 0.000, NULL, + ai_charge, 0.000, NULL, + ai_charge, 0.000, gekk_check_refire + +}; +mmove_t gekk_move_attack2 = {FRAME_clawatk5_01, FRAME_clawatk5_09, gekk_frames_attack2, gekk_run_start}; + +void gekk_check_underwater (edict_t *self) +{ + if (self->waterlevel) + { + land_to_water (self); + } +} + +mframe_t gekk_frames_leapatk[] = +{ + ai_charge, 0.000, NULL, // frame 0 + ai_charge, -0.387, NULL, // frame 1 + ai_charge, -1.113, NULL, // frame 2 + ai_charge, -0.237, NULL, // frame 3 + ai_charge, 6.720, gekk_jump_takeoff, // frame 4 last frame on ground + ai_charge, 6.414, NULL, // frame 5 leaves ground + ai_charge, 0.163, NULL, // frame 6 + ai_charge, 28.316, NULL, // frame 7 + ai_charge, 24.198, NULL, // frame 8 + ai_charge, 31.742, NULL, // frame 9 + ai_charge, 35.977, gekk_check_landing, // frame 10 last frame in air + ai_charge, 12.303, gekk_stop_skid, // frame 11 feet back on ground + ai_charge, 20.122, gekk_stop_skid, // frame 12 + ai_charge, -1.042, gekk_stop_skid, // frame 13 + ai_charge, 2.556, gekk_stop_skid, // frame 14 + ai_charge, 0.544, gekk_stop_skid, // frame 15 + ai_charge, 1.862, gekk_stop_skid, // frame 16 + ai_charge, 1.224, gekk_stop_skid, // frame 17 + + ai_charge, -0.457, gekk_check_underwater, // frame 18 +}; +mmove_t gekk_move_leapatk = {FRAME_leapatk_01, FRAME_leapatk_19, gekk_frames_leapatk, gekk_run_start}; + + +mframe_t gekk_frames_leapatk2[] = +{ + ai_charge, 0.000, NULL, // frame 0 + ai_charge, -0.387, NULL, // frame 1 + ai_charge, -1.113, NULL, // frame 2 + ai_charge, -0.237, NULL, // frame 3 + ai_charge, 6.720, gekk_jump_takeoff2, // frame 4 last frame on ground + ai_charge, 6.414, NULL, // frame 5 leaves ground + ai_charge, 0.163, NULL, // frame 6 + ai_charge, 28.316, NULL, // frame 7 + ai_charge, 24.198, NULL, // frame 8 + ai_charge, 31.742, NULL, // frame 9 + ai_charge, 35.977, gekk_check_landing, // frame 10 last frame in air + ai_charge, 12.303, gekk_stop_skid, // frame 11 feet back on ground + ai_charge, 20.122, gekk_stop_skid, // frame 12 + ai_charge, -1.042, gekk_stop_skid, // frame 13 + ai_charge, 2.556, gekk_stop_skid, // frame 14 + ai_charge, 0.544, gekk_stop_skid, // frame 15 + ai_charge, 1.862, gekk_stop_skid, // frame 16 + ai_charge, 1.224, gekk_stop_skid, // frame 17 + + ai_charge, -0.457, gekk_check_underwater, // frame 18 +}; +mmove_t gekk_move_leapatk2 = {FRAME_leapatk_01, FRAME_leapatk_19, gekk_frames_leapatk2, gekk_run_start}; + + +void gekk_bite (edict_t *self) +{ + vec3_t aim; + + VectorSet (aim, MELEE_DISTANCE, 0, 0); + fire_hit (self, aim, 5, 0); +} + +void gekk_preattack (edict_t *self) +{ + // underwater attack sound + // gi.sound (self, CHAN_WEAPON, something something underwater sound, 1, ATTN_NORM, 0); + return; +} + + +mframe_t gekk_frames_attack [] = +{ + ai_charge, 16, gekk_preattack, + ai_charge, 16, NULL, + ai_charge, 16, NULL, + ai_charge, 16, NULL, + ai_charge, 16, gekk_bite, + ai_charge, 16, NULL, + ai_charge, 16, NULL, + ai_charge, 16, NULL, + ai_charge, 16, NULL, + ai_charge, 16, gekk_bite, + + ai_charge, 16, NULL, + ai_charge, 16, NULL, + ai_charge, 16, NULL, + ai_charge, 16, gekk_hit_left, + ai_charge, 16, NULL, + ai_charge, 16, NULL, + ai_charge, 16, NULL, + ai_charge, 16, NULL, + ai_charge, 16, gekk_hit_right, + ai_charge, 16, NULL, + + ai_charge, 16, NULL + +}; +mmove_t gekk_move_attack = {FRAME_attack_01, FRAME_attack_21, gekk_frames_attack, gekk_run_start}; + +void gekk_melee (edict_t *self) +{ + + float r; + + if (self->waterlevel) + { + self->monsterinfo.currentmove = &gekk_move_attack; + } + else + { + r = random(); + + if (r > 0.66) + self->monsterinfo.currentmove = &gekk_move_attack1; + else + self->monsterinfo.currentmove = &gekk_move_attack2; + + } + +} + + +// +// ATTACK +// + +void gekk_jump_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (self->health <= 0) + { + self->touch = NULL; + return; + } + + if (other->takedamage) + { + if (VectorLength(self->velocity) > 200) + { + vec3_t point; + vec3_t normal; + int damage; + + VectorCopy (self->velocity, normal); + VectorNormalize(normal); + VectorMA (self->s.origin, self->maxs[0], normal, point); + damage = 10 + 10 * random(); + T_Damage (other, self, self, self->velocity, point, normal, damage, damage, 0, MOD_GEKK); + } + } + + if (!M_CheckBottom (self)) + { + if (self->groundentity) + { + self->monsterinfo.nextframe = FRAME_leapatk_11; + self->touch = NULL; + } + return; + } + + self->touch = NULL; +} + +void gekk_jump_takeoff (edict_t *self) +{ + vec3_t forward; + + gi.sound (self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); + AngleVectors (self->s.angles, forward, NULL, NULL); + self->s.origin[2] += 1; + + // high jump + if (gekk_check_jump (self)) + { + VectorScale (forward, 700, self->velocity); + self->velocity[2] = 250; + } + else + { + VectorScale (forward, 250, self->velocity); + self->velocity[2] = 400; + } + + + self->groundentity = NULL; + self->monsterinfo.aiflags |= AI_DUCKED; + self->monsterinfo.attack_finished = level.time + 3; + self->touch = gekk_jump_touch; +} + +void gekk_jump_takeoff2 (edict_t *self) +{ + vec3_t forward; + + gi.sound (self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); + AngleVectors (self->s.angles, forward, NULL, NULL); + self->s.origin[2] = self->enemy->s.origin[2]; + + if (gekk_check_jump (self)) + { + VectorScale (forward, 300, self->velocity); + self->velocity[2] = 250; + } + else + { + VectorScale (forward, 150, self->velocity); + self->velocity[2] = 300; + } + + self->groundentity = NULL; + self->monsterinfo.aiflags |= AI_DUCKED; + self->monsterinfo.attack_finished = level.time + 3; + self->touch = gekk_jump_touch; + +} + +void gekk_stop_skid (edict_t *self) +{ + if (self->groundentity) + { + VectorClear (self->velocity); + } +} + +void gekk_check_landing (edict_t *self) +{ + if (self->groundentity) + { + gi.sound (self, CHAN_WEAPON, sound_thud, 1, ATTN_NORM, 0); + self->monsterinfo.attack_finished = 0; + self->monsterinfo.aiflags &= ~AI_DUCKED; + + VectorClear (self->velocity); + + return; + } + + // note to self + // causing skid + if (level.time > self->monsterinfo.attack_finished) + self->monsterinfo.nextframe = FRAME_leapatk_11; + else + { + self->monsterinfo.nextframe = FRAME_leapatk_12; + } +} + +void gekk_jump (edict_t *self) +{ + + if (self->flags & FL_SWIM || self->waterlevel) + { + return; + } + else + { + //if (random() > 0.8 && self->health < self->max_health) + // self->monsterinfo.currentmove = &gekk_move_idle2; + //else + { + if (random() > 0.5 && (range (self, self->enemy) >= RANGE_NEAR)) + self->monsterinfo.currentmove = &gekk_move_spit; + else if (random() > 0.8) + self->monsterinfo.currentmove = &gekk_move_spit; + else + self->monsterinfo.currentmove = &gekk_move_leapatk; + + } + } +} + +// +// PAIN +// + +mframe_t gekk_frames_pain[] = +{ + ai_move, 0.000, NULL, // frame 0 + ai_move, 0.000, NULL, // frame 1 + ai_move, 0.000, NULL, // frame 2 + ai_move, 0.000, NULL, // frame 3 + ai_move, 0.000, NULL, // frame 4 + ai_move, 0.000, NULL, // frame 5 +}; +mmove_t gekk_move_pain = {FRAME_pain_01, FRAME_pain_06, gekk_frames_pain, gekk_run_start}; + +mframe_t gekk_frames_pain1[] = +{ + ai_move, 0.000, NULL, // frame 0 + ai_move, 0.000, NULL, // frame 1 + ai_move, 0.000, NULL, // frame 2 + ai_move, 0.000, NULL, // frame 3 + ai_move, 0.000, NULL, // frame 4 + ai_move, 0.000, NULL, // frame 5 + ai_move, 0.000, NULL, // frame 6 + ai_move, 0.000, NULL, // frame 7 + ai_move, 0.000, NULL, // frame 8 + ai_move, 0.000, NULL, // frame 9 + + ai_move, 0.000, gekk_check_underwater +}; +mmove_t gekk_move_pain1 = {FRAME_pain3_01, FRAME_pain3_11, gekk_frames_pain1, gekk_run_start}; + +mframe_t gekk_frames_pain2[] = +{ + ai_move, 0.000, NULL, // frame 0 + ai_move, 0.000, NULL, // frame 1 + ai_move, 0.000, NULL, // frame 2 + ai_move, 0.000, NULL, // frame 3 + ai_move, 0.000, NULL, // frame 4 + ai_move, 0.000, NULL, // frame 5 + ai_move, 0.000, NULL, // frame 6 + ai_move, 0.000, NULL, // frame 7 + ai_move, 0.000, NULL, // frame 8 + ai_move, 0.000, NULL, // frame 9 + + ai_move, 0.000, NULL, // frame 10 + ai_move, 0.000, NULL, // frame 11 + ai_move, 0.000, gekk_check_underwater, +}; +mmove_t gekk_move_pain2 = {FRAME_pain4_01, FRAME_pain4_13, gekk_frames_pain2, gekk_run_start}; + +void gekk_pain (edict_t *self, edict_t *other, float kick, int damage) +{ + float r; + + if (self->spawnflags & 8) + { + self->spawnflags &= ~8; + return; + } + + if (self->health < (self->max_health /4)) + self->s.skinnum = 2; + else if (self->health < (self->max_health / 2)) + self->s.skinnum = 1; + + if (level.time < self->pain_debounce_time) + return; + + self->pain_debounce_time = level.time + 3; + + gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); + + if (self->waterlevel) + { + if (!self->flags & FL_SWIM) + self->flags |= FL_SWIM; + + self->monsterinfo.currentmove = &gekk_move_pain; + } + else + { + r = random(); + + if (r > 0.5) + self->monsterinfo.currentmove = &gekk_move_pain1; + else + self->monsterinfo.currentmove = &gekk_move_pain2; + } +} + + +// +// DEATH +// + +void gekk_dead (edict_t *self) +{ + // fix this because of no blocking problem + if (self->waterlevel) + { + return; + } + else + { + VectorSet (self->mins, -16, -16, -24); + VectorSet (self->maxs, 16, 16, -8); + self->movetype = MOVETYPE_TOSS; + self->svflags |= SVF_DEADMONSTER; + self->nextthink = 0; + gi.linkentity (self); + } + + +} + +void gekk_gibfest (edict_t *self) +{ + + int damage = 20; + + gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0); + + ThrowGibACID (self, "models/objects/gekkgib/pelvis/tris.md2", damage, GIB_ORGANIC); + ThrowGibACID (self, "models/objects/gekkgib/arm/tris.md2", damage, GIB_ORGANIC); + ThrowGibACID (self, "models/objects/gekkgib/arm/tris.md2", damage, GIB_ORGANIC); + ThrowGibACID (self, "models/objects/gekkgib/torso/tris.md2", damage, GIB_ORGANIC); + ThrowGibACID (self, "models/objects/gekkgib/claw/tris.md2", damage, GIB_ORGANIC); + ThrowGibACID (self, "models/objects/gekkgib/leg/tris.md2", damage, GIB_ORGANIC); + ThrowGibACID (self, "models/objects/gekkgib/leg/tris.md2", damage, GIB_ORGANIC); + + ThrowHeadACID (self, "models/objects/gekkgib/head/tris.md2", damage, GIB_ORGANIC); + + + self->deadflag = DEAD_DEAD; + +} + +void isgibfest (edict_t *self) +{ + if (random() > 0.9) + gekk_gibfest (self); +} + +mframe_t gekk_frames_death1[] = +{ + + ai_move, -5.151, NULL, // frame 0 + ai_move, -12.223, NULL, // frame 1 + ai_move, -11.484, NULL, // frame 2 + ai_move, -17.952, NULL, // frame 3 + ai_move, -6.953, NULL, // frame 4 + ai_move, -7.393, NULL, // frame 5 + ai_move, -10.713, NULL, // frame 6 + ai_move, -17.464, NULL, // frame 7 + ai_move, -11.678, NULL, // frame 8 + ai_move, -11.678, NULL // frame 9 +}; +mmove_t gekk_move_death1 = {FRAME_death1_01, FRAME_death1_10, gekk_frames_death1, gekk_dead}; + +mframe_t gekk_frames_death3[] = +{ + ai_move, 0.000, NULL, // frame 0 + ai_move, 0.022, NULL, // frame 1 + ai_move, 0.169, NULL, // frame 2 + ai_move, -0.710, NULL, // frame 3 + ai_move, -13.446, NULL, // frame 4 + ai_move, -7.654, isgibfest, // frame 5 + ai_move, -31.951, NULL, // frame 6 + +}; +mmove_t gekk_move_death3 = {FRAME_death3_01, FRAME_death3_07, gekk_frames_death3, gekk_dead}; + +mframe_t gekk_frames_death4[] = +{ + ai_move, 5.103, NULL, // frame 0 + ai_move, -4.808, NULL, // frame 1 + ai_move, -10.509, NULL, // frame 2 + ai_move, -9.899, NULL, // frame 3 + ai_move, 4.033, isgibfest, // frame 4 + ai_move, -5.197, NULL, // frame 5 + ai_move, -0.919, NULL, // frame 6 + ai_move, -8.821, NULL, // frame 7 + ai_move, -5.626, NULL, // frame 8 + ai_move, -8.865, isgibfest, // frame 9 + ai_move, -0.845, NULL, // frame 10 + ai_move, 1.986, NULL, // frame 11 + ai_move, 0.170, NULL, // frame 12 + ai_move, 1.339, isgibfest, // frame 13 + ai_move, -0.922, NULL, // frame 14 + ai_move, 0.818, NULL, // frame 15 + ai_move, -1.288, NULL, // frame 16 + ai_move, -1.408, isgibfest, // frame 17 + ai_move, -7.787, NULL, // frame 18 + ai_move, -3.995, NULL, // frame 19 + ai_move, -4.604, NULL, // frame 20 + ai_move, -1.715, isgibfest, // frame 21 + ai_move, -0.564, NULL, // frame 22 + ai_move, -0.597, NULL, // frame 23 + ai_move, 0.074, NULL, // frame 24 + ai_move, -0.309, isgibfest, // frame 25 + ai_move, -0.395, NULL, // frame 26 + ai_move, -0.501, NULL, // frame 27 + ai_move, -0.325, NULL, // frame 28 + ai_move, -0.931, isgibfest, // frame 29 + ai_move, -1.433, NULL, // frame 30 + ai_move, -1.626, NULL, // frame 31 + ai_move, 4.680, NULL, // frame 32 + ai_move, 0.560, NULL, // frame 33 + ai_move, -0.549, gekk_gibfest // frame 34 +}; +mmove_t gekk_move_death4 = {FRAME_death4_01, FRAME_death4_35, gekk_frames_death4, gekk_dead}; + +mframe_t gekk_frames_wdeath[] = +{ + ai_move, 0.000, NULL, // frame 0 + ai_move, 0.000, NULL, // frame 1 + ai_move, 0.000, NULL, // frame 2 + ai_move, 0.000, NULL, // frame 3 + ai_move, 0.000, NULL, // frame 4 + ai_move, 0.000, NULL, // frame 5 + ai_move, 0.000, NULL, // frame 6 + ai_move, 0.000, NULL, // frame 7 + ai_move, 0.000, NULL, // frame 8 + ai_move, 0.000, NULL, // frame 9 + ai_move, 0.000, NULL, // frame 10 + ai_move, 0.000, NULL, // frame 11 + ai_move, 0.000, NULL, // frame 12 + ai_move, 0.000, NULL, // frame 13 + ai_move, 0.000, NULL, // frame 14 + ai_move, 0.000, NULL, // frame 15 + ai_move, 0.000, NULL, // frame 16 + ai_move, 0.000, NULL, // frame 17 + ai_move, 0.000, NULL, // frame 18 + ai_move, 0.000, NULL, // frame 19 + ai_move, 0.000, NULL, // frame 20 + ai_move, 0.000, NULL, // frame 21 + ai_move, 0.000, NULL, // frame 22 + ai_move, 0.000, NULL, // frame 23 + ai_move, 0.000, NULL, // frame 24 + ai_move, 0.000, NULL, // frame 25 + ai_move, 0.000, NULL, // frame 26 + ai_move, 0.000, NULL, // frame 27 + ai_move, 0.000, NULL, // frame 28 + ai_move, 0.000, NULL, // frame 29 + ai_move, 0.000, NULL, // frame 30 + ai_move, 0.000, NULL, // frame 31 + ai_move, 0.000, NULL, // frame 32 + ai_move, 0.000, NULL, // frame 33 + ai_move, 0.000, NULL, // frame 34 + ai_move, 0.000, NULL, // frame 35 + ai_move, 0.000, NULL, // frame 36 + ai_move, 0.000, NULL, // frame 37 + ai_move, 0.000, NULL, // frame 38 + ai_move, 0.000, NULL, // frame 39 + ai_move, 0.000, NULL, // frame 40 + ai_move, 0.000, NULL, // frame 41 + ai_move, 0.000, NULL, // frame 42 + ai_move, 0.000, NULL, // frame 43 + ai_move, 0.000, NULL // frame 44 +}; +mmove_t gekk_move_wdeath = {FRAME_wdeath_01, FRAME_wdeath_45, gekk_frames_wdeath, gekk_dead}; + +void gekk_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + + float r; + + if (self->health <= self->gib_health) + { + gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0); + + ThrowGibACID (self, "models/objects/gekkgib/pelvis/tris.md2", damage, GIB_ORGANIC); + ThrowGibACID (self, "models/objects/gekkgib/arm/tris.md2", damage, GIB_ORGANIC); + ThrowGibACID (self, "models/objects/gekkgib/arm/tris.md2", damage, GIB_ORGANIC); + ThrowGibACID (self, "models/objects/gekkgib/torso/tris.md2", damage, GIB_ORGANIC); + ThrowGibACID (self, "models/objects/gekkgib/claw/tris.md2", damage, GIB_ORGANIC); + ThrowGibACID (self, "models/objects/gekkgib/leg/tris.md2", damage, GIB_ORGANIC); + ThrowGibACID (self, "models/objects/gekkgib/leg/tris.md2", damage, GIB_ORGANIC); + + ThrowHeadACID (self, "models/objects/gekkgib/head/tris.md2", damage, GIB_ORGANIC); + + self->deadflag = DEAD_DEAD; + return; + } + + if (self->deadflag == DEAD_DEAD) + return; + + gi.sound (self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0); + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_YES; + self->s.skinnum = 2; + + if (self->waterlevel) + self->monsterinfo.currentmove = &gekk_move_wdeath; + else + { + r = random(); + if (r > 0.66) + self->monsterinfo.currentmove = &gekk_move_death1; + else if (r > 0.33) + self->monsterinfo.currentmove = &gekk_move_death3; + else + self->monsterinfo.currentmove = &gekk_move_death4; + + } + +} + + +/* + duck +*/ +void gekk_duck_down (edict_t *self) +{ + if (self->monsterinfo.aiflags & AI_DUCKED) + return; + self->monsterinfo.aiflags |= AI_DUCKED; + self->maxs[2] -= 32; + self->takedamage = DAMAGE_YES; + self->monsterinfo.pausetime = level.time + 1; + gi.linkentity (self); +} + +void gekk_duck_up (edict_t *self) +{ + self->monsterinfo.aiflags &= ~AI_DUCKED; + self->maxs[2] += 32; + self->takedamage = DAMAGE_AIM; + gi.linkentity (self); +} + + +void gekk_duck_hold (edict_t *self) +{ + if (level.time >= self->monsterinfo.pausetime) + self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; + else + self->monsterinfo.aiflags |= AI_HOLD_FRAME; +} + +mframe_t gekk_frames_lduck[] = +{ + ai_move, 0.000, NULL, // frame 0 + ai_move, 0.000, NULL, // frame 1 + ai_move, 0.000, NULL, // frame 2 + ai_move, 0.000, NULL, // frame 3 + ai_move, 0.000, NULL, // frame 4 + ai_move, 0.000, NULL, // frame 5 + ai_move, 0.000, NULL, // frame 6 + ai_move, 0.000, NULL, // frame 7 + ai_move, 0.000, NULL, // frame 8 + ai_move, 0.000, NULL, // frame 9 + + ai_move, 0.000, NULL, // frame 10 + ai_move, 0.000, NULL, // frame 11 + ai_move, 0.000, NULL // frame 12 + +}; +mmove_t gekk_move_lduck = {FRAME_lduck_01, FRAME_lduck_13, gekk_frames_lduck, gekk_run_start}; + +mframe_t gekk_frames_rduck[] = +{ + ai_move, 0.000, NULL, // frame 0 + ai_move, 0.000, NULL, // frame 1 + ai_move, 0.000, NULL, // frame 2 + ai_move, 0.000, NULL, // frame 3 + ai_move, 0.000, NULL, // frame 4 + ai_move, 0.000, NULL, // frame 5 + ai_move, 0.000, NULL, // frame 6 + ai_move, 0.000, NULL, // frame 7 + ai_move, 0.000, NULL, // frame 8 + ai_move, 0.000, NULL, // frame 9 + ai_move, 0.000, NULL, // frame 10 + ai_move, 0.000, NULL, // frame 11 + ai_move, 0.000, NULL // frame 12 + +}; +mmove_t gekk_move_rduck = {FRAME_rduck_01, FRAME_rduck_13, gekk_frames_rduck, gekk_run_start}; + + +void gekk_dodge (edict_t *self, edict_t *attacker, float eta) +{ + float r; + + r = random(); + if (r > 0.25) + return; + + if (!self->enemy) + self->enemy = attacker; + + if (self->waterlevel) + { + self->monsterinfo.currentmove = &gekk_move_attack; + return; + } + + if (skill->value == 0) + { + r = random(); + if (r > 0.5) + self->monsterinfo.currentmove = &gekk_move_lduck; + else + self->monsterinfo.currentmove = &gekk_move_rduck; + return; + } + + self->monsterinfo.pausetime = level.time + eta + 0.3; + r = random(); + + if (skill->value == 1) + { + if (r > 0.33) + { + r = random(); + if (r > 0.5) + self->monsterinfo.currentmove = &gekk_move_lduck; + else + self->monsterinfo.currentmove = &gekk_move_rduck; + } + else + { + r = random(); + if (r > 0.66) + self->monsterinfo.currentmove = &gekk_move_attack1; + else + self->monsterinfo.currentmove = &gekk_move_attack2; + + } + return; + } + + if (skill->value == 2) + { + if (r > 0.66) + { + r = random(); + if (r > 0.5) + self->monsterinfo.currentmove = &gekk_move_lduck; + else + self->monsterinfo.currentmove = &gekk_move_rduck; + } + else + { + r = random(); + if (r > 0.66) + self->monsterinfo.currentmove = &gekk_move_attack1; + else + self->monsterinfo.currentmove = &gekk_move_attack2; + } + return; + } + + r = random(); + if (r > 0.66) + self->monsterinfo.currentmove = &gekk_move_attack1; + else + self->monsterinfo.currentmove = &gekk_move_attack2; + + +} + +// +// SPAWN +// + +/*QUAKED monster_gekk (1 .5 0) (-24 -24 -24) (24 24 24) Ambush Trigger_Spawn Sight Chant +*/ +void SP_monster_gekk (edict_t *self) +{ + if (deathmatch->value) + { + G_FreeEdict (self); + return; + } + + sound_swing = gi.soundindex ("gek/gk_atck1.wav"); + sound_hit = gi.soundindex ("gek/gk_atck2.wav"); + sound_hit2 = gi.soundindex ("gek/gk_atck3.wav"); + sound_death = gi.soundindex ("gek/gk_deth1.wav"); + sound_pain1 = gi.soundindex ("gek/gk_pain1.wav"); + sound_sight = gi.soundindex ("gek/gk_sght1.wav"); + sound_search = gi.soundindex ("gek/gk_idle1.wav"); + sound_step1 = gi.soundindex ("gek/gk_step1.wav"); + sound_step2 = gi.soundindex ("gek/gk_step2.wav"); + sound_step3 = gi.soundindex ("gek/gk_step3.wav"); + sound_thud = gi.soundindex ("mutant/thud1.wav"); + + sound_chantlow = gi.soundindex ("gek/gek_low.wav"); + sound_chantmid = gi.soundindex ("gek/gek_mid.wav"); + sound_chanthigh = gi.soundindex ("gek/gek_high.wav"); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex ("models/monsters/gekk/tris.md2"); + VectorSet (self->mins, -24, -24, -24); + VectorSet (self->maxs, 24, 24, 24); + + gi.modelindex ("models/objects/gekkgib/pelvis/tris.md2"); + gi.modelindex ("models/objects/gekkgib/arm/tris.md2"); + gi.modelindex ("models/objects/gekkgib/torso/tris.md2"); + gi.modelindex ("models/objects/gekkgib/claw/tris.md2"); + gi.modelindex ("models/objects/gekkgib/leg/tris.md2"); + gi.modelindex ("models/objects/gekkgib/head/tris.md2"); + + self->health = 125; + self->gib_health = -30; + self->mass = 300; + + self->pain = gekk_pain; + self->die = gekk_die; + + self->monsterinfo.stand = gekk_stand; + + self->monsterinfo.walk = gekk_walk; + self->monsterinfo.run = gekk_run_start; + self->monsterinfo.dodge = gekk_dodge; + self->monsterinfo.attack = gekk_jump; + self->monsterinfo.melee = gekk_melee; + self->monsterinfo.sight = gekk_sight; + + self->monsterinfo.search = gekk_search; + self->monsterinfo.idle = gekk_idle; + self->monsterinfo.checkattack = gekk_checkattack; + + gi.linkentity (self); + + self->monsterinfo.currentmove = &gekk_move_stand; + + self->monsterinfo.scale = MODEL_SCALE; + walkmonster_start (self); + + if (self->spawnflags & 8) + self->monsterinfo.currentmove = &gekk_move_chant; + +} + + +void water_to_land (edict_t *self) +{ + self->flags &= ~FL_SWIM; + self->yaw_speed = 20; + self->viewheight = 25; + + self->monsterinfo.currentmove = &gekk_move_leapatk2; + + VectorSet (self->mins, -24, -24, -24); + VectorSet (self->maxs, 24, 24, 24); +} + +void land_to_water (edict_t *self) +{ + self->flags |= FL_SWIM; + self->yaw_speed = 10; + self->viewheight = 10; + + self->monsterinfo.currentmove = &gekk_move_swim_start; + + VectorSet (self->mins, -24, -24, -24); + VectorSet (self->maxs, 24, 24, 16); +} \ No newline at end of file diff --git a/original/xatrix/m_gekk.h b/original/xatrix/m_gekk.h new file mode 100644 index 0000000..8f503c9 --- /dev/null +++ b/original/xatrix/m_gekk.h @@ -0,0 +1,358 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// ./gekk + +// This file generated by ModelGen - Do NOT Modify + +#define FRAME_stand_01 0 +#define FRAME_stand_02 1 +#define FRAME_stand_03 2 +#define FRAME_stand_04 3 +#define FRAME_stand_05 4 +#define FRAME_stand_06 5 +#define FRAME_stand_07 6 +#define FRAME_stand_08 7 +#define FRAME_stand_09 8 +#define FRAME_stand_10 9 +#define FRAME_stand_11 10 +#define FRAME_stand_12 11 +#define FRAME_stand_13 12 +#define FRAME_stand_14 13 +#define FRAME_stand_15 14 +#define FRAME_stand_16 15 +#define FRAME_stand_17 16 +#define FRAME_stand_18 17 +#define FRAME_stand_19 18 +#define FRAME_stand_20 19 +#define FRAME_stand_21 20 +#define FRAME_stand_22 21 +#define FRAME_stand_23 22 +#define FRAME_stand_24 23 +#define FRAME_stand_25 24 +#define FRAME_stand_26 25 +#define FRAME_stand_27 26 +#define FRAME_stand_28 27 +#define FRAME_stand_29 28 +#define FRAME_stand_30 29 +#define FRAME_stand_31 30 +#define FRAME_stand_32 31 +#define FRAME_stand_33 32 +#define FRAME_stand_34 33 +#define FRAME_stand_35 34 +#define FRAME_stand_36 35 +#define FRAME_stand_37 36 +#define FRAME_stand_38 37 +#define FRAME_stand_39 38 +#define FRAME_run_01 39 +#define FRAME_run_02 40 +#define FRAME_run_03 41 +#define FRAME_run_04 42 +#define FRAME_run_05 43 +#define FRAME_run_06 44 +#define FRAME_clawatk3_01 45 +#define FRAME_clawatk3_02 46 +#define FRAME_clawatk3_03 47 +#define FRAME_clawatk3_04 48 +#define FRAME_clawatk3_05 49 +#define FRAME_clawatk3_06 50 +#define FRAME_clawatk3_07 51 +#define FRAME_clawatk3_08 52 +#define FRAME_clawatk3_09 53 +#define FRAME_clawatk4_01 54 +#define FRAME_clawatk4_02 55 +#define FRAME_clawatk4_03 56 +#define FRAME_clawatk4_04 57 +#define FRAME_clawatk4_05 58 +#define FRAME_clawatk4_06 59 +#define FRAME_clawatk4_07 60 +#define FRAME_clawatk4_08 61 +#define FRAME_clawatk5_01 62 +#define FRAME_clawatk5_02 63 +#define FRAME_clawatk5_03 64 +#define FRAME_clawatk5_04 65 +#define FRAME_clawatk5_05 66 +#define FRAME_clawatk5_06 67 +#define FRAME_clawatk5_07 68 +#define FRAME_clawatk5_08 69 +#define FRAME_clawatk5_09 70 +#define FRAME_leapatk_01 71 +#define FRAME_leapatk_02 72 +#define FRAME_leapatk_03 73 +#define FRAME_leapatk_04 74 +#define FRAME_leapatk_05 75 +#define FRAME_leapatk_06 76 +#define FRAME_leapatk_07 77 +#define FRAME_leapatk_08 78 +#define FRAME_leapatk_09 79 +#define FRAME_leapatk_10 80 +#define FRAME_leapatk_11 81 +#define FRAME_leapatk_12 82 +#define FRAME_leapatk_13 83 +#define FRAME_leapatk_14 84 +#define FRAME_leapatk_15 85 +#define FRAME_leapatk_16 86 +#define FRAME_leapatk_17 87 +#define FRAME_leapatk_18 88 +#define FRAME_leapatk_19 89 +#define FRAME_pain3_01 90 +#define FRAME_pain3_02 91 +#define FRAME_pain3_03 92 +#define FRAME_pain3_04 93 +#define FRAME_pain3_05 94 +#define FRAME_pain3_06 95 +#define FRAME_pain3_07 96 +#define FRAME_pain3_08 97 +#define FRAME_pain3_09 98 +#define FRAME_pain3_10 99 +#define FRAME_pain3_11 100 +#define FRAME_pain4_01 101 +#define FRAME_pain4_02 102 +#define FRAME_pain4_03 103 +#define FRAME_pain4_04 104 +#define FRAME_pain4_05 105 +#define FRAME_pain4_06 106 +#define FRAME_pain4_07 107 +#define FRAME_pain4_08 108 +#define FRAME_pain4_09 109 +#define FRAME_pain4_10 110 +#define FRAME_pain4_11 111 +#define FRAME_pain4_12 112 +#define FRAME_pain4_13 113 +#define FRAME_death1_01 114 +#define FRAME_death1_02 115 +#define FRAME_death1_03 116 +#define FRAME_death1_04 117 +#define FRAME_death1_05 118 +#define FRAME_death1_06 119 +#define FRAME_death1_07 120 +#define FRAME_death1_08 121 +#define FRAME_death1_09 122 +#define FRAME_death1_10 123 +#define FRAME_death2_01 124 +#define FRAME_death2_02 125 +#define FRAME_death2_03 126 +#define FRAME_death2_04 127 +#define FRAME_death2_05 128 +#define FRAME_death2_06 129 +#define FRAME_death2_07 130 +#define FRAME_death2_08 131 +#define FRAME_death2_09 132 +#define FRAME_death2_10 133 +#define FRAME_death2_11 134 +#define FRAME_death3_01 135 +#define FRAME_death3_02 136 +#define FRAME_death3_03 137 +#define FRAME_death3_04 138 +#define FRAME_death3_05 139 +#define FRAME_death3_06 140 +#define FRAME_death3_07 141 +#define FRAME_death4_01 142 +#define FRAME_death4_02 143 +#define FRAME_death4_03 144 +#define FRAME_death4_04 145 +#define FRAME_death4_05 146 +#define FRAME_death4_06 147 +#define FRAME_death4_07 148 +#define FRAME_death4_08 149 +#define FRAME_death4_09 150 +#define FRAME_death4_10 151 +#define FRAME_death4_11 152 +#define FRAME_death4_12 153 +#define FRAME_death4_13 154 +#define FRAME_death4_14 155 +#define FRAME_death4_15 156 +#define FRAME_death4_16 157 +#define FRAME_death4_17 158 +#define FRAME_death4_18 159 +#define FRAME_death4_19 160 +#define FRAME_death4_20 161 +#define FRAME_death4_21 162 +#define FRAME_death4_22 163 +#define FRAME_death4_23 164 +#define FRAME_death4_24 165 +#define FRAME_death4_25 166 +#define FRAME_death4_26 167 +#define FRAME_death4_27 168 +#define FRAME_death4_28 169 +#define FRAME_death4_29 170 +#define FRAME_death4_30 171 +#define FRAME_death4_31 172 +#define FRAME_death4_32 173 +#define FRAME_death4_33 174 +#define FRAME_death4_34 175 +#define FRAME_death4_35 176 +#define FRAME_rduck_01 177 +#define FRAME_rduck_02 178 +#define FRAME_rduck_03 179 +#define FRAME_rduck_04 180 +#define FRAME_rduck_05 181 +#define FRAME_rduck_06 182 +#define FRAME_rduck_07 183 +#define FRAME_rduck_08 184 +#define FRAME_rduck_09 185 +#define FRAME_rduck_10 186 +#define FRAME_rduck_11 187 +#define FRAME_rduck_12 188 +#define FRAME_rduck_13 189 +#define FRAME_lduck_01 190 +#define FRAME_lduck_02 191 +#define FRAME_lduck_03 192 +#define FRAME_lduck_04 193 +#define FRAME_lduck_05 194 +#define FRAME_lduck_06 195 +#define FRAME_lduck_07 196 +#define FRAME_lduck_08 197 +#define FRAME_lduck_09 198 +#define FRAME_lduck_10 199 +#define FRAME_lduck_11 200 +#define FRAME_lduck_12 201 +#define FRAME_lduck_13 202 +#define FRAME_idle_01 203 +#define FRAME_idle_02 204 +#define FRAME_idle_03 205 +#define FRAME_idle_04 206 +#define FRAME_idle_05 207 +#define FRAME_idle_06 208 +#define FRAME_idle_07 209 +#define FRAME_idle_08 210 +#define FRAME_idle_09 211 +#define FRAME_idle_10 212 +#define FRAME_idle_11 213 +#define FRAME_idle_12 214 +#define FRAME_idle_13 215 +#define FRAME_idle_14 216 +#define FRAME_idle_15 217 +#define FRAME_idle_16 218 +#define FRAME_idle_17 219 +#define FRAME_idle_18 220 +#define FRAME_idle_19 221 +#define FRAME_idle_20 222 +#define FRAME_idle_21 223 +#define FRAME_idle_22 224 +#define FRAME_idle_23 225 +#define FRAME_idle_24 226 +#define FRAME_idle_25 227 +#define FRAME_idle_26 228 +#define FRAME_idle_27 229 +#define FRAME_idle_28 230 +#define FRAME_idle_29 231 +#define FRAME_idle_30 232 +#define FRAME_idle_31 233 +#define FRAME_idle_32 234 +#define FRAME_spit_01 235 +#define FRAME_spit_02 236 +#define FRAME_spit_03 237 +#define FRAME_spit_04 238 +#define FRAME_spit_05 239 +#define FRAME_spit_06 240 +#define FRAME_spit_07 241 +#define FRAME_amb_01 242 +#define FRAME_amb_02 243 +#define FRAME_amb_03 244 +#define FRAME_amb_04 245 +#define FRAME_wdeath_01 246 +#define FRAME_wdeath_02 247 +#define FRAME_wdeath_03 248 +#define FRAME_wdeath_04 249 +#define FRAME_wdeath_05 250 +#define FRAME_wdeath_06 251 +#define FRAME_wdeath_07 252 +#define FRAME_wdeath_08 253 +#define FRAME_wdeath_09 254 +#define FRAME_wdeath_10 255 +#define FRAME_wdeath_11 256 +#define FRAME_wdeath_12 257 +#define FRAME_wdeath_13 258 +#define FRAME_wdeath_14 259 +#define FRAME_wdeath_15 260 +#define FRAME_wdeath_16 261 +#define FRAME_wdeath_17 262 +#define FRAME_wdeath_18 263 +#define FRAME_wdeath_19 264 +#define FRAME_wdeath_20 265 +#define FRAME_wdeath_21 266 +#define FRAME_wdeath_22 267 +#define FRAME_wdeath_23 268 +#define FRAME_wdeath_24 269 +#define FRAME_wdeath_25 270 +#define FRAME_wdeath_26 271 +#define FRAME_wdeath_27 272 +#define FRAME_wdeath_28 273 +#define FRAME_wdeath_29 274 +#define FRAME_wdeath_30 275 +#define FRAME_wdeath_31 276 +#define FRAME_wdeath_32 277 +#define FRAME_wdeath_33 278 +#define FRAME_wdeath_34 279 +#define FRAME_wdeath_35 280 +#define FRAME_wdeath_36 281 +#define FRAME_wdeath_37 282 +#define FRAME_wdeath_38 283 +#define FRAME_wdeath_39 284 +#define FRAME_wdeath_40 285 +#define FRAME_wdeath_41 286 +#define FRAME_wdeath_42 287 +#define FRAME_wdeath_43 288 +#define FRAME_wdeath_44 289 +#define FRAME_wdeath_45 290 +#define FRAME_swim_01 291 +#define FRAME_swim_02 292 +#define FRAME_swim_03 293 +#define FRAME_swim_04 294 +#define FRAME_swim_05 295 +#define FRAME_swim_06 296 +#define FRAME_swim_07 297 +#define FRAME_swim_08 298 +#define FRAME_swim_09 299 +#define FRAME_swim_10 300 +#define FRAME_swim_11 301 +#define FRAME_swim_12 302 +#define FRAME_swim_13 303 +#define FRAME_swim_14 304 +#define FRAME_swim_15 305 +#define FRAME_swim_16 306 +#define FRAME_swim_17 307 +#define FRAME_swim_18 308 +#define FRAME_swim_19 309 +#define FRAME_swim_20 310 +#define FRAME_swim_21 311 +#define FRAME_swim_22 312 +#define FRAME_swim_23 313 +#define FRAME_swim_24 314 +#define FRAME_swim_25 315 +#define FRAME_swim_26 316 +#define FRAME_swim_27 317 +#define FRAME_swim_28 318 +#define FRAME_swim_29 319 +#define FRAME_swim_30 320 +#define FRAME_swim_31 321 +#define FRAME_swim_32 322 +#define FRAME_attack_01 323 +#define FRAME_attack_02 324 +#define FRAME_attack_03 325 +#define FRAME_attack_04 326 +#define FRAME_attack_05 327 +#define FRAME_attack_06 328 +#define FRAME_attack_07 329 +#define FRAME_attack_08 330 +#define FRAME_attack_09 331 +#define FRAME_attack_10 332 +#define FRAME_attack_11 333 +#define FRAME_attack_12 334 +#define FRAME_attack_13 335 +#define FRAME_attack_14 336 +#define FRAME_attack_15 337 +#define FRAME_attack_16 338 +#define FRAME_attack_17 339 +#define FRAME_attack_18 340 +#define FRAME_attack_19 341 +#define FRAME_attack_20 342 +#define FRAME_attack_21 343 +#define FRAME_pain_01 344 +#define FRAME_pain_02 345 +#define FRAME_pain_03 346 +#define FRAME_pain_04 347 +#define FRAME_pain_05 348 +#define FRAME_pain_06 349 + +#define MODEL_SCALE 1.000000 diff --git a/original/xatrix/m_gladb.c b/original/xatrix/m_gladb.c new file mode 100644 index 0000000..512bad5 --- /dev/null +++ b/original/xatrix/m_gladb.c @@ -0,0 +1,382 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. + +/* +============================================================================== + + GLADIATOR BOSS + +============================================================================== +*/ + +#include "g_local.h" +#include "m_gladiator.h" + + +static int sound_pain1; +static int sound_pain2; +static int sound_die; +static int sound_gun; +static int sound_cleaver_swing; +static int sound_cleaver_hit; +static int sound_cleaver_miss; +static int sound_idle; +static int sound_search; +static int sound_sight; + + +void gladb_idle (edict_t *self) +{ + gi.sound (self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0); +} + +void gladb_sight (edict_t *self, edict_t *other) +{ + gi.sound (self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); +} + +void gladb_search (edict_t *self) +{ + gi.sound (self, CHAN_VOICE, sound_search, 1, ATTN_NORM, 0); +} + +void gladb_cleaver_swing (edict_t *self) +{ + gi.sound (self, CHAN_WEAPON, sound_cleaver_swing, 1, ATTN_NORM, 0); +} + +mframe_t gladb_frames_stand [] = +{ + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL +}; +mmove_t gladb_move_stand = {FRAME_stand1, FRAME_stand7, gladb_frames_stand, NULL}; + +void gladb_stand (edict_t *self) +{ + self->monsterinfo.currentmove = &gladb_move_stand; +} + + +mframe_t gladb_frames_walk [] = +{ + ai_walk, 15, NULL, + ai_walk, 7, NULL, + ai_walk, 6, NULL, + ai_walk, 5, NULL, + ai_walk, 2, NULL, + ai_walk, 0, NULL, + ai_walk, 2, NULL, + ai_walk, 8, NULL, + ai_walk, 12, NULL, + ai_walk, 8, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 2, NULL, + ai_walk, 2, NULL, + ai_walk, 1, NULL, + ai_walk, 8, NULL +}; +mmove_t gladb_move_walk = {FRAME_walk1, FRAME_walk16, gladb_frames_walk, NULL}; + +void gladb_walk (edict_t *self) +{ + self->monsterinfo.currentmove = &gladb_move_walk; +} + + +mframe_t gladb_frames_run [] = +{ + ai_run, 23, NULL, + ai_run, 14, NULL, + ai_run, 14, NULL, + ai_run, 21, NULL, + ai_run, 12, NULL, + ai_run, 13, NULL +}; +mmove_t gladb_move_run = {FRAME_run1, FRAME_run6, gladb_frames_run, NULL}; + +void gladb_run (edict_t *self) +{ + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + self->monsterinfo.currentmove = &gladb_move_stand; + else + self->monsterinfo.currentmove = &gladb_move_run; +} + + +void GladbMelee (edict_t *self) +{ + vec3_t aim; + + VectorSet (aim, MELEE_DISTANCE, self->mins[0], -4); + if (fire_hit (self, aim, (20 + (rand() %5)), 300)) + gi.sound (self, CHAN_AUTO, sound_cleaver_hit, 1, ATTN_NORM, 0); + else + gi.sound (self, CHAN_AUTO, sound_cleaver_miss, 1, ATTN_NORM, 0); +} + +mframe_t gladb_frames_attack_melee [] = +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, gladb_cleaver_swing, + ai_charge, 0, NULL, + ai_charge, 0, GladbMelee, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, gladb_cleaver_swing, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, GladbMelee, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL +}; +mmove_t gladb_move_attack_melee = {FRAME_melee1, FRAME_melee17, gladb_frames_attack_melee, gladb_run}; + +void gladb_melee(edict_t *self) +{ + self->monsterinfo.currentmove = &gladb_move_attack_melee; +} + + +void gladbGun (edict_t *self) +{ + vec3_t start; + vec3_t dir; + vec3_t forward, right; + + AngleVectors (self->s.angles, forward, right, NULL); + G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_GLADIATOR_RAILGUN_1], forward, right, start); + + // calc direction to where we targted + VectorSubtract (self->pos1, start, dir); + VectorNormalize (dir); + + fire_plasma (self, start, dir, 100, 725, 60, 60); +} + +void gladbGun_check (edict_t *self) +{ + if (skill->value == 3) + gladbGun (self); +} + +mframe_t gladb_frames_attack_gun [] = +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, gladbGun, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, gladbGun, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, gladbGun_check +}; +mmove_t gladb_move_attack_gun = {FRAME_attack1, FRAME_attack9, gladb_frames_attack_gun, gladb_run}; + +void gladb_attack(edict_t *self) +{ + float range; + vec3_t v; + + // a small safe zone + VectorSubtract (self->s.origin, self->enemy->s.origin, v); + range = VectorLength(v); + if (range <= (MELEE_DISTANCE + 32)) + return; + + // charge up the railgun + gi.sound (self, CHAN_WEAPON, sound_gun, 1, ATTN_NORM, 0); + VectorCopy (self->enemy->s.origin, self->pos1); //save for aiming the shot + self->pos1[2] += self->enemy->viewheight; + self->monsterinfo.currentmove = &gladb_move_attack_gun; +} + + +mframe_t gladb_frames_pain [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t gladb_move_pain = {FRAME_pain1, FRAME_pain6, gladb_frames_pain, gladb_run}; + +mframe_t gladb_frames_pain_air [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t gladb_move_pain_air = {FRAME_painup1, FRAME_painup7, gladb_frames_pain_air, gladb_run}; + +void gladb_pain (edict_t *self, edict_t *other, float kick, int damage) +{ + + if (self->health < (self->max_health / 2)) + self->s.skinnum = 1; + + if (level.time < self->pain_debounce_time) + { + if ((self->velocity[2] > 100) && (self->monsterinfo.currentmove == &gladb_move_pain)) + self->monsterinfo.currentmove = &gladb_move_pain_air; + return; + } + + self->pain_debounce_time = level.time + 3; + + if (random() < 0.5) + gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); + else + gi.sound (self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0); + + if (self->velocity[2] > 100) + self->monsterinfo.currentmove = &gladb_move_pain_air; + else + self->monsterinfo.currentmove = &gladb_move_pain; + +} + + +void gladb_dead (edict_t *self) +{ + VectorSet (self->mins, -16, -16, -24); + VectorSet (self->maxs, 16, 16, -8); + self->movetype = MOVETYPE_TOSS; + self->svflags |= SVF_DEADMONSTER; + self->nextthink = 0; + gi.linkentity (self); +} + +mframe_t gladb_frames_death [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t gladb_move_death = {FRAME_death1, FRAME_death22, gladb_frames_death, gladb_dead}; + +void gladb_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + int n; + +// check for gib + if (self->health <= self->gib_health) + { + gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0); + for (n= 0; n < 2; n++) + ThrowGib (self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC); + for (n= 0; n < 4; n++) + ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); + ThrowHead (self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC); + self->deadflag = DEAD_DEAD; + return; + } + + if (self->deadflag == DEAD_DEAD) + return; + +// regular death + gi.sound (self, CHAN_VOICE, sound_die, 1, ATTN_NORM, 0); + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_YES; + + self->monsterinfo.currentmove = &gladb_move_death; +} + + +/*QUAKED monster_gladb (1 .5 0) (-32 -32 -24) (32 32 64) Ambush Trigger_Spawn Sight +*/ +void SP_monster_gladb (edict_t *self) +{ + if (deathmatch->value) + { + G_FreeEdict (self); + return; + } + + + sound_pain1 = gi.soundindex ("gladiator/pain.wav"); + sound_pain2 = gi.soundindex ("gladiator/gldpain2.wav"); + sound_die = gi.soundindex ("gladiator/glddeth2.wav"); + // note to self + // need to change to PHALANX sound + sound_gun = gi.soundindex ("weapons/plasshot.wav"); + + sound_cleaver_swing = gi.soundindex ("gladiator/melee1.wav"); + sound_cleaver_hit = gi.soundindex ("gladiator/melee2.wav"); + sound_cleaver_miss = gi.soundindex ("gladiator/melee3.wav"); + sound_idle = gi.soundindex ("gladiator/gldidle1.wav"); + sound_search = gi.soundindex ("gladiator/gldsrch1.wav"); + sound_sight = gi.soundindex ("gladiator/sight.wav"); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex ("models/monsters/gladb/tris.md2"); + VectorSet (self->mins, -32, -32, -24); + VectorSet (self->maxs, 32, 32, 64); + + self->health = 800; + self->gib_health = -175; + self->mass = 350; + + self->pain = gladb_pain; + self->die = gladb_die; + + self->monsterinfo.stand = gladb_stand; + self->monsterinfo.walk = gladb_walk; + self->monsterinfo.run = gladb_run; + self->monsterinfo.dodge = NULL; + self->monsterinfo.attack = gladb_attack; + self->monsterinfo.melee = gladb_melee; + self->monsterinfo.sight = gladb_sight; + self->monsterinfo.idle = gladb_idle; + self->monsterinfo.search = gladb_search; + + gi.linkentity (self); + self->monsterinfo.currentmove = &gladb_move_stand; + self->monsterinfo.scale = MODEL_SCALE; + + self->monsterinfo.power_armor_type = POWER_ARMOR_SHIELD; + self->monsterinfo.power_armor_power = 400; + + + walkmonster_start (self); +} + diff --git a/original/xatrix/m_gladiator.c b/original/xatrix/m_gladiator.c new file mode 100644 index 0000000..5dc7863 --- /dev/null +++ b/original/xatrix/m_gladiator.c @@ -0,0 +1,371 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +/* +============================================================================== + +GLADIATOR + +============================================================================== +*/ + +#include "g_local.h" +#include "m_gladiator.h" + + +static int sound_pain1; +static int sound_pain2; +static int sound_die; +static int sound_gun; +static int sound_cleaver_swing; +static int sound_cleaver_hit; +static int sound_cleaver_miss; +static int sound_idle; +static int sound_search; +static int sound_sight; + + +void gladiator_idle (edict_t *self) +{ + gi.sound (self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0); +} + +void gladiator_sight (edict_t *self, edict_t *other) +{ + gi.sound (self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); +} + +void gladiator_search (edict_t *self) +{ + gi.sound (self, CHAN_VOICE, sound_search, 1, ATTN_NORM, 0); +} + +void gladiator_cleaver_swing (edict_t *self) +{ + gi.sound (self, CHAN_WEAPON, sound_cleaver_swing, 1, ATTN_NORM, 0); +} + +mframe_t gladiator_frames_stand [] = +{ + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL +}; +mmove_t gladiator_move_stand = {FRAME_stand1, FRAME_stand7, gladiator_frames_stand, NULL}; + +void gladiator_stand (edict_t *self) +{ + self->monsterinfo.currentmove = &gladiator_move_stand; +} + + +mframe_t gladiator_frames_walk [] = +{ + ai_walk, 15, NULL, + ai_walk, 7, NULL, + ai_walk, 6, NULL, + ai_walk, 5, NULL, + ai_walk, 2, NULL, + ai_walk, 0, NULL, + ai_walk, 2, NULL, + ai_walk, 8, NULL, + ai_walk, 12, NULL, + ai_walk, 8, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 2, NULL, + ai_walk, 2, NULL, + ai_walk, 1, NULL, + ai_walk, 8, NULL +}; +mmove_t gladiator_move_walk = {FRAME_walk1, FRAME_walk16, gladiator_frames_walk, NULL}; + +void gladiator_walk (edict_t *self) +{ + self->monsterinfo.currentmove = &gladiator_move_walk; +} + + +mframe_t gladiator_frames_run [] = +{ + ai_run, 23, NULL, + ai_run, 14, NULL, + ai_run, 14, NULL, + ai_run, 21, NULL, + ai_run, 12, NULL, + ai_run, 13, NULL +}; +mmove_t gladiator_move_run = {FRAME_run1, FRAME_run6, gladiator_frames_run, NULL}; + +void gladiator_run (edict_t *self) +{ + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + self->monsterinfo.currentmove = &gladiator_move_stand; + else + self->monsterinfo.currentmove = &gladiator_move_run; +} + + +void GaldiatorMelee (edict_t *self) +{ + vec3_t aim; + + VectorSet (aim, MELEE_DISTANCE, self->mins[0], -4); + if (fire_hit (self, aim, (20 + (rand() %5)), 300)) + gi.sound (self, CHAN_AUTO, sound_cleaver_hit, 1, ATTN_NORM, 0); + else + gi.sound (self, CHAN_AUTO, sound_cleaver_miss, 1, ATTN_NORM, 0); +} + +mframe_t gladiator_frames_attack_melee [] = +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, gladiator_cleaver_swing, + ai_charge, 0, NULL, + ai_charge, 0, GaldiatorMelee, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, gladiator_cleaver_swing, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, GaldiatorMelee, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL +}; +mmove_t gladiator_move_attack_melee = {FRAME_melee1, FRAME_melee17, gladiator_frames_attack_melee, gladiator_run}; + +void gladiator_melee(edict_t *self) +{ + self->monsterinfo.currentmove = &gladiator_move_attack_melee; +} + + +void GladiatorGun (edict_t *self) +{ + vec3_t start; + vec3_t dir; + vec3_t forward, right; + + AngleVectors (self->s.angles, forward, right, NULL); + G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_GLADIATOR_RAILGUN_1], forward, right, start); + + // calc direction to where we targted + VectorSubtract (self->pos1, start, dir); + VectorNormalize (dir); + + monster_fire_railgun (self, start, dir, 50, 100, MZ2_GLADIATOR_RAILGUN_1); +} + +mframe_t gladiator_frames_attack_gun [] = +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, GladiatorGun, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL +}; +mmove_t gladiator_move_attack_gun = {FRAME_attack1, FRAME_attack9, gladiator_frames_attack_gun, gladiator_run}; + +void gladiator_attack(edict_t *self) +{ + float range; + vec3_t v; + + // a small safe zone + VectorSubtract (self->s.origin, self->enemy->s.origin, v); + range = VectorLength(v); + if (range <= (MELEE_DISTANCE + 32)) + return; + + // charge up the railgun + gi.sound (self, CHAN_WEAPON, sound_gun, 1, ATTN_NORM, 0); + VectorCopy (self->enemy->s.origin, self->pos1); //save for aiming the shot + self->pos1[2] += self->enemy->viewheight; + self->monsterinfo.currentmove = &gladiator_move_attack_gun; +} + + +mframe_t gladiator_frames_pain [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t gladiator_move_pain = {FRAME_pain1, FRAME_pain6, gladiator_frames_pain, gladiator_run}; + +mframe_t gladiator_frames_pain_air [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t gladiator_move_pain_air = {FRAME_painup1, FRAME_painup7, gladiator_frames_pain_air, gladiator_run}; + +void gladiator_pain (edict_t *self, edict_t *other, float kick, int damage) +{ + + if (self->health < (self->max_health / 2)) + self->s.skinnum = 1; + + if (level.time < self->pain_debounce_time) + { + if ((self->velocity[2] > 100) && (self->monsterinfo.currentmove == &gladiator_move_pain)) + self->monsterinfo.currentmove = &gladiator_move_pain_air; + return; + } + + self->pain_debounce_time = level.time + 3; + + if (random() < 0.5) + gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); + else + gi.sound (self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0); + + if (skill->value == 3) + return; // no pain anims in nightmare + + if (self->velocity[2] > 100) + self->monsterinfo.currentmove = &gladiator_move_pain_air; + else + self->monsterinfo.currentmove = &gladiator_move_pain; + +} + + +void gladiator_dead (edict_t *self) +{ + VectorSet (self->mins, -16, -16, -24); + VectorSet (self->maxs, 16, 16, -8); + self->movetype = MOVETYPE_TOSS; + self->svflags |= SVF_DEADMONSTER; + self->nextthink = 0; + gi.linkentity (self); +} + +mframe_t gladiator_frames_death [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t gladiator_move_death = {FRAME_death1, FRAME_death22, gladiator_frames_death, gladiator_dead}; + +void gladiator_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + int n; + +// check for gib + if (self->health <= self->gib_health) + { + gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0); + for (n= 0; n < 2; n++) + ThrowGib (self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC); + for (n= 0; n < 4; n++) + ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); + ThrowHead (self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC); + self->deadflag = DEAD_DEAD; + return; + } + + if (self->deadflag == DEAD_DEAD) + return; + +// regular death + gi.sound (self, CHAN_VOICE, sound_die, 1, ATTN_NORM, 0); + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_YES; + + self->monsterinfo.currentmove = &gladiator_move_death; +} + + +/*QUAKED monster_gladiator (1 .5 0) (-32 -32 -24) (32 32 64) Ambush Trigger_Spawn Sight +*/ +void SP_monster_gladiator (edict_t *self) +{ + if (deathmatch->value) + { + G_FreeEdict (self); + return; + } + + + sound_pain1 = gi.soundindex ("gladiator/pain.wav"); + sound_pain2 = gi.soundindex ("gladiator/gldpain2.wav"); + sound_die = gi.soundindex ("gladiator/glddeth2.wav"); + sound_gun = gi.soundindex ("gladiator/railgun.wav"); + sound_cleaver_swing = gi.soundindex ("gladiator/melee1.wav"); + sound_cleaver_hit = gi.soundindex ("gladiator/melee2.wav"); + sound_cleaver_miss = gi.soundindex ("gladiator/melee3.wav"); + sound_idle = gi.soundindex ("gladiator/gldidle1.wav"); + sound_search = gi.soundindex ("gladiator/gldsrch1.wav"); + sound_sight = gi.soundindex ("gladiator/sight.wav"); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex ("models/monsters/gladiatr/tris.md2"); + VectorSet (self->mins, -32, -32, -24); + VectorSet (self->maxs, 32, 32, 64); + + self->health = 400; + self->gib_health = -175; + self->mass = 400; + + self->pain = gladiator_pain; + self->die = gladiator_die; + + self->monsterinfo.stand = gladiator_stand; + self->monsterinfo.walk = gladiator_walk; + self->monsterinfo.run = gladiator_run; + self->monsterinfo.dodge = NULL; + self->monsterinfo.attack = gladiator_attack; + self->monsterinfo.melee = gladiator_melee; + self->monsterinfo.sight = gladiator_sight; + self->monsterinfo.idle = gladiator_idle; + self->monsterinfo.search = gladiator_search; + + gi.linkentity (self); + self->monsterinfo.currentmove = &gladiator_move_stand; + self->monsterinfo.scale = MODEL_SCALE; + + walkmonster_start (self); +} + diff --git a/original/xatrix/m_gladiator.h b/original/xatrix/m_gladiator.h new file mode 100644 index 0000000..94fac63 --- /dev/null +++ b/original/xatrix/m_gladiator.h @@ -0,0 +1,98 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// G:\quake2\baseq2\models/monsters/gladiatr + +// This file generated by ModelGen - Do NOT Modify + +#define FRAME_stand1 0 +#define FRAME_stand2 1 +#define FRAME_stand3 2 +#define FRAME_stand4 3 +#define FRAME_stand5 4 +#define FRAME_stand6 5 +#define FRAME_stand7 6 +#define FRAME_walk1 7 +#define FRAME_walk2 8 +#define FRAME_walk3 9 +#define FRAME_walk4 10 +#define FRAME_walk5 11 +#define FRAME_walk6 12 +#define FRAME_walk7 13 +#define FRAME_walk8 14 +#define FRAME_walk9 15 +#define FRAME_walk10 16 +#define FRAME_walk11 17 +#define FRAME_walk12 18 +#define FRAME_walk13 19 +#define FRAME_walk14 20 +#define FRAME_walk15 21 +#define FRAME_walk16 22 +#define FRAME_run1 23 +#define FRAME_run2 24 +#define FRAME_run3 25 +#define FRAME_run4 26 +#define FRAME_run5 27 +#define FRAME_run6 28 +#define FRAME_melee1 29 +#define FRAME_melee2 30 +#define FRAME_melee3 31 +#define FRAME_melee4 32 +#define FRAME_melee5 33 +#define FRAME_melee6 34 +#define FRAME_melee7 35 +#define FRAME_melee8 36 +#define FRAME_melee9 37 +#define FRAME_melee10 38 +#define FRAME_melee11 39 +#define FRAME_melee12 40 +#define FRAME_melee13 41 +#define FRAME_melee14 42 +#define FRAME_melee15 43 +#define FRAME_melee16 44 +#define FRAME_melee17 45 +#define FRAME_attack1 46 +#define FRAME_attack2 47 +#define FRAME_attack3 48 +#define FRAME_attack4 49 +#define FRAME_attack5 50 +#define FRAME_attack6 51 +#define FRAME_attack7 52 +#define FRAME_attack8 53 +#define FRAME_attack9 54 +#define FRAME_pain1 55 +#define FRAME_pain2 56 +#define FRAME_pain3 57 +#define FRAME_pain4 58 +#define FRAME_pain5 59 +#define FRAME_pain6 60 +#define FRAME_death1 61 +#define FRAME_death2 62 +#define FRAME_death3 63 +#define FRAME_death4 64 +#define FRAME_death5 65 +#define FRAME_death6 66 +#define FRAME_death7 67 +#define FRAME_death8 68 +#define FRAME_death9 69 +#define FRAME_death10 70 +#define FRAME_death11 71 +#define FRAME_death12 72 +#define FRAME_death13 73 +#define FRAME_death14 74 +#define FRAME_death15 75 +#define FRAME_death16 76 +#define FRAME_death17 77 +#define FRAME_death18 78 +#define FRAME_death19 79 +#define FRAME_death20 80 +#define FRAME_death21 81 +#define FRAME_death22 82 +#define FRAME_painup1 83 +#define FRAME_painup2 84 +#define FRAME_painup3 85 +#define FRAME_painup4 86 +#define FRAME_painup5 87 +#define FRAME_painup6 88 +#define FRAME_painup7 89 + +#define MODEL_SCALE 1.000000 diff --git a/original/xatrix/m_gunner.c b/original/xatrix/m_gunner.c new file mode 100644 index 0000000..d07684c --- /dev/null +++ b/original/xatrix/m_gunner.c @@ -0,0 +1,611 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +/* +============================================================================== + +GUNNER + +============================================================================== +*/ + +#include "g_local.h" +#include "m_gunner.h" + + +static int sound_pain; +static int sound_pain2; +static int sound_death; +static int sound_idle; +static int sound_open; +static int sound_search; +static int sound_sight; + + +void gunner_idlesound (edict_t *self) +{ + gi.sound (self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0); +} + +void gunner_sight (edict_t *self, edict_t *other) +{ + gi.sound (self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); +} + +void gunner_search (edict_t *self) +{ + gi.sound (self, CHAN_VOICE, sound_search, 1, ATTN_NORM, 0); +} + + +qboolean visible (edict_t *self, edict_t *other); +void GunnerGrenade (edict_t *self); +void GunnerFire (edict_t *self); +void gunner_fire_chain(edict_t *self); +void gunner_refire_chain(edict_t *self); + + +void gunner_stand (edict_t *self); + +mframe_t gunner_frames_fidget [] = +{ + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, gunner_idlesound, + ai_stand, 0, NULL, + + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL +}; +mmove_t gunner_move_fidget = {FRAME_stand31, FRAME_stand70, gunner_frames_fidget, gunner_stand}; + +void gunner_fidget (edict_t *self) +{ + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + return; + if (random() <= 0.05) + self->monsterinfo.currentmove = &gunner_move_fidget; +} + +mframe_t gunner_frames_stand [] = +{ + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, gunner_fidget, + + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, gunner_fidget, + + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, gunner_fidget +}; +mmove_t gunner_move_stand = {FRAME_stand01, FRAME_stand30, gunner_frames_stand, NULL}; + +void gunner_stand (edict_t *self) +{ + self->monsterinfo.currentmove = &gunner_move_stand; +} + + +mframe_t gunner_frames_walk [] = +{ + ai_walk, 0, NULL, + ai_walk, 3, NULL, + ai_walk, 4, NULL, + ai_walk, 5, NULL, + ai_walk, 7, NULL, + ai_walk, 2, NULL, + ai_walk, 6, NULL, + ai_walk, 4, NULL, + ai_walk, 2, NULL, + ai_walk, 7, NULL, + ai_walk, 5, NULL, + ai_walk, 7, NULL, + ai_walk, 4, NULL +}; +mmove_t gunner_move_walk = {FRAME_walk07, FRAME_walk19, gunner_frames_walk, NULL}; + +void gunner_walk (edict_t *self) +{ + self->monsterinfo.currentmove = &gunner_move_walk; +} + +mframe_t gunner_frames_run [] = +{ + ai_run, 26, NULL, + ai_run, 9, NULL, + ai_run, 9, NULL, + ai_run, 9, NULL, + ai_run, 15, NULL, + ai_run, 10, NULL, + ai_run, 13, NULL, + ai_run, 6, NULL +}; + +mmove_t gunner_move_run = {FRAME_run01, FRAME_run08, gunner_frames_run, NULL}; + +void gunner_run (edict_t *self) +{ + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + self->monsterinfo.currentmove = &gunner_move_stand; + else + self->monsterinfo.currentmove = &gunner_move_run; +} + +mframe_t gunner_frames_runandshoot [] = +{ + ai_run, 32, NULL, + ai_run, 15, NULL, + ai_run, 10, NULL, + ai_run, 18, NULL, + ai_run, 8, NULL, + ai_run, 20, NULL +}; + +mmove_t gunner_move_runandshoot = {FRAME_runs01, FRAME_runs06, gunner_frames_runandshoot, NULL}; + +void gunner_runandshoot (edict_t *self) +{ + self->monsterinfo.currentmove = &gunner_move_runandshoot; +} + +mframe_t gunner_frames_pain3 [] = +{ + ai_move, -3, NULL, + ai_move, 1, NULL, + ai_move, 1, NULL, + ai_move, 0, NULL, + ai_move, 1, NULL +}; +mmove_t gunner_move_pain3 = {FRAME_pain301, FRAME_pain305, gunner_frames_pain3, gunner_run}; + +mframe_t gunner_frames_pain2 [] = +{ + ai_move, -2, NULL, + ai_move, 11, NULL, + ai_move, 6, NULL, + ai_move, 2, NULL, + ai_move, -1, NULL, + ai_move, -7, NULL, + ai_move, -2, NULL, + ai_move, -7, NULL +}; +mmove_t gunner_move_pain2 = {FRAME_pain201, FRAME_pain208, gunner_frames_pain2, gunner_run}; + +mframe_t gunner_frames_pain1 [] = +{ + ai_move, 2, NULL, + ai_move, 0, NULL, + ai_move, -5, NULL, + ai_move, 3, NULL, + ai_move, -1, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 1, NULL, + ai_move, 1, NULL, + ai_move, 2, NULL, + ai_move, 1, NULL, + ai_move, 0, NULL, + ai_move, -2, NULL, + ai_move, -2, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t gunner_move_pain1 = {FRAME_pain101, FRAME_pain118, gunner_frames_pain1, gunner_run}; + +void gunner_pain (edict_t *self, edict_t *other, float kick, int damage) +{ + if (self->health < (self->max_health / 2)) + self->s.skinnum = 1; + + if (level.time < self->pain_debounce_time) + return; + + self->pain_debounce_time = level.time + 3; + + if (rand()&1) + gi.sound (self, CHAN_VOICE, sound_pain, 1, ATTN_NORM, 0); + else + gi.sound (self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0); + + if (skill->value == 3) + return; // no pain anims in nightmare + + if (damage <= 10) + self->monsterinfo.currentmove = &gunner_move_pain3; + else if (damage <= 25) + self->monsterinfo.currentmove = &gunner_move_pain2; + else + self->monsterinfo.currentmove = &gunner_move_pain1; +} + +void gunner_dead (edict_t *self) +{ + VectorSet (self->mins, -16, -16, -24); + VectorSet (self->maxs, 16, 16, -8); + self->movetype = MOVETYPE_TOSS; + self->svflags |= SVF_DEADMONSTER; + self->nextthink = 0; + gi.linkentity (self); +} + +mframe_t gunner_frames_death [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, -7, NULL, + ai_move, -3, NULL, + ai_move, -5, NULL, + ai_move, 8, NULL, + ai_move, 6, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t gunner_move_death = {FRAME_death01, FRAME_death11, gunner_frames_death, gunner_dead}; + +void gunner_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + int n; + +// check for gib + if (self->health <= self->gib_health) + { + gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0); + for (n= 0; n < 2; n++) + ThrowGib (self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC); + for (n= 0; n < 4; n++) + ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); + ThrowHead (self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC); + self->deadflag = DEAD_DEAD; + return; + } + + if (self->deadflag == DEAD_DEAD) + return; + +// regular death + gi.sound (self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0); + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_YES; + self->monsterinfo.currentmove = &gunner_move_death; +} + + +void gunner_duck_down (edict_t *self) +{ + if (self->monsterinfo.aiflags & AI_DUCKED) + return; + self->monsterinfo.aiflags |= AI_DUCKED; + if (skill->value >= 2) + { + if (random() > 0.5) + GunnerGrenade (self); + } + + self->maxs[2] -= 32; + self->takedamage = DAMAGE_YES; + self->monsterinfo.pausetime = level.time + 1; + gi.linkentity (self); +} + +void gunner_duck_hold (edict_t *self) +{ + if (level.time >= self->monsterinfo.pausetime) + self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; + else + self->monsterinfo.aiflags |= AI_HOLD_FRAME; +} + +void gunner_duck_up (edict_t *self) +{ + self->monsterinfo.aiflags &= ~AI_DUCKED; + self->maxs[2] += 32; + self->takedamage = DAMAGE_AIM; + gi.linkentity (self); +} + +mframe_t gunner_frames_duck [] = +{ + ai_move, 1, gunner_duck_down, + ai_move, 1, NULL, + ai_move, 1, gunner_duck_hold, + ai_move, 0, NULL, + ai_move, -1, NULL, + ai_move, -1, NULL, + ai_move, 0, gunner_duck_up, + ai_move, -1, NULL +}; +mmove_t gunner_move_duck = {FRAME_duck01, FRAME_duck08, gunner_frames_duck, gunner_run}; + +void gunner_dodge (edict_t *self, edict_t *attacker, float eta) +{ + if (random() > 0.25) + return; + + if (!self->enemy) + self->enemy = attacker; + + self->monsterinfo.currentmove = &gunner_move_duck; +} + + +void gunner_opengun (edict_t *self) +{ + gi.sound (self, CHAN_VOICE, sound_open, 1, ATTN_IDLE, 0); +} + +void GunnerFire (edict_t *self) +{ + vec3_t start; + vec3_t forward, right; + vec3_t target; + vec3_t aim; + int flash_number; + + flash_number = MZ2_GUNNER_MACHINEGUN_1 + (self->s.frame - FRAME_attak216); + + AngleVectors (self->s.angles, forward, right, NULL); + G_ProjectSource (self->s.origin, monster_flash_offset[flash_number], forward, right, start); + + // project enemy back a bit and target there + VectorCopy (self->enemy->s.origin, target); + VectorMA (target, -0.2, self->enemy->velocity, target); + target[2] += self->enemy->viewheight; + + VectorSubtract (target, start, aim); + VectorNormalize (aim); + monster_fire_bullet (self, start, aim, 3, 4, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, flash_number); +} + +void GunnerGrenade (edict_t *self) +{ + vec3_t start; + vec3_t forward, right; + vec3_t aim; + int flash_number; + + if (self->s.frame == FRAME_attak105) + flash_number = MZ2_GUNNER_GRENADE_1; + else if (self->s.frame == FRAME_attak108) + flash_number = MZ2_GUNNER_GRENADE_2; + else if (self->s.frame == FRAME_attak111) + flash_number = MZ2_GUNNER_GRENADE_3; + else // (self->s.frame == FRAME_attak114) + flash_number = MZ2_GUNNER_GRENADE_4; + + AngleVectors (self->s.angles, forward, right, NULL); + G_ProjectSource (self->s.origin, monster_flash_offset[flash_number], forward, right, start); + + //FIXME : do a spread -225 -75 75 225 degrees around forward + VectorCopy (forward, aim); + + monster_fire_grenade (self, start, aim, 50, 600, flash_number); +} + +mframe_t gunner_frames_attack_chain [] = +{ + /* + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + */ + ai_charge, 0, gunner_opengun, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL +}; +mmove_t gunner_move_attack_chain = {FRAME_attak209, FRAME_attak215, gunner_frames_attack_chain, gunner_fire_chain}; + +mframe_t gunner_frames_fire_chain [] = +{ + ai_charge, 0, GunnerFire, + ai_charge, 0, GunnerFire, + ai_charge, 0, GunnerFire, + ai_charge, 0, GunnerFire, + ai_charge, 0, GunnerFire, + ai_charge, 0, GunnerFire, + ai_charge, 0, GunnerFire, + ai_charge, 0, GunnerFire +}; +mmove_t gunner_move_fire_chain = {FRAME_attak216, FRAME_attak223, gunner_frames_fire_chain, gunner_refire_chain}; + +mframe_t gunner_frames_endfire_chain [] = +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL +}; +mmove_t gunner_move_endfire_chain = {FRAME_attak224, FRAME_attak230, gunner_frames_endfire_chain, gunner_run}; + +mframe_t gunner_frames_attack_grenade [] = +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, GunnerGrenade, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, GunnerGrenade, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, GunnerGrenade, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, GunnerGrenade, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL +}; +mmove_t gunner_move_attack_grenade = {FRAME_attak101, FRAME_attak121, gunner_frames_attack_grenade, gunner_run}; + +void gunner_attack(edict_t *self) +{ + if (range (self, self->enemy) == RANGE_MELEE) + { + self->monsterinfo.currentmove = &gunner_move_attack_chain; + } + else + { + if (random() <= 0.5) + self->monsterinfo.currentmove = &gunner_move_attack_grenade; + else + self->monsterinfo.currentmove = &gunner_move_attack_chain; + } +} + +void gunner_fire_chain(edict_t *self) +{ + self->monsterinfo.currentmove = &gunner_move_fire_chain; +} + +void gunner_refire_chain(edict_t *self) +{ + if (self->enemy->health > 0) + if ( visible (self, self->enemy) ) + if (random() <= 0.5) + { + self->monsterinfo.currentmove = &gunner_move_fire_chain; + return; + } + self->monsterinfo.currentmove = &gunner_move_endfire_chain; +} + +/*QUAKED monster_gunner (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight +*/ +void SP_monster_gunner (edict_t *self) +{ + if (deathmatch->value) + { + G_FreeEdict (self); + return; + } + + sound_death = gi.soundindex ("gunner/death1.wav"); + sound_pain = gi.soundindex ("gunner/gunpain2.wav"); + sound_pain2 = gi.soundindex ("gunner/gunpain1.wav"); + sound_idle = gi.soundindex ("gunner/gunidle1.wav"); + sound_open = gi.soundindex ("gunner/gunatck1.wav"); + sound_search = gi.soundindex ("gunner/gunsrch1.wav"); + sound_sight = gi.soundindex ("gunner/sight1.wav"); + + gi.soundindex ("gunner/gunatck2.wav"); + gi.soundindex ("gunner/gunatck3.wav"); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex ("models/monsters/gunner/tris.md2"); + VectorSet (self->mins, -16, -16, -24); + VectorSet (self->maxs, 16, 16, 32); + + self->health = 175; + self->gib_health = -70; + self->mass = 200; + + self->pain = gunner_pain; + self->die = gunner_die; + + self->monsterinfo.stand = gunner_stand; + self->monsterinfo.walk = gunner_walk; + self->monsterinfo.run = gunner_run; + self->monsterinfo.dodge = gunner_dodge; + self->monsterinfo.attack = gunner_attack; + self->monsterinfo.melee = NULL; + self->monsterinfo.sight = gunner_sight; + self->monsterinfo.search = gunner_search; + + gi.linkentity (self); + + self->monsterinfo.currentmove = &gunner_move_stand; + self->monsterinfo.scale = MODEL_SCALE; + + walkmonster_start (self); +} diff --git a/original/xatrix/m_gunner.h b/original/xatrix/m_gunner.h new file mode 100644 index 0000000..6d08b8b --- /dev/null +++ b/original/xatrix/m_gunner.h @@ -0,0 +1,217 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// G:\quake2\baseq2\models/gunner + +// This file generated by ModelGen - Do NOT Modify + +#define FRAME_stand01 0 +#define FRAME_stand02 1 +#define FRAME_stand03 2 +#define FRAME_stand04 3 +#define FRAME_stand05 4 +#define FRAME_stand06 5 +#define FRAME_stand07 6 +#define FRAME_stand08 7 +#define FRAME_stand09 8 +#define FRAME_stand10 9 +#define FRAME_stand11 10 +#define FRAME_stand12 11 +#define FRAME_stand13 12 +#define FRAME_stand14 13 +#define FRAME_stand15 14 +#define FRAME_stand16 15 +#define FRAME_stand17 16 +#define FRAME_stand18 17 +#define FRAME_stand19 18 +#define FRAME_stand20 19 +#define FRAME_stand21 20 +#define FRAME_stand22 21 +#define FRAME_stand23 22 +#define FRAME_stand24 23 +#define FRAME_stand25 24 +#define FRAME_stand26 25 +#define FRAME_stand27 26 +#define FRAME_stand28 27 +#define FRAME_stand29 28 +#define FRAME_stand30 29 +#define FRAME_stand31 30 +#define FRAME_stand32 31 +#define FRAME_stand33 32 +#define FRAME_stand34 33 +#define FRAME_stand35 34 +#define FRAME_stand36 35 +#define FRAME_stand37 36 +#define FRAME_stand38 37 +#define FRAME_stand39 38 +#define FRAME_stand40 39 +#define FRAME_stand41 40 +#define FRAME_stand42 41 +#define FRAME_stand43 42 +#define FRAME_stand44 43 +#define FRAME_stand45 44 +#define FRAME_stand46 45 +#define FRAME_stand47 46 +#define FRAME_stand48 47 +#define FRAME_stand49 48 +#define FRAME_stand50 49 +#define FRAME_stand51 50 +#define FRAME_stand52 51 +#define FRAME_stand53 52 +#define FRAME_stand54 53 +#define FRAME_stand55 54 +#define FRAME_stand56 55 +#define FRAME_stand57 56 +#define FRAME_stand58 57 +#define FRAME_stand59 58 +#define FRAME_stand60 59 +#define FRAME_stand61 60 +#define FRAME_stand62 61 +#define FRAME_stand63 62 +#define FRAME_stand64 63 +#define FRAME_stand65 64 +#define FRAME_stand66 65 +#define FRAME_stand67 66 +#define FRAME_stand68 67 +#define FRAME_stand69 68 +#define FRAME_stand70 69 +#define FRAME_walk01 70 +#define FRAME_walk02 71 +#define FRAME_walk03 72 +#define FRAME_walk04 73 +#define FRAME_walk05 74 +#define FRAME_walk06 75 +#define FRAME_walk07 76 +#define FRAME_walk08 77 +#define FRAME_walk09 78 +#define FRAME_walk10 79 +#define FRAME_walk11 80 +#define FRAME_walk12 81 +#define FRAME_walk13 82 +#define FRAME_walk14 83 +#define FRAME_walk15 84 +#define FRAME_walk16 85 +#define FRAME_walk17 86 +#define FRAME_walk18 87 +#define FRAME_walk19 88 +#define FRAME_walk20 89 +#define FRAME_walk21 90 +#define FRAME_walk22 91 +#define FRAME_walk23 92 +#define FRAME_walk24 93 +#define FRAME_run01 94 +#define FRAME_run02 95 +#define FRAME_run03 96 +#define FRAME_run04 97 +#define FRAME_run05 98 +#define FRAME_run06 99 +#define FRAME_run07 100 +#define FRAME_run08 101 +#define FRAME_runs01 102 +#define FRAME_runs02 103 +#define FRAME_runs03 104 +#define FRAME_runs04 105 +#define FRAME_runs05 106 +#define FRAME_runs06 107 +#define FRAME_attak101 108 +#define FRAME_attak102 109 +#define FRAME_attak103 110 +#define FRAME_attak104 111 +#define FRAME_attak105 112 +#define FRAME_attak106 113 +#define FRAME_attak107 114 +#define FRAME_attak108 115 +#define FRAME_attak109 116 +#define FRAME_attak110 117 +#define FRAME_attak111 118 +#define FRAME_attak112 119 +#define FRAME_attak113 120 +#define FRAME_attak114 121 +#define FRAME_attak115 122 +#define FRAME_attak116 123 +#define FRAME_attak117 124 +#define FRAME_attak118 125 +#define FRAME_attak119 126 +#define FRAME_attak120 127 +#define FRAME_attak121 128 +#define FRAME_attak201 129 +#define FRAME_attak202 130 +#define FRAME_attak203 131 +#define FRAME_attak204 132 +#define FRAME_attak205 133 +#define FRAME_attak206 134 +#define FRAME_attak207 135 +#define FRAME_attak208 136 +#define FRAME_attak209 137 +#define FRAME_attak210 138 +#define FRAME_attak211 139 +#define FRAME_attak212 140 +#define FRAME_attak213 141 +#define FRAME_attak214 142 +#define FRAME_attak215 143 +#define FRAME_attak216 144 +#define FRAME_attak217 145 +#define FRAME_attak218 146 +#define FRAME_attak219 147 +#define FRAME_attak220 148 +#define FRAME_attak221 149 +#define FRAME_attak222 150 +#define FRAME_attak223 151 +#define FRAME_attak224 152 +#define FRAME_attak225 153 +#define FRAME_attak226 154 +#define FRAME_attak227 155 +#define FRAME_attak228 156 +#define FRAME_attak229 157 +#define FRAME_attak230 158 +#define FRAME_pain101 159 +#define FRAME_pain102 160 +#define FRAME_pain103 161 +#define FRAME_pain104 162 +#define FRAME_pain105 163 +#define FRAME_pain106 164 +#define FRAME_pain107 165 +#define FRAME_pain108 166 +#define FRAME_pain109 167 +#define FRAME_pain110 168 +#define FRAME_pain111 169 +#define FRAME_pain112 170 +#define FRAME_pain113 171 +#define FRAME_pain114 172 +#define FRAME_pain115 173 +#define FRAME_pain116 174 +#define FRAME_pain117 175 +#define FRAME_pain118 176 +#define FRAME_pain201 177 +#define FRAME_pain202 178 +#define FRAME_pain203 179 +#define FRAME_pain204 180 +#define FRAME_pain205 181 +#define FRAME_pain206 182 +#define FRAME_pain207 183 +#define FRAME_pain208 184 +#define FRAME_pain301 185 +#define FRAME_pain302 186 +#define FRAME_pain303 187 +#define FRAME_pain304 188 +#define FRAME_pain305 189 +#define FRAME_death01 190 +#define FRAME_death02 191 +#define FRAME_death03 192 +#define FRAME_death04 193 +#define FRAME_death05 194 +#define FRAME_death06 195 +#define FRAME_death07 196 +#define FRAME_death08 197 +#define FRAME_death09 198 +#define FRAME_death10 199 +#define FRAME_death11 200 +#define FRAME_duck01 201 +#define FRAME_duck02 202 +#define FRAME_duck03 203 +#define FRAME_duck04 204 +#define FRAME_duck05 205 +#define FRAME_duck06 206 +#define FRAME_duck07 207 +#define FRAME_duck08 208 + +#define MODEL_SCALE 1.150000 diff --git a/original/xatrix/m_hover.c b/original/xatrix/m_hover.c new file mode 100644 index 0000000..a22c7ce --- /dev/null +++ b/original/xatrix/m_hover.c @@ -0,0 +1,603 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +/* +============================================================================== + +hover + +============================================================================== +*/ + +#include "g_local.h" +#include "m_hover.h" + +qboolean visible (edict_t *self, edict_t *other); + + +static int sound_pain1; +static int sound_pain2; +static int sound_death1; +static int sound_death2; +static int sound_sight; +static int sound_search1; +static int sound_search2; + + +void hover_sight (edict_t *self, edict_t *other) +{ + gi.sound (self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); +} + +void hover_search (edict_t *self) +{ + if (random() < 0.5) + gi.sound (self, CHAN_VOICE, sound_search1, 1, ATTN_NORM, 0); + else + gi.sound (self, CHAN_VOICE, sound_search2, 1, ATTN_NORM, 0); +} + + +void hover_run (edict_t *self); +void hover_stand (edict_t *self); +void hover_dead (edict_t *self); +void hover_attack (edict_t *self); +void hover_reattack (edict_t *self); +void hover_fire_blaster (edict_t *self); +void hover_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point); + +mframe_t hover_frames_stand [] = +{ + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL +}; +mmove_t hover_move_stand = {FRAME_stand01, FRAME_stand30, hover_frames_stand, NULL}; + +mframe_t hover_frames_stop1 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t hover_move_stop1 = {FRAME_stop101, FRAME_stop109, hover_frames_stop1, NULL}; + +mframe_t hover_frames_stop2 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t hover_move_stop2 = {FRAME_stop201, FRAME_stop208, hover_frames_stop2, NULL}; + +mframe_t hover_frames_takeoff [] = +{ + ai_move, 0, NULL, + ai_move, -2, NULL, + ai_move, 5, NULL, + ai_move, -1, NULL, + ai_move, 1, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, -1, NULL, + ai_move, -1, NULL, + ai_move, -1, NULL, + ai_move, 0, NULL, + ai_move, 2, NULL, + ai_move, 2, NULL, + ai_move, 1, NULL, + ai_move, 1, NULL, + ai_move, -6, NULL, + ai_move, -9, NULL, + ai_move, 1, NULL, + ai_move, 0, NULL, + ai_move, 2, NULL, + ai_move, 2, NULL, + ai_move, 1, NULL, + ai_move, 1, NULL, + ai_move, 1, NULL, + ai_move, 2, NULL, + ai_move, 0, NULL, + ai_move, 2, NULL, + ai_move, 3, NULL, + ai_move, 2, NULL, + ai_move, 0, NULL +}; +mmove_t hover_move_takeoff = {FRAME_takeof01, FRAME_takeof30, hover_frames_takeoff, NULL}; + +mframe_t hover_frames_pain3 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t hover_move_pain3 = {FRAME_pain301, FRAME_pain309, hover_frames_pain3, hover_run}; + +mframe_t hover_frames_pain2 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t hover_move_pain2 = {FRAME_pain201, FRAME_pain212, hover_frames_pain2, hover_run}; + +mframe_t hover_frames_pain1 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 2, NULL, + ai_move, -8, NULL, + ai_move, -4, NULL, + ai_move, -6, NULL, + ai_move, -4, NULL, + ai_move, -3, NULL, + ai_move, 1, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 3, NULL, + ai_move, 1, NULL, + ai_move, 0, NULL, + ai_move, 2, NULL, + ai_move, 3, NULL, + ai_move, 2, NULL, + ai_move, 7, NULL, + ai_move, 1, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 2, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 5, NULL, + ai_move, 3, NULL, + ai_move, 4, NULL +}; +mmove_t hover_move_pain1 = {FRAME_pain101, FRAME_pain128, hover_frames_pain1, hover_run}; + +mframe_t hover_frames_land [] = +{ + ai_move, 0, NULL +}; +mmove_t hover_move_land = {FRAME_land01, FRAME_land01, hover_frames_land, NULL}; + +mframe_t hover_frames_forward [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t hover_move_forward = {FRAME_forwrd01, FRAME_forwrd35, hover_frames_forward, NULL}; + +mframe_t hover_frames_walk [] = +{ + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL +}; +mmove_t hover_move_walk = {FRAME_forwrd01, FRAME_forwrd35, hover_frames_walk, NULL}; + +mframe_t hover_frames_run [] = +{ + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL, + ai_run, 10, NULL +}; +mmove_t hover_move_run = {FRAME_forwrd01, FRAME_forwrd35, hover_frames_run, NULL}; + +mframe_t hover_frames_death1 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, -10,NULL, + ai_move, 3, NULL, + ai_move, 5, NULL, + ai_move, 4, NULL, + ai_move, 7, NULL +}; +mmove_t hover_move_death1 = {FRAME_death101, FRAME_death111, hover_frames_death1, hover_dead}; + +mframe_t hover_frames_backward [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t hover_move_backward = {FRAME_backwd01, FRAME_backwd24, hover_frames_backward, NULL}; + +mframe_t hover_frames_start_attack [] = +{ + ai_charge, 1, NULL, + ai_charge, 1, NULL, + ai_charge, 1, NULL +}; +mmove_t hover_move_start_attack = {FRAME_attak101, FRAME_attak103, hover_frames_start_attack, hover_attack}; + +mframe_t hover_frames_attack1 [] = +{ + ai_charge, -10, hover_fire_blaster, + ai_charge, -10, hover_fire_blaster, + ai_charge, 0, hover_reattack, +}; +mmove_t hover_move_attack1 = {FRAME_attak104, FRAME_attak106, hover_frames_attack1, NULL}; + + +mframe_t hover_frames_end_attack [] = +{ + ai_charge, 1, NULL, + ai_charge, 1, NULL +}; +mmove_t hover_move_end_attack = {FRAME_attak107, FRAME_attak108, hover_frames_end_attack, hover_run}; + +void hover_reattack (edict_t *self) +{ + if (self->enemy->health > 0 ) + if (visible (self, self->enemy) ) + if (random() <= 0.6) + { + self->monsterinfo.currentmove = &hover_move_attack1; + return; + } + self->monsterinfo.currentmove = &hover_move_end_attack; +} + + +void hover_fire_blaster (edict_t *self) +{ + vec3_t start; + vec3_t forward, right; + vec3_t end; + vec3_t dir; + int effect; + + if (self->s.frame == FRAME_attak104) + effect = EF_HYPERBLASTER; + else + effect = 0; + + AngleVectors (self->s.angles, forward, right, NULL); + G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_HOVER_BLASTER_1], forward, right, start); + + VectorCopy (self->enemy->s.origin, end); + end[2] += self->enemy->viewheight; + VectorSubtract (end, start, dir); + + monster_fire_blaster (self, start, dir, 1, 1000, MZ2_HOVER_BLASTER_1, effect); +} + + +void hover_stand (edict_t *self) +{ + self->monsterinfo.currentmove = &hover_move_stand; +} + +void hover_run (edict_t *self) +{ + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + self->monsterinfo.currentmove = &hover_move_stand; + else + self->monsterinfo.currentmove = &hover_move_run; +} + +void hover_walk (edict_t *self) +{ + self->monsterinfo.currentmove = &hover_move_walk; +} + +void hover_start_attack (edict_t *self) +{ + self->monsterinfo.currentmove = &hover_move_start_attack; +} + +void hover_attack(edict_t *self) +{ + self->monsterinfo.currentmove = &hover_move_attack1; +} + + +void hover_pain (edict_t *self, edict_t *other, float kick, int damage) +{ + if (self->health < (self->max_health / 2)) + self->s.skinnum = 1; + + if (level.time < self->pain_debounce_time) + return; + + self->pain_debounce_time = level.time + 3; + + if (skill->value == 3) + return; // no pain anims in nightmare + + if (damage <= 25) + { + if (random() < 0.5) + { + gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &hover_move_pain3; + } + else + { + gi.sound (self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &hover_move_pain2; + } + } + else + { + gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &hover_move_pain1; + } +} + +void hover_deadthink (edict_t *self) +{ + if (!self->groundentity && level.time < self->timestamp) + { + self->nextthink = level.time + FRAMETIME; + return; + } + BecomeExplosion1(self); +} + +void hover_dead (edict_t *self) +{ + VectorSet (self->mins, -16, -16, -24); + VectorSet (self->maxs, 16, 16, -8); + self->movetype = MOVETYPE_TOSS; + self->think = hover_deadthink; + self->nextthink = level.time + FRAMETIME; + self->timestamp = level.time + 15; + gi.linkentity (self); +} + +void hover_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + int n; + +// check for gib + if (self->health <= self->gib_health) + { + gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0); + for (n= 0; n < 2; n++) + ThrowGib (self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC); + for (n= 0; n < 2; n++) + ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); + ThrowHead (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); + self->deadflag = DEAD_DEAD; + return; + } + + if (self->deadflag == DEAD_DEAD) + return; + +// regular death + if (random() < 0.5) + gi.sound (self, CHAN_VOICE, sound_death1, 1, ATTN_NORM, 0); + else + gi.sound (self, CHAN_VOICE, sound_death2, 1, ATTN_NORM, 0); + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_YES; + self->monsterinfo.currentmove = &hover_move_death1; +} + +/*QUAKED monster_hover (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight +*/ +void SP_monster_hover (edict_t *self) +{ + if (deathmatch->value) + { + G_FreeEdict (self); + return; + } + + sound_pain1 = gi.soundindex ("hover/hovpain1.wav"); + sound_pain2 = gi.soundindex ("hover/hovpain2.wav"); + sound_death1 = gi.soundindex ("hover/hovdeth1.wav"); + sound_death2 = gi.soundindex ("hover/hovdeth2.wav"); + sound_sight = gi.soundindex ("hover/hovsght1.wav"); + sound_search1 = gi.soundindex ("hover/hovsrch1.wav"); + sound_search2 = gi.soundindex ("hover/hovsrch2.wav"); + + gi.soundindex ("hover/hovatck1.wav"); + + self->s.sound = gi.soundindex ("hover/hovidle1.wav"); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex("models/monsters/hover/tris.md2"); + VectorSet (self->mins, -24, -24, -24); + VectorSet (self->maxs, 24, 24, 32); + + self->health = 240; + self->gib_health = -100; + self->mass = 150; + + self->pain = hover_pain; + self->die = hover_die; + + self->monsterinfo.stand = hover_stand; + self->monsterinfo.walk = hover_walk; + self->monsterinfo.run = hover_run; +// self->monsterinfo.dodge = hover_dodge; + self->monsterinfo.attack = hover_start_attack; + self->monsterinfo.sight = hover_sight; + self->monsterinfo.search = hover_search; + + gi.linkentity (self); + + self->monsterinfo.currentmove = &hover_move_stand; + self->monsterinfo.scale = MODEL_SCALE; + + flymonster_start (self); +} diff --git a/original/xatrix/m_hover.h b/original/xatrix/m_hover.h new file mode 100644 index 0000000..c1754f0 --- /dev/null +++ b/original/xatrix/m_hover.h @@ -0,0 +1,213 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// G:\quake2\baseq2\models/monsters/hover + +// This file generated by ModelGen - Do NOT Modify + +#define FRAME_stand01 0 +#define FRAME_stand02 1 +#define FRAME_stand03 2 +#define FRAME_stand04 3 +#define FRAME_stand05 4 +#define FRAME_stand06 5 +#define FRAME_stand07 6 +#define FRAME_stand08 7 +#define FRAME_stand09 8 +#define FRAME_stand10 9 +#define FRAME_stand11 10 +#define FRAME_stand12 11 +#define FRAME_stand13 12 +#define FRAME_stand14 13 +#define FRAME_stand15 14 +#define FRAME_stand16 15 +#define FRAME_stand17 16 +#define FRAME_stand18 17 +#define FRAME_stand19 18 +#define FRAME_stand20 19 +#define FRAME_stand21 20 +#define FRAME_stand22 21 +#define FRAME_stand23 22 +#define FRAME_stand24 23 +#define FRAME_stand25 24 +#define FRAME_stand26 25 +#define FRAME_stand27 26 +#define FRAME_stand28 27 +#define FRAME_stand29 28 +#define FRAME_stand30 29 +#define FRAME_forwrd01 30 +#define FRAME_forwrd02 31 +#define FRAME_forwrd03 32 +#define FRAME_forwrd04 33 +#define FRAME_forwrd05 34 +#define FRAME_forwrd06 35 +#define FRAME_forwrd07 36 +#define FRAME_forwrd08 37 +#define FRAME_forwrd09 38 +#define FRAME_forwrd10 39 +#define FRAME_forwrd11 40 +#define FRAME_forwrd12 41 +#define FRAME_forwrd13 42 +#define FRAME_forwrd14 43 +#define FRAME_forwrd15 44 +#define FRAME_forwrd16 45 +#define FRAME_forwrd17 46 +#define FRAME_forwrd18 47 +#define FRAME_forwrd19 48 +#define FRAME_forwrd20 49 +#define FRAME_forwrd21 50 +#define FRAME_forwrd22 51 +#define FRAME_forwrd23 52 +#define FRAME_forwrd24 53 +#define FRAME_forwrd25 54 +#define FRAME_forwrd26 55 +#define FRAME_forwrd27 56 +#define FRAME_forwrd28 57 +#define FRAME_forwrd29 58 +#define FRAME_forwrd30 59 +#define FRAME_forwrd31 60 +#define FRAME_forwrd32 61 +#define FRAME_forwrd33 62 +#define FRAME_forwrd34 63 +#define FRAME_forwrd35 64 +#define FRAME_stop101 65 +#define FRAME_stop102 66 +#define FRAME_stop103 67 +#define FRAME_stop104 68 +#define FRAME_stop105 69 +#define FRAME_stop106 70 +#define FRAME_stop107 71 +#define FRAME_stop108 72 +#define FRAME_stop109 73 +#define FRAME_stop201 74 +#define FRAME_stop202 75 +#define FRAME_stop203 76 +#define FRAME_stop204 77 +#define FRAME_stop205 78 +#define FRAME_stop206 79 +#define FRAME_stop207 80 +#define FRAME_stop208 81 +#define FRAME_takeof01 82 +#define FRAME_takeof02 83 +#define FRAME_takeof03 84 +#define FRAME_takeof04 85 +#define FRAME_takeof05 86 +#define FRAME_takeof06 87 +#define FRAME_takeof07 88 +#define FRAME_takeof08 89 +#define FRAME_takeof09 90 +#define FRAME_takeof10 91 +#define FRAME_takeof11 92 +#define FRAME_takeof12 93 +#define FRAME_takeof13 94 +#define FRAME_takeof14 95 +#define FRAME_takeof15 96 +#define FRAME_takeof16 97 +#define FRAME_takeof17 98 +#define FRAME_takeof18 99 +#define FRAME_takeof19 100 +#define FRAME_takeof20 101 +#define FRAME_takeof21 102 +#define FRAME_takeof22 103 +#define FRAME_takeof23 104 +#define FRAME_takeof24 105 +#define FRAME_takeof25 106 +#define FRAME_takeof26 107 +#define FRAME_takeof27 108 +#define FRAME_takeof28 109 +#define FRAME_takeof29 110 +#define FRAME_takeof30 111 +#define FRAME_land01 112 +#define FRAME_pain101 113 +#define FRAME_pain102 114 +#define FRAME_pain103 115 +#define FRAME_pain104 116 +#define FRAME_pain105 117 +#define FRAME_pain106 118 +#define FRAME_pain107 119 +#define FRAME_pain108 120 +#define FRAME_pain109 121 +#define FRAME_pain110 122 +#define FRAME_pain111 123 +#define FRAME_pain112 124 +#define FRAME_pain113 125 +#define FRAME_pain114 126 +#define FRAME_pain115 127 +#define FRAME_pain116 128 +#define FRAME_pain117 129 +#define FRAME_pain118 130 +#define FRAME_pain119 131 +#define FRAME_pain120 132 +#define FRAME_pain121 133 +#define FRAME_pain122 134 +#define FRAME_pain123 135 +#define FRAME_pain124 136 +#define FRAME_pain125 137 +#define FRAME_pain126 138 +#define FRAME_pain127 139 +#define FRAME_pain128 140 +#define FRAME_pain201 141 +#define FRAME_pain202 142 +#define FRAME_pain203 143 +#define FRAME_pain204 144 +#define FRAME_pain205 145 +#define FRAME_pain206 146 +#define FRAME_pain207 147 +#define FRAME_pain208 148 +#define FRAME_pain209 149 +#define FRAME_pain210 150 +#define FRAME_pain211 151 +#define FRAME_pain212 152 +#define FRAME_pain301 153 +#define FRAME_pain302 154 +#define FRAME_pain303 155 +#define FRAME_pain304 156 +#define FRAME_pain305 157 +#define FRAME_pain306 158 +#define FRAME_pain307 159 +#define FRAME_pain308 160 +#define FRAME_pain309 161 +#define FRAME_death101 162 +#define FRAME_death102 163 +#define FRAME_death103 164 +#define FRAME_death104 165 +#define FRAME_death105 166 +#define FRAME_death106 167 +#define FRAME_death107 168 +#define FRAME_death108 169 +#define FRAME_death109 170 +#define FRAME_death110 171 +#define FRAME_death111 172 +#define FRAME_backwd01 173 +#define FRAME_backwd02 174 +#define FRAME_backwd03 175 +#define FRAME_backwd04 176 +#define FRAME_backwd05 177 +#define FRAME_backwd06 178 +#define FRAME_backwd07 179 +#define FRAME_backwd08 180 +#define FRAME_backwd09 181 +#define FRAME_backwd10 182 +#define FRAME_backwd11 183 +#define FRAME_backwd12 184 +#define FRAME_backwd13 185 +#define FRAME_backwd14 186 +#define FRAME_backwd15 187 +#define FRAME_backwd16 188 +#define FRAME_backwd17 189 +#define FRAME_backwd18 190 +#define FRAME_backwd19 191 +#define FRAME_backwd20 192 +#define FRAME_backwd21 193 +#define FRAME_backwd22 194 +#define FRAME_backwd23 195 +#define FRAME_backwd24 196 +#define FRAME_attak101 197 +#define FRAME_attak102 198 +#define FRAME_attak103 199 +#define FRAME_attak104 200 +#define FRAME_attak105 201 +#define FRAME_attak106 202 +#define FRAME_attak107 203 +#define FRAME_attak108 204 + +#define MODEL_SCALE 1.000000 diff --git a/original/xatrix/m_infantry.c b/original/xatrix/m_infantry.c new file mode 100644 index 0000000..e48b16f --- /dev/null +++ b/original/xatrix/m_infantry.c @@ -0,0 +1,594 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +/* +============================================================================== + +INFANTRY + +============================================================================== +*/ + +#include "g_local.h" +#include "m_infantry.h" + +void InfantryMachineGun (edict_t *self); + + +static int sound_pain1; +static int sound_pain2; +static int sound_die1; +static int sound_die2; + +static int sound_gunshot; +static int sound_weapon_cock; +static int sound_punch_swing; +static int sound_punch_hit; +static int sound_sight; +static int sound_search; +static int sound_idle; + + +mframe_t infantry_frames_stand [] = +{ + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL +}; +mmove_t infantry_move_stand = {FRAME_stand50, FRAME_stand71, infantry_frames_stand, NULL}; + +void infantry_stand (edict_t *self) +{ + self->monsterinfo.currentmove = &infantry_move_stand; +} + + +mframe_t infantry_frames_fidget [] = +{ + ai_stand, 1, NULL, + ai_stand, 0, NULL, + ai_stand, 1, NULL, + ai_stand, 3, NULL, + ai_stand, 6, NULL, + ai_stand, 3, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 1, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 1, NULL, + ai_stand, 0, NULL, + ai_stand, -1, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 1, NULL, + ai_stand, 0, NULL, + ai_stand, -2, NULL, + ai_stand, 1, NULL, + ai_stand, 1, NULL, + ai_stand, 1, NULL, + ai_stand, -1, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, -1, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, -1, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 1, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, -1, NULL, + ai_stand, -1, NULL, + ai_stand, 0, NULL, + ai_stand, -3, NULL, + ai_stand, -2, NULL, + ai_stand, -3, NULL, + ai_stand, -3, NULL, + ai_stand, -2, NULL +}; +mmove_t infantry_move_fidget = {FRAME_stand01, FRAME_stand49, infantry_frames_fidget, infantry_stand}; + +void infantry_fidget (edict_t *self) +{ + self->monsterinfo.currentmove = &infantry_move_fidget; + gi.sound (self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0); +} + +mframe_t infantry_frames_walk [] = +{ + ai_walk, 5, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 5, NULL, + ai_walk, 4, NULL, + ai_walk, 5, NULL, + ai_walk, 6, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 5, NULL +}; +mmove_t infantry_move_walk = {FRAME_walk03, FRAME_walk14, infantry_frames_walk, NULL}; + +void infantry_walk (edict_t *self) +{ + self->monsterinfo.currentmove = &infantry_move_walk; +} + +mframe_t infantry_frames_run [] = +{ + ai_run, 10, NULL, + ai_run, 20, NULL, + ai_run, 5, NULL, + ai_run, 7, NULL, + ai_run, 30, NULL, + ai_run, 35, NULL, + ai_run, 2, NULL, + ai_run, 6, NULL +}; +mmove_t infantry_move_run = {FRAME_run01, FRAME_run08, infantry_frames_run, NULL}; + +void infantry_run (edict_t *self) +{ + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + self->monsterinfo.currentmove = &infantry_move_stand; + else + self->monsterinfo.currentmove = &infantry_move_run; +} + + +mframe_t infantry_frames_pain1 [] = +{ + ai_move, -3, NULL, + ai_move, -2, NULL, + ai_move, -1, NULL, + ai_move, -2, NULL, + ai_move, -1, NULL, + ai_move, 1, NULL, + ai_move, -1, NULL, + ai_move, 1, NULL, + ai_move, 6, NULL, + ai_move, 2, NULL +}; +mmove_t infantry_move_pain1 = {FRAME_pain101, FRAME_pain110, infantry_frames_pain1, infantry_run}; + +mframe_t infantry_frames_pain2 [] = +{ + ai_move, -3, NULL, + ai_move, -3, NULL, + ai_move, 0, NULL, + ai_move, -1, NULL, + ai_move, -2, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 2, NULL, + ai_move, 5, NULL, + ai_move, 2, NULL +}; +mmove_t infantry_move_pain2 = {FRAME_pain201, FRAME_pain210, infantry_frames_pain2, infantry_run}; + +void infantry_pain (edict_t *self, edict_t *other, float kick, int damage) +{ + int n; + + if (self->health < (self->max_health / 2)) + self->s.skinnum = 1; + + if (level.time < self->pain_debounce_time) + return; + + self->pain_debounce_time = level.time + 3; + + if (skill->value == 3) + return; // no pain anims in nightmare + + n = rand() % 2; + if (n == 0) + { + self->monsterinfo.currentmove = &infantry_move_pain1; + gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); + } + else + { + self->monsterinfo.currentmove = &infantry_move_pain2; + gi.sound (self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0); + } +} + + +vec3_t aimangles[] = +{ + 0.0, 5.0, 0.0, + 10.0, 15.0, 0.0, + 20.0, 25.0, 0.0, + 25.0, 35.0, 0.0, + 30.0, 40.0, 0.0, + 30.0, 45.0, 0.0, + 25.0, 50.0, 0.0, + 20.0, 40.0, 0.0, + 15.0, 35.0, 0.0, + 40.0, 35.0, 0.0, + 70.0, 35.0, 0.0, + 90.0, 35.0, 0.0 +}; + +void InfantryMachineGun (edict_t *self) +{ + vec3_t start, target; + vec3_t forward, right; + vec3_t vec; + int flash_number; + + if (self->s.frame == FRAME_attak103) + { + flash_number = MZ2_INFANTRY_MACHINEGUN_1; + AngleVectors (self->s.angles, forward, right, NULL); + G_ProjectSource (self->s.origin, monster_flash_offset[flash_number], forward, right, start); + + if (self->enemy) + { + VectorMA (self->enemy->s.origin, -0.2, self->enemy->velocity, target); + target[2] += self->enemy->viewheight; + VectorSubtract (target, start, forward); + VectorNormalize (forward); + } + else + { + AngleVectors (self->s.angles, forward, right, NULL); + } + } + else + { + flash_number = MZ2_INFANTRY_MACHINEGUN_2 + (self->s.frame - FRAME_death211); + + AngleVectors (self->s.angles, forward, right, NULL); + G_ProjectSource (self->s.origin, monster_flash_offset[flash_number], forward, right, start); + + VectorSubtract (self->s.angles, aimangles[flash_number-MZ2_INFANTRY_MACHINEGUN_2], vec); + AngleVectors (vec, forward, NULL, NULL); + } + + monster_fire_bullet (self, start, forward, 3, 4, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, flash_number); +} + +void infantry_sight (edict_t *self, edict_t *other) +{ + gi.sound (self, CHAN_BODY, sound_sight, 1, ATTN_NORM, 0); +} + +void infantry_dead (edict_t *self) +{ + VectorSet (self->mins, -16, -16, -24); + VectorSet (self->maxs, 16, 16, -8); + self->movetype = MOVETYPE_TOSS; + self->svflags |= SVF_DEADMONSTER; + gi.linkentity (self); + + M_FlyCheck (self); +} + +mframe_t infantry_frames_death1 [] = +{ + ai_move, -4, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, -1, NULL, + ai_move, -4, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, -1, NULL, + ai_move, 3, NULL, + ai_move, 1, NULL, + ai_move, 1, NULL, + ai_move, -2, NULL, + ai_move, 2, NULL, + ai_move, 2, NULL, + ai_move, 9, NULL, + ai_move, 9, NULL, + ai_move, 5, NULL, + ai_move, -3, NULL, + ai_move, -3, NULL +}; +mmove_t infantry_move_death1 = {FRAME_death101, FRAME_death120, infantry_frames_death1, infantry_dead}; + +// Off with his head +mframe_t infantry_frames_death2 [] = +{ + ai_move, 0, NULL, + ai_move, 1, NULL, + ai_move, 5, NULL, + ai_move, -1, NULL, + ai_move, 0, NULL, + ai_move, 1, NULL, + ai_move, 1, NULL, + ai_move, 4, NULL, + ai_move, 3, NULL, + ai_move, 0, NULL, + ai_move, -2, InfantryMachineGun, + ai_move, -2, InfantryMachineGun, + ai_move, -3, InfantryMachineGun, + ai_move, -1, InfantryMachineGun, + ai_move, -2, InfantryMachineGun, + ai_move, 0, InfantryMachineGun, + ai_move, 2, InfantryMachineGun, + ai_move, 2, InfantryMachineGun, + ai_move, 3, InfantryMachineGun, + ai_move, -10, InfantryMachineGun, + ai_move, -7, InfantryMachineGun, + ai_move, -8, InfantryMachineGun, + ai_move, -6, NULL, + ai_move, 4, NULL, + ai_move, 0, NULL +}; +mmove_t infantry_move_death2 = {FRAME_death201, FRAME_death225, infantry_frames_death2, infantry_dead}; + +mframe_t infantry_frames_death3 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, -6, NULL, + ai_move, -11, NULL, + ai_move, -3, NULL, + ai_move, -11, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t infantry_move_death3 = {FRAME_death301, FRAME_death309, infantry_frames_death3, infantry_dead}; + + +void infantry_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + int n; + +// check for gib + if (self->health <= self->gib_health) + { + gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0); + for (n= 0; n < 2; n++) + ThrowGib (self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC); + for (n= 0; n < 4; n++) + ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); + ThrowHead (self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC); + self->deadflag = DEAD_DEAD; + return; + } + + if (self->deadflag == DEAD_DEAD) + return; + +// regular death + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_YES; + + n = rand() % 3; + if (n == 0) + { + self->monsterinfo.currentmove = &infantry_move_death1; + gi.sound (self, CHAN_VOICE, sound_die2, 1, ATTN_NORM, 0); + } + else if (n == 1) + { + self->monsterinfo.currentmove = &infantry_move_death2; + gi.sound (self, CHAN_VOICE, sound_die1, 1, ATTN_NORM, 0); + } + else + { + self->monsterinfo.currentmove = &infantry_move_death3; + gi.sound (self, CHAN_VOICE, sound_die2, 1, ATTN_NORM, 0); + } +} + + +void infantry_duck_down (edict_t *self) +{ + if (self->monsterinfo.aiflags & AI_DUCKED) + return; + self->monsterinfo.aiflags |= AI_DUCKED; + self->maxs[2] -= 32; + self->takedamage = DAMAGE_YES; + self->monsterinfo.pausetime = level.time + 1; + gi.linkentity (self); +} + +void infantry_duck_hold (edict_t *self) +{ + if (level.time >= self->monsterinfo.pausetime) + self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; + else + self->monsterinfo.aiflags |= AI_HOLD_FRAME; +} + +void infantry_duck_up (edict_t *self) +{ + self->monsterinfo.aiflags &= ~AI_DUCKED; + self->maxs[2] += 32; + self->takedamage = DAMAGE_AIM; + gi.linkentity (self); +} + +mframe_t infantry_frames_duck [] = +{ + ai_move, -2, infantry_duck_down, + ai_move, -5, infantry_duck_hold, + ai_move, 3, NULL, + ai_move, 4, infantry_duck_up, + ai_move, 0, NULL +}; +mmove_t infantry_move_duck = {FRAME_duck01, FRAME_duck05, infantry_frames_duck, infantry_run}; + +void infantry_dodge (edict_t *self, edict_t *attacker, float eta) +{ + if (random() > 0.25) + return; + + if (!self->enemy) + self->enemy = attacker; + + self->monsterinfo.currentmove = &infantry_move_duck; +} + + +void infantry_set_firetime (edict_t *self) +{ + int n; + + n = (rand() & 15) + 5; + self->monsterinfo.pausetime = level.time + n * FRAMETIME; +} + +void infantry_cock_gun (edict_t *self) +{ + gi.sound (self, CHAN_WEAPON, sound_weapon_cock, 1, ATTN_NORM, 0); +} + +void infantry_fire (edict_t *self) +{ + InfantryMachineGun (self); + + if (level.time >= self->monsterinfo.pausetime) + self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; + else + self->monsterinfo.aiflags |= AI_HOLD_FRAME; +} + +mframe_t infantry_frames_attack1 [] = +{ + ai_charge, 10, infantry_set_firetime, + ai_charge, 6, NULL, + ai_charge, 0, infantry_fire, + ai_charge, 0, NULL, + ai_charge, 1, NULL, + ai_charge, -7, NULL, + ai_charge, -6, NULL, + ai_charge, -1, NULL, + ai_charge, 0, infantry_cock_gun, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, -1, NULL, + ai_charge, -1, NULL +}; +mmove_t infantry_move_attack1 = {FRAME_attak101, FRAME_attak115, infantry_frames_attack1, infantry_run}; + + +void infantry_swing (edict_t *self) +{ + gi.sound (self, CHAN_WEAPON, sound_punch_swing, 1, ATTN_NORM, 0); +} + +void infantry_smack (edict_t *self) +{ + vec3_t aim; + + VectorSet (aim, MELEE_DISTANCE, 0, 0); + if (fire_hit (self, aim, (5 + (rand() % 5)), 50)) + gi.sound (self, CHAN_WEAPON, sound_punch_hit, 1, ATTN_NORM, 0); +} + +mframe_t infantry_frames_attack2 [] = +{ + ai_charge, 3, NULL, + ai_charge, 6, NULL, + ai_charge, 0, infantry_swing, + ai_charge, 8, NULL, + ai_charge, 5, NULL, + ai_charge, 8, infantry_smack, + ai_charge, 6, NULL, + ai_charge, 3, NULL, +}; +mmove_t infantry_move_attack2 = {FRAME_attak201, FRAME_attak208, infantry_frames_attack2, infantry_run}; + +void infantry_attack(edict_t *self) +{ + if (range (self, self->enemy) == RANGE_MELEE) + self->monsterinfo.currentmove = &infantry_move_attack2; + else + self->monsterinfo.currentmove = &infantry_move_attack1; +} + + +/*QUAKED monster_infantry (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight +*/ +void SP_monster_infantry (edict_t *self) +{ + if (deathmatch->value) + { + G_FreeEdict (self); + return; + } + + sound_pain1 = gi.soundindex ("infantry/infpain1.wav"); + sound_pain2 = gi.soundindex ("infantry/infpain2.wav"); + sound_die1 = gi.soundindex ("infantry/infdeth1.wav"); + sound_die2 = gi.soundindex ("infantry/infdeth2.wav"); + + sound_gunshot = gi.soundindex ("infantry/infatck1.wav"); + sound_weapon_cock = gi.soundindex ("infantry/infatck3.wav"); + sound_punch_swing = gi.soundindex ("infantry/infatck2.wav"); + sound_punch_hit = gi.soundindex ("infantry/melee2.wav"); + + sound_sight = gi.soundindex ("infantry/infsght1.wav"); + sound_search = gi.soundindex ("infantry/infsrch1.wav"); + sound_idle = gi.soundindex ("infantry/infidle1.wav"); + + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex("models/monsters/infantry/tris.md2"); + VectorSet (self->mins, -16, -16, -24); + VectorSet (self->maxs, 16, 16, 32); + + self->health = 100; + self->gib_health = -40; + self->mass = 200; + + self->pain = infantry_pain; + self->die = infantry_die; + + self->monsterinfo.stand = infantry_stand; + self->monsterinfo.walk = infantry_walk; + self->monsterinfo.run = infantry_run; + self->monsterinfo.dodge = infantry_dodge; + self->monsterinfo.attack = infantry_attack; + self->monsterinfo.melee = NULL; + self->monsterinfo.sight = infantry_sight; + self->monsterinfo.idle = infantry_fidget; + + gi.linkentity (self); + + self->monsterinfo.currentmove = &infantry_move_stand; + self->monsterinfo.scale = MODEL_SCALE; + + walkmonster_start (self); +} diff --git a/original/xatrix/m_infantry.h b/original/xatrix/m_infantry.h new file mode 100644 index 0000000..c7dee89 --- /dev/null +++ b/original/xatrix/m_infantry.h @@ -0,0 +1,215 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// G:\quake2\baseq2\models/monsters/infantry + +// This file generated by ModelGen - Do NOT Modify + +#define FRAME_gun02 0 +#define FRAME_stand01 1 +#define FRAME_stand02 2 +#define FRAME_stand03 3 +#define FRAME_stand04 4 +#define FRAME_stand05 5 +#define FRAME_stand06 6 +#define FRAME_stand07 7 +#define FRAME_stand08 8 +#define FRAME_stand09 9 +#define FRAME_stand10 10 +#define FRAME_stand11 11 +#define FRAME_stand12 12 +#define FRAME_stand13 13 +#define FRAME_stand14 14 +#define FRAME_stand15 15 +#define FRAME_stand16 16 +#define FRAME_stand17 17 +#define FRAME_stand18 18 +#define FRAME_stand19 19 +#define FRAME_stand20 20 +#define FRAME_stand21 21 +#define FRAME_stand22 22 +#define FRAME_stand23 23 +#define FRAME_stand24 24 +#define FRAME_stand25 25 +#define FRAME_stand26 26 +#define FRAME_stand27 27 +#define FRAME_stand28 28 +#define FRAME_stand29 29 +#define FRAME_stand30 30 +#define FRAME_stand31 31 +#define FRAME_stand32 32 +#define FRAME_stand33 33 +#define FRAME_stand34 34 +#define FRAME_stand35 35 +#define FRAME_stand36 36 +#define FRAME_stand37 37 +#define FRAME_stand38 38 +#define FRAME_stand39 39 +#define FRAME_stand40 40 +#define FRAME_stand41 41 +#define FRAME_stand42 42 +#define FRAME_stand43 43 +#define FRAME_stand44 44 +#define FRAME_stand45 45 +#define FRAME_stand46 46 +#define FRAME_stand47 47 +#define FRAME_stand48 48 +#define FRAME_stand49 49 +#define FRAME_stand50 50 +#define FRAME_stand51 51 +#define FRAME_stand52 52 +#define FRAME_stand53 53 +#define FRAME_stand54 54 +#define FRAME_stand55 55 +#define FRAME_stand56 56 +#define FRAME_stand57 57 +#define FRAME_stand58 58 +#define FRAME_stand59 59 +#define FRAME_stand60 60 +#define FRAME_stand61 61 +#define FRAME_stand62 62 +#define FRAME_stand63 63 +#define FRAME_stand64 64 +#define FRAME_stand65 65 +#define FRAME_stand66 66 +#define FRAME_stand67 67 +#define FRAME_stand68 68 +#define FRAME_stand69 69 +#define FRAME_stand70 70 +#define FRAME_stand71 71 +#define FRAME_walk01 72 +#define FRAME_walk02 73 +#define FRAME_walk03 74 +#define FRAME_walk04 75 +#define FRAME_walk05 76 +#define FRAME_walk06 77 +#define FRAME_walk07 78 +#define FRAME_walk08 79 +#define FRAME_walk09 80 +#define FRAME_walk10 81 +#define FRAME_walk11 82 +#define FRAME_walk12 83 +#define FRAME_walk13 84 +#define FRAME_walk14 85 +#define FRAME_walk15 86 +#define FRAME_walk16 87 +#define FRAME_walk17 88 +#define FRAME_walk18 89 +#define FRAME_walk19 90 +#define FRAME_walk20 91 +#define FRAME_run01 92 +#define FRAME_run02 93 +#define FRAME_run03 94 +#define FRAME_run04 95 +#define FRAME_run05 96 +#define FRAME_run06 97 +#define FRAME_run07 98 +#define FRAME_run08 99 +#define FRAME_pain101 100 +#define FRAME_pain102 101 +#define FRAME_pain103 102 +#define FRAME_pain104 103 +#define FRAME_pain105 104 +#define FRAME_pain106 105 +#define FRAME_pain107 106 +#define FRAME_pain108 107 +#define FRAME_pain109 108 +#define FRAME_pain110 109 +#define FRAME_pain201 110 +#define FRAME_pain202 111 +#define FRAME_pain203 112 +#define FRAME_pain204 113 +#define FRAME_pain205 114 +#define FRAME_pain206 115 +#define FRAME_pain207 116 +#define FRAME_pain208 117 +#define FRAME_pain209 118 +#define FRAME_pain210 119 +#define FRAME_duck01 120 +#define FRAME_duck02 121 +#define FRAME_duck03 122 +#define FRAME_duck04 123 +#define FRAME_duck05 124 +#define FRAME_death101 125 +#define FRAME_death102 126 +#define FRAME_death103 127 +#define FRAME_death104 128 +#define FRAME_death105 129 +#define FRAME_death106 130 +#define FRAME_death107 131 +#define FRAME_death108 132 +#define FRAME_death109 133 +#define FRAME_death110 134 +#define FRAME_death111 135 +#define FRAME_death112 136 +#define FRAME_death113 137 +#define FRAME_death114 138 +#define FRAME_death115 139 +#define FRAME_death116 140 +#define FRAME_death117 141 +#define FRAME_death118 142 +#define FRAME_death119 143 +#define FRAME_death120 144 +#define FRAME_death201 145 +#define FRAME_death202 146 +#define FRAME_death203 147 +#define FRAME_death204 148 +#define FRAME_death205 149 +#define FRAME_death206 150 +#define FRAME_death207 151 +#define FRAME_death208 152 +#define FRAME_death209 153 +#define FRAME_death210 154 +#define FRAME_death211 155 +#define FRAME_death212 156 +#define FRAME_death213 157 +#define FRAME_death214 158 +#define FRAME_death215 159 +#define FRAME_death216 160 +#define FRAME_death217 161 +#define FRAME_death218 162 +#define FRAME_death219 163 +#define FRAME_death220 164 +#define FRAME_death221 165 +#define FRAME_death222 166 +#define FRAME_death223 167 +#define FRAME_death224 168 +#define FRAME_death225 169 +#define FRAME_death301 170 +#define FRAME_death302 171 +#define FRAME_death303 172 +#define FRAME_death304 173 +#define FRAME_death305 174 +#define FRAME_death306 175 +#define FRAME_death307 176 +#define FRAME_death308 177 +#define FRAME_death309 178 +#define FRAME_block01 179 +#define FRAME_block02 180 +#define FRAME_block03 181 +#define FRAME_block04 182 +#define FRAME_block05 183 +#define FRAME_attak101 184 +#define FRAME_attak102 185 +#define FRAME_attak103 186 +#define FRAME_attak104 187 +#define FRAME_attak105 188 +#define FRAME_attak106 189 +#define FRAME_attak107 190 +#define FRAME_attak108 191 +#define FRAME_attak109 192 +#define FRAME_attak110 193 +#define FRAME_attak111 194 +#define FRAME_attak112 195 +#define FRAME_attak113 196 +#define FRAME_attak114 197 +#define FRAME_attak115 198 +#define FRAME_attak201 199 +#define FRAME_attak202 200 +#define FRAME_attak203 201 +#define FRAME_attak204 202 +#define FRAME_attak205 203 +#define FRAME_attak206 204 +#define FRAME_attak207 205 +#define FRAME_attak208 206 + +#define MODEL_SCALE 1.000000 diff --git a/original/xatrix/m_insane.c b/original/xatrix/m_insane.c new file mode 100644 index 0000000..c570bad --- /dev/null +++ b/original/xatrix/m_insane.c @@ -0,0 +1,676 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +/* +============================================================================== + +insane + +============================================================================== +*/ + +#include "g_local.h" +#include "m_insane.h" + + +static int sound_fist; +static int sound_shake; +static int sound_moan; +static int sound_scream[8]; + +void insane_fist (edict_t *self) +{ + gi.sound (self, CHAN_VOICE, sound_fist, 1, ATTN_IDLE, 0); +} + +void insane_shake (edict_t *self) +{ + gi.sound (self, CHAN_VOICE, sound_shake, 1, ATTN_IDLE, 0); +} + +void insane_moan (edict_t *self) +{ + gi.sound (self, CHAN_VOICE, sound_moan, 1, ATTN_IDLE, 0); +} + +void insane_scream (edict_t *self) +{ + gi.sound (self, CHAN_VOICE, sound_scream[rand()%8], 1, ATTN_IDLE, 0); +} + + +void insane_stand (edict_t *self); +void insane_dead (edict_t *self); +void insane_cross (edict_t *self); +void insane_walk (edict_t *self); +void insane_run (edict_t *self); +void insane_checkdown (edict_t *self); +void insane_checkup (edict_t *self); +void insane_onground (edict_t *self); + + +mframe_t insane_frames_stand_normal [] = +{ + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, insane_checkdown +}; +mmove_t insane_move_stand_normal = {FRAME_stand60, FRAME_stand65, insane_frames_stand_normal, insane_stand}; + +mframe_t insane_frames_stand_insane [] = +{ + ai_stand, 0, insane_shake, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, insane_checkdown +}; +mmove_t insane_move_stand_insane = {FRAME_stand65, FRAME_stand94, insane_frames_stand_insane, insane_stand}; + +mframe_t insane_frames_uptodown [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, insane_moan, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 2.7, NULL, + ai_move, 4.1, NULL, + ai_move, 6, NULL, + ai_move, 7.6, NULL, + ai_move, 3.6, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, insane_fist, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, insane_fist, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t insane_move_uptodown = {FRAME_stand1, FRAME_stand40, insane_frames_uptodown, insane_onground}; + + +mframe_t insane_frames_downtoup [] = +{ + ai_move, -0.7, NULL, // 41 + ai_move, -1.2, NULL, // 42 + ai_move, -1.5, NULL, // 43 + ai_move, -4.5, NULL, // 44 + ai_move, -3.5, NULL, // 45 + ai_move, -0.2, NULL, // 46 + ai_move, 0, NULL, // 47 + ai_move, -1.3, NULL, // 48 + ai_move, -3, NULL, // 49 + ai_move, -2, NULL, // 50 + ai_move, 0, NULL, // 51 + ai_move, 0, NULL, // 52 + ai_move, 0, NULL, // 53 + ai_move, -3.3, NULL, // 54 + ai_move, -1.6, NULL, // 55 + ai_move, -0.3, NULL, // 56 + ai_move, 0, NULL, // 57 + ai_move, 0, NULL, // 58 + ai_move, 0, NULL // 59 +}; +mmove_t insane_move_downtoup = {FRAME_stand41, FRAME_stand59, insane_frames_downtoup, insane_stand}; + +mframe_t insane_frames_jumpdown [] = +{ + ai_move, 0.2, NULL, + ai_move, 11.5, NULL, + ai_move, 5.1, NULL, + ai_move, 7.1, NULL, + ai_move, 0, NULL +}; +mmove_t insane_move_jumpdown = {FRAME_stand96, FRAME_stand100, insane_frames_jumpdown, insane_onground}; + + +mframe_t insane_frames_down [] = +{ + ai_move, 0, NULL, // 100 + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, // 110 + ai_move, -1.7, NULL, + ai_move, -1.6, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, insane_fist, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, // 120 + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, // 130 + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, insane_moan, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, // 140 + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, // 150 + ai_move, 0.5, NULL, + ai_move, 0, NULL, + ai_move, -0.2, insane_scream, + ai_move, 0, NULL, + ai_move, 0.2, NULL, + ai_move, 0.4, NULL, + ai_move, 0.6, NULL, + ai_move, 0.8, NULL, + ai_move, 0.7, NULL, + ai_move, 0, insane_checkup // 160 +}; +mmove_t insane_move_down = {FRAME_stand100, FRAME_stand160, insane_frames_down, insane_onground}; + +mframe_t insane_frames_walk_normal [] = +{ + ai_walk, 0, insane_scream, + ai_walk, 2.5, NULL, + ai_walk, 3.5, NULL, + ai_walk, 1.7, NULL, + ai_walk, 2.3, NULL, + ai_walk, 2.4, NULL, + ai_walk, 2.2, NULL, + ai_walk, 4.2, NULL, + ai_walk, 5.6, NULL, + ai_walk, 3.3, NULL, + ai_walk, 2.4, NULL, + ai_walk, 0.9, NULL, + ai_walk, 0, NULL +}; +mmove_t insane_move_walk_normal = {FRAME_walk27, FRAME_walk39, insane_frames_walk_normal, insane_walk}; +mmove_t insane_move_run_normal = {FRAME_walk27, FRAME_walk39, insane_frames_walk_normal, insane_run}; + +mframe_t insane_frames_walk_insane [] = +{ + ai_walk, 0, insane_scream, // walk 1 + ai_walk, 3.4, NULL, // walk 2 + ai_walk, 3.6, NULL, // 3 + ai_walk, 2.9, NULL, // 4 + ai_walk, 2.2, NULL, // 5 + ai_walk, 2.6, NULL, // 6 + ai_walk, 0, NULL, // 7 + ai_walk, 0.7, NULL, // 8 + ai_walk, 4.8, NULL, // 9 + ai_walk, 5.3, NULL, // 10 + ai_walk, 1.1, NULL, // 11 + ai_walk, 2, NULL, // 12 + ai_walk, 0.5, NULL, // 13 + ai_walk, 0, NULL, // 14 + ai_walk, 0, NULL, // 15 + ai_walk, 4.9, NULL, // 16 + ai_walk, 6.7, NULL, // 17 + ai_walk, 3.8, NULL, // 18 + ai_walk, 2, NULL, // 19 + ai_walk, 0.2, NULL, // 20 + ai_walk, 0, NULL, // 21 + ai_walk, 3.4, NULL, // 22 + ai_walk, 6.4, NULL, // 23 + ai_walk, 5, NULL, // 24 + ai_walk, 1.8, NULL, // 25 + ai_walk, 0, NULL // 26 +}; +mmove_t insane_move_walk_insane = {FRAME_walk1, FRAME_walk26, insane_frames_walk_insane, insane_walk}; +mmove_t insane_move_run_insane = {FRAME_walk1, FRAME_walk26, insane_frames_walk_insane, insane_run}; + +mframe_t insane_frames_stand_pain [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t insane_move_stand_pain = {FRAME_st_pain2, FRAME_st_pain12, insane_frames_stand_pain, insane_run}; + +mframe_t insane_frames_stand_death [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t insane_move_stand_death = {FRAME_st_death2, FRAME_st_death18, insane_frames_stand_death, insane_dead}; + +mframe_t insane_frames_crawl [] = +{ + ai_walk, 0, insane_scream, + ai_walk, 1.5, NULL, + ai_walk, 2.1, NULL, + ai_walk, 3.6, NULL, + ai_walk, 2, NULL, + ai_walk, 0.9, NULL, + ai_walk, 3, NULL, + ai_walk, 3.4, NULL, + ai_walk, 2.4, NULL +}; +mmove_t insane_move_crawl = {FRAME_crawl1, FRAME_crawl9, insane_frames_crawl, NULL}; +mmove_t insane_move_runcrawl = {FRAME_crawl1, FRAME_crawl9, insane_frames_crawl, NULL}; + +mframe_t insane_frames_crawl_pain [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t insane_move_crawl_pain = {FRAME_cr_pain2, FRAME_cr_pain10, insane_frames_crawl_pain, insane_run}; + +mframe_t insane_frames_crawl_death [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t insane_move_crawl_death = {FRAME_cr_death10, FRAME_cr_death16, insane_frames_crawl_death, insane_dead}; + +mframe_t insane_frames_cross [] = +{ + ai_move, 0, insane_moan, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t insane_move_cross = {FRAME_cross1, FRAME_cross15, insane_frames_cross, insane_cross}; + +mframe_t insane_frames_struggle_cross [] = +{ + ai_move, 0, insane_scream, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t insane_move_struggle_cross = {FRAME_cross16, FRAME_cross30, insane_frames_struggle_cross, insane_cross}; + +void insane_cross (edict_t *self) +{ + if (random() < 0.8) + self->monsterinfo.currentmove = &insane_move_cross; + else + self->monsterinfo.currentmove = &insane_move_struggle_cross; +} + +void insane_walk (edict_t *self) +{ + if ( self->spawnflags & 16 ) // Hold Ground? + if (self->s.frame == FRAME_cr_pain10) + { + self->monsterinfo.currentmove = &insane_move_down; + return; + } + if (self->spawnflags & 4) + self->monsterinfo.currentmove = &insane_move_crawl; + else + if (random() <= 0.5) + self->monsterinfo.currentmove = &insane_move_walk_normal; + else + self->monsterinfo.currentmove = &insane_move_walk_insane; +} + +void insane_run (edict_t *self) +{ + if ( self->spawnflags & 16 ) // Hold Ground? + if (self->s.frame == FRAME_cr_pain10) + { + self->monsterinfo.currentmove = &insane_move_down; + return; + } + if (self->spawnflags & 4) // Crawling? + self->monsterinfo.currentmove = &insane_move_runcrawl; + else + if (random() <= 0.5) // Else, mix it up + self->monsterinfo.currentmove = &insane_move_run_normal; + else + self->monsterinfo.currentmove = &insane_move_run_insane; +} + + +void insane_pain (edict_t *self, edict_t *other, float kick, int damage) +{ + int l,r; + +// if (self->health < (self->max_health / 2)) +// self->s.skinnum = 1; + + if (level.time < self->pain_debounce_time) + return; + + self->pain_debounce_time = level.time + 3; + + r = 1 + (rand()&1); + if (self->health < 25) + l = 25; + else if (self->health < 50) + l = 50; + else if (self->health < 75) + l = 75; + else + l = 100; + gi.sound (self, CHAN_VOICE, gi.soundindex (va("player/male/pain%i_%i.wav", l, r)), 1, ATTN_IDLE, 0); + + if (skill->value == 3) + return; // no pain anims in nightmare + + // Don't go into pain frames if crucified. + if (self->spawnflags & 8) + { + self->monsterinfo.currentmove = &insane_move_struggle_cross; + return; + } + + if ( ((self->s.frame >= FRAME_crawl1) && (self->s.frame <= FRAME_crawl9)) || ((self->s.frame >= FRAME_stand99) && (self->s.frame <= FRAME_stand160)) ) + { + self->monsterinfo.currentmove = &insane_move_crawl_pain; + } + else + self->monsterinfo.currentmove = &insane_move_stand_pain; + +} + +void insane_onground (edict_t *self) +{ + self->monsterinfo.currentmove = &insane_move_down; +} + +void insane_checkdown (edict_t *self) +{ +// if ( (self->s.frame == FRAME_stand94) || (self->s.frame == FRAME_stand65) ) + if (self->spawnflags & 32) // Always stand + return; + if (random() < 0.3) + if (random() < 0.5) + self->monsterinfo.currentmove = &insane_move_uptodown; + else + self->monsterinfo.currentmove = &insane_move_jumpdown; +} + +void insane_checkup (edict_t *self) +{ + // If Hold_Ground and Crawl are set + if ( (self->spawnflags & 4) && (self->spawnflags & 16) ) + return; + if (random() < 0.5) + self->monsterinfo.currentmove = &insane_move_downtoup; + +} + +void insane_stand (edict_t *self) +{ + if (self->spawnflags & 8) // If crucified + { + self->monsterinfo.currentmove = &insane_move_cross; + self->monsterinfo.aiflags |= AI_STAND_GROUND; + } + // If Hold_Ground and Crawl are set + else if ( (self->spawnflags & 4) && (self->spawnflags & 16) ) + self->monsterinfo.currentmove = &insane_move_down; + else + if (random() < 0.5) + self->monsterinfo.currentmove = &insane_move_stand_normal; + else + self->monsterinfo.currentmove = &insane_move_stand_insane; +} + +void insane_dead (edict_t *self) +{ + if (self->spawnflags & 8) + { + self->flags |= FL_FLY; + } + else + { + VectorSet (self->mins, -16, -16, -24); + VectorSet (self->maxs, 16, 16, -8); + self->movetype = MOVETYPE_TOSS; + } + self->svflags |= SVF_DEADMONSTER; + self->nextthink = 0; + gi.linkentity (self); +} + + +void insane_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + int n; + + if (self->health <= self->gib_health) + { + gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_IDLE, 0); + for (n= 0; n < 2; n++) + ThrowGib (self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC); + for (n= 0; n < 4; n++) + ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); + ThrowHead (self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC); + self->deadflag = DEAD_DEAD; + return; + } + + if (self->deadflag == DEAD_DEAD) + return; + + gi.sound (self, CHAN_VOICE, gi.soundindex(va("player/male/death%i.wav", (rand()%4)+1)), 1, ATTN_IDLE, 0); + + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_YES; + + if (self->spawnflags & 8) + { + insane_dead (self); + } + else + { + if ( ((self->s.frame >= FRAME_crawl1) && (self->s.frame <= FRAME_crawl9)) || ((self->s.frame >= FRAME_stand99) && (self->s.frame <= FRAME_stand160)) ) + self->monsterinfo.currentmove = &insane_move_crawl_death; + else + self->monsterinfo.currentmove = &insane_move_stand_death; + } +} + + +/*QUAKED misc_insane (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn CRAWL CRUCIFIED STAND_GROUND ALWAYS_STAND +*/ +void SP_misc_insane (edict_t *self) +{ +// static int skin = 0; //@@ + + if (deathmatch->value) + { + G_FreeEdict (self); + return; + } + + sound_fist = gi.soundindex ("insane/insane11.wav"); + sound_shake = gi.soundindex ("insane/insane5.wav"); + sound_moan = gi.soundindex ("insane/insane7.wav"); + sound_scream[0] = gi.soundindex ("insane/insane1.wav"); + sound_scream[1] = gi.soundindex ("insane/insane2.wav"); + sound_scream[2] = gi.soundindex ("insane/insane3.wav"); + sound_scream[3] = gi.soundindex ("insane/insane4.wav"); + sound_scream[4] = gi.soundindex ("insane/insane6.wav"); + sound_scream[5] = gi.soundindex ("insane/insane8.wav"); + sound_scream[6] = gi.soundindex ("insane/insane9.wav"); + sound_scream[7] = gi.soundindex ("insane/insane10.wav"); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex("models/monsters/insane/tris.md2"); + + VectorSet (self->mins, -16, -16, -24); + VectorSet (self->maxs, 16, 16, 32); + + self->health = 100; + self->gib_health = -50; + self->mass = 300; + + self->pain = insane_pain; + self->die = insane_die; + + self->monsterinfo.stand = insane_stand; + self->monsterinfo.walk = insane_walk; + self->monsterinfo.run = insane_run; + self->monsterinfo.dodge = NULL; + self->monsterinfo.attack = NULL; + self->monsterinfo.melee = NULL; + self->monsterinfo.sight = NULL; + self->monsterinfo.aiflags |= AI_GOOD_GUY; + +//@@ +// self->s.skinnum = skin; +// skin++; +// if (skin > 12) +// skin = 0; + + gi.linkentity (self); + + if (self->spawnflags & 16) // Stand Ground + self->monsterinfo.aiflags |= AI_STAND_GROUND; + + self->monsterinfo.currentmove = &insane_move_stand_normal; + + self->monsterinfo.scale = MODEL_SCALE; + + if (self->spawnflags & 8) // Crucified ? + { + VectorSet (self->mins, -16, 0, 0); + VectorSet (self->maxs, 16, 8, 32); + self->flags |= FL_NO_KNOCKBACK; + flymonster_start (self); + } + else + { + walkmonster_start (self); + self->s.skinnum = rand()%3; + } +} diff --git a/original/xatrix/m_insane.h b/original/xatrix/m_insane.h new file mode 100644 index 0000000..a9ce838 --- /dev/null +++ b/original/xatrix/m_insane.h @@ -0,0 +1,290 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// G:\quake2\baseq2\models/monsters/insane + +// This file generated by ModelGen - Do NOT Modify + +#define FRAME_stand1 0 +#define FRAME_stand2 1 +#define FRAME_stand3 2 +#define FRAME_stand4 3 +#define FRAME_stand5 4 +#define FRAME_stand6 5 +#define FRAME_stand7 6 +#define FRAME_stand8 7 +#define FRAME_stand9 8 +#define FRAME_stand10 9 +#define FRAME_stand11 10 +#define FRAME_stand12 11 +#define FRAME_stand13 12 +#define FRAME_stand14 13 +#define FRAME_stand15 14 +#define FRAME_stand16 15 +#define FRAME_stand17 16 +#define FRAME_stand18 17 +#define FRAME_stand19 18 +#define FRAME_stand20 19 +#define FRAME_stand21 20 +#define FRAME_stand22 21 +#define FRAME_stand23 22 +#define FRAME_stand24 23 +#define FRAME_stand25 24 +#define FRAME_stand26 25 +#define FRAME_stand27 26 +#define FRAME_stand28 27 +#define FRAME_stand29 28 +#define FRAME_stand30 29 +#define FRAME_stand31 30 +#define FRAME_stand32 31 +#define FRAME_stand33 32 +#define FRAME_stand34 33 +#define FRAME_stand35 34 +#define FRAME_stand36 35 +#define FRAME_stand37 36 +#define FRAME_stand38 37 +#define FRAME_stand39 38 +#define FRAME_stand40 39 +#define FRAME_stand41 40 +#define FRAME_stand42 41 +#define FRAME_stand43 42 +#define FRAME_stand44 43 +#define FRAME_stand45 44 +#define FRAME_stand46 45 +#define FRAME_stand47 46 +#define FRAME_stand48 47 +#define FRAME_stand49 48 +#define FRAME_stand50 49 +#define FRAME_stand51 50 +#define FRAME_stand52 51 +#define FRAME_stand53 52 +#define FRAME_stand54 53 +#define FRAME_stand55 54 +#define FRAME_stand56 55 +#define FRAME_stand57 56 +#define FRAME_stand58 57 +#define FRAME_stand59 58 +#define FRAME_stand60 59 +#define FRAME_stand61 60 +#define FRAME_stand62 61 +#define FRAME_stand63 62 +#define FRAME_stand64 63 +#define FRAME_stand65 64 +#define FRAME_stand66 65 +#define FRAME_stand67 66 +#define FRAME_stand68 67 +#define FRAME_stand69 68 +#define FRAME_stand70 69 +#define FRAME_stand71 70 +#define FRAME_stand72 71 +#define FRAME_stand73 72 +#define FRAME_stand74 73 +#define FRAME_stand75 74 +#define FRAME_stand76 75 +#define FRAME_stand77 76 +#define FRAME_stand78 77 +#define FRAME_stand79 78 +#define FRAME_stand80 79 +#define FRAME_stand81 80 +#define FRAME_stand82 81 +#define FRAME_stand83 82 +#define FRAME_stand84 83 +#define FRAME_stand85 84 +#define FRAME_stand86 85 +#define FRAME_stand87 86 +#define FRAME_stand88 87 +#define FRAME_stand89 88 +#define FRAME_stand90 89 +#define FRAME_stand91 90 +#define FRAME_stand92 91 +#define FRAME_stand93 92 +#define FRAME_stand94 93 +#define FRAME_stand95 94 +#define FRAME_stand96 95 +#define FRAME_stand97 96 +#define FRAME_stand98 97 +#define FRAME_stand99 98 +#define FRAME_stand100 99 +#define FRAME_stand101 100 +#define FRAME_stand102 101 +#define FRAME_stand103 102 +#define FRAME_stand104 103 +#define FRAME_stand105 104 +#define FRAME_stand106 105 +#define FRAME_stand107 106 +#define FRAME_stand108 107 +#define FRAME_stand109 108 +#define FRAME_stand110 109 +#define FRAME_stand111 110 +#define FRAME_stand112 111 +#define FRAME_stand113 112 +#define FRAME_stand114 113 +#define FRAME_stand115 114 +#define FRAME_stand116 115 +#define FRAME_stand117 116 +#define FRAME_stand118 117 +#define FRAME_stand119 118 +#define FRAME_stand120 119 +#define FRAME_stand121 120 +#define FRAME_stand122 121 +#define FRAME_stand123 122 +#define FRAME_stand124 123 +#define FRAME_stand125 124 +#define FRAME_stand126 125 +#define FRAME_stand127 126 +#define FRAME_stand128 127 +#define FRAME_stand129 128 +#define FRAME_stand130 129 +#define FRAME_stand131 130 +#define FRAME_stand132 131 +#define FRAME_stand133 132 +#define FRAME_stand134 133 +#define FRAME_stand135 134 +#define FRAME_stand136 135 +#define FRAME_stand137 136 +#define FRAME_stand138 137 +#define FRAME_stand139 138 +#define FRAME_stand140 139 +#define FRAME_stand141 140 +#define FRAME_stand142 141 +#define FRAME_stand143 142 +#define FRAME_stand144 143 +#define FRAME_stand145 144 +#define FRAME_stand146 145 +#define FRAME_stand147 146 +#define FRAME_stand148 147 +#define FRAME_stand149 148 +#define FRAME_stand150 149 +#define FRAME_stand151 150 +#define FRAME_stand152 151 +#define FRAME_stand153 152 +#define FRAME_stand154 153 +#define FRAME_stand155 154 +#define FRAME_stand156 155 +#define FRAME_stand157 156 +#define FRAME_stand158 157 +#define FRAME_stand159 158 +#define FRAME_stand160 159 +#define FRAME_walk27 160 +#define FRAME_walk28 161 +#define FRAME_walk29 162 +#define FRAME_walk30 163 +#define FRAME_walk31 164 +#define FRAME_walk32 165 +#define FRAME_walk33 166 +#define FRAME_walk34 167 +#define FRAME_walk35 168 +#define FRAME_walk36 169 +#define FRAME_walk37 170 +#define FRAME_walk38 171 +#define FRAME_walk39 172 +#define FRAME_walk1 173 +#define FRAME_walk2 174 +#define FRAME_walk3 175 +#define FRAME_walk4 176 +#define FRAME_walk5 177 +#define FRAME_walk6 178 +#define FRAME_walk7 179 +#define FRAME_walk8 180 +#define FRAME_walk9 181 +#define FRAME_walk10 182 +#define FRAME_walk11 183 +#define FRAME_walk12 184 +#define FRAME_walk13 185 +#define FRAME_walk14 186 +#define FRAME_walk15 187 +#define FRAME_walk16 188 +#define FRAME_walk17 189 +#define FRAME_walk18 190 +#define FRAME_walk19 191 +#define FRAME_walk20 192 +#define FRAME_walk21 193 +#define FRAME_walk22 194 +#define FRAME_walk23 195 +#define FRAME_walk24 196 +#define FRAME_walk25 197 +#define FRAME_walk26 198 +#define FRAME_st_pain2 199 +#define FRAME_st_pain3 200 +#define FRAME_st_pain4 201 +#define FRAME_st_pain5 202 +#define FRAME_st_pain6 203 +#define FRAME_st_pain7 204 +#define FRAME_st_pain8 205 +#define FRAME_st_pain9 206 +#define FRAME_st_pain10 207 +#define FRAME_st_pain11 208 +#define FRAME_st_pain12 209 +#define FRAME_st_death2 210 +#define FRAME_st_death3 211 +#define FRAME_st_death4 212 +#define FRAME_st_death5 213 +#define FRAME_st_death6 214 +#define FRAME_st_death7 215 +#define FRAME_st_death8 216 +#define FRAME_st_death9 217 +#define FRAME_st_death10 218 +#define FRAME_st_death11 219 +#define FRAME_st_death12 220 +#define FRAME_st_death13 221 +#define FRAME_st_death14 222 +#define FRAME_st_death15 223 +#define FRAME_st_death16 224 +#define FRAME_st_death17 225 +#define FRAME_st_death18 226 +#define FRAME_crawl1 227 +#define FRAME_crawl2 228 +#define FRAME_crawl3 229 +#define FRAME_crawl4 230 +#define FRAME_crawl5 231 +#define FRAME_crawl6 232 +#define FRAME_crawl7 233 +#define FRAME_crawl8 234 +#define FRAME_crawl9 235 +#define FRAME_cr_pain2 236 +#define FRAME_cr_pain3 237 +#define FRAME_cr_pain4 238 +#define FRAME_cr_pain5 239 +#define FRAME_cr_pain6 240 +#define FRAME_cr_pain7 241 +#define FRAME_cr_pain8 242 +#define FRAME_cr_pain9 243 +#define FRAME_cr_pain10 244 +#define FRAME_cr_death10 245 +#define FRAME_cr_death11 246 +#define FRAME_cr_death12 247 +#define FRAME_cr_death13 248 +#define FRAME_cr_death14 249 +#define FRAME_cr_death15 250 +#define FRAME_cr_death16 251 +#define FRAME_cross1 252 +#define FRAME_cross2 253 +#define FRAME_cross3 254 +#define FRAME_cross4 255 +#define FRAME_cross5 256 +#define FRAME_cross6 257 +#define FRAME_cross7 258 +#define FRAME_cross8 259 +#define FRAME_cross9 260 +#define FRAME_cross10 261 +#define FRAME_cross11 262 +#define FRAME_cross12 263 +#define FRAME_cross13 264 +#define FRAME_cross14 265 +#define FRAME_cross15 266 +#define FRAME_cross16 267 +#define FRAME_cross17 268 +#define FRAME_cross18 269 +#define FRAME_cross19 270 +#define FRAME_cross20 271 +#define FRAME_cross21 272 +#define FRAME_cross22 273 +#define FRAME_cross23 274 +#define FRAME_cross24 275 +#define FRAME_cross25 276 +#define FRAME_cross26 277 +#define FRAME_cross27 278 +#define FRAME_cross28 279 +#define FRAME_cross29 280 +#define FRAME_cross30 281 + +#define MODEL_SCALE 1.000000 diff --git a/original/xatrix/m_medic.c b/original/xatrix/m_medic.c new file mode 100644 index 0000000..f3d981b --- /dev/null +++ b/original/xatrix/m_medic.c @@ -0,0 +1,752 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +/* +============================================================================== + +MEDIC + +============================================================================== +*/ + +#include "g_local.h" +#include "m_medic.h" + +qboolean visible (edict_t *self, edict_t *other); + + +static int sound_idle1; +static int sound_pain1; +static int sound_pain2; +static int sound_die; +static int sound_sight; +static int sound_search; +static int sound_hook_launch; +static int sound_hook_hit; +static int sound_hook_heal; +static int sound_hook_retract; + + +edict_t *medic_FindDeadMonster (edict_t *self) +{ + edict_t *ent = NULL; + edict_t *best = NULL; + + while ((ent = findradius(ent, self->s.origin, 1024)) != NULL) + { + if (ent == self) + continue; + if (!(ent->svflags & SVF_MONSTER)) + continue; + if (ent->monsterinfo.aiflags & AI_GOOD_GUY) + continue; + if (ent->owner) + continue; + if (ent->health > 0) + continue; + if (ent->nextthink) + continue; + if (!visible(self, ent)) + continue; + if (!best) + { + best = ent; + continue; + } + if (ent->max_health <= best->max_health) + continue; + best = ent; + } + + return best; +} + +void medic_idle (edict_t *self) +{ + edict_t *ent; + + gi.sound (self, CHAN_VOICE, sound_idle1, 1, ATTN_IDLE, 0); + + ent = medic_FindDeadMonster(self); + if (ent) + { + self->enemy = ent; + self->enemy->owner = self; + self->monsterinfo.aiflags |= AI_MEDIC; + FoundTarget (self); + } +} + +void medic_search (edict_t *self) +{ + edict_t *ent; + + gi.sound (self, CHAN_VOICE, sound_search, 1, ATTN_IDLE, 0); + + if (!self->oldenemy) + { + ent = medic_FindDeadMonster(self); + if (ent) + { + self->oldenemy = self->enemy; + self->enemy = ent; + self->enemy->owner = self; + self->monsterinfo.aiflags |= AI_MEDIC; + FoundTarget (self); + } + } +} + +void medic_sight (edict_t *self, edict_t *other) +{ + gi.sound (self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); +} + + +mframe_t medic_frames_stand [] = +{ + ai_stand, 0, medic_idle, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + +}; +mmove_t medic_move_stand = {FRAME_wait1, FRAME_wait90, medic_frames_stand, NULL}; + +void medic_stand (edict_t *self) +{ + self->monsterinfo.currentmove = &medic_move_stand; +} + + +mframe_t medic_frames_walk [] = +{ + ai_walk, 6.2, NULL, + ai_walk, 18.1, NULL, + ai_walk, 1, NULL, + ai_walk, 9, NULL, + ai_walk, 10, NULL, + ai_walk, 9, NULL, + ai_walk, 11, NULL, + ai_walk, 11.6, NULL, + ai_walk, 2, NULL, + ai_walk, 9.9, NULL, + ai_walk, 14, NULL, + ai_walk, 9.3, NULL +}; +mmove_t medic_move_walk = {FRAME_walk1, FRAME_walk12, medic_frames_walk, NULL}; + +void medic_walk (edict_t *self) +{ + self->monsterinfo.currentmove = &medic_move_walk; +} + + +mframe_t medic_frames_run [] = +{ + ai_run, 18, NULL, + ai_run, 22.5, NULL, + ai_run, 25.4, NULL, + ai_run, 23.4, NULL, + ai_run, 24, NULL, + ai_run, 35.6, NULL + +}; +mmove_t medic_move_run = {FRAME_run1, FRAME_run6, medic_frames_run, NULL}; + +void medic_run (edict_t *self) +{ + if (!(self->monsterinfo.aiflags & AI_MEDIC)) + { + edict_t *ent; + + ent = medic_FindDeadMonster(self); + if (ent) + { + self->oldenemy = self->enemy; + self->enemy = ent; + self->enemy->owner = self; + self->monsterinfo.aiflags |= AI_MEDIC; + FoundTarget (self); + return; + } + } + + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + self->monsterinfo.currentmove = &medic_move_stand; + else + self->monsterinfo.currentmove = &medic_move_run; +} + + +mframe_t medic_frames_pain1 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t medic_move_pain1 = {FRAME_paina1, FRAME_paina8, medic_frames_pain1, medic_run}; + +mframe_t medic_frames_pain2 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t medic_move_pain2 = {FRAME_painb1, FRAME_painb15, medic_frames_pain2, medic_run}; + +void medic_pain (edict_t *self, edict_t *other, float kick, int damage) +{ + if (self->health < (self->max_health / 2)) + self->s.skinnum = 1; + + if (level.time < self->pain_debounce_time) + return; + + self->pain_debounce_time = level.time + 3; + + if (skill->value == 3) + return; // no pain anims in nightmare + + if (random() < 0.5) + { + self->monsterinfo.currentmove = &medic_move_pain1; + gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); + } + else + { + self->monsterinfo.currentmove = &medic_move_pain2; + gi.sound (self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0); + } +} + +void medic_fire_blaster (edict_t *self) +{ + vec3_t start; + vec3_t forward, right; + vec3_t end; + vec3_t dir; + int effect; + + if ((self->s.frame == FRAME_attack9) || (self->s.frame == FRAME_attack12)) + effect = EF_BLASTER; + else if ((self->s.frame == FRAME_attack19) || (self->s.frame == FRAME_attack22) || (self->s.frame == FRAME_attack25) || (self->s.frame == FRAME_attack28)) + effect = EF_HYPERBLASTER; + else + effect = 0; + + AngleVectors (self->s.angles, forward, right, NULL); + G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_MEDIC_BLASTER_1], forward, right, start); + + VectorCopy (self->enemy->s.origin, end); + end[2] += self->enemy->viewheight; + VectorSubtract (end, start, dir); + + monster_fire_blaster (self, start, dir, 2, 1000, MZ2_MEDIC_BLASTER_1, effect); +} + + +void medic_dead (edict_t *self) +{ + VectorSet (self->mins, -16, -16, -24); + VectorSet (self->maxs, 16, 16, -8); + self->movetype = MOVETYPE_TOSS; + self->svflags |= SVF_DEADMONSTER; + self->nextthink = 0; + gi.linkentity (self); +} + +mframe_t medic_frames_death [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t medic_move_death = {FRAME_death1, FRAME_death30, medic_frames_death, medic_dead}; + +void medic_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + int n; + + // if we had a pending patient, free him up for another medic + if ((self->enemy) && (self->enemy->owner == self)) + self->enemy->owner = NULL; + +// check for gib + if (self->health <= self->gib_health) + { + gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0); + for (n= 0; n < 2; n++) + ThrowGib (self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC); + for (n= 0; n < 4; n++) + ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); + ThrowHead (self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC); + self->deadflag = DEAD_DEAD; + return; + } + + if (self->deadflag == DEAD_DEAD) + return; + +// regular death + gi.sound (self, CHAN_VOICE, sound_die, 1, ATTN_NORM, 0); + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_YES; + + self->monsterinfo.currentmove = &medic_move_death; +} + + +void medic_duck_down (edict_t *self) +{ + if (self->monsterinfo.aiflags & AI_DUCKED) + return; + self->monsterinfo.aiflags |= AI_DUCKED; + self->maxs[2] -= 32; + self->takedamage = DAMAGE_YES; + self->monsterinfo.pausetime = level.time + 1; + gi.linkentity (self); +} + +void medic_duck_hold (edict_t *self) +{ + if (level.time >= self->monsterinfo.pausetime) + self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; + else + self->monsterinfo.aiflags |= AI_HOLD_FRAME; +} + +void medic_duck_up (edict_t *self) +{ + self->monsterinfo.aiflags &= ~AI_DUCKED; + self->maxs[2] += 32; + self->takedamage = DAMAGE_AIM; + gi.linkentity (self); +} + +mframe_t medic_frames_duck [] = +{ + ai_move, -1, NULL, + ai_move, -1, NULL, + ai_move, -1, medic_duck_down, + ai_move, -1, medic_duck_hold, + ai_move, -1, NULL, + ai_move, -1, NULL, + ai_move, -1, medic_duck_up, + ai_move, -1, NULL, + ai_move, -1, NULL, + ai_move, -1, NULL, + ai_move, -1, NULL, + ai_move, -1, NULL, + ai_move, -1, NULL, + ai_move, -1, NULL, + ai_move, -1, NULL, + ai_move, -1, NULL +}; +mmove_t medic_move_duck = {FRAME_duck1, FRAME_duck16, medic_frames_duck, medic_run}; + +void medic_dodge (edict_t *self, edict_t *attacker, float eta) +{ + if (random() > 0.25) + return; + + if (!self->enemy) + self->enemy = attacker; + + self->monsterinfo.currentmove = &medic_move_duck; +} + +mframe_t medic_frames_attackHyperBlaster [] = +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, medic_fire_blaster, + ai_charge, 0, medic_fire_blaster, + ai_charge, 0, medic_fire_blaster, + ai_charge, 0, medic_fire_blaster, + ai_charge, 0, medic_fire_blaster, + ai_charge, 0, medic_fire_blaster, + ai_charge, 0, medic_fire_blaster, + ai_charge, 0, medic_fire_blaster, + ai_charge, 0, medic_fire_blaster, + ai_charge, 0, medic_fire_blaster, + ai_charge, 0, medic_fire_blaster, + ai_charge, 0, medic_fire_blaster +}; +mmove_t medic_move_attackHyperBlaster = {FRAME_attack15, FRAME_attack30, medic_frames_attackHyperBlaster, medic_run}; + + +void medic_continue (edict_t *self) +{ + if (visible (self, self->enemy) ) + if (random() <= 0.95) + self->monsterinfo.currentmove = &medic_move_attackHyperBlaster; +} + + +mframe_t medic_frames_attackBlaster [] = +{ + ai_charge, 0, NULL, + ai_charge, 5, NULL, + ai_charge, 5, NULL, + ai_charge, 3, NULL, + ai_charge, 2, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, medic_fire_blaster, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, medic_fire_blaster, + ai_charge, 0, NULL, + ai_charge, 0, medic_continue // Change to medic_continue... Else, go to frame 32 +}; +mmove_t medic_move_attackBlaster = {FRAME_attack1, FRAME_attack14, medic_frames_attackBlaster, medic_run}; + + +void medic_hook_launch (edict_t *self) +{ + gi.sound (self, CHAN_WEAPON, sound_hook_launch, 1, ATTN_NORM, 0); +} + +void ED_CallSpawn (edict_t *ent); + +static vec3_t medic_cable_offsets[] = +{ + 45.0, -9.2, 15.5, + 48.4, -9.7, 15.2, + 47.8, -9.8, 15.8, + 47.3, -9.3, 14.3, + 45.4, -10.1, 13.1, + 41.9, -12.7, 12.0, + 37.8, -15.8, 11.2, + 34.3, -18.4, 10.7, + 32.7, -19.7, 10.4, + 32.7, -19.7, 10.4 +}; + +void medic_cable_attack (edict_t *self) +{ + vec3_t offset, start, end, f, r; + trace_t tr; + vec3_t dir, angles; + float distance; + + if (!self->enemy->inuse) + return; + + AngleVectors (self->s.angles, f, r, NULL); + VectorCopy (medic_cable_offsets[self->s.frame - FRAME_attack42], offset); + G_ProjectSource (self->s.origin, offset, f, r, start); + + // check for max distance + VectorSubtract (start, self->enemy->s.origin, dir); + distance = VectorLength(dir); + if (distance > 256) + return; + + // check for min/max pitch + vectoangles (dir, angles); + if (angles[0] < -180) + angles[0] += 360; + if (fabs(angles[0]) > 45) + return; + + tr = gi.trace (start, NULL, NULL, self->enemy->s.origin, self, MASK_SHOT); + if (tr.fraction != 1.0 && tr.ent != self->enemy) + return; + + if (self->s.frame == FRAME_attack43) + { + gi.sound (self->enemy, CHAN_AUTO, sound_hook_hit, 1, ATTN_NORM, 0); + self->enemy->monsterinfo.aiflags |= AI_RESURRECTING; + } + else if (self->s.frame == FRAME_attack50) + { + self->enemy->spawnflags = 0; + self->enemy->monsterinfo.aiflags = 0; + self->enemy->target = NULL; + self->enemy->targetname = NULL; + self->enemy->combattarget = NULL; + self->enemy->deathtarget = NULL; + self->enemy->owner = self; + ED_CallSpawn (self->enemy); + self->enemy->owner = NULL; + if (self->enemy->think) + { + self->enemy->nextthink = level.time; + self->enemy->think (self->enemy); + } + self->enemy->monsterinfo.aiflags |= AI_RESURRECTING; + if (self->oldenemy && self->oldenemy->client) + { + self->enemy->enemy = self->oldenemy; + FoundTarget (self->enemy); + } + } + else + { + if (self->s.frame == FRAME_attack44) + gi.sound (self, CHAN_WEAPON, sound_hook_heal, 1, ATTN_NORM, 0); + } + + // adjust start for beam origin being in middle of a segment + VectorMA (start, 8, f, start); + + // adjust end z for end spot since the monster is currently dead + VectorCopy (self->enemy->s.origin, end); + end[2] = self->enemy->absmin[2] + self->enemy->size[2] / 2; + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_MEDIC_CABLE_ATTACK); + gi.WriteShort (self - g_edicts); + gi.WritePosition (start); + gi.WritePosition (end); + gi.multicast (self->s.origin, MULTICAST_PVS); +} + +void medic_hook_retract (edict_t *self) +{ + gi.sound (self, CHAN_WEAPON, sound_hook_retract, 1, ATTN_NORM, 0); + self->enemy->monsterinfo.aiflags &= ~AI_RESURRECTING; +} + +mframe_t medic_frames_attackCable [] = +{ + ai_move, 2, NULL, + ai_move, 3, NULL, + ai_move, 5, NULL, + ai_move, 4.4, NULL, + ai_charge, 4.7, NULL, + ai_charge, 5, NULL, + ai_charge, 6, NULL, + ai_charge, 4, NULL, + ai_charge, 0, NULL, + ai_move, 0, medic_hook_launch, + ai_move, 0, medic_cable_attack, + ai_move, 0, medic_cable_attack, + ai_move, 0, medic_cable_attack, + ai_move, 0, medic_cable_attack, + ai_move, 0, medic_cable_attack, + ai_move, 0, medic_cable_attack, + ai_move, 0, medic_cable_attack, + ai_move, 0, medic_cable_attack, + ai_move, 0, medic_cable_attack, + ai_move, -15, medic_hook_retract, + ai_move, -1.5, NULL, + ai_move, -1.2, NULL, + ai_move, -3, NULL, + ai_move, -2, NULL, + ai_move, 0.3, NULL, + ai_move, 0.7, NULL, + ai_move, 1.2, NULL, + ai_move, 1.3, NULL +}; +mmove_t medic_move_attackCable = {FRAME_attack33, FRAME_attack60, medic_frames_attackCable, medic_run}; + + +void medic_attack(edict_t *self) +{ + if (self->monsterinfo.aiflags & AI_MEDIC) + self->monsterinfo.currentmove = &medic_move_attackCable; + else + self->monsterinfo.currentmove = &medic_move_attackBlaster; +} + +qboolean medic_checkattack (edict_t *self) +{ + if (self->monsterinfo.aiflags & AI_MEDIC) + { + medic_attack(self); + return true; + } + + return M_CheckAttack (self); +} + + +/*QUAKED monster_medic (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight +*/ +void SP_monster_medic (edict_t *self) +{ + if (deathmatch->value) + { + G_FreeEdict (self); + return; + } + + sound_idle1 = gi.soundindex ("medic/idle.wav"); + sound_pain1 = gi.soundindex ("medic/medpain1.wav"); + sound_pain2 = gi.soundindex ("medic/medpain2.wav"); + sound_die = gi.soundindex ("medic/meddeth1.wav"); + sound_sight = gi.soundindex ("medic/medsght1.wav"); + sound_search = gi.soundindex ("medic/medsrch1.wav"); + sound_hook_launch = gi.soundindex ("medic/medatck2.wav"); + sound_hook_hit = gi.soundindex ("medic/medatck3.wav"); + sound_hook_heal = gi.soundindex ("medic/medatck4.wav"); + sound_hook_retract = gi.soundindex ("medic/medatck5.wav"); + + gi.soundindex ("medic/medatck1.wav"); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex ("models/monsters/medic/tris.md2"); + VectorSet (self->mins, -24, -24, -24); + VectorSet (self->maxs, 24, 24, 32); + + self->health = 300; + self->gib_health = -130; + self->mass = 400; + + self->pain = medic_pain; + self->die = medic_die; + + self->monsterinfo.stand = medic_stand; + self->monsterinfo.walk = medic_walk; + self->monsterinfo.run = medic_run; + self->monsterinfo.dodge = medic_dodge; + self->monsterinfo.attack = medic_attack; + self->monsterinfo.melee = NULL; + self->monsterinfo.sight = medic_sight; + self->monsterinfo.idle = medic_idle; + self->monsterinfo.search = medic_search; + self->monsterinfo.checkattack = medic_checkattack; + + gi.linkentity (self); + + self->monsterinfo.currentmove = &medic_move_stand; + self->monsterinfo.scale = MODEL_SCALE; + + walkmonster_start (self); +} diff --git a/original/xatrix/m_medic.h b/original/xatrix/m_medic.h new file mode 100644 index 0000000..947e824 --- /dev/null +++ b/original/xatrix/m_medic.h @@ -0,0 +1,245 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// G:\quake2\baseq2\models/monsters/medic + +// This file generated by ModelGen - Do NOT Modify + +#define FRAME_walk1 0 +#define FRAME_walk2 1 +#define FRAME_walk3 2 +#define FRAME_walk4 3 +#define FRAME_walk5 4 +#define FRAME_walk6 5 +#define FRAME_walk7 6 +#define FRAME_walk8 7 +#define FRAME_walk9 8 +#define FRAME_walk10 9 +#define FRAME_walk11 10 +#define FRAME_walk12 11 +#define FRAME_wait1 12 +#define FRAME_wait2 13 +#define FRAME_wait3 14 +#define FRAME_wait4 15 +#define FRAME_wait5 16 +#define FRAME_wait6 17 +#define FRAME_wait7 18 +#define FRAME_wait8 19 +#define FRAME_wait9 20 +#define FRAME_wait10 21 +#define FRAME_wait11 22 +#define FRAME_wait12 23 +#define FRAME_wait13 24 +#define FRAME_wait14 25 +#define FRAME_wait15 26 +#define FRAME_wait16 27 +#define FRAME_wait17 28 +#define FRAME_wait18 29 +#define FRAME_wait19 30 +#define FRAME_wait20 31 +#define FRAME_wait21 32 +#define FRAME_wait22 33 +#define FRAME_wait23 34 +#define FRAME_wait24 35 +#define FRAME_wait25 36 +#define FRAME_wait26 37 +#define FRAME_wait27 38 +#define FRAME_wait28 39 +#define FRAME_wait29 40 +#define FRAME_wait30 41 +#define FRAME_wait31 42 +#define FRAME_wait32 43 +#define FRAME_wait33 44 +#define FRAME_wait34 45 +#define FRAME_wait35 46 +#define FRAME_wait36 47 +#define FRAME_wait37 48 +#define FRAME_wait38 49 +#define FRAME_wait39 50 +#define FRAME_wait40 51 +#define FRAME_wait41 52 +#define FRAME_wait42 53 +#define FRAME_wait43 54 +#define FRAME_wait44 55 +#define FRAME_wait45 56 +#define FRAME_wait46 57 +#define FRAME_wait47 58 +#define FRAME_wait48 59 +#define FRAME_wait49 60 +#define FRAME_wait50 61 +#define FRAME_wait51 62 +#define FRAME_wait52 63 +#define FRAME_wait53 64 +#define FRAME_wait54 65 +#define FRAME_wait55 66 +#define FRAME_wait56 67 +#define FRAME_wait57 68 +#define FRAME_wait58 69 +#define FRAME_wait59 70 +#define FRAME_wait60 71 +#define FRAME_wait61 72 +#define FRAME_wait62 73 +#define FRAME_wait63 74 +#define FRAME_wait64 75 +#define FRAME_wait65 76 +#define FRAME_wait66 77 +#define FRAME_wait67 78 +#define FRAME_wait68 79 +#define FRAME_wait69 80 +#define FRAME_wait70 81 +#define FRAME_wait71 82 +#define FRAME_wait72 83 +#define FRAME_wait73 84 +#define FRAME_wait74 85 +#define FRAME_wait75 86 +#define FRAME_wait76 87 +#define FRAME_wait77 88 +#define FRAME_wait78 89 +#define FRAME_wait79 90 +#define FRAME_wait80 91 +#define FRAME_wait81 92 +#define FRAME_wait82 93 +#define FRAME_wait83 94 +#define FRAME_wait84 95 +#define FRAME_wait85 96 +#define FRAME_wait86 97 +#define FRAME_wait87 98 +#define FRAME_wait88 99 +#define FRAME_wait89 100 +#define FRAME_wait90 101 +#define FRAME_run1 102 +#define FRAME_run2 103 +#define FRAME_run3 104 +#define FRAME_run4 105 +#define FRAME_run5 106 +#define FRAME_run6 107 +#define FRAME_paina1 108 +#define FRAME_paina2 109 +#define FRAME_paina3 110 +#define FRAME_paina4 111 +#define FRAME_paina5 112 +#define FRAME_paina6 113 +#define FRAME_paina7 114 +#define FRAME_paina8 115 +#define FRAME_painb1 116 +#define FRAME_painb2 117 +#define FRAME_painb3 118 +#define FRAME_painb4 119 +#define FRAME_painb5 120 +#define FRAME_painb6 121 +#define FRAME_painb7 122 +#define FRAME_painb8 123 +#define FRAME_painb9 124 +#define FRAME_painb10 125 +#define FRAME_painb11 126 +#define FRAME_painb12 127 +#define FRAME_painb13 128 +#define FRAME_painb14 129 +#define FRAME_painb15 130 +#define FRAME_duck1 131 +#define FRAME_duck2 132 +#define FRAME_duck3 133 +#define FRAME_duck4 134 +#define FRAME_duck5 135 +#define FRAME_duck6 136 +#define FRAME_duck7 137 +#define FRAME_duck8 138 +#define FRAME_duck9 139 +#define FRAME_duck10 140 +#define FRAME_duck11 141 +#define FRAME_duck12 142 +#define FRAME_duck13 143 +#define FRAME_duck14 144 +#define FRAME_duck15 145 +#define FRAME_duck16 146 +#define FRAME_death1 147 +#define FRAME_death2 148 +#define FRAME_death3 149 +#define FRAME_death4 150 +#define FRAME_death5 151 +#define FRAME_death6 152 +#define FRAME_death7 153 +#define FRAME_death8 154 +#define FRAME_death9 155 +#define FRAME_death10 156 +#define FRAME_death11 157 +#define FRAME_death12 158 +#define FRAME_death13 159 +#define FRAME_death14 160 +#define FRAME_death15 161 +#define FRAME_death16 162 +#define FRAME_death17 163 +#define FRAME_death18 164 +#define FRAME_death19 165 +#define FRAME_death20 166 +#define FRAME_death21 167 +#define FRAME_death22 168 +#define FRAME_death23 169 +#define FRAME_death24 170 +#define FRAME_death25 171 +#define FRAME_death26 172 +#define FRAME_death27 173 +#define FRAME_death28 174 +#define FRAME_death29 175 +#define FRAME_death30 176 +#define FRAME_attack1 177 +#define FRAME_attack2 178 +#define FRAME_attack3 179 +#define FRAME_attack4 180 +#define FRAME_attack5 181 +#define FRAME_attack6 182 +#define FRAME_attack7 183 +#define FRAME_attack8 184 +#define FRAME_attack9 185 +#define FRAME_attack10 186 +#define FRAME_attack11 187 +#define FRAME_attack12 188 +#define FRAME_attack13 189 +#define FRAME_attack14 190 +#define FRAME_attack15 191 +#define FRAME_attack16 192 +#define FRAME_attack17 193 +#define FRAME_attack18 194 +#define FRAME_attack19 195 +#define FRAME_attack20 196 +#define FRAME_attack21 197 +#define FRAME_attack22 198 +#define FRAME_attack23 199 +#define FRAME_attack24 200 +#define FRAME_attack25 201 +#define FRAME_attack26 202 +#define FRAME_attack27 203 +#define FRAME_attack28 204 +#define FRAME_attack29 205 +#define FRAME_attack30 206 +#define FRAME_attack31 207 +#define FRAME_attack32 208 +#define FRAME_attack33 209 +#define FRAME_attack34 210 +#define FRAME_attack35 211 +#define FRAME_attack36 212 +#define FRAME_attack37 213 +#define FRAME_attack38 214 +#define FRAME_attack39 215 +#define FRAME_attack40 216 +#define FRAME_attack41 217 +#define FRAME_attack42 218 +#define FRAME_attack43 219 +#define FRAME_attack44 220 +#define FRAME_attack45 221 +#define FRAME_attack46 222 +#define FRAME_attack47 223 +#define FRAME_attack48 224 +#define FRAME_attack49 225 +#define FRAME_attack50 226 +#define FRAME_attack51 227 +#define FRAME_attack52 228 +#define FRAME_attack53 229 +#define FRAME_attack54 230 +#define FRAME_attack55 231 +#define FRAME_attack56 232 +#define FRAME_attack57 233 +#define FRAME_attack58 234 +#define FRAME_attack59 235 +#define FRAME_attack60 236 + +#define MODEL_SCALE 1.000000 diff --git a/original/xatrix/m_move.c b/original/xatrix/m_move.c new file mode 100644 index 0000000..459af38 --- /dev/null +++ b/original/xatrix/m_move.c @@ -0,0 +1,568 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// m_move.c -- monster movement + +#include "g_local.h" + +#define STEPSIZE 18 + +/* +============= +M_CheckBottom + +Returns false if any part of the bottom of the entity is off an edge that +is not a staircase. + +============= +*/ +int c_yes, c_no; + +qboolean M_CheckBottom (edict_t *ent) +{ + vec3_t mins, maxs, start, stop; + trace_t trace; + int x, y; + float mid, bottom; + + VectorAdd (ent->s.origin, ent->mins, mins); + VectorAdd (ent->s.origin, ent->maxs, maxs); + +// if all of the points under the corners are solid world, don't bother +// with the tougher checks +// the corners must be within 16 of the midpoint + start[2] = mins[2] - 1; + for (x=0 ; x<=1 ; x++) + for (y=0 ; y<=1 ; y++) + { + start[0] = x ? maxs[0] : mins[0]; + start[1] = y ? maxs[1] : mins[1]; + if (gi.pointcontents (start) != CONTENTS_SOLID) + goto realcheck; + } + + c_yes++; + return true; // we got out easy + +realcheck: + c_no++; +// +// check it for real... +// + start[2] = mins[2]; + +// the midpoint must be within 16 of the bottom + start[0] = stop[0] = (mins[0] + maxs[0])*0.5; + start[1] = stop[1] = (mins[1] + maxs[1])*0.5; + stop[2] = start[2] - 2*STEPSIZE; + trace = gi.trace (start, vec3_origin, vec3_origin, stop, ent, MASK_MONSTERSOLID); + + if (trace.fraction == 1.0) + return false; + mid = bottom = trace.endpos[2]; + +// the corners must be within 16 of the midpoint + for (x=0 ; x<=1 ; x++) + for (y=0 ; y<=1 ; y++) + { + start[0] = stop[0] = x ? maxs[0] : mins[0]; + start[1] = stop[1] = y ? maxs[1] : mins[1]; + + trace = gi.trace (start, vec3_origin, vec3_origin, stop, ent, MASK_MONSTERSOLID); + + if (trace.fraction != 1.0 && trace.endpos[2] > bottom) + bottom = trace.endpos[2]; + if (trace.fraction == 1.0 || mid - trace.endpos[2] > STEPSIZE) + return false; + } + + c_yes++; + return true; +} + + +/* +============= +SV_movestep + +Called by monster program code. +The move will be adjusted for slopes and stairs, but if the move isn't +possible, no move is done, false is returned, and +pr_global_struct->trace_normal is set to the normal of the blocking wall +============= +*/ +//FIXME since we need to test end position contents here, can we avoid doing +//it again later in catagorize position? +qboolean SV_movestep (edict_t *ent, vec3_t move, qboolean relink) +{ + float dz; + vec3_t oldorg, neworg, end; + trace_t trace; + int i; + float stepsize; + vec3_t test; + int contents; + +// try the move + VectorCopy (ent->s.origin, oldorg); + VectorAdd (ent->s.origin, move, neworg); + +// flying monsters don't step up + if ( ent->flags & (FL_SWIM | FL_FLY) ) + { + // try one move with vertical motion, then one without + for (i=0 ; i<2 ; i++) + { + VectorAdd (ent->s.origin, move, neworg); + if (i == 0 && ent->enemy) + { + if (!ent->goalentity) + ent->goalentity = ent->enemy; + dz = ent->s.origin[2] - ent->goalentity->s.origin[2]; + if (ent->goalentity->client) + { + if (dz > 40) + neworg[2] -= 8; + if (!((ent->flags & FL_SWIM) && (ent->waterlevel < 2))) + if (dz < 30) + neworg[2] += 8; + } + else + { + // RAFAEL + if (strcmp (ent->classname , "monster_fixbot") == 0) + { + if (ent->s.frame >= 105 && ent->s.frame <= 120) + { + if (dz > 12) + neworg[2] --; + else if (dz < -12) + neworg[2] ++; + } + else if (ent->s.frame >= 31 && ent->s.frame <= 88) + { + if (dz > 12) + neworg[2] -= 12; + else if (dz < -12) + neworg[2] += 12; + } + else + { + if (dz > 12) + neworg[2] -= 8; + else if (dz < -12) + neworg[2] += 8; + } + } + // RAFAEL ( else ) + else + { + if (dz > 8) + neworg[2] -= 8; + else if (dz > 0) + neworg[2] -= dz; + else if (dz < -8) + neworg[2] += 8; + else + neworg[2] += dz; + } + } + } + trace = gi.trace (ent->s.origin, ent->mins, ent->maxs, neworg, ent, MASK_MONSTERSOLID); + + // fly monsters don't enter water voluntarily + if (ent->flags & FL_FLY) + { + if (!ent->waterlevel) + { + test[0] = trace.endpos[0]; + test[1] = trace.endpos[1]; + test[2] = trace.endpos[2] + ent->mins[2] + 1; + contents = gi.pointcontents(test); + if (contents & MASK_WATER) + return false; + } + } + + // swim monsters don't exit water voluntarily + if (ent->flags & FL_SWIM) + { + if (ent->waterlevel < 2) + { + test[0] = trace.endpos[0]; + test[1] = trace.endpos[1]; + test[2] = trace.endpos[2] + ent->mins[2] + 1; + contents = gi.pointcontents(test); + if (!(contents & MASK_WATER)) + return false; + } + } + + if (trace.fraction == 1) + { + VectorCopy (trace.endpos, ent->s.origin); + if (relink) + { + gi.linkentity (ent); + G_TouchTriggers (ent); + } + return true; + } + + if (!ent->enemy) + break; + } + + return false; + } + +// push down from a step height above the wished position + if (!(ent->monsterinfo.aiflags & AI_NOSTEP)) + stepsize = STEPSIZE; + else + stepsize = 1; + + neworg[2] += stepsize; + VectorCopy (neworg, end); + end[2] -= stepsize*2; + + trace = gi.trace (neworg, ent->mins, ent->maxs, end, ent, MASK_MONSTERSOLID); + + if (trace.allsolid) + return false; + + if (trace.startsolid) + { + neworg[2] -= stepsize; + trace = gi.trace (neworg, ent->mins, ent->maxs, end, ent, MASK_MONSTERSOLID); + if (trace.allsolid || trace.startsolid) + return false; + } + + + // don't go in to water + if (ent->waterlevel == 0) + { + test[0] = trace.endpos[0]; + test[1] = trace.endpos[1]; + test[2] = trace.endpos[2] + ent->mins[2] + 1; + contents = gi.pointcontents(test); + + if (contents & MASK_WATER) + return false; + } + + if (trace.fraction == 1) + { + // if monster had the ground pulled out, go ahead and fall + if ( ent->flags & FL_PARTIALGROUND ) + { + VectorAdd (ent->s.origin, move, ent->s.origin); + if (relink) + { + gi.linkentity (ent); + G_TouchTriggers (ent); + } + ent->groundentity = NULL; + return true; + } + + return false; // walked off an edge + } + +// check point traces down for dangling corners + VectorCopy (trace.endpos, ent->s.origin); + + if (!M_CheckBottom (ent)) + { + if ( ent->flags & FL_PARTIALGROUND ) + { // entity had floor mostly pulled out from underneath it + // and is trying to correct + if (relink) + { + gi.linkentity (ent); + G_TouchTriggers (ent); + } + return true; + } + VectorCopy (oldorg, ent->s.origin); + return false; + } + + if ( ent->flags & FL_PARTIALGROUND ) + { + ent->flags &= ~FL_PARTIALGROUND; + } + ent->groundentity = trace.ent; + ent->groundentity_linkcount = trace.ent->linkcount; + +// the move is ok + if (relink) + { + gi.linkentity (ent); + G_TouchTriggers (ent); + } + return true; +} + + +//============================================================================ + +/* +=============== +M_ChangeYaw + +=============== +*/ +void M_ChangeYaw (edict_t *ent) +{ + float ideal; + float current; + float move; + float speed; + + current = anglemod(ent->s.angles[YAW]); + ideal = ent->ideal_yaw; + + if (current == ideal) + return; + + move = ideal - current; + speed = ent->yaw_speed; + if (ideal > current) + { + if (move >= 180) + move = move - 360; + } + else + { + if (move <= -180) + move = move + 360; + } + if (move > 0) + { + if (move > speed) + move = speed; + } + else + { + if (move < -speed) + move = -speed; + } + + ent->s.angles[YAW] = anglemod (current + move); +} + + +/* +====================== +SV_StepDirection + +Turns to the movement direction, and walks the current distance if +facing it. + +====================== +*/ +qboolean SV_StepDirection (edict_t *ent, float yaw, float dist) +{ + vec3_t move, oldorigin; + float delta; + + ent->ideal_yaw = yaw; + M_ChangeYaw (ent); + + yaw = yaw*M_PI*2 / 360; + move[0] = cos(yaw)*dist; + move[1] = sin(yaw)*dist; + move[2] = 0; + + VectorCopy (ent->s.origin, oldorigin); + if (SV_movestep (ent, move, false)) + { + delta = ent->s.angles[YAW] - ent->ideal_yaw; + if (delta > 45 && delta < 315) + { // not turned far enough, so don't take the step + VectorCopy (oldorigin, ent->s.origin); + } + gi.linkentity (ent); + G_TouchTriggers (ent); + return true; + } + gi.linkentity (ent); + G_TouchTriggers (ent); + return false; +} + +/* +====================== +SV_FixCheckBottom + +====================== +*/ +void SV_FixCheckBottom (edict_t *ent) +{ + ent->flags |= FL_PARTIALGROUND; +} + + + +/* +================ +SV_NewChaseDir + +================ +*/ +#define DI_NODIR -1 +void SV_NewChaseDir (edict_t *actor, edict_t *enemy, float dist) +{ + float deltax,deltay; + float d[3]; + float tdir, olddir, turnaround; + + //FIXME: how did we get here with no enemy + if (!enemy) + return; + + olddir = anglemod( (int)(actor->ideal_yaw/45)*45 ); + turnaround = anglemod(olddir - 180); + + deltax = enemy->s.origin[0] - actor->s.origin[0]; + deltay = enemy->s.origin[1] - actor->s.origin[1]; + if (deltax>10) + d[1]= 0; + else if (deltax<-10) + d[1]= 180; + else + d[1]= DI_NODIR; + if (deltay<-10) + d[2]= 270; + else if (deltay>10) + d[2]= 90; + else + d[2]= DI_NODIR; + +// try direct route + if (d[1] != DI_NODIR && d[2] != DI_NODIR) + { + if (d[1] == 0) + tdir = d[2] == 90 ? 45 : 315; + else + tdir = d[2] == 90 ? 135 : 215; + + if (tdir != turnaround && SV_StepDirection(actor, tdir, dist)) + return; + } + +// try other directions + if ( ((rand()&3) & 1) || abs(deltay)>abs(deltax)) + { + tdir=d[1]; + d[1]=d[2]; + d[2]=tdir; + } + + if (d[1]!=DI_NODIR && d[1]!=turnaround + && SV_StepDirection(actor, d[1], dist)) + return; + + if (d[2]!=DI_NODIR && d[2]!=turnaround + && SV_StepDirection(actor, d[2], dist)) + return; + +/* there is no direct path to the player, so pick another direction */ + + if (olddir!=DI_NODIR && SV_StepDirection(actor, olddir, dist)) + return; + + if (rand()&1) /*randomly determine direction of search*/ + { + for (tdir=0 ; tdir<=315 ; tdir += 45) + if (tdir!=turnaround && SV_StepDirection(actor, tdir, dist) ) + return; + } + else + { + for (tdir=315 ; tdir >=0 ; tdir -= 45) + if (tdir!=turnaround && SV_StepDirection(actor, tdir, dist) ) + return; + } + + if (turnaround != DI_NODIR && SV_StepDirection(actor, turnaround, dist) ) + return; + + actor->ideal_yaw = olddir; // can't move + +// if a bridge was pulled out from underneath a monster, it may not have +// a valid standing position at all + + if (!M_CheckBottom (actor)) + SV_FixCheckBottom (actor); +} + +/* +====================== +SV_CloseEnough + +====================== +*/ +qboolean SV_CloseEnough (edict_t *ent, edict_t *goal, float dist) +{ + int i; + + for (i=0 ; i<3 ; i++) + { + if (goal->absmin[i] > ent->absmax[i] + dist) + return false; + if (goal->absmax[i] < ent->absmin[i] - dist) + return false; + } + return true; +} + + +/* +====================== +M_MoveToGoal +====================== +*/ +void M_MoveToGoal (edict_t *ent, float dist) +{ + edict_t *goal; + + goal = ent->goalentity; + + if (!ent->groundentity && !(ent->flags & (FL_FLY|FL_SWIM))) + return; + +// if the next step hits the enemy, return immediately + if (ent->enemy && SV_CloseEnough (ent, ent->enemy, dist) ) + return; + +// bump around... + if ( (rand()&3)==1 || !SV_StepDirection (ent, ent->ideal_yaw, dist)) + { + if (ent->inuse) + SV_NewChaseDir (ent, goal, dist); + } +} + + +/* +=============== +M_walkmove +=============== +*/ +qboolean M_walkmove (edict_t *ent, float yaw, float dist) +{ + vec3_t move; + + if (!ent->groundentity && !(ent->flags & (FL_FLY|FL_SWIM))) + return false; + + yaw = yaw*M_PI*2 / 360; + + move[0] = cos(yaw)*dist; + move[1] = sin(yaw)*dist; + move[2] = 0; + + return SV_movestep(ent, move, true); +} diff --git a/original/xatrix/m_mutant.c b/original/xatrix/m_mutant.c new file mode 100644 index 0000000..188ce76 --- /dev/null +++ b/original/xatrix/m_mutant.c @@ -0,0 +1,646 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +/* +============================================================================== + +mutant + +============================================================================== +*/ + +#include "g_local.h" +#include "m_mutant.h" + + +static int sound_swing; +static int sound_hit; +static int sound_hit2; +static int sound_death; +static int sound_idle; +static int sound_pain1; +static int sound_pain2; +static int sound_sight; +static int sound_search; +static int sound_step1; +static int sound_step2; +static int sound_step3; +static int sound_thud; + +// +// SOUNDS +// + +void mutant_step (edict_t *self) +{ + int n; + n = (rand() + 1) % 3; + if (n == 0) + gi.sound (self, CHAN_VOICE, sound_step1, 1, ATTN_NORM, 0); + else if (n == 1) + gi.sound (self, CHAN_VOICE, sound_step2, 1, ATTN_NORM, 0); + else + gi.sound (self, CHAN_VOICE, sound_step3, 1, ATTN_NORM, 0); +} + +void mutant_sight (edict_t *self, edict_t *other) +{ + gi.sound (self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); +} + +void mutant_search (edict_t *self) +{ + gi.sound (self, CHAN_VOICE, sound_search, 1, ATTN_NORM, 0); +} + +void mutant_swing (edict_t *self) +{ + gi.sound (self, CHAN_VOICE, sound_swing, 1, ATTN_NORM, 0); +} + + +// +// STAND +// + +mframe_t mutant_frames_stand [] = +{ + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, // 10 + + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, // 20 + + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, // 30 + + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, // 40 + + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, // 50 + + ai_stand, 0, NULL +}; +mmove_t mutant_move_stand = {FRAME_stand101, FRAME_stand151, mutant_frames_stand, NULL}; + +void mutant_stand (edict_t *self) +{ + self->monsterinfo.currentmove = &mutant_move_stand; +} + + +// +// IDLE +// + +void mutant_idle_loop (edict_t *self) +{ + if (random() < 0.75) + self->monsterinfo.nextframe = FRAME_stand155; +} + +mframe_t mutant_frames_idle [] = +{ + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, // scratch loop start + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, mutant_idle_loop, // scratch loop end + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL +}; +mmove_t mutant_move_idle = {FRAME_stand152, FRAME_stand164, mutant_frames_idle, mutant_stand}; + +void mutant_idle (edict_t *self) +{ + self->monsterinfo.currentmove = &mutant_move_idle; + gi.sound (self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0); +} + + +// +// WALK +// + +void mutant_walk (edict_t *self); + +mframe_t mutant_frames_walk [] = +{ + ai_walk, 3, NULL, + ai_walk, 1, NULL, + ai_walk, 5, NULL, + ai_walk, 10, NULL, + ai_walk, 13, NULL, + ai_walk, 10, NULL, + ai_walk, 0, NULL, + ai_walk, 5, NULL, + ai_walk, 6, NULL, + ai_walk, 16, NULL, + ai_walk, 15, NULL, + ai_walk, 6, NULL +}; +mmove_t mutant_move_walk = {FRAME_walk05, FRAME_walk16, mutant_frames_walk, NULL}; + +void mutant_walk_loop (edict_t *self) +{ + self->monsterinfo.currentmove = &mutant_move_walk; +} + +mframe_t mutant_frames_start_walk [] = +{ + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, -2, NULL, + ai_walk, 1, NULL +}; +mmove_t mutant_move_start_walk = {FRAME_walk01, FRAME_walk04, mutant_frames_start_walk, mutant_walk_loop}; + +void mutant_walk (edict_t *self) +{ + self->monsterinfo.currentmove = &mutant_move_start_walk; +} + + +// +// RUN +// + +mframe_t mutant_frames_run [] = +{ + ai_run, 40, NULL, + ai_run, 40, mutant_step, + ai_run, 24, NULL, + ai_run, 5, mutant_step, + ai_run, 17, NULL, + ai_run, 10, NULL +}; +mmove_t mutant_move_run = {FRAME_run03, FRAME_run08, mutant_frames_run, NULL}; + +void mutant_run (edict_t *self) +{ + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + self->monsterinfo.currentmove = &mutant_move_stand; + else + self->monsterinfo.currentmove = &mutant_move_run; +} + + +// +// MELEE +// + +void mutant_hit_left (edict_t *self) +{ + vec3_t aim; + + VectorSet (aim, MELEE_DISTANCE, self->mins[0], 8); + if (fire_hit (self, aim, (10 + (rand() %5)), 100)) + gi.sound (self, CHAN_WEAPON, sound_hit, 1, ATTN_NORM, 0); + else + gi.sound (self, CHAN_WEAPON, sound_swing, 1, ATTN_NORM, 0); +} + +void mutant_hit_right (edict_t *self) +{ + vec3_t aim; + + VectorSet (aim, MELEE_DISTANCE, self->maxs[0], 8); + if (fire_hit (self, aim, (10 + (rand() %5)), 100)) + gi.sound (self, CHAN_WEAPON, sound_hit2, 1, ATTN_NORM, 0); + else + gi.sound (self, CHAN_WEAPON, sound_swing, 1, ATTN_NORM, 0); +} + +void mutant_check_refire (edict_t *self) +{ + if (!self->enemy || !self->enemy->inuse || self->enemy->health <= 0) + return; + + if ( ((skill->value == 3) && (random() < 0.5)) || (range(self, self->enemy) == RANGE_MELEE) ) + self->monsterinfo.nextframe = FRAME_attack09; +} + +mframe_t mutant_frames_attack [] = +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, mutant_hit_left, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, mutant_hit_right, + ai_charge, 0, mutant_check_refire +}; +mmove_t mutant_move_attack = {FRAME_attack09, FRAME_attack15, mutant_frames_attack, mutant_run}; + +void mutant_melee (edict_t *self) +{ + self->monsterinfo.currentmove = &mutant_move_attack; +} + + +// +// ATTACK +// + +void mutant_jump_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (self->health <= 0) + { + self->touch = NULL; + return; + } + + if (other->takedamage) + { + if (VectorLength(self->velocity) > 400) + { + vec3_t point; + vec3_t normal; + int damage; + + VectorCopy (self->velocity, normal); + VectorNormalize(normal); + VectorMA (self->s.origin, self->maxs[0], normal, point); + damage = 40 + 10 * random(); + T_Damage (other, self, self, self->velocity, point, normal, damage, damage, 0, MOD_UNKNOWN); + } + } + + if (!M_CheckBottom (self)) + { + if (self->groundentity) + { + self->monsterinfo.nextframe = FRAME_attack02; + self->touch = NULL; + } + return; + } + + self->touch = NULL; +} + +void mutant_jump_takeoff (edict_t *self) +{ + vec3_t forward; + + gi.sound (self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); + AngleVectors (self->s.angles, forward, NULL, NULL); + self->s.origin[2] += 1; + VectorScale (forward, 600, self->velocity); + self->velocity[2] = 250; + self->groundentity = NULL; + self->monsterinfo.aiflags |= AI_DUCKED; + self->monsterinfo.attack_finished = level.time + 3; + self->touch = mutant_jump_touch; +} + +void mutant_check_landing (edict_t *self) +{ + if (self->groundentity) + { + gi.sound (self, CHAN_WEAPON, sound_thud, 1, ATTN_NORM, 0); + self->monsterinfo.attack_finished = 0; + self->monsterinfo.aiflags &= ~AI_DUCKED; + return; + } + + if (level.time > self->monsterinfo.attack_finished) + self->monsterinfo.nextframe = FRAME_attack02; + else + self->monsterinfo.nextframe = FRAME_attack05; +} + +mframe_t mutant_frames_jump [] = +{ + ai_charge, 0, NULL, + ai_charge, 17, NULL, + ai_charge, 15, mutant_jump_takeoff, + ai_charge, 15, NULL, + ai_charge, 15, mutant_check_landing, + ai_charge, 0, NULL, + ai_charge, 3, NULL, + ai_charge, 0, NULL +}; +mmove_t mutant_move_jump = {FRAME_attack01, FRAME_attack08, mutant_frames_jump, mutant_run}; + +void mutant_jump (edict_t *self) +{ + self->monsterinfo.currentmove = &mutant_move_jump; +} + + +// +// CHECKATTACK +// + +qboolean mutant_check_melee (edict_t *self) +{ + if (range (self, self->enemy) == RANGE_MELEE) + return true; + return false; +} + +qboolean mutant_check_jump (edict_t *self) +{ + vec3_t v; + float distance; + + if (self->absmin[2] > (self->enemy->absmin[2] + 0.75 * self->enemy->size[2])) + return false; + + if (self->absmax[2] < (self->enemy->absmin[2] + 0.25 * self->enemy->size[2])) + return false; + + v[0] = self->s.origin[0] - self->enemy->s.origin[0]; + v[1] = self->s.origin[1] - self->enemy->s.origin[1]; + v[2] = 0; + distance = VectorLength(v); + + if (distance < 100) + return false; + if (distance > 100) + { + if (random() < 0.9) + return false; + } + + return true; +} + +qboolean mutant_checkattack (edict_t *self) +{ + if (!self->enemy || self->enemy->health <= 0) + return false; + + if (mutant_check_melee(self)) + { + self->monsterinfo.attack_state = AS_MELEE; + return true; + } + + if (mutant_check_jump(self)) + { + self->monsterinfo.attack_state = AS_MISSILE; + // FIXME play a jump sound here + return true; + } + + return false; +} + + +// +// PAIN +// + +mframe_t mutant_frames_pain1 [] = +{ + ai_move, 4, NULL, + ai_move, -3, NULL, + ai_move, -8, NULL, + ai_move, 2, NULL, + ai_move, 5, NULL +}; +mmove_t mutant_move_pain1 = {FRAME_pain101, FRAME_pain105, mutant_frames_pain1, mutant_run}; + +mframe_t mutant_frames_pain2 [] = +{ + ai_move, -24,NULL, + ai_move, 11, NULL, + ai_move, 5, NULL, + ai_move, -2, NULL, + ai_move, 6, NULL, + ai_move, 4, NULL +}; +mmove_t mutant_move_pain2 = {FRAME_pain201, FRAME_pain206, mutant_frames_pain2, mutant_run}; + +mframe_t mutant_frames_pain3 [] = +{ + ai_move, -22,NULL, + ai_move, 3, NULL, + ai_move, 3, NULL, + ai_move, 2, NULL, + ai_move, 1, NULL, + ai_move, 1, NULL, + ai_move, 6, NULL, + ai_move, 3, NULL, + ai_move, 2, NULL, + ai_move, 0, NULL, + ai_move, 1, NULL +}; +mmove_t mutant_move_pain3 = {FRAME_pain301, FRAME_pain311, mutant_frames_pain3, mutant_run}; + +void mutant_pain (edict_t *self, edict_t *other, float kick, int damage) +{ + float r; + + if (self->health < (self->max_health / 2)) + self->s.skinnum = 1; + + if (level.time < self->pain_debounce_time) + return; + + self->pain_debounce_time = level.time + 3; + + if (skill->value == 3) + return; // no pain anims in nightmare + + r = random(); + if (r < 0.33) + { + gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &mutant_move_pain1; + } + else if (r < 0.66) + { + gi.sound (self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &mutant_move_pain2; + } + else + { + gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &mutant_move_pain3; + } +} + + +// +// DEATH +// + +void mutant_dead (edict_t *self) +{ + VectorSet (self->mins, -16, -16, -24); + VectorSet (self->maxs, 16, 16, -8); + self->movetype = MOVETYPE_TOSS; + self->svflags |= SVF_DEADMONSTER; + gi.linkentity (self); + + M_FlyCheck (self); +} + +mframe_t mutant_frames_death1 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t mutant_move_death1 = {FRAME_death101, FRAME_death109, mutant_frames_death1, mutant_dead}; + +mframe_t mutant_frames_death2 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t mutant_move_death2 = {FRAME_death201, FRAME_death210, mutant_frames_death2, mutant_dead}; + +void mutant_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + int n; + + if (self->health <= self->gib_health) + { + gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0); + for (n= 0; n < 2; n++) + ThrowGib (self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC); + for (n= 0; n < 4; n++) + ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); + ThrowHead (self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC); + self->deadflag = DEAD_DEAD; + return; + } + + if (self->deadflag == DEAD_DEAD) + return; + + gi.sound (self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0); + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_YES; + self->s.skinnum = 1; + + if (random() < 0.5) + self->monsterinfo.currentmove = &mutant_move_death1; + else + self->monsterinfo.currentmove = &mutant_move_death2; +} + + +// +// SPAWN +// + +/*QUAKED monster_mutant (1 .5 0) (-32 -32 -24) (32 32 32) Ambush Trigger_Spawn Sight +*/ +void SP_monster_mutant (edict_t *self) +{ + if (deathmatch->value) + { + G_FreeEdict (self); + return; + } + + sound_swing = gi.soundindex ("mutant/mutatck1.wav"); + sound_hit = gi.soundindex ("mutant/mutatck2.wav"); + sound_hit2 = gi.soundindex ("mutant/mutatck3.wav"); + sound_death = gi.soundindex ("mutant/mutdeth1.wav"); + sound_idle = gi.soundindex ("mutant/mutidle1.wav"); + sound_pain1 = gi.soundindex ("mutant/mutpain1.wav"); + sound_pain2 = gi.soundindex ("mutant/mutpain2.wav"); + sound_sight = gi.soundindex ("mutant/mutsght1.wav"); + sound_search = gi.soundindex ("mutant/mutsrch1.wav"); + sound_step1 = gi.soundindex ("mutant/step1.wav"); + sound_step2 = gi.soundindex ("mutant/step2.wav"); + sound_step3 = gi.soundindex ("mutant/step3.wav"); + sound_thud = gi.soundindex ("mutant/thud1.wav"); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex ("models/monsters/mutant/tris.md2"); + VectorSet (self->mins, -32, -32, -24); + VectorSet (self->maxs, 32, 32, 48); + + self->health = 300; + self->gib_health = -120; + self->mass = 300; + + self->pain = mutant_pain; + self->die = mutant_die; + + self->monsterinfo.stand = mutant_stand; + self->monsterinfo.walk = mutant_walk; + self->monsterinfo.run = mutant_run; + self->monsterinfo.dodge = NULL; + self->monsterinfo.attack = mutant_jump; + self->monsterinfo.melee = mutant_melee; + self->monsterinfo.sight = mutant_sight; + self->monsterinfo.search = mutant_search; + self->monsterinfo.idle = mutant_idle; + self->monsterinfo.checkattack = mutant_checkattack; + + gi.linkentity (self); + + self->monsterinfo.currentmove = &mutant_move_stand; + + self->monsterinfo.scale = MODEL_SCALE; + walkmonster_start (self); +} diff --git a/original/xatrix/m_mutant.h b/original/xatrix/m_mutant.h new file mode 100644 index 0000000..b90b640 --- /dev/null +++ b/original/xatrix/m_mutant.h @@ -0,0 +1,157 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// G:\quake2\baseq2\models/monsters/mutant + +// This file generated by ModelGen - Do NOT Modify + +#define FRAME_attack01 0 +#define FRAME_attack02 1 +#define FRAME_attack03 2 +#define FRAME_attack04 3 +#define FRAME_attack05 4 +#define FRAME_attack06 5 +#define FRAME_attack07 6 +#define FRAME_attack08 7 +#define FRAME_attack09 8 +#define FRAME_attack10 9 +#define FRAME_attack11 10 +#define FRAME_attack12 11 +#define FRAME_attack13 12 +#define FRAME_attack14 13 +#define FRAME_attack15 14 +#define FRAME_death101 15 +#define FRAME_death102 16 +#define FRAME_death103 17 +#define FRAME_death104 18 +#define FRAME_death105 19 +#define FRAME_death106 20 +#define FRAME_death107 21 +#define FRAME_death108 22 +#define FRAME_death109 23 +#define FRAME_death201 24 +#define FRAME_death202 25 +#define FRAME_death203 26 +#define FRAME_death204 27 +#define FRAME_death205 28 +#define FRAME_death206 29 +#define FRAME_death207 30 +#define FRAME_death208 31 +#define FRAME_death209 32 +#define FRAME_death210 33 +#define FRAME_pain101 34 +#define FRAME_pain102 35 +#define FRAME_pain103 36 +#define FRAME_pain104 37 +#define FRAME_pain105 38 +#define FRAME_pain201 39 +#define FRAME_pain202 40 +#define FRAME_pain203 41 +#define FRAME_pain204 42 +#define FRAME_pain205 43 +#define FRAME_pain206 44 +#define FRAME_pain301 45 +#define FRAME_pain302 46 +#define FRAME_pain303 47 +#define FRAME_pain304 48 +#define FRAME_pain305 49 +#define FRAME_pain306 50 +#define FRAME_pain307 51 +#define FRAME_pain308 52 +#define FRAME_pain309 53 +#define FRAME_pain310 54 +#define FRAME_pain311 55 +#define FRAME_run03 56 +#define FRAME_run04 57 +#define FRAME_run05 58 +#define FRAME_run06 59 +#define FRAME_run07 60 +#define FRAME_run08 61 +#define FRAME_stand101 62 +#define FRAME_stand102 63 +#define FRAME_stand103 64 +#define FRAME_stand104 65 +#define FRAME_stand105 66 +#define FRAME_stand106 67 +#define FRAME_stand107 68 +#define FRAME_stand108 69 +#define FRAME_stand109 70 +#define FRAME_stand110 71 +#define FRAME_stand111 72 +#define FRAME_stand112 73 +#define FRAME_stand113 74 +#define FRAME_stand114 75 +#define FRAME_stand115 76 +#define FRAME_stand116 77 +#define FRAME_stand117 78 +#define FRAME_stand118 79 +#define FRAME_stand119 80 +#define FRAME_stand120 81 +#define FRAME_stand121 82 +#define FRAME_stand122 83 +#define FRAME_stand123 84 +#define FRAME_stand124 85 +#define FRAME_stand125 86 +#define FRAME_stand126 87 +#define FRAME_stand127 88 +#define FRAME_stand128 89 +#define FRAME_stand129 90 +#define FRAME_stand130 91 +#define FRAME_stand131 92 +#define FRAME_stand132 93 +#define FRAME_stand133 94 +#define FRAME_stand134 95 +#define FRAME_stand135 96 +#define FRAME_stand136 97 +#define FRAME_stand137 98 +#define FRAME_stand138 99 +#define FRAME_stand139 100 +#define FRAME_stand140 101 +#define FRAME_stand141 102 +#define FRAME_stand142 103 +#define FRAME_stand143 104 +#define FRAME_stand144 105 +#define FRAME_stand145 106 +#define FRAME_stand146 107 +#define FRAME_stand147 108 +#define FRAME_stand148 109 +#define FRAME_stand149 110 +#define FRAME_stand150 111 +#define FRAME_stand151 112 +#define FRAME_stand152 113 +#define FRAME_stand153 114 +#define FRAME_stand154 115 +#define FRAME_stand155 116 +#define FRAME_stand156 117 +#define FRAME_stand157 118 +#define FRAME_stand158 119 +#define FRAME_stand159 120 +#define FRAME_stand160 121 +#define FRAME_stand161 122 +#define FRAME_stand162 123 +#define FRAME_stand163 124 +#define FRAME_stand164 125 +#define FRAME_walk01 126 +#define FRAME_walk02 127 +#define FRAME_walk03 128 +#define FRAME_walk04 129 +#define FRAME_walk05 130 +#define FRAME_walk06 131 +#define FRAME_walk07 132 +#define FRAME_walk08 133 +#define FRAME_walk09 134 +#define FRAME_walk10 135 +#define FRAME_walk11 136 +#define FRAME_walk12 137 +#define FRAME_walk13 138 +#define FRAME_walk14 139 +#define FRAME_walk15 140 +#define FRAME_walk16 141 +#define FRAME_walk17 142 +#define FRAME_walk18 143 +#define FRAME_walk19 144 +#define FRAME_walk20 145 +#define FRAME_walk21 146 +#define FRAME_walk22 147 +#define FRAME_walk23 148 + +#define MODEL_SCALE 1.000000 diff --git a/original/xatrix/m_parasite.c b/original/xatrix/m_parasite.c new file mode 100644 index 0000000..914ab21 --- /dev/null +++ b/original/xatrix/m_parasite.c @@ -0,0 +1,535 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +/* +============================================================================== + +parasite + +============================================================================== +*/ + +#include "g_local.h" +#include "m_parasite.h" + + +static int sound_pain1; +static int sound_pain2; +static int sound_die; +static int sound_launch; +static int sound_impact; +static int sound_suck; +static int sound_reelin; +static int sound_sight; +static int sound_tap; +static int sound_scratch; +static int sound_search; + + +void parasite_stand (edict_t *self); +void parasite_start_run (edict_t *self); +void parasite_run (edict_t *self); +void parasite_walk (edict_t *self); +void parasite_start_walk (edict_t *self); +void parasite_end_fidget (edict_t *self); +void parasite_do_fidget (edict_t *self); +void parasite_refidget (edict_t *self); + + +void parasite_launch (edict_t *self) +{ + gi.sound (self, CHAN_WEAPON, sound_launch, 1, ATTN_NORM, 0); +} + +void parasite_reel_in (edict_t *self) +{ + gi.sound (self, CHAN_WEAPON, sound_reelin, 1, ATTN_NORM, 0); +} + +void parasite_sight (edict_t *self, edict_t *other) +{ + gi.sound (self, CHAN_WEAPON, sound_sight, 1, ATTN_NORM, 0); +} + +void parasite_tap (edict_t *self) +{ + gi.sound (self, CHAN_WEAPON, sound_tap, 1, ATTN_IDLE, 0); +} + +void parasite_scratch (edict_t *self) +{ + gi.sound (self, CHAN_WEAPON, sound_scratch, 1, ATTN_IDLE, 0); +} + +void parasite_search (edict_t *self) +{ + gi.sound (self, CHAN_WEAPON, sound_search, 1, ATTN_IDLE, 0); +} + + +mframe_t parasite_frames_start_fidget [] = +{ + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL +}; +mmove_t parasite_move_start_fidget = {FRAME_stand18, FRAME_stand21, parasite_frames_start_fidget, parasite_do_fidget}; + +mframe_t parasite_frames_fidget [] = +{ + ai_stand, 0, parasite_scratch, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, parasite_scratch, + ai_stand, 0, NULL, + ai_stand, 0, NULL +}; +mmove_t parasite_move_fidget = {FRAME_stand22, FRAME_stand27, parasite_frames_fidget, parasite_refidget}; + +mframe_t parasite_frames_end_fidget [] = +{ + ai_stand, 0, parasite_scratch, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL +}; +mmove_t parasite_move_end_fidget = {FRAME_stand28, FRAME_stand35, parasite_frames_end_fidget, parasite_stand}; + +void parasite_end_fidget (edict_t *self) +{ + self->monsterinfo.currentmove = ¶site_move_end_fidget; +} + +void parasite_do_fidget (edict_t *self) +{ + self->monsterinfo.currentmove = ¶site_move_fidget; +} + +void parasite_refidget (edict_t *self) +{ + if (random() <= 0.8) + self->monsterinfo.currentmove = ¶site_move_fidget; + else + self->monsterinfo.currentmove = ¶site_move_end_fidget; +} + +void parasite_idle (edict_t *self) +{ + self->monsterinfo.currentmove = ¶site_move_start_fidget; +} + + +mframe_t parasite_frames_stand [] = +{ + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, parasite_tap, + ai_stand, 0, NULL, + ai_stand, 0, parasite_tap, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, parasite_tap, + ai_stand, 0, NULL, + ai_stand, 0, parasite_tap, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, parasite_tap, + ai_stand, 0, NULL, + ai_stand, 0, parasite_tap +}; +mmove_t parasite_move_stand = {FRAME_stand01, FRAME_stand17, parasite_frames_stand, parasite_stand}; + +void parasite_stand (edict_t *self) +{ + self->monsterinfo.currentmove = ¶site_move_stand; +} + + +mframe_t parasite_frames_run [] = +{ + ai_run, 30, NULL, + ai_run, 30, NULL, + ai_run, 22, NULL, + ai_run, 19, NULL, + ai_run, 24, NULL, + ai_run, 28, NULL, + ai_run, 25, NULL +}; +mmove_t parasite_move_run = {FRAME_run03, FRAME_run09, parasite_frames_run, NULL}; + +mframe_t parasite_frames_start_run [] = +{ + ai_run, 0, NULL, + ai_run, 30, NULL, +}; +mmove_t parasite_move_start_run = {FRAME_run01, FRAME_run02, parasite_frames_start_run, parasite_run}; + +mframe_t parasite_frames_stop_run [] = +{ + ai_run, 20, NULL, + ai_run, 20, NULL, + ai_run, 12, NULL, + ai_run, 10, NULL, + ai_run, 0, NULL, + ai_run, 0, NULL +}; +mmove_t parasite_move_stop_run = {FRAME_run10, FRAME_run15, parasite_frames_stop_run, NULL}; + +void parasite_start_run (edict_t *self) +{ + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + self->monsterinfo.currentmove = ¶site_move_stand; + else + self->monsterinfo.currentmove = ¶site_move_start_run; +} + +void parasite_run (edict_t *self) +{ + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + self->monsterinfo.currentmove = ¶site_move_stand; + else + self->monsterinfo.currentmove = ¶site_move_run; +} + + +mframe_t parasite_frames_walk [] = +{ + ai_walk, 30, NULL, + ai_walk, 30, NULL, + ai_walk, 22, NULL, + ai_walk, 19, NULL, + ai_walk, 24, NULL, + ai_walk, 28, NULL, + ai_walk, 25, NULL +}; +mmove_t parasite_move_walk = {FRAME_run03, FRAME_run09, parasite_frames_walk, parasite_walk}; + +mframe_t parasite_frames_start_walk [] = +{ + ai_walk, 0, NULL, + ai_walk, 30, parasite_walk +}; +mmove_t parasite_move_start_walk = {FRAME_run01, FRAME_run02, parasite_frames_start_walk, NULL}; + +mframe_t parasite_frames_stop_walk [] = +{ + ai_walk, 20, NULL, + ai_walk, 20, NULL, + ai_walk, 12, NULL, + ai_walk, 10, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL +}; +mmove_t parasite_move_stop_walk = {FRAME_run10, FRAME_run15, parasite_frames_stop_walk, NULL}; + +void parasite_start_walk (edict_t *self) +{ + self->monsterinfo.currentmove = ¶site_move_start_walk; +} + +void parasite_walk (edict_t *self) +{ + self->monsterinfo.currentmove = ¶site_move_walk; +} + + +mframe_t parasite_frames_pain1 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 6, NULL, + ai_move, 16, NULL, + ai_move, -6, NULL, + ai_move, -7, NULL, + ai_move, 0, NULL +}; +mmove_t parasite_move_pain1 = {FRAME_pain101, FRAME_pain111, parasite_frames_pain1, parasite_start_run}; + +void parasite_pain (edict_t *self, edict_t *other, float kick, int damage) +{ + if (self->health < (self->max_health / 2)) + self->s.skinnum = 1; + + if (level.time < self->pain_debounce_time) + return; + + self->pain_debounce_time = level.time + 3; + + if (skill->value == 3) + return; // no pain anims in nightmare + + if (random() < 0.5) + gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); + else + gi.sound (self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0); + + self->monsterinfo.currentmove = ¶site_move_pain1; +} + + +static qboolean parasite_drain_attack_ok (vec3_t start, vec3_t end) +{ + vec3_t dir, angles; + + // check for max distance + VectorSubtract (start, end, dir); + if (VectorLength(dir) > 256) + return false; + + // check for min/max pitch + vectoangles (dir, angles); + if (angles[0] < -180) + angles[0] += 360; + if (fabs(angles[0]) > 30) + return false; + + return true; +} + +void parasite_drain_attack (edict_t *self) +{ + vec3_t offset, start, f, r, end, dir; + trace_t tr; + int damage; + + AngleVectors (self->s.angles, f, r, NULL); + VectorSet (offset, 24, 0, 6); + G_ProjectSource (self->s.origin, offset, f, r, start); + + VectorCopy (self->enemy->s.origin, end); + if (!parasite_drain_attack_ok(start, end)) + { + end[2] = self->enemy->s.origin[2] + self->enemy->maxs[2] - 8; + if (!parasite_drain_attack_ok(start, end)) + { + end[2] = self->enemy->s.origin[2] + self->enemy->mins[2] + 8; + if (!parasite_drain_attack_ok(start, end)) + return; + } + } + VectorCopy (self->enemy->s.origin, end); + + tr = gi.trace (start, NULL, NULL, end, self, MASK_SHOT); + if (tr.ent != self->enemy) + return; + + if (self->s.frame == FRAME_drain03) + { + damage = 5; + gi.sound (self->enemy, CHAN_AUTO, sound_impact, 1, ATTN_NORM, 0); + } + else + { + if (self->s.frame == FRAME_drain04) + gi.sound (self, CHAN_WEAPON, sound_suck, 1, ATTN_NORM, 0); + damage = 2; + } + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_PARASITE_ATTACK); + gi.WriteShort (self - g_edicts); + gi.WritePosition (start); + gi.WritePosition (end); + gi.multicast (self->s.origin, MULTICAST_PVS); + + VectorSubtract (start, end, dir); + T_Damage (self->enemy, self, self, dir, self->enemy->s.origin, vec3_origin, damage, 0, DAMAGE_NO_KNOCKBACK, MOD_UNKNOWN); +} + +mframe_t parasite_frames_drain [] = +{ + ai_charge, 0, parasite_launch, + ai_charge, 0, NULL, + ai_charge, 15, parasite_drain_attack, // Target hits + ai_charge, 0, parasite_drain_attack, // drain + ai_charge, 0, parasite_drain_attack, // drain + ai_charge, 0, parasite_drain_attack, // drain + ai_charge, 0, parasite_drain_attack, // drain + ai_charge, -2, parasite_drain_attack, // drain + ai_charge, -2, parasite_drain_attack, // drain + ai_charge, -3, parasite_drain_attack, // drain + ai_charge, -2, parasite_drain_attack, // drain + ai_charge, 0, parasite_drain_attack, // drain + ai_charge, -1, parasite_drain_attack, // drain + ai_charge, 0, parasite_reel_in, // let go + ai_charge, -2, NULL, + ai_charge, -2, NULL, + ai_charge, -3, NULL, + ai_charge, 0, NULL +}; +mmove_t parasite_move_drain = {FRAME_drain01, FRAME_drain18, parasite_frames_drain, parasite_start_run}; + + +mframe_t parasite_frames_break [] = +{ + ai_charge, 0, NULL, + ai_charge, -3, NULL, + ai_charge, 1, NULL, + ai_charge, 2, NULL, + ai_charge, -3, NULL, + ai_charge, 1, NULL, + ai_charge, 1, NULL, + ai_charge, 3, NULL, + ai_charge, 0, NULL, + ai_charge, -18, NULL, + ai_charge, 3, NULL, + ai_charge, 9, NULL, + ai_charge, 6, NULL, + ai_charge, 0, NULL, + ai_charge, -18, NULL, + ai_charge, 0, NULL, + ai_charge, 8, NULL, + ai_charge, 9, NULL, + ai_charge, 0, NULL, + ai_charge, -18, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, // airborne + ai_charge, 0, NULL, // airborne + ai_charge, 0, NULL, // slides + ai_charge, 0, NULL, // slides + ai_charge, 0, NULL, // slides + ai_charge, 0, NULL, // slides + ai_charge, 4, NULL, + ai_charge, 11, NULL, + ai_charge, -2, NULL, + ai_charge, -5, NULL, + ai_charge, 1, NULL +}; +mmove_t parasite_move_break = {FRAME_break01, FRAME_break32, parasite_frames_break, parasite_start_run}; + +/* +=== +Break Stuff Ends +=== +*/ + +void parasite_attack (edict_t *self) +{ +// if (random() <= 0.2) +// self->monsterinfo.currentmove = ¶site_move_break; +// else + self->monsterinfo.currentmove = ¶site_move_drain; +} + + + +/* +=== +Death Stuff Starts +=== +*/ + +void parasite_dead (edict_t *self) +{ + VectorSet (self->mins, -16, -16, -24); + VectorSet (self->maxs, 16, 16, -8); + self->movetype = MOVETYPE_TOSS; + self->svflags |= SVF_DEADMONSTER; + self->nextthink = 0; + gi.linkentity (self); +} + +mframe_t parasite_frames_death [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t parasite_move_death = {FRAME_death101, FRAME_death107, parasite_frames_death, parasite_dead}; + +void parasite_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + int n; + +// check for gib + if (self->health <= self->gib_health) + { + gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0); + for (n= 0; n < 2; n++) + ThrowGib (self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC); + for (n= 0; n < 4; n++) + ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); + ThrowHead (self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC); + self->deadflag = DEAD_DEAD; + return; + } + + if (self->deadflag == DEAD_DEAD) + return; + +// regular death + gi.sound (self, CHAN_VOICE, sound_die, 1, ATTN_NORM, 0); + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_YES; + self->monsterinfo.currentmove = ¶site_move_death; +} + +/* +=== +End Death Stuff +=== +*/ + +/*QUAKED monster_parasite (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight +*/ +void SP_monster_parasite (edict_t *self) +{ + if (deathmatch->value) + { + G_FreeEdict (self); + return; + } + + sound_pain1 = gi.soundindex ("parasite/parpain1.wav"); + sound_pain2 = gi.soundindex ("parasite/parpain2.wav"); + sound_die = gi.soundindex ("parasite/pardeth1.wav"); + sound_launch = gi.soundindex("parasite/paratck1.wav"); + sound_impact = gi.soundindex("parasite/paratck2.wav"); + sound_suck = gi.soundindex("parasite/paratck3.wav"); + sound_reelin = gi.soundindex("parasite/paratck4.wav"); + sound_sight = gi.soundindex("parasite/parsght1.wav"); + sound_tap = gi.soundindex("parasite/paridle1.wav"); + sound_scratch = gi.soundindex("parasite/paridle2.wav"); + sound_search = gi.soundindex("parasite/parsrch1.wav"); + + self->s.modelindex = gi.modelindex ("models/monsters/parasite/tris.md2"); + VectorSet (self->mins, -16, -16, -24); + VectorSet (self->maxs, 16, 16, 24); + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + + self->health = 175; + self->gib_health = -50; + self->mass = 250; + + self->pain = parasite_pain; + self->die = parasite_die; + + self->monsterinfo.stand = parasite_stand; + self->monsterinfo.walk = parasite_start_walk; + self->monsterinfo.run = parasite_start_run; + self->monsterinfo.attack = parasite_attack; + self->monsterinfo.sight = parasite_sight; + self->monsterinfo.idle = parasite_idle; + + gi.linkentity (self); + + self->monsterinfo.currentmove = ¶site_move_stand; + self->monsterinfo.scale = MODEL_SCALE; + + walkmonster_start (self); +} diff --git a/original/xatrix/m_parasite.h b/original/xatrix/m_parasite.h new file mode 100644 index 0000000..d4c4063 --- /dev/null +++ b/original/xatrix/m_parasite.h @@ -0,0 +1,126 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// G:\quake2\baseq2\models/monsters/parasite + +// This file generated by ModelGen - Do NOT Modify + +#define FRAME_break01 0 +#define FRAME_break02 1 +#define FRAME_break03 2 +#define FRAME_break04 3 +#define FRAME_break05 4 +#define FRAME_break06 5 +#define FRAME_break07 6 +#define FRAME_break08 7 +#define FRAME_break09 8 +#define FRAME_break10 9 +#define FRAME_break11 10 +#define FRAME_break12 11 +#define FRAME_break13 12 +#define FRAME_break14 13 +#define FRAME_break15 14 +#define FRAME_break16 15 +#define FRAME_break17 16 +#define FRAME_break18 17 +#define FRAME_break19 18 +#define FRAME_break20 19 +#define FRAME_break21 20 +#define FRAME_break22 21 +#define FRAME_break23 22 +#define FRAME_break24 23 +#define FRAME_break25 24 +#define FRAME_break26 25 +#define FRAME_break27 26 +#define FRAME_break28 27 +#define FRAME_break29 28 +#define FRAME_break30 29 +#define FRAME_break31 30 +#define FRAME_break32 31 +#define FRAME_death101 32 +#define FRAME_death102 33 +#define FRAME_death103 34 +#define FRAME_death104 35 +#define FRAME_death105 36 +#define FRAME_death106 37 +#define FRAME_death107 38 +#define FRAME_drain01 39 +#define FRAME_drain02 40 +#define FRAME_drain03 41 +#define FRAME_drain04 42 +#define FRAME_drain05 43 +#define FRAME_drain06 44 +#define FRAME_drain07 45 +#define FRAME_drain08 46 +#define FRAME_drain09 47 +#define FRAME_drain10 48 +#define FRAME_drain11 49 +#define FRAME_drain12 50 +#define FRAME_drain13 51 +#define FRAME_drain14 52 +#define FRAME_drain15 53 +#define FRAME_drain16 54 +#define FRAME_drain17 55 +#define FRAME_drain18 56 +#define FRAME_pain101 57 +#define FRAME_pain102 58 +#define FRAME_pain103 59 +#define FRAME_pain104 60 +#define FRAME_pain105 61 +#define FRAME_pain106 62 +#define FRAME_pain107 63 +#define FRAME_pain108 64 +#define FRAME_pain109 65 +#define FRAME_pain110 66 +#define FRAME_pain111 67 +#define FRAME_run01 68 +#define FRAME_run02 69 +#define FRAME_run03 70 +#define FRAME_run04 71 +#define FRAME_run05 72 +#define FRAME_run06 73 +#define FRAME_run07 74 +#define FRAME_run08 75 +#define FRAME_run09 76 +#define FRAME_run10 77 +#define FRAME_run11 78 +#define FRAME_run12 79 +#define FRAME_run13 80 +#define FRAME_run14 81 +#define FRAME_run15 82 +#define FRAME_stand01 83 +#define FRAME_stand02 84 +#define FRAME_stand03 85 +#define FRAME_stand04 86 +#define FRAME_stand05 87 +#define FRAME_stand06 88 +#define FRAME_stand07 89 +#define FRAME_stand08 90 +#define FRAME_stand09 91 +#define FRAME_stand10 92 +#define FRAME_stand11 93 +#define FRAME_stand12 94 +#define FRAME_stand13 95 +#define FRAME_stand14 96 +#define FRAME_stand15 97 +#define FRAME_stand16 98 +#define FRAME_stand17 99 +#define FRAME_stand18 100 +#define FRAME_stand19 101 +#define FRAME_stand20 102 +#define FRAME_stand21 103 +#define FRAME_stand22 104 +#define FRAME_stand23 105 +#define FRAME_stand24 106 +#define FRAME_stand25 107 +#define FRAME_stand26 108 +#define FRAME_stand27 109 +#define FRAME_stand28 110 +#define FRAME_stand29 111 +#define FRAME_stand30 112 +#define FRAME_stand31 113 +#define FRAME_stand32 114 +#define FRAME_stand33 115 +#define FRAME_stand34 116 +#define FRAME_stand35 117 + +#define MODEL_SCALE 1.000000 diff --git a/original/xatrix/m_player.h b/original/xatrix/m_player.h new file mode 100644 index 0000000..990a100 --- /dev/null +++ b/original/xatrix/m_player.h @@ -0,0 +1,207 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// G:\quake2\baseq2\models/player_x/frames + +// This file generated by qdata - Do NOT Modify + +#define FRAME_stand01 0 +#define FRAME_stand02 1 +#define FRAME_stand03 2 +#define FRAME_stand04 3 +#define FRAME_stand05 4 +#define FRAME_stand06 5 +#define FRAME_stand07 6 +#define FRAME_stand08 7 +#define FRAME_stand09 8 +#define FRAME_stand10 9 +#define FRAME_stand11 10 +#define FRAME_stand12 11 +#define FRAME_stand13 12 +#define FRAME_stand14 13 +#define FRAME_stand15 14 +#define FRAME_stand16 15 +#define FRAME_stand17 16 +#define FRAME_stand18 17 +#define FRAME_stand19 18 +#define FRAME_stand20 19 +#define FRAME_stand21 20 +#define FRAME_stand22 21 +#define FRAME_stand23 22 +#define FRAME_stand24 23 +#define FRAME_stand25 24 +#define FRAME_stand26 25 +#define FRAME_stand27 26 +#define FRAME_stand28 27 +#define FRAME_stand29 28 +#define FRAME_stand30 29 +#define FRAME_stand31 30 +#define FRAME_stand32 31 +#define FRAME_stand33 32 +#define FRAME_stand34 33 +#define FRAME_stand35 34 +#define FRAME_stand36 35 +#define FRAME_stand37 36 +#define FRAME_stand38 37 +#define FRAME_stand39 38 +#define FRAME_stand40 39 +#define FRAME_run1 40 +#define FRAME_run2 41 +#define FRAME_run3 42 +#define FRAME_run4 43 +#define FRAME_run5 44 +#define FRAME_run6 45 +#define FRAME_attack1 46 +#define FRAME_attack2 47 +#define FRAME_attack3 48 +#define FRAME_attack4 49 +#define FRAME_attack5 50 +#define FRAME_attack6 51 +#define FRAME_attack7 52 +#define FRAME_attack8 53 +#define FRAME_pain101 54 +#define FRAME_pain102 55 +#define FRAME_pain103 56 +#define FRAME_pain104 57 +#define FRAME_pain201 58 +#define FRAME_pain202 59 +#define FRAME_pain203 60 +#define FRAME_pain204 61 +#define FRAME_pain301 62 +#define FRAME_pain302 63 +#define FRAME_pain303 64 +#define FRAME_pain304 65 +#define FRAME_jump1 66 +#define FRAME_jump2 67 +#define FRAME_jump3 68 +#define FRAME_jump4 69 +#define FRAME_jump5 70 +#define FRAME_jump6 71 +#define FRAME_flip01 72 +#define FRAME_flip02 73 +#define FRAME_flip03 74 +#define FRAME_flip04 75 +#define FRAME_flip05 76 +#define FRAME_flip06 77 +#define FRAME_flip07 78 +#define FRAME_flip08 79 +#define FRAME_flip09 80 +#define FRAME_flip10 81 +#define FRAME_flip11 82 +#define FRAME_flip12 83 +#define FRAME_salute01 84 +#define FRAME_salute02 85 +#define FRAME_salute03 86 +#define FRAME_salute04 87 +#define FRAME_salute05 88 +#define FRAME_salute06 89 +#define FRAME_salute07 90 +#define FRAME_salute08 91 +#define FRAME_salute09 92 +#define FRAME_salute10 93 +#define FRAME_salute11 94 +#define FRAME_taunt01 95 +#define FRAME_taunt02 96 +#define FRAME_taunt03 97 +#define FRAME_taunt04 98 +#define FRAME_taunt05 99 +#define FRAME_taunt06 100 +#define FRAME_taunt07 101 +#define FRAME_taunt08 102 +#define FRAME_taunt09 103 +#define FRAME_taunt10 104 +#define FRAME_taunt11 105 +#define FRAME_taunt12 106 +#define FRAME_taunt13 107 +#define FRAME_taunt14 108 +#define FRAME_taunt15 109 +#define FRAME_taunt16 110 +#define FRAME_taunt17 111 +#define FRAME_wave01 112 +#define FRAME_wave02 113 +#define FRAME_wave03 114 +#define FRAME_wave04 115 +#define FRAME_wave05 116 +#define FRAME_wave06 117 +#define FRAME_wave07 118 +#define FRAME_wave08 119 +#define FRAME_wave09 120 +#define FRAME_wave10 121 +#define FRAME_wave11 122 +#define FRAME_point01 123 +#define FRAME_point02 124 +#define FRAME_point03 125 +#define FRAME_point04 126 +#define FRAME_point05 127 +#define FRAME_point06 128 +#define FRAME_point07 129 +#define FRAME_point08 130 +#define FRAME_point09 131 +#define FRAME_point10 132 +#define FRAME_point11 133 +#define FRAME_point12 134 +#define FRAME_crstnd01 135 +#define FRAME_crstnd02 136 +#define FRAME_crstnd03 137 +#define FRAME_crstnd04 138 +#define FRAME_crstnd05 139 +#define FRAME_crstnd06 140 +#define FRAME_crstnd07 141 +#define FRAME_crstnd08 142 +#define FRAME_crstnd09 143 +#define FRAME_crstnd10 144 +#define FRAME_crstnd11 145 +#define FRAME_crstnd12 146 +#define FRAME_crstnd13 147 +#define FRAME_crstnd14 148 +#define FRAME_crstnd15 149 +#define FRAME_crstnd16 150 +#define FRAME_crstnd17 151 +#define FRAME_crstnd18 152 +#define FRAME_crstnd19 153 +#define FRAME_crwalk1 154 +#define FRAME_crwalk2 155 +#define FRAME_crwalk3 156 +#define FRAME_crwalk4 157 +#define FRAME_crwalk5 158 +#define FRAME_crwalk6 159 +#define FRAME_crattak1 160 +#define FRAME_crattak2 161 +#define FRAME_crattak3 162 +#define FRAME_crattak4 163 +#define FRAME_crattak5 164 +#define FRAME_crattak6 165 +#define FRAME_crattak7 166 +#define FRAME_crattak8 167 +#define FRAME_crattak9 168 +#define FRAME_crpain1 169 +#define FRAME_crpain2 170 +#define FRAME_crpain3 171 +#define FRAME_crpain4 172 +#define FRAME_crdeath1 173 +#define FRAME_crdeath2 174 +#define FRAME_crdeath3 175 +#define FRAME_crdeath4 176 +#define FRAME_crdeath5 177 +#define FRAME_death101 178 +#define FRAME_death102 179 +#define FRAME_death103 180 +#define FRAME_death104 181 +#define FRAME_death105 182 +#define FRAME_death106 183 +#define FRAME_death201 184 +#define FRAME_death202 185 +#define FRAME_death203 186 +#define FRAME_death204 187 +#define FRAME_death205 188 +#define FRAME_death206 189 +#define FRAME_death301 190 +#define FRAME_death302 191 +#define FRAME_death303 192 +#define FRAME_death304 193 +#define FRAME_death305 194 +#define FRAME_death306 195 +#define FRAME_death307 196 +#define FRAME_death308 197 + +#define MODEL_SCALE 1.000000 + diff --git a/original/xatrix/m_rider.h b/original/xatrix/m_rider.h new file mode 100644 index 0000000..e7f3e7a --- /dev/null +++ b/original/xatrix/m_rider.h @@ -0,0 +1,68 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// G:\quake2\baseq2\models/monsters/boss3/rider + +// This file generated by ModelGen - Do NOT Modify + +#define FRAME_stand201 0 +#define FRAME_stand202 1 +#define FRAME_stand203 2 +#define FRAME_stand204 3 +#define FRAME_stand205 4 +#define FRAME_stand206 5 +#define FRAME_stand207 6 +#define FRAME_stand208 7 +#define FRAME_stand209 8 +#define FRAME_stand210 9 +#define FRAME_stand211 10 +#define FRAME_stand212 11 +#define FRAME_stand213 12 +#define FRAME_stand214 13 +#define FRAME_stand215 14 +#define FRAME_stand216 15 +#define FRAME_stand217 16 +#define FRAME_stand218 17 +#define FRAME_stand219 18 +#define FRAME_stand220 19 +#define FRAME_stand221 20 +#define FRAME_stand222 21 +#define FRAME_stand223 22 +#define FRAME_stand224 23 +#define FRAME_stand225 24 +#define FRAME_stand226 25 +#define FRAME_stand227 26 +#define FRAME_stand228 27 +#define FRAME_stand229 28 +#define FRAME_stand230 29 +#define FRAME_stand231 30 +#define FRAME_stand232 31 +#define FRAME_stand233 32 +#define FRAME_stand234 33 +#define FRAME_stand235 34 +#define FRAME_stand236 35 +#define FRAME_stand237 36 +#define FRAME_stand238 37 +#define FRAME_stand239 38 +#define FRAME_stand240 39 +#define FRAME_stand241 40 +#define FRAME_stand242 41 +#define FRAME_stand243 42 +#define FRAME_stand244 43 +#define FRAME_stand245 44 +#define FRAME_stand246 45 +#define FRAME_stand247 46 +#define FRAME_stand248 47 +#define FRAME_stand249 48 +#define FRAME_stand250 49 +#define FRAME_stand251 50 +#define FRAME_stand252 51 +#define FRAME_stand253 52 +#define FRAME_stand254 53 +#define FRAME_stand255 54 +#define FRAME_stand256 55 +#define FRAME_stand257 56 +#define FRAME_stand258 57 +#define FRAME_stand259 58 +#define FRAME_stand260 59 + +#define MODEL_SCALE 1.000000 diff --git a/original/xatrix/m_soldier.c b/original/xatrix/m_soldier.c new file mode 100644 index 0000000..91fb094 --- /dev/null +++ b/original/xatrix/m_soldier.c @@ -0,0 +1,2610 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +/* +============================================================================== + +SOLDIER + +============================================================================== +*/ + +#include "g_local.h" +#include "m_soldier.h" + + +static int sound_idle; +static int sound_sight1; +static int sound_sight2; +static int sound_pain_light; +static int sound_pain; +static int sound_pain_ss; +static int sound_death_light; +static int sound_death; +static int sound_death_ss; +static int sound_cock; + + +void soldier_idle (edict_t *self) +{ + if (random() > 0.8) + gi.sound (self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0); +} + +void soldier_cock (edict_t *self) +{ + if (self->s.frame == FRAME_stand322) + gi.sound (self, CHAN_WEAPON, sound_cock, 1, ATTN_IDLE, 0); + else + gi.sound (self, CHAN_WEAPON, sound_cock, 1, ATTN_NORM, 0); +} + + +// STAND + +void soldier_stand (edict_t *self); + +mframe_t soldier_frames_stand1 [] = +{ + ai_stand, 0, soldier_idle, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL +}; +mmove_t soldier_move_stand1 = {FRAME_stand101, FRAME_stand130, soldier_frames_stand1, soldier_stand}; + +mframe_t soldier_frames_stand3 [] = +{ + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + + ai_stand, 0, NULL, + ai_stand, 0, soldier_cock, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL +}; +mmove_t soldier_move_stand3 = {FRAME_stand301, FRAME_stand339, soldier_frames_stand3, soldier_stand}; + +#if 0 +mframe_t soldier_frames_stand4 [] = +{ + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 4, NULL, + ai_stand, 1, NULL, + ai_stand, -1, NULL, + ai_stand, -2, NULL, + + ai_stand, 0, NULL, + ai_stand, 0, NULL +}; +mmove_t soldier_move_stand4 = {FRAME_stand401, FRAME_stand452, soldier_frames_stand4, NULL}; +#endif + +void soldier_stand (edict_t *self) +{ + if ((self->monsterinfo.currentmove == &soldier_move_stand3) || (random() < 0.8)) + self->monsterinfo.currentmove = &soldier_move_stand1; + else + self->monsterinfo.currentmove = &soldier_move_stand3; +} + + +// +// WALK +// + +void soldier_walk1_random (edict_t *self) +{ + if (random() > 0.1) + self->monsterinfo.nextframe = FRAME_walk101; +} + +mframe_t soldier_frames_walk1 [] = +{ + ai_walk, 3, NULL, + ai_walk, 6, NULL, + ai_walk, 2, NULL, + ai_walk, 2, NULL, + ai_walk, 2, NULL, + ai_walk, 1, NULL, + ai_walk, 6, NULL, + ai_walk, 5, NULL, + ai_walk, 3, NULL, + ai_walk, -1, soldier_walk1_random, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL +}; +mmove_t soldier_move_walk1 = {FRAME_walk101, FRAME_walk133, soldier_frames_walk1, NULL}; + +mframe_t soldier_frames_walk2 [] = +{ + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 9, NULL, + ai_walk, 8, NULL, + ai_walk, 5, NULL, + ai_walk, 1, NULL, + ai_walk, 3, NULL, + ai_walk, 7, NULL, + ai_walk, 6, NULL, + ai_walk, 7, NULL +}; +mmove_t soldier_move_walk2 = {FRAME_walk209, FRAME_walk218, soldier_frames_walk2, NULL}; + +void soldier_walk (edict_t *self) +{ + if (random() < 0.5) + self->monsterinfo.currentmove = &soldier_move_walk1; + else + self->monsterinfo.currentmove = &soldier_move_walk2; +} + + +// +// RUN +// + +void soldier_run (edict_t *self); + +mframe_t soldier_frames_start_run [] = +{ + ai_run, 7, NULL, + ai_run, 5, NULL +}; +mmove_t soldier_move_start_run = {FRAME_run01, FRAME_run02, soldier_frames_start_run, soldier_run}; + +mframe_t soldier_frames_run [] = +{ + ai_run, 10, NULL, + ai_run, 11, NULL, + ai_run, 11, NULL, + ai_run, 16, NULL, + ai_run, 10, NULL, + ai_run, 15, NULL +}; +mmove_t soldier_move_run = {FRAME_run03, FRAME_run08, soldier_frames_run, NULL}; + +void soldier_run (edict_t *self) +{ + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + { + self->monsterinfo.currentmove = &soldier_move_stand1; + return; + } + + if (self->monsterinfo.currentmove == &soldier_move_walk1 || + self->monsterinfo.currentmove == &soldier_move_walk2 || + self->monsterinfo.currentmove == &soldier_move_start_run) + { + self->monsterinfo.currentmove = &soldier_move_run; + } + else + { + self->monsterinfo.currentmove = &soldier_move_start_run; + } +} + + +// +// PAIN +// + +mframe_t soldier_frames_pain1 [] = +{ + ai_move, -3, NULL, + ai_move, 4, NULL, + ai_move, 1, NULL, + ai_move, 1, NULL, + ai_move, 0, NULL +}; +mmove_t soldier_move_pain1 = {FRAME_pain101, FRAME_pain105, soldier_frames_pain1, soldier_run}; + +mframe_t soldier_frames_pain2 [] = +{ + ai_move, -13, NULL, + ai_move, -1, NULL, + ai_move, 2, NULL, + ai_move, 4, NULL, + ai_move, 2, NULL, + ai_move, 3, NULL, + ai_move, 2, NULL +}; +mmove_t soldier_move_pain2 = {FRAME_pain201, FRAME_pain207, soldier_frames_pain2, soldier_run}; + +mframe_t soldier_frames_pain3 [] = +{ + ai_move, -8, NULL, + ai_move, 10, NULL, + ai_move, -4, NULL, + ai_move, -1, NULL, + ai_move, -3, NULL, + ai_move, 0, NULL, + ai_move, 3, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 1, NULL, + ai_move, 0, NULL, + ai_move, 1, NULL, + ai_move, 2, NULL, + ai_move, 4, NULL, + ai_move, 3, NULL, + ai_move, 2, NULL +}; +mmove_t soldier_move_pain3 = {FRAME_pain301, FRAME_pain318, soldier_frames_pain3, soldier_run}; + +mframe_t soldier_frames_pain4 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, -10, NULL, + ai_move, -6, NULL, + ai_move, 8, NULL, + ai_move, 4, NULL, + ai_move, 1, NULL, + ai_move, 0, NULL, + ai_move, 2, NULL, + ai_move, 5, NULL, + ai_move, 2, NULL, + ai_move, -1, NULL, + ai_move, -1, NULL, + ai_move, 3, NULL, + ai_move, 2, NULL, + ai_move, 0, NULL +}; +mmove_t soldier_move_pain4 = {FRAME_pain401, FRAME_pain417, soldier_frames_pain4, soldier_run}; + + +void soldier_pain (edict_t *self, edict_t *other, float kick, int damage) +{ + float r; + int n; + + if (self->health < (self->max_health / 2)) + self->s.skinnum |= 1; + + if (level.time < self->pain_debounce_time) + { + if ((self->velocity[2] > 100) && ( (self->monsterinfo.currentmove == &soldier_move_pain1) || (self->monsterinfo.currentmove == &soldier_move_pain2) || (self->monsterinfo.currentmove == &soldier_move_pain3))) + self->monsterinfo.currentmove = &soldier_move_pain4; + return; + } + + self->pain_debounce_time = level.time + 3; + + n = self->s.skinnum | 1; + if (n == 1) + gi.sound (self, CHAN_VOICE, sound_pain_light, 1, ATTN_NORM, 0); + else if (n == 3) + gi.sound (self, CHAN_VOICE, sound_pain, 1, ATTN_NORM, 0); + else + gi.sound (self, CHAN_VOICE, sound_pain_ss, 1, ATTN_NORM, 0); + + if (self->velocity[2] > 100) + { + self->monsterinfo.currentmove = &soldier_move_pain4; + return; + } + + if (skill->value == 3) + return; // no pain anims in nightmare + + r = random(); + + if (r < 0.33) + self->monsterinfo.currentmove = &soldier_move_pain1; + else if (r < 0.66) + self->monsterinfo.currentmove = &soldier_move_pain2; + else + self->monsterinfo.currentmove = &soldier_move_pain3; +} + + +// +// ATTACK +// + +static int blaster_flash [] = {MZ2_SOLDIER_BLASTER_1, MZ2_SOLDIER_BLASTER_2, MZ2_SOLDIER_BLASTER_3, MZ2_SOLDIER_BLASTER_4, MZ2_SOLDIER_BLASTER_5, MZ2_SOLDIER_BLASTER_6, MZ2_SOLDIER_BLASTER_7, MZ2_SOLDIER_BLASTER_8}; +static int shotgun_flash [] = {MZ2_SOLDIER_SHOTGUN_1, MZ2_SOLDIER_SHOTGUN_2, MZ2_SOLDIER_SHOTGUN_3, MZ2_SOLDIER_SHOTGUN_4, MZ2_SOLDIER_SHOTGUN_5, MZ2_SOLDIER_SHOTGUN_6, MZ2_SOLDIER_SHOTGUN_7, MZ2_SOLDIER_SHOTGUN_8}; +static int machinegun_flash [] = {MZ2_SOLDIER_MACHINEGUN_1, MZ2_SOLDIER_MACHINEGUN_2, MZ2_SOLDIER_MACHINEGUN_3, MZ2_SOLDIER_MACHINEGUN_4, MZ2_SOLDIER_MACHINEGUN_5, MZ2_SOLDIER_MACHINEGUN_6, MZ2_SOLDIER_MACHINEGUN_7, MZ2_SOLDIER_MACHINEGUN_8}; + +void soldier_fire (edict_t *self, int flash_number) +{ + vec3_t start; + vec3_t forward, right, up; + vec3_t aim; + vec3_t dir; + vec3_t end; + float r, u; + int flash_index; + + if (self->s.skinnum < 2) + flash_index = blaster_flash[flash_number]; + else if (self->s.skinnum < 4) + flash_index = shotgun_flash[flash_number]; + else + flash_index = machinegun_flash[flash_number]; + + AngleVectors (self->s.angles, forward, right, NULL); + G_ProjectSource (self->s.origin, monster_flash_offset[flash_index], forward, right, start); + + if (flash_number == 5 || flash_number == 6) + { + VectorCopy (forward, aim); + } + else + { + VectorCopy (self->enemy->s.origin, end); + end[2] += self->enemy->viewheight; + VectorSubtract (end, start, aim); + vectoangles (aim, dir); + AngleVectors (dir, forward, right, up); + + r = crandom()*1000; + u = crandom()*500; + VectorMA (start, 8192, forward, end); + VectorMA (end, r, right, end); + VectorMA (end, u, up, end); + + VectorSubtract (end, start, aim); + VectorNormalize (aim); + } + + if (self->s.skinnum <= 1) + { + monster_fire_blaster (self, start, aim, 5, 600, flash_index, EF_BLASTER); + } + else if (self->s.skinnum <= 3) + { + monster_fire_shotgun (self, start, aim, 2, 1, DEFAULT_SHOTGUN_HSPREAD, DEFAULT_SHOTGUN_VSPREAD, DEFAULT_SHOTGUN_COUNT, flash_index); + } + else + { + if (!(self->monsterinfo.aiflags & AI_HOLD_FRAME)) + self->monsterinfo.pausetime = level.time + (3 + rand() % 8) * FRAMETIME; + + monster_fire_bullet (self, start, aim, 2, 4, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, flash_index); + + if (level.time >= self->monsterinfo.pausetime) + self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; + else + self->monsterinfo.aiflags |= AI_HOLD_FRAME; + } +} + +// ATTACK1 (blaster/shotgun) + +void soldier_fire1 (edict_t *self) +{ + soldier_fire (self, 0); +} + +void soldier_attack1_refire1 (edict_t *self) +{ + if (self->s.skinnum > 1) + return; + + if (self->enemy->health <= 0) + return; + + if ( ((skill->value == 3) && (random() < 0.5)) || (range(self, self->enemy) == RANGE_MELEE) ) + self->monsterinfo.nextframe = FRAME_attak102; + else + self->monsterinfo.nextframe = FRAME_attak110; +} + +void soldier_attack1_refire2 (edict_t *self) +{ + if (self->s.skinnum < 2) + return; + + if (self->enemy->health <= 0) + return; + + if ( ((skill->value == 3) && (random() < 0.5)) || (range(self, self->enemy) == RANGE_MELEE) ) + self->monsterinfo.nextframe = FRAME_attak102; +} + +mframe_t soldier_frames_attack1 [] = +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, soldier_fire1, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, soldier_attack1_refire1, + ai_charge, 0, NULL, + ai_charge, 0, soldier_cock, + ai_charge, 0, soldier_attack1_refire2, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL +}; +mmove_t soldier_move_attack1 = {FRAME_attak101, FRAME_attak112, soldier_frames_attack1, soldier_run}; + +// ATTACK2 (blaster/shotgun) + +void soldier_fire2 (edict_t *self) +{ + soldier_fire (self, 1); +} + +void soldier_attack2_refire1 (edict_t *self) +{ + if (self->s.skinnum > 1) + return; + + if (self->enemy->health <= 0) + return; + + if ( ((skill->value == 3) && (random() < 0.5)) || (range(self, self->enemy) == RANGE_MELEE) ) + self->monsterinfo.nextframe = FRAME_attak204; + else + self->monsterinfo.nextframe = FRAME_attak216; +} + +void soldier_attack2_refire2 (edict_t *self) +{ + if (self->s.skinnum < 2) + return; + + if (self->enemy->health <= 0) + return; + + if ( ((skill->value == 3) && (random() < 0.5)) || (range(self, self->enemy) == RANGE_MELEE) ) + self->monsterinfo.nextframe = FRAME_attak204; +} + +mframe_t soldier_frames_attack2 [] = +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, soldier_fire2, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, soldier_attack2_refire1, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, soldier_cock, + ai_charge, 0, NULL, + ai_charge, 0, soldier_attack2_refire2, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL +}; +mmove_t soldier_move_attack2 = {FRAME_attak201, FRAME_attak218, soldier_frames_attack2, soldier_run}; + +// ATTACK3 (duck and shoot) + +void soldier_duck_down (edict_t *self) +{ + if (self->monsterinfo.aiflags & AI_DUCKED) + return; + self->monsterinfo.aiflags |= AI_DUCKED; + self->maxs[2] -= 32; + self->takedamage = DAMAGE_YES; + self->monsterinfo.pausetime = level.time + 1; + gi.linkentity (self); +} + +void soldier_duck_up (edict_t *self) +{ + self->monsterinfo.aiflags &= ~AI_DUCKED; + self->maxs[2] += 32; + self->takedamage = DAMAGE_AIM; + gi.linkentity (self); +} + +void soldier_fire3 (edict_t *self) +{ + soldier_duck_down (self); + soldier_fire (self, 2); +} + +void soldier_attack3_refire (edict_t *self) +{ + if ((level.time + 0.4) < self->monsterinfo.pausetime) + self->monsterinfo.nextframe = FRAME_attak303; +} + +mframe_t soldier_frames_attack3 [] = +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, soldier_fire3, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, soldier_attack3_refire, + ai_charge, 0, soldier_duck_up, + ai_charge, 0, NULL, + ai_charge, 0, NULL +}; +mmove_t soldier_move_attack3 = {FRAME_attak301, FRAME_attak309, soldier_frames_attack3, soldier_run}; + +// ATTACK4 (machinegun) + +void soldier_fire4 (edict_t *self) +{ + soldier_fire (self, 3); +// +// if (self->enemy->health <= 0) +// return; +// +// if ( ((skill->value == 3) && (random() < 0.5)) || (range(self, self->enemy) == RANGE_MELEE) ) +// self->monsterinfo.nextframe = FRAME_attak402; +} + +mframe_t soldier_frames_attack4 [] = +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, soldier_fire4, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL +}; +mmove_t soldier_move_attack4 = {FRAME_attak401, FRAME_attak406, soldier_frames_attack4, soldier_run}; + +#if 0 +// ATTACK5 (prone) + +void soldier_fire5 (edict_t *self) +{ + soldier_fire (self, 4); +} + +void soldier_attack5_refire (edict_t *self) +{ + if (self->enemy->health <= 0) + return; + + if ( ((skill->value == 3) && (random() < 0.5)) || (range(self, self->enemy) == RANGE_MELEE) ) + self->monsterinfo.nextframe = FRAME_attak505; +} + +mframe_t soldier_frames_attack5 [] = +{ + ai_charge, 8, NULL, + ai_charge, 8, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, soldier_fire5, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, soldier_attack5_refire +}; +mmove_t soldier_move_attack5 = {FRAME_attak501, FRAME_attak508, soldier_frames_attack5, soldier_run}; +#endif + +// ATTACK6 (run & shoot) + +void soldier_fire8 (edict_t *self) +{ + soldier_fire (self, 7); +} + +void soldier_attack6_refire (edict_t *self) +{ + if (self->enemy->health <= 0) + return; + + if (range(self, self->enemy) < RANGE_MID) + return; + + if (skill->value == 3) + self->monsterinfo.nextframe = FRAME_runs03; +} + +mframe_t soldier_frames_attack6 [] = +{ + ai_charge, 10, NULL, + ai_charge, 4, NULL, + ai_charge, 12, NULL, + ai_charge, 11, soldier_fire8, + ai_charge, 13, NULL, + ai_charge, 18, NULL, + ai_charge, 15, NULL, + ai_charge, 14, NULL, + ai_charge, 11, NULL, + ai_charge, 8, NULL, + ai_charge, 11, NULL, + ai_charge, 12, NULL, + ai_charge, 12, NULL, + ai_charge, 17, soldier_attack6_refire +}; +mmove_t soldier_move_attack6 = {FRAME_runs01, FRAME_runs14, soldier_frames_attack6, soldier_run}; + +void soldier_attack(edict_t *self) +{ + if (self->s.skinnum < 4) + { + if (random() < 0.5) + self->monsterinfo.currentmove = &soldier_move_attack1; + else + self->monsterinfo.currentmove = &soldier_move_attack2; + } + else + { + self->monsterinfo.currentmove = &soldier_move_attack4; + } +} + + +// +// SIGHT +// + +void soldier_sight(edict_t *self, edict_t *other) +{ + if (random() < 0.5) + gi.sound (self, CHAN_VOICE, sound_sight1, 1, ATTN_NORM, 0); + else + gi.sound (self, CHAN_VOICE, sound_sight2, 1, ATTN_NORM, 0); + + if ((skill->value > 0) && (range(self, self->enemy) >= RANGE_MID)) + { + if (random() > 0.5) + self->monsterinfo.currentmove = &soldier_move_attack6; + } +} + +// +// DUCK +// + +void soldier_duck_hold (edict_t *self) +{ + if (level.time >= self->monsterinfo.pausetime) + self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; + else + self->monsterinfo.aiflags |= AI_HOLD_FRAME; +} + +mframe_t soldier_frames_duck [] = +{ + ai_move, 5, soldier_duck_down, + ai_move, -1, soldier_duck_hold, + ai_move, 1, NULL, + ai_move, 0, soldier_duck_up, + ai_move, 5, NULL +}; +mmove_t soldier_move_duck = {FRAME_duck01, FRAME_duck05, soldier_frames_duck, soldier_run}; + +void soldier_dodge (edict_t *self, edict_t *attacker, float eta) +{ + float r; + + r = random(); + if (r > 0.25) + return; + + if (!self->enemy) + self->enemy = attacker; + + if (skill->value == 0) + { + self->monsterinfo.currentmove = &soldier_move_duck; + return; + } + + self->monsterinfo.pausetime = level.time + eta + 0.3; + r = random(); + + if (skill->value == 1) + { + if (r > 0.33) + self->monsterinfo.currentmove = &soldier_move_duck; + else + self->monsterinfo.currentmove = &soldier_move_attack3; + return; + } + + if (skill->value >= 2) + { + if (r > 0.66) + self->monsterinfo.currentmove = &soldier_move_duck; + else + self->monsterinfo.currentmove = &soldier_move_attack3; + return; + } + + self->monsterinfo.currentmove = &soldier_move_attack3; +} + + +// +// DEATH +// + +void soldier_fire6 (edict_t *self) +{ + soldier_fire (self, 5); +} + +void soldier_fire7 (edict_t *self) +{ + soldier_fire (self, 6); +} + +void soldier_dead (edict_t *self) +{ + VectorSet (self->mins, -16, -16, -24); + VectorSet (self->maxs, 16, 16, -8); + self->movetype = MOVETYPE_TOSS; + self->svflags |= SVF_DEADMONSTER; + self->nextthink = 0; + gi.linkentity (self); +} + +mframe_t soldier_frames_death1 [] = +{ + ai_move, 0, NULL, + ai_move, -10, NULL, + ai_move, -10, NULL, + ai_move, -10, NULL, + ai_move, -5, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 0, NULL, + ai_move, 0, soldier_fire6, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, soldier_fire7, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t soldier_move_death1 = {FRAME_death101, FRAME_death136, soldier_frames_death1, soldier_dead}; + +mframe_t soldier_frames_death2 [] = +{ + ai_move, -5, NULL, + ai_move, -5, NULL, + ai_move, -5, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t soldier_move_death2 = {FRAME_death201, FRAME_death235, soldier_frames_death2, soldier_dead}; + +mframe_t soldier_frames_death3 [] = +{ + ai_move, -5, NULL, + ai_move, -5, NULL, + ai_move, -5, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, +}; +mmove_t soldier_move_death3 = {FRAME_death301, FRAME_death345, soldier_frames_death3, soldier_dead}; + +mframe_t soldier_frames_death4 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t soldier_move_death4 = {FRAME_death401, FRAME_death453, soldier_frames_death4, soldier_dead}; + +mframe_t soldier_frames_death5 [] = +{ + ai_move, -5, NULL, + ai_move, -5, NULL, + ai_move, -5, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t soldier_move_death5 = {FRAME_death501, FRAME_death524, soldier_frames_death5, soldier_dead}; + +mframe_t soldier_frames_death6 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t soldier_move_death6 = {FRAME_death601, FRAME_death610, soldier_frames_death6, soldier_dead}; + +void soldier_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + int n; + +// check for gib + if (self->health <= self->gib_health) + { + gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0); + for (n= 0; n < 3; n++) + ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); + ThrowGib (self, "models/objects/gibs/chest/tris.md2", damage, GIB_ORGANIC); + ThrowHead (self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC); + self->deadflag = DEAD_DEAD; + return; + } + + if (self->deadflag == DEAD_DEAD) + return; + +// regular death + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_YES; + self->s.skinnum |= 1; + + if (self->s.skinnum == 1) + gi.sound (self, CHAN_VOICE, sound_death_light, 1, ATTN_NORM, 0); + else if (self->s.skinnum == 3) + gi.sound (self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0); + else // (self->s.skinnum == 5) + gi.sound (self, CHAN_VOICE, sound_death_ss, 1, ATTN_NORM, 0); + + if (fabs((self->s.origin[2] + self->viewheight) - point[2]) <= 4) + { + // head shot + self->monsterinfo.currentmove = &soldier_move_death3; + return; + } + + n = rand() % 5; + if (n == 0) + self->monsterinfo.currentmove = &soldier_move_death1; + else if (n == 1) + self->monsterinfo.currentmove = &soldier_move_death2; + else if (n == 2) + self->monsterinfo.currentmove = &soldier_move_death4; + else if (n == 3) + self->monsterinfo.currentmove = &soldier_move_death5; + else + self->monsterinfo.currentmove = &soldier_move_death6; +} + + +// +// SPAWN +// + +void SP_monster_soldier_x (edict_t *self) +{ + + self->s.modelindex = gi.modelindex ("models/monsters/soldier/tris.md2"); + self->monsterinfo.scale = MODEL_SCALE; + VectorSet (self->mins, -16, -16, -24); + VectorSet (self->maxs, 16, 16, 32); + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + + sound_idle = gi.soundindex ("soldier/solidle1.wav"); + sound_sight1 = gi.soundindex ("soldier/solsght1.wav"); + sound_sight2 = gi.soundindex ("soldier/solsrch1.wav"); + sound_cock = gi.soundindex ("infantry/infatck3.wav"); + + self->mass = 100; + + self->pain = soldier_pain; + self->die = soldier_die; + + self->monsterinfo.stand = soldier_stand; + self->monsterinfo.walk = soldier_walk; + self->monsterinfo.run = soldier_run; + self->monsterinfo.dodge = soldier_dodge; + self->monsterinfo.attack = soldier_attack; + self->monsterinfo.melee = NULL; + self->monsterinfo.sight = soldier_sight; + + gi.linkentity (self); + + self->monsterinfo.stand (self); + + walkmonster_start (self); +} + + +/*QUAKED monster_soldier_light (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight +*/ +void SP_monster_soldier_light (edict_t *self) +{ + if (deathmatch->value) + { + G_FreeEdict (self); + return; + } + + SP_monster_soldier_x (self); + + sound_pain_light = gi.soundindex ("soldier/solpain2.wav"); + sound_death_light = gi.soundindex ("soldier/soldeth2.wav"); + gi.modelindex ("models/objects/laser/tris.md2"); + gi.soundindex ("misc/lasfly.wav"); + gi.soundindex ("soldier/solatck2.wav"); + + self->s.skinnum = 0; + self->health = 20; + self->gib_health = -30; +} + +/*QUAKED monster_soldier (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight +*/ +void SP_monster_soldier (edict_t *self) +{ + if (deathmatch->value) + { + G_FreeEdict (self); + return; + } + + SP_monster_soldier_x (self); + + sound_pain = gi.soundindex ("soldier/solpain1.wav"); + sound_death = gi.soundindex ("soldier/soldeth1.wav"); + gi.soundindex ("soldier/solatck1.wav"); + + self->s.skinnum = 2; + self->health = 30; + self->gib_health = -30; +} + +/*QUAKED monster_soldier_ss (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight +*/ +void SP_monster_soldier_ss (edict_t *self) +{ + if (deathmatch->value) + { + G_FreeEdict (self); + return; + } + + SP_monster_soldier_x (self); + + sound_pain_ss = gi.soundindex ("soldier/solpain3.wav"); + sound_death_ss = gi.soundindex ("soldier/soldeth3.wav"); + gi.soundindex ("soldier/solatck3.wav"); + + self->s.skinnum = 4; + self->health = 40; + self->gib_health = -30; +} + + +// RAFAEL 13-APR-98 + + +#include "m_soldierh.h" + +void soldierh_idle (edict_t *self) +{ + if (random() > 0.8) + gi.sound (self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0); +} + +void soldierh_cock (edict_t *self) +{ + if (self->s.frame == FRAME_stand322) + gi.sound (self, CHAN_WEAPON, sound_cock, 1, ATTN_IDLE, 0); + else + gi.sound (self, CHAN_WEAPON, sound_cock, 1, ATTN_NORM, 0); +} + + +// STAND + +void soldierh_stand (edict_t *self); + +mframe_t soldierh_frames_stand1 [] = +{ + ai_stand, 0, soldierh_idle, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL +}; +mmove_t soldierh_move_stand1 = {FRAME_stand101, FRAME_stand130, soldierh_frames_stand1, soldierh_stand}; + +mframe_t soldierh_frames_stand3 [] = +{ + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + + ai_stand, 0, NULL, + ai_stand, 0, soldierh_cock, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL +}; +mmove_t soldierh_move_stand3 = {FRAME_stand301, FRAME_stand339, soldierh_frames_stand3, soldierh_stand}; + + +void soldierh_stand (edict_t *self) +{ + if ((self->monsterinfo.currentmove == &soldierh_move_stand3) || (random() < 0.8)) + self->monsterinfo.currentmove = &soldierh_move_stand1; + else + self->monsterinfo.currentmove = &soldierh_move_stand3; +} + + +// +// WALK +// + +void soldierh_walk1_random (edict_t *self) +{ + if (random() > 0.1) + self->monsterinfo.nextframe = FRAME_walk101; +} + +mframe_t soldierh_frames_walk1 [] = +{ + ai_walk, 3, NULL, + ai_walk, 6, NULL, + ai_walk, 2, NULL, + ai_walk, 2, NULL, + ai_walk, 2, NULL, + ai_walk, 1, NULL, + ai_walk, 6, NULL, + ai_walk, 5, NULL, + ai_walk, 3, NULL, + ai_walk, -1, soldierh_walk1_random, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL +}; +mmove_t soldierh_move_walk1 = {FRAME_walk101, FRAME_walk133, soldierh_frames_walk1, NULL}; + +mframe_t soldierh_frames_walk2 [] = +{ + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 9, NULL, + ai_walk, 8, NULL, + ai_walk, 5, NULL, + ai_walk, 1, NULL, + ai_walk, 3, NULL, + ai_walk, 7, NULL, + ai_walk, 6, NULL, + ai_walk, 7, NULL +}; +mmove_t soldierh_move_walk2 = {FRAME_walk209, FRAME_walk218, soldierh_frames_walk2, NULL}; + +void soldierh_walk (edict_t *self) +{ + if (random() < 0.5) + self->monsterinfo.currentmove = &soldierh_move_walk1; + else + self->monsterinfo.currentmove = &soldierh_move_walk2; +} + + +// +// RUN +// + +void soldierh_run (edict_t *self); + +mframe_t soldierh_frames_start_run [] = +{ + ai_run, 7, NULL, + ai_run, 5, NULL +}; +mmove_t soldierh_move_start_run = {FRAME_run01, FRAME_run02, soldierh_frames_start_run, soldierh_run}; + +mframe_t soldierh_frames_run [] = +{ + ai_run, 10, NULL, + ai_run, 11, NULL, + ai_run, 11, NULL, + ai_run, 16, NULL, + ai_run, 10, NULL, + ai_run, 15, NULL +}; +mmove_t soldierh_move_run = {FRAME_run03, FRAME_run08, soldierh_frames_run, NULL}; + +void soldierh_run (edict_t *self) +{ + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + { + self->monsterinfo.currentmove = &soldierh_move_stand1; + return; + } + + if (self->monsterinfo.currentmove == &soldierh_move_walk1 || + self->monsterinfo.currentmove == &soldierh_move_walk2 || + self->monsterinfo.currentmove == &soldierh_move_start_run) + { + self->monsterinfo.currentmove = &soldierh_move_run; + } + else + { + self->monsterinfo.currentmove = &soldierh_move_start_run; + } +} + + +// +// PAIN +// + +mframe_t soldierh_frames_pain1 [] = +{ + ai_move, -3, NULL, + ai_move, 4, NULL, + ai_move, 1, NULL, + ai_move, 1, NULL, + ai_move, 0, NULL +}; +mmove_t soldierh_move_pain1 = {FRAME_pain101, FRAME_pain105, soldierh_frames_pain1, soldierh_run}; + +mframe_t soldierh_frames_pain2 [] = +{ + ai_move, -13, NULL, + ai_move, -1, NULL, + ai_move, 2, NULL, + ai_move, 4, NULL, + ai_move, 2, NULL, + ai_move, 3, NULL, + ai_move, 2, NULL +}; +mmove_t soldierh_move_pain2 = {FRAME_pain201, FRAME_pain207, soldierh_frames_pain2, soldierh_run}; + +mframe_t soldierh_frames_pain3 [] = +{ + ai_move, -8, NULL, + ai_move, 10, NULL, + ai_move, -4, NULL, + ai_move, -1, NULL, + ai_move, -3, NULL, + ai_move, 0, NULL, + ai_move, 3, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 1, NULL, + ai_move, 0, NULL, + ai_move, 1, NULL, + ai_move, 2, NULL, + ai_move, 4, NULL, + ai_move, 3, NULL, + ai_move, 2, NULL +}; +mmove_t soldierh_move_pain3 = {FRAME_pain301, FRAME_pain318, soldierh_frames_pain3, soldierh_run}; + +mframe_t soldierh_frames_pain4 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, -10, NULL, + ai_move, -6, NULL, + ai_move, 8, NULL, + ai_move, 4, NULL, + ai_move, 1, NULL, + ai_move, 0, NULL, + ai_move, 2, NULL, + ai_move, 5, NULL, + ai_move, 2, NULL, + ai_move, -1, NULL, + ai_move, -1, NULL, + ai_move, 3, NULL, + ai_move, 2, NULL, + ai_move, 0, NULL +}; +mmove_t soldierh_move_pain4 = {FRAME_pain401, FRAME_pain417, soldierh_frames_pain4, soldierh_run}; + +void soldierh_pain (edict_t *self, edict_t *other, float kick, int damage) +{ + float r; + int n; + + if (self->health < (self->max_health / 2)) + self->s.skinnum |= 1; + + if (level.time < self->pain_debounce_time) + { + if ((self->velocity[2] > 100) && ( (self->monsterinfo.currentmove == &soldierh_move_pain1) || (self->monsterinfo.currentmove == &soldierh_move_pain2) || (self->monsterinfo.currentmove == &soldierh_move_pain3))) + self->monsterinfo.currentmove = &soldierh_move_pain4; + return; + } + + self->pain_debounce_time = level.time + 3; + + n = self->s.skinnum | 1; + if (n == 1) + gi.sound (self, CHAN_VOICE, sound_pain_light, 1, ATTN_NORM, 0); + else if (n == 3) + gi.sound (self, CHAN_VOICE, sound_pain, 1, ATTN_NORM, 0); + else + gi.sound (self, CHAN_VOICE, sound_pain_ss, 1, ATTN_NORM, 0); + + if (self->velocity[2] > 100) + { + self->monsterinfo.currentmove = &soldierh_move_pain4; + return; + } + + if (skill->value == 3) + return; // no pain anims in nightmare + + r = random(); + + if (r < 0.33) + self->monsterinfo.currentmove = &soldierh_move_pain1; + else if (r < 0.66) + self->monsterinfo.currentmove = &soldierh_move_pain2; + else + self->monsterinfo.currentmove = &soldierh_move_pain3; +} + + +// +// ATTACK +// + +extern void brain_dabeam (edict_t *self); + +void soldierh_laserbeam (edict_t *self, int flash_index) +{ + + vec3_t forward, right, up; + vec3_t tempang, start; + vec3_t dir, angles, end; + vec3_t tempvec; + edict_t *ent; + + // RAFAEL + // this sound can't be called this frequent + if (random() > 0.8) + gi.sound(self, CHAN_AUTO, gi.soundindex("misc/lasfly.wav"), 1, ATTN_STATIC, 0); + + VectorCopy (self->s.origin, start); + VectorCopy (self->enemy->s.origin, end); + VectorSubtract (end, start, dir); + vectoangles (dir, angles); + VectorCopy (monster_flash_offset[flash_index], tempvec); + + ent = G_Spawn (); + VectorCopy (self->s.origin, ent->s.origin); + VectorCopy (angles, tempang); + AngleVectors (tempang, forward, right, up); + VectorCopy (tempang, ent->s.angles); + VectorCopy (ent->s.origin, start); + + if (flash_index == 85) + { + VectorMA (start, tempvec[0]-14, right, start); + VectorMA (start, tempvec[2]+8, up, start); + VectorMA (start, tempvec[1], forward, start); + } + else + { + VectorMA (start, tempvec[0]+2, right, start); + VectorMA (start, tempvec[2]+8, up, start); + VectorMA (start, tempvec[1], forward, start); + } + + VectorCopy (start, ent->s.origin); + ent->enemy = self->enemy; + ent->owner = self; + + ent->dmg = 1; + + monster_dabeam (ent); + +} + + +void soldierh_fire (edict_t *self, int flash_number) +{ + vec3_t start; + vec3_t forward, right, up; + vec3_t aim; + vec3_t dir; + vec3_t end; + float r, u; + int flash_index; + qboolean tone = true; + + if (self->s.skinnum < 2) + flash_index = blaster_flash[flash_number]; // ripper + else if (self->s.skinnum < 4) + flash_index = blaster_flash[flash_number]; // hyperblaster + else + flash_index = machinegun_flash[flash_number]; // laserbeam + + AngleVectors (self->s.angles, forward, right, NULL); + G_ProjectSource (self->s.origin, monster_flash_offset[flash_index], forward, right, start); + + if (flash_number == 5 || flash_number == 6) + { + VectorCopy (forward, aim); + } + else + { + VectorCopy (self->enemy->s.origin, end); + end[2] += self->enemy->viewheight; + VectorSubtract (end, start, aim); + vectoangles (aim, dir); + AngleVectors (dir, forward, right, up); + + r = crandom()*100; + u = crandom()*50; + VectorMA (start, 8192, forward, end); + VectorMA (end, r, right, end); + VectorMA (end, u, up, end); + + VectorSubtract (end, start, aim); + VectorNormalize (aim); + } + + if (self->s.skinnum <= 1) + { + // RAFAEL 24-APR-98 + // droped the damage from 15 to 5 + monster_fire_ionripper (self, start, aim, 5, 600, flash_index, EF_IONRIPPER); + + } + else if (self->s.skinnum <= 3) + { + + monster_fire_blueblaster (self, start, aim, 1, 600, MZ_BLUEHYPERBLASTER, EF_BLUEHYPERBLASTER); + + } + else + { + if (!(self->monsterinfo.aiflags & AI_HOLD_FRAME)) + self->monsterinfo.pausetime = level.time + (3 + rand() % 8) * FRAMETIME; + + soldierh_laserbeam (self, flash_index); + + if (level.time >= self->monsterinfo.pausetime) + self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; + else + self->monsterinfo.aiflags |= AI_HOLD_FRAME; + } +} + +// ATTACK1 (blaster/shotgun) + +void soldierh_hyper_refire1 (edict_t *self) +{ + if (self->s.skinnum < 2) + return; + else if (self->s.skinnum < 4) + { + if (random() < 0.7) + self->s.frame = FRAME_attak103; + else + gi.sound(self, CHAN_AUTO, gi.soundindex("weapons/hyprbd1a.wav"), 1, ATTN_NORM, 0); + } +} + +void soldierh_ripper1 (edict_t *self) +{ + if (self->s.skinnum < 2) + soldierh_fire (self, 0); + else if (self->s.skinnum < 4) + soldierh_fire (self, 0); +} + + +void soldierh_fire1 (edict_t *self) +{ + soldierh_fire (self, 0); +} + +void soldierh_attack1_refire1 (edict_t *self) +{ + if (self->s.skinnum > 1) + return; + + if (self->enemy->health <= 0) + return; + + if ( ((skill->value == 3) && (random() < 0.5)) || (range(self, self->enemy) == RANGE_MELEE) ) + self->monsterinfo.nextframe = FRAME_attak102; + else + self->monsterinfo.nextframe = FRAME_attak110; +} + +void soldierh_attack1_refire2 (edict_t *self) +{ + if (self->s.skinnum < 2) + return; + + if (self->enemy->health <= 0) + return; + + if ( ((skill->value == 3) && (random() < 0.5)) || (range(self, self->enemy) == RANGE_MELEE) ) + self->monsterinfo.nextframe = FRAME_attak102; +} + +void soldierh_hyper_sound (edict_t *self) +{ + if (self->s.skinnum < 2) + return; + else if (self->s.skinnum < 4) + gi.sound(self, CHAN_AUTO, gi.soundindex("weapons/hyprbl1a.wav"), 1, ATTN_NORM, 0); + else + return; +} + +mframe_t soldierh_frames_attack1 [] = +{ + ai_charge, 0, NULL, + ai_charge, 0, soldierh_hyper_sound, + ai_charge, 0, soldierh_fire1, + ai_charge, 0, soldierh_ripper1, + ai_charge, 0, soldierh_ripper1, + ai_charge, 0, soldierh_attack1_refire1, + ai_charge, 0, soldierh_hyper_refire1, + ai_charge, 0, soldierh_cock, + ai_charge, 0, soldierh_attack1_refire2, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL +}; +mmove_t soldierh_move_attack1 = {FRAME_attak101, FRAME_attak112, soldierh_frames_attack1, soldierh_run}; + +// ATTACK2 (blaster/shotgun) + +void soldierh_hyper_refire2 (edict_t *self) +{ + if (self->s.skinnum < 2) + return; + else if (self->s.skinnum < 4) + { + if (random() < 0.7) + self->s.frame = FRAME_attak205; + else + gi.sound(self, CHAN_AUTO, gi.soundindex("weapons/hyprbd1a.wav"), 1, ATTN_NORM, 0); + } +} + +void soldierh_ripper2 (edict_t *self) +{ + if (self->s.skinnum < 2) + soldierh_fire (self, 1); + else if (self->s.skinnum < 4) + soldierh_fire (self, 1); +} + +void soldierh_fire2 (edict_t *self) +{ + soldierh_fire (self, 1); +} + + +void soldierh_attack2_refire1 (edict_t *self) +{ + if (self->s.skinnum > 1) + return; + + if (self->enemy->health <= 0) + return; + + if ( ((skill->value == 3) && (random() < 0.5)) || (range(self, self->enemy) == RANGE_MELEE)) + self->monsterinfo.nextframe = FRAME_attak204; + else + self->monsterinfo.nextframe = FRAME_attak216; +} + +void soldierh_attack2_refire2 (edict_t *self) +{ + if (self->s.skinnum < 2) + return; + + if (self->enemy->health <= 0) + return; + + if ( ((skill->value == 3) && (random() < 0.5)) || (range(self, self->enemy) == RANGE_MELEE) && self->s.skinnum < 4) + self->monsterinfo.nextframe = FRAME_attak204; +} + +mframe_t soldierh_frames_attack2 [] = +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, soldierh_hyper_sound, + ai_charge, 0, soldierh_fire2, + ai_charge, 0, soldierh_ripper2, + ai_charge, 0, soldierh_ripper2, + ai_charge, 0, soldierh_attack2_refire1, + ai_charge, 0, soldierh_hyper_refire2, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, soldierh_cock, + ai_charge, 0, NULL, + ai_charge, 0, soldierh_attack2_refire2, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL +}; +mmove_t soldierh_move_attack2 = {FRAME_attak201, FRAME_attak218, soldierh_frames_attack2, soldierh_run}; + +// ATTACK3 (duck and shoot) + +void soldierh_duck_down (edict_t *self) +{ + if (self->monsterinfo.aiflags & AI_DUCKED) + return; + self->monsterinfo.aiflags |= AI_DUCKED; + self->maxs[2] -= 32; + self->takedamage = DAMAGE_YES; + self->monsterinfo.pausetime = level.time + 1; + gi.linkentity (self); +} + +void soldierh_duck_up (edict_t *self) +{ + self->monsterinfo.aiflags &= ~AI_DUCKED; + self->maxs[2] += 32; + self->takedamage = DAMAGE_AIM; + gi.linkentity (self); +} + +void soldierh_fire3 (edict_t *self) +{ + soldierh_duck_down (self); + soldierh_fire (self, 2); +} + +void soldierh_attack3_refire (edict_t *self) +{ + if ((level.time + 0.4) < self->monsterinfo.pausetime) + self->monsterinfo.nextframe = FRAME_attak303; +} + +mframe_t soldierh_frames_attack3 [] = +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, soldierh_fire3, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, soldierh_attack3_refire, + ai_charge, 0, soldierh_duck_up, + ai_charge, 0, NULL, + ai_charge, 0, NULL +}; +mmove_t soldierh_move_attack3 = {FRAME_attak301, FRAME_attak309, soldierh_frames_attack3, soldierh_run}; + +// ATTACK4 (machinegun) + +void soldierh_fire4 (edict_t *self) +{ + soldierh_fire (self, 3); +// +// if (self->enemy->health <= 0) +// return; +// +// if ( ((skill->value == 3) && (random() < 0.5)) || (range(self, self->enemy) == RANGE_MELEE) ) +// self->monsterinfo.nextframe = FRAME_attak402; +} + +mframe_t soldierh_frames_attack4 [] = +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, soldierh_fire4, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL +}; +mmove_t soldierh_move_attack4 = {FRAME_attak401, FRAME_attak406, soldierh_frames_attack4, soldierh_run}; + +#if 0 +// ATTACK5 (prone) + +void soldierh_fire5 (edict_t *self) +{ + soldierh_fire (self, 4); +} + +void soldierh_attack5_refire (edict_t *self) +{ + if (self->enemy->health <= 0) + return; + + if ( ((skill->value == 3) && (random() < 0.5)) || (range(self, self->enemy) == RANGE_MELEE) ) + self->monsterinfo.nextframe = FRAME_attak505; +} + +mframe_t soldierh_frames_attack5 [] = +{ + ai_charge, 8, NULL, + ai_charge, 8, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, soldierh_fire5, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, soldierh_attack5_refire +}; +mmove_t soldierh_move_attack5 = {FRAME_attak501, FRAME_attak508, soldierh_frames_attack5, soldierh_run}; +#endif + +// ATTACK6 (run & shoot) + +void soldierh_fire8 (edict_t *self) +{ + soldierh_fire (self, 7); +} + +void soldierh_attack6_refire (edict_t *self) +{ + if (self->enemy->health <= 0) + return; + + if (range(self, self->enemy) < RANGE_MID) + return; + + if (skill->value == 3) + self->monsterinfo.nextframe = FRAME_runs03; +} + +mframe_t soldierh_frames_attack6 [] = +{ + ai_charge, 10, NULL, + ai_charge, 4, NULL, + ai_charge, 12, NULL, + ai_charge, 11, soldierh_fire8, + ai_charge, 13, NULL, + ai_charge, 18, NULL, + ai_charge, 15, NULL, + ai_charge, 14, NULL, + ai_charge, 11, NULL, + ai_charge, 8, NULL, + ai_charge, 11, NULL, + ai_charge, 12, NULL, + ai_charge, 12, NULL, + ai_charge, 17, soldierh_attack6_refire +}; +mmove_t soldierh_move_attack6 = {FRAME_runs01, FRAME_runs14, soldierh_frames_attack6, soldierh_run}; + +void soldierh_attack(edict_t *self) +{ + if (self->s.skinnum < 4) + { + if (random() < 0.5) + self->monsterinfo.currentmove = &soldierh_move_attack1; + else + self->monsterinfo.currentmove = &soldierh_move_attack2; + } + else + { + self->monsterinfo.currentmove = &soldierh_move_attack4; + } +} + + +// +// SIGHT +// + +void soldierh_sight(edict_t *self, edict_t *other) +{ + if (random() < 0.5) + gi.sound (self, CHAN_VOICE, sound_sight1, 1, ATTN_NORM, 0); + else + gi.sound (self, CHAN_VOICE, sound_sight2, 1, ATTN_NORM, 0); + + if ((skill->value > 0) && (range(self, self->enemy) >= RANGE_MID)) + { + if (random() > 0.5) + { + if (self->s.skinnum < 4) + self->monsterinfo.currentmove = &soldierh_move_attack6; + else + self->monsterinfo.currentmove = &soldierh_move_attack4; + } + } +} + +// +// DUCK +// + +void soldierh_duck_hold (edict_t *self) +{ + if (level.time >= self->monsterinfo.pausetime) + self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; + else + self->monsterinfo.aiflags |= AI_HOLD_FRAME; +} + +mframe_t soldierh_frames_duck [] = +{ + ai_move, 5, soldierh_duck_down, + ai_move, -1, soldierh_duck_hold, + ai_move, 1, NULL, + ai_move, 0, soldierh_duck_up, + ai_move, 5, NULL +}; +mmove_t soldierh_move_duck = {FRAME_duck01, FRAME_duck05, soldierh_frames_duck, soldierh_run}; + +void soldierh_dodge (edict_t *self, edict_t *attacker, float eta) +{ + float r; + + r = random(); + if (r > 0.25) + return; + + if (!self->enemy) + self->enemy = attacker; + + if (skill->value == 0) + { + self->monsterinfo.currentmove = &soldierh_move_duck; + return; + } + + self->monsterinfo.pausetime = level.time + eta + 0.3; + r = random(); + + if (skill->value == 1) + { + if (r > 0.33) + self->monsterinfo.currentmove = &soldierh_move_duck; + else + self->monsterinfo.currentmove = &soldierh_move_attack3; + return; + } + + if (skill->value >= 2) + { + if (r > 0.66) + self->monsterinfo.currentmove = &soldierh_move_duck; + else + self->monsterinfo.currentmove = &soldierh_move_attack3; + return; + } + + self->monsterinfo.currentmove = &soldierh_move_attack3; +} + + +// +// DEATH +// + +void soldierh_fire6 (edict_t *self) +{ + + // no fire laser + if (self->s.skinnum < 4) + soldierh_fire (self, 5); + +} + +void soldierh_fire7 (edict_t *self) +{ + + // no fire laser + if (self->s.skinnum < 4) + soldierh_fire (self, 6); + +} + +void soldierh_dead (edict_t *self) +{ + VectorSet (self->mins, -16, -16, -24); + VectorSet (self->maxs, 16, 16, -8); + self->movetype = MOVETYPE_TOSS; + self->svflags |= SVF_DEADMONSTER; + self->nextthink = 0; + gi.linkentity (self); +} + +mframe_t soldierh_frames_death1 [] = +{ + ai_move, 0, NULL, + ai_move, -10, NULL, + ai_move, -10, NULL, + ai_move, -10, NULL, + ai_move, -5, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 0, NULL, + ai_move, 0, soldierh_fire6, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, soldierh_fire7, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t soldierh_move_death1 = {FRAME_death101, FRAME_death136, soldierh_frames_death1, soldierh_dead}; + +mframe_t soldierh_frames_death2 [] = +{ + ai_move, -5, NULL, + ai_move, -5, NULL, + ai_move, -5, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t soldierh_move_death2 = {FRAME_death201, FRAME_death235, soldierh_frames_death2, soldierh_dead}; + +mframe_t soldierh_frames_death3 [] = +{ + ai_move, -5, NULL, + ai_move, -5, NULL, + ai_move, -5, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, +}; +mmove_t soldierh_move_death3 = {FRAME_death301, FRAME_death345, soldierh_frames_death3, soldierh_dead}; + +mframe_t soldierh_frames_death4 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t soldierh_move_death4 = {FRAME_death401, FRAME_death453, soldierh_frames_death4, soldierh_dead}; + +mframe_t soldierh_frames_death5 [] = +{ + ai_move, -5, NULL, + ai_move, -5, NULL, + ai_move, -5, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t soldierh_move_death5 = {FRAME_death501, FRAME_death524, soldierh_frames_death5, soldierh_dead}; + +mframe_t soldierh_frames_death6 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t soldierh_move_death6 = {FRAME_death601, FRAME_death610, soldierh_frames_death6, soldierh_dead}; + +void soldierh_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + int n; + +// check for gib + if (self->health <= self->gib_health) + { + gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0); + for (n= 0; n < 3; n++) + ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); + ThrowGib (self, "models/objects/gibs/chest/tris.md2", damage, GIB_ORGANIC); + ThrowHead (self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC); + + self->deadflag = DEAD_DEAD; + return; + } + + if (self->deadflag == DEAD_DEAD) + return; + +// regular death + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_YES; + self->s.skinnum |= 1; + + if (self->s.skinnum == 1) + gi.sound (self, CHAN_VOICE, sound_death_light, 1, ATTN_NORM, 0); + else if (self->s.skinnum == 3) + gi.sound (self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0); + else // (self->s.skinnum == 5) + gi.sound (self, CHAN_VOICE, sound_death_ss, 1, ATTN_NORM, 0); + + if (fabs((self->s.origin[2] + self->viewheight) - point[2]) <= 4) + { + // head shot + self->monsterinfo.currentmove = &soldierh_move_death3; + return; + } + + n = rand() % 5; + if (n == 0) + self->monsterinfo.currentmove = &soldierh_move_death1; + else if (n == 1) + self->monsterinfo.currentmove = &soldierh_move_death2; + else if (n == 2) + self->monsterinfo.currentmove = &soldierh_move_death4; + else if (n == 3) + self->monsterinfo.currentmove = &soldierh_move_death5; + else + self->monsterinfo.currentmove = &soldierh_move_death6; +} + + +// +// SPAWN +// + +void SP_monster_soldier_h (edict_t *self) +{ + + self->s.modelindex = gi.modelindex ("models/monsters/soldierh/tris.md2"); + self->monsterinfo.scale = MODEL_SCALE; + VectorSet (self->mins, -16, -16, -24); + VectorSet (self->maxs, 16, 16, 32); + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + + sound_idle = gi.soundindex ("soldier/solidle1.wav"); + sound_sight1 = gi.soundindex ("soldier/solsght1.wav"); + sound_sight2 = gi.soundindex ("soldier/solsrch1.wav"); + sound_cock = gi.soundindex ("infantry/infatck3.wav"); + + self->mass = 100; + + self->pain = soldierh_pain; + self->die = soldierh_die; + + self->monsterinfo.stand = soldierh_stand; + self->monsterinfo.walk = soldierh_walk; + self->monsterinfo.run = soldierh_run; + self->monsterinfo.dodge = soldierh_dodge; + self->monsterinfo.attack = soldierh_attack; + self->monsterinfo.melee = NULL; + self->monsterinfo.sight = soldierh_sight; + + gi.linkentity (self); + + // self->monsterinfo.stand (self); + self->monsterinfo.currentmove = &soldierh_move_stand3; + + walkmonster_start (self); +} + + +/*QUAKED monster_soldier_ripper (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight +*/ +void SP_monster_soldier_ripper (edict_t *self) +{ + if (deathmatch->value) + { + G_FreeEdict (self); + return; + } + + SP_monster_soldier_h (self); + + sound_pain_light = gi.soundindex ("soldier/solpain2.wav"); + sound_death_light = gi.soundindex ("soldier/soldeth2.wav"); + + gi.modelindex ("models/objects/boomrang/tris.md2"); + gi.soundindex ("misc/lasfly.wav"); + gi.soundindex ("soldier/solatck2.wav"); + + self->s.skinnum = 0; + self->health = 50; + self->gib_health = -30; +} + +/*QUAKED monster_soldier_hypergun (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight +*/ +void SP_monster_soldier_hypergun (edict_t *self) +{ + if (deathmatch->value) + { + G_FreeEdict (self); + return; + } + + SP_monster_soldier_h (self); + + gi.modelindex ("models/objects/blaser/tris.md2"); + sound_pain = gi.soundindex ("soldier/solpain1.wav"); + sound_death = gi.soundindex ("soldier/soldeth1.wav"); + gi.soundindex ("soldier/solatck1.wav"); + + self->s.skinnum = 2; + self->health = 60; + self->gib_health = -30; +} + +/*QUAKED monster_soldier_lasergun (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight +*/ +void SP_monster_soldier_lasergun (edict_t *self) +{ + if (deathmatch->value) + { + G_FreeEdict (self); + return; + } + + SP_monster_soldier_h (self); + + sound_pain_ss = gi.soundindex ("soldier/solpain3.wav"); + sound_death_ss = gi.soundindex ("soldier/soldeth3.wav"); + gi.soundindex ("soldier/solatck3.wav"); + + self->s.skinnum = 4; + self->health = 70; + self->gib_health = -30; + +} + +// END 13-APR-98 diff --git a/original/xatrix/m_soldier.h b/original/xatrix/m_soldier.h new file mode 100644 index 0000000..86d2567 --- /dev/null +++ b/original/xatrix/m_soldier.h @@ -0,0 +1,483 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// G:\quake2\baseq2\models/monsters/soldier + +// This file generated by ModelGen - Do NOT Modify + +#define FRAME_attak101 0 +#define FRAME_attak102 1 +#define FRAME_attak103 2 +#define FRAME_attak104 3 +#define FRAME_attak105 4 +#define FRAME_attak106 5 +#define FRAME_attak107 6 +#define FRAME_attak108 7 +#define FRAME_attak109 8 +#define FRAME_attak110 9 +#define FRAME_attak111 10 +#define FRAME_attak112 11 +#define FRAME_attak201 12 +#define FRAME_attak202 13 +#define FRAME_attak203 14 +#define FRAME_attak204 15 +#define FRAME_attak205 16 +#define FRAME_attak206 17 +#define FRAME_attak207 18 +#define FRAME_attak208 19 +#define FRAME_attak209 20 +#define FRAME_attak210 21 +#define FRAME_attak211 22 +#define FRAME_attak212 23 +#define FRAME_attak213 24 +#define FRAME_attak214 25 +#define FRAME_attak215 26 +#define FRAME_attak216 27 +#define FRAME_attak217 28 +#define FRAME_attak218 29 +#define FRAME_attak301 30 +#define FRAME_attak302 31 +#define FRAME_attak303 32 +#define FRAME_attak304 33 +#define FRAME_attak305 34 +#define FRAME_attak306 35 +#define FRAME_attak307 36 +#define FRAME_attak308 37 +#define FRAME_attak309 38 +#define FRAME_attak401 39 +#define FRAME_attak402 40 +#define FRAME_attak403 41 +#define FRAME_attak404 42 +#define FRAME_attak405 43 +#define FRAME_attak406 44 +#define FRAME_duck01 45 +#define FRAME_duck02 46 +#define FRAME_duck03 47 +#define FRAME_duck04 48 +#define FRAME_duck05 49 +#define FRAME_pain101 50 +#define FRAME_pain102 51 +#define FRAME_pain103 52 +#define FRAME_pain104 53 +#define FRAME_pain105 54 +#define FRAME_pain201 55 +#define FRAME_pain202 56 +#define FRAME_pain203 57 +#define FRAME_pain204 58 +#define FRAME_pain205 59 +#define FRAME_pain206 60 +#define FRAME_pain207 61 +#define FRAME_pain301 62 +#define FRAME_pain302 63 +#define FRAME_pain303 64 +#define FRAME_pain304 65 +#define FRAME_pain305 66 +#define FRAME_pain306 67 +#define FRAME_pain307 68 +#define FRAME_pain308 69 +#define FRAME_pain309 70 +#define FRAME_pain310 71 +#define FRAME_pain311 72 +#define FRAME_pain312 73 +#define FRAME_pain313 74 +#define FRAME_pain314 75 +#define FRAME_pain315 76 +#define FRAME_pain316 77 +#define FRAME_pain317 78 +#define FRAME_pain318 79 +#define FRAME_pain401 80 +#define FRAME_pain402 81 +#define FRAME_pain403 82 +#define FRAME_pain404 83 +#define FRAME_pain405 84 +#define FRAME_pain406 85 +#define FRAME_pain407 86 +#define FRAME_pain408 87 +#define FRAME_pain409 88 +#define FRAME_pain410 89 +#define FRAME_pain411 90 +#define FRAME_pain412 91 +#define FRAME_pain413 92 +#define FRAME_pain414 93 +#define FRAME_pain415 94 +#define FRAME_pain416 95 +#define FRAME_pain417 96 +#define FRAME_run01 97 +#define FRAME_run02 98 +#define FRAME_run03 99 +#define FRAME_run04 100 +#define FRAME_run05 101 +#define FRAME_run06 102 +#define FRAME_run07 103 +#define FRAME_run08 104 +#define FRAME_run09 105 +#define FRAME_run10 106 +#define FRAME_run11 107 +#define FRAME_run12 108 +#define FRAME_runs01 109 +#define FRAME_runs02 110 +#define FRAME_runs03 111 +#define FRAME_runs04 112 +#define FRAME_runs05 113 +#define FRAME_runs06 114 +#define FRAME_runs07 115 +#define FRAME_runs08 116 +#define FRAME_runs09 117 +#define FRAME_runs10 118 +#define FRAME_runs11 119 +#define FRAME_runs12 120 +#define FRAME_runs13 121 +#define FRAME_runs14 122 +#define FRAME_runs15 123 +#define FRAME_runs16 124 +#define FRAME_runs17 125 +#define FRAME_runs18 126 +#define FRAME_runt01 127 +#define FRAME_runt02 128 +#define FRAME_runt03 129 +#define FRAME_runt04 130 +#define FRAME_runt05 131 +#define FRAME_runt06 132 +#define FRAME_runt07 133 +#define FRAME_runt08 134 +#define FRAME_runt09 135 +#define FRAME_runt10 136 +#define FRAME_runt11 137 +#define FRAME_runt12 138 +#define FRAME_runt13 139 +#define FRAME_runt14 140 +#define FRAME_runt15 141 +#define FRAME_runt16 142 +#define FRAME_runt17 143 +#define FRAME_runt18 144 +#define FRAME_runt19 145 +#define FRAME_stand101 146 +#define FRAME_stand102 147 +#define FRAME_stand103 148 +#define FRAME_stand104 149 +#define FRAME_stand105 150 +#define FRAME_stand106 151 +#define FRAME_stand107 152 +#define FRAME_stand108 153 +#define FRAME_stand109 154 +#define FRAME_stand110 155 +#define FRAME_stand111 156 +#define FRAME_stand112 157 +#define FRAME_stand113 158 +#define FRAME_stand114 159 +#define FRAME_stand115 160 +#define FRAME_stand116 161 +#define FRAME_stand117 162 +#define FRAME_stand118 163 +#define FRAME_stand119 164 +#define FRAME_stand120 165 +#define FRAME_stand121 166 +#define FRAME_stand122 167 +#define FRAME_stand123 168 +#define FRAME_stand124 169 +#define FRAME_stand125 170 +#define FRAME_stand126 171 +#define FRAME_stand127 172 +#define FRAME_stand128 173 +#define FRAME_stand129 174 +#define FRAME_stand130 175 +#define FRAME_stand301 176 +#define FRAME_stand302 177 +#define FRAME_stand303 178 +#define FRAME_stand304 179 +#define FRAME_stand305 180 +#define FRAME_stand306 181 +#define FRAME_stand307 182 +#define FRAME_stand308 183 +#define FRAME_stand309 184 +#define FRAME_stand310 185 +#define FRAME_stand311 186 +#define FRAME_stand312 187 +#define FRAME_stand313 188 +#define FRAME_stand314 189 +#define FRAME_stand315 190 +#define FRAME_stand316 191 +#define FRAME_stand317 192 +#define FRAME_stand318 193 +#define FRAME_stand319 194 +#define FRAME_stand320 195 +#define FRAME_stand321 196 +#define FRAME_stand322 197 +#define FRAME_stand323 198 +#define FRAME_stand324 199 +#define FRAME_stand325 200 +#define FRAME_stand326 201 +#define FRAME_stand327 202 +#define FRAME_stand328 203 +#define FRAME_stand329 204 +#define FRAME_stand330 205 +#define FRAME_stand331 206 +#define FRAME_stand332 207 +#define FRAME_stand333 208 +#define FRAME_stand334 209 +#define FRAME_stand335 210 +#define FRAME_stand336 211 +#define FRAME_stand337 212 +#define FRAME_stand338 213 +#define FRAME_stand339 214 +#define FRAME_walk101 215 +#define FRAME_walk102 216 +#define FRAME_walk103 217 +#define FRAME_walk104 218 +#define FRAME_walk105 219 +#define FRAME_walk106 220 +#define FRAME_walk107 221 +#define FRAME_walk108 222 +#define FRAME_walk109 223 +#define FRAME_walk110 224 +#define FRAME_walk111 225 +#define FRAME_walk112 226 +#define FRAME_walk113 227 +#define FRAME_walk114 228 +#define FRAME_walk115 229 +#define FRAME_walk116 230 +#define FRAME_walk117 231 +#define FRAME_walk118 232 +#define FRAME_walk119 233 +#define FRAME_walk120 234 +#define FRAME_walk121 235 +#define FRAME_walk122 236 +#define FRAME_walk123 237 +#define FRAME_walk124 238 +#define FRAME_walk125 239 +#define FRAME_walk126 240 +#define FRAME_walk127 241 +#define FRAME_walk128 242 +#define FRAME_walk129 243 +#define FRAME_walk130 244 +#define FRAME_walk131 245 +#define FRAME_walk132 246 +#define FRAME_walk133 247 +#define FRAME_walk201 248 +#define FRAME_walk202 249 +#define FRAME_walk203 250 +#define FRAME_walk204 251 +#define FRAME_walk205 252 +#define FRAME_walk206 253 +#define FRAME_walk207 254 +#define FRAME_walk208 255 +#define FRAME_walk209 256 +#define FRAME_walk210 257 +#define FRAME_walk211 258 +#define FRAME_walk212 259 +#define FRAME_walk213 260 +#define FRAME_walk214 261 +#define FRAME_walk215 262 +#define FRAME_walk216 263 +#define FRAME_walk217 264 +#define FRAME_walk218 265 +#define FRAME_walk219 266 +#define FRAME_walk220 267 +#define FRAME_walk221 268 +#define FRAME_walk222 269 +#define FRAME_walk223 270 +#define FRAME_walk224 271 +#define FRAME_death101 272 +#define FRAME_death102 273 +#define FRAME_death103 274 +#define FRAME_death104 275 +#define FRAME_death105 276 +#define FRAME_death106 277 +#define FRAME_death107 278 +#define FRAME_death108 279 +#define FRAME_death109 280 +#define FRAME_death110 281 +#define FRAME_death111 282 +#define FRAME_death112 283 +#define FRAME_death113 284 +#define FRAME_death114 285 +#define FRAME_death115 286 +#define FRAME_death116 287 +#define FRAME_death117 288 +#define FRAME_death118 289 +#define FRAME_death119 290 +#define FRAME_death120 291 +#define FRAME_death121 292 +#define FRAME_death122 293 +#define FRAME_death123 294 +#define FRAME_death124 295 +#define FRAME_death125 296 +#define FRAME_death126 297 +#define FRAME_death127 298 +#define FRAME_death128 299 +#define FRAME_death129 300 +#define FRAME_death130 301 +#define FRAME_death131 302 +#define FRAME_death132 303 +#define FRAME_death133 304 +#define FRAME_death134 305 +#define FRAME_death135 306 +#define FRAME_death136 307 +#define FRAME_death201 308 +#define FRAME_death202 309 +#define FRAME_death203 310 +#define FRAME_death204 311 +#define FRAME_death205 312 +#define FRAME_death206 313 +#define FRAME_death207 314 +#define FRAME_death208 315 +#define FRAME_death209 316 +#define FRAME_death210 317 +#define FRAME_death211 318 +#define FRAME_death212 319 +#define FRAME_death213 320 +#define FRAME_death214 321 +#define FRAME_death215 322 +#define FRAME_death216 323 +#define FRAME_death217 324 +#define FRAME_death218 325 +#define FRAME_death219 326 +#define FRAME_death220 327 +#define FRAME_death221 328 +#define FRAME_death222 329 +#define FRAME_death223 330 +#define FRAME_death224 331 +#define FRAME_death225 332 +#define FRAME_death226 333 +#define FRAME_death227 334 +#define FRAME_death228 335 +#define FRAME_death229 336 +#define FRAME_death230 337 +#define FRAME_death231 338 +#define FRAME_death232 339 +#define FRAME_death233 340 +#define FRAME_death234 341 +#define FRAME_death235 342 +#define FRAME_death301 343 +#define FRAME_death302 344 +#define FRAME_death303 345 +#define FRAME_death304 346 +#define FRAME_death305 347 +#define FRAME_death306 348 +#define FRAME_death307 349 +#define FRAME_death308 350 +#define FRAME_death309 351 +#define FRAME_death310 352 +#define FRAME_death311 353 +#define FRAME_death312 354 +#define FRAME_death313 355 +#define FRAME_death314 356 +#define FRAME_death315 357 +#define FRAME_death316 358 +#define FRAME_death317 359 +#define FRAME_death318 360 +#define FRAME_death319 361 +#define FRAME_death320 362 +#define FRAME_death321 363 +#define FRAME_death322 364 +#define FRAME_death323 365 +#define FRAME_death324 366 +#define FRAME_death325 367 +#define FRAME_death326 368 +#define FRAME_death327 369 +#define FRAME_death328 370 +#define FRAME_death329 371 +#define FRAME_death330 372 +#define FRAME_death331 373 +#define FRAME_death332 374 +#define FRAME_death333 375 +#define FRAME_death334 376 +#define FRAME_death335 377 +#define FRAME_death336 378 +#define FRAME_death337 379 +#define FRAME_death338 380 +#define FRAME_death339 381 +#define FRAME_death340 382 +#define FRAME_death341 383 +#define FRAME_death342 384 +#define FRAME_death343 385 +#define FRAME_death344 386 +#define FRAME_death345 387 +#define FRAME_death401 388 +#define FRAME_death402 389 +#define FRAME_death403 390 +#define FRAME_death404 391 +#define FRAME_death405 392 +#define FRAME_death406 393 +#define FRAME_death407 394 +#define FRAME_death408 395 +#define FRAME_death409 396 +#define FRAME_death410 397 +#define FRAME_death411 398 +#define FRAME_death412 399 +#define FRAME_death413 400 +#define FRAME_death414 401 +#define FRAME_death415 402 +#define FRAME_death416 403 +#define FRAME_death417 404 +#define FRAME_death418 405 +#define FRAME_death419 406 +#define FRAME_death420 407 +#define FRAME_death421 408 +#define FRAME_death422 409 +#define FRAME_death423 410 +#define FRAME_death424 411 +#define FRAME_death425 412 +#define FRAME_death426 413 +#define FRAME_death427 414 +#define FRAME_death428 415 +#define FRAME_death429 416 +#define FRAME_death430 417 +#define FRAME_death431 418 +#define FRAME_death432 419 +#define FRAME_death433 420 +#define FRAME_death434 421 +#define FRAME_death435 422 +#define FRAME_death436 423 +#define FRAME_death437 424 +#define FRAME_death438 425 +#define FRAME_death439 426 +#define FRAME_death440 427 +#define FRAME_death441 428 +#define FRAME_death442 429 +#define FRAME_death443 430 +#define FRAME_death444 431 +#define FRAME_death445 432 +#define FRAME_death446 433 +#define FRAME_death447 434 +#define FRAME_death448 435 +#define FRAME_death449 436 +#define FRAME_death450 437 +#define FRAME_death451 438 +#define FRAME_death452 439 +#define FRAME_death453 440 +#define FRAME_death501 441 +#define FRAME_death502 442 +#define FRAME_death503 443 +#define FRAME_death504 444 +#define FRAME_death505 445 +#define FRAME_death506 446 +#define FRAME_death507 447 +#define FRAME_death508 448 +#define FRAME_death509 449 +#define FRAME_death510 450 +#define FRAME_death511 451 +#define FRAME_death512 452 +#define FRAME_death513 453 +#define FRAME_death514 454 +#define FRAME_death515 455 +#define FRAME_death516 456 +#define FRAME_death517 457 +#define FRAME_death518 458 +#define FRAME_death519 459 +#define FRAME_death520 460 +#define FRAME_death521 461 +#define FRAME_death522 462 +#define FRAME_death523 463 +#define FRAME_death524 464 +#define FRAME_death601 465 +#define FRAME_death602 466 +#define FRAME_death603 467 +#define FRAME_death604 468 +#define FRAME_death605 469 +#define FRAME_death606 470 +#define FRAME_death607 471 +#define FRAME_death608 472 +#define FRAME_death609 473 +#define FRAME_death610 474 + +#define MODEL_SCALE 1.200000 diff --git a/original/xatrix/m_soldierh.h b/original/xatrix/m_soldierh.h new file mode 100644 index 0000000..7546843 --- /dev/null +++ b/original/xatrix/m_soldierh.h @@ -0,0 +1,483 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// D:\quake2\baseq2\models/monsters/soldier + +// This file generated by qdata - Do NOT Modify + +#define FRAME_attak101 0 +#define FRAME_attak102 1 +#define FRAME_attak103 2 +#define FRAME_attak104 3 +#define FRAME_attak105 4 +#define FRAME_attak106 5 +#define FRAME_attak107 6 +#define FRAME_attak108 7 +#define FRAME_attak109 8 +#define FRAME_attak110 9 +#define FRAME_attak111 10 +#define FRAME_attak112 11 +#define FRAME_attak201 12 +#define FRAME_attak202 13 +#define FRAME_attak203 14 +#define FRAME_attak204 15 +#define FRAME_attak205 16 +#define FRAME_attak206 17 +#define FRAME_attak207 18 +#define FRAME_attak208 19 +#define FRAME_attak209 20 +#define FRAME_attak210 21 +#define FRAME_attak211 22 +#define FRAME_attak212 23 +#define FRAME_attak213 24 +#define FRAME_attak214 25 +#define FRAME_attak215 26 +#define FRAME_attak216 27 +#define FRAME_attak217 28 +#define FRAME_attak218 29 +#define FRAME_attak301 30 +#define FRAME_attak302 31 +#define FRAME_attak303 32 +#define FRAME_attak304 33 +#define FRAME_attak305 34 +#define FRAME_attak306 35 +#define FRAME_attak307 36 +#define FRAME_attak308 37 +#define FRAME_attak309 38 +#define FRAME_attak401 39 +#define FRAME_attak402 40 +#define FRAME_attak403 41 +#define FRAME_attak404 42 +#define FRAME_attak405 43 +#define FRAME_attak406 44 +#define FRAME_duck01 45 +#define FRAME_duck02 46 +#define FRAME_duck03 47 +#define FRAME_duck04 48 +#define FRAME_duck05 49 +#define FRAME_pain101 50 +#define FRAME_pain102 51 +#define FRAME_pain103 52 +#define FRAME_pain104 53 +#define FRAME_pain105 54 +#define FRAME_pain201 55 +#define FRAME_pain202 56 +#define FRAME_pain203 57 +#define FRAME_pain204 58 +#define FRAME_pain205 59 +#define FRAME_pain206 60 +#define FRAME_pain207 61 +#define FRAME_pain301 62 +#define FRAME_pain302 63 +#define FRAME_pain303 64 +#define FRAME_pain304 65 +#define FRAME_pain305 66 +#define FRAME_pain306 67 +#define FRAME_pain307 68 +#define FRAME_pain308 69 +#define FRAME_pain309 70 +#define FRAME_pain310 71 +#define FRAME_pain311 72 +#define FRAME_pain312 73 +#define FRAME_pain313 74 +#define FRAME_pain314 75 +#define FRAME_pain315 76 +#define FRAME_pain316 77 +#define FRAME_pain317 78 +#define FRAME_pain318 79 +#define FRAME_pain401 80 +#define FRAME_pain402 81 +#define FRAME_pain403 82 +#define FRAME_pain404 83 +#define FRAME_pain405 84 +#define FRAME_pain406 85 +#define FRAME_pain407 86 +#define FRAME_pain408 87 +#define FRAME_pain409 88 +#define FRAME_pain410 89 +#define FRAME_pain411 90 +#define FRAME_pain412 91 +#define FRAME_pain413 92 +#define FRAME_pain414 93 +#define FRAME_pain415 94 +#define FRAME_pain416 95 +#define FRAME_pain417 96 +#define FRAME_run01 97 +#define FRAME_run02 98 +#define FRAME_run03 99 +#define FRAME_run04 100 +#define FRAME_run05 101 +#define FRAME_run06 102 +#define FRAME_run07 103 +#define FRAME_run08 104 +#define FRAME_run09 105 +#define FRAME_run10 106 +#define FRAME_run11 107 +#define FRAME_run12 108 +#define FRAME_runs01 109 +#define FRAME_runs02 110 +#define FRAME_runs03 111 +#define FRAME_runs04 112 +#define FRAME_runs05 113 +#define FRAME_runs06 114 +#define FRAME_runs07 115 +#define FRAME_runs08 116 +#define FRAME_runs09 117 +#define FRAME_runs10 118 +#define FRAME_runs11 119 +#define FRAME_runs12 120 +#define FRAME_runs13 121 +#define FRAME_runs14 122 +#define FRAME_runs15 123 +#define FRAME_runs16 124 +#define FRAME_runs17 125 +#define FRAME_runs18 126 +#define FRAME_runt01 127 +#define FRAME_runt02 128 +#define FRAME_runt03 129 +#define FRAME_runt04 130 +#define FRAME_runt05 131 +#define FRAME_runt06 132 +#define FRAME_runt07 133 +#define FRAME_runt08 134 +#define FRAME_runt09 135 +#define FRAME_runt10 136 +#define FRAME_runt11 137 +#define FRAME_runt12 138 +#define FRAME_runt13 139 +#define FRAME_runt14 140 +#define FRAME_runt15 141 +#define FRAME_runt16 142 +#define FRAME_runt17 143 +#define FRAME_runt18 144 +#define FRAME_runt19 145 +#define FRAME_stand101 146 +#define FRAME_stand102 147 +#define FRAME_stand103 148 +#define FRAME_stand104 149 +#define FRAME_stand105 150 +#define FRAME_stand106 151 +#define FRAME_stand107 152 +#define FRAME_stand108 153 +#define FRAME_stand109 154 +#define FRAME_stand110 155 +#define FRAME_stand111 156 +#define FRAME_stand112 157 +#define FRAME_stand113 158 +#define FRAME_stand114 159 +#define FRAME_stand115 160 +#define FRAME_stand116 161 +#define FRAME_stand117 162 +#define FRAME_stand118 163 +#define FRAME_stand119 164 +#define FRAME_stand120 165 +#define FRAME_stand121 166 +#define FRAME_stand122 167 +#define FRAME_stand123 168 +#define FRAME_stand124 169 +#define FRAME_stand125 170 +#define FRAME_stand126 171 +#define FRAME_stand127 172 +#define FRAME_stand128 173 +#define FRAME_stand129 174 +#define FRAME_stand130 175 +#define FRAME_stand301 176 +#define FRAME_stand302 177 +#define FRAME_stand303 178 +#define FRAME_stand304 179 +#define FRAME_stand305 180 +#define FRAME_stand306 181 +#define FRAME_stand307 182 +#define FRAME_stand308 183 +#define FRAME_stand309 184 +#define FRAME_stand310 185 +#define FRAME_stand311 186 +#define FRAME_stand312 187 +#define FRAME_stand313 188 +#define FRAME_stand314 189 +#define FRAME_stand315 190 +#define FRAME_stand316 191 +#define FRAME_stand317 192 +#define FRAME_stand318 193 +#define FRAME_stand319 194 +#define FRAME_stand320 195 +#define FRAME_stand321 196 +#define FRAME_stand322 197 +#define FRAME_stand323 198 +#define FRAME_stand324 199 +#define FRAME_stand325 200 +#define FRAME_stand326 201 +#define FRAME_stand327 202 +#define FRAME_stand328 203 +#define FRAME_stand329 204 +#define FRAME_stand330 205 +#define FRAME_stand331 206 +#define FRAME_stand332 207 +#define FRAME_stand333 208 +#define FRAME_stand334 209 +#define FRAME_stand335 210 +#define FRAME_stand336 211 +#define FRAME_stand337 212 +#define FRAME_stand338 213 +#define FRAME_stand339 214 +#define FRAME_walk101 215 +#define FRAME_walk102 216 +#define FRAME_walk103 217 +#define FRAME_walk104 218 +#define FRAME_walk105 219 +#define FRAME_walk106 220 +#define FRAME_walk107 221 +#define FRAME_walk108 222 +#define FRAME_walk109 223 +#define FRAME_walk110 224 +#define FRAME_walk111 225 +#define FRAME_walk112 226 +#define FRAME_walk113 227 +#define FRAME_walk114 228 +#define FRAME_walk115 229 +#define FRAME_walk116 230 +#define FRAME_walk117 231 +#define FRAME_walk118 232 +#define FRAME_walk119 233 +#define FRAME_walk120 234 +#define FRAME_walk121 235 +#define FRAME_walk122 236 +#define FRAME_walk123 237 +#define FRAME_walk124 238 +#define FRAME_walk125 239 +#define FRAME_walk126 240 +#define FRAME_walk127 241 +#define FRAME_walk128 242 +#define FRAME_walk129 243 +#define FRAME_walk130 244 +#define FRAME_walk131 245 +#define FRAME_walk132 246 +#define FRAME_walk133 247 +#define FRAME_walk201 248 +#define FRAME_walk202 249 +#define FRAME_walk203 250 +#define FRAME_walk204 251 +#define FRAME_walk205 252 +#define FRAME_walk206 253 +#define FRAME_walk207 254 +#define FRAME_walk208 255 +#define FRAME_walk209 256 +#define FRAME_walk210 257 +#define FRAME_walk211 258 +#define FRAME_walk212 259 +#define FRAME_walk213 260 +#define FRAME_walk214 261 +#define FRAME_walk215 262 +#define FRAME_walk216 263 +#define FRAME_walk217 264 +#define FRAME_walk218 265 +#define FRAME_walk219 266 +#define FRAME_walk220 267 +#define FRAME_walk221 268 +#define FRAME_walk222 269 +#define FRAME_walk223 270 +#define FRAME_walk224 271 +#define FRAME_death101 272 +#define FRAME_death102 273 +#define FRAME_death103 274 +#define FRAME_death104 275 +#define FRAME_death105 276 +#define FRAME_death106 277 +#define FRAME_death107 278 +#define FRAME_death108 279 +#define FRAME_death109 280 +#define FRAME_death110 281 +#define FRAME_death111 282 +#define FRAME_death112 283 +#define FRAME_death113 284 +#define FRAME_death114 285 +#define FRAME_death115 286 +#define FRAME_death116 287 +#define FRAME_death117 288 +#define FRAME_death118 289 +#define FRAME_death119 290 +#define FRAME_death120 291 +#define FRAME_death121 292 +#define FRAME_death122 293 +#define FRAME_death123 294 +#define FRAME_death124 295 +#define FRAME_death125 296 +#define FRAME_death126 297 +#define FRAME_death127 298 +#define FRAME_death128 299 +#define FRAME_death129 300 +#define FRAME_death130 301 +#define FRAME_death131 302 +#define FRAME_death132 303 +#define FRAME_death133 304 +#define FRAME_death134 305 +#define FRAME_death135 306 +#define FRAME_death136 307 +#define FRAME_death201 308 +#define FRAME_death202 309 +#define FRAME_death203 310 +#define FRAME_death204 311 +#define FRAME_death205 312 +#define FRAME_death206 313 +#define FRAME_death207 314 +#define FRAME_death208 315 +#define FRAME_death209 316 +#define FRAME_death210 317 +#define FRAME_death211 318 +#define FRAME_death212 319 +#define FRAME_death213 320 +#define FRAME_death214 321 +#define FRAME_death215 322 +#define FRAME_death216 323 +#define FRAME_death217 324 +#define FRAME_death218 325 +#define FRAME_death219 326 +#define FRAME_death220 327 +#define FRAME_death221 328 +#define FRAME_death222 329 +#define FRAME_death223 330 +#define FRAME_death224 331 +#define FRAME_death225 332 +#define FRAME_death226 333 +#define FRAME_death227 334 +#define FRAME_death228 335 +#define FRAME_death229 336 +#define FRAME_death230 337 +#define FRAME_death231 338 +#define FRAME_death232 339 +#define FRAME_death233 340 +#define FRAME_death234 341 +#define FRAME_death235 342 +#define FRAME_death301 343 +#define FRAME_death302 344 +#define FRAME_death303 345 +#define FRAME_death304 346 +#define FRAME_death305 347 +#define FRAME_death306 348 +#define FRAME_death307 349 +#define FRAME_death308 350 +#define FRAME_death309 351 +#define FRAME_death310 352 +#define FRAME_death311 353 +#define FRAME_death312 354 +#define FRAME_death313 355 +#define FRAME_death314 356 +#define FRAME_death315 357 +#define FRAME_death316 358 +#define FRAME_death317 359 +#define FRAME_death318 360 +#define FRAME_death319 361 +#define FRAME_death320 362 +#define FRAME_death321 363 +#define FRAME_death322 364 +#define FRAME_death323 365 +#define FRAME_death324 366 +#define FRAME_death325 367 +#define FRAME_death326 368 +#define FRAME_death327 369 +#define FRAME_death328 370 +#define FRAME_death329 371 +#define FRAME_death330 372 +#define FRAME_death331 373 +#define FRAME_death332 374 +#define FRAME_death333 375 +#define FRAME_death334 376 +#define FRAME_death335 377 +#define FRAME_death336 378 +#define FRAME_death337 379 +#define FRAME_death338 380 +#define FRAME_death339 381 +#define FRAME_death340 382 +#define FRAME_death341 383 +#define FRAME_death342 384 +#define FRAME_death343 385 +#define FRAME_death344 386 +#define FRAME_death345 387 +#define FRAME_death401 388 +#define FRAME_death402 389 +#define FRAME_death403 390 +#define FRAME_death404 391 +#define FRAME_death405 392 +#define FRAME_death406 393 +#define FRAME_death407 394 +#define FRAME_death408 395 +#define FRAME_death409 396 +#define FRAME_death410 397 +#define FRAME_death411 398 +#define FRAME_death412 399 +#define FRAME_death413 400 +#define FRAME_death414 401 +#define FRAME_death415 402 +#define FRAME_death416 403 +#define FRAME_death417 404 +#define FRAME_death418 405 +#define FRAME_death419 406 +#define FRAME_death420 407 +#define FRAME_death421 408 +#define FRAME_death422 409 +#define FRAME_death423 410 +#define FRAME_death424 411 +#define FRAME_death425 412 +#define FRAME_death426 413 +#define FRAME_death427 414 +#define FRAME_death428 415 +#define FRAME_death429 416 +#define FRAME_death430 417 +#define FRAME_death431 418 +#define FRAME_death432 419 +#define FRAME_death433 420 +#define FRAME_death434 421 +#define FRAME_death435 422 +#define FRAME_death436 423 +#define FRAME_death437 424 +#define FRAME_death438 425 +#define FRAME_death439 426 +#define FRAME_death440 427 +#define FRAME_death441 428 +#define FRAME_death442 429 +#define FRAME_death443 430 +#define FRAME_death444 431 +#define FRAME_death445 432 +#define FRAME_death446 433 +#define FRAME_death447 434 +#define FRAME_death448 435 +#define FRAME_death449 436 +#define FRAME_death450 437 +#define FRAME_death451 438 +#define FRAME_death452 439 +#define FRAME_death453 440 +#define FRAME_death501 441 +#define FRAME_death502 442 +#define FRAME_death503 443 +#define FRAME_death504 444 +#define FRAME_death505 445 +#define FRAME_death506 446 +#define FRAME_death507 447 +#define FRAME_death508 448 +#define FRAME_death509 449 +#define FRAME_death510 450 +#define FRAME_death511 451 +#define FRAME_death512 452 +#define FRAME_death513 453 +#define FRAME_death514 454 +#define FRAME_death515 455 +#define FRAME_death516 456 +#define FRAME_death517 457 +#define FRAME_death518 458 +#define FRAME_death519 459 +#define FRAME_death520 460 +#define FRAME_death521 461 +#define FRAME_death522 462 +#define FRAME_death523 463 +#define FRAME_death524 464 +#define FRAME_death601 465 +#define FRAME_death602 466 +#define FRAME_death603 467 +#define FRAME_death604 468 +#define FRAME_death605 469 +#define FRAME_death606 470 +#define FRAME_death607 471 +#define FRAME_death608 472 +#define FRAME_death609 473 +#define FRAME_death610 474 + +#define MODEL_SCALE 1.200000 diff --git a/original/xatrix/m_supertank.c b/original/xatrix/m_supertank.c new file mode 100644 index 0000000..0d24e67 --- /dev/null +++ b/original/xatrix/m_supertank.c @@ -0,0 +1,707 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +/* +============================================================================== + +SUPERTANK + +============================================================================== +*/ + +#include "g_local.h" +#include "m_supertank.h" + +qboolean visible (edict_t *self, edict_t *other); + +static int sound_pain1; +static int sound_pain2; +static int sound_pain3; +static int sound_death; +static int sound_search1; +static int sound_search2; + +static int tread_sound; + +void BossExplode (edict_t *self); + +void TreadSound (edict_t *self) +{ + gi.sound (self, CHAN_VOICE, tread_sound, 1, ATTN_NORM, 0); +} + +void supertank_search (edict_t *self) +{ + if (random() < 0.5) + gi.sound (self, CHAN_VOICE, sound_search1, 1, ATTN_NORM, 0); + else + gi.sound (self, CHAN_VOICE, sound_search2, 1, ATTN_NORM, 0); +} + + +void supertank_dead (edict_t *self); +void supertankRocket (edict_t *self); +void supertankMachineGun (edict_t *self); +void supertank_reattack1(edict_t *self); + + +// +// stand +// + +mframe_t supertank_frames_stand []= +{ + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL +}; +mmove_t supertank_move_stand = {FRAME_stand_1, FRAME_stand_60, supertank_frames_stand, NULL}; + +void supertank_stand (edict_t *self) +{ + self->monsterinfo.currentmove = &supertank_move_stand; +} + + +mframe_t supertank_frames_run [] = +{ + ai_run, 12, TreadSound, + ai_run, 12, NULL, + ai_run, 12, NULL, + ai_run, 12, NULL, + ai_run, 12, NULL, + ai_run, 12, NULL, + ai_run, 12, NULL, + ai_run, 12, NULL, + ai_run, 12, NULL, + ai_run, 12, NULL, + ai_run, 12, NULL, + ai_run, 12, NULL, + ai_run, 12, NULL, + ai_run, 12, NULL, + ai_run, 12, NULL, + ai_run, 12, NULL, + ai_run, 12, NULL, + ai_run, 12, NULL +}; +mmove_t supertank_move_run = {FRAME_forwrd_1, FRAME_forwrd_18, supertank_frames_run, NULL}; + +// +// walk +// + + +mframe_t supertank_frames_forward [] = +{ + ai_walk, 4, TreadSound, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL, + ai_walk, 4, NULL +}; +mmove_t supertank_move_forward = {FRAME_forwrd_1, FRAME_forwrd_18, supertank_frames_forward, NULL}; + +void supertank_forward (edict_t *self) +{ + self->monsterinfo.currentmove = &supertank_move_forward; +} + +void supertank_walk (edict_t *self) +{ + self->monsterinfo.currentmove = &supertank_move_forward; +} + +void supertank_run (edict_t *self) +{ + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + self->monsterinfo.currentmove = &supertank_move_stand; + else + self->monsterinfo.currentmove = &supertank_move_run; +} + +mframe_t supertank_frames_turn_right [] = +{ + ai_move, 0, TreadSound, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t supertank_move_turn_right = {FRAME_right_1, FRAME_right_18, supertank_frames_turn_right, supertank_run}; + +mframe_t supertank_frames_turn_left [] = +{ + ai_move, 0, TreadSound, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t supertank_move_turn_left = {FRAME_left_1, FRAME_left_18, supertank_frames_turn_left, supertank_run}; + + +mframe_t supertank_frames_pain3 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t supertank_move_pain3 = {FRAME_pain3_9, FRAME_pain3_12, supertank_frames_pain3, supertank_run}; + +mframe_t supertank_frames_pain2 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t supertank_move_pain2 = {FRAME_pain2_5, FRAME_pain2_8, supertank_frames_pain2, supertank_run}; + +mframe_t supertank_frames_pain1 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t supertank_move_pain1 = {FRAME_pain1_1, FRAME_pain1_4, supertank_frames_pain1, supertank_run}; + +mframe_t supertank_frames_death1 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, BossExplode +}; +mmove_t supertank_move_death = {FRAME_death_1, FRAME_death_24, supertank_frames_death1, supertank_dead}; + +mframe_t supertank_frames_backward[] = +{ + ai_walk, 0, TreadSound, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL, + ai_walk, 0, NULL +}; +mmove_t supertank_move_backward = {FRAME_backwd_1, FRAME_backwd_18, supertank_frames_backward, NULL}; + +mframe_t supertank_frames_attack4[]= +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t supertank_move_attack4 = {FRAME_attak4_1, FRAME_attak4_6, supertank_frames_attack4, supertank_run}; + +mframe_t supertank_frames_attack3[]= +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t supertank_move_attack3 = {FRAME_attak3_1, FRAME_attak3_27, supertank_frames_attack3, supertank_run}; + +mframe_t supertank_frames_attack2[]= +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, supertankRocket, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, supertankRocket, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, supertankRocket, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t supertank_move_attack2 = {FRAME_attak2_1, FRAME_attak2_27, supertank_frames_attack2, supertank_run}; + +mframe_t supertank_frames_attack1[]= +{ + ai_charge, 0, supertankMachineGun, + ai_charge, 0, supertankMachineGun, + ai_charge, 0, supertankMachineGun, + ai_charge, 0, supertankMachineGun, + ai_charge, 0, supertankMachineGun, + ai_charge, 0, supertankMachineGun, + +}; +mmove_t supertank_move_attack1 = {FRAME_attak1_1, FRAME_attak1_6, supertank_frames_attack1, supertank_reattack1}; + +mframe_t supertank_frames_end_attack1[]= +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t supertank_move_end_attack1 = {FRAME_attak1_7, FRAME_attak1_20, supertank_frames_end_attack1, supertank_run}; + + +void supertank_reattack1(edict_t *self) +{ + if (visible(self, self->enemy)) + if (random() < 0.9) + self->monsterinfo.currentmove = &supertank_move_attack1; + else + self->monsterinfo.currentmove = &supertank_move_end_attack1; + else + self->monsterinfo.currentmove = &supertank_move_end_attack1; +} + +void supertank_pain (edict_t *self, edict_t *other, float kick, int damage) +{ + + if (self->health < (self->max_health / 2)) + self->s.skinnum = 1; + + if (level.time < self->pain_debounce_time) + return; + + // Lessen the chance of him going into his pain frames + if (damage <=25) + if (random()<0.2) + return; + + // Don't go into pain if he's firing his rockets + if (skill->value >= 2) + if ( (self->s.frame >= FRAME_attak2_1) && (self->s.frame <= FRAME_attak2_14) ) + return; + + self->pain_debounce_time = level.time + 3; + + if (skill->value == 3) + return; // no pain anims in nightmare + + if (damage <= 10) + { + gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM,0); + self->monsterinfo.currentmove = &supertank_move_pain1; + } + else if (damage <= 25) + { + gi.sound (self, CHAN_VOICE, sound_pain3, 1, ATTN_NORM,0); + self->monsterinfo.currentmove = &supertank_move_pain2; + } + else + { + gi.sound (self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM,0); + self->monsterinfo.currentmove = &supertank_move_pain3; + } +}; + + +void supertankRocket (edict_t *self) +{ + vec3_t forward, right; + vec3_t start; + vec3_t dir; + vec3_t vec; + int flash_number; + + if (self->s.frame == FRAME_attak2_8) + flash_number = MZ2_SUPERTANK_ROCKET_1; + else if (self->s.frame == FRAME_attak2_11) + flash_number = MZ2_SUPERTANK_ROCKET_2; + else // (self->s.frame == FRAME_attak2_14) + flash_number = MZ2_SUPERTANK_ROCKET_3; + + AngleVectors (self->s.angles, forward, right, NULL); + G_ProjectSource (self->s.origin, monster_flash_offset[flash_number], forward, right, start); + + VectorCopy (self->enemy->s.origin, vec); + vec[2] += self->enemy->viewheight; + VectorSubtract (vec, start, dir); + VectorNormalize (dir); + + monster_fire_rocket (self, start, dir, 50, 500, flash_number); +} + +void supertankMachineGun (edict_t *self) +{ + vec3_t dir; + vec3_t vec; + vec3_t start; + vec3_t forward, right; + int flash_number; + + flash_number = MZ2_SUPERTANK_MACHINEGUN_1 + (self->s.frame - FRAME_attak1_1); + + //FIXME!!! + dir[0] = 0; + dir[1] = self->s.angles[1]; + dir[2] = 0; + + AngleVectors (dir, forward, right, NULL); + G_ProjectSource (self->s.origin, monster_flash_offset[flash_number], forward, right, start); + + if (self->enemy) + { + VectorCopy (self->enemy->s.origin, vec); + VectorMA (vec, 0, self->enemy->velocity, vec); + vec[2] += self->enemy->viewheight; + VectorSubtract (vec, start, forward); + VectorNormalize (forward); + } + + monster_fire_bullet (self, start, forward, 6, 4, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, flash_number); +} + + +void supertank_attack(edict_t *self) +{ + vec3_t vec; + float range; + //float r; + + VectorSubtract (self->enemy->s.origin, self->s.origin, vec); + range = VectorLength (vec); + + //r = random(); + + // Attack 1 == Chaingun + // Attack 2 == Rocket Launcher + + if (range <= 160) + { + self->monsterinfo.currentmove = &supertank_move_attack1; + } + else + { // fire rockets more often at distance + if (random() < 0.3) + self->monsterinfo.currentmove = &supertank_move_attack1; + else + self->monsterinfo.currentmove = &supertank_move_attack2; + } +} + + +// +// death +// + +void supertank_dead (edict_t *self) +{ + VectorSet (self->mins, -60, -60, 0); + VectorSet (self->maxs, 60, 60, 72); + self->movetype = MOVETYPE_TOSS; + self->svflags |= SVF_DEADMONSTER; + self->nextthink = 0; + gi.linkentity (self); +} + + +void BossExplode (edict_t *self) +{ + vec3_t org; + int n; + + self->think = BossExplode; + VectorCopy (self->s.origin, org); + org[2] += 24 + (rand()&15); + switch (self->count++) + { + case 0: + org[0] -= 24; + org[1] -= 24; + break; + case 1: + org[0] += 24; + org[1] += 24; + break; + case 2: + org[0] += 24; + org[1] -= 24; + break; + case 3: + org[0] -= 24; + org[1] += 24; + break; + case 4: + org[0] -= 48; + org[1] -= 48; + break; + case 5: + org[0] += 48; + org[1] += 48; + break; + case 6: + org[0] -= 48; + org[1] += 48; + break; + case 7: + org[0] += 48; + org[1] -= 48; + break; + case 8: + self->s.sound = 0; + for (n= 0; n < 4; n++) + ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", 500, GIB_ORGANIC); + for (n= 0; n < 8; n++) + ThrowGib (self, "models/objects/gibs/sm_metal/tris.md2", 500, GIB_METALLIC); + ThrowGib (self, "models/objects/gibs/chest/tris.md2", 500, GIB_ORGANIC); + ThrowHead (self, "models/objects/gibs/gear/tris.md2", 500, GIB_METALLIC); + self->deadflag = DEAD_DEAD; + return; + } + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_EXPLOSION1); + gi.WritePosition (org); + gi.multicast (self->s.origin, MULTICAST_PVS); + + self->nextthink = level.time + 0.1; +} + + +void supertank_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + gi.sound (self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0); + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_NO; + self->count = 0; + self->monsterinfo.currentmove = &supertank_move_death; +} + +// +// monster_supertank +// + +// RAFAEL (Powershield) + +/*QUAKED monster_supertank (1 .5 0) (-64 -64 0) (64 64 72) Ambush Trigger_Spawn Sight Powershield +*/ +void SP_monster_supertank (edict_t *self) +{ + if (deathmatch->value) + { + G_FreeEdict (self); + return; + } + + sound_pain1 = gi.soundindex ("bosstank/btkpain1.wav"); + sound_pain2 = gi.soundindex ("bosstank/btkpain2.wav"); + sound_pain3 = gi.soundindex ("bosstank/btkpain3.wav"); + sound_death = gi.soundindex ("bosstank/btkdeth1.wav"); + sound_search1 = gi.soundindex ("bosstank/btkunqv1.wav"); + sound_search2 = gi.soundindex ("bosstank/btkunqv2.wav"); + +// self->s.sound = gi.soundindex ("bosstank/btkengn1.wav"); + tread_sound = gi.soundindex ("bosstank/btkengn1.wav"); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex ("models/monsters/boss1/tris.md2"); + VectorSet (self->mins, -64, -64, 0); + VectorSet (self->maxs, 64, 64, 112); + + self->health = 1500; + self->gib_health = -500; + self->mass = 800; + + self->pain = supertank_pain; + self->die = supertank_die; + self->monsterinfo.stand = supertank_stand; + self->monsterinfo.walk = supertank_walk; + self->monsterinfo.run = supertank_run; + self->monsterinfo.dodge = NULL; + self->monsterinfo.attack = supertank_attack; + self->monsterinfo.search = supertank_search; + self->monsterinfo.melee = NULL; + self->monsterinfo.sight = NULL; + + gi.linkentity (self); + + self->monsterinfo.currentmove = &supertank_move_stand; + self->monsterinfo.scale = MODEL_SCALE; + + if (self->spawnflags & 8) + { + self->monsterinfo.power_armor_type = POWER_ARMOR_SHIELD; + self->monsterinfo.power_armor_power = 400; + } + walkmonster_start(self); +} diff --git a/original/xatrix/m_supertank.h b/original/xatrix/m_supertank.h new file mode 100644 index 0000000..c745d70 --- /dev/null +++ b/original/xatrix/m_supertank.h @@ -0,0 +1,262 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// G:\quake2\baseq2\models/monsters/boss1/backup + +// This file generated by ModelGen - Do NOT Modify + +#define FRAME_attak1_1 0 +#define FRAME_attak1_2 1 +#define FRAME_attak1_3 2 +#define FRAME_attak1_4 3 +#define FRAME_attak1_5 4 +#define FRAME_attak1_6 5 +#define FRAME_attak1_7 6 +#define FRAME_attak1_8 7 +#define FRAME_attak1_9 8 +#define FRAME_attak1_10 9 +#define FRAME_attak1_11 10 +#define FRAME_attak1_12 11 +#define FRAME_attak1_13 12 +#define FRAME_attak1_14 13 +#define FRAME_attak1_15 14 +#define FRAME_attak1_16 15 +#define FRAME_attak1_17 16 +#define FRAME_attak1_18 17 +#define FRAME_attak1_19 18 +#define FRAME_attak1_20 19 +#define FRAME_attak2_1 20 +#define FRAME_attak2_2 21 +#define FRAME_attak2_3 22 +#define FRAME_attak2_4 23 +#define FRAME_attak2_5 24 +#define FRAME_attak2_6 25 +#define FRAME_attak2_7 26 +#define FRAME_attak2_8 27 +#define FRAME_attak2_9 28 +#define FRAME_attak2_10 29 +#define FRAME_attak2_11 30 +#define FRAME_attak2_12 31 +#define FRAME_attak2_13 32 +#define FRAME_attak2_14 33 +#define FRAME_attak2_15 34 +#define FRAME_attak2_16 35 +#define FRAME_attak2_17 36 +#define FRAME_attak2_18 37 +#define FRAME_attak2_19 38 +#define FRAME_attak2_20 39 +#define FRAME_attak2_21 40 +#define FRAME_attak2_22 41 +#define FRAME_attak2_23 42 +#define FRAME_attak2_24 43 +#define FRAME_attak2_25 44 +#define FRAME_attak2_26 45 +#define FRAME_attak2_27 46 +#define FRAME_attak3_1 47 +#define FRAME_attak3_2 48 +#define FRAME_attak3_3 49 +#define FRAME_attak3_4 50 +#define FRAME_attak3_5 51 +#define FRAME_attak3_6 52 +#define FRAME_attak3_7 53 +#define FRAME_attak3_8 54 +#define FRAME_attak3_9 55 +#define FRAME_attak3_10 56 +#define FRAME_attak3_11 57 +#define FRAME_attak3_12 58 +#define FRAME_attak3_13 59 +#define FRAME_attak3_14 60 +#define FRAME_attak3_15 61 +#define FRAME_attak3_16 62 +#define FRAME_attak3_17 63 +#define FRAME_attak3_18 64 +#define FRAME_attak3_19 65 +#define FRAME_attak3_20 66 +#define FRAME_attak3_21 67 +#define FRAME_attak3_22 68 +#define FRAME_attak3_23 69 +#define FRAME_attak3_24 70 +#define FRAME_attak3_25 71 +#define FRAME_attak3_26 72 +#define FRAME_attak3_27 73 +#define FRAME_attak4_1 74 +#define FRAME_attak4_2 75 +#define FRAME_attak4_3 76 +#define FRAME_attak4_4 77 +#define FRAME_attak4_5 78 +#define FRAME_attak4_6 79 +#define FRAME_backwd_1 80 +#define FRAME_backwd_2 81 +#define FRAME_backwd_3 82 +#define FRAME_backwd_4 83 +#define FRAME_backwd_5 84 +#define FRAME_backwd_6 85 +#define FRAME_backwd_7 86 +#define FRAME_backwd_8 87 +#define FRAME_backwd_9 88 +#define FRAME_backwd_10 89 +#define FRAME_backwd_11 90 +#define FRAME_backwd_12 91 +#define FRAME_backwd_13 92 +#define FRAME_backwd_14 93 +#define FRAME_backwd_15 94 +#define FRAME_backwd_16 95 +#define FRAME_backwd_17 96 +#define FRAME_backwd_18 97 +#define FRAME_death_1 98 +#define FRAME_death_2 99 +#define FRAME_death_3 100 +#define FRAME_death_4 101 +#define FRAME_death_5 102 +#define FRAME_death_6 103 +#define FRAME_death_7 104 +#define FRAME_death_8 105 +#define FRAME_death_9 106 +#define FRAME_death_10 107 +#define FRAME_death_11 108 +#define FRAME_death_12 109 +#define FRAME_death_13 110 +#define FRAME_death_14 111 +#define FRAME_death_15 112 +#define FRAME_death_16 113 +#define FRAME_death_17 114 +#define FRAME_death_18 115 +#define FRAME_death_19 116 +#define FRAME_death_20 117 +#define FRAME_death_21 118 +#define FRAME_death_22 119 +#define FRAME_death_23 120 +#define FRAME_death_24 121 +#define FRAME_death_31 122 +#define FRAME_death_32 123 +#define FRAME_death_33 124 +#define FRAME_death_45 125 +#define FRAME_death_46 126 +#define FRAME_death_47 127 +#define FRAME_forwrd_1 128 +#define FRAME_forwrd_2 129 +#define FRAME_forwrd_3 130 +#define FRAME_forwrd_4 131 +#define FRAME_forwrd_5 132 +#define FRAME_forwrd_6 133 +#define FRAME_forwrd_7 134 +#define FRAME_forwrd_8 135 +#define FRAME_forwrd_9 136 +#define FRAME_forwrd_10 137 +#define FRAME_forwrd_11 138 +#define FRAME_forwrd_12 139 +#define FRAME_forwrd_13 140 +#define FRAME_forwrd_14 141 +#define FRAME_forwrd_15 142 +#define FRAME_forwrd_16 143 +#define FRAME_forwrd_17 144 +#define FRAME_forwrd_18 145 +#define FRAME_left_1 146 +#define FRAME_left_2 147 +#define FRAME_left_3 148 +#define FRAME_left_4 149 +#define FRAME_left_5 150 +#define FRAME_left_6 151 +#define FRAME_left_7 152 +#define FRAME_left_8 153 +#define FRAME_left_9 154 +#define FRAME_left_10 155 +#define FRAME_left_11 156 +#define FRAME_left_12 157 +#define FRAME_left_13 158 +#define FRAME_left_14 159 +#define FRAME_left_15 160 +#define FRAME_left_16 161 +#define FRAME_left_17 162 +#define FRAME_left_18 163 +#define FRAME_pain1_1 164 +#define FRAME_pain1_2 165 +#define FRAME_pain1_3 166 +#define FRAME_pain1_4 167 +#define FRAME_pain2_5 168 +#define FRAME_pain2_6 169 +#define FRAME_pain2_7 170 +#define FRAME_pain2_8 171 +#define FRAME_pain3_9 172 +#define FRAME_pain3_10 173 +#define FRAME_pain3_11 174 +#define FRAME_pain3_12 175 +#define FRAME_right_1 176 +#define FRAME_right_2 177 +#define FRAME_right_3 178 +#define FRAME_right_4 179 +#define FRAME_right_5 180 +#define FRAME_right_6 181 +#define FRAME_right_7 182 +#define FRAME_right_8 183 +#define FRAME_right_9 184 +#define FRAME_right_10 185 +#define FRAME_right_11 186 +#define FRAME_right_12 187 +#define FRAME_right_13 188 +#define FRAME_right_14 189 +#define FRAME_right_15 190 +#define FRAME_right_16 191 +#define FRAME_right_17 192 +#define FRAME_right_18 193 +#define FRAME_stand_1 194 +#define FRAME_stand_2 195 +#define FRAME_stand_3 196 +#define FRAME_stand_4 197 +#define FRAME_stand_5 198 +#define FRAME_stand_6 199 +#define FRAME_stand_7 200 +#define FRAME_stand_8 201 +#define FRAME_stand_9 202 +#define FRAME_stand_10 203 +#define FRAME_stand_11 204 +#define FRAME_stand_12 205 +#define FRAME_stand_13 206 +#define FRAME_stand_14 207 +#define FRAME_stand_15 208 +#define FRAME_stand_16 209 +#define FRAME_stand_17 210 +#define FRAME_stand_18 211 +#define FRAME_stand_19 212 +#define FRAME_stand_20 213 +#define FRAME_stand_21 214 +#define FRAME_stand_22 215 +#define FRAME_stand_23 216 +#define FRAME_stand_24 217 +#define FRAME_stand_25 218 +#define FRAME_stand_26 219 +#define FRAME_stand_27 220 +#define FRAME_stand_28 221 +#define FRAME_stand_29 222 +#define FRAME_stand_30 223 +#define FRAME_stand_31 224 +#define FRAME_stand_32 225 +#define FRAME_stand_33 226 +#define FRAME_stand_34 227 +#define FRAME_stand_35 228 +#define FRAME_stand_36 229 +#define FRAME_stand_37 230 +#define FRAME_stand_38 231 +#define FRAME_stand_39 232 +#define FRAME_stand_40 233 +#define FRAME_stand_41 234 +#define FRAME_stand_42 235 +#define FRAME_stand_43 236 +#define FRAME_stand_44 237 +#define FRAME_stand_45 238 +#define FRAME_stand_46 239 +#define FRAME_stand_47 240 +#define FRAME_stand_48 241 +#define FRAME_stand_49 242 +#define FRAME_stand_50 243 +#define FRAME_stand_51 244 +#define FRAME_stand_52 245 +#define FRAME_stand_53 246 +#define FRAME_stand_54 247 +#define FRAME_stand_55 248 +#define FRAME_stand_56 249 +#define FRAME_stand_57 250 +#define FRAME_stand_58 251 +#define FRAME_stand_59 252 +#define FRAME_stand_60 253 + +#define MODEL_SCALE 1.000000 diff --git a/original/xatrix/m_tank.c b/original/xatrix/m_tank.c new file mode 100644 index 0000000..754ea08 --- /dev/null +++ b/original/xatrix/m_tank.c @@ -0,0 +1,839 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +/* +============================================================================== + +TANK + +============================================================================== +*/ + +#include "g_local.h" +#include "m_tank.h" + + +void tank_refire_rocket (edict_t *self); +void tank_doattack_rocket (edict_t *self); +void tank_reattack_blaster (edict_t *self); + +static int sound_thud; +static int sound_pain; +static int sound_idle; +static int sound_die; +static int sound_step; +static int sound_sight; +static int sound_windup; +static int sound_strike; + +// +// misc +// + +void tank_sight (edict_t *self, edict_t *other) +{ + gi.sound (self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); +} + + +void tank_footstep (edict_t *self) +{ + gi.sound (self, CHAN_BODY, sound_step, 1, ATTN_NORM, 0); +} + +void tank_thud (edict_t *self) +{ + gi.sound (self, CHAN_BODY, sound_thud, 1, ATTN_NORM, 0); +} + +void tank_windup (edict_t *self) +{ + gi.sound (self, CHAN_WEAPON, sound_windup, 1, ATTN_NORM, 0); +} + +void tank_idle (edict_t *self) +{ + gi.sound (self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0); +} + + +// +// stand +// + +mframe_t tank_frames_stand []= +{ + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL +}; +mmove_t tank_move_stand = {FRAME_stand01, FRAME_stand30, tank_frames_stand, NULL}; + +void tank_stand (edict_t *self) +{ + self->monsterinfo.currentmove = &tank_move_stand; +} + + +// +// walk +// + +void tank_walk (edict_t *self); + +mframe_t tank_frames_start_walk [] = +{ + ai_walk, 0, NULL, + ai_walk, 6, NULL, + ai_walk, 6, NULL, + ai_walk, 11, tank_footstep +}; +mmove_t tank_move_start_walk = {FRAME_walk01, FRAME_walk04, tank_frames_start_walk, tank_walk}; + +mframe_t tank_frames_walk [] = +{ + ai_walk, 4, NULL, + ai_walk, 5, NULL, + ai_walk, 3, NULL, + ai_walk, 2, NULL, + ai_walk, 5, NULL, + ai_walk, 5, NULL, + ai_walk, 4, NULL, + ai_walk, 4, tank_footstep, + ai_walk, 3, NULL, + ai_walk, 5, NULL, + ai_walk, 4, NULL, + ai_walk, 5, NULL, + ai_walk, 7, NULL, + ai_walk, 7, NULL, + ai_walk, 6, NULL, + ai_walk, 6, tank_footstep +}; +mmove_t tank_move_walk = {FRAME_walk05, FRAME_walk20, tank_frames_walk, NULL}; + +mframe_t tank_frames_stop_walk [] = +{ + ai_walk, 3, NULL, + ai_walk, 3, NULL, + ai_walk, 2, NULL, + ai_walk, 2, NULL, + ai_walk, 4, tank_footstep +}; +mmove_t tank_move_stop_walk = {FRAME_walk21, FRAME_walk25, tank_frames_stop_walk, tank_stand}; + +void tank_walk (edict_t *self) +{ + self->monsterinfo.currentmove = &tank_move_walk; +} + + +// +// run +// + +void tank_run (edict_t *self); + +mframe_t tank_frames_start_run [] = +{ + ai_run, 0, NULL, + ai_run, 6, NULL, + ai_run, 6, NULL, + ai_run, 11, tank_footstep +}; +mmove_t tank_move_start_run = {FRAME_walk01, FRAME_walk04, tank_frames_start_run, tank_run}; + +mframe_t tank_frames_run [] = +{ + ai_run, 4, NULL, + ai_run, 5, NULL, + ai_run, 3, NULL, + ai_run, 2, NULL, + ai_run, 5, NULL, + ai_run, 5, NULL, + ai_run, 4, NULL, + ai_run, 4, tank_footstep, + ai_run, 3, NULL, + ai_run, 5, NULL, + ai_run, 4, NULL, + ai_run, 5, NULL, + ai_run, 7, NULL, + ai_run, 7, NULL, + ai_run, 6, NULL, + ai_run, 6, tank_footstep +}; +mmove_t tank_move_run = {FRAME_walk05, FRAME_walk20, tank_frames_run, NULL}; + +mframe_t tank_frames_stop_run [] = +{ + ai_run, 3, NULL, + ai_run, 3, NULL, + ai_run, 2, NULL, + ai_run, 2, NULL, + ai_run, 4, tank_footstep +}; +mmove_t tank_move_stop_run = {FRAME_walk21, FRAME_walk25, tank_frames_stop_run, tank_walk}; + +void tank_run (edict_t *self) +{ + if (self->enemy && self->enemy->client) + self->monsterinfo.aiflags |= AI_BRUTAL; + else + self->monsterinfo.aiflags &= ~AI_BRUTAL; + + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + { + self->monsterinfo.currentmove = &tank_move_stand; + return; + } + + if (self->monsterinfo.currentmove == &tank_move_walk || + self->monsterinfo.currentmove == &tank_move_start_run) + { + self->monsterinfo.currentmove = &tank_move_run; + } + else + { + self->monsterinfo.currentmove = &tank_move_start_run; + } +} + +// +// pain +// + +mframe_t tank_frames_pain1 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t tank_move_pain1 = {FRAME_pain101, FRAME_pain104, tank_frames_pain1, tank_run}; + +mframe_t tank_frames_pain2 [] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t tank_move_pain2 = {FRAME_pain201, FRAME_pain205, tank_frames_pain2, tank_run}; + +mframe_t tank_frames_pain3 [] = +{ + ai_move, -7, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 2, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 3, NULL, + ai_move, 0, NULL, + ai_move, 2, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, tank_footstep +}; +mmove_t tank_move_pain3 = {FRAME_pain301, FRAME_pain316, tank_frames_pain3, tank_run}; + + +void tank_pain (edict_t *self, edict_t *other, float kick, int damage) +{ + if (self->health < (self->max_health / 2)) + self->s.skinnum |= 1; + + if (damage <= 10) + return; + + if (level.time < self->pain_debounce_time) + return; + + if (damage <= 30) + if (random() > 0.2) + return; + + // If hard or nightmare, don't go into pain while attacking + if ( skill->value >= 2) + { + if ( (self->s.frame >= FRAME_attak301) && (self->s.frame <= FRAME_attak330) ) + return; + if ( (self->s.frame >= FRAME_attak101) && (self->s.frame <= FRAME_attak116) ) + return; + } + + self->pain_debounce_time = level.time + 3; + gi.sound (self, CHAN_VOICE, sound_pain, 1, ATTN_NORM, 0); + + if (skill->value == 3) + return; // no pain anims in nightmare + + if (damage <= 30) + self->monsterinfo.currentmove = &tank_move_pain1; + else if (damage <= 60) + self->monsterinfo.currentmove = &tank_move_pain2; + else + self->monsterinfo.currentmove = &tank_move_pain3; +}; + + +// +// attacks +// + +void TankBlaster (edict_t *self) +{ + vec3_t forward, right; + vec3_t start; + vec3_t end; + vec3_t dir; + int flash_number; + + if (self->s.frame == FRAME_attak110) + flash_number = MZ2_TANK_BLASTER_1; + else if (self->s.frame == FRAME_attak113) + flash_number = MZ2_TANK_BLASTER_2; + else // (self->s.frame == FRAME_attak116) + flash_number = MZ2_TANK_BLASTER_3; + + AngleVectors (self->s.angles, forward, right, NULL); + G_ProjectSource (self->s.origin, monster_flash_offset[flash_number], forward, right, start); + + VectorCopy (self->enemy->s.origin, end); + end[2] += self->enemy->viewheight; + VectorSubtract (end, start, dir); + + monster_fire_blaster (self, start, dir, 30, 800, flash_number, EF_BLASTER); +} + +void TankStrike (edict_t *self) +{ + gi.sound (self, CHAN_WEAPON, sound_strike, 1, ATTN_NORM, 0); +} + +void TankRocket (edict_t *self) +{ + vec3_t forward, right; + vec3_t start; + vec3_t dir; + vec3_t vec; + int flash_number; + + if (self->s.frame == FRAME_attak324) + flash_number = MZ2_TANK_ROCKET_1; + else if (self->s.frame == FRAME_attak327) + flash_number = MZ2_TANK_ROCKET_2; + else // (self->s.frame == FRAME_attak330) + flash_number = MZ2_TANK_ROCKET_3; + + AngleVectors (self->s.angles, forward, right, NULL); + G_ProjectSource (self->s.origin, monster_flash_offset[flash_number], forward, right, start); + + VectorCopy (self->enemy->s.origin, vec); + vec[2] += self->enemy->viewheight; + VectorSubtract (vec, start, dir); + VectorNormalize (dir); + + monster_fire_rocket (self, start, dir, 50, 550, flash_number); +} + +void TankMachineGun (edict_t *self) +{ + vec3_t dir; + vec3_t vec; + vec3_t start; + vec3_t forward, right; + int flash_number; + + flash_number = MZ2_TANK_MACHINEGUN_1 + (self->s.frame - FRAME_attak406); + + AngleVectors (self->s.angles, forward, right, NULL); + G_ProjectSource (self->s.origin, monster_flash_offset[flash_number], forward, right, start); + + if (self->enemy) + { + VectorCopy (self->enemy->s.origin, vec); + vec[2] += self->enemy->viewheight; + VectorSubtract (vec, start, vec); + vectoangles (vec, vec); + dir[0] = vec[0]; + } + else + { + dir[0] = 0; + } + if (self->s.frame <= FRAME_attak415) + dir[1] = self->s.angles[1] - 8 * (self->s.frame - FRAME_attak411); + else + dir[1] = self->s.angles[1] + 8 * (self->s.frame - FRAME_attak419); + dir[2] = 0; + + AngleVectors (dir, forward, NULL, NULL); + + monster_fire_bullet (self, start, forward, 20, 4, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, flash_number); +} + + +mframe_t tank_frames_attack_blast [] = +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, -1, NULL, + ai_charge, -2, NULL, + ai_charge, -1, NULL, + ai_charge, -1, NULL, + ai_charge, 0, NULL, + ai_charge, 0, TankBlaster, // 10 + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, TankBlaster, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, TankBlaster // 16 +}; +mmove_t tank_move_attack_blast = {FRAME_attak101, FRAME_attak116, tank_frames_attack_blast, tank_reattack_blaster}; + +mframe_t tank_frames_reattack_blast [] = +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, TankBlaster, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, TankBlaster // 16 +}; +mmove_t tank_move_reattack_blast = {FRAME_attak111, FRAME_attak116, tank_frames_reattack_blast, tank_reattack_blaster}; + +mframe_t tank_frames_attack_post_blast [] = +{ + ai_move, 0, NULL, // 17 + ai_move, 0, NULL, + ai_move, 2, NULL, + ai_move, 3, NULL, + ai_move, 2, NULL, + ai_move, -2, tank_footstep // 22 +}; +mmove_t tank_move_attack_post_blast = {FRAME_attak117, FRAME_attak122, tank_frames_attack_post_blast, tank_run}; + +void tank_reattack_blaster (edict_t *self) +{ + if (skill->value >= 2) + if (visible (self, self->enemy)) + if (self->enemy->health > 0) + if (random() <= 0.6) + { + self->monsterinfo.currentmove = &tank_move_reattack_blast; + return; + } + self->monsterinfo.currentmove = &tank_move_attack_post_blast; +} + + +void tank_poststrike (edict_t *self) +{ + self->enemy = NULL; + tank_run (self); +} + +mframe_t tank_frames_attack_strike [] = +{ + ai_move, 3, NULL, + ai_move, 2, NULL, + ai_move, 2, NULL, + ai_move, 1, NULL, + ai_move, 6, NULL, + ai_move, 7, NULL, + ai_move, 9, tank_footstep, + ai_move, 2, NULL, + ai_move, 1, NULL, + ai_move, 2, NULL, + ai_move, 2, tank_footstep, + ai_move, 2, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, -2, NULL, + ai_move, -2, NULL, + ai_move, 0, tank_windup, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, TankStrike, + ai_move, 0, NULL, + ai_move, -1, NULL, + ai_move, -1, NULL, + ai_move, -1, NULL, + ai_move, -1, NULL, + ai_move, -1, NULL, + ai_move, -3, NULL, + ai_move, -10, NULL, + ai_move, -10, NULL, + ai_move, -2, NULL, + ai_move, -3, NULL, + ai_move, -2, tank_footstep +}; +mmove_t tank_move_attack_strike = {FRAME_attak201, FRAME_attak238, tank_frames_attack_strike, tank_poststrike}; + +mframe_t tank_frames_attack_pre_rocket [] = +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, // 10 + + ai_charge, 0, NULL, + ai_charge, 1, NULL, + ai_charge, 2, NULL, + ai_charge, 7, NULL, + ai_charge, 7, NULL, + ai_charge, 7, tank_footstep, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, // 20 + + ai_charge, -3, NULL +}; +mmove_t tank_move_attack_pre_rocket = {FRAME_attak301, FRAME_attak321, tank_frames_attack_pre_rocket, tank_doattack_rocket}; + +mframe_t tank_frames_attack_fire_rocket [] = +{ + ai_charge, -3, NULL, // Loop Start 22 + ai_charge, 0, NULL, + ai_charge, 0, TankRocket, // 24 + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, TankRocket, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, -1, TankRocket // 30 Loop End +}; +mmove_t tank_move_attack_fire_rocket = {FRAME_attak322, FRAME_attak330, tank_frames_attack_fire_rocket, tank_refire_rocket}; + +mframe_t tank_frames_attack_post_rocket [] = +{ + ai_charge, 0, NULL, // 31 + ai_charge, -1, NULL, + ai_charge, -1, NULL, + ai_charge, 0, NULL, + ai_charge, 2, NULL, + ai_charge, 3, NULL, + ai_charge, 4, NULL, + ai_charge, 2, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, // 40 + + ai_charge, 0, NULL, + ai_charge, -9, NULL, + ai_charge, -8, NULL, + ai_charge, -7, NULL, + ai_charge, -1, NULL, + ai_charge, -1, tank_footstep, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, // 50 + + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL +}; +mmove_t tank_move_attack_post_rocket = {FRAME_attak331, FRAME_attak353, tank_frames_attack_post_rocket, tank_run}; + +mframe_t tank_frames_attack_chain [] = +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + NULL, 0, TankMachineGun, + NULL, 0, TankMachineGun, + NULL, 0, TankMachineGun, + NULL, 0, TankMachineGun, + NULL, 0, TankMachineGun, + NULL, 0, TankMachineGun, + NULL, 0, TankMachineGun, + NULL, 0, TankMachineGun, + NULL, 0, TankMachineGun, + NULL, 0, TankMachineGun, + NULL, 0, TankMachineGun, + NULL, 0, TankMachineGun, + NULL, 0, TankMachineGun, + NULL, 0, TankMachineGun, + NULL, 0, TankMachineGun, + NULL, 0, TankMachineGun, + NULL, 0, TankMachineGun, + NULL, 0, TankMachineGun, + NULL, 0, TankMachineGun, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL +}; +mmove_t tank_move_attack_chain = {FRAME_attak401, FRAME_attak429, tank_frames_attack_chain, tank_run}; + +void tank_refire_rocket (edict_t *self) +{ + // Only on hard or nightmare + if ( skill->value >= 2 ) + if (self->enemy->health > 0) + if (visible(self, self->enemy) ) + if (random() <= 0.4) + { + self->monsterinfo.currentmove = &tank_move_attack_fire_rocket; + return; + } + self->monsterinfo.currentmove = &tank_move_attack_post_rocket; +} + +void tank_doattack_rocket (edict_t *self) +{ + self->monsterinfo.currentmove = &tank_move_attack_fire_rocket; +} + +void tank_attack(edict_t *self) +{ + vec3_t vec; + float range; + float r; + + if (self->enemy->health < 0) + { + self->monsterinfo.currentmove = &tank_move_attack_strike; + self->monsterinfo.aiflags &= ~AI_BRUTAL; + return; + } + + VectorSubtract (self->enemy->s.origin, self->s.origin, vec); + range = VectorLength (vec); + + r = random(); + + if (range <= 125) + { + if (r < 0.4) + self->monsterinfo.currentmove = &tank_move_attack_chain; + else + self->monsterinfo.currentmove = &tank_move_attack_blast; + } + else if (range <= 250) + { + if (r < 0.5) + self->monsterinfo.currentmove = &tank_move_attack_chain; + else + self->monsterinfo.currentmove = &tank_move_attack_blast; + } + else + { + if (r < 0.33) + self->monsterinfo.currentmove = &tank_move_attack_chain; + else if (r < 0.66) + { + self->monsterinfo.currentmove = &tank_move_attack_pre_rocket; + self->pain_debounce_time = level.time + 5.0; // no pain for a while + } + else + self->monsterinfo.currentmove = &tank_move_attack_blast; + } +} + + +// +// death +// + +void tank_dead (edict_t *self) +{ + VectorSet (self->mins, -16, -16, -16); + VectorSet (self->maxs, 16, 16, -0); + self->movetype = MOVETYPE_TOSS; + self->svflags |= SVF_DEADMONSTER; + self->nextthink = 0; + gi.linkentity (self); +} + +mframe_t tank_frames_death1 [] = +{ + ai_move, -7, NULL, + ai_move, -2, NULL, + ai_move, -2, NULL, + ai_move, 1, NULL, + ai_move, 3, NULL, + ai_move, 6, NULL, + ai_move, 1, NULL, + ai_move, 1, NULL, + ai_move, 2, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, -2, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, -3, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, -4, NULL, + ai_move, -6, NULL, + ai_move, -4, NULL, + ai_move, -5, NULL, + ai_move, -7, NULL, + ai_move, -15, tank_thud, + ai_move, -5, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t tank_move_death = {FRAME_death101, FRAME_death132, tank_frames_death1, tank_dead}; + +void tank_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + int n; + +// check for gib + if (self->health <= self->gib_health) + { + gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0); + for (n= 0; n < 1 /*4*/; n++) + ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); + for (n= 0; n < 4; n++) + ThrowGib (self, "models/objects/gibs/sm_metal/tris.md2", damage, GIB_METALLIC); + ThrowGib (self, "models/objects/gibs/chest/tris.md2", damage, GIB_ORGANIC); + ThrowHead (self, "models/objects/gibs/gear/tris.md2", damage, GIB_METALLIC); + self->deadflag = DEAD_DEAD; + return; + } + + if (self->deadflag == DEAD_DEAD) + return; + +// regular death + gi.sound (self, CHAN_VOICE, sound_die, 1, ATTN_NORM, 0); + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_YES; + + self->monsterinfo.currentmove = &tank_move_death; + +} + + +// +// monster_tank +// + +/*QUAKED monster_tank (1 .5 0) (-32 -32 -16) (32 32 72) Ambush Trigger_Spawn Sight +*/ +/*QUAKED monster_tank_commander (1 .5 0) (-32 -32 -16) (32 32 72) Ambush Trigger_Spawn Sight +*/ +void SP_monster_tank (edict_t *self) +{ + if (deathmatch->value) + { + G_FreeEdict (self); + return; + } + + self->s.modelindex = gi.modelindex ("models/monsters/tank/tris.md2"); + VectorSet (self->mins, -32, -32, -16); + VectorSet (self->maxs, 32, 32, 72); + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + + sound_pain = gi.soundindex ("tank/tnkpain2.wav"); + sound_thud = gi.soundindex ("tank/tnkdeth2.wav"); + sound_idle = gi.soundindex ("tank/tnkidle1.wav"); + sound_die = gi.soundindex ("tank/death.wav"); + sound_step = gi.soundindex ("tank/step.wav"); + sound_windup = gi.soundindex ("tank/tnkatck4.wav"); + sound_strike = gi.soundindex ("tank/tnkatck5.wav"); + sound_sight = gi.soundindex ("tank/sight1.wav"); + + gi.soundindex ("tank/tnkatck1.wav"); + gi.soundindex ("tank/tnkatk2a.wav"); + gi.soundindex ("tank/tnkatk2b.wav"); + gi.soundindex ("tank/tnkatk2c.wav"); + gi.soundindex ("tank/tnkatk2d.wav"); + gi.soundindex ("tank/tnkatk2e.wav"); + gi.soundindex ("tank/tnkatck3.wav"); + + if (strcmp(self->classname, "monster_tank_commander") == 0) + { + self->health = 1000; + self->gib_health = -225; + } + else + { + self->health = 750; + self->gib_health = -200; + } + + self->mass = 500; + + self->pain = tank_pain; + self->die = tank_die; + self->monsterinfo.stand = tank_stand; + self->monsterinfo.walk = tank_walk; + self->monsterinfo.run = tank_run; + self->monsterinfo.dodge = NULL; + self->monsterinfo.attack = tank_attack; + self->monsterinfo.melee = NULL; + self->monsterinfo.sight = tank_sight; + self->monsterinfo.idle = tank_idle; + + gi.linkentity (self); + + self->monsterinfo.currentmove = &tank_move_stand; + self->monsterinfo.scale = MODEL_SCALE; + + walkmonster_start(self); + + if (strcmp(self->classname, "monster_tank_commander") == 0) + self->s.skinnum = 2; +} diff --git a/original/xatrix/m_tank.h b/original/xatrix/m_tank.h new file mode 100644 index 0000000..ea7bc07 --- /dev/null +++ b/original/xatrix/m_tank.h @@ -0,0 +1,302 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// G:\quake2\baseq2\models/monsters/tank + +// This file generated by qdata - Do NOT Modify + +#define FRAME_stand01 0 +#define FRAME_stand02 1 +#define FRAME_stand03 2 +#define FRAME_stand04 3 +#define FRAME_stand05 4 +#define FRAME_stand06 5 +#define FRAME_stand07 6 +#define FRAME_stand08 7 +#define FRAME_stand09 8 +#define FRAME_stand10 9 +#define FRAME_stand11 10 +#define FRAME_stand12 11 +#define FRAME_stand13 12 +#define FRAME_stand14 13 +#define FRAME_stand15 14 +#define FRAME_stand16 15 +#define FRAME_stand17 16 +#define FRAME_stand18 17 +#define FRAME_stand19 18 +#define FRAME_stand20 19 +#define FRAME_stand21 20 +#define FRAME_stand22 21 +#define FRAME_stand23 22 +#define FRAME_stand24 23 +#define FRAME_stand25 24 +#define FRAME_stand26 25 +#define FRAME_stand27 26 +#define FRAME_stand28 27 +#define FRAME_stand29 28 +#define FRAME_stand30 29 +#define FRAME_walk01 30 +#define FRAME_walk02 31 +#define FRAME_walk03 32 +#define FRAME_walk04 33 +#define FRAME_walk05 34 +#define FRAME_walk06 35 +#define FRAME_walk07 36 +#define FRAME_walk08 37 +#define FRAME_walk09 38 +#define FRAME_walk10 39 +#define FRAME_walk11 40 +#define FRAME_walk12 41 +#define FRAME_walk13 42 +#define FRAME_walk14 43 +#define FRAME_walk15 44 +#define FRAME_walk16 45 +#define FRAME_walk17 46 +#define FRAME_walk18 47 +#define FRAME_walk19 48 +#define FRAME_walk20 49 +#define FRAME_walk21 50 +#define FRAME_walk22 51 +#define FRAME_walk23 52 +#define FRAME_walk24 53 +#define FRAME_walk25 54 +#define FRAME_attak101 55 +#define FRAME_attak102 56 +#define FRAME_attak103 57 +#define FRAME_attak104 58 +#define FRAME_attak105 59 +#define FRAME_attak106 60 +#define FRAME_attak107 61 +#define FRAME_attak108 62 +#define FRAME_attak109 63 +#define FRAME_attak110 64 +#define FRAME_attak111 65 +#define FRAME_attak112 66 +#define FRAME_attak113 67 +#define FRAME_attak114 68 +#define FRAME_attak115 69 +#define FRAME_attak116 70 +#define FRAME_attak117 71 +#define FRAME_attak118 72 +#define FRAME_attak119 73 +#define FRAME_attak120 74 +#define FRAME_attak121 75 +#define FRAME_attak122 76 +#define FRAME_attak201 77 +#define FRAME_attak202 78 +#define FRAME_attak203 79 +#define FRAME_attak204 80 +#define FRAME_attak205 81 +#define FRAME_attak206 82 +#define FRAME_attak207 83 +#define FRAME_attak208 84 +#define FRAME_attak209 85 +#define FRAME_attak210 86 +#define FRAME_attak211 87 +#define FRAME_attak212 88 +#define FRAME_attak213 89 +#define FRAME_attak214 90 +#define FRAME_attak215 91 +#define FRAME_attak216 92 +#define FRAME_attak217 93 +#define FRAME_attak218 94 +#define FRAME_attak219 95 +#define FRAME_attak220 96 +#define FRAME_attak221 97 +#define FRAME_attak222 98 +#define FRAME_attak223 99 +#define FRAME_attak224 100 +#define FRAME_attak225 101 +#define FRAME_attak226 102 +#define FRAME_attak227 103 +#define FRAME_attak228 104 +#define FRAME_attak229 105 +#define FRAME_attak230 106 +#define FRAME_attak231 107 +#define FRAME_attak232 108 +#define FRAME_attak233 109 +#define FRAME_attak234 110 +#define FRAME_attak235 111 +#define FRAME_attak236 112 +#define FRAME_attak237 113 +#define FRAME_attak238 114 +#define FRAME_attak301 115 +#define FRAME_attak302 116 +#define FRAME_attak303 117 +#define FRAME_attak304 118 +#define FRAME_attak305 119 +#define FRAME_attak306 120 +#define FRAME_attak307 121 +#define FRAME_attak308 122 +#define FRAME_attak309 123 +#define FRAME_attak310 124 +#define FRAME_attak311 125 +#define FRAME_attak312 126 +#define FRAME_attak313 127 +#define FRAME_attak314 128 +#define FRAME_attak315 129 +#define FRAME_attak316 130 +#define FRAME_attak317 131 +#define FRAME_attak318 132 +#define FRAME_attak319 133 +#define FRAME_attak320 134 +#define FRAME_attak321 135 +#define FRAME_attak322 136 +#define FRAME_attak323 137 +#define FRAME_attak324 138 +#define FRAME_attak325 139 +#define FRAME_attak326 140 +#define FRAME_attak327 141 +#define FRAME_attak328 142 +#define FRAME_attak329 143 +#define FRAME_attak330 144 +#define FRAME_attak331 145 +#define FRAME_attak332 146 +#define FRAME_attak333 147 +#define FRAME_attak334 148 +#define FRAME_attak335 149 +#define FRAME_attak336 150 +#define FRAME_attak337 151 +#define FRAME_attak338 152 +#define FRAME_attak339 153 +#define FRAME_attak340 154 +#define FRAME_attak341 155 +#define FRAME_attak342 156 +#define FRAME_attak343 157 +#define FRAME_attak344 158 +#define FRAME_attak345 159 +#define FRAME_attak346 160 +#define FRAME_attak347 161 +#define FRAME_attak348 162 +#define FRAME_attak349 163 +#define FRAME_attak350 164 +#define FRAME_attak351 165 +#define FRAME_attak352 166 +#define FRAME_attak353 167 +#define FRAME_attak401 168 +#define FRAME_attak402 169 +#define FRAME_attak403 170 +#define FRAME_attak404 171 +#define FRAME_attak405 172 +#define FRAME_attak406 173 +#define FRAME_attak407 174 +#define FRAME_attak408 175 +#define FRAME_attak409 176 +#define FRAME_attak410 177 +#define FRAME_attak411 178 +#define FRAME_attak412 179 +#define FRAME_attak413 180 +#define FRAME_attak414 181 +#define FRAME_attak415 182 +#define FRAME_attak416 183 +#define FRAME_attak417 184 +#define FRAME_attak418 185 +#define FRAME_attak419 186 +#define FRAME_attak420 187 +#define FRAME_attak421 188 +#define FRAME_attak422 189 +#define FRAME_attak423 190 +#define FRAME_attak424 191 +#define FRAME_attak425 192 +#define FRAME_attak426 193 +#define FRAME_attak427 194 +#define FRAME_attak428 195 +#define FRAME_attak429 196 +#define FRAME_pain101 197 +#define FRAME_pain102 198 +#define FRAME_pain103 199 +#define FRAME_pain104 200 +#define FRAME_pain201 201 +#define FRAME_pain202 202 +#define FRAME_pain203 203 +#define FRAME_pain204 204 +#define FRAME_pain205 205 +#define FRAME_pain301 206 +#define FRAME_pain302 207 +#define FRAME_pain303 208 +#define FRAME_pain304 209 +#define FRAME_pain305 210 +#define FRAME_pain306 211 +#define FRAME_pain307 212 +#define FRAME_pain308 213 +#define FRAME_pain309 214 +#define FRAME_pain310 215 +#define FRAME_pain311 216 +#define FRAME_pain312 217 +#define FRAME_pain313 218 +#define FRAME_pain314 219 +#define FRAME_pain315 220 +#define FRAME_pain316 221 +#define FRAME_death101 222 +#define FRAME_death102 223 +#define FRAME_death103 224 +#define FRAME_death104 225 +#define FRAME_death105 226 +#define FRAME_death106 227 +#define FRAME_death107 228 +#define FRAME_death108 229 +#define FRAME_death109 230 +#define FRAME_death110 231 +#define FRAME_death111 232 +#define FRAME_death112 233 +#define FRAME_death113 234 +#define FRAME_death114 235 +#define FRAME_death115 236 +#define FRAME_death116 237 +#define FRAME_death117 238 +#define FRAME_death118 239 +#define FRAME_death119 240 +#define FRAME_death120 241 +#define FRAME_death121 242 +#define FRAME_death122 243 +#define FRAME_death123 244 +#define FRAME_death124 245 +#define FRAME_death125 246 +#define FRAME_death126 247 +#define FRAME_death127 248 +#define FRAME_death128 249 +#define FRAME_death129 250 +#define FRAME_death130 251 +#define FRAME_death131 252 +#define FRAME_death132 253 +#define FRAME_recln101 254 +#define FRAME_recln102 255 +#define FRAME_recln103 256 +#define FRAME_recln104 257 +#define FRAME_recln105 258 +#define FRAME_recln106 259 +#define FRAME_recln107 260 +#define FRAME_recln108 261 +#define FRAME_recln109 262 +#define FRAME_recln110 263 +#define FRAME_recln111 264 +#define FRAME_recln112 265 +#define FRAME_recln113 266 +#define FRAME_recln114 267 +#define FRAME_recln115 268 +#define FRAME_recln116 269 +#define FRAME_recln117 270 +#define FRAME_recln118 271 +#define FRAME_recln119 272 +#define FRAME_recln120 273 +#define FRAME_recln121 274 +#define FRAME_recln122 275 +#define FRAME_recln123 276 +#define FRAME_recln124 277 +#define FRAME_recln125 278 +#define FRAME_recln126 279 +#define FRAME_recln127 280 +#define FRAME_recln128 281 +#define FRAME_recln129 282 +#define FRAME_recln130 283 +#define FRAME_recln131 284 +#define FRAME_recln132 285 +#define FRAME_recln133 286 +#define FRAME_recln134 287 +#define FRAME_recln135 288 +#define FRAME_recln136 289 +#define FRAME_recln137 290 +#define FRAME_recln138 291 +#define FRAME_recln139 292 +#define FRAME_recln140 293 + +#define MODEL_SCALE 1.000000 diff --git a/original/xatrix/p_client.c b/original/xatrix/p_client.c new file mode 100644 index 0000000..2400d40 --- /dev/null +++ b/original/xatrix/p_client.c @@ -0,0 +1,1857 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +#include "g_local.h" +#include "m_player.h" + +void ClientUserinfoChanged (edict_t *ent, char *userinfo); + +void SP_misc_teleporter_dest (edict_t *ent); + +// +// Gross, ugly, disgustuing hack section +// + +// this function is an ugly as hell hack to fix some map flaws +// +// the coop spawn spots on some maps are SNAFU. There are coop spots +// with the wrong targetname as well as spots with no name at all +// +// we use carnal knowledge of the maps to fix the coop spot targetnames to match +// that of the nearest named single player spot + +static void SP_FixCoopSpots (edict_t *self) +{ + edict_t *spot; + vec3_t d; + + spot = NULL; + + while(1) + { + spot = G_Find(spot, FOFS(classname), "info_player_start"); + if (!spot) + return; + if (!spot->targetname) + continue; + VectorSubtract(self->s.origin, spot->s.origin, d); + if (VectorLength(d) < 384) + { + if ((!self->targetname) || stricmp(self->targetname, spot->targetname) != 0) + { +// gi.dprintf("FixCoopSpots changed %s at %s targetname from %s to %s\n", self->classname, vtos(self->s.origin), self->targetname, spot->targetname); + self->targetname = spot->targetname; + } + return; + } + } +} + +// now if that one wasn't ugly enough for you then try this one on for size +// some maps don't have any coop spots at all, so we need to create them +// where they should have been + +static void SP_CreateCoopSpots (edict_t *self) +{ + edict_t *spot; + + if(stricmp(level.mapname, "security") == 0) + { + spot = G_Spawn(); + spot->classname = "info_player_coop"; + spot->s.origin[0] = 188 - 64; + spot->s.origin[1] = -164; + spot->s.origin[2] = 80; + spot->targetname = "jail3"; + spot->s.angles[1] = 90; + + spot = G_Spawn(); + spot->classname = "info_player_coop"; + spot->s.origin[0] = 188 + 64; + spot->s.origin[1] = -164; + spot->s.origin[2] = 80; + spot->targetname = "jail3"; + spot->s.angles[1] = 90; + + spot = G_Spawn(); + spot->classname = "info_player_coop"; + spot->s.origin[0] = 188 + 128; + spot->s.origin[1] = -164; + spot->s.origin[2] = 80; + spot->targetname = "jail3"; + spot->s.angles[1] = 90; + + return; + } +} + + +/*QUAKED info_player_start (1 0 0) (-16 -16 -24) (16 16 32) +The normal starting point for a level. +*/ +void SP_info_player_start(edict_t *self) +{ + if (!coop->value) + return; + if(stricmp(level.mapname, "security") == 0) + { + // invoke one of our gross, ugly, disgusting hacks + self->think = SP_CreateCoopSpots; + self->nextthink = level.time + FRAMETIME; + } +} + +/*QUAKED info_player_deathmatch (1 0 1) (-16 -16 -24) (16 16 32) +potential spawning position for deathmatch games +*/ +void SP_info_player_deathmatch(edict_t *self) +{ + if (!deathmatch->value) + { + G_FreeEdict (self); + return; + } + SP_misc_teleporter_dest (self); +} + +/*QUAKED info_player_coop (1 0 1) (-16 -16 -24) (16 16 32) +potential spawning position for coop games +*/ + +void SP_info_player_coop(edict_t *self) +{ + if (!coop->value) + { + G_FreeEdict (self); + return; + } + + if((stricmp(level.mapname, "jail2") == 0) || + (stricmp(level.mapname, "jail4") == 0) || + (stricmp(level.mapname, "mine1") == 0) || + (stricmp(level.mapname, "mine2") == 0) || + (stricmp(level.mapname, "mine3") == 0) || + (stricmp(level.mapname, "mine4") == 0) || + (stricmp(level.mapname, "lab") == 0) || + (stricmp(level.mapname, "boss1") == 0) || + (stricmp(level.mapname, "fact3") == 0) || + (stricmp(level.mapname, "biggun") == 0) || + (stricmp(level.mapname, "space") == 0) || + (stricmp(level.mapname, "command") == 0) || + (stricmp(level.mapname, "power2") == 0) || + (stricmp(level.mapname, "strike") == 0)) + { + // invoke one of our gross, ugly, disgusting hacks + self->think = SP_FixCoopSpots; + self->nextthink = level.time + FRAMETIME; + } +} + + +/*QUAKED info_player_intermission (1 0 1) (-16 -16 -24) (16 16 32) +The deathmatch intermission point will be at one of these +Use 'angles' instead of 'angle', so you can set pitch or roll as well as yaw. 'pitch yaw roll' +*/ +void SP_info_player_intermission(void) +{ +} + + +//======================================================================= + + +void player_pain (edict_t *self, edict_t *other, float kick, int damage) +{ + // player pain is handled at the end of the frame in P_DamageFeedback +} + + +qboolean IsFemale (edict_t *ent) +{ + char *info; + + if (!ent->client) + return false; + + info = Info_ValueForKey (ent->client->pers.userinfo, "gender"); + if (info[0] == 'f' || info[0] == 'F') + return true; + return false; +} + +qboolean IsNeutral (edict_t *ent) +{ + char *info; + + if (!ent->client) + return false; + + info = Info_ValueForKey (ent->client->pers.userinfo, "gender"); + if (info[0] != 'f' && info[0] != 'F' && info[0] != 'm' && info[0] != 'M') + return true; + return false; +} + +void ClientObituary (edict_t *self, edict_t *inflictor, edict_t *attacker) +{ + int mod; + char *message; + char *message2; + qboolean ff; + + if (coop->value && attacker->client) + meansOfDeath |= MOD_FRIENDLY_FIRE; + + if (deathmatch->value || coop->value) + { + ff = meansOfDeath & MOD_FRIENDLY_FIRE; + mod = meansOfDeath & ~MOD_FRIENDLY_FIRE; + message = NULL; + message2 = ""; + + switch (mod) + { + case MOD_SUICIDE: + message = "suicides"; + break; + case MOD_FALLING: + message = "cratered"; + break; + case MOD_CRUSH: + message = "was squished"; + break; + case MOD_WATER: + message = "sank like a rock"; + break; + case MOD_SLIME: + message = "melted"; + break; + case MOD_LAVA: + message = "does a back flip into the lava"; + break; + case MOD_EXPLOSIVE: + case MOD_BARREL: + message = "blew up"; + break; + case MOD_EXIT: + message = "found a way out"; + break; + case MOD_TARGET_LASER: + message = "saw the light"; + break; + case MOD_TARGET_BLASTER: + message = "got blasted"; + break; + case MOD_BOMB: + case MOD_SPLASH: + case MOD_TRIGGER_HURT: + message = "was in the wrong place"; + break; + // RAFAEL + case MOD_GEKK: + case MOD_BRAINTENTACLE: + message = "that's gotta hurt"; + break; + } + if (attacker == self) + { + switch (mod) + { + case MOD_HELD_GRENADE: + message = "tried to put the pin back in"; + break; + case MOD_HG_SPLASH: + case MOD_G_SPLASH: + if (IsNeutral(self)) + message = "tripped on its own grenade"; + else if (IsFemale(self)) + message = "tripped on her own grenade"; + else + message = "tripped on his own grenade"; + break; + case MOD_R_SPLASH: + if (IsNeutral(self)) + message = "blew itself up"; + else if (IsFemale(self)) + message = "blew herself up"; + else + message = "blew himself up"; + break; + case MOD_BFG_BLAST: + message = "should have used a smaller gun"; + break; + // RAFAEL 03-MAY-98 + case MOD_TRAP: + message = "sucked into his own trap"; + break; + default: + if (IsNeutral(self)) + message = "killed itself"; + else if (IsFemale(self)) + message = "killed herself"; + else + message = "killed himself"; + break; + } + } + if (message) + { + gi.bprintf (PRINT_MEDIUM, "%s %s.\n", self->client->pers.netname, message); + if (deathmatch->value) + self->client->resp.score--; + self->enemy = NULL; + return; + } + + self->enemy = attacker; + if (attacker && attacker->client) + { + switch (mod) + { + case MOD_BLASTER: + message = "was blasted by"; + break; + case MOD_SHOTGUN: + message = "was gunned down by"; + break; + case MOD_SSHOTGUN: + message = "was blown away by"; + message2 = "'s super shotgun"; + break; + case MOD_MACHINEGUN: + message = "was machinegunned by"; + break; + case MOD_CHAINGUN: + message = "was cut in half by"; + message2 = "'s chaingun"; + break; + case MOD_GRENADE: + message = "was popped by"; + message2 = "'s grenade"; + break; + case MOD_G_SPLASH: + message = "was shredded by"; + message2 = "'s shrapnel"; + break; + case MOD_ROCKET: + message = "ate"; + message2 = "'s rocket"; + break; + case MOD_R_SPLASH: + message = "almost dodged"; + message2 = "'s rocket"; + break; + case MOD_HYPERBLASTER: + message = "was melted by"; + message2 = "'s hyperblaster"; + break; + case MOD_RAILGUN: + message = "was railed by"; + break; + case MOD_BFG_LASER: + message = "saw the pretty lights from"; + message2 = "'s BFG"; + break; + case MOD_BFG_BLAST: + message = "was disintegrated by"; + message2 = "'s BFG blast"; + break; + case MOD_BFG_EFFECT: + message = "couldn't hide from"; + message2 = "'s BFG"; + break; + case MOD_HANDGRENADE: + message = "caught"; + message2 = "'s handgrenade"; + break; + case MOD_HG_SPLASH: + message = "didn't see"; + message2 = "'s handgrenade"; + break; + case MOD_HELD_GRENADE: + message = "feels"; + message2 = "'s pain"; + break; + case MOD_TELEFRAG: + message = "tried to invade"; + message2 = "'s personal space"; + break; + // RAFAEL 14-APR-98 + case MOD_RIPPER: + message = "ripped to shreds by"; + message2 = "'s ripper gun"; + break; + case MOD_PHALANX: + message = "was evaporated by"; + break; + case MOD_TRAP: + message = "caught in trap by"; + break; + // END 14-APR-98 + } + if (message) + { + gi.bprintf (PRINT_MEDIUM,"%s %s %s%s\n", self->client->pers.netname, message, attacker->client->pers.netname, message2); + if (deathmatch->value) + { + if (ff) + attacker->client->resp.score--; + else + attacker->client->resp.score++; + } + return; + } + } + } + + gi.bprintf (PRINT_MEDIUM,"%s died.\n", self->client->pers.netname); + if (deathmatch->value) + self->client->resp.score--; +} + + +void Touch_Item (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf); + +void TossClientWeapon (edict_t *self) +{ + gitem_t *item; + edict_t *drop; + qboolean quad; + // RAFAEL + qboolean quadfire; + float spread; + + if (!deathmatch->value) + return; + + item = self->client->pers.weapon; + if (! self->client->pers.inventory[self->client->ammo_index] ) + item = NULL; + if (item && (strcmp (item->pickup_name, "Blaster") == 0)) + item = NULL; + + if (!((int)(dmflags->value) & DF_QUAD_DROP)) + quad = false; + else + quad = (self->client->quad_framenum > (level.framenum + 10)); + + // RAFAEL + if (!((int)(dmflags->value) & DF_QUADFIRE_DROP)) + quadfire = false; + else + quadfire = (self->client->quadfire_framenum > (level.framenum + 10)); + + + if (item && quad) + spread = 22.5; + else if (item && quadfire) + spread = 12.5; + else + spread = 0.0; + + if (item) + { + self->client->v_angle[YAW] -= spread; + drop = Drop_Item (self, item); + self->client->v_angle[YAW] += spread; + drop->spawnflags = DROPPED_PLAYER_ITEM; + } + + if (quad) + { + self->client->v_angle[YAW] += spread; + drop = Drop_Item (self, FindItemByClassname ("item_quad")); + self->client->v_angle[YAW] -= spread; + drop->spawnflags |= DROPPED_PLAYER_ITEM; + + drop->touch = Touch_Item; + drop->nextthink = level.time + (self->client->quad_framenum - level.framenum) * FRAMETIME; + drop->think = G_FreeEdict; + } + + // RAFAEL + if (quadfire) + { + self->client->v_angle[YAW] += spread; + drop = Drop_Item (self, FindItemByClassname ("item_quadfire")); + self->client->v_angle[YAW] -= spread; + drop->spawnflags |= DROPPED_PLAYER_ITEM; + + drop->touch = Touch_Item; + drop->nextthink = level.time + (self->client->quadfire_framenum - level.framenum) * FRAMETIME; + drop->think = G_FreeEdict; + } +} + + +/* +================== +LookAtKiller +================== +*/ +void LookAtKiller (edict_t *self, edict_t *inflictor, edict_t *attacker) +{ + vec3_t dir; + + if (attacker && attacker != world && attacker != self) + { + VectorSubtract (attacker->s.origin, self->s.origin, dir); + } + else if (inflictor && inflictor != world && inflictor != self) + { + VectorSubtract (inflictor->s.origin, self->s.origin, dir); + } + else + { + self->client->killer_yaw = self->s.angles[YAW]; + return; + } + + if (dir[0]) + self->client->killer_yaw = 180/M_PI*atan2(dir[1], dir[0]); + else { + self->client->killer_yaw = 0; + if (dir[1] > 0) + self->client->killer_yaw = 90; + else if (dir[1] < 0) + self->client->killer_yaw = -90; + } + if (self->client->killer_yaw < 0) + self->client->killer_yaw += 360; +} + +/* +================== +player_die +================== +*/ +void player_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + int n; + + VectorClear (self->avelocity); + + self->takedamage = DAMAGE_YES; + self->movetype = MOVETYPE_TOSS; + + self->s.modelindex2 = 0; // remove linked weapon model + + self->s.angles[0] = 0; + self->s.angles[2] = 0; + + self->s.sound = 0; + self->client->weapon_sound = 0; + + self->maxs[2] = -8; + +// self->solid = SOLID_NOT; + self->svflags |= SVF_DEADMONSTER; + + if (!self->deadflag) + { + self->client->respawn_time = level.time + 1.0; + LookAtKiller (self, inflictor, attacker); + self->client->ps.pmove.pm_type = PM_DEAD; + ClientObituary (self, inflictor, attacker); + TossClientWeapon (self); + if (deathmatch->value) + Cmd_Help_f (self); // show scores + + // clear inventory + // this is kind of ugly, but it's how we want to handle keys in coop + for (n = 0; n < game.num_items; n++) + { + if (coop->value && itemlist[n].flags & IT_KEY) + self->client->resp.coop_respawn.inventory[n] = self->client->pers.inventory[n]; + self->client->pers.inventory[n] = 0; + } + } + + // remove powerups + self->client->quad_framenum = 0; + self->client->invincible_framenum = 0; + self->client->breather_framenum = 0; + self->client->enviro_framenum = 0; + self->flags &= ~FL_POWER_ARMOR; + + // RAFAEL + self->client->quadfire_framenum = 0; + + if (self->health < -40) + { // gib + gi.sound (self, CHAN_BODY, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0); + for (n= 0; n < 4; n++) + ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); + ThrowClientHead (self, damage); + + self->takedamage = DAMAGE_NO; + } + else + { // normal death + if (!self->deadflag) + { + static int i; + + i = (i+1)%3; + // start a death animation + self->client->anim_priority = ANIM_DEATH; + if (self->client->ps.pmove.pm_flags & PMF_DUCKED) + { + self->s.frame = FRAME_crdeath1-1; + self->client->anim_end = FRAME_crdeath5; + } + else switch (i) + { + case 0: + self->s.frame = FRAME_death101-1; + self->client->anim_end = FRAME_death106; + break; + case 1: + self->s.frame = FRAME_death201-1; + self->client->anim_end = FRAME_death206; + break; + case 2: + self->s.frame = FRAME_death301-1; + self->client->anim_end = FRAME_death308; + break; + } + gi.sound (self, CHAN_VOICE, gi.soundindex(va("*death%i.wav", (rand()%4)+1)), 1, ATTN_NORM, 0); + } + } + + self->deadflag = DEAD_DEAD; + + gi.linkentity (self); +} + +//======================================================================= + +/* +============== +InitClientPersistant + +This is only called when the game first initializes in single player, +but is called after each death and level change in deathmatch +============== +*/ +void InitClientPersistant (gclient_t *client) +{ + gitem_t *item; + +// gi.dprintf("InitClientPersistant()\n"); + + memset (&client->pers, 0, sizeof(client->pers)); + + item = FindItem("Blaster"); + client->pers.selected_item = ITEM_INDEX(item); + client->pers.inventory[client->pers.selected_item] = 1; + + client->pers.weapon = item; + + client->pers.health = 100; + client->pers.max_health = 100; + + client->pers.max_bullets = 200; + client->pers.max_shells = 100; + client->pers.max_rockets = 50; + client->pers.max_grenades = 50; + client->pers.max_cells = 200; + client->pers.max_slugs = 50; + + // RAFAEL + client->pers.max_magslug = 50; + client->pers.max_trap = 5; + + client->pers.connected = true; +} + + +void InitClientResp (gclient_t *client) +{ + memset (&client->resp, 0, sizeof(client->resp)); + client->resp.enterframe = level.framenum; + client->resp.coop_respawn = client->pers; +} + +/* +================== +SaveClientData + +Some information that should be persistant, like health, +is still stored in the edict structure, so it needs to +be mirrored out to the client structure before all the +edicts are wiped. +================== +*/ +void SaveClientData (void) +{ + int i; + edict_t *ent; + + for (i=0 ; iinuse) + continue; + game.clients[i].pers.health = ent->health; + game.clients[i].pers.max_health = ent->max_health; + game.clients[i].pers.savedFlags = (ent->flags & (FL_GODMODE|FL_NOTARGET|FL_POWER_ARMOR)); + if (coop->value) + game.clients[i].pers.score = ent->client->resp.score; + } +} + +void FetchClientEntData (edict_t *ent) +{ + ent->health = ent->client->pers.health; + ent->max_health = ent->client->pers.max_health; + ent->flags |= ent->client->pers.savedFlags; + if (coop->value) + ent->client->resp.score = ent->client->pers.score; +} + + + +/* +======================================================================= + + SelectSpawnPoint + +======================================================================= +*/ + +/* +================ +PlayersRangeFromSpot + +Returns the distance to the nearest player from the given spot +================ +*/ +float PlayersRangeFromSpot (edict_t *spot) +{ + edict_t *player; + float bestplayerdistance; + vec3_t v; + int n; + float playerdistance; + + + bestplayerdistance = 9999999; + + for (n = 1; n <= maxclients->value; n++) + { + player = &g_edicts[n]; + + if (!player->inuse) + continue; + + if (player->health <= 0) + continue; + + VectorSubtract (spot->s.origin, player->s.origin, v); + playerdistance = VectorLength (v); + + if (playerdistance < bestplayerdistance) + bestplayerdistance = playerdistance; + } + + return bestplayerdistance; +} + +/* +================ +SelectRandomDeathmatchSpawnPoint + +go to a random point, but NOT the two points closest +to other players +================ +*/ +edict_t *SelectRandomDeathmatchSpawnPoint (void) +{ + edict_t *spot, *spot1, *spot2; + int count = 0; + int selection; + float range, range1, range2; + + spot = NULL; + range1 = range2 = 99999; + spot1 = spot2 = NULL; + + while ((spot = G_Find (spot, FOFS(classname), "info_player_deathmatch")) != NULL) + { + count++; + range = PlayersRangeFromSpot(spot); + if (range < range1) + { + range1 = range; + spot1 = spot; + } + else if (range < range2) + { + range2 = range; + spot2 = spot; + } + } + + if (!count) + return NULL; + + if (count <= 2) + { + spot1 = spot2 = NULL; + } + else + count -= 2; + + selection = rand() % count; + + spot = NULL; + do + { + spot = G_Find (spot, FOFS(classname), "info_player_deathmatch"); + if (spot == spot1 || spot == spot2) + selection++; + } while(selection--); + + return spot; +} + +/* +================ +SelectFarthestDeathmatchSpawnPoint + +================ +*/ +edict_t *SelectFarthestDeathmatchSpawnPoint (void) +{ + edict_t *bestspot; + float bestdistance, bestplayerdistance; + edict_t *spot; + + + spot = NULL; + bestspot = NULL; + bestdistance = 0; + while ((spot = G_Find (spot, FOFS(classname), "info_player_deathmatch")) != NULL) + { + bestplayerdistance = PlayersRangeFromSpot (spot); + + if (bestplayerdistance > bestdistance) + { + bestspot = spot; + bestdistance = bestplayerdistance; + } + } + + if (bestspot) + { + return bestspot; + } + + // if there is a player just spawned on each and every start spot + // we have no choice to turn one into a telefrag meltdown + spot = G_Find (NULL, FOFS(classname), "info_player_deathmatch"); + + return spot; +} + +edict_t *SelectDeathmatchSpawnPoint (void) +{ + if ( (int)(dmflags->value) & DF_SPAWN_FARTHEST) + return SelectFarthestDeathmatchSpawnPoint (); + else + return SelectRandomDeathmatchSpawnPoint (); +} + + +edict_t *SelectCoopSpawnPoint (edict_t *ent) +{ + int index; + edict_t *spot = NULL; + char *target; + + index = ent->client - game.clients; + + // player 0 starts in normal player spawn point + if (!index) + return NULL; + + spot = NULL; + + // assume there are four coop spots at each spawnpoint + while (1) + { + spot = G_Find (spot, FOFS(classname), "info_player_coop"); + if (!spot) + return NULL; // we didn't have enough... + + target = spot->targetname; + if (!target) + target = ""; + if ( Q_stricmp(game.spawnpoint, target) == 0 ) + { // this is a coop spawn point for one of the clients here + index--; + if (!index) + return spot; // this is it + } + } + + + return spot; +} + + +/* +=========== +SelectSpawnPoint + +Chooses a player start, deathmatch start, coop start, etc +============ +*/ +void SelectSpawnPoint (edict_t *ent, vec3_t origin, vec3_t angles) +{ + edict_t *spot = NULL; + + if (deathmatch->value) + spot = SelectDeathmatchSpawnPoint (); + else if (coop->value) + spot = SelectCoopSpawnPoint (ent); + + // find a single player start spot + if (!spot) + { + while ((spot = G_Find (spot, FOFS(classname), "info_player_start")) != NULL) + { + if (!game.spawnpoint[0] && !spot->targetname) + break; + + if (!game.spawnpoint[0] || !spot->targetname) + continue; + + if (Q_stricmp(game.spawnpoint, spot->targetname) == 0) + break; + } + + if (!spot) + { + if (!game.spawnpoint[0]) + { // there wasn't a spawnpoint without a target, so use any + spot = G_Find (spot, FOFS(classname), "info_player_start"); + } + if (!spot) + gi.error ("Couldn't find spawn point %s\n", game.spawnpoint); + } + } + + VectorCopy (spot->s.origin, origin); + origin[2] += 9; + VectorCopy (spot->s.angles, angles); +} + +//====================================================================== + + +void InitBodyQue (void) +{ + int i; + edict_t *ent; + + level.body_que = 0; + for (i=0; iclassname = "bodyque"; + } +} + +void body_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + int n; + + if (self->health < -40) + { + gi.sound (self, CHAN_BODY, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0); + for (n= 0; n < 4; n++) + ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); + self->s.origin[2] -= 48; + ThrowClientHead (self, damage); + self->takedamage = DAMAGE_NO; + } +} + +void CopyToBodyQue (edict_t *ent) +{ + edict_t *body; + + // grab a body que and cycle to the next one + body = &g_edicts[(int)maxclients->value + level.body_que + 1]; + level.body_que = (level.body_que + 1) % BODY_QUEUE_SIZE; + + // FIXME: send an effect on the removed body + + gi.unlinkentity (ent); + + gi.unlinkentity (body); + body->s = ent->s; + body->s.number = body - g_edicts; + + body->svflags = ent->svflags; + VectorCopy (ent->mins, body->mins); + VectorCopy (ent->maxs, body->maxs); + VectorCopy (ent->absmin, body->absmin); + VectorCopy (ent->absmax, body->absmax); + VectorCopy (ent->size, body->size); + body->solid = ent->solid; + body->clipmask = ent->clipmask; + body->owner = ent->owner; + body->movetype = ent->movetype; + + body->die = body_die; + body->takedamage = DAMAGE_YES; + + gi.linkentity (body); +} + + +void respawn (edict_t *self) +{ + if (deathmatch->value || coop->value) + { + // spectator's don't leave bodies + if (self->movetype != MOVETYPE_NOCLIP) + CopyToBodyQue (self); + self->svflags &= ~SVF_NOCLIENT; + PutClientInServer (self); + + // add a teleportation effect + self->s.event = EV_PLAYER_TELEPORT; + + // hold in place briefly + self->client->ps.pmove.pm_flags = PMF_TIME_TELEPORT; + self->client->ps.pmove.pm_time = 14; + + self->client->respawn_time = level.time; + + return; + } + + // restart the entire server + gi.AddCommandString ("menu_loadgame\n"); +} + +/* + * only called when pers.spectator changes + * note that resp.spectator should be the opposite of pers.spectator here + */ +void spectator_respawn (edict_t *ent) +{ + int i, numspec; + + // if the user wants to become a spectator, make sure he doesn't + // exceed max_spectators + + if (ent->client->pers.spectator) { + char *value = Info_ValueForKey (ent->client->pers.userinfo, "spectator"); + if (*spectator_password->string && + strcmp(spectator_password->string, "none") && + strcmp(spectator_password->string, value)) { + gi.cprintf(ent, PRINT_HIGH, "Spectator password incorrect.\n"); + ent->client->pers.spectator = false; + gi.WriteByte (svc_stufftext); + gi.WriteString ("spectator 0\n"); + gi.unicast(ent, true); + return; + } + + // count spectators + for (i = 1, numspec = 0; i <= maxclients->value; i++) + if (g_edicts[i].inuse && g_edicts[i].client->pers.spectator) + numspec++; + + if (numspec >= maxspectators->value) { + gi.cprintf(ent, PRINT_HIGH, "Server spectator limit is full."); + ent->client->pers.spectator = false; + // reset his spectator var + gi.WriteByte (svc_stufftext); + gi.WriteString ("spectator 0\n"); + gi.unicast(ent, true); + return; + } + } else { + // he was a spectator and wants to join the game + // he must have the right password + char *value = Info_ValueForKey (ent->client->pers.userinfo, "password"); + if (*password->string && strcmp(password->string, "none") && + strcmp(password->string, value)) { + gi.cprintf(ent, PRINT_HIGH, "Password incorrect.\n"); + ent->client->pers.spectator = true; + gi.WriteByte (svc_stufftext); + gi.WriteString ("spectator 1\n"); + gi.unicast(ent, true); + return; + } + } + + // clear score on respawn + ent->client->pers.score = ent->client->resp.score = 0; + + ent->svflags &= ~SVF_NOCLIENT; + PutClientInServer (ent); + + // add a teleportation effect + if (!ent->client->pers.spectator) { + // send effect + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + gi.WriteByte (MZ_LOGIN); + gi.multicast (ent->s.origin, MULTICAST_PVS); + + // hold in place briefly + ent->client->ps.pmove.pm_flags = PMF_TIME_TELEPORT; + ent->client->ps.pmove.pm_time = 14; + } + + ent->client->respawn_time = level.time; + + if (ent->client->pers.spectator) + gi.bprintf (PRINT_HIGH, "%s has moved to the sidelines\n", ent->client->pers.netname); + else + gi.bprintf (PRINT_HIGH, "%s joined the game\n", ent->client->pers.netname); +} + +//============================================================== + + +/* +=========== +PutClientInServer + +Called when a player connects to a server or respawns in +a deathmatch. +============ +*/ +void PutClientInServer (edict_t *ent) +{ + vec3_t mins = {-16, -16, -24}; + vec3_t maxs = {16, 16, 32}; + int index; + vec3_t spawn_origin, spawn_angles; + gclient_t *client; + int i; + client_persistant_t saved; + client_respawn_t resp; + + // find a spawn point + // do it before setting health back up, so farthest + // ranging doesn't count this client + SelectSpawnPoint (ent, spawn_origin, spawn_angles); + + index = ent-g_edicts-1; + client = ent->client; + + // deathmatch wipes most client data every spawn + if (deathmatch->value) + { + char userinfo[MAX_INFO_STRING]; + + resp = client->resp; + memcpy (userinfo, client->pers.userinfo, sizeof(userinfo)); + InitClientPersistant (client); + ClientUserinfoChanged (ent, userinfo); + } + else if (coop->value) + { +// int n; + char userinfo[MAX_INFO_STRING]; + + resp = client->resp; + memcpy (userinfo, client->pers.userinfo, sizeof(userinfo)); + // this is kind of ugly, but it's how we want to handle keys in coop +// for (n = 0; n < game.num_items; n++) +// { +// if (itemlist[n].flags & IT_KEY) +// resp.coop_respawn.inventory[n] = client->pers.inventory[n]; +// } + resp.coop_respawn.game_helpchanged = client->pers.game_helpchanged; + resp.coop_respawn.helpchanged = client->pers.helpchanged; + client->pers = resp.coop_respawn; + ClientUserinfoChanged (ent, userinfo); + if (resp.score > client->pers.score) + client->pers.score = resp.score; + } + else + { + memset (&resp, 0, sizeof(resp)); + } + + // clear everything but the persistant data + saved = client->pers; + memset (client, 0, sizeof(*client)); + client->pers = saved; + if (client->pers.health <= 0) + InitClientPersistant(client); + client->resp = resp; + + // copy some data from the client to the entity + FetchClientEntData (ent); + + // clear entity values + ent->groundentity = NULL; + ent->client = &game.clients[index]; + ent->takedamage = DAMAGE_AIM; + ent->movetype = MOVETYPE_WALK; + ent->viewheight = 22; + ent->inuse = true; + ent->classname = "player"; + ent->mass = 200; + ent->solid = SOLID_BBOX; + ent->deadflag = DEAD_NO; + ent->air_finished = level.time + 12; + ent->clipmask = MASK_PLAYERSOLID; + ent->model = "players/male/tris.md2"; + ent->pain = player_pain; + ent->die = player_die; + ent->waterlevel = 0; + ent->watertype = 0; + ent->flags &= ~FL_NO_KNOCKBACK; + ent->svflags &= ~SVF_DEADMONSTER; + + VectorCopy (mins, ent->mins); + VectorCopy (maxs, ent->maxs); + VectorClear (ent->velocity); + + // clear playerstate values + memset (&ent->client->ps, 0, sizeof(client->ps)); + + client->ps.pmove.origin[0] = spawn_origin[0]*8; + client->ps.pmove.origin[1] = spawn_origin[1]*8; + client->ps.pmove.origin[2] = spawn_origin[2]*8; + + if (deathmatch->value && ((int)dmflags->value & DF_FIXED_FOV)) + { + client->ps.fov = 90; + } + else + { + client->ps.fov = atoi(Info_ValueForKey(client->pers.userinfo, "fov")); + if (client->ps.fov < 1) + client->ps.fov = 90; + else if (client->ps.fov > 160) + client->ps.fov = 160; + } + + client->ps.gunindex = gi.modelindex(client->pers.weapon->view_model); + + // clear entity state values + ent->s.effects = 0; + ent->s.skinnum = ent - g_edicts - 1; + ent->s.modelindex = 255; // will use the skin specified model + ent->s.modelindex2 = 255; // custom gun model + ent->s.frame = 0; + VectorCopy (spawn_origin, ent->s.origin); + ent->s.origin[2] += 1; // make sure off ground + VectorCopy (ent->s.origin, ent->s.old_origin); + + // set the delta angle + for (i=0 ; i<3 ; i++) + client->ps.pmove.delta_angles[i] = ANGLE2SHORT(spawn_angles[i] - client->resp.cmd_angles[i]); + + ent->s.angles[PITCH] = 0; + ent->s.angles[YAW] = spawn_angles[YAW]; + ent->s.angles[ROLL] = 0; + VectorCopy (ent->s.angles, client->ps.viewangles); + VectorCopy (ent->s.angles, client->v_angle); + + // spawn a spectator + if (client->pers.spectator) { + client->chase_target = NULL; + + client->resp.spectator = true; + + + ent->movetype = MOVETYPE_NOCLIP; + ent->solid = SOLID_NOT; + ent->svflags |= SVF_NOCLIENT; + ent->client->ps.gunindex = 0; + gi.linkentity (ent); + return; + } else + + client->resp.spectator = false; + + + if (!KillBox (ent)) + { // could't spawn in? + } + + gi.linkentity (ent); + + // force the current weapon up + client->newweapon = client->pers.weapon; + ChangeWeapon (ent); +} + +/* +===================== +ClientBeginDeathmatch + +A client has just connected to the server in +deathmatch mode, so clear everything out before starting them. +===================== +*/ +void ClientBeginDeathmatch (edict_t *ent) +{ + G_InitEdict (ent); + + InitClientResp (ent->client); + + // locate ent at a spawn point + PutClientInServer (ent); + + if (level.intermissiontime) + { + MoveClientToIntermission (ent); + } + else + { + // send effect + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + gi.WriteByte (MZ_LOGIN); + gi.multicast (ent->s.origin, MULTICAST_PVS); + } + + gi.bprintf (PRINT_HIGH, "%s entered the game\n", ent->client->pers.netname); + + // make sure all view stuff is valid + ClientEndServerFrame (ent); +} + + +/* +=========== +ClientBegin + +called when a client has finished connecting, and is ready +to be placed into the game. This will happen every level load. +============ +*/ +void ClientBegin (edict_t *ent) +{ + int i; + + ent->client = game.clients + (ent - g_edicts - 1); + + if (deathmatch->value) + { + ClientBeginDeathmatch (ent); + return; + } + + // if there is already a body waiting for us (a loadgame), just + // take it, otherwise spawn one from scratch + if (ent->inuse == true) + { + // the client has cleared the client side viewangles upon + // connecting to the server, which is different than the + // state when the game is saved, so we need to compensate + // with deltaangles + for (i=0 ; i<3 ; i++) + ent->client->ps.pmove.delta_angles[i] = ANGLE2SHORT(ent->client->ps.viewangles[i]); + } + else + { + // a spawn point will completely reinitialize the entity + // except for the persistant data that was initialized at + // ClientConnect() time + G_InitEdict (ent); + ent->classname = "player"; + InitClientResp (ent->client); + PutClientInServer (ent); + } + + if (level.intermissiontime) + { + MoveClientToIntermission (ent); + } + else + { + // send effect if in a multiplayer game + if (game.maxclients > 1) + { + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + gi.WriteByte (MZ_LOGIN); + gi.multicast (ent->s.origin, MULTICAST_PVS); + + gi.bprintf (PRINT_HIGH, "%s entered the game\n", ent->client->pers.netname); + } + } + + // make sure all view stuff is valid + ClientEndServerFrame (ent); +} + +/* +=========== +ClientUserInfoChanged + +called whenever the player updates a userinfo variable. + +The game can override any of the settings in place +(forcing skins or names, etc) before copying it off. +============ +*/ +void ClientUserinfoChanged (edict_t *ent, char *userinfo) +{ + char *s; + int playernum; + + // check for malformed or illegal info strings + if (!Info_Validate(userinfo)) + { + strcpy (userinfo, "\\name\\badinfo\\skin\\male/grunt"); + } + + // set name + s = Info_ValueForKey (userinfo, "name"); + strncpy (ent->client->pers.netname, s, sizeof(ent->client->pers.netname)-1); + + // set spectator + s = Info_ValueForKey (userinfo, "spectator"); + // spectators are only supported in deathmatch + + if (deathmatch->value && *s && strcmp(s, "0")) + ent->client->pers.spectator = true; + else + ent->client->pers.spectator = false; + + // set skin + s = Info_ValueForKey (userinfo, "skin"); + + playernum = ent-g_edicts-1; + + // combine name and skin into a configstring + gi.configstring (CS_PLAYERSKINS+playernum, va("%s\\%s", ent->client->pers.netname, s) ); + + // fov + if (deathmatch->value && ((int)dmflags->value & DF_FIXED_FOV)) + { + ent->client->ps.fov = 90; + } + else + { + ent->client->ps.fov = atoi(Info_ValueForKey(userinfo, "fov")); + if (ent->client->ps.fov < 1) + ent->client->ps.fov = 90; + else if (ent->client->ps.fov > 160) + ent->client->ps.fov = 160; + } + + // handedness + s = Info_ValueForKey (userinfo, "hand"); + if (strlen(s)) + { + ent->client->pers.hand = atoi(s); + } + + // save off the userinfo in case we want to check something later + strncpy (ent->client->pers.userinfo, userinfo, sizeof(ent->client->pers.userinfo)-1); +} + + +/* +=========== +ClientConnect + +Called when a player begins connecting to the server. +The game can refuse entrance to a client by returning false. +If the client is allowed, the connection process will continue +and eventually get to ClientBegin() +Changing levels will NOT cause this to be called again, but +loadgames will. +============ +*/ +qboolean ClientConnect (edict_t *ent, char *userinfo) +{ + char *value; + + // check to see if they are on the banned IP list + value = Info_ValueForKey (userinfo, "ip"); + if (SV_FilterPacket(value)) { + Info_SetValueForKey(userinfo, "rejmsg", "Banned."); + return false; + } + + // check for a spectator + value = Info_ValueForKey (userinfo, "spectator"); + if (deathmatch->value && *value && strcmp(value, "0")) { + int i, numspec; + + if (*spectator_password->string && + strcmp(spectator_password->string, "none") && + strcmp(spectator_password->string, value)) { + Info_SetValueForKey(userinfo, "rejmsg", "Spectator password required or incorrect."); + return false; + } + + // count spectators + for (i = numspec = 0; i < maxclients->value; i++) + if (g_edicts[i+1].inuse && g_edicts[i+1].client->pers.spectator) + numspec++; + + if (numspec >= maxspectators->value) { + Info_SetValueForKey(userinfo, "rejmsg", "Server spectator limit is full."); + return false; + } + } else { + // check for a password + value = Info_ValueForKey (userinfo, "password"); + if (*password->string && strcmp(password->string, "none") && + strcmp(password->string, value)) { + Info_SetValueForKey(userinfo, "rejmsg", "Password required or incorrect."); + return false; + } + } + + // they can connect + ent->client = game.clients + (ent - g_edicts - 1); + + // if there is already a body waiting for us (a loadgame), just + // take it, otherwise spawn one from scratch + if (ent->inuse == false) + { + // clear the respawning variables + InitClientResp (ent->client); + if (!game.autosaved || !ent->client->pers.weapon) + InitClientPersistant (ent->client); + } + + + ClientUserinfoChanged (ent, userinfo); + + if (game.maxclients > 1) + gi.dprintf ("%s connected\n", ent->client->pers.netname); + + ent->svflags = 0; // make sure we start with known default + ent->client->pers.connected = true; + return true; +} + +/* +=========== +ClientDisconnect + +Called when a player drops from the server. +Will not be called between levels. +============ +*/ +void ClientDisconnect (edict_t *ent) +{ + int playernum; + + if (!ent->client) + return; + + gi.bprintf (PRINT_HIGH, "%s disconnected\n", ent->client->pers.netname); + + // send effect + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + gi.WriteByte (MZ_LOGOUT); + gi.multicast (ent->s.origin, MULTICAST_PVS); + + gi.unlinkentity (ent); + ent->s.modelindex = 0; + ent->solid = SOLID_NOT; + ent->inuse = false; + ent->classname = "disconnected"; + ent->client->pers.connected = false; + + playernum = ent-g_edicts-1; + gi.configstring (CS_PLAYERSKINS+playernum, ""); +} + + +//============================================================== + + +edict_t *pm_passent; + +// pmove doesn't need to know about passent and contentmask +trace_t PM_trace (vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end) +{ + if (pm_passent->health > 0) + return gi.trace (start, mins, maxs, end, pm_passent, MASK_PLAYERSOLID); + else + return gi.trace (start, mins, maxs, end, pm_passent, MASK_DEADSOLID); +} + +unsigned CheckBlock (void *b, int c) +{ + int v,i; + v = 0; + for (i=0 ; is, sizeof(pm->s)); + c2 = CheckBlock (&pm->cmd, sizeof(pm->cmd)); + Com_Printf ("sv %3i:%i %i\n", pm->cmd.impulse, c1, c2); +} + +/* +============== +ClientThink + +This will be called once for each client frame, which will +usually be a couple times for each server frame. +============== +*/ +void ClientThink (edict_t *ent, usercmd_t *ucmd) +{ + gclient_t *client; + edict_t *other; + int i, j; + pmove_t pm; + + level.current_entity = ent; + client = ent->client; + + if (level.intermissiontime) + { + client->ps.pmove.pm_type = PM_FREEZE; + // can exit intermission after five seconds + if (level.time > level.intermissiontime + 5.0 + && (ucmd->buttons & BUTTON_ANY) ) + level.exitintermission = true; + return; + } + + pm_passent = ent; + + if (ent->client->chase_target) { + + client->resp.cmd_angles[0] = SHORT2ANGLE(ucmd->angles[0]); + client->resp.cmd_angles[1] = SHORT2ANGLE(ucmd->angles[1]); + client->resp.cmd_angles[2] = SHORT2ANGLE(ucmd->angles[2]); + + } else { + + // set up for pmove + memset (&pm, 0, sizeof(pm)); + + if (ent->movetype == MOVETYPE_NOCLIP) + client->ps.pmove.pm_type = PM_SPECTATOR; + else if (ent->s.modelindex != 255) + client->ps.pmove.pm_type = PM_GIB; + else if (ent->deadflag) + client->ps.pmove.pm_type = PM_DEAD; + else + client->ps.pmove.pm_type = PM_NORMAL; + + client->ps.pmove.gravity = sv_gravity->value; + pm.s = client->ps.pmove; + + for (i=0 ; i<3 ; i++) + { + pm.s.origin[i] = ent->s.origin[i]*8; + pm.s.velocity[i] = ent->velocity[i]*8; + } + + if (memcmp(&client->old_pmove, &pm.s, sizeof(pm.s))) + { + pm.snapinitial = true; + // gi.dprintf ("pmove changed!\n"); + } + + pm.cmd = *ucmd; + + pm.trace = PM_trace; // adds default parms + pm.pointcontents = gi.pointcontents; + + // perform a pmove + gi.Pmove (&pm); + + // save results of pmove + client->ps.pmove = pm.s; + client->old_pmove = pm.s; + + for (i=0 ; i<3 ; i++) + { + ent->s.origin[i] = pm.s.origin[i]*0.125; + ent->velocity[i] = pm.s.velocity[i]*0.125; + } + + VectorCopy (pm.mins, ent->mins); + VectorCopy (pm.maxs, ent->maxs); + + client->resp.cmd_angles[0] = SHORT2ANGLE(ucmd->angles[0]); + client->resp.cmd_angles[1] = SHORT2ANGLE(ucmd->angles[1]); + client->resp.cmd_angles[2] = SHORT2ANGLE(ucmd->angles[2]); + + if (ent->groundentity && !pm.groundentity && (pm.cmd.upmove >= 10) && (pm.waterlevel == 0)) + { + gi.sound(ent, CHAN_VOICE, gi.soundindex("*jump1.wav"), 1, ATTN_NORM, 0); + PlayerNoise(ent, ent->s.origin, PNOISE_SELF); + } + + ent->viewheight = pm.viewheight; + ent->waterlevel = pm.waterlevel; + ent->watertype = pm.watertype; + ent->groundentity = pm.groundentity; + if (pm.groundentity) + ent->groundentity_linkcount = pm.groundentity->linkcount; + + if (ent->deadflag) + { + client->ps.viewangles[ROLL] = 40; + client->ps.viewangles[PITCH] = -15; + client->ps.viewangles[YAW] = client->killer_yaw; + } + else + { + VectorCopy (pm.viewangles, client->v_angle); + VectorCopy (pm.viewangles, client->ps.viewangles); + } + + gi.linkentity (ent); + + if (ent->movetype != MOVETYPE_NOCLIP) + G_TouchTriggers (ent); + + // touch other objects + for (i=0 ; itouch) + continue; + other->touch (other, ent, NULL, NULL); + } + } + + + client->oldbuttons = client->buttons; + client->buttons = ucmd->buttons; + client->latched_buttons |= client->buttons & ~client->oldbuttons; + + // save light level the player is standing on for + // monster sighting AI + ent->light_level = ucmd->lightlevel; + + // fire weapon from final position if needed + if (client->latched_buttons & BUTTON_ATTACK) + { + if (client->resp.spectator) { + + client->latched_buttons = 0; + + if (client->chase_target) { + client->chase_target = NULL; + client->ps.pmove.pm_flags &= ~PMF_NO_PREDICTION; + } else + GetChaseTarget(ent); + + } else if (!client->weapon_thunk) { + client->weapon_thunk = true; + Think_Weapon (ent); + } + } + + if (client->resp.spectator) { + if (ucmd->upmove >= 10) { + if (!(client->ps.pmove.pm_flags & PMF_JUMP_HELD)) { + client->ps.pmove.pm_flags |= PMF_JUMP_HELD; + if (client->chase_target) + ChaseNext(ent); + else + GetChaseTarget(ent); + } + } else + client->ps.pmove.pm_flags &= ~PMF_JUMP_HELD; + } + + // update chase cam if being followed + for (i = 1; i <= maxclients->value; i++) { + other = g_edicts + i; + if (other->inuse && other->client->chase_target == ent) + UpdateChaseCam(other); + } +} + + +/* +============== +ClientBeginServerFrame + +This will be called once for each server frame, before running +any other entities in the world. +============== +*/ +void ClientBeginServerFrame (edict_t *ent) +{ + gclient_t *client; + int buttonMask; + + if (level.intermissiontime) + return; + + client = ent->client; + + if (deathmatch->value && + + client->pers.spectator != client->resp.spectator && + + (level.time - client->respawn_time) >= 5) { + + spectator_respawn(ent); + + return; + + } + + + + // run weapon animations if it hasn't been done by a ucmd_t + if (!client->weapon_thunk && !client->resp.spectator) + + Think_Weapon (ent); + else + client->weapon_thunk = false; + + if (ent->deadflag) + { + // wait for any button just going down + if ( level.time > client->respawn_time) + { + // in deathmatch, only wait for attack button + if (deathmatch->value) + buttonMask = BUTTON_ATTACK; + else + buttonMask = -1; + + if ( ( client->latched_buttons & buttonMask ) || + (deathmatch->value && ((int)dmflags->value & DF_FORCE_RESPAWN) ) ) + { + respawn(ent); + client->latched_buttons = 0; + } + } + return; + } + + // add player trail so monsters can follow + if (!deathmatch->value) + if (!visible (ent, PlayerTrail_LastSpot() ) ) + PlayerTrail_Add (ent->s.old_origin); + + client->latched_buttons = 0; +} diff --git a/original/xatrix/p_hud.c b/original/xatrix/p_hud.c new file mode 100644 index 0000000..12c73bf --- /dev/null +++ b/original/xatrix/p_hud.c @@ -0,0 +1,571 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +#include "g_local.h" + + + +/* +====================================================================== + +INTERMISSION + +====================================================================== +*/ + +void MoveClientToIntermission (edict_t *ent) +{ + if (deathmatch->value || coop->value) + ent->client->showscores = true; + VectorCopy (level.intermission_origin, ent->s.origin); + ent->client->ps.pmove.origin[0] = level.intermission_origin[0]*8; + ent->client->ps.pmove.origin[1] = level.intermission_origin[1]*8; + ent->client->ps.pmove.origin[2] = level.intermission_origin[2]*8; + VectorCopy (level.intermission_angle, ent->client->ps.viewangles); + ent->client->ps.pmove.pm_type = PM_FREEZE; + ent->client->ps.gunindex = 0; + ent->client->ps.blend[3] = 0; + ent->client->ps.rdflags &= ~RDF_UNDERWATER; + + // clean up powerup info + ent->client->quad_framenum = 0; + ent->client->invincible_framenum = 0; + ent->client->breather_framenum = 0; + ent->client->enviro_framenum = 0; + ent->client->grenade_blew_up = false; + ent->client->grenade_time = 0; + + // RAFAEL + ent->client->quadfire_framenum = 0; + + // RAFAEL + ent->client->trap_blew_up = false; + ent->client->trap_time = 0; + + ent->viewheight = 0; + ent->s.modelindex = 0; + ent->s.modelindex2 = 0; + ent->s.modelindex3 = 0; + ent->s.modelindex = 0; + ent->s.effects = 0; + ent->s.sound = 0; + ent->solid = SOLID_NOT; + + gi.linkentity(ent); + + // add the layout + + if (deathmatch->value || coop->value) + { + DeathmatchScoreboardMessage (ent, NULL); + gi.unicast (ent, true); + } + +} + +void BeginIntermission (edict_t *targ) +{ + int i, n; + edict_t *ent, *client; + + if (level.intermissiontime) + return; // already activated + + game.autosaved = false; + + // respawn any dead clients + for (i=0 ; ivalue ; i++) + { + client = g_edicts + 1 + i; + if (!client->inuse) + continue; + if (client->health <= 0) + respawn(client); + } + + level.intermissiontime = level.time; + level.changemap = targ->map; + + if (strstr(level.changemap, "*")) + { + if (coop->value) + { + for (i=0 ; ivalue ; i++) + { + client = g_edicts + 1 + i; + if (!client->inuse) + continue; + // strip players of all keys between units + for (n = 0; n < MAX_ITEMS; n++) + { + if (itemlist[n].flags & IT_KEY) + client->client->pers.inventory[n] = 0; + } + } + } + } + else + { + if (!deathmatch->value) + { + level.exitintermission = 1; // go immediately to the next level + return; + } + } + + level.exitintermission = 0; + + // find an intermission spot + ent = G_Find (NULL, FOFS(classname), "info_player_intermission"); + if (!ent) + { // the map creator forgot to put in an intermission point... + ent = G_Find (NULL, FOFS(classname), "info_player_start"); + if (!ent) + ent = G_Find (NULL, FOFS(classname), "info_player_deathmatch"); + } + else + { // chose one of four spots + i = rand() & 3; + while (i--) + { + ent = G_Find (ent, FOFS(classname), "info_player_intermission"); + if (!ent) // wrap around the list + ent = G_Find (ent, FOFS(classname), "info_player_intermission"); + } + } + + VectorCopy (ent->s.origin, level.intermission_origin); + VectorCopy (ent->s.angles, level.intermission_angle); + + // move all clients to the intermission point + for (i=0 ; ivalue ; i++) + { + client = g_edicts + 1 + i; + if (!client->inuse) + continue; + MoveClientToIntermission (client); + } +} + + +/* +================== +DeathmatchScoreboardMessage + +================== +*/ +void DeathmatchScoreboardMessage (edict_t *ent, edict_t *killer) +{ + char entry[1024]; + char string[1400]; + int stringlength; + int i, j, k; + int sorted[MAX_CLIENTS]; + int sortedscores[MAX_CLIENTS]; + int score, total; + int picnum; + int x, y; + gclient_t *cl; + edict_t *cl_ent; + char *tag; + + // sort the clients by score + total = 0; + for (i=0 ; iinuse || game.clients[i].resp.spectator) + continue; + score = game.clients[i].resp.score; + for (j=0 ; j sortedscores[j]) + break; + } + for (k=total ; k>j ; k--) + { + sorted[k] = sorted[k-1]; + sortedscores[k] = sortedscores[k-1]; + } + sorted[j] = i; + sortedscores[j] = score; + total++; + } + + // print level name and exit rules + string[0] = 0; + + stringlength = strlen(string); + + // add the clients in sorted order + if (total > 12) + total = 12; + + for (i=0 ; i=6) ? 160 : 0; + y = 32 + 32 * (i%6); + + // add a dogtag + if (cl_ent == ent) + tag = "tag1"; + else if (cl_ent == killer) + tag = "tag2"; + else + tag = NULL; + if (tag) + { + Com_sprintf (entry, sizeof(entry), + "xv %i yv %i picn %s ",x+32, y, tag); + j = strlen(entry); + if (stringlength + j > 1024) + break; + strcpy (string + stringlength, entry); + stringlength += j; + } + + // send the layout + Com_sprintf (entry, sizeof(entry), + "client %i %i %i %i %i %i ", + x, y, sorted[i], cl->resp.score, cl->ping, (level.framenum - cl->resp.enterframe)/600); + j = strlen(entry); + if (stringlength + j > 1024) + break; + strcpy (string + stringlength, entry); + stringlength += j; + } + + gi.WriteByte (svc_layout); + gi.WriteString (string); +} + + +/* +================== +DeathmatchScoreboard + +Draw instead of help message. +Note that it isn't that hard to overflow the 1400 byte message limit! +================== +*/ +void DeathmatchScoreboard (edict_t *ent) +{ + DeathmatchScoreboardMessage (ent, ent->enemy); + gi.unicast (ent, true); +} + + +/* +================== +Cmd_Score_f + +Display the scoreboard +================== +*/ +void Cmd_Score_f (edict_t *ent) +{ + ent->client->showinventory = false; + ent->client->showhelp = false; + + if (!deathmatch->value && !coop->value) + return; + + if (ent->client->showscores) + { + ent->client->showscores = false; + return; + } + + ent->client->showscores = true; + DeathmatchScoreboard (ent); +} + + +/* +================== +HelpComputer + +Draw help computer. +================== +*/ +void HelpComputer (edict_t *ent) +{ + char string[1024]; + char *sk; + + if (skill->value == 0) + sk = "easy"; + else if (skill->value == 1) + sk = "medium"; + else if (skill->value == 2) + sk = "hard"; + else + sk = "hard+"; + + // send the layout + Com_sprintf (string, sizeof(string), + "xv 32 yv 8 picn help " // background + "xv 202 yv 12 string2 \"%s\" " // skill + "xv 0 yv 24 cstring2 \"%s\" " // level name + "xv 0 yv 54 cstring2 \"%s\" " // help 1 + "xv 0 yv 110 cstring2 \"%s\" " // help 2 + "xv 50 yv 164 string2 \" kills goals secrets\" " + "xv 50 yv 172 string2 \"%3i/%3i %i/%i %i/%i\" ", + sk, + level.level_name, + game.helpmessage1, + game.helpmessage2, + level.killed_monsters, level.total_monsters, + level.found_goals, level.total_goals, + level.found_secrets, level.total_secrets); + + gi.WriteByte (svc_layout); + gi.WriteString (string); + gi.unicast (ent, true); +} + + +/* +================== +Cmd_Help_f + +Display the current help message +================== +*/ +void Cmd_Help_f (edict_t *ent) +{ + // this is for backwards compatability + if (deathmatch->value) + { + Cmd_Score_f (ent); + return; + } + + ent->client->showinventory = false; + ent->client->showscores = false; + + if (ent->client->showhelp && (ent->client->pers.game_helpchanged == game.helpchanged)) + { + ent->client->showhelp = false; + return; + } + + ent->client->showhelp = true; + ent->client->pers.helpchanged = 0; + HelpComputer (ent); +} + + +//======================================================================= + +/* +=============== +G_SetStats +=============== +*/ +void G_SetStats (edict_t *ent) +{ + gitem_t *item; + int index, cells; + int power_armor_type; + + // + // health + // + ent->client->ps.stats[STAT_HEALTH_ICON] = level.pic_health; + ent->client->ps.stats[STAT_HEALTH] = ent->health; + + // + // ammo + // + if (!ent->client->ammo_index /* || !ent->client->pers.inventory[ent->client->ammo_index] */) + { + ent->client->ps.stats[STAT_AMMO_ICON] = 0; + ent->client->ps.stats[STAT_AMMO] = 0; + } + else + { + item = &itemlist[ent->client->ammo_index]; + ent->client->ps.stats[STAT_AMMO_ICON] = gi.imageindex (item->icon); + ent->client->ps.stats[STAT_AMMO] = ent->client->pers.inventory[ent->client->ammo_index]; + } + + // + // armor + // + power_armor_type = PowerArmorType (ent); + if (power_armor_type) + { + cells = ent->client->pers.inventory[ITEM_INDEX(FindItem ("cells"))]; + if (cells == 0) + { // ran out of cells for power armor + ent->flags &= ~FL_POWER_ARMOR; + gi.sound(ent, CHAN_ITEM, gi.soundindex("misc/power2.wav"), 1, ATTN_NORM, 0); + power_armor_type = 0;; + } + } + + index = ArmorIndex (ent); + if (power_armor_type && (!index || (level.framenum & 8) ) ) + { // flash between power armor and other armor icon + ent->client->ps.stats[STAT_ARMOR_ICON] = gi.imageindex ("i_powershield"); + ent->client->ps.stats[STAT_ARMOR] = cells; + } + else if (index) + { + item = GetItemByIndex (index); + ent->client->ps.stats[STAT_ARMOR_ICON] = gi.imageindex (item->icon); + ent->client->ps.stats[STAT_ARMOR] = ent->client->pers.inventory[index]; + } + else + { + ent->client->ps.stats[STAT_ARMOR_ICON] = 0; + ent->client->ps.stats[STAT_ARMOR] = 0; + } + + // + // pickup message + // + if (level.time > ent->client->pickup_msg_time) + { + ent->client->ps.stats[STAT_PICKUP_ICON] = 0; + ent->client->ps.stats[STAT_PICKUP_STRING] = 0; + } + + // + // timers + // + if (ent->client->quad_framenum > level.framenum) + { + ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex ("p_quad"); + ent->client->ps.stats[STAT_TIMER] = (ent->client->quad_framenum - level.framenum)/10; + } + // RAFAEL + else if (ent->client->quadfire_framenum > level.framenum) + { + // note to self + // need to change imageindex + ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex ("p_quadfire"); + ent->client->ps.stats[STAT_TIMER] = (ent->client->quadfire_framenum - level.framenum)/10; + } + else if (ent->client->invincible_framenum > level.framenum) + { + ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex ("p_invulnerability"); + ent->client->ps.stats[STAT_TIMER] = (ent->client->invincible_framenum - level.framenum)/10; + } + else if (ent->client->enviro_framenum > level.framenum) + { + ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex ("p_envirosuit"); + ent->client->ps.stats[STAT_TIMER] = (ent->client->enviro_framenum - level.framenum)/10; + } + else if (ent->client->breather_framenum > level.framenum) + { + ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex ("p_rebreather"); + ent->client->ps.stats[STAT_TIMER] = (ent->client->breather_framenum - level.framenum)/10; + } + else + { + ent->client->ps.stats[STAT_TIMER_ICON] = 0; + ent->client->ps.stats[STAT_TIMER] = 0; + } + + // + // selected item + // + if (ent->client->pers.selected_item == -1) + ent->client->ps.stats[STAT_SELECTED_ICON] = 0; + else + ent->client->ps.stats[STAT_SELECTED_ICON] = gi.imageindex (itemlist[ent->client->pers.selected_item].icon); + + ent->client->ps.stats[STAT_SELECTED_ITEM] = ent->client->pers.selected_item; + + // + // layouts + // + ent->client->ps.stats[STAT_LAYOUTS] = 0; + + if (deathmatch->value) + { + if (ent->client->pers.health <= 0 || level.intermissiontime + || ent->client->showscores) + ent->client->ps.stats[STAT_LAYOUTS] |= 1; + if (ent->client->showinventory && ent->client->pers.health > 0) + ent->client->ps.stats[STAT_LAYOUTS] |= 2; + } + else + { + if (ent->client->showscores || ent->client->showhelp) + ent->client->ps.stats[STAT_LAYOUTS] |= 1; + if (ent->client->showinventory && ent->client->pers.health > 0) + ent->client->ps.stats[STAT_LAYOUTS] |= 2; + } + + // + // frags + // + ent->client->ps.stats[STAT_FRAGS] = ent->client->resp.score; + + // + // help icon / current weapon if not shown + // + if (ent->client->pers.helpchanged && (level.framenum&8) ) + ent->client->ps.stats[STAT_HELPICON] = gi.imageindex ("i_help"); + else if ( (ent->client->pers.hand == CENTER_HANDED || ent->client->ps.fov > 91) + && ent->client->pers.weapon) + ent->client->ps.stats[STAT_HELPICON] = gi.imageindex (ent->client->pers.weapon->icon); + else + ent->client->ps.stats[STAT_HELPICON] = 0; + + ent->client->ps.stats[STAT_SPECTATOR] = 0; +} + +/* +=============== +G_CheckChaseStats +=============== +*/ +void G_CheckChaseStats (edict_t *ent) +{ + int i; + gclient_t *cl; + + for (i = 1; i <= maxclients->value; i++) { + cl = g_edicts[i].client; + if (!g_edicts[i].inuse || cl->chase_target != ent) + continue; + memcpy(cl->ps.stats, ent->client->ps.stats, sizeof(cl->ps.stats)); + G_SetSpectatorStats(g_edicts + i); + } +} + +/* +=============== +G_SetSpectatorStats +=============== +*/ +void G_SetSpectatorStats (edict_t *ent) +{ + gclient_t *cl = ent->client; + + if (!cl->chase_target) + G_SetStats (ent); + + cl->ps.stats[STAT_SPECTATOR] = 1; + + // layouts are independant in spectator + cl->ps.stats[STAT_LAYOUTS] = 0; + if (cl->pers.health <= 0 || level.intermissiontime || cl->showscores) + cl->ps.stats[STAT_LAYOUTS] |= 1; + if (cl->showinventory && cl->pers.health > 0) + cl->ps.stats[STAT_LAYOUTS] |= 2; + + if (cl->chase_target && cl->chase_target->inuse) + cl->ps.stats[STAT_CHASE] = CS_PLAYERSKINS + + (cl->chase_target - g_edicts) - 1; + else + cl->ps.stats[STAT_CHASE] = 0; +} + diff --git a/original/xatrix/p_trail.c b/original/xatrix/p_trail.c new file mode 100644 index 0000000..abb108c --- /dev/null +++ b/original/xatrix/p_trail.c @@ -0,0 +1,129 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +#include "g_local.h" + + +/* +============================================================================== + +PLAYER TRAIL + +============================================================================== + +This is a circular list containing the a list of points of where +the player has been recently. It is used by monsters for pursuit. + +.origin the spot +.owner forward link +.aiment backward link +*/ + + +#define TRAIL_LENGTH 8 + +edict_t *trail[TRAIL_LENGTH]; +int trail_head; +qboolean trail_active = false; + +#define NEXT(n) (((n) + 1) & (TRAIL_LENGTH - 1)) +#define PREV(n) (((n) - 1) & (TRAIL_LENGTH - 1)) + + +void PlayerTrail_Init (void) +{ + int n; + + if (deathmatch->value /* FIXME || coop */) + return; + + for (n = 0; n < TRAIL_LENGTH; n++) + { + trail[n] = G_Spawn(); + trail[n]->classname = "player_trail"; + } + + trail_head = 0; + trail_active = true; +} + + +void PlayerTrail_Add (vec3_t spot) +{ + vec3_t temp; + + if (!trail_active) + return; + + VectorCopy (spot, trail[trail_head]->s.origin); + + trail[trail_head]->timestamp = level.time; + + VectorSubtract (spot, trail[PREV(trail_head)]->s.origin, temp); + trail[trail_head]->s.angles[1] = vectoyaw (temp); + + trail_head = NEXT(trail_head); +} + + +void PlayerTrail_New (vec3_t spot) +{ + if (!trail_active) + return; + + PlayerTrail_Init (); + PlayerTrail_Add (spot); +} + + +edict_t *PlayerTrail_PickFirst (edict_t *self) +{ + int marker; + int n; + + if (!trail_active) + return NULL; + + for (marker = trail_head, n = TRAIL_LENGTH; n; n--) + { + if(trail[marker]->timestamp <= self->monsterinfo.trail_time) + marker = NEXT(marker); + else + break; + } + + if (visible(self, trail[marker])) + { + return trail[marker]; + } + + if (visible(self, trail[PREV(marker)])) + { + return trail[PREV(marker)]; + } + + return trail[marker]; +} + +edict_t *PlayerTrail_PickNext (edict_t *self) +{ + int marker; + int n; + + if (!trail_active) + return NULL; + + for (marker = trail_head, n = TRAIL_LENGTH; n; n--) + { + if(trail[marker]->timestamp <= self->monsterinfo.trail_time) + marker = NEXT(marker); + else + break; + } + + return trail[marker]; +} + +edict_t *PlayerTrail_LastSpot (void) +{ + return trail[PREV(trail_head)]; +} diff --git a/original/xatrix/p_view.c b/original/xatrix/p_view.c new file mode 100644 index 0000000..34d9ee8 --- /dev/null +++ b/original/xatrix/p_view.c @@ -0,0 +1,1091 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. + +#include "g_local.h" +#include "m_player.h" + + + +static edict_t *current_player; +static gclient_t *current_client; + +static vec3_t forward, right, up; +float xyspeed; + +float bobmove; +int bobcycle; // odd cycles are right foot going forward +float bobfracsin; // sin(bobfrac*M_PI) + +/* +=============== +SV_CalcRoll + +=============== +*/ +float SV_CalcRoll (vec3_t angles, vec3_t velocity) +{ + float sign; + float side; + float value; + + side = DotProduct (velocity, right); + sign = side < 0 ? -1 : 1; + side = fabs(side); + + value = sv_rollangle->value; + + if (side < sv_rollspeed->value) + side = side * value / sv_rollspeed->value; + else + side = value; + + return side*sign; + +} + + +/* +=============== +P_DamageFeedback + +Handles color blends and view kicks +=============== +*/ +void P_DamageFeedback (edict_t *player) +{ + gclient_t *client; + float side; + float realcount, count, kick; + vec3_t v; + int r, l; + static vec3_t power_color = {0.0, 1.0, 0.0}; + static vec3_t acolor = {1.0, 1.0, 1.0}; + static vec3_t bcolor = {1.0, 0.0, 0.0}; + + client = player->client; + + // flash the backgrounds behind the status numbers + client->ps.stats[STAT_FLASHES] = 0; + if (client->damage_blood) + client->ps.stats[STAT_FLASHES] |= 1; + if (client->damage_armor && !(player->flags & FL_GODMODE) && (client->invincible_framenum <= level.framenum)) + client->ps.stats[STAT_FLASHES] |= 2; + + // total points of damage shot at the player this frame + count = (client->damage_blood + client->damage_armor + client->damage_parmor); + if (count == 0) + return; // didn't take any damage + + // start a pain animation if still in the player model + if (client->anim_priority < ANIM_PAIN && player->s.modelindex == 255) + { + static int i; + + client->anim_priority = ANIM_PAIN; + if (client->ps.pmove.pm_flags & PMF_DUCKED) + { + player->s.frame = FRAME_crpain1-1; + client->anim_end = FRAME_crpain4; + } + else + { + i = (i+1)%3; + switch (i) + { + case 0: + player->s.frame = FRAME_pain101-1; + client->anim_end = FRAME_pain104; + break; + case 1: + player->s.frame = FRAME_pain201-1; + client->anim_end = FRAME_pain204; + break; + case 2: + player->s.frame = FRAME_pain301-1; + client->anim_end = FRAME_pain304; + break; + } + } + } + + realcount = count; + if (count < 10) + count = 10; // always make a visible effect + + // play an apropriate pain sound + if ((level.time > player->pain_debounce_time) && !(player->flags & FL_GODMODE) && (client->invincible_framenum <= level.framenum)) + { + r = 1 + (rand()&1); + player->pain_debounce_time = level.time + 0.7; + if (player->health < 25) + l = 25; + else if (player->health < 50) + l = 50; + else if (player->health < 75) + l = 75; + else + l = 100; + gi.sound (player, CHAN_VOICE, gi.soundindex(va("*pain%i_%i.wav", l, r)), 1, ATTN_NORM, 0); + } + + // the total alpha of the blend is always proportional to count + if (client->damage_alpha < 0) + client->damage_alpha = 0; + client->damage_alpha += count*0.01; + if (client->damage_alpha < 0.2) + client->damage_alpha = 0.2; + if (client->damage_alpha > 0.6) + client->damage_alpha = 0.6; // don't go too saturated + + // the color of the blend will vary based on how much was absorbed + // by different armors + VectorClear (v); + if (client->damage_parmor) + VectorMA (v, (float)client->damage_parmor/realcount, power_color, v); + if (client->damage_armor) + VectorMA (v, (float)client->damage_armor/realcount, acolor, v); + if (client->damage_blood) + VectorMA (v, (float)client->damage_blood/realcount, bcolor, v); + VectorCopy (v, client->damage_blend); + + + // + // calculate view angle kicks + // + kick = abs(client->damage_knockback); + if (kick && player->health > 0) // kick of 0 means no view adjust at all + { + kick = kick * 100 / player->health; + + if (kick < count*0.5) + kick = count*0.5; + if (kick > 50) + kick = 50; + + VectorSubtract (client->damage_from, player->s.origin, v); + VectorNormalize (v); + + side = DotProduct (v, right); + client->v_dmg_roll = kick*side*0.3; + + side = -DotProduct (v, forward); + client->v_dmg_pitch = kick*side*0.3; + + client->v_dmg_time = level.time + DAMAGE_TIME; + } + + // + // clear totals + // + client->damage_blood = 0; + client->damage_armor = 0; + client->damage_parmor = 0; + client->damage_knockback = 0; +} + + + + +/* +=============== +SV_CalcViewOffset + +Auto pitching on slopes? + + fall from 128: 400 = 160000 + fall from 256: 580 = 336400 + fall from 384: 720 = 518400 + fall from 512: 800 = 640000 + fall from 640: 960 = + + damage = deltavelocity*deltavelocity * 0.0001 + +=============== +*/ +void SV_CalcViewOffset (edict_t *ent) +{ + float *angles; + float bob; + float ratio; + float delta; + vec3_t v; + + +//=================================== + + // base angles + angles = ent->client->ps.kick_angles; + + // if dead, fix the angle and don't add any kick + if (ent->deadflag) + { + VectorClear (angles); + + ent->client->ps.viewangles[ROLL] = 40; + ent->client->ps.viewangles[PITCH] = -15; + ent->client->ps.viewangles[YAW] = ent->client->killer_yaw; + } + else + { + // add angles based on weapon kick + + VectorCopy (ent->client->kick_angles, angles); + + // add angles based on damage kick + + ratio = (ent->client->v_dmg_time - level.time) / DAMAGE_TIME; + if (ratio < 0) + { + ratio = 0; + ent->client->v_dmg_pitch = 0; + ent->client->v_dmg_roll = 0; + } + angles[PITCH] += ratio * ent->client->v_dmg_pitch; + angles[ROLL] += ratio * ent->client->v_dmg_roll; + + // add pitch based on fall kick + + ratio = (ent->client->fall_time - level.time) / FALL_TIME; + if (ratio < 0) + ratio = 0; + angles[PITCH] += ratio * ent->client->fall_value; + + // add angles based on velocity + + delta = DotProduct (ent->velocity, forward); + angles[PITCH] += delta*run_pitch->value; + + delta = DotProduct (ent->velocity, right); + angles[ROLL] += delta*run_roll->value; + + // add angles based on bob + + delta = bobfracsin * bob_pitch->value * xyspeed; + if (ent->client->ps.pmove.pm_flags & PMF_DUCKED) + delta *= 6; // crouching + angles[PITCH] += delta; + delta = bobfracsin * bob_roll->value * xyspeed; + if (ent->client->ps.pmove.pm_flags & PMF_DUCKED) + delta *= 6; // crouching + if (bobcycle & 1) + delta = -delta; + angles[ROLL] += delta; + } + +//=================================== + + // base origin + + VectorClear (v); + + // add view height + + v[2] += ent->viewheight; + + // add fall height + + ratio = (ent->client->fall_time - level.time) / FALL_TIME; + if (ratio < 0) + ratio = 0; + v[2] -= ratio * ent->client->fall_value * 0.4; + + // add bob height + + bob = bobfracsin * xyspeed * bob_up->value; + if (bob > 6) + bob = 6; + //gi.DebugGraph (bob *2, 255); + v[2] += bob; + + // add kick offset + + VectorAdd (v, ent->client->kick_origin, v); + + // absolutely bound offsets + // so the view can never be outside the player box + + if (v[0] < -14) + v[0] = -14; + else if (v[0] > 14) + v[0] = 14; + if (v[1] < -14) + v[1] = -14; + else if (v[1] > 14) + v[1] = 14; + if (v[2] < -22) + v[2] = -22; + else if (v[2] > 30) + v[2] = 30; + + VectorCopy (v, ent->client->ps.viewoffset); +} + +/* +============== +SV_CalcGunOffset +============== +*/ +void SV_CalcGunOffset (edict_t *ent) +{ + int i; + float delta; + + // gun angles from bobbing + ent->client->ps.gunangles[ROLL] = xyspeed * bobfracsin * 0.005; + ent->client->ps.gunangles[YAW] = xyspeed * bobfracsin * 0.01; + if (bobcycle & 1) + { + ent->client->ps.gunangles[ROLL] = -ent->client->ps.gunangles[ROLL]; + ent->client->ps.gunangles[YAW] = -ent->client->ps.gunangles[YAW]; + } + + ent->client->ps.gunangles[PITCH] = xyspeed * bobfracsin * 0.005; + + // gun angles from delta movement + for (i=0 ; i<3 ; i++) + { + delta = ent->client->oldviewangles[i] - ent->client->ps.viewangles[i]; + if (delta > 180) + delta -= 360; + if (delta < -180) + delta += 360; + if (delta > 45) + delta = 45; + if (delta < -45) + delta = -45; + if (i == YAW) + ent->client->ps.gunangles[ROLL] += 0.1*delta; + ent->client->ps.gunangles[i] += 0.2 * delta; + } + + // gun height + VectorClear (ent->client->ps.gunoffset); +// ent->ps->gunorigin[2] += bob; + + // gun_x / gun_y / gun_z are development tools + for (i=0 ; i<3 ; i++) + { + ent->client->ps.gunoffset[i] += forward[i]*(gun_y->value); + ent->client->ps.gunoffset[i] += right[i]*gun_x->value; + ent->client->ps.gunoffset[i] += up[i]* (-gun_z->value); + } +} + + +/* +============= +SV_AddBlend +============= +*/ +void SV_AddBlend (float r, float g, float b, float a, float *v_blend) +{ + float a2, a3; + + if (a <= 0) + return; + a2 = v_blend[3] + (1-v_blend[3])*a; // new total alpha + a3 = v_blend[3]/a2; // fraction of color from old + + v_blend[0] = v_blend[0]*a3 + r*(1-a3); + v_blend[1] = v_blend[1]*a3 + g*(1-a3); + v_blend[2] = v_blend[2]*a3 + b*(1-a3); + v_blend[3] = a2; +} + + +/* +============= +SV_CalcBlend +============= +*/ +void SV_CalcBlend (edict_t *ent) +{ + int contents; + vec3_t vieworg; + int remaining; + + ent->client->ps.blend[0] = ent->client->ps.blend[1] = + ent->client->ps.blend[2] = ent->client->ps.blend[3] = 0; + + // add for contents + VectorAdd (ent->s.origin, ent->client->ps.viewoffset, vieworg); + contents = gi.pointcontents (vieworg); + if (contents & (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER) ) + ent->client->ps.rdflags |= RDF_UNDERWATER; + else + ent->client->ps.rdflags &= ~RDF_UNDERWATER; + + if (contents & (CONTENTS_SOLID|CONTENTS_LAVA)) + SV_AddBlend (1.0, 0.3, 0.0, 0.6, ent->client->ps.blend); + else if (contents & CONTENTS_SLIME) + SV_AddBlend (0.0, 0.1, 0.05, 0.6, ent->client->ps.blend); + else if (contents & CONTENTS_WATER) + SV_AddBlend (0.5, 0.3, 0.2, 0.4, ent->client->ps.blend); + + // add for powerups + if (ent->client->quad_framenum > level.framenum) + { + remaining = ent->client->quad_framenum - level.framenum; + if (remaining == 30) // beginning to fade + gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage2.wav"), 1, ATTN_NORM, 0); + if (remaining > 30 || (remaining & 4) ) + SV_AddBlend (0, 0, 1, 0.08, ent->client->ps.blend); + } + // RAFAEL + else if (ent->client->quadfire_framenum > level.framenum) + { + remaining = ent->client->quadfire_framenum - level.framenum; + if (remaining == 30) // beginning to fade + gi.sound(ent, CHAN_ITEM, gi.soundindex("items/quadfire2.wav"), 1, ATTN_NORM, 0); + if (remaining > 30 || (remaining & 4) ) + SV_AddBlend (1, 0.2, 0.5, 0.08, ent->client->ps.blend); + } + else if (ent->client->invincible_framenum > level.framenum) + { + remaining = ent->client->invincible_framenum - level.framenum; + if (remaining == 30) // beginning to fade + gi.sound(ent, CHAN_ITEM, gi.soundindex("items/protect2.wav"), 1, ATTN_NORM, 0); + if (remaining > 30 || (remaining & 4) ) + SV_AddBlend (1, 1, 0, 0.08, ent->client->ps.blend); + } + else if (ent->client->enviro_framenum > level.framenum) + { + remaining = ent->client->enviro_framenum - level.framenum; + if (remaining == 30) // beginning to fade + gi.sound(ent, CHAN_ITEM, gi.soundindex("items/airout.wav"), 1, ATTN_NORM, 0); + if (remaining > 30 || (remaining & 4) ) + SV_AddBlend (0, 1, 0, 0.08, ent->client->ps.blend); + } + else if (ent->client->breather_framenum > level.framenum) + { + remaining = ent->client->breather_framenum - level.framenum; + if (remaining == 30) // beginning to fade + gi.sound(ent, CHAN_ITEM, gi.soundindex("items/airout.wav"), 1, ATTN_NORM, 0); + if (remaining > 30 || (remaining & 4) ) + SV_AddBlend (0.4, 1, 0.4, 0.04, ent->client->ps.blend); + } + + // add for damage + if (ent->client->damage_alpha > 0) + SV_AddBlend (ent->client->damage_blend[0],ent->client->damage_blend[1] + ,ent->client->damage_blend[2], ent->client->damage_alpha, ent->client->ps.blend); + + if (ent->client->bonus_alpha > 0) + SV_AddBlend (0.85, 0.7, 0.3, ent->client->bonus_alpha, ent->client->ps.blend); + + // drop the damage value + ent->client->damage_alpha -= 0.06; + if (ent->client->damage_alpha < 0) + ent->client->damage_alpha = 0; + + // drop the bonus value + ent->client->bonus_alpha -= 0.1; + if (ent->client->bonus_alpha < 0) + ent->client->bonus_alpha = 0; +} + + +/* +================= +P_FallingDamage +================= +*/ +void P_FallingDamage (edict_t *ent) +{ + float delta; + int damage; + vec3_t dir; + + if (ent->s.modelindex != 255) + return; // not in the player model + + if (ent->movetype == MOVETYPE_NOCLIP) + return; + + if ((ent->client->oldvelocity[2] < 0) && (ent->velocity[2] > ent->client->oldvelocity[2]) && (!ent->groundentity)) + { + delta = ent->client->oldvelocity[2]; + } + else + { + if (!ent->groundentity) + return; + delta = ent->velocity[2] - ent->client->oldvelocity[2]; + } + delta = delta*delta * 0.0001; + + // never take falling damage if completely underwater + if (ent->waterlevel == 3) + return; + if (ent->waterlevel == 2) + delta *= 0.25; + if (ent->waterlevel == 1) + delta *= 0.5; + + if (delta < 1) + return; + + if (delta < 15) + { + ent->s.event = EV_FOOTSTEP; + return; + } + + ent->client->fall_value = delta*0.5; + if (ent->client->fall_value > 40) + ent->client->fall_value = 40; + ent->client->fall_time = level.time + FALL_TIME; + + if (delta > 30) + { + if (ent->health > 0) + { + if (delta >= 55) + ent->s.event = EV_FALLFAR; + else + ent->s.event = EV_FALL; + } + ent->pain_debounce_time = level.time; // no normal pain sound + damage = (delta-30)/2; + if (damage < 1) + damage = 1; + VectorSet (dir, 0, 0, 1); + + if (!deathmatch->value || !((int)dmflags->value & DF_NO_FALLING) ) + T_Damage (ent, world, world, dir, ent->s.origin, vec3_origin, damage, 0, 0, MOD_FALLING); + } + else + { + ent->s.event = EV_FALLSHORT; + return; + } +} + + + +/* +============= +P_WorldEffects +============= +*/ +void P_WorldEffects (void) +{ + qboolean breather; + qboolean envirosuit; + int waterlevel, old_waterlevel; + + if (current_player->movetype == MOVETYPE_NOCLIP) + { + current_player->air_finished = level.time + 12; // don't need air + return; + } + + waterlevel = current_player->waterlevel; + old_waterlevel = current_client->old_waterlevel; + current_client->old_waterlevel = waterlevel; + + breather = current_client->breather_framenum > level.framenum; + envirosuit = current_client->enviro_framenum > level.framenum; + + // + // if just entered a water volume, play a sound + // + if (!old_waterlevel && waterlevel) + { + PlayerNoise(current_player, current_player->s.origin, PNOISE_SELF); + if (current_player->watertype & CONTENTS_LAVA) + gi.sound (current_player, CHAN_BODY, gi.soundindex("player/lava_in.wav"), 1, ATTN_NORM, 0); + else if (current_player->watertype & CONTENTS_SLIME) + gi.sound (current_player, CHAN_BODY, gi.soundindex("player/watr_in.wav"), 1, ATTN_NORM, 0); + else if (current_player->watertype & CONTENTS_WATER) + gi.sound (current_player, CHAN_BODY, gi.soundindex("player/watr_in.wav"), 1, ATTN_NORM, 0); + current_player->flags |= FL_INWATER; + + // clear damage_debounce, so the pain sound will play immediately + current_player->damage_debounce_time = level.time - 1; + } + + // + // if just completely exited a water volume, play a sound + // + if (old_waterlevel && ! waterlevel) + { + PlayerNoise(current_player, current_player->s.origin, PNOISE_SELF); + gi.sound (current_player, CHAN_BODY, gi.soundindex("player/watr_out.wav"), 1, ATTN_NORM, 0); + current_player->flags &= ~FL_INWATER; + } + + // + // check for head just going under water + // + if (old_waterlevel != 3 && waterlevel == 3) + { + gi.sound (current_player, CHAN_BODY, gi.soundindex("player/watr_un.wav"), 1, ATTN_NORM, 0); + } + + // + // check for head just coming out of water + // + if (old_waterlevel == 3 && waterlevel != 3) + { + if (current_player->air_finished < level.time) + { // gasp for air + gi.sound (current_player, CHAN_VOICE, gi.soundindex("player/gasp1.wav"), 1, ATTN_NORM, 0); + PlayerNoise(current_player, current_player->s.origin, PNOISE_SELF); + } + else if (current_player->air_finished < level.time + 11) + { // just break surface + gi.sound (current_player, CHAN_VOICE, gi.soundindex("player/gasp2.wav"), 1, ATTN_NORM, 0); + } + } + + // + // check for drowning + // + if (waterlevel == 3) + { + // breather or envirosuit give air + if (breather || envirosuit) + { + current_player->air_finished = level.time + 10; + + if (((int)(current_client->breather_framenum - level.framenum) % 25) == 0) + { + if (!current_client->breather_sound) + gi.sound (current_player, CHAN_AUTO, gi.soundindex("player/u_breath1.wav"), 1, ATTN_NORM, 0); + else + gi.sound (current_player, CHAN_AUTO, gi.soundindex("player/u_breath2.wav"), 1, ATTN_NORM, 0); + current_client->breather_sound ^= 1; + PlayerNoise(current_player, current_player->s.origin, PNOISE_SELF); + //FIXME: release a bubble? + } + } + + // if out of air, start drowning + if (current_player->air_finished < level.time) + { // drown! + if (current_player->client->next_drown_time < level.time + && current_player->health > 0) + { + current_player->client->next_drown_time = level.time + 1; + + // take more damage the longer underwater + current_player->dmg += 2; + if (current_player->dmg > 15) + current_player->dmg = 15; + + // play a gurp sound instead of a normal pain sound + if (current_player->health <= current_player->dmg) + gi.sound (current_player, CHAN_VOICE, gi.soundindex("player/drown1.wav"), 1, ATTN_NORM, 0); + else if (rand()&1) + gi.sound (current_player, CHAN_VOICE, gi.soundindex("*gurp1.wav"), 1, ATTN_NORM, 0); + else + gi.sound (current_player, CHAN_VOICE, gi.soundindex("*gurp2.wav"), 1, ATTN_NORM, 0); + + current_player->pain_debounce_time = level.time; + + T_Damage (current_player, world, world, vec3_origin, current_player->s.origin, vec3_origin, current_player->dmg, 0, DAMAGE_NO_ARMOR, MOD_WATER); + } + } + } + else + { + current_player->air_finished = level.time + 12; + current_player->dmg = 2; + } + + // + // check for sizzle damage + // + if (waterlevel && (current_player->watertype&(CONTENTS_LAVA|CONTENTS_SLIME)) ) + { + if (current_player->watertype & CONTENTS_LAVA) + { + if (current_player->health > 0 + && current_player->pain_debounce_time <= level.time + && current_client->invincible_framenum < level.framenum) + { + if (rand()&1) + gi.sound (current_player, CHAN_VOICE, gi.soundindex("player/burn1.wav"), 1, ATTN_NORM, 0); + else + gi.sound (current_player, CHAN_VOICE, gi.soundindex("player/burn2.wav"), 1, ATTN_NORM, 0); + current_player->pain_debounce_time = level.time + 1; + } + + if (envirosuit) // take 1/3 damage with envirosuit + T_Damage (current_player, world, world, vec3_origin, current_player->s.origin, vec3_origin, 1*waterlevel, 0, 0, MOD_LAVA); + else + T_Damage (current_player, world, world, vec3_origin, current_player->s.origin, vec3_origin, 3*waterlevel, 0, 0, MOD_LAVA); + } + + if (current_player->watertype & CONTENTS_SLIME) + { + if (!envirosuit) + { // no damage from slime with envirosuit + T_Damage (current_player, world, world, vec3_origin, current_player->s.origin, vec3_origin, 1*waterlevel, 0, 0, MOD_SLIME); + } + } + } +} + + +/* +=============== +G_SetClientEffects +=============== +*/ +void G_SetClientEffects (edict_t *ent) +{ + int pa_type; + int remaining; + + ent->s.effects = 0; + ent->s.renderfx = 0; + + if (ent->health <= 0 || level.intermissiontime) + return; + + if (ent->powerarmor_time > level.time) + { + pa_type = PowerArmorType (ent); + if (pa_type == POWER_ARMOR_SCREEN) + { + ent->s.effects |= EF_POWERSCREEN; + } + else if (pa_type == POWER_ARMOR_SHIELD) + { + ent->s.effects |= EF_COLOR_SHELL; + ent->s.renderfx |= RF_SHELL_GREEN; + } + } + + if (ent->client->quad_framenum > level.framenum) + { + remaining = ent->client->quad_framenum - level.framenum; + if (remaining > 30 || (remaining & 4) ) + ent->s.effects |= EF_QUAD; + } + + // RAFAEL + if (ent->client->quadfire_framenum > level.framenum) + { + remaining = ent->client->quadfire_framenum - level.framenum; + if (remaining > 30 || (remaining & 4) ) + ent->s.effects |= EF_QUAD; + } + + + if (ent->client->invincible_framenum > level.framenum) + { + remaining = ent->client->invincible_framenum - level.framenum; + if (remaining > 30 || (remaining & 4) ) + ent->s.effects |= EF_PENT; + } + + // show cheaters!!! + if (ent->flags & FL_GODMODE) + { + ent->s.effects |= EF_COLOR_SHELL; + ent->s.renderfx |= (RF_SHELL_RED|RF_SHELL_GREEN|RF_SHELL_BLUE); + } +} + + +/* +=============== +G_SetClientEvent +=============== +*/ +void G_SetClientEvent (edict_t *ent) +{ + if (ent->s.event) + return; + + if ( ent->groundentity && xyspeed > 225) + { + if ( (int)(current_client->bobtime+bobmove) != bobcycle ) + ent->s.event = EV_FOOTSTEP; + } +} + +/* +=============== +G_SetClientSound +=============== +*/ +void G_SetClientSound (edict_t *ent) +{ + char *weap; + + if (ent->client->pers.game_helpchanged != game.helpchanged) + { + ent->client->pers.game_helpchanged = game.helpchanged; + ent->client->pers.helpchanged = 1; + } + + // help beep (no more than three times) + if (ent->client->pers.helpchanged && ent->client->pers.helpchanged <= 3 && !(level.framenum&63) ) + { + ent->client->pers.helpchanged++; + gi.sound (ent, CHAN_VOICE, gi.soundindex ("misc/pc_up.wav"), 1, ATTN_STATIC, 0); + } + + + if (ent->client->pers.weapon) + weap = ent->client->pers.weapon->classname; + else + weap = ""; + + if (ent->waterlevel && (ent->watertype&(CONTENTS_LAVA|CONTENTS_SLIME)) ) + ent->s.sound = snd_fry; + else if (strcmp(weap, "weapon_railgun") == 0) + ent->s.sound = gi.soundindex("weapons/rg_hum.wav"); + else if (strcmp(weap, "weapon_bfg") == 0) + ent->s.sound = gi.soundindex("weapons/bfg_hum.wav"); + // RAFAEL + else if (strcmp (weap, "weapon_phalanx") == 0) + ent->s.sound = gi.soundindex ("weapons/phaloop.wav"); + else if (ent->client->weapon_sound) + ent->s.sound = ent->client->weapon_sound; + else + ent->s.sound = 0; +} + +/* +=============== +G_SetClientFrame +=============== +*/ +void G_SetClientFrame (edict_t *ent) +{ + gclient_t *client; + qboolean duck, run; + + if (ent->s.modelindex != 255) + return; // not in the player model + + client = ent->client; + + if (client->ps.pmove.pm_flags & PMF_DUCKED) + duck = true; + else + duck = false; + if (xyspeed) + run = true; + else + run = false; + + // check for stand/duck and stop/go transitions + if (duck != client->anim_duck && client->anim_priority < ANIM_DEATH) + goto newanim; + if (run != client->anim_run && client->anim_priority == ANIM_BASIC) + goto newanim; + if (!ent->groundentity && client->anim_priority <= ANIM_WAVE) + goto newanim; + + if(client->anim_priority == ANIM_REVERSE) + { + if(ent->s.frame > client->anim_end) + { + ent->s.frame--; + return; + } + } + else if (ent->s.frame < client->anim_end) + { // continue an animation + ent->s.frame++; + return; + } + + if (client->anim_priority == ANIM_DEATH) + return; // stay there + if (client->anim_priority == ANIM_JUMP) + { + if (!ent->groundentity) + return; // stay there + ent->client->anim_priority = ANIM_WAVE; + ent->s.frame = FRAME_jump3; + ent->client->anim_end = FRAME_jump6; + return; + } + +newanim: + // return to either a running or standing frame + client->anim_priority = ANIM_BASIC; + client->anim_duck = duck; + client->anim_run = run; + + if (!ent->groundentity) + { + client->anim_priority = ANIM_JUMP; + if (ent->s.frame != FRAME_jump2) + ent->s.frame = FRAME_jump1; + client->anim_end = FRAME_jump2; + } + else if (run) + { // running + if (duck) + { + ent->s.frame = FRAME_crwalk1; + client->anim_end = FRAME_crwalk6; + } + else + { + ent->s.frame = FRAME_run1; + client->anim_end = FRAME_run6; + } + } + else + { // standing + if (duck) + { + ent->s.frame = FRAME_crstnd01; + client->anim_end = FRAME_crstnd19; + } + else + { + ent->s.frame = FRAME_stand01; + client->anim_end = FRAME_stand40; + } + } +} + + +/* +================= +ClientEndServerFrame + +Called for each player at the end of the server frame +and right after spawning +================= +*/ +void ClientEndServerFrame (edict_t *ent) +{ + float bobtime; + int i; + + current_player = ent; + current_client = ent->client; + + // + // If the origin or velocity have changed since ClientThink(), + // update the pmove values. This will happen when the client + // is pushed by a bmodel or kicked by an explosion. + // + // If it wasn't updated here, the view position would lag a frame + // behind the body position when pushed -- "sinking into plats" + // + for (i=0 ; i<3 ; i++) + { + current_client->ps.pmove.origin[i] = ent->s.origin[i]*8.0; + current_client->ps.pmove.velocity[i] = ent->velocity[i]*8.0; + } + + // + // If the end of unit layout is displayed, don't give + // the player any normal movement attributes + // + if (level.intermissiontime) + { + // FIXME: add view drifting here? + current_client->ps.blend[3] = 0; + current_client->ps.fov = 90; + G_SetStats (ent); + return; + } + + AngleVectors (ent->client->v_angle, forward, right, up); + + // burn from lava, etc + P_WorldEffects (); + + // + // set model angles from view angles so other things in + // the world can tell which direction you are looking + // + if (ent->client->v_angle[PITCH] > 180) + ent->s.angles[PITCH] = (-360 + ent->client->v_angle[PITCH])/3; + else + ent->s.angles[PITCH] = ent->client->v_angle[PITCH]/3; + ent->s.angles[YAW] = ent->client->v_angle[YAW]; + ent->s.angles[ROLL] = 0; + ent->s.angles[ROLL] = SV_CalcRoll (ent->s.angles, ent->velocity)*4; + + // + // calculate speed and cycle to be used for + // all cyclic walking effects + // + xyspeed = sqrt(ent->velocity[0]*ent->velocity[0] + ent->velocity[1]*ent->velocity[1]); + + if (xyspeed < 5) + { + bobmove = 0; + current_client->bobtime = 0; // start at beginning of cycle again + } + else if (ent->groundentity) + { // so bobbing only cycles when on ground + if (xyspeed > 210) + bobmove = 0.25; + else if (xyspeed > 100) + bobmove = 0.125; + else + bobmove = 0.0625; + } + + bobtime = (current_client->bobtime += bobmove); + + if (current_client->ps.pmove.pm_flags & PMF_DUCKED) + bobtime *= 4; + + bobcycle = (int)bobtime; + bobfracsin = fabs(sin(bobtime*M_PI)); + + // detect hitting the floor + P_FallingDamage (ent); + + // apply all the damage taken this frame + P_DamageFeedback (ent); + + // determine the view offsets + SV_CalcViewOffset (ent); + + // determine the gun offsets + SV_CalcGunOffset (ent); + + // determine the full screen color blend + // must be after viewoffset, so eye contents can be + // accurately determined + // FIXME: with client prediction, the contents + // should be determined by the client + SV_CalcBlend (ent); + + // chase cam stuff + if (ent->client->resp.spectator) + G_SetSpectatorStats(ent); + else + G_SetStats (ent); + G_CheckChaseStats(ent); + + G_SetClientEvent (ent); + + G_SetClientEffects (ent); + + G_SetClientSound (ent); + + G_SetClientFrame (ent); + + VectorCopy (ent->velocity, ent->client->oldvelocity); + VectorCopy (ent->client->ps.viewangles, ent->client->oldviewangles); + + // clear weapon kicks + VectorClear (ent->client->kick_origin); + VectorClear (ent->client->kick_angles); + + // if the scoreboard is up, update it + if (ent->client->showscores && !(level.framenum & 31) ) + { + DeathmatchScoreboardMessage (ent, ent->enemy); + gi.unicast (ent, false); + } +} + diff --git a/original/xatrix/p_weapon.c b/original/xatrix/p_weapon.c new file mode 100644 index 0000000..986a75f --- /dev/null +++ b/original/xatrix/p_weapon.c @@ -0,0 +1,1867 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// g_weapon.c + +#include "g_local.h" +#include "m_player.h" + + +static qboolean is_quad; +// RAFAEL +static qboolean is_quadfire; +static byte is_silenced; + + +void weapon_grenade_fire (edict_t *ent, qboolean held); +// RAFAEL +void weapon_trap_fire (edict_t *ent, qboolean held); + + +static void P_ProjectSource (gclient_t *client, vec3_t point, vec3_t distance, vec3_t forward, vec3_t right, vec3_t result) +{ + vec3_t _distance; + + VectorCopy (distance, _distance); + if (client->pers.hand == LEFT_HANDED) + _distance[1] *= -1; + else if (client->pers.hand == CENTER_HANDED) + _distance[1] = 0; + G_ProjectSource (point, _distance, forward, right, result); +} + + +/* +=============== +PlayerNoise + +Each player can have two noise objects associated with it: +a personal noise (jumping, pain, weapon firing), and a weapon +target noise (bullet wall impacts) + +Monsters that don't directly see the player can move +to a noise in hopes of seeing the player from there. +=============== +*/ +void PlayerNoise(edict_t *who, vec3_t where, int type) +{ + edict_t *noise; + + if (type == PNOISE_WEAPON) + { + if (who->client->silencer_shots) + { + who->client->silencer_shots--; + return; + } + } + + if (deathmatch->value) + return; + + if (who->flags & FL_NOTARGET) + return; + + + if (!who->mynoise) + { + noise = G_Spawn(); + noise->classname = "player_noise"; + VectorSet (noise->mins, -8, -8, -8); + VectorSet (noise->maxs, 8, 8, 8); + noise->owner = who; + noise->svflags = SVF_NOCLIENT; + who->mynoise = noise; + + noise = G_Spawn(); + noise->classname = "player_noise"; + VectorSet (noise->mins, -8, -8, -8); + VectorSet (noise->maxs, 8, 8, 8); + noise->owner = who; + noise->svflags = SVF_NOCLIENT; + who->mynoise2 = noise; + } + + if (type == PNOISE_SELF || type == PNOISE_WEAPON) + { + noise = who->mynoise; + level.sound_entity = noise; + level.sound_entity_framenum = level.framenum; + } + else // type == PNOISE_IMPACT + { + noise = who->mynoise2; + level.sound2_entity = noise; + level.sound2_entity_framenum = level.framenum; + } + + VectorCopy (where, noise->s.origin); + VectorSubtract (where, noise->maxs, noise->absmin); + VectorAdd (where, noise->maxs, noise->absmax); + noise->teleport_time = level.time; + gi.linkentity (noise); +} + + +qboolean Pickup_Weapon (edict_t *ent, edict_t *other) +{ + int index; + gitem_t *ammo; + + index = ITEM_INDEX(ent->item); + + if ( ( ((int)(dmflags->value) & DF_WEAPONS_STAY) || coop->value) + && other->client->pers.inventory[index]) + { + if (!(ent->spawnflags & (DROPPED_ITEM | DROPPED_PLAYER_ITEM) ) ) + return false; // leave the weapon for others to pickup + } + + other->client->pers.inventory[index]++; + + if (!(ent->spawnflags & DROPPED_ITEM) ) + { + // give them some ammo with it + ammo = FindItem (ent->item->ammo); + // Don't get infinite ammo with trap + if ( ((int)dmflags->value & DF_INFINITE_AMMO) && Q_stricmp(ent->item->pickup_name, "ammo_trap") ) + Add_Ammo (other, ammo, 1000); + else + Add_Ammo (other, ammo, ammo->quantity); + + if (! (ent->spawnflags & DROPPED_PLAYER_ITEM) ) + { + if (deathmatch->value) + { + if ((int)(dmflags->value) & DF_WEAPONS_STAY) + ent->flags |= FL_RESPAWN; + else + SetRespawn (ent, 30); + } + if (coop->value) + ent->flags |= FL_RESPAWN; + } + } + + if (other->client->pers.weapon != ent->item && + (other->client->pers.inventory[index] == 1) && + ( !deathmatch->value || other->client->pers.weapon == FindItem("blaster") ) ) + other->client->newweapon = ent->item; + + return true; +} + + +/* +=============== +ChangeWeapon + +The old weapon has been dropped all the way, so make the new one +current +=============== +*/ +void ChangeWeapon (edict_t *ent) +{ + int i; + + if (ent->client->grenade_time) + { + ent->client->grenade_time = level.time; + ent->client->weapon_sound = 0; + weapon_grenade_fire (ent, false); + ent->client->grenade_time = 0; + } + + ent->client->pers.lastweapon = ent->client->pers.weapon; + ent->client->pers.weapon = ent->client->newweapon; + ent->client->newweapon = NULL; + ent->client->machinegun_shots = 0; + + // set visible model + if (ent->s.modelindex == 255) + { + if (ent->client->pers.weapon) + i = ((ent->client->pers.weapon->weapmodel & 0xff) << 8); + else + i = 0; + ent->s.skinnum = (ent - g_edicts - 1) | i; + } + + if (ent->client->pers.weapon && ent->client->pers.weapon->ammo) + ent->client->ammo_index = ITEM_INDEX(FindItem(ent->client->pers.weapon->ammo)); + else + ent->client->ammo_index = 0; + + if (!ent->client->pers.weapon) + { // dead + ent->client->ps.gunindex = 0; + return; + } + + ent->client->weaponstate = WEAPON_ACTIVATING; + ent->client->ps.gunframe = 0; + ent->client->ps.gunindex = gi.modelindex(ent->client->pers.weapon->view_model); + + ent->client->anim_priority = ANIM_PAIN; + if(ent->client->ps.pmove.pm_flags & PMF_DUCKED) + { + ent->s.frame = FRAME_crpain1; + ent->client->anim_end = FRAME_crpain4; + } + else + { + ent->s.frame = FRAME_pain301; + ent->client->anim_end = FRAME_pain304; + + } +} + +/* +================= +NoAmmoWeaponChange +================= +*/ +void NoAmmoWeaponChange (edict_t *ent) +{ + if ( ent->client->pers.inventory[ITEM_INDEX(FindItem("slugs"))] + && ent->client->pers.inventory[ITEM_INDEX(FindItem("railgun"))] ) + { + ent->client->newweapon = FindItem ("railgun"); + return; + } + // RAFAEL + if ( ent->client->pers.inventory[ITEM_INDEX (FindItem ("mag slug"))] + && ent->client->pers.inventory[ITEM_INDEX (FindItem ("phalanx"))]) + { + ent->client->newweapon = FindItem ("phalanx"); + } + // RAFAEL + if ( ent->client->pers.inventory[ITEM_INDEX (FindItem ("cells"))] + && ent->client->pers.inventory[ITEM_INDEX (FindItem ("ionripper"))]) + { + ent->client->newweapon = FindItem ("ionrippergun"); + } + + if ( ent->client->pers.inventory[ITEM_INDEX(FindItem("cells"))] + && ent->client->pers.inventory[ITEM_INDEX(FindItem("hyperblaster"))] ) + { + ent->client->newweapon = FindItem ("hyperblaster"); + return; + } + if ( ent->client->pers.inventory[ITEM_INDEX(FindItem("bullets"))] + && ent->client->pers.inventory[ITEM_INDEX(FindItem("chaingun"))] ) + { + ent->client->newweapon = FindItem ("chaingun"); + return; + } + if ( ent->client->pers.inventory[ITEM_INDEX(FindItem("bullets"))] + && ent->client->pers.inventory[ITEM_INDEX(FindItem("machinegun"))] ) + { + ent->client->newweapon = FindItem ("machinegun"); + return; + } + if ( ent->client->pers.inventory[ITEM_INDEX(FindItem("shells"))] > 1 + && ent->client->pers.inventory[ITEM_INDEX(FindItem("super shotgun"))] ) + { + ent->client->newweapon = FindItem ("super shotgun"); + return; + } + if ( ent->client->pers.inventory[ITEM_INDEX(FindItem("shells"))] + && ent->client->pers.inventory[ITEM_INDEX(FindItem("shotgun"))] ) + { + ent->client->newweapon = FindItem ("shotgun"); + return; + } + ent->client->newweapon = FindItem ("blaster"); +} + +/* +================= +Think_Weapon + +Called by ClientBeginServerFrame and ClientThink +================= +*/ +void Think_Weapon (edict_t *ent) +{ + // if just died, put the weapon away + if (ent->health < 1) + { + ent->client->newweapon = NULL; + ChangeWeapon (ent); + } + + // call active weapon think routine + if (ent->client->pers.weapon && ent->client->pers.weapon->weaponthink) + { + is_quad = (ent->client->quad_framenum > level.framenum); + // RAFAEL + is_quadfire = (ent->client->quadfire_framenum > level.framenum); + if (ent->client->silencer_shots) + is_silenced = MZ_SILENCED; + else + is_silenced = 0; + ent->client->pers.weapon->weaponthink (ent); + } +} + + +/* +================ +Use_Weapon + +Make the weapon ready if there is ammo +================ +*/ +void Use_Weapon (edict_t *ent, gitem_t *item) +{ + int ammo_index; + gitem_t *ammo_item; + + // see if we're already using it + if (item == ent->client->pers.weapon) + return; + + if (item->ammo && !g_select_empty->value && !(item->flags & IT_AMMO)) + { + ammo_item = FindItem(item->ammo); + ammo_index = ITEM_INDEX(ammo_item); + + if (!ent->client->pers.inventory[ammo_index]) + { + gi.cprintf (ent, PRINT_HIGH, "No %s for %s.\n", ammo_item->pickup_name, item->pickup_name); + return; + } + + if (ent->client->pers.inventory[ammo_index] < item->quantity) + { + gi.cprintf (ent, PRINT_HIGH, "Not enough %s for %s.\n", ammo_item->pickup_name, item->pickup_name); + return; + } + } + + // change to this weapon when down + ent->client->newweapon = item; +} + +// RAFAEL 14-APR-98 +void Use_Weapon2 (edict_t *ent, gitem_t *item) +{ + int ammo_index; + gitem_t *ammo_item; + gitem_t *nextitem; + int index; + + if (strcmp (item->pickup_name, "HyperBlaster") == 0) + { + if (item == ent->client->pers.weapon) + { + item = FindItem ("Ionripper"); + index = ITEM_INDEX (item); + if (!ent->client->pers.inventory[index]) + { + item = FindItem ("HyperBlaster"); + } + } + } + + else if (strcmp (item->pickup_name, "Railgun") == 0) + { + ammo_item = FindItem(item->ammo); + ammo_index = ITEM_INDEX(ammo_item); + if (!ent->client->pers.inventory[ammo_index]) + { + nextitem = FindItem ("Phalanx"); + ammo_item = FindItem(nextitem->ammo); + ammo_index = ITEM_INDEX(ammo_item); + if (ent->client->pers.inventory[ammo_index]) + { + item = FindItem ("Phalanx"); + index = ITEM_INDEX (item); + if (!ent->client->pers.inventory[index]) + { + item = FindItem ("Railgun"); + } + } + } + else if (item == ent->client->pers.weapon) + { + item = FindItem ("Phalanx"); + index = ITEM_INDEX (item); + if (!ent->client->pers.inventory[index]) + { + item = FindItem ("Railgun"); + } + } + + } + + + // see if we're already using it + if (item == ent->client->pers.weapon) + return; + + if (item->ammo) + { + ammo_item = FindItem(item->ammo); + ammo_index = ITEM_INDEX(ammo_item); + if (!ent->client->pers.inventory[ammo_index] && !g_select_empty->value) + { + gi.cprintf (ent, PRINT_HIGH, "No %s for %s.\n", ammo_item->pickup_name, item->pickup_name); + return; + } + } + + // change to this weapon when down + ent->client->newweapon = item; + +} +// END 14-APR-98 + +/* +================ +Drop_Weapon +================ +*/ +void Drop_Weapon (edict_t *ent, gitem_t *item) +{ + int index; + + if ((int)(dmflags->value) & DF_WEAPONS_STAY) + return; + + index = ITEM_INDEX(item); + // see if we're already using it + if ( ((item == ent->client->pers.weapon) || (item == ent->client->newweapon))&& (ent->client->pers.inventory[index] == 1) ) + { + gi.cprintf (ent, PRINT_HIGH, "Can't drop current weapon\n"); + return; + } + + Drop_Item (ent, item); + ent->client->pers.inventory[index]--; +} + + +/* +================ +Weapon_Generic + +A generic function to handle the basics of weapon thinking +================ +*/ +#define FRAME_FIRE_FIRST (FRAME_ACTIVATE_LAST + 1) +#define FRAME_IDLE_FIRST (FRAME_FIRE_LAST + 1) +#define FRAME_DEACTIVATE_FIRST (FRAME_IDLE_LAST + 1) + +void Weapon_Generic (edict_t *ent, int FRAME_ACTIVATE_LAST, int FRAME_FIRE_LAST, int FRAME_IDLE_LAST, int FRAME_DEACTIVATE_LAST, int *pause_frames, int *fire_frames, void (*fire)(edict_t *ent)) +{ + int n; + + if (ent->client->weaponstate == WEAPON_DROPPING) + { + if (ent->client->ps.gunframe == FRAME_DEACTIVATE_LAST) + { + ChangeWeapon (ent); + return; + } + else if ((FRAME_DEACTIVATE_LAST - ent->client->ps.gunframe) == 4) + { + ent->client->anim_priority = ANIM_REVERSE; + if(ent->client->ps.pmove.pm_flags & PMF_DUCKED) + { + ent->s.frame = FRAME_crpain4+1; + ent->client->anim_end = FRAME_crpain1; + } + else + { + ent->s.frame = FRAME_pain304+1; + ent->client->anim_end = FRAME_pain301; + + } + } + + ent->client->ps.gunframe++; + return; + } + + if (ent->client->weaponstate == WEAPON_ACTIVATING) + { + if (ent->client->ps.gunframe == FRAME_ACTIVATE_LAST) + { + ent->client->weaponstate = WEAPON_READY; + ent->client->ps.gunframe = FRAME_IDLE_FIRST; + return; + } + + ent->client->ps.gunframe++; + return; + } + + if ((ent->client->newweapon) && (ent->client->weaponstate != WEAPON_FIRING)) + { + ent->client->weaponstate = WEAPON_DROPPING; + ent->client->ps.gunframe = FRAME_DEACTIVATE_FIRST; + + if ((FRAME_DEACTIVATE_LAST - FRAME_DEACTIVATE_FIRST) < 4) + { + ent->client->anim_priority = ANIM_REVERSE; + if(ent->client->ps.pmove.pm_flags & PMF_DUCKED) + { + ent->s.frame = FRAME_crpain4+1; + ent->client->anim_end = FRAME_crpain1; + } + else + { + ent->s.frame = FRAME_pain304+1; + ent->client->anim_end = FRAME_pain301; + + } + } + return; + } + + if (ent->client->weaponstate == WEAPON_READY) + { + if ( ((ent->client->latched_buttons|ent->client->buttons) & BUTTON_ATTACK) ) + { + ent->client->latched_buttons &= ~BUTTON_ATTACK; + if ((!ent->client->ammo_index) || + ( ent->client->pers.inventory[ent->client->ammo_index] >= ent->client->pers.weapon->quantity)) + { + ent->client->ps.gunframe = FRAME_FIRE_FIRST; + ent->client->weaponstate = WEAPON_FIRING; + + // start the animation + ent->client->anim_priority = ANIM_ATTACK; + if (ent->client->ps.pmove.pm_flags & PMF_DUCKED) + { + ent->s.frame = FRAME_crattak1-1; + ent->client->anim_end = FRAME_crattak9; + } + else + { + ent->s.frame = FRAME_attack1-1; + ent->client->anim_end = FRAME_attack8; + } + } + else + { + if (level.time >= ent->pain_debounce_time) + { + gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/noammo.wav"), 1, ATTN_NORM, 0); + ent->pain_debounce_time = level.time + 1; + } + NoAmmoWeaponChange (ent); + } + } + else + { + if (ent->client->ps.gunframe == FRAME_IDLE_LAST) + { + ent->client->ps.gunframe = FRAME_IDLE_FIRST; + return; + } + + if (pause_frames) + { + for (n = 0; pause_frames[n]; n++) + { + if (ent->client->ps.gunframe == pause_frames[n]) + { + if (rand()&15) + return; + } + } + } + + ent->client->ps.gunframe++; + return; + } + } + + if (ent->client->weaponstate == WEAPON_FIRING) + { + for (n = 0; fire_frames[n]; n++) + { + if (ent->client->ps.gunframe == fire_frames[n]) + { + if (ent->client->quad_framenum > level.framenum) + gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage3.wav"), 1, ATTN_NORM, 0); + + fire (ent); + break; + } + } + + if (!fire_frames[n]) + ent->client->ps.gunframe++; + + if (ent->client->ps.gunframe == FRAME_IDLE_FIRST+1) + ent->client->weaponstate = WEAPON_READY; + } +} + + +/* +====================================================================== + +GRENADE + +====================================================================== +*/ + +#define GRENADE_TIMER 3.0 +#define GRENADE_MINSPEED 400 +#define GRENADE_MAXSPEED 800 + +void weapon_grenade_fire (edict_t *ent, qboolean held) +{ + vec3_t offset; + vec3_t forward, right; + vec3_t start; + int damage = 125; + float timer; + int speed; + float radius; + + radius = damage+40; + if (is_quad) + damage *= 4; + + VectorSet(offset, 8, 8, ent->viewheight-8); + AngleVectors (ent->client->v_angle, forward, right, NULL); + P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start); + + timer = ent->client->grenade_time - level.time; + speed = GRENADE_MINSPEED + (GRENADE_TIMER - timer) * ((GRENADE_MAXSPEED - GRENADE_MINSPEED) / GRENADE_TIMER); + fire_grenade2 (ent, start, forward, damage, speed, timer, radius, held); + + if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) ) + ent->client->pers.inventory[ent->client->ammo_index]--; + + ent->client->grenade_time = level.time + 1.0; + + if (ent->health <= 0) + return; + + if (ent->client->ps.pmove.pm_flags & PMF_DUCKED) + { + ent->client->anim_priority = ANIM_ATTACK; + ent->s.frame = FRAME_crattak1-1; + ent->client->anim_end = FRAME_crattak3; + } + else + { + ent->client->anim_priority = ANIM_REVERSE; + ent->s.frame = FRAME_wave08; + ent->client->anim_end = FRAME_wave01; + } +} + +void Weapon_Grenade (edict_t *ent) +{ + if ((ent->client->newweapon) && (ent->client->weaponstate == WEAPON_READY)) + { + ChangeWeapon (ent); + return; + } + + if (ent->client->weaponstate == WEAPON_ACTIVATING) + { + ent->client->weaponstate = WEAPON_READY; + ent->client->ps.gunframe = 16; + return; + } + + if (ent->client->weaponstate == WEAPON_READY) + { + if ( ((ent->client->latched_buttons|ent->client->buttons) & BUTTON_ATTACK) ) + { + ent->client->latched_buttons &= ~BUTTON_ATTACK; + if (ent->client->pers.inventory[ent->client->ammo_index]) + { + ent->client->ps.gunframe = 1; + ent->client->weaponstate = WEAPON_FIRING; + ent->client->grenade_time = 0; + } + else + { + if (level.time >= ent->pain_debounce_time) + { + gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/noammo.wav"), 1, ATTN_NORM, 0); + ent->pain_debounce_time = level.time + 1; + } + NoAmmoWeaponChange (ent); + } + return; + } + + if ((ent->client->ps.gunframe == 29) || (ent->client->ps.gunframe == 34) || (ent->client->ps.gunframe == 39) || (ent->client->ps.gunframe == 48)) + { + if (rand()&15) + return; + } + + if (++ent->client->ps.gunframe > 48) + ent->client->ps.gunframe = 16; + return; + } + + if (ent->client->weaponstate == WEAPON_FIRING) + { + if (ent->client->ps.gunframe == 5) + gi.sound(ent, CHAN_WEAPON, gi.soundindex("weapons/hgrena1b.wav"), 1, ATTN_NORM, 0); + + if (ent->client->ps.gunframe == 11) + { + if (!ent->client->grenade_time) + { + ent->client->grenade_time = level.time + GRENADE_TIMER + 0.2; + ent->client->weapon_sound = gi.soundindex("weapons/hgrenc1b.wav"); + } + + // they waited too long, detonate it in their hand + if (!ent->client->grenade_blew_up && level.time >= ent->client->grenade_time) + { + ent->client->weapon_sound = 0; + weapon_grenade_fire (ent, true); + ent->client->grenade_blew_up = true; + } + + if (ent->client->buttons & BUTTON_ATTACK) + return; + + if (ent->client->grenade_blew_up) + { + if (level.time >= ent->client->grenade_time) + { + ent->client->ps.gunframe = 15; + ent->client->grenade_blew_up = false; + } + else + { + return; + } + } + } + + if (ent->client->ps.gunframe == 12) + { + ent->client->weapon_sound = 0; + weapon_grenade_fire (ent, false); + } + + if ((ent->client->ps.gunframe == 15) && (level.time < ent->client->grenade_time)) + return; + + ent->client->ps.gunframe++; + + if (ent->client->ps.gunframe == 16) + { + ent->client->grenade_time = 0; + ent->client->weaponstate = WEAPON_READY; + } + } +} + +/* +====================================================================== + +GRENADE LAUNCHER + +====================================================================== +*/ + +void weapon_grenadelauncher_fire (edict_t *ent) +{ + vec3_t offset; + vec3_t forward, right; + vec3_t start; + int damage = 120; + float radius; + + radius = damage+40; + if (is_quad) + damage *= 4; + + VectorSet(offset, 8, 8, ent->viewheight-8); + AngleVectors (ent->client->v_angle, forward, right, NULL); + P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start); + + VectorScale (forward, -2, ent->client->kick_origin); + ent->client->kick_angles[0] = -1; + + fire_grenade (ent, start, forward, damage, 600, 2.5, radius); + + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + gi.WriteByte (MZ_GRENADE | is_silenced); + gi.multicast (ent->s.origin, MULTICAST_PVS); + + ent->client->ps.gunframe++; + + PlayerNoise(ent, start, PNOISE_WEAPON); + + if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) ) + ent->client->pers.inventory[ent->client->ammo_index]--; +} + +void Weapon_GrenadeLauncher (edict_t *ent) +{ + static int pause_frames[] = {34, 51, 59, 0}; + static int fire_frames[] = {6, 0}; + + Weapon_Generic (ent, 5, 16, 59, 64, pause_frames, fire_frames, weapon_grenadelauncher_fire); + + // RAFAEL + if (is_quadfire) + Weapon_Generic (ent, 5, 16, 59, 64, pause_frames, fire_frames, weapon_grenadelauncher_fire); + +} + +/* +====================================================================== + +ROCKET + +====================================================================== +*/ + +void Weapon_RocketLauncher_Fire (edict_t *ent) +{ + vec3_t offset, start; + vec3_t forward, right; + int damage; + float damage_radius; + int radius_damage; + + damage = 100 + (int)(random() * 20.0); + radius_damage = 120; + damage_radius = 120; + if (is_quad) + { + damage *= 4; + radius_damage *= 4; + } + + AngleVectors (ent->client->v_angle, forward, right, NULL); + + VectorScale (forward, -2, ent->client->kick_origin); + ent->client->kick_angles[0] = -1; + + VectorSet(offset, 8, 8, ent->viewheight-8); + P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start); + fire_rocket (ent, start, forward, damage, 650, damage_radius, radius_damage); + + // send muzzle flash + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + gi.WriteByte (MZ_ROCKET | is_silenced); + gi.multicast (ent->s.origin, MULTICAST_PVS); + + ent->client->ps.gunframe++; + + PlayerNoise(ent, start, PNOISE_WEAPON); + + if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) ) + ent->client->pers.inventory[ent->client->ammo_index]--; +} + +void Weapon_RocketLauncher (edict_t *ent) +{ + static int pause_frames[] = {25, 33, 42, 50, 0}; + static int fire_frames[] = {5, 0}; + + Weapon_Generic (ent, 4, 12, 50, 54, pause_frames, fire_frames, Weapon_RocketLauncher_Fire); + + + // RAFAEL + if (is_quadfire) + Weapon_Generic (ent, 4, 12, 50, 54, pause_frames, fire_frames, Weapon_RocketLauncher_Fire); + + +} + + +/* +====================================================================== + +BLASTER / HYPERBLASTER + +====================================================================== +*/ + +void Blaster_Fire (edict_t *ent, vec3_t g_offset, int damage, qboolean hyper, int effect) +{ + vec3_t forward, right; + vec3_t start; + vec3_t offset; + + if (is_quad) + damage *= 4; + AngleVectors (ent->client->v_angle, forward, right, NULL); + VectorSet(offset, 24, 8, ent->viewheight-8); + VectorAdd (offset, g_offset, offset); + P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start); + + VectorScale (forward, -2, ent->client->kick_origin); + ent->client->kick_angles[0] = -1; + + fire_blaster (ent, start, forward, damage, 1000, effect, hyper); + + // send muzzle flash + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + if (hyper) + gi.WriteByte (MZ_HYPERBLASTER | is_silenced); + else + gi.WriteByte (MZ_BLASTER | is_silenced); + gi.multicast (ent->s.origin, MULTICAST_PVS); + + PlayerNoise(ent, start, PNOISE_WEAPON); +} + + +void Weapon_Blaster_Fire (edict_t *ent) +{ + int damage; + + if (deathmatch->value) + damage = 15; + else + damage = 10; + Blaster_Fire (ent, vec3_origin, damage, false, EF_BLASTER); + ent->client->ps.gunframe++; +} + +void Weapon_Blaster (edict_t *ent) +{ + static int pause_frames[] = {19, 32, 0}; + static int fire_frames[] = {5, 0}; + + Weapon_Generic (ent, 4, 8, 52, 55, pause_frames, fire_frames, Weapon_Blaster_Fire); + + // RAFAEL + if (is_quadfire) + Weapon_Generic (ent, 4, 8, 52, 55, pause_frames, fire_frames, Weapon_Blaster_Fire); + +} + + +void Weapon_HyperBlaster_Fire (edict_t *ent) +{ + float rotation; + vec3_t offset; + int effect; + int damage; + + ent->client->weapon_sound = gi.soundindex("weapons/hyprbl1a.wav"); + + if (!(ent->client->buttons & BUTTON_ATTACK)) + { + ent->client->ps.gunframe++; + } + else + { + if (! ent->client->pers.inventory[ent->client->ammo_index] ) + { + if (level.time >= ent->pain_debounce_time) + { + gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/noammo.wav"), 1, ATTN_NORM, 0); + ent->pain_debounce_time = level.time + 1; + } + NoAmmoWeaponChange (ent); + } + else + { + rotation = (ent->client->ps.gunframe - 5) * 2*M_PI/6; + offset[0] = -4 * sin(rotation); + offset[1] = 0; + offset[2] = 4 * cos(rotation); + + if ((ent->client->ps.gunframe == 6) || (ent->client->ps.gunframe == 9)) + effect = EF_HYPERBLASTER; + else + effect = 0; + if (deathmatch->value) + damage = 15; + else + damage = 20; + Blaster_Fire (ent, offset, damage, true, effect); + if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) ) + ent->client->pers.inventory[ent->client->ammo_index]--; + + ent->client->anim_priority = ANIM_ATTACK; + if (ent->client->ps.pmove.pm_flags & PMF_DUCKED) + { + ent->s.frame = FRAME_crattak1 - 1; + ent->client->anim_end = FRAME_crattak9; + } + else + { + ent->s.frame = FRAME_attack1 - 1; + ent->client->anim_end = FRAME_attack8; + } + } + + ent->client->ps.gunframe++; + if (ent->client->ps.gunframe == 12 && ent->client->pers.inventory[ent->client->ammo_index]) + ent->client->ps.gunframe = 6; + } + + if (ent->client->ps.gunframe == 12) + { + gi.sound(ent, CHAN_AUTO, gi.soundindex("weapons/hyprbd1a.wav"), 1, ATTN_NORM, 0); + ent->client->weapon_sound = 0; + } + +} + +void Weapon_HyperBlaster (edict_t *ent) +{ + static int pause_frames[] = {0}; + static int fire_frames[] = {6, 7, 8, 9, 10, 11, 0}; + + Weapon_Generic (ent, 5, 20, 49, 53, pause_frames, fire_frames, Weapon_HyperBlaster_Fire); + + + // RAFAEL + if (is_quadfire) + Weapon_Generic (ent, 5, 20, 49, 53, pause_frames, fire_frames, Weapon_HyperBlaster_Fire); + +} + +/* +====================================================================== + +MACHINEGUN / CHAINGUN + +====================================================================== +*/ + +void Machinegun_Fire (edict_t *ent) +{ + int i; + vec3_t start; + vec3_t forward, right; + vec3_t angles; + int damage = 8; + int kick = 2; + vec3_t offset; + + if (!(ent->client->buttons & BUTTON_ATTACK)) + { + ent->client->machinegun_shots = 0; + ent->client->ps.gunframe++; + return; + } + + if (ent->client->ps.gunframe == 5) + ent->client->ps.gunframe = 4; + else + ent->client->ps.gunframe = 5; + + if (ent->client->pers.inventory[ent->client->ammo_index] < 1) + { + ent->client->ps.gunframe = 6; + if (level.time >= ent->pain_debounce_time) + { + gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/noammo.wav"), 1, ATTN_NORM, 0); + ent->pain_debounce_time = level.time + 1; + } + NoAmmoWeaponChange (ent); + return; + } + + if (is_quad) + { + damage *= 4; + kick *= 4; + } + + for (i=1 ; i<3 ; i++) + { + ent->client->kick_origin[i] = crandom() * 0.35; + ent->client->kick_angles[i] = crandom() * 0.7; + } + ent->client->kick_origin[0] = crandom() * 0.35; + ent->client->kick_angles[0] = ent->client->machinegun_shots * -1.5; + + // raise the gun as it is firing + if (!deathmatch->value) + { + ent->client->machinegun_shots++; + if (ent->client->machinegun_shots > 9) + ent->client->machinegun_shots = 9; + } + + // get start / end positions + VectorAdd (ent->client->v_angle, ent->client->kick_angles, angles); + AngleVectors (angles, forward, right, NULL); + VectorSet(offset, 0, 8, ent->viewheight-8); + P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start); + fire_bullet (ent, start, forward, damage, kick, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, MOD_MACHINEGUN); + + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + gi.WriteByte (MZ_MACHINEGUN | is_silenced); + gi.multicast (ent->s.origin, MULTICAST_PVS); + + PlayerNoise(ent, start, PNOISE_WEAPON); + + if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) ) + ent->client->pers.inventory[ent->client->ammo_index]--; + + ent->client->anim_priority = ANIM_ATTACK; + if (ent->client->ps.pmove.pm_flags & PMF_DUCKED) + { + ent->s.frame = FRAME_crattak1 - (int) (random()+0.25); + ent->client->anim_end = FRAME_crattak9; + } + else + { + ent->s.frame = FRAME_attack1 - (int) (random()+0.25); + ent->client->anim_end = FRAME_attack8; + } +} + +void Weapon_Machinegun (edict_t *ent) +{ + static int pause_frames[] = {23, 45, 0}; + static int fire_frames[] = {4, 5, 0}; + + Weapon_Generic (ent, 3, 5, 45, 49, pause_frames, fire_frames, Machinegun_Fire); + + // RAFAEL + if (is_quadfire) + Weapon_Generic (ent, 3, 5, 45, 49, pause_frames, fire_frames, Machinegun_Fire); + +} + +void Chaingun_Fire (edict_t *ent) +{ + int i; + int shots; + vec3_t start; + vec3_t forward, right, up; + float r, u; + vec3_t offset; + int damage; + int kick = 2; + + if (deathmatch->value) + damage = 6; + else + damage = 8; + + if (ent->client->ps.gunframe == 5) + gi.sound(ent, CHAN_AUTO, gi.soundindex("weapons/chngnu1a.wav"), 1, ATTN_IDLE, 0); + + if ((ent->client->ps.gunframe == 14) && !(ent->client->buttons & BUTTON_ATTACK)) + { + ent->client->ps.gunframe = 32; + ent->client->weapon_sound = 0; + return; + } + else if ((ent->client->ps.gunframe == 21) && (ent->client->buttons & BUTTON_ATTACK) + && ent->client->pers.inventory[ent->client->ammo_index]) + { + ent->client->ps.gunframe = 15; + } + else + { + ent->client->ps.gunframe++; + } + + if (ent->client->ps.gunframe == 22) + { + ent->client->weapon_sound = 0; + gi.sound(ent, CHAN_AUTO, gi.soundindex("weapons/chngnd1a.wav"), 1, ATTN_IDLE, 0); + } + else + { + ent->client->weapon_sound = gi.soundindex("weapons/chngnl1a.wav"); + } + + ent->client->anim_priority = ANIM_ATTACK; + if (ent->client->ps.pmove.pm_flags & PMF_DUCKED) + { + ent->s.frame = FRAME_crattak1 - (ent->client->ps.gunframe & 1); + ent->client->anim_end = FRAME_crattak9; + } + else + { + ent->s.frame = FRAME_attack1 - (ent->client->ps.gunframe & 1); + ent->client->anim_end = FRAME_attack8; + } + + if (ent->client->ps.gunframe <= 9) + shots = 1; + else if (ent->client->ps.gunframe <= 14) + { + if (ent->client->buttons & BUTTON_ATTACK) + shots = 2; + else + shots = 1; + } + else + shots = 3; + + if (ent->client->pers.inventory[ent->client->ammo_index] < shots) + shots = ent->client->pers.inventory[ent->client->ammo_index]; + + if (!shots) + { + if (level.time >= ent->pain_debounce_time) + { + gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/noammo.wav"), 1, ATTN_NORM, 0); + ent->pain_debounce_time = level.time + 1; + } + NoAmmoWeaponChange (ent); + return; + } + + if (is_quad) + { + damage *= 4; + kick *= 4; + } + + for (i=0 ; i<3 ; i++) + { + ent->client->kick_origin[i] = crandom() * 0.35; + ent->client->kick_angles[i] = crandom() * 0.7; + } + + for (i=0 ; iclient->v_angle, forward, right, up); + r = 7 + crandom()*4; + u = crandom()*4; + VectorSet(offset, 0, r, u + ent->viewheight-8); + P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start); + + fire_bullet (ent, start, forward, damage, kick, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, MOD_CHAINGUN); + } + + // send muzzle flash + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + gi.WriteByte ((MZ_CHAINGUN1 + shots - 1) | is_silenced); + gi.multicast (ent->s.origin, MULTICAST_PVS); + + PlayerNoise(ent, start, PNOISE_WEAPON); + + if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) ) + ent->client->pers.inventory[ent->client->ammo_index] -= shots; +} + + +void Weapon_Chaingun (edict_t *ent) +{ + static int pause_frames[] = {38, 43, 51, 61, 0}; + static int fire_frames[] = {5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 0}; + + Weapon_Generic (ent, 4, 31, 61, 64, pause_frames, fire_frames, Chaingun_Fire); + + + // RAFAEL + if (is_quadfire) + Weapon_Generic (ent, 4, 31, 61, 64, pause_frames, fire_frames, Chaingun_Fire); + +} + + +/* +====================================================================== + +SHOTGUN / SUPERSHOTGUN + +====================================================================== +*/ + +void weapon_shotgun_fire (edict_t *ent) +{ + vec3_t start; + vec3_t forward, right; + vec3_t offset; + int damage = 4; + int kick = 8; + + if (ent->client->ps.gunframe == 9) + { + ent->client->ps.gunframe++; + return; + } + + AngleVectors (ent->client->v_angle, forward, right, NULL); + + VectorScale (forward, -2, ent->client->kick_origin); + ent->client->kick_angles[0] = -2; + + VectorSet(offset, 0, 8, ent->viewheight-8); + P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start); + + if (is_quad) + { + damage *= 4; + kick *= 4; + } + + if (deathmatch->value) + fire_shotgun (ent, start, forward, damage, kick, 500, 500, DEFAULT_DEATHMATCH_SHOTGUN_COUNT, MOD_SHOTGUN); + else + fire_shotgun (ent, start, forward, damage, kick, 500, 500, DEFAULT_SHOTGUN_COUNT, MOD_SHOTGUN); + + // send muzzle flash + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + gi.WriteByte (MZ_SHOTGUN | is_silenced); + gi.multicast (ent->s.origin, MULTICAST_PVS); + + ent->client->ps.gunframe++; + PlayerNoise(ent, start, PNOISE_WEAPON); + + if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) ) + ent->client->pers.inventory[ent->client->ammo_index]--; +} + +void Weapon_Shotgun (edict_t *ent) +{ + static int pause_frames[] = {22, 28, 34, 0}; + static int fire_frames[] = {8, 9, 0}; + + Weapon_Generic (ent, 7, 18, 36, 39, pause_frames, fire_frames, weapon_shotgun_fire); + + + // RAFAEL + if (is_quadfire) + Weapon_Generic (ent, 7, 18, 36, 39, pause_frames, fire_frames, weapon_shotgun_fire); +} + + +void weapon_supershotgun_fire (edict_t *ent) +{ + vec3_t start; + vec3_t forward, right; + vec3_t offset; + vec3_t v; + int damage = 6; + int kick = 12; + + AngleVectors (ent->client->v_angle, forward, right, NULL); + + VectorScale (forward, -2, ent->client->kick_origin); + ent->client->kick_angles[0] = -2; + + VectorSet(offset, 0, 8, ent->viewheight-8); + P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start); + + if (is_quad) + { + damage *= 4; + kick *= 4; + } + + v[PITCH] = ent->client->v_angle[PITCH]; + v[YAW] = ent->client->v_angle[YAW] - 5; + v[ROLL] = ent->client->v_angle[ROLL]; + AngleVectors (v, forward, NULL, NULL); + fire_shotgun (ent, start, forward, damage, kick, DEFAULT_SHOTGUN_HSPREAD, DEFAULT_SHOTGUN_VSPREAD, DEFAULT_SSHOTGUN_COUNT/2, MOD_SSHOTGUN); + v[YAW] = ent->client->v_angle[YAW] + 5; + AngleVectors (v, forward, NULL, NULL); + fire_shotgun (ent, start, forward, damage, kick, DEFAULT_SHOTGUN_HSPREAD, DEFAULT_SHOTGUN_VSPREAD, DEFAULT_SSHOTGUN_COUNT/2, MOD_SSHOTGUN); + + // send muzzle flash + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + gi.WriteByte (MZ_SSHOTGUN | is_silenced); + gi.multicast (ent->s.origin, MULTICAST_PVS); + + ent->client->ps.gunframe++; + PlayerNoise(ent, start, PNOISE_WEAPON); + + if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) ) + ent->client->pers.inventory[ent->client->ammo_index] -= 2; +} + +void Weapon_SuperShotgun (edict_t *ent) +{ + static int pause_frames[] = {29, 42, 57, 0}; + static int fire_frames[] = {7, 0}; + + Weapon_Generic (ent, 6, 17, 57, 61, pause_frames, fire_frames, weapon_supershotgun_fire); + + + // RAFAEL + if (is_quadfire) + Weapon_Generic (ent, 6, 17, 57, 61, pause_frames, fire_frames, weapon_supershotgun_fire); +} + + + +/* +====================================================================== + +RAILGUN + +====================================================================== +*/ + +void weapon_railgun_fire (edict_t *ent) +{ + vec3_t start; + vec3_t forward, right; + vec3_t offset; + int damage; + int kick; + + if (deathmatch->value) + { // normal damage is too extreme in dm + damage = 100; + kick = 200; + } + else + { + damage = 150; + kick = 250; + } + + if (is_quad) + { + damage *= 4; + kick *= 4; + } + + AngleVectors (ent->client->v_angle, forward, right, NULL); + + VectorScale (forward, -3, ent->client->kick_origin); + ent->client->kick_angles[0] = -3; + + VectorSet(offset, 0, 7, ent->viewheight-8); + P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start); + fire_rail (ent, start, forward, damage, kick); + + // send muzzle flash + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + gi.WriteByte (MZ_RAILGUN | is_silenced); + gi.multicast (ent->s.origin, MULTICAST_PVS); + + ent->client->ps.gunframe++; + PlayerNoise(ent, start, PNOISE_WEAPON); + + if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) ) + ent->client->pers.inventory[ent->client->ammo_index]--; +} + + +void Weapon_Railgun (edict_t *ent) +{ + static int pause_frames[] = {56, 0}; + static int fire_frames[] = {4, 0}; + + Weapon_Generic (ent, 3, 18, 56, 61, pause_frames, fire_frames, weapon_railgun_fire); + + + // RAFAEL + if (is_quadfire) + Weapon_Generic (ent, 3, 18, 56, 61, pause_frames, fire_frames, weapon_railgun_fire); +} + + +/* +====================================================================== + +BFG10K + +====================================================================== +*/ + +void weapon_bfg_fire (edict_t *ent) +{ + vec3_t offset, start; + vec3_t forward, right; + int damage; + float damage_radius = 1000; + + if (deathmatch->value) + damage = 200; + else + damage = 500; + + if (ent->client->ps.gunframe == 9) + { + // send muzzle flash + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + gi.WriteByte (MZ_BFG | is_silenced); + gi.multicast (ent->s.origin, MULTICAST_PVS); + + ent->client->ps.gunframe++; + + PlayerNoise(ent, start, PNOISE_WEAPON); + return; + } + + // cells can go down during windup (from power armor hits), so + // check again and abort firing if we don't have enough now + if (ent->client->pers.inventory[ent->client->ammo_index] < 50) + { + ent->client->ps.gunframe++; + return; + } + + if (is_quad) + damage *= 4; + + AngleVectors (ent->client->v_angle, forward, right, NULL); + + VectorScale (forward, -2, ent->client->kick_origin); + + // make a big pitch kick with an inverse fall + ent->client->v_dmg_pitch = -40; + ent->client->v_dmg_roll = crandom()*8; + ent->client->v_dmg_time = level.time + DAMAGE_TIME; + + VectorSet(offset, 8, 8, ent->viewheight-8); + P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start); + fire_bfg (ent, start, forward, damage, 400, damage_radius); + + ent->client->ps.gunframe++; + + PlayerNoise(ent, start, PNOISE_WEAPON); + + if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) ) + ent->client->pers.inventory[ent->client->ammo_index] -= 50; +} + +void Weapon_BFG (edict_t *ent) +{ + static int pause_frames[] = {39, 45, 50, 55, 0}; + static int fire_frames[] = {9, 17, 0}; + + Weapon_Generic (ent, 8, 32, 55, 58, pause_frames, fire_frames, weapon_bfg_fire); + + // RAFAEL + if (is_quadfire) + Weapon_Generic (ent, 8, 32, 55, 58, pause_frames, fire_frames, weapon_bfg_fire); +} + + +//====================================================================== + + +// RAFAEL +/* + RipperGun +*/ + +void weapon_ionripper_fire (edict_t *ent) +{ + vec3_t start; + vec3_t forward, right; + vec3_t offset; + vec3_t tempang; + int damage; + int kick; + + if (deathmatch->value) + { + // tone down for deathmatch + damage = 30; + kick = 40; + } + else + { + damage = 50; + kick = 60; + } + + if (is_quad) + { + damage *= 4; + kick *= 4; + } + + VectorCopy (ent->client->v_angle, tempang); + tempang[YAW] += crandom(); + + AngleVectors (tempang, forward, right, NULL); + + VectorScale (forward, -3, ent->client->kick_origin); + ent->client->kick_angles[0] = -3; + + // VectorSet (offset, 0, 7, ent->viewheight - 8); + VectorSet (offset, 16, 7, ent->viewheight - 8); + + P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start); + + fire_ionripper (ent, start, forward, damage, 500, EF_IONRIPPER); + + // send muzzle flash + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent - g_edicts); + gi.WriteByte (MZ_IONRIPPER | is_silenced); + gi.multicast (ent->s.origin, MULTICAST_PVS); + + ent->client->ps.gunframe++; + PlayerNoise (ent, start, PNOISE_WEAPON); + + if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) ) + ent->client->pers.inventory[ent->client->ammo_index] -= ent->client->pers.weapon->quantity; + + if (ent->client->pers.inventory[ent->client->ammo_index] < 0) + ent->client->pers.inventory[ent->client->ammo_index] = 0; +} + + +void Weapon_Ionripper (edict_t *ent) +{ + static int pause_frames[] = {36, 0}; + static int fire_frames[] = {5, 0}; + + Weapon_Generic (ent, 4, 6, 36, 39, pause_frames, fire_frames, weapon_ionripper_fire); + + if (is_quadfire) + Weapon_Generic (ent, 4, 6, 36, 39, pause_frames, fire_frames, weapon_ionripper_fire); +} + + +// +// Phalanx +// + +void weapon_phalanx_fire (edict_t *ent) +{ + vec3_t start; + vec3_t forward, right, up; + vec3_t offset; + vec3_t v; + int kick = 12; + int damage; + float damage_radius; + int radius_damage; + + damage = 70 + (int)(random() * 10.0); + radius_damage = 120; + damage_radius = 120; + + if (is_quad) + { + damage *= 4; + radius_damage *= 4; + } + + AngleVectors (ent->client->v_angle, forward, right, NULL); + + VectorScale (forward, -2, ent->client->kick_origin); + ent->client->kick_angles[0] = -2; + + VectorSet(offset, 0, 8, ent->viewheight-8); + P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start); + + if (ent->client->ps.gunframe == 8) + { + v[PITCH] = ent->client->v_angle[PITCH]; + v[YAW] = ent->client->v_angle[YAW] - 1.5; + v[ROLL] = ent->client->v_angle[ROLL]; + AngleVectors (v, forward, right, up); + + radius_damage = 30; + damage_radius = 120; + + fire_plasma (ent, start, forward, damage, 725, damage_radius, radius_damage); + + if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) ) + ent->client->pers.inventory[ent->client->ammo_index]--; + } + else + { + v[PITCH] = ent->client->v_angle[PITCH]; + v[YAW] = ent->client->v_angle[YAW] + 1.5; + v[ROLL] = ent->client->v_angle[ROLL]; + AngleVectors (v, forward, right, up); + fire_plasma (ent, start, forward, damage, 725, damage_radius, radius_damage); + + // send muzzle flash + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + gi.WriteByte (MZ_PHALANX | is_silenced); + gi.multicast (ent->s.origin, MULTICAST_PVS); + + PlayerNoise(ent, start, PNOISE_WEAPON); + } + + ent->client->ps.gunframe++; + +} + +void Weapon_Phalanx (edict_t *ent) +{ + static int pause_frames[] = {29, 42, 55, 0}; + static int fire_frames[] = {7, 8, 0}; + + Weapon_Generic (ent, 5, 20, 58, 63, pause_frames, fire_frames, weapon_phalanx_fire); + + if (is_quadfire) + Weapon_Generic (ent, 5, 20, 58, 63, pause_frames, fire_frames, weapon_phalanx_fire); + +} + +/* +====================================================================== + +TRAP + +====================================================================== +*/ + +#define TRAP_TIMER 5.0 +#define TRAP_MINSPEED 300 +#define TRAP_MAXSPEED 700 + +void weapon_trap_fire (edict_t *ent, qboolean held) +{ + vec3_t offset; + vec3_t forward, right; + vec3_t start; + int damage = 125; + float timer; + int speed; + float radius; + + radius = damage+40; + if (is_quad) + damage *= 4; + + VectorSet(offset, 8, 8, ent->viewheight-8); + AngleVectors (ent->client->v_angle, forward, right, NULL); + P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start); + + timer = ent->client->grenade_time - level.time; + speed = GRENADE_MINSPEED + (GRENADE_TIMER - timer) * ((GRENADE_MAXSPEED - GRENADE_MINSPEED) / GRENADE_TIMER); + // fire_grenade2 (ent, start, forward, damage, speed, timer, radius, held); + fire_trap (ent, start, forward, damage, speed, timer, radius, held); + +// you don't get infinite traps! ZOID +// if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) ) + + ent->client->pers.inventory[ent->client->ammo_index]--; + + ent->client->grenade_time = level.time + 1.0; +} + +void Weapon_Trap (edict_t *ent) +{ + if ((ent->client->newweapon) && (ent->client->weaponstate == WEAPON_READY)) + { + ChangeWeapon (ent); + return; + } + + if (ent->client->weaponstate == WEAPON_ACTIVATING) + { + ent->client->weaponstate = WEAPON_READY; + ent->client->ps.gunframe = 16; + return; + } + + if (ent->client->weaponstate == WEAPON_READY) + { + if ( ((ent->client->latched_buttons|ent->client->buttons) & BUTTON_ATTACK) ) + { + ent->client->latched_buttons &= ~BUTTON_ATTACK; + if (ent->client->pers.inventory[ent->client->ammo_index]) + { + ent->client->ps.gunframe = 1; + ent->client->weaponstate = WEAPON_FIRING; + ent->client->grenade_time = 0; + } + else + { + if (level.time >= ent->pain_debounce_time) + { + gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/noammo.wav"), 1, ATTN_NORM, 0); + ent->pain_debounce_time = level.time + 1; + } + NoAmmoWeaponChange (ent); + } + return; + } + + if ((ent->client->ps.gunframe == 29) || (ent->client->ps.gunframe == 34) || (ent->client->ps.gunframe == 39) || (ent->client->ps.gunframe == 48)) + { + if (rand()&15) + return; + } + + if (++ent->client->ps.gunframe > 48) + ent->client->ps.gunframe = 16; + return; + } + + if (ent->client->weaponstate == WEAPON_FIRING) + { + if (ent->client->ps.gunframe == 5) + // RAFAEL 16-APR-98 + // gi.sound(ent, CHAN_WEAPON, gi.soundindex("weapons/hgrena1b.wav"), 1, ATTN_NORM, 0); + gi.sound(ent, CHAN_WEAPON, gi.soundindex("weapons/trapcock.wav"), 1, ATTN_NORM, 0); + // END 16-APR-98 + + if (ent->client->ps.gunframe == 11) + { + if (!ent->client->grenade_time) + { + ent->client->grenade_time = level.time + GRENADE_TIMER + 0.2; + // RAFAEL 16-APR-98 + ent->client->weapon_sound = gi.soundindex("weapons/traploop.wav"); + // END 16-APR-98 + } + + // they waited too long, detonate it in their hand + if (!ent->client->grenade_blew_up && level.time >= ent->client->grenade_time) + { + ent->client->weapon_sound = 0; + weapon_trap_fire (ent, true); + ent->client->grenade_blew_up = true; + } + + if (ent->client->buttons & BUTTON_ATTACK) + return; + + if (ent->client->grenade_blew_up) + { + if (level.time >= ent->client->grenade_time) + { + ent->client->ps.gunframe = 15; + ent->client->grenade_blew_up = false; + } + else + { + return; + } + } + } + + if (ent->client->ps.gunframe == 12) + { + ent->client->weapon_sound = 0; + weapon_trap_fire (ent, false); + if (ent->client->pers.inventory[ent->client->ammo_index] == 0) + NoAmmoWeaponChange (ent); + } + + if ((ent->client->ps.gunframe == 15) && (level.time < ent->client->grenade_time)) + return; + + ent->client->ps.gunframe++; + + if (ent->client->ps.gunframe == 16) + { + ent->client->grenade_time = 0; + ent->client->weaponstate = WEAPON_READY; + } + } +} diff --git a/original/xatrix/q_shared.c b/original/xatrix/q_shared.c new file mode 100644 index 0000000..5696fb1 --- /dev/null +++ b/original/xatrix/q_shared.c @@ -0,0 +1,1401 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +#include "q_shared.h" + +#define DEG2RAD( a ) ( a * M_PI ) / 180.0F + +vec3_t vec3_origin = {0,0,0}; + +//============================================================================ + +#ifdef _WIN32 +#pragma optimize( "", off ) +#endif + +void RotatePointAroundVector( vec3_t dst, const vec3_t dir, const vec3_t point, float degrees ) +{ + float m[3][3]; + float im[3][3]; + float zrot[3][3]; + float tmpmat[3][3]; + float rot[3][3]; + int i; + vec3_t vr, vup, vf; + + vf[0] = dir[0]; + vf[1] = dir[1]; + vf[2] = dir[2]; + + PerpendicularVector( vr, dir ); + CrossProduct( vr, vf, vup ); + + m[0][0] = vr[0]; + m[1][0] = vr[1]; + m[2][0] = vr[2]; + + m[0][1] = vup[0]; + m[1][1] = vup[1]; + m[2][1] = vup[2]; + + m[0][2] = vf[0]; + m[1][2] = vf[1]; + m[2][2] = vf[2]; + + memcpy( im, m, sizeof( im ) ); + + im[0][1] = m[1][0]; + im[0][2] = m[2][0]; + im[1][0] = m[0][1]; + im[1][2] = m[2][1]; + im[2][0] = m[0][2]; + im[2][1] = m[1][2]; + + memset( zrot, 0, sizeof( zrot ) ); + zrot[0][0] = zrot[1][1] = zrot[2][2] = 1.0F; + + zrot[0][0] = cos( DEG2RAD( degrees ) ); + zrot[0][1] = sin( DEG2RAD( degrees ) ); + zrot[1][0] = -sin( DEG2RAD( degrees ) ); + zrot[1][1] = cos( DEG2RAD( degrees ) ); + + R_ConcatRotations( m, zrot, tmpmat ); + R_ConcatRotations( tmpmat, im, rot ); + + for ( i = 0; i < 3; i++ ) + { + dst[i] = rot[i][0] * point[0] + rot[i][1] * point[1] + rot[i][2] * point[2]; + } +} + +#ifdef _WIN32 +#pragma optimize( "", on ) +#endif + + + +void AngleVectors (vec3_t angles, vec3_t forward, vec3_t right, vec3_t up) +{ + float angle; + static float sr, sp, sy, cr, cp, cy; + // static to help MS compiler fp bugs + + angle = angles[YAW] * (M_PI*2 / 360); + sy = sin(angle); + cy = cos(angle); + angle = angles[PITCH] * (M_PI*2 / 360); + sp = sin(angle); + cp = cos(angle); + angle = angles[ROLL] * (M_PI*2 / 360); + sr = sin(angle); + cr = cos(angle); + + if (forward) + { + forward[0] = cp*cy; + forward[1] = cp*sy; + forward[2] = -sp; + } + if (right) + { + right[0] = (-1*sr*sp*cy+-1*cr*-sy); + right[1] = (-1*sr*sp*sy+-1*cr*cy); + right[2] = -1*sr*cp; + } + if (up) + { + up[0] = (cr*sp*cy+-sr*-sy); + up[1] = (cr*sp*sy+-sr*cy); + up[2] = cr*cp; + } +} + + +void ProjectPointOnPlane( vec3_t dst, const vec3_t p, const vec3_t normal ) +{ + float d; + vec3_t n; + float inv_denom; + + inv_denom = 1.0F / DotProduct( normal, normal ); + + d = DotProduct( normal, p ) * inv_denom; + + n[0] = normal[0] * inv_denom; + n[1] = normal[1] * inv_denom; + n[2] = normal[2] * inv_denom; + + dst[0] = p[0] - d * n[0]; + dst[1] = p[1] - d * n[1]; + dst[2] = p[2] - d * n[2]; +} + +/* +** assumes "src" is normalized +*/ +void PerpendicularVector( vec3_t dst, const vec3_t src ) +{ + int pos; + int i; + float minelem = 1.0F; + vec3_t tempvec; + + /* + ** find the smallest magnitude axially aligned vector + */ + for ( pos = 0, i = 0; i < 3; i++ ) + { + if ( fabs( src[i] ) < minelem ) + { + pos = i; + minelem = fabs( src[i] ); + } + } + tempvec[0] = tempvec[1] = tempvec[2] = 0.0F; + tempvec[pos] = 1.0F; + + /* + ** project the point onto the plane defined by src + */ + ProjectPointOnPlane( dst, tempvec, src ); + + /* + ** normalize the result + */ + VectorNormalize( dst ); +} + + + +/* +================ +R_ConcatRotations +================ +*/ +void R_ConcatRotations (float in1[3][3], float in2[3][3], float out[3][3]) +{ + out[0][0] = in1[0][0] * in2[0][0] + in1[0][1] * in2[1][0] + + in1[0][2] * in2[2][0]; + out[0][1] = in1[0][0] * in2[0][1] + in1[0][1] * in2[1][1] + + in1[0][2] * in2[2][1]; + out[0][2] = in1[0][0] * in2[0][2] + in1[0][1] * in2[1][2] + + in1[0][2] * in2[2][2]; + out[1][0] = in1[1][0] * in2[0][0] + in1[1][1] * in2[1][0] + + in1[1][2] * in2[2][0]; + out[1][1] = in1[1][0] * in2[0][1] + in1[1][1] * in2[1][1] + + in1[1][2] * in2[2][1]; + out[1][2] = in1[1][0] * in2[0][2] + in1[1][1] * in2[1][2] + + in1[1][2] * in2[2][2]; + out[2][0] = in1[2][0] * in2[0][0] + in1[2][1] * in2[1][0] + + in1[2][2] * in2[2][0]; + out[2][1] = in1[2][0] * in2[0][1] + in1[2][1] * in2[1][1] + + in1[2][2] * in2[2][1]; + out[2][2] = in1[2][0] * in2[0][2] + in1[2][1] * in2[1][2] + + in1[2][2] * in2[2][2]; +} + + +/* +================ +R_ConcatTransforms +================ +*/ +void R_ConcatTransforms (float in1[3][4], float in2[3][4], float out[3][4]) +{ + out[0][0] = in1[0][0] * in2[0][0] + in1[0][1] * in2[1][0] + + in1[0][2] * in2[2][0]; + out[0][1] = in1[0][0] * in2[0][1] + in1[0][1] * in2[1][1] + + in1[0][2] * in2[2][1]; + out[0][2] = in1[0][0] * in2[0][2] + in1[0][1] * in2[1][2] + + in1[0][2] * in2[2][2]; + out[0][3] = in1[0][0] * in2[0][3] + in1[0][1] * in2[1][3] + + in1[0][2] * in2[2][3] + in1[0][3]; + out[1][0] = in1[1][0] * in2[0][0] + in1[1][1] * in2[1][0] + + in1[1][2] * in2[2][0]; + out[1][1] = in1[1][0] * in2[0][1] + in1[1][1] * in2[1][1] + + in1[1][2] * in2[2][1]; + out[1][2] = in1[1][0] * in2[0][2] + in1[1][1] * in2[1][2] + + in1[1][2] * in2[2][2]; + out[1][3] = in1[1][0] * in2[0][3] + in1[1][1] * in2[1][3] + + in1[1][2] * in2[2][3] + in1[1][3]; + out[2][0] = in1[2][0] * in2[0][0] + in1[2][1] * in2[1][0] + + in1[2][2] * in2[2][0]; + out[2][1] = in1[2][0] * in2[0][1] + in1[2][1] * in2[1][1] + + in1[2][2] * in2[2][1]; + out[2][2] = in1[2][0] * in2[0][2] + in1[2][1] * in2[1][2] + + in1[2][2] * in2[2][2]; + out[2][3] = in1[2][0] * in2[0][3] + in1[2][1] * in2[1][3] + + in1[2][2] * in2[2][3] + in1[2][3]; +} + + +//============================================================================ + + +float Q_fabs (float f) +{ +#if 0 + if (f >= 0) + return f; + return -f; +#else + int tmp = * ( int * ) &f; + tmp &= 0x7FFFFFFF; + return * ( float * ) &tmp; +#endif +} + +#if defined _M_IX86 && !defined C_ONLY +#pragma warning (disable:4035) +__declspec( naked ) long Q_ftol( float f ) +{ + static int tmp; + __asm fld dword ptr [esp+4] + __asm fistp tmp + __asm mov eax, tmp + __asm ret +} +#pragma warning (default:4035) +#endif + +/* +=============== +LerpAngle + +=============== +*/ +float LerpAngle (float a2, float a1, float frac) +{ + if (a1 - a2 > 180) + a1 -= 360; + if (a1 - a2 < -180) + a1 += 360; + return a2 + frac * (a1 - a2); +} + + +float anglemod(float a) +{ +#if 0 + if (a >= 0) + a -= 360*(int)(a/360); + else + a += 360*( 1 + (int)(-a/360) ); +#endif + a = (360.0/65536) * ((int)(a*(65536/360.0)) & 65535); + return a; +} + + int i; + vec3_t corners[2]; + + +// this is the slow, general version +int BoxOnPlaneSide2 (vec3_t emins, vec3_t emaxs, struct cplane_s *p) +{ + int i; + float dist1, dist2; + int sides; + vec3_t corners[2]; + + for (i=0 ; i<3 ; i++) + { + if (p->normal[i] < 0) + { + corners[0][i] = emins[i]; + corners[1][i] = emaxs[i]; + } + else + { + corners[1][i] = emins[i]; + corners[0][i] = emaxs[i]; + } + } + dist1 = DotProduct (p->normal, corners[0]) - p->dist; + dist2 = DotProduct (p->normal, corners[1]) - p->dist; + sides = 0; + if (dist1 >= 0) + sides = 1; + if (dist2 < 0) + sides |= 2; + + return sides; +} + +/* +================== +BoxOnPlaneSide + +Returns 1, 2, or 1 + 2 +================== +*/ +#if !id386 || defined __linux__ +int BoxOnPlaneSide (vec3_t emins, vec3_t emaxs, struct cplane_s *p) +{ + float dist1, dist2; + int sides; + +// fast axial cases + if (p->type < 3) + { + if (p->dist <= emins[p->type]) + return 1; + if (p->dist >= emaxs[p->type]) + return 2; + return 3; + } + +// general case + switch (p->signbits) + { + case 0: +dist1 = p->normal[0]*emaxs[0] + p->normal[1]*emaxs[1] + p->normal[2]*emaxs[2]; +dist2 = p->normal[0]*emins[0] + p->normal[1]*emins[1] + p->normal[2]*emins[2]; + break; + case 1: +dist1 = p->normal[0]*emins[0] + p->normal[1]*emaxs[1] + p->normal[2]*emaxs[2]; +dist2 = p->normal[0]*emaxs[0] + p->normal[1]*emins[1] + p->normal[2]*emins[2]; + break; + case 2: +dist1 = p->normal[0]*emaxs[0] + p->normal[1]*emins[1] + p->normal[2]*emaxs[2]; +dist2 = p->normal[0]*emins[0] + p->normal[1]*emaxs[1] + p->normal[2]*emins[2]; + break; + case 3: +dist1 = p->normal[0]*emins[0] + p->normal[1]*emins[1] + p->normal[2]*emaxs[2]; +dist2 = p->normal[0]*emaxs[0] + p->normal[1]*emaxs[1] + p->normal[2]*emins[2]; + break; + case 4: +dist1 = p->normal[0]*emaxs[0] + p->normal[1]*emaxs[1] + p->normal[2]*emins[2]; +dist2 = p->normal[0]*emins[0] + p->normal[1]*emins[1] + p->normal[2]*emaxs[2]; + break; + case 5: +dist1 = p->normal[0]*emins[0] + p->normal[1]*emaxs[1] + p->normal[2]*emins[2]; +dist2 = p->normal[0]*emaxs[0] + p->normal[1]*emins[1] + p->normal[2]*emaxs[2]; + break; + case 6: +dist1 = p->normal[0]*emaxs[0] + p->normal[1]*emins[1] + p->normal[2]*emins[2]; +dist2 = p->normal[0]*emins[0] + p->normal[1]*emaxs[1] + p->normal[2]*emaxs[2]; + break; + case 7: +dist1 = p->normal[0]*emins[0] + p->normal[1]*emins[1] + p->normal[2]*emins[2]; +dist2 = p->normal[0]*emaxs[0] + p->normal[1]*emaxs[1] + p->normal[2]*emaxs[2]; + break; + default: + dist1 = dist2 = 0; // shut up compiler + assert( 0 ); + break; + } + + sides = 0; + if (dist1 >= p->dist) + sides = 1; + if (dist2 < p->dist) + sides |= 2; + + assert( sides != 0 ); + + return sides; +} +#else +#pragma warning( disable: 4035 ) + +__declspec( naked ) int BoxOnPlaneSide (vec3_t emins, vec3_t emaxs, struct cplane_s *p) +{ + static int bops_initialized; + static int Ljmptab[8]; + + __asm { + + push ebx + + cmp bops_initialized, 1 + je initialized + mov bops_initialized, 1 + + mov Ljmptab[0*4], offset Lcase0 + mov Ljmptab[1*4], offset Lcase1 + mov Ljmptab[2*4], offset Lcase2 + mov Ljmptab[3*4], offset Lcase3 + mov Ljmptab[4*4], offset Lcase4 + mov Ljmptab[5*4], offset Lcase5 + mov Ljmptab[6*4], offset Lcase6 + mov Ljmptab[7*4], offset Lcase7 + +initialized: + + mov edx,ds:dword ptr[4+12+esp] + mov ecx,ds:dword ptr[4+4+esp] + xor eax,eax + mov ebx,ds:dword ptr[4+8+esp] + mov al,ds:byte ptr[17+edx] + cmp al,8 + jge Lerror + fld ds:dword ptr[0+edx] + fld st(0) + jmp dword ptr[Ljmptab+eax*4] +Lcase0: + fmul ds:dword ptr[ebx] + fld ds:dword ptr[0+4+edx] + fxch st(2) + fmul ds:dword ptr[ecx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[4+ebx] + fld ds:dword ptr[0+8+edx] + fxch st(2) + fmul ds:dword ptr[4+ecx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[8+ebx] + fxch st(5) + faddp st(3),st(0) + fmul ds:dword ptr[8+ecx] + fxch st(1) + faddp st(3),st(0) + fxch st(3) + faddp st(2),st(0) + jmp LSetSides +Lcase1: + fmul ds:dword ptr[ecx] + fld ds:dword ptr[0+4+edx] + fxch st(2) + fmul ds:dword ptr[ebx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[4+ebx] + fld ds:dword ptr[0+8+edx] + fxch st(2) + fmul ds:dword ptr[4+ecx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[8+ebx] + fxch st(5) + faddp st(3),st(0) + fmul ds:dword ptr[8+ecx] + fxch st(1) + faddp st(3),st(0) + fxch st(3) + faddp st(2),st(0) + jmp LSetSides +Lcase2: + fmul ds:dword ptr[ebx] + fld ds:dword ptr[0+4+edx] + fxch st(2) + fmul ds:dword ptr[ecx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[4+ecx] + fld ds:dword ptr[0+8+edx] + fxch st(2) + fmul ds:dword ptr[4+ebx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[8+ebx] + fxch st(5) + faddp st(3),st(0) + fmul ds:dword ptr[8+ecx] + fxch st(1) + faddp st(3),st(0) + fxch st(3) + faddp st(2),st(0) + jmp LSetSides +Lcase3: + fmul ds:dword ptr[ecx] + fld ds:dword ptr[0+4+edx] + fxch st(2) + fmul ds:dword ptr[ebx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[4+ecx] + fld ds:dword ptr[0+8+edx] + fxch st(2) + fmul ds:dword ptr[4+ebx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[8+ebx] + fxch st(5) + faddp st(3),st(0) + fmul ds:dword ptr[8+ecx] + fxch st(1) + faddp st(3),st(0) + fxch st(3) + faddp st(2),st(0) + jmp LSetSides +Lcase4: + fmul ds:dword ptr[ebx] + fld ds:dword ptr[0+4+edx] + fxch st(2) + fmul ds:dword ptr[ecx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[4+ebx] + fld ds:dword ptr[0+8+edx] + fxch st(2) + fmul ds:dword ptr[4+ecx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[8+ecx] + fxch st(5) + faddp st(3),st(0) + fmul ds:dword ptr[8+ebx] + fxch st(1) + faddp st(3),st(0) + fxch st(3) + faddp st(2),st(0) + jmp LSetSides +Lcase5: + fmul ds:dword ptr[ecx] + fld ds:dword ptr[0+4+edx] + fxch st(2) + fmul ds:dword ptr[ebx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[4+ebx] + fld ds:dword ptr[0+8+edx] + fxch st(2) + fmul ds:dword ptr[4+ecx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[8+ecx] + fxch st(5) + faddp st(3),st(0) + fmul ds:dword ptr[8+ebx] + fxch st(1) + faddp st(3),st(0) + fxch st(3) + faddp st(2),st(0) + jmp LSetSides +Lcase6: + fmul ds:dword ptr[ebx] + fld ds:dword ptr[0+4+edx] + fxch st(2) + fmul ds:dword ptr[ecx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[4+ecx] + fld ds:dword ptr[0+8+edx] + fxch st(2) + fmul ds:dword ptr[4+ebx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[8+ecx] + fxch st(5) + faddp st(3),st(0) + fmul ds:dword ptr[8+ebx] + fxch st(1) + faddp st(3),st(0) + fxch st(3) + faddp st(2),st(0) + jmp LSetSides +Lcase7: + fmul ds:dword ptr[ecx] + fld ds:dword ptr[0+4+edx] + fxch st(2) + fmul ds:dword ptr[ebx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[4+ecx] + fld ds:dword ptr[0+8+edx] + fxch st(2) + fmul ds:dword ptr[4+ebx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[8+ecx] + fxch st(5) + faddp st(3),st(0) + fmul ds:dword ptr[8+ebx] + fxch st(1) + faddp st(3),st(0) + fxch st(3) + faddp st(2),st(0) +LSetSides: + faddp st(2),st(0) + fcomp ds:dword ptr[12+edx] + xor ecx,ecx + fnstsw ax + fcomp ds:dword ptr[12+edx] + and ah,1 + xor ah,1 + add cl,ah + fnstsw ax + and ah,1 + add ah,ah + add cl,ah + pop ebx + mov eax,ecx + ret +Lerror: + int 3 + } +} +#pragma warning( default: 4035 ) +#endif + +void ClearBounds (vec3_t mins, vec3_t maxs) +{ + mins[0] = mins[1] = mins[2] = 99999; + maxs[0] = maxs[1] = maxs[2] = -99999; +} + +void AddPointToBounds (vec3_t v, vec3_t mins, vec3_t maxs) +{ + int i; + vec_t val; + + for (i=0 ; i<3 ; i++) + { + val = v[i]; + if (val < mins[i]) + mins[i] = val; + if (val > maxs[i]) + maxs[i] = val; + } +} + + +int VectorCompare (vec3_t v1, vec3_t v2) +{ + if (v1[0] != v2[0] || v1[1] != v2[1] || v1[2] != v2[2]) + return 0; + + return 1; +} + + +vec_t VectorNormalize (vec3_t v) +{ + float length, ilength; + + length = v[0]*v[0] + v[1]*v[1] + v[2]*v[2]; + length = sqrt (length); // FIXME + + if (length) + { + ilength = 1/length; + v[0] *= ilength; + v[1] *= ilength; + v[2] *= ilength; + } + + return length; + +} + +vec_t VectorNormalize2 (vec3_t v, vec3_t out) +{ + float length, ilength; + + length = v[0]*v[0] + v[1]*v[1] + v[2]*v[2]; + length = sqrt (length); // FIXME + + if (length) + { + ilength = 1/length; + out[0] = v[0]*ilength; + out[1] = v[1]*ilength; + out[2] = v[2]*ilength; + } + + return length; + +} + +void VectorMA (vec3_t veca, float scale, vec3_t vecb, vec3_t vecc) +{ + vecc[0] = veca[0] + scale*vecb[0]; + vecc[1] = veca[1] + scale*vecb[1]; + vecc[2] = veca[2] + scale*vecb[2]; +} + + +vec_t _DotProduct (vec3_t v1, vec3_t v2) +{ + return v1[0]*v2[0] + v1[1]*v2[1] + v1[2]*v2[2]; +} + +void _VectorSubtract (vec3_t veca, vec3_t vecb, vec3_t out) +{ + out[0] = veca[0]-vecb[0]; + out[1] = veca[1]-vecb[1]; + out[2] = veca[2]-vecb[2]; +} + +void _VectorAdd (vec3_t veca, vec3_t vecb, vec3_t out) +{ + out[0] = veca[0]+vecb[0]; + out[1] = veca[1]+vecb[1]; + out[2] = veca[2]+vecb[2]; +} + +void _VectorCopy (vec3_t in, vec3_t out) +{ + out[0] = in[0]; + out[1] = in[1]; + out[2] = in[2]; +} + +void CrossProduct (vec3_t v1, vec3_t v2, vec3_t cross) +{ + cross[0] = v1[1]*v2[2] - v1[2]*v2[1]; + cross[1] = v1[2]*v2[0] - v1[0]*v2[2]; + cross[2] = v1[0]*v2[1] - v1[1]*v2[0]; +} + +double sqrt(double x); + +vec_t VectorLength(vec3_t v) +{ + int i; + float length; + + length = 0; + for (i=0 ; i< 3 ; i++) + length += v[i]*v[i]; + length = sqrt (length); // FIXME + + return length; +} + +void VectorInverse (vec3_t v) +{ + v[0] = -v[0]; + v[1] = -v[1]; + v[2] = -v[2]; +} + +void VectorScale (vec3_t in, vec_t scale, vec3_t out) +{ + out[0] = in[0]*scale; + out[1] = in[1]*scale; + out[2] = in[2]*scale; +} + + +int Q_log2(int val) +{ + int answer=0; + while (val>>=1) + answer++; + return answer; +} + + + +//==================================================================================== + +/* +============ +COM_SkipPath +============ +*/ +char *COM_SkipPath (char *pathname) +{ + char *last; + + last = pathname; + while (*pathname) + { + if (*pathname=='/') + last = pathname+1; + pathname++; + } + return last; +} + +/* +============ +COM_StripExtension +============ +*/ +void COM_StripExtension (char *in, char *out) +{ + while (*in && *in != '.') + *out++ = *in++; + *out = 0; +} + +/* +============ +COM_FileExtension +============ +*/ +char *COM_FileExtension (char *in) +{ + static char exten[8]; + int i; + + while (*in && *in != '.') + in++; + if (!*in) + return ""; + in++; + for (i=0 ; i<7 && *in ; i++,in++) + exten[i] = *in; + exten[i] = 0; + return exten; +} + +/* +============ +COM_FileBase +============ +*/ +void COM_FileBase (char *in, char *out) +{ + char *s, *s2; + + s = in + strlen(in) - 1; + + while (s != in && *s != '.') + s--; + + for (s2 = s ; s2 != in && *s2 != '/' ; s2--) + ; + + if (s-s2 < 2) + out[0] = 0; + else + { + s--; + strncpy (out,s2+1, s-s2); + out[s-s2] = 0; + } +} + +/* +============ +COM_FilePath + +Returns the path up to, but not including the last / +============ +*/ +void COM_FilePath (char *in, char *out) +{ + char *s; + + s = in + strlen(in) - 1; + + while (s != in && *s != '/') + s--; + + strncpy (out,in, s-in); + out[s-in] = 0; +} + + +/* +================== +COM_DefaultExtension +================== +*/ +void COM_DefaultExtension (char *path, char *extension) +{ + char *src; +// +// if path doesn't have a .EXT, append extension +// (extension should include the .) +// + src = path + strlen(path) - 1; + + while (*src != '/' && src != path) + { + if (*src == '.') + return; // it has an extension + src--; + } + + strcat (path, extension); +} + +/* +============================================================================ + + BYTE ORDER FUNCTIONS + +============================================================================ +*/ + +qboolean bigendien; + +// can't just use function pointers, or dll linkage can +// mess up when qcommon is included in multiple places +short (*_BigShort) (short l); +short (*_LittleShort) (short l); +int (*_BigLong) (int l); +int (*_LittleLong) (int l); +float (*_BigFloat) (float l); +float (*_LittleFloat) (float l); + +short BigShort(short l){return _BigShort(l);} +short LittleShort(short l) {return _LittleShort(l);} +int BigLong (int l) {return _BigLong(l);} +int LittleLong (int l) {return _LittleLong(l);} +float BigFloat (float l) {return _BigFloat(l);} +float LittleFloat (float l) {return _LittleFloat(l);} + +short ShortSwap (short l) +{ + byte b1,b2; + + b1 = l&255; + b2 = (l>>8)&255; + + return (b1<<8) + b2; +} + +short ShortNoSwap (short l) +{ + return l; +} + +int LongSwap (int l) +{ + byte b1,b2,b3,b4; + + b1 = l&255; + b2 = (l>>8)&255; + b3 = (l>>16)&255; + b4 = (l>>24)&255; + + return ((int)b1<<24) + ((int)b2<<16) + ((int)b3<<8) + b4; +} + +int LongNoSwap (int l) +{ + return l; +} + +float FloatSwap (float f) +{ + union + { + float f; + byte b[4]; + } dat1, dat2; + + + dat1.f = f; + dat2.b[0] = dat1.b[3]; + dat2.b[1] = dat1.b[2]; + dat2.b[2] = dat1.b[1]; + dat2.b[3] = dat1.b[0]; + return dat2.f; +} + +float FloatNoSwap (float f) +{ + return f; +} + +/* +================ +Swap_Init +================ +*/ +void Swap_Init (void) +{ + byte swaptest[2] = {1,0}; + +// set the byte swapping variables in a portable manner + if ( *(short *)swaptest == 1) + { + bigendien = false; + _BigShort = ShortSwap; + _LittleShort = ShortNoSwap; + _BigLong = LongSwap; + _LittleLong = LongNoSwap; + _BigFloat = FloatSwap; + _LittleFloat = FloatNoSwap; + } + else + { + bigendien = true; + _BigShort = ShortNoSwap; + _LittleShort = ShortSwap; + _BigLong = LongNoSwap; + _LittleLong = LongSwap; + _BigFloat = FloatNoSwap; + _LittleFloat = FloatSwap; + } + +} + + + +/* +============ +va + +does a varargs printf into a temp buffer, so I don't need to have +varargs versions of all text functions. +FIXME: make this buffer size safe someday +============ +*/ +char *va(char *format, ...) +{ + va_list argptr; + static char string[1024]; + + va_start (argptr, format); + vsprintf (string, format,argptr); + va_end (argptr); + + return string; +} + + +char com_token[MAX_TOKEN_CHARS]; + +/* +============== +COM_Parse + +Parse a token out of a string +============== +*/ +char *COM_Parse (char **data_p) +{ + int c; + int len; + char *data; + + data = *data_p; + len = 0; + com_token[0] = 0; + + if (!data) + { + *data_p = NULL; + return ""; + } + +// skip whitespace +skipwhite: + while ( (c = *data) <= ' ') + { + if (c == 0) + { + *data_p = NULL; + return ""; + } + data++; + } + +// skip // comments + if (c=='/' && data[1] == '/') + { + while (*data && *data != '\n') + data++; + goto skipwhite; + } + +// handle quoted strings specially + if (c == '\"') + { + data++; + while (1) + { + c = *data++; + if (c=='\"' || !c) + { + com_token[len] = 0; + *data_p = data; + return com_token; + } + if (len < MAX_TOKEN_CHARS) + { + com_token[len] = c; + len++; + } + } + } + +// parse a regular word + do + { + if (len < MAX_TOKEN_CHARS) + { + com_token[len] = c; + len++; + } + data++; + c = *data; + } while (c>32); + + if (len == MAX_TOKEN_CHARS) + { +// Com_Printf ("Token exceeded %i chars, discarded.\n", MAX_TOKEN_CHARS); + len = 0; + } + com_token[len] = 0; + + *data_p = data; + return com_token; +} + + +/* +=============== +Com_PageInMemory + +=============== +*/ +int paged_total; + +void Com_PageInMemory (byte *buffer, int size) +{ + int i; + + for (i=size-1 ; i>0 ; i-=4096) + paged_total += buffer[i]; +} + + + +/* +============================================================================ + + LIBRARY REPLACEMENT FUNCTIONS + +============================================================================ +*/ + +// FIXME: replace all Q_stricmp with Q_strcasecmp +int Q_stricmp (char *s1, char *s2) +{ +#if defined(WIN32) + return _stricmp (s1, s2); +#else + return strcasecmp (s1, s2); +#endif +} + + +int Q_strncasecmp (char *s1, char *s2, int n) +{ + int c1, c2; + + do + { + c1 = *s1++; + c2 = *s2++; + + if (!n--) + return 0; // strings are equal until end point + + if (c1 != c2) + { + if (c1 >= 'a' && c1 <= 'z') + c1 -= ('a' - 'A'); + if (c2 >= 'a' && c2 <= 'z') + c2 -= ('a' - 'A'); + if (c1 != c2) + return -1; // strings not equal + } + } while (c1); + + return 0; // strings are equal +} + +int Q_strcasecmp (char *s1, char *s2) +{ + return Q_strncasecmp (s1, s2, 99999); +} + + + +void Com_sprintf (char *dest, int size, char *fmt, ...) +{ + int len; + va_list argptr; + char bigbuffer[0x10000]; + + va_start (argptr,fmt); + len = vsprintf (bigbuffer,fmt,argptr); + va_end (argptr); + if (len >= size) + Com_Printf ("Com_sprintf: overflow of %i in %i\n", len, size); + strncpy (dest, bigbuffer, size-1); +} + +/* +===================================================================== + + INFO STRINGS + +===================================================================== +*/ + +/* +=============== +Info_ValueForKey + +Searches the string for the given +key and returns the associated value, or an empty string. +=============== +*/ +char *Info_ValueForKey (char *s, char *key) +{ + char pkey[512]; + static char value[2][512]; // use two buffers so compares + // work without stomping on each other + static int valueindex; + char *o; + + valueindex ^= 1; + if (*s == '\\') + s++; + while (1) + { + o = pkey; + while (*s != '\\') + { + if (!*s) + return ""; + *o++ = *s++; + } + *o = 0; + s++; + + o = value[valueindex]; + + while (*s != '\\' && *s) + { + if (!*s) + return ""; + *o++ = *s++; + } + *o = 0; + + if (!strcmp (key, pkey) ) + return value[valueindex]; + + if (!*s) + return ""; + s++; + } +} + +void Info_RemoveKey (char *s, char *key) +{ + char *start; + char pkey[512]; + char value[512]; + char *o; + + if (strstr (key, "\\")) + { +// Com_Printf ("Can't use a key with a \\\n"); + return; + } + + while (1) + { + start = s; + if (*s == '\\') + s++; + o = pkey; + while (*s != '\\') + { + if (!*s) + return; + *o++ = *s++; + } + *o = 0; + s++; + + o = value; + while (*s != '\\' && *s) + { + if (!*s) + return; + *o++ = *s++; + } + *o = 0; + + if (!strcmp (key, pkey) ) + { + strcpy (start, s); // remove this part + return; + } + + if (!*s) + return; + } + +} + + +/* +================== +Info_Validate + +Some characters are illegal in info strings because they +can mess up the server's parsing +================== +*/ +qboolean Info_Validate (char *s) +{ + if (strstr (s, "\"")) + return false; + if (strstr (s, ";")) + return false; + return true; +} + +void Info_SetValueForKey (char *s, char *key, char *value) +{ + char newi[MAX_INFO_STRING], *v; + int c; + int maxsize = MAX_INFO_STRING; + + if (strstr (key, "\\") || strstr (value, "\\") ) + { + Com_Printf ("Can't use keys or values with a \\\n"); + return; + } + + if (strstr (key, ";") ) + { + Com_Printf ("Can't use keys or values with a semicolon\n"); + return; + } + + if (strstr (key, "\"") || strstr (value, "\"") ) + { + Com_Printf ("Can't use keys or values with a \"\n"); + return; + } + + if (strlen(key) > MAX_INFO_KEY-1 || strlen(value) > MAX_INFO_KEY-1) + { + Com_Printf ("Keys and values must be < 64 characters.\n"); + return; + } + Info_RemoveKey (s, key); + if (!value || !strlen(value)) + return; + + Com_sprintf (newi, sizeof(newi), "\\%s\\%s", key, value); + + if (strlen(newi) + strlen(s) > maxsize) + { + Com_Printf ("Info string length exceeded\n"); + return; + } + + // only copy ascii values + s += strlen(s); + v = newi; + while (*v) + { + c = *v++; + c &= 127; // strip high bits + if (c >= 32 && c < 127) + *s++ = c; + } + *s = 0; +} + +//==================================================================== + + diff --git a/original/xatrix/q_shared.h b/original/xatrix/q_shared.h new file mode 100644 index 0000000..db25448 --- /dev/null +++ b/original/xatrix/q_shared.h @@ -0,0 +1,1183 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. + +// q_shared.h -- included first by ALL program modules + +#ifdef _WIN32 +// unknown pragmas are SUPPOSED to be ignored, but.... +#pragma warning(disable : 4244) // MIPS +#pragma warning(disable : 4136) // X86 +#pragma warning(disable : 4051) // ALPHA + +#pragma warning(disable : 4018) // signed/unsigned mismatch +#pragma warning(disable : 4305) // truncation from const double to float + +#endif + +#include +#include +#include +#include +#include +#include +#include + +#if (defined _M_IX86 || defined __i386__) && !defined C_ONLY && !defined __sun__ +#define id386 1 +#else +#define id386 0 +#endif + +#if defined _M_ALPHA && !defined C_ONLY +#define idaxp 1 +#else +#define idaxp 0 +#endif + +typedef unsigned char byte; +typedef enum {false, true} qboolean; + + +#ifndef NULL +#define NULL ((void *)0) +#endif + + +// angle indexes +#define PITCH 0 // up / down +#define YAW 1 // left / right +#define ROLL 2 // fall over + +#define MAX_STRING_CHARS 1024 // max length of a string passed to Cmd_TokenizeString +#define MAX_STRING_TOKENS 80 // max tokens resulting from Cmd_TokenizeString +#define MAX_TOKEN_CHARS 128 // max length of an individual token + +#define MAX_QPATH 64 // max length of a quake game pathname +#define MAX_OSPATH 128 // max length of a filesystem pathname + +// +// per-level limits +// +#define MAX_CLIENTS 256 // absolute limit +#define MAX_EDICTS 1024 // must change protocol to increase more +#define MAX_LIGHTSTYLES 256 +#define MAX_MODELS 256 // these are sent over the net as bytes +#define MAX_SOUNDS 256 // so they cannot be blindly increased +#define MAX_IMAGES 256 +#define MAX_ITEMS 256 +#define MAX_GENERAL (MAX_CLIENTS*2) // general config strings + + +// game print flags +#define PRINT_LOW 0 // pickup messages +#define PRINT_MEDIUM 1 // death messages +#define PRINT_HIGH 2 // critical messages +#define PRINT_CHAT 3 // chat messages + + + +#define ERR_FATAL 0 // exit the entire game with a popup window +#define ERR_DROP 1 // print to console and disconnect from game +#define ERR_DISCONNECT 2 // don't kill server + +#define PRINT_ALL 0 +#define PRINT_DEVELOPER 1 // only print when "developer 1" +#define PRINT_ALERT 2 + + +// destination class for gi.multicast() +typedef enum +{ +MULTICAST_ALL, +MULTICAST_PHS, +MULTICAST_PVS, +MULTICAST_ALL_R, +MULTICAST_PHS_R, +MULTICAST_PVS_R +} multicast_t; + + +/* +============================================================== + +MATHLIB + +============================================================== +*/ + +typedef float vec_t; +typedef vec_t vec3_t[3]; +typedef vec_t vec5_t[5]; + +typedef int fixed4_t; +typedef int fixed8_t; +typedef int fixed16_t; + +#ifndef M_PI +#define M_PI 3.14159265358979323846 // matches value in gcc v2 math.h +#endif + +struct cplane_s; + +extern vec3_t vec3_origin; + +#define nanmask (255<<23) + +#define IS_NAN(x) (((*(int *)&x)&nanmask)==nanmask) + +// microsoft's fabs seems to be ungodly slow... +//float Q_fabs (float f); +//#define fabs(f) Q_fabs(f) +#if !defined C_ONLY && !defined __linux__ && !defined __sgi +extern long Q_ftol( float f ); +#else +#define Q_ftol( f ) ( long ) (f) +#endif + +#define DotProduct(x,y) (x[0]*y[0]+x[1]*y[1]+x[2]*y[2]) +#define VectorSubtract(a,b,c) (c[0]=a[0]-b[0],c[1]=a[1]-b[1],c[2]=a[2]-b[2]) +#define VectorAdd(a,b,c) (c[0]=a[0]+b[0],c[1]=a[1]+b[1],c[2]=a[2]+b[2]) +#define VectorCopy(a,b) (b[0]=a[0],b[1]=a[1],b[2]=a[2]) +#define VectorClear(a) (a[0]=a[1]=a[2]=0) +#define VectorNegate(a,b) (b[0]=-a[0],b[1]=-a[1],b[2]=-a[2]) +#define VectorSet(v, x, y, z) (v[0]=(x), v[1]=(y), v[2]=(z)) + +void VectorMA (vec3_t veca, float scale, vec3_t vecb, vec3_t vecc); + +// just in case you do't want to use the macros +vec_t _DotProduct (vec3_t v1, vec3_t v2); +void _VectorSubtract (vec3_t veca, vec3_t vecb, vec3_t out); +void _VectorAdd (vec3_t veca, vec3_t vecb, vec3_t out); +void _VectorCopy (vec3_t in, vec3_t out); + +void ClearBounds (vec3_t mins, vec3_t maxs); +void AddPointToBounds (vec3_t v, vec3_t mins, vec3_t maxs); +int VectorCompare (vec3_t v1, vec3_t v2); +vec_t VectorLength (vec3_t v); +void CrossProduct (vec3_t v1, vec3_t v2, vec3_t cross); +vec_t VectorNormalize (vec3_t v); // returns vector length +vec_t VectorNormalize2 (vec3_t v, vec3_t out); +void VectorInverse (vec3_t v); +void VectorScale (vec3_t in, vec_t scale, vec3_t out); +int Q_log2(int val); + +void R_ConcatRotations (float in1[3][3], float in2[3][3], float out[3][3]); +void R_ConcatTransforms (float in1[3][4], float in2[3][4], float out[3][4]); + +void AngleVectors (vec3_t angles, vec3_t forward, vec3_t right, vec3_t up); +int BoxOnPlaneSide (vec3_t emins, vec3_t emaxs, struct cplane_s *plane); +float anglemod(float a); +float LerpAngle (float a1, float a2, float frac); + +#define BOX_ON_PLANE_SIDE(emins, emaxs, p) \ + (((p)->type < 3)? \ + ( \ + ((p)->dist <= (emins)[(p)->type])? \ + 1 \ + : \ + ( \ + ((p)->dist >= (emaxs)[(p)->type])?\ + 2 \ + : \ + 3 \ + ) \ + ) \ + : \ + BoxOnPlaneSide( (emins), (emaxs), (p))) + +void ProjectPointOnPlane( vec3_t dst, const vec3_t p, const vec3_t normal ); +void PerpendicularVector( vec3_t dst, const vec3_t src ); +void RotatePointAroundVector( vec3_t dst, const vec3_t dir, const vec3_t point, float degrees ); + + +//============================================= + +char *COM_SkipPath (char *pathname); +void COM_StripExtension (char *in, char *out); +void COM_FileBase (char *in, char *out); +void COM_FilePath (char *in, char *out); +void COM_DefaultExtension (char *path, char *extension); + +char *COM_Parse (char **data_p); +// data is an in/out parm, returns a parsed out token + +void Com_sprintf (char *dest, int size, char *fmt, ...); + +void Com_PageInMemory (byte *buffer, int size); + +//============================================= + +// portable case insensitive compare +int Q_stricmp (char *s1, char *s2); +int Q_strcasecmp (char *s1, char *s2); +int Q_strncasecmp (char *s1, char *s2, int n); + +//============================================= + +short BigShort(short l); +short LittleShort(short l); +int BigLong (int l); +int LittleLong (int l); +float BigFloat (float l); +float LittleFloat (float l); + +void Swap_Init (void); +char *va(char *format, ...); + +//============================================= + +// +// key / value info strings +// +#define MAX_INFO_KEY 64 +#define MAX_INFO_VALUE 64 +#define MAX_INFO_STRING 512 + +char *Info_ValueForKey (char *s, char *key); +void Info_RemoveKey (char *s, char *key); +void Info_SetValueForKey (char *s, char *key, char *value); +qboolean Info_Validate (char *s); + +/* +============================================================== + +SYSTEM SPECIFIC + +============================================================== +*/ + +extern int curtime; // time returned by last Sys_Milliseconds + +int Sys_Milliseconds (void); +void Sys_Mkdir (char *path); + +// large block stack allocation routines +void *Hunk_Begin (int maxsize); +void *Hunk_Alloc (int size); +void Hunk_Free (void *buf); +int Hunk_End (void); + +// directory searching +#define SFF_ARCH 0x01 +#define SFF_HIDDEN 0x02 +#define SFF_RDONLY 0x04 +#define SFF_SUBDIR 0x08 +#define SFF_SYSTEM 0x10 + +/* +** pass in an attribute mask of things you wish to REJECT +*/ +char *Sys_FindFirst (char *path, unsigned musthave, unsigned canthave ); +char *Sys_FindNext ( unsigned musthave, unsigned canthave ); +void Sys_FindClose (void); + + +// this is only here so the functions in q_shared.c and q_shwin.c can link +void Sys_Error (char *error, ...); +void Com_Printf (char *msg, ...); + + +/* +========================================================== + +CVARS (console variables) + +========================================================== +*/ + +#ifndef CVAR +#define CVAR + +#define CVAR_ARCHIVE 1 // set to cause it to be saved to vars.rc +#define CVAR_USERINFO 2 // added to userinfo when changed +#define CVAR_SERVERINFO 4 // added to serverinfo when changed +#define CVAR_NOSET 8 // don't allow change from console at all, + // but can be set from the command line +#define CVAR_LATCH 16 // save changes until server restart + +// nothing outside the Cvar_*() functions should modify these fields! +typedef struct cvar_s +{ + char *name; + char *string; + char *latched_string; // for CVAR_LATCH vars + int flags; + qboolean modified; // set each time the cvar is changed + float value; + struct cvar_s *next; +} cvar_t; + +#endif // CVAR + +/* +============================================================== + +COLLISION DETECTION + +============================================================== +*/ + +// lower bits are stronger, and will eat weaker brushes completely +#define CONTENTS_SOLID 1 // an eye is never valid in a solid +#define CONTENTS_WINDOW 2 // translucent, but not watery +#define CONTENTS_AUX 4 +#define CONTENTS_LAVA 8 +#define CONTENTS_SLIME 16 +#define CONTENTS_WATER 32 +#define CONTENTS_MIST 64 +#define LAST_VISIBLE_CONTENTS 64 + +// remaining contents are non-visible, and don't eat brushes + +#define CONTENTS_AREAPORTAL 0x8000 + +#define CONTENTS_PLAYERCLIP 0x10000 +#define CONTENTS_MONSTERCLIP 0x20000 + +// currents can be added to any other contents, and may be mixed +#define CONTENTS_CURRENT_0 0x40000 +#define CONTENTS_CURRENT_90 0x80000 +#define CONTENTS_CURRENT_180 0x100000 +#define CONTENTS_CURRENT_270 0x200000 +#define CONTENTS_CURRENT_UP 0x400000 +#define CONTENTS_CURRENT_DOWN 0x800000 + +#define CONTENTS_ORIGIN 0x1000000 // removed before bsping an entity + +#define CONTENTS_MONSTER 0x2000000 // should never be on a brush, only in game +#define CONTENTS_DEADMONSTER 0x4000000 +#define CONTENTS_DETAIL 0x8000000 // brushes to be added after vis leafs +#define CONTENTS_TRANSLUCENT 0x10000000 // auto set if any surface has trans +#define CONTENTS_LADDER 0x20000000 + + + +#define SURF_LIGHT 0x1 // value will hold the light strength + +#define SURF_SLICK 0x2 // effects game physics + +#define SURF_SKY 0x4 // don't draw, but add to skybox +#define SURF_WARP 0x8 // turbulent water warp +#define SURF_TRANS33 0x10 +#define SURF_TRANS66 0x20 +#define SURF_FLOWING 0x40 // scroll towards angle +#define SURF_NODRAW 0x80 // don't bother referencing the texture + + + +// content masks +#define MASK_ALL (-1) +#define MASK_SOLID (CONTENTS_SOLID|CONTENTS_WINDOW) +#define MASK_PLAYERSOLID (CONTENTS_SOLID|CONTENTS_PLAYERCLIP|CONTENTS_WINDOW|CONTENTS_MONSTER) +#define MASK_DEADSOLID (CONTENTS_SOLID|CONTENTS_PLAYERCLIP|CONTENTS_WINDOW) +#define MASK_MONSTERSOLID (CONTENTS_SOLID|CONTENTS_MONSTERCLIP|CONTENTS_WINDOW|CONTENTS_MONSTER) +#define MASK_WATER (CONTENTS_WATER|CONTENTS_LAVA|CONTENTS_SLIME) +#define MASK_OPAQUE (CONTENTS_SOLID|CONTENTS_SLIME|CONTENTS_LAVA) +#define MASK_SHOT (CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_WINDOW|CONTENTS_DEADMONSTER) +#define MASK_CURRENT (CONTENTS_CURRENT_0|CONTENTS_CURRENT_90|CONTENTS_CURRENT_180|CONTENTS_CURRENT_270|CONTENTS_CURRENT_UP|CONTENTS_CURRENT_DOWN) + + +// gi.BoxEdicts() can return a list of either solid or trigger entities +// FIXME: eliminate AREA_ distinction? +#define AREA_SOLID 1 +#define AREA_TRIGGERS 2 + + +// plane_t structure +// !!! if this is changed, it must be changed in asm code too !!! +typedef struct cplane_s +{ + vec3_t normal; + float dist; + byte type; // for fast side tests + byte signbits; // signx + (signy<<1) + (signz<<1) + byte pad[2]; +} cplane_t; + +// structure offset for asm code +#define CPLANE_NORMAL_X 0 +#define CPLANE_NORMAL_Y 4 +#define CPLANE_NORMAL_Z 8 +#define CPLANE_DIST 12 +#define CPLANE_TYPE 16 +#define CPLANE_SIGNBITS 17 +#define CPLANE_PAD0 18 +#define CPLANE_PAD1 19 + +typedef struct cmodel_s +{ + vec3_t mins, maxs; + vec3_t origin; // for sounds or lights + int headnode; +} cmodel_t; + +typedef struct csurface_s +{ + char name[16]; + int flags; + int value; +} csurface_t; + +typedef struct mapsurface_s // used internally due to name len probs //ZOID +{ + csurface_t c; + char rname[32]; +} mapsurface_t; + +// a trace is returned when a box is swept through the world +typedef struct +{ + qboolean allsolid; // if true, plane is not valid + qboolean startsolid; // if true, the initial point was in a solid area + float fraction; // time completed, 1.0 = didn't hit anything + vec3_t endpos; // final position + cplane_t plane; // surface normal at impact + csurface_t *surface; // surface hit + int contents; // contents on other side of surface hit + struct edict_s *ent; // not set by CM_*() functions +} trace_t; + + + +// pmove_state_t is the information necessary for client side movement +// prediction +typedef enum +{ + // can accelerate and turn + PM_NORMAL, + PM_SPECTATOR, + // no acceleration or turning + PM_DEAD, + PM_GIB, // different bounding box + PM_FREEZE +} pmtype_t; + +// pmove->pm_flags +#define PMF_DUCKED 1 +#define PMF_JUMP_HELD 2 +#define PMF_ON_GROUND 4 +#define PMF_TIME_WATERJUMP 8 // pm_time is waterjump +#define PMF_TIME_LAND 16 // pm_time is time before rejump +#define PMF_TIME_TELEPORT 32 // pm_time is non-moving time +#define PMF_NO_PREDICTION 64 // temporarily disables prediction (used for grappling hook) + +// this structure needs to be communicated bit-accurate +// from the server to the client to guarantee that +// prediction stays in sync, so no floats are used. +// if any part of the game code modifies this struct, it +// will result in a prediction error of some degree. +typedef struct +{ + pmtype_t pm_type; + + short origin[3]; // 12.3 + short velocity[3]; // 12.3 + byte pm_flags; // ducked, jump_held, etc + byte pm_time; // each unit = 8 ms + short gravity; + short delta_angles[3]; // add to command angles to get view direction + // changed by spawns, rotating objects, and teleporters +} pmove_state_t; + + +// +// button bits +// +#define BUTTON_ATTACK 1 +#define BUTTON_USE 2 +#define BUTTON_ANY 128 // any key whatsoever + + +// usercmd_t is sent to the server each client frame +typedef struct usercmd_s +{ + byte msec; + byte buttons; + short angles[3]; + short forwardmove, sidemove, upmove; + byte impulse; // remove? + byte lightlevel; // light level the player is standing on +} usercmd_t; + + +#define MAXTOUCH 32 +typedef struct +{ + // state (in / out) + pmove_state_t s; + + // command (in) + usercmd_t cmd; + qboolean snapinitial; // if s has been changed outside pmove + + // results (out) + int numtouch; + struct edict_s *touchents[MAXTOUCH]; + + vec3_t viewangles; // clamped + float viewheight; + + vec3_t mins, maxs; // bounding box size + + struct edict_s *groundentity; + int watertype; + int waterlevel; + + // callbacks to test the world + trace_t (*trace) (vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end); + int (*pointcontents) (vec3_t point); +} pmove_t; + + +// entity_state_t->effects +// Effects are things handled on the client side (lights, particles, frame animations) +// that happen constantly on the given entity. +// An entity that has effects will be sent to the client +// even if it has a zero index model. +#define EF_ROTATE 0x00000001 // rotate (bonus items) +#define EF_GIB 0x00000002 // leave a trail +#define EF_BLASTER 0x00000008 // redlight + trail +#define EF_ROCKET 0x00000010 // redlight + trail +#define EF_GRENADE 0x00000020 +#define EF_HYPERBLASTER 0x00000040 +#define EF_BFG 0x00000080 +#define EF_COLOR_SHELL 0x00000100 +#define EF_POWERSCREEN 0x00000200 +#define EF_ANIM01 0x00000400 // automatically cycle between frames 0 and 1 at 2 hz +#define EF_ANIM23 0x00000800 // automatically cycle between frames 2 and 3 at 2 hz +#define EF_ANIM_ALL 0x00001000 // automatically cycle through all frames at 2hz +#define EF_ANIM_ALLFAST 0x00002000 // automatically cycle through all frames at 10hz +#define EF_FLIES 0x00004000 +#define EF_QUAD 0x00008000 +#define EF_PENT 0x00010000 +#define EF_TELEPORTER 0x00020000 // particle fountain +#define EF_FLAG1 0x00040000 +#define EF_FLAG2 0x00080000 +// RAFAEL +#define EF_IONRIPPER 0x00100000 +#define EF_GREENGIB 0x00200000 +#define EF_BLUEHYPERBLASTER 0x00400000 +#define EF_SPINNINGLIGHTS 0x00800000 +#define EF_PLASMA 0x01000000 +#define EF_TRAP 0x02000000 + +//ROGUE +#define EF_TRACKER 0x04000000 +#define EF_DOUBLE 0x08000000 +#define EF_SPHERETRANS 0x10000000 +#define EF_TAGTRAIL 0x20000000 +#define EF_HALF_DAMAGE 0x40000000 +#define EF_TRACKERTRAIL 0x80000000 +//ROGUE + +// entity_state_t->renderfx flags +#define RF_MINLIGHT 1 // allways have some light (viewmodel) +#define RF_VIEWERMODEL 2 // don't draw through eyes, only mirrors +#define RF_WEAPONMODEL 4 // only draw through eyes +#define RF_FULLBRIGHT 8 // allways draw full intensity +#define RF_DEPTHHACK 16 // for view weapon Z crunching +#define RF_TRANSLUCENT 32 +#define RF_FRAMELERP 64 +#define RF_BEAM 128 +#define RF_CUSTOMSKIN 256 // skin is an index in image_precache +#define RF_GLOW 512 // pulse lighting for bonus items +#define RF_SHELL_RED 1024 +#define RF_SHELL_GREEN 2048 +#define RF_SHELL_BLUE 4096 + +//ROGUE +#define RF_IR_VISIBLE 0x00008000 // 32768 +#define RF_SHELL_DOUBLE 0x00010000 // 65536 +#define RF_SHELL_HALF_DAM 0x00020000 +#define RF_USE_DISGUISE 0x00040000 +//ROGUE + +// player_state_t->refdef flags +#define RDF_UNDERWATER 1 // warp the screen as apropriate +#define RDF_NOWORLDMODEL 2 // used for player configuration screen + +//ROGUE +#define RDF_IRGOGGLES 4 +#define RDF_UVGOGGLES 8 +//ROGUE + +// +// muzzle flashes / player effects +// +#define MZ_BLASTER 0 +#define MZ_MACHINEGUN 1 +#define MZ_SHOTGUN 2 +#define MZ_CHAINGUN1 3 +#define MZ_CHAINGUN2 4 +#define MZ_CHAINGUN3 5 +#define MZ_RAILGUN 6 +#define MZ_ROCKET 7 +#define MZ_GRENADE 8 +#define MZ_LOGIN 9 +#define MZ_LOGOUT 10 +#define MZ_RESPAWN 11 +#define MZ_BFG 12 +#define MZ_SSHOTGUN 13 +#define MZ_HYPERBLASTER 14 +#define MZ_ITEMRESPAWN 15 +// RAFAEL +#define MZ_IONRIPPER 16 +#define MZ_BLUEHYPERBLASTER 17 +#define MZ_PHALANX 18 +#define MZ_SILENCED 128 // bit flag ORed with one of the above numbers + +//ROGUE +#define MZ_ETF_RIFLE 30 +#define MZ_UNUSED 31 +#define MZ_SHOTGUN2 32 +#define MZ_HEATBEAM 33 +#define MZ_BLASTER2 34 +#define MZ_TRACKER 35 +#define MZ_NUKE1 36 +#define MZ_NUKE2 37 +#define MZ_NUKE4 38 +#define MZ_NUKE8 39 +//ROGUE + +// +// monster muzzle flashes +// +#define MZ2_TANK_BLASTER_1 1 +#define MZ2_TANK_BLASTER_2 2 +#define MZ2_TANK_BLASTER_3 3 +#define MZ2_TANK_MACHINEGUN_1 4 +#define MZ2_TANK_MACHINEGUN_2 5 +#define MZ2_TANK_MACHINEGUN_3 6 +#define MZ2_TANK_MACHINEGUN_4 7 +#define MZ2_TANK_MACHINEGUN_5 8 +#define MZ2_TANK_MACHINEGUN_6 9 +#define MZ2_TANK_MACHINEGUN_7 10 +#define MZ2_TANK_MACHINEGUN_8 11 +#define MZ2_TANK_MACHINEGUN_9 12 +#define MZ2_TANK_MACHINEGUN_10 13 +#define MZ2_TANK_MACHINEGUN_11 14 +#define MZ2_TANK_MACHINEGUN_12 15 +#define MZ2_TANK_MACHINEGUN_13 16 +#define MZ2_TANK_MACHINEGUN_14 17 +#define MZ2_TANK_MACHINEGUN_15 18 +#define MZ2_TANK_MACHINEGUN_16 19 +#define MZ2_TANK_MACHINEGUN_17 20 +#define MZ2_TANK_MACHINEGUN_18 21 +#define MZ2_TANK_MACHINEGUN_19 22 +#define MZ2_TANK_ROCKET_1 23 +#define MZ2_TANK_ROCKET_2 24 +#define MZ2_TANK_ROCKET_3 25 + +#define MZ2_INFANTRY_MACHINEGUN_1 26 +#define MZ2_INFANTRY_MACHINEGUN_2 27 +#define MZ2_INFANTRY_MACHINEGUN_3 28 +#define MZ2_INFANTRY_MACHINEGUN_4 29 +#define MZ2_INFANTRY_MACHINEGUN_5 30 +#define MZ2_INFANTRY_MACHINEGUN_6 31 +#define MZ2_INFANTRY_MACHINEGUN_7 32 +#define MZ2_INFANTRY_MACHINEGUN_8 33 +#define MZ2_INFANTRY_MACHINEGUN_9 34 +#define MZ2_INFANTRY_MACHINEGUN_10 35 +#define MZ2_INFANTRY_MACHINEGUN_11 36 +#define MZ2_INFANTRY_MACHINEGUN_12 37 +#define MZ2_INFANTRY_MACHINEGUN_13 38 + +#define MZ2_SOLDIER_BLASTER_1 39 +#define MZ2_SOLDIER_BLASTER_2 40 +#define MZ2_SOLDIER_SHOTGUN_1 41 +#define MZ2_SOLDIER_SHOTGUN_2 42 +#define MZ2_SOLDIER_MACHINEGUN_1 43 +#define MZ2_SOLDIER_MACHINEGUN_2 44 + +#define MZ2_GUNNER_MACHINEGUN_1 45 +#define MZ2_GUNNER_MACHINEGUN_2 46 +#define MZ2_GUNNER_MACHINEGUN_3 47 +#define MZ2_GUNNER_MACHINEGUN_4 48 +#define MZ2_GUNNER_MACHINEGUN_5 49 +#define MZ2_GUNNER_MACHINEGUN_6 50 +#define MZ2_GUNNER_MACHINEGUN_7 51 +#define MZ2_GUNNER_MACHINEGUN_8 52 +#define MZ2_GUNNER_GRENADE_1 53 +#define MZ2_GUNNER_GRENADE_2 54 +#define MZ2_GUNNER_GRENADE_3 55 +#define MZ2_GUNNER_GRENADE_4 56 + +#define MZ2_CHICK_ROCKET_1 57 + +#define MZ2_FLYER_BLASTER_1 58 +#define MZ2_FLYER_BLASTER_2 59 + +#define MZ2_MEDIC_BLASTER_1 60 + +#define MZ2_GLADIATOR_RAILGUN_1 61 + +#define MZ2_HOVER_BLASTER_1 62 + +#define MZ2_ACTOR_MACHINEGUN_1 63 + +#define MZ2_SUPERTANK_MACHINEGUN_1 64 +#define MZ2_SUPERTANK_MACHINEGUN_2 65 +#define MZ2_SUPERTANK_MACHINEGUN_3 66 +#define MZ2_SUPERTANK_MACHINEGUN_4 67 +#define MZ2_SUPERTANK_MACHINEGUN_5 68 +#define MZ2_SUPERTANK_MACHINEGUN_6 69 +#define MZ2_SUPERTANK_ROCKET_1 70 +#define MZ2_SUPERTANK_ROCKET_2 71 +#define MZ2_SUPERTANK_ROCKET_3 72 + +#define MZ2_BOSS2_MACHINEGUN_L1 73 +#define MZ2_BOSS2_MACHINEGUN_L2 74 +#define MZ2_BOSS2_MACHINEGUN_L3 75 +#define MZ2_BOSS2_MACHINEGUN_L4 76 +#define MZ2_BOSS2_MACHINEGUN_L5 77 +#define MZ2_BOSS2_ROCKET_1 78 +#define MZ2_BOSS2_ROCKET_2 79 +#define MZ2_BOSS2_ROCKET_3 80 +#define MZ2_BOSS2_ROCKET_4 81 + +#define MZ2_FLOAT_BLASTER_1 82 + +#define MZ2_SOLDIER_BLASTER_3 83 +#define MZ2_SOLDIER_SHOTGUN_3 84 +#define MZ2_SOLDIER_MACHINEGUN_3 85 +#define MZ2_SOLDIER_BLASTER_4 86 +#define MZ2_SOLDIER_SHOTGUN_4 87 +#define MZ2_SOLDIER_MACHINEGUN_4 88 +#define MZ2_SOLDIER_BLASTER_5 89 +#define MZ2_SOLDIER_SHOTGUN_5 90 +#define MZ2_SOLDIER_MACHINEGUN_5 91 +#define MZ2_SOLDIER_BLASTER_6 92 +#define MZ2_SOLDIER_SHOTGUN_6 93 +#define MZ2_SOLDIER_MACHINEGUN_6 94 +#define MZ2_SOLDIER_BLASTER_7 95 +#define MZ2_SOLDIER_SHOTGUN_7 96 +#define MZ2_SOLDIER_MACHINEGUN_7 97 +#define MZ2_SOLDIER_BLASTER_8 98 +#define MZ2_SOLDIER_SHOTGUN_8 99 +#define MZ2_SOLDIER_MACHINEGUN_8 100 + +// --- Xian shit below --- +#define MZ2_MAKRON_BFG 101 +#define MZ2_MAKRON_BLASTER_1 102 +#define MZ2_MAKRON_BLASTER_2 103 +#define MZ2_MAKRON_BLASTER_3 104 +#define MZ2_MAKRON_BLASTER_4 105 +#define MZ2_MAKRON_BLASTER_5 106 +#define MZ2_MAKRON_BLASTER_6 107 +#define MZ2_MAKRON_BLASTER_7 108 +#define MZ2_MAKRON_BLASTER_8 109 +#define MZ2_MAKRON_BLASTER_9 110 +#define MZ2_MAKRON_BLASTER_10 111 +#define MZ2_MAKRON_BLASTER_11 112 +#define MZ2_MAKRON_BLASTER_12 113 +#define MZ2_MAKRON_BLASTER_13 114 +#define MZ2_MAKRON_BLASTER_14 115 +#define MZ2_MAKRON_BLASTER_15 116 +#define MZ2_MAKRON_BLASTER_16 117 +#define MZ2_MAKRON_BLASTER_17 118 +#define MZ2_MAKRON_RAILGUN_1 119 +#define MZ2_JORG_MACHINEGUN_L1 120 +#define MZ2_JORG_MACHINEGUN_L2 121 +#define MZ2_JORG_MACHINEGUN_L3 122 +#define MZ2_JORG_MACHINEGUN_L4 123 +#define MZ2_JORG_MACHINEGUN_L5 124 +#define MZ2_JORG_MACHINEGUN_L6 125 +#define MZ2_JORG_MACHINEGUN_R1 126 +#define MZ2_JORG_MACHINEGUN_R2 127 +#define MZ2_JORG_MACHINEGUN_R3 128 +#define MZ2_JORG_MACHINEGUN_R4 129 +#define MZ2_JORG_MACHINEGUN_R5 130 +#define MZ2_JORG_MACHINEGUN_R6 131 +#define MZ2_JORG_BFG_1 132 +#define MZ2_BOSS2_MACHINEGUN_R1 133 +#define MZ2_BOSS2_MACHINEGUN_R2 134 +#define MZ2_BOSS2_MACHINEGUN_R3 135 +#define MZ2_BOSS2_MACHINEGUN_R4 136 +#define MZ2_BOSS2_MACHINEGUN_R5 137 + +//ROGUE +#define MZ2_CARRIER_MACHINEGUN_L1 138 +#define MZ2_CARRIER_MACHINEGUN_R1 139 +#define MZ2_CARRIER_GRENADE 140 +#define MZ2_TURRET_MACHINEGUN 141 +#define MZ2_TURRET_ROCKET 142 +#define MZ2_TURRET_BLASTER 143 +#define MZ2_STALKER_BLASTER 144 +#define MZ2_DAEDALUS_BLASTER 145 +#define MZ2_MEDIC_BLASTER_2 146 +#define MZ2_CARRIER_RAILGUN 147 +#define MZ2_WIDOW_DISRUPTOR 148 +#define MZ2_WIDOW_BLASTER 149 +#define MZ2_WIDOW_RAIL 150 +#define MZ2_WIDOW_PLASMABEAM 151 // PMM - not used +#define MZ2_CARRIER_MACHINEGUN_L2 152 +#define MZ2_CARRIER_MACHINEGUN_R2 153 +#define MZ2_WIDOW_RAIL_LEFT 154 +#define MZ2_WIDOW_RAIL_RIGHT 155 +#define MZ2_WIDOW_BLASTER_SWEEP1 156 +#define MZ2_WIDOW_BLASTER_SWEEP2 157 +#define MZ2_WIDOW_BLASTER_SWEEP3 158 +#define MZ2_WIDOW_BLASTER_SWEEP4 159 +#define MZ2_WIDOW_BLASTER_SWEEP5 160 +#define MZ2_WIDOW_BLASTER_SWEEP6 161 +#define MZ2_WIDOW_BLASTER_SWEEP7 162 +#define MZ2_WIDOW_BLASTER_SWEEP8 163 +#define MZ2_WIDOW_BLASTER_SWEEP9 164 +#define MZ2_WIDOW_BLASTER_100 165 +#define MZ2_WIDOW_BLASTER_90 166 +#define MZ2_WIDOW_BLASTER_80 167 +#define MZ2_WIDOW_BLASTER_70 168 +#define MZ2_WIDOW_BLASTER_60 169 +#define MZ2_WIDOW_BLASTER_50 170 +#define MZ2_WIDOW_BLASTER_40 171 +#define MZ2_WIDOW_BLASTER_30 172 +#define MZ2_WIDOW_BLASTER_20 173 +#define MZ2_WIDOW_BLASTER_10 174 +#define MZ2_WIDOW_BLASTER_0 175 +#define MZ2_WIDOW_BLASTER_10L 176 +#define MZ2_WIDOW_BLASTER_20L 177 +#define MZ2_WIDOW_BLASTER_30L 178 +#define MZ2_WIDOW_BLASTER_40L 179 +#define MZ2_WIDOW_BLASTER_50L 180 +#define MZ2_WIDOW_BLASTER_60L 181 +#define MZ2_WIDOW_BLASTER_70L 182 +#define MZ2_WIDOW_RUN_1 183 +#define MZ2_WIDOW_RUN_2 184 +#define MZ2_WIDOW_RUN_3 185 +#define MZ2_WIDOW_RUN_4 186 +#define MZ2_WIDOW_RUN_5 187 +#define MZ2_WIDOW_RUN_6 188 +#define MZ2_WIDOW_RUN_7 189 +#define MZ2_WIDOW_RUN_8 190 +#define MZ2_CARRIER_ROCKET_1 191 +#define MZ2_CARRIER_ROCKET_2 192 +#define MZ2_CARRIER_ROCKET_3 193 +#define MZ2_CARRIER_ROCKET_4 194 +#define MZ2_WIDOW2_BEAMER_1 195 +#define MZ2_WIDOW2_BEAMER_2 196 +#define MZ2_WIDOW2_BEAMER_3 197 +#define MZ2_WIDOW2_BEAMER_4 198 +#define MZ2_WIDOW2_BEAMER_5 199 +#define MZ2_WIDOW2_BEAM_SWEEP_1 200 +#define MZ2_WIDOW2_BEAM_SWEEP_2 201 +#define MZ2_WIDOW2_BEAM_SWEEP_3 202 +#define MZ2_WIDOW2_BEAM_SWEEP_4 203 +#define MZ2_WIDOW2_BEAM_SWEEP_5 204 +#define MZ2_WIDOW2_BEAM_SWEEP_6 205 +#define MZ2_WIDOW2_BEAM_SWEEP_7 206 +#define MZ2_WIDOW2_BEAM_SWEEP_8 207 +#define MZ2_WIDOW2_BEAM_SWEEP_9 208 +#define MZ2_WIDOW2_BEAM_SWEEP_10 209 +#define MZ2_WIDOW2_BEAM_SWEEP_11 210 + +// ROGUE + +extern vec3_t monster_flash_offset []; + + +// temp entity events +// +// Temp entity events are for things that happen +// at a location seperate from any existing entity. +// Temporary entity messages are explicitly constructed +// and broadcast. +typedef enum +{ + TE_GUNSHOT, + TE_BLOOD, + TE_BLASTER, + TE_RAILTRAIL, + TE_SHOTGUN, + TE_EXPLOSION1, + TE_EXPLOSION2, + TE_ROCKET_EXPLOSION, + TE_GRENADE_EXPLOSION, + TE_SPARKS, + TE_SPLASH, + TE_BUBBLETRAIL, + TE_SCREEN_SPARKS, + TE_SHIELD_SPARKS, + TE_BULLET_SPARKS, + TE_LASER_SPARKS, + TE_PARASITE_ATTACK, + TE_ROCKET_EXPLOSION_WATER, + TE_GRENADE_EXPLOSION_WATER, + TE_MEDIC_CABLE_ATTACK, + TE_BFG_EXPLOSION, + TE_BFG_BIGEXPLOSION, + TE_BOSSTPORT, // used as '22' in a map, so DON'T RENUMBER!!! + TE_BFG_LASER, + TE_GRAPPLE_CABLE, + TE_WELDING_SPARKS, + TE_GREENBLOOD, + TE_BLUEHYPERBLASTER, + TE_PLASMA_EXPLOSION, + TE_TUNNEL_SPARKS, +//ROGUE + TE_BLASTER2, + TE_RAILTRAIL2, + TE_FLAME, + TE_LIGHTNING, + TE_DEBUGTRAIL, + TE_PLAIN_EXPLOSION, + TE_FLASHLIGHT, + TE_FORCEWALL, + TE_HEATBEAM, + TE_MONSTER_HEATBEAM, + TE_STEAM, + TE_BUBBLETRAIL2, + TE_MOREBLOOD, + TE_HEATBEAM_SPARKS, + TE_HEATBEAM_STEAM, + TE_CHAINFIST_SMOKE, + TE_ELECTRIC_SPARKS, + TE_TRACKER_EXPLOSION, + TE_TELEPORT_EFFECT, + TE_DBALL_GOAL, + TE_WIDOWBEAMOUT, + TE_NUKEBLAST, + TE_WIDOWSPLASH, + TE_EXPLOSION1_BIG, + TE_EXPLOSION1_NP, + TE_FLECHETTE +//ROGUE +} temp_event_t; + +#define SPLASH_UNKNOWN 0 +#define SPLASH_SPARKS 1 +#define SPLASH_BLUE_WATER 2 +#define SPLASH_BROWN_WATER 3 +#define SPLASH_SLIME 4 +#define SPLASH_LAVA 5 +#define SPLASH_BLOOD 6 + + +// sound channels +// channel 0 never willingly overrides +// other channels (1-7) allways override a playing sound on that channel +#define CHAN_AUTO 0 +#define CHAN_WEAPON 1 +#define CHAN_VOICE 2 +#define CHAN_ITEM 3 +#define CHAN_BODY 4 +// modifier flags +#define CHAN_NO_PHS_ADD 8 // send to all clients, not just ones in PHS (ATTN 0 will also do this) +#define CHAN_RELIABLE 16 // send by reliable message, not datagram + + +// sound attenuation values +#define ATTN_NONE 0 // full volume the entire level +#define ATTN_NORM 1 +#define ATTN_IDLE 2 +#define ATTN_STATIC 3 // diminish very rapidly with distance + + +// player_state->stats[] indexes +#define STAT_HEALTH_ICON 0 +#define STAT_HEALTH 1 +#define STAT_AMMO_ICON 2 +#define STAT_AMMO 3 +#define STAT_ARMOR_ICON 4 +#define STAT_ARMOR 5 +#define STAT_SELECTED_ICON 6 +#define STAT_PICKUP_ICON 7 +#define STAT_PICKUP_STRING 8 +#define STAT_TIMER_ICON 9 +#define STAT_TIMER 10 +#define STAT_HELPICON 11 +#define STAT_SELECTED_ITEM 12 +#define STAT_LAYOUTS 13 +#define STAT_FRAGS 14 +#define STAT_FLASHES 15 // cleared each frame, 1 = health, 2 = armor +#define STAT_CHASE 16 +#define STAT_SPECTATOR 17 + +#define MAX_STATS 32 + + +// dmflags->value flags +#define DF_NO_HEALTH 0x00000001 // 1 +#define DF_NO_ITEMS 0x00000002 // 2 +#define DF_WEAPONS_STAY 0x00000004 // 4 +#define DF_NO_FALLING 0x00000008 // 8 +#define DF_INSTANT_ITEMS 0x00000010 // 16 +#define DF_SAME_LEVEL 0x00000020 // 32 +#define DF_SKINTEAMS 0x00000040 // 64 +#define DF_MODELTEAMS 0x00000080 // 128 +#define DF_NO_FRIENDLY_FIRE 0x00000100 // 256 +#define DF_SPAWN_FARTHEST 0x00000200 // 512 +#define DF_FORCE_RESPAWN 0x00000400 // 1024 +#define DF_NO_ARMOR 0x00000800 // 2048 +#define DF_ALLOW_EXIT 0x00001000 // 4096 +#define DF_INFINITE_AMMO 0x00002000 // 8192 +#define DF_QUAD_DROP 0x00004000 // 16384 +#define DF_FIXED_FOV 0x00008000 // 32768 + +// RAFAEL +#define DF_QUADFIRE_DROP 0x00010000 // 65536 + +//ROGUE +#define DF_NO_MINES 0x00020000 +#define DF_NO_STACK_DOUBLE 0x00040000 +#define DF_NO_NUKES 0x00080000 +#define DF_NO_SPHERES 0x00100000 +//ROGUE + +/* +ROGUE - VERSIONS +1234 08/13/1998 Activision +1235 08/14/1998 Id Software +1236 08/15/1998 Steve Tietze +1237 08/15/1998 Phil Dobranski +1238 08/15/1998 John Sheley +1239 08/17/1998 Barrett Alexander +1230 08/17/1998 Brandon Fish +1245 08/17/1998 Don MacAskill +1246 08/17/1998 David "Zoid" Kirsch +1247 08/17/1998 Manu Smith +1248 08/17/1998 Geoff Scully +1249 08/17/1998 Andy Van Fossen +1240 08/20/1998 Activision Build 2 +1256 08/20/1998 Ranger Clan +1257 08/20/1998 Ensemble Studios +1258 08/21/1998 Robert Duffy +1259 08/21/1998 Stephen Seachord +1250 08/21/1998 Stephen Heaslip +1267 08/21/1998 Samir Sandesara +1268 08/21/1998 Oliver Wyman +1269 08/21/1998 Steven Marchegiano +1260 08/21/1998 Build #2 for Nihilistic +1278 08/21/1998 Build #2 for Ensemble + +9999 08/20/1998 Internal Use +*/ +#define ROGUE_VERSION_ID 1278 + +#define ROGUE_VERSION_STRING "08/21/1998 Beta 2 for Ensemble" + +// ROGUE +/* +========================================================== + + ELEMENTS COMMUNICATED ACROSS THE NET + +========================================================== +*/ + +#define ANGLE2SHORT(x) ((int)((x)*65536/360) & 65535) +#define SHORT2ANGLE(x) ((x)*(360.0/65536)) + + +// +// config strings are a general means of communication from +// the server to all connected clients. +// Each config string can be at most MAX_QPATH characters. +// +#define CS_NAME 0 +#define CS_CDTRACK 1 +#define CS_SKY 2 +#define CS_SKYAXIS 3 // %f %f %f format +#define CS_SKYROTATE 4 +#define CS_STATUSBAR 5 // display program string + +#define CS_AIRACCEL 29 // air acceleration control +#define CS_MAXCLIENTS 30 +#define CS_MAPCHECKSUM 31 // for catching cheater maps + +#define CS_MODELS 32 +#define CS_SOUNDS (CS_MODELS+MAX_MODELS) +#define CS_IMAGES (CS_SOUNDS+MAX_SOUNDS) +#define CS_LIGHTS (CS_IMAGES+MAX_IMAGES) +#define CS_ITEMS (CS_LIGHTS+MAX_LIGHTSTYLES) +#define CS_PLAYERSKINS (CS_ITEMS+MAX_ITEMS) +#define CS_GENERAL (CS_PLAYERSKINS+MAX_CLIENTS) +#define MAX_CONFIGSTRINGS (CS_GENERAL+MAX_GENERAL) + + +//============================================== + + +// entity_state_t->event values +// ertity events are for effects that take place reletive +// to an existing entities origin. Very network efficient. +// All muzzle flashes really should be converted to events... +typedef enum +{ + EV_NONE, + EV_ITEM_RESPAWN, + EV_FOOTSTEP, + EV_FALLSHORT, + EV_FALL, + EV_FALLFAR, + EV_PLAYER_TELEPORT, + EV_OTHER_TELEPORT +} entity_event_t; + + +// entity_state_t is the information conveyed from the server +// in an update message about entities that the client will +// need to render in some way +typedef struct entity_state_s +{ + int number; // edict index + + vec3_t origin; + vec3_t angles; + vec3_t old_origin; // for lerping + int modelindex; + int modelindex2, modelindex3, modelindex4; // weapons, CTF flags, etc + int frame; + int skinnum; + unsigned int effects; // PGM - we're filling it, so it needs to be unsigned + int renderfx; + int solid; // for client side prediction, 8*(bits 0-4) is x/y radius + // 8*(bits 5-9) is z down distance, 8(bits10-15) is z up + // gi.linkentity sets this properly + int sound; // for looping sounds, to guarantee shutoff + int event; // impulse events -- muzzle flashes, footsteps, etc + // events only go out for a single frame, they + // are automatically cleared each frame +} entity_state_t; + +//============================================== + + +// player_state_t is the information needed in addition to pmove_state_t +// to rendered a view. There will only be 10 player_state_t sent each second, +// but the number of pmove_state_t changes will be reletive to client +// frame rates +typedef struct +{ + pmove_state_t pmove; // for prediction + + // these fields do not need to be communicated bit-precise + + vec3_t viewangles; // for fixed views + vec3_t viewoffset; // add to pmovestate->origin + vec3_t kick_angles; // add to view direction to get render angles + // set by weapon kicks, pain effects, etc + + vec3_t gunangles; + vec3_t gunoffset; + int gunindex; + int gunframe; + + float blend[4]; // rgba full screen effect + + float fov; // horizontal field of view + + int rdflags; // refdef flags + + short stats[MAX_STATS]; // fast status bar updates +} player_state_t; + + +// ================== +// PGM +#define VIDREF_GL 1 +#define VIDREF_SOFT 2 +#define VIDREF_OTHER 3 + +extern int vidref_val; +// PGM +// ================== diff --git a/original/xatrix/xatrix.def b/original/xatrix/xatrix.def new file mode 100644 index 0000000..4461243 --- /dev/null +++ b/original/xatrix/xatrix.def @@ -0,0 +1,2 @@ +EXPORTS + GetGameAPI diff --git a/original/xatrix/xatrix.dsp b/original/xatrix/xatrix.dsp new file mode 100644 index 0000000..02b5caf --- /dev/null +++ b/original/xatrix/xatrix.dsp @@ -0,0 +1,1751 @@ +# Microsoft Developer Studio Project File - Name="xatrix" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 5.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102 +# TARGTYPE "Win32 (ALPHA) Dynamic-Link Library" 0x0602 + +CFG=xatrix - Win32 Debug Alpha +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "xatrix.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "xatrix.mak" CFG="xatrix - Win32 Debug Alpha" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "xatrix - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "xatrix - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "xatrix - Win32 Debug Alpha" (based on\ + "Win32 (ALPHA) Dynamic-Link Library") +!MESSAGE "xatrix - Win32 Release Alpha" (based on\ + "Win32 (ALPHA) Dynamic-Link Library") +!MESSAGE + +# Begin Project +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +# PROP WCE_Configuration "H/PC Ver. 2.00" + +!IF "$(CFG)" == "xatrix - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir ".\release" +# PROP Intermediate_Dir ".\release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +CPP=cl.exe +# ADD BASE CPP /nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /YX /FD /c +# ADD CPP /nologo /MT /W1 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /YX /FD /c +MTL=midl.exe +# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /o NUL /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /o NUL /win32 +RSC=rc.exe +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /dll /machine:I386 +# ADD LINK32 kernel32.lib user32.lib winmm.lib /nologo /subsystem:windows /dll /machine:I386 /out:".\release\gamex86.dll" + +!ELSEIF "$(CFG)" == "xatrix - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir ".\debug" +# PROP Intermediate_Dir ".\debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +CPP=cl.exe +# ADD BASE CPP /nologo /MTd /W3 /Gm /GX /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /YX /FD /c +# ADD CPP /nologo /MTd /W3 /Gm /GX /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /YX /FD /c +MTL=midl.exe +# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /o NUL /win32 +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /o NUL /win32 +RSC=rc.exe +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /dll /debug /machine:I386 /pdbtype:sept +# ADD LINK32 kernel32.lib user32.lib winmm.lib /nologo /subsystem:windows /dll /incremental:no /map /debug /machine:I386 /out:".\debug\gamex86.dll" /pdbtype:sept + +!ELSEIF "$(CFG)" == "xatrix - Win32 Debug Alpha" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "xatrix__" +# PROP BASE Intermediate_Dir "xatrix__" +# PROP BASE Ignore_Export_Lib 0 +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "..\DebugAXP" +# PROP Intermediate_Dir ".\DebugAXP" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +MTL=midl.exe +# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /o NUL /win32 +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /o NUL /win32 +CPP=cl.exe +# ADD BASE CPP /nologo /Gt0 /W3 /GX /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /YX /FD /MTd /c +# ADD CPP /nologo /Gt0 /W3 /GX /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "C_ONLY" /YX /FD /MTd /c +RSC=rc.exe +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 winmm.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib /nologo /subsystem:windows /dll /map /debug /machine:ALPHA /out:".\debug\gamex86.dll" /pdbtype:sept +# ADD LINK32 winmm.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib /nologo /subsystem:windows /dll /map /debug /machine:ALPHA /out:".\debugAXP\gameaxp.dll" /pdbtype:sept + +!ELSEIF "$(CFG)" == "xatrix - Win32 Release Alpha" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "xatrix_0" +# PROP BASE Intermediate_Dir "xatrix_0" +# PROP BASE Ignore_Export_Lib 0 +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "..\ReleaseAXP" +# PROP Intermediate_Dir ".\ReleaseAXP" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +MTL=midl.exe +# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /o NUL /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /o NUL /win32 +CPP=cl.exe +# ADD BASE CPP /nologo /MT /Gt0 /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /YX /FD /c +# ADD CPP /nologo /MT /Gt0 /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "C_ONLY" /YX /FD /c +RSC=rc.exe +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 winmm.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib /nologo /subsystem:windows /dll /machine:ALPHA /out:".\release\gamex86.dll" +# ADD LINK32 winmm.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib /nologo /subsystem:windows /dll /machine:ALPHA /out:".\ReleaseAXP\gameaxp.dll" + +!ENDIF + +# Begin Target + +# Name "xatrix - Win32 Release" +# Name "xatrix - Win32 Debug" +# Name "xatrix - Win32 Debug Alpha" +# Name "xatrix - Win32 Release Alpha" +# Begin Group "Source Files" + +# PROP Default_Filter "*.c" +# Begin Source File + +SOURCE=.\g_ai.c + +!IF "$(CFG)" == "xatrix - Win32 Release" + +!ELSEIF "$(CFG)" == "xatrix - Win32 Debug" + +!ELSEIF "$(CFG)" == "xatrix - Win32 Debug Alpha" + +DEP_CPP_G_AI_=\ + ".\g_local.h"\ + ".\game.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "xatrix - Win32 Release Alpha" + +DEP_CPP_G_AI_=\ + ".\g_local.h"\ + ".\game.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\g_chase.c + +!IF "$(CFG)" == "xatrix - Win32 Release" + +!ELSEIF "$(CFG)" == "xatrix - Win32 Debug" + +!ELSEIF "$(CFG)" == "xatrix - Win32 Debug Alpha" + +DEP_CPP_G_CHA=\ + ".\g_local.h"\ + ".\game.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "xatrix - Win32 Release Alpha" + +DEP_CPP_G_CHA=\ + ".\g_local.h"\ + ".\game.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\g_cmds.c + +!IF "$(CFG)" == "xatrix - Win32 Release" + +!ELSEIF "$(CFG)" == "xatrix - Win32 Debug" + +!ELSEIF "$(CFG)" == "xatrix - Win32 Debug Alpha" + +DEP_CPP_G_CMD=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_player.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "xatrix - Win32 Release Alpha" + +DEP_CPP_G_CMD=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_player.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\g_combat.c + +!IF "$(CFG)" == "xatrix - Win32 Release" + +!ELSEIF "$(CFG)" == "xatrix - Win32 Debug" + +!ELSEIF "$(CFG)" == "xatrix - Win32 Debug Alpha" + +DEP_CPP_G_COM=\ + ".\g_local.h"\ + ".\game.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "xatrix - Win32 Release Alpha" + +DEP_CPP_G_COM=\ + ".\g_local.h"\ + ".\game.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\g_func.c + +!IF "$(CFG)" == "xatrix - Win32 Release" + +!ELSEIF "$(CFG)" == "xatrix - Win32 Debug" + +!ELSEIF "$(CFG)" == "xatrix - Win32 Debug Alpha" + +DEP_CPP_G_FUN=\ + ".\g_local.h"\ + ".\game.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "xatrix - Win32 Release Alpha" + +DEP_CPP_G_FUN=\ + ".\g_local.h"\ + ".\game.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\g_items.c + +!IF "$(CFG)" == "xatrix - Win32 Release" + +!ELSEIF "$(CFG)" == "xatrix - Win32 Debug" + +!ELSEIF "$(CFG)" == "xatrix - Win32 Debug Alpha" + +DEP_CPP_G_ITE=\ + ".\g_local.h"\ + ".\game.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "xatrix - Win32 Release Alpha" + +DEP_CPP_G_ITE=\ + ".\g_local.h"\ + ".\game.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\g_main.c + +!IF "$(CFG)" == "xatrix - Win32 Release" + +!ELSEIF "$(CFG)" == "xatrix - Win32 Debug" + +!ELSEIF "$(CFG)" == "xatrix - Win32 Debug Alpha" + +DEP_CPP_G_MAI=\ + ".\g_local.h"\ + ".\game.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "xatrix - Win32 Release Alpha" + +DEP_CPP_G_MAI=\ + ".\g_local.h"\ + ".\game.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\g_misc.c + +!IF "$(CFG)" == "xatrix - Win32 Release" + +!ELSEIF "$(CFG)" == "xatrix - Win32 Debug" + +!ELSEIF "$(CFG)" == "xatrix - Win32 Debug Alpha" + +DEP_CPP_G_MIS=\ + ".\g_local.h"\ + ".\game.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "xatrix - Win32 Release Alpha" + +DEP_CPP_G_MIS=\ + ".\g_local.h"\ + ".\game.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\g_monster.c + +!IF "$(CFG)" == "xatrix - Win32 Release" + +!ELSEIF "$(CFG)" == "xatrix - Win32 Debug" + +!ELSEIF "$(CFG)" == "xatrix - Win32 Debug Alpha" + +DEP_CPP_G_MON=\ + ".\g_local.h"\ + ".\game.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "xatrix - Win32 Release Alpha" + +DEP_CPP_G_MON=\ + ".\g_local.h"\ + ".\game.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\g_phys.c + +!IF "$(CFG)" == "xatrix - Win32 Release" + +!ELSEIF "$(CFG)" == "xatrix - Win32 Debug" + +!ELSEIF "$(CFG)" == "xatrix - Win32 Debug Alpha" + +DEP_CPP_G_PHY=\ + ".\g_local.h"\ + ".\game.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "xatrix - Win32 Release Alpha" + +DEP_CPP_G_PHY=\ + ".\g_local.h"\ + ".\game.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\g_save.c + +!IF "$(CFG)" == "xatrix - Win32 Release" + +!ELSEIF "$(CFG)" == "xatrix - Win32 Debug" + +!ELSEIF "$(CFG)" == "xatrix - Win32 Debug Alpha" + +DEP_CPP_G_SAV=\ + ".\g_local.h"\ + ".\game.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "xatrix - Win32 Release Alpha" + +DEP_CPP_G_SAV=\ + ".\g_local.h"\ + ".\game.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\g_spawn.c + +!IF "$(CFG)" == "xatrix - Win32 Release" + +!ELSEIF "$(CFG)" == "xatrix - Win32 Debug" + +!ELSEIF "$(CFG)" == "xatrix - Win32 Debug Alpha" + +DEP_CPP_G_SPA=\ + ".\g_local.h"\ + ".\game.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "xatrix - Win32 Release Alpha" + +DEP_CPP_G_SPA=\ + ".\g_local.h"\ + ".\game.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\g_svcmds.c + +!IF "$(CFG)" == "xatrix - Win32 Release" + +!ELSEIF "$(CFG)" == "xatrix - Win32 Debug" + +!ELSEIF "$(CFG)" == "xatrix - Win32 Debug Alpha" + +DEP_CPP_G_SVC=\ + ".\g_local.h"\ + ".\game.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "xatrix - Win32 Release Alpha" + +DEP_CPP_G_SVC=\ + ".\g_local.h"\ + ".\game.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\g_target.c + +!IF "$(CFG)" == "xatrix - Win32 Release" + +!ELSEIF "$(CFG)" == "xatrix - Win32 Debug" + +!ELSEIF "$(CFG)" == "xatrix - Win32 Debug Alpha" + +DEP_CPP_G_TAR=\ + ".\g_local.h"\ + ".\game.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "xatrix - Win32 Release Alpha" + +DEP_CPP_G_TAR=\ + ".\g_local.h"\ + ".\game.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\g_trigger.c + +!IF "$(CFG)" == "xatrix - Win32 Release" + +!ELSEIF "$(CFG)" == "xatrix - Win32 Debug" + +!ELSEIF "$(CFG)" == "xatrix - Win32 Debug Alpha" + +DEP_CPP_G_TRI=\ + ".\g_local.h"\ + ".\game.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "xatrix - Win32 Release Alpha" + +DEP_CPP_G_TRI=\ + ".\g_local.h"\ + ".\game.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\g_turret.c + +!IF "$(CFG)" == "xatrix - Win32 Release" + +!ELSEIF "$(CFG)" == "xatrix - Win32 Debug" + +!ELSEIF "$(CFG)" == "xatrix - Win32 Debug Alpha" + +DEP_CPP_G_TUR=\ + ".\g_local.h"\ + ".\game.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "xatrix - Win32 Release Alpha" + +DEP_CPP_G_TUR=\ + ".\g_local.h"\ + ".\game.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\g_utils.c + +!IF "$(CFG)" == "xatrix - Win32 Release" + +!ELSEIF "$(CFG)" == "xatrix - Win32 Debug" + +!ELSEIF "$(CFG)" == "xatrix - Win32 Debug Alpha" + +DEP_CPP_G_UTI=\ + ".\g_local.h"\ + ".\game.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "xatrix - Win32 Release Alpha" + +DEP_CPP_G_UTI=\ + ".\g_local.h"\ + ".\game.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\g_weapon.c + +!IF "$(CFG)" == "xatrix - Win32 Release" + +!ELSEIF "$(CFG)" == "xatrix - Win32 Debug" + +!ELSEIF "$(CFG)" == "xatrix - Win32 Debug Alpha" + +DEP_CPP_G_WEA=\ + ".\g_local.h"\ + ".\game.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "xatrix - Win32 Release Alpha" + +DEP_CPP_G_WEA=\ + ".\g_local.h"\ + ".\game.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\m_actor.c + +!IF "$(CFG)" == "xatrix - Win32 Release" + +!ELSEIF "$(CFG)" == "xatrix - Win32 Debug" + +!ELSEIF "$(CFG)" == "xatrix - Win32 Debug Alpha" + +DEP_CPP_M_ACT=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_actor.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "xatrix - Win32 Release Alpha" + +DEP_CPP_M_ACT=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_actor.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\m_berserk.c + +!IF "$(CFG)" == "xatrix - Win32 Release" + +!ELSEIF "$(CFG)" == "xatrix - Win32 Debug" + +!ELSEIF "$(CFG)" == "xatrix - Win32 Debug Alpha" + +DEP_CPP_M_BER=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_berserk.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "xatrix - Win32 Release Alpha" + +DEP_CPP_M_BER=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_berserk.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\m_boss2.c + +!IF "$(CFG)" == "xatrix - Win32 Release" + +!ELSEIF "$(CFG)" == "xatrix - Win32 Debug" + +!ELSEIF "$(CFG)" == "xatrix - Win32 Debug Alpha" + +DEP_CPP_M_BOS=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_boss2.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "xatrix - Win32 Release Alpha" + +DEP_CPP_M_BOS=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_boss2.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\m_boss3.c + +!IF "$(CFG)" == "xatrix - Win32 Release" + +!ELSEIF "$(CFG)" == "xatrix - Win32 Debug" + +!ELSEIF "$(CFG)" == "xatrix - Win32 Debug Alpha" + +DEP_CPP_M_BOSS=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_boss32.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "xatrix - Win32 Release Alpha" + +DEP_CPP_M_BOSS=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_boss32.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\m_boss31.c + +!IF "$(CFG)" == "xatrix - Win32 Release" + +!ELSEIF "$(CFG)" == "xatrix - Win32 Debug" + +!ELSEIF "$(CFG)" == "xatrix - Win32 Debug Alpha" + +DEP_CPP_M_BOSS3=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_boss31.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "xatrix - Win32 Release Alpha" + +DEP_CPP_M_BOSS3=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_boss31.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\m_boss32.c + +!IF "$(CFG)" == "xatrix - Win32 Release" + +!ELSEIF "$(CFG)" == "xatrix - Win32 Debug" + +!ELSEIF "$(CFG)" == "xatrix - Win32 Debug Alpha" + +DEP_CPP_M_BOSS32=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_boss32.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "xatrix - Win32 Release Alpha" + +DEP_CPP_M_BOSS32=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_boss32.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\m_boss5.c + +!IF "$(CFG)" == "xatrix - Win32 Release" + +!ELSEIF "$(CFG)" == "xatrix - Win32 Debug" + +!ELSEIF "$(CFG)" == "xatrix - Win32 Debug Alpha" + +DEP_CPP_M_BOSS5=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_supertank.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "xatrix - Win32 Release Alpha" + +DEP_CPP_M_BOSS5=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_supertank.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\m_brain.c + +!IF "$(CFG)" == "xatrix - Win32 Release" + +!ELSEIF "$(CFG)" == "xatrix - Win32 Debug" + +!ELSEIF "$(CFG)" == "xatrix - Win32 Debug Alpha" + +DEP_CPP_M_BRA=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_brain.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "xatrix - Win32 Release Alpha" + +DEP_CPP_M_BRA=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_brain.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\m_chick.c + +!IF "$(CFG)" == "xatrix - Win32 Release" + +!ELSEIF "$(CFG)" == "xatrix - Win32 Debug" + +!ELSEIF "$(CFG)" == "xatrix - Win32 Debug Alpha" + +DEP_CPP_M_CHI=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_chick.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "xatrix - Win32 Release Alpha" + +DEP_CPP_M_CHI=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_chick.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\m_fixbot.c + +!IF "$(CFG)" == "xatrix - Win32 Release" + +!ELSEIF "$(CFG)" == "xatrix - Win32 Debug" + +!ELSEIF "$(CFG)" == "xatrix - Win32 Debug Alpha" + +DEP_CPP_M_FIX=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_fixbot.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "xatrix - Win32 Release Alpha" + +DEP_CPP_M_FIX=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_fixbot.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\m_flash.c + +!IF "$(CFG)" == "xatrix - Win32 Release" + +!ELSEIF "$(CFG)" == "xatrix - Win32 Debug" + +!ELSEIF "$(CFG)" == "xatrix - Win32 Debug Alpha" + +DEP_CPP_M_FLA=\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "xatrix - Win32 Release Alpha" + +DEP_CPP_M_FLA=\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\m_flipper.c + +!IF "$(CFG)" == "xatrix - Win32 Release" + +!ELSEIF "$(CFG)" == "xatrix - Win32 Debug" + +!ELSEIF "$(CFG)" == "xatrix - Win32 Debug Alpha" + +DEP_CPP_M_FLI=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_flipper.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "xatrix - Win32 Release Alpha" + +DEP_CPP_M_FLI=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_flipper.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\m_float.c + +!IF "$(CFG)" == "xatrix - Win32 Release" + +!ELSEIF "$(CFG)" == "xatrix - Win32 Debug" + +!ELSEIF "$(CFG)" == "xatrix - Win32 Debug Alpha" + +DEP_CPP_M_FLO=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_float.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "xatrix - Win32 Release Alpha" + +DEP_CPP_M_FLO=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_float.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\m_flyer.c + +!IF "$(CFG)" == "xatrix - Win32 Release" + +!ELSEIF "$(CFG)" == "xatrix - Win32 Debug" + +!ELSEIF "$(CFG)" == "xatrix - Win32 Debug Alpha" + +DEP_CPP_M_FLY=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_flyer.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "xatrix - Win32 Release Alpha" + +DEP_CPP_M_FLY=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_flyer.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\m_gekk.c + +!IF "$(CFG)" == "xatrix - Win32 Release" + +!ELSEIF "$(CFG)" == "xatrix - Win32 Debug" + +!ELSEIF "$(CFG)" == "xatrix - Win32 Debug Alpha" + +DEP_CPP_M_GEK=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_gekk.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "xatrix - Win32 Release Alpha" + +DEP_CPP_M_GEK=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_gekk.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\m_gladb.c + +!IF "$(CFG)" == "xatrix - Win32 Release" + +!ELSEIF "$(CFG)" == "xatrix - Win32 Debug" + +!ELSEIF "$(CFG)" == "xatrix - Win32 Debug Alpha" + +DEP_CPP_M_GLA=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_gladiator.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "xatrix - Win32 Release Alpha" + +DEP_CPP_M_GLA=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_gladiator.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\m_gladiator.c + +!IF "$(CFG)" == "xatrix - Win32 Release" + +!ELSEIF "$(CFG)" == "xatrix - Win32 Debug" + +!ELSEIF "$(CFG)" == "xatrix - Win32 Debug Alpha" + +DEP_CPP_M_GLAD=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_gladiator.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "xatrix - Win32 Release Alpha" + +DEP_CPP_M_GLAD=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_gladiator.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\m_gunner.c + +!IF "$(CFG)" == "xatrix - Win32 Release" + +!ELSEIF "$(CFG)" == "xatrix - Win32 Debug" + +!ELSEIF "$(CFG)" == "xatrix - Win32 Debug Alpha" + +DEP_CPP_M_GUN=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_gunner.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "xatrix - Win32 Release Alpha" + +DEP_CPP_M_GUN=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_gunner.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\m_hover.c + +!IF "$(CFG)" == "xatrix - Win32 Release" + +!ELSEIF "$(CFG)" == "xatrix - Win32 Debug" + +!ELSEIF "$(CFG)" == "xatrix - Win32 Debug Alpha" + +DEP_CPP_M_HOV=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_hover.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "xatrix - Win32 Release Alpha" + +DEP_CPP_M_HOV=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_hover.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\m_infantry.c + +!IF "$(CFG)" == "xatrix - Win32 Release" + +!ELSEIF "$(CFG)" == "xatrix - Win32 Debug" + +!ELSEIF "$(CFG)" == "xatrix - Win32 Debug Alpha" + +DEP_CPP_M_INF=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_infantry.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "xatrix - Win32 Release Alpha" + +DEP_CPP_M_INF=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_infantry.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\m_insane.c + +!IF "$(CFG)" == "xatrix - Win32 Release" + +!ELSEIF "$(CFG)" == "xatrix - Win32 Debug" + +!ELSEIF "$(CFG)" == "xatrix - Win32 Debug Alpha" + +DEP_CPP_M_INS=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_insane.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "xatrix - Win32 Release Alpha" + +DEP_CPP_M_INS=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_insane.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\m_medic.c + +!IF "$(CFG)" == "xatrix - Win32 Release" + +!ELSEIF "$(CFG)" == "xatrix - Win32 Debug" + +!ELSEIF "$(CFG)" == "xatrix - Win32 Debug Alpha" + +DEP_CPP_M_MED=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_medic.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "xatrix - Win32 Release Alpha" + +DEP_CPP_M_MED=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_medic.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\m_move.c + +!IF "$(CFG)" == "xatrix - Win32 Release" + +!ELSEIF "$(CFG)" == "xatrix - Win32 Debug" + +!ELSEIF "$(CFG)" == "xatrix - Win32 Debug Alpha" + +DEP_CPP_M_MOV=\ + ".\g_local.h"\ + ".\game.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "xatrix - Win32 Release Alpha" + +DEP_CPP_M_MOV=\ + ".\g_local.h"\ + ".\game.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\m_mutant.c + +!IF "$(CFG)" == "xatrix - Win32 Release" + +!ELSEIF "$(CFG)" == "xatrix - Win32 Debug" + +!ELSEIF "$(CFG)" == "xatrix - Win32 Debug Alpha" + +DEP_CPP_M_MUT=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_mutant.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "xatrix - Win32 Release Alpha" + +DEP_CPP_M_MUT=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_mutant.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\m_parasite.c + +!IF "$(CFG)" == "xatrix - Win32 Release" + +!ELSEIF "$(CFG)" == "xatrix - Win32 Debug" + +!ELSEIF "$(CFG)" == "xatrix - Win32 Debug Alpha" + +DEP_CPP_M_PAR=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_parasite.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "xatrix - Win32 Release Alpha" + +DEP_CPP_M_PAR=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_parasite.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\m_soldier.c + +!IF "$(CFG)" == "xatrix - Win32 Release" + +!ELSEIF "$(CFG)" == "xatrix - Win32 Debug" + +!ELSEIF "$(CFG)" == "xatrix - Win32 Debug Alpha" + +DEP_CPP_M_SOL=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_soldier.h"\ + ".\m_soldierh.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "xatrix - Win32 Release Alpha" + +DEP_CPP_M_SOL=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_soldier.h"\ + ".\m_soldierh.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\m_supertank.c + +!IF "$(CFG)" == "xatrix - Win32 Release" + +!ELSEIF "$(CFG)" == "xatrix - Win32 Debug" + +!ELSEIF "$(CFG)" == "xatrix - Win32 Debug Alpha" + +DEP_CPP_M_SUP=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_supertank.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "xatrix - Win32 Release Alpha" + +DEP_CPP_M_SUP=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_supertank.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\m_tank.c + +!IF "$(CFG)" == "xatrix - Win32 Release" + +!ELSEIF "$(CFG)" == "xatrix - Win32 Debug" + +!ELSEIF "$(CFG)" == "xatrix - Win32 Debug Alpha" + +DEP_CPP_M_TAN=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_tank.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "xatrix - Win32 Release Alpha" + +DEP_CPP_M_TAN=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_tank.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\p_client.c + +!IF "$(CFG)" == "xatrix - Win32 Release" + +!ELSEIF "$(CFG)" == "xatrix - Win32 Debug" + +!ELSEIF "$(CFG)" == "xatrix - Win32 Debug Alpha" + +DEP_CPP_P_CLI=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_player.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "xatrix - Win32 Release Alpha" + +DEP_CPP_P_CLI=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_player.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\p_hud.c + +!IF "$(CFG)" == "xatrix - Win32 Release" + +!ELSEIF "$(CFG)" == "xatrix - Win32 Debug" + +!ELSEIF "$(CFG)" == "xatrix - Win32 Debug Alpha" + +DEP_CPP_P_HUD=\ + ".\g_local.h"\ + ".\game.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "xatrix - Win32 Release Alpha" + +DEP_CPP_P_HUD=\ + ".\g_local.h"\ + ".\game.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\p_trail.c + +!IF "$(CFG)" == "xatrix - Win32 Release" + +!ELSEIF "$(CFG)" == "xatrix - Win32 Debug" + +!ELSEIF "$(CFG)" == "xatrix - Win32 Debug Alpha" + +DEP_CPP_P_TRA=\ + ".\g_local.h"\ + ".\game.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "xatrix - Win32 Release Alpha" + +DEP_CPP_P_TRA=\ + ".\g_local.h"\ + ".\game.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\p_view.c + +!IF "$(CFG)" == "xatrix - Win32 Release" + +!ELSEIF "$(CFG)" == "xatrix - Win32 Debug" + +!ELSEIF "$(CFG)" == "xatrix - Win32 Debug Alpha" + +DEP_CPP_P_VIE=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_player.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "xatrix - Win32 Release Alpha" + +DEP_CPP_P_VIE=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_player.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\p_weapon.c + +!IF "$(CFG)" == "xatrix - Win32 Release" + +!ELSEIF "$(CFG)" == "xatrix - Win32 Debug" + +!ELSEIF "$(CFG)" == "xatrix - Win32 Debug Alpha" + +DEP_CPP_P_WEA=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_player.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "xatrix - Win32 Release Alpha" + +DEP_CPP_P_WEA=\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_player.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\q_shared.c + +!IF "$(CFG)" == "xatrix - Win32 Release" + +!ELSEIF "$(CFG)" == "xatrix - Win32 Debug" + +!ELSEIF "$(CFG)" == "xatrix - Win32 Debug Alpha" + +DEP_CPP_Q_SHA=\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "xatrix - Win32 Release Alpha" + +DEP_CPP_Q_SHA=\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "*.h" +# Begin Source File + +SOURCE=.\g_local.h +# End Source File +# Begin Source File + +SOURCE=.\game.h +# End Source File +# Begin Source File + +SOURCE=.\m_actor.h +# End Source File +# Begin Source File + +SOURCE=.\m_berserk.h +# End Source File +# Begin Source File + +SOURCE=.\m_boss2.h +# End Source File +# Begin Source File + +SOURCE=.\m_boss31.h +# End Source File +# Begin Source File + +SOURCE=.\m_boss32.h +# End Source File +# Begin Source File + +SOURCE=.\m_brain.h +# End Source File +# Begin Source File + +SOURCE=.\m_chick.h +# End Source File +# Begin Source File + +SOURCE=.\m_fixbot.h +# End Source File +# Begin Source File + +SOURCE=.\m_flipper.h +# End Source File +# Begin Source File + +SOURCE=.\m_float.h +# End Source File +# Begin Source File + +SOURCE=.\m_flyer.h +# End Source File +# Begin Source File + +SOURCE=.\m_gekk.h +# End Source File +# Begin Source File + +SOURCE=.\m_gladiator.h +# End Source File +# Begin Source File + +SOURCE=.\m_gunner.h +# End Source File +# Begin Source File + +SOURCE=.\m_hover.h +# End Source File +# Begin Source File + +SOURCE=.\m_infantry.h +# End Source File +# Begin Source File + +SOURCE=.\m_insane.h +# End Source File +# Begin Source File + +SOURCE=.\m_medic.h +# End Source File +# Begin Source File + +SOURCE=.\m_mutant.h +# End Source File +# Begin Source File + +SOURCE=.\m_parasite.h +# End Source File +# Begin Source File + +SOURCE=.\m_player.h +# End Source File +# Begin Source File + +SOURCE=.\m_rider.h +# End Source File +# Begin Source File + +SOURCE=.\m_soldier.h +# End Source File +# Begin Source File + +SOURCE=.\m_soldierh.h +# End Source File +# Begin Source File + +SOURCE=.\m_supertank.h +# End Source File +# Begin Source File + +SOURCE=.\m_tank.h +# End Source File +# Begin Source File + +SOURCE=.\q_shared.h +# End Source File +# End Group +# Begin Group "Resource Files" + +# PROP Default_Filter "*.def,*.res" +# Begin Source File + +SOURCE=.\xatrix.def +# End Source File +# End Group +# End Target +# End Project diff --git a/rerelease/bg_local.h b/rerelease/bg_local.h new file mode 100644 index 0000000..44fc069 --- /dev/null +++ b/rerelease/bg_local.h @@ -0,0 +1,263 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. + +// g_local.h -- local definitions for game module +#pragma once + +#include "q_std.h" + +// define GAME_INCLUDE so that game.h does not define the +// short, server-visible gclient_t and edict_t structures, +// because we define the full size ones in this file +#define GAME_INCLUDE +#include "game.h" + +// +// p_move.c +// +struct pm_config_t +{ + int32_t airaccel = 0; + bool n64_physics = false; +}; + +extern pm_config_t pm_config; + +void Pmove(pmove_t *pmove); +using pm_trace_func_t = trace_t(const vec3_t &start, const vec3_t &mins, const vec3_t &maxs, const vec3_t &end); +using pm_trace_t = std::function; +void PM_StepSlideMove_Generic(vec3_t &origin, vec3_t &velocity, float frametime, const vec3_t &mins, const vec3_t &maxs, touch_list_t &touch, bool has_time, pm_trace_t trace); + +enum class stuck_result_t +{ + GOOD_POSITION, + FIXED, + NO_GOOD_POSITION +}; + +using stuck_object_trace_fn_t = trace_t(const vec3_t &, const vec3_t &, const vec3_t &, const vec3_t &); + +stuck_result_t G_FixStuckObject_Generic(vec3_t &origin, const vec3_t &own_mins, const vec3_t &own_maxs, std::function trace); + +// state for coop respawning; used to select which +// message to print for the player this is set on. +enum coop_respawn_t +{ + COOP_RESPAWN_NONE, // no messagee + COOP_RESPAWN_IN_COMBAT, // player is in combat + COOP_RESPAWN_BAD_AREA, // player not in a good spot + COOP_RESPAWN_BLOCKED, // spawning was blocked by something + COOP_RESPAWN_WAITING, // for players that are waiting to respawn + COOP_RESPAWN_NO_LIVES, // out of lives, so need to wait until level switch + COOP_RESPAWN_TOTAL +}; + +// reserved general CS ranges +enum +{ + CONFIG_CTF_MATCH = CS_GENERAL, + CONFIG_CTF_TEAMINFO, + CONFIG_CTF_PLAYER_NAME, + CONFIG_CTF_PLAYER_NAME_END = CONFIG_CTF_PLAYER_NAME + MAX_CLIENTS, + + // nb: offset by 1 since NONE is zero + CONFIG_COOP_RESPAWN_STRING, + CONFIG_COOP_RESPAWN_STRING_END = CONFIG_COOP_RESPAWN_STRING + (COOP_RESPAWN_TOTAL - 1), + + // [Paril-KEX] if 1, n64 player physics apply + CONFIG_N64_PHYSICS, + CONFIG_HEALTH_BAR_NAME, // active health bar name + + CONFIG_STORY, + + CONFIG_LAST +}; + +static_assert(CONFIG_LAST <= CS_GENERAL + MAX_GENERAL); + +// ammo IDs +enum ammo_t : uint8_t +{ + AMMO_BULLETS, + AMMO_SHELLS, + AMMO_ROCKETS, + AMMO_GRENADES, + AMMO_CELLS, + AMMO_SLUGS, + // RAFAEL + AMMO_MAGSLUG, + AMMO_TRAP, + // RAFAEL + // ROGUE + AMMO_FLECHETTES, + AMMO_TESLA, + AMMO_DISRUPTOR, + AMMO_PROX, + // ROGUE + AMMO_MAX +}; + +// powerup IDs +enum powerup_t : uint8_t +{ + POWERUP_SCREEN, + POWERUP_SHIELD, + + POWERUP_AM_BOMB, + + POWERUP_QUAD, + POWERUP_QUADFIRE, + POWERUP_INVULNERABILITY, + POWERUP_INVISIBILITY, + POWERUP_SILENCER, + POWERUP_REBREATHER, + POWERUP_ENVIROSUIT, + POWERUP_ADRENALINE, + POWERUP_IR_GOGGLES, + POWERUP_DOUBLE, + POWERUP_SPHERE_VENGEANCE, + POWERUP_SPHERE_HUNTER, + POWERUP_SPHERE_DEFENDER, + POWERUP_DOPPELGANGER, + + POWERUP_FLASHLIGHT, + POWERUP_COMPASS, + POWERUP_TECH1, + POWERUP_TECH2, + POWERUP_TECH3, + POWERUP_TECH4, + POWERUP_MAX +}; + +// ammo stats compressed in 9 bits per entry +// since the range is 0-300 +constexpr size_t BITS_PER_AMMO = 9; + +template +constexpr size_t num_of_type_for_bits(size_t num_bits) +{ + return (num_bits + (sizeof(TI) * 8) - 1) / ((sizeof(TI) * 8) + 1); +} + +template +constexpr void set_compressed_integer(uint16_t *start, uint8_t id, uint16_t count) +{ + uint16_t bit_offset = bits_per_value * id; + uint16_t byte = bit_offset / 8; + uint16_t bit_shift = bit_offset % 8; + uint16_t mask = (bit_v - 1) << bit_shift; + uint16_t *base = (uint16_t *) ((uint8_t *) start + byte); + *base = (*base & ~mask) | ((count << bit_shift) & mask); +} + +template +constexpr uint16_t get_compressed_integer(uint16_t *start, uint8_t id) +{ + uint16_t bit_offset = bits_per_value * id; + uint16_t byte = bit_offset / 8; + uint16_t bit_shift = bit_offset % 8; + uint16_t mask = (bit_v - 1) << bit_shift; + uint16_t *base = (uint16_t *) ((uint8_t *) start + byte); + return (*base & mask) >> bit_shift; +} + +constexpr size_t NUM_BITS_FOR_AMMO = 9; +constexpr size_t NUM_AMMO_STATS = num_of_type_for_bits(NUM_BITS_FOR_AMMO * AMMO_MAX); +// if this value is set on an STAT_AMMO_INFO_xxx, don't render ammo +constexpr uint16_t AMMO_VALUE_INFINITE = bit_v - 1; + +constexpr void G_SetAmmoStat(uint16_t *start, uint8_t ammo_id, uint16_t count) +{ + set_compressed_integer(start, ammo_id, count); +} + +constexpr uint16_t G_GetAmmoStat(uint16_t *start, uint8_t ammo_id) +{ + return get_compressed_integer(start, ammo_id); +} + +// powerup stats compressed in 2 bits per entry; +// 3 is the max you'll ever hold, and for some +// (flashlight) it's to indicate on/off state +constexpr size_t NUM_BITS_PER_POWERUP = 2; +constexpr size_t NUM_POWERUP_STATS = num_of_type_for_bits(NUM_BITS_PER_POWERUP * POWERUP_MAX); + +constexpr void G_SetPowerupStat(uint16_t *start, uint8_t powerup_id, uint16_t count) +{ + set_compressed_integer(start, powerup_id, count); +} + +constexpr uint16_t G_GetPowerupStat(uint16_t *start, uint8_t powerup_id) +{ + return get_compressed_integer(start, powerup_id); +} + +// player_state->stats[] indexes +enum player_stat_t +{ + STAT_HEALTH_ICON = 0, + STAT_HEALTH = 1, + STAT_AMMO_ICON = 2, + STAT_AMMO = 3, + STAT_ARMOR_ICON = 4, + STAT_ARMOR = 5, + STAT_SELECTED_ICON = 6, + STAT_PICKUP_ICON = 7, + STAT_PICKUP_STRING = 8, + STAT_TIMER_ICON = 9, + STAT_TIMER = 10, + STAT_HELPICON = 11, + STAT_SELECTED_ITEM = 12, + STAT_LAYOUTS = 13, + STAT_FRAGS = 14, + STAT_FLASHES = 15, // cleared each frame, 1 = health, 2 = armor + STAT_CHASE = 16, + STAT_SPECTATOR = 17, + + STAT_CTF_TEAM1_PIC = 18, + STAT_CTF_TEAM1_CAPS = 19, + STAT_CTF_TEAM2_PIC = 20, + STAT_CTF_TEAM2_CAPS = 21, + STAT_CTF_FLAG_PIC = 22, + STAT_CTF_JOINED_TEAM1_PIC = 23, + STAT_CTF_JOINED_TEAM2_PIC = 24, + STAT_CTF_TEAM1_HEADER = 25, + STAT_CTF_TEAM2_HEADER = 26, + STAT_CTF_TECH = 27, + STAT_CTF_ID_VIEW = 28, + STAT_CTF_MATCH = 29, + STAT_CTF_ID_VIEW_COLOR = 30, + STAT_CTF_TEAMINFO = 31, + + // [Kex] More stats for weapon wheel + STAT_WEAPONS_OWNED_1 = 32, + STAT_WEAPONS_OWNED_2 = 33, + STAT_AMMO_INFO_START = 34, + STAT_AMMO_INFO_END = STAT_AMMO_INFO_START + NUM_AMMO_STATS - 1, + STAT_POWERUP_INFO_START, + STAT_POWERUP_INFO_END = STAT_POWERUP_INFO_START + NUM_POWERUP_STATS - 1, + + // [Paril-KEX] Key display + STAT_KEY_A, + STAT_KEY_B, + STAT_KEY_C, + + // [Paril-KEX] currently active wheel weapon (or one we're switching to) + STAT_ACTIVE_WHEEL_WEAPON, + // [Paril-KEX] top of screen coop respawn state + STAT_COOP_RESPAWN, + // [Paril-KEX] respawns remaining + STAT_LIVES, + // [Paril-KEX] hit marker; # of damage we successfully landed + STAT_HIT_MARKER, + // [Paril-KEX] + STAT_SELECTED_ITEM_NAME, + // [Paril-KEX] + STAT_HEALTH_BARS, // two health bar values; 7 bits for value, 1 bit for active + // if active, + + // don't use; just for verification + STAT_LAST +}; + +static_assert(STAT_LAST <= MAX_STATS + 1, "stats list overflow"); \ No newline at end of file diff --git a/rerelease/bots/bot_debug.cpp b/rerelease/bots/bot_debug.cpp new file mode 100644 index 0000000..0a6771e --- /dev/null +++ b/rerelease/bots/bot_debug.cpp @@ -0,0 +1,178 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. + +#include "../g_local.h" +#include "bot_utils.h" +#include "bot_debug.h" + +static const edict_t * escortBot = nullptr; +static const edict_t * escortActor = nullptr; + +static const edict_t * moveToPointBot = nullptr; +static vec3_t moveToPointPos = vec3_origin; + +// how close the bot will try to get to the move to point goal +constexpr float moveToPointTolerance = 16.0f; + +/* +================ +ShowMonsterPathToPlayer +================ +*/ +void ShowMonsterPathToPlayer( const edict_t * player ) { + const edict_t * monster = FindFirstMonster(); + if ( monster == nullptr ) { + return; + } + + const float moveDist = 8.0f; + + std::array pathPoints; + + PathRequest request; + request.start = monster->s.origin; + request.goal = player->s.origin; + request.moveDist = moveDist; + request.pathFlags = PathFlags::All; + request.debugging.drawTime = 0.10f; + request.nodeSearch.minHeight = 64.0f; + request.nodeSearch.maxHeight = 64.0f; + request.nodeSearch.radius = 512.0f; + request.pathPoints.array = &pathPoints.front(); + request.pathPoints.count = pathPoints.size(); + + PathInfo info; + if ( gi.GetPathToGoal( request, info ) ) { + // Do movement stuff.... + for ( int i = 0; i < info.numPathPoints; ++i ) { + const gvec3_t & point = pathPoints[ i ]; + gi.Draw_Point( point, 8.0f, rgba_yellow, 0.10f, false ); + } + } +} + +/* +================ +UpdateFollowActorDebug + +Set cvar "bot_debug_follow_actor" to 1 +and then run your cursor over any player/monster to pick +that "actor" for the bot to follow. + +When successful, you will see the player/monster highlighted +with a yellow box, and the bot will follow them around the map until +the actor they're following dies, or the bot is told to do something +else by you. + +Check the console for debugging feedback... +================ +*/ +void UpdateFollowActorDebug( const edict_t * localPlayer ) { + if ( bot_debug_follow_actor->integer ) { + if ( bot_debug_follow_actor->integer == 1 ) { + escortBot = FindFirstBot(); + escortActor = FindActorUnderCrosshair( localPlayer ); + + if ( gi.Bot_FollowActor( escortBot, escortActor ) != GoalReturnCode::Error ) { + gi.cvar_set( "bot_debug_follow_actor", "2" ); + gi.Com_Print( "Follow_Actor: Bot Found Actor To Follow!\n" ); + } else { + gi.Com_Print( "Follow_Actor: Hover Over Monster/Player To Follow...\n" ); + } + } else { + if ( gi.Bot_FollowActor( escortBot, escortActor ) != GoalReturnCode::Error ) { + gi.Draw_Bounds( escortActor->absmin, escortActor->absmax, rgba_yellow, gi.frame_time_s, false ); + gi.Draw_Bounds( escortBot->absmin, escortBot->absmax, rgba_cyan, gi.frame_time_s, false ); + } else { + gi.Com_Print( "Follow_Actor: Bot Or Actor Removed...\n" ); + gi.cvar_set( "bot_debug_follow_actor", "0" ); + } + } + } else { + escortBot = nullptr; + escortActor = nullptr; + } +} + +/* +================ +UpdateMoveToPointDebug + +Set cvar "bot_debug_move_to_point" to 1, +look anywhere in world you'd like the bot to move to, +and then fire your weapon. The point at the end of your crosshair +will be the point in the world the bot will move toward. + +When successful, a point marker will be drawn where the bot will move +toward, and the bot itself will have a box drawn around it. + +Once bot reaches the point, it will clear the goal and go about it's +business until you give it something else to do. + +Check the console for debugging feedback... +================ +*/ +void UpdateMoveToPointDebug( const edict_t * localPlayer ) { + if ( bot_debug_move_to_point->integer ) { + if ( bot_debug_move_to_point->integer == 1 ) { + if ( localPlayer->client->buttons & BUTTON_ATTACK ) { + vec3_t localPlayerForward, right, up; + AngleVectors( localPlayer->client->v_angle, localPlayerForward, right, up ); + + const vec3_t localPlayerViewPos = ( localPlayer->s.origin + vec3_t{ 0.0f, 0.0f, (float)localPlayer->viewheight } ); + const vec3_t end = ( localPlayerViewPos + ( localPlayerForward * 8192.0f ) ); + const contents_t mask = ( MASK_PROJECTILE & ~CONTENTS_DEADMONSTER ); + + trace_t tr = gi.traceline( localPlayerViewPos, end, localPlayer, mask ); + moveToPointPos = tr.endpos; + + moveToPointBot = FindFirstBot(); + if ( gi.Bot_MoveToPoint( moveToPointBot, moveToPointPos, moveToPointTolerance ) != GoalReturnCode::Error ) { + gi.cvar_set( "bot_debug_move_to_point", "2" ); + gi.Com_Print( "Move_To_Point: Bot Has Position To Move Toward!\n" ); + } + } else { + gi.Com_Print( "Move_To_Point: Fire Weapon To Select Move Point...\n" ); + } + } else { + const GoalReturnCode result = gi.Bot_MoveToPoint( moveToPointBot, moveToPointPos, moveToPointTolerance ); + if ( result == GoalReturnCode::Error ) { + gi.cvar_set( "bot_debug_move_to_point", "0" ); + gi.Com_Print( "Move_To_Point: Bot Can't Reach Goal Position!\n" ); + } else if ( result == GoalReturnCode::Finished ) { + gi.cvar_set( "bot_debug_move_to_point", "0" ); + gi.Com_Print( "Move_To_Point: Bot Reached Goal Position!\n" ); + } else { + gi.Draw_Point( moveToPointPos, 8.0f, rgba_yellow, gi.frame_time_s, false ); + gi.Draw_Bounds( moveToPointBot->absmin, moveToPointBot->absmax, rgba_cyan, gi.frame_time_s, false ); + } + } + } else { + moveToPointBot = nullptr; + moveToPointPos = vec3_origin; + } +} + +/* +================ +Bot_UpdateDebug +================ +*/ +void Bot_UpdateDebug() { + if ( !sv_cheats->integer ) { + return; + } + + const edict_t * localPlayer = FindLocalPlayer(); + if ( localPlayer == nullptr ) { + return; + } + + if ( g_debug_monster_paths->integer == 2 ) { + ShowMonsterPathToPlayer( localPlayer ); + } + + UpdateFollowActorDebug( localPlayer ); + + UpdateMoveToPointDebug( localPlayer ); +} \ No newline at end of file diff --git a/rerelease/bots/bot_debug.h b/rerelease/bots/bot_debug.h new file mode 100644 index 0000000..ccd687c --- /dev/null +++ b/rerelease/bots/bot_debug.h @@ -0,0 +1,6 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. + +#pragma once + +void Bot_UpdateDebug(); \ No newline at end of file diff --git a/rerelease/bots/bot_exports.cpp b/rerelease/bots/bot_exports.cpp new file mode 100644 index 0000000..56a8646 --- /dev/null +++ b/rerelease/bots/bot_exports.cpp @@ -0,0 +1,151 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. + +#include "../g_local.h" +#include "bot_exports.h" + +/* +================ +Bot_SetWeapon +================ +*/ +void Bot_SetWeapon( edict_t * bot, const int weaponIndex, const bool instantSwitch ) { + if ( weaponIndex <= IT_NULL || weaponIndex > IT_TOTAL ) { + return; + } + + if ( ( bot->svflags & SVF_BOT ) == 0 ) { + return; + } + + gclient_t * client = bot->client; + if ( client == nullptr || !client->pers.inventory[ weaponIndex ] ) { + return; + } + + const item_id_t weaponItemID = static_cast( weaponIndex ); + + const gitem_t * currentGun = client->pers.weapon; + if ( currentGun != nullptr ) { + if ( currentGun->id == weaponItemID ) { + return; + } // already have the gun in hand. + } + + const gitem_t * pendingGun = client->newweapon; + if ( pendingGun != nullptr ) { + if ( pendingGun->id == weaponItemID ) { + return; + } // already in the process of switching to that gun, just be patient! + } + + gitem_t * item = &itemlist[ weaponIndex ]; + if ( ( item->flags & IF_WEAPON ) == 0 ) { + return; + } + + if ( item->use == nullptr ) { + return; + } + + bot->client->no_weapon_chains = true; + item->use( bot, item ); + + if ( instantSwitch ) { + // FIXME: ugly, maybe store in client later + const int temp_instant_weapon = g_instant_weapon_switch->integer; + g_instant_weapon_switch->integer = 1; + ChangeWeapon( bot ); + g_instant_weapon_switch->integer = temp_instant_weapon; + } +} + +/* +================ +Bot_TriggerEdict +================ +*/ +void Bot_TriggerEdict( edict_t * bot, edict_t * edict ) { + if ( !bot->inuse || !edict->inuse ) { + return; + } + + if ( ( bot->svflags & SVF_BOT ) == 0 ) { + return; + } + + if ( edict->use ) { + edict->use( edict, bot, bot ); + } + + trace_t unUsed; + if ( edict->touch ) { + edict->touch( edict, bot, unUsed, true ); + } +} + +/* +================ +Bot_UseItem +================ +*/ +void Bot_UseItem( edict_t * bot, const int32_t itemID ) { + if ( !bot->inuse ) { + return; + } + + if ( ( bot->svflags & SVF_BOT ) == 0 ) { + return; + } + + const item_id_t desiredItemID = item_id_t( itemID ); + bot->client->pers.selected_item = desiredItemID; + + ValidateSelectedItem( bot ); + + if ( bot->client->pers.selected_item == IT_NULL ) { + return; + } + + if ( bot->client->pers.selected_item != desiredItemID ) { + return; + } // the itemID changed on us - don't use it! + + gitem_t * item = &itemlist[ bot->client->pers.selected_item ]; + bot->client->pers.selected_item = IT_NULL; + + if ( item->use == nullptr ) { + return; + } + + bot->client->no_weapon_chains = true; + item->use( bot, item ); +} + +/* +================ +Bot_GetItemID +================ +*/ +int32_t Bot_GetItemID( const char * classname ) { + if ( classname == nullptr || classname[ 0 ] == '\0' ) { + return Item_Invalid; + } + + if ( Q_strcasecmp( classname, "none" ) == 0 ) { + return Item_Null; + } + + for ( int i = 0; i < IT_TOTAL; ++i ) { + const gitem_t * item = itemlist + i; + if ( item->classname == nullptr || item->classname[ 0 ] == '\0' ) { + continue; + } + + if ( Q_strcasecmp( item->classname, classname ) == 0 ) { + return item->id; + } + } + + return Item_Invalid; +} diff --git a/rerelease/bots/bot_exports.h b/rerelease/bots/bot_exports.h new file mode 100644 index 0000000..dd2c90f --- /dev/null +++ b/rerelease/bots/bot_exports.h @@ -0,0 +1,9 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. + +#pragma once + +void Bot_SetWeapon( edict_t * bot, const int weaponIndex, const bool instantSwitch ); +void Bot_TriggerEdict( edict_t * bot, edict_t * edict ); +int32_t Bot_GetItemID( const char * classname ); +void Bot_UseItem( edict_t * bot, const int32_t itemID ); \ No newline at end of file diff --git a/rerelease/bots/bot_includes.h b/rerelease/bots/bot_includes.h new file mode 100644 index 0000000..b8455ad --- /dev/null +++ b/rerelease/bots/bot_includes.h @@ -0,0 +1,9 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. + +#pragma once + +#include "bot_utils.h" +#include "bot_think.h" +#include "bot_debug.h" +#include "bot_exports.h" diff --git a/rerelease/bots/bot_think.cpp b/rerelease/bots/bot_think.cpp new file mode 100644 index 0000000..cfc5bae --- /dev/null +++ b/rerelease/bots/bot_think.cpp @@ -0,0 +1,23 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. + +#include "../g_local.h" +#include "bot_think.h" + +/* +================ +Bot_BeginFrame +================ +*/ +void Bot_BeginFrame( edict_t * bot ) { + +} + +/* +================ +Bot_EndFrame +================ +*/ +void Bot_EndFrame( edict_t * bot ) { + +} diff --git a/rerelease/bots/bot_think.h b/rerelease/bots/bot_think.h new file mode 100644 index 0000000..0576b1c --- /dev/null +++ b/rerelease/bots/bot_think.h @@ -0,0 +1,7 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. + +#pragma once + +void Bot_BeginFrame( edict_t * bot ); +void Bot_EndFrame( edict_t * bot ); diff --git a/rerelease/bots/bot_utils.cpp b/rerelease/bots/bot_utils.cpp new file mode 100644 index 0000000..e13f90e --- /dev/null +++ b/rerelease/bots/bot_utils.cpp @@ -0,0 +1,532 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. + +#include "../g_local.h" +#include "../m_player.h" +#include "bot_utils.h" + +constexpr int Team_Coop_Monster = 0; + +/* +================ +Player_UpdateState +================ +*/ +void Player_UpdateState( edict_t * player ) { + const client_persistant_t & persistant = player->client->pers; + + player->sv.ent_flags = SVFL_NONE; + if ( player->groundentity != nullptr || ( player->flags & FL_PARTIALGROUND ) != 0 ) { + player->sv.ent_flags |= SVFL_ONGROUND; + } else { + if ( player->client->ps.pmove.pm_flags & PMF_JUMP_HELD ) { + player->sv.ent_flags |= SVFL_IS_JUMPING; + } + } + + if ( player->client->ps.pmove.pm_flags & PMF_ON_LADDER ) { + player->sv.ent_flags |= SVFL_ON_LADDER; + } + + if ( ( player->client->ps.pmove.pm_flags & PMF_DUCKED ) != 0 ) { + player->sv.ent_flags |= SVFL_IS_CROUCHING; + } + + if ( player->client->quad_time > level.time ) { + player->sv.ent_flags |= SVFL_HAS_DMG_BOOST; + } else if ( player->client->quadfire_time > level.time ) { + player->sv.ent_flags |= SVFL_HAS_DMG_BOOST; + } else if ( player->client->double_time > level.time ) { + player->sv.ent_flags |= SVFL_HAS_DMG_BOOST; + } + + if ( player->client->invincible_time > level.time ) { + player->sv.ent_flags |= SVFL_HAS_PROTECTION; + } + + if ( player->client->invisible_time > level.time ) { + player->sv.ent_flags |= SVFL_HAS_INVISIBILITY; + } + + if ( ( player->client->ps.pmove.pm_flags & PMF_TIME_TELEPORT ) != 0 ) { + player->sv.ent_flags |= SVFL_HAS_TELEPORTED; + } + + if ( player->takedamage ) { + player->sv.ent_flags |= SVFL_TAKES_DAMAGE; + } + + if ( player->solid == SOLID_NOT ) { + player->sv.ent_flags |= SVFL_IS_HIDDEN; + } + + if ( ( player->flags & FL_INWATER ) != 0 ) { + if ( player->waterlevel >= WATER_WAIST ) { + player->sv.ent_flags |= SVFL_IN_WATER; + } + } + + if ( ( player->flags & FL_NOTARGET ) != 0 ) { + player->sv.ent_flags |= SVFL_NO_TARGET; + } + + if ( ( player->flags & FL_GODMODE ) != 0 ) { + player->sv.ent_flags |= SVFL_GOD_MODE; + } + + if ( player->movetype == MOVETYPE_NOCLIP ) { + player->sv.ent_flags |= SVFL_IS_NOCLIP; + } + + if ( player->client->anim_end == FRAME_flip12 ) { + player->sv.ent_flags |= SVFL_IS_FLIPPING_OFF; + } + + if ( player->client->anim_end == FRAME_salute11 ) { + player->sv.ent_flags |= SVFL_IS_SALUTING; + } + + if ( player->client->anim_end == FRAME_taunt17 ) { + player->sv.ent_flags |= SVFL_IS_TAUNTING; + } + + if ( player->client->anim_end == FRAME_wave11 ) { + player->sv.ent_flags |= SVFL_IS_WAVING; + } + + if ( player->client->anim_end == FRAME_point12 ) { + player->sv.ent_flags |= SVFL_IS_POINTING; + } + + if ( ( player->client->ps.pmove.pm_flags & PMF_DUCKED ) == 0 && player->client->anim_priority <= ANIM_WAVE ) { + player->sv.ent_flags |= SVFL_CAN_GESTURE; + } + + if ( player->lastMOD.id == MOD_TELEFRAG || player->lastMOD.id == MOD_TELEFRAG_SPAWN ) { + player->sv.ent_flags |= SVFL_WAS_TELEFRAGGED; + } + + if ( player->client->resp.spectator ) { + player->sv.ent_flags |= SVFL_IS_SPECTATOR; + } + + player_skinnum_t pl_skinnum; + pl_skinnum.skinnum = player->s.skinnum; + player->sv.team = pl_skinnum.team_index; + + player->sv.buttons = player->client->buttons; + + const item_id_t armorType = ArmorIndex( player ); + player->sv.armor_type = armorType; + player->sv.armor_value = persistant.inventory[ armorType ]; + + player->sv.health = ( player->deadflag != true ) ? player->health : -1; + player->sv.weapon = ( persistant.weapon != nullptr ) ? persistant.weapon->id : IT_NULL; + + player->sv.last_attackertime = static_cast( player->client->last_attacker_time.milliseconds() ); + player->sv.respawntime = static_cast( player->client->respawn_time.milliseconds() ); + player->sv.waterlevel = player->waterlevel; + player->sv.viewheight = player->viewheight; + + player->sv.viewangles = player->client->v_angle; + player->sv.viewforward = player->client->v_forward; + player->sv.velocity = player->velocity; + + player->sv.ground_entity = player->groundentity; + player->sv.enemy = player->enemy; + + static_assert( sizeof( persistant.inventory ) <= sizeof( player->sv.inventory ) ); + memcpy( &player->sv.inventory, &persistant.inventory, sizeof( persistant.inventory ) ); + + if ( !player->sv.init ) { + player->sv.init = true; + player->sv.classname = player->classname; + player->sv.targetname = player->targetname; + player->sv.lobby_usernum = P_GetLobbyUserNum( player ); + player->sv.starting_health = player->health; + player->sv.max_health = player->max_health; + + // NOTE: entries are assumed to be ranked with the first armor assumed + // NOTE: to be the "best", and last the "worst". You don't need to add + // NOTE: entries for things like armor shards, only actual armors. + // NOTE: Check "Max_Armor_Types" to raise/lower the armor count. + armorInfo_t * armorInfo = player->sv.armor_info; + armorInfo[ 0 ].item_id = IT_ARMOR_BODY; + armorInfo[ 0 ].max_count = bodyarmor_info.max_count; + armorInfo[ 1 ].item_id = IT_ARMOR_COMBAT; + armorInfo[ 1 ].max_count = combatarmor_info.max_count; + armorInfo[ 2 ].item_id = IT_ARMOR_JACKET; + armorInfo[ 2 ].max_count = jacketarmor_info.max_count; + + gi.Info_ValueForKey( player->client->pers.userinfo, "name", player->sv.netname, sizeof( player->sv.netname ) ); + + gi.Bot_RegisterEdict( player ); + } +} + +/* +================ +Monster_UpdateState +================ +*/ +void Monster_UpdateState( edict_t * monster ) { + monster->sv.ent_flags = SVFL_NONE; + if ( monster->groundentity != nullptr ) { + monster->sv.ent_flags |= SVFL_ONGROUND; + } + + if ( monster->takedamage ) { + monster->sv.ent_flags |= SVFL_TAKES_DAMAGE; + } + + if ( monster->solid == SOLID_NOT || monster->movetype == MOVETYPE_NONE ) { + monster->sv.ent_flags |= SVFL_IS_HIDDEN; + } + + if ( ( monster->flags & FL_INWATER ) != 0 ) { + monster->sv.ent_flags |= SVFL_IN_WATER; + } + + if ( coop->integer ) { + monster->sv.team = Team_Coop_Monster; + } else { + monster->sv.team = Team_None; // TODO: CTF/TDM/etc... + } + + monster->sv.health = ( monster->deadflag != true ) ? monster->health : -1; + monster->sv.waterlevel = monster->waterlevel; + monster->sv.enemy = monster->enemy; + monster->sv.ground_entity = monster->groundentity; + + int32_t viewHeight = monster->viewheight; + if ( ( monster->monsterinfo.aiflags & AI_DUCKED ) != 0 ) { + viewHeight = int32_t( monster->maxs[ 2 ] - 4.0f ); + } + monster->sv.viewheight = viewHeight; + + monster->sv.viewangles = monster->s.angles; + + AngleVectors( monster->s.angles, monster->sv.viewforward, nullptr, nullptr ); + + monster->sv.velocity = monster->velocity; + + if ( !monster->sv.init ) { + monster->sv.init = true; + monster->sv.classname = monster->classname; + monster->sv.targetname = monster->targetname; + monster->sv.starting_health = monster->health; + monster->sv.max_health = monster->max_health; + + gi.Bot_RegisterEdict( monster ); + } +} + +/* +================ +Item_UpdateState +================ +*/ +void Item_UpdateState( edict_t * item ) { + item->sv.ent_flags = SVFL_IS_ITEM; + item->sv.respawntime = 0; + + if ( item->team != nullptr ) { + item->sv.ent_flags |= SVFL_IN_TEAM; + } // some DM maps have items chained together in teams... + + if ( item->solid == SOLID_NOT ) { + item->sv.ent_flags |= SVFL_IS_HIDDEN; + + if ( item->nextthink.milliseconds() > 0 ) { + if ( ( item->svflags & SVF_RESPAWNING ) != 0 ) { + const gtime_t pendingRespawnTime = ( item->nextthink - level.time ); + item->sv.respawntime = static_cast( pendingRespawnTime.milliseconds() ); + } else { + // item will respawn at some unknown time in the future... + item->sv.respawntime = Item_UnknownRespawnTime; + } + } + } + + // track who has picked us up so far... + item->sv.pickedup_list = item->item_picked_up_by; + + const item_id_t itemID = item->item->id; + if ( itemID == IT_FLAG1 || itemID == IT_FLAG2 ) { + item->sv.ent_flags |= SVFL_IS_OBJECTIVE; + // TODO: figure out if the objective is dropped/carried/home... + } + + // always need to update these for items, since random item spawning + // could change them at any time... + item->sv.classname = item->classname; + item->sv.item_id = item->item->id; + + if ( !item->sv.init ) { + item->sv.init = true; + item->sv.targetname = item->targetname; + + gi.Bot_RegisterEdict( item ); + } +} + +/* +================ +Trap_UpdateState +================ +*/ +void Trap_UpdateState( edict_t * danger ) { + danger->sv.ent_flags = SVFL_TRAP_DANGER; + danger->sv.velocity = danger->velocity; + + if ( danger->owner != nullptr && danger->owner->client != nullptr ) { + player_skinnum_t pl_skinnum; + pl_skinnum.skinnum = danger->owner->s.skinnum; + danger->sv.team = pl_skinnum.team_index; + } + + if ( danger->groundentity != nullptr ) { + danger->sv.ent_flags |= SVFL_ONGROUND; + } + + if ( ( danger->flags & FL_TRAP_LASER_FIELD ) == 0 ) { + danger->sv.ent_flags |= SVFL_ACTIVE; // non-lasers are always active + } else { + danger->sv.start_origin = danger->s.origin; + danger->sv.end_origin = danger->s.old_origin; + if ( ( danger->svflags & SVF_NOCLIENT ) == 0 ) { + if ( ( danger->s.renderfx & RF_BEAM ) ) { + danger->sv.ent_flags |= SVFL_ACTIVE; // lasers are active!! + } + } + } + + if ( !danger->sv.init ) { + danger->sv.init = true; + danger->sv.classname = danger->classname; + + gi.Bot_RegisterEdict( danger ); + } +} + +/* +================ +Edict_UpdateState +================ +*/ +void Edict_UpdateState( edict_t * edict ) { + edict->sv.ent_flags = SVFL_NONE; + edict->sv.health = edict->health; + + if ( edict->takedamage ) { + edict->sv.ent_flags |= SVFL_TAKES_DAMAGE; + } + + // plats, movers, and doors use this to determine move state. + const bool isDoor = ( ( edict->svflags & SVF_DOOR ) != 0 ); + const bool isReversedDoor = ( isDoor && edict->spawnflags.has( SPAWNFLAG_DOOR_REVERSE ) ); + + // doors have their top/bottom states reversed from plats + // ( unless "reverse" spawnflag is set! ) + if ( isDoor && !isReversedDoor ) { + if ( edict->moveinfo.state == STATE_TOP ) { + edict->sv.ent_flags |= SVFL_MOVESTATE_BOTTOM; + } else if ( edict->moveinfo.state == STATE_BOTTOM ) { + edict->sv.ent_flags |= SVFL_MOVESTATE_TOP; + } + } else { + if ( edict->moveinfo.state == STATE_TOP ) { + edict->sv.ent_flags |= SVFL_MOVESTATE_TOP; + } else if ( edict->moveinfo.state == STATE_BOTTOM ) { + edict->sv.ent_flags |= SVFL_MOVESTATE_BOTTOM; + } + } + + if ( edict->moveinfo.state == STATE_UP || edict->moveinfo.state == STATE_DOWN ) { + edict->sv.ent_flags |= SVFL_MOVESTATE_MOVING; + } + + edict->sv.start_origin = edict->moveinfo.start_origin; + edict->sv.end_origin = edict->moveinfo.end_origin; + + if ( edict->svflags & SVF_DOOR ) { + if ( edict->flags & FL_LOCKED ) { + edict->sv.ent_flags |= SVFL_IS_LOCKED_DOOR; + } + } + + if ( !edict->sv.init ) { + edict->sv.init = true; + edict->sv.classname = edict->classname; + edict->sv.targetname = edict->targetname; + edict->sv.spawnflags = edict->spawnflags.value; + } +} + +/* +================ +Entity_UpdateState +================ +*/ +void Entity_UpdateState( edict_t * edict ) { + if ( edict->svflags & SVF_MONSTER ) { + Monster_UpdateState( edict ); + } else if ( edict->flags & FL_TRAP || edict->flags & FL_TRAP_LASER_FIELD ) { + Trap_UpdateState( edict ); + } else if ( edict->item != nullptr ) { + Item_UpdateState( edict ); + } else if ( edict->client != nullptr ) { + Player_UpdateState( edict ); + } else { + Edict_UpdateState( edict ); + } +} + +USE( info_nav_lock_use ) ( edict_t * self, edict_t * other, edict_t * activator ) -> void { + edict_t * n = nullptr; + + while ( ( n = G_FindByString<&edict_t::targetname>( n, self->target ) ) ) { + if ( !( n->svflags & SVF_DOOR ) ) { + gi.Com_PrintFmt( "{} tried targeting {}, a non-SVF_DOOR\n", *self, *n ); + continue; + } + + n->flags ^= FL_LOCKED; + } +} + +/*QUAKED info_nav_lock (1.0 1.0 0.0) (-16 -16 0) (16 16 32) +toggle locked state on linked entity +*/ +void SP_info_nav_lock( edict_t * self ) { + if ( !self->targetname ) { + gi.Com_PrintFmt( "{} missing targetname\n", *self ); + G_FreeEdict( self ); + return; + } + + if ( !self->target ) { + gi.Com_PrintFmt( "{} missing target\n", *self ); + G_FreeEdict( self ); + return; + } + + self->svflags |= SVF_NOCLIENT; + self->use = info_nav_lock_use; +} + +/* +================ +FindLocalPlayer +================ +*/ +const edict_t * FindLocalPlayer() { + const edict_t * localPlayer = nullptr; + + const edict_t * ent = &g_edicts[ 0 ]; + for ( uint32_t i = 0; i < globals.num_edicts; i++, ent++ ) { + if ( !ent->inuse || !( ent->svflags & SVF_PLAYER ) ) { + continue; + } + + if ( ent->health <= 0 ) { + continue; + } + + localPlayer = ent; + break; + } + + return localPlayer; +} + +/* +================ +FindFirstBot +================ +*/ +const edict_t * FindFirstBot() { + const edict_t * firstBot = nullptr; + + const edict_t * ent = &g_edicts[ 0 ]; + for ( uint32_t i = 0; i < globals.num_edicts; i++, ent++ ) { + if ( !ent->inuse || !( ent->svflags & SVF_PLAYER ) ) { + continue; + } + + if ( ent->health <= 0 ) { + continue; + } + + if ( !( ent->svflags & SVF_BOT ) ) { + continue; + } + + firstBot = ent; + break; + } + + return firstBot; +} + +/* +================ +FindFirstMonster +================ +*/ +const edict_t * FindFirstMonster() { + const edict_t * firstMonster = nullptr; + + const edict_t * ent = &g_edicts[ 0 ]; + for ( uint32_t i = 0; i < globals.num_edicts; i++, ent++ ) { + if ( !ent->inuse || !( ent->svflags & SVF_MONSTER ) ) { + continue; + } + + if ( ent->health <= 0 ) { + continue; + } + + firstMonster = ent; + break; + } + + return firstMonster; +} + +/* +================ +FindFirstMonster + +"Actors" are either players or monsters - i.e. something alive and thinking. +================ +*/ +const edict_t * FindActorUnderCrosshair( const edict_t * player ) { + if ( player == nullptr || !player->inuse ) { + return nullptr; + } + + vec3_t forward, right, up; + AngleVectors( player->client->v_angle, forward, right, up ); + + const vec3_t eye_position = ( player->s.origin + vec3_t{ 0.0f, 0.0f, (float)player->viewheight } ); + const vec3_t end = ( eye_position + ( forward * 8192.0f ) ); + const contents_t mask = ( MASK_PROJECTILE & ~CONTENTS_DEADMONSTER ); + + trace_t tr = gi.traceline( eye_position, end, player, mask ); + + const edict_t * traceEnt = tr.ent; + if ( traceEnt == nullptr || !tr.ent->inuse ) { + return nullptr; + } + + if ( !( traceEnt->svflags & SVF_PLAYER ) && !( traceEnt->svflags & SVF_MONSTER ) ) { + return nullptr; + } + + if ( traceEnt->health <= 0 ) { + return nullptr; + } + + return traceEnt; +} \ No newline at end of file diff --git a/rerelease/bots/bot_utils.h b/rerelease/bots/bot_utils.h new file mode 100644 index 0000000..225aad2 --- /dev/null +++ b/rerelease/bots/bot_utils.h @@ -0,0 +1,10 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. + +#pragma once + +void Entity_UpdateState( edict_t * edict ); +const edict_t * FindLocalPlayer(); +const edict_t * FindFirstBot(); +const edict_t * FindFirstMonster(); +const edict_t * FindActorUnderCrosshair( const edict_t * player ); diff --git a/rerelease/cg_local.h b/rerelease/cg_local.h new file mode 100644 index 0000000..1bd7d79 --- /dev/null +++ b/rerelease/cg_local.h @@ -0,0 +1,14 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. + +// g_local.h -- local definitions for game module +#pragma once + +#include "bg_local.h" + +extern cgame_import_t cgi; +extern cgame_export_t cglobals; + +#define SERVER_TICK_RATE cgi.tick_rate // in hz +#define FRAME_TIME_S cgi.frame_time_s +#define FRAME_TIME_MS cgi.frame_time_ms \ No newline at end of file diff --git a/rerelease/cg_main.cpp b/rerelease/cg_main.cpp new file mode 100644 index 0000000..9714004 --- /dev/null +++ b/rerelease/cg_main.cpp @@ -0,0 +1,124 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. + +#include "cg_local.h" +#include "m_flash.h" + +cgame_import_t cgi; +cgame_export_t cglobals; + +static void *CG_GetExtension(const char *name) +{ + return nullptr; +} + +void CG_InitScreen(); + +uint64_t cgame_init_time = 0; + +static void InitCGame() +{ + CG_InitScreen(); + + cgame_init_time = cgi.CL_ClientRealTime(); + + pm_config.n64_physics = !!atoi(cgi.get_configstring(CONFIG_N64_PHYSICS)); + pm_config.airaccel = atoi(cgi.get_configstring(CS_AIRACCEL)); +} + +static void ShutdownCGame() +{ +} + +void CG_DrawHUD (int32_t isplit, const cg_server_data_t *data, vrect_t hud_vrect, vrect_t hud_safe, int32_t scale, int32_t playernum, const player_state_t *ps); +void CG_TouchPics(); +layout_flags_t CG_LayoutFlags(const player_state_t *ps); + +int32_t CG_GetActiveWeaponWheelWeapon(const player_state_t *ps) +{ + return ps->stats[STAT_ACTIVE_WHEEL_WEAPON]; +} + +uint32_t CG_GetOwnedWeaponWheelWeapons(const player_state_t *ps) +{ + return ((uint32_t) (uint16_t) ps->stats[STAT_WEAPONS_OWNED_1]) | ((uint32_t) (uint16_t) (ps->stats[STAT_WEAPONS_OWNED_2]) << 16); +} + +int16_t CG_GetWeaponWheelAmmoCount(const player_state_t *ps, int32_t ammo_id) +{ + uint16_t ammo = G_GetAmmoStat((uint16_t *) &ps->stats[STAT_AMMO_INFO_START], ammo_id); + + if (ammo == AMMO_VALUE_INFINITE) + return -1; + + return ammo; +} + +int16_t CG_GetPowerupWheelCount(const player_state_t *ps, int32_t powerup_id) +{ + return G_GetPowerupStat((uint16_t *) &ps->stats[STAT_POWERUP_INFO_START], powerup_id); +} + +int16_t CG_GetHitMarkerDamage(const player_state_t *ps) +{ + return ps->stats[STAT_HIT_MARKER]; +} + +static void CG_ParseConfigString(int32_t i, const char *s) +{ + if (i == CONFIG_N64_PHYSICS) + pm_config.n64_physics = !!atoi(s); + else if (i == CS_AIRACCEL) + pm_config.airaccel = atoi(s); +} + +void CG_ParseCenterPrint (const char *str, int isplit, bool instant); +void CG_ClearNotify(int32_t isplit); +void CG_ClearCenterprint(int32_t isplit); +void CG_NotifyMessage(int32_t isplit, const char *msg, bool is_chat); + +void CG_GetMonsterFlashOffset(monster_muzzleflash_id_t id, gvec3_ref_t offset) +{ + if (id >= q_countof(monster_flash_offset)) + cgi.Com_Error("Bad muzzle flash offset"); + + offset = monster_flash_offset[id]; +} + +/* +================= +GetCGameAPI + +Returns a pointer to the structure with all entry points +and global variables +================= +*/ +Q2GAME_API cgame_export_t *GetCGameAPI(cgame_import_t *import) +{ + cgi = *import; + + cglobals.apiversion = CGAME_API_VERSION; + cglobals.Init = InitCGame; + cglobals.Shutdown = ShutdownCGame; + + cglobals.Pmove = Pmove; + cglobals.DrawHUD = CG_DrawHUD; + cglobals.LayoutFlags = CG_LayoutFlags; + cglobals.TouchPics = CG_TouchPics; + + cglobals.GetActiveWeaponWheelWeapon = CG_GetActiveWeaponWheelWeapon; + cglobals.GetOwnedWeaponWheelWeapons = CG_GetOwnedWeaponWheelWeapons; + cglobals.GetWeaponWheelAmmoCount = CG_GetWeaponWheelAmmoCount; + cglobals.GetPowerupWheelCount = CG_GetPowerupWheelCount; + cglobals.GetHitMarkerDamage = CG_GetHitMarkerDamage; + cglobals.ParseConfigString = CG_ParseConfigString; + cglobals.ParseCenterPrint = CG_ParseCenterPrint; + cglobals.ClearNotify = CG_ClearNotify; + cglobals.ClearCenterprint = CG_ClearCenterprint; + cglobals.NotifyMessage = CG_NotifyMessage; + cglobals.GetMonsterFlashOffset = CG_GetMonsterFlashOffset; + + cglobals.GetExtension = CG_GetExtension; + + return &cglobals; +} diff --git a/rerelease/cg_screen.cpp b/rerelease/cg_screen.cpp new file mode 100644 index 0000000..227fb9b --- /dev/null +++ b/rerelease/cg_screen.cpp @@ -0,0 +1,1781 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +#include "cg_local.h" + +constexpr int32_t STAT_MINUS = 10; // num frame for '-' stats digit +constexpr const char *sb_nums[2][11] = +{ + { "num_0", "num_1", "num_2", "num_3", "num_4", "num_5", + "num_6", "num_7", "num_8", "num_9", "num_minus" + }, + { "anum_0", "anum_1", "anum_2", "anum_3", "anum_4", "anum_5", + "anum_6", "anum_7", "anum_8", "anum_9", "anum_minus" + } +}; + +constexpr int32_t CHAR_WIDTH = 16; +constexpr int32_t CONCHAR_WIDTH = 8; + +static int32_t font_y_offset; + +constexpr rgba_t alt_color { 112, 255, 52, 255 }; + +static cvar_t *scr_usekfont; + +static cvar_t *scr_centertime; +static cvar_t *scr_printspeed; +static cvar_t *cl_notifytime; +static cvar_t *scr_maxlines; +static cvar_t *ui_acc_contrast; +static cvar_t* ui_acc_alttypeface; + +// static temp data used for hud +static struct +{ + struct { + struct { + char text[24]; + } table_cells[6]; + } table_rows[11]; // just enough to store 8 levels + header + total (+ one slack) + + size_t column_widths[6]; + int32_t num_rows = 0; + int32_t num_columns = 0; +} hud_temp; + +#include + +// max number of centerprints in the rotating buffer +constexpr size_t MAX_CENTER_PRINTS = 4; + +struct cl_bind_t { + std::string bind; + std::string purpose; +}; + +struct cl_centerprint_t { + std::vector binds; // binds + + std::vector lines; + bool instant; // don't type out + + size_t current_line; // current line we're typing out + size_t line_count; // byte count to draw on current line + bool finished; // done typing it out + uint64_t time_tick, time_off; // time to remove at +}; + +inline bool CG_ViewingLayout(const player_state_t *ps) +{ + return ps->stats[STAT_LAYOUTS] & (LAYOUTS_LAYOUT | LAYOUTS_INVENTORY); +} + +inline bool CG_InIntermission(const player_state_t *ps) +{ + return ps->stats[STAT_LAYOUTS] & LAYOUTS_INTERMISSION; +} + +inline bool CG_HudHidden(const player_state_t *ps) +{ + return ps->stats[STAT_LAYOUTS] & LAYOUTS_HIDE_HUD; +} + +layout_flags_t CG_LayoutFlags(const player_state_t *ps) +{ + return (layout_flags_t) ps->stats[STAT_LAYOUTS]; +} + +#include +#include + +constexpr size_t MAX_NOTIFY = 8; + +struct cl_notify_t { + std::string message; // utf8 message + bool is_active; // filled or not + bool is_chat; // green or not + uint64_t time; // rotate us when < CL_Time() +}; + +// per-splitscreen client hud storage +struct hud_data_t { + std::array centers; // list of centers + std::optional center_index; // current index we're drawing, or unset if none left + std::array notify; // list of notifies +}; + +static std::array hud_data; + +void CG_ClearCenterprint(int32_t isplit) +{ + hud_data[isplit].center_index = {}; +} + +void CG_ClearNotify(int32_t isplit) +{ + for (auto &msg : hud_data[isplit].notify) + msg.is_active = false; +} + +// if the top one is expired, cycle the ones ahead backwards (since +// the times are always increasing) +static void CG_Notify_CheckExpire(hud_data_t &data) +{ + while (data.notify[0].is_active && data.notify[0].time < cgi.CL_ClientTime()) + { + data.notify[0].is_active = false; + + for (size_t i = 1; i < MAX_NOTIFY; i++) + if (data.notify[i].is_active) + std::swap(data.notify[i], data.notify[i - 1]); + } +} + +// add notify to list +static void CG_AddNotify(hud_data_t &data, const char *msg, bool is_chat) +{ + size_t i = 0; + + if (scr_maxlines->integer <= 0) + return; + + const int max = min(MAX_NOTIFY, (size_t)scr_maxlines->integer); + + for (; i < max; i++) + if (!data.notify[i].is_active) + break; + + // none left, so expire the topmost one + if (i == max) + { + data.notify[0].time = 0; + CG_Notify_CheckExpire(data); + i = max - 1; + } + + data.notify[i].message.assign(msg); + data.notify[i].is_active = true; + data.notify[i].is_chat = is_chat; + data.notify[i].time = cgi.CL_ClientTime() + (cl_notifytime->value * 1000); +} + +// draw notifies +static void CG_DrawNotify(int32_t isplit, vrect_t hud_vrect, vrect_t hud_safe, int32_t scale) +{ + auto &data = hud_data[isplit]; + + CG_Notify_CheckExpire(data); + + int y; + + y = (hud_vrect.y * scale) + hud_safe.y; + + cgi.SCR_SetAltTypeface(ui_acc_alttypeface->integer && true); + + if (ui_acc_contrast->integer) + { + for (auto& msg : data.notify) + { + if (!msg.is_active || !msg.message.length()) + break; + + vec2_t sz = cgi.SCR_MeasureFontString(msg.message.c_str(), scale); + sz.x += 10; // extra padding for black bars + cgi.SCR_DrawColorPic((hud_vrect.x * scale) + hud_safe.x - 5, y, sz.x, 15 * scale, "_white", rgba_black); + y += 10 * scale; + } + } + + y = (hud_vrect.y * scale) + hud_safe.y; + for (auto &msg : data.notify) + { + if (!msg.is_active) + break; + + cgi.SCR_DrawFontString(msg.message.c_str(), (hud_vrect.x * scale) + hud_safe.x, y, scale, msg.is_chat ? alt_color : rgba_white, true, text_align_t::LEFT); + y += 10 * scale; + } + + cgi.SCR_SetAltTypeface(false); + + // draw text input (only the main player can really chat anyways...) + if (isplit == 0) + { + const char *input_msg; + bool input_team; + + if (cgi.CL_GetTextInput(&input_msg, &input_team)) + cgi.SCR_DrawFontString(G_Fmt("{}: {}", input_team ? "say_team" : "say", input_msg).data(), (hud_vrect.x * scale) + hud_safe.x, y, scale, rgba_white, true, text_align_t::LEFT); + } +} + +/* +============== +CG_DrawHUDString +============== +*/ +static int CG_DrawHUDString (const char *string, int x, int y, int centerwidth, int _xor, int scale, bool shadow = true) +{ + int margin; + char line[1024]; + int width; + int i; + + margin = x; + + while (*string) + { + // scan out one line of text from the string + width = 0; + while (*string && *string != '\n') + line[width++] = *string++; + line[width] = 0; + + vec2_t size; + + if (scr_usekfont->integer) + size = cgi.SCR_MeasureFontString(line, scale); + + if (centerwidth) + { + if (!scr_usekfont->integer) + x = margin + ((centerwidth - width*CONCHAR_WIDTH*scale))/2; + else + x = margin + ((centerwidth - size.x))/2; + } + else + x = margin; + + if (!scr_usekfont->integer) + { + for (i=0 ; iinteger) + y += CONCHAR_WIDTH * scale; + else + // TODO + y += 10 * scale;//size.y; + } + } + + return x; +} + +// Shamefully stolen from Kex +size_t FindStartOfUTF8Codepoint(const std::string &str, size_t pos) +{ + if(pos >= str.size()) + { + return std::string::npos; + } + + for(ptrdiff_t i = pos; i >= 0; i--) + { + const char &ch = str[i]; + + if((ch & 0x80) == 0) + { + // character is one byte + return i; + } + else if((ch & 0xC0) == 0x80) + { + // character is part of a multi-byte sequence, keep going + continue; + } + else + { + // character is the start of a multi-byte sequence, so stop now + return i; + } + } + + return std::string::npos; +} + +size_t FindEndOfUTF8Codepoint(const std::string &str, size_t pos) +{ + if(pos >= str.size()) + { + return std::string::npos; + } + + for(size_t i = pos; i < str.size(); i++) + { + const char &ch = str[i]; + + if((ch & 0x80) == 0) + { + // character is one byte + return i; + } + else if((ch & 0xC0) == 0x80) + { + // character is part of a multi-byte sequence, keep going + continue; + } + else + { + // character is the start of a multi-byte sequence, so stop now + return i; + } + } + + return std::string::npos; +} + +void CG_NotifyMessage(int32_t isplit, const char *msg, bool is_chat) +{ + CG_AddNotify(hud_data[isplit], msg, is_chat); +} + +// centerprint stuff +static cl_centerprint_t &CG_QueueCenterPrint(int isplit, bool instant) +{ + auto &icl = hud_data[isplit]; + + // just use first index + if (!icl.center_index.has_value() || instant) + { + icl.center_index = 0; + + for (size_t i = 1; i < MAX_CENTER_PRINTS; i++) + icl.centers[i].lines.clear(); + + return icl.centers[0]; + } + + // pick the next free index if we can find one + for (size_t i = 1; i < MAX_CENTER_PRINTS; i++) + { + auto ¢er = icl.centers[(icl.center_index.value() + i) % MAX_CENTER_PRINTS]; + + if (center.lines.empty()) + return center; + } + + // none, so update the current one (the new end of buffer) + // and skip ahead + auto ¢er = icl.centers[icl.center_index.value()]; + icl.center_index = (icl.center_index.value() + 1) % MAX_CENTER_PRINTS; + return center; +} + +/* +============== +SCR_CenterPrint + +Called for important messages that should stay in the center of the screen +for a few moments +============== +*/ +void CG_ParseCenterPrint (const char *str, int isplit, bool instant) // [Sam-KEX] Made 1st param const +{ + const char *s; + char line[64]; + int i, j, l; + + // handle center queueing + cl_centerprint_t ¢er = CG_QueueCenterPrint(isplit, instant); + + center.lines.clear(); + + // split the string into lines + size_t line_start = 0; + + std::string string(str); + + center.binds.clear(); + + // [Paril-KEX] pull out bindings. they'll always be at the start + while (string.compare(0, 6, "%bind:") == 0) + { + size_t end_of_bind = string.find_first_of('%', 1); + + if (end_of_bind == std::string::npos) + break; + + std::string bind = string.substr(6, end_of_bind - 6); + + if (auto purpose_index = bind.find_first_of(':'); purpose_index != std::string::npos) + center.binds.emplace_back(cl_bind_t { bind.substr(0, purpose_index), bind.substr(purpose_index + 1) }); + else + center.binds.emplace_back(cl_bind_t { bind }); + + string = string.substr(end_of_bind + 1); + } + + // echo it to the console + cgi.Com_Print("\n\n\35\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\37\n\n"); + + s = string.c_str(); + do + { + // scan the width of the line + for (l=0 ; l<40 ; l++) + if (s[l] == '\n' || !s[l]) + break; + for (i=0 ; i<(40-l)/2 ; i++) + line[i] = ' '; + + for (j=0 ; j line_start) + center.lines.emplace_back(string.c_str() + line_start, line_end - line_start); + else + center.lines.emplace_back(); + line_start = line_end + 1; + line_end++; + continue; + } + + line_end++; + } + + if (center.lines.empty()) + { + center.finished = true; + return; + } + + center.time_tick = cgi.CL_ClientRealTime() + (scr_printspeed->value * 1000); + center.instant = instant; + center.finished = false; + center.current_line = 0; + center.line_count = 0; +} + +static void CG_DrawCenterString( const player_state_t *ps, const vrect_t &hud_vrect, const vrect_t &hud_safe, int isplit, int scale, cl_centerprint_t ¢er) +{ + int32_t y = hud_vrect.y * scale; + + if (CG_ViewingLayout(ps)) + y += hud_safe.y; + else if (center.lines.size() <= 4) + y += (hud_vrect.height * 0.2f) * scale; + else + y += 48 * scale; + + int lineHeight = (scr_usekfont->integer ? 10 : 8) * scale; + if (ui_acc_alttypeface->integer) lineHeight *= 1.5f; + + // easy! + if (center.instant) + { + for (size_t i = 0; i < center.lines.size(); i++) + { + auto &line = center.lines[i]; + + cgi.SCR_SetAltTypeface(ui_acc_alttypeface->integer && true); + + if (ui_acc_contrast->integer && line.length()) + { + vec2_t sz = cgi.SCR_MeasureFontString(line.c_str(), scale); + sz.x += 10; // extra padding for black bars + int barY = ui_acc_alttypeface->integer ? y - 8 : y; + cgi.SCR_DrawColorPic((hud_vrect.x + hud_vrect.width / 2) * scale - (sz.x / 2), barY, sz.x, lineHeight, "_white", rgba_black); + } + CG_DrawHUDString(line.c_str(), (hud_vrect.x + hud_vrect.width/2 + -160) * scale, y, (320 / 2) * 2 * scale, 0, scale); + + cgi.SCR_SetAltTypeface(false); + + y += lineHeight; + } + + for (auto &bind : center.binds) + { + y += lineHeight * 2; + cgi.SCR_DrawBind(isplit, bind.bind.c_str(), bind.purpose.c_str(), (hud_vrect.x + (hud_vrect.width / 2)) * scale, y, scale); + } + + if (!center.finished) + { + center.finished = true; + center.time_off = cgi.CL_ClientRealTime() + (scr_centertime->value * 1000); + } + + return; + } + + // hard and annoying! + // check if it's time to fetch a new char + const uint64_t t = cgi.CL_ClientRealTime(); + + if (!center.finished) + { + if (center.time_tick < t) + { + center.time_tick = t + (scr_printspeed->value * 1000); + center.line_count = FindEndOfUTF8Codepoint(center.lines[center.current_line], center.line_count + 1); + + if (center.line_count == std::string::npos) + { + center.current_line++; + center.line_count = 0; + + if (center.current_line == center.lines.size()) + { + center.current_line--; + center.finished = true; + center.time_off = t + (scr_centertime->value * 1000); + } + } + } + } + + // smallish byte buffer for single line of data... + char buffer[256]; + + for (size_t i = 0; i < center.lines.size(); i++) + { + cgi.SCR_SetAltTypeface(ui_acc_alttypeface->integer && true); + + auto &line = center.lines[i]; + + buffer[0] = 0; + + if (center.finished || i != center.current_line) + Q_strlcpy(buffer, line.c_str(), sizeof(buffer)); + else + Q_strlcpy(buffer, line.c_str(), min(center.line_count + 1, sizeof(buffer))); + + int blinky_x; + + if (ui_acc_contrast->integer && line.length()) + { + vec2_t sz = cgi.SCR_MeasureFontString(line.c_str(), scale); + sz.x += 10; // extra padding for black bars + int barY = ui_acc_alttypeface->integer ? y - 8 : y; + cgi.SCR_DrawColorPic((hud_vrect.x + hud_vrect.width / 2) * scale - (sz.x / 2), barY, sz.x, lineHeight, "_white", rgba_black); + } + + if (buffer[0]) + blinky_x = CG_DrawHUDString(buffer, (hud_vrect.x + hud_vrect.width/2 + -160) * scale, y, (320 / 2) * 2 * scale, 0, scale); + else + blinky_x = (hud_vrect.width / 2) * scale; + + cgi.SCR_SetAltTypeface(false); + + if (i == center.current_line && !ui_acc_alttypeface->integer) + cgi.SCR_DrawChar(blinky_x, y, scale, 10 + ((cgi.CL_ClientRealTime() >> 8) & 1), true); + + y += lineHeight; + + if (i == center.current_line) + break; + } +} + +static void CG_CheckDrawCenterString( const player_state_t *ps, const vrect_t &hud_vrect, const vrect_t &hud_safe, int isplit, int scale ) +{ + if (CG_InIntermission(ps)) + return; + if (!hud_data[isplit].center_index.has_value()) + return; + + auto &data = hud_data[isplit]; + auto ¢er = data.centers[data.center_index.value()]; + + // ran out of center time + if (center.finished && center.time_off < cgi.CL_ClientRealTime()) + { + center.lines.clear(); + + size_t next_index = (data.center_index.value() + 1) % MAX_CENTER_PRINTS; + auto &next_center = data.centers[next_index]; + + // no more + if (next_center.lines.empty()) + { + data.center_index.reset(); + return; + } + + // buffer rotated; start timer now + data.center_index = next_index; + next_center.current_line = next_center.line_count = 0; + } + + if (!data.center_index.has_value()) + return; + + CG_DrawCenterString( ps, hud_vrect, hud_safe, isplit, scale, data.centers[data.center_index.value()] ); +} + +/* +============== +CG_DrawString +============== +*/ +static void CG_DrawString (int x, int y, int scale, const char *s, bool alt = false, bool shadow = true) +{ + while (*s) + { + cgi.SCR_DrawChar (x, y, scale, *s ^ (alt ? 0x80 : 0), shadow); + x+=8*scale; + s++; + } +} + +#include + +/* +============== +CG_DrawField +============== +*/ +static void CG_DrawField (int x, int y, int color, int width, int value, int scale) +{ + char num[16], *ptr; + int l; + int frame; + + if (width < 1) + return; + + // draw number string + if (width > 5) + width = 5; + + auto result = std::to_chars(num, num + sizeof(num) - 1, value); + *(result.ptr) = '\0'; + + l = (result.ptr - num); + + if (l > width) + l = width; + + x += (2 + CHAR_WIDTH*(width - l)) * scale; + + ptr = num; + while (*ptr && l) + { + if (*ptr == '-') + frame = STAT_MINUS; + else + frame = *ptr -'0'; + int w, h; + cgi.Draw_GetPicSize(&w, &h, sb_nums[color][frame]); + cgi.SCR_DrawPic(x, y, w * scale, h * scale, sb_nums[color][frame]); + x += CHAR_WIDTH * scale; + ptr++; + l--; + } +} + +// [Paril-KEX] +static void CG_DrawTable(int x, int y, uint32_t width, uint32_t height, int32_t scale) +{ + // half left + int32_t width_pixels = width; + x -= width_pixels / 2; + y += CONCHAR_WIDTH * scale; + // use Y as top though + + int32_t height_pixels = height; + + // draw border + // KEX_FIXME method that requires less chars + cgi.SCR_DrawChar(x - (CONCHAR_WIDTH * scale), y - (CONCHAR_WIDTH * scale), scale, 18, false); + cgi.SCR_DrawChar((x + width_pixels), y - (CONCHAR_WIDTH * scale), scale, 20, false); + cgi.SCR_DrawChar(x - (CONCHAR_WIDTH * scale), y + height_pixels, scale, 24, false); + cgi.SCR_DrawChar((x + width_pixels), y + height_pixels, scale, 26, false); + + for (int cx = x; cx < x + width_pixels; cx += CONCHAR_WIDTH * scale) + { + cgi.SCR_DrawChar(cx, y - (CONCHAR_WIDTH * scale), scale, 19, false); + cgi.SCR_DrawChar(cx, y + height_pixels, scale, 25, false); + } + + for (int cy = y; cy < y + height_pixels; cy += CONCHAR_WIDTH * scale) + { + cgi.SCR_DrawChar(x - (CONCHAR_WIDTH * scale), cy, scale, 21, false); + cgi.SCR_DrawChar((x + width_pixels), cy, scale, 23, false); + } + + cgi.SCR_DrawColorPic(x, y, width_pixels, height_pixels, "_white", { 0, 0, 0, 255 }); + + // draw in columns + for (int i = 0; i < hud_temp.num_columns; i++) + { + for (int r = 0, ry = y; r < hud_temp.num_rows; r++, ry += (CONCHAR_WIDTH + font_y_offset) * scale) + { + int x_offset = 0; + + // center + if (r == 0) + { + x_offset = ((hud_temp.column_widths[i]) / 2) - + ((cgi.SCR_MeasureFontString(hud_temp.table_rows[r].table_cells[i].text, scale).x) / 2); + } + // right align + else if (i != 0) + { + x_offset = (hud_temp.column_widths[i] - cgi.SCR_MeasureFontString(hud_temp.table_rows[r].table_cells[i].text, scale).x); + } + + //CG_DrawString(x + x_offset, ry, scale, hud_temp.table_rows[r].table_cells[i].text, r == 0, true); + cgi.SCR_DrawFontString(hud_temp.table_rows[r].table_cells[i].text, x + x_offset, ry - (font_y_offset * scale), scale, r == 0 ? alt_color : rgba_white, true, text_align_t::LEFT); + } + + x += (hud_temp.column_widths[i] + cgi.SCR_MeasureFontString(" ", 1).x); + } +} + +/* +================ +CG_ExecuteLayoutString + +================ +*/ +static void CG_ExecuteLayoutString (const char *s, vrect_t hud_vrect, vrect_t hud_safe, int32_t scale, int32_t playernum, const player_state_t *ps) +{ + int x, y; + int w, h; + int hx, hy; + int value; + const char *token; + int width; + int index; + + if (!s[0]) + return; + + x = hud_vrect.x; + y = hud_vrect.y; + width = 3; + + hx = 320 / 2; + hy = 240 / 2; + + bool flash_frame = (cgi.CL_ClientTime() % 1000) < 500; + + // if non-zero, parse but don't affect state + int32_t if_depth = 0; // current if statement depth + int32_t endif_depth = 0; // at this depth, toggle skip_depth + bool skip_depth = false; // whether we're in a dead stmt or not + + while (s) + { + token = COM_Parse (&s); + if (!strcmp(token, "xl")) + { + token = COM_Parse (&s); + if (!skip_depth) + x = ((hud_vrect.x + atoi(token)) * scale) + hud_safe.x; + continue; + } + if (!strcmp(token, "xr")) + { + token = COM_Parse (&s); + if (!skip_depth) + x = ((hud_vrect.x + hud_vrect.width + atoi(token)) * scale) - hud_safe.x; + continue; + } + if (!strcmp(token, "xv")) + { + token = COM_Parse (&s); + if (!skip_depth) + x = (hud_vrect.x + hud_vrect.width/2 + (atoi(token) - hx)) * scale; + continue; + } + + if (!strcmp(token, "yt")) + { + token = COM_Parse (&s); + if (!skip_depth) + y = ((hud_vrect.y + atoi(token)) * scale) + hud_safe.y; + continue; + } + if (!strcmp(token, "yb")) + { + token = COM_Parse (&s); + if (!skip_depth) + y = ((hud_vrect.y + hud_vrect.height + atoi(token)) * scale) - hud_safe.y; + continue; + } + if (!strcmp(token, "yv")) + { + token = COM_Parse (&s); + if (!skip_depth) + y = (hud_vrect.y + hud_vrect.height/2 + (atoi(token) - hy)) * scale; + continue; + } + + if (!strcmp(token, "pic")) + { // draw a pic from a stat number + token = COM_Parse (&s); + if (!skip_depth) + { + value = ps->stats[atoi(token)]; + if (value >= MAX_IMAGES) + cgi.Com_Error("Pic >= MAX_IMAGES"); + + const char *const pic = cgi.get_configstring(CS_IMAGES + value); + + if (pic && *pic) + { + cgi.Draw_GetPicSize (&w, &h, pic); + cgi.SCR_DrawPic (x, y, w * scale, h * scale, pic); + } + } + + continue; + } + + if (!strcmp(token, "client")) + { // draw a deathmatch client block + token = COM_Parse (&s); + if (!skip_depth) + { + x = (hud_vrect.x + hud_vrect.width/2 + (atoi(token) - hx)) * scale; + x += 8 * scale; + } + token = COM_Parse (&s); + if (!skip_depth) + { + y = (hud_vrect.y + hud_vrect.height/2 + (atoi(token) - hy)) * scale; + y += 7 * scale; + } + + token = COM_Parse (&s); + + if (!skip_depth) + { + value = atoi(token); + if (value >= MAX_CLIENTS || value < 0) + cgi.Com_Error("client >= MAX_CLIENTS"); + } + + int score, ping; + + token = COM_Parse (&s); + if (!skip_depth) + score = atoi(token); + + token = COM_Parse (&s); + if (!skip_depth) + { + ping = atoi(token); + + if (!scr_usekfont->integer) + CG_DrawString (x + 32 * scale, y, scale, cgi.CL_GetClientName(value)); + else + cgi.SCR_DrawFontString(cgi.CL_GetClientName(value), x + 32 * scale, y - (font_y_offset * scale), scale, rgba_white, true, text_align_t::LEFT); + + if (!scr_usekfont->integer) + CG_DrawString (x + 32 * scale, y + 10 * scale, scale, G_Fmt("{}", score).data(), true); + else + cgi.SCR_DrawFontString(G_Fmt("{}", score).data(), x + 32 * scale, y + (10 - font_y_offset) * scale, scale, rgba_white, true, text_align_t::LEFT); + + cgi.SCR_DrawPic(x + 96 * scale, y + 10 * scale, 9 * scale, 9 * scale, "ping"); + + if (!scr_usekfont->integer) + CG_DrawString (x + 73 * scale + 32 * scale, y + 10 * scale, scale, G_Fmt("{}", ping).data()); + else + cgi.SCR_DrawFontString (G_Fmt("{}", ping).data(), x + 107 * scale, y + (10 - font_y_offset) * scale, scale, rgba_white, true, text_align_t::LEFT); + } + continue; + } + + if (!strcmp(token, "ctf")) + { // draw a ctf client block + int score, ping; + + token = COM_Parse (&s); + if (!skip_depth) + x = (hud_vrect.x + hud_vrect.width/2 - hx + atoi(token)) * scale; + token = COM_Parse (&s); + if (!skip_depth) + y = (hud_vrect.y + hud_vrect.height/2 - hy + atoi(token)) * scale; + + token = COM_Parse (&s); + if (!skip_depth) + { + value = atoi(token); + if (value >= MAX_CLIENTS || value < 0) + cgi.Com_Error("client >= MAX_CLIENTS"); + } + + token = COM_Parse (&s); + if (!skip_depth) + score = atoi(token); + + token = COM_Parse (&s); + if (!skip_depth) + { + ping = atoi(token); + if (ping > 999) + ping = 999; + } + + token = COM_Parse (&s); + + if (!skip_depth) + { + + cgi.SCR_DrawFontString (G_Fmt("{}", score).data(), x, y - (font_y_offset * scale), scale, value == playernum ? alt_color : rgba_white, true, text_align_t::LEFT); + x += 3 * 9 * scale; + cgi.SCR_DrawFontString (G_Fmt("{}", ping).data(), x, y - (font_y_offset * scale), scale, value == playernum ? alt_color : rgba_white, true, text_align_t::LEFT); + x += 3 * 9 * scale; + cgi.SCR_DrawFontString (cgi.CL_GetClientName(value), x, y - (font_y_offset * scale), scale, value == playernum ? alt_color : rgba_white, true, text_align_t::LEFT); + + if (*token) + { + cgi.Draw_GetPicSize(&w, &h, token); + cgi.SCR_DrawPic(x - ((w + 2) * scale), y, w * scale, h * scale, token); + } + } + continue; + } + + if (!strcmp(token, "picn")) + { // draw a pic from a name + token = COM_Parse (&s); + if (!skip_depth) + { + cgi.Draw_GetPicSize(&w, &h, token); + cgi.SCR_DrawPic(x, y, w * scale, h * scale, token); + } + continue; + } + + if (!strcmp(token, "num")) + { // draw a number + token = COM_Parse (&s); + if (!skip_depth) + width = atoi(token); + token = COM_Parse (&s); + if (!skip_depth) + { + value = ps->stats[atoi(token)]; + CG_DrawField (x, y, 0, width, value, scale); + } + continue; + } + // [Paril-KEX] special handling for the lives number + else if (!strcmp(token, "lives_num")) + { + token = COM_Parse (&s); + if (!skip_depth) + { + value = ps->stats[atoi(token)]; + CG_DrawField(x, y, value <= 2 ? flash_frame : 0, 1, max(0, value - 2), scale); + } + } + + if (!strcmp(token, "hnum")) + { + // health number + if (!skip_depth) + { + int color; + + width = 3; + value = ps->stats[STAT_HEALTH]; + if (value > 25) + color = 0; // green + else if (value > 0) + color = flash_frame; // flash + else + color = 1; + if (ps->stats[STAT_FLASHES] & 1) + { + cgi.Draw_GetPicSize(&w, &h, "field_3"); + cgi.SCR_DrawPic(x, y, w * scale, h * scale, "field_3"); + } + + CG_DrawField (x, y, color, width, value, scale); + } + continue; + } + + if (!strcmp(token, "anum")) + { + // ammo number + if (!skip_depth) + { + int color; + + width = 3; + value = ps->stats[STAT_AMMO]; + + int32_t min_ammo = cgi.CL_GetWarnAmmoCount(ps->stats[STAT_ACTIVE_WHEEL_WEAPON]); + + if (!min_ammo) + min_ammo = 5; // back compat + + if (value > min_ammo) + color = 0; // green + else if (value >= 0) + color = flash_frame; // flash + else + continue; // negative number = don't show + if (ps->stats[STAT_FLASHES] & 4) + { + cgi.Draw_GetPicSize(&w, &h, "field_3"); + cgi.SCR_DrawPic(x, y, w * scale, h * scale, "field_3"); + } + + CG_DrawField (x, y, color, width, value, scale); + } + continue; + } + + if (!strcmp(token, "rnum")) + { + // armor number + if (!skip_depth) + { + int color; + + width = 3; + value = ps->stats[STAT_ARMOR]; + if (value < 0) + continue; + + color = 0; // green + if (ps->stats[STAT_FLASHES] & 2) + { + cgi.Draw_GetPicSize(&w, &h, "field_3"); + cgi.SCR_DrawPic(x, y, w * scale, h * scale, "field_3"); + } + + CG_DrawField (x, y, color, width, value, scale); + } + continue; + } + + if (!strcmp(token, "stat_string")) + { + token = COM_Parse (&s); + + if (!skip_depth) + { + index = atoi(token); + if (index < 0 || index >= MAX_STATS) + cgi.Com_Error("Bad stat_string index"); + index = ps->stats[index]; + + if (cgi.CL_ServerProtocol() <= PROTOCOL_VERSION_3XX) + index = CS_REMAP(index).start / CS_MAX_STRING_LENGTH; + + if (index < 0 || index >= MAX_CONFIGSTRINGS) + cgi.Com_Error("Bad stat_string index"); + if (!scr_usekfont->integer) + CG_DrawString (x, y, scale, cgi.get_configstring(index)); + else + cgi.SCR_DrawFontString(cgi.get_configstring(index), x, y - (font_y_offset * scale), scale, rgba_white, true, text_align_t::LEFT); + } + continue; + } + + if (!strcmp(token, "cstring")) + { + token = COM_Parse (&s); + if (!skip_depth) + CG_DrawHUDString (token, x, y, hx*2*scale, 0, scale); + continue; + } + + if (!strcmp(token, "string")) + { + token = COM_Parse (&s); + if (!skip_depth) + { + if (!scr_usekfont->integer) + CG_DrawString (x, y, scale, token); + else + cgi.SCR_DrawFontString(token, x, y - (font_y_offset * scale), scale, rgba_white, true, text_align_t::LEFT); + } + continue; + } + + if (!strcmp(token, "cstring2")) + { + token = COM_Parse (&s); + if (!skip_depth) + CG_DrawHUDString (token, x, y, hx*2*scale, 0x80, scale); + continue; + } + + if (!strcmp(token, "string2")) + { + token = COM_Parse (&s); + if (!skip_depth) + { + if (!scr_usekfont->integer) + CG_DrawString (x, y, scale, token, true); + else + cgi.SCR_DrawFontString(token, x, y - (font_y_offset * scale), scale, alt_color, true, text_align_t::LEFT); + } + continue; + } + + if (!strcmp(token, "if")) + { + // if stmt + token = COM_Parse (&s); + + if_depth++; + + // skip to endif + if (!skip_depth && !ps->stats[atoi(token)]) + { + skip_depth = true; + endif_depth = if_depth; + } + + continue; + } + + if (!strcmp(token, "ifgef")) + { + // if stmt + token = COM_Parse (&s); + + if_depth++; + + // skip to endif + if (!skip_depth && cgi.CL_ServerFrame() < atoi(token)) + { + skip_depth = true; + endif_depth = if_depth; + } + + continue; + } + + if (!strcmp(token, "endif")) + { + if (skip_depth && (if_depth == endif_depth)) + skip_depth = false; + + if_depth--; + + if (if_depth < 0) + cgi.Com_Error("endif without matching if"); + + continue; + } + + // localization stuff + if (!strcmp(token, "loc_stat_string")) + { + token = COM_Parse (&s); + + if (!skip_depth) + { + index = atoi(token); + if (index < 0 || index >= MAX_STATS) + cgi.Com_Error("Bad stat_string index"); + index = ps->stats[index]; + + if (cgi.CL_ServerProtocol() <= PROTOCOL_VERSION_3XX) + index = CS_REMAP(index).start / CS_MAX_STRING_LENGTH; + + if (index < 0 || index >= MAX_CONFIGSTRINGS) + cgi.Com_Error("Bad stat_string index"); + if (!scr_usekfont->integer) + CG_DrawString (x, y, scale, cgi.Localize(cgi.get_configstring(index), nullptr, 0)); + else + cgi.SCR_DrawFontString(cgi.Localize(cgi.get_configstring(index), nullptr, 0), x, y - (font_y_offset * scale), scale, rgba_white, true, text_align_t::LEFT); + } + continue; + } + + if (!strcmp(token, "loc_stat_rstring")) + { + token = COM_Parse (&s); + + if (!skip_depth) + { + index = atoi(token); + if (index < 0 || index >= MAX_STATS) + cgi.Com_Error("Bad stat_string index"); + index = ps->stats[index]; + + if (cgi.CL_ServerProtocol() <= PROTOCOL_VERSION_3XX) + index = CS_REMAP(index).start / CS_MAX_STRING_LENGTH; + + if (index < 0 || index >= MAX_CONFIGSTRINGS) + cgi.Com_Error("Bad stat_string index"); + const char *s = cgi.Localize(cgi.get_configstring(index), nullptr, 0); + if (!scr_usekfont->integer) + CG_DrawString (x - (strlen(s) * CONCHAR_WIDTH * scale), y, scale, s); + else + { + vec2_t size = cgi.SCR_MeasureFontString(s, scale); + cgi.SCR_DrawFontString(s, x - size.x, y - (font_y_offset * scale), scale, rgba_white, true, text_align_t::LEFT); + } + } + continue; + } + + if (!strcmp(token, "loc_stat_cstring")) + { + token = COM_Parse (&s); + + if (!skip_depth) + { + index = atoi(token); + if (index < 0 || index >= MAX_STATS) + cgi.Com_Error("Bad stat_string index"); + index = ps->stats[index]; + + if (cgi.CL_ServerProtocol() <= PROTOCOL_VERSION_3XX) + index = CS_REMAP(index).start / CS_MAX_STRING_LENGTH; + + if (index < 0 || index >= MAX_CONFIGSTRINGS) + cgi.Com_Error("Bad stat_string index"); + CG_DrawHUDString (cgi.Localize(cgi.get_configstring(index), nullptr, 0), x, y, hx*2*scale, 0, scale); + } + continue; + } + + if (!strcmp(token, "loc_stat_cstring2")) + { + token = COM_Parse (&s); + + if (!skip_depth) + { + index = atoi(token); + if (index < 0 || index >= MAX_STATS) + cgi.Com_Error("Bad stat_string index"); + index = ps->stats[index]; + + if (cgi.CL_ServerProtocol() <= PROTOCOL_VERSION_3XX) + index = CS_REMAP(index).start / CS_MAX_STRING_LENGTH; + + if (index < 0 || index >= MAX_CONFIGSTRINGS) + cgi.Com_Error("Bad stat_string index"); + CG_DrawHUDString (cgi.Localize(cgi.get_configstring(index), nullptr, 0), x, y, hx*2*scale, 0x80, scale); + } + continue; + } + + static char arg_tokens[MAX_LOCALIZATION_ARGS + 1][MAX_TOKEN_CHARS]; + static const char *arg_buffers[MAX_LOCALIZATION_ARGS]; + + if (!strcmp(token, "loc_cstring")) + { + int32_t num_args = atoi(COM_Parse (&s)); + + if (num_args < 0 || num_args >= MAX_LOCALIZATION_ARGS) + cgi.Com_Error("Bad loc string"); + + // parse base + token = COM_Parse (&s); + Q_strlcpy(arg_tokens[0], token, sizeof(arg_tokens[0])); + + // parse args + for (int32_t i = 0; i < num_args; i++) + { + token = COM_Parse (&s); + Q_strlcpy(arg_tokens[1 + i], token, sizeof(arg_tokens[0])); + arg_buffers[i] = arg_tokens[1 + i]; + } + + if (!skip_depth) + CG_DrawHUDString (cgi.Localize(arg_tokens[0], arg_buffers, num_args), x, y, hx*2*scale, 0, scale); + continue; + } + + if (!strcmp(token, "loc_string")) + { + int32_t num_args = atoi(COM_Parse (&s)); + + if (num_args < 0 || num_args >= MAX_LOCALIZATION_ARGS) + cgi.Com_Error("Bad loc string"); + + // parse base + token = COM_Parse (&s); + Q_strlcpy(arg_tokens[0], token, sizeof(arg_tokens[0])); + + // parse args + for (int32_t i = 0; i < num_args; i++) + { + token = COM_Parse (&s); + Q_strlcpy(arg_tokens[1 + i], token, sizeof(arg_tokens[0])); + arg_buffers[i] = arg_tokens[1 + i]; + } + + if (!skip_depth) + { + if (!scr_usekfont->integer) + CG_DrawString (x, y, scale, cgi.Localize(arg_tokens[0], arg_buffers, num_args)); + else + cgi.SCR_DrawFontString(cgi.Localize(arg_tokens[0], arg_buffers, num_args), x, y - (font_y_offset * scale), scale, rgba_white, true, text_align_t::LEFT); + } + continue; + } + + if (!strcmp(token, "loc_cstring2")) + { + int32_t num_args = atoi(COM_Parse (&s)); + + if (num_args < 0 || num_args >= MAX_LOCALIZATION_ARGS) + cgi.Com_Error("Bad loc string"); + + // parse base + token = COM_Parse (&s); + Q_strlcpy(arg_tokens[0], token, sizeof(arg_tokens[0])); + + // parse args + for (int32_t i = 0; i < num_args; i++) + { + token = COM_Parse (&s); + Q_strlcpy(arg_tokens[1 + i], token, sizeof(arg_tokens[0])); + arg_buffers[i] = arg_tokens[1 + i]; + } + + if (!skip_depth) + CG_DrawHUDString (cgi.Localize(arg_tokens[0], arg_buffers, num_args), x, y, hx*2*scale, 0x80, scale); + continue; + } + + if (!strcmp(token, "loc_string2") || !strcmp(token, "loc_rstring2") || + !strcmp(token, "loc_string") || !strcmp(token, "loc_rstring")) + { + bool green = token[strlen(token) - 1] == '2'; + bool rightAlign = !Q_strncasecmp(token, "loc_rstring", strlen("loc_rstring")); + int32_t num_args = atoi(COM_Parse (&s)); + + if (num_args < 0 || num_args >= MAX_LOCALIZATION_ARGS) + cgi.Com_Error("Bad loc string"); + + // parse base + token = COM_Parse (&s); + Q_strlcpy(arg_tokens[0], token, sizeof(arg_tokens[0])); + + // parse args + for (int32_t i = 0; i < num_args; i++) + { + token = COM_Parse (&s); + Q_strlcpy(arg_tokens[1 + i], token, sizeof(arg_tokens[0])); + arg_buffers[i] = arg_tokens[1 + i]; + } + + if (!skip_depth) + { + const char *locStr = cgi.Localize(arg_tokens[0], arg_buffers, num_args); + int xOffs = 0; + if (rightAlign) + { + xOffs = scr_usekfont->integer ? cgi.SCR_MeasureFontString(locStr, scale).x : (strlen(locStr) * CONCHAR_WIDTH * scale); + } + + if (!scr_usekfont->integer) + CG_DrawString (x - xOffs, y, scale, locStr, green); + else + cgi.SCR_DrawFontString(locStr, x - xOffs, y - (font_y_offset * scale), scale, green ? alt_color : rgba_white, true, text_align_t::LEFT); + } + continue; + } + + // draw time remaining + if (!strcmp(token, "time_limit")) + { + // end frame + token = COM_Parse (&s); + + if (!skip_depth) + { + int32_t end_frame = atoi(token); + + if (end_frame < cgi.CL_ServerFrame()) + continue; + + uint64_t remaining_ms = (end_frame - cgi.CL_ServerFrame()) * cgi.frame_time_ms; + + const bool green = true; + arg_buffers[0] = G_Fmt("{:02}:{:02}", (remaining_ms / 1000) / 60, (remaining_ms / 1000) % 60).data(); + + const char *locStr = cgi.Localize("$g_score_time", arg_buffers, 1); + int xOffs = scr_usekfont->integer ? cgi.SCR_MeasureFontString(locStr, scale).x : (strlen(locStr) * CONCHAR_WIDTH * scale); + if (!scr_usekfont->integer) + CG_DrawString (x - xOffs, y, scale, locStr, green); + else + cgi.SCR_DrawFontString(locStr, x - xOffs, y - (font_y_offset * scale), scale, green ? alt_color : rgba_white, true, text_align_t::LEFT); + } + } + + // draw client dogtag + if (!strcmp(token, "dogtag")) + { + token = COM_Parse (&s); + + if (!skip_depth) + { + value = atoi(token); + if (value >= MAX_CLIENTS || value < 0) + cgi.Com_Error("client >= MAX_CLIENTS"); + + const std::string_view path = G_Fmt("/tags/{}", cgi.CL_GetClientDogtag(value)); + cgi.SCR_DrawPic(x, y, 198 * scale, 32 * scale, path.data()); + } + } + + if (!strcmp(token, "start_table")) + { + token = COM_Parse (&s); + value = atoi(token); + + if (!skip_depth) + { + if (value >= q_countof(hud_temp.table_rows[0].table_cells)) + cgi.Com_Error("table too big"); + + hud_temp.num_columns = value; + hud_temp.num_rows = 1; + + for (int i = 0; i < value; i++) + hud_temp.column_widths[i] = 0; + } + + for (int i = 0; i < value; i++) + { + token = COM_Parse (&s); + if (!skip_depth) + { + token = cgi.Localize(token, nullptr, 0); + Q_strlcpy(hud_temp.table_rows[0].table_cells[i].text, token, sizeof(hud_temp.table_rows[0].table_cells[i].text)); + hud_temp.column_widths[i] = max(hud_temp.column_widths[i], (size_t) cgi.SCR_MeasureFontString(hud_temp.table_rows[0].table_cells[i].text, scale).x); + } + } + } + + if (!strcmp(token, "table_row")) + { + token = COM_Parse (&s); + value = atoi(token); + + if (!skip_depth) + { + if (hud_temp.num_rows >= q_countof(hud_temp.table_rows)) + { + cgi.Com_Error("table too big"); + return; + } + } + + auto &row = hud_temp.table_rows[hud_temp.num_rows]; + + for (int i = 0; i < value; i++) + { + token = COM_Parse (&s); + if (!skip_depth) + { + Q_strlcpy(row.table_cells[i].text, token, sizeof(row.table_cells[i].text)); + hud_temp.column_widths[i] = max(hud_temp.column_widths[i], (size_t) cgi.SCR_MeasureFontString(row.table_cells[i].text, scale).x); + } + } + + if (!skip_depth) + { + for (int i = value; i < hud_temp.num_columns; i++) + row.table_cells[i].text[0] = '\0'; + + hud_temp.num_rows++; + } + } + + if (!strcmp(token, "draw_table")) + { + if (!skip_depth) + { + // in scaled pixels, incl padding between elements + uint32_t total_inner_table_width = 0; + + for (int i = 0; i < hud_temp.num_columns; i++) + { + if (i != 0) + total_inner_table_width += cgi.SCR_MeasureFontString(" ", scale).x; + + total_inner_table_width += hud_temp.column_widths[i]; + } + + // in scaled pixels + uint32_t total_table_height = hud_temp.num_rows * (CONCHAR_WIDTH + font_y_offset) * scale; + + CG_DrawTable(x, y, total_inner_table_width, total_table_height, scale); + } + } + + if (!strcmp(token, "stat_pname")) + { + token = COM_Parse(&s); + + if (!skip_depth) + { + index = atoi(token); + if (index < 0 || index >= MAX_STATS) + cgi.Com_Error("Bad stat_string index"); + index = ps->stats[index] - 1; + + if (!scr_usekfont->integer) + CG_DrawString(x, y, scale, cgi.CL_GetClientName(index)); + else + cgi.SCR_DrawFontString(cgi.CL_GetClientName(index), x, y - (font_y_offset * scale), scale, rgba_white, true, text_align_t::LEFT); + } + continue; + } + + if (!strcmp(token, "health_bars")) + { + if (skip_depth) + continue; + + const byte *stat = reinterpret_cast(&ps->stats[STAT_HEALTH_BARS]); + const char *name = cgi.Localize(cgi.get_configstring(CONFIG_HEALTH_BAR_NAME), nullptr, 0); + + CG_DrawHUDString(name, (hud_vrect.x + hud_vrect.width/2 + -160) * scale, y, (320 / 2) * 2 * scale, 0, scale); + + float bar_width = ((hud_vrect.width * scale) - (hud_safe.x * 2)) * 0.50f; + float bar_height = 4 * scale; + + y += cgi.SCR_FontLineHeight(scale); + + float x = ((hud_vrect.x + (hud_vrect.width * 0.5f)) * scale) - (bar_width * 0.5f); + + // 2 health bars, hardcoded + for (size_t i = 0; i < 2; i++, stat++) + { + if (!(*stat & 0b10000000)) + continue; + + float percent = (*stat & 0b01111111) / 127.f; + + cgi.SCR_DrawColorPic(x, y, bar_width + scale, bar_height + scale, "_white", rgba_black); + + if (percent > 0) + cgi.SCR_DrawColorPic(x, y, bar_width * percent, bar_height, "_white", rgba_red); + if (percent < 1) + cgi.SCR_DrawColorPic(x + (bar_width * percent), y, bar_width * (1.f - percent), bar_height, "_white", { 80, 80, 80, 255 }); + + y += bar_height * 3; + } + } + + if (!strcmp(token, "story")) + { + const char *story_str = cgi.get_configstring(CONFIG_STORY); + + if (!*story_str) + continue; + + const char *localized = cgi.Localize(story_str, nullptr, 0); + vec2_t size = cgi.SCR_MeasureFontString(localized, scale); + float centerx = ((hud_vrect.x + (hud_vrect.width * 0.5f)) * scale); + float centery = ((hud_vrect.y + (hud_vrect.height * 0.5f)) * scale) - (size.y * 0.5f); + + cgi.SCR_DrawFontString(localized, centerx, centery, scale, rgba_white, true, text_align_t::CENTER); + } + } + + if (skip_depth) + cgi.Com_Error("if with no matching endif"); +} + +static cvar_t *cl_skipHud; +static cvar_t *cl_paused; + +/* +================ +CL_DrawInventory +================ +*/ +constexpr size_t DISPLAY_ITEMS = 19; + +static void CG_DrawInventory(const player_state_t *ps, const std::array &inventory, vrect_t hud_vrect, int32_t scale) +{ + int i; + int num, selected_num, item; + int index[MAX_ITEMS]; + int x, y; + int width, height; + int selected; + int top; + + selected = ps->stats[STAT_SELECTED_ITEM]; + + num = 0; + selected_num = 0; + for (i=0 ; iinteger) + { + CG_DrawString(x, y, scale, + G_Fmt("{:3} {}", inventory[item], + cgi.Localize(cgi.get_configstring(CS_ITEMS + item), nullptr, 0)).data(), + item == selected, false); + } + else + { + const char *string = G_Fmt("{}", inventory[item]).data(); + vec2_t strSz = cgi.SCR_MeasureFontString(string, scale); + cgi.SCR_DrawFontString(string, x + (216 * scale) - (16 * scale), y - (font_y_offset * scale), scale, (item == selected) ? alt_color : rgba_white, true, text_align_t::RIGHT); + + string = cgi.Localize(cgi.get_configstring(CS_ITEMS + item), nullptr, 0); + cgi.SCR_DrawFontString(string, x + (16 * scale), y - (font_y_offset * scale), scale, (item == selected) ? alt_color : rgba_white, true, text_align_t::LEFT); + } + + y += 8 * scale; + } +} + +extern uint64_t cgame_init_time; + +void CG_DrawHUD (int32_t isplit, const cg_server_data_t *data, vrect_t hud_vrect, vrect_t hud_safe, int32_t scale, int32_t playernum, const player_state_t *ps) +{ + if (cgi.CL_InAutoDemoLoop()) + { + if (cl_paused->integer) return; // demo is paused, menu is open + + uint64_t time = cgi.CL_ClientRealTime() - cgame_init_time; + if (time < 20000 && + (time % 4000) < 2000) + cgi.SCR_DrawFontString(cgi.Localize("$m_eou_press_button", nullptr, 0), hud_vrect.width * 0.5f * scale, (hud_vrect.height - 64.f) * scale, scale, rgba_green, true, text_align_t::CENTER); + return; + } + + // draw HUD + if (!cl_skipHud->integer && !(ps->stats[STAT_LAYOUTS] & LAYOUTS_HIDE_HUD)) + CG_ExecuteLayoutString(cgi.get_configstring(CS_STATUSBAR), hud_vrect, hud_safe, scale, playernum, ps); + + // draw centerprint string + CG_CheckDrawCenterString(ps, hud_vrect, hud_safe, isplit, scale); + + // draw notify + CG_DrawNotify(isplit, hud_vrect, hud_safe, scale); + + // svc_layout still drawn with hud off + if (ps->stats[STAT_LAYOUTS] & LAYOUTS_LAYOUT) + CG_ExecuteLayoutString(data->layout, hud_vrect, hud_safe, scale, playernum, ps); + + // inventory too + if (ps->stats[STAT_LAYOUTS] & LAYOUTS_INVENTORY) + CG_DrawInventory(ps, data->inventory, hud_vrect, scale); +} + +/* +================ +CG_TouchPics + +================ +*/ +void CG_TouchPics() +{ + for (auto &nums : sb_nums) + for (auto &str : nums) + cgi.Draw_RegisterPic(str); + + cgi.Draw_RegisterPic("inventory"); + + font_y_offset = (cgi.SCR_FontLineHeight(1) - CONCHAR_WIDTH) / 2; +} + +void CG_InitScreen() +{ + cl_paused = cgi.cvar("paused", "0", CVAR_NOFLAGS); + cl_skipHud = cgi.cvar("cl_skipHud", "0", CVAR_ARCHIVE); + scr_usekfont = cgi.cvar("scr_usekfont", "1", CVAR_NOFLAGS); + + scr_centertime = cgi.cvar ("scr_centertime", "5.0", CVAR_ARCHIVE); // [Sam-KEX] Changed from 2.5 + scr_printspeed = cgi.cvar ("scr_printspeed", "0.04", CVAR_NOFLAGS); // [Sam-KEX] Changed from 8 + cl_notifytime = cgi.cvar ("cl_notifytime", "5.0", CVAR_ARCHIVE); + scr_maxlines = cgi.cvar ("scr_maxlines", "4", CVAR_ARCHIVE); + ui_acc_contrast = cgi.cvar ("ui_acc_contrast", "0", CVAR_NOFLAGS); + ui_acc_alttypeface = cgi.cvar("ui_acc_alttypeface", "0", CVAR_NOFLAGS); + + hud_data = {}; +} \ No newline at end of file diff --git a/rerelease/ctf/g_ctf.cpp b/rerelease/ctf/g_ctf.cpp new file mode 100644 index 0000000..6ebcdd0 --- /dev/null +++ b/rerelease/ctf/g_ctf.cpp @@ -0,0 +1,3832 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +#include "../g_local.h" +#include "../m_player.h" + +#include + +enum match_t +{ + MATCH_NONE, + MATCH_SETUP, + MATCH_PREGAME, + MATCH_GAME, + MATCH_POST +}; + +enum elect_t +{ + ELECT_NONE, + ELECT_MATCH, + ELECT_ADMIN, + ELECT_MAP +}; + +struct ctfgame_t +{ + int team1, team2; + int total1, total2; // these are only set when going into intermission except in teamplay + gtime_t last_flag_capture; + int last_capture_team; + + match_t match; // match state + gtime_t matchtime; // time for match start/end (depends on state) + int lasttime; // last time update, explicitly truncated to seconds + bool countdown; // has audio countdown started? + + elect_t election; // election type + edict_t *etarget; // for admin election, who's being elected + char elevel[32]; // for map election, target level + int evotes; // votes so far + int needvotes; // votes needed + gtime_t electtime; // remaining time until election times out + char emsg[256]; // election name + int warnactive; // true if stat string 30 is active + + ghost_t ghosts[MAX_CLIENTS]; // ghost codes +}; + +ctfgame_t ctfgame; + +cvar_t *ctf; +cvar_t *teamplay; +cvar_t *g_teamplay_force_join; + +// [Paril-KEX] +bool G_TeamplayEnabled() +{ + return ctf->integer || teamplay->integer; +} + +// [Paril-KEX] +void G_AdjustTeamScore(ctfteam_t team, int32_t offset) +{ + if (team == CTF_TEAM1) + ctfgame.total1 += offset; + else if (team == CTF_TEAM2) + ctfgame.total2 += offset; +} + +cvar_t *competition; +cvar_t *matchlock; +cvar_t *electpercentage; +cvar_t *matchtime; +cvar_t *matchsetuptime; +cvar_t *matchstarttime; +cvar_t *admin_password; +cvar_t *allow_admin; +cvar_t *warp_list; +cvar_t *warn_unbalanced; + +// Index for various CTF pics, this saves us from calling gi.imageindex +// all the time and saves a few CPU cycles since we don't have to do +// a bunch of string compares all the time. +// These are set in CTFPrecache() called from worldspawn +int imageindex_i_ctf1; +int imageindex_i_ctf2; +int imageindex_i_ctf1d; +int imageindex_i_ctf2d; +int imageindex_i_ctf1t; +int imageindex_i_ctf2t; +int imageindex_i_ctfj; +int imageindex_sbfctf1; +int imageindex_sbfctf2; +int imageindex_ctfsb1; +int imageindex_ctfsb2; +int modelindex_flag1, modelindex_flag2; // [Paril-KEX] + +constexpr item_id_t tech_ids[] = { IT_TECH_RESISTANCE, IT_TECH_STRENGTH, IT_TECH_HASTE, IT_TECH_REGENERATION }; + +/*--------------------------------------------------------------------------*/ + +#ifndef KEX_Q2_GAME +/* +================= +findradius + +Returns entities that have origins within a spherical area + +findradius (origin, radius) +================= +*/ +static edict_t *loc_findradius(edict_t *from, const vec3_t &org, float rad) +{ + vec3_t eorg; + int j; + + if (!from) + from = g_edicts; + else + from++; + for (; from < &g_edicts[globals.num_edicts]; from++) + { + if (!from->inuse) + continue; + for (j = 0; j < 3; j++) + eorg[j] = org[j] - (from->s.origin[j] + (from->mins[j] + from->maxs[j]) * 0.5f); + if (eorg.length() > rad) + continue; + return from; + } + + return nullptr; +} +#endif + +static void loc_buildboxpoints(vec3_t (&p)[8], const vec3_t &org, const vec3_t &mins, const vec3_t &maxs) +{ + p[0] = org + mins; + p[1] = p[0]; + p[1][0] -= mins[0]; + p[2] = p[0]; + p[2][1] -= mins[1]; + p[3] = p[0]; + p[3][0] -= mins[0]; + p[3][1] -= mins[1]; + p[4] = org + maxs; + p[5] = p[4]; + p[5][0] -= maxs[0]; + p[6] = p[0]; + p[6][1] -= maxs[1]; + p[7] = p[0]; + p[7][0] -= maxs[0]; + p[7][1] -= maxs[1]; +} + +static bool loc_CanSee(edict_t *targ, edict_t *inflictor) +{ + trace_t trace; + vec3_t targpoints[8]; + int i; + vec3_t viewpoint; + + // bmodels need special checking because their origin is 0,0,0 + if (targ->movetype == MOVETYPE_PUSH) + return false; // bmodels not supported + + loc_buildboxpoints(targpoints, targ->s.origin, targ->mins, targ->maxs); + + viewpoint = inflictor->s.origin; + viewpoint[2] += inflictor->viewheight; + + for (i = 0; i < 8; i++) + { + trace = gi.traceline(viewpoint, targpoints[i], inflictor, MASK_SOLID); + if (trace.fraction == 1.0f) + return true; + } + + return false; +} +# + +/*--------------------------------------------------------------------------*/ + +void CTFSpawn() +{ + memset(&ctfgame, 0, sizeof(ctfgame)); + CTFSetupTechSpawn(); + + if (competition->integer > 1) + { + ctfgame.match = MATCH_SETUP; + ctfgame.matchtime = level.time + gtime_t::from_min(matchsetuptime->value); + } +} + +void CTFInit() +{ + ctf = gi.cvar("ctf", "0", CVAR_SERVERINFO | CVAR_LATCH); + competition = gi.cvar("competition", "0", CVAR_SERVERINFO); + matchlock = gi.cvar("matchlock", "1", CVAR_SERVERINFO); + electpercentage = gi.cvar("electpercentage", "66", CVAR_NOFLAGS); + matchtime = gi.cvar("matchtime", "20", CVAR_SERVERINFO); + matchsetuptime = gi.cvar("matchsetuptime", "10", CVAR_NOFLAGS); + matchstarttime = gi.cvar("matchstarttime", "20", CVAR_NOFLAGS); + admin_password = gi.cvar("admin_password", "", CVAR_NOFLAGS); + allow_admin = gi.cvar("allow_admin", "1", CVAR_NOFLAGS); + warp_list = gi.cvar("warp_list", "q2ctf1 q2ctf2 q2ctf3 q2ctf4 q2ctf5", CVAR_NOFLAGS); + warn_unbalanced = gi.cvar("warn_unbalanced", "0", CVAR_NOFLAGS); +} + +/* + * Precache CTF items + */ + +void CTFPrecache() +{ + imageindex_i_ctf1 = gi.imageindex("i_ctf1"); + imageindex_i_ctf2 = gi.imageindex("i_ctf2"); + imageindex_i_ctf1d = gi.imageindex("i_ctf1d"); + imageindex_i_ctf2d = gi.imageindex("i_ctf2d"); + imageindex_i_ctf1t = gi.imageindex("i_ctf1t"); + imageindex_i_ctf2t = gi.imageindex("i_ctf2t"); + imageindex_i_ctfj = gi.imageindex("i_ctfj"); + imageindex_sbfctf1 = gi.imageindex("sbfctf1"); + imageindex_sbfctf2 = gi.imageindex("sbfctf2"); + imageindex_ctfsb1 = gi.imageindex("tag4"); + imageindex_ctfsb2 = gi.imageindex("tag5"); + modelindex_flag1 = gi.modelindex("players/male/flag1.md2"); + modelindex_flag2 = gi.modelindex("players/male/flag2.md2"); + + PrecacheItem(GetItemByIndex(IT_WEAPON_GRAPPLE)); +} + +/*--------------------------------------------------------------------------*/ + +const char *CTFTeamName(int team) +{ + switch (team) + { + case CTF_TEAM1: + return "RED"; + case CTF_TEAM2: + return "BLUE"; + case CTF_NOTEAM: + return "SPECTATOR"; + } + return "UNKNOWN"; // Hanzo pointed out this was spelled wrong as "UKNOWN" +} + +const char *CTFOtherTeamName(int team) +{ + switch (team) + { + case CTF_TEAM1: + return "BLUE"; + case CTF_TEAM2: + return "RED"; + } + return "UNKNOWN"; // Hanzo pointed out this was spelled wrong as "UKNOWN" +} + +int CTFOtherTeam(int team) +{ + switch (team) + { + case CTF_TEAM1: + return CTF_TEAM2; + case CTF_TEAM2: + return CTF_TEAM1; + } + return -1; // invalid value +} + +/*--------------------------------------------------------------------------*/ + +float PlayersRangeFromSpot(edict_t *spot); +bool SpawnPointClear(edict_t *spot); + +void CTFAssignSkin(edict_t *ent, const char *s) +{ + int playernum = ent - g_edicts - 1; + std::string_view t(s); + + if (size_t i = t.find_first_of('/'); i != std::string_view::npos) + t = t.substr(0, i + 1); + else + t = "male/"; + + switch (ent->client->resp.ctf_team) + { + case CTF_TEAM1: + t = G_Fmt("{}\\{}{}\\default", ent->client->pers.netname, t, CTF_TEAM1_SKIN); + break; + case CTF_TEAM2: + t = G_Fmt("{}\\{}{}\\default", ent->client->pers.netname, t, CTF_TEAM2_SKIN); + break; + default: + t = G_Fmt("{}\\{}\\default", ent->client->pers.netname, s); + break; + } + + gi.configstring(CS_PLAYERSKINS + playernum, t.data()); + + // gi.LocClient_Print(ent, PRINT_HIGH, "$g_assigned_team", ent->client->pers.netname); +} + +void CTFAssignTeam(gclient_t *who) +{ + edict_t *player; + uint32_t team1count = 0, team2count = 0; + + who->resp.ctf_state = 0; + + if (!g_teamplay_force_join->integer && !(g_edicts[1 + (who - game.clients)].svflags & SVF_BOT)) + { + who->resp.ctf_team = CTF_NOTEAM; + return; + } + + for (uint32_t i = 1; i <= game.maxclients; i++) + { + player = &g_edicts[i]; + + if (!player->inuse || player->client == who) + continue; + + switch (player->client->resp.ctf_team) + { + case CTF_TEAM1: + team1count++; + break; + case CTF_TEAM2: + team2count++; + break; + default: + break; + } + } + if (team1count < team2count) + who->resp.ctf_team = CTF_TEAM1; + else if (team2count < team1count) + who->resp.ctf_team = CTF_TEAM2; + else if (brandom()) + who->resp.ctf_team = CTF_TEAM1; + else + who->resp.ctf_team = CTF_TEAM2; +} + +/* +================ +SelectCTFSpawnPoint + +go to a ctf point, but NOT the two points closest +to other players +================ +*/ +edict_t *SelectCTFSpawnPoint(edict_t *ent, bool force_spawn) +{ + if (ent->client->resp.ctf_state) + { + select_spawn_result_t result = SelectDeathmatchSpawnPoint(g_dm_spawn_farthest->integer, force_spawn, false); + + if (result.any_valid) + return result.spot; + } + + const char *cname; + + switch (ent->client->resp.ctf_team) + { + case CTF_TEAM1: + cname = "info_player_team1"; + break; + case CTF_TEAM2: + cname = "info_player_team2"; + break; + default: + { + select_spawn_result_t result = SelectDeathmatchSpawnPoint(g_dm_spawn_farthest->integer, force_spawn, true); + + if (result.any_valid) + return result.spot; + + gi.Com_Error("can't find suitable spectator spawn point"); + return nullptr; + } + } + + static std::vector spawn_points; + edict_t *spot = nullptr; + + spawn_points.clear(); + + while ((spot = G_FindByString<&edict_t::classname>(spot, cname)) != nullptr) + spawn_points.push_back(spot); + + if (!spawn_points.size()) + { + select_spawn_result_t result = SelectDeathmatchSpawnPoint(g_dm_spawn_farthest->integer, force_spawn, true); + + if (!result.any_valid) + gi.Com_Error("can't find suitable CTF spawn point"); + + return result.spot; + } + + std::shuffle(spawn_points.begin(), spawn_points.end(), mt_rand); + + for (auto &point : spawn_points) + if (SpawnPointClear(point)) + return point; + + if (force_spawn) + return random_element(spawn_points); + + return nullptr; +} + +/*------------------------------------------------------------------------*/ +/* +CTFFragBonuses + +Calculate the bonuses for flag defense, flag carrier defense, etc. +Note that bonuses are not cumaltive. You get one, they are in importance +order. +*/ +void CTFFragBonuses(edict_t *targ, edict_t *inflictor, edict_t *attacker) +{ + edict_t *ent; + item_id_t flag_item, enemy_flag_item; + int otherteam; + edict_t *flag, *carrier = nullptr; + const char *c; + vec3_t v1, v2; + + if (targ->client && attacker->client) + { + if (attacker->client->resp.ghost) + if (attacker != targ) + attacker->client->resp.ghost->kills++; + if (targ->client->resp.ghost) + targ->client->resp.ghost->deaths++; + } + + // no bonus for fragging yourself + if (!targ->client || !attacker->client || targ == attacker) + return; + + otherteam = CTFOtherTeam(targ->client->resp.ctf_team); + if (otherteam < 0) + return; // whoever died isn't on a team + + // same team, if the flag at base, check to he has the enemy flag + if (targ->client->resp.ctf_team == CTF_TEAM1) + { + flag_item = IT_FLAG1; + enemy_flag_item = IT_FLAG2; + } + else + { + flag_item = IT_FLAG2; + enemy_flag_item = IT_FLAG1; + } + + // did the attacker frag the flag carrier? + if (targ->client->pers.inventory[enemy_flag_item]) + { + attacker->client->resp.ctf_lastfraggedcarrier = level.time; + attacker->client->resp.score += CTF_FRAG_CARRIER_BONUS; + gi.LocClient_Print(attacker, PRINT_MEDIUM, "$g_bonus_enemy_carrier", + CTF_FRAG_CARRIER_BONUS); + + // the target had the flag, clear the hurt carrier + // field on the other team + for (uint32_t i = 1; i <= game.maxclients; i++) + { + ent = g_edicts + i; + if (ent->inuse && ent->client->resp.ctf_team == otherteam) + ent->client->resp.ctf_lasthurtcarrier = 0_ms; + } + return; + } + + if (targ->client->resp.ctf_lasthurtcarrier && + level.time - targ->client->resp.ctf_lasthurtcarrier < CTF_CARRIER_DANGER_PROTECT_TIMEOUT && + !attacker->client->pers.inventory[flag_item]) + { + // attacker is on the same team as the flag carrier and + // fragged a guy who hurt our flag carrier + attacker->client->resp.score += CTF_CARRIER_DANGER_PROTECT_BONUS; + gi.LocBroadcast_Print(PRINT_MEDIUM, "$g_bonus_flag_defense", + attacker->client->pers.netname, + CTFTeamName(attacker->client->resp.ctf_team)); + if (attacker->client->resp.ghost) + attacker->client->resp.ghost->carrierdef++; + return; + } + + // flag and flag carrier area defense bonuses + + // we have to find the flag and carrier entities + + // find the flag + switch (attacker->client->resp.ctf_team) + { + case CTF_TEAM1: + c = "item_flag_team1"; + break; + case CTF_TEAM2: + c = "item_flag_team2"; + break; + default: + return; + } + + flag = nullptr; + while ((flag = G_FindByString<&edict_t::classname>(flag, c)) != nullptr) + { + if (!(flag->spawnflags & SPAWNFLAG_ITEM_DROPPED)) + break; + } + + if (!flag) + return; // can't find attacker's flag + + // find attacker's team's flag carrier + for (uint32_t i = 1; i <= game.maxclients; i++) + { + carrier = g_edicts + i; + if (carrier->inuse && + carrier->client->pers.inventory[flag_item]) + break; + carrier = nullptr; + } + + // ok we have the attackers flag and a pointer to the carrier + + // check to see if we are defending the base's flag + v1 = targ->s.origin - flag->s.origin; + v2 = attacker->s.origin - flag->s.origin; + + if ((v1.length() < CTF_TARGET_PROTECT_RADIUS || + v2.length() < CTF_TARGET_PROTECT_RADIUS || + loc_CanSee(flag, targ) || loc_CanSee(flag, attacker)) && + attacker->client->resp.ctf_team != targ->client->resp.ctf_team) + { + // we defended the base flag + attacker->client->resp.score += CTF_FLAG_DEFENSE_BONUS; + if (flag->solid == SOLID_NOT) + gi.LocBroadcast_Print(PRINT_MEDIUM, "$g_bonus_defend_base", + attacker->client->pers.netname, + CTFTeamName(attacker->client->resp.ctf_team)); + else + gi.LocBroadcast_Print(PRINT_MEDIUM, "$g_bonus_defend_flag", + attacker->client->pers.netname, + CTFTeamName(attacker->client->resp.ctf_team)); + if (attacker->client->resp.ghost) + attacker->client->resp.ghost->basedef++; + return; + } + + if (carrier && carrier != attacker) + { + v1 = targ->s.origin - carrier->s.origin; + v2 = attacker->s.origin - carrier->s.origin; + + if (v1.length() < CTF_ATTACKER_PROTECT_RADIUS || + v2.length() < CTF_ATTACKER_PROTECT_RADIUS || + loc_CanSee(carrier, targ) || loc_CanSee(carrier, attacker)) + { + attacker->client->resp.score += CTF_CARRIER_PROTECT_BONUS; + gi.LocBroadcast_Print(PRINT_MEDIUM, "$g_bonus_defend_carrier", + attacker->client->pers.netname, + CTFTeamName(attacker->client->resp.ctf_team)); + if (attacker->client->resp.ghost) + attacker->client->resp.ghost->carrierdef++; + return; + } + } +} + +void CTFCheckHurtCarrier(edict_t *targ, edict_t *attacker) +{ + item_id_t flag_item; + + if (!targ->client || !attacker->client) + return; + + if (targ->client->resp.ctf_team == CTF_TEAM1) + flag_item = IT_FLAG2; + else + flag_item = IT_FLAG1; + + if (targ->client->pers.inventory[flag_item] && + targ->client->resp.ctf_team != attacker->client->resp.ctf_team) + attacker->client->resp.ctf_lasthurtcarrier = level.time; +} + +/*------------------------------------------------------------------------*/ + +void CTFResetFlag(int ctf_team) +{ + const char *c; + edict_t *ent; + + switch (ctf_team) + { + case CTF_TEAM1: + c = "item_flag_team1"; + break; + case CTF_TEAM2: + c = "item_flag_team2"; + break; + default: + return; + } + + ent = nullptr; + while ((ent = G_FindByString<&edict_t::classname>(ent, c)) != nullptr) + { + if (ent->spawnflags.has(SPAWNFLAG_ITEM_DROPPED)) + G_FreeEdict(ent); + else + { + ent->svflags &= ~SVF_NOCLIENT; + ent->solid = SOLID_TRIGGER; + gi.linkentity(ent); + ent->s.event = EV_ITEM_RESPAWN; + } + } +} + +void CTFResetFlags() +{ + CTFResetFlag(CTF_TEAM1); + CTFResetFlag(CTF_TEAM2); +} + +bool CTFPickup_Flag(edict_t *ent, edict_t *other) +{ + int ctf_team; + edict_t *player; + item_id_t flag_item, enemy_flag_item; + + // figure out what team this flag is + if (ent->item->id == IT_FLAG1) + ctf_team = CTF_TEAM1; + else if (ent->item->id == IT_FLAG2) + ctf_team = CTF_TEAM2; + else + { + gi.LocClient_Print(ent, PRINT_HIGH, "Don't know what team the flag is on.\n"); + return false; + } + + // same team, if the flag at base, check to he has the enemy flag + if (ctf_team == CTF_TEAM1) + { + flag_item = IT_FLAG1; + enemy_flag_item = IT_FLAG2; + } + else + { + flag_item = IT_FLAG2; + enemy_flag_item = IT_FLAG1; + } + + if (ctf_team == other->client->resp.ctf_team) + { + + if (!(ent->spawnflags & SPAWNFLAG_ITEM_DROPPED)) + { + // the flag is at home base. if the player has the enemy + // flag, he's just won! + + if (other->client->pers.inventory[enemy_flag_item]) + { + gi.LocBroadcast_Print(PRINT_HIGH, "$g_flag_captured", + other->client->pers.netname, CTFOtherTeamName(ctf_team)); + other->client->pers.inventory[enemy_flag_item] = 0; + + ctfgame.last_flag_capture = level.time; + ctfgame.last_capture_team = ctf_team; + if (ctf_team == CTF_TEAM1) + ctfgame.team1++; + else + ctfgame.team2++; + + gi.sound(ent, CHAN_RELIABLE | CHAN_NO_PHS_ADD | CHAN_AUX, gi.soundindex("ctf/flagcap.wav"), 1, ATTN_NONE, 0); + + // other gets another 10 frag bonus + other->client->resp.score += CTF_CAPTURE_BONUS; + if (other->client->resp.ghost) + other->client->resp.ghost->caps++; + + // Ok, let's do the player loop, hand out the bonuses + for (uint32_t i = 1; i <= game.maxclients; i++) + { + player = &g_edicts[i]; + if (!player->inuse) + continue; + + if (player->client->resp.ctf_team != other->client->resp.ctf_team) + player->client->resp.ctf_lasthurtcarrier = -5_sec; + else if (player->client->resp.ctf_team == other->client->resp.ctf_team) + { + if (player != other) + player->client->resp.score += CTF_TEAM_BONUS; + // award extra points for capture assists + if (player->client->resp.ctf_lastreturnedflag && player->client->resp.ctf_lastreturnedflag + CTF_RETURN_FLAG_ASSIST_TIMEOUT > level.time) + { + gi.LocBroadcast_Print(PRINT_HIGH, "$g_bonus_assist_return", player->client->pers.netname); + player->client->resp.score += CTF_RETURN_FLAG_ASSIST_BONUS; + } + if (player->client->resp.ctf_lastfraggedcarrier && player->client->resp.ctf_lastfraggedcarrier + CTF_FRAG_CARRIER_ASSIST_TIMEOUT > level.time) + { + gi.LocBroadcast_Print(PRINT_HIGH, "$g_bonus_assist_frag_carrier", player->client->pers.netname); + player->client->resp.score += CTF_FRAG_CARRIER_ASSIST_BONUS; + } + } + } + + CTFResetFlags(); + return false; + } + return false; // its at home base already + } + // hey, its not home. return it by teleporting it back + gi.LocBroadcast_Print(PRINT_HIGH, "$g_returned_flag", + other->client->pers.netname, CTFTeamName(ctf_team)); + other->client->resp.score += CTF_RECOVERY_BONUS; + other->client->resp.ctf_lastreturnedflag = level.time; + gi.sound(ent, CHAN_RELIABLE | CHAN_NO_PHS_ADD | CHAN_AUX, gi.soundindex("ctf/flagret.wav"), 1, ATTN_NONE, 0); + // CTFResetFlag will remove this entity! We must return false + CTFResetFlag(ctf_team); + return false; + } + + // hey, its not our flag, pick it up + gi.LocBroadcast_Print(PRINT_HIGH, "$g_got_flag", + other->client->pers.netname, CTFTeamName(ctf_team)); + other->client->resp.score += CTF_FLAG_BONUS; + + other->client->pers.inventory[flag_item] = 1; + other->client->resp.ctf_flagsince = level.time; + + // pick up the flag + // if it's not a dropped flag, we just make is disappear + // if it's dropped, it will be removed by the pickup caller + if (!(ent->spawnflags & SPAWNFLAG_ITEM_DROPPED)) + { + ent->flags |= FL_RESPAWN; + ent->svflags |= SVF_NOCLIENT; + ent->solid = SOLID_NOT; + } + return true; +} + +TOUCH(CTFDropFlagTouch) (edict_t *ent, edict_t *other, const trace_t &tr, bool other_touching_self) -> void +{ + // owner (who dropped us) can't touch for two secs + if (other == ent->owner && + ent->nextthink - level.time > CTF_AUTO_FLAG_RETURN_TIMEOUT - 2_sec) + return; + + Touch_Item(ent, other, tr, other_touching_self); +} + +THINK(CTFDropFlagThink) (edict_t *ent) -> void +{ + // auto return the flag + // reset flag will remove ourselves + if (ent->item->id == IT_FLAG1) + { + CTFResetFlag(CTF_TEAM1); + gi.LocBroadcast_Print(PRINT_HIGH, "$g_flag_returned", + CTFTeamName(CTF_TEAM1)); + } + else if (ent->item->id == IT_FLAG2) + { + CTFResetFlag(CTF_TEAM2); + gi.LocBroadcast_Print(PRINT_HIGH, "$g_flag_returned", + CTFTeamName(CTF_TEAM2)); + } +} + +// Called from PlayerDie, to drop the flag from a dying player +void CTFDeadDropFlag(edict_t *self) +{ + edict_t *dropped = nullptr; + + if (self->client->pers.inventory[IT_FLAG1]) + { + dropped = Drop_Item(self, GetItemByIndex(IT_FLAG1)); + self->client->pers.inventory[IT_FLAG1] = 0; + gi.LocBroadcast_Print(PRINT_HIGH, "$g_lost_flag", + self->client->pers.netname, CTFTeamName(CTF_TEAM1)); + } + else if (self->client->pers.inventory[IT_FLAG2]) + { + dropped = Drop_Item(self, GetItemByIndex(IT_FLAG2)); + self->client->pers.inventory[IT_FLAG2] = 0; + gi.LocBroadcast_Print(PRINT_HIGH, "$g_lost_flag", + self->client->pers.netname, CTFTeamName(CTF_TEAM2)); + } + + if (dropped) + { + dropped->think = CTFDropFlagThink; + dropped->nextthink = level.time + CTF_AUTO_FLAG_RETURN_TIMEOUT; + dropped->touch = CTFDropFlagTouch; + } +} + +void CTFDrop_Flag(edict_t *ent, gitem_t *item) +{ + if (brandom()) + gi.LocClient_Print(ent, PRINT_HIGH, "$g_lusers_drop_flags"); + else + gi.LocClient_Print(ent, PRINT_HIGH, "$g_winners_drop_flags"); +} + +THINK(CTFFlagThink) (edict_t *ent) -> void +{ + if (ent->solid != SOLID_NOT) + ent->s.frame = 173 + (((ent->s.frame - 173) + 1) % 16); + ent->nextthink = level.time + 10_hz; +} + +THINK(CTFFlagSetup) (edict_t *ent) -> void +{ + trace_t tr; + vec3_t dest; + + ent->mins = { -15, -15, -15 }; + ent->maxs = { 15, 15, 15 }; + + if (ent->model) + gi.setmodel(ent, ent->model); + else + gi.setmodel(ent, ent->item->world_model); + ent->solid = SOLID_TRIGGER; + ent->movetype = MOVETYPE_TOSS; + ent->touch = Touch_Item; + ent->s.frame = 173; + + dest = ent->s.origin + vec3_t { 0, 0, -128 }; + + tr = gi.trace(ent->s.origin, ent->mins, ent->maxs, dest, ent, MASK_SOLID); + if (tr.startsolid) + { + gi.Com_PrintFmt("CTFFlagSetup: {} startsolid at {}\n", ent->classname, ent->s.origin); + G_FreeEdict(ent); + return; + } + + ent->s.origin = tr.endpos; + + gi.linkentity(ent); + + ent->nextthink = level.time + 10_hz; + ent->think = CTFFlagThink; +} + +void CTFEffects(edict_t *player) +{ + player->s.effects &= ~(EF_FLAG1 | EF_FLAG2); + if (player->health > 0) + { + if (player->client->pers.inventory[IT_FLAG1]) + { + player->s.effects |= EF_FLAG1; + } + if (player->client->pers.inventory[IT_FLAG2]) + { + player->s.effects |= EF_FLAG2; + } + } + + if (player->client->pers.inventory[IT_FLAG1]) + player->s.modelindex3 = modelindex_flag1; + else if (player->client->pers.inventory[IT_FLAG2]) + player->s.modelindex3 = modelindex_flag2; + else + player->s.modelindex3 = 0; +} + +// called when we enter the intermission +void CTFCalcScores() +{ + ctfgame.total1 = ctfgame.total2 = 0; + for (uint32_t i = 0; i < game.maxclients; i++) + { + if (!g_edicts[i + 1].inuse) + continue; + if (game.clients[i].resp.ctf_team == CTF_TEAM1) + ctfgame.total1 += game.clients[i].resp.score; + else if (game.clients[i].resp.ctf_team == CTF_TEAM2) + ctfgame.total2 += game.clients[i].resp.score; + } +} + +// [Paril-KEX] end game rankings +void CTFCalcRankings(std::array &player_ranks) +{ + // we're all winners.. or losers. whatever + if (ctfgame.total1 == ctfgame.total2) + { + player_ranks.fill(1); + return; + } + + ctfteam_t winning_team = (ctfgame.total1 > ctfgame.total2) ? CTF_TEAM1 : CTF_TEAM2; + + for (auto player : active_players()) + if (player->client->pers.spawned && player->client->resp.ctf_team != CTF_NOTEAM) + player_ranks[player->s.number - 1] = player->client->resp.ctf_team == winning_team ? 1 : 2; +} + +void CheckEndTDMLevel() +{ + if (ctfgame.total1 >= fraglimit->integer || ctfgame.total2 >= fraglimit->integer) + { + gi.LocBroadcast_Print(PRINT_HIGH, "$g_fraglimit_hit"); + EndDMLevel(); + } +} + +void CTFID_f(edict_t *ent) +{ + if (ent->client->resp.id_state) + { + gi.LocClient_Print(ent, PRINT_HIGH, "Disabling player identication display.\n"); + ent->client->resp.id_state = false; + } + else + { + gi.LocClient_Print(ent, PRINT_HIGH, "Activating player identication display.\n"); + ent->client->resp.id_state = true; + } +} + +static void CTFSetIDView(edict_t *ent) +{ + vec3_t forward, dir; + trace_t tr; + edict_t *who, *best; + float bd = 0, d; + + // only check every few frames + if (level.time - ent->client->resp.lastidtime < 250_ms) + return; + ent->client->resp.lastidtime = level.time; + + ent->client->ps.stats[STAT_CTF_ID_VIEW] = 0; + ent->client->ps.stats[STAT_CTF_ID_VIEW_COLOR] = 0; + + AngleVectors(ent->client->v_angle, forward, nullptr, nullptr); + forward *= 1024; + forward = ent->s.origin + forward; + tr = gi.traceline(ent->s.origin, forward, ent, MASK_SOLID); + if (tr.fraction < 1 && tr.ent && tr.ent->client) + { + ent->client->ps.stats[STAT_CTF_ID_VIEW] = (tr.ent - g_edicts); + if (tr.ent->client->resp.ctf_team == CTF_TEAM1) + ent->client->ps.stats[STAT_CTF_ID_VIEW_COLOR] = imageindex_sbfctf1; + else if (tr.ent->client->resp.ctf_team == CTF_TEAM2) + ent->client->ps.stats[STAT_CTF_ID_VIEW_COLOR] = imageindex_sbfctf2; + return; + } + + AngleVectors(ent->client->v_angle, forward, nullptr, nullptr); + best = nullptr; + for (uint32_t i = 1; i <= game.maxclients; i++) + { + who = g_edicts + i; + if (!who->inuse || who->solid == SOLID_NOT) + continue; + dir = who->s.origin - ent->s.origin; + dir.normalize(); + d = forward.dot(dir); + + // we have teammate indicators that are better for this + if (ent->client->resp.ctf_team == who->client->resp.ctf_team) + continue; + + if (d > bd && loc_CanSee(ent, who)) + { + bd = d; + best = who; + } + } + if (bd > 0.90f) + { + ent->client->ps.stats[STAT_CTF_ID_VIEW] = (best - g_edicts); + if (best->client->resp.ctf_team == CTF_TEAM1) + ent->client->ps.stats[STAT_CTF_ID_VIEW_COLOR] = imageindex_sbfctf1; + else if (best->client->resp.ctf_team == CTF_TEAM2) + ent->client->ps.stats[STAT_CTF_ID_VIEW_COLOR] = imageindex_sbfctf2; + } +} + +void SetCTFStats(edict_t *ent) +{ + uint32_t i; + int p1, p2; + edict_t *e; + + if (ctfgame.match > MATCH_NONE) + ent->client->ps.stats[STAT_CTF_MATCH] = CONFIG_CTF_MATCH; + else + ent->client->ps.stats[STAT_CTF_MATCH] = 0; + + if (ctfgame.warnactive) + ent->client->ps.stats[STAT_CTF_TEAMINFO] = CONFIG_CTF_TEAMINFO; + else + ent->client->ps.stats[STAT_CTF_TEAMINFO] = 0; + + // ghosting + if (ent->client->resp.ghost) + { + ent->client->resp.ghost->score = ent->client->resp.score; + Q_strlcpy(ent->client->resp.ghost->netname, ent->client->pers.netname, sizeof(ent->client->resp.ghost->netname)); + ent->client->resp.ghost->number = ent->s.number; + } + + // logo headers for the frag display + ent->client->ps.stats[STAT_CTF_TEAM1_HEADER] = imageindex_ctfsb1; + ent->client->ps.stats[STAT_CTF_TEAM2_HEADER] = imageindex_ctfsb2; + + bool blink = (level.time.milliseconds() % 1000) < 500; + + // if during intermission, we must blink the team header of the winning team + if (level.intermissiontime && blink) + { + // blink half second + // note that ctfgame.total[12] is set when we go to intermission + if (ctfgame.team1 > ctfgame.team2) + ent->client->ps.stats[STAT_CTF_TEAM1_HEADER] = 0; + else if (ctfgame.team2 > ctfgame.team1) + ent->client->ps.stats[STAT_CTF_TEAM2_HEADER] = 0; + else if (ctfgame.total1 > ctfgame.total2) // frag tie breaker + ent->client->ps.stats[STAT_CTF_TEAM1_HEADER] = 0; + else if (ctfgame.total2 > ctfgame.total1) + ent->client->ps.stats[STAT_CTF_TEAM2_HEADER] = 0; + else + { // tie game! + ent->client->ps.stats[STAT_CTF_TEAM1_HEADER] = 0; + ent->client->ps.stats[STAT_CTF_TEAM2_HEADER] = 0; + } + } + + // tech icon + i = 0; + ent->client->ps.stats[STAT_CTF_TECH] = 0; + for (; i < q_countof(tech_ids); i++) + { + if (ent->client->pers.inventory[tech_ids[i]]) + { + ent->client->ps.stats[STAT_CTF_TECH] = gi.imageindex(GetItemByIndex(tech_ids[i])->icon); + break; + } + } + + if (ctf->integer) + { + // figure out what icon to display for team logos + // three states: + // flag at base + // flag taken + // flag dropped + p1 = imageindex_i_ctf1; + e = G_FindByString<&edict_t::classname>(nullptr, "item_flag_team1"); + if (e != nullptr) + { + if (e->solid == SOLID_NOT) + { + // not at base + // check if on player + p1 = imageindex_i_ctf1d; // default to dropped + for (i = 1; i <= game.maxclients; i++) + if (g_edicts[i].inuse && + g_edicts[i].client->pers.inventory[IT_FLAG1]) + { + // enemy has it + p1 = imageindex_i_ctf1t; + break; + } + } + else if (e->spawnflags.has(SPAWNFLAG_ITEM_DROPPED)) + p1 = imageindex_i_ctf1d; // must be dropped + } + p2 = imageindex_i_ctf2; + e = G_FindByString<&edict_t::classname>(nullptr, "item_flag_team2"); + if (e != nullptr) + { + if (e->solid == SOLID_NOT) + { + // not at base + // check if on player + p2 = imageindex_i_ctf2d; // default to dropped + for (i = 1; i <= game.maxclients; i++) + if (g_edicts[i].inuse && + g_edicts[i].client->pers.inventory[IT_FLAG2]) + { + // enemy has it + p2 = imageindex_i_ctf2t; + break; + } + } + else if (e->spawnflags.has(SPAWNFLAG_ITEM_DROPPED)) + p2 = imageindex_i_ctf2d; // must be dropped + } + + ent->client->ps.stats[STAT_CTF_TEAM1_PIC] = p1; + ent->client->ps.stats[STAT_CTF_TEAM2_PIC] = p2; + + if (ctfgame.last_flag_capture && level.time - ctfgame.last_flag_capture < 5_sec) + { + if (ctfgame.last_capture_team == CTF_TEAM1) + if (blink) + ent->client->ps.stats[STAT_CTF_TEAM1_PIC] = p1; + else + ent->client->ps.stats[STAT_CTF_TEAM1_PIC] = 0; + else if (blink) + ent->client->ps.stats[STAT_CTF_TEAM2_PIC] = p2; + else + ent->client->ps.stats[STAT_CTF_TEAM2_PIC] = 0; + } + + ent->client->ps.stats[STAT_CTF_TEAM1_CAPS] = ctfgame.team1; + ent->client->ps.stats[STAT_CTF_TEAM2_CAPS] = ctfgame.team2; + + ent->client->ps.stats[STAT_CTF_FLAG_PIC] = 0; + if (ent->client->resp.ctf_team == CTF_TEAM1 && + ent->client->pers.inventory[IT_FLAG2] && + (blink)) + ent->client->ps.stats[STAT_CTF_FLAG_PIC] = imageindex_i_ctf2; + + else if (ent->client->resp.ctf_team == CTF_TEAM2 && + ent->client->pers.inventory[IT_FLAG1] && + (blink)) + ent->client->ps.stats[STAT_CTF_FLAG_PIC] = imageindex_i_ctf1; + } + else + { + ent->client->ps.stats[STAT_CTF_TEAM1_PIC] = imageindex_i_ctf1; + ent->client->ps.stats[STAT_CTF_TEAM2_PIC] = imageindex_i_ctf2; + + ent->client->ps.stats[STAT_CTF_TEAM1_CAPS] = ctfgame.total1; + ent->client->ps.stats[STAT_CTF_TEAM2_CAPS] = ctfgame.total2; + } + + ent->client->ps.stats[STAT_CTF_JOINED_TEAM1_PIC] = 0; + ent->client->ps.stats[STAT_CTF_JOINED_TEAM2_PIC] = 0; + if (ent->client->resp.ctf_team == CTF_TEAM1) + ent->client->ps.stats[STAT_CTF_JOINED_TEAM1_PIC] = imageindex_i_ctfj; + else if (ent->client->resp.ctf_team == CTF_TEAM2) + ent->client->ps.stats[STAT_CTF_JOINED_TEAM2_PIC] = imageindex_i_ctfj; + + if (ent->client->resp.id_state) + CTFSetIDView(ent); + else + { + ent->client->ps.stats[STAT_CTF_ID_VIEW] = 0; + ent->client->ps.stats[STAT_CTF_ID_VIEW_COLOR] = 0; + } +} + +/*------------------------------------------------------------------------*/ + +/*QUAKED info_player_team1 (1 0 0) (-16 -16 -24) (16 16 32) +potential team1 spawning position for ctf games +*/ +void SP_info_player_team1(edict_t *self) +{ +} + +/*QUAKED info_player_team2 (0 0 1) (-16 -16 -24) (16 16 32) +potential team2 spawning position for ctf games +*/ +void SP_info_player_team2(edict_t *self) +{ +} + +/*------------------------------------------------------------------------*/ +/* GRAPPLE */ +/*------------------------------------------------------------------------*/ + +// ent is player +void CTFPlayerResetGrapple(edict_t *ent) +{ + if (ent->client && ent->client->ctf_grapple) + CTFResetGrapple(ent->client->ctf_grapple); +} + +// self is grapple, not player +void CTFResetGrapple(edict_t *self) +{ + if (!self->owner->client->ctf_grapple) + return; + + gi.sound(self->owner, CHAN_WEAPON, gi.soundindex("weapons/grapple/grreset.wav"), self->owner->client->silencer_shots ? 0.2f : 1.0f, ATTN_NORM, 0); + + gclient_t *cl; + cl = self->owner->client; + cl->ctf_grapple = nullptr; + cl->ctf_grapplereleasetime = level.time + 1_sec; + cl->ctf_grapplestate = CTF_GRAPPLE_STATE_FLY; // we're firing, not on hook + self->owner->flags &= ~FL_NO_KNOCKBACK; + G_FreeEdict(self); +} + +TOUCH(CTFGrappleTouch) (edict_t *self, edict_t *other, const trace_t &tr, bool other_touching_self) -> void +{ + float volume = 1.0; + + if (other == self->owner) + return; + + if (self->owner->client->ctf_grapplestate != CTF_GRAPPLE_STATE_FLY) + return; + + if (tr.surface && (tr.surface->flags & SURF_SKY)) + { + CTFResetGrapple(self); + return; + } + + self->velocity = {}; + + PlayerNoise(self->owner, self->s.origin, PNOISE_IMPACT); + + if (other->takedamage) + { + if (self->dmg) + T_Damage(other, self, self->owner, self->velocity, self->s.origin, tr.plane.normal, self->dmg, 1, DAMAGE_NONE, MOD_GRAPPLE); + CTFResetGrapple(self); + return; + } + + self->owner->client->ctf_grapplestate = CTF_GRAPPLE_STATE_PULL; // we're on hook + self->enemy = other; + + self->solid = SOLID_NOT; + + if (self->owner->client->silencer_shots) + volume = 0.2f; + + gi.sound(self, CHAN_WEAPON, gi.soundindex("weapons/grapple/grhit.wav"), volume, ATTN_NORM, 0); + self->s.sound = gi.soundindex("weapons/grapple/grpull.wav"); + + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_SPARKS); + gi.WritePosition(self->s.origin); + gi.WriteDir(tr.plane.normal); + gi.multicast(self->s.origin, MULTICAST_PVS, false); +} + +// draw beam between grapple and self +void CTFGrappleDrawCable(edict_t *self) +{ + if (self->owner->client->ctf_grapplestate == CTF_GRAPPLE_STATE_HANG) + return; + + vec3_t start, dir; + P_ProjectSource(self->owner, self->owner->client->v_angle, { 7, 2, -9 }, start, dir); + + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_GRAPPLE_CABLE_2); + gi.WriteEntity(self->owner); + gi.WritePosition(start); + gi.WritePosition(self->s.origin); + gi.multicast(self->s.origin, MULTICAST_PVS, false); +} + +void SV_AddGravity(edict_t *ent); + +// pull the player toward the grapple +void CTFGrapplePull(edict_t *self) +{ + vec3_t hookdir, v; + float vlen; + + if (self->owner->client->pers.weapon && self->owner->client->pers.weapon->id == IT_WEAPON_GRAPPLE && + !(self->owner->client->newweapon || ((self->owner->client->latched_buttons | self->owner->client->buttons) & BUTTON_HOLSTER)) && + self->owner->client->weaponstate != WEAPON_FIRING && + self->owner->client->weaponstate != WEAPON_ACTIVATING) + { + if (!self->owner->client->newweapon) + self->owner->client->newweapon = self->owner->client->pers.weapon; + + CTFResetGrapple(self); + return; + } + + if (self->enemy) + { + if (self->enemy->solid == SOLID_NOT) + { + CTFResetGrapple(self); + return; + } + if (self->enemy->solid == SOLID_BBOX) + { + v = self->enemy->size * 0.5f; + v += self->enemy->s.origin; + self->s.origin = v + self->enemy->mins; + gi.linkentity(self); + } + else + self->velocity = self->enemy->velocity; + + if (self->enemy->deadflag) + { // he died + CTFResetGrapple(self); + return; + } + } + + CTFGrappleDrawCable(self); + + if (self->owner->client->ctf_grapplestate > CTF_GRAPPLE_STATE_FLY) + { + // pull player toward grapple + vec3_t forward, up; + + AngleVectors(self->owner->client->v_angle, forward, nullptr, up); + v = self->owner->s.origin; + v[2] += self->owner->viewheight; + hookdir = self->s.origin - v; + + vlen = hookdir.length(); + + if (self->owner->client->ctf_grapplestate == CTF_GRAPPLE_STATE_PULL && + vlen < 64) + { + self->owner->client->ctf_grapplestate = CTF_GRAPPLE_STATE_HANG; + self->s.sound = gi.soundindex("weapons/grapple/grhang.wav"); + } + + hookdir.normalize(); + hookdir = hookdir * g_grapple_pull_speed->value; + self->owner->velocity = hookdir; + self->owner->flags |= FL_NO_KNOCKBACK; + SV_AddGravity(self->owner); + } +} + +DIE(grapple_die) (edict_t *self, edict_t *other, edict_t *inflictor, int damage, const vec3_t &point, const mod_t &mod) -> void +{ + if (mod.id == MOD_CRUSH) + CTFResetGrapple(self); +} + +bool CTFFireGrapple(edict_t *self, const vec3_t &start, const vec3_t &dir, int damage, int speed, effects_t effect) +{ + edict_t *grapple; + trace_t tr; + vec3_t normalized = dir.normalized(); + + grapple = G_Spawn(); + grapple->s.origin = start; + grapple->s.old_origin = start; + grapple->s.angles = vectoangles(normalized); + grapple->velocity = normalized * speed; + grapple->movetype = MOVETYPE_FLYMISSILE; + grapple->clipmask = MASK_PROJECTILE; + // [Paril-KEX] + if (self->client && !G_ShouldPlayersCollide(true)) + grapple->clipmask &= ~CONTENTS_PLAYER; + grapple->solid = SOLID_BBOX; + grapple->s.effects |= effect; + grapple->s.modelindex = gi.modelindex("models/weapons/grapple/hook/tris.md2"); + grapple->owner = self; + grapple->touch = CTFGrappleTouch; + grapple->dmg = damage; + grapple->flags |= FL_NO_KNOCKBACK | FL_NO_DAMAGE_EFFECTS; + grapple->takedamage = true; + grapple->die = grapple_die; + self->client->ctf_grapple = grapple; + self->client->ctf_grapplestate = CTF_GRAPPLE_STATE_FLY; // we're firing, not on hook + gi.linkentity(grapple); + + tr = gi.traceline(self->s.origin, grapple->s.origin, grapple, grapple->clipmask); + if (tr.fraction < 1.0f) + { + grapple->s.origin = tr.endpos + (tr.plane.normal * 1.f); + grapple->touch(grapple, tr.ent, tr, false); + return false; + } + + grapple->s.sound = gi.soundindex("weapons/grapple/grfly.wav"); + + return true; +} + +void CTFGrappleFire(edict_t *ent, const vec3_t &g_offset, int damage, effects_t effect) +{ + float volume = 1.0; + + if (ent->client->ctf_grapplestate > CTF_GRAPPLE_STATE_FLY) + return; // it's already out + + vec3_t start, dir; + P_ProjectSource(ent, ent->client->v_angle, vec3_t{ 24, 8, -8 + 2 } + g_offset, start, dir); + + if (ent->client->silencer_shots) + volume = 0.2f; + + if (CTFFireGrapple(ent, start, dir, damage, g_grapple_fly_speed->value, effect)) + gi.sound(ent, CHAN_WEAPON, gi.soundindex("weapons/grapple/grfire.wav"), volume, ATTN_NORM, 0); + + PlayerNoise(ent, start, PNOISE_WEAPON); +} + +void CTFWeapon_Grapple_Fire(edict_t *ent) +{ + CTFGrappleFire(ent, vec3_origin, g_grapple_damage->integer, EF_NONE); +} + +void CTFWeapon_Grapple(edict_t *ent) +{ + constexpr int pause_frames[] = { 10, 18, 27, 0 }; + constexpr int fire_frames[] = { 6, 0 }; + int prevstate; + + // if the the attack button is still down, stay in the firing frame + if ((ent->client->buttons & (BUTTON_ATTACK | BUTTON_HOLSTER)) && + ent->client->weaponstate == WEAPON_FIRING && + ent->client->ctf_grapple) + ent->client->ps.gunframe = 6; + + if (!(ent->client->buttons & (BUTTON_ATTACK | BUTTON_HOLSTER)) && + ent->client->ctf_grapple) + { + CTFResetGrapple(ent->client->ctf_grapple); + if (ent->client->weaponstate == WEAPON_FIRING) + ent->client->weaponstate = WEAPON_READY; + } + + if ((ent->client->newweapon || ((ent->client->latched_buttons | ent->client->buttons) & BUTTON_HOLSTER)) && + ent->client->ctf_grapplestate > CTF_GRAPPLE_STATE_FLY && + ent->client->weaponstate == WEAPON_FIRING) + { + // he wants to change weapons while grappled + if (!ent->client->newweapon) + ent->client->newweapon = ent->client->pers.weapon; + ent->client->weaponstate = WEAPON_DROPPING; + ent->client->ps.gunframe = 32; + } + + prevstate = ent->client->weaponstate; + Weapon_Generic(ent, 5, 10, 31, 36, pause_frames, fire_frames, + CTFWeapon_Grapple_Fire); + + // if the the attack button is still down, stay in the firing frame + if ((ent->client->buttons & (BUTTON_ATTACK | BUTTON_HOLSTER)) && + ent->client->weaponstate == WEAPON_FIRING && + ent->client->ctf_grapple) + ent->client->ps.gunframe = 6; + + // if we just switched back to grapple, immediately go to fire frame + if (prevstate == WEAPON_ACTIVATING && + ent->client->weaponstate == WEAPON_READY && + ent->client->ctf_grapplestate > CTF_GRAPPLE_STATE_FLY) + { + if (!(ent->client->buttons & (BUTTON_ATTACK | BUTTON_HOLSTER))) + ent->client->ps.gunframe = 6; + else + ent->client->ps.gunframe = 5; + ent->client->weaponstate = WEAPON_FIRING; + } +} + +void CTFDirtyTeamMenu() +{ + for (auto player : active_players()) + if (player->client->menu) + { + player->client->menudirty = true; + player->client->menutime = level.time; + } +} + +void CTFTeam_f(edict_t *ent) +{ + if (!G_TeamplayEnabled()) + return; + + const char *t; + ctfteam_t desired_team; + + t = gi.args(); + if (!*t) + { + gi.LocClient_Print(ent, PRINT_HIGH, "$g_you_are_on_team", + CTFTeamName(ent->client->resp.ctf_team)); + return; + } + + if (ctfgame.match > MATCH_SETUP) + { + gi.LocClient_Print(ent, PRINT_HIGH, "$g_cant_change_teams"); + return; + } + + // [Paril-KEX] with force-join, don't allow us to switch + // using this command. + if (g_teamplay_force_join->integer) + { + if (!(ent->svflags & SVF_BOT)) + { + gi.LocClient_Print(ent, PRINT_HIGH, "$g_cant_change_teams"); + return; + } + } + + if (Q_strcasecmp(t, "red") == 0) + desired_team = CTF_TEAM1; + else if (Q_strcasecmp(t, "blue") == 0) + desired_team = CTF_TEAM2; + else + { + gi.LocClient_Print(ent, PRINT_HIGH, "$g_unknown_team", t); + return; + } + + if (ent->client->resp.ctf_team == desired_team) + { + gi.LocClient_Print(ent, PRINT_HIGH, "$g_already_on_team", + CTFTeamName(ent->client->resp.ctf_team)); + return; + } + + //// + ent->svflags = SVF_NONE; + ent->flags &= ~FL_GODMODE; + ent->client->resp.ctf_team = desired_team; + ent->client->resp.ctf_state = 0; + char value[MAX_INFO_VALUE] = { 0 }; + gi.Info_ValueForKey(ent->client->pers.userinfo, "skin", value, sizeof(value)); + CTFAssignSkin(ent, value); + + // if anybody has a menu open, update it immediately + CTFDirtyTeamMenu(); + + if (ent->solid == SOLID_NOT) + { + // spectator + PutClientInServer(ent); + + G_PostRespawn(ent); + + gi.LocBroadcast_Print(PRINT_HIGH, "$g_joined_team", + ent->client->pers.netname, CTFTeamName(desired_team)); + return; + } + + ent->health = 0; + player_die(ent, ent, ent, 100000, vec3_origin, { MOD_SUICIDE, true }); + + // don't even bother waiting for death frames + ent->deadflag = true; + respawn(ent); + + ent->client->resp.score = 0; + + gi.LocBroadcast_Print(PRINT_HIGH, "$g_changed_team", + ent->client->pers.netname, CTFTeamName(desired_team)); +} + +constexpr size_t MAX_CTF_STAT_LENGTH = 1024; + +/* +================== +CTFScoreboardMessage +================== +*/ +void CTFScoreboardMessage(edict_t *ent, edict_t *killer) +{ + uint32_t i, j, k, n; + uint32_t sorted[2][MAX_CLIENTS]; + int32_t sortedscores[2][MAX_CLIENTS]; + int score; + uint32_t total[2]; + int totalscore[2]; + uint32_t last[2]; + gclient_t *cl; + edict_t *cl_ent; + int team; + + // sort the clients by team and score + total[0] = total[1] = 0; + last[0] = last[1] = 0; + totalscore[0] = totalscore[1] = 0; + for (i = 0; i < game.maxclients; i++) + { + cl_ent = g_edicts + 1 + i; + if (!cl_ent->inuse) + continue; + if (game.clients[i].resp.ctf_team == CTF_TEAM1) + team = 0; + else if (game.clients[i].resp.ctf_team == CTF_TEAM2) + team = 1; + else + continue; // unknown team? + + score = game.clients[i].resp.score; + for (j = 0; j < total[team]; j++) + { + if (score > sortedscores[team][j]) + break; + } + for (k = total[team]; k > j; k--) + { + sorted[team][k] = sorted[team][k - 1]; + sortedscores[team][k] = sortedscores[team][k - 1]; + } + sorted[team][j] = i; + sortedscores[team][j] = score; + totalscore[team] += score; + total[team]++; + } + + // print level name and exit rules + // add the clients in sorted order + static std::string string; + string.clear(); + + // [Paril-KEX] time & frags + if (teamplay->integer) + { + if (fraglimit->integer) + { + fmt::format_to(std::back_inserter(string), FMT_STRING("xv -20 yv -10 loc_string2 1 $g_score_frags \"{}\" "), fraglimit->integer); + } + } + else + { + if (capturelimit->integer) + { + fmt::format_to(std::back_inserter(string), FMT_STRING("xv -20 yv -10 loc_string2 1 $g_score_captures \"{}\" "), capturelimit->integer); + } + } + if (timelimit->value) + { + fmt::format_to(std::back_inserter(string), FMT_STRING("xv 340 yv -10 time_limit {} "), gi.ServerFrame() + ((gtime_t::from_min(timelimit->value) - level.time)).milliseconds() / gi.frame_time_ms); + } + + // team one + if (teamplay->integer) + { + fmt::format_to(std::back_inserter(string), + FMT_STRING("if 25 xv -32 yv 8 pic 25 endif " + "xv -123 yv 28 cstring \"{}\" " + "xv 41 yv 12 num 3 19 " + "if 26 xv 208 yv 8 pic 26 endif " + "xv 117 yv 28 cstring \"{}\" " + "xv 280 yv 12 num 3 21 "), + total[0], + total[1]); + } + else + { + fmt::format_to(std::back_inserter(string), + FMT_STRING("if 25 xv -32 yv 8 pic 25 endif " + "xv 0 yv 28 string \"{:4}/{:<3}\" " + "xv 58 yv 12 num 2 19 " + "if 26 xv 208 yv 8 pic 26 endif " + "xv 240 yv 28 string \"{:4}/{:<3}\" " + "xv 296 yv 12 num 2 21 "), + totalscore[0], total[0], + totalscore[1], total[1]); + } + + for (i = 0; i < 16; i++) + { + if (i >= total[0] && i >= total[1]) + break; // we're done + + // left side + if (i < total[0]) + { + cl = &game.clients[sorted[0][i]]; + cl_ent = g_edicts + 1 + sorted[0][i]; + + std::string_view entry = G_Fmt("ctf -40 {} {} {} {} {} ", + 42 + i * 8, + sorted[0][i], + cl->resp.score, + cl->ping > 999 ? 999 : cl->ping, + cl_ent->client->pers.inventory[IT_FLAG2] ? "sbfctf2" : "\"\""); + + if (string.size() + entry.size() < MAX_CTF_STAT_LENGTH) + { + string += entry; + last[0] = i; + } + } + + // right side + if (i < total[1]) + { + cl = &game.clients[sorted[1][i]]; + cl_ent = g_edicts + 1 + sorted[1][i]; + + std::string_view entry = G_Fmt("ctf 200 {} {} {} {} {} ", + 42 + i * 8, + sorted[1][i], + cl->resp.score, + cl->ping > 999 ? 999 : cl->ping, + cl_ent->client->pers.inventory[IT_FLAG1] ? "sbfctf1" : "\"\""); + + if (string.size() + entry.size() < MAX_CTF_STAT_LENGTH) + { + string += entry; + last[1] = i; + } + } + } + + // put in spectators if we have enough room + if (last[0] > last[1]) + j = last[0]; + else + j = last[1]; + j = (j + 2) * 8 + 42; + + k = n = 0; + if (string.size() < MAX_CTF_STAT_LENGTH - 50) + { + for (i = 0; i < game.maxclients; i++) + { + cl_ent = g_edicts + 1 + i; + cl = &game.clients[i]; + if (!cl_ent->inuse || + cl_ent->solid != SOLID_NOT || + cl_ent->client->resp.ctf_team != CTF_NOTEAM) + continue; + + if (!k) + { + k = 1; + fmt::format_to(std::back_inserter(string), FMT_STRING("xv 0 yv {} loc_string2 0 \"$g_pc_spectators\" "), j); + j += 8; + } + + std::string_view entry = G_Fmt("ctf {} {} {} {} {} \"\" ", + (n & 1) ? 200 : -40, // x + j, // y + i, // playernum + cl->resp.score, + cl->ping > 999 ? 999 : cl->ping); + + if (string.size() + entry.size() < MAX_CTF_STAT_LENGTH) + string += entry; + + if (n & 1) + j += 8; + n++; + } + } + + if (total[0] - last[0] > 1) // couldn't fit everyone + fmt::format_to(std::back_inserter(string), FMT_STRING("xv -32 yv {} loc_string 1 $g_ctf_and_more {} "), + 42 + (last[0] + 1) * 8, total[0] - last[0] - 1); + if (total[1] - last[1] > 1) // couldn't fit everyone + fmt::format_to(std::back_inserter(string), FMT_STRING("xv 208 yv {} loc_string 1 $g_ctf_and_more {} "), + 42 + (last[1] + 1) * 8, total[1] - last[1] - 1); + + if (level.intermissiontime) + fmt::format_to(std::back_inserter(string), FMT_STRING("ifgef {} yb -48 xv 0 loc_cstring2 0 \"$m_eou_press_button\" endif "), (level.intermission_server_frame + (5_sec).frames())); + + gi.WriteByte(svc_layout); + gi.WriteString(string.c_str()); +} + +/*------------------------------------------------------------------------*/ +/* TECH */ +/*------------------------------------------------------------------------*/ + +void CTFHasTech(edict_t *who) +{ + if (level.time - who->client->ctf_lasttechmsg > 2_sec) + { + gi.LocCenter_Print(who, "$g_already_have_tech"); + who->client->ctf_lasttechmsg = level.time; + } +} + +gitem_t *CTFWhat_Tech(edict_t *ent) +{ + int i; + + i = 0; + for (; i < q_countof(tech_ids); i++) + { + if (ent->client->pers.inventory[tech_ids[i]]) + { + return GetItemByIndex(tech_ids[i]); + } + } + return nullptr; +} + +bool CTFPickup_Tech(edict_t *ent, edict_t *other) +{ + int i; + + i = 0; + for (; i < q_countof(tech_ids); i++) + { + if (other->client->pers.inventory[tech_ids[i]]) + { + CTFHasTech(other); + return false; // has this one + } + } + + // client only gets one tech + other->client->pers.inventory[ent->item->id]++; + other->client->ctf_regentime = level.time; + return true; +} + +static void SpawnTech(gitem_t *item, edict_t *spot); + +static edict_t *FindTechSpawn() +{ + return SelectDeathmatchSpawnPoint(false, true, true).spot; +} + +THINK(TechThink) (edict_t *tech) -> void +{ + edict_t *spot; + + if ((spot = FindTechSpawn()) != nullptr) + { + SpawnTech(tech->item, spot); + G_FreeEdict(tech); + } + else + { + tech->nextthink = level.time + CTF_TECH_TIMEOUT; + tech->think = TechThink; + } +} + +void CTFDrop_Tech(edict_t *ent, gitem_t *item) +{ + edict_t *tech; + + tech = Drop_Item(ent, item); + tech->nextthink = level.time + CTF_TECH_TIMEOUT; + tech->think = TechThink; + ent->client->pers.inventory[item->id] = 0; +} + +void CTFDeadDropTech(edict_t *ent) +{ + edict_t *dropped; + int i; + + i = 0; + for (; i < q_countof(tech_ids); i++) + { + if (ent->client->pers.inventory[tech_ids[i]]) + { + dropped = Drop_Item(ent, GetItemByIndex(tech_ids[i])); + // hack the velocity to make it bounce random + dropped->velocity[0] = crandom_open() * 300; + dropped->velocity[1] = crandom_open() * 300; + dropped->nextthink = level.time + CTF_TECH_TIMEOUT; + dropped->think = TechThink; + dropped->owner = nullptr; + ent->client->pers.inventory[tech_ids[i]] = 0; + } + } +} + +static void SpawnTech(gitem_t *item, edict_t *spot) +{ + edict_t *ent; + vec3_t forward, right; + vec3_t angles; + + ent = G_Spawn(); + + ent->classname = item->classname; + ent->item = item; + ent->spawnflags = SPAWNFLAG_ITEM_DROPPED; + ent->s.effects = item->world_model_flags; + ent->s.renderfx = RF_GLOW | RF_NO_LOD; + ent->mins = { -15, -15, -15 }; + ent->maxs = { 15, 15, 15 }; + gi.setmodel(ent, ent->item->world_model); + ent->solid = SOLID_TRIGGER; + ent->movetype = MOVETYPE_TOSS; + ent->touch = Touch_Item; + ent->owner = ent; + + angles[0] = 0; + angles[1] = (float) irandom(360); + angles[2] = 0; + + AngleVectors(angles, forward, right, nullptr); + ent->s.origin = spot->s.origin; + ent->s.origin[2] += 16; + ent->velocity = forward * 100; + ent->velocity[2] = 300; + + ent->nextthink = level.time + CTF_TECH_TIMEOUT; + ent->think = TechThink; + + gi.linkentity(ent); +} + +THINK(SpawnTechs) (edict_t *ent) -> void +{ + edict_t *spot; + int i; + + i = 0; + for (; i < q_countof(tech_ids); i++) + { + if ((spot = FindTechSpawn()) != nullptr) + SpawnTech(GetItemByIndex(tech_ids[i]), spot); + } + if (ent) + G_FreeEdict(ent); +} + +// frees the passed edict! +void CTFRespawnTech(edict_t *ent) +{ + edict_t *spot; + + if ((spot = FindTechSpawn()) != nullptr) + SpawnTech(ent->item, spot); + G_FreeEdict(ent); +} + +void CTFSetupTechSpawn() +{ + edict_t *ent; + bool techs_allowed; + + // [Paril-KEX] + if (!strcmp(g_allow_techs->string, "auto")) + techs_allowed = !!ctf->integer; + else + techs_allowed = !!g_allow_techs->integer; + + if (!techs_allowed) + return; + + ent = G_Spawn(); + ent->nextthink = level.time + 2_sec; + ent->think = SpawnTechs; +} + +void CTFResetTech() +{ + edict_t *ent; + uint32_t i; + + for (ent = g_edicts + 1, i = 1; i < globals.num_edicts; i++, ent++) + { + if (ent->inuse) + if (ent->item && (ent->item->flags & IF_TECH)) + G_FreeEdict(ent); + } + SpawnTechs(nullptr); +} + +int CTFApplyResistance(edict_t *ent, int dmg) +{ + float volume = 1.0; + + if (ent->client && ent->client->silencer_shots) + volume = 0.2f; + + if (dmg && ent->client && ent->client->pers.inventory[IT_TECH_RESISTANCE]) + { + // make noise + gi.sound(ent, CHAN_AUX, gi.soundindex("ctf/tech1.wav"), volume, ATTN_NORM, 0); + return dmg / 2; + } + return dmg; +} + +int CTFApplyStrength(edict_t *ent, int dmg) +{ + if (dmg && ent->client && ent->client->pers.inventory[IT_TECH_STRENGTH]) + { + return dmg * 2; + } + return dmg; +} + +bool CTFApplyStrengthSound(edict_t *ent) +{ + float volume = 1.0; + + if (ent->client && ent->client->silencer_shots) + volume = 0.2f; + + if (ent->client && + ent->client->pers.inventory[IT_TECH_STRENGTH]) + { + if (ent->client->ctf_techsndtime < level.time) + { + ent->client->ctf_techsndtime = level.time + 1_sec; + if (ent->client->quad_time > level.time) + gi.sound(ent, CHAN_AUX, gi.soundindex("ctf/tech2x.wav"), volume, ATTN_NORM, 0); + else + gi.sound(ent, CHAN_AUX, gi.soundindex("ctf/tech2.wav"), volume, ATTN_NORM, 0); + } + return true; + } + return false; +} + +bool CTFApplyHaste(edict_t *ent) +{ + if (ent->client && + ent->client->pers.inventory[IT_TECH_HASTE]) + return true; + return false; +} + +void CTFApplyHasteSound(edict_t *ent) +{ + float volume = 1.0; + + if (ent->client && ent->client->silencer_shots) + volume = 0.2f; + + if (ent->client && + ent->client->pers.inventory[IT_TECH_HASTE] && + ent->client->ctf_techsndtime < level.time) + { + ent->client->ctf_techsndtime = level.time + 1_sec; + gi.sound(ent, CHAN_AUX, gi.soundindex("ctf/tech3.wav"), volume, ATTN_NORM, 0); + } +} + +void CTFApplyRegeneration(edict_t *ent) +{ + bool noise = false; + gclient_t *client; + int index; + float volume = 1.0; + + client = ent->client; + if (!client) + return; + + if (ent->client->silencer_shots) + volume = 0.2f; + + if (client->pers.inventory[IT_TECH_REGENERATION]) + { + if (client->ctf_regentime < level.time) + { + client->ctf_regentime = level.time; + if (ent->health < 150) + { + ent->health += 5; + if (ent->health > 150) + ent->health = 150; + client->ctf_regentime += 500_ms; + noise = true; + } + index = ArmorIndex(ent); + if (index && client->pers.inventory[index] < 150) + { + client->pers.inventory[index] += 5; + if (client->pers.inventory[index] > 150) + client->pers.inventory[index] = 150; + client->ctf_regentime += 500_ms; + noise = true; + } + } + if (noise && ent->client->ctf_techsndtime < level.time) + { + ent->client->ctf_techsndtime = level.time + 1_sec; + gi.sound(ent, CHAN_AUX, gi.soundindex("ctf/tech4.wav"), volume, ATTN_NORM, 0); + } + } +} + +bool CTFHasRegeneration(edict_t *ent) +{ + if (ent->client && + ent->client->pers.inventory[IT_TECH_REGENERATION]) + return true; + return false; +} + +void CTFSay_Team(edict_t *who, const char *msg_in) +{ + edict_t *cl_ent; + char outmsg[256]; + + if (CheckFlood(who)) + return; + + Q_strlcpy(outmsg, msg_in, sizeof(outmsg)); + + char *msg = outmsg; + + if (*msg == '\"') + { + msg[strlen(msg) - 1] = 0; + msg++; + } + + for (uint32_t i = 0; i < game.maxclients; i++) + { + cl_ent = g_edicts + 1 + i; + if (!cl_ent->inuse) + continue; + if (cl_ent->client->resp.ctf_team == who->client->resp.ctf_team) + gi.LocClient_Print(cl_ent, PRINT_CHAT, "({}): {}\n", + who->client->pers.netname, msg); + } +} + +/*-----------------------------------------------------------------------*/ +/*QUAKED misc_ctf_banner (1 .5 0) (-4 -64 0) (4 64 248) TEAM2 +The origin is the bottom of the banner. +The banner is 248 tall. +*/ +THINK(misc_ctf_banner_think) (edict_t *ent) -> void +{ + ent->s.frame = (ent->s.frame + 1) % 16; + ent->nextthink = level.time + 10_hz; +} + +constexpr spawnflags_t SPAWNFLAG_CTF_BANNER_BLUE = 1_spawnflag; + +void SP_misc_ctf_banner(edict_t *ent) +{ + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_NOT; + ent->s.modelindex = gi.modelindex("models/ctf/banner/tris.md2"); + if (ent->spawnflags.has(SPAWNFLAG_CTF_BANNER_BLUE)) // team2 + ent->s.skinnum = 1; + + ent->s.frame = irandom(16); + gi.linkentity(ent); + + ent->think = misc_ctf_banner_think; + ent->nextthink = level.time + 10_hz; +} + +/*QUAKED misc_ctf_small_banner (1 .5 0) (-4 -32 0) (4 32 124) TEAM2 +The origin is the bottom of the banner. +The banner is 124 tall. +*/ +void SP_misc_ctf_small_banner(edict_t *ent) +{ + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_NOT; + ent->s.modelindex = gi.modelindex("models/ctf/banner/small.md2"); + if (ent->spawnflags.has(SPAWNFLAG_CTF_BANNER_BLUE)) // team2 + ent->s.skinnum = 1; + + ent->s.frame = irandom(16); + gi.linkentity(ent); + + ent->think = misc_ctf_banner_think; + ent->nextthink = level.time + 10_hz; +} + +/*-----------------------------------------------------------------------*/ + +static void SetGameName(pmenu_t *p) +{ + if (ctf->integer) + Q_strlcpy(p->text, "$g_pc_3wctf", sizeof(p->text)); + else + Q_strlcpy(p->text, "$g_pc_teamplay", sizeof(p->text)); +} + +static void SetLevelName(pmenu_t *p) +{ + static char levelname[33]; + + levelname[0] = '*'; + if (g_edicts[0].message) + Q_strlcpy(levelname + 1, g_edicts[0].message, sizeof(levelname) - 1); + else + Q_strlcpy(levelname + 1, level.mapname, sizeof(levelname) - 1); + levelname[sizeof(levelname) - 1] = 0; + Q_strlcpy(p->text, levelname, sizeof(p->text)); +} + +/*-----------------------------------------------------------------------*/ + +/* ELECTIONS */ + +bool CTFBeginElection(edict_t *ent, elect_t type, const char *msg) +{ + int count; + edict_t *e; + + if (electpercentage->value == 0) + { + gi.LocClient_Print(ent, PRINT_HIGH, "Elections are disabled, only an admin can process this action.\n"); + return false; + } + + if (ctfgame.election != ELECT_NONE) + { + gi.LocClient_Print(ent, PRINT_HIGH, "Election already in progress.\n"); + return false; + } + + // clear votes + count = 0; + for (uint32_t i = 1; i <= game.maxclients; i++) + { + e = g_edicts + i; + e->client->resp.voted = false; + if (e->inuse) + count++; + } + + if (count < 2) + { + gi.LocClient_Print(ent, PRINT_HIGH, "Not enough players for election.\n"); + return false; + } + + ctfgame.etarget = ent; + ctfgame.election = type; + ctfgame.evotes = 0; + ctfgame.needvotes = (int) ((count * electpercentage->value) / 100); + ctfgame.electtime = level.time + 20_sec; // twenty seconds for election + Q_strlcpy(ctfgame.emsg, msg, sizeof(ctfgame.emsg)); + + // tell everyone + gi.Broadcast_Print(PRINT_CHAT, ctfgame.emsg); + gi.LocBroadcast_Print(PRINT_HIGH, "Type YES or NO to vote on this request.\n"); + gi.LocBroadcast_Print(PRINT_HIGH, "Votes: {} Needed: {} Time left: {}s\n", ctfgame.evotes, ctfgame.needvotes, + (ctfgame.electtime - level.time).seconds()); + + return true; +} + +void DoRespawn(edict_t *ent); + +void CTFResetAllPlayers() +{ + uint32_t i; + edict_t *ent; + + for (i = 1; i <= game.maxclients; i++) + { + ent = g_edicts + i; + if (!ent->inuse) + continue; + + if (ent->client->menu) + PMenu_Close(ent); + + CTFPlayerResetGrapple(ent); + CTFDeadDropFlag(ent); + CTFDeadDropTech(ent); + + ent->client->resp.ctf_team = CTF_NOTEAM; + ent->client->resp.ready = false; + + ent->svflags = SVF_NONE; + ent->flags &= ~FL_GODMODE; + PutClientInServer(ent); + } + + // reset the level + CTFResetTech(); + CTFResetFlags(); + + for (ent = g_edicts + 1, i = 1; i < globals.num_edicts; i++, ent++) + { + if (ent->inuse && !ent->client) + { + if (ent->solid == SOLID_NOT && ent->think == DoRespawn && + ent->nextthink >= level.time) + { + ent->nextthink = 0_ms; + DoRespawn(ent); + } + } + } + if (ctfgame.match == MATCH_SETUP) + ctfgame.matchtime = level.time + gtime_t::from_min(matchsetuptime->value); +} + +void CTFAssignGhost(edict_t *ent) +{ + int ghost, i; + + for (ghost = 0; ghost < MAX_CLIENTS; ghost++) + if (!ctfgame.ghosts[ghost].code) + break; + if (ghost == MAX_CLIENTS) + return; + ctfgame.ghosts[ghost].team = ent->client->resp.ctf_team; + ctfgame.ghosts[ghost].score = 0; + for (;;) + { + ctfgame.ghosts[ghost].code = irandom(10000, 100000); + for (i = 0; i < MAX_CLIENTS; i++) + if (i != ghost && ctfgame.ghosts[i].code == ctfgame.ghosts[ghost].code) + break; + if (i == MAX_CLIENTS) + break; + } + ctfgame.ghosts[ghost].ent = ent; + Q_strlcpy(ctfgame.ghosts[ghost].netname, ent->client->pers.netname, sizeof(ctfgame.ghosts[ghost].netname)); + ent->client->resp.ghost = ctfgame.ghosts + ghost; + gi.LocClient_Print(ent, PRINT_CHAT, "Your ghost code is **** {} ****\n", ctfgame.ghosts[ghost].code); + gi.LocClient_Print(ent, PRINT_HIGH, "If you lose connection, you can rejoin with your score intact by typing \"ghost {}\".\n", + ctfgame.ghosts[ghost].code); +} + +// start a match +void CTFStartMatch() +{ + edict_t *ent; + + ctfgame.match = MATCH_GAME; + ctfgame.matchtime = level.time + gtime_t::from_min(matchtime->value); + ctfgame.countdown = false; + + ctfgame.team1 = ctfgame.team2 = 0; + + memset(ctfgame.ghosts, 0, sizeof(ctfgame.ghosts)); + + for (uint32_t i = 1; i <= game.maxclients; i++) + { + ent = g_edicts + i; + if (!ent->inuse) + continue; + + ent->client->resp.score = 0; + ent->client->resp.ctf_state = 0; + ent->client->resp.ghost = nullptr; + + gi.LocCenter_Print(ent, "******************\n\nMATCH HAS STARTED!\n\n******************"); + + if (ent->client->resp.ctf_team != CTF_NOTEAM) + { + // make up a ghost code + CTFAssignGhost(ent); + CTFPlayerResetGrapple(ent); + ent->svflags = SVF_NOCLIENT; + ent->flags &= ~FL_GODMODE; + + ent->client->respawn_time = level.time + random_time(1_sec, 4_sec); + ent->client->ps.pmove.pm_type = PM_DEAD; + ent->client->anim_priority = ANIM_DEATH; + ent->s.frame = FRAME_death308 - 1; + ent->client->anim_end = FRAME_death308; + ent->deadflag = true; + ent->movetype = MOVETYPE_NOCLIP; + ent->client->ps.gunindex = 0; + ent->client->ps.gunskin = 0; + gi.linkentity(ent); + } + } +} + +void CTFEndMatch() +{ + ctfgame.match = MATCH_POST; + gi.LocBroadcast_Print(PRINT_CHAT, "MATCH COMPLETED!\n"); + + CTFCalcScores(); + + gi.LocBroadcast_Print(PRINT_HIGH, "RED TEAM: {} captures, {} points\n", + ctfgame.team1, ctfgame.total1); + gi.LocBroadcast_Print(PRINT_HIGH, "BLUE TEAM: {} captures, {} points\n", + ctfgame.team2, ctfgame.total2); + + if (ctfgame.team1 > ctfgame.team2) + gi.LocBroadcast_Print(PRINT_CHAT, "$g_ctf_red_wins_caps", + ctfgame.team1 - ctfgame.team2); + else if (ctfgame.team2 > ctfgame.team1) + gi.LocBroadcast_Print(PRINT_CHAT, "$g_ctf_blue_wins_caps", + ctfgame.team2 - ctfgame.team1); + else if (ctfgame.total1 > ctfgame.total2) // frag tie breaker + gi.LocBroadcast_Print(PRINT_CHAT, "$g_ctf_red_wins_points", + ctfgame.total1 - ctfgame.total2); + else if (ctfgame.total2 > ctfgame.total1) + gi.LocBroadcast_Print(PRINT_CHAT, "$g_ctf_blue_wins_points", + ctfgame.total2 - ctfgame.total1); + else + gi.LocBroadcast_Print(PRINT_CHAT, "$g_ctf_tie_game"); + + EndDMLevel(); +} + +bool CTFNextMap() +{ + if (ctfgame.match == MATCH_POST) + { + ctfgame.match = MATCH_SETUP; + CTFResetAllPlayers(); + return true; + } + return false; +} + +void CTFWinElection() +{ + switch (ctfgame.election) + { + case ELECT_MATCH: + // reset into match mode + if (competition->integer < 3) + gi.cvar_set("competition", "2"); + ctfgame.match = MATCH_SETUP; + CTFResetAllPlayers(); + break; + + case ELECT_ADMIN: + ctfgame.etarget->client->resp.admin = true; + gi.LocBroadcast_Print(PRINT_HIGH, "{} has become an admin.\n", ctfgame.etarget->client->pers.netname); + gi.LocClient_Print(ctfgame.etarget, PRINT_HIGH, "Type 'admin' to access the adminstration menu.\n"); + break; + + case ELECT_MAP: + gi.LocBroadcast_Print(PRINT_HIGH, "{} is warping to level {}.\n", + ctfgame.etarget->client->pers.netname, ctfgame.elevel); + Q_strlcpy(level.forcemap, ctfgame.elevel, sizeof(level.forcemap)); + EndDMLevel(); + break; + + default: + break; + } + ctfgame.election = ELECT_NONE; +} + +void CTFVoteYes(edict_t *ent) +{ + if (ctfgame.election == ELECT_NONE) + { + gi.LocClient_Print(ent, PRINT_HIGH, "No election is in progress.\n"); + return; + } + if (ent->client->resp.voted) + { + gi.LocClient_Print(ent, PRINT_HIGH, "You already voted.\n"); + return; + } + if (ctfgame.etarget == ent) + { + gi.LocClient_Print(ent, PRINT_HIGH, "You can't vote for yourself.\n"); + return; + } + + ent->client->resp.voted = true; + + ctfgame.evotes++; + if (ctfgame.evotes == ctfgame.needvotes) + { + // the election has been won + CTFWinElection(); + return; + } + gi.LocBroadcast_Print(PRINT_HIGH, "{}\n", ctfgame.emsg); + gi.LocBroadcast_Print(PRINT_CHAT, "Votes: {} Needed: {} Time left: {}s\n", ctfgame.evotes, ctfgame.needvotes, + (ctfgame.electtime - level.time).seconds()); +} + +void CTFVoteNo(edict_t *ent) +{ + if (ctfgame.election == ELECT_NONE) + { + gi.LocClient_Print(ent, PRINT_HIGH, "No election is in progress.\n"); + return; + } + if (ent->client->resp.voted) + { + gi.LocClient_Print(ent, PRINT_HIGH, "You already voted.\n"); + return; + } + if (ctfgame.etarget == ent) + { + gi.LocClient_Print(ent, PRINT_HIGH, "You can't vote for yourself.\n"); + return; + } + + ent->client->resp.voted = true; + + gi.LocBroadcast_Print(PRINT_HIGH, "{}\n", ctfgame.emsg); + gi.LocBroadcast_Print(PRINT_CHAT, "Votes: {} Needed: {} Time left: {}s\n", ctfgame.evotes, ctfgame.needvotes, + (ctfgame.electtime - level.time).seconds()); +} + +void CTFReady(edict_t *ent) +{ + uint32_t i, j; + edict_t *e; + uint32_t t1, t2; + + if (ent->client->resp.ctf_team == CTF_NOTEAM) + { + gi.LocClient_Print(ent, PRINT_HIGH, "Pick a team first (hit for menu)\n"); + return; + } + + if (ctfgame.match != MATCH_SETUP) + { + gi.LocClient_Print(ent, PRINT_HIGH, "A match is not being setup.\n"); + return; + } + + if (ent->client->resp.ready) + { + gi.LocClient_Print(ent, PRINT_HIGH, "You have already commited.\n"); + return; + } + + ent->client->resp.ready = true; + gi.LocBroadcast_Print(PRINT_HIGH, "{} is ready.\n", ent->client->pers.netname); + + t1 = t2 = 0; + for (j = 0, i = 1; i <= game.maxclients; i++) + { + e = g_edicts + i; + if (!e->inuse) + continue; + if (e->client->resp.ctf_team != CTF_NOTEAM && !e->client->resp.ready) + j++; + if (e->client->resp.ctf_team == CTF_TEAM1) + t1++; + else if (e->client->resp.ctf_team == CTF_TEAM2) + t2++; + } + if (!j && t1 && t2) + { + // everyone has commited + gi.LocBroadcast_Print(PRINT_CHAT, "All players have committed. Match starting\n"); + ctfgame.match = MATCH_PREGAME; + ctfgame.matchtime = level.time + gtime_t::from_sec(matchstarttime->value); + ctfgame.countdown = false; + gi.positioned_sound(world->s.origin, world, CHAN_AUTO | CHAN_RELIABLE, gi.soundindex("misc/talk1.wav"), 1, ATTN_NONE, 0); + } +} + +void CTFNotReady(edict_t *ent) +{ + if (ent->client->resp.ctf_team == CTF_NOTEAM) + { + gi.LocClient_Print(ent, PRINT_HIGH, "Pick a team first (hit for menu)\n"); + return; + } + + if (ctfgame.match != MATCH_SETUP && ctfgame.match != MATCH_PREGAME) + { + gi.LocClient_Print(ent, PRINT_HIGH, "A match is not being setup.\n"); + return; + } + + if (!ent->client->resp.ready) + { + gi.LocClient_Print(ent, PRINT_HIGH, "You haven't commited.\n"); + return; + } + + ent->client->resp.ready = false; + gi.LocBroadcast_Print(PRINT_HIGH, "{} is no longer ready.\n", ent->client->pers.netname); + + if (ctfgame.match == MATCH_PREGAME) + { + gi.LocBroadcast_Print(PRINT_CHAT, "Match halted.\n"); + ctfgame.match = MATCH_SETUP; + ctfgame.matchtime = level.time + gtime_t::from_min(matchsetuptime->value); + } +} + +void CTFGhost(edict_t *ent) +{ + int i; + int n; + + if (gi.argc() < 2) + { + gi.LocClient_Print(ent, PRINT_HIGH, "Usage: ghost \n"); + return; + } + + if (ent->client->resp.ctf_team != CTF_NOTEAM) + { + gi.LocClient_Print(ent, PRINT_HIGH, "You are already in the game.\n"); + return; + } + if (ctfgame.match != MATCH_GAME) + { + gi.LocClient_Print(ent, PRINT_HIGH, "No match is in progress.\n"); + return; + } + + n = atoi(gi.argv(1)); + + for (i = 0; i < MAX_CLIENTS; i++) + { + if (ctfgame.ghosts[i].code && ctfgame.ghosts[i].code == n) + { + gi.LocClient_Print(ent, PRINT_HIGH, "Ghost code accepted, your position has been reinstated.\n"); + ctfgame.ghosts[i].ent->client->resp.ghost = nullptr; + ent->client->resp.ctf_team = ctfgame.ghosts[i].team; + ent->client->resp.ghost = ctfgame.ghosts + i; + ent->client->resp.score = ctfgame.ghosts[i].score; + ent->client->resp.ctf_state = 0; + ctfgame.ghosts[i].ent = ent; + ent->svflags = SVF_NONE; + ent->flags &= ~FL_GODMODE; + PutClientInServer(ent); + gi.LocBroadcast_Print(PRINT_HIGH, "{} has been reinstated to {} team.\n", + ent->client->pers.netname, CTFTeamName(ent->client->resp.ctf_team)); + return; + } + } + gi.LocClient_Print(ent, PRINT_HIGH, "Invalid ghost code.\n"); +} + +bool CTFMatchSetup() +{ + if (ctfgame.match == MATCH_SETUP || ctfgame.match == MATCH_PREGAME) + return true; + return false; +} + +bool CTFMatchOn() +{ + if (ctfgame.match == MATCH_GAME) + return true; + return false; +} + +/*-----------------------------------------------------------------------*/ + +void CTFJoinTeam1(edict_t *ent, pmenuhnd_t *p); +void CTFJoinTeam2(edict_t *ent, pmenuhnd_t *p); +void CTFReturnToMain(edict_t *ent, pmenuhnd_t *p); +void CTFChaseCam(edict_t *ent, pmenuhnd_t *p); + +static const int jmenu_level = 1; +static const int jmenu_match = 2; +static const int jmenu_red = 4; +static const int jmenu_blue = 7; +static const int jmenu_chase = 10; +static const int jmenu_reqmatch = 12; + +const pmenu_t joinmenu[] = { + { "*$g_pc_3wctf", PMENU_ALIGN_CENTER, nullptr }, + { "", PMENU_ALIGN_CENTER, nullptr }, + { "", PMENU_ALIGN_CENTER, nullptr }, + { "", PMENU_ALIGN_CENTER, nullptr }, + { "$g_pc_join_red_team", PMENU_ALIGN_LEFT, CTFJoinTeam1 }, + { "", PMENU_ALIGN_LEFT, nullptr }, + { "", PMENU_ALIGN_LEFT, nullptr }, + { "$g_pc_join_blue_team", PMENU_ALIGN_LEFT, CTFJoinTeam2 }, + { "", PMENU_ALIGN_LEFT, nullptr }, + { "", PMENU_ALIGN_LEFT, nullptr }, + { "$g_pc_chase_camera", PMENU_ALIGN_LEFT, CTFChaseCam }, + { "", PMENU_ALIGN_LEFT, nullptr }, + { "", PMENU_ALIGN_LEFT, nullptr }, +}; + +const pmenu_t nochasemenu[] = { + { "$g_pc_3wctf", PMENU_ALIGN_CENTER, nullptr }, + { "", PMENU_ALIGN_CENTER, nullptr }, + { "", PMENU_ALIGN_CENTER, nullptr }, + { "$g_pc_no_chase", PMENU_ALIGN_LEFT, nullptr }, + { "", PMENU_ALIGN_CENTER, nullptr }, + { "$g_pc_return", PMENU_ALIGN_LEFT, CTFReturnToMain } +}; + +void CTFJoinTeam(edict_t *ent, ctfteam_t desired_team) +{ + PMenu_Close(ent); + + ent->svflags &= ~SVF_NOCLIENT; + ent->client->resp.ctf_team = desired_team; + ent->client->resp.ctf_state = 0; + char value[MAX_INFO_VALUE] = { 0 }; + gi.Info_ValueForKey(ent->client->pers.userinfo, "skin", value, sizeof(value)); + CTFAssignSkin(ent, value); + + // assign a ghost if we are in match mode + if (ctfgame.match == MATCH_GAME) + { + if (ent->client->resp.ghost) + ent->client->resp.ghost->code = 0; + ent->client->resp.ghost = nullptr; + CTFAssignGhost(ent); + } + + PutClientInServer(ent); + + G_PostRespawn(ent); + + gi.LocBroadcast_Print(PRINT_HIGH, "$g_joined_team", + ent->client->pers.netname, CTFTeamName(desired_team)); + + if (ctfgame.match == MATCH_SETUP) + { + gi.LocCenter_Print(ent, "Type \"ready\" in console to ready up.\n"); + } + + // if anybody has a menu open, update it immediately + CTFDirtyTeamMenu(); +} + +void CTFJoinTeam1(edict_t *ent, pmenuhnd_t *p) +{ + CTFJoinTeam(ent, CTF_TEAM1); +} + +void CTFJoinTeam2(edict_t *ent, pmenuhnd_t *p) +{ + CTFJoinTeam(ent, CTF_TEAM2); +} + +static void CTFNoChaseCamUpdate(edict_t *ent) +{ + pmenu_t *entries = ent->client->menu->entries; + + SetGameName(&entries[0]); + SetLevelName(&entries[jmenu_level]); +} + +void CTFChaseCam(edict_t *ent, pmenuhnd_t *p) +{ + edict_t *e; + + CTFJoinTeam(ent, CTF_NOTEAM); + + if (ent->client->chase_target) + { + ent->client->chase_target = nullptr; + ent->client->ps.pmove.pm_flags &= ~(PMF_NO_POSITIONAL_PREDICTION | PMF_NO_ANGULAR_PREDICTION); + PMenu_Close(ent); + return; + } + + for (uint32_t i = 1; i <= game.maxclients; i++) + { + e = g_edicts + i; + if (e->inuse && e->solid != SOLID_NOT) + { + ent->client->chase_target = e; + PMenu_Close(ent); + ent->client->update_chase = true; + return; + } + } + + PMenu_Close(ent); + PMenu_Open(ent, nochasemenu, -1, sizeof(nochasemenu) / sizeof(pmenu_t), nullptr, CTFNoChaseCamUpdate); +} + +void CTFReturnToMain(edict_t *ent, pmenuhnd_t *p) +{ + PMenu_Close(ent); + CTFOpenJoinMenu(ent); +} + +void CTFRequestMatch(edict_t *ent, pmenuhnd_t *p) +{ + PMenu_Close(ent); + + CTFBeginElection(ent, ELECT_MATCH, G_Fmt("{} has requested to switch to competition mode.\n", + ent->client->pers.netname).data()); +} + +void DeathmatchScoreboard(edict_t *ent); + +void CTFShowScores(edict_t *ent, pmenu_t *p) +{ + PMenu_Close(ent); + + ent->client->showscores = true; + ent->client->showinventory = false; + DeathmatchScoreboard(ent); +} + +void CTFUpdateJoinMenu(edict_t *ent) +{ + pmenu_t *entries = ent->client->menu->entries; + + SetGameName(entries); + + if (ctfgame.match >= MATCH_PREGAME && matchlock->integer) + { + Q_strlcpy(entries[jmenu_red].text, "MATCH IS LOCKED", sizeof(entries[jmenu_red].text)); + entries[jmenu_red].SelectFunc = nullptr; + Q_strlcpy(entries[jmenu_blue].text, " (entry is not permitted)", sizeof(entries[jmenu_blue].text)); + entries[jmenu_blue].SelectFunc = nullptr; + } + else + { + if (ctfgame.match >= MATCH_PREGAME) + { + Q_strlcpy(entries[jmenu_red].text, "Join Red MATCH Team", sizeof(entries[jmenu_red].text)); + Q_strlcpy(entries[jmenu_blue].text, "Join Blue MATCH Team", sizeof(entries[jmenu_blue].text)); + } + else + { + Q_strlcpy(entries[jmenu_red].text, "$g_pc_join_red_team", sizeof(entries[jmenu_red].text)); + Q_strlcpy(entries[jmenu_blue].text, "$g_pc_join_blue_team", sizeof(entries[jmenu_blue].text)); + } + entries[jmenu_red].SelectFunc = CTFJoinTeam1; + entries[jmenu_blue].SelectFunc = CTFJoinTeam2; + } + + // KEX_FIXME: what's this for? + if (g_teamplay_force_join->string && *g_teamplay_force_join->string) + { + if (Q_strcasecmp(g_teamplay_force_join->string, "red") == 0) + { + entries[jmenu_blue].text[0] = '\0'; + entries[jmenu_blue].SelectFunc = nullptr; + } + else if (Q_strcasecmp(g_teamplay_force_join->string, "blue") == 0) + { + entries[jmenu_red].text[0] = '\0'; + entries[jmenu_red].SelectFunc = nullptr; + } + } + + if (ent->client->chase_target) + Q_strlcpy(entries[jmenu_chase].text, "$g_pc_leave_chase_camera", sizeof(entries[jmenu_chase].text)); + else + Q_strlcpy(entries[jmenu_chase].text, "$g_pc_chase_camera", sizeof(entries[jmenu_chase].text)); + + SetLevelName(entries + jmenu_level); + + uint32_t num1 = 0, num2 = 0; + for (uint32_t i = 0; i < game.maxclients; i++) + { + if (!g_edicts[i + 1].inuse) + continue; + if (game.clients[i].resp.ctf_team == CTF_TEAM1) + num1++; + else if (game.clients[i].resp.ctf_team == CTF_TEAM2) + num2++; + } + + switch (ctfgame.match) + { + case MATCH_NONE: + entries[jmenu_match].text[0] = '\0'; + break; + + case MATCH_SETUP: + Q_strlcpy(entries[jmenu_match].text, "*MATCH SETUP IN PROGRESS", sizeof(entries[jmenu_match].text)); + break; + + case MATCH_PREGAME: + Q_strlcpy(entries[jmenu_match].text, "*MATCH STARTING", sizeof(entries[jmenu_match].text)); + break; + + case MATCH_GAME: + Q_strlcpy(entries[jmenu_match].text, "*MATCH IN PROGRESS", sizeof(entries[jmenu_match].text)); + break; + + default: + break; + } + + if (*entries[jmenu_red].text) + { + Q_strlcpy(entries[jmenu_red + 1].text, "$g_pc_playercount", sizeof(entries[jmenu_red + 1].text)); + G_FmtTo(entries[jmenu_red + 1].text_arg1, "{}", num1); + } + else + { + entries[jmenu_red + 1].text[0] = '\0'; + entries[jmenu_red + 1].text_arg1[0] = '\0'; + } + if (*entries[jmenu_blue].text) + { + Q_strlcpy(entries[jmenu_blue + 1].text, "$g_pc_playercount", sizeof(entries[jmenu_blue + 1].text)); + G_FmtTo(entries[jmenu_blue + 1].text_arg1, "{}", num2); + } + else + { + entries[jmenu_blue + 1].text[0] = '\0'; + entries[jmenu_blue + 1].text_arg1[0] = '\0'; + } + + entries[jmenu_reqmatch].text[0] = '\0'; + entries[jmenu_reqmatch].SelectFunc = nullptr; + if (competition->integer && ctfgame.match < MATCH_SETUP) + { + Q_strlcpy(entries[jmenu_reqmatch].text, "Request Match", sizeof(entries[jmenu_reqmatch].text)); + entries[jmenu_reqmatch].SelectFunc = CTFRequestMatch; + } +} + +void CTFOpenJoinMenu(edict_t *ent) +{ + uint32_t num1 = 0, num2 = 0; + for (uint32_t i = 0; i < game.maxclients; i++) + { + if (!g_edicts[i + 1].inuse) + continue; + if (game.clients[i].resp.ctf_team == CTF_TEAM1) + num1++; + else if (game.clients[i].resp.ctf_team == CTF_TEAM2) + num2++; + } + + int team; + + if (num1 > num2) + team = CTF_TEAM1; + else if (num2 > num1) + team = CTF_TEAM2; + team = brandom() ? CTF_TEAM1 : CTF_TEAM2; + + PMenu_Open(ent, joinmenu, team, sizeof(joinmenu) / sizeof(pmenu_t), nullptr, CTFUpdateJoinMenu); +} + +bool CTFStartClient(edict_t *ent) +{ + if (!G_TeamplayEnabled()) + return false; + + if (ent->client->resp.ctf_team != CTF_NOTEAM) + return false; + + if ((!(ent->svflags & SVF_BOT) && !g_teamplay_force_join->integer) || ctfgame.match >= MATCH_SETUP) + { + // start as 'observer' + ent->movetype = MOVETYPE_NOCLIP; + ent->solid = SOLID_NOT; + ent->svflags |= SVF_NOCLIENT; + ent->client->resp.ctf_team = CTF_NOTEAM; + ent->client->resp.spectator = true; + ent->client->ps.gunindex = 0; + ent->client->ps.gunskin = 0; + gi.linkentity(ent); + + CTFOpenJoinMenu(ent); + return true; + } + return false; +} + +void CTFObserver(edict_t *ent) +{ + if (!G_TeamplayEnabled()) + return; + + // start as 'observer' + if (ent->movetype == MOVETYPE_NOCLIP) + CTFPlayerResetGrapple(ent); + + CTFDeadDropFlag(ent); + CTFDeadDropTech(ent); + + ent->deadflag = false; + ent->movetype = MOVETYPE_NOCLIP; + ent->solid = SOLID_NOT; + ent->svflags |= SVF_NOCLIENT; + ent->client->resp.ctf_team = CTF_NOTEAM; + ent->client->ps.gunindex = 0; + ent->client->ps.gunskin = 0; + ent->client->resp.score = 0; + PutClientInServer(ent); +} + +bool CTFInMatch() +{ + if (ctfgame.match > MATCH_NONE) + return true; + return false; +} + +bool CTFCheckRules() +{ + int t; + uint32_t i, j; + char text[64]; + edict_t *ent; + + if (ctfgame.election != ELECT_NONE && ctfgame.electtime <= level.time) + { + gi.LocBroadcast_Print(PRINT_CHAT, "Election timed out and has been cancelled.\n"); + ctfgame.election = ELECT_NONE; + } + + if (ctfgame.match != MATCH_NONE) + { + t = (ctfgame.matchtime - level.time).seconds(); + + // no team warnings in match mode + ctfgame.warnactive = 0; + + if (t <= 0) + { // time ended on something + switch (ctfgame.match) + { + case MATCH_SETUP: + // go back to normal mode + if (competition->integer < 3) + { + ctfgame.match = MATCH_NONE; + gi.cvar_set("competition", "1"); + CTFResetAllPlayers(); + } + else + { + // reset the time + ctfgame.matchtime = level.time + gtime_t::from_min(matchsetuptime->value); + } + return false; + + case MATCH_PREGAME: + // match started! + CTFStartMatch(); + gi.positioned_sound(world->s.origin, world, CHAN_AUTO | CHAN_RELIABLE, gi.soundindex("misc/tele_up.wav"), 1, ATTN_NONE, 0); + return false; + + case MATCH_GAME: + // match ended! + CTFEndMatch(); + gi.positioned_sound(world->s.origin, world, CHAN_AUTO | CHAN_RELIABLE, gi.soundindex("misc/bigtele.wav"), 1, ATTN_NONE, 0); + return false; + + default: + break; + } + } + + if (t == ctfgame.lasttime) + return false; + + ctfgame.lasttime = t; + + switch (ctfgame.match) + { + case MATCH_SETUP: + for (j = 0, i = 1; i <= game.maxclients; i++) + { + ent = g_edicts + i; + if (!ent->inuse) + continue; + if (ent->client->resp.ctf_team != CTF_NOTEAM && + !ent->client->resp.ready) + j++; + } + + if (competition->integer < 3) + G_FmtTo(text, "{:02}:{:02} SETUP: {} not ready", t / 60, t % 60, j); + else + G_FmtTo(text, "SETUP: {} not ready", j); + + gi.configstring(CONFIG_CTF_MATCH, text); + break; + + case MATCH_PREGAME: + G_FmtTo(text, "{:02}:{:02} UNTIL START", t / 60, t % 60); + gi.configstring(CONFIG_CTF_MATCH, text); + + if (t <= 10 && !ctfgame.countdown) + { + ctfgame.countdown = true; + gi.positioned_sound(world->s.origin, world, CHAN_AUTO | CHAN_RELIABLE, gi.soundindex("world/10_0.wav"), 1, ATTN_NONE, 0); + } + break; + + case MATCH_GAME: + G_FmtTo(text, "{:02}:{:02} MATCH", t / 60, t % 60); + gi.configstring(CONFIG_CTF_MATCH, text); + if (t <= 10 && !ctfgame.countdown) + { + ctfgame.countdown = true; + gi.positioned_sound(world->s.origin, world, CHAN_AUTO | CHAN_RELIABLE, gi.soundindex("world/10_0.wav"), 1, ATTN_NONE, 0); + } + break; + + default: + break; + } + return false; + } + else + { + int team1 = 0, team2 = 0; + + if (level.time == gtime_t::from_sec(ctfgame.lasttime)) + return false; + ctfgame.lasttime = level.time.seconds(); + // this is only done in non-match (public) mode + + if (warn_unbalanced->integer) + { + // count up the team totals + for (i = 1; i <= game.maxclients; i++) + { + ent = g_edicts + i; + if (!ent->inuse) + continue; + if (ent->client->resp.ctf_team == CTF_TEAM1) + team1++; + else if (ent->client->resp.ctf_team == CTF_TEAM2) + team2++; + } + + if (team1 - team2 >= 2 && team2 >= 2) + { + if (ctfgame.warnactive != CTF_TEAM1) + { + ctfgame.warnactive = CTF_TEAM1; + gi.configstring(CONFIG_CTF_TEAMINFO, "WARNING: Red has too many players"); + } + } + else if (team2 - team1 >= 2 && team1 >= 2) + { + if (ctfgame.warnactive != CTF_TEAM2) + { + ctfgame.warnactive = CTF_TEAM2; + gi.configstring(CONFIG_CTF_TEAMINFO, "WARNING: Blue has too many players"); + } + } + else + ctfgame.warnactive = 0; + } + else + ctfgame.warnactive = 0; + } + + if (capturelimit->integer && + (ctfgame.team1 >= capturelimit->integer || + ctfgame.team2 >= capturelimit->integer)) + { + gi.LocBroadcast_Print(PRINT_HIGH, "$g_capturelimit_hit"); + return true; + } + return false; +} + +/*-------------------------------------------------------------------------- + * just here to help old map conversions + *--------------------------------------------------------------------------*/ + +TOUCH(old_teleporter_touch) (edict_t *self, edict_t *other, const trace_t &tr, bool other_touching_self) -> void +{ + edict_t *dest; + vec3_t forward; + + if (!other->client) + return; + dest = G_PickTarget(self->target); + if (!dest) + { + gi.Com_Print("Couldn't find destination\n"); + return; + } + + // ZOID + CTFPlayerResetGrapple(other); + // ZOID + + // unlink to make sure it can't possibly interfere with KillBox + gi.unlinkentity(other); + + other->s.origin = dest->s.origin; + other->s.old_origin = dest->s.origin; + // other->s.origin[2] += 10; + + // clear the velocity and hold them in place briefly + other->velocity = {}; + other->client->ps.pmove.pm_time = 160; // hold time + other->client->ps.pmove.pm_flags |= PMF_TIME_TELEPORT; + + // draw the teleport splash at source and on the player + self->enemy->s.event = EV_PLAYER_TELEPORT; + other->s.event = EV_PLAYER_TELEPORT; + + // set angles + other->client->ps.pmove.delta_angles = dest->s.angles - other->client->resp.cmd_angles; + + other->s.angles[PITCH] = 0; + other->s.angles[YAW] = dest->s.angles[YAW]; + other->s.angles[ROLL] = 0; + other->client->ps.viewangles = dest->s.angles; + other->client->v_angle = dest->s.angles; + + // give a little forward velocity + AngleVectors(other->client->v_angle, forward, nullptr, nullptr); + other->velocity = forward * 200; + + gi.linkentity(other); + + // kill anything at the destination + if (!KillBox(other, true)) + { + } + + // [Paril-KEX] move sphere, if we own it + if (other->client->owned_sphere) + { + edict_t *sphere = other->client->owned_sphere; + sphere->s.origin = other->s.origin; + sphere->s.origin[2] = other->absmax[2]; + sphere->s.angles[YAW] = other->s.angles[YAW]; + gi.linkentity(sphere); + } +} + +/*QUAKED trigger_ctf_teleport (0.5 0.5 0.5) ? +Players touching this will be teleported +*/ +void SP_trigger_ctf_teleport(edict_t *ent) +{ + edict_t *s; + int i; + + if (!ent->target) + { + gi.Com_Print("teleporter without a target.\n"); + G_FreeEdict(ent); + return; + } + + ent->svflags |= SVF_NOCLIENT; + ent->solid = SOLID_TRIGGER; + ent->touch = old_teleporter_touch; + gi.setmodel(ent, ent->model); + gi.linkentity(ent); + + // noise maker and splash effect dude + s = G_Spawn(); + ent->enemy = s; + for (i = 0; i < 3; i++) + s->s.origin[i] = ent->mins[i] + (ent->maxs[i] - ent->mins[i]) / 2; + s->s.sound = gi.soundindex("world/hum1.wav"); + gi.linkentity(s); +} + +/*QUAKED info_ctf_teleport_destination (0.5 0.5 0.5) (-16 -16 -24) (16 16 32) +Point trigger_teleports at these. +*/ +void SP_info_ctf_teleport_destination(edict_t *ent) +{ + ent->s.origin[2] += 16; +} + +/*----------------------------------------------------------------------------------*/ +/* ADMIN */ + +struct admin_settings_t +{ + int matchlen; + int matchsetuplen; + int matchstartlen; + bool weaponsstay; + bool instantitems; + bool quaddrop; + bool instantweap; + bool matchlock; +}; + +void CTFAdmin_UpdateSettings(edict_t *ent, pmenuhnd_t *setmenu); +void CTFOpenAdminMenu(edict_t *ent); + +void CTFAdmin_SettingsApply(edict_t *ent, pmenuhnd_t *p) +{ + admin_settings_t *settings = (admin_settings_t *) p->arg; + + if (settings->matchlen != matchtime->value) + { + gi.LocBroadcast_Print(PRINT_HIGH, "{} changed the match length to {} minutes.\n", + ent->client->pers.netname, settings->matchlen); + if (ctfgame.match == MATCH_GAME) + { + // in the middle of a match, change it on the fly + ctfgame.matchtime = (ctfgame.matchtime - gtime_t::from_min(matchtime->value)) + gtime_t::from_min(settings->matchlen); + } + ; + gi.cvar_set("matchtime", G_Fmt("{}", settings->matchlen).data()); + } + + if (settings->matchsetuplen != matchsetuptime->value) + { + gi.LocBroadcast_Print(PRINT_HIGH, "{} changed the match setup time to {} minutes.\n", + ent->client->pers.netname, settings->matchsetuplen); + if (ctfgame.match == MATCH_SETUP) + { + // in the middle of a match, change it on the fly + ctfgame.matchtime = (ctfgame.matchtime - gtime_t::from_min(matchsetuptime->value)) + gtime_t::from_min(settings->matchsetuplen); + } + ; + gi.cvar_set("matchsetuptime", G_Fmt("{}", settings->matchsetuplen).data()); + } + + if (settings->matchstartlen != matchstarttime->value) + { + gi.LocBroadcast_Print(PRINT_HIGH, "{} changed the match start time to {} seconds.\n", + ent->client->pers.netname, settings->matchstartlen); + if (ctfgame.match == MATCH_PREGAME) + { + // in the middle of a match, change it on the fly + ctfgame.matchtime = (ctfgame.matchtime - gtime_t::from_sec(matchstarttime->value)) + gtime_t::from_sec(settings->matchstartlen); + } + gi.cvar_set("matchstarttime", G_Fmt("{}", settings->matchstartlen).data()); + } + + if (settings->weaponsstay != !!g_dm_weapons_stay->integer) + { + gi.LocBroadcast_Print(PRINT_HIGH, "{} turned {} weapons stay.\n", + ent->client->pers.netname, settings->weaponsstay ? "on" : "off"); + gi.cvar_set("g_dm_weapons_stay", settings->weaponsstay ? "1" : "0"); + } + + if (settings->instantitems != !!g_dm_instant_items->integer) + { + gi.LocBroadcast_Print(PRINT_HIGH, "{} turned {} instant items.\n", + ent->client->pers.netname, settings->instantitems ? "on" : "off"); + gi.cvar_set("g_dm_instant_items", settings->instantitems ? "1" : "0"); + } + + if (settings->quaddrop != (bool) !g_dm_no_quad_drop->integer) + { + gi.LocBroadcast_Print(PRINT_HIGH, "{} turned {} quad drop.\n", + ent->client->pers.netname, settings->quaddrop ? "on" : "off"); + gi.cvar_set("g_dm_no_quad_drop", !settings->quaddrop ? "1" : "0"); + } + + if (settings->instantweap != !!g_instant_weapon_switch->integer) + { + gi.LocBroadcast_Print(PRINT_HIGH, "{} turned {} instant weapons.\n", + ent->client->pers.netname, settings->instantweap ? "on" : "off"); + gi.cvar_set("g_instant_weapon_switch", settings->instantweap ? "1" : "0"); + } + + if (settings->matchlock != !!matchlock->integer) + { + gi.LocBroadcast_Print(PRINT_HIGH, "{} turned {} match lock.\n", + ent->client->pers.netname, settings->matchlock ? "on" : "off"); + gi.cvar_set("matchlock", settings->matchlock ? "1" : "0"); + } + + PMenu_Close(ent); + CTFOpenAdminMenu(ent); +} + +void CTFAdmin_SettingsCancel(edict_t *ent, pmenuhnd_t *p) +{ + PMenu_Close(ent); + CTFOpenAdminMenu(ent); +} + +void CTFAdmin_ChangeMatchLen(edict_t *ent, pmenuhnd_t *p) +{ + admin_settings_t *settings = (admin_settings_t *) p->arg; + + settings->matchlen = (settings->matchlen % 60) + 5; + if (settings->matchlen < 5) + settings->matchlen = 5; + + CTFAdmin_UpdateSettings(ent, p); +} + +void CTFAdmin_ChangeMatchSetupLen(edict_t *ent, pmenuhnd_t *p) +{ + admin_settings_t *settings = (admin_settings_t *) p->arg; + + settings->matchsetuplen = (settings->matchsetuplen % 60) + 5; + if (settings->matchsetuplen < 5) + settings->matchsetuplen = 5; + + CTFAdmin_UpdateSettings(ent, p); +} + +void CTFAdmin_ChangeMatchStartLen(edict_t *ent, pmenuhnd_t *p) +{ + admin_settings_t *settings = (admin_settings_t *) p->arg; + + settings->matchstartlen = (settings->matchstartlen % 600) + 10; + if (settings->matchstartlen < 20) + settings->matchstartlen = 20; + + CTFAdmin_UpdateSettings(ent, p); +} + +void CTFAdmin_ChangeWeapStay(edict_t *ent, pmenuhnd_t *p) +{ + admin_settings_t *settings = (admin_settings_t *) p->arg; + + settings->weaponsstay = !settings->weaponsstay; + CTFAdmin_UpdateSettings(ent, p); +} + +void CTFAdmin_ChangeInstantItems(edict_t *ent, pmenuhnd_t *p) +{ + admin_settings_t *settings = (admin_settings_t *) p->arg; + + settings->instantitems = !settings->instantitems; + CTFAdmin_UpdateSettings(ent, p); +} + +void CTFAdmin_ChangeQuadDrop(edict_t *ent, pmenuhnd_t *p) +{ + admin_settings_t *settings = (admin_settings_t *) p->arg; + + settings->quaddrop = !settings->quaddrop; + CTFAdmin_UpdateSettings(ent, p); +} + +void CTFAdmin_ChangeInstantWeap(edict_t *ent, pmenuhnd_t *p) +{ + admin_settings_t *settings = (admin_settings_t *) p->arg; + + settings->instantweap = !settings->instantweap; + CTFAdmin_UpdateSettings(ent, p); +} + +void CTFAdmin_ChangeMatchLock(edict_t *ent, pmenuhnd_t *p) +{ + admin_settings_t *settings = (admin_settings_t *) p->arg; + + settings->matchlock = !settings->matchlock; + CTFAdmin_UpdateSettings(ent, p); +} + +void CTFAdmin_UpdateSettings(edict_t *ent, pmenuhnd_t *setmenu) +{ + int i = 2; + admin_settings_t *settings = (admin_settings_t *) setmenu->arg; + + PMenu_UpdateEntry(setmenu->entries + i, G_Fmt("Match Len: {:2} mins", settings->matchlen).data(), PMENU_ALIGN_LEFT, CTFAdmin_ChangeMatchLen); + i++; + + PMenu_UpdateEntry(setmenu->entries + i, G_Fmt("Match Setup Len: {:2} mins", settings->matchsetuplen).data(), PMENU_ALIGN_LEFT, CTFAdmin_ChangeMatchSetupLen); + i++; + + PMenu_UpdateEntry(setmenu->entries + i, G_Fmt("Match Start Len: {:2} secs", settings->matchstartlen).data(), PMENU_ALIGN_LEFT, CTFAdmin_ChangeMatchStartLen); + i++; + + PMenu_UpdateEntry(setmenu->entries + i, G_Fmt("Weapons Stay: {}", settings->weaponsstay ? "Yes" : "No").data(), PMENU_ALIGN_LEFT, CTFAdmin_ChangeWeapStay); + i++; + + PMenu_UpdateEntry(setmenu->entries + i, G_Fmt("Instant Items: {}", settings->instantitems ? "Yes" : "No").data(), PMENU_ALIGN_LEFT, CTFAdmin_ChangeInstantItems); + i++; + + PMenu_UpdateEntry(setmenu->entries + i, G_Fmt("Quad Drop: {}", settings->quaddrop ? "Yes" : "No").data(), PMENU_ALIGN_LEFT, CTFAdmin_ChangeQuadDrop); + i++; + + PMenu_UpdateEntry(setmenu->entries + i, G_Fmt("Instant Weapons: {}", settings->instantweap ? "Yes" : "No").data(), PMENU_ALIGN_LEFT, CTFAdmin_ChangeInstantWeap); + i++; + + PMenu_UpdateEntry(setmenu->entries + i, G_Fmt("Match Lock: {}", settings->matchlock ? "Yes" : "No").data(), PMENU_ALIGN_LEFT, CTFAdmin_ChangeMatchLock); + i++; + + PMenu_Update(ent); +} + +const pmenu_t def_setmenu[] = { + { "*Settings Menu", PMENU_ALIGN_CENTER, nullptr }, + { "", PMENU_ALIGN_CENTER, nullptr }, + { "", PMENU_ALIGN_LEFT, nullptr }, // int matchlen; + { "", PMENU_ALIGN_LEFT, nullptr }, // int matchsetuplen; + { "", PMENU_ALIGN_LEFT, nullptr }, // int matchstartlen; + { "", PMENU_ALIGN_LEFT, nullptr }, // bool weaponsstay; + { "", PMENU_ALIGN_LEFT, nullptr }, // bool instantitems; + { "", PMENU_ALIGN_LEFT, nullptr }, // bool quaddrop; + { "", PMENU_ALIGN_LEFT, nullptr }, // bool instantweap; + { "", PMENU_ALIGN_LEFT, nullptr }, // bool matchlock; + { "", PMENU_ALIGN_LEFT, nullptr }, + { "Apply", PMENU_ALIGN_LEFT, CTFAdmin_SettingsApply }, + { "Cancel", PMENU_ALIGN_LEFT, CTFAdmin_SettingsCancel } +}; + +void CTFAdmin_Settings(edict_t *ent, pmenuhnd_t *p) +{ + admin_settings_t *settings; + pmenuhnd_t *menu; + + PMenu_Close(ent); + + settings = (admin_settings_t *) gi.TagMalloc(sizeof(*settings), TAG_LEVEL); + + settings->matchlen = matchtime->integer; + settings->matchsetuplen = matchsetuptime->integer; + settings->matchstartlen = matchstarttime->integer; + settings->weaponsstay = g_dm_weapons_stay->integer; + settings->instantitems = g_dm_instant_items->integer; + settings->quaddrop = !g_dm_no_quad_drop->integer; + settings->instantweap = g_instant_weapon_switch->integer != 0; + settings->matchlock = matchlock->integer != 0; + + menu = PMenu_Open(ent, def_setmenu, -1, sizeof(def_setmenu) / sizeof(pmenu_t), settings, nullptr); + CTFAdmin_UpdateSettings(ent, menu); +} + +void CTFAdmin_MatchSet(edict_t *ent, pmenuhnd_t *p) +{ + PMenu_Close(ent); + + if (ctfgame.match == MATCH_SETUP) + { + gi.LocBroadcast_Print(PRINT_CHAT, "Match has been forced to start.\n"); + ctfgame.match = MATCH_PREGAME; + ctfgame.matchtime = level.time + gtime_t::from_sec(matchstarttime->value); + gi.positioned_sound(world->s.origin, world, CHAN_AUTO | CHAN_RELIABLE, gi.soundindex("misc/talk1.wav"), 1, ATTN_NONE, 0); + ctfgame.countdown = false; + } + else if (ctfgame.match == MATCH_GAME) + { + gi.LocBroadcast_Print(PRINT_CHAT, "Match has been forced to terminate.\n"); + ctfgame.match = MATCH_SETUP; + ctfgame.matchtime = level.time + gtime_t::from_min(matchsetuptime->value); + CTFResetAllPlayers(); + } +} + +void CTFAdmin_MatchMode(edict_t *ent, pmenuhnd_t *p) +{ + PMenu_Close(ent); + + if (ctfgame.match != MATCH_SETUP) + { + if (competition->integer < 3) + gi.cvar_set("competition", "2"); + ctfgame.match = MATCH_SETUP; + CTFResetAllPlayers(); + } +} + +void CTFAdmin_Reset(edict_t *ent, pmenuhnd_t *p) +{ + PMenu_Close(ent); + + // go back to normal mode + gi.LocBroadcast_Print(PRINT_CHAT, "Match mode has been terminated, reseting to normal game.\n"); + ctfgame.match = MATCH_NONE; + gi.cvar_set("competition", "1"); + CTFResetAllPlayers(); +} + +void CTFAdmin_Cancel(edict_t *ent, pmenuhnd_t *p) +{ + PMenu_Close(ent); +} + +pmenu_t adminmenu[] = { + { "*Administration Menu", PMENU_ALIGN_CENTER, nullptr }, + { "", PMENU_ALIGN_CENTER, nullptr }, // blank + { "Settings", PMENU_ALIGN_LEFT, CTFAdmin_Settings }, + { "", PMENU_ALIGN_LEFT, nullptr }, + { "", PMENU_ALIGN_LEFT, nullptr }, + { "Cancel", PMENU_ALIGN_LEFT, CTFAdmin_Cancel }, + { "", PMENU_ALIGN_CENTER, nullptr }, +}; + +void CTFOpenAdminMenu(edict_t *ent) +{ + adminmenu[3].text[0] = '\0'; + adminmenu[3].SelectFunc = nullptr; + adminmenu[4].text[0] = '\0'; + adminmenu[4].SelectFunc = nullptr; + if (ctfgame.match == MATCH_SETUP) + { + Q_strlcpy(adminmenu[3].text, "Force start match", sizeof(adminmenu[3].text)); + adminmenu[3].SelectFunc = CTFAdmin_MatchSet; + Q_strlcpy(adminmenu[4].text, "Reset to pickup mode", sizeof(adminmenu[4].text)); + adminmenu[4].SelectFunc = CTFAdmin_Reset; + } + else if (ctfgame.match == MATCH_GAME || ctfgame.match == MATCH_PREGAME) + { + Q_strlcpy(adminmenu[3].text, "Cancel match", sizeof(adminmenu[3].text)); + adminmenu[3].SelectFunc = CTFAdmin_MatchSet; + } + else if (ctfgame.match == MATCH_NONE && competition->integer) + { + Q_strlcpy(adminmenu[3].text, "Switch to match mode", sizeof(adminmenu[3].text)); + adminmenu[3].SelectFunc = CTFAdmin_MatchMode; + } + + // if (ent->client->menu) + // PMenu_Close(ent->client->menu); + + PMenu_Open(ent, adminmenu, -1, sizeof(adminmenu) / sizeof(pmenu_t), nullptr, nullptr); +} + +void CTFAdmin(edict_t *ent) +{ + if (!allow_admin->integer) + { + gi.LocClient_Print(ent, PRINT_HIGH, "Administration is disabled\n"); + return; + } + + if (gi.argc() > 1 && admin_password->string && *admin_password->string && + !ent->client->resp.admin && strcmp(admin_password->string, gi.argv(1)) == 0) + { + ent->client->resp.admin = true; + gi.LocBroadcast_Print(PRINT_HIGH, "{} has become an admin.\n", ent->client->pers.netname); + gi.LocClient_Print(ent, PRINT_HIGH, "Type 'admin' to access the adminstration menu.\n"); + } + + if (!ent->client->resp.admin) + { + CTFBeginElection(ent, ELECT_ADMIN, G_Fmt("{} has requested admin rights.\n", + ent->client->pers.netname).data()); + return; + } + + if (ent->client->menu) + PMenu_Close(ent); + + CTFOpenAdminMenu(ent); +} + +/*----------------------------------------------------------------*/ + +void CTFStats(edict_t *ent) +{ + if (!G_TeamplayEnabled()) + return; + + ghost_t *g; + static std::string text; + edict_t *e2; + + text.clear(); + + if (ctfgame.match == MATCH_SETUP) + { + for (uint32_t i = 1; i <= game.maxclients; i++) + { + e2 = g_edicts + i; + if (!e2->inuse) + continue; + if (!e2->client->resp.ready && e2->client->resp.ctf_team != CTF_NOTEAM) + { + std::string_view str = G_Fmt("{} is not ready.\n", e2->client->pers.netname); + + if (text.length() + str.length() < MAX_CTF_STAT_LENGTH - 50) + text += str; + } + } + } + + uint32_t i; + for (i = 0, g = ctfgame.ghosts; i < MAX_CLIENTS; i++, g++) + if (g->ent) + break; + + if (i == MAX_CLIENTS) + { + if (!text.length()) + text = "No statistics available.\n"; + + gi.Client_Print(ent, PRINT_HIGH, text.c_str()); + return; + } + + text += " #|Name |Score|Kills|Death|BasDf|CarDf|Effcy|\n"; + + for (i = 0, g = ctfgame.ghosts; i < MAX_CLIENTS; i++, g++) + { + if (!*g->netname) + continue; + + int32_t e; + + if (g->deaths + g->kills == 0) + e = 50; + else + e = g->kills * 100 / (g->kills + g->deaths); + std::string_view str = G_Fmt("{:3}|{:<16.16}|{:5}|{:5}|{:5}|{:5}|{:5}|{:4}%|\n", + g->number, + g->netname, + g->score, + g->kills, + g->deaths, + g->basedef, + g->carrierdef, + e); + + if (text.length() + str.length() > MAX_CTF_STAT_LENGTH - 50) + { + text += "And more...\n"; + break; + } + + text += str; + } + + gi.Client_Print(ent, PRINT_HIGH, text.c_str()); +} + +void CTFPlayerList(edict_t *ent) +{ + static std::string text; + edict_t *e2; + + // number, name, connect time, ping, score, admin + text.clear(); + + for (uint32_t i = 1; i <= game.maxclients; i++) + { + e2 = g_edicts + i; + if (!e2->inuse) + continue; + + std::string_view str = G_Fmt("{:3} {:<16.16} {:02}:{:02} {:4} {:3}{}{}\n", + i, + e2->client->pers.netname, + (level.time - e2->client->resp.entertime).milliseconds() / 60000, + ((level.time - e2->client->resp.entertime).milliseconds() % 60000) / 1000, + e2->client->ping, + e2->client->resp.score, + (ctfgame.match == MATCH_SETUP || ctfgame.match == MATCH_PREGAME) ? (e2->client->resp.ready ? " (ready)" : " (notready)") : "", + e2->client->resp.admin ? " (admin)" : ""); + + if (text.length() + str.length() > MAX_CTF_STAT_LENGTH - 50) + { + text += "And more...\n"; + break; + + } + + text += str; + } + + gi.Client_Print(ent, PRINT_HIGH, text.data()); +} + +void CTFWarp(edict_t *ent) +{ + char *token; + + if (gi.argc() < 2) + { + gi.LocClient_Print(ent, PRINT_HIGH, "Where do you want to warp to?\n"); + gi.LocClient_Print(ent, PRINT_HIGH, "Available levels are: {}\n", warp_list->string); + return; + } + + const char *mlist = warp_list->string; + + while (*(token = COM_Parse(&mlist))) + { + if (Q_strcasecmp(token, gi.argv(1)) == 0) + break; + } + + if (!*token) + { + gi.LocClient_Print(ent, PRINT_HIGH, "Unknown CTF level.\n"); + gi.LocClient_Print(ent, PRINT_HIGH, "Available levels are: {}\n", warp_list->string); + return; + } + + if (ent->client->resp.admin) + { + gi.LocBroadcast_Print(PRINT_HIGH, "{} is warping to level {}.\n", + ent->client->pers.netname, gi.argv(1)); + Q_strlcpy(level.forcemap, gi.argv(1), sizeof(level.forcemap)); + EndDMLevel(); + return; + } + + if (CTFBeginElection(ent, ELECT_MAP, G_Fmt("{} has requested warping to level {}.\n", + ent->client->pers.netname, gi.argv(1)).data())) + Q_strlcpy(ctfgame.elevel, gi.argv(1), sizeof(ctfgame.elevel)); +} + +void CTFBoot(edict_t *ent) +{ + edict_t *targ; + + if (!ent->client->resp.admin) + { + gi.LocClient_Print(ent, PRINT_HIGH, "You are not an admin.\n"); + return; + } + + if (gi.argc() < 2) + { + gi.LocClient_Print(ent, PRINT_HIGH, "Who do you want to kick?\n"); + return; + } + + if (*gi.argv(1) < '0' && *gi.argv(1) > '9') + { + gi.LocClient_Print(ent, PRINT_HIGH, "Specify the player number to kick.\n"); + return; + } + + uint32_t i = strtoul(gi.argv(1), nullptr, 10); + if (i < 1 || i > game.maxclients) + { + gi.LocClient_Print(ent, PRINT_HIGH, "Invalid player number.\n"); + return; + } + + targ = g_edicts + i; + if (!targ->inuse) + { + gi.LocClient_Print(ent, PRINT_HIGH, "That player number is not connected.\n"); + return; + } + + gi.AddCommandString(G_Fmt("kick {}\n", i - 1).data()); +} + +void CTFSetPowerUpEffect(edict_t *ent, effects_t def) +{ + if (ent->client->resp.ctf_team == CTF_TEAM1 && def == EF_QUAD) + ent->s.effects |= EF_PENT; // red + else if (ent->client->resp.ctf_team == CTF_TEAM2 && def == EF_PENT) + ent->s.effects |= EF_QUAD; // blue + else + ent->s.effects |= def; +} diff --git a/rerelease/ctf/g_ctf.h b/rerelease/ctf/g_ctf.h new file mode 100644 index 0000000..3b89e69 --- /dev/null +++ b/rerelease/ctf/g_ctf.h @@ -0,0 +1,162 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. + +#define CTF_VERSION 1.52 +#define CTF_VSTRING2(x) #x +#define CTF_VSTRING(x) CTF_VSTRING2(x) +#define CTF_STRING_VERSION CTF_VSTRING(CTF_VERSION) + +enum ctfteam_t +{ + CTF_NOTEAM, + CTF_TEAM1, + CTF_TEAM2 +}; + +enum ctfgrapplestate_t +{ + CTF_GRAPPLE_STATE_FLY, + CTF_GRAPPLE_STATE_PULL, + CTF_GRAPPLE_STATE_HANG +}; + +struct ghost_t +{ + char netname[MAX_NETNAME]; + int number; + + // stats + int deaths; + int kills; + int caps; + int basedef; + int carrierdef; + + int code; // ghost code + ctfteam_t team; // team + int score; // frags at time of disconnect + edict_t *ent; +}; + +extern cvar_t *ctf; +extern cvar_t *g_teamplay_force_join; +extern cvar_t *teamplay; + +constexpr const char *CTF_TEAM1_SKIN = "ctf_r"; +constexpr const char *CTF_TEAM2_SKIN = "ctf_b"; + +constexpr int32_t CTF_CAPTURE_BONUS = 15; // what you get for capture +constexpr int32_t CTF_TEAM_BONUS = 10; // what your team gets for capture +constexpr int32_t CTF_RECOVERY_BONUS = 1; // what you get for recovery +constexpr int32_t CTF_FLAG_BONUS = 0; // what you get for picking up enemy flag +constexpr int32_t CTF_FRAG_CARRIER_BONUS = 2; // what you get for fragging enemy flag carrier +constexpr gtime_t CTF_FLAG_RETURN_TIME = 40_sec; // seconds until auto return + +constexpr int32_t CTF_CARRIER_DANGER_PROTECT_BONUS = 2; // bonus for fraggin someone who has recently hurt your flag carrier +constexpr int32_t CTF_CARRIER_PROTECT_BONUS = 1; // bonus for fraggin someone while either you or your target are near your flag carrier +constexpr int32_t CTF_FLAG_DEFENSE_BONUS = 1; // bonus for fraggin someone while either you or your target are near your flag +constexpr int32_t CTF_RETURN_FLAG_ASSIST_BONUS = 1; // awarded for returning a flag that causes a capture to happen almost immediately +constexpr int32_t CTF_FRAG_CARRIER_ASSIST_BONUS = 2; // award for fragging a flag carrier if a capture happens almost immediately + +constexpr float CTF_TARGET_PROTECT_RADIUS = 400; // the radius around an object being defended where a target will be worth extra frags +constexpr float CTF_ATTACKER_PROTECT_RADIUS = 400; // the radius around an object being defended where an attacker will get extra frags when making kills + +constexpr gtime_t CTF_CARRIER_DANGER_PROTECT_TIMEOUT = 8_sec; +constexpr gtime_t CTF_FRAG_CARRIER_ASSIST_TIMEOUT = 10_sec; +constexpr gtime_t CTF_RETURN_FLAG_ASSIST_TIMEOUT = 10_sec; + +constexpr gtime_t CTF_AUTO_FLAG_RETURN_TIMEOUT = 30_sec; // number of seconds before dropped flag auto-returns + +constexpr gtime_t CTF_TECH_TIMEOUT = 60_sec; // seconds before techs spawn again + +constexpr int32_t CTF_DEFAULT_GRAPPLE_SPEED = 650; // speed of grapple in flight +constexpr float CTF_DEFAULT_GRAPPLE_PULL_SPEED = 650; // speed player is pulled at + +void CTFInit(); +void CTFSpawn(); +void CTFPrecache(); +bool G_TeamplayEnabled(); +void G_AdjustTeamScore(ctfteam_t team, int32_t offset); + +void SP_info_player_team1(edict_t *self); +void SP_info_player_team2(edict_t *self); + +const char *CTFTeamName(int team); +const char *CTFOtherTeamName(int team); +void CTFAssignSkin(edict_t *ent, const char *s); +void CTFAssignTeam(gclient_t *who); +edict_t *SelectCTFSpawnPoint(edict_t *ent, bool force_spawn); +bool CTFPickup_Flag(edict_t *ent, edict_t *other); +void CTFDrop_Flag(edict_t *ent, gitem_t *item); +void CTFEffects(edict_t *player); +void CTFCalcScores(); +void CTFCalcRankings(std::array &player_ranks); // [Paril-KEX] +void CheckEndTDMLevel(); // [Paril-KEX] +void SetCTFStats(edict_t *ent); +void CTFDeadDropFlag(edict_t *self); +void CTFScoreboardMessage(edict_t *ent, edict_t *killer); +void CTFTeam_f(edict_t *ent); +void CTFID_f(edict_t *ent); +#ifndef KEX_Q2_GAME +void CTFSay_Team(edict_t *who, const char *msg); +#endif +void CTFFlagSetup(edict_t *ent); +void CTFResetFlag(int ctf_team); +void CTFFragBonuses(edict_t *targ, edict_t *inflictor, edict_t *attacker); +void CTFCheckHurtCarrier(edict_t *targ, edict_t *attacker); +void CTFDirtyTeamMenu(); + +// GRAPPLE +void CTFWeapon_Grapple(edict_t *ent); +void CTFPlayerResetGrapple(edict_t *ent); +void CTFGrapplePull(edict_t *self); +void CTFResetGrapple(edict_t *self); + +// TECH +gitem_t *CTFWhat_Tech(edict_t *ent); +bool CTFPickup_Tech(edict_t *ent, edict_t *other); +void CTFDrop_Tech(edict_t *ent, gitem_t *item); +void CTFDeadDropTech(edict_t *ent); +void CTFSetupTechSpawn(); +int CTFApplyResistance(edict_t *ent, int dmg); +int CTFApplyStrength(edict_t *ent, int dmg); +bool CTFApplyStrengthSound(edict_t *ent); +bool CTFApplyHaste(edict_t *ent); +void CTFApplyHasteSound(edict_t *ent); +void CTFApplyRegeneration(edict_t *ent); +bool CTFHasRegeneration(edict_t *ent); +void CTFRespawnTech(edict_t *ent); +void CTFResetTech(); + +void CTFOpenJoinMenu(edict_t *ent); +bool CTFStartClient(edict_t *ent); +void CTFVoteYes(edict_t *ent); +void CTFVoteNo(edict_t *ent); +void CTFReady(edict_t *ent); +void CTFNotReady(edict_t *ent); +bool CTFNextMap(); +bool CTFMatchSetup(); +bool CTFMatchOn(); +void CTFGhost(edict_t *ent); +void CTFAdmin(edict_t *ent); +bool CTFInMatch(); +void CTFStats(edict_t *ent); +void CTFWarp(edict_t *ent); +void CTFBoot(edict_t *ent); +void CTFPlayerList(edict_t *ent); + +bool CTFCheckRules(); + +void SP_misc_ctf_banner(edict_t *ent); +void SP_misc_ctf_small_banner(edict_t *ent); + +void UpdateChaseCam(edict_t *ent); +void ChaseNext(edict_t *ent); +void ChasePrev(edict_t *ent); + +void CTFObserver(edict_t *ent); + +void SP_trigger_teleport(edict_t *ent); +void SP_info_teleport_destination(edict_t *ent); + +void CTFSetPowerUpEffect(edict_t *ent, effects_t def); diff --git a/rerelease/ctf/p_ctf_menu.cpp b/rerelease/ctf/p_ctf_menu.cpp new file mode 100644 index 0000000..317a964 --- /dev/null +++ b/rerelease/ctf/p_ctf_menu.cpp @@ -0,0 +1,282 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +#include "../g_local.h" + +// Note that the pmenu entries are duplicated +// this is so that a static set of pmenu entries can be used +// for multiple clients and changed without interference +// note that arg will be freed when the menu is closed, it must be allocated memory +pmenuhnd_t *PMenu_Open(edict_t *ent, const pmenu_t *entries, int cur, int num, void *arg, UpdateFunc_t UpdateFunc) +{ + pmenuhnd_t *hnd; + const pmenu_t *p; + int i; + + if (!ent->client) + return nullptr; + + if (ent->client->menu) + { + gi.Com_Print("warning, ent already has a menu\n"); + PMenu_Close(ent); + } + + hnd = (pmenuhnd_t *) gi.TagMalloc(sizeof(*hnd), TAG_LEVEL); + hnd->UpdateFunc = UpdateFunc; + + hnd->arg = arg; + hnd->entries = (pmenu_t *) gi.TagMalloc(sizeof(pmenu_t) * num, TAG_LEVEL); + memcpy(hnd->entries, entries, sizeof(pmenu_t) * num); + // duplicate the strings since they may be from static memory + for (i = 0; i < num; i++) + Q_strlcpy(hnd->entries[i].text, entries[i].text, sizeof(entries[i].text)); + + hnd->num = num; + + if (cur < 0 || !entries[cur].SelectFunc) + { + for (i = 0, p = entries; i < num; i++, p++) + if (p->SelectFunc) + break; + } + else + i = cur; + + if (i >= num) + hnd->cur = -1; + else + hnd->cur = i; + + ent->client->showscores = true; + ent->client->inmenu = true; + ent->client->menu = hnd; + + if (UpdateFunc) + UpdateFunc(ent); + + PMenu_Do_Update(ent); + gi.unicast(ent, true); + + return hnd; +} + +void PMenu_Close(edict_t *ent) +{ + pmenuhnd_t *hnd; + + if (!ent->client->menu) + return; + + hnd = ent->client->menu; + gi.TagFree(hnd->entries); + if (hnd->arg) + gi.TagFree(hnd->arg); + gi.TagFree(hnd); + ent->client->menu = nullptr; + ent->client->showscores = false; +} + +// only use on pmenu's that have been called with PMenu_Open +void PMenu_UpdateEntry(pmenu_t *entry, const char *text, int align, SelectFunc_t SelectFunc) +{ + Q_strlcpy(entry->text, text, sizeof(entry->text)); + entry->align = align; + entry->SelectFunc = SelectFunc; +} + +#include "../g_statusbar.h" + +void PMenu_Do_Update(edict_t *ent) +{ + int i; + pmenu_t *p; + int x; + pmenuhnd_t *hnd; + const char *t; + bool alt = false; + + if (!ent->client->menu) + { + gi.Com_Print("warning: ent has no menu\n"); + return; + } + + hnd = ent->client->menu; + + if (hnd->UpdateFunc) + hnd->UpdateFunc(ent); + + statusbar_t sb; + + sb.xv(32).yv(8).picn("inventory"); + + for (i = 0, p = hnd->entries; i < hnd->num; i++, p++) + { + if (!*(p->text)) + continue; // blank line + + t = p->text; + + if (*t == '*') + { + alt = true; + t++; + } + + sb.yv(32 + i * 8); + + const char *loc_func = "loc_string"; + + if (p->align == PMENU_ALIGN_CENTER) + { + x = 0; + loc_func = "loc_cstring"; + } + else if (p->align == PMENU_ALIGN_RIGHT) + { + x = 260; + loc_func = "loc_rstring"; + } + else + x = 64; + + sb.xv(x); + + sb.sb << loc_func; + + if (hnd->cur == i || alt) + sb.sb << '2'; + + sb.sb << " 1 \"" << t << "\" \"" << p->text_arg1 << "\" "; + + if (hnd->cur == i) + { + sb.xv(56); + sb.string2("\">\""); + } + + alt = false; + } + + gi.WriteByte(svc_layout); + gi.WriteString(sb.sb.str().c_str()); +} + +void PMenu_Update(edict_t *ent) +{ + if (!ent->client->menu) + { + gi.Com_Print("warning: ent has no menu\n"); + return; + } + + if (level.time - ent->client->menutime >= 1_sec) + { + // been a second or more since last update, update now + PMenu_Do_Update(ent); + gi.unicast(ent, true); + ent->client->menutime = level.time + 1_sec; + ent->client->menudirty = false; + } + ent->client->menutime = level.time; + ent->client->menudirty = true; +} + +void PMenu_Next(edict_t *ent) +{ + pmenuhnd_t *hnd; + int i; + pmenu_t *p; + + if (!ent->client->menu) + { + gi.Com_Print("warning: ent has no menu\n"); + return; + } + + hnd = ent->client->menu; + + if (hnd->cur < 0) + return; // no selectable entries + + i = hnd->cur; + p = hnd->entries + hnd->cur; + do + { + i++; + p++; + if (i == hnd->num) + { + i = 0; + p = hnd->entries; + } + if (p->SelectFunc) + break; + } while (i != hnd->cur); + + hnd->cur = i; + + PMenu_Update(ent); +} + +void PMenu_Prev(edict_t *ent) +{ + pmenuhnd_t *hnd; + int i; + pmenu_t *p; + + if (!ent->client->menu) + { + gi.Com_Print("warning: ent has no menu\n"); + return; + } + + hnd = ent->client->menu; + + if (hnd->cur < 0) + return; // no selectable entries + + i = hnd->cur; + p = hnd->entries + hnd->cur; + do + { + if (i == 0) + { + i = hnd->num - 1; + p = hnd->entries + i; + } + else + { + i--; + p--; + } + if (p->SelectFunc) + break; + } while (i != hnd->cur); + + hnd->cur = i; + + PMenu_Update(ent); +} + +void PMenu_Select(edict_t *ent) +{ + pmenuhnd_t *hnd; + pmenu_t *p; + + if (!ent->client->menu) + { + gi.Com_Print("warning: ent has no menu\n"); + return; + } + + hnd = ent->client->menu; + + if (hnd->cur < 0) + return; // no selectable entries + + p = hnd->entries + hnd->cur; + + if (p->SelectFunc) + p->SelectFunc(ent, hnd); +} diff --git a/rerelease/ctf/p_ctf_menu.h b/rerelease/ctf/p_ctf_menu.h new file mode 100644 index 0000000..8b7bfe6 --- /dev/null +++ b/rerelease/ctf/p_ctf_menu.h @@ -0,0 +1,41 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. + +enum +{ + PMENU_ALIGN_LEFT, + PMENU_ALIGN_CENTER, + PMENU_ALIGN_RIGHT +}; + +struct pmenu_t; + +using UpdateFunc_t = void (*)(edict_t *ent); + +struct pmenuhnd_t +{ + pmenu_t *entries; + int cur; + int num; + void *arg; + UpdateFunc_t UpdateFunc; +}; + +using SelectFunc_t = void (*)(edict_t *ent, pmenuhnd_t *hnd); + +struct pmenu_t +{ + char text[64]; + int align; + SelectFunc_t SelectFunc; + char text_arg1[64]; +}; + +pmenuhnd_t *PMenu_Open(edict_t *ent, const pmenu_t *entries, int cur, int num, void *arg, UpdateFunc_t UpdateFunc); +void PMenu_Close(edict_t *ent); +void PMenu_UpdateEntry(pmenu_t *entry, const char *text, int align, SelectFunc_t SelectFunc); +void PMenu_Do_Update(edict_t *ent); +void PMenu_Update(edict_t *ent); +void PMenu_Next(edict_t *ent); +void PMenu_Prev(edict_t *ent); +void PMenu_Select(edict_t *ent); diff --git a/rerelease/g_ai.cpp b/rerelease/g_ai.cpp new file mode 100644 index 0000000..b542b5e --- /dev/null +++ b/rerelease/g_ai.cpp @@ -0,0 +1,1769 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// g_ai.c + +#include "g_local.h" + +bool FindTarget(edict_t *self); +bool ai_checkattack(edict_t *self, float dist); + +bool enemy_vis; +bool enemy_infront; +float enemy_yaw; + +// ROGUE +constexpr float MAX_SIDESTEP = 8.0f; +// ROGUE + +//============================================================================ + +/* +================= +AI_GetSightClient + +For a given monster, check active players to see +who we can see. We don't care who we see, as long +as it's something we can shoot. +================= +*/ +edict_t *AI_GetSightClient(edict_t *self) +{ + if (level.intermissiontime) + return nullptr; + + edict_t **visible_players = (edict_t **) alloca(sizeof(edict_t *) * game.maxclients); + size_t num_visible = 0; + + for (auto player : active_players()) + { + if (player->health <= 0 || player->deadflag || !player->solid) + continue; + else if (player->flags & (FL_NOTARGET | FL_DISGUISED)) + continue; + + // if we're touching them, allow to pass through + if (!boxes_intersect(self->absmin, self->absmax, player->absmin, player->absmax)) + { + if ((!(self->monsterinfo.aiflags & AI_THIRD_EYE) && !infront(self, player)) || !visible(self, player)) + continue; + } + + visible_players[num_visible++] = player; // got one + } + + if (!num_visible) + return nullptr; + + return visible_players[irandom(num_visible)]; +} + +//============================================================================ + +/* +============= +ai_move + +Move the specified distance at current facing. +This replaces the QC functions: ai_forward, ai_back, ai_pain, and ai_painforward +============== +*/ +void ai_move(edict_t *self, float dist) +{ + M_walkmove(self, self->s.angles[YAW], dist); +} + +/* +============= +ai_stand + +Used for standing around and looking for players +Distance is for slight position adjustments needed by the animations +============== +*/ +void ai_stand(edict_t *self, float dist) +{ + vec3_t v; + // ROGUE + bool retval; + // ROGUE + + if (dist || (self->monsterinfo.aiflags & AI_ALTERNATE_FLY)) + M_walkmove(self, self->s.angles[YAW], dist); + + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + { + // [Paril-KEX] check if we've been pushed out of our point_combat + if (self->movetarget) + { + if (!boxes_intersect(self->absmin, self->absmax, self->movetarget->absmin, self->movetarget->absmax)) + { + self->monsterinfo.aiflags &= ~AI_STAND_GROUND; + self->monsterinfo.aiflags |= AI_COMBAT_POINT; + self->goalentity = self->movetarget; + self->monsterinfo.run(self); + return; + } + } + + if (self->enemy) + { + v = self->enemy->s.origin - self->s.origin; + self->ideal_yaw = vectoyaw(v); + if (!FacingIdeal(self) && (self->monsterinfo.aiflags & AI_TEMP_STAND_GROUND)) + { + self->monsterinfo.aiflags &= ~(AI_STAND_GROUND | AI_TEMP_STAND_GROUND); + self->monsterinfo.run(self); + } + // ROGUE + if (!(self->monsterinfo.aiflags & AI_MANUAL_STEERING)) + // ROGUE + M_ChangeYaw(self); + // find out if we're going to be shooting + retval = ai_checkattack(self, 0); + // record sightings of player + if ((self->enemy) && (self->enemy->inuse)) + { + if (visible(self, self->enemy)) + { + self->monsterinfo.aiflags &= ~AI_LOST_SIGHT; + self->monsterinfo.last_sighting = self->monsterinfo.saved_goal = self->enemy->s.origin; + self->monsterinfo.blind_fire_target = self->monsterinfo.last_sighting + (self->enemy->velocity * -0.1f); + self->monsterinfo.trail_time = level.time; + self->monsterinfo.blind_fire_delay = 0_ms; + } + else + { + if (FindTarget(self)) + return; + + self->monsterinfo.aiflags |= AI_LOST_SIGHT; + } + + // Paril: fixes rare cases of a stand ground monster being stuck + // aiming at a sound target that they can still see + if ((self->monsterinfo.aiflags & AI_SOUND_TARGET) && !retval) + { + if (FindTarget(self)) + return; + } + } + // check retval to make sure we're not blindfiring + else if (!retval) + { + FindTarget(self); + return; + } + // ROGUE + } + else + FindTarget(self); + return; + } + + // Paril: this fixes a bug somewhere else that sometimes causes + // a monster to be given an enemy without ever calling HuntTarget. + if (self->enemy && !(self->monsterinfo.aiflags & AI_SOUND_TARGET)) + { + HuntTarget(self); + return; + } + + if (FindTarget(self)) + return; + + if (level.time > self->monsterinfo.pausetime) + { + self->monsterinfo.walk(self); + return; + } + + if (!(self->spawnflags & SPAWNFLAG_MONSTER_AMBUSH) && (self->monsterinfo.idle) && + (level.time > self->monsterinfo.idle_time)) + { + if (self->monsterinfo.idle_time) + { + self->monsterinfo.idle(self); + self->monsterinfo.idle_time = level.time + random_time(15_sec, 30_sec); + } + else + { + self->monsterinfo.idle_time = level.time + random_time(15_sec); + } + } +} + +/* +============= +ai_walk + +The monster is walking it's beat +============= +*/ +void ai_walk(edict_t *self, float dist) +{ + edict_t *temp_goal = nullptr; + + if (!self->goalentity && (self->monsterinfo.aiflags & AI_GOOD_GUY)) + { + vec3_t fwd; + AngleVectors(self->s.angles, fwd, nullptr, nullptr); + + temp_goal = G_Spawn(); + temp_goal->s.origin = self->s.origin + fwd * 64; + self->goalentity = temp_goal; + } + + M_MoveToGoal(self, dist); + + if (temp_goal) + { + G_FreeEdict(temp_goal); + self->goalentity = nullptr; + } + + // check for noticing a player + if (FindTarget(self)) + return; + + if ((self->monsterinfo.search) && (level.time > self->monsterinfo.idle_time)) + { + if (self->monsterinfo.idle_time) + { + self->monsterinfo.search(self); + self->monsterinfo.idle_time = level.time + random_time(15_sec, 30_sec); + } + else + { + self->monsterinfo.idle_time = level.time + random_time(15_sec); + } + } +} + +/* +============= +ai_charge + +Turns towards target and advances +Use this call with a distance of 0 to replace ai_face +============== +*/ +void ai_charge(edict_t *self, float dist) +{ + vec3_t v; + // ROGUE + float ofs; + + // PMM - made AI_MANUAL_STEERING affect things differently here .. they turn, but + // don't set the ideal_yaw + + // This is put in there so monsters won't move towards the origin after killing + // a tesla. This could be problematic, so keep an eye on it. + if (!self->enemy || !self->enemy->inuse) // PGM + return; // PGM + + // PMM - save blindfire target + if (visible(self, self->enemy)) + self->monsterinfo.blind_fire_target = self->enemy->s.origin + (self->enemy->velocity * -0.1f); + // pmm + + if (!(self->monsterinfo.aiflags & AI_MANUAL_STEERING)) + { + // ROGUE + v = self->enemy->s.origin - self->s.origin; + self->ideal_yaw = vectoyaw(v); + // ROGUE + } + // ROGUE + M_ChangeYaw(self); + + if (dist || (self->monsterinfo.aiflags & AI_ALTERNATE_FLY)) + // ROGUE + { + if (self->monsterinfo.aiflags & AI_CHARGING) + { + M_MoveToGoal(self, dist); + return; + } + // circle strafe support + if (self->monsterinfo.attack_state == AS_SLIDING) + { + // if we're fighting a tesla, NEVER circle strafe + if ((self->enemy) && (self->enemy->classname) && (!strcmp(self->enemy->classname, "tesla_mine"))) + ofs = 0; + else if (self->monsterinfo.lefty) + ofs = 90; + else + ofs = -90; + + dist *= self->monsterinfo.active_move->sidestep_scale; + + if (M_walkmove(self, self->ideal_yaw + ofs, dist)) + return; + + self->monsterinfo.lefty = !self->monsterinfo.lefty; + M_walkmove(self, self->ideal_yaw - ofs, dist); + } + else + // ROGUE + M_walkmove(self, self->s.angles[YAW], dist); + // ROGUE + } + // ROGUE + + // [Paril-KEX] if our enemy is literally right next to us, give + // us more rotational speed so we don't get circled + if (range_to(self, self->enemy) <= RANGE_MELEE * 2.5f) + M_ChangeYaw(self); +} + +/* +============= +ai_turn + +don't move, but turn towards ideal_yaw +Distance is for slight position adjustments needed by the animations +============= +*/ +void ai_turn(edict_t *self, float dist) +{ + if (dist || (self->monsterinfo.aiflags & AI_ALTERNATE_FLY)) + M_walkmove(self, self->s.angles[YAW], dist); + + if (FindTarget(self)) + return; + + // ROGUE + if (!(self->monsterinfo.aiflags & AI_MANUAL_STEERING)) + // ROGUE + M_ChangeYaw(self); +} + +/* + +.enemy +Will be world if not currently angry at anyone. + +.movetarget +The next path spot to walk toward. If .enemy, ignore .movetarget. +When an enemy is killed, the monster will try to return to it's path. + +.hunt_time +Set to time + something when the player is in sight, but movement straight for +him is blocked. This causes the monster to use wall following code for +movement direction instead of sighting on the player. + +.ideal_yaw +A yaw angle of the intended direction, which will be turned towards at up +to 45 deg / state. If the enemy is in view and hunt_time is not active, +this will be the exact line towards the enemy. + +.pausetime +A monster will leave it's stand state and head towards it's .movetarget when +time > .pausetime. + +walkmove(angle, speed) primitive is all or nothing +*/ + +/* +============= +range_to + +returns the distance of an entity relative to self. +in general, the results determine how an AI reacts: +melee melee range, will become hostile even if back is turned +near visibility and infront, or visibility and show hostile +mid infront and show hostile +> mid only triggered by damage +============= +*/ +float range_to(edict_t *self, edict_t *other) +{ + return distance_between_boxes(self->absmin, self->absmax, other->absmin, other->absmax); +} + +/* +============= +visible + +returns 1 if the entity is visible to self, even if not infront () +============= +*/ +bool visible(edict_t *self, edict_t *other, bool through_glass) +{ + // never visible + if (other->flags & FL_NOVISIBLE) + return false; + + // [Paril-KEX] bit of a hack, but we'll tweak monster-player visibility + // if they have the invisibility powerup. + if (other->client) + { + // always visible in rtest + if (self->hackflags & HACKFLAG_ATTACK_PLAYER) + return self->inuse; + + // fix intermission + if (!other->solid) + return false; + + if (other->client->invisible_time > level.time) + { + // can't see us at all after this time + if (other->client->invisibility_fade_time <= level.time) + return false; + + // otherwise, throw in some randomness + if (frandom() > other->s.alpha) + return false; + } + } + + vec3_t spot1; + vec3_t spot2; + trace_t trace; + + spot1 = self->s.origin; + spot1[2] += self->viewheight; + spot2 = other->s.origin; + spot2[2] += other->viewheight; + + contents_t mask = MASK_OPAQUE; + + if (!through_glass) + mask |= CONTENTS_WINDOW; + + trace = gi.traceline(spot1, spot2, self, mask); + return trace.fraction == 1.0f || trace.ent == other; // PGM +} + +/* +============= +infront + +returns 1 if the entity is in front (in sight) of self +============= +*/ +bool infront(edict_t *self, edict_t *other) +{ + vec3_t vec; + float dot; + vec3_t forward; + + AngleVectors(self->s.angles, forward, nullptr, nullptr); + vec = other->s.origin - self->s.origin; + vec.normalize(); + dot = vec.dot(forward); + return dot > -0.30f; +} + +//============================================================================ + +void HuntTarget(edict_t *self, bool animate_state) +{ + vec3_t vec; + + self->goalentity = self->enemy; + if (animate_state) + { + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + self->monsterinfo.stand(self); + else + self->monsterinfo.run(self); + } + vec = self->enemy->s.origin - self->s.origin; + self->ideal_yaw = vectoyaw(vec); +} + +void FoundTarget(edict_t *self) +{ + // let other monsters see this monster for a while + if (self->enemy->client) + { + // ROGUE + if (self->enemy->flags & FL_DISGUISED) + self->enemy->flags &= ~FL_DISGUISED; + // ROGUE + + self->enemy->client->sight_entity = self; + self->enemy->client->sight_entity_time = level.time; + + self->enemy->show_hostile = level.time + 1_sec; // wake up other monsters + } + + // [Paril-KEX] the first time we spot something, give us a bit of a grace + // period on firing + if (!self->monsterinfo.trail_time) + self->monsterinfo.attack_finished = level.time + 600_ms; + + // give easy/medium a little more reaction time + self->monsterinfo.attack_finished += skill->integer == 0 ? 400_ms : skill->integer == 1 ? 200_ms : 0_ms; + + self->monsterinfo.last_sighting = self->monsterinfo.saved_goal = self->enemy->s.origin; + self->monsterinfo.trail_time = level.time; + // ROGUE + self->monsterinfo.blind_fire_target = self->monsterinfo.last_sighting + (self->enemy->velocity * -0.1f); + self->monsterinfo.blind_fire_delay = 0_ms; + // ROGUE + // [Paril-KEX] for alternate fly, pick a new position immediately + self->monsterinfo.fly_position_time = 0_ms; + + self->monsterinfo.aiflags &= ~AI_THIRD_EYE; + + // Paril: if we're heading to a combat point/path corner, don't + // hunt the new target yet. + if (self->monsterinfo.aiflags & AI_COMBAT_POINT) + return; + + if (!self->combattarget) + { + HuntTarget(self); + return; + } + + self->goalentity = self->movetarget = G_PickTarget(self->combattarget); + if (!self->movetarget) + { + self->goalentity = self->movetarget = self->enemy; + HuntTarget(self); + gi.Com_PrintFmt("{}: combattarget {} not found\n", *self, self->combattarget); + return; + } + + // clear out our combattarget, these are a one shot deal + self->combattarget = nullptr; + self->monsterinfo.aiflags |= AI_COMBAT_POINT; + + // clear the targetname, that point is ours! + // [Paril-KEX] not any more, we can re-use them + //self->movetarget->targetname = nullptr; + self->monsterinfo.pausetime = 0_ms; + + // run for it + self->monsterinfo.run(self); +} + +// [Paril-KEX] monsters that were alerted by players will +// be temporarily stored on player entities, so we can +// check them & get mad at them even around corners +static edict_t *AI_GetMonsterAlertedByPlayers(edict_t *self) +{ + for (auto player : active_players()) + { + // dead + if (player->health <= 0 || player->deadflag || !player->solid) + continue; + + // we didn't alert any other monster, or it wasn't recently + if (!player->client->sight_entity || !(player->client->sight_entity_time >= (level.time - FRAME_TIME_S))) + continue; + + // if we can't see the monster, don't bother + if (!visible(self, player->client->sight_entity)) + continue; + + // probably good + return player->client->sight_entity; + } + + return nullptr; +} + +// [Paril-KEX] per-player sounds +static edict_t *AI_GetSoundClient(edict_t *self, bool direct) +{ + edict_t *best_sound = nullptr; + float best_distance = std::numeric_limits::max(); + + for (auto player : active_players()) + { + // dead + if (player->health <= 0 || player->deadflag || !player->solid) + continue; + + edict_t *sound = direct ? player->client->sound_entity : player->client->sound2_entity; + + if (!sound) + continue; + + // too late + gtime_t &time = direct ? player->client->sound_entity_time : player->client->sound2_entity_time; + + if (!(time >= (level.time - FRAME_TIME_S))) + continue; + + // prefer the closest one we heard + float dist = (self->s.origin - sound->s.origin).length(); + + if (!best_sound || dist < best_distance) + { + best_distance = dist; + best_sound = sound; + } + } + + return best_sound; +} + +bool G_MonsterSourceVisible(edict_t *self, edict_t *client) +{ + // this is where we would check invisibility + float r = range_to(self, client); + + if (r > RANGE_MID) + return false; + + // Paril: revised so that monsters can be woken up + // by players 'seen' and attacked at by other monsters + // if they are close enough. they don't have to be visible. + bool is_visible = + ((r <= RANGE_NEAR && client->show_hostile >= level.time && !(self->spawnflags & SPAWNFLAG_MONSTER_AMBUSH)) || + (visible(self, client) && (r <= RANGE_MELEE || (self->monsterinfo.aiflags & AI_THIRD_EYE) || infront(self, client)))); + + return is_visible; +} + +/* +=========== +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. +============ +*/ +bool FindTarget(edict_t *self) +{ + edict_t *client = nullptr; + bool heardit; + bool ignore_sight_sound = false; + + // [Paril-KEX] if we're in a level transition, don't worry about enemies + if (globals.server_flags & SERVER_FLAG_LOADING) + return false; + + // N64 cutscene behavior + if (self->hackflags & HACKFLAG_END_CUTSCENE) + return false; + + if (self->monsterinfo.aiflags & AI_GOOD_GUY) + { + if (self->goalentity && self->goalentity->inuse && self->goalentity->classname) + { + if (strcmp(self->goalentity->classname, "target_actor") == 0) + return false; + } + + // FIXME look for monsters? + return false; + } + + // if we're going to a combat point, just proceed + if (self->monsterinfo.aiflags & AI_COMBAT_POINT) + return false; + + // if the first spawnflag bit is set, the monster will only wake up on + // really seeing the player, not another monster getting angry or hearing + // something + + // revised behavior so they will wake up if they "see" a player make a noise + // but not weapon impact/explosion noises + heardit = false; + + // Paril: revised so that monsters will first try to consider + // the current sight client immediately if they can see it. + // this fixes them dancing in front of you if you fire every frame. + if ((client = AI_GetSightClient(self))) + { + if (client == self->enemy) + { + return false; + } + } + // check indirect sources + if (!client) + { + // check monsters that were alerted by players; we can only be alerted if we + // can see them + if (!(self->spawnflags & SPAWNFLAG_MONSTER_AMBUSH) && (client = AI_GetMonsterAlertedByPlayers(self))) + { + // KEX_FIXME: when does this happen? + // [Paril-KEX] adjusted to clear the client + // so we can try other things + if (client->enemy == self->enemy || + !G_MonsterSourceVisible(self, client)) + client = nullptr; + } + // ROGUE + + if (client == nullptr) + { + if (level.disguise_violation_time > level.time) + { + client = level.disguise_violator; + } + // ROGUE + else if ((client = AI_GetSoundClient(self, true))) + { + heardit = true; + } + else if (!(self->enemy) && !(self->spawnflags & SPAWNFLAG_MONSTER_AMBUSH) && + (client = AI_GetSoundClient(self, false))) + { + heardit = true; + } + } + } + + if (!client) + return false; // no clients to get mad at + + // if the entity went away, forget it + if (!client->inuse) + return false; + + if (client == self->enemy) + return true; // JDC false; + + // ROGUE - hintpath coop fix + if ((self->monsterinfo.aiflags & AI_HINT_PATH) && coop->integer) + heardit = false; + // ROGUE + + if (client->svflags & SVF_MONSTER) + { + if (!client->enemy) + return false; + if (client->enemy->flags & FL_NOTARGET) + return false; + } + else if (heardit) + { + // pgm - a little more paranoia won't hurt.... + if ((client->owner) && (client->owner->flags & FL_NOTARGET)) + return false; + } + else if (!client->client) + return false; + + if (!heardit) + { + // this is where we would check invisibility + float r = range_to(self, client); + + if (r > RANGE_MID) + return false; + + // Paril: revised so that monsters can be woken up + // by players 'seen' and attacked at by other monsters + // if they are close enough. they don't have to be visible. + bool is_visible = + ((r <= RANGE_NEAR && client->show_hostile >= level.time && !(self->spawnflags & SPAWNFLAG_MONSTER_AMBUSH)) || + (visible(self, client) && (r <= RANGE_MELEE || (self->monsterinfo.aiflags & AI_THIRD_EYE) || infront(self, client)))); + + if (!is_visible) + return false; + + self->enemy = client; + + if (strcmp(self->enemy->classname, "player_noise") != 0) + { + self->monsterinfo.aiflags &= ~AI_SOUND_TARGET; + + if (!self->enemy->client) + { + self->enemy = self->enemy->enemy; + if (!self->enemy->client) + { + self->enemy = nullptr; + return false; + } + } + } + + if (self->enemy->client && self->enemy->client->invisible_time > level.time && self->enemy->client->invisibility_fade_time <= level.time) + { + self->enemy = nullptr; + return false; + } + + if (self->monsterinfo.close_sight_tripped) + ignore_sight_sound = true; + else + self->monsterinfo.close_sight_tripped = true; + } + else // heardit + { + vec3_t temp; + + if (self->spawnflags.has(SPAWNFLAG_MONSTER_AMBUSH)) + { + if (!visible(self, client)) + return false; + } + else + { + if (!gi.inPHS(self->s.origin, client->s.origin, true)) + return false; + } + + temp = client->s.origin - self->s.origin; + + if (temp.length() > 1000) // too far to hear + return false; + + // check area portals - if they are different and not connected then we can't hear it + if (client->areanum != self->areanum) + if (!gi.AreasConnected(self->areanum, client->areanum)) + return false; + + self->ideal_yaw = vectoyaw(temp); + // ROGUE + if (!(self->monsterinfo.aiflags & AI_MANUAL_STEERING)) + // ROGUE + M_ChangeYaw(self); + + // hunt the sound for a bit; hopefully find the real player + self->monsterinfo.aiflags |= AI_SOUND_TARGET; + self->enemy = client; + } + + // + // got one + // + // ROGUE - if we got an enemy, we need to bail out of hint paths, so take over here + if (self->monsterinfo.aiflags & AI_HINT_PATH) + hintpath_stop(self); // this calls foundtarget for us + else + FoundTarget(self); + + // ROGUE + if (!(self->monsterinfo.aiflags & AI_SOUND_TARGET) && (self->monsterinfo.sight) && + // Paril: adjust to prevent monsters getting stuck in sight loops + !ignore_sight_sound) + self->monsterinfo.sight(self, self->enemy); + + return true; +} + +//============================================================================= + +/* +============ +FacingIdeal + +============ +*/ +bool FacingIdeal(edict_t *self) +{ + float delta = anglemod(self->s.angles[YAW] - self->ideal_yaw); + + if (self->monsterinfo.aiflags & AI_PATHING) + return !(delta > 5 && delta < 355); + + return !(delta > 45 && delta < 315); +} + +//============================================================================= + +MONSTERINFO_CHECKATTACK(M_CheckAttack) (edict_t *self) -> bool +{ + vec3_t spot1, spot2; + float chance; + trace_t tr; + + if (self->enemy->flags & FL_NOVISIBLE) + return false; + + if (self->enemy->health > 0) + { + if (self->enemy->client) + { + if (self->enemy->client->invisible_time > level.time) + { + // can't see us at all after this time + if (self->enemy->client->invisibility_fade_time <= level.time) + return false; + } + } + + spot1 = self->s.origin; + spot1[2] += self->viewheight; + // see if any entities are in the way of the shot + if (!self->enemy->client || self->enemy->solid) + { + spot2 = self->enemy->s.origin; + spot2[2] += self->enemy->viewheight; + + tr = gi.traceline(spot1, spot2, self, + MASK_SOLID | CONTENTS_MONSTER | CONTENTS_PLAYER | CONTENTS_SLIME | CONTENTS_LAVA); + } + else + { + tr.ent = world; + tr.fraction = 0; + } + + // do we have a clear shot? + if (!(self->hackflags & HACKFLAG_ATTACK_PLAYER) && tr.ent != self->enemy && !(tr.ent->svflags & SVF_PLAYER)) + { + // ROGUE - we want them to go ahead and shoot at info_notnulls if they can. + if (self->enemy->solid != SOLID_NOT || tr.fraction < 1.0f) // PGM + { + // PMM - if we can't see our target, and we're not blocked by a monster, go into blind fire if available + // Paril - *and* we have at least seen them once + if (!(tr.ent->svflags & SVF_MONSTER) && !visible(self, self->enemy) && self->monsterinfo.had_visibility) + { + if (self->monsterinfo.blindfire && (self->monsterinfo.blind_fire_delay <= 20_sec)) + { + if (level.time < self->monsterinfo.attack_finished) + { + // ROGUE + return false; + } + // ROGUE + if (level.time < (self->monsterinfo.trail_time + self->monsterinfo.blind_fire_delay)) + { + // wait for our time + return false; + } + else + { + // make sure we're not going to shoot a monster + tr = gi.traceline(spot1, self->monsterinfo.blind_fire_target, self, + CONTENTS_MONSTER); + if (tr.allsolid || tr.startsolid || ((tr.fraction < 1.0f) && (tr.ent != self->enemy))) + return false; + + self->monsterinfo.attack_state = AS_BLIND; + return true; + } + } + } + // pmm + return false; + } + } + } + // ROGUE + + float enemy_range = range_to(self, self->enemy); + + // melee attack + if (enemy_range <= RANGE_MELEE) + { + if (self->monsterinfo.melee && self->monsterinfo.melee_debounce_time <= level.time) + self->monsterinfo.attack_state = AS_MELEE; + else + self->monsterinfo.attack_state = AS_MISSILE; + return true; + } + + // if we were in melee just before this but we're too far away, get out of melee state now + if (self->monsterinfo.attack_state == AS_MELEE && self->monsterinfo.melee_debounce_time > level.time) + self->monsterinfo.attack_state = AS_MISSILE; + + // missile attack + if (!self->monsterinfo.attack) + { + // ROGUE - fix for melee only monsters & strafing + self->monsterinfo.attack_state = AS_STRAIGHT; + // ROGUE + return false; + } + + if (level.time < self->monsterinfo.attack_finished) + return false; + + if (enemy_range > RANGE_MID) + return false; + + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + { + chance = 0.7f; + } + else if (enemy_range <= RANGE_MELEE) + { + chance = 0.4f; + } + else if (enemy_range <= RANGE_NEAR) + { + chance = 0.25f; + } + else + { + chance = 0.06f; + } + + // PGM - go ahead and shoot every time if it's a info_notnull + if ((frandom() < chance) || (!self->enemy->client && self->enemy->solid == SOLID_NOT)) + { + self->monsterinfo.attack_state = AS_MISSILE; + self->monsterinfo.attack_finished = level.time; + return true; + } + + // ROGUE -daedalus should strafe more .. this can be done here or in a customized + // check_attack code for the hover. + if (self->flags & FL_FLY) + { + if (self->monsterinfo.strafe_check_time <= level.time) + { + // originally, just 0.3 + float strafe_chance; + if (!(strcmp(self->classname, "monster_daedalus"))) + strafe_chance = 0.8f; + else + strafe_chance = 0.6f; + + // if enemy is tesla, never strafe + if ((self->enemy) && (self->enemy->classname) && (!strcmp(self->enemy->classname, "tesla_mine"))) + strafe_chance = 0; + + monster_attack_state_t new_state = AS_STRAIGHT; + + if (frandom() < strafe_chance) + new_state = AS_SLIDING; + + if (new_state != self->monsterinfo.attack_state) + { + self->monsterinfo.strafe_check_time = level.time + random_time(1_sec, 3_sec); + self->monsterinfo.attack_state = new_state; + } + } + } + // do we want the monsters strafing? + // [Paril-KEX] no, we don't + // [Paril-KEX] if we're pathing, don't immediately reset us to + // straight; this allows us to turn to fire and not jerk back and + // forth. + else if (!(self->monsterinfo.aiflags & AI_PATHING)) + self->monsterinfo.attack_state = AS_STRAIGHT; + // ROGUE + + return false; +} + +/* +============= +ai_run_melee + +Turn and close until within an angle to launch a melee attack +============= +*/ +void ai_run_melee(edict_t *self) +{ + self->ideal_yaw = enemy_yaw; + // ROGUE + if (!(self->monsterinfo.aiflags & AI_MANUAL_STEERING)) + // ROGUE + M_ChangeYaw(self); + + if (FacingIdeal(self)) + { + self->monsterinfo.melee(self); + self->monsterinfo.attack_state = AS_STRAIGHT; + } +} + +/* +============= +ai_run_missile + +Turn in place until within an angle to launch a missile attack +============= +*/ +void ai_run_missile(edict_t *self) +{ + self->ideal_yaw = enemy_yaw; + // ROGUE + if (!(self->monsterinfo.aiflags & AI_MANUAL_STEERING)) + // ROGUE + M_ChangeYaw(self); + + if (FacingIdeal(self)) + { + if (self->monsterinfo.attack) + { + self->monsterinfo.attack(self); + self->monsterinfo.attack_finished = level.time + random_time(1.0_sec, 2.0_sec); + } + + // ROGUE + if ((self->monsterinfo.attack_state == AS_MISSILE) || (self->monsterinfo.attack_state == AS_BLIND)) + // ROGUE + self->monsterinfo.attack_state = AS_STRAIGHT; + } +}; + +/* +============= +ai_run_slide + +Strafe sideways, but stay at aproximately the same range +============= +*/ +// ROGUE +void ai_run_slide(edict_t *self, float distance) +{ + float ofs; + float angle; + + self->ideal_yaw = enemy_yaw; + + angle = 90; + + if (self->monsterinfo.lefty) + ofs = angle; + else + ofs = -angle; + + if (!(self->monsterinfo.aiflags & AI_MANUAL_STEERING)) + M_ChangeYaw(self); + + // PMM - clamp maximum sideways move for non flyers to make them look less jerky + if (!(self->flags & FL_FLY)) + distance = min(distance, MAX_SIDESTEP / (gi.frame_time_ms / 10)); + if (M_walkmove(self, self->ideal_yaw + ofs, distance)) + return; + // PMM - if we're dodging, give up on it and go straight + if (self->monsterinfo.aiflags & AI_DODGING) + { + monster_done_dodge(self); + // by setting as_straight, caller will know to try straight move + self->monsterinfo.attack_state = AS_STRAIGHT; + return; + } + + self->monsterinfo.lefty = !self->monsterinfo.lefty; + if (M_walkmove(self, self->ideal_yaw - ofs, distance)) + return; + // PMM - if we're dodging, give up on it and go straight + if (self->monsterinfo.aiflags & AI_DODGING) + monster_done_dodge(self); + + // PMM - the move failed, so signal the caller (ai_run) to try going straight + self->monsterinfo.attack_state = AS_STRAIGHT; +} +// ROGUE + +/* +============= +ai_checkattack + +Decides if we're going to attack or do something else +used by ai_run and ai_stand +============= +*/ +bool ai_checkattack(edict_t *self, float dist) +{ + vec3_t temp; + bool hesDeadJim; + // ROGUE + bool retval; + // ROGUE + + if (self->monsterinfo.aiflags & AI_TEMP_STAND_GROUND) + self->monsterinfo.aiflags &= ~(AI_STAND_GROUND | AI_TEMP_STAND_GROUND); + + // this causes monsters to run blindly to the combat point w/o firing + if (self->goalentity) + { + if (self->monsterinfo.aiflags & AI_COMBAT_POINT) + { + if (self->enemy && range_to(self, self->enemy) > 100.f) + return false; + } + + if (self->monsterinfo.aiflags & AI_SOUND_TARGET) + { + if ((level.time - self->enemy->teleport_time) > 5_sec) + { + if (self->goalentity == self->enemy) + { + if (self->movetarget) + self->goalentity = self->movetarget; + else + self->goalentity = nullptr; + } + self->monsterinfo.aiflags &= ~AI_SOUND_TARGET; + } + else + { + self->enemy->show_hostile = level.time + 1_sec; + return false; + } + } + } + + enemy_vis = false; + + // see if the enemy is dead + hesDeadJim = false; + if ((!self->enemy) || (!self->enemy->inuse)) + { + hesDeadJim = true; + } else if ( self->monsterinfo.aiflags & AI_FORGET_ENEMY ) + { + self->monsterinfo.aiflags &= ~AI_FORGET_ENEMY; + hesDeadJim = true; + } + else if (self->monsterinfo.aiflags & AI_MEDIC) + { + if (!(self->enemy->inuse) || (self->enemy->health > 0)) + hesDeadJim = true; + } + else + { + if (self->monsterinfo.aiflags & AI_BRUTAL) + { + if (self->enemy->health <= self->enemy->gib_health) + hesDeadJim = true; + } + else + { + if (self->enemy->health <= 0) + hesDeadJim = true; + } + + // [Paril-KEX] if our enemy was invisible, lose sight now + if (self->enemy->client && self->enemy->client->invisible_time > level.time && self->enemy->client->invisibility_fade_time <= level.time && + (self->monsterinfo.aiflags & AI_PURSUE_NEXT)) + { + hesDeadJim = true; + } + } + + if (hesDeadJim && !(self->hackflags & HACKFLAG_ATTACK_PLAYER)) + { + // ROGUE + self->monsterinfo.aiflags &= ~AI_MEDIC; + // ROGUE + self->enemy = self->goalentity = nullptr; + self->monsterinfo.close_sight_tripped = false; + // FIXME: look all around for other targets + if (self->oldenemy && self->oldenemy->health > 0) + { + self->enemy = self->oldenemy; + self->oldenemy = nullptr; + HuntTarget(self); + } + // ROGUE - multiple teslas make monsters lose track of the player. + else if (self->monsterinfo.last_player_enemy && self->monsterinfo.last_player_enemy->health > 0) + { + self->enemy = self->monsterinfo.last_player_enemy; + self->oldenemy = nullptr; + self->monsterinfo.last_player_enemy = nullptr; + HuntTarget(self); + } + // ROGUE + else + { + if (self->movetarget && !(self->monsterinfo.aiflags & AI_STAND_GROUND)) + { + self->goalentity = self->movetarget; + self->monsterinfo.walk(self); + } + else + { + // we need the pausetime otherwise the stand code + // will just revert to walking with no target and + // the monsters will wonder around aimlessly trying + // to hunt the world entity + self->monsterinfo.pausetime = HOLD_FOREVER; + self->monsterinfo.stand(self); + + if (self->monsterinfo.aiflags & AI_TEMP_STAND_GROUND) + self->monsterinfo.aiflags &= ~(AI_STAND_GROUND | AI_TEMP_STAND_GROUND); + } + return true; + } + } + + // check knowledge of enemy + enemy_vis = visible(self, self->enemy); + if (enemy_vis) + { + self->monsterinfo.had_visibility = true; + self->enemy->show_hostile = level.time + 1_sec; // wake up other monsters + self->monsterinfo.search_time = level.time + 5_sec; + self->monsterinfo.last_sighting = self->monsterinfo.saved_goal = self->enemy->s.origin; + // ROGUE + if (self->monsterinfo.aiflags & AI_LOST_SIGHT) + { + self->monsterinfo.aiflags &= ~AI_LOST_SIGHT; + + if (self->monsterinfo.move_block_change_time < level.time) + self->monsterinfo.aiflags &= ~AI_TEMP_MELEE_COMBAT; + } + self->monsterinfo.trail_time = level.time; + self->monsterinfo.blind_fire_target = self->monsterinfo.last_sighting + (self->enemy->velocity * -0.1f); + self->monsterinfo.blind_fire_delay = 0_ms; + // ROGUE + } + + enemy_infront = infront(self, self->enemy); + temp = self->enemy->s.origin - self->s.origin; + enemy_yaw = vectoyaw(temp); + + // PMM -- reordered so the monster specific checkattack is called before the run_missle/melee/checkvis + // stuff .. this allows for, among other things, circle strafing and attacking while in ai_run + retval = false; + + if (self->monsterinfo.checkattack_time <= level.time) + { + self->monsterinfo.checkattack_time = level.time + 0.1_sec; + retval = self->monsterinfo.checkattack(self); + } + + if (retval || self->monsterinfo.attack_state >= AS_MISSILE) + { + // PMM + if (self->monsterinfo.attack_state == AS_MISSILE) + { + ai_run_missile(self); + return true; + } + if (self->monsterinfo.attack_state == AS_MELEE) + { + ai_run_melee(self); + return true; + } + // PMM -- added so monsters can shoot blind + if (self->monsterinfo.attack_state == AS_BLIND) + { + ai_run_missile(self); + return true; + } + // pmm + + // if enemy is not currently visible, we will never attack + if (!enemy_vis) + return false; + // PMM + } + + return retval; + // PMM +} + +/* +============= +ai_run + +The monster has an enemy it is trying to kill +============= +*/ +void ai_run(edict_t *self, float dist) +{ + vec3_t v; + edict_t *tempgoal; + edict_t *save; + bool newEnemy; + edict_t *marker; + float d1, d2; + trace_t tr; + vec3_t v_forward, v_right; + float left, center, right; + vec3_t left_target, right_target; + // ROGUE + bool retval; + bool alreadyMoved = false; + bool gotcha = false; + edict_t *realEnemy; + // ROGUE + + // if we're going to a combat point, just proceed + if (self->monsterinfo.aiflags & AI_COMBAT_POINT) + { + ai_checkattack(self, dist); + M_MoveToGoal(self, dist); + + if (self->movetarget) + { + // nb: this is done from the centroid and not viewheight on purpose; + trace_t tr = gi.trace((self->absmax + self->absmin) * 0.5f, { -2.f, -2.f, -2.f }, { 2.f, 2.f, 2.f }, self->movetarget->s.origin, self, CONTENTS_SOLID); + + // [Paril-KEX] special case: if we're stand ground & knocked way too far away + // from our path_corner, or we can't see it any more, assume all + // is lost. + if ((self->monsterinfo.aiflags & AI_REACHED_HOLD_COMBAT) && (((closest_point_to_box(self->movetarget->s.origin, self->absmin, self->absmax) - self->movetarget->s.origin).length() > 160.f) + || (tr.fraction < 1.0f && tr.plane.normal.z <= 0.7f))) // if we hit a climbable, ignore this result + { + self->monsterinfo.aiflags &= ~AI_COMBAT_POINT; + self->movetarget = nullptr; + self->target = nullptr; + self->goalentity = self->enemy; + } + else + return; + } + else + return; + } + + // PMM + if ((self->monsterinfo.aiflags & AI_DUCKED) && self->monsterinfo.unduck) + self->monsterinfo.unduck(self); + + //========== + // PGM + // if we're currently looking for a hint path + if (self->monsterinfo.aiflags & AI_HINT_PATH) + { + // determine direction to our destination hintpath. + M_MoveToGoal(self, dist); + if (!self->inuse) + return; + + // first off, make sure we're looking for the player, not a noise he made + if (self->enemy) + { + if (self->enemy->inuse) + { + if (strcmp(self->enemy->classname, "player_noise") != 0) + realEnemy = self->enemy; + else if (self->enemy->owner) + realEnemy = self->enemy->owner; + else // uh oh, can't figure out enemy, bail + { + self->enemy = nullptr; + hintpath_stop(self); + return; + } + } + else + { + self->enemy = nullptr; + hintpath_stop(self); + return; + } + } + else + { + hintpath_stop(self); + return; + } + + if (coop->integer) + { + // if we're in coop, check my real enemy first .. if I SEE him, set gotcha to true + if (self->enemy && visible(self, realEnemy)) + gotcha = true; + else // otherwise, let FindTarget bump us out of hint paths, if appropriate + FindTarget(self); + } + else + { + if (self->enemy && visible(self, realEnemy)) + gotcha = true; + } + + // if we see the player, stop following hintpaths. + if (gotcha) + // disconnect from hintpaths and start looking normally for players. + hintpath_stop(self); + + return; + } + // PGM + //========== + + if (self->monsterinfo.aiflags & AI_SOUND_TARGET) + { + // PMM - paranoia checking + if (self->enemy) + v = self->s.origin - self->enemy->s.origin; + + bool touching_noise = SV_CloseEnough(self, self->enemy, dist * (gi.tick_rate / 10)); + + if ((!self->enemy) || (touching_noise && FacingIdeal(self))) + // pmm + { + self->monsterinfo.aiflags |= (AI_STAND_GROUND | AI_TEMP_STAND_GROUND); + self->s.angles[YAW] = self->ideal_yaw; + self->monsterinfo.stand(self); + self->monsterinfo.close_sight_tripped = false; + return; + } + + // if we're close to the goal, just turn + if (touching_noise) + M_ChangeYaw(self); + else + M_MoveToGoal(self, dist); + + // ROGUE - prevent double moves for sound_targets + alreadyMoved = true; + + if (!self->inuse) + return; // PGM - g_touchtrigger free problem + // ROGUE + + if (!FindTarget(self)) + return; + } + + // PMM -- moved ai_checkattack up here so the monsters can attack while strafing or charging + + // PMM -- if we're dodging, make sure to keep the attack_state AS_SLIDING + retval = ai_checkattack(self, dist); + + // PMM - don't strafe if we can't see our enemy + if ((!enemy_vis) && (self->monsterinfo.attack_state == AS_SLIDING)) + self->monsterinfo.attack_state = AS_STRAIGHT; + // unless we're dodging (dodging out of view looks smart) + if (self->monsterinfo.aiflags & AI_DODGING) + self->monsterinfo.attack_state = AS_SLIDING; + // pmm + + if (self->monsterinfo.attack_state == AS_SLIDING) + { + // PMM - protect against double moves + if (!alreadyMoved) + ai_run_slide(self, dist); + // PMM + // we're using attack_state as the return value out of ai_run_slide to indicate whether or not the + // move succeeded. If the move succeeded, and we're still sliding, we're done in here (since we've + // had our chance to shoot in ai_checkattack, and have moved). + // if the move failed, our state is as_straight, and it will be taken care of below + if ((!retval) && (self->monsterinfo.attack_state == AS_SLIDING)) + return; + } + else if (self->monsterinfo.aiflags & AI_CHARGING) + { + self->ideal_yaw = enemy_yaw; + if (!(self->monsterinfo.aiflags & AI_MANUAL_STEERING)) + M_ChangeYaw(self); + } + if (retval) + { + // PMM - is this useful? Monsters attacking usually call the ai_charge routine.. + // the only monster this affects should be the soldier + if ((dist || (self->monsterinfo.aiflags & AI_ALTERNATE_FLY)) && (!alreadyMoved) && (self->monsterinfo.attack_state == AS_STRAIGHT) && + (!(self->monsterinfo.aiflags & AI_STAND_GROUND))) + { + M_MoveToGoal(self, dist); + } + if ((self->enemy) && (self->enemy->inuse) && (enemy_vis)) + { + if (self->monsterinfo.aiflags & AI_LOST_SIGHT) + { + self->monsterinfo.aiflags &= ~AI_LOST_SIGHT; + + if (self->monsterinfo.move_block_change_time < level.time) + self->monsterinfo.aiflags &= ~AI_TEMP_MELEE_COMBAT; + } + self->monsterinfo.last_sighting = self->monsterinfo.saved_goal = self->enemy->s.origin; + self->monsterinfo.trail_time = level.time; + // PMM + self->monsterinfo.blind_fire_target = self->monsterinfo.last_sighting + (self->enemy->velocity * -0.1f); + self->monsterinfo.blind_fire_delay = 0_ms; + // pmm + } + return; + } + // PMM + + // PGM - added a little paranoia checking here... 9/22/98 + if ((self->enemy) && (self->enemy->inuse) && (enemy_vis)) + { + // PMM - check for alreadyMoved + if (!alreadyMoved) + M_MoveToGoal(self, dist); + if (!self->inuse) + return; // PGM - g_touchtrigger free problem + + if (self->monsterinfo.aiflags & AI_LOST_SIGHT) + { + self->monsterinfo.aiflags &= ~AI_LOST_SIGHT; + + if (self->monsterinfo.move_block_change_time < level.time) + self->monsterinfo.aiflags &= ~AI_TEMP_MELEE_COMBAT; + } + self->monsterinfo.last_sighting = self->monsterinfo.saved_goal = self->enemy->s.origin; + self->monsterinfo.trail_time = level.time; + // PMM + self->monsterinfo.blind_fire_target = self->monsterinfo.last_sighting + (self->enemy->velocity * -0.1f); + self->monsterinfo.blind_fire_delay = 0_ms; + // pmm + + // [Paril-KEX] if our enemy is literally right next to us, give + // us more rotational speed so we don't get circled + if (range_to(self, self->enemy) <= RANGE_MELEE * 2.5f) + M_ChangeYaw(self); + + return; + } + + //======= + // PGM + // if we've been looking (unsuccessfully) for the player for 10 seconds + // PMM - reduced to 5, makes them much nastier + if ((self->monsterinfo.trail_time + 5_sec) <= level.time) + { + // and we haven't checked for valid hint paths in the last 10 seconds + if ((self->monsterinfo.last_hint_time + 10_sec) <= level.time) + { + // check for hint_paths. + self->monsterinfo.last_hint_time = level.time; + if (monsterlost_checkhint(self)) + return; + } + } + // PGM + //======= + + // PMM - moved down here to allow monsters to get on hint paths + // coop will change to another enemy if visible + if (coop->integer) + FindTarget(self); + // pmm + + if ((self->monsterinfo.search_time) && (level.time > (self->monsterinfo.search_time + 20_sec))) + { + // PMM - double move protection + if (!alreadyMoved) + M_MoveToGoal(self, dist); + self->monsterinfo.search_time = 0_ms; + return; + } + + save = self->goalentity; + tempgoal = G_Spawn(); + self->goalentity = tempgoal; + + newEnemy = false; + + if (!(self->monsterinfo.aiflags & AI_LOST_SIGHT)) + { + // just lost sight of the player, decide where to go first + self->monsterinfo.aiflags |= (AI_LOST_SIGHT | AI_PURSUIT_LAST_SEEN); + self->monsterinfo.aiflags &= ~(AI_PURSUE_NEXT | AI_PURSUE_TEMP); + newEnemy = true; + + // immediately try paths + self->monsterinfo.path_blocked_counter = 0_ms; + self->monsterinfo.path_wait_time = 0_ms; + } + + if (self->monsterinfo.aiflags & AI_PURSUE_NEXT) + { + self->monsterinfo.aiflags &= ~AI_PURSUE_NEXT; + + // give ourself more time since we got this far + self->monsterinfo.search_time = level.time + 5_sec; + + if (self->monsterinfo.aiflags & AI_PURSUE_TEMP) + { + self->monsterinfo.aiflags &= ~AI_PURSUE_TEMP; + marker = nullptr; + self->monsterinfo.last_sighting = self->monsterinfo.saved_goal; + newEnemy = true; + } + else if (self->monsterinfo.aiflags & AI_PURSUIT_LAST_SEEN) + { + self->monsterinfo.aiflags &= ~AI_PURSUIT_LAST_SEEN; + marker = PlayerTrail_Pick(self, false); + } + else + { + marker = PlayerTrail_Pick(self, true); + } + + if (marker) + { + self->monsterinfo.last_sighting = marker->s.origin; + self->monsterinfo.trail_time = marker->timestamp; + self->s.angles[YAW] = self->ideal_yaw = marker->s.angles[YAW]; + + newEnemy = true; + } + } + + if (!(self->monsterinfo.aiflags & AI_PATHING) && + boxes_intersect(self->monsterinfo.last_sighting, self->monsterinfo.last_sighting, self->s.origin + self->mins, self->s.origin + self->maxs)) + { + self->monsterinfo.aiflags |= AI_PURSUE_NEXT; + dist = min(dist, (self->s.origin - self->monsterinfo.last_sighting).length()); + // [Paril-KEX] this helps them navigate corners when two next pursuits + // are really close together + self->monsterinfo.random_change_time = level.time + 10_hz; + } + + self->goalentity->s.origin = self->monsterinfo.last_sighting; + + if (newEnemy) + { + tr = + gi.trace(self->s.origin, self->mins, self->maxs, self->monsterinfo.last_sighting, self, MASK_PLAYERSOLID); + if (tr.fraction < 1) + { + v = self->goalentity->s.origin - self->s.origin; + d1 = v.length(); + center = tr.fraction; + d2 = d1 * ((center + 1) / 2); + float backup_yaw = self->s.angles.y; + self->s.angles[YAW] = self->ideal_yaw = vectoyaw(v); + AngleVectors(self->s.angles, v_forward, v_right, nullptr); + + v = { d2, -16, 0 }; + left_target = G_ProjectSource(self->s.origin, v, v_forward, v_right); + tr = gi.trace(self->s.origin, self->mins, self->maxs, left_target, self, MASK_PLAYERSOLID); + left = tr.fraction; + + v = { d2, 16, 0 }; + right_target = G_ProjectSource(self->s.origin, v, v_forward, v_right); + tr = gi.trace(self->s.origin, self->mins, self->maxs, right_target, self, MASK_PLAYERSOLID); + right = tr.fraction; + + center = (d1 * center) / d2; + if (left >= center && left > right) + { + if (left < 1) + { + v = { d2 * left * 0.5f, -16, 0 }; + left_target = G_ProjectSource(self->s.origin, v, v_forward, v_right); + } + self->monsterinfo.saved_goal = self->monsterinfo.last_sighting; + self->monsterinfo.aiflags |= AI_PURSUE_TEMP; + self->goalentity->s.origin = left_target; + self->monsterinfo.last_sighting = left_target; + v = self->goalentity->s.origin - self->s.origin; + self->ideal_yaw = vectoyaw(v); + } + else if (right >= center && right > left) + { + if (right < 1) + { + v = { d2 * right * 0.5f, 16, 0 }; + right_target = G_ProjectSource(self->s.origin, v, v_forward, v_right); + } + self->monsterinfo.saved_goal = self->monsterinfo.last_sighting; + self->monsterinfo.aiflags |= AI_PURSUE_TEMP; + self->goalentity->s.origin = right_target; + self->monsterinfo.last_sighting = right_target; + v = self->goalentity->s.origin - self->s.origin; + self->ideal_yaw = vectoyaw(v); + } + self->s.angles[YAW] = backup_yaw; + } + } + + M_MoveToGoal(self, dist); + + G_FreeEdict(tempgoal); + + if (!self->inuse) + return; // PGM - g_touchtrigger free problem + + if (self) + self->goalentity = save; +} diff --git a/rerelease/g_chase.cpp b/rerelease/g_chase.cpp new file mode 100644 index 0000000..cc614f3 --- /dev/null +++ b/rerelease/g_chase.cpp @@ -0,0 +1,171 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +#include "g_local.h" + +void UpdateChaseCam(edict_t *ent) +{ + vec3_t o, ownerv, goal; + edict_t *targ; + vec3_t forward, right; + trace_t trace; + vec3_t oldgoal; + vec3_t angles; + + // is our chase target gone? + if (!ent->client->chase_target->inuse || ent->client->chase_target->client->resp.spectator) + { + edict_t *old = ent->client->chase_target; + ChaseNext(ent); + if (ent->client->chase_target == old) + { + ent->client->chase_target = nullptr; + ent->client->ps.pmove.pm_flags &= ~(PMF_NO_POSITIONAL_PREDICTION | PMF_NO_ANGULAR_PREDICTION); + return; + } + } + + targ = ent->client->chase_target; + + ownerv = targ->s.origin; + oldgoal = ent->s.origin; + + ownerv[2] += targ->viewheight; + + angles = targ->client->v_angle; + if (angles[PITCH] > 56) + angles[PITCH] = 56; + AngleVectors(angles, forward, right, nullptr); + forward.normalize(); + o = ownerv + (forward * -30); + + if (o[2] < targ->s.origin[2] + 20) + o[2] = targ->s.origin[2] + 20; + + // jump animation lifts + if (!targ->groundentity) + o[2] += 16; + + trace = gi.traceline(ownerv, o, targ, MASK_SOLID); + + goal = trace.endpos; + + goal += (forward * 2); + + // pad for floors and ceilings + o = goal; + o[2] += 6; + trace = gi.traceline(goal, o, targ, MASK_SOLID); + if (trace.fraction < 1) + { + goal = trace.endpos; + goal[2] -= 6; + } + + o = goal; + o[2] -= 6; + trace = gi.traceline(goal, o, targ, MASK_SOLID); + if (trace.fraction < 1) + { + goal = trace.endpos; + goal[2] += 6; + } + + if (targ->deadflag) + ent->client->ps.pmove.pm_type = PM_DEAD; + else + ent->client->ps.pmove.pm_type = PM_FREEZE; + + ent->s.origin = goal; + ent->client->ps.pmove.delta_angles = targ->client->v_angle - ent->client->resp.cmd_angles; + + if (targ->deadflag) + { + ent->client->ps.viewangles[ROLL] = 40; + ent->client->ps.viewangles[PITCH] = -15; + ent->client->ps.viewangles[YAW] = targ->client->killer_yaw; + } + else + { + ent->client->ps.viewangles = targ->client->v_angle; + ent->client->v_angle = targ->client->v_angle; + AngleVectors(ent->client->v_angle, ent->client->v_forward, nullptr, nullptr); + } + + ent->viewheight = 0; + ent->client->ps.pmove.pm_flags |= PMF_NO_POSITIONAL_PREDICTION | PMF_NO_ANGULAR_PREDICTION; + gi.linkentity(ent); +} + +void ChaseNext(edict_t *ent) +{ + ptrdiff_t i; + edict_t *e; + + if (!ent->client->chase_target) + return; + + i = ent->client->chase_target - g_edicts; + do + { + i++; + if (i > game.maxclients) + i = 1; + e = g_edicts + i; + if (!e->inuse) + continue; + if (!e->client->resp.spectator) + break; + } while (e != ent->client->chase_target); + + ent->client->chase_target = e; + ent->client->update_chase = true; +} + +void ChasePrev(edict_t *ent) +{ + int i; + edict_t *e; + + if (!ent->client->chase_target) + return; + + i = ent->client->chase_target - g_edicts; + do + { + i--; + if (i < 1) + i = game.maxclients; + e = g_edicts + i; + if (!e->inuse) + continue; + if (!e->client->resp.spectator) + break; + } while (e != ent->client->chase_target); + + ent->client->chase_target = e; + ent->client->update_chase = true; +} + +void GetChaseTarget(edict_t *ent) +{ + uint32_t i; + edict_t *other; + + for (i = 1; i <= game.maxclients; i++) + { + other = g_edicts + i; + if (other->inuse && !other->client->resp.spectator) + { + ent->client->chase_target = other; + ent->client->update_chase = true; + UpdateChaseCam(ent); + return; + } + } + + if (ent->client->chase_msg_time <= level.time) + { + gi.LocCenter_Print(ent, "$g_no_players_chase"); + ent->client->chase_msg_time = level.time + 5_sec; + } +} diff --git a/rerelease/g_cmds.cpp b/rerelease/g_cmds.cpp new file mode 100644 index 0000000..87b982b --- /dev/null +++ b/rerelease/g_cmds.cpp @@ -0,0 +1,1741 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +#include "g_local.h" +#include "m_player.h" + +void SelectNextItem(edict_t *ent, item_flags_t itflags) +{ + gclient_t *cl; + item_id_t i, index; + gitem_t *it; + + cl = ent->client; + + // ZOID + if (cl->menu) + { + PMenu_Next(ent); + return; + } + else if (cl->chase_target) + { + ChaseNext(ent); + return; + } + // ZOID + + // scan for the next valid one + for (i = static_cast(IT_NULL + 1); i <= IT_TOTAL; i = static_cast(i + 1)) + { + index = static_cast((cl->pers.selected_item + i) % IT_TOTAL); + if (!cl->pers.inventory[index]) + continue; + it = &itemlist[index]; + if (!it->use) + continue; + if (!(it->flags & itflags)) + continue; + + cl->pers.selected_item = index; + cl->pers.selected_item_time = level.time + SELECTED_ITEM_TIME; + cl->ps.stats[STAT_SELECTED_ITEM_NAME] = CS_ITEMS + index; + return; + } + + cl->pers.selected_item = IT_NULL; +} + +void SelectPrevItem(edict_t *ent, item_flags_t itflags) +{ + gclient_t *cl; + item_id_t i, index; + gitem_t *it; + + cl = ent->client; + + // ZOID + if (cl->menu) + { + PMenu_Prev(ent); + return; + } + else if (cl->chase_target) + { + ChasePrev(ent); + return; + } + // ZOID + + // scan for the next valid one + for (i = static_cast(IT_NULL + 1); i <= IT_TOTAL; i = static_cast(i + 1)) + { + index = static_cast((cl->pers.selected_item + IT_TOTAL - i) % IT_TOTAL); + if (!cl->pers.inventory[index]) + continue; + it = &itemlist[index]; + if (!it->use) + continue; + if (!(it->flags & itflags)) + continue; + + cl->pers.selected_item = index; + cl->pers.selected_item_time = level.time + SELECTED_ITEM_TIME; + cl->ps.stats[STAT_SELECTED_ITEM_NAME] = CS_ITEMS + index; + return; + } + + cl->pers.selected_item = IT_NULL; +} + +void ValidateSelectedItem(edict_t *ent) +{ + gclient_t *cl; + + cl = ent->client; + + if (cl->pers.inventory[cl->pers.selected_item]) + return; // valid + + SelectNextItem(ent, IF_ANY); +} + +//================================================================================= + +inline bool G_CheatCheck(edict_t *ent) +{ + if (game.maxclients > 1 && !sv_cheats->integer) + { + gi.LocClient_Print(ent, PRINT_HIGH, "$g_need_cheats"); + return false; + } + + return true; +} + +static void SpawnAndGiveItem(edict_t *ent, item_id_t id) +{ + gitem_t *it = GetItemByIndex(id); + + if (!it) + return; + + edict_t *it_ent = G_Spawn(); + it_ent->classname = it->classname; + SpawnItem(it_ent, it); + + if (it_ent->inuse) + { + Touch_Item(it_ent, ent, null_trace, true); + if (it_ent->inuse) + G_FreeEdict(it_ent); + } +} + +/* +================== +Cmd_Give_f + +Give items to a client +================== +*/ +void Cmd_Give_f(edict_t *ent) +{ + const char *name; + gitem_t *it; + item_id_t index; + int i; + bool give_all; + edict_t *it_ent; + + if (!G_CheatCheck(ent)) + return; + + name = gi.args(); + + if (Q_strcasecmp(name, "all") == 0) + give_all = true; + else + give_all = false; + + if (give_all || Q_strcasecmp(gi.argv(1), "health") == 0) + { + if (gi.argc() == 3) + ent->health = atoi(gi.argv(2)); + else + ent->health = ent->max_health; + if (!give_all) + return; + } + + if (give_all || Q_strcasecmp(name, "weapons") == 0) + { + for (i = 0; i < IT_TOTAL; i++) + { + it = itemlist + i; + if (!it->pickup) + continue; + if (!(it->flags & IF_WEAPON)) + continue; + ent->client->pers.inventory[i] += 1; + } + if (!give_all) + return; + } + + if (give_all || Q_strcasecmp(name, "ammo") == 0) + { + if (give_all) + SpawnAndGiveItem(ent, IT_ITEM_PACK); + + for (i = 0; i < IT_TOTAL; i++) + { + it = itemlist + i; + if (!it->pickup) + continue; + if (!(it->flags & IF_AMMO)) + continue; + Add_Ammo(ent, it, 1000); + } + if (!give_all) + return; + } + + if (give_all || Q_strcasecmp(name, "armor") == 0) + { + ent->client->pers.inventory[IT_ARMOR_JACKET] = 0; + ent->client->pers.inventory[IT_ARMOR_COMBAT] = 0; + ent->client->pers.inventory[IT_ARMOR_BODY] = GetItemByIndex(IT_ARMOR_BODY)->armor_info->max_count; + + if (!give_all) + return; + } + + if (give_all) + { + SpawnAndGiveItem(ent, IT_ITEM_POWER_SHIELD); + + if (!give_all) + return; + } + + if (give_all) + { + for (i = 0; i < IT_TOTAL; i++) + { + it = itemlist + i; + if (!it->pickup) + continue; + // ROGUE + if (it->flags & (IF_ARMOR | IF_WEAPON | IF_AMMO | IF_NOT_GIVEABLE | IF_TECH)) + continue; + else if (it->pickup == CTFPickup_Flag) + continue; + else if ((it->flags & IF_HEALTH) && !it->use) + continue; + // ROGUE + ent->client->pers.inventory[i] = (it->flags & IF_KEY) ? 8 : 1; + } + + G_CheckPowerArmor(ent); + ent->client->pers.power_cubes = 0xFF; + return; + } + + it = FindItem(name); + if (!it) + { + name = gi.argv(1); + it = FindItem(name); + } + if (!it) + it = FindItemByClassname(name); + + if (!it) + { + gi.LocClient_Print(ent, PRINT_HIGH, "$g_unknown_item"); + return; + } + + // ROGUE + if (it->flags & IF_NOT_GIVEABLE) + { + gi.LocClient_Print(ent, PRINT_HIGH, "$g_not_giveable"); + return; + } + // ROGUE + + index = it->id; + + if (!it->pickup) + { + ent->client->pers.inventory[index] = 1; + return; + } + + if (it->flags & IF_AMMO) + { + if (gi.argc() == 3) + ent->client->pers.inventory[index] = atoi(gi.argv(2)); + else + ent->client->pers.inventory[index] += it->quantity; + } + else + { + it_ent = G_Spawn(); + it_ent->classname = it->classname; + SpawnItem(it_ent, it); + // PMM - since some items don't actually spawn when you say to .. + if (!it_ent->inuse) + return; + // pmm + Touch_Item(it_ent, ent, null_trace, true); + if (it_ent->inuse) + G_FreeEdict(it_ent); + } +} + +void Cmd_SetPOI_f(edict_t *self) +{ + if (!G_CheatCheck(self)) + return; + + level.current_poi = self->s.origin; + level.valid_poi = true; +} + +void Cmd_CheckPOI_f(edict_t *self) +{ + if (!G_CheatCheck(self)) + return; + + if (!level.valid_poi) + return; + + char visible_pvs = gi.inPVS(self->s.origin, level.current_poi, false) ? 'y' : 'n'; + char visible_pvs_portals = gi.inPVS(self->s.origin, level.current_poi, true) ? 'y' : 'n'; + char visible_phs = gi.inPHS(self->s.origin, level.current_poi, false) ? 'y' : 'n'; + char visible_phs_portals = gi.inPHS(self->s.origin, level.current_poi, true) ? 'y' : 'n'; + + gi.Com_PrintFmt("pvs {} + portals {}, phs {} + portals {}\n", visible_pvs, visible_pvs_portals, visible_phs, visible_phs_portals); +} + +// [Paril-KEX] +static void Cmd_Target_f(edict_t *ent) +{ + if (!G_CheatCheck(ent)) + return; + + ent->target = gi.argv(1); + G_UseTargets(ent, ent); + ent->target = nullptr; +} + +/* +================== +Cmd_God_f + +Sets client to godmode + +argv(0) god +================== +*/ +void Cmd_God_f(edict_t *ent) +{ + const char *msg; + + if (!G_CheatCheck(ent)) + return; + + ent->flags ^= FL_GODMODE; + if (!(ent->flags & FL_GODMODE)) + msg = "godmode OFF\n"; + else + msg = "godmode ON\n"; + + gi.LocClient_Print(ent, PRINT_HIGH, msg); +} +void ED_ParseField(const char *key, const char *value, edict_t *ent); + +/* +================== +Cmd_Immortal_f + +Sets client to immortal - take damage but never go below 1 hp + +argv(0) immortal +================== +*/ +void Cmd_Immortal_f(edict_t *ent) +{ + const char *msg; + + if (!G_CheatCheck(ent)) + return; + + ent->flags ^= FL_IMMORTAL; + if (!(ent->flags & FL_IMMORTAL)) + msg = "immortal OFF\n"; + else + msg = "immortal ON\n"; + + gi.LocClient_Print(ent, PRINT_HIGH, msg); +} + +/* +================= +Cmd_Spawn_f + +Spawn class name + +argv(0) spawn +argv(1) +argv(2+n) "key"... +argv(3+n) "value"... +================= +*/ +void Cmd_Spawn_f(edict_t *ent) +{ + if (!G_CheatCheck(ent)) + return; + + solid_t backup = ent->solid; + ent->solid = SOLID_NOT; + gi.linkentity(ent); + + edict_t* other = G_Spawn(); + other->classname = gi.argv(1); + + other->s.origin = ent->s.origin + (AngleVectors(ent->s.angles).forward * 24.f); + other->s.angles[1] = ent->s.angles[1]; + + st = {}; + + if (gi.argc() > 3) + { + for (int i = 2; i < gi.argc(); i += 2) + ED_ParseField(gi.argv(i), gi.argv(i + 1), other); + } + + ED_CallSpawn(other); + + if (other->inuse) + { + vec3_t forward, end; + AngleVectors(ent->client->v_angle, forward, nullptr, nullptr); + end = ent->s.origin; + end[2] += ent->viewheight; + end += (forward * 8192); + + trace_t tr = gi.traceline(ent->s.origin + vec3_t{0.f, 0.f, (float) ent->viewheight}, end, other, MASK_SHOT | CONTENTS_MONSTERCLIP); + other->s.origin = tr.endpos; + + for (int32_t i = 0; i < 3; i++) + { + if (tr.plane.normal[i] > 0) + other->s.origin[i] -= other->mins[i] * tr.plane.normal[i]; + else + other->s.origin[i] += other->maxs[i] * -tr.plane.normal[i]; + } + + while (gi.trace(other->s.origin, other->mins, other->maxs, other->s.origin, other, + MASK_SHOT | CONTENTS_MONSTERCLIP) + .startsolid) + { + float dx = other->mins[0] - other->maxs[0]; + float dy = other->mins[1] - other->maxs[1]; + other->s.origin += forward * -sqrtf(dx * dx + dy * dy); + + if ((other->s.origin - ent->s.origin).dot(forward) < 0) + { + gi.Client_Print(ent, PRINT_HIGH, "Couldn't find a suitable spawn location\n"); + G_FreeEdict(other); + break; + } + } + + if (other->inuse) + gi.linkentity(other); + + if (other->svflags & SVF_MONSTER) + other->think(other); + } + + ent->solid = backup; + gi.linkentity(ent); +} + +/* +================= +Cmd_Spawn_f + +Telepo' + +argv(0) teleport +argv(1) x +argv(2) y +argv(3) z +================= +*/ +void Cmd_Teleport_f(edict_t *ent) +{ + if (!G_CheatCheck(ent)) + return; + + if (gi.argc() < 4) + { + gi.LocClient_Print(ent, PRINT_HIGH, "Not enough args; teleport x y z\n"); + return; + } + + ent->s.origin[0] = (float) atof(gi.argv(1)); + ent->s.origin[1] = (float) atof(gi.argv(2)); + ent->s.origin[2] = (float) atof(gi.argv(3)); + gi.linkentity(ent); +} + +/* +================== +Cmd_Notarget_f + +Sets client to notarget + +argv(0) notarget +================== +*/ +void Cmd_Notarget_f(edict_t *ent) +{ + const char *msg; + + if (!G_CheatCheck(ent)) + return; + + ent->flags ^= FL_NOTARGET; + if (!(ent->flags & FL_NOTARGET)) + msg = "notarget OFF\n"; + else + msg = "notarget ON\n"; + + gi.LocClient_Print(ent, PRINT_HIGH, msg); +} + +/* +================== +Cmd_Novisible_f + +Sets client to "super notarget" + +argv(0) notarget +================== +*/ +void Cmd_Novisible_f(edict_t *ent) +{ + const char *msg; + + if (!G_CheatCheck(ent)) + return; + + ent->flags ^= FL_NOVISIBLE; + if (!(ent->flags & FL_NOVISIBLE)) + msg = "novisible OFF\n"; + else + msg = "novisible ON\n"; + + gi.LocClient_Print(ent, PRINT_HIGH, msg); +} + +void Cmd_AlertAll_f(edict_t *ent) +{ + if (!G_CheatCheck(ent)) + return; + + for (size_t i = 0; i < globals.num_edicts; i++) + { + edict_t *t = &g_edicts[i]; + + if (!t->inuse || t->health <= 0 || !(t->svflags & SVF_MONSTER)) + continue; + + t->enemy = ent; + FoundTarget(t); + } +} + +/* +================== +Cmd_Noclip_f + +argv(0) noclip +================== +*/ +void Cmd_Noclip_f(edict_t *ent) +{ + const char *msg; + + if (!G_CheatCheck(ent)) + return; + + if (ent->movetype == MOVETYPE_NOCLIP) + { + ent->movetype = MOVETYPE_WALK; + msg = "noclip OFF\n"; + } + else + { + ent->movetype = MOVETYPE_NOCLIP; + msg = "noclip ON\n"; + } + + gi.LocClient_Print(ent, PRINT_HIGH, msg); +} + +/* +================== +Cmd_Use_f + +Use an inventory item +================== +*/ +void Cmd_Use_f(edict_t *ent) +{ + item_id_t index; + gitem_t *it; + const char *s; + + if (ent->health <= 0 || ent->deadflag) + return; + + s = gi.args(); + + const char *cmd = gi.argv(0); + if (!Q_strcasecmp(cmd, "use_index") || !Q_strcasecmp(cmd, "use_index_only")) + { + it = GetItemByIndex((item_id_t) atoi(s)); + } + else + { + it = FindItem(s); + } + + if (!it) + { + gi.LocClient_Print(ent, PRINT_HIGH, "$g_unknown_item_name", s); + return; + } + if (!it->use) + { + gi.LocClient_Print(ent, PRINT_HIGH, "$g_item_not_usable"); + return; + } + index = it->id; + + // Paril: Use_Weapon handles weapon availability + if (!(it->flags & IF_WEAPON) && !ent->client->pers.inventory[index]) + { + gi.LocClient_Print(ent, PRINT_HIGH, "$g_out_of_item", it->pickup_name); + return; + } + + // allow weapon chains for use + ent->client->no_weapon_chains = !!strcmp(gi.argv(0), "use") && !!strcmp(gi.argv(0), "use_index"); + + it->use(ent, it); + + ValidateSelectedItem(ent); +} + +/* +================== +Cmd_Drop_f + +Drop an inventory item +================== +*/ +void Cmd_Drop_f(edict_t *ent) +{ + item_id_t index; + gitem_t *it; + const char *s; + + if (ent->health <= 0 || ent->deadflag) + return; + + // ZOID--special case for tech powerups + if (Q_strcasecmp(gi.args(), "tech") == 0 && (it = CTFWhat_Tech(ent)) != nullptr) + { + it->drop(ent, it); + ValidateSelectedItem(ent); + return; + } + // ZOID + + s = gi.args(); + + const char *cmd = gi.argv(0); + + if (!Q_strcasecmp(cmd, "drop_index")) + { + it = GetItemByIndex((item_id_t) atoi(s)); + } + else + { + it = FindItem(s); + } + + if (!it) + { + gi.LocClient_Print(ent, PRINT_HIGH, "Unknown item : {}\n", s); + return; + } + if (!it->drop) + { + gi.LocClient_Print(ent, PRINT_HIGH, "$g_item_not_droppable"); + return; + } + index = it->id; + if (!ent->client->pers.inventory[index]) + { + gi.LocClient_Print(ent, PRINT_HIGH, "$g_out_of_item", it->pickup_name); + return; + } + + it->drop(ent, it); + + ValidateSelectedItem(ent); +} + +/* +================= +Cmd_Inven_f +================= +*/ +void Cmd_Inven_f(edict_t *ent) +{ + int i; + gclient_t *cl; + + cl = ent->client; + + cl->showscores = false; + cl->showhelp = false; + + globals.server_flags &= ~SERVER_FLAG_SLOW_TIME; + + // ZOID + if (ent->client->menu) + { + PMenu_Close(ent); + ent->client->update_chase = true; + return; + } + // ZOID + + if (cl->showinventory) + { + cl->showinventory = false; + return; + } + + // ZOID + if (G_TeamplayEnabled() && cl->resp.ctf_team == CTF_NOTEAM) + { + CTFOpenJoinMenu(ent); + return; + } + // ZOID + + cl->showinventory = true; + + gi.WriteByte(svc_inventory); + for (i = 0; i < IT_TOTAL; i++) + gi.WriteShort(cl->pers.inventory[i]); + for (; i < MAX_ITEMS; i++) + gi.WriteShort(0); + gi.unicast(ent, true); +} + +/* +================= +Cmd_InvUse_f +================= +*/ +void Cmd_InvUse_f(edict_t *ent) +{ + gitem_t *it; + + // ZOID + if (ent->client->menu) + { + PMenu_Select(ent); + return; + } + // ZOID + + if (ent->health <= 0 || ent->deadflag) + return; + + ValidateSelectedItem(ent); + + if (ent->client->pers.selected_item == IT_NULL) + { + gi.LocClient_Print(ent, PRINT_HIGH, "$g_no_item_to_use"); + return; + } + + it = &itemlist[ent->client->pers.selected_item]; + if (!it->use) + { + gi.LocClient_Print(ent, PRINT_HIGH, "$g_item_not_usable"); + return; + } + + // don't allow weapon chains for invuse + ent->client->no_weapon_chains = true; + it->use(ent, it); + + ValidateSelectedItem(ent); +} + +/* +================= +Cmd_WeapPrev_f +================= +*/ +void Cmd_WeapPrev_f(edict_t *ent) +{ + gclient_t *cl; + item_id_t i, index; + gitem_t *it; + item_id_t selected_weapon; + + cl = ent->client; + + if (ent->health <= 0 || ent->deadflag) + return; + if (!cl->pers.weapon) + return; + + // don't allow weapon chains for weapprev + cl->no_weapon_chains = true; + + selected_weapon = cl->pers.weapon->id; + + // scan for the next valid one + for (i = static_cast(IT_NULL + 1); i <= IT_TOTAL; i = static_cast(i + 1)) + { + // PMM - prevent scrolling through ALL weapons + index = static_cast((selected_weapon + IT_TOTAL - i) % IT_TOTAL); + if (!cl->pers.inventory[index]) + continue; + it = &itemlist[index]; + if (!it->use) + continue; + if (!(it->flags & IF_WEAPON)) + continue; + it->use(ent, it); + // ROGUE + if (cl->newweapon == it) + return; // successful + // ROGUE + } +} + +/* +================= +Cmd_WeapNext_f +================= +*/ +void Cmd_WeapNext_f(edict_t *ent) +{ + gclient_t *cl; + item_id_t i, index; + gitem_t *it; + item_id_t selected_weapon; + + cl = ent->client; + + if (ent->health <= 0 || ent->deadflag) + return; + if (!cl->pers.weapon) + return; + + // don't allow weapon chains for weapnext + cl->no_weapon_chains = true; + + selected_weapon = cl->pers.weapon->id; + + // scan for the next valid one + for (i = static_cast(IT_NULL + 1); i <= IT_TOTAL; i = static_cast(i + 1)) + { + // PMM - prevent scrolling through ALL weapons + index = static_cast((selected_weapon + i) % IT_TOTAL); + if (!cl->pers.inventory[index]) + continue; + it = &itemlist[index]; + if (!it->use) + continue; + if (!(it->flags & IF_WEAPON)) + continue; + it->use(ent, it); + // PMM - prevent scrolling through ALL weapons + + // ROGUE + if (cl->newweapon == it) + return; + // ROGUE + } +} + +/* +================= +Cmd_WeapLast_f +================= +*/ +void Cmd_WeapLast_f(edict_t *ent) +{ + gclient_t *cl; + int index; + gitem_t *it; + + cl = ent->client; + + if (ent->health <= 0 || ent->deadflag) + return; + if (!cl->pers.weapon || !cl->pers.lastweapon) + return; + + // don't allow weapon chains for weaplast + cl->no_weapon_chains = true; + + index = cl->pers.lastweapon->id; + if (!cl->pers.inventory[index]) + return; + it = &itemlist[index]; + if (!it->use) + return; + if (!(it->flags & IF_WEAPON)) + return; + it->use(ent, it); +} + +/* +================= +Cmd_InvDrop_f +================= +*/ +void Cmd_InvDrop_f(edict_t *ent) +{ + gitem_t *it; + + if (ent->health <= 0 || ent->deadflag) + return; + + ValidateSelectedItem(ent); + + if (ent->client->pers.selected_item == IT_NULL) + { + gi.LocClient_Print(ent, PRINT_HIGH, "$g_no_item_to_drop"); + return; + } + + it = &itemlist[ent->client->pers.selected_item]; + if (!it->drop) + { + gi.LocClient_Print(ent, PRINT_HIGH, "$g_item_not_droppable"); + return; + } + it->drop(ent, it); + + ValidateSelectedItem(ent); +} + +/* +================= +Cmd_Kill_f +================= +*/ +void Cmd_Kill_f(edict_t *ent) +{ + // ZOID + if (ent->client->resp.spectator) + return; + // ZOID + + if ((level.time - ent->client->respawn_time) < 5_sec) + return; + + ent->flags &= ~FL_GODMODE; + ent->health = 0; + + // ROGUE + // make sure no trackers are still hurting us. + if (ent->client->tracker_pain_time) + RemoveAttackingPainDaemons(ent); + + if (ent->client->owned_sphere) + { + G_FreeEdict(ent->client->owned_sphere); + ent->client->owned_sphere = nullptr; + } + // ROGUE + + // [Paril-KEX] don't allow kill to take points away in TDM + player_die(ent, ent, ent, 100000, vec3_origin, { MOD_SUICIDE, !!teamplay->integer }); +} + +/* +================= +Cmd_Kill_AI_f +================= +*/ +void Cmd_Kill_AI_f( edict_t * ent ) { + if ( !sv_cheats->integer ) { + gi.LocClient_Print( ent, PRINT_HIGH, "Kill_AI: Cheats Must Be Enabled!\n" ); + return; + } + + // except the one we're looking at... + edict_t *looked_at = nullptr; + + vec3_t start = ent->s.origin + vec3_t{0.f, 0.f, (float) ent->viewheight}; + vec3_t end = start + ent->client->v_forward * 1024.f; + + looked_at = gi.traceline(start, end, ent, MASK_SHOT).ent; + + const int numEdicts = globals.num_edicts; + for ( int edictIdx = 1; edictIdx < numEdicts; ++edictIdx ) + { + edict_t * edict = &g_edicts[ edictIdx ]; + if ( !edict->inuse || edict == looked_at ) { + continue; + } + + if ( ( edict->svflags & SVF_MONSTER ) == 0 ) + { + continue; + } + + G_FreeEdict( edict ); + } + + gi.LocClient_Print( ent, PRINT_HIGH, "Kill_AI: All AI Are Dead...\n" ); +} + +/* +================= +Cmd_Where_f +================= +*/ +void Cmd_Where_f( edict_t * ent ) { + if ( ent == nullptr || ent->client == nullptr ) { + return; + } + + const vec3_t & origin = ent->s.origin; + + std::string location; + fmt::format_to( std::back_inserter( location ), FMT_STRING( "{:.1f} {:.1f} {:.1f}\n" ), origin[ 0 ], origin[ 1 ], origin[ 2 ] ); + gi.LocClient_Print( ent, PRINT_HIGH, "Location: {}\n", location.c_str() ); + gi.SendToClipBoard( location.c_str() ); +} + +/* +================= +Cmd_Clear_AI_Enemy_f +================= +*/ +void Cmd_Clear_AI_Enemy_f( edict_t * ent ) { + if ( !sv_cheats->integer ) { + gi.LocClient_Print( ent, PRINT_HIGH, "Cmd_Clear_AI_Enemy: Cheats Must Be Enabled!\n" ); + return; + } + + const int numEdicts = globals.num_edicts; + for ( int edictIdx = 1; edictIdx < numEdicts; ++edictIdx ) { + edict_t * edict = &g_edicts[ edictIdx ]; + if ( !edict->inuse ) { + continue; + } + + if ( ( edict->svflags & SVF_MONSTER ) == 0 ) { + continue; + } + + edict->monsterinfo.aiflags |= AI_FORGET_ENEMY; + } + + gi.LocClient_Print( ent, PRINT_HIGH, "Cmd_Clear_AI_Enemy: Clear All AI Enemies...\n" ); +} + +/* +================= +Cmd_PutAway_f +================= +*/ +void Cmd_PutAway_f(edict_t *ent) +{ + ent->client->showscores = false; + ent->client->showhelp = false; + ent->client->showinventory = false; + + globals.server_flags &= ~SERVER_FLAG_SLOW_TIME; + + // ZOID + if (ent->client->menu) + PMenu_Close(ent); + ent->client->update_chase = true; + // ZOID +} + +int PlayerSort(const void *a, const void *b) +{ + int anum, bnum; + + anum = *(const int *) a; + bnum = *(const int *) b; + + anum = game.clients[anum].ps.stats[STAT_FRAGS]; + bnum = game.clients[bnum].ps.stats[STAT_FRAGS]; + + if (anum < bnum) + return -1; + if (anum > bnum) + return 1; + return 0; +} + +constexpr size_t MAX_IDEAL_PACKET_SIZE = 1024; + +/* +================= +Cmd_Players_f +================= +*/ +void Cmd_Players_f(edict_t *ent) +{ + size_t i; + size_t count; + static std::string small, large; + int index[MAX_CLIENTS]; + + small.clear(); + large.clear(); + + count = 0; + for (i = 0; i < game.maxclients; i++) + if (game.clients[i].pers.connected) + { + index[count] = i; + count++; + } + + // sort by frags + qsort(index, count, sizeof(index[0]), PlayerSort); + + // print information + large[0] = 0; + + if (count) + { + for (i = 0; i < count; i++) + { + fmt::format_to(std::back_inserter(small), FMT_STRING("{:3} {}\n"), game.clients[index[i]].ps.stats[STAT_FRAGS], + game.clients[index[i]].pers.netname); + + if (small.length() + large.length() > MAX_IDEAL_PACKET_SIZE - 50) + { // can't print all of them in one packet + large += "...\n"; + break; + } + + large += small; + small.clear(); + } + + // remove the last newline + large.pop_back(); + } + + gi.LocClient_Print(ent, PRINT_HIGH | PRINT_NO_NOTIFY, "$g_players", large.c_str(), count); +} + +bool CheckFlood(edict_t *ent) +{ + int i; + gclient_t *cl; + + if (flood_msgs->integer) + { + cl = ent->client; + + if (level.time < cl->flood_locktill) + { + gi.LocClient_Print(ent, PRINT_HIGH, "$g_flood_cant_talk", + (cl->flood_locktill - level.time).seconds()); + return true; + } + i = cl->flood_whenhead - flood_msgs->integer + 1; + if (i < 0) + i = (sizeof(cl->flood_when) / sizeof(cl->flood_when[0])) + i; + if (i >= q_countof(cl->flood_when)) + i = 0; + if (cl->flood_when[i] && level.time - cl->flood_when[i] < gtime_t::from_sec(flood_persecond->value)) + { + cl->flood_locktill = level.time + gtime_t::from_sec(flood_waitdelay->value); + gi.LocClient_Print(ent, PRINT_CHAT, "$g_flood_cant_talk", + flood_waitdelay->integer); + return true; + } + cl->flood_whenhead = (cl->flood_whenhead + 1) % (sizeof(cl->flood_when) / sizeof(cl->flood_when[0])); + cl->flood_when[cl->flood_whenhead] = level.time; + } + return false; +} + +/* +================= +Cmd_Wave_f +================= +*/ +void Cmd_Wave_f(edict_t *ent) +{ + int i; + + i = atoi(gi.argv(1)); + + // no dead or noclip waving + if (ent->deadflag || ent->movetype == MOVETYPE_NOCLIP) + return; + + // can't wave when ducked + bool do_animate = ent->client->anim_priority <= ANIM_WAVE && !(ent->client->ps.pmove.pm_flags & PMF_DUCKED); + + if (do_animate) + ent->client->anim_priority = ANIM_WAVE; + + constexpr float NOTIFY_DISTANCE = 256.f; + bool notified_anybody = false; + const char *self_notify_msg = nullptr, *other_notify_msg = nullptr, *other_notify_none_msg = nullptr; + + vec3_t start, dir; + P_ProjectSource(ent, ent->client->v_angle, { 0, 0, 0 }, start, dir); + + // see who we're aiming at + edict_t *aiming_at = nullptr; + float best_dist = -9999; + + for (auto player : active_players()) + { + if (player == ent) + continue; + + vec3_t cdir = player->s.origin - start; + float dist = cdir.normalize(); + + float dot = ent->client->v_forward.dot(cdir); + + if (dot < 0.97) + continue; + else if (dist < best_dist) + continue; + + best_dist = dist; + aiming_at = player; + } + + switch (i) + { + case GESTURE_FLIP_OFF: + self_notify_msg = "$g_flipoff"; + other_notify_msg = "$g_flipoff_other"; + other_notify_none_msg = "$g_flipoff_none"; + if (do_animate) + { + ent->s.frame = FRAME_flip01 - 1; + ent->client->anim_end = FRAME_flip12; + } + break; + case GESTURE_SALUTE: + self_notify_msg = "$g_salute"; + other_notify_msg = "$g_salute_other"; + other_notify_none_msg = "$g_salute_none"; + if (do_animate) + { + ent->s.frame = FRAME_salute01 - 1; + ent->client->anim_end = FRAME_salute11; + } + break; + case GESTURE_TAUNT: + self_notify_msg = "$g_taunt"; + other_notify_msg = "$g_taunt_other"; + other_notify_none_msg = "$g_taunt_none"; + if (do_animate) + { + ent->s.frame = FRAME_taunt01 - 1; + ent->client->anim_end = FRAME_taunt17; + } + break; + case GESTURE_WAVE: + self_notify_msg = "$g_wave"; + other_notify_msg = "$g_wave_other"; + other_notify_none_msg = "$g_wave_none"; + if (do_animate) + { + ent->s.frame = FRAME_wave01 - 1; + ent->client->anim_end = FRAME_wave11; + } + break; + case GESTURE_POINT: + default: + self_notify_msg = "$g_point"; + other_notify_msg = "$g_point_other"; + other_notify_none_msg = "$g_point_none"; + if (do_animate) + { + ent->s.frame = FRAME_point01 - 1; + ent->client->anim_end = FRAME_point12; + } + break; + } + + bool has_a_target = false; + + if (i == GESTURE_POINT) + { + for (auto player : active_players()) + { + if (player == ent) + continue; + else if (!OnSameTeam(ent, player)) + continue; + + has_a_target = true; + break; + } + } + + if (i == GESTURE_POINT && has_a_target) + { + // don't do this stuff if we're flooding + if (CheckFlood(ent)) + return; + + trace_t tr = gi.traceline(start, start + (ent->client->v_forward * 2048), ent, MASK_SHOT & ~CONTENTS_WINDOW); + other_notify_msg = "$g_point_other_ping"; + + uint32_t key = GetUnicastKey(); + + if (tr.fraction != 1.0f) + { + // send to all teammates + for (auto player : active_players()) + { + if (player != ent && !OnSameTeam(ent, player)) + continue; + + gi.WriteByte(svc_poi); + gi.WriteShort(POI_PING + (ent->s.number - 1)); + gi.WriteShort(5000); + gi.WritePosition(tr.endpos); + gi.WriteShort(gi.imageindex("loc_ping")); + gi.WriteByte(208); + gi.WriteByte(POI_FLAG_NONE); + gi.unicast(player, false); + + gi.local_sound(player, CHAN_AUTO, gi.soundindex("misc/help_marker.wav"), 1.0f, ATTN_NONE, 0.0f, key); + gi.LocClient_Print(player, PRINT_HIGH, other_notify_msg, ent->client->pers.netname); + } + } + } + else + { + if (CheckFlood(ent)) + return; + + edict_t* targ = nullptr; + while ((targ = findradius(targ, ent->s.origin, 1024)) != nullptr) + { + if (ent == targ) continue; + if (!targ->client) continue; + if (!gi.inPVS(ent->s.origin, targ->s.origin, false)) continue; + + if (aiming_at && other_notify_msg) + gi.LocClient_Print(targ, PRINT_TTS, other_notify_msg, ent->client->pers.netname, aiming_at->client->pers.netname); + else if (other_notify_none_msg) + gi.LocClient_Print(targ, PRINT_TTS, other_notify_none_msg, ent->client->pers.netname); + } + + if (aiming_at && other_notify_msg) + gi.LocClient_Print(ent, PRINT_TTS, other_notify_msg, ent->client->pers.netname, aiming_at->client->pers.netname); + else if (other_notify_none_msg) + gi.LocClient_Print(ent, PRINT_TTS, other_notify_none_msg, ent->client->pers.netname); + } + + ent->client->anim_time = 0_ms; +} + +#ifndef KEX_Q2_GAME +/* +================== +Cmd_Say_f + +NB: only used for non-Playfab stuff +================== +*/ +void Cmd_Say_f(edict_t *ent, bool arg0) +{ + edict_t *other; + const char *p_in; + static std::string text; + + if (gi.argc() < 2 && !arg0) + return; + else if (CheckFlood(ent)) + return; + + text.clear(); + fmt::format_to(std::back_inserter(text), FMT_STRING("{}: "), ent->client->pers.netname); + + if (arg0) + { + text += gi.argv(0); + text += " "; + text += gi.args(); + } + else + { + p_in = gi.args(); + size_t in_len = strlen(p_in); + + if (p_in[0] == '\"' && p_in[in_len - 1] == '\"') + text += std::string_view(p_in + 1, in_len - 2); + else + text += p_in; + } + + // don't let text be too long for malicious reasons + if (text.length() > 150) + text.resize(150); + + if (text.back() != '\n') + text.push_back('\n'); + + if (sv_dedicated->integer) + gi.Client_Print(nullptr, PRINT_CHAT, text.c_str()); + + for (uint32_t j = 1; j <= game.maxclients; j++) + { + other = &g_edicts[j]; + if (!other->inuse) + continue; + if (!other->client) + continue; + gi.Client_Print(other, PRINT_CHAT, text.c_str()); + } +} +#endif + +void Cmd_PlayerList_f(edict_t *ent) +{ + uint32_t i; + static std::string str, text; + edict_t *e2; + + str.clear(); + text.clear(); + + // connect time, ping, score, name + for (i = 0, e2 = g_edicts + 1; i < game.maxclients; i++, e2++) + { + if (!e2->inuse) + continue; + + fmt::format_to(std::back_inserter(str), FMT_STRING("{:02}:{:02} {:4} {:3} {}{}\n"), (level.time - e2->client->resp.entertime).milliseconds() / 60000, + ((level.time - e2->client->resp.entertime).milliseconds() % 60000) / 1000, e2->client->ping, + e2->client->resp.score, e2->client->pers.netname, e2->client->resp.spectator ? " (spectator)" : ""); + + if (text.length() + str.length() > MAX_IDEAL_PACKET_SIZE - 50) + { + text += "...\n"; + break; + } + + text += str; + } + + if (text.length()) + gi.Client_Print(ent, PRINT_HIGH, text.c_str()); +} + +void Cmd_Switchteam_f(edict_t* ent) +{ + if (!G_TeamplayEnabled()) + return; + + // [Paril-KEX] in force-join, just do a regular team join. + if (g_teamplay_force_join->integer) + { + // check if we should even switch teams + edict_t *player; + uint32_t team1count = 0, team2count = 0; + ctfteam_t best_team; + + for (uint32_t i = 1; i <= game.maxclients; i++) + { + player = &g_edicts[i]; + + // NB: we are counting ourselves in this one, unlike + // the other assign team func + if (!player->inuse) + continue; + + switch (player->client->resp.ctf_team) + { + case CTF_TEAM1: + team1count++; + break; + case CTF_TEAM2: + team2count++; + break; + default: + break; + } + } + + if (team1count < team2count) + best_team = CTF_TEAM1; + else + best_team = CTF_TEAM2; + + if (ent->client->resp.ctf_team != best_team) + { + //// + ent->svflags = SVF_NONE; + ent->flags &= ~FL_GODMODE; + ent->client->resp.ctf_team = best_team; + ent->client->resp.ctf_state = 0; + char value[MAX_INFO_VALUE] = { 0 }; + gi.Info_ValueForKey(ent->client->pers.userinfo, "skin", value, sizeof(value)); + CTFAssignSkin(ent, value); + + // if anybody has a menu open, update it immediately + CTFDirtyTeamMenu(); + + if (ent->solid == SOLID_NOT) + { + // spectator + PutClientInServer(ent); + + G_PostRespawn(ent); + + gi.LocBroadcast_Print(PRINT_HIGH, "$g_joined_team", + ent->client->pers.netname, CTFTeamName(best_team)); + return; + } + + ent->health = 0; + player_die(ent, ent, ent, 100000, vec3_origin, { MOD_SUICIDE, true }); + + // don't even bother waiting for death frames + ent->deadflag = true; + respawn(ent); + + ent->client->resp.score = 0; + + gi.LocBroadcast_Print(PRINT_HIGH, "$g_changed_team", + ent->client->pers.netname, CTFTeamName(best_team)); + } + + return; + } + + if (ent->client->resp.ctf_team != CTF_NOTEAM) + CTFObserver(ent); + + if (!ent->client->menu) + CTFOpenJoinMenu(ent); +} + +static void Cmd_ListMonsters_f(edict_t *ent) +{ + if (!G_CheatCheck(ent)) + return; + else if (!g_debug_monster_kills->integer) + return; + + for (size_t i = 0; i < level.total_monsters; i++) + { + edict_t *e = level.monsters_registered[i]; + + if (!e || !e->inuse) + continue; + else if (!(e->svflags & SVF_MONSTER) || (e->monsterinfo.aiflags & AI_DO_NOT_COUNT)) + continue; + else if (e->deadflag) + continue; + + gi.Com_PrintFmt("{}\n", *e); + } +} + +/* +================= +ClientCommand +================= +*/ +void ClientCommand(edict_t *ent) +{ + const char *cmd; + + if (!ent->client) + return; // not fully in game yet + + cmd = gi.argv(0); + + if (Q_strcasecmp(cmd, "players") == 0) + { + Cmd_Players_f(ent); + return; + } + // [Paril-KEX] these have to go through the lobby system +#ifndef KEX_Q2_GAME + if (Q_strcasecmp(cmd, "say") == 0) + { + Cmd_Say_f(ent, false); + return; + } + if (Q_strcasecmp(cmd, "say_team") == 0 || Q_strcasecmp(cmd, "steam") == 0) + { + if (G_TeamplayEnabled()) + CTFSay_Team(ent, gi.args()); + else + Cmd_Say_f(ent, false); + return; + } +#endif + if (Q_strcasecmp(cmd, "score") == 0) + { + Cmd_Score_f(ent); + return; + } + if (Q_strcasecmp(cmd, "help") == 0) + { + Cmd_Help_f(ent); + return; + } + if (Q_strcasecmp(cmd, "listmonsters") == 0) + { + Cmd_ListMonsters_f(ent); + return; + } + + if (level.intermissiontime) + return; + + if ( Q_strcasecmp( cmd, "target" ) == 0 ) + Cmd_Target_f( ent ); + else if ( Q_strcasecmp( cmd, "use" ) == 0 || Q_strcasecmp( cmd, "use_only" ) == 0 || + Q_strcasecmp( cmd, "use_index" ) == 0 || Q_strcasecmp( cmd, "use_index_only" ) == 0 ) + Cmd_Use_f( ent ); + else if ( Q_strcasecmp( cmd, "drop" ) == 0 || + Q_strcasecmp( cmd, "drop_index" ) == 0 ) + Cmd_Drop_f( ent ); + else if ( Q_strcasecmp( cmd, "give" ) == 0 ) + Cmd_Give_f( ent ); + else if ( Q_strcasecmp( cmd, "god" ) == 0 ) + Cmd_God_f( ent ); + else if (Q_strcasecmp(cmd, "immortal") == 0) + Cmd_Immortal_f(ent); + else if ( Q_strcasecmp( cmd, "setpoi" ) == 0 ) + Cmd_SetPOI_f( ent ); + else if ( Q_strcasecmp( cmd, "checkpoi" ) == 0 ) + Cmd_CheckPOI_f( ent ); + // Paril: cheats to help with dev + else if ( Q_strcasecmp( cmd, "spawn" ) == 0 ) + Cmd_Spawn_f( ent ); + else if ( Q_strcasecmp( cmd, "teleport" ) == 0 ) + Cmd_Teleport_f( ent ); + else if ( Q_strcasecmp( cmd, "notarget" ) == 0 ) + Cmd_Notarget_f( ent ); + else if ( Q_strcasecmp( cmd, "novisible" ) == 0 ) + Cmd_Novisible_f( ent ); + else if ( Q_strcasecmp( cmd, "alertall" ) == 0 ) + Cmd_AlertAll_f( ent ); + else if ( Q_strcasecmp( cmd, "noclip" ) == 0 ) + Cmd_Noclip_f( ent ); + else if ( Q_strcasecmp( cmd, "inven" ) == 0 ) + Cmd_Inven_f( ent ); + else if ( Q_strcasecmp( cmd, "invnext" ) == 0 ) + SelectNextItem( ent, IF_ANY ); + else if ( Q_strcasecmp( cmd, "invprev" ) == 0 ) + SelectPrevItem( ent, IF_ANY ); + else if ( Q_strcasecmp( cmd, "invnextw" ) == 0 ) + SelectNextItem( ent, IF_WEAPON ); + else if ( Q_strcasecmp( cmd, "invprevw" ) == 0 ) + SelectPrevItem( ent, IF_WEAPON ); + else if ( Q_strcasecmp( cmd, "invnextp" ) == 0 ) + SelectNextItem( ent, IF_POWERUP ); + else if ( Q_strcasecmp( cmd, "invprevp" ) == 0 ) + SelectPrevItem( ent, IF_POWERUP ); + else if ( Q_strcasecmp( cmd, "invuse" ) == 0 ) + Cmd_InvUse_f( ent ); + else if ( Q_strcasecmp( cmd, "invdrop" ) == 0 ) + Cmd_InvDrop_f( ent ); + else if ( Q_strcasecmp( cmd, "weapprev" ) == 0 ) + Cmd_WeapPrev_f( ent ); + else if ( Q_strcasecmp( cmd, "weapnext" ) == 0 ) + Cmd_WeapNext_f( ent ); + else if ( Q_strcasecmp( cmd, "weaplast" ) == 0 || Q_strcasecmp( cmd, "lastweap" ) == 0 ) + Cmd_WeapLast_f( ent ); + else if ( Q_strcasecmp( cmd, "kill" ) == 0 ) + Cmd_Kill_f( ent ); + else if ( Q_strcasecmp( cmd, "kill_ai" ) == 0 ) + Cmd_Kill_AI_f( ent ); + else if ( Q_strcasecmp( cmd, "where" ) == 0 ) + Cmd_Where_f( ent ); + else if ( Q_strcasecmp( cmd, "clear_ai_enemy" ) == 0 ) + Cmd_Clear_AI_Enemy_f( ent ); + else if (Q_strcasecmp(cmd, "putaway") == 0) + Cmd_PutAway_f(ent); + else if (Q_strcasecmp(cmd, "wave") == 0) + Cmd_Wave_f(ent); + else if (Q_strcasecmp(cmd, "playerlist") == 0) + Cmd_PlayerList_f(ent); + // ZOID + else if (Q_strcasecmp(cmd, "team") == 0) + CTFTeam_f(ent); + else if (Q_strcasecmp(cmd, "id") == 0) + CTFID_f(ent); + else if (Q_strcasecmp(cmd, "yes") == 0) + CTFVoteYes(ent); + else if (Q_strcasecmp(cmd, "no") == 0) + CTFVoteNo(ent); + else if (Q_strcasecmp(cmd, "ready") == 0) + CTFReady(ent); + else if (Q_strcasecmp(cmd, "notready") == 0) + CTFNotReady(ent); + else if (Q_strcasecmp(cmd, "ghost") == 0) + CTFGhost(ent); + else if (Q_strcasecmp(cmd, "admin") == 0) + CTFAdmin(ent); + else if (Q_strcasecmp(cmd, "stats") == 0) + CTFStats(ent); + else if (Q_strcasecmp(cmd, "warp") == 0) + CTFWarp(ent); + else if (Q_strcasecmp(cmd, "boot") == 0) + CTFBoot(ent); + else if (Q_strcasecmp(cmd, "playerlist") == 0) + CTFPlayerList(ent); + else if (Q_strcasecmp(cmd, "observer") == 0) + CTFObserver(ent); + // ZOID + else if (Q_strcasecmp(cmd, "switchteam") == 0) + Cmd_Switchteam_f(ent); +#ifndef KEX_Q2_GAME + else // anything that doesn't match a command will be a chat + Cmd_Say_f(ent, true); +#else + // anything that doesn't match a command will inform them + else + gi.LocClient_Print(ent, PRINT_HIGH, "invalid game command \"{}\"\n", gi.argv(0)); +#endif +} diff --git a/rerelease/g_combat.cpp b/rerelease/g_combat.cpp new file mode 100644 index 0000000..2a2a340 --- /dev/null +++ b/rerelease/g_combat.cpp @@ -0,0 +1,911 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// g_combat.c + +#include "g_local.h" + +/* +============ +CanDamage + +Returns true if the inflictor can directly damage the target. Used for +explosions and melee attacks. +============ +*/ +bool CanDamage(edict_t *targ, edict_t *inflictor) +{ + vec3_t dest; + trace_t trace; + + // bmodels need special checking because their origin is 0,0,0 + vec3_t inflictor_center; + + if (inflictor->linked) + inflictor_center = (inflictor->absmin + inflictor->absmax) * 0.5f; + else + inflictor_center = inflictor->s.origin; + + if (targ->solid == SOLID_BSP) + { + dest = closest_point_to_box(inflictor_center, targ->absmin, targ->absmax); + + trace = gi.traceline(inflictor_center, dest, inflictor, MASK_SOLID); + if (trace.fraction == 1.0f) + return true; + } + + vec3_t targ_center; + + if (targ->linked) + targ_center = (targ->absmin + targ->absmax) * 0.5f; + else + targ_center = targ->s.origin; + + trace = gi.traceline(inflictor_center, targ_center, inflictor, MASK_SOLID); + if (trace.fraction == 1.0f) + return true; + + dest = targ_center; + dest[0] += 15.0f; + dest[1] += 15.0f; + trace = gi.traceline(inflictor_center, dest, inflictor, MASK_SOLID); + if (trace.fraction == 1.0f) + return true; + + dest = targ_center; + dest[0] += 15.0f; + dest[1] -= 15.0f; + trace = gi.traceline(inflictor_center, dest, inflictor, MASK_SOLID); + if (trace.fraction == 1.0f) + return true; + + dest = targ_center; + dest[0] -= 15.0f; + dest[1] += 15.0f; + trace = gi.traceline(inflictor_center, dest, inflictor, MASK_SOLID); + if (trace.fraction == 1.0f) + return true; + + dest = targ_center; + dest[0] -= 15.0f; + dest[1] -= 15.0f; + trace = gi.traceline(inflictor_center, dest, inflictor, MASK_SOLID); + if (trace.fraction == 1.0f) + return true; + + return false; +} + +/* +============ +Killed +============ +*/ +void Killed(edict_t *targ, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, mod_t mod) +{ + if (targ->health < -999) + targ->health = -999; + + // [Paril-KEX] + if ((targ->svflags & SVF_MONSTER) && targ->monsterinfo.aiflags & AI_MEDIC) + { + if (targ->enemy && targ->enemy->inuse && (targ->enemy->svflags & SVF_MONSTER)) // god, I hope so + { + cleanupHealTarget(targ->enemy); + } + + // clean up self + targ->monsterinfo.aiflags &= ~AI_MEDIC; + } + + targ->enemy = attacker; + targ->lastMOD = mod; + + // [Paril-KEX] monsters call die in their damage handler + if (targ->svflags & SVF_MONSTER) + return; + + targ->die(targ, inflictor, attacker, damage, point, mod); + + if (targ->monsterinfo.setskin) + targ->monsterinfo.setskin(targ); +} + +/* +================ +SpawnDamage +================ +*/ +void SpawnDamage(int type, const vec3_t &origin, const vec3_t &normal, int damage) +{ + if (damage > 255) + damage = 255; + gi.WriteByte(svc_temp_entity); + gi.WriteByte(type); + // gi.WriteByte (damage); + gi.WritePosition(origin); + gi.WriteDir(normal); + gi.multicast(origin, MULTICAST_PVS, false); +} + +/* +============ +T_Damage + +targ entity that is being damaged +inflictor entity that is causing the damage +attacker entity that caused the inflictor to damage targ + example: targ=monster, inflictor=rocket, attacker=player + +dir direction of the attack +point point at which the damage is being inflicted +normal normal vector from that point +damage amount of damage being inflicted +knockback force to be applied against targ as a result of the damage + +dflags these flags are used to control how T_Damage works + DAMAGE_RADIUS damage was indirect (from a nearby explosion) + DAMAGE_NO_ARMOR armor does not protect from this damage + DAMAGE_ENERGY damage is from an energy based weapon + DAMAGE_NO_KNOCKBACK do not affect velocity, just view angles + DAMAGE_BULLET damage is from a bullet (used for ricochets) + DAMAGE_NO_PROTECTION kills godmode, armor, everything +============ +*/ +static int CheckPowerArmor(edict_t *ent, const vec3_t &point, const vec3_t &normal, int damage, damageflags_t dflags) +{ + gclient_t *client; + int save; + item_id_t power_armor_type; + int damagePerCell; + int pa_te_type; + int *power; + int power_used; + + if (ent->health <= 0) + return 0; + + if (!damage) + return 0; + + client = ent->client; + + if (dflags & (DAMAGE_NO_ARMOR | DAMAGE_NO_POWER_ARMOR)) // PGM + return 0; + + if (client) + { + power_armor_type = PowerArmorType(ent); + power = &client->pers.inventory[IT_AMMO_CELLS]; + } + else if (ent->svflags & SVF_MONSTER) + { + power_armor_type = ent->monsterinfo.power_armor_type; + power = &ent->monsterinfo.power_armor_power; + } + else + return 0; + + if (power_armor_type == IT_NULL) + return 0; + if (!*power) + return 0; + + if (power_armor_type == IT_ITEM_POWER_SCREEN) + { + vec3_t vec; + float dot; + vec3_t forward; + + // only works if damage point is in front + AngleVectors(ent->s.angles, forward, nullptr, nullptr); + vec = point - ent->s.origin; + vec.normalize(); + dot = vec.dot(forward); + if (dot <= 0.3f) + return 0; + + damagePerCell = 1; + pa_te_type = TE_SCREEN_SPARKS; + damage = damage / 3; + } + else + { + if (ctf->integer) + damagePerCell = 1; // power armor is weaker in CTF + else + damagePerCell = 2; + pa_te_type = TE_SCREEN_SPARKS; + damage = (2 * damage) / 3; + } + + // Paril: fix small amounts of damage not + // being absorbed + damage = max(1, damage); + + save = *power * damagePerCell; + + if (!save) + return 0; + + // [Paril-KEX] energy damage should do more to power armor, not ETF Rifle shots. + if (dflags & DAMAGE_ENERGY) + save = max(1, save / 2); + + if (save > damage) + save = damage; + + // [Paril-KEX] energy damage should do more to power armor, not ETF Rifle shots. + if (dflags & DAMAGE_ENERGY) + power_used = (save / damagePerCell) * 2; + else + power_used = save / damagePerCell; + + power_used = max(1, power_used); + + SpawnDamage(pa_te_type, point, normal, save); + ent->powerarmor_time = level.time + 200_ms; + + // Paril: adjustment so that power armor + // always uses damagePerCell even if it does + // only a single point of damage + *power = max(0, *power - max(damagePerCell, power_used)); + + // check power armor turn-off states + if (ent->client) + G_CheckPowerArmor(ent); + else if (!*power) + { + gi.sound(ent, CHAN_AUTO, gi.soundindex("misc/mon_power2.wav"), 1.f, ATTN_NORM, 0.f); + + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_POWER_SPLASH); + gi.WriteEntity(ent); + gi.WriteByte((power_armor_type == IT_ITEM_POWER_SCREEN) ? 1 : 0); + gi.multicast(ent->s.origin, MULTICAST_PHS, false); + } + + return save; +} + +static int CheckArmor(edict_t *ent, const vec3_t &point, const vec3_t &normal, int damage, int te_sparks, + damageflags_t dflags) +{ + gclient_t *client; + int save; + item_id_t index; + gitem_t *armor; + int *power; + + if (!damage) + return 0; + + // ROGUE + if (dflags & (DAMAGE_NO_ARMOR | DAMAGE_NO_REG_ARMOR)) + // ROGUE + return 0; + + client = ent->client; + index = ArmorIndex(ent); + + if (!index) + return 0; + + armor = GetItemByIndex(index); + + if (dflags & DAMAGE_ENERGY) + save = (int) ceilf(armor->armor_info->energy_protection * damage); + else + save = (int) ceilf(armor->armor_info->normal_protection * damage); + + if (client) + power = &client->pers.inventory[index]; + else + power = &ent->monsterinfo.armor_power; + + if (save >= *power) + save = *power; + + if (!save) + return 0; + + *power -= save; + + if (!client && !ent->monsterinfo.armor_power) + ent->monsterinfo.armor_type = IT_NULL; + + SpawnDamage(te_sparks, point, normal, save); + + return save; +} + +void M_ReactToDamage(edict_t *targ, edict_t *attacker, edict_t *inflictor) +{ + // pmm + bool new_tesla; + + if (!(attacker->client) && !(attacker->svflags & SVF_MONSTER)) + return; + + //======= + // ROGUE + // logic for tesla - if you are hit by a tesla, and can't see who you should be mad at (attacker) + // attack the tesla + // also, target the tesla if it's a "new" tesla + if ((inflictor) && (!strcmp(inflictor->classname, "tesla_mine"))) + { + new_tesla = MarkTeslaArea(targ, inflictor); + if ((new_tesla || brandom()) && (!targ->enemy || !targ->enemy->classname || strcmp(targ->enemy->classname, "tesla_mine"))) + TargetTesla(targ, inflictor); + return; + } + // ROGUE + //======= + + if (attacker == targ || attacker == targ->enemy) + return; + + // if we are a good guy monster and our attacker is a player + // or another good guy, do not get mad at them + if (targ->monsterinfo.aiflags & AI_GOOD_GUY) + { + if (attacker->client || (attacker->monsterinfo.aiflags & AI_GOOD_GUY)) + return; + } + + // PGM + // if we're currently mad at something a target_anger made us mad at, ignore + // damage + if (targ->enemy && targ->monsterinfo.aiflags & AI_TARGET_ANGER) + { + float percentHealth; + + // make sure whatever we were pissed at is still around. + if (targ->enemy->inuse) + { + percentHealth = (float) (targ->health) / (float) (targ->max_health); + if (targ->enemy->inuse && percentHealth > 0.33f) + return; + } + + // remove the target anger flag + targ->monsterinfo.aiflags &= ~AI_TARGET_ANGER; + } + // PGM + + // we recently switched from reacting to damage, don't do it + if (targ->monsterinfo.react_to_damage_time > level.time) + return; + + // PMM + // if we're healing someone, do like above and try to stay with them + if ((targ->enemy) && (targ->monsterinfo.aiflags & AI_MEDIC)) + { + float percentHealth; + + percentHealth = (float) (targ->health) / (float) (targ->max_health); + // ignore it some of the time + if (targ->enemy->inuse && percentHealth > 0.25f) + return; + + // remove the medic flag + cleanupHealTarget(targ->enemy); + targ->monsterinfo.aiflags &= ~AI_MEDIC; + } + // PMM + + // we now know that we are not both good guys + targ->monsterinfo.react_to_damage_time = level.time + random_time(3_sec, 5_sec); + + // if attacker is a client, get mad at them because he's good and we're not + if (attacker->client) + { + targ->monsterinfo.aiflags &= ~AI_SOUND_TARGET; + + // this can only happen in coop (both new and old enemies are clients) + // only switch if can't see the current enemy + if (targ->enemy != attacker) + { + if (targ->enemy && targ->enemy->client) + { + if (visible(targ, targ->enemy)) + { + targ->oldenemy = attacker; + return; + } + targ->oldenemy = targ->enemy; + } + + // [Paril-KEX] + if ((targ->svflags & SVF_MONSTER) && targ->monsterinfo.aiflags & AI_MEDIC) + { + if (targ->enemy && targ->enemy->inuse && (targ->enemy->svflags & SVF_MONSTER)) // god, I hope so + { + cleanupHealTarget(targ->enemy); + } + + // clean up self + targ->monsterinfo.aiflags &= ~AI_MEDIC; + } + + targ->enemy = attacker; + if (!(targ->monsterinfo.aiflags & AI_DUCKED)) + FoundTarget(targ); + } + return; + } + + if (attacker->enemy == targ // if they *meant* to shoot us, then shoot back + // it's the same base (walk/swim/fly) type and both don't ignore shots, + // get mad at them + || (((targ->flags & (FL_FLY | FL_SWIM)) == (attacker->flags & (FL_FLY | FL_SWIM))) && + (strcmp(targ->classname, attacker->classname) != 0) && !(attacker->monsterinfo.aiflags & AI_IGNORE_SHOTS) && + !(targ->monsterinfo.aiflags & AI_IGNORE_SHOTS))) + { + if (targ->enemy != attacker) + { + // [Paril-KEX] + if ((targ->svflags & SVF_MONSTER) && targ->monsterinfo.aiflags & AI_MEDIC) + { + if (targ->enemy && targ->enemy->inuse && (targ->enemy->svflags & SVF_MONSTER)) // god, I hope so + { + cleanupHealTarget(targ->enemy); + } + + // clean up self + targ->monsterinfo.aiflags &= ~AI_MEDIC; + } + + if (targ->enemy && targ->enemy->client) + targ->oldenemy = targ->enemy; + targ->enemy = attacker; + if (!(targ->monsterinfo.aiflags & AI_DUCKED)) + FoundTarget(targ); + } + } + // otherwise get mad at whoever they are mad at (help our buddy) unless it is us! + else if (attacker->enemy && attacker->enemy != targ && targ->enemy != attacker->enemy) + { + if (targ->enemy != attacker->enemy) + { + // [Paril-KEX] + if ((targ->svflags & SVF_MONSTER) && targ->monsterinfo.aiflags & AI_MEDIC) + { + if (targ->enemy && targ->enemy->inuse && (targ->enemy->svflags & SVF_MONSTER)) // god, I hope so + { + cleanupHealTarget(targ->enemy); + } + + // clean up self + targ->monsterinfo.aiflags &= ~AI_MEDIC; + } + + if (targ->enemy && targ->enemy->client) + targ->oldenemy = targ->enemy; + targ->enemy = attacker->enemy; + if (!(targ->monsterinfo.aiflags & AI_DUCKED)) + FoundTarget(targ); + } + } +} + +// check if the two given entities are on the same team +bool OnSameTeam(edict_t *ent1, edict_t *ent2) +{ + // monsters are never on our team atm + if (!ent1->client || !ent2->client) + return false; + // we're never on our own team + else if (ent1 == ent2) + return false; + + // [Paril-KEX] coop 'team' support + if (coop->integer) + return ent1->client && ent2->client; + // ZOID + else if (G_TeamplayEnabled() && ent1->client && ent2->client) + { + if (ent1->client->resp.ctf_team == ent2->client->resp.ctf_team) + return true; + } + // ZOID + + return false; +} + +// check if the two entities are on a team and that +// they wouldn't damage each other +bool CheckTeamDamage(edict_t *targ, edict_t *attacker) +{ + // always damage teammates if friendly fire is enabled + if (g_friendly_fire->integer) + return false; + + return OnSameTeam(targ, attacker); +} + +void T_Damage(edict_t *targ, edict_t *inflictor, edict_t *attacker, const vec3_t &dir, const vec3_t &point, + const vec3_t &normal, int damage, int knockback, damageflags_t dflags, mod_t mod) +{ + gclient_t *client; + int take; + int save; + int asave; + int psave; + int te_sparks; + bool sphere_notified; // PGM + + if (!targ->takedamage) + return; + + if (g_instagib->integer && attacker->client && targ->client) + { + // [Kex] always kill no matter what on instagib + damage = 9999; + } + + sphere_notified = false; // PGM + + // friendly fire avoidance + // if enabled you can't hurt teammates (but you can hurt yourself) + // knockback still occurs + if ((targ != attacker) && !(dflags & DAMAGE_NO_PROTECTION)) + { + // mark as friendly fire + if (OnSameTeam(targ, attacker)) + { + mod.friendly_fire = true; + + // if we're not a nuke & friendly fire is disabled, just kill the damage + if (!g_friendly_fire->integer && (mod.id != MOD_NUKE)) + damage = 0; + } + } + + // ROGUE + // allow the deathmatch game to change values + if (deathmatch->integer && gamerules->integer) + { + if (DMGame.ChangeDamage) + damage = DMGame.ChangeDamage(targ, attacker, damage, mod); + if (DMGame.ChangeKnockback) + knockback = DMGame.ChangeKnockback(targ, attacker, knockback, mod); + + if (!damage) + return; + } + // ROGUE + + // easy mode takes half damage + if (skill->integer == 0 && deathmatch->integer == 0 && targ->client && damage) + { + damage /= 2; + if (!damage) + damage = 1; + } + + if ( ( targ->svflags & SVF_MONSTER ) != 0 ) { + damage *= ai_damage_scale->integer; + } else { + damage *= g_damage_scale->integer; + } // mal: just for debugging... + + client = targ->client; + + // PMM - defender sphere takes half damage + if (damage && (client) && (client->owned_sphere) && (client->owned_sphere->spawnflags == SPHERE_DEFENDER)) + { + damage /= 2; + if (!damage) + damage = 1; + } + + if (dflags & DAMAGE_BULLET) + te_sparks = TE_BULLET_SPARKS; + else + te_sparks = TE_SPARKS; + + // bonus damage for surprising a monster + if (!(dflags & DAMAGE_RADIUS) && (targ->svflags & SVF_MONSTER) && (attacker->client) && + (!targ->enemy || targ->monsterinfo.surprise_time == level.time) && (targ->health > 0)) + { + damage *= 2; + targ->monsterinfo.surprise_time = level.time; + } + + // ZOID + // strength tech + damage = CTFApplyStrength(attacker, damage); + // ZOID + + if ((targ->flags & FL_NO_KNOCKBACK) || + ((targ->flags & FL_ALIVE_KNOCKBACK_ONLY) && (!targ->deadflag || targ->dead_time != level.time))) + knockback = 0; + + // figure momentum add + if (!(dflags & DAMAGE_NO_KNOCKBACK)) + { + if ((knockback) && (targ->movetype != MOVETYPE_NONE) && (targ->movetype != MOVETYPE_BOUNCE) && + (targ->movetype != MOVETYPE_PUSH) && (targ->movetype != MOVETYPE_STOP)) + { + vec3_t normalized = dir.normalized(); + vec3_t kvel; + float mass; + + if (targ->mass < 50) + mass = 50; + else + mass = (float) targ->mass; + + if (targ->client && attacker == targ) + kvel = normalized * (1600.0f * knockback / mass); // the rocket jump hack... + else + kvel = normalized * (500.0f * knockback / mass); + + targ->velocity += kvel; + } + } + + take = damage; + save = 0; + + // check for godmode + if ((targ->flags & FL_GODMODE) && !(dflags & DAMAGE_NO_PROTECTION)) + { + take = 0; + save = damage; + SpawnDamage(te_sparks, point, normal, save); + } + + // check for invincibility + // ROGUE + if (!(dflags & DAMAGE_NO_PROTECTION) && + (((client && client->invincible_time > level.time)) || + ((targ->svflags & SVF_MONSTER) && targ->monsterinfo.invincible_time > level.time))) + // ROGUE + { + if (targ->pain_debounce_time < level.time) + { + gi.sound(targ, CHAN_ITEM, gi.soundindex("items/protect4.wav"), 1, ATTN_NORM, 0); + targ->pain_debounce_time = level.time + 2_sec; + } + take = 0; + save = damage; + } + + // ZOID + // team armor protect + if (G_TeamplayEnabled() && targ->client && attacker->client && + targ->client->resp.ctf_team == attacker->client->resp.ctf_team && targ != attacker && + g_teamplay_armor_protect->integer) + { + psave = asave = 0; + } + else + { + // ZOID + psave = CheckPowerArmor(targ, point, normal, take, dflags); + take -= psave; + + asave = CheckArmor(targ, point, normal, take, te_sparks, dflags); + take -= asave; + } + + // treat cheat/powerup savings the same as armor + asave += save; + + // ZOID + // resistance tech + take = CTFApplyResistance(targ, take); + // ZOID + + // ZOID + CTFCheckHurtCarrier(targ, attacker); + // ZOID + + // ROGUE - this option will do damage both to the armor and person. originally for DPU rounds + if (dflags & DAMAGE_DESTROY_ARMOR) + { + if (!(targ->flags & FL_GODMODE) && !(dflags & DAMAGE_NO_PROTECTION) && + !(client && client->invincible_time > level.time)) + { + take = damage; + } + } + // ROGUE + + // [Paril-KEX] player hit markers + if (targ != attacker && attacker->client && targ->health > 0 && !((targ->svflags & SVF_DEADMONSTER) || (targ->flags & FL_NO_DAMAGE_EFFECTS)) && mod.id != MOD_TARGET_LASER) + attacker->client->ps.stats[STAT_HIT_MARKER] += take + psave + asave; + + // do the damage + if (take) + { + if (!(targ->flags & FL_NO_DAMAGE_EFFECTS)) + { + // ROGUE + if (targ->flags & FL_MECHANICAL) + SpawnDamage(TE_ELECTRIC_SPARKS, point, normal, take); + // ROGUE + else if ((targ->svflags & SVF_MONSTER) || (client)) + { + // XATRIX + if (strcmp(targ->classname, "monster_gekk") == 0) + SpawnDamage(TE_GREENBLOOD, point, normal, take); + // XATRIX + // ROGUE + else if (mod.id == MOD_CHAINFIST) + SpawnDamage(TE_MOREBLOOD, point, normal, 255); + // ROGUE + else + SpawnDamage(TE_BLOOD, point, normal, take); + } + else + SpawnDamage(te_sparks, point, normal, take); + } + + if (!CTFMatchSetup()) + targ->health = targ->health - take; + + if ((targ->flags & FL_IMMORTAL) && targ->health <= 0) + targ->health = 1; + + // PGM - spheres need to know who to shoot at + if (client && client->owned_sphere) + { + sphere_notified = true; + if (client->owned_sphere->pain) + client->owned_sphere->pain(client->owned_sphere, attacker, 0, 0, mod); + } + // PGM + + if (targ->health <= 0) + { + if ((targ->svflags & SVF_MONSTER) || (client)) + { + targ->flags |= FL_ALIVE_KNOCKBACK_ONLY; + targ->dead_time = level.time; + } + targ->monsterinfo.damage_blood += take; + targ->monsterinfo.damage_attacker = attacker; + targ->monsterinfo.damage_inflictor = inflictor; + targ->monsterinfo.damage_from = point; + targ->monsterinfo.damage_mod = mod; + targ->monsterinfo.damage_knockback += knockback; + Killed(targ, inflictor, attacker, take, point, mod); + return; + } + } + + // PGM - spheres need to know who to shoot at + if (!sphere_notified) + { + if (client && client->owned_sphere) + { + sphere_notified = true; + if (client->owned_sphere->pain) + client->owned_sphere->pain(client->owned_sphere, attacker, 0, 0, mod); + } + } + // PGM + + if ( targ->client ) { + targ->client->last_attacker_time = level.time; + } + + if (targ->svflags & SVF_MONSTER) + { + if (damage > 0) + { + M_ReactToDamage(targ, attacker, inflictor); + + targ->monsterinfo.damage_attacker = attacker; + targ->monsterinfo.damage_inflictor = inflictor; + targ->monsterinfo.damage_blood += take; + targ->monsterinfo.damage_from = point; + targ->monsterinfo.damage_mod = mod; + targ->monsterinfo.damage_knockback += knockback; + } + + if (targ->monsterinfo.setskin) + targ->monsterinfo.setskin(targ); + } + else if (take && targ->pain) + targ->pain(targ, attacker, (float) knockback, take, mod); + + // add to the damage inflicted on a player this frame + // the total will be turned into screen blends and view angle kicks + // at the end of the frame + if (client) + { + client->damage_parmor += psave; + client->damage_armor += asave; + client->damage_blood += take; + client->damage_knockback += knockback; + client->damage_from = point; + client->last_damage_time = level.time + COOP_DAMAGE_RESPAWN_TIME; + + if (!(dflags & DAMAGE_NO_INDICATOR) && inflictor != world && attacker != world && (take || psave || asave)) + { + damage_indicator_t *indicator = nullptr; + size_t i; + + for (i = 0; i < client->num_damage_indicators; i++) + { + if ((point - client->damage_indicators[i].from).length() < 32.f) + { + indicator = &client->damage_indicators[i]; + break; + } + } + + if (!indicator && i != MAX_DAMAGE_INDICATORS) + { + indicator = &client->damage_indicators[i]; + // for projectile direct hits, use the attacker; otherwise + // use the inflictor (rocket splash should point to the rocket) + indicator->from = (dflags & DAMAGE_RADIUS) ? inflictor->s.origin : attacker->s.origin; + indicator->health = indicator->armor = indicator->power = 0; + client->num_damage_indicators++; + } + + if (indicator) + { + indicator->health += take; + indicator->power += psave; + indicator->armor += asave; + } + } + } +} + +/* +============ +T_RadiusDamage +============ +*/ +void T_RadiusDamage(edict_t *inflictor, edict_t *attacker, float damage, edict_t *ignore, float radius, damageflags_t dflags, mod_t mod) +{ + float points; + edict_t *ent = nullptr; + vec3_t v; + vec3_t dir; + vec3_t inflictor_center; + + if (inflictor->linked) + inflictor_center = (inflictor->absmax + inflictor->absmin) * 0.5f; + else + inflictor_center = inflictor->s.origin; + + while ((ent = findradius(ent, inflictor_center, radius)) != nullptr) + { + if (ent == ignore) + continue; + if (!ent->takedamage) + continue; + + if (ent->solid == SOLID_BSP && ent->linked) + v = closest_point_to_box(inflictor_center, ent->absmin, ent->absmax); + else + { + v = ent->mins + ent->maxs; + v = ent->s.origin + (v * 0.5f); + } + v = inflictor_center - v; + points = damage - 0.5f * v.length(); + if (ent == attacker) + points = points * 0.5f; + if (points > 0) + { + if (CanDamage(ent, inflictor)) + { + dir = (ent->s.origin - inflictor_center).normalized(); + // [Paril-KEX] use closest point on bbox to explosion position + // to spawn damage effect + + T_Damage(ent, inflictor, attacker, dir, closest_point_to_box(inflictor_center, ent->absmin, ent->absmax), dir, (int) points, (int) points, + dflags | DAMAGE_RADIUS, mod); + } + } + } +} diff --git a/rerelease/g_func.cpp b/rerelease/g_func.cpp new file mode 100644 index 0000000..bca3b89 --- /dev/null +++ b/rerelease/g_func.cpp @@ -0,0 +1,2897 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +#include "g_local.h" + +/* +========================================================= + + PLATS + + movement options: + + linear + smooth start, hard stop + smooth start, smooth stop + + start + end + acceleration + speed + deceleration + begin sound + end sound + target fired when reaching end + wait at end + + object characteristics that use move segments + --------------------------------------------- + movetype_push, or movetype_stop + action when touched + action when blocked + action when used + disabled? + auto trigger spawning + + +========================================================= +*/ + +constexpr spawnflags_t SPAWNFLAG_DOOR_START_OPEN = 1_spawnflag; +constexpr spawnflags_t SPAWNFLAG_DOOR_CRUSHER = 4_spawnflag; +constexpr spawnflags_t SPAWNFLAG_DOOR_NOMONSTER = 8_spawnflag; +constexpr spawnflags_t SPAWNFLAG_DOOR_ANIMATED = 16_spawnflag; +constexpr spawnflags_t SPAWNFLAG_DOOR_TOGGLE = 32_spawnflag; +constexpr spawnflags_t SPAWNFLAG_DOOR_ANIMATED_FAST = 64_spawnflag; + +constexpr spawnflags_t SPAWNFLAG_DOOR_ROTATING_X_AXIS = 64_spawnflag; +constexpr spawnflags_t SPAWNFLAG_DOOR_ROTATING_Y_AXIS = 128_spawnflag; +constexpr spawnflags_t SPAWNFLAG_DOOR_ROTATING_INACTIVE = 0x10000_spawnflag; // Paril: moved to non-reserved +constexpr spawnflags_t SPAWNFLAG_DOOR_ROTATING_SAFE_OPEN = 0x20000_spawnflag; + +// support routine for setting moveinfo sounds +inline int32_t G_GetMoveinfoSoundIndex(edict_t *self, const char *default_value, const char *wanted_value) +{ + if (!wanted_value) + { + if (default_value) + return gi.soundindex(default_value); + + return 0; + } + else if (!*wanted_value || *wanted_value == '0' || *wanted_value == ' ') + return 0; + + return gi.soundindex(wanted_value); +} + +void G_SetMoveinfoSounds(edict_t *self, const char *default_start, const char *default_mid, const char *default_end) +{ + self->moveinfo.sound_start = G_GetMoveinfoSoundIndex(self, default_start, st.noise_start); + self->moveinfo.sound_middle = G_GetMoveinfoSoundIndex(self, default_mid, st.noise_middle); + self->moveinfo.sound_end = G_GetMoveinfoSoundIndex(self, default_end, st.noise_end); +} + +// +// Support routines for movement (changes in origin using velocity) +// + +THINK(Move_Done) (edict_t *ent) -> void +{ + ent->velocity = {}; + ent->moveinfo.endfunc(ent); +} + +THINK(Move_Final) (edict_t *ent) -> void +{ + if (ent->moveinfo.remaining_distance == 0) + { + Move_Done(ent); + return; + } + + // [Paril-KEX] use exact remaining distance + ent->velocity = (ent->moveinfo.dest - ent->s.origin) * (1.f / gi.frame_time_s); + + ent->think = Move_Done; + ent->nextthink = level.time + FRAME_TIME_S; +} + +THINK(Move_Begin) (edict_t *ent) -> void +{ + float frames; + + if ((ent->moveinfo.speed * gi.frame_time_s) >= ent->moveinfo.remaining_distance) + { + Move_Final(ent); + return; + } + ent->velocity = ent->moveinfo.dir * ent->moveinfo.speed; + frames = floor((ent->moveinfo.remaining_distance / ent->moveinfo.speed) / gi.frame_time_s); + ent->moveinfo.remaining_distance -= frames * ent->moveinfo.speed * gi.frame_time_s; + ent->nextthink = level.time + (FRAME_TIME_S * frames); + ent->think = Move_Final; +} + +void Think_AccelMove(edict_t *ent); + +void Move_Calc(edict_t *ent, const vec3_t &dest, void(*endfunc)(edict_t *self)) +{ + ent->velocity = {}; + ent->moveinfo.dest = dest; + ent->moveinfo.dir = dest - ent->s.origin; + ent->moveinfo.remaining_distance = ent->moveinfo.dir.normalize(); + ent->moveinfo.endfunc = endfunc; + + if (ent->moveinfo.speed == ent->moveinfo.accel && ent->moveinfo.speed == ent->moveinfo.decel) + { + if (level.current_entity == ((ent->flags & FL_TEAMSLAVE) ? ent->teammaster : ent)) + { + Move_Begin(ent); + } + else + { + ent->nextthink = level.time + FRAME_TIME_S; + ent->think = Move_Begin; + } + } + else + { + // accelerative + ent->moveinfo.current_speed = 0; + ent->think = Think_AccelMove; + ent->nextthink = level.time + FRAME_TIME_S; + } +} + +// +// Support routines for angular movement (changes in angle using avelocity) +// + +THINK(AngleMove_Done) (edict_t *ent) -> void +{ + ent->avelocity = {}; + ent->moveinfo.endfunc(ent); +} + +THINK(AngleMove_Final) (edict_t *ent) -> void +{ + vec3_t move; + + if (ent->moveinfo.state == STATE_UP) + { + if (ent->moveinfo.reversing) + move = ent->moveinfo.end_angles_reversed - ent->s.angles; + else + move = ent->moveinfo.end_angles - ent->s.angles; + } + else + move = ent->moveinfo.start_angles - ent->s.angles; + + if (!move) + { + AngleMove_Done(ent); + return; + } + + ent->avelocity = move * (1.0f / gi.frame_time_s); + + ent->think = AngleMove_Done; + ent->nextthink = level.time + FRAME_TIME_S; +} + +THINK(AngleMove_Begin) (edict_t *ent) -> void +{ + vec3_t destdelta; + float len; + float traveltime; + float frames; + + // PGM accelerate as needed + if (ent->moveinfo.speed < ent->speed) + { + ent->moveinfo.speed += ent->accel; + if (ent->moveinfo.speed > ent->speed) + ent->moveinfo.speed = ent->speed; + } + // PGM + + // set destdelta to the vector needed to move + if (ent->moveinfo.state == STATE_UP) + { + if (ent->moveinfo.reversing) + destdelta = ent->moveinfo.end_angles_reversed - ent->s.angles; + else + destdelta = ent->moveinfo.end_angles - ent->s.angles; + } + else + destdelta = ent->moveinfo.start_angles - ent->s.angles; + + // calculate length of vector + len = destdelta.length(); + + // divide by speed to get time to reach dest + traveltime = len / ent->moveinfo.speed; + + if (traveltime < gi.frame_time_s) + { + AngleMove_Final(ent); + return; + } + + frames = floor(traveltime / gi.frame_time_s); + + // scale the destdelta vector by the time spent traveling to get velocity + ent->avelocity = destdelta * (1.0f / traveltime); + + // PGM + // if we're done accelerating, act as a normal rotation + if (ent->moveinfo.speed >= ent->speed) + { + // set nextthink to trigger a think when dest is reached + ent->nextthink = level.time + (FRAME_TIME_S * frames); + ent->think = AngleMove_Final; + } + else + { + ent->nextthink = level.time + FRAME_TIME_S; + ent->think = AngleMove_Begin; + } + // PGM +} + +void AngleMove_Calc(edict_t *ent, void(*endfunc)(edict_t *self)) +{ + ent->avelocity = {}; + ent->moveinfo.endfunc = endfunc; + + // PGM + // if we're supposed to accelerate, this will tell anglemove_begin to do so + if (ent->accel != ent->speed) + ent->moveinfo.speed = 0; + // PGM + + if (level.current_entity == ((ent->flags & FL_TEAMSLAVE) ? ent->teammaster : ent)) + { + AngleMove_Begin(ent); + } + else + { + ent->nextthink = level.time + FRAME_TIME_S; + ent->think = AngleMove_Begin; + } +} + +/* +============== +Think_AccelMove + +The team has completed a frame of movement, so +change the speed for the next frame +============== +*/ +constexpr float AccelerationDistance(float target, float rate) +{ + return (target * ((target / rate) + 1) / 2); +} + +void plat_CalcAcceleratedMove(moveinfo_t *moveinfo) +{ + float accel_dist; + float decel_dist; + + if (moveinfo->remaining_distance < moveinfo->accel) + { + moveinfo->move_speed = moveinfo->speed; + moveinfo->current_speed = moveinfo->remaining_distance; + return; + } + + accel_dist = AccelerationDistance(moveinfo->speed, moveinfo->accel); + decel_dist = AccelerationDistance(moveinfo->speed, moveinfo->decel); + + if ((moveinfo->remaining_distance - accel_dist - decel_dist) < 0) + { + float f; + + f = (moveinfo->accel + moveinfo->decel) / (moveinfo->accel * moveinfo->decel); + moveinfo->move_speed = moveinfo->current_speed = + (-2 + sqrt(4 - 4 * f * (-2 * moveinfo->remaining_distance))) / (2 * f); + decel_dist = AccelerationDistance(moveinfo->move_speed, moveinfo->decel); + } + else + moveinfo->move_speed = moveinfo->speed; + + moveinfo->decel_distance = decel_dist; +}; + +void plat_Accelerate(moveinfo_t *moveinfo) +{ + // are we decelerating? + if (moveinfo->remaining_distance <= moveinfo->decel_distance) + { + if (moveinfo->remaining_distance < moveinfo->decel_distance) + { + if (moveinfo->next_speed) + { + moveinfo->current_speed = moveinfo->next_speed; + moveinfo->next_speed = 0; + return; + } + if (moveinfo->current_speed > moveinfo->decel) + { + moveinfo->current_speed -= moveinfo->decel; + + // [Paril-KEX] fix platforms in xdm6, etc + if (fabsf(moveinfo->current_speed) < 0.01f) + moveinfo->current_speed = moveinfo->remaining_distance + 1; + } + } + return; + } + + // are we at full speed and need to start decelerating during this move? + if (moveinfo->current_speed == moveinfo->move_speed) + if ((moveinfo->remaining_distance - moveinfo->current_speed) < moveinfo->decel_distance) + { + float p1_distance; + float p2_distance; + float distance; + + p1_distance = moveinfo->remaining_distance - moveinfo->decel_distance; + p2_distance = moveinfo->move_speed * (1.0f - (p1_distance / moveinfo->move_speed)); + distance = p1_distance + p2_distance; + moveinfo->current_speed = moveinfo->move_speed; + moveinfo->next_speed = moveinfo->move_speed - moveinfo->decel * (p2_distance / distance); + return; + } + + // are we accelerating? + if (moveinfo->current_speed < moveinfo->speed) + { + float old_speed; + float p1_distance; + float p1_speed; + float p2_distance; + float distance; + + old_speed = moveinfo->current_speed; + + // figure simple acceleration up to move_speed + moveinfo->current_speed += moveinfo->accel; + if (moveinfo->current_speed > moveinfo->speed) + moveinfo->current_speed = moveinfo->speed; + + // are we accelerating throughout this entire move? + if ((moveinfo->remaining_distance - moveinfo->current_speed) >= moveinfo->decel_distance) + return; + + // during this move we will accelerate from current_speed to move_speed + // and cross over the decel_distance; figure the average speed for the + // entire move + p1_distance = moveinfo->remaining_distance - moveinfo->decel_distance; + p1_speed = (old_speed + moveinfo->move_speed) / 2.0f; + p2_distance = moveinfo->move_speed * (1.0f - (p1_distance / p1_speed)); + distance = p1_distance + p2_distance; + moveinfo->current_speed = + (p1_speed * (p1_distance / distance)) + (moveinfo->move_speed * (p2_distance / distance)); + moveinfo->next_speed = moveinfo->move_speed - moveinfo->decel * (p2_distance / distance); + return; + } + + // we are at constant velocity (move_speed) + return; +} + +THINK(Think_AccelMove) (edict_t *ent) -> void +{ + // [Paril-KEX] calculate distance dynamically + if (ent->moveinfo.state == STATE_UP) + ent->moveinfo.remaining_distance = (ent->moveinfo.start_origin - ent->s.origin).length(); + else + ent->moveinfo.remaining_distance = (ent->moveinfo.end_origin - ent->s.origin).length(); + + if (ent->moveinfo.current_speed == 0) // starting or blocked + plat_CalcAcceleratedMove(&ent->moveinfo); + + plat_Accelerate(&ent->moveinfo); + + // will the entire move complete on next frame? + if (ent->moveinfo.remaining_distance <= ent->moveinfo.current_speed) + { + Move_Final(ent); + return; + } + + ent->velocity = ent->moveinfo.dir * (ent->moveinfo.current_speed * 10); + ent->nextthink = level.time + 10_hz; + ent->think = Think_AccelMove; +} + +void plat_go_down(edict_t *ent); + +MOVEINFO_ENDFUNC(plat_hit_top) (edict_t *ent) -> void +{ + if (!(ent->flags & FL_TEAMSLAVE)) + { + if (ent->moveinfo.sound_end) + gi.sound(ent, CHAN_NO_PHS_ADD | CHAN_VOICE, ent->moveinfo.sound_end, 1, ATTN_STATIC, 0); + } + ent->s.sound = 0; + ent->moveinfo.state = STATE_TOP; + + ent->think = plat_go_down; + ent->nextthink = level.time + 3_sec; +} + +MOVEINFO_ENDFUNC(plat_hit_bottom) (edict_t *ent) -> void +{ + if (!(ent->flags & FL_TEAMSLAVE)) + { + if (ent->moveinfo.sound_end) + gi.sound(ent, CHAN_NO_PHS_ADD | CHAN_VOICE, ent->moveinfo.sound_end, 1, ATTN_STATIC, 0); + } + ent->s.sound = 0; + ent->moveinfo.state = STATE_BOTTOM; + + // ROGUE + plat2_kill_danger_area(ent); + // ROGUE +} + +THINK(plat_go_down) (edict_t *ent) -> void +{ + if (!(ent->flags & FL_TEAMSLAVE)) + { + if (ent->moveinfo.sound_start) + gi.sound(ent, CHAN_NO_PHS_ADD | CHAN_VOICE, ent->moveinfo.sound_start, 1, ATTN_STATIC, 0); + } + + ent->s.sound = ent->moveinfo.sound_middle; + + ent->moveinfo.state = STATE_DOWN; + Move_Calc(ent, ent->moveinfo.end_origin, plat_hit_bottom); +} + +void plat_go_up(edict_t *ent) +{ + if (!(ent->flags & FL_TEAMSLAVE)) + { + if (ent->moveinfo.sound_start) + gi.sound(ent, CHAN_NO_PHS_ADD | CHAN_VOICE, ent->moveinfo.sound_start, 1, ATTN_STATIC, 0); + } + + ent->s.sound = ent->moveinfo.sound_middle; + + ent->moveinfo.state = STATE_UP; + Move_Calc(ent, ent->moveinfo.start_origin, plat_hit_top); + + // ROGUE + plat2_spawn_danger_area(ent); + // ROGUE +} + +MOVEINFO_BLOCKED(plat_blocked) (edict_t *self, edict_t *other) -> void +{ + if (!(other->svflags & SVF_MONSTER) && (!other->client)) + { + // give it a chance to go away on it's own terms (like gibs) + T_Damage(other, self, self, vec3_origin, other->s.origin, vec3_origin, 100000, 1, DAMAGE_NONE, MOD_CRUSH); + // if it's still there, nuke it + if (other && other->inuse && other->solid) // PGM + BecomeExplosion1(other); + return; + } + + // PGM + // gib dead things + if (other->health < 1) + T_Damage(other, self, self, vec3_origin, other->s.origin, vec3_origin, 100, 1, DAMAGE_NONE, MOD_CRUSH); + // PGM + + T_Damage(other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, DAMAGE_NONE, MOD_CRUSH); + + // [Paril-KEX] killed the thing, so don't switch directions + if (!other->inuse || !other->solid) + return; + + if (self->moveinfo.state == STATE_UP) + plat_go_down(self); + else if (self->moveinfo.state == STATE_DOWN) + plat_go_up(self); +} + +constexpr spawnflags_t SPAWNFLAG_PLAT_LOW_TRIGGER = 1_spawnflag; +constexpr spawnflags_t SPAWNFLAG_PLAT_NO_MONSTER = 2_spawnflag; + +USE(Use_Plat) (edict_t *ent, edict_t *other, edict_t *activator) -> void +{ + //====== + // ROGUE + // if a monster is using us, then allow the activity when stopped. + if ((other->svflags & SVF_MONSTER) && !(ent->spawnflags & SPAWNFLAG_PLAT_NO_MONSTER)) + { + if (ent->moveinfo.state == STATE_TOP) + plat_go_down(ent); + else if (ent->moveinfo.state == STATE_BOTTOM) + plat_go_up(ent); + + return; + } + // ROGUE + //====== + + if (ent->think) + return; // already down + plat_go_down(ent); +} + +TOUCH(Touch_Plat_Center) (edict_t *ent, edict_t *other, const trace_t &tr, bool other_touching_self) -> void +{ + if (!other->client) + return; + + if (other->health <= 0) + return; + + ent = ent->enemy; // now point at the plat, not the trigger + if (ent->moveinfo.state == STATE_BOTTOM) + plat_go_up(ent); + else if (ent->moveinfo.state == STATE_TOP) + ent->nextthink = level.time + 1_sec; // the player is still on the plat, so delay going down +} + +// PGM - plat2's change the trigger field +edict_t *plat_spawn_inside_trigger(edict_t *ent) +{ + edict_t *trigger; + vec3_t tmin, tmax; + + // + // middle trigger + // + trigger = G_Spawn(); + trigger->touch = Touch_Plat_Center; + trigger->movetype = MOVETYPE_NONE; + trigger->solid = SOLID_TRIGGER; + trigger->enemy = ent; + + tmin[0] = ent->mins[0] + 25; + tmin[1] = ent->mins[1] + 25; + tmin[2] = ent->mins[2]; + + tmax[0] = ent->maxs[0] - 25; + tmax[1] = ent->maxs[1] - 25; + tmax[2] = ent->maxs[2] + 8; + + tmin[2] = tmax[2] - (ent->pos1[2] - ent->pos2[2] + st.lip); + + if (ent->spawnflags.has(SPAWNFLAG_PLAT_LOW_TRIGGER)) + tmax[2] = tmin[2] + 8; + + if (tmax[0] - tmin[0] <= 0) + { + tmin[0] = (ent->mins[0] + ent->maxs[0]) * 0.5f; + tmax[0] = tmin[0] + 1; + } + if (tmax[1] - tmin[1] <= 0) + { + tmin[1] = (ent->mins[1] + ent->maxs[1]) * 0.5f; + tmax[1] = tmin[1] + 1; + } + + trigger->mins = tmin; + trigger->maxs = tmax; + + gi.linkentity(trigger); + + return trigger; // PGM 11/17/97 +} + +/*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. + +"speed" overrides default 200. +"accel" overrides default 500 +"lip" overrides default 8 pixel lip + +If the "height" key is set, that will determine the amount the plat moves, instead of being implicitly determined by the model's height. + +Set "sounds" to one of the following: +1) base fast +2) chain slow +*/ +void SP_func_plat(edict_t *ent) +{ + ent->s.angles = {}; + ent->solid = SOLID_BSP; + ent->movetype = MOVETYPE_PUSH; + + gi.setmodel(ent, ent->model); + + ent->moveinfo.blocked = plat_blocked; + + if (!ent->speed) + ent->speed = 20; + else + ent->speed *= 0.1f; + + if (!ent->accel) + ent->accel = 5; + else + ent->accel *= 0.1f; + + if (!ent->decel) + ent->decel = 5; + else + ent->decel *= 0.1f; + + if (!ent->dmg) + ent->dmg = 2; + + if (!st.lip) + st.lip = 8; + + // pos1 is the top position, pos2 is the bottom + ent->pos1 = ent->s.origin; + ent->pos2 = ent->s.origin; + if (st.height) + ent->pos2[2] -= st.height; + else + ent->pos2[2] -= (ent->maxs[2] - ent->mins[2]) - st.lip; + + ent->use = Use_Plat; + + plat_spawn_inside_trigger(ent); // the "start moving" trigger + + if (ent->targetname) + { + ent->moveinfo.state = STATE_UP; + } + else + { + ent->s.origin = ent->pos2; + gi.linkentity(ent); + ent->moveinfo.state = STATE_BOTTOM; + } + + ent->moveinfo.speed = ent->speed; + ent->moveinfo.accel = ent->accel; + ent->moveinfo.decel = ent->decel; + ent->moveinfo.wait = ent->wait; + ent->moveinfo.start_origin = ent->pos1; + ent->moveinfo.start_angles = ent->s.angles; + ent->moveinfo.end_origin = ent->pos2; + ent->moveinfo.end_angles = ent->s.angles; + + G_SetMoveinfoSounds(ent, "plats/pt1_strt.wav", "plats/pt1_mid.wav", "plats/pt1_end.wav"); +} + +//==================================================================== + +// Paril: Rogue added a spawnflag in func_rotating that +// is a reserved editor flag. +constexpr spawnflags_t SPAWNFLAG_ROTATING_START_ON = 1_spawnflag; +constexpr spawnflags_t SPAWNFLAG_ROTATING_REVERSE = 2_spawnflag; +constexpr spawnflags_t SPAWNFLAG_ROTATING_X_AXIS = 4_spawnflag; +constexpr spawnflags_t SPAWNFLAG_ROTATING_Y_AXIS = 8_spawnflag; +constexpr spawnflags_t SPAWNFLAG_ROTATING_TOUCH_PAIN = 16_spawnflag; +constexpr spawnflags_t SPAWNFLAG_ROTATING_STOP = 32_spawnflag; +constexpr spawnflags_t SPAWNFLAG_ROTATING_ANIMATED = 64_spawnflag; +constexpr spawnflags_t SPAWNFLAG_ROTATING_ANIMATED_FAST = 128_spawnflag; +constexpr spawnflags_t SPAWNFLAG_ROTATING_ACCEL = 0x00010000_spawnflag; + +/*QUAKED func_rotating (0 .5 .8) ? START_ON REVERSE X_AXIS Y_AXIS TOUCH_PAIN STOP ANIMATED ANIMATED_FAST NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP RESERVED1 COOP_ONLY RESERVED2 ACCEL +You need to have an origin brush as part of this entity. +The center of that brush will be the point around which it is rotated. It will rotate around the Z axis by default. +You can check either the X_AXIS or Y_AXIS box to change that. + +func_rotating will use it's targets when it stops and starts. + +"speed" determines how fast it moves; default value is 100. +"dmg" damage to inflict when blocked (2 default) +"accel" if specified, is how much the rotation speed will increase per .1sec. + +REVERSE will cause the it to rotate in the opposite direction. +STOP mean it will stop moving instead of pushing entities +ACCEL means it will accelerate to it's final speed and decelerate when shutting down. +*/ + +//============ +// PGM +THINK(rotating_accel) (edict_t *self) -> void +{ + float current_speed; + + current_speed = self->avelocity.length(); + if (current_speed >= (self->speed - self->accel)) // done + { + self->avelocity = self->movedir * self->speed; + G_UseTargets(self, self); + } + else + { + current_speed += self->accel; + self->avelocity = self->movedir * current_speed; + self->think = rotating_accel; + self->nextthink = level.time + FRAME_TIME_S; + } +} + +THINK(rotating_decel) (edict_t *self) -> void +{ + float current_speed; + + current_speed = self->avelocity.length(); + if (current_speed <= self->decel) // done + { + self->avelocity = {}; + G_UseTargets(self, self); + self->touch = nullptr; + } + else + { + current_speed -= self->decel; + self->avelocity = self->movedir * current_speed; + self->think = rotating_decel; + self->nextthink = level.time + FRAME_TIME_S; + } +} +// PGM +//============ + +MOVEINFO_BLOCKED(rotating_blocked) (edict_t *self, edict_t *other) -> void +{ + if (!self->dmg) + return; + if (level.time < self->touch_debounce_time) + return; + self->touch_debounce_time = level.time + 10_hz; + T_Damage(other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, DAMAGE_NONE, MOD_CRUSH); +} + +TOUCH(rotating_touch) (edict_t *self, edict_t *other, const trace_t &tr, bool other_touching_self) -> void +{ + if (self->avelocity[0] || self->avelocity[1] || self->avelocity[2]) + T_Damage(other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, DAMAGE_NONE, MOD_CRUSH); +} + +USE(rotating_use) (edict_t *self, edict_t *other, edict_t *activator) -> void +{ + if (self->avelocity) + { + self->s.sound = 0; + // PGM + if (self->spawnflags.has(SPAWNFLAG_ROTATING_ACCEL)) // Decelerate + rotating_decel(self); + else + { + self->avelocity = {}; + G_UseTargets(self, self); + self->touch = nullptr; + } + // PGM + } + else + { + self->s.sound = self->moveinfo.sound_middle; + // PGM + if (self->spawnflags.has(SPAWNFLAG_ROTATING_ACCEL)) // accelerate + rotating_accel(self); + else + { + self->avelocity = self->movedir * self->speed; + G_UseTargets(self, self); + } + if (self->spawnflags.has(SPAWNFLAG_ROTATING_TOUCH_PAIN)) + self->touch = rotating_touch; + // PGM + } +} + +void SP_func_rotating(edict_t *ent) +{ + ent->solid = SOLID_BSP; + if (ent->spawnflags.has(SPAWNFLAG_ROTATING_STOP)) + ent->movetype = MOVETYPE_STOP; + else + ent->movetype = MOVETYPE_PUSH; + + if (st.noise) + { + ent->moveinfo.sound_middle = gi.soundindex(st.noise); + + // [Paril-KEX] for rhangar1 doors + if (!st.was_key_specified("attenuation")) + ent->attenuation = ATTN_STATIC; + else + { + if (ent->attenuation == -1) + { + ent->s.loop_attenuation = ATTN_LOOP_NONE; + ent->attenuation = ATTN_NONE; + } + else + { + ent->s.loop_attenuation = ent->attenuation; + } + } + } + + // set the axis of rotation + ent->movedir = {}; + if (ent->spawnflags.has(SPAWNFLAG_ROTATING_X_AXIS)) + ent->movedir[2] = 1.0; + else if (ent->spawnflags.has(SPAWNFLAG_ROTATING_Y_AXIS)) + ent->movedir[0] = 1.0; + else // Z_AXIS + ent->movedir[1] = 1.0; + + // check for reverse rotation + if (ent->spawnflags.has(SPAWNFLAG_ROTATING_REVERSE)) + ent->movedir = -ent->movedir; + + if (!ent->speed) + ent->speed = 100; + if (!st.was_key_specified("dmg")) + ent->dmg = 2; + + ent->use = rotating_use; + if (ent->dmg) + ent->moveinfo.blocked = rotating_blocked; + + if (ent->spawnflags.has(SPAWNFLAG_ROTATING_START_ON)) + ent->use(ent, nullptr, nullptr); + + if (ent->spawnflags.has(SPAWNFLAG_ROTATING_ANIMATED)) + ent->s.effects |= EF_ANIM_ALL; + if (ent->spawnflags.has(SPAWNFLAG_ROTATING_ANIMATED_FAST)) + ent->s.effects |= EF_ANIM_ALLFAST; + + // PGM + if (ent->spawnflags.has(SPAWNFLAG_ROTATING_ACCEL)) // Accelerate / Decelerate + { + if (!ent->accel) + ent->accel = 1; + else if (ent->accel > ent->speed) + ent->accel = ent->speed; + + if (!ent->decel) + ent->decel = 1; + else if (ent->decel > ent->speed) + ent->decel = ent->speed; + } + // PGM + + gi.setmodel(ent, ent->model); + gi.linkentity(ent); +} + +THINK(func_spinning_think) (edict_t *ent) -> void +{ + if (ent->timestamp <= level.time) + { + ent->timestamp = level.time + random_time(1_sec, 6_sec); + ent->movedir = { ent->decel + frandom(ent->speed - ent->decel), ent->decel + frandom(ent->speed - ent->decel), ent->decel + frandom(ent->speed - ent->decel) }; + + for (int32_t i = 0; i < 3; i++) + { + if (brandom()) + ent->movedir[i] = -ent->movedir[i]; + } + } + + for (int32_t i = 0; i < 3; i++) + { + if (ent->avelocity[i] == ent->movedir[i]) + continue; + + if (ent->avelocity[i] < ent->movedir[i]) + ent->avelocity[i] = min(ent->movedir[i], ent->avelocity[i] + ent->accel); + else + ent->avelocity[i] = max(ent->movedir[i], ent->avelocity[i] - ent->accel); + } + + ent->nextthink = level.time + FRAME_TIME_MS; +} + +// [Paril-KEX] +void SP_func_spinning(edict_t *ent) +{ + ent->solid = SOLID_BSP; + + if (!ent->speed) + ent->speed = 100; + if (!ent->dmg) + ent->dmg = 2; + + ent->movetype = MOVETYPE_PUSH; + + ent->timestamp = 0_ms; + ent->nextthink = level.time + FRAME_TIME_MS; + ent->think = func_spinning_think; + + gi.setmodel(ent, ent->model); + gi.linkentity(ent); +} + +/* +====================================================================== + +BUTTONS + +====================================================================== +*/ + +/*QUAKED func_button (0 .5 .8) ? +When a button is touched, it moves some distance in the direction of it's angle, triggers all of it's targets, waits some time, then returns to it's original position where it can be triggered again. + +"angle" determines the opening direction +"target" all entities with a matching targetname will be used +"speed" override the default 40 speed +"wait" override the default 1 second wait (-1 = never return) +"lip" override the default 4 pixel lip remaining at end of move +"health" if set, the button must be killed instead of touched +"sounds" +1) silent +2) steam metal +3) wooden clunk +4) metallic click +5) in-out +*/ + +MOVEINFO_ENDFUNC(button_done) (edict_t *self) -> void +{ + self->moveinfo.state = STATE_BOTTOM; + if (!self->bmodel_anim.enabled) + { + if (level.is_n64) + self->s.frame = 0; + else + self->s.effects &= ~EF_ANIM23; + self->s.effects |= EF_ANIM01; + } + else + self->bmodel_anim.alternate = false; +} + +THINK(button_return) (edict_t *self) -> void +{ + self->moveinfo.state = STATE_DOWN; + + Move_Calc(self, self->moveinfo.start_origin, button_done); + + if (self->health) + self->takedamage = true; +} + +MOVEINFO_ENDFUNC(button_wait) (edict_t *self) -> void +{ + self->moveinfo.state = STATE_TOP; + + if (!self->bmodel_anim.enabled) + { + self->s.effects &= ~EF_ANIM01; + if (level.is_n64) + self->s.frame = 2; + else + self->s.effects |= EF_ANIM23; + } + else + self->bmodel_anim.alternate = true; + + G_UseTargets(self, self->activator); + + if (self->moveinfo.wait >= 0) + { + self->nextthink = level.time + gtime_t::from_sec(self->moveinfo.wait); + self->think = button_return; + } +} + +void button_fire(edict_t *self) +{ + if (self->moveinfo.state == STATE_UP || self->moveinfo.state == STATE_TOP) + return; + + self->moveinfo.state = STATE_UP; + if (self->moveinfo.sound_start && !(self->flags & FL_TEAMSLAVE)) + gi.sound(self, CHAN_NO_PHS_ADD | CHAN_VOICE, self->moveinfo.sound_start, 1, ATTN_STATIC, 0); + Move_Calc(self, self->moveinfo.end_origin, button_wait); +} + +USE(button_use) (edict_t *self, edict_t *other, edict_t *activator) -> void +{ + self->activator = activator; + button_fire(self); +} + +TOUCH(button_touch) (edict_t *self, edict_t *other, const trace_t &tr, bool other_touching_self) -> void +{ + if (!other->client) + return; + + if (other->health <= 0) + return; + + self->activator = other; + button_fire(self); +} + +DIE(button_killed) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void +{ + self->activator = attacker; + self->health = self->max_health; + self->takedamage = false; + button_fire(self); +} + +void SP_func_button(edict_t *ent) +{ + vec3_t abs_movedir; + float dist; + + G_SetMovedir(ent->s.angles, ent->movedir); + ent->movetype = MOVETYPE_STOP; + ent->solid = SOLID_BSP; + gi.setmodel(ent, ent->model); + + if (ent->sounds != 1) + G_SetMoveinfoSounds(ent, "switches/butn2.wav", nullptr, nullptr); + else + G_SetMoveinfoSounds(ent, nullptr, nullptr, nullptr); + + if (!ent->speed) + ent->speed = 40; + if (!ent->accel) + ent->accel = ent->speed; + if (!ent->decel) + ent->decel = ent->speed; + + if (!ent->wait) + ent->wait = 3; + if (!st.lip) + st.lip = 4; + + ent->pos1 = ent->s.origin; + abs_movedir[0] = fabsf(ent->movedir[0]); + abs_movedir[1] = fabsf(ent->movedir[1]); + abs_movedir[2] = fabsf(ent->movedir[2]); + dist = abs_movedir[0] * ent->size[0] + abs_movedir[1] * ent->size[1] + abs_movedir[2] * ent->size[2] - st.lip; + ent->pos2 = ent->pos1 + (ent->movedir * dist); + + ent->use = button_use; + + if (!ent->bmodel_anim.enabled) + ent->s.effects |= EF_ANIM01; + + if (ent->health) + { + ent->max_health = ent->health; + ent->die = button_killed; + ent->takedamage = true; + } + else if (!ent->targetname) + ent->touch = button_touch; + + ent->moveinfo.state = STATE_BOTTOM; + + ent->moveinfo.speed = ent->speed; + ent->moveinfo.accel = ent->accel; + ent->moveinfo.decel = ent->decel; + ent->moveinfo.wait = ent->wait; + ent->moveinfo.start_origin = ent->pos1; + ent->moveinfo.start_angles = ent->s.angles; + ent->moveinfo.end_origin = ent->pos2; + ent->moveinfo.end_angles = ent->s.angles; + + gi.linkentity(ent); +} + +/* +====================================================================== + +DOORS + + spawn a trigger surrounding the entire team unless it is + already targeted by another + +====================================================================== +*/ + +/*QUAKED func_door (0 .5 .8) ? START_OPEN x CRUSHER NOMONSTER ANIMATED TOGGLE ANIMATED_FAST +TOGGLE wait in both the start and end states for a trigger event. +START_OPEN the door to moves to its destination when spawned, and operate in reverse. It is used to temporarily or permanently close off an area when triggered (not useful for touch or takedamage doors). +NOMONSTER monsters will not trigger this door + +"message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet +"angle" determines the opening direction +"targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door. +"health" if set, door must be shot open +"speed" movement speed (100 default) +"wait" wait before returning (3 default, -1 = never return) +"lip" lip remaining at end of move (8 default) +"dmg" damage to inflict when blocked (2 default) +"sounds" +1) silent +2) light +3) medium +4) heavy +*/ + +void door_use_areaportals(edict_t *self, bool open) +{ + edict_t *t = nullptr; + + if (!self->target) + return; + + while ((t = G_FindByString<&edict_t::targetname>(t, self->target))) + { + if (Q_strcasecmp(t->classname, "func_areaportal") == 0) + { + gi.SetAreaPortalState(t->style, open); + } + } +} + +void door_go_down(edict_t *self); + +static void door_play_sound(edict_t *self, int32_t sound) +{ + if (!self->teammaster) + { + gi.sound(self, CHAN_NO_PHS_ADD | CHAN_VOICE, sound, 1, self->attenuation, 0); + return; + } + + vec3_t p = {}; + int32_t c = 0; + + for (edict_t *t = self->teammaster; t; t = t->teamchain) + { + p += (t->absmin + t->absmax) * 0.5f; + c++; + } + + if (c == 1) + { + gi.sound(self, CHAN_NO_PHS_ADD | CHAN_VOICE, sound, 1, self->attenuation, 0); + return; + } + + p /= c; + + if (gi.pointcontents(p) & CONTENTS_SOLID) + { + gi.sound(self, CHAN_NO_PHS_ADD | CHAN_VOICE, sound, 1, self->attenuation, 0); + return; + } + + gi.positioned_sound(p, self, CHAN_NO_PHS_ADD | CHAN_VOICE, sound, 1, self->attenuation, 0); +} + +MOVEINFO_ENDFUNC(door_hit_top) (edict_t *self) -> void +{ + if (!(self->flags & FL_TEAMSLAVE)) + { + if (self->moveinfo.sound_end) + door_play_sound(self, self->moveinfo.sound_end); + } + self->s.sound = 0; + self->moveinfo.state = STATE_TOP; + if (self->spawnflags.has(SPAWNFLAG_DOOR_TOGGLE)) + return; + if (self->moveinfo.wait >= 0) + { + self->think = door_go_down; + self->nextthink = level.time + gtime_t::from_sec(self->moveinfo.wait); + } + + if (self->spawnflags.has(SPAWNFLAG_DOOR_START_OPEN)) + door_use_areaportals(self, false); +} + +MOVEINFO_ENDFUNC(door_hit_bottom) (edict_t *self) -> void +{ + if (!(self->flags & FL_TEAMSLAVE)) + { + if (self->moveinfo.sound_end) + door_play_sound(self, self->moveinfo.sound_end); + } + self->s.sound = 0; + self->moveinfo.state = STATE_BOTTOM; + + if (!self->spawnflags.has(SPAWNFLAG_DOOR_START_OPEN)) + door_use_areaportals(self, false); +} + +THINK(door_go_down) (edict_t *self) -> void +{ + if (!(self->flags & FL_TEAMSLAVE)) + { + if (self->moveinfo.sound_start) + door_play_sound(self, self->moveinfo.sound_start); + } + + self->s.sound = self->moveinfo.sound_middle; + + if (self->max_health) + { + self->takedamage = true; + self->health = self->max_health; + } + + self->moveinfo.state = STATE_DOWN; + if (strcmp(self->classname, "func_door") == 0 || + strcmp(self->classname, "func_water") == 0 || + strcmp(self->classname, "func_door_secret") == 0) + Move_Calc(self, self->moveinfo.start_origin, door_hit_bottom); + else if (strcmp(self->classname, "func_door_rotating") == 0) + AngleMove_Calc(self, door_hit_bottom); + + if (self->spawnflags.has(SPAWNFLAG_DOOR_START_OPEN)) + door_use_areaportals(self, true); +} + +void door_go_up(edict_t *self, edict_t *activator) +{ + if (self->moveinfo.state == STATE_UP) + return; // already going up + + if (self->moveinfo.state == STATE_TOP) + { // reset top wait time + if (self->moveinfo.wait >= 0) + self->nextthink = level.time + gtime_t::from_sec(self->moveinfo.wait); + return; + } + + if (!(self->flags & FL_TEAMSLAVE)) + { + if (self->moveinfo.sound_start) + door_play_sound(self, self->moveinfo.sound_start); + } + + self->s.sound = self->moveinfo.sound_middle; + + self->moveinfo.state = STATE_UP; + if (strcmp(self->classname, "func_door") == 0 || + strcmp(self->classname, "func_water") == 0 || + strcmp(self->classname, "func_door_secret") == 0) + Move_Calc(self, self->moveinfo.end_origin, door_hit_top); + else if (strcmp(self->classname, "func_door_rotating") == 0) + AngleMove_Calc(self, door_hit_top); + + G_UseTargets(self, activator); + + if (!(self->spawnflags & SPAWNFLAG_DOOR_START_OPEN)) + door_use_areaportals(self, true); +} + +//====== +// PGM + +THINK(smart_water_go_up) (edict_t *self) -> void +{ + float distance; + edict_t *lowestPlayer; + edict_t *ent; + float lowestPlayerPt; + + if (self->moveinfo.state == STATE_TOP) + { // reset top wait time + if (self->moveinfo.wait >= 0) + self->nextthink = level.time + gtime_t::from_sec(self->moveinfo.wait); + return; + } + + if (self->health) + { + if (self->absmax[2] >= self->health) + { + self->velocity = {}; + self->nextthink = 0_ms; + self->moveinfo.state = STATE_TOP; + return; + } + } + + if (!(self->flags & FL_TEAMSLAVE)) + { + if (self->moveinfo.sound_start) + gi.sound(self, CHAN_NO_PHS_ADD | CHAN_VOICE, self->moveinfo.sound_start, 1, ATTN_STATIC, 0); + } + + self->s.sound = self->moveinfo.sound_middle; + + // find the lowest player point. + lowestPlayerPt = 999999; + lowestPlayer = nullptr; + for (uint32_t i = 0; i < game.maxclients; i++) + { + ent = &g_edicts[1 + i]; + + // don't count dead or unused player slots + if ((ent->inuse) && (ent->health > 0)) + { + if (ent->absmin[2] < lowestPlayerPt) + { + lowestPlayerPt = ent->absmin[2]; + lowestPlayer = ent; + } + } + } + + if (!lowestPlayer) + { + return; + } + + distance = lowestPlayerPt - self->absmax[2]; + + // for the calculations, make sure we intend to go up at least a little. + if (distance < self->accel) + { + distance = 100; + self->moveinfo.speed = 5; + } + else + self->moveinfo.speed = distance / self->accel; + + if (self->moveinfo.speed < 5) + self->moveinfo.speed = 5; + else if (self->moveinfo.speed > self->speed) + self->moveinfo.speed = self->speed; + + // FIXME - should this allow any movement other than straight up? + self->moveinfo.dir = { 0, 0, 1 }; + self->velocity = self->moveinfo.dir * self->moveinfo.speed; + self->moveinfo.remaining_distance = distance; + + if (self->moveinfo.state != STATE_UP) + { + G_UseTargets(self, lowestPlayer); + door_use_areaportals(self, true); + self->moveinfo.state = STATE_UP; + } + + self->think = smart_water_go_up; + self->nextthink = level.time + FRAME_TIME_S; +} +// PGM +//====== + +USE(door_use) (edict_t *self, edict_t *other, edict_t *activator) -> void +{ + edict_t *ent; + vec3_t center; // PGM + + if (self->flags & FL_TEAMSLAVE) + return; + + if ((strcmp(self->classname, "func_door_rotating") == 0) && self->spawnflags.has(SPAWNFLAG_DOOR_ROTATING_SAFE_OPEN) && + (self->moveinfo.state == STATE_BOTTOM || self->moveinfo.state == STATE_DOWN)) + { + if (self->moveinfo.dir) + { + vec3_t forward = (activator->s.origin - self->s.origin).normalized(); + self->moveinfo.reversing = forward.dot(self->moveinfo.dir) > 0; + } + } + + if (self->spawnflags.has(SPAWNFLAG_DOOR_TOGGLE)) + { + if (self->moveinfo.state == STATE_UP || self->moveinfo.state == STATE_TOP) + { + // trigger all paired doors + for (ent = self; ent; ent = ent->teamchain) + { + ent->message = nullptr; + ent->touch = nullptr; + door_go_down(ent); + } + return; + } + } + + // PGM + // smart water is different + center = self->mins + self->maxs; + center *= 0.5f; + if ((strcmp(self->classname, "func_water") == 0) && (gi.pointcontents(center) & MASK_WATER) && self->spawnflags.has(SPAWNFLAG_WATER_SMART)) + { + self->message = nullptr; + self->touch = nullptr; + self->enemy = activator; + smart_water_go_up(self); + return; + } + // PGM + + // trigger all paired doors + for (ent = self; ent; ent = ent->teamchain) + { + ent->message = nullptr; + ent->touch = nullptr; + door_go_up(ent, activator); + } +}; + +TOUCH(Touch_DoorTrigger) (edict_t *self, edict_t *other, const trace_t &tr, bool other_touching_self) -> void +{ + if (other->health <= 0) + return; + + if (!(other->svflags & SVF_MONSTER) && (!other->client)) + return; + + if (self->owner->spawnflags.has(SPAWNFLAG_DOOR_NOMONSTER) && (other->svflags & SVF_MONSTER)) + return; + + if (level.time < self->touch_debounce_time) + return; + self->touch_debounce_time = level.time + 1_sec; + + door_use(self->owner, other, other); +} + +THINK(Think_CalcMoveSpeed) (edict_t *self) -> void +{ + edict_t *ent; + float min; + float time; + float newspeed; + float ratio; + float dist; + + if (self->flags & FL_TEAMSLAVE) + return; // only the team master does this + + // find the smallest distance any member of the team will be moving + min = fabsf(self->moveinfo.distance); + for (ent = self->teamchain; ent; ent = ent->teamchain) + { + dist = fabsf(ent->moveinfo.distance); + if (dist < min) + min = dist; + } + + time = min / self->moveinfo.speed; + + // adjust speeds so they will all complete at the same time + for (ent = self; ent; ent = ent->teamchain) + { + newspeed = fabsf(ent->moveinfo.distance) / time; + ratio = newspeed / ent->moveinfo.speed; + if (ent->moveinfo.accel == ent->moveinfo.speed) + ent->moveinfo.accel = newspeed; + else + ent->moveinfo.accel *= ratio; + if (ent->moveinfo.decel == ent->moveinfo.speed) + ent->moveinfo.decel = newspeed; + else + ent->moveinfo.decel *= ratio; + ent->moveinfo.speed = newspeed; + } +} + +THINK(Think_SpawnDoorTrigger) (edict_t *ent) -> void +{ + edict_t *other; + vec3_t mins, maxs; + + if (ent->flags & FL_TEAMSLAVE) + return; // only the team leader spawns a trigger + + mins = ent->absmin; + maxs = ent->absmax; + + for (other = ent->teamchain; other; other = other->teamchain) + { + AddPointToBounds(other->absmin, mins, maxs); + AddPointToBounds(other->absmax, mins, maxs); + } + + // expand + mins[0] -= 60; + mins[1] -= 60; + maxs[0] += 60; + maxs[1] += 60; + + other = G_Spawn(); + other->mins = mins; + other->maxs = maxs; + other->owner = ent; + other->solid = SOLID_TRIGGER; + other->movetype = MOVETYPE_NONE; + other->touch = Touch_DoorTrigger; + gi.linkentity(other); + + Think_CalcMoveSpeed(ent); +} + +MOVEINFO_BLOCKED(door_blocked) (edict_t *self, edict_t *other) -> void +{ + edict_t *ent; + + if (!(other->svflags & SVF_MONSTER) && (!other->client)) + { + // give it a chance to go away on it's own terms (like gibs) + T_Damage(other, self, self, vec3_origin, other->s.origin, vec3_origin, 100000, 1, DAMAGE_NONE, MOD_CRUSH); + // if it's still there, nuke it + if (other && other->inuse) + BecomeExplosion1(other); + return; + } + + if (self->dmg && !(level.time < self->touch_debounce_time)) + { + self->touch_debounce_time = level.time + 10_hz; + T_Damage(other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, DAMAGE_NONE, MOD_CRUSH); + } + + // [Paril-KEX] don't allow wait -1 doors to return + if (self->spawnflags.has(SPAWNFLAG_DOOR_CRUSHER) || self->wait == -1) + return; + + // if a door has a negative wait, it would never come back if blocked, + // so let it just squash the object to death real fast + if (self->moveinfo.wait >= 0) + { + if (self->moveinfo.state == STATE_DOWN) + { + for (ent = self->teammaster; ent; ent = ent->teamchain) + door_go_up(ent, ent->activator); + } + else + { + for (ent = self->teammaster; ent; ent = ent->teamchain) + door_go_down(ent); + } + } +} + +DIE(door_killed) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void +{ + edict_t *ent; + + for (ent = self->teammaster; ent; ent = ent->teamchain) + { + ent->health = ent->max_health; + ent->takedamage = false; + } + door_use(self->teammaster, attacker, attacker); +} + +TOUCH(door_touch) (edict_t *self, edict_t *other, const trace_t &tr, bool other_touching_self) -> void +{ + if (!other->client) + return; + + if (level.time < self->touch_debounce_time) + return; + self->touch_debounce_time = level.time + 5_sec; + + gi.LocCenter_Print(other, "{}", self->message); + gi.sound(other, CHAN_AUTO, gi.soundindex("misc/talk1.wav"), 1, ATTN_NORM, 0); +} + +THINK(Think_DoorActivateAreaPortal) (edict_t *ent) -> void +{ + door_use_areaportals(ent, true); + + if (ent->health || ent->targetname) + Think_CalcMoveSpeed(ent); + else + Think_SpawnDoorTrigger(ent); +} + +void SP_func_door(edict_t *ent) +{ + vec3_t abs_movedir; + + if (ent->sounds != 1) + G_SetMoveinfoSounds(ent, "doors/dr1_strt.wav", "doors/dr1_mid.wav", "doors/dr1_end.wav"); + else + G_SetMoveinfoSounds(ent, nullptr, nullptr, nullptr); + + // [Paril-KEX] for rhangar1 doors + if (!st.was_key_specified("attenuation")) + ent->attenuation = ATTN_STATIC; + else + { + if (ent->attenuation == -1) + { + ent->s.loop_attenuation = ATTN_LOOP_NONE; + ent->attenuation = ATTN_NONE; + } + else + { + ent->s.loop_attenuation = ent->attenuation; + } + } + + G_SetMovedir(ent->s.angles, ent->movedir); + ent->movetype = MOVETYPE_PUSH; + ent->solid = SOLID_BSP; + ent->svflags |= SVF_DOOR; + gi.setmodel(ent, ent->model); + + ent->moveinfo.blocked = door_blocked; + ent->use = door_use; + + if (!ent->speed) + ent->speed = 100; + if (deathmatch->integer) + ent->speed *= 2; + + if (!ent->accel) + ent->accel = ent->speed; + if (!ent->decel) + ent->decel = ent->speed; + + if (!ent->wait) + ent->wait = 3; + if (!st.lip) + st.lip = 8; + if (!ent->dmg) + ent->dmg = 2; + + // calculate second position + ent->pos1 = ent->s.origin; + abs_movedir[0] = fabsf(ent->movedir[0]); + abs_movedir[1] = fabsf(ent->movedir[1]); + abs_movedir[2] = fabsf(ent->movedir[2]); + ent->moveinfo.distance = + abs_movedir[0] * ent->size[0] + abs_movedir[1] * ent->size[1] + abs_movedir[2] * ent->size[2] - st.lip; + ent->pos2 = ent->pos1 + (ent->movedir * ent->moveinfo.distance); + + // if it starts open, switch the positions + if (ent->spawnflags.has(SPAWNFLAG_DOOR_START_OPEN)) + { + ent->s.origin = ent->pos2; + ent->pos2 = ent->pos1; + ent->pos1 = ent->s.origin; + } + + ent->moveinfo.state = STATE_BOTTOM; + + if (ent->health) + { + ent->takedamage = true; + ent->die = door_killed; + ent->max_health = ent->health; + } + else if (ent->targetname) + { + if (ent->message) + { + gi.soundindex("misc/talk.wav"); + ent->touch = door_touch; + } + ent->flags |= FL_LOCKED; + } + + ent->moveinfo.speed = ent->speed; + ent->moveinfo.accel = ent->accel; + ent->moveinfo.decel = ent->decel; + ent->moveinfo.wait = ent->wait; + ent->moveinfo.start_origin = ent->pos1; + ent->moveinfo.start_angles = ent->s.angles; + ent->moveinfo.end_origin = ent->pos2; + ent->moveinfo.end_angles = ent->s.angles; + + if (ent->spawnflags.has(SPAWNFLAG_DOOR_ANIMATED)) + ent->s.effects |= EF_ANIM_ALL; + if (ent->spawnflags.has(SPAWNFLAG_DOOR_ANIMATED_FAST)) + ent->s.effects |= EF_ANIM_ALLFAST; + + // to simplify logic elsewhere, make non-teamed doors into a team of one + if (!ent->team) + ent->teammaster = ent; + + gi.linkentity(ent); + + ent->nextthink = level.time + FRAME_TIME_S; + + if (ent->spawnflags.has(SPAWNFLAG_DOOR_START_OPEN)) + ent->think = Think_DoorActivateAreaPortal; + else if (ent->health || ent->targetname) + ent->think = Think_CalcMoveSpeed; + else + ent->think = Think_SpawnDoorTrigger; +} + +// PGM +USE(Door_Activate) (edict_t *self, edict_t *other, edict_t *activator) -> void +{ + self->use = nullptr; + + if (self->health) + { + self->takedamage = true; + self->die = door_killed; + self->max_health = self->health; + } + + if (self->health) + self->think = Think_CalcMoveSpeed; + else + self->think = Think_SpawnDoorTrigger; + self->nextthink = level.time + FRAME_TIME_S; +} +// PGM + +/*QUAKED func_door_rotating (0 .5 .8) ? START_OPEN REVERSE CRUSHER NOMONSTER ANIMATED TOGGLE X_AXIS Y_AXIS NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP RESERVED1 COOP_ONLY RESERVED2 INACTIVE SAFE_OPEN +TOGGLE causes the door to wait in both the start and end states for a trigger event. + +START_OPEN the door to moves to its destination when spawned, and operate in reverse. It is used to temporarily or permanently close off an area when triggered (not useful for touch or takedamage doors). +NOMONSTER monsters will not trigger this door + +You need to have an origin brush as part of this entity. The center of that brush will be +the point around which it is rotated. It will rotate around the Z axis by default. You can +check either the X_AXIS or Y_AXIS box to change that. + +"distance" is how many degrees the door will be rotated. +"speed" determines how fast the door moves; default value is 100. +"accel" if specified,is how much the rotation speed will increase each .1 sec. (default: no accel) + +REVERSE will cause the door to rotate in the opposite direction. +INACTIVE will cause the door to be inactive until triggered. +SAFE_OPEN will cause the door to open in reverse if you are on the `angles` side of the door. + +"message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet +"angle" determines the opening direction +"targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door. +"health" if set, door must be shot open +"speed" movement speed (100 default) +"wait" wait before returning (3 default, -1 = never return) +"dmg" damage to inflict when blocked (2 default) +"sounds" +1) silent +2) light +3) medium +4) heavy +*/ + +void SP_func_door_rotating(edict_t *ent) +{ + if (ent->spawnflags.has(SPAWNFLAG_DOOR_ROTATING_SAFE_OPEN)) + G_SetMovedir(ent->s.angles, ent->moveinfo.dir); + + ent->s.angles = {}; + + // set the axis of rotation + ent->movedir = {}; + if (ent->spawnflags.has(SPAWNFLAG_DOOR_ROTATING_X_AXIS)) + ent->movedir[2] = 1.0; + else if (ent->spawnflags.has(SPAWNFLAG_DOOR_ROTATING_Y_AXIS)) + ent->movedir[0] = 1.0; + else // Z_AXIS + ent->movedir[1] = 1.0; + + // check for reverse rotation + if (ent->spawnflags.has(SPAWNFLAG_DOOR_REVERSE)) + ent->movedir = -ent->movedir; + + if (!st.distance) + { + gi.Com_PrintFmt("{}: no distance set\n", *ent); + st.distance = 90; + } + + ent->pos1 = ent->s.angles; + ent->pos2 = ent->s.angles + (ent->movedir * st.distance); + ent->pos3 = ent->s.angles + (ent->movedir * -st.distance); + ent->moveinfo.distance = (float) st.distance; + + ent->movetype = MOVETYPE_PUSH; + ent->solid = SOLID_BSP; + ent->svflags |= SVF_DOOR; + gi.setmodel(ent, ent->model); + + ent->moveinfo.blocked = door_blocked; + ent->use = door_use; + + if (!ent->speed) + ent->speed = 100; + if (!ent->accel) + ent->accel = ent->speed; + if (!ent->decel) + ent->decel = ent->speed; + + if (!ent->wait) + ent->wait = 3; + if (!ent->dmg) + ent->dmg = 2; + + if (ent->sounds != 1) + G_SetMoveinfoSounds(ent, "doors/dr1_strt.wav", "doors/dr1_mid.wav", "doors/dr1_end.wav"); + else + G_SetMoveinfoSounds(ent, nullptr, nullptr, nullptr); + + // [Paril-KEX] for rhangar1 doors + if (!st.was_key_specified("attenuation")) + ent->attenuation = ATTN_STATIC; + else + { + if (ent->attenuation == -1) + { + ent->s.loop_attenuation = ATTN_LOOP_NONE; + ent->attenuation = ATTN_NONE; + } + else + { + ent->s.loop_attenuation = ent->attenuation; + } + } + + // if it starts open, switch the positions + if (ent->spawnflags.has(SPAWNFLAG_DOOR_START_OPEN)) + { + if (ent->spawnflags.has(SPAWNFLAG_DOOR_ROTATING_SAFE_OPEN)) + { + ent->spawnflags &= ~SPAWNFLAG_DOOR_ROTATING_SAFE_OPEN; + gi.Com_PrintFmt("{}: SAFE_OPEN is not compatible with START_OPEN\n", *ent); + } + + ent->s.angles = ent->pos2; + ent->pos2 = ent->pos1; + ent->pos1 = ent->s.angles; + ent->movedir = -ent->movedir; + } + + if (ent->health) + { + ent->takedamage = true; + ent->die = door_killed; + ent->max_health = ent->health; + } + + if (ent->targetname && ent->message) + { + gi.soundindex("misc/talk.wav"); + ent->touch = door_touch; + } + + ent->moveinfo.state = STATE_BOTTOM; + ent->moveinfo.speed = ent->speed; + ent->moveinfo.accel = ent->accel; + ent->moveinfo.decel = ent->decel; + ent->moveinfo.wait = ent->wait; + ent->moveinfo.start_origin = ent->s.origin; + ent->moveinfo.start_angles = ent->pos1; + ent->moveinfo.end_origin = ent->s.origin; + ent->moveinfo.end_angles = ent->pos2; + ent->moveinfo.end_angles_reversed = ent->pos3; + + if (ent->spawnflags.has(SPAWNFLAG_DOOR_ANIMATED)) + ent->s.effects |= EF_ANIM_ALL; + + // to simplify logic elsewhere, make non-teamed doors into a team of one + if (!ent->team) + ent->teammaster = ent; + + gi.linkentity(ent); + + ent->nextthink = level.time + FRAME_TIME_S; + if (ent->health || ent->targetname) + ent->think = Think_CalcMoveSpeed; + else + ent->think = Think_SpawnDoorTrigger; + + // PGM + if (ent->spawnflags.has(SPAWNFLAG_DOOR_ROTATING_INACTIVE)) + { + ent->takedamage = false; + ent->die = nullptr; + ent->think = nullptr; + ent->nextthink = 0_ms; + ent->use = Door_Activate; + } + // PGM +} + +MOVEINFO_BLOCKED(smart_water_blocked) (edict_t *self, edict_t *other) -> void +{ + if (!(other->svflags & SVF_MONSTER) && (!other->client)) + { + // give it a chance to go away on it's own terms (like gibs) + T_Damage(other, self, self, vec3_origin, other->s.origin, vec3_origin, 100000, 1, DAMAGE_NONE, MOD_LAVA); + // if it's still there, nuke it + if (other && other->inuse && other->solid) // PGM + BecomeExplosion1(other); + return; + } + + T_Damage(other, self, self, vec3_origin, other->s.origin, vec3_origin, 100, 1, DAMAGE_NONE, MOD_LAVA); +} + +/*QUAKED func_water (0 .5 .8) ? START_OPEN SMART +func_water is a moveable water brush. It must be targeted to operate. Use a non-water texture at your own risk. + +START_OPEN causes the water to move to its destination when spawned and operate in reverse. + +SMART causes the water to adjust its speed depending on distance to player. +(speed = distance/accel, min 5, max self->speed) +"accel" for smart water, the divisor to determine water speed. default 20 (smaller = faster) + +"health" maximum height of this water brush +"angle" determines the opening direction (up or down only) +"speed" movement speed (25 default) +"wait" wait before returning (-1 default, -1 = TOGGLE) +"lip" lip remaining at end of move (0 default) +"sounds" (yes, these need to be changed) +0) no sound +1) water +2) lava +*/ + +void SP_func_water(edict_t *self) +{ + vec3_t abs_movedir; + + G_SetMovedir(self->s.angles, self->movedir); + self->movetype = MOVETYPE_PUSH; + self->solid = SOLID_BSP; + gi.setmodel(self, self->model); + + switch (self->sounds) + { + default: + G_SetMoveinfoSounds(self, nullptr, nullptr, nullptr); + break; + + case 1: // water + case 2: // lava + G_SetMoveinfoSounds(self, "world/mov_watr.wav", nullptr, "world/stp_watr.wav"); + break; + } + + self->attenuation = ATTN_STATIC; + + // calculate second position + self->pos1 = self->s.origin; + abs_movedir[0] = fabsf(self->movedir[0]); + abs_movedir[1] = fabsf(self->movedir[1]); + abs_movedir[2] = fabsf(self->movedir[2]); + self->moveinfo.distance = + abs_movedir[0] * self->size[0] + abs_movedir[1] * self->size[1] + abs_movedir[2] * self->size[2] - st.lip; + self->pos2 = self->pos1 + (self->movedir * self->moveinfo.distance); + + // if it starts open, switch the positions + if (self->spawnflags.has(SPAWNFLAG_DOOR_START_OPEN)) + { + self->s.origin = self->pos2; + self->pos2 = self->pos1; + self->pos1 = self->s.origin; + } + + self->moveinfo.start_origin = self->pos1; + self->moveinfo.start_angles = self->s.angles; + self->moveinfo.end_origin = self->pos2; + self->moveinfo.end_angles = self->s.angles; + + self->moveinfo.state = STATE_BOTTOM; + + if (!self->speed) + self->speed = 25; + self->moveinfo.accel = self->moveinfo.decel = self->moveinfo.speed = self->speed; + + // ROGUE + if (self->spawnflags.has(SPAWNFLAG_WATER_SMART)) // smart water + { + // this is actually the divisor of the lowest player's distance to determine speed. + // self->speed then becomes the cap of the speed. + if (!self->accel) + self->accel = 20; + self->moveinfo.blocked = smart_water_blocked; + } + // ROGUE + + if (!self->wait) + self->wait = -1; + self->moveinfo.wait = self->wait; + + self->use = door_use; + + if (self->wait == -1) + self->spawnflags |= SPAWNFLAG_DOOR_TOGGLE; + + gi.linkentity(self); +} + +constexpr spawnflags_t SPAWNFLAG_TRAIN_TOGGLE = 2_spawnflag; +constexpr spawnflags_t SPAWNFLAG_TRAIN_BLOCK_STOPS = 4_spawnflag; +constexpr spawnflags_t SPAWNFLAG_TRAIN_FIX_OFFSET = 16_spawnflag; +constexpr spawnflags_t SPAWNFLAG_TRAIN_USE_ORIGIN = 32_spawnflag; + +/*QUAKED func_train (0 .5 .8) ? START_ON TOGGLE BLOCK_STOPS MOVE_TEAMCHAIN FIX_OFFSET USE_ORIGIN +Trains are moving platforms that players can ride. +The targets origin specifies the min point of the train at each corner. +The train spawns at the first target it is pointing at. +If the train is the target of a button or trigger, it will not begin moving until activated. +speed default 100 +dmg default 2 +noise looping sound to play when the train is in motion + +To have other entities move with the train, set all the piece's team value to the same thing. They will move in unison. +*/ +void train_next(edict_t *self); + +MOVEINFO_BLOCKED(train_blocked) (edict_t *self, edict_t *other) -> void +{ + if (!(other->svflags & SVF_MONSTER) && (!other->client)) + { + // give it a chance to go away on it's own terms (like gibs) + T_Damage(other, self, self, vec3_origin, other->s.origin, vec3_origin, 100000, 1, DAMAGE_NONE, MOD_CRUSH); + // if it's still there, nuke it + if (other && other->inuse && other->solid) + BecomeExplosion1(other); + return; + } + + if (level.time < self->touch_debounce_time) + return; + + if (!self->dmg) + return; + self->touch_debounce_time = level.time + 500_ms; + T_Damage(other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, DAMAGE_NONE, MOD_CRUSH); +} + +MOVEINFO_ENDFUNC(train_wait) (edict_t *self) -> void +{ + if (self->target_ent->pathtarget) + { + const char *savetarget; + edict_t *ent; + + ent = self->target_ent; + savetarget = ent->target; + ent->target = ent->pathtarget; + G_UseTargets(ent, self->activator); + ent->target = savetarget; + + // make sure we didn't get killed by a killtarget + if (!self->inuse) + return; + } + + if (self->moveinfo.wait) + { + if (self->moveinfo.wait > 0) + { + self->nextthink = level.time + gtime_t::from_sec(self->moveinfo.wait); + self->think = train_next; + } + else if (self->spawnflags.has(SPAWNFLAG_TRAIN_TOGGLE)) // && wait < 0 + { + // PMM - clear target_ent, let train_next get called when we get used + // train_next (self); + self->target_ent = nullptr; + // pmm + self->spawnflags &= ~SPAWNFLAG_TRAIN_START_ON; + self->velocity = {}; + self->nextthink = 0_ms; + } + + if (!(self->flags & FL_TEAMSLAVE)) + { + if (self->moveinfo.sound_end) + gi.sound(self, CHAN_NO_PHS_ADD | CHAN_VOICE, self->moveinfo.sound_end, 1, ATTN_STATIC, 0); + } + self->s.sound = 0; + } + else + { + train_next(self); + } +} + +// PGM +MOVEINFO_ENDFUNC(train_piece_wait) (edict_t *self) -> void +{ +} +// PGM + +THINK(train_next) (edict_t *self) -> void +{ + edict_t *ent; + vec3_t dest; + bool first; + + first = true; +again: + if (!self->target) + { + self->s.sound = 0; + return; + } + + ent = G_PickTarget(self->target); + if (!ent) + { + gi.Com_PrintFmt("{}: train_next: bad target {}\n", *self, self->target); + return; + } + + self->target = ent->target; + + // check for a teleport path_corner + if (ent->spawnflags.has(SPAWNFLAG_PATH_CORNER_TELEPORT)) + { + if (!first) + { + gi.Com_PrintFmt("{}: connected teleport path_corners\n", *ent); + return; + } + first = false; + + if (self->spawnflags.has(SPAWNFLAG_TRAIN_USE_ORIGIN)) + self->s.origin = ent->s.origin; + else + { + self->s.origin = ent->s.origin - self->mins; + + if (self->spawnflags.has(SPAWNFLAG_TRAIN_FIX_OFFSET)) + self->s.origin -= vec3_t{1.f, 1.f, 1.f}; + } + + self->s.old_origin = self->s.origin; + self->s.event = EV_OTHER_TELEPORT; + gi.linkentity(self); + goto again; + } + + // PGM + if (ent->speed) + { + self->speed = ent->speed; + self->moveinfo.speed = ent->speed; + if (ent->accel) + self->moveinfo.accel = ent->accel; + else + self->moveinfo.accel = ent->speed; + if (ent->decel) + self->moveinfo.decel = ent->decel; + else + self->moveinfo.decel = ent->speed; + self->moveinfo.current_speed = 0; + } + // PGM + + self->moveinfo.wait = ent->wait; + self->target_ent = ent; + + if (!(self->flags & FL_TEAMSLAVE)) + { + if (self->moveinfo.sound_start) + gi.sound(self, CHAN_NO_PHS_ADD | CHAN_VOICE, self->moveinfo.sound_start, 1, ATTN_STATIC, 0); + } + + self->s.sound = self->moveinfo.sound_middle; + + if (self->spawnflags.has(SPAWNFLAG_TRAIN_USE_ORIGIN)) + dest = ent->s.origin; + else + { + dest = ent->s.origin - self->mins; + + if (self->spawnflags.has(SPAWNFLAG_TRAIN_FIX_OFFSET)) + dest -= vec3_t{1.f, 1.f, 1.f}; + } + + self->moveinfo.state = STATE_TOP; + self->moveinfo.start_origin = self->s.origin; + self->moveinfo.end_origin = dest; + Move_Calc(self, dest, train_wait); + self->spawnflags |= SPAWNFLAG_TRAIN_START_ON; + + // PGM + if (self->spawnflags.has(SPAWNFLAG_TRAIN_MOVE_TEAMCHAIN)) + { + edict_t *e; + vec3_t dir, dst; + + dir = dest - self->s.origin; + for (e = self->teamchain; e; e = e->teamchain) + { + dst = dir + e->s.origin; + e->moveinfo.start_origin = e->s.origin; + e->moveinfo.end_origin = dst; + + e->moveinfo.state = STATE_TOP; + e->speed = self->speed; + e->moveinfo.speed = self->moveinfo.speed; + e->moveinfo.accel = self->moveinfo.accel; + e->moveinfo.decel = self->moveinfo.decel; + e->movetype = MOVETYPE_PUSH; + Move_Calc(e, dst, train_piece_wait); + } + } + // PGM +} + +void train_resume(edict_t *self) +{ + edict_t *ent; + vec3_t dest; + + ent = self->target_ent; + + if (self->spawnflags.has(SPAWNFLAG_TRAIN_USE_ORIGIN)) + dest = ent->s.origin; + else + { + dest = ent->s.origin - self->mins; + + if (self->spawnflags.has(SPAWNFLAG_TRAIN_FIX_OFFSET)) + dest -= vec3_t{1.f, 1.f, 1.f}; + } + + self->moveinfo.state = STATE_TOP; + self->moveinfo.start_origin = self->s.origin; + self->moveinfo.end_origin = dest; + Move_Calc(self, dest, train_wait); + self->spawnflags |= SPAWNFLAG_TRAIN_START_ON; +} + +THINK(func_train_find) (edict_t *self) -> void +{ + edict_t *ent; + + if (!self->target) + { + gi.Com_PrintFmt("{}: train_find: no target\n", *self); + return; + } + ent = G_PickTarget(self->target); + if (!ent) + { + gi.Com_PrintFmt("{}: train_find: target {} not found\n", *self, self->target); + return; + } + self->target = ent->target; + + if (self->spawnflags.has(SPAWNFLAG_TRAIN_USE_ORIGIN)) + self->s.origin = ent->s.origin; + else + { + self->s.origin = ent->s.origin - self->mins; + + if (self->spawnflags.has(SPAWNFLAG_TRAIN_FIX_OFFSET)) + self->s.origin -= vec3_t{1.f, 1.f, 1.f}; + } + + gi.linkentity(self); + + // if not triggered, start immediately + if (!self->targetname) + self->spawnflags |= SPAWNFLAG_TRAIN_START_ON; + + if (self->spawnflags.has(SPAWNFLAG_TRAIN_START_ON)) + { + self->nextthink = level.time + FRAME_TIME_S; + self->think = train_next; + self->activator = self; + } +} + +USE(train_use) (edict_t *self, edict_t *other, edict_t *activator) -> void +{ + self->activator = activator; + + if (self->spawnflags.has(SPAWNFLAG_TRAIN_START_ON)) + { + if (!self->spawnflags.has(SPAWNFLAG_TRAIN_TOGGLE)) + return; + self->spawnflags &= ~SPAWNFLAG_TRAIN_START_ON; + self->velocity = {}; + self->nextthink = 0_ms; + } + else + { + if (self->target_ent) + train_resume(self); + else + train_next(self); + } +} + +void SP_func_train(edict_t *self) +{ + self->movetype = MOVETYPE_PUSH; + + self->s.angles = {}; + self->moveinfo.blocked = train_blocked; + if (self->spawnflags.has(SPAWNFLAG_TRAIN_BLOCK_STOPS)) + self->dmg = 0; + else + { + if (!self->dmg) + self->dmg = 100; + } + self->solid = SOLID_BSP; + gi.setmodel(self, self->model); + + if (st.noise) + { + self->moveinfo.sound_middle = gi.soundindex(st.noise); + + // [Paril-KEX] for rhangar1 doors + if (!st.was_key_specified("attenuation")) + self->attenuation = ATTN_STATIC; + else + { + if (self->attenuation == -1) + { + self->s.loop_attenuation = ATTN_LOOP_NONE; + self->attenuation = ATTN_NONE; + } + else + { + self->s.loop_attenuation = self->attenuation; + } + } + } + + if (!self->speed) + self->speed = 100; + + self->moveinfo.speed = self->speed; + self->moveinfo.accel = self->moveinfo.decel = self->moveinfo.speed; + + self->use = train_use; + + gi.linkentity(self); + + if (self->target) + { + // start trains on the second frame, to make sure their targets have had + // a chance to spawn + self->nextthink = level.time + FRAME_TIME_S; + self->think = func_train_find; + } + else + { + gi.Com_PrintFmt("{}: no target\n", *self); + } +} + +/*QUAKED trigger_elevator (0.3 0.1 0.6) (-8 -8 -8) (8 8 8) + */ +USE(trigger_elevator_use) (edict_t *self, edict_t *other, edict_t *activator) -> void +{ + edict_t *target; + + if (self->movetarget->nextthink) + return; + + if (!other->pathtarget) + { + gi.Com_PrintFmt("{}: elevator used with no pathtarget\n", *self); + return; + } + + target = G_PickTarget(other->pathtarget); + if (!target) + { + gi.Com_PrintFmt("{}: elevator used with bad pathtarget: {}\n", *self, other->pathtarget); + return; + } + + self->movetarget->target_ent = target; + train_resume(self->movetarget); +} + +THINK(trigger_elevator_init) (edict_t *self) -> void +{ + if (!self->target) + { + gi.Com_PrintFmt("{}: has no target\n", *self); + return; + } + self->movetarget = G_PickTarget(self->target); + if (!self->movetarget) + { + gi.Com_PrintFmt("{}: unable to find target {}\n", *self, self->target); + return; + } + if (strcmp(self->movetarget->classname, "func_train") != 0) + { + gi.Com_PrintFmt("{}: target {} is not a train\n", *self, self->target); + return; + } + + self->use = trigger_elevator_use; + self->svflags = SVF_NOCLIENT; +} + +void SP_trigger_elevator(edict_t *self) +{ + self->think = trigger_elevator_init; + self->nextthink = level.time + FRAME_TIME_S; +} + +/*QUAKED func_timer (0.3 0.1 0.6) (-8 -8 -8) (8 8 8) START_ON +"wait" base time between triggering all targets, default is 1 +"random" wait variance, default is 0 + +so, the basic time between firing is a random time between +(wait - random) and (wait + random) + +"delay" delay before first firing when turned on, default is 0 + +"pausetime" additional delay used only the very first time + and only if spawned with START_ON + +These can used but not touched. +*/ + +constexpr spawnflags_t SPAWNFLAG_TIMER_START_ON = 1_spawnflag; + +THINK(func_timer_think) (edict_t *self) -> void +{ + G_UseTargets(self, self->activator); + self->nextthink = level.time + gtime_t::from_sec(self->wait + crandom() * self->random); +} + +USE(func_timer_use) (edict_t *self, edict_t *other, edict_t *activator) -> void +{ + self->activator = activator; + + // if on, turn it off + if (self->nextthink) + { + self->nextthink = 0_ms; + return; + } + + // turn it on + if (self->delay) + self->nextthink = level.time + gtime_t::from_sec(self->delay); + else + func_timer_think(self); +} + +void SP_func_timer(edict_t *self) +{ + if (!self->wait) + self->wait = 1.0; + + self->use = func_timer_use; + self->think = func_timer_think; + + if (self->random >= self->wait) + { + self->random = self->wait - gi.frame_time_s; + gi.Com_PrintFmt("{}: random >= wait\n", *self); + } + + if (self->spawnflags.has(SPAWNFLAG_TIMER_START_ON)) + { + self->nextthink = level.time + 1_sec + gtime_t::from_sec(st.pausetime + self->delay + self->wait + crandom() * self->random); + self->activator = self; + } + + self->svflags = SVF_NOCLIENT; +} + +constexpr spawnflags_t SPAWNFLAG_CONVEYOR_START_ON = 1_spawnflag; +constexpr spawnflags_t SPAWNFLAG_CONVEYOR_TOGGLE = 2_spawnflag; + +/*QUAKED func_conveyor (0 .5 .8) ? START_ON TOGGLE +Conveyors are stationary brushes that move what's on them. +The brush should be have a surface with at least one current content enabled. +speed default 100 +*/ + +USE(func_conveyor_use) (edict_t *self, edict_t *other, edict_t *activator) -> void +{ + if (self->spawnflags.has(SPAWNFLAG_CONVEYOR_START_ON)) + { + self->speed = 0; + self->spawnflags &= ~SPAWNFLAG_CONVEYOR_START_ON; + } + else + { + self->speed = (float) self->count; + self->spawnflags |= SPAWNFLAG_CONVEYOR_START_ON; + } + + if (!self->spawnflags.has(SPAWNFLAG_CONVEYOR_TOGGLE)) + self->count = 0; +} + +void SP_func_conveyor(edict_t *self) +{ + if (!self->speed) + self->speed = 100; + + if (!self->spawnflags.has(SPAWNFLAG_CONVEYOR_START_ON)) + { + self->count = (int) self->speed; + self->speed = 0; + } + + self->use = func_conveyor_use; + + gi.setmodel(self, self->model); + self->solid = SOLID_BSP; + gi.linkentity(self); +} + +/*QUAKED func_door_secret (0 .5 .8) ? always_shoot 1st_left 1st_down +A secret door. Slide back and then to the side. + +open_once doors never closes +1st_left 1st move is left of arrow +1st_down 1st move is down from arrow +always_shoot door is shootebale even if targeted + +"angle" determines the direction +"dmg" damage to inflic when blocked (default 2) +"wait" how long to hold in the open position (default 5, -1 means hold) +*/ + +constexpr spawnflags_t SPAWNFLAG_SECRET_ALWAYS_SHOOT = 1_spawnflag; +constexpr spawnflags_t SPAWNFLAG_SECRET_1ST_LEFT = 2_spawnflag; +constexpr spawnflags_t SPAWNFLAG_SECRET_1ST_DOWN = 4_spawnflag; + +void door_secret_move1(edict_t *self); +void door_secret_move2(edict_t *self); +void door_secret_move3(edict_t *self); +void door_secret_move4(edict_t *self); +void door_secret_move5(edict_t *self); +void door_secret_move6(edict_t *self); +void door_secret_done(edict_t *self); + +USE(door_secret_use) (edict_t *self, edict_t *other, edict_t *activator) -> void +{ + // make sure we're not already moving + if (self->s.origin) + return; + + Move_Calc(self, self->pos1, door_secret_move1); + door_use_areaportals(self, true); +} + +MOVEINFO_ENDFUNC(door_secret_move1) (edict_t *self) -> void +{ + self->nextthink = level.time + 1_sec; + self->think = door_secret_move2; +} + +THINK(door_secret_move2) (edict_t *self) -> void +{ + Move_Calc(self, self->pos2, door_secret_move3); +} + +MOVEINFO_ENDFUNC(door_secret_move3) (edict_t *self) -> void +{ + if (self->wait == -1) + return; + self->nextthink = level.time + gtime_t::from_sec(self->wait); + self->think = door_secret_move4; +} + +THINK(door_secret_move4) (edict_t *self) -> void +{ + Move_Calc(self, self->pos1, door_secret_move5); +} + +MOVEINFO_ENDFUNC(door_secret_move5) (edict_t *self) -> void +{ + self->nextthink = level.time + 1_sec; + self->think = door_secret_move6; +} + +THINK(door_secret_move6) (edict_t *self) -> void +{ + Move_Calc(self, vec3_origin, door_secret_done); +} + +MOVEINFO_ENDFUNC(door_secret_done) (edict_t *self) -> void +{ + if (!(self->targetname) || self->spawnflags.has(SPAWNFLAG_SECRET_ALWAYS_SHOOT)) + { + self->health = 0; + self->takedamage = true; + } + door_use_areaportals(self, false); +} + +MOVEINFO_BLOCKED(door_secret_blocked) (edict_t *self, edict_t *other) -> void +{ + if (!(other->svflags & SVF_MONSTER) && (!other->client)) + { + // give it a chance to go away on it's own terms (like gibs) + T_Damage(other, self, self, vec3_origin, other->s.origin, vec3_origin, 100000, 1, DAMAGE_NONE, MOD_CRUSH); + // if it's still there, nuke it + if (other && other->inuse && other->solid) + BecomeExplosion1(other); + return; + } + + if (level.time < self->touch_debounce_time) + return; + self->touch_debounce_time = level.time + 500_ms; + + T_Damage(other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, DAMAGE_NONE, MOD_CRUSH); +} + +DIE(door_secret_die) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void +{ + self->takedamage = false; + door_secret_use(self, attacker, attacker); +} + +void SP_func_door_secret(edict_t *ent) +{ + vec3_t forward, right, up; + float side; + float width; + float length; + + G_SetMoveinfoSounds(ent, "doors/dr1_strt.wav", "doors/dr1_mid.wav", "doors/dr1_end.wav"); + + ent->attenuation = ATTN_STATIC; + + ent->movetype = MOVETYPE_PUSH; + ent->solid = SOLID_BSP; + ent->svflags |= SVF_DOOR; + gi.setmodel(ent, ent->model); + + ent->moveinfo.blocked = door_secret_blocked; + ent->use = door_secret_use; + + if (!(ent->targetname) || ent->spawnflags.has(SPAWNFLAG_SECRET_ALWAYS_SHOOT)) + { + ent->health = 0; + ent->takedamage = true; + ent->die = door_secret_die; + } + + if (!ent->dmg) + ent->dmg = 2; + + if (!ent->wait) + ent->wait = 5; + + ent->moveinfo.accel = ent->moveinfo.decel = ent->moveinfo.speed = 50; + + // calculate positions + AngleVectors(ent->s.angles, forward, right, up); + ent->s.angles = {}; + side = 1.0f - (ent->spawnflags.has(SPAWNFLAG_SECRET_1ST_LEFT) ? 2 : 0); + if (ent->spawnflags.has(SPAWNFLAG_SECRET_1ST_DOWN)) + width = fabsf(up.dot(ent->size)); + else + width = fabsf(right.dot(ent->size)); + length = fabsf(forward.dot(ent->size)); + if (ent->spawnflags.has(SPAWNFLAG_SECRET_1ST_DOWN)) + ent->pos1 = ent->s.origin + (up * (-1 * width)); + else + ent->pos1 = ent->s.origin + (right * (side * width)); + ent->pos2 = ent->pos1 + (forward * length); + + if (ent->health) + { + ent->takedamage = true; + ent->die = door_killed; + ent->max_health = ent->health; + } + else if (ent->targetname && ent->message) + { + gi.soundindex("misc/talk.wav"); + ent->touch = door_touch; + } + + gi.linkentity(ent); +} + +/*QUAKED func_killbox (1 0 0) ? +Kills everything inside when fired, irrespective of protection. +*/ +constexpr spawnflags_t SPAWNFLAG_KILLBOX_DEADLY_COOP = 2_spawnflag; +constexpr spawnflags_t SPAWNFLAG_KILLBOX_EXACT_COLLISION = 4_spawnflag; + +USE(use_killbox) (edict_t *self, edict_t *other, edict_t *activator) -> void +{ + if (self->spawnflags.has(SPAWNFLAG_KILLBOX_DEADLY_COOP)) + level.deadly_kill_box = true; + + self->solid = SOLID_TRIGGER; + gi.linkentity(self); + + KillBox(self, false, MOD_TELEFRAG, self->spawnflags.has(SPAWNFLAG_KILLBOX_EXACT_COLLISION)); + + self->solid = SOLID_NOT; + gi.linkentity(self); + + level.deadly_kill_box = false; +} + +void SP_func_killbox(edict_t *ent) +{ + gi.setmodel(ent, ent->model); + ent->use = use_killbox; + ent->svflags = SVF_NOCLIENT; +} + +/*QUAKED func_eye (0 1 0) ? +Camera-like eye that can track entities. +"pathtarget" point to an info_notnull (which gets freed after spawn) to automatically set +the eye_position +"target"/"killtarget"/"delay"/"message" target keys to fire when we first spot a player +"eye_position" manually set the eye position; note that this is in "forward right up" format, relative to +the origin of the brush and using the entity's angles +"radius" default 512, detection radius for entities +"speed" default 45, how fast, in degrees per second, we should move on each axis to reach the target +"vision_cone" default 0.5 for half cone; how wide the cone of vision should be (relative to their initial angles) +"wait" default 0, the amount of time to wait before returning to neutral angles +*/ +constexpr spawnflags_t SPAWNFLAG_FUNC_EYE_FIRED_TARGETS = 17_spawnflag_bit; // internal use only + +THINK(func_eye_think) (edict_t *self) -> void +{ + // find enemy to track + float closest_dist = 0; + edict_t *closest_player = nullptr; + + for (auto player : active_players()) + { + vec3_t dir = player->s.origin - self->s.origin; + float dist = dir.normalize(); + + if (dir.dot(self->movedir) < self->yaw_speed) + continue; + + if (dist >= self->dmg_radius) + continue; + + if (!closest_player || dist < closest_dist) + { + closest_player = player; + closest_dist = dist; + } + } + + self->enemy = closest_player; + + // tracking player + vec3_t wanted_angles; + + vec3_t fwd, rgt, up; + AngleVectors(self->s.angles, fwd, rgt, up); + + vec3_t eye_pos = self->s.origin; + eye_pos += fwd * self->move_origin[0]; + eye_pos += rgt * self->move_origin[1]; + eye_pos += up * self->move_origin[2]; + + if (self->enemy) + { + if (!(self->spawnflags & SPAWNFLAG_FUNC_EYE_FIRED_TARGETS)) + { + G_UseTargets(self, self->enemy); + self->spawnflags |= SPAWNFLAG_FUNC_EYE_FIRED_TARGETS; + } + + vec3_t dir = (self->enemy->s.origin - eye_pos).normalized(); + wanted_angles = vectoangles(dir); + + self->s.frame = 2; + self->timestamp = level.time + gtime_t::from_sec(self->wait); + } + else + { + if (self->timestamp <= level.time) + { + // return to neutral + wanted_angles = self->move_angles; + self->s.frame = 0; + } + else + wanted_angles = self->s.angles; + } + + for (int i = 0; i < 2; i++) + { + float current = anglemod(self->s.angles[i]); + float ideal = wanted_angles[i]; + + if (current == ideal) + continue; + + float move = ideal - current; + + if (ideal > current) + { + if (move >= 180) + move = move - 360; + } + else + { + if (move <= -180) + move = move + 360; + } + if (move > 0) + { + if (move > self->speed) + move = self->speed; + } + else + { + if (move < -self->speed) + move = -self->speed; + } + + self->s.angles[i] = anglemod(current + move); + } + + self->nextthink = level.time + FRAME_TIME_S; +} + +THINK(func_eye_setup) (edict_t *self) -> void +{ + edict_t *eye_pos = G_PickTarget(self->pathtarget); + + if (!eye_pos) + gi.Com_PrintFmt("{}: bad target\n", *self); + else + self->move_origin = eye_pos->s.origin - self->s.origin; + + self->movedir = self->move_origin.normalized(); + + self->think = func_eye_think; + self->nextthink = level.time + 10_hz; +} + +void SP_func_eye(edict_t *ent) +{ + ent->movetype = MOVETYPE_PUSH; + ent->solid = SOLID_BSP; + gi.setmodel(ent, ent->model); + + if (!st.radius) + ent->dmg_radius = 512; + else + ent->dmg_radius = st.radius; + + if (!ent->speed) + ent->speed = 45; + + if (!ent->yaw_speed) + ent->yaw_speed = 0.5f; + + ent->speed *= gi.frame_time_s; + ent->move_angles = ent->s.angles; + + ent->wait = 1.0f; + + if (ent->pathtarget) + { + ent->think = func_eye_setup; + ent->nextthink = level.time + 10_hz; + } + else + { + ent->think = func_eye_think; + ent->nextthink = level.time + 10_hz; + + vec3_t right, up; + AngleVectors(ent->move_angles, ent->movedir, right, up); + + vec3_t move_origin = ent->move_origin; + ent->move_origin = ent->movedir * move_origin[0]; + ent->move_origin += right * move_origin[1]; + ent->move_origin += up * move_origin[2]; + } + + gi.linkentity(ent); +} diff --git a/rerelease/g_items.cpp b/rerelease/g_items.cpp new file mode 100644 index 0000000..1197d66 --- /dev/null +++ b/rerelease/g_items.cpp @@ -0,0 +1,4019 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +#include "g_local.h" +#include "bots/bot_includes.h" + +bool Pickup_Weapon(edict_t *ent, edict_t *other); +void Use_Weapon(edict_t *ent, gitem_t *inv); +void Drop_Weapon(edict_t *ent, gitem_t *inv); + +void Weapon_Blaster(edict_t *ent); +void Weapon_Shotgun(edict_t *ent); +void Weapon_SuperShotgun(edict_t *ent); +void Weapon_Machinegun(edict_t *ent); +void Weapon_Chaingun(edict_t *ent); +void Weapon_HyperBlaster(edict_t *ent); +void Weapon_RocketLauncher(edict_t *ent); +void Weapon_Grenade(edict_t *ent); +void Weapon_GrenadeLauncher(edict_t *ent); +void Weapon_Railgun(edict_t *ent); +void Weapon_BFG(edict_t *ent); +// RAFAEL +void Weapon_Ionripper(edict_t *ent); +void Weapon_Phalanx(edict_t *ent); +void Weapon_Trap(edict_t *ent); +// RAFAEL +// ROGUE +void Weapon_ChainFist(edict_t *ent); +void Weapon_Disintegrator(edict_t *ent); +void Weapon_ETF_Rifle(edict_t *ent); +void Weapon_Heatbeam(edict_t *ent); +void Weapon_Prox(edict_t *ent); +void Weapon_Tesla(edict_t *ent); +void Weapon_ProxLauncher(edict_t *ent); +// ROGUE +void Weapon_Beta_Disintegrator(edict_t *ent); + +void Use_Quad(edict_t *ent, gitem_t *item); +static gtime_t quad_drop_timeout_hack; + +// RAFAEL +void Use_QuadFire(edict_t *ent, gitem_t *item); +static gtime_t quad_fire_drop_timeout_hack; +// RAFAEL + +//====================================================================== + +/* +=============== +GetItemByIndex +=============== +*/ +gitem_t *GetItemByIndex(item_id_t index) +{ + if (index <= IT_NULL || index >= IT_TOTAL) + return nullptr; + + return &itemlist[index]; +} + +static gitem_t *ammolist[AMMO_MAX]; + +gitem_t *GetItemByAmmo(ammo_t ammo) +{ + return ammolist[ammo]; +} + +static gitem_t *poweruplist[POWERUP_MAX]; + +gitem_t *GetItemByPowerup(powerup_t powerup) +{ + return poweruplist[powerup]; +} + +/* +=============== +FindItemByClassname + +=============== +*/ +gitem_t *FindItemByClassname(const char *classname) +{ + int i; + gitem_t *it; + + it = itemlist; + for (i = 0; i < IT_TOTAL; i++, it++) + { + if (!it->classname) + continue; + if (!Q_strcasecmp(it->classname, classname)) + return it; + } + + return nullptr; +} + +/* +=============== +FindItem + +=============== +*/ +gitem_t *FindItem(const char *pickup_name) +{ + int i; + gitem_t *it; + + it = itemlist; + for (i = 0; i < IT_TOTAL; i++, it++) + { + if (!it->use_name) + continue; + if (!Q_strcasecmp(it->use_name, pickup_name)) + return it; + } + + return nullptr; +} + +//====================================================================== + +THINK(DoRespawn) (edict_t *ent) -> void +{ + if (ent->team) + { + edict_t *master; + int count; + int choice; + + master = ent->teammaster; + + // ZOID + // in ctf, when we are weapons stay, only the master of a team of weapons + // is spawned + if (ctf->integer && g_dm_weapons_stay->integer && master->item && (master->item->flags & IF_WEAPON)) + ent = master; + else + { + // ZOID + + for (count = 0, ent = master; ent; ent = ent->chain, count++) + ; + + choice = irandom(count); + + for (count = 0, ent = master; count < choice; ent = ent->chain, count++) + ; + } + } + + ent->svflags &= ~SVF_NOCLIENT; + ent->svflags &= ~SVF_RESPAWNING; + ent->solid = SOLID_TRIGGER; + gi.linkentity(ent); + + // send an effect + ent->s.event = EV_ITEM_RESPAWN; + + // ROGUE + if (g_dm_random_items->integer) + { + item_id_t new_item = DoRandomRespawn(ent); + + // if we've changed entities, then do some sleight of hand. + // otherwise, the old entity will respawn + if (new_item) + { + ent->item = GetItemByIndex(new_item); + + ent->classname = ent->item->classname; + ent->s.effects = ent->item->world_model_flags; + gi.setmodel(ent, ent->item->world_model); + } + } + // ROGUE +} + +void SetRespawn(edict_t *ent, gtime_t delay, bool hide_self) +{ + // already respawning + if (ent->think == DoRespawn && ent->nextthink >= level.time) + return; + + ent->flags |= FL_RESPAWN; + + if (hide_self) + { + ent->svflags |= ( SVF_NOCLIENT | SVF_RESPAWNING ); + ent->solid = SOLID_NOT; + gi.linkentity(ent); + } + + ent->nextthink = level.time + delay; + ent->think = DoRespawn; +} + +//====================================================================== + +bool IsInstantItemsEnabled() +{ + if (deathmatch->integer && g_dm_instant_items->integer) + { + return true; + } + + if (!deathmatch->integer && level.instantitems) + { + return true; + } + + return false; +} + +bool Pickup_Powerup(edict_t *ent, edict_t *other) +{ + int quantity; + + quantity = other->client->pers.inventory[ent->item->id]; + if ((skill->integer == 0 && quantity >= 3) || + (skill->integer == 1 && quantity >= 2) || + (skill->integer >= 2 && quantity >= 1)) + return false; + + if (coop->integer && !P_UseCoopInstancedItems() && (ent->item->flags & IF_STAY_COOP) && (quantity > 0)) + return false; + + other->client->pers.inventory[ent->item->id]++; + + bool is_dropped_from_death = ent->spawnflags.has(SPAWNFLAG_ITEM_DROPPED_PLAYER) && !ent->spawnflags.has(SPAWNFLAG_ITEM_DROPPED); + + if (IsInstantItemsEnabled() || + ((ent->item->use == Use_Quad) && is_dropped_from_death) || + ((ent->item->use == Use_QuadFire) && is_dropped_from_death)) + { + if ((ent->item->use == Use_Quad) && is_dropped_from_death) + quad_drop_timeout_hack = (ent->nextthink - level.time); + else if ((ent->item->use == Use_QuadFire) && is_dropped_from_death) + quad_fire_drop_timeout_hack = (ent->nextthink - level.time); + + if (ent->item->use) + ent->item->use(other, ent->item); + } + + if (deathmatch->integer) + { + if (!(ent->spawnflags & SPAWNFLAG_ITEM_DROPPED)) + SetRespawn(ent, gtime_t::from_sec(ent->item->quantity)); + } + + return true; +} + +bool Pickup_General(edict_t *ent, edict_t *other) +{ + if (other->client->pers.inventory[ent->item->id]) + return false; + + other->client->pers.inventory[ent->item->id]++; + + if (deathmatch->integer) + { + if (!(ent->spawnflags & SPAWNFLAG_ITEM_DROPPED)) + SetRespawn(ent, gtime_t::from_sec(ent->item->quantity)); + } + + return true; +} + +void Drop_General(edict_t *ent, gitem_t *item) +{ + edict_t *dropped = Drop_Item(ent, item); + dropped->spawnflags |= SPAWNFLAG_ITEM_DROPPED_PLAYER; + dropped->svflags &= ~SVF_INSTANCED; + ent->client->pers.inventory[item->id]--; +} + +//====================================================================== + +void Use_Adrenaline(edict_t *ent, gitem_t *item) +{ + if (!deathmatch->integer) + ent->max_health += 1; + + if (ent->health < ent->max_health) + ent->health = ent->max_health; + + gi.sound(ent, CHAN_ITEM, gi.soundindex("items/n_health.wav"), 1, ATTN_NORM, 0); + + ent->client->pers.inventory[item->id]--; +} + +bool Pickup_LegacyHead(edict_t *ent, edict_t *other) +{ + other->max_health += 5; + other->health += 5; + + if (!(ent->spawnflags & SPAWNFLAG_ITEM_DROPPED) && deathmatch->integer) + SetRespawn(ent, gtime_t::from_sec(ent->item->quantity)); + + return true; +} + +void G_CheckPowerArmor(edict_t *ent) +{ + bool has_enough_cells; + + if (!ent->client->pers.inventory[IT_AMMO_CELLS]) + has_enough_cells = false; + else if (ent->client->pers.autoshield >= AUTO_SHIELD_AUTO) + has_enough_cells = !(ent->flags & FL_WANTS_POWER_ARMOR) || ent->client->pers.inventory[IT_AMMO_CELLS] > ent->client->pers.autoshield; + else + has_enough_cells = true; + + if (ent->flags & FL_POWER_ARMOR) + { + if (!has_enough_cells) + { + // ran out of cells for power armor + ent->flags &= ~FL_POWER_ARMOR; + gi.sound(ent, CHAN_AUTO, gi.soundindex("misc/power2.wav"), 1, ATTN_NORM, 0); + } + } + else + { + // special case for power armor, for auto-shields + if (ent->client->pers.autoshield != AUTO_SHIELD_MANUAL && + has_enough_cells && (ent->client->pers.inventory[IT_ITEM_POWER_SCREEN] || + ent->client->pers.inventory[IT_ITEM_POWER_SHIELD])) + { + ent->flags |= FL_POWER_ARMOR; + gi.sound(ent, CHAN_AUTO, gi.soundindex("misc/power1.wav"), 1, ATTN_NORM, 0); + } + } +} + +inline bool G_AddAmmoAndCap(edict_t *other, item_id_t item, int32_t max, int32_t quantity) +{ + if (other->client->pers.inventory[item] >= max) + return false; + + other->client->pers.inventory[item] += quantity; + if (other->client->pers.inventory[item] > max) + other->client->pers.inventory[item] = max; + + G_CheckPowerArmor(other); + + return true; +} + +inline bool G_AddAmmoAndCapQuantity(edict_t *other, ammo_t ammo) +{ + gitem_t *item = GetItemByAmmo(ammo); + return G_AddAmmoAndCap(other, item->id, other->client->pers.max_ammo[ammo], item->quantity); +} + +inline void G_AdjustAmmoCap(edict_t *other, ammo_t ammo, int16_t new_max) +{ + other->client->pers.max_ammo[ammo] = max(other->client->pers.max_ammo[ammo], new_max); +} + +bool Pickup_Bandolier(edict_t *ent, edict_t *other) +{ + G_AdjustAmmoCap(other, AMMO_BULLETS, 250); + G_AdjustAmmoCap(other, AMMO_SHELLS, 150); + G_AdjustAmmoCap(other, AMMO_CELLS, 250); + G_AdjustAmmoCap(other, AMMO_SLUGS, 75); + G_AdjustAmmoCap(other, AMMO_MAGSLUG, 75); + G_AdjustAmmoCap(other, AMMO_FLECHETTES, 250); + G_AdjustAmmoCap(other, AMMO_DISRUPTOR, 21); + + G_AddAmmoAndCapQuantity(other, AMMO_BULLETS); + G_AddAmmoAndCapQuantity(other, AMMO_SHELLS); + + if (!(ent->spawnflags & SPAWNFLAG_ITEM_DROPPED) && deathmatch->integer) + SetRespawn(ent, gtime_t::from_sec(ent->item->quantity)); + + return true; +} + +bool Pickup_Pack(edict_t *ent, edict_t *other) +{ + G_AdjustAmmoCap(other, AMMO_BULLETS, 300); + G_AdjustAmmoCap(other, AMMO_SHELLS, 200); + G_AdjustAmmoCap(other, AMMO_ROCKETS, 100); + G_AdjustAmmoCap(other, AMMO_GRENADES, 100); + G_AdjustAmmoCap(other, AMMO_CELLS, 300); + G_AdjustAmmoCap(other, AMMO_SLUGS, 100); + G_AdjustAmmoCap(other, AMMO_MAGSLUG, 100); + G_AdjustAmmoCap(other, AMMO_FLECHETTES, 300); + G_AdjustAmmoCap(other, AMMO_DISRUPTOR, 30); + + G_AddAmmoAndCapQuantity(other, AMMO_BULLETS); + G_AddAmmoAndCapQuantity(other, AMMO_SHELLS); + G_AddAmmoAndCapQuantity(other, AMMO_CELLS); + G_AddAmmoAndCapQuantity(other, AMMO_GRENADES); + G_AddAmmoAndCapQuantity(other, AMMO_ROCKETS); + G_AddAmmoAndCapQuantity(other, AMMO_SLUGS); + + // RAFAEL + G_AddAmmoAndCapQuantity(other, AMMO_MAGSLUG); + // RAFAEL + + // ROGUE + G_AddAmmoAndCapQuantity(other, AMMO_FLECHETTES); + G_AddAmmoAndCapQuantity(other, AMMO_DISRUPTOR); + // ROGUE + + if (!(ent->spawnflags & SPAWNFLAG_ITEM_DROPPED) && deathmatch->integer) + SetRespawn(ent, gtime_t::from_sec(ent->item->quantity)); + + return true; +} + +//====================================================================== + +void Use_Quad(edict_t *ent, gitem_t *item) +{ + gtime_t timeout; + + ent->client->pers.inventory[item->id]--; + + if (quad_drop_timeout_hack) + { + timeout = quad_drop_timeout_hack; + quad_drop_timeout_hack = 0_ms; + } + else + { + timeout = 30_sec; + } + + ent->client->quad_time = max(level.time, ent->client->quad_time) + timeout; + + gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage.wav"), 1, ATTN_NORM, 0); +} +// ===================================================================== + +// RAFAEL +void Use_QuadFire(edict_t *ent, gitem_t *item) +{ + gtime_t timeout; + + ent->client->pers.inventory[item->id]--; + + if (quad_fire_drop_timeout_hack) + { + timeout = quad_fire_drop_timeout_hack; + quad_fire_drop_timeout_hack = 0_ms; + } + else + { + timeout = 30_sec; + } + + ent->client->quadfire_time = max(level.time, ent->client->quadfire_time) + timeout; + + gi.sound(ent, CHAN_ITEM, gi.soundindex("items/quadfire1.wav"), 1, ATTN_NORM, 0); +} +// RAFAEL + +//====================================================================== + +void Use_Breather(edict_t *ent, gitem_t *item) +{ + ent->client->pers.inventory[item->id]--; + + ent->client->breather_time = max(level.time, ent->client->breather_time) + 30_sec; + + // gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage.wav"), 1, ATTN_NORM, 0); +} + +//====================================================================== + +void Use_Envirosuit(edict_t *ent, gitem_t *item) +{ + ent->client->pers.inventory[item->id]--; + + ent->client->enviro_time = max(level.time, ent->client->enviro_time) + 30_sec; + + // gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage.wav"), 1, ATTN_NORM, 0); +} + +//====================================================================== + +void Use_Invulnerability(edict_t *ent, gitem_t *item) +{ + ent->client->pers.inventory[item->id]--; + + ent->client->invincible_time = max(level.time, ent->client->invincible_time) + 30_sec; + + gi.sound(ent, CHAN_ITEM, gi.soundindex("items/protect.wav"), 1, ATTN_NORM, 0); +} + +void Use_Invisibility(edict_t *ent, gitem_t *item) +{ + ent->client->pers.inventory[item->id]--; + + ent->client->invisible_time = max(level.time, ent->client->invisible_time) + 30_sec; + + gi.sound(ent, CHAN_ITEM, gi.soundindex("items/protect.wav"), 1, ATTN_NORM, 0); +} + +//====================================================================== + +void Use_Silencer(edict_t *ent, gitem_t *item) +{ + ent->client->pers.inventory[item->id]--; + ent->client->silencer_shots += 30; + + // gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage.wav"), 1, ATTN_NORM, 0); +} + +//====================================================================== + +bool Pickup_Key(edict_t *ent, edict_t *other) +{ + if (coop->integer) + { + if (ent->item->id == IT_KEY_POWER_CUBE || ent->item->id == IT_KEY_EXPLOSIVE_CHARGES) + { + if (other->client->pers.power_cubes & ((ent->spawnflags & SPAWNFLAG_EDITOR_MASK).value >> 8)) + return false; + other->client->pers.inventory[ent->item->id]++; + other->client->pers.power_cubes |= ((ent->spawnflags & SPAWNFLAG_EDITOR_MASK).value >> 8); + } + else + { + if (other->client->pers.inventory[ent->item->id]) + return false; + other->client->pers.inventory[ent->item->id] = 1; + } + return true; + } + other->client->pers.inventory[ent->item->id]++; + return true; +} + +//====================================================================== + +bool Add_Ammo(edict_t *ent, gitem_t *item, int count) +{ + if (!ent->client || item->tag < AMMO_BULLETS || item->tag >= AMMO_MAX) + return false; + + return G_AddAmmoAndCap(ent, item->id, ent->client->pers.max_ammo[item->tag], count); +} + +// we just got weapon `item`, check if we should switch to it +void G_CheckAutoSwitch(edict_t *ent, gitem_t *item, bool is_new) +{ + // already using or switching to + if (ent->client->pers.weapon == item || + ent->client->newweapon == item) + return; + // need ammo + else if (item->ammo) + { + int32_t required_ammo = (item->flags & IF_AMMO) ? 1 : item->quantity; + + if (ent->client->pers.inventory[item->ammo] < required_ammo) + return; + } + + // check autoswitch setting + if (ent->client->pers.autoswitch == auto_switch_t::NEVER) + return; + else if ((item->flags & IF_AMMO) && ent->client->pers.autoswitch == auto_switch_t::ALWAYS_NO_AMMO) + return; + else if (ent->client->pers.autoswitch == auto_switch_t::SMART) + { + bool using_blaster = ent->client->pers.weapon && ent->client->pers.weapon->id == IT_WEAPON_BLASTER; + + // smartness algorithm: in DM, we will always switch if we have the blaster out + // otherwise leave our active weapon alone + if (deathmatch->integer && !using_blaster) + return; + // in SP, only switch if it's a new weapon, or we have the blaster out + else if (!deathmatch->integer && !using_blaster && !is_new) + return; + } + + // switch! + ent->client->newweapon = item; +} + +bool Pickup_Ammo(edict_t *ent, edict_t *other) +{ + int oldcount; + int count; + bool weapon; + + weapon = (ent->item->flags & IF_WEAPON); + if (weapon && G_CheckInfiniteAmmo(ent->item)) + count = 1000; + else if (ent->count) + count = ent->count; + else + count = ent->item->quantity; + + oldcount = other->client->pers.inventory[ent->item->id]; + + if (!Add_Ammo(other, ent->item, count)) + return false; + + if (weapon) + G_CheckAutoSwitch(other, ent->item, !oldcount); + + if (!(ent->spawnflags & (SPAWNFLAG_ITEM_DROPPED | SPAWNFLAG_ITEM_DROPPED_PLAYER)) && deathmatch->integer) + SetRespawn(ent, 30_sec); + return true; +} + +void Drop_Ammo(edict_t *ent, gitem_t *item) +{ + item_id_t index = item->id; + edict_t *dropped = Drop_Item(ent, item); + dropped->spawnflags |= SPAWNFLAG_ITEM_DROPPED_PLAYER; + dropped->svflags &= ~SVF_INSTANCED; + + if (ent->client->pers.inventory[index] >= item->quantity) + dropped->count = item->quantity; + else + dropped->count = ent->client->pers.inventory[index]; + + if (ent->client->pers.weapon && ent->client->pers.weapon == item && (item->flags & IF_AMMO) && + ent->client->pers.inventory[index] - dropped->count <= 0) + { + gi.LocClient_Print(ent, PRINT_HIGH, "$g_cant_drop_weapon"); + G_FreeEdict(dropped); + return; + } + + ent->client->pers.inventory[index] -= dropped->count; + G_CheckPowerArmor(ent); +} + +//====================================================================== + +THINK(MegaHealth_think) (edict_t *self) -> void +{ + if (self->owner->health > self->owner->max_health) + { + self->nextthink = level.time + 1_sec; + self->owner->health -= 1; + return; + } + + if (!(self->spawnflags & SPAWNFLAG_ITEM_DROPPED) && deathmatch->integer) + SetRespawn(self, 20_sec); + else + G_FreeEdict(self); +} + +bool Pickup_Health(edict_t *ent, edict_t *other) +{ + int health_flags = (ent->style ? ent->style : ent->item->tag); + + if (!(health_flags & HEALTH_IGNORE_MAX)) + if (other->health >= other->max_health) + return false; + + int count = ent->count ? ent->count : ent->item->quantity; + + // ZOID + if (deathmatch->integer && other->health >= 250 && count > 25) + return false; + // ZOID + + other->health += count; + + if (!(health_flags & HEALTH_IGNORE_MAX)) + { + if (other->health > other->max_health) + other->health = other->max_health; + } + + if (ent->item->tag & HEALTH_TIMED) + { + if (!deathmatch->integer) + { + // mega health doesn't need to be special in SP + // since it never respawns. + other->client->pers.megahealth_time = 5_sec; + } + else + { + ent->think = MegaHealth_think; + ent->nextthink = level.time + 5_sec; + ent->owner = other; + ent->flags |= FL_RESPAWN; + ent->svflags |= SVF_NOCLIENT; + ent->solid = SOLID_NOT; + } + } + else + { + if (!(ent->spawnflags & SPAWNFLAG_ITEM_DROPPED) && deathmatch->integer) + SetRespawn(ent, 30_sec); + } + + return true; +} + +//====================================================================== + +item_id_t ArmorIndex(edict_t *ent) +{ + if (ent->svflags & SVF_MONSTER) + return ent->monsterinfo.armor_type; + + if (ent->client) + { + if (ent->client->pers.inventory[IT_ARMOR_JACKET] > 0) + return IT_ARMOR_JACKET; + else if (ent->client->pers.inventory[IT_ARMOR_COMBAT] > 0) + return IT_ARMOR_COMBAT; + else if (ent->client->pers.inventory[IT_ARMOR_BODY] > 0) + return IT_ARMOR_BODY; + } + + return IT_NULL; +} + +bool Pickup_Armor(edict_t *ent, edict_t *other) +{ + item_id_t old_armor_index; + const gitem_armor_t *oldinfo; + const gitem_armor_t *newinfo; + int newcount; + float salvage; + int salvagecount; + + // get info on new armor + newinfo = ent->item->armor_info; + + old_armor_index = ArmorIndex(other); + + // handle armor shards specially + if (ent->item->id == IT_ARMOR_SHARD) + { + if (!old_armor_index) + other->client->pers.inventory[IT_ARMOR_JACKET] = 2; + else + other->client->pers.inventory[old_armor_index] += 2; + } + + // if player has no armor, just use it + else if (!old_armor_index) + { + other->client->pers.inventory[ent->item->id] = newinfo->base_count; + } + + // use the better armor + else + { + // get info on old armor + if (old_armor_index == IT_ARMOR_JACKET) + oldinfo = &jacketarmor_info; + else if (old_armor_index == IT_ARMOR_COMBAT) + oldinfo = &combatarmor_info; + else + oldinfo = &bodyarmor_info; + + if (newinfo->normal_protection > oldinfo->normal_protection) + { + // calc new armor values + salvage = oldinfo->normal_protection / newinfo->normal_protection; + salvagecount = (int) (salvage * other->client->pers.inventory[old_armor_index]); + newcount = newinfo->base_count + salvagecount; + if (newcount > newinfo->max_count) + newcount = newinfo->max_count; + + // zero count of old armor so it goes away + other->client->pers.inventory[old_armor_index] = 0; + + // change armor to new item with computed value + other->client->pers.inventory[ent->item->id] = newcount; + } + else + { + // calc new armor values + salvage = newinfo->normal_protection / oldinfo->normal_protection; + salvagecount = (int) (salvage * newinfo->base_count); + newcount = other->client->pers.inventory[old_armor_index] + salvagecount; + if (newcount > oldinfo->max_count) + newcount = oldinfo->max_count; + + // if we're already maxed out then we don't need the new armor + if (other->client->pers.inventory[old_armor_index] >= newcount) + return false; + + // update current armor value + other->client->pers.inventory[old_armor_index] = newcount; + } + } + + if (!(ent->spawnflags & SPAWNFLAG_ITEM_DROPPED) && deathmatch->integer) + SetRespawn(ent, 20_sec); + + return true; +} + +//====================================================================== + +item_id_t PowerArmorType(edict_t *ent) +{ + if (!ent->client) + return IT_NULL; + + if (!(ent->flags & FL_POWER_ARMOR)) + return IT_NULL; + + if (ent->client->pers.inventory[IT_ITEM_POWER_SHIELD] > 0) + return IT_ITEM_POWER_SHIELD; + + if (ent->client->pers.inventory[IT_ITEM_POWER_SCREEN] > 0) + return IT_ITEM_POWER_SCREEN; + + return IT_NULL; +} + +void Use_PowerArmor(edict_t *ent, gitem_t *item) +{ + if (ent->flags & FL_POWER_ARMOR) + { + ent->flags &= ~(FL_POWER_ARMOR | FL_WANTS_POWER_ARMOR); + gi.sound(ent, CHAN_AUTO, gi.soundindex("misc/power2.wav"), 1, ATTN_NORM, 0); + } + else + { + if (!ent->client->pers.inventory[IT_AMMO_CELLS]) + { + gi.LocClient_Print(ent, PRINT_HIGH, "$g_no_cells_power_armor"); + return; + } + + ent->flags |= FL_POWER_ARMOR; + + if (ent->client->pers.autoshield != AUTO_SHIELD_MANUAL && + ent->client->pers.inventory[IT_AMMO_CELLS] > ent->client->pers.autoshield) + ent->flags |= FL_WANTS_POWER_ARMOR; + + gi.sound(ent, CHAN_AUTO, gi.soundindex("misc/power1.wav"), 1, ATTN_NORM, 0); + } +} + +bool Pickup_PowerArmor(edict_t *ent, edict_t *other) +{ + int quantity; + + quantity = other->client->pers.inventory[ent->item->id]; + + other->client->pers.inventory[ent->item->id]++; + + if (deathmatch->integer) + { + if (!(ent->spawnflags & SPAWNFLAG_ITEM_DROPPED)) + SetRespawn(ent, gtime_t::from_sec(ent->item->quantity)); + // auto-use for DM only if we didn't already have one + if (!quantity) + G_CheckPowerArmor(other); + } + else + G_CheckPowerArmor(other); + + return true; +} + +void Drop_PowerArmor(edict_t *ent, gitem_t *item) +{ + if ((ent->flags & FL_POWER_ARMOR) && (ent->client->pers.inventory[item->id] == 1)) + Use_PowerArmor(ent, item); + Drop_General(ent, item); +} + +//====================================================================== + +bool Entity_IsVisibleToPlayer(edict_t* ent, edict_t* player) +{ + return !ent->item_picked_up_by[player->s.number - 1]; +} + +/* +=============== +Touch_Item +=============== +*/ +TOUCH(Touch_Item) (edict_t *ent, edict_t *other, const trace_t &tr, bool other_touching_self) -> void +{ + bool taken; + + if (!other->client) + return; + if (other->health < 1) + return; // dead people can't pickup + if (!ent->item->pickup) + return; // not a grabbable item? + + // already got this instanced item + if (coop->integer && P_UseCoopInstancedItems()) + { + if (ent->item_picked_up_by[other->s.number - 1]) + return; + } + + // ZOID + if (CTFMatchSetup()) + return; // can't pick stuff up right now + // ZOID + + taken = ent->item->pickup(ent, other); + + ValidateSelectedItem(other); + + if (taken) + { + // flash the screen + other->client->bonus_alpha = 0.25; + + // show icon and name on status bar + other->client->ps.stats[STAT_PICKUP_ICON] = gi.imageindex(ent->item->icon); + other->client->ps.stats[STAT_PICKUP_STRING] = CS_ITEMS + ent->item->id; + other->client->pickup_msg_time = level.time + 3_sec; + + // change selected item if we still have it + if (ent->item->use && other->client->pers.inventory[ent->item->id]) + { + other->client->ps.stats[STAT_SELECTED_ITEM] = other->client->pers.selected_item = ent->item->id; + other->client->ps.stats[STAT_SELECTED_ITEM_NAME] = 0; // don't set name on pickup item since it's already there + } + + if (ent->noise_index) + gi.sound(other, CHAN_ITEM, ent->noise_index, 1, ATTN_NORM, 0); + else if (ent->item->pickup_sound) + gi.sound(other, CHAN_ITEM, gi.soundindex(ent->item->pickup_sound), 1, ATTN_NORM, 0); + + int32_t player_number = other->s.number - 1; + + if (coop->integer && P_UseCoopInstancedItems() && !ent->item_picked_up_by[player_number]) + { + ent->item_picked_up_by[player_number] = true; + + // [Paril-KEX] this is to fix a coop quirk where items + // that send a message on pick up will only print on the + // player that picked them up, and never anybody else; + // when instanced items are enabled we don't need to limit + // ourselves to this, but it does mean that relays that trigger + // messages won't work, so we'll have to fix those + if (ent->message) + G_PrintActivationMessage(ent, other, false); + } + } + + if (!(ent->spawnflags & SPAWNFLAG_ITEM_TARGETS_USED)) + { + // [Paril-KEX] see above msg; this also disables the message in DM + // since there's no need to print pickup messages in DM (this wasn't + // even a documented feature, relays were traditionally used for this) + const char *message_backup = nullptr; + + if (deathmatch->integer || (coop->integer && P_UseCoopInstancedItems())) + std::swap(message_backup, ent->message); + + G_UseTargets(ent, other); + + if (deathmatch->integer || (coop->integer && P_UseCoopInstancedItems())) + std::swap(message_backup, ent->message); + + ent->spawnflags |= SPAWNFLAG_ITEM_TARGETS_USED; + } + + if (taken) + { + bool should_remove = false; + + if (coop->integer) + { + // in coop with instanced items, *only* dropped + // player items will ever get deleted permanently. + if (P_UseCoopInstancedItems()) + should_remove = ent->spawnflags.has(SPAWNFLAG_ITEM_DROPPED_PLAYER); + // in coop without instanced items, IF_STAY_COOP items remain + // if not dropped + else + should_remove = ent->spawnflags.has(SPAWNFLAG_ITEM_DROPPED | SPAWNFLAG_ITEM_DROPPED_PLAYER) || !(ent->item->flags & IF_STAY_COOP); + } + else + should_remove = !deathmatch->integer || ent->spawnflags.has(SPAWNFLAG_ITEM_DROPPED | SPAWNFLAG_ITEM_DROPPED_PLAYER); + + if (should_remove) + { + if (ent->flags & FL_RESPAWN) + ent->flags &= ~FL_RESPAWN; + else + G_FreeEdict(ent); + } + } +} + +//====================================================================== + +TOUCH(drop_temp_touch) (edict_t *ent, edict_t *other, const trace_t &tr, bool other_touching_self) -> void +{ + if (other == ent->owner) + return; + + Touch_Item(ent, other, tr, other_touching_self); +} + +THINK(drop_make_touchable) (edict_t *ent) -> void +{ + ent->touch = Touch_Item; + if (deathmatch->integer) + { + ent->nextthink = level.time + 29_sec; + ent->think = G_FreeEdict; + } +} + +edict_t *Drop_Item(edict_t *ent, gitem_t *item) +{ + edict_t *dropped; + vec3_t forward, right; + vec3_t offset; + + dropped = G_Spawn(); + + dropped->item = item; + dropped->spawnflags = SPAWNFLAG_ITEM_DROPPED; + dropped->classname = item->classname; + dropped->s.effects = item->world_model_flags; + gi.setmodel(dropped, dropped->item->world_model); + dropped->s.renderfx = RF_GLOW | RF_NO_LOD | RF_IR_VISIBLE; // PGM + dropped->mins = { -15, -15, -15 }; + dropped->maxs = { 15, 15, 15 }; + dropped->solid = SOLID_TRIGGER; + dropped->movetype = MOVETYPE_TOSS; + dropped->touch = drop_temp_touch; + dropped->owner = ent; + + if (ent->client) + { + trace_t trace; + + AngleVectors(ent->client->v_angle, forward, right, nullptr); + offset = { 24, 0, -16 }; + dropped->s.origin = G_ProjectSource(ent->s.origin, offset, forward, right); + trace = gi.trace(ent->s.origin, dropped->mins, dropped->maxs, dropped->s.origin, ent, CONTENTS_SOLID); + dropped->s.origin = trace.endpos; + } + else + { + AngleVectors(ent->s.angles, forward, right, nullptr); + dropped->s.origin = (ent->absmin + ent->absmax) / 2; + } + + G_FixStuckObject(dropped, dropped->s.origin); + + dropped->velocity = forward * 100; + dropped->velocity[2] = 300; + + dropped->think = drop_make_touchable; + dropped->nextthink = level.time + 1_sec; + + if (coop->integer && P_UseCoopInstancedItems()) + dropped->svflags |= SVF_INSTANCED; + + gi.linkentity(dropped); + return dropped; +} + +USE(Use_Item) (edict_t *ent, edict_t *other, edict_t *activator) -> void +{ + ent->svflags &= ~SVF_NOCLIENT; + ent->use = nullptr; + + if (ent->spawnflags.has(SPAWNFLAG_ITEM_NO_TOUCH)) + { + ent->solid = SOLID_BBOX; + ent->touch = nullptr; + } + else + { + ent->solid = SOLID_TRIGGER; + ent->touch = Touch_Item; + } + + gi.linkentity(ent); +} + +//====================================================================== + +/* +================ +droptofloor +================ +*/ +THINK(droptofloor) (edict_t *ent) -> void +{ + trace_t tr; + vec3_t dest; + + // [Paril-KEX] scale foodcube based on how much we ingested + if (strcmp(ent->classname, "item_foodcube") == 0) + { + ent->mins = vec3_t { -8, -8, -8 } * ent->s.scale; + ent->maxs = vec3_t { 8, 8, 8 } * ent->s.scale; + } + else + { + ent->mins = { -15, -15, -15 }; + ent->maxs = { 15, 15, 15 }; + } + + if (ent->model) + gi.setmodel(ent, ent->model); + else + gi.setmodel(ent, ent->item->world_model); + ent->solid = SOLID_TRIGGER; + ent->movetype = MOVETYPE_TOSS; + ent->touch = Touch_Item; + + dest = ent->s.origin + vec3_t { 0, 0, -128 }; + + tr = gi.trace(ent->s.origin, ent->mins, ent->maxs, dest, ent, MASK_SOLID); + if (tr.startsolid) + { + if (G_FixStuckObject(ent, ent->s.origin) == stuck_result_t::NO_GOOD_POSITION) + { + // RAFAEL + if (strcmp(ent->classname, "item_foodcube") == 0) + ent->velocity[2] = 0; + else + { + // RAFAEL + gi.Com_PrintFmt("{}: droptofloor: startsolid\n", *ent); + G_FreeEdict(ent); + return; + // RAFAEL + } + // RAFAEL + } + } + else + ent->s.origin = tr.endpos; + + if (ent->team) + { + ent->flags &= ~FL_TEAMSLAVE; + ent->chain = ent->teamchain; + ent->teamchain = nullptr; + + ent->svflags |= SVF_NOCLIENT; + ent->solid = SOLID_NOT; + + if (ent == ent->teammaster) + { + ent->nextthink = level.time + 10_hz; + ent->think = DoRespawn; + } + } + + if (ent->spawnflags.has(SPAWNFLAG_ITEM_NO_TOUCH)) + { + ent->solid = SOLID_BBOX; + ent->touch = nullptr; + ent->s.effects &= ~(EF_ROTATE | EF_BOB); + ent->s.renderfx &= ~RF_GLOW; + } + + if (ent->spawnflags.has(SPAWNFLAG_ITEM_TRIGGER_SPAWN)) + { + ent->svflags |= SVF_NOCLIENT; + ent->solid = SOLID_NOT; + ent->use = Use_Item; + } + + ent->watertype = gi.pointcontents(ent->s.origin); + gi.linkentity(ent); +} + +/* +=============== +PrecacheItem + +Precaches all data needed for a given item. +This will be called for each item spawned in a level, +and for each item in each client's inventory. +=============== +*/ +void PrecacheItem(gitem_t *it) +{ + const char *s, *start; + char data[MAX_QPATH]; + ptrdiff_t len; + gitem_t *ammo; + + if (!it) + return; + + if (it->pickup_sound) + gi.soundindex(it->pickup_sound); + if (it->world_model) + gi.modelindex(it->world_model); + if (it->view_model) + gi.modelindex(it->view_model); + if (it->icon) + gi.imageindex(it->icon); + + // parse everything for its ammo + if (it->ammo) + { + ammo = GetItemByIndex(it->ammo); + if (ammo != it) + PrecacheItem(ammo); + } + + // parse the space seperated precache string for other items + s = it->precaches; + if (!s || !s[0]) + return; + + while (*s) + { + start = s; + while (*s && *s != ' ') + s++; + + len = s - start; + if (len >= MAX_QPATH || len < 5) + gi.Com_ErrorFmt("PrecacheItem: {} has bad precache string", it->classname); + memcpy(data, start, len); + data[len] = 0; + if (*s) + s++; + + // determine type based on extension + if (!strcmp(data + len - 3, "md2")) + gi.modelindex(data); + else if (!strcmp(data + len - 3, "sp2")) + gi.modelindex(data); + else if (!strcmp(data + len - 3, "wav")) + gi.soundindex(data); + if (!strcmp(data + len - 3, "pcx")) + gi.imageindex(data); + } +} + +/* +============ +SpawnItem + +Sets the clipping size and plants the object on the floor. + +Items can't be immediately dropped to floor, because they might +be on an entity that hasn't spawned yet. +============ +*/ +void SpawnItem(edict_t *ent, gitem_t *item) +{ + // [Sam-KEX] + // Paril: allow all keys to be trigger_spawn'd (N64 uses this + // a few different times) + if (item->flags & IF_KEY) + { + if (ent->spawnflags.has(SPAWNFLAG_ITEM_TRIGGER_SPAWN)) + { + ent->svflags |= SVF_NOCLIENT; + ent->solid = SOLID_NOT; + ent->use = Use_Item; + } + if (ent->spawnflags.has(SPAWNFLAG_ITEM_NO_TOUCH)) + { + ent->solid = SOLID_BBOX; + ent->touch = nullptr; + ent->s.effects &= ~(EF_ROTATE | EF_BOB); + ent->s.renderfx &= ~RF_GLOW; + } + } + else if (ent->spawnflags.value >= SPAWNFLAG_ITEM_MAX.value) // PGM + { + ent->spawnflags = SPAWNFLAG_NONE; + gi.Com_PrintFmt("{} has invalid spawnflags set\n", *ent); + } + + // some items will be prevented in deathmatch + if (deathmatch->integer) + { + // [Kex] In instagib, spawn no pickups! + if (g_instagib->value) + { + if (item->pickup == Pickup_Armor || item->pickup == Pickup_PowerArmor || + item->pickup == Pickup_Powerup || item->pickup == Pickup_Sphere || item->pickup == Pickup_Doppleganger || + (item->flags & IF_HEALTH) || (item->flags & IF_AMMO) || item->pickup == Pickup_Weapon || item->pickup == Pickup_Pack) + { + G_FreeEdict(ent); + return; + } + } + + if (g_no_armor->integer) + { + if (item->pickup == Pickup_Armor || item->pickup == Pickup_PowerArmor) + { + G_FreeEdict(ent); + return; + } + } + if (g_no_items->integer) + { + if (item->pickup == Pickup_Powerup) + { + G_FreeEdict(ent); + return; + } + //===== + // ROGUE + if (item->pickup == Pickup_Sphere) + { + G_FreeEdict(ent); + return; + } + if (item->pickup == Pickup_Doppleganger) + { + G_FreeEdict(ent); + return; + } + // ROGUE + //===== + } + if (g_no_health->integer) + { + if (item->flags & IF_HEALTH) + { + G_FreeEdict(ent); + return; + } + } + if (G_CheckInfiniteAmmo(item)) + { + if (item->flags == IF_AMMO) + { + G_FreeEdict(ent); + return; + } + + // [Paril-KEX] some item swappage + // BFG too strong in Infinite Ammo mode + if (item->id == IT_WEAPON_BFG) + item = GetItemByIndex(IT_WEAPON_DISRUPTOR); + } + + //========== + // ROGUE + if (g_no_mines->integer) + { + if (item->id == IT_WEAPON_PROXLAUNCHER || item->id == IT_AMMO_PROX || item->id == IT_AMMO_TESLA || item->id == IT_AMMO_TRAP) + { + G_FreeEdict(ent); + return; + } + } + if (g_no_nukes->integer) + { + if (item->id == IT_AMMO_NUKE) + { + G_FreeEdict(ent); + return; + } + } + if (g_no_spheres->integer) + { + if (item->pickup == Pickup_Sphere) + { + G_FreeEdict(ent); + return; + } + } + // ROGUE + //========== + } + + //========== + // ROGUE + // DM only items + if (!deathmatch->integer) + { + if (item->pickup == Pickup_Doppleganger || item->pickup == Pickup_Nuke) + { + gi.Com_PrintFmt("{} spawned in non-DM; freeing...\n", *ent); + G_FreeEdict(ent); + return; + } + if ((item->use == Use_Vengeance) || (item->use == Use_Hunter)) + { + gi.Com_PrintFmt("{} spawned in non-DM; freeing...\n", *ent); + G_FreeEdict(ent); + return; + } + } + // ROGUE + //========== + + // [Paril-KEX] power armor breaks infinite ammo + if (G_CheckInfiniteAmmo(item)) + { + if (item->id == IT_ITEM_POWER_SHIELD || item->id == IT_ITEM_POWER_SCREEN) + item = GetItemByIndex(IT_ARMOR_BODY); + } + + // ZOID + // Don't spawn the flags unless enabled + if (!ctf->integer && (item->id == IT_FLAG1 || item->id == IT_FLAG2)) + { + G_FreeEdict(ent); + return; + } + // ZOID + + // set final classname now + ent->classname = item->classname; + + PrecacheItem(item); + + if (coop->integer && (item->id == IT_KEY_POWER_CUBE || item->id == IT_KEY_EXPLOSIVE_CHARGES)) + { + ent->spawnflags.value |= (1 << (8 + level.power_cubes)); + level.power_cubes++; + } + + // mark all items as instanced + if (coop->integer) + { + if (P_UseCoopInstancedItems()) + ent->svflags |= SVF_INSTANCED; + } + + ent->item = item; + ent->nextthink = level.time + 20_hz; // items start after other solids + ent->think = droptofloor; + ent->s.effects = item->world_model_flags; + ent->s.renderfx = RF_GLOW | RF_NO_LOD; + if (ent->model) + gi.modelindex(ent->model); + + if (ent->spawnflags.has(SPAWNFLAG_ITEM_TRIGGER_SPAWN)) + SetTriggeredSpawn(ent); + + // ZOID + // flags are server animated and have special handling + if (item->id == IT_FLAG1 || item->id == IT_FLAG2) + { + ent->think = CTFFlagSetup; + } + // ZOID +} + +void P_ToggleFlashlight(edict_t *ent, bool state) +{ + if (!!(ent->flags & FL_FLASHLIGHT) == state) + return; + + ent->flags ^= FL_FLASHLIGHT; + + gi.sound(ent, CHAN_AUTO, gi.soundindex(ent->flags & FL_FLASHLIGHT ? "items/flashlight_on.wav" : "items/flashlight_off.wav"), 1.f, ATTN_STATIC, 0); +} + +static void Use_Flashlight(edict_t *ent, gitem_t *inv) +{ + P_ToggleFlashlight(ent, !(ent->flags & FL_FLASHLIGHT)); +} + +constexpr size_t MAX_TEMP_POI_POINTS = 128; + +void Compass_Update(edict_t *ent, bool first) +{ + vec3_t *&points = level.poi_points[ent->s.number - 1]; + + // deleted for some reason + if (!points) + return; + + if (!ent->client->help_draw_points) + return; + if (ent->client->help_draw_time >= level.time) + return; + + // don't draw too many points + float distance = (points[ent->client->help_draw_index] - ent->s.origin).length(); + if (distance > 4096 || + !gi.inPHS(ent->s.origin, points[ent->client->help_draw_index], false)) + { + ent->client->help_draw_points = false; + return; + } + + gi.WriteByte(svc_help_path); + gi.WriteByte(first ? 1 : 0); + gi.WritePosition(points[ent->client->help_draw_index]); + + if (ent->client->help_draw_index == ent->client->help_draw_count - 1) + gi.WriteDir((ent->client->help_poi_location - points[ent->client->help_draw_index]).normalized()); + else + gi.WriteDir((points[ent->client->help_draw_index + 1] - points[ent->client->help_draw_index]).normalized()); + gi.unicast(ent, false); + + P_SendLevelPOI(ent); + + gi.local_sound(ent, points[ent->client->help_draw_index], world, CHAN_AUTO, gi.soundindex("misc/help_marker.wav"), 1.0f, ATTN_NORM, 0.0f, GetUnicastKey()); + + // done + if (ent->client->help_draw_index == ent->client->help_draw_count - 1) + { + ent->client->help_draw_points = false; + return; + } + + ent->client->help_draw_index++; + ent->client->help_draw_time = level.time + 200_ms; +} + +static void Use_Compass(edict_t *ent, gitem_t *inv) +{ + if (!level.valid_poi) + { + gi.LocClient_Print(ent, PRINT_HIGH, "$no_valid_poi"); + return; + } + + if (level.current_dynamic_poi) + level.current_dynamic_poi->use(level.current_dynamic_poi, ent, ent); + + ent->client->help_poi_location = level.current_poi; + ent->client->help_poi_image = level.current_poi_image; + + vec3_t *&points = level.poi_points[ent->s.number - 1]; + + if (!points) + points = (vec3_t *) gi.TagMalloc(sizeof(vec3_t) * (MAX_TEMP_POI_POINTS + 1), TAG_LEVEL); + + PathRequest request; + request.start = ent->s.origin; + request.goal = level.current_poi; + request.moveDist = 64.f; + request.pathFlags = PathFlags::All; + request.nodeSearch.ignoreNodeFlags = true; + request.nodeSearch.minHeight = 128.0f; + request.nodeSearch.maxHeight = 128.0f; + request.nodeSearch.radius = 1024.0f; + request.pathPoints.array = points + 1; + request.pathPoints.count = MAX_TEMP_POI_POINTS; + + PathInfo info; + + if (gi.GetPathToGoal(request, info)) + { + // TODO: optimize points? + ent->client->help_draw_points = true; + ent->client->help_draw_count = min((size_t)info.numPathPoints, MAX_TEMP_POI_POINTS); + ent->client->help_draw_index = 1; + + // remove points too close to the player so they don't have to backtrack + for (int i = 1; i < 1 + ent->client->help_draw_count; i++) + { + float distance = (points[i] - ent->s.origin).length(); + if (distance > 192) + { + break; + } + + ent->client->help_draw_index = i; + } + + // create an extra point in front of us if we're facing away from the first real point + float d = ((*(points + ent->client->help_draw_index)) - ent->s.origin).normalized().dot(ent->client->v_forward); + + if (d < 0.3f) + { + vec3_t p = ent->s.origin + (ent->client->v_forward * 64.f); + + trace_t tr = gi.traceline(ent->s.origin + vec3_t{0.f, 0.f, (float) ent->viewheight}, p, nullptr, MASK_SOLID); + + ent->client->help_draw_index--; + ent->client->help_draw_count++; + + if (tr.fraction < 1.0f) + tr.endpos += tr.plane.normal * 8.f; + + *(points + ent->client->help_draw_index) = tr.endpos; + } + + ent->client->help_draw_time = 0_ms; + Compass_Update(ent, true); + } + else + { + P_SendLevelPOI(ent); + gi.local_sound(ent, CHAN_AUTO, gi.soundindex("misc/help_marker.wav"), 1.f, ATTN_NORM, 0, GetUnicastKey()); + } +} + +//====================================================================== + +// clang-format off +gitem_t itemlist[] = +{ + { }, // leave index 0 alone + + // + // ARMOR + // + + +/*QUAKED item_armor_body (.3 .3 1) (-16 -16 -16) (16 16 16) +-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models/items/armor/body/tris.md2" +*/ + { + /* id */ IT_ARMOR_BODY, + /* classname */ "item_armor_body", + /* pickup */ Pickup_Armor, + /* use */ nullptr, + /* drop */ nullptr, + /* weaponthink */ nullptr, + /* pickup_sound */ "misc/ar3_pkup.wav", + /* world_model */ "models/items/armor/body/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ nullptr, + /* icon */ "i_bodyarmor", + /* use_name */ "Body Armor", + /* pickup_name */ "$item_body_armor", + /* pickup_name_definite */ "$item_body_armor_def", + /* quantity */ 0, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_ARMOR, + /* vwep_model */ nullptr, + /* armor_info */ &bodyarmor_info + }, + +/*QUAKED item_armor_combat (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + /* id */ IT_ARMOR_COMBAT, + /* classname */ "item_armor_combat", + /* pickup */ Pickup_Armor, + /* use */ nullptr, + /* drop */ nullptr, + /* weaponthink */ nullptr, + /* pickup_sound */ "misc/ar1_pkup.wav", + /* world_model */ "models/items/armor/combat/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ nullptr, + /* icon */ "i_combatarmor", + /* use_name */ "Combat Armor", + /* pickup_name */ "$item_combat_armor", + /* pickup_name_definite */ "$item_combat_armor_def", + /* quantity */ 0, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_ARMOR, + /* vwep_model */ nullptr, + /* armor_info */ &combatarmor_info + }, + +/*QUAKED item_armor_jacket (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + /* id */ IT_ARMOR_JACKET, + /* classname */ "item_armor_jacket", + /* pickup */ Pickup_Armor, + /* use */ nullptr, + /* drop */ nullptr, + /* weaponthink */ nullptr, + /* pickup_sound */ "misc/ar1_pkup.wav", + /* world_model */ "models/items/armor/jacket/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ nullptr, + /* icon */ "i_jacketarmor", + /* use_name */ "Jacket Armor", + /* pickup_name */ "$item_jacket_armor", + /* pickup_name_definite */ "$item_jacket_armor_def", + /* quantity */ 0, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_ARMOR, + /* vwep_model */ nullptr, + /* armor_info */ &jacketarmor_info + }, + +/*QUAKED item_armor_shard (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + /* id */ IT_ARMOR_SHARD, + /* classname */ "item_armor_shard", + /* pickup */ Pickup_Armor, + /* use */ nullptr, + /* drop */ nullptr, + /* weaponthink */ nullptr, + /* pickup_sound */ "misc/ar2_pkup.wav", + /* world_model */ "models/items/armor/shard/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ nullptr, + /* icon */ "i_armor_shard", + /* use_name */ "Armor Shard", + /* pickup_name */ "$item_armor_shard", + /* pickup_name_definite */ "$item_armor_shard_def", + /* quantity */ 0, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_ARMOR + }, + +/*QUAKED item_power_screen (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + /* id */ IT_ITEM_POWER_SCREEN, + /* classname */ "item_power_screen", + /* pickup */ Pickup_PowerArmor, + /* use */ Use_PowerArmor, + /* drop */ Drop_PowerArmor, + /* weaponthink */ nullptr, + /* pickup_sound */ "misc/ar3_pkup.wav", + /* world_model */ "models/items/armor/screen/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ nullptr, + /* icon */ "i_powerscreen", + /* use_name */ "Power Screen", + /* pickup_name */ "$item_power_screen", + /* pickup_name_definite */ "$item_power_screen_def", + /* quantity */ 60, + /* ammo */ IT_AMMO_CELLS, + /* chain */ IT_NULL, + /* flags */ IF_ARMOR | IF_POWERUP_WHEEL | IF_POWERUP_ONOFF, + /* vwep_model */ nullptr, + /* armor_info */ nullptr, + /* tag */ POWERUP_SCREEN, + /* precaches */ "misc/power2.wav misc/power1.wav" + }, + +/*QUAKED item_power_shield (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + /* id */ IT_ITEM_POWER_SHIELD, + /* classname */ "item_power_shield", + /* pickup */ Pickup_PowerArmor, + /* use */ Use_PowerArmor, + /* drop */ Drop_PowerArmor, + /* weaponthink */ nullptr, + /* pickup_sound */ "misc/ar3_pkup.wav", + /* world_model */ "models/items/armor/shield/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ nullptr, + /* icon */ "i_powershield", + /* use_name */ "Power Shield", + /* pickup_name */ "$item_power_shield", + /* pickup_name_definite */ "$item_power_shield_def", + /* quantity */ 60, + /* ammo */ IT_AMMO_CELLS, + /* chain */ IT_NULL, + /* flags */ IF_ARMOR | IF_POWERUP_WHEEL | IF_POWERUP_ONOFF, + /* vwep_model */ nullptr, + /* armor_info */ nullptr, + /* tag */ POWERUP_SHIELD, + /* precaches */ "misc/power2.wav misc/power1.wav" + }, + + // + // WEAPONS + // + +/* weapon_grapple (.3 .3 1) (-16 -16 -16) (16 16 16) +always owned, never in the world +*/ + { + /* id */ IT_WEAPON_GRAPPLE, + /* classname */ "weapon_grapple", + /* pickup */ nullptr, + /* use */ Use_Weapon, + /* drop */ nullptr, + /* weaponthink */ CTFWeapon_Grapple, + /* pickup_sound */ nullptr, + /* world_model */ nullptr, + /* world_model_flags */ EF_NONE, + /* view_model */ "models/weapons/grapple/tris.md2", + /* icon */ "w_grapple", + /* use_name */ "Grapple", + /* pickup_name */ "$item_grapple", + /* pickup_name_definite */ "$item_grapple_def", + /* quantity */ 0, + /* ammo */ IT_NULL, + /* chain */ IT_WEAPON_BLASTER, + /* flags */ IF_WEAPON | IF_NO_HASTE | IF_POWERUP_WHEEL | IF_NOT_RANDOM, + /* vwep_model */ "#w_grapple.md2", + /* armor_info */ nullptr, + /* tag */ 0, + /* precaches */ "weapons/grapple/grfire.wav weapons/grapple/grpull.wav weapons/grapple/grhang.wav weapons/grapple/grreset.wav weapons/grapple/grhit.wav weapons/grapple/grfly.wav" + }, + +/* weapon_blaster (.3 .3 1) (-16 -16 -16) (16 16 16) +always owned, never in the world +*/ + { + /* id */ IT_WEAPON_BLASTER, + /* classname */ "weapon_blaster", + /* pickup */ Pickup_Weapon, + /* use */ Use_Weapon, + /* drop */ nullptr, + /* weaponthink */ Weapon_Blaster, + /* pickup_sound */ "misc/w_pkup.wav", + /* world_model */ "models/weapons/g_blast/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ "models/weapons/v_blast/tris.md2", + /* icon */ "w_blaster", + /* use_name */ "Blaster", + /* pickup_name */ "$item_blaster", + /* pickup_name_definite */ "$item_blaster_def", + /* quantity */ 0, + /* ammo */ IT_NULL, + /* chain */ IT_WEAPON_BLASTER, + /* flags */ IF_WEAPON | IF_STAY_COOP | IF_NOT_RANDOM, + /* vwep_model */ "#w_blaster.md2", + /* armor_info */ nullptr, + /* tag */ 0, + /* precaches */ "weapons/blastf1a.wav misc/lasfly.wav" + }, + + /*QUAKED weapon_chainfist (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN + */ + { + /* id */ IT_WEAPON_CHAINFIST, + /* classname */ "weapon_chainfist", + /* pickup */ Pickup_Weapon, + /* use */ Use_Weapon, + /* drop */ Drop_Weapon, + /* weaponthink */ Weapon_ChainFist, + /* pickup_sound */ "misc/w_pkup.wav", + /* world_model */ "models/weapons/g_chainf/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ "models/weapons/v_chainf/tris.md2", + /* icon */ "w_chainfist", + /* use_name */ "Chainfist", + /* pickup_name */ "$item_chainfist", + /* pickup_name_definite */ "$item_chainfist_def", + /* quantity */ 0, + /* ammo */ IT_NULL, + /* chain */ IT_WEAPON_BLASTER, + /* flags */ IF_WEAPON | IF_STAY_COOP | IF_NO_HASTE, + /* vwep_model */ "#w_chainfist.md2", + /* armor_info */ nullptr, + /* tag */ 0, + /* precaches */ "weapons/sawidle.wav weapons/sawhit.wav weapons/sawslice.wav", + }, + +/*QUAKED weapon_shotgun (.3 .3 1) (-16 -16 -16) (16 16 16) +-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models/weapons/g_shotg/tris.md2" +*/ + { + /* id */ IT_WEAPON_SHOTGUN, + /* classname */ "weapon_shotgun", + /* pickup */ Pickup_Weapon, + /* use */ Use_Weapon, + /* drop */ Drop_Weapon, + /* weaponthink */ Weapon_Shotgun, + /* pickup_sound */ "misc/w_pkup.wav", + /* world_model */ "models/weapons/g_shotg/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ "models/weapons/v_shotg/tris.md2", + /* icon */ "w_shotgun", + /* use_name */ "Shotgun", + /* pickup_name */ "$item_shotgun", + /* pickup_name_definite */ "$item_shotgun_def", + /* quantity */ 1, + /* ammo */ IT_AMMO_SHELLS, + /* chain */ IT_NULL, + /* flags */ IF_WEAPON | IF_STAY_COOP, + /* vwep_model */ "#w_shotgun.md2", + /* armor_info */ nullptr, + /* tag */ 0, + /* precaches */ "weapons/shotgf1b.wav weapons/shotgr1b.wav" + }, + +/*QUAKED weapon_supershotgun (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + /* id */ IT_WEAPON_SSHOTGUN, + /* classname */ "weapon_supershotgun", + /* pickup */ Pickup_Weapon, + /* use */ Use_Weapon, + /* drop */ Drop_Weapon, + /* weaponthink */ Weapon_SuperShotgun, + /* pickup_sound */ "misc/w_pkup.wav", + /* world_model */ "models/weapons/g_shotg2/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ "models/weapons/v_shotg2/tris.md2", + /* icon */ "w_sshotgun", + /* use_name */ "Super Shotgun", + /* pickup_name */ "$item_super_shotgun", + /* pickup_name_definite */ "$item_super_shotgun_def", + /* quantity */ 2, + /* ammo */ IT_AMMO_SHELLS, + /* chain */ IT_NULL, + /* flags */ IF_WEAPON | IF_STAY_COOP, + /* vwep_model */ "#w_sshotgun.md2", + /* armor_info */ nullptr, + /* tag */ 0, + /* precaches */ "weapons/sshotf1b.wav", + /* sort_id */ 0, + /* quantity_warn */ 10 + }, + +/*QUAKED weapon_machinegun (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + /* id */ IT_WEAPON_MACHINEGUN, + /* classname */ "weapon_machinegun", + /* pickup */ Pickup_Weapon, + /* use */ Use_Weapon, + /* drop */ Drop_Weapon, + /* weaponthink */ Weapon_Machinegun, + /* pickup_sound */ "misc/w_pkup.wav", + /* world_model */ "models/weapons/g_machn/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ "models/weapons/v_machn/tris.md2", + /* icon */ "w_machinegun", + /* use_name */ "Machinegun", + /* pickup_name */ "$item_machinegun", + /* pickup_name_definite */ "$item_machinegun_def", + /* quantity */ 1, + /* ammo */ IT_AMMO_BULLETS, + /* chain */ IT_WEAPON_MACHINEGUN, + /* flags */ IF_WEAPON | IF_STAY_COOP, + /* vwep_model */ "#w_machinegun.md2", + /* armor_info */ nullptr, + /* tag */ 0, + /* precaches */ "weapons/machgf1b.wav weapons/machgf2b.wav weapons/machgf3b.wav weapons/machgf4b.wav weapons/machgf5b.wav", + /* sort_id */ 0, + /* quantity_warn */ 30 + }, + + // ROGUE +/*QUAKED weapon_etf_rifle (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN +*/ + { + /* id */ IT_WEAPON_ETF_RIFLE, + /* classname */ "weapon_etf_rifle", + /* pickup */ Pickup_Weapon, + /* use */ Use_Weapon, + /* drop */ Drop_Weapon, + /* weaponthink */ Weapon_ETF_Rifle, + /* pickup_sound */ "misc/w_pkup.wav", + /* world_model */ "models/weapons/g_etf_rifle/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ "models/weapons/v_etf_rifle/tris.md2", + /* icon */ "w_etf_rifle", + /* use_name */ "ETF Rifle", + /* pickup_name */ "$item_etf_rifle", + /* pickup_name_definite */ "$item_etf_rifle_def", + /* quantity */ 1, + /* ammo */ IT_AMMO_FLECHETTES, + /* chain */ IT_WEAPON_MACHINEGUN, + /* flags */ IF_WEAPON | IF_STAY_COOP, + /* vwep_model */ "#w_etfrifle.md2", + /* armor_info */ nullptr, + /* tag */ 0, + /* precaches */ "weapons/nail1.wav models/proj/flechette/tris.md2", + /* sort_id */ 0, + /* quantity_warn */ 30 + }, + // ROGUE + +/*QUAKED weapon_chaingun (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + /* id */ IT_WEAPON_CHAINGUN, + /* classname */ "weapon_chaingun", + /* pickup */ Pickup_Weapon, + /* use */ Use_Weapon, + /* drop */ Drop_Weapon, + /* weaponthink */ Weapon_Chaingun, + /* pickup_sound */ "misc/w_pkup.wav", + /* world_model */ "models/weapons/g_chain/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ "models/weapons/v_chain/tris.md2", + /* icon */ "w_chaingun", + /* use_name */ "Chaingun", + /* pickup_name */ "$item_chaingun", + /* pickup_name_definite */ "$item_chaingun_def", + /* quantity */ 1, + /* ammo */ IT_AMMO_BULLETS, + /* chain */ IT_NULL, + /* flags */ IF_WEAPON | IF_STAY_COOP, + /* vwep_model */ "#w_chaingun.md2", + /* armor_info */ nullptr, + /* tag */ 0, + /* precaches */ "weapons/chngnu1a.wav weapons/chngnl1a.wav weapons/machgf3b.wav weapons/chngnd1a.wav", + /* sort_id */ 0, + /* quantity_warn */ 60 + }, + +/*QUAKED ammo_grenades (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + /* id */ IT_AMMO_GRENADES, + /* classname */ "ammo_grenades", + /* pickup */ Pickup_Ammo, + /* use */ Use_Weapon, + /* drop */ Drop_Ammo, + /* weaponthink */ Weapon_Grenade, + /* pickup_sound */ "misc/am_pkup.wav", + /* world_model */ "models/items/ammo/grenades/medium/tris.md2", + /* world_model_flags */ EF_NONE, + /* view_model */ "models/weapons/v_handgr/tris.md2", + /* icon */ "a_grenades", + /* use_name */ "Grenades", + /* pickup_name */ "$item_grenades", + /* pickup_name_definite */ "$item_grenades_def", + /* quantity */ 5, + /* ammo */ IT_AMMO_GRENADES, + /* chain */ IT_AMMO_GRENADES, + /* flags */ IF_AMMO | IF_WEAPON, + /* vwep_model */ "#a_grenades.md2", + /* armor_info */ nullptr, + /* tag */ AMMO_GRENADES, + /* precaches */ "weapons/hgrent1a.wav weapons/hgrena1b.wav weapons/hgrenc1b.wav weapons/hgrenb1a.wav weapons/hgrenb2a.wav models/objects/grenade3/tris.md2", + /* sort_id */ 0, + /* quantity_warn */ 2 + }, + +// RAFAEL +/*QUAKED ammo_trap (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + /* id */ IT_AMMO_TRAP, + /* classname */ "ammo_trap", + /* pickup */ Pickup_Ammo, + /* use */ Use_Weapon, + /* drop */ Drop_Ammo, + /* weaponthink */ Weapon_Trap, + /* pickup_sound */ "misc/am_pkup.wav", + /* world_model */ "models/weapons/g_trap/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ "models/weapons/v_trap/tris.md2", + /* icon */ "a_trap", + /* use_name */ "Trap", + /* pickup_name */ "$item_trap", + /* pickup_name_definite */ "$item_trap_def", + /* quantity */ 1, + /* ammo */ IT_AMMO_TRAP, + /* chain */ IT_AMMO_GRENADES, + /* flags */ IF_AMMO | IF_WEAPON | IF_NO_INFINITE_AMMO, + /* vwep_model */ "#a_trap.md2", + /* armor_info */ nullptr, + /* tag */ AMMO_TRAP, + /* precaches */ "misc/fhit3.wav weapons/trapcock.wav weapons/traploop.wav weapons/trapsuck.wav weapons/trapdown.wav items/s_health.wav items/n_health.wav items/l_health.wav items/m_health.wav models/weapons/z_trap/tris.md2", + /* sort_id */ 0, + /* quantity_warn */ 1 + }, +// RAFAEL + +/*QUAKED ammo_tesla (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN +*/ + { + /* id */ IT_AMMO_TESLA, + /* classname */ "ammo_tesla", + /* pickup */ Pickup_Ammo, + /* use */ Use_Weapon, + /* drop */ Drop_Ammo, + /* weaponthink */ Weapon_Tesla, + /* pickup_sound */ "misc/am_pkup.wav", + /* world_model */ "models/ammo/am_tesl/tris.md2", + /* world_model_flags */ EF_NONE, + /* view_model */ "models/weapons/v_tesla/tris.md2", + /* icon */ "a_tesla", + /* use_name */ "Tesla", + /* pickup_name */ "$item_tesla", + /* pickup_name_definite */ "$item_tesla_def", + /* quantity */ 3, + /* ammo */ IT_AMMO_TESLA, + /* chain */ IT_AMMO_GRENADES, + /* flags */ IF_AMMO | IF_WEAPON | IF_NO_INFINITE_AMMO, + /* vwep_model */ "#a_tesla.md2", + /* armor_info */ nullptr, + /* tag */ AMMO_TESLA, + /* precaches */ "weapons/teslaopen.wav weapons/hgrenb1a.wav weapons/hgrenb2a.wav models/weapons/g_tesla/tris.md2", + /* sort_id */ 0, + /* quantity_warn */ 1 + }, + +/*QUAKED weapon_grenadelauncher (.3 .3 1) (-16 -16 -16) (16 16 16) +-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models/weapons/g_launch/tris.md2" +*/ + { + /* id */ IT_WEAPON_GLAUNCHER, + /* classname */ "weapon_grenadelauncher", + /* pickup */ Pickup_Weapon, + /* use */ Use_Weapon, + /* drop */ Drop_Weapon, + /* weaponthink */ Weapon_GrenadeLauncher, + /* pickup_sound */ "misc/w_pkup.wav", + /* world_model */ "models/weapons/g_launch/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ "models/weapons/v_launch/tris.md2", + /* icon */ "w_glauncher", + /* use_name */ "Grenade Launcher", + /* pickup_name */ "$item_grenade_launcher", + /* pickup_name_definite */ "$item_grenade_launcher_def", + /* quantity */ 1, + /* ammo */ IT_AMMO_GRENADES, + /* chain */ IT_WEAPON_GLAUNCHER, + /* flags */ IF_WEAPON | IF_STAY_COOP, + /* vwep_model */ "#w_glauncher.md2", + /* armor_info */ nullptr, + /* tag */ 0, + /* precaches */ "models/objects/grenade4/tris.md2 weapons/grenlf1a.wav weapons/grenlr1b.wav weapons/grenlb1b.wav" + }, + + // ROGUE +/*QUAKED weapon_proxlauncher (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN +*/ + { + /* id */ IT_WEAPON_PROXLAUNCHER, + /* classname */ "weapon_proxlauncher", + /* pickup */ Pickup_Weapon, + /* use */ Use_Weapon, + /* drop */ Drop_Weapon, + /* weaponthink */ Weapon_ProxLauncher, + /* pickup_sound */ "misc/w_pkup.wav", + /* world_model */ "models/weapons/g_plaunch/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ "models/weapons/v_plaunch/tris.md2", + /* icon */ "w_proxlaunch", + /* use_name */ "Prox Launcher", + /* pickup_name */ "$item_prox_launcher", + /* pickup_name_definite */ "$item_prox_launcher_def", + /* quantity */ 1, + /* ammo */ IT_AMMO_PROX, + /* chain */ IT_WEAPON_GLAUNCHER, + /* flags */ IF_WEAPON | IF_STAY_COOP, + /* vwep_model */ "#w_plauncher.md2", + /* armor_info */ nullptr, + /* tag */ AMMO_PROX, + /* precaches */ "weapons/grenlf1a.wav weapons/grenlr1b.wav weapons/grenlb1b.wav weapons/proxwarn.wav weapons/proxopen.wav", + }, + // ROGUE + +/*QUAKED weapon_rocketlauncher (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + /* id */ IT_WEAPON_RLAUNCHER, + /* classname */ "weapon_rocketlauncher", + /* pickup */ Pickup_Weapon, + /* use */ Use_Weapon, + /* drop */ Drop_Weapon, + /* weaponthink */ Weapon_RocketLauncher, + /* pickup_sound */ "misc/w_pkup.wav", + /* world_model */ "models/weapons/g_rocket/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ "models/weapons/v_rocket/tris.md2", + /* icon */ "w_rlauncher", + /* use_name */ "Rocket Launcher", + /* pickup_name */ "$item_rocket_launcher", + /* pickup_name_definite */ "$item_rocket_launcher_def", + /* quantity */ 1, + /* ammo */ IT_AMMO_ROCKETS, + /* chain */ IT_NULL, + /* flags */ IF_WEAPON|IF_STAY_COOP, + /* vwep_model */ "#w_rlauncher.md2", + /* armor_info */ nullptr, + /* tag */ 0, + /* precaches */ "models/objects/rocket/tris.md2 weapons/rockfly.wav weapons/rocklf1a.wav weapons/rocklr1b.wav models/objects/debris2/tris.md2" + }, + +/*QUAKED weapon_hyperblaster (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + /* id */ IT_WEAPON_HYPERBLASTER, + /* classname */ "weapon_hyperblaster", + /* pickup */ Pickup_Weapon, + /* use */ Use_Weapon, + /* drop */ Drop_Weapon, + /* weaponthink */ Weapon_HyperBlaster, + /* pickup_sound */ "misc/w_pkup.wav", + /* world_model */ "models/weapons/g_hyperb/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ "models/weapons/v_hyperb/tris.md2", + /* icon */ "w_hyperblaster", + /* use_name */ "HyperBlaster", + /* pickup_name */ "$item_hyperblaster", + /* pickup_name_definite */ "$item_hyperblaster_def", + /* quantity */ 1, + /* ammo */ IT_AMMO_CELLS, + /* chain */ IT_WEAPON_HYPERBLASTER, + /* flags */ IF_WEAPON|IF_STAY_COOP, + /* vwep_model */ "#w_hyperblaster.md2", + /* armor_info */ nullptr, + /* tag */ 0, + /* precaches */ "weapons/hyprbu1a.wav weapons/hyprbl1a.wav weapons/hyprbf1a.wav weapons/hyprbd1a.wav misc/lasfly.wav", + /* sort_id */ 0, + /* quantity_warn */ 30 + }, + +// RAFAEL +/*QUAKED weapon_boomer (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + /* id */ IT_WEAPON_IONRIPPER, + /* classname */ "weapon_boomer", + /* pickup */ Pickup_Weapon, + /* use */ Use_Weapon, + /* drop */ Drop_Weapon, + /* weaponthink */ Weapon_Ionripper, + /* pickup_sound */ "misc/w_pkup.wav", + /* world_model */ "models/weapons/g_boom/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ "models/weapons/v_boomer/tris.md2", + /* icon */ "w_ripper", + /* use_name */ "Ionripper", + /* pickup_name */ "$item_ionripper", + /* pickup_name_definite */ "$item_ionripper_def", + /* quantity */ 2, + /* ammo */ IT_AMMO_CELLS, + /* chain */ IT_WEAPON_HYPERBLASTER, + /* flags */ IF_WEAPON | IF_STAY_COOP, + /* vwep_model */ "#w_ripper.md2", + /* armor_info */ nullptr, + /* tag */ 0, + /* precaches */ "weapons/rippfire.wav models/objects/boomrang/tris.md2 misc/lasfly.wav", + /* sort_id */ 0, + /* quantity_warn */ 30 + }, +// RAFAEL + +// ROGUE + /*QUAKED weapon_plasmabeam (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN + */ + { + /* id */ IT_WEAPON_PLASMABEAM, + /* classname */ "weapon_plasmabeam", + /* pickup */ Pickup_Weapon, + /* use */ Use_Weapon, + /* drop */ Drop_Weapon, + /* weaponthink */ Weapon_Heatbeam, + /* pickup_sound */ "misc/w_pkup.wav", + /* world_model */ "models/weapons/g_beamer/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ "models/weapons/v_beamer/tris.md2", + /* icon */ "w_heatbeam", + /* use_name */ "Plasma Beam", + /* pickup_name */ "$item_plasma_beam", + /* pickup_name_definite */ "$item_plasma_beam_def", + /* quantity */ 2, + /* ammo */ IT_AMMO_CELLS, + /* chain */ IT_WEAPON_HYPERBLASTER, + /* flags */ IF_WEAPON | IF_STAY_COOP, + /* vwep_model */ "#w_plasma.md2", + /* armor_info */ nullptr, + /* tag */ 0, + /* precaches */ "weapons/bfg__l1a.wav", + /* sort_id */ 0, + /* quantity_warn */ 50 + }, +//rogue + +/*QUAKED weapon_railgun (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + /* id */ IT_WEAPON_RAILGUN, + /* classname */ "weapon_railgun", + /* pickup */ Pickup_Weapon, + /* use */ Use_Weapon, + /* drop */ Drop_Weapon, + /* weaponthink */ Weapon_Railgun, + /* pickup_sound */ "misc/w_pkup.wav", + /* world_model */ "models/weapons/g_rail/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ "models/weapons/v_rail/tris.md2", + /* icon */ "w_railgun", + /* use_name */ "Railgun", + /* pickup_name */ "$item_railgun", + /* pickup_name_definite */ "$item_railgun_def", + /* quantity */ 1, + /* ammo */ IT_AMMO_SLUGS, + /* chain */ IT_WEAPON_RAILGUN, + /* flags */ IF_WEAPON|IF_STAY_COOP, + /* vwep_model */ "#w_railgun.md2", + /* armor_info */ nullptr, + /* tag */ 0, + /* precaches */ "weapons/rg_hum.wav" + }, + +// RAFAEL 14-APR-98 +/*QUAKED weapon_phalanx (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + /* id */ IT_WEAPON_PHALANX, + /* classname */ "weapon_phalanx", + /* pickup */ Pickup_Weapon, + /* use */ Use_Weapon, + /* drop */ Drop_Weapon, + /* weaponthink */ Weapon_Phalanx, + /* pickup_sound */ "misc/w_pkup.wav", + /* world_model */ "models/weapons/g_shotx/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ "models/weapons/v_shotx/tris.md2", + /* icon */ "w_phallanx", + /* use_name */ "Phalanx", + /* pickup_name */ "$item_phalanx", + /* pickup_name_definite */ "$item_phalanx_def", + /* quantity */ 1, + /* ammo */ IT_AMMO_MAGSLUG, + /* chain */ IT_WEAPON_RAILGUN, + /* flags */ IF_WEAPON | IF_STAY_COOP, + /* vwep_model */ "#w_phalanx.md2", + /* armor_info */ nullptr, + /* tag */ 0, + /* precaches */ "weapons/plasshot.wav sprites/s_photon.sp2 weapons/rockfly.wav" + }, +// RAFAEL + +/*QUAKED weapon_bfg (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + /* id */ IT_WEAPON_BFG, + /* classname */ "weapon_bfg", + /* pickup */ Pickup_Weapon, + /* use */ Use_Weapon, + /* drop */ Drop_Weapon, + /* weaponthink */ Weapon_BFG, + /* pickup_sound */ "misc/w_pkup.wav", + /* world_model */ "models/weapons/g_bfg/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ "models/weapons/v_bfg/tris.md2", + /* icon */ "w_bfg", + /* use_name */ "BFG10K", + /* pickup_name */ "$item_bfg10k", + /* pickup_name_definite */ "$item_bfg10k_def", + /* quantity */ 50, + /* ammo */ IT_AMMO_CELLS, + /* chain */ IT_WEAPON_BFG, + /* flags */ IF_WEAPON|IF_STAY_COOP, + /* vwep_model */ "#w_bfg.md2", + /* armor_info */ nullptr, + /* tag */ 0, + /* precaches */ "sprites/s_bfg1.sp2 sprites/s_bfg2.sp2 sprites/s_bfg3.sp2 weapons/bfg__f1y.wav weapons/bfg__l1a.wav weapons/bfg__x1b.wav weapons/bfg_hum.wav", + /* sort_id */ 0, + /* quantity_warn */ 50 + }, + + // ========================= + // ROGUE WEAPONS + /*QUAKED weapon_disintegrator (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN + */ + { + /* id */ IT_WEAPON_DISRUPTOR, + /* classname */ "weapon_disintegrator", + /* pickup */ Pickup_Weapon, + /* use */ Use_Weapon, + /* drop */ Drop_Weapon, + /* weaponthink */ Weapon_Disintegrator, + /* pickup_sound */ "misc/w_pkup.wav", + /* world_model */ "models/weapons/g_dist/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ "models/weapons/v_dist/tris.md2", + /* icon */ "w_disintegrator", + /* use_name */ "Disruptor", + /* pickup_name */ "$item_disruptor", + /* pickup_name_definite */ "$item_disruptor_def", + /* quantity */ 1, + /* ammo */ IT_AMMO_ROUNDS, + /* chain */ IT_WEAPON_BFG, + /* flags */ IF_WEAPON | IF_STAY_COOP, + /* vwep_model */ "#w_disrupt.md2", + /* armor_info */ nullptr, + /* tag */ 0, + /* precaches */ "models/proj/disintegrator/tris.md2 weapons/disrupt.wav weapons/disint2.wav weapons/disrupthit.wav", + }, + + // ROGUE WEAPONS + // ========================= + +#if 0 // sorry little guy + { + /* id */ IT_WEAPON_DISINTEGRATOR, + /* classname */ "weapon_beta_disintegrator", + /* pickup */ Pickup_Weapon, + /* use */ Use_Weapon, + /* drop */ Drop_Weapon, + /* weaponthink */ Weapon_Beta_Disintegrator, + /* pickup_sound */ "misc/w_pkup.wav", + /* world_model */ "models/weapons/g_disint/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ "models/weapons/v_disint/tris.md2", + /* icon */ "w_bfg", + /* use_name */ "Disintegrator", + /* pickup_name */ "$item_disintegrator", + /* pickup_name_definite */ "$item_disintegrator_def", + /* quantity */ 1, + /* ammo */ IT_AMMO_ROUNDS, + /* chain */ IT_WEAPON_BFG, + /* flags */ IF_WEAPON | IF_STAY_COOP, + /* vwep_model */ "#w_bfg.md2", + /* armor_info */ nullptr, + /* tag */ 0, + /* precaches */ "", + }, +#endif + + // + // AMMO ITEMS + // + +/*QUAKED ammo_shells (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + /* id */ IT_AMMO_SHELLS, + /* classname */ "ammo_shells", + /* pickup */ Pickup_Ammo, + /* use */ nullptr, + /* drop */ Drop_Ammo, + /* weaponthink */ nullptr, + /* pickup_sound */ "misc/am_pkup.wav", + /* world_model */ "models/items/ammo/shells/medium/tris.md2", + /* world_model_flags */ EF_NONE, + /* view_model */ nullptr, + /* icon */ "a_shells", + /* use_name */ "Shells", + /* pickup_name */ "$item_shells", + /* pickup_name_definite */ "$item_shells_def", + /* quantity */ 10, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_AMMO, + /* vwep_model */ nullptr, + /* armor_info */ nullptr, + /* tag */ AMMO_SHELLS + }, + +/*QUAKED ammo_bullets (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + /* id */ IT_AMMO_BULLETS, + /* classname */ "ammo_bullets", + /* pickup */ Pickup_Ammo, + /* use */ nullptr, + /* drop */ Drop_Ammo, + /* weaponthink */ nullptr, + /* pickup_sound */ "misc/am_pkup.wav", + /* world_model */ "models/items/ammo/bullets/medium/tris.md2", + /* world_model_flags */ EF_NONE, + /* view_model */ nullptr, + /* icon */ "a_bullets", + /* use_name */ "Bullets", + /* pickup_name */ "$item_bullets", + /* pickup_name_definite */ "$item_bullets_def", + /* quantity */ 50, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_AMMO, + /* vwep_model */ nullptr, + /* armor_info */ nullptr, + /* tag */ AMMO_BULLETS + }, + +/*QUAKED ammo_cells (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + /* id */ IT_AMMO_CELLS, + /* classname */ "ammo_cells", + /* pickup */ Pickup_Ammo, + /* use */ nullptr, + /* drop */ Drop_Ammo, + /* weaponthink */ nullptr, + /* pickup_sound */ "misc/am_pkup.wav", + /* world_model */ "models/items/ammo/cells/medium/tris.md2", + /* world_model_flags */ EF_NONE, + /* view_model */ nullptr, + /* icon */ "a_cells", + /* use_name */ "Cells", + /* pickup_name */ "$item_cells", + /* pickup_name_definite */ "$item_cells_def", + /* quantity */ 50, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_AMMO, + /* vwep_model */ nullptr, + /* armor_info */ nullptr, + /* tag */ AMMO_CELLS + }, + +/*QUAKED ammo_rockets (.3 .3 1) (-16 -16 -16) (16 16 16) +model="models/items/ammo/rockets/medium/tris.md2" +*/ + { + /* id */ IT_AMMO_ROCKETS, + /* classname */ "ammo_rockets", + /* pickup */ Pickup_Ammo, + /* use */ nullptr, + /* drop */ Drop_Ammo, + /* weaponthink */ nullptr, + /* pickup_sound */ "misc/am_pkup.wav", + /* world_model */ "models/items/ammo/rockets/medium/tris.md2", + /* world_model_flags */ EF_NONE, + /* view_model */ nullptr, + /* icon */ "a_rockets", + /* use_name */ "Rockets", + /* pickup_name */ "$item_rockets", + /* pickup_name_definite */ "$item_rockets_def", + /* quantity */ 5, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_AMMO, + /* vwep_model */ nullptr, + /* armor_info */ nullptr, + /* tag */ AMMO_ROCKETS + }, + +/*QUAKED ammo_slugs (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + /* id */ IT_AMMO_SLUGS, + /* classname */ "ammo_slugs", + /* pickup */ Pickup_Ammo, + /* use */ nullptr, + /* drop */ Drop_Ammo, + /* weaponthink */ nullptr, + /* pickup_sound */ "misc/am_pkup.wav", + /* world_model */ "models/items/ammo/slugs/medium/tris.md2", + /* world_model_flags */ EF_NONE, + /* view_model */ nullptr, + /* icon */ "a_slugs", + /* use_name */ "Slugs", + /* pickup_name */ "$item_slugs", + /* pickup_name_definite */ "$item_slugs_def", + /* quantity */ 10, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_AMMO, + /* vwep_model */ nullptr, + /* armor_info */ nullptr, + /* tag */ AMMO_SLUGS + }, + +// RAFAEL +/*QUAKED ammo_magslug (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + /* id */ IT_AMMO_MAGSLUG, + /* classname */ "ammo_magslug", + /* pickup */ Pickup_Ammo, + /* use */ nullptr, + /* drop */ Drop_Ammo, + /* weaponthink */ nullptr, + /* pickup_sound */ "misc/am_pkup.wav", + /* world_model */ "models/objects/ammo/tris.md2", + /* world_model_flags */ EF_NONE, + /* view_model */ nullptr, + /* icon */ "a_mslugs", + /* use_name */ "Mag Slug", + /* pickup_name */ "$item_mag_slug", + /* pickup_name_definite */ "$item_mag_slug_def", + /* quantity */ 10, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_AMMO, + /* vwep_model */ nullptr, + /* armor_info */ nullptr, + /* tag */ AMMO_MAGSLUG + }, +// RAFAEL + +// ======================================= +// ROGUE AMMO + +/*QUAKED ammo_flechettes (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN +*/ + { + /* id */ IT_AMMO_FLECHETTES, + /* classname */ "ammo_flechettes", + /* pickup */ Pickup_Ammo, + /* use */ nullptr, + /* drop */ Drop_Ammo, + /* weaponthink */ nullptr, + /* pickup_sound */ "misc/am_pkup.wav", + /* world_model */ "models/ammo/am_flechette/tris.md2", + /* world_model_flags */ EF_NONE, + /* view_model */ nullptr, + /* icon */ "a_flechettes", + /* use_name */ "Flechettes", + /* pickup_name */ "$item_flechettes", + /* pickup_name_definite */ "$item_flechettes_def", + /* quantity */ 50, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_AMMO, + /* vwep_model */ nullptr, + /* armor_info */ nullptr, + /* tag */ AMMO_FLECHETTES + }, + +/*QUAKED ammo_prox (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN +*/ + { + /* id */ IT_AMMO_PROX, + /* classname */ "ammo_prox", + /* pickup */ Pickup_Ammo, + /* use */ nullptr, + /* drop */ Drop_Ammo, + /* weaponthink */ nullptr, + /* pickup_sound */ "misc/am_pkup.wav", + /* world_model */ "models/ammo/am_prox/tris.md2", + /* world_model_flags */ EF_NONE, + /* view_model */ nullptr, + /* icon */ "a_prox", + /* use_name */ "Prox", + /* pickup_name */ "$item_prox", + /* pickup_name_definite */ "$item_prox_def", + /* quantity */ 5, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_AMMO, + /* vwep_model */ nullptr, + /* armor_info */ nullptr, + /* tag */ AMMO_PROX, + /* precaches */ "models/weapons/g_prox/tris.md2 weapons/proxwarn.wav" + }, + +/*QUAKED ammo_nuke (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN +*/ + { + /* id */ IT_AMMO_NUKE, + /* classname */ "ammo_nuke", + /* pickup */ Pickup_Nuke, + /* use */ Use_Nuke, + /* drop */ Drop_Ammo, + /* weaponthink */ nullptr, + /* pickup_sound */ "misc/am_pkup.wav", + /* world_model */ "models/weapons/g_nuke/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ nullptr, + /* icon */ "p_nuke", + /* use_name */ "A-M Bomb", + /* pickup_name */ "$item_am_bomb", + /* pickup_name_definite */ "$item_am_bomb_def", + /* quantity */ 300, + /* ammo */ IT_AMMO_NUKE, + /* chain */ IT_NULL, + /* flags */ IF_POWERUP | IF_POWERUP_WHEEL, + /* vwep_model */ nullptr, + /* armor_info */ nullptr, + /* tag */ POWERUP_AM_BOMB, + /* precaches */ "weapons/nukewarn2.wav world/rumble.wav" + }, + +/*QUAKED ammo_disruptor (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN +*/ + { + /* id */ IT_AMMO_ROUNDS, + /* classname */ "ammo_disruptor", + /* pickup */ Pickup_Ammo, + /* use */ nullptr, + /* drop */ Drop_Ammo, + /* weaponthink */ nullptr, + /* pickup_sound */ "misc/am_pkup.wav", + /* world_model */ "models/ammo/am_disr/tris.md2", + /* world_model_flags */ EF_NONE, + /* view_model */ nullptr, + /* icon */ "a_disruptor", + /* use_name */ "Rounds", + /* pickup_name */ "$item_rounds", + /* pickup_name_definite */ "$item_rounds_def", + /* quantity */ 3, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_AMMO, + /* vwep_model */ nullptr, + /* armor_info */ nullptr, + /* tag */ AMMO_DISRUPTOR + }, +// ROGUE AMMO +// ======================================= + + // + // POWERUP ITEMS + // +/*QUAKED item_quad (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + /* id */ IT_ITEM_QUAD, + /* classname */ "item_quad", + /* pickup */ Pickup_Powerup, + /* use */ Use_Quad, + /* drop */ Drop_General, + /* weaponthink */ nullptr, + /* pickup_sound */ "items/pkup.wav", + /* world_model */ "models/items/quaddama/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ nullptr, + /* icon */ "p_quad", + /* use_name */ "Quad Damage", + /* pickup_name */ "$item_quad_damage", + /* pickup_name_definite */ "$item_quad_damage_def", + /* quantity */ 60, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_POWERUP | IF_POWERUP_WHEEL, + /* vwep_model */ nullptr, + /* armor_info */ nullptr, + /* tag */ POWERUP_QUAD, + /* precaches */ "items/damage.wav items/damage2.wav items/damage3.wav ctf/tech2x.wav" + }, + +// RAFAEL +/*QUAKED item_quadfire (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + /* id */ IT_ITEM_QUADFIRE, + /* classname */ "item_quadfire", + /* pickup */ Pickup_Powerup, + /* use */ Use_QuadFire, + /* drop */ Drop_General, + /* weaponthink */ nullptr, + /* pickup_sound */ "items/pkup.wav", + /* world_model */ "models/items/quadfire/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ nullptr, + /* icon */ "p_quadfire", + /* use_name */ "DualFire Damage", + /* pickup_name */ "$item_dualfire_damage", + /* pickup_name_definite */ "$item_dualfire_damage_def", + /* quantity */ 60, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_POWERUP | IF_POWERUP_WHEEL, + /* vwep_model */ nullptr, + /* armor_info */ nullptr, + /* tag */ POWERUP_QUADFIRE, + /* precaches */ "items/quadfire1.wav items/quadfire2.wav items/quadfire3.wav" + }, +// RAFAEL + +/*QUAKED item_invulnerability (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + /* id */ IT_ITEM_INVULNERABILITY, + /* classname */ "item_invulnerability", + /* pickup */ Pickup_Powerup, + /* use */ Use_Invulnerability, + /* drop */ Drop_General, + /* weaponthink */ nullptr, + /* pickup_sound */ "items/pkup.wav", + /* world_model */ "models/items/invulner/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ nullptr, + /* icon */ "p_invulnerability", + /* use_name */ "Invulnerability", + /* pickup_name */ "$item_invulnerability", + /* pickup_name_definite */ "$item_invulnerability_def", + /* quantity */ 300, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_POWERUP | IF_POWERUP_WHEEL, + /* vwep_model */ nullptr, + /* armor_info */ nullptr, + /* tag */ POWERUP_INVULNERABILITY, + /* precaches */ "items/protect.wav items/protect2.wav items/protect4.wav" + }, + +/*QUAKED item_invisibility (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + /* id */ IT_ITEM_INVISIBILITY, + /* classname */ "item_invisibility", + /* pickup */ Pickup_Powerup, + /* use */ Use_Invisibility, + /* drop */ Drop_General, + /* weaponthink */ nullptr, + /* pickup_sound */ "items/pkup.wav", + /* world_model */ "models/items/cloaker/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ nullptr, + /* icon */ "p_cloaker", + /* use_name */ "Invisibility", + /* pickup_name */ "$item_invisibility", + /* pickup_name_definite */ "$item_invisibility_def", + /* quantity */ 300, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_POWERUP | IF_POWERUP_WHEEL, + /* vwep_model */ nullptr, + /* armor_info */ nullptr, + /* tag */ POWERUP_INVISIBILITY, + }, + +/*QUAKED item_silencer (.3 .3 1) (-16 -16 -16) (16 16 16) +model="models/items/silencer/tris.md2" +*/ + { + /* id */ IT_ITEM_SILENCER, + /* classname */ "item_silencer", + /* pickup */ Pickup_Powerup, + /* use */ Use_Silencer, + /* drop */ Drop_General, + /* weaponthink */ nullptr, + /* pickup_sound */ "items/pkup.wav", + /* world_model */ "models/items/silencer/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ nullptr, + /* icon */ "p_silencer", + /* use_name */ "Silencer", + /* pickup_name */ "$item_silencer", + /* pickup_name_definite */ "$item_silencer_def", + /* quantity */ 60, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_POWERUP | IF_POWERUP_WHEEL, + /* vwep_model */ nullptr, + /* armor_info */ nullptr, + /* tag */ POWERUP_SILENCER, + }, + +/*QUAKED item_breather (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + /* id */ IT_ITEM_REBREATHER, + /* classname */ "item_breather", + /* pickup */ Pickup_Powerup, + /* use */ Use_Breather, + /* drop */ Drop_General, + /* weaponthink */ nullptr, + /* pickup_sound */ "items/pkup.wav", + /* world_model */ "models/items/breather/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ nullptr, + /* icon */ "p_rebreather", + /* use_name */ "Rebreather", + /* pickup_name */ "$item_rebreather", + /* pickup_name_definite */ "$item_rebreather_def", + /* quantity */ 60, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_STAY_COOP|IF_POWERUP | IF_POWERUP_WHEEL, + /* vwep_model */ nullptr, + /* armor_info */ nullptr, + /* tag */ POWERUP_REBREATHER, + /* precaches */ "items/airout.wav" + }, + +/*QUAKED item_enviro (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + /* id */ IT_ITEM_ENVIROSUIT, + /* classname */ "item_enviro", + /* pickup */ Pickup_Powerup, + /* use */ Use_Envirosuit, + /* drop */ Drop_General, + /* weaponthink */ nullptr, + /* pickup_sound */ "items/pkup.wav", + /* world_model */ "models/items/enviro/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ nullptr, + /* icon */ "p_envirosuit", + /* use_name */ "Environment Suit", + /* pickup_name */ "$item_environment_suit", + /* pickup_name_definite */ "$item_environment_suit_def", + /* quantity */ 60, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_STAY_COOP|IF_POWERUP | IF_POWERUP_WHEEL, + /* vwep_model */ nullptr, + /* armor_info */ nullptr, + /* tag */ POWERUP_ENVIROSUIT, + /* precaches */ "items/airout.wav" + }, + +/*QUAKED item_ancient_head (.3 .3 1) (-16 -16 -16) (16 16 16) +Special item that gives +2 to maximum health +model="models/items/c_head/tris.md2" +*/ + { + /* id */ IT_ITEM_ANCIENT_HEAD, + /* classname */ "item_ancient_head", + /* pickup */ Pickup_LegacyHead, + /* use */ nullptr, + /* drop */ nullptr, + /* weaponthink */ nullptr, + /* pickup_sound */ "items/pkup.wav", + /* world_model */ "models/items/c_head/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ nullptr, + /* icon */ "i_fixme", + /* use_name */ "Ancient Head", + /* pickup_name */ "$item_ancient_head", + /* pickup_name_definite */ "$item_ancient_head_def", + /* quantity */ 60, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_HEALTH | IF_NOT_RANDOM, + }, + + /*QUAKED item_legacy_head (.3 .3 1) (-16 -16 -16) (16 16 16) + Special item that gives +5 to maximum health + model="models/items/legacyhead/tris.md2" + */ + { + /* id */ IT_ITEM_LEGACY_HEAD, + /* classname */ "item_legacy_head", + /* pickup */ Pickup_LegacyHead, + /* use */ nullptr, + /* drop */ nullptr, + /* weaponthink */ nullptr, + /* pickup_sound */ "items/pkup.wav", + /* world_model */ "models/items/legacyhead/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ nullptr, + /* icon */ "i_fixme", + /* use_name */ "Legacy Head", + /* pickup_name */ "$item_legacy_head", + /* pickup_name_definite */ "$item_legacy_head_def", + /* quantity */ 60, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_HEALTH | IF_NOT_RANDOM, + }, + +/*QUAKED item_adrenaline (.3 .3 1) (-16 -16 -16) (16 16 16) +gives +1 to maximum health +*/ + { + /* id */ IT_ITEM_ADRENALINE, + /* classname */ "item_adrenaline", + /* pickup */ Pickup_Powerup, + /* use */ Use_Adrenaline, + /* drop */ Drop_General, + /* weaponthink */ nullptr, + /* pickup_sound */ "items/pkup.wav", + /* world_model */ "models/items/adrenal/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ nullptr, + /* icon */ "p_adrenaline", + /* use_name */ "Adrenaline", + /* pickup_name */ "$item_adrenaline", + /* pickup_name_definite */ "$item_adrenaline_def", + /* quantity */ 60, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_HEALTH | IF_POWERUP_WHEEL, + /* vwep_model */ nullptr, + /* armor_info */ nullptr, + /* tag */ POWERUP_ADRENALINE, + /* precache */ "items/n_health.wav" + }, + +/*QUAKED item_bandolier (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + /* id */ IT_ITEM_BANDOLIER, + /* classname */ "item_bandolier", + /* pickup */ Pickup_Bandolier, + /* use */ nullptr, + /* drop */ nullptr, + /* weaponthink */ nullptr, + /* pickup_sound */ "items/pkup.wav", + /* world_model */ "models/items/band/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ nullptr, + /* icon */ "p_bandolier", + /* use_name */ "Bandolier", + /* pickup_name */ "$item_bandolier", + /* pickup_name_definite */ "$item_bandolier_def", + /* quantity */ 60 + }, + +/*QUAKED item_pack (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + /* id */ IT_ITEM_PACK, + /* classname */ "item_pack", + /* pickup */ Pickup_Pack, + /* use */ nullptr, + /* drop */ nullptr, + /* weaponthink */ nullptr, + /* pickup_sound */ "items/pkup.wav", + /* world_model */ "models/items/pack/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ nullptr, + /* icon */ "i_pack", + /* use_name */ "Ammo Pack", + /* pickup_name */ "$item_ammo_pack", + /* pickup_name_definite */ "$item_ammo_pack_def", + /* quantity */ 180, + }, + + +// ====================================== +// PGM + +/*QUAKED item_ir_goggles (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN +gives +1 to maximum health +*/ + { + /* id */ IT_ITEM_IR_GOGGLES, + /* classname */ "item_ir_goggles", + /* pickup */ Pickup_Powerup, + /* use */ Use_IR, + /* drop */ Drop_General, + /* weaponthink */ nullptr, + /* pickup_sound */ "items/pkup.wav", + /* world_model */ "models/items/goggles/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ nullptr, + /* icon */ "p_ir", + /* use_name */ "IR Goggles", + /* pickup_name */ "$item_ir_goggles", + /* pickup_name_definite */ "$item_ir_goggles_def", + /* quantity */ 60, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_POWERUP | IF_POWERUP_WHEEL, + /* vwep_model */ nullptr, + /* armor_info */ nullptr, + /* tag */ POWERUP_IR_GOGGLES, + /* precaches */ "misc/ir_start.wav" + }, + +/*QUAKED item_double (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN +*/ + { + /* id */ IT_ITEM_DOUBLE, + /* classname */ "item_double", + /* pickup */ Pickup_Powerup, + /* use */ Use_Double, + /* drop */ Drop_General, + /* weaponthink */ nullptr, + /* pickup_sound */ "items/pkup.wav", + /* world_model */ "models/items/ddamage/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ nullptr, + /* icon */ "p_double", + /* use_name */ "Double Damage", + /* pickup_name */ "$item_double_damage", + /* pickup_name_definite */ "$item_double_damage_def", + /* quantity */ 60, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_POWERUP | IF_POWERUP_WHEEL, + /* vwep_model */ nullptr, + /* armor_info */ nullptr, + /* tag */ POWERUP_DOUBLE, + /* precaches */ "misc/ddamage1.wav misc/ddamage2.wav misc/ddamage3.wav ctf/tech2x.wav" + }, + +/*QUAKED item_sphere_vengeance (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN +*/ + { + /* id */ IT_ITEM_SPHERE_VENGEANCE, + /* classname */ "item_sphere_vengeance", + /* pickup */ Pickup_Sphere, + /* use */ Use_Vengeance, + /* drop */ nullptr, + /* weaponthink */ nullptr, + /* pickup_sound */ "items/pkup.wav", + /* world_model */ "models/items/vengnce/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ nullptr, + /* icon */ "p_vengeance", + /* use_name */ "vengeance sphere", + /* pickup_name */ "$item_vengeance_sphere", + /* pickup_name_definite */ "$item_vengeance_sphere_def", + /* quantity */ 60, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_POWERUP | IF_POWERUP_WHEEL, + /* vwep_model */ nullptr, + /* armor_info */ nullptr, + /* tag */ POWERUP_SPHERE_VENGEANCE, + /* precaches */ "spheres/v_idle.wav" + }, + +/*QUAKED item_sphere_hunter (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN +*/ + { + /* id */ IT_ITEM_SPHERE_HUNTER, + /* classname */ "item_sphere_hunter", + /* pickup */ Pickup_Sphere, + /* use */ Use_Hunter, + /* drop */ nullptr, + /* weaponthink */ nullptr, + /* pickup_sound */ "items/pkup.wav", + /* world_model */ "models/items/hunter/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ nullptr, + /* icon */ "p_hunter", + /* use_name */ "hunter sphere", + /* pickup_name */ "$item_hunter_sphere", + /* pickup_name_definite */ "$item_hunter_sphere_def", + /* quantity */ 120, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_POWERUP | IF_POWERUP_WHEEL, + /* vwep_model */ nullptr, + /* armor_info */ nullptr, + /* tag */ POWERUP_SPHERE_HUNTER, + /* precaches */ "spheres/h_idle.wav spheres/h_active.wav spheres/h_lurk.wav" + }, + +/*QUAKED item_sphere_defender (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN +*/ + { + /* id */ IT_ITEM_SPHERE_DEFENDER, + /* classname */ "item_sphere_defender", + /* pickup */ Pickup_Sphere, + /* use */ Use_Defender, + /* drop */ nullptr, + /* weaponthink */ nullptr, + /* pickup_sound */ "items/pkup.wav", + /* world_model */ "models/items/defender/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ nullptr, + /* icon */ "p_defender", + /* use_name */ "defender sphere", + /* pickup_name */ "$item_defender_sphere", + /* pickup_name_definite */ "$item_defender_sphere_def", + /* quantity */ 60, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_POWERUP | IF_POWERUP_WHEEL, + /* vwep_model */ nullptr, + /* armor_info */ nullptr, + /* tag */ POWERUP_SPHERE_DEFENDER, + /* precaches */ "models/objects/laser/tris.md2 models/items/shell/tris.md2 spheres/d_idle.wav" + }, + +/*QUAKED item_doppleganger (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN +*/ + { + /* id */ IT_ITEM_DOPPELGANGER, + /* classname */ "item_doppleganger", + /* pickup */ Pickup_Doppleganger, + /* use */ Use_Doppleganger, + /* drop */ Drop_General, + /* weaponthink */ nullptr, + /* pickup_sound */ "items/pkup.wav", + /* world_model */ "models/items/dopple/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ nullptr, + /* icon */ "p_doppleganger", + /* use_name */ "Doppelganger", + /* pickup_name */ "$item_doppleganger", + /* pickup_name_definite */ "$item_doppleganger_def", + /* quantity */ 90, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_POWERUP | IF_POWERUP_WHEEL, + /* vwep_model */ nullptr, + /* armor_info */ nullptr, + /* tag */ POWERUP_DOPPELGANGER, + /* precaches */ "models/objects/dopplebase/tris.md2 models/items/spawngro3/tris.md2 medic_commander/monsterspawn1.wav models/items/hunter/tris.md2 models/items/vengnce/tris.md2", + }, + + { + /* id */ IT_ITEM_TAG_TOKEN, + /* classname */ nullptr, + /* pickup */ Tag_PickupToken, + /* use */ nullptr, + /* drop */ nullptr, + /* weaponthink */ nullptr, + /* pickup_sound */ "items/pkup.wav", + /* world_model */ "models/items/tagtoken/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB | EF_TAGTRAIL, + /* view_model */ nullptr, + /* icon */ "i_tagtoken", + /* use_name */ "Tag Token", + /* pickup_name */ "$item_tag_token", + /* pickup_name_definite */ "$item_tag_token_def", + /* quantity */ 0, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_POWERUP | IF_NOT_GIVEABLE + }, + +// PGM +// ====================================== + + // + // KEYS + // +/*QUAKED key_data_cd (0 .5 .8) (-16 -16 -16) (16 16 16) +key for computer centers +*/ + { + /* id */ IT_KEY_DATA_CD, + /* classname */ "key_data_cd", + /* pickup */ Pickup_Key, + /* use */ nullptr, + /* drop */ Drop_General, + /* weaponthink */ nullptr, + /* pickup_sound */ "items/pkup.wav", + /* world_model */ "models/items/keys/data_cd/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ nullptr, + /* icon */ "k_datacd", + /* use_name */ "Data CD", + /* pickup_name */ "$item_data_cd", + /* pickup_name_definite */ "$item_data_cd_def", + /* quantity */ 0, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_STAY_COOP|IF_KEY + }, + +/*QUAKED key_power_cube (0 .5 .8) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN NO_TOUCH +warehouse circuits +*/ + { + /* id */ IT_KEY_POWER_CUBE, + /* classname */ "key_power_cube", + /* pickup */ Pickup_Key, + /* use */ nullptr, + /* drop */ Drop_General, + /* weaponthink */ nullptr, + /* pickup_sound */ "items/pkup.wav", + /* world_model */ "models/items/keys/power/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ nullptr, + /* icon */ "k_powercube", + /* use_name */ "Power Cube", + /* pickup_name */ "$item_power_cube", + /* pickup_name_definite */ "$item_power_cube_def", + /* quantity */ 0, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_STAY_COOP|IF_KEY + }, + +/*QUAKED key_explosive_charges (0 .5 .8) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN NO_TOUCH +warehouse circuits +*/ + { + /* id */ IT_KEY_EXPLOSIVE_CHARGES, + /* classname */ "key_explosive_charges", + /* pickup */ Pickup_Key, + /* use */ nullptr, + /* drop */ Drop_General, + /* weaponthink */ nullptr, + /* pickup_sound */ "items/pkup.wav", + /* world_model */ "models/items/n64/charge/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ nullptr, + /* icon */ "n64/i_charges", + /* use_name */ "Explosive Charges", + /* pickup_name */ "$item_explosive_charges", + /* pickup_name_definite */ "$item_explosive_charges_def", + /* quantity */ 0, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_STAY_COOP|IF_KEY + }, + +/*QUAKED key_yellow_key (0 .5 .8) (-16 -16 -16) (16 16 16) +normal door key - yellow +[Sam-KEX] New key type for Q2 N64 +*/ + { + /* id */ IT_KEY_YELLOW, + /* classname */ "key_yellow_key", + /* pickup */ Pickup_Key, + /* use */ nullptr, + /* drop */ Drop_General, + /* weaponthink */ nullptr, + /* pickup_sound */ "items/pkup.wav", + /* world_model */ "models/items/n64/yellow_key/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ nullptr, + /* icon */ "n64/i_yellow_key", + /* use_name */ "Yellow Key", + /* pickup_name */ "$item_yellow_key", + /* pickup_name_definite */ "$item_yellow_key_def", + /* quantity */ 0, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_STAY_COOP | IF_KEY + }, + +/*QUAKED key_power_core (0 .5 .8) (-16 -16 -16) (16 16 16) +key for N64 +*/ + { + /* id */ IT_KEY_POWER_CORE, + /* classname */ "key_power_core", + /* pickup */ Pickup_Key, + /* use */ nullptr, + /* drop */ Drop_General, + /* weaponthink */ nullptr, + /* pickup_sound */ "items/pkup.wav", + /* world_model */ "models/items/n64/power_core/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ nullptr, + /* icon */ "k_pyramid", + /* use_name */ "Power Core", + /* pickup_name */ "$item_power_core", + /* pickup_name_definite */ "$item_power_core_def", + /* quantity */ 0, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_STAY_COOP|IF_KEY + }, + +/*QUAKED key_pyramid (0 .5 .8) (-16 -16 -16) (16 16 16) +key for the entrance of jail3 +*/ + { + /* id */ IT_KEY_PYRAMID, + /* classname */ "key_pyramid", + /* pickup */ Pickup_Key, + /* use */ nullptr, + /* drop */ Drop_General, + /* weaponthink */ nullptr, + /* pickup_sound */ "items/pkup.wav", + /* world_model */ "models/items/keys/pyramid/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ nullptr, + /* icon */ "k_pyramid", + /* use_name */ "Pyramid Key", + /* pickup_name */ "$item_pyramid_key", + /* pickup_name_definite */ "$item_pyramid_key_def", + /* quantity */ 0, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_STAY_COOP|IF_KEY + }, + +/*QUAKED key_data_spinner (0 .5 .8) (-16 -16 -16) (16 16 16) +key for the city computer +model="models/items/keys/spinner/tris.md2" +*/ + { + /* id */ IT_KEY_DATA_SPINNER, + /* classname */ "key_data_spinner", + /* pickup */ Pickup_Key, + /* use */ nullptr, + /* drop */ Drop_General, + /* weaponthink */ nullptr, + /* pickup_sound */ "items/pkup.wav", + /* world_model */ "models/items/keys/spinner/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ nullptr, + /* icon */ "k_dataspin", + /* use_name */ "Data Spinner", + /* pickup_name */ "$item_data_spinner", + /* pickup_name_definite */ "$item_data_spinner_def", + /* quantity */ 0, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_STAY_COOP|IF_KEY + }, + +/*QUAKED key_pass (0 .5 .8) (-16 -16 -16) (16 16 16) +security pass for the security level +model="models/items/keys/pass/tris.md2" +*/ + { + /* id */ IT_KEY_PASS, + /* classname */ "key_pass", + /* pickup */ Pickup_Key, + /* use */ nullptr, + /* drop */ Drop_General, + /* weaponthink */ nullptr, + /* pickup_sound */ "items/pkup.wav", + /* world_model */ "models/items/keys/pass/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ nullptr, + /* icon */ "k_security", + /* use_name */ "Security Pass", + /* pickup_name */ "$item_security_pass", + /* pickup_name_definite */ "$item_security_pass_def", + /* quantity */ 0, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_STAY_COOP|IF_KEY + }, + +/*QUAKED key_blue_key (0 .5 .8) (-16 -16 -16) (16 16 16) +normal door key - blue +*/ + { + /* id */ IT_KEY_BLUE_KEY, + /* classname */ "key_blue_key", + /* pickup */ Pickup_Key, + /* use */ nullptr, + /* drop */ Drop_General, + /* weaponthink */ nullptr, + /* pickup_sound */ "items/pkup.wav", + /* world_model */ "models/items/keys/key/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ nullptr, + /* icon */ "k_bluekey", + /* use_name */ "Blue Key", + /* pickup_name */ "$item_blue_key", + /* pickup_name_definite */ "$item_blue_key_def", + /* quantity */ 0, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_STAY_COOP|IF_KEY + }, + +/*QUAKED key_red_key (0 .5 .8) (-16 -16 -16) (16 16 16) +normal door key - red +*/ + { + /* id */ IT_KEY_RED_KEY, + /* classname */ "key_red_key", + /* pickup */ Pickup_Key, + /* use */ nullptr, + /* drop */ Drop_General, + /* weaponthink */ nullptr, + /* pickup_sound */ "items/pkup.wav", + /* world_model */ "models/items/keys/red_key/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ nullptr, + /* icon */ "k_redkey", + /* use_name */ "Red Key", + /* pickup_name */ "$item_red_key", + /* pickup_name_definite */ "$item_red_key_def", + /* quantity */ 0, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_STAY_COOP|IF_KEY + }, + +// RAFAEL +/*QUAKED key_green_key (0 .5 .8) (-16 -16 -16) (16 16 16) +normal door key - blue +*/ + { + /* id */ IT_KEY_GREEN_KEY, + /* classname */ "key_green_key", + /* pickup */ Pickup_Key, + /* use */ nullptr, + /* drop */ Drop_General, + /* weaponthink */ nullptr, + /* pickup_sound */ "items/pkup.wav", + /* world_model */ "models/items/keys/green_key/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ nullptr, + /* icon */ "k_green", + /* use_name */ "Green Key", + /* pickup_name */ "$item_green_key", + /* pickup_name_definite */ "$item_green_key_def", + /* quantity */ 0, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_STAY_COOP|IF_KEY + }, +// RAFAEL + +/*QUAKED key_commander_head (0 .5 .8) (-16 -16 -16) (16 16 16) +tank commander's head +*/ + { + /* id */ IT_KEY_COMMANDER_HEAD, + /* classname */ "key_commander_head", + /* pickup */ Pickup_Key, + /* use */ nullptr, + /* drop */ Drop_General, + /* weaponthink */ nullptr, + /* pickup_sound */ "items/pkup.wav", + /* world_model */ "models/monsters/commandr/head/tris.md2", + /* world_model_flags */ EF_GIB, + /* view_model */ nullptr, + /* icon */ "k_comhead", + /* use_name */ "Commander's Head", + /* pickup_name */ "$item_commanders_head", + /* pickup_name_definite */ "$item_commanders_head_def", + /* quantity */ 0, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_STAY_COOP|IF_KEY + }, + +/*QUAKED key_airstrike_target (0 .5 .8) (-16 -16 -16) (16 16 16) +*/ + { + /* id */ IT_KEY_AIRSTRIKE, + /* classname */ "key_airstrike_target", + /* pickup */ Pickup_Key, + /* use */ nullptr, + /* drop */ Drop_General, + /* weaponthink */ nullptr, + /* pickup_sound */ "items/pkup.wav", + /* world_model */ "models/items/keys/target/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ nullptr, + /* icon */ "i_airstrike", + /* use_name */ "Airstrike Marker", + /* pickup_name */ "$item_airstrike_marker", + /* pickup_name_definite */ "$item_airstrike_marker_def", + /* quantity */ 0, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_STAY_COOP|IF_KEY + }, + +// ====================================== +// PGM + +/*QUAKED key_nuke_container (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN +*/ + { + /* id */ IT_KEY_NUKE_CONTAINER, + /* classname */ "key_nuke_container", + /* pickup */ Pickup_Key, + /* use */ nullptr, + /* drop */ Drop_General, + /* weaponthink */ nullptr, + /* pickup_sound */ "items/pkup.wav", + /* world_model */ "models/weapons/g_nuke/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ nullptr, + /* icon */ "i_contain", + /* use_name */ "Antimatter Pod", + /* pickup_name */ "$item_antimatter_pod", + /* pickup_name_definite */ "$item_antimatter_pod_def", + /* quantity */ 0, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_STAY_COOP|IF_KEY, + }, + +/*QUAKED key_nuke (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN +*/ + { + /* id */ IT_KEY_NUKE, + /* classname */ "key_nuke", + /* pickup */ Pickup_Key, + /* use */ nullptr, + /* drop */ Drop_General, + /* weaponthink */ nullptr, + /* pickup_sound */ "items/pkup.wav", + /* world_model */ "models/weapons/g_nuke/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ nullptr, + /* icon */ "i_nuke", + /* use_name */ "Antimatter Bomb", + /* pickup_name */ "$item_antimatter_bomb", + /* pickup_name_definite */ "$item_antimatter_bomb_def", + /* quantity */ 0, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_STAY_COOP|IF_KEY, + }, + +// PGM +// + +// PGM +// ====================================== + +/*QUAKED item_health_small (.3 .3 1) (-16 -16 -16) (16 16 16) +model="models/items/healing/stimpack/tris.md2" +*/ + // Paril: split the healths up so they are always valid classnames + { + /* id */ IT_HEALTH_SMALL, + /* classname */ "item_health_small", + /* pickup */ Pickup_Health, + /* use */ nullptr, + /* drop */ nullptr, + /* weaponthink */ nullptr, + /* pickup_sound */ "items/s_health.wav", + /* world_model */ "models/items/healing/stimpack/tris.md2", + /* world_model_flags */ EF_NONE, + /* view_model */ nullptr, + /* icon */ "i_health", + /* use_name */ "Health", + /* pickup_name */ "$item_stimpack", + /* pickup_name_definite */ "$item_stimpack_def", + /* quantity */ 2, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_HEALTH, + /* vwep_model */ nullptr, + /* armor_info */ nullptr, + /* tag */ HEALTH_IGNORE_MAX + }, +/*QUAKED item_health (.3 .3 1) (-16 -16 -16) (16 16 16) +model="models/items/healing/medium/tris.md2" +*/ + { + /* id */ IT_HEALTH_MEDIUM, + /* classname */ "item_health", + /* pickup */ Pickup_Health, + /* use */ nullptr, + /* drop */ nullptr, + /* weaponthink */ nullptr, + /* pickup_sound */ "items/n_health.wav", + /* world_model */ "models/items/healing/medium/tris.md2", + /* world_model_flags */ EF_NONE, + /* view_model */ nullptr, + /* icon */ "i_health", + /* use_name */ "Health", + /* pickup_name */ "$item_small_medkit", + /* pickup_name_definite */ "$item_small_medkit_def", + /* quantity */ 10, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_HEALTH + }, +/*QUAKED item_health_large (.3 .3 1) (-16 -16 -16) (16 16 16) +model="models/items/healing/large/tris.md2" +*/ + { + /* id */ IT_HEALTH_LARGE, + /* classname */ "item_health_large", + /* pickup */ Pickup_Health, + /* use */ nullptr, + /* drop */ nullptr, + /* weaponthink */ nullptr, + /* pickup_sound */ "items/l_health.wav", + /* world_model */ "models/items/healing/large/tris.md2", + /* world_model_flags */ EF_NONE, + /* view_model */ nullptr, + /* icon */ "i_health", + /* use_name */ "Health", + /* pickup_name */ "$item_large_medkit", + /* pickup_name_definite */ "$item_large_medkit", + /* quantity */ 25, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_HEALTH + }, +/*QUAKED item_health_mega (.3 .3 1) (-16 -16 -16) (16 16 16) +model="models/items/mega_h/tris.md2" +*/ + { + /* id */ IT_HEALTH_MEGA, + /* classname */ "item_health_mega", + /* pickup */ Pickup_Health, + /* use */ nullptr, + /* drop */ nullptr, + /* weaponthink */ nullptr, + /* pickup_sound */ "items/m_health.wav", + /* world_model */ "models/items/mega_h/tris.md2", + /* world_model_flags */ EF_NONE, + /* view_model */ nullptr, + /* icon */ "p_megahealth", + /* use_name */ "Health", + /* pickup_name */ "$item_mega_health", + /* pickup_name_definite */ "$item_mega_health_def", + /* quantity */ 100, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_HEALTH, + /* vwep_model */ nullptr, + /* armor_info */ nullptr, + /* tag */ HEALTH_IGNORE_MAX | HEALTH_TIMED + }, + +//ZOID +/*QUAKED item_flag_team1 (1 0.2 0) (-16 -16 -24) (16 16 32) +*/ + { + /* id */ IT_FLAG1, + /* classname */ "item_flag_team1", + /* pickup */ CTFPickup_Flag, + /* use */ nullptr, + /* drop */ CTFDrop_Flag, //Should this be null if we don't want players to drop it manually? + /* weaponthink */ nullptr, + /* pickup_sound */ "ctf/flagtk.wav", + /* world_model */ "players/male/flag1.md2", + /* world_model_flags */ EF_FLAG1, + /* view_model */ nullptr, + /* icon */ "i_ctf1", + /* use_name */ "Red Flag", + /* pickup_name */ "$item_red_flag", + /* pickup_name_definite */ "$item_red_flag_def", + /* quantity */ 0, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_NONE, + /* vwep_model */ nullptr, + /* armor_info */ nullptr, + /* tag */ 0, + /* precaches */ "ctf/flagcap.wav" + }, + +/*QUAKED item_flag_team2 (1 0.2 0) (-16 -16 -24) (16 16 32) +*/ + { + /* id */ IT_FLAG2, + /* classname */ "item_flag_team2", + /* pickup */ CTFPickup_Flag, + /* use */ nullptr, + /* drop */ CTFDrop_Flag, + /* weaponthink */ nullptr, + /* pickup_sound */ "ctf/flagtk.wav", + /* world_model */ "players/male/flag2.md2", + /* world_model_flags */ EF_FLAG2, + /* view_model */ nullptr, + /* icon */ "i_ctf2", + /* use_name */ "Blue Flag", + /* pickup_name */ "$item_blue_flag", + /* pickup_name_definite */ "$item_blue_flag_def", + /* quantity */ 0, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_NONE, + /* vwep_model */ nullptr, + /* armor_info */ nullptr, + /* tag */ 0, + /* precaches */ "ctf/flagcap.wav" + }, + +/* Resistance Tech */ + { + /* id */ IT_TECH_RESISTANCE, + /* classname */ "item_tech1", + /* pickup */ CTFPickup_Tech, + /* use */ nullptr, + /* drop */ CTFDrop_Tech, + /* weaponthink */ nullptr, + /* pickup_sound */ "items/pkup.wav", + /* world_model */ "models/ctf/resistance/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ nullptr, + /* icon */ "tech1", + /* use_name */ "Disruptor Shield", + /* pickup_name */ "$item_disruptor_shield", + /* pickup_name_definite */ "$item_disruptor_shield_def", + /* quantity */ 0, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_TECH | IF_POWERUP_WHEEL, + /* vwep_model */ nullptr, + /* armor_info */ nullptr, + /* tag */ POWERUP_TECH1, + /* precaches */ "ctf/tech1.wav" + }, + +/* Strength Tech */ + { + /* id */ IT_TECH_STRENGTH, + /* classname */ "item_tech2", + /* pickup */ CTFPickup_Tech, + /* use */ nullptr, + /* drop */ CTFDrop_Tech, + /* weaponthink */ nullptr, + /* pickup_sound */ "items/pkup.wav", + /* world_model */ "models/ctf/strength/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ nullptr, + /* icon */ "tech2", + /* use_name */ "Power Amplifier", + /* pickup_name */ "$item_power_amplifier", + /* pickup_name_definite */ "$item_power_amplifier_def", + /* quantity */ 0, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_TECH | IF_POWERUP_WHEEL, + /* vwep_model */ nullptr, + /* armor_info */ nullptr, + /* tag */ POWERUP_TECH2, + /* precaches */ "ctf/tech2.wav ctf/tech2x.wav" + }, + +/* Haste Tech */ + { + /* id */ IT_TECH_HASTE, + /* classname */ "item_tech3", + /* pickup */ CTFPickup_Tech, + /* use */ nullptr, + /* drop */ CTFDrop_Tech, + /* weaponthink */ nullptr, + /* pickup_sound */ "items/pkup.wav", + /* world_model */ "models/ctf/haste/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ nullptr, + /* icon */ "tech3", + /* use_name */ "Time Accel", + /* pickup_name */ "$item_time_accel", + /* pickup_name_definite */ "$item_time_accel_def", + /* quantity */ 0, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_TECH | IF_POWERUP_WHEEL, + /* vwep_model */ nullptr, + /* armor_info */ nullptr, + /* tag */ POWERUP_TECH3, + /* precaches */ "ctf/tech3.wav" + }, + +/* Regeneration Tech */ + { + /* id */ IT_TECH_REGENERATION, + /* classname */ "item_tech4", + /* pickup */ CTFPickup_Tech, + /* use */ nullptr, + /* drop */ CTFDrop_Tech, + /* weaponthink */ nullptr, + /* pickup_sound */ "items/pkup.wav", + /* world_model */ "models/ctf/regeneration/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ nullptr, + /* icon */ "tech4", + /* use_name */ "AutoDoc", + /* pickup_name */ "$item_autodoc", + /* pickup_name_definite */ "$item_autodoc_def", + /* quantity */ 0, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_TECH | IF_POWERUP_WHEEL, + /* vwep_model */ nullptr, + /* armor_info */ nullptr, + /* tag */ POWERUP_TECH4, + /* precaches */ "ctf/tech4.wav" + }, + + { + /* id */ IT_ITEM_FLASHLIGHT, + /* classname */ "item_flashlight", + /* pickup */ Pickup_General, + /* use */ Use_Flashlight, + /* drop */ nullptr, + /* weaponthink */ nullptr, + /* pickup_sound */ "items/pkup.wav", + /* world_model */ "models/items/flashlight/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ nullptr, + /* icon */ "p_torch", + /* use_name */ "Flashlight", + /* pickup_name */ "$item_flashlight", + /* pickup_name_definite */ "$item_flashlight_def", + /* quantity */ 0, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_STAY_COOP | IF_POWERUP_WHEEL | IF_POWERUP_ONOFF | IF_NOT_RANDOM, + /* vwep_model */ nullptr, + /* armor_info */ nullptr, + /* tag */ POWERUP_FLASHLIGHT, + /* precaches */ "items/flashlight_on.wav items/flashlight_off.wav", + /* sort_id */ -1 + }, + + { + /* id */ IT_ITEM_COMPASS, + /* classname */ "item_compass", + /* pickup */ nullptr, + /* use */ Use_Compass, + /* drop */ nullptr, + /* weaponthink */ nullptr, + /* pickup_sound */ nullptr, + /* world_model */ nullptr, + /* world_model_flags */ EF_NONE, + /* view_model */ nullptr, + /* icon */ "p_compass", + /* use_name */ "Compass", + /* pickup_name */ "$item_compass", + /* pickup_name_definite */ "$item_compass_def", + /* quantity */ 0, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_STAY_COOP | IF_POWERUP_WHEEL | IF_POWERUP_ONOFF, + /* vwep_model */ nullptr, + /* armor_info */ nullptr, + /* tag */ POWERUP_COMPASS, + /* precaches */ "misc/help_marker.wav", + /* sort_id */ -2 + } +}; +// clang-format on + +void InitItems() +{ + // validate item integrity + for (item_id_t i = IT_NULL; i < IT_TOTAL; i = static_cast(i + 1)) + if (itemlist[i].id != i) + gi.Com_ErrorFmt("Item {} has wrong enum ID {} (should be {})", itemlist[i].pickup_name, (int32_t) itemlist[i].id, (int32_t) i); + + // set up weapon chains + for (item_id_t i = IT_NULL; i < IT_TOTAL; i = static_cast(i + 1)) + { + if (!itemlist[i].chain) + continue; + + gitem_t *item = &itemlist[i]; + + // already initialized + if (item->chain_next) + continue; + + gitem_t *chain_item = &itemlist[item->chain]; + + if (!chain_item) + gi.Com_ErrorFmt("Invalid item chain {} for {}", (int32_t) item->chain, item->pickup_name); + + // set up initial chain + if (!chain_item->chain_next) + chain_item->chain_next = chain_item; + + // if we're not the first in chain, add us now + if (chain_item != item) + { + gitem_t *c; + + // end of chain is one whose chain_next points to chain_item + for (c = chain_item; c->chain_next != chain_item; c = c->chain_next) + continue; + + // splice us in + item->chain_next = chain_item; + c->chain_next = item; + } + } + + // set up ammo + for (auto &it : itemlist) + { + if ((it.flags & IF_AMMO) && it.tag >= AMMO_BULLETS && it.tag < AMMO_MAX) + ammolist[it.tag] = ⁢ + else if ((it.flags & IF_POWERUP_WHEEL) && !(it.flags & IF_WEAPON) && it.tag >= POWERUP_SCREEN && it.tag < POWERUP_MAX) + poweruplist[it.tag] = ⁢ + } + + // in coop or DM with Weapons' Stay, remove drop ptr + for (auto &it : itemlist) + { + if (coop->integer) + { + if (!P_UseCoopInstancedItems() && (it.flags & IF_STAY_COOP)) + it.drop = nullptr; + } + else if (deathmatch->integer) + { + if (g_dm_weapons_stay->integer && it.drop == Drop_Weapon) + it.drop = nullptr; + } + } +} + +/* +=============== +SetItemNames + +Called by worldspawn +=============== +*/ +void SetItemNames() +{ + for (item_id_t i = IT_NULL; i < IT_TOTAL; i = static_cast(i + 1)) + gi.configstring(CS_ITEMS + i, itemlist[i].pickup_name); + + // [Paril-KEX] set ammo wheel indices first + int32_t cs_index = 0; + + for (item_id_t i = IT_NULL; i < IT_TOTAL; i = static_cast(i + 1)) + { + if (!(itemlist[i].flags & IF_AMMO)) + continue; + + if (cs_index >= MAX_WHEEL_ITEMS) + gi.Com_Error("out of wheel indices"); + + gi.configstring(CS_WHEEL_AMMO + cs_index, G_Fmt("{}|{}", (int32_t) i, gi.imageindex(itemlist[i].icon)).data()); + itemlist[i].ammo_wheel_index = cs_index; + cs_index++; + } + + // set weapon wheel indices + cs_index = 0; + + for (item_id_t i = IT_NULL; i < IT_TOTAL; i = static_cast(i + 1)) + { + if (!(itemlist[i].flags & IF_WEAPON)) + continue; + + if (cs_index >= MAX_WHEEL_ITEMS) + gi.Com_Error("out of wheel indices"); + + int32_t min_ammo = (itemlist[i].flags & IF_AMMO) ? 1 : itemlist[i].quantity; + + gi.configstring(CS_WHEEL_WEAPONS + cs_index, G_Fmt("{}|{}|{}|{}|{}|{}|{}|{}", + (int32_t) i, + gi.imageindex(itemlist[i].icon), + itemlist[i].ammo ? GetItemByIndex(itemlist[i].ammo)->ammo_wheel_index : -1, + min_ammo, + (itemlist[i].flags & IF_POWERUP_WHEEL) ? 1 : 0, + itemlist[i].sort_id, + itemlist[i].quantity_warn, + itemlist[i].drop != nullptr ? 1 : 0 + ).data()); + itemlist[i].weapon_wheel_index = cs_index; + cs_index++; + } + + // set powerup wheel indices + cs_index = 0; + + for (item_id_t i = IT_NULL; i < IT_TOTAL; i = static_cast(i + 1)) + { + if (!(itemlist[i].flags & IF_POWERUP_WHEEL) || (itemlist[i].flags & IF_WEAPON)) + continue; + + if (cs_index >= MAX_WHEEL_ITEMS) + gi.Com_Error("out of wheel indices"); + + gi.configstring(CS_WHEEL_POWERUPS + cs_index, G_Fmt("{}|{}|{}|{}|{}|{}", + (int32_t) i, + gi.imageindex(itemlist[i].icon), + (itemlist[i].flags & IF_POWERUP_ONOFF) ? 1 : 0, + itemlist[i].sort_id, + itemlist[i].drop != nullptr ? 1 : 0, + itemlist[i].ammo ? GetItemByIndex(itemlist[i].ammo)->ammo_wheel_index : -1 + ).data()); + itemlist[i].powerup_wheel_index = cs_index; + cs_index++; + } +} diff --git a/rerelease/g_local.h b/rerelease/g_local.h new file mode 100644 index 0000000..4611690 --- /dev/null +++ b/rerelease/g_local.h @@ -0,0 +1,3500 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. + +// g_local.h -- local definitions for game module +#pragma once + +#include "bg_local.h" + +// the "gameversion" client command will print this plus compile date +constexpr const char *GAMEVERSION = "baseq2"; + +//================================================================== + +constexpr vec3_t PLAYER_MINS = { -16, -16, -24 }; +constexpr vec3_t PLAYER_MAXS = { 16, 16, 32 }; + +#include + +template +constexpr bool is_char_ptr_v = std::is_convertible_v; + +template +constexpr bool is_valid_loc_embed_v = !std::is_null_pointer_v && (std::is_floating_point_v> || std::is_integral_v> || is_char_ptr_v); + +struct local_game_import_t : game_import_t +{ + inline local_game_import_t() = default; + inline local_game_import_t(const game_import_t &imports) : + game_import_t(imports) + { + } + +private: + // shared buffer for wrappers below + static char print_buffer[0x10000]; + +public: +#ifdef USE_CPP20_FORMAT + template + inline void Com_PrintFmt(std::format_string format_str, Args &&... args) +#else +#define Com_PrintFmt(str, ...) \ + Com_PrintFmt_(FMT_STRING(str), __VA_ARGS__) + + template + inline void Com_PrintFmt_(const S &format_str, Args &&... args) +#endif + { + G_FmtTo_(print_buffer, format_str, std::forward(args)...); + Com_Print(print_buffer); + } + +#ifdef USE_CPP20_FORMAT + template + inline void Com_ErrorFmt(std::format_string format_str, Args &&... args) +#else +#define Com_ErrorFmt(str, ...) \ + Com_ErrorFmt_(FMT_STRING(str), __VA_ARGS__) + + template + inline void Com_ErrorFmt_(const S &format_str, Args &&... args) +#endif + { + G_FmtTo_(print_buffer, format_str, std::forward(args)...); + Com_Error(print_buffer); + } + +private: + // localized print functions + template + inline void loc_embed(T input, char* buffer, const char*& output) + { + if constexpr (std::is_floating_point_v || std::is_integral_v) + { + auto result = std::to_chars(buffer, buffer + MAX_INFO_STRING - 1, input); + *result.ptr = '\0'; + output = buffer; + } + else if constexpr (is_char_ptr_v) + { + if (!input) + Com_Error("null const char ptr passed to loc"); + + output = input; + } + else + Com_Error("invalid loc argument"); + } + + static std::array buffers; + static std::array buffer_ptrs; + +public: + template + inline void LocClient_Print(edict_t* e, print_type_t level, const char* base, Args&& ...args) + { + static_assert(sizeof...(args) < MAX_LOCALIZATION_ARGS, "too many arguments to gi.LocClient_Print"); + static_assert(((is_valid_loc_embed_v) && ...), "invalid argument passed to gi.LocClient_Print"); + + size_t n = 0; + ((loc_embed(args, buffers[n], buffer_ptrs[n]), ++n), ...); + + Loc_Print(e, level, base, &buffer_ptrs.front(), sizeof...(args)); + } + + template + inline void LocBroadcast_Print(print_type_t level, const char* base, Args&& ...args) + { + static_assert(sizeof...(args) < MAX_LOCALIZATION_ARGS, "too many arguments to gi.LocBroadcast_Print"); + static_assert(((is_valid_loc_embed_v) && ...), "invalid argument passed to gi.LocBroadcast_Print"); + + size_t n = 0; + ((loc_embed(args, buffers[n], buffer_ptrs[n]), ++n), ...); + + Loc_Print(nullptr, (print_type_t)(level | print_type_t::PRINT_BROADCAST), base, &buffer_ptrs.front(), sizeof...(args)); + } + + template + inline void LocCenter_Print(edict_t* e, const char* base, Args&& ...args) + { + static_assert(sizeof...(args) < MAX_LOCALIZATION_ARGS, "too many arguments to gi.LocCenter_Print"); + static_assert(((is_valid_loc_embed_v) && ...), "invalid argument passed to gi.LocCenter_Print"); + + size_t n = 0; + ((loc_embed(args, buffers[n], buffer_ptrs[n]), ++n), ...); + + Loc_Print(e, PRINT_CENTER, base, &buffer_ptrs.front(), sizeof...(args)); + } + + // collision detection + [[nodiscard]] inline trace_t trace(const vec3_t &start, const vec3_t &mins, const vec3_t &maxs, const vec3_t &end, const edict_t *passent, contents_t contentmask) + { + return game_import_t::trace(start, &mins, &maxs, end, passent, contentmask); + } + + [[nodiscard]] inline trace_t traceline(const vec3_t &start, const vec3_t &end, const edict_t *passent, contents_t contentmask) + { + return game_import_t::trace(start, nullptr, nullptr, end, passent, contentmask); + } + + // [Paril-KEX] clip the box against the specified entity + [[nodiscard]] inline trace_t clip(edict_t *entity, const vec3_t &start, const vec3_t &mins, const vec3_t &maxs, const vec3_t &end, contents_t contentmask) + { + return game_import_t::clip(entity, start, &mins, &maxs, end, contentmask); + } + + [[nodiscard]] inline trace_t clip(edict_t *entity, const vec3_t &start, const vec3_t &end, contents_t contentmask) + { + return game_import_t::clip(entity, start, nullptr, nullptr, end, contentmask); + } + + void unicast(edict_t *ent, bool reliable, uint32_t dupe_key = 0) + { + game_import_t::unicast(ent, reliable, dupe_key); + } + + void local_sound(edict_t *target, const vec3_t &origin, edict_t *ent, soundchan_t channel, int soundindex, float volume, float attenuation, float timeofs, uint32_t dupe_key = 0) + { + game_import_t::local_sound(target, &origin, ent, channel, soundindex, volume, attenuation, timeofs, dupe_key); + } + + void local_sound(edict_t *target, edict_t *ent, soundchan_t channel, int soundindex, float volume, float attenuation, float timeofs, uint32_t dupe_key = 0) + { + game_import_t::local_sound(target, nullptr, ent, channel, soundindex, volume, attenuation, timeofs, dupe_key); + } + + void local_sound(const vec3_t &origin, edict_t *ent, soundchan_t channel, int soundindex, float volume, float attenuation, float timeofs, uint32_t dupe_key = 0) + { + game_import_t::local_sound(ent, &origin, ent, channel, soundindex, volume, attenuation, timeofs, dupe_key); + } + + void local_sound(edict_t *ent, soundchan_t channel, int soundindex, float volume, float attenuation, float timeofs, uint32_t dupe_key = 0) + { + game_import_t::local_sound(ent, nullptr, ent, channel, soundindex, volume, attenuation, timeofs, dupe_key); + } +}; + +extern local_game_import_t gi; + +// edict->spawnflags +// these are set with checkboxes on each entity in the map editor. +// the following 8 are reserved and should never be used by any entity. +// (power cubes in coop use these after spawning as well) +struct spawnflags_t +{ + uint32_t value; + + explicit constexpr spawnflags_t(uint32_t v) : + value(v) + { + } + + explicit operator uint32_t() const + { + return value; + } + + // has any flags at all (!!a) + constexpr bool any() const { return !!value; } + // has any of the given flags (!!(a & b)) + constexpr bool has(const spawnflags_t &flags) const { return !!(value & flags.value); } + // has all of the given flags ((a & b) == b) + constexpr bool has_all(const spawnflags_t &flags) const { return (value & flags.value) == flags.value; } + constexpr bool operator!() const { return !value; } + + constexpr bool operator==(const spawnflags_t &flags) const + { + return value == flags.value; + } + + constexpr bool operator!=(const spawnflags_t &flags) const + { + return value != flags.value; + } + + constexpr spawnflags_t operator~() const + { + return spawnflags_t(~value); + } + constexpr spawnflags_t operator|(const spawnflags_t &v2) const + { + return spawnflags_t(value | v2.value); + } + constexpr spawnflags_t operator&(const spawnflags_t &v2) const + { + return spawnflags_t(value & v2.value); + } + constexpr spawnflags_t operator^(const spawnflags_t &v2) const + { + return spawnflags_t(value ^ v2.value); + } + constexpr spawnflags_t &operator|=(const spawnflags_t &v2) + { + value |= v2.value; + return *this; + } + constexpr spawnflags_t &operator&=(const spawnflags_t &v2) + { + value &= v2.value; + return *this; + } + constexpr spawnflags_t &operator^=(const spawnflags_t &v2) + { + value ^= v2.value; + return *this; + } +}; + +// these spawnflags affect every entity. note that items are a bit special +// because these 8 bits are instead used for power cube bits. +constexpr spawnflags_t SPAWNFLAG_NONE = spawnflags_t(0); +constexpr spawnflags_t SPAWNFLAG_NOT_EASY = spawnflags_t(0x00000100), + SPAWNFLAG_NOT_MEDIUM = spawnflags_t(0x00000200), + SPAWNFLAG_NOT_HARD = spawnflags_t(0x00000400), + SPAWNFLAG_NOT_DEATHMATCH = spawnflags_t(0x00000800), + SPAWNFLAG_NOT_COOP = spawnflags_t(0x00001000), + SPAWNFLAG_RESERVED1 = spawnflags_t(0x00002000), + SPAWNFLAG_COOP_ONLY = spawnflags_t(0x00004000), + SPAWNFLAG_RESERVED2 = spawnflags_t(0x00008000); + +constexpr spawnflags_t SPAWNFLAG_EDITOR_MASK = (SPAWNFLAG_NOT_EASY | SPAWNFLAG_NOT_MEDIUM | SPAWNFLAG_NOT_HARD | SPAWNFLAG_NOT_DEATHMATCH | + SPAWNFLAG_NOT_COOP | SPAWNFLAG_RESERVED1 | SPAWNFLAG_COOP_ONLY | SPAWNFLAG_RESERVED2); + +// use this for global spawnflags +constexpr spawnflags_t operator "" _spawnflag(unsigned long long int v) +{ + if (v & SPAWNFLAG_EDITOR_MASK.value) + throw std::invalid_argument("attempting to use reserved spawnflag"); + + return static_cast(static_cast(v)); +} + +// use this for global spawnflags +constexpr spawnflags_t operator "" _spawnflag_bit(unsigned long long int v) +{ + v = 1ull << v; + + if (v & SPAWNFLAG_EDITOR_MASK.value) + throw std::invalid_argument("attempting to use reserved spawnflag"); + + return static_cast(static_cast(v)); +} + +// stores a level time; most newer engines use int64_t for +// time storage, but seconds are handy for compatibility +// with Quake and older mods. +struct gtime_t +{ +private: + // times always start at zero, just to prevent memory issues + int64_t _ms = 0; + + // internal; use _sec/_ms/_min or gtime_t::from_sec(n)/gtime_t::from_ms(n)/gtime_t::from_min(n) + constexpr explicit gtime_t(const int64_t &ms) : _ms(ms) + { + } + +public: + constexpr gtime_t() = default; + constexpr gtime_t(const gtime_t &) = default; + constexpr gtime_t &operator=(const gtime_t &) = default; + + // constructors are here, explicitly named, so you always + // know what you're getting. + + // new time from ms + static constexpr gtime_t from_ms(const int64_t &ms) + { + return gtime_t(ms); + } + + // new time from seconds + template || std::is_integral_v>> + static constexpr gtime_t from_sec(const T &s) + { + return gtime_t(static_cast(s * 1000)); + } + + // new time from minutes + template || std::is_integral_v>> + static constexpr gtime_t from_min(const T &s) + { + return gtime_t(static_cast(s * 60000)); + } + + // new time from hz + static constexpr gtime_t from_hz(const uint64_t &hz) + { + return from_ms(static_cast((1.0 / hz) * 1000)); + } + + // get value in minutes (truncated for integers) + template + constexpr T minutes() const + { + return static_cast(_ms / static_cast, T, float>>(60000)); + } + + // get value in seconds (truncated for integers) + template + constexpr T seconds() const + { + return static_cast(_ms / static_cast, T, float>>(1000)); + } + + // get value in milliseconds + constexpr const int64_t &milliseconds() const + { + return _ms; + } + + int64_t frames() const + { + return _ms / gi.frame_time_ms; + } + + // check if non-zero + constexpr explicit operator bool() const + { + return !!_ms; + } + + // invert time + constexpr gtime_t operator-() const + { + return gtime_t(-_ms); + } + + // operations with other times as input + constexpr gtime_t operator+(const gtime_t &r) const + { + return gtime_t(_ms + r._ms); + } + constexpr gtime_t operator-(const gtime_t &r) const + { + return gtime_t(_ms - r._ms); + } + constexpr gtime_t &operator+=(const gtime_t &r) + { + return *this = *this + r; + } + constexpr gtime_t &operator-=(const gtime_t &r) + { + return *this = *this - r; + } + + // operations with scalars as input + template || std::is_integral_v>> + constexpr gtime_t operator*(const T &r) const + { + return gtime_t::from_ms(static_cast(_ms * r)); + } + template || std::is_integral_v>> + constexpr gtime_t operator/(const T &r) const + { + return gtime_t::from_ms(static_cast(_ms / r)); + } + template || std::is_integral_v>> + constexpr gtime_t &operator*=(const T &r) + { + return *this = *this * r; + } + template || std::is_integral_v>> + constexpr gtime_t &operator/=(const T &r) + { + return *this = *this / r; + } + + // comparisons with gtime_ts + constexpr bool operator==(const gtime_t &time) const + { + return _ms == time._ms; + } + constexpr bool operator!=(const gtime_t &time) const + { + return _ms != time._ms; + } + constexpr bool operator<(const gtime_t &time) const + { + return _ms < time._ms; + } + constexpr bool operator>(const gtime_t &time) const + { + return _ms > time._ms; + } + constexpr bool operator<=(const gtime_t &time) const + { + return _ms <= time._ms; + } + constexpr bool operator>=(const gtime_t &time) const + { + return _ms >= time._ms; + } +}; + +// user literals, allowing you to specify times +// as 128_sec and 128_ms +constexpr gtime_t operator"" _min(long double s) +{ + return gtime_t::from_min(s); +} +constexpr gtime_t operator"" _min(unsigned long long int s) +{ + return gtime_t::from_min(s); +} +constexpr gtime_t operator"" _sec(long double s) +{ + return gtime_t::from_sec(s); +} +constexpr gtime_t operator"" _sec(unsigned long long int s) +{ + return gtime_t::from_sec(s); +} +constexpr gtime_t operator"" _ms(long double s) +{ + return gtime_t::from_ms(static_cast(s)); +} +constexpr gtime_t operator"" _ms(unsigned long long int s) +{ + return gtime_t::from_ms(static_cast(s)); +} +constexpr gtime_t operator"" _hz(unsigned long long int s) +{ + return gtime_t::from_ms(static_cast((1.0 / s) * 1000)); +} + +#define SERVER_TICK_RATE gi.tick_rate // in hz +extern gtime_t FRAME_TIME_S; +extern gtime_t FRAME_TIME_MS; + +// view pitching times +inline gtime_t DAMAGE_TIME_SLACK() +{ + return (100_ms - FRAME_TIME_MS); +} + +inline gtime_t DAMAGE_TIME() +{ + return 500_ms + DAMAGE_TIME_SLACK(); +} + +inline gtime_t FALL_TIME() +{ + return 300_ms + DAMAGE_TIME_SLACK(); +} + +// every save_data_list_t has a tag +// which is used for integrity checks. +enum save_data_tag_t +{ + SAVE_DATA_MMOVE, + + SAVE_FUNC_MONSTERINFO_STAND, + SAVE_FUNC_MONSTERINFO_IDLE, + SAVE_FUNC_MONSTERINFO_SEARCH, + SAVE_FUNC_MONSTERINFO_WALK, + SAVE_FUNC_MONSTERINFO_RUN, + SAVE_FUNC_MONSTERINFO_DODGE, + SAVE_FUNC_MONSTERINFO_ATTACK, + SAVE_FUNC_MONSTERINFO_MELEE, + SAVE_FUNC_MONSTERINFO_SIGHT, + SAVE_FUNC_MONSTERINFO_CHECKATTACK, + SAVE_FUNC_MONSTERINFO_SETSKIN, + + SAVE_FUNC_MONSTERINFO_BLOCKED, + SAVE_FUNC_MONSTERINFO_DUCK, + SAVE_FUNC_MONSTERINFO_UNDUCK, + SAVE_FUNC_MONSTERINFO_SIDESTEP, + SAVE_FUNC_MONSTERINFO_PHYSCHANGED, + + SAVE_FUNC_MOVEINFO_ENDFUNC, + SAVE_FUNC_MOVEINFO_BLOCKED, + + SAVE_FUNC_PRETHINK, + SAVE_FUNC_THINK, + SAVE_FUNC_TOUCH, + SAVE_FUNC_USE, + SAVE_FUNC_PAIN, + SAVE_FUNC_DIE +}; + +// forward-linked list, storing data for +// saving pointers. every save_data_ptr has an +// instance of this; there's one head instance of this +// in g_save.cpp. +struct save_data_list_t +{ + const char *name; // name of savable object; persisted in the JSON file + save_data_tag_t tag; + const void *ptr; // pointer to raw data + const save_data_list_t *next; // next in list + + save_data_list_t(const char *name, save_data_tag_t tag, const void *ptr); + + static const save_data_list_t *fetch(const void *link_ptr, save_data_tag_t tag); +}; + +#include + +// save data wrapper, which holds a pointer to a T +// and the tag value for integrity. this is how you +// store a savable pointer of data safely. +template +struct save_data_t +{ + using value_type = typename std::conditional::value && + std::is_function::type>::value, + T, + const T *>::type; +private: + value_type value; + const save_data_list_t *list; + +public: + constexpr save_data_t() : + value(nullptr), + list(nullptr) + { + } + + constexpr save_data_t(nullptr_t) : + save_data_t() + { + } + + constexpr save_data_t(const save_data_list_t *list_in) : + value(list_in->ptr), + list(list_in) + { + } + + inline save_data_t(value_type ptr_in) : + value(ptr_in), + list(ptr_in ? save_data_list_t::fetch(reinterpret_cast(ptr_in), static_cast(Tag)) : nullptr) + { + } + + inline save_data_t(const save_data_t &ref_in) : + save_data_t(ref_in.value) + { + } + + inline save_data_t &operator=(value_type ptr_in) + { + if (value != ptr_in) + { + value = ptr_in; + list = value ? save_data_list_t::fetch(reinterpret_cast(ptr_in), static_cast(Tag)) : nullptr; + } + + return *this; + } + + constexpr const value_type pointer() const { return value; } + constexpr const save_data_list_t *save_list() const { return list; } + constexpr const char *name() const { return value ? list->name : "null"; } + constexpr const value_type operator->() const { return value; } + constexpr explicit operator bool() const { return value; } + constexpr bool operator==(value_type ptr_in) const { return value == ptr_in; } + constexpr bool operator!=(value_type ptr_in) const { return value != ptr_in; } + constexpr bool operator==(const save_data_t *ptr_in) const { return value == ptr_in->value; } + constexpr bool operator==(const save_data_t &ref_in) const { return value == ref_in.value; } + constexpr bool operator!=(const save_data_t *ptr_in) const { return value != ptr_in->value; } + constexpr bool operator!=(const save_data_t &ref_in) const { return value != ref_in.value; } + + // invoke wrapper, for function-likes + template + inline auto operator()(Args&& ...args) const + { + static_assert(std::is_invocable_v, Args...>, "bad invoke args"); + return std::invoke(value, std::forward(args)...); + } +}; + +// memory tags to allow dynamic memory to be cleaned up +enum +{ + TAG_GAME = 765, // clear when unloading the dll + TAG_LEVEL = 766 // clear when loading a new level +}; + +constexpr float MELEE_DISTANCE = 50; + +constexpr size_t BODY_QUEUE_SIZE = 8; + +// null trace used when touches don't need a trace +constexpr trace_t null_trace {}; + +enum weaponstate_t +{ + WEAPON_READY, + WEAPON_ACTIVATING, + WEAPON_DROPPING, + WEAPON_FIRING +}; + +// gib flags +enum gib_type_t +{ + GIB_NONE = 0, // no flags (organic) + GIB_METALLIC = 1, // bouncier + GIB_ACID = 2, // acidic (gekk) + GIB_HEAD = 4, // head gib; the input entity will transform into this + GIB_DEBRIS = 8, // explode outwards rather than in velocity, no blood + GIB_SKINNED = 16, // use skinnum + GIB_UPRIGHT = 32, // stay upright on ground +}; +MAKE_ENUM_BITFLAGS(gib_type_t); + +// monster ai flags +enum monster_ai_flags_t : uint64_t +{ + AI_NONE = 0, + AI_STAND_GROUND = bit_v<0>, + AI_TEMP_STAND_GROUND = bit_v<1>, + AI_SOUND_TARGET = bit_v<2>, + AI_LOST_SIGHT = bit_v<3>, + AI_PURSUIT_LAST_SEEN = bit_v<4>, + AI_PURSUE_NEXT = bit_v<5>, + AI_PURSUE_TEMP = bit_v<6>, + AI_HOLD_FRAME = bit_v<7>, + AI_GOOD_GUY = bit_v<8>, + AI_BRUTAL = bit_v<9>, + AI_NOSTEP = bit_v<10>, + AI_DUCKED = bit_v<11>, + AI_COMBAT_POINT = bit_v<12>, + AI_MEDIC = bit_v<13>, + AI_RESURRECTING = bit_v<14>, + + // ROGUE + AI_MANUAL_STEERING = bit_v<15>, + AI_TARGET_ANGER = bit_v<16>, + AI_DODGING = bit_v<17>, + AI_CHARGING = bit_v<18>, + AI_HINT_PATH = bit_v<19>, + AI_IGNORE_SHOTS = bit_v<20>, + // PMM - FIXME - last second added for E3 .. there's probably a better way to do this, but + // this works + AI_DO_NOT_COUNT = bit_v<21>, // set for healed monsters + AI_SPAWNED_CARRIER = bit_v<22>, // both do_not_count and spawned are set for spawned monsters + AI_SPAWNED_MEDIC_C = bit_v<23>, // both do_not_count and spawned are set for spawned monsters + AI_SPAWNED_WIDOW = bit_v<24>, // both do_not_count and spawned are set for spawned monsters + AI_BLOCKED = bit_v<25>, // used by blocked_checkattack: set to say I'm attacking while blocked + // (prevents run-attacks) + // ROGUE + AI_SPAWNED_ALIVE = bit_v<26>, // [Paril-KEX] for spawning dead + AI_SPAWNED_DEAD = bit_v<27>, + AI_HIGH_TICK_RATE = bit_v<28>, // not limited by 10hz actions + AI_NO_PATH_FINDING = bit_v<29>, // don't try nav nodes for path finding + AI_PATHING = bit_v<30>, // using nav nodes currently + AI_STINKY = bit_v<31>, // spawn flies + AI_STUNK = bit_v<32>, // already spawned files + + AI_ALTERNATE_FLY = bit_v<33>, // use alternate flying mechanics; see monsterinfo.fly_xxx + AI_TEMP_MELEE_COMBAT = bit_v<34>, // temporarily switch to the melee combat style + AI_FORGET_ENEMY = bit_v<35>, // forget the current enemy + AI_DOUBLE_TROUBLE = bit_v<36>, // JORG only + AI_REACHED_HOLD_COMBAT = bit_v<37>, + AI_THIRD_EYE = bit_v<38> +}; +MAKE_ENUM_BITFLAGS(monster_ai_flags_t); + +constexpr monster_ai_flags_t AI_SPAWNED_MASK = + AI_SPAWNED_CARRIER | AI_SPAWNED_MEDIC_C | AI_SPAWNED_WIDOW; // mask to catch all three flavors of spawned + +// monster attack state +enum monster_attack_state_t +{ + AS_NONE, + AS_STRAIGHT, + AS_SLIDING, + AS_MELEE, + AS_MISSILE, + AS_BLIND // PMM - used by boss code to do nasty things even if it can't see you +}; + +// handedness values +enum handedness_t +{ + RIGHT_HANDED, + LEFT_HANDED, + CENTER_HANDED +}; + +enum class auto_switch_t +{ + SMART, + ALWAYS, + ALWAYS_NO_AMMO, + NEVER +}; + +constexpr uint32_t SFL_CROSS_TRIGGER_MASK = (0xffffffffu & ~SPAWNFLAG_EDITOR_MASK.value); + +// noise types for PlayerNoise +enum player_noise_t +{ + PNOISE_SELF, + PNOISE_WEAPON, + PNOISE_IMPACT +}; + +struct gitem_armor_t +{ + int32_t base_count; + int32_t max_count; + float normal_protection; + float energy_protection; +}; + +static constexpr gitem_armor_t jacketarmor_info = { 25, 50, .30f, .00f }; +static constexpr gitem_armor_t combatarmor_info = { 50, 100, .60f, .30f }; +static constexpr gitem_armor_t bodyarmor_info = { 100, 200, .80f, .60f }; + +// edict->movetype values +enum movetype_t { + MOVETYPE_NONE, // never moves + MOVETYPE_NOCLIP, // origin and angles change with no interaction + MOVETYPE_PUSH, // no clip to world, push on box contact + MOVETYPE_STOP, // no clip to world, stops on box contact + + MOVETYPE_WALK, // gravity + MOVETYPE_STEP, // gravity, special edge handling + MOVETYPE_FLY, + MOVETYPE_TOSS, // gravity + MOVETYPE_FLYMISSILE, // extra size to monsters + MOVETYPE_BOUNCE, + // RAFAEL + MOVETYPE_WALLBOUNCE, + // RAFAEL + // ROGUE + MOVETYPE_NEWTOSS // PGM - for deathball + // ROGUE +}; + +// edict->flags +enum ent_flags_t : uint64_t { + FL_NONE = 0, // no flags + FL_FLY = bit_v<0>, + FL_SWIM = bit_v<1>, // implied immunity to drowning + FL_IMMUNE_LASER = bit_v<2>, + FL_INWATER = bit_v<3>, + FL_GODMODE = bit_v<4>, + FL_NOTARGET = bit_v<5>, + FL_IMMUNE_SLIME = bit_v<6>, + FL_IMMUNE_LAVA = bit_v<7>, + FL_PARTIALGROUND = bit_v<8>, // not all corners are valid + FL_WATERJUMP = bit_v<9>, // player jumping out of water + FL_TEAMSLAVE = bit_v<10>, // not the first on the team + FL_NO_KNOCKBACK = bit_v<11>, + FL_POWER_ARMOR = bit_v<12>, // power armor (if any) is active + + // ROGUE + FL_MECHANICAL = bit_v<13>, // entity is mechanical, use sparks not blood + FL_SAM_RAIMI = bit_v<14>, // entity is in sam raimi cam mode + FL_DISGUISED = bit_v<15>, // entity is in disguise, monsters will not recognize. + FL_NOGIB = bit_v<16>, // player has been vaporized by a nuke, drop no gibs + FL_DAMAGEABLE = bit_v<17>, + FL_STATIONARY = bit_v<18>, + // ROGUE + + FL_ALIVE_KNOCKBACK_ONLY = bit_v<19>, // only apply knockback if alive or on same frame as death + FL_NO_DAMAGE_EFFECTS = bit_v<20>, + + // [Paril-KEX] gets scaled by coop health scaling + FL_COOP_HEALTH_SCALE = bit_v<21>, + FL_FLASHLIGHT = bit_v<22>, // enable flashlight + FL_KILL_VELOCITY = bit_v<23>, // for berserker slam + FL_NOVISIBLE = bit_v<24>, // super invisibility + FL_DODGE = bit_v<25>, // monster should try to dodge this + FL_TEAMMASTER = bit_v<26>, // is a team master (only here so that entities abusing teammaster/teamchain for stuff don't break) + FL_LOCKED = bit_v<27>, // entity is locked for the purposes of navigation + FL_ALWAYS_TOUCH = bit_v<28>, // always touch, even if we normally wouldn't + FL_NO_STANDING = bit_v<29>, // don't allow "standing" on non-brush entities + FL_WANTS_POWER_ARMOR = bit_v<30>, // for players, auto-shield + + FL_RESPAWN = bit_v<31>, // used for item respawning + FL_TRAP = bit_v<32>, // entity is a trap of some kind + FL_TRAP_LASER_FIELD = bit_v<33>, // enough of a special case to get it's own flag... + FL_IMMORTAL = bit_v<34> // never go below 1hp +}; +MAKE_ENUM_BITFLAGS( ent_flags_t ); + +// gitem_t->flags +enum item_flags_t : uint32_t +{ + IF_NONE = 0, + IF_WEAPON = bit_v<0>, // use makes active weapon + IF_AMMO = bit_v<1>, + IF_ARMOR = bit_v<2>, + IF_STAY_COOP = bit_v<3>, + IF_KEY = bit_v<4>, + IF_POWERUP = bit_v<5>, + // ROGUE + IF_NOT_GIVEABLE = bit_v<6>, // item can not be given + // ROGUE + IF_HEALTH = bit_v<7>, + // ZOID + IF_TECH = bit_v<8>, + IF_NO_HASTE = bit_v<9>, + // ZOID + + IF_NO_INFINITE_AMMO = bit_v<10>, // [Paril-KEX] don't allow infinite ammo to affect + IF_POWERUP_WHEEL = bit_v<11>, // [Paril-KEX] item should be in powerup wheel + IF_POWERUP_ONOFF = bit_v<12>, // [Paril-KEX] for wheel; can't store more than one, show on/off state + + IF_NOT_RANDOM = bit_v<13>, // [Paril-KEX] item never shows up in randomizations + + IF_ANY = 0xFFFFFFFF +}; + +MAKE_ENUM_BITFLAGS(item_flags_t); + +// health edict_t->style +enum +{ + HEALTH_IGNORE_MAX = 1, + HEALTH_TIMED = 2 +}; + +// item IDs; must match itemlist order +enum item_id_t : int32_t { + IT_NULL, // must always be zero + + IT_ARMOR_BODY, + IT_ARMOR_COMBAT, + IT_ARMOR_JACKET, + IT_ARMOR_SHARD, + + IT_ITEM_POWER_SCREEN, + IT_ITEM_POWER_SHIELD, + + IT_WEAPON_GRAPPLE, + IT_WEAPON_BLASTER, + IT_WEAPON_CHAINFIST, + IT_WEAPON_SHOTGUN, + IT_WEAPON_SSHOTGUN, + IT_WEAPON_MACHINEGUN, + IT_WEAPON_ETF_RIFLE, + IT_WEAPON_CHAINGUN, + IT_AMMO_GRENADES, + IT_AMMO_TRAP, + IT_AMMO_TESLA, + IT_WEAPON_GLAUNCHER, + IT_WEAPON_PROXLAUNCHER, + IT_WEAPON_RLAUNCHER, + IT_WEAPON_HYPERBLASTER, + IT_WEAPON_IONRIPPER, + IT_WEAPON_PLASMABEAM, + IT_WEAPON_RAILGUN, + IT_WEAPON_PHALANX, + IT_WEAPON_BFG, + IT_WEAPON_DISRUPTOR, +#if 0 + IT_WEAPON_DISINTEGRATOR, +#endif + + IT_AMMO_SHELLS, + IT_AMMO_BULLETS, + IT_AMMO_CELLS, + IT_AMMO_ROCKETS, + IT_AMMO_SLUGS, + IT_AMMO_MAGSLUG, + IT_AMMO_FLECHETTES, + IT_AMMO_PROX, + IT_AMMO_NUKE, + IT_AMMO_ROUNDS, + + IT_ITEM_QUAD, + IT_ITEM_QUADFIRE, + IT_ITEM_INVULNERABILITY, + IT_ITEM_INVISIBILITY, + IT_ITEM_SILENCER, + IT_ITEM_REBREATHER, + IT_ITEM_ENVIROSUIT, + IT_ITEM_ANCIENT_HEAD, + IT_ITEM_LEGACY_HEAD, + IT_ITEM_ADRENALINE, + IT_ITEM_BANDOLIER, + IT_ITEM_PACK, + IT_ITEM_IR_GOGGLES, + IT_ITEM_DOUBLE, + IT_ITEM_SPHERE_VENGEANCE, + IT_ITEM_SPHERE_HUNTER, + IT_ITEM_SPHERE_DEFENDER, + IT_ITEM_DOPPELGANGER, + IT_ITEM_TAG_TOKEN, + + IT_KEY_DATA_CD, + IT_KEY_POWER_CUBE, + IT_KEY_EXPLOSIVE_CHARGES, + IT_KEY_YELLOW, + IT_KEY_POWER_CORE, + IT_KEY_PYRAMID, + IT_KEY_DATA_SPINNER, + IT_KEY_PASS, + IT_KEY_BLUE_KEY, + IT_KEY_RED_KEY, + IT_KEY_GREEN_KEY, + IT_KEY_COMMANDER_HEAD, + IT_KEY_AIRSTRIKE, + IT_KEY_NUKE_CONTAINER, + IT_KEY_NUKE, + + IT_HEALTH_SMALL, + IT_HEALTH_MEDIUM, + IT_HEALTH_LARGE, + IT_HEALTH_MEGA, + + IT_FLAG1, + IT_FLAG2, + + IT_TECH_RESISTANCE, + IT_TECH_STRENGTH, + IT_TECH_HASTE, + IT_TECH_REGENERATION, + + IT_ITEM_FLASHLIGHT, + IT_ITEM_COMPASS, + + IT_TOTAL +}; + +struct gitem_t +{ + item_id_t id; // matches item list index + const char *classname; // spawning name + bool (*pickup)(edict_t *ent, edict_t *other); + void (*use)(edict_t *ent, gitem_t *item); + void (*drop)(edict_t *ent, gitem_t *item); + void (*weaponthink)(edict_t *ent); + const char *pickup_sound; + const char *world_model; + effects_t world_model_flags; + const char *view_model; + + // client side info + const char *icon; + const char *use_name; // for use command, english only + const char *pickup_name; // for printing on pickup + const char *pickup_name_definite; // definite article version for languages that need it + + int quantity = 0; // for ammo how much, for weapons how much is used per shot + item_id_t ammo = IT_NULL; // for weapons + item_id_t chain = IT_NULL; // weapon chain root + item_flags_t flags = IF_NONE; // IT_* flags + + const char *vwep_model = nullptr; // vwep model string (for weapons) + + const gitem_armor_t *armor_info = nullptr; + int tag = 0; + + const char *precaches = nullptr; // string of all models, sounds, and images this item will use + + int32_t sort_id = 0; // used by some items to control their sorting + int32_t quantity_warn = 5; // when to warn on low ammo + + // set in InitItems, don't set by hand + // circular list of chained weapons + gitem_t *chain_next = nullptr; + // set in SP_worldspawn, don't set by hand + // model index for vwep + int32_t vwep_index = 0; + // set in SetItemNames, don't set by hand + // offset into CS_WHEEL_AMMO/CS_WHEEL_WEAPONS/CS_WHEEL_POWERUPS + int32_t ammo_wheel_index = -1; + int32_t weapon_wheel_index = -1; + int32_t powerup_wheel_index = -1; +}; + +// means of death +enum mod_id_t : uint8_t +{ + MOD_UNKNOWN, + MOD_BLASTER, + MOD_SHOTGUN, + MOD_SSHOTGUN, + MOD_MACHINEGUN, + MOD_CHAINGUN, + MOD_GRENADE, + MOD_G_SPLASH, + MOD_ROCKET, + MOD_R_SPLASH, + MOD_HYPERBLASTER, + MOD_RAILGUN, + MOD_BFG_LASER, + MOD_BFG_BLAST, + MOD_BFG_EFFECT, + MOD_HANDGRENADE, + MOD_HG_SPLASH, + MOD_WATER, + MOD_SLIME, + MOD_LAVA, + MOD_CRUSH, + MOD_TELEFRAG, + MOD_TELEFRAG_SPAWN, + MOD_FALLING, + MOD_SUICIDE, + MOD_HELD_GRENADE, + MOD_EXPLOSIVE, + MOD_BARREL, + MOD_BOMB, + MOD_EXIT, + MOD_SPLASH, + MOD_TARGET_LASER, + MOD_TRIGGER_HURT, + MOD_HIT, + MOD_TARGET_BLASTER, + // RAFAEL 14-APR-98 + MOD_RIPPER, + MOD_PHALANX, + MOD_BRAINTENTACLE, + MOD_BLASTOFF, + MOD_GEKK, + MOD_TRAP, + // END 14-APR-98 + //======== + // ROGUE + MOD_CHAINFIST, + MOD_DISINTEGRATOR, + MOD_ETF_RIFLE, + MOD_BLASTER2, + MOD_HEATBEAM, + MOD_TESLA, + MOD_PROX, + MOD_NUKE, + MOD_VENGEANCE_SPHERE, + MOD_HUNTER_SPHERE, + MOD_DEFENDER_SPHERE, + MOD_TRACKER, + MOD_DBALL_CRUSH, + MOD_DOPPLE_EXPLODE, + MOD_DOPPLE_VENGEANCE, + MOD_DOPPLE_HUNTER, + // ROGUE + //======== + MOD_GRAPPLE, + MOD_BLUEBLASTER +}; + +struct mod_t +{ + mod_id_t id; + bool friendly_fire = false; + bool no_point_loss = false; + + mod_t() = default; + constexpr mod_t(mod_id_t id, bool no_point_loss = false) : + id(id), + no_point_loss(no_point_loss) + { + } +}; + +// the total number of levels we'll track for the +// end of unit screen. +constexpr size_t MAX_LEVELS_PER_UNIT = 8; + +struct level_entry_t +{ + // bsp name + char map_name[MAX_QPATH]; + // map name + char pretty_name[MAX_QPATH]; + // these are set when we leave the level + int32_t total_secrets; + int32_t found_secrets; + int32_t total_monsters; + int32_t killed_monsters; + // total time spent in the level, for end screen + gtime_t time; + // the order we visited levels in + int32_t visit_order; +}; + +// +// this structure is left intact through an entire game +// it should be initialized at dll load time, and read/written to +// the server.ssv file for savegames +// +struct game_locals_t +{ + char helpmessage1[MAX_TOKEN_CHARS]; + char helpmessage2[MAX_TOKEN_CHARS]; + int32_t help1changed, help2changed; + + gclient_t *clients; // [maxclients] + + // can't store spawnpoint in level, because + // it would get overwritten by the savegame restore + char spawnpoint[MAX_TOKEN_CHARS]; // needed for coop respawns + + // store latched cvars here that we want to get at often + uint32_t maxclients; + uint32_t maxentities; + + // cross level triggers + uint32_t cross_level_flags, cross_unit_flags; + + bool autosaved; + + // [Paril-KEX] + int32_t airacceleration_modified, gravity_modified; + std::array level_entries; + int32_t max_lag_origins; + vec3_t *lag_origins; // maxclients * max_lag_origins +}; + +constexpr size_t MAX_HEALTH_BARS = 2; + +// +// this structure is cleared as each map is entered +// it is read/written to the level.sav file for savegames +// +struct level_locals_t +{ + bool in_frame; + gtime_t time; + + char level_name[MAX_QPATH]; // the descriptive name (Outer Base, etc) + char mapname[MAX_QPATH]; // the server name (base1, etc) + char nextmap[MAX_QPATH]; // go here when fraglimit is hit + char forcemap[MAX_QPATH]; // go here + + // intermission state + gtime_t intermissiontime; // time the intermission was started + const char *changemap; + const char *achievement; + bool exitintermission; + bool intermission_eou; + bool intermission_clear; // [Paril-KEX] clear inventory on switch + bool level_intermission_set; // [Paril-KEX] for target_camera switches; don't find intermission point + bool intermission_fade, intermission_fading; // [Paril-KEX] fade on exit instead of immediately leaving + gtime_t intermission_fade_time; + vec3_t intermission_origin; + vec3_t intermission_angle; + bool respawn_intermission; // only set once for respawning players + + int32_t pic_health; + + int32_t total_secrets; + int32_t found_secrets; + + int32_t total_goals; + int32_t found_goals; + + int32_t total_monsters; + std::array monsters_registered; // only for debug + int32_t killed_monsters; + + edict_t *current_entity; // entity running from G_RunFrame + int32_t body_que; // dead bodies + + int32_t power_cubes; // ugly necessity for coop + + // ROGUE + edict_t *disguise_violator; + gtime_t disguise_violation_time; + int32_t disguise_icon; // [Paril-KEX] + // ROGUE + + int32_t shadow_light_count; // [Sam-KEX] + bool is_n64; + gtime_t coop_level_restart_time; // restart the level after this time + bool instantitems; // instantitems 1 set in worldspawn + + // N64 goal stuff + const char *goals; // nullptr if no goals in world + int32_t goal_num; // current relative goal number, increased with each target_goal + + // offset for the first vwep model, for + // skinnum encoding + int32_t vwep_offset; + + // coop health scaling factor; + // this percentage of health is added + // to the monster's health per player. + float coop_health_scaling; + // this isn't saved in the save file, but stores + // the amount of players currently active in the + // level, compared against monsters' individual + // scale # + int32_t coop_scale_players; + + // [Paril-KEX] current level entry + level_entry_t *entry; + + // [Paril-KEX] current poi + bool valid_poi; + vec3_t current_poi; + int32_t current_poi_image; + int32_t current_poi_stage; + edict_t *current_dynamic_poi; + vec3_t *poi_points[MAX_SPLIT_PLAYERS]; // temporary storage for POIs in coop + + // start items + const char *start_items; + // disable grappling hook + bool no_grapple; + + // saved gravity + float gravity; + // level is a hub map, and shouldn't be included in EOU stuff + bool hub_map; + // active health bar entities + std::array health_bar_entities; + int32_t intermission_server_frame; + bool deadly_kill_box; + bool story_active; + gtime_t next_auto_save; + gtime_t next_match_report; +}; + +struct shadow_light_temp_t +{ + shadow_light_data_t data; + const char *lightstyletarget = nullptr; +}; + +#include + +// spawn_temp_t is only used to hold entity field values that +// can be set from the editor, but aren't actualy present +// in edict_t during gameplay. +// defaults can/should be set in the struct. +struct spawn_temp_t +{ + // world vars + const char *sky; + float skyrotate; + vec3_t skyaxis; + int32_t skyautorotate = 1; + const char *nextmap; + + int32_t lip; + int32_t distance; + int32_t height; + const char *noise; + float pausetime; + const char *item; + const char *gravity; + + float minyaw; + float maxyaw; + float minpitch; + float maxpitch; + + shadow_light_temp_t sl; // [Sam-KEX] + const char* music; // [Edward-KEX] + int instantitems; + float radius; // [Paril-KEX] + bool hub_map; // [Paril-KEX] + const char *achievement; // [Paril-KEX] + + // [Paril-KEX] + const char *goals; + + // [Paril-KEX] + const char *image; + + int fade_start_dist = 96; + int fade_end_dist = 384; + const char *start_items; + int no_grapple = 0; + float health_multiplier = 1.0f; + + const char *reinforcements; // [Paril-KEX] + const char *noise_start, *noise_middle, *noise_end; // [Paril-KEX] + int32_t loop_count; // [Paril-KEX] + + std::unordered_set keys_specified; + + inline bool was_key_specified(const char *key) const + { + return keys_specified.find(key) != keys_specified.end(); + } +}; + +enum move_state_t +{ + STATE_TOP, + STATE_BOTTOM, + STATE_UP, + STATE_DOWN +}; + +#define DEFINE_DATA_FUNC(ns_lower, ns_upper, returnType, ...) \ + using save_##ns_lower##_t = save_data_t + +#define SAVE_DATA_FUNC(n, ns, returnType, ...) \ + using save_##n##_t = save_data_t; \ + extern returnType n(__VA_ARGS__); \ + static const save_data_list_t save__##n(#n, SAVE_FUNC_##ns, reinterpret_cast(n)); \ + auto n + +DEFINE_DATA_FUNC(moveinfo_endfunc, MOVEINFO_ENDFUNC, void, edict_t *self); +#define MOVEINFO_ENDFUNC(n) \ + SAVE_DATA_FUNC(n, MOVEINFO_ENDFUNC, void, edict_t *self) + +DEFINE_DATA_FUNC(moveinfo_blocked, MOVEINFO_BLOCKED, void, edict_t *self, edict_t *other); +#define MOVEINFO_BLOCKED(n) \ + SAVE_DATA_FUNC(n, MOVEINFO_BLOCKED, void, edict_t *self, edict_t *other) + +struct moveinfo_t +{ + // fixed data + vec3_t start_origin; + vec3_t start_angles; + vec3_t end_origin; + vec3_t end_angles, end_angles_reversed; + + int32_t sound_start; + int32_t sound_middle; + int32_t sound_end; + + float accel; + float speed; + float decel; + float distance; + + float wait; + + // state data + move_state_t state; + bool reversing; + vec3_t dir; + vec3_t dest; + float current_speed; + float move_speed; + float next_speed; + float remaining_distance; + float decel_distance; + save_moveinfo_endfunc_t endfunc; + save_moveinfo_blocked_t blocked; +}; + +struct mframe_t +{ + void (*aifunc)(edict_t *self, float dist) = nullptr; + float dist = 0; + void (*thinkfunc)(edict_t *self) = nullptr; + int32_t lerp_frame = -1; +}; + +// this check only works on windows, and is only +// of importance to developers anyways +#if defined(_WIN32) && defined(_MSC_VER) +#if _MSC_VER >= 1934 +#define COMPILE_TIME_MOVE_CHECK +#endif +#endif + +struct mmove_t +{ + int32_t firstframe; + int32_t lastframe; + const mframe_t *frame; + void (*endfunc)(edict_t *self); + float sidestep_scale; + +#ifdef COMPILE_TIME_MOVE_CHECK + template + constexpr mmove_t(int32_t firstframe, int32_t lastframe, const mframe_t (&frames)[N], void (*endfunc)(edict_t *self) = nullptr, float sidestep_scale = 0.0f) : + firstframe(firstframe), + lastframe(lastframe), + frame(frames), + endfunc(endfunc), + sidestep_scale(sidestep_scale) + { + if ((lastframe - firstframe + 1) != N) + throw std::exception("bad animation frames; check your numbers!"); + } +#endif +}; + +using save_mmove_t = save_data_t; +#ifdef COMPILE_TIME_MOVE_CHECK +#define MMOVE_T(n) \ + extern const mmove_t n; \ + static const save_data_list_t save__##n(#n, SAVE_DATA_MMOVE, &n); \ + constexpr mmove_t n +#else +#define MMOVE_T(n) \ + extern const mmove_t n; \ + static const save_data_list_t save__##n(#n, SAVE_DATA_MMOVE, &n); \ + const mmove_t n +#endif + +DEFINE_DATA_FUNC(monsterinfo_stand, MONSTERINFO_STAND, void, edict_t *self); +#define MONSTERINFO_STAND(n) \ + SAVE_DATA_FUNC(n, MONSTERINFO_STAND, void, edict_t *self) + +DEFINE_DATA_FUNC(monsterinfo_idle, MONSTERINFO_IDLE, void, edict_t *self); +#define MONSTERINFO_IDLE(n) \ + SAVE_DATA_FUNC(n, MONSTERINFO_IDLE, void, edict_t *self) + +DEFINE_DATA_FUNC(monsterinfo_search, MONSTERINFO_SEARCH, void, edict_t *self); +#define MONSTERINFO_SEARCH(n) \ + SAVE_DATA_FUNC(n, MONSTERINFO_SEARCH, void, edict_t *self) + +DEFINE_DATA_FUNC(monsterinfo_walk, MONSTERINFO_WALK, void, edict_t *self); +#define MONSTERINFO_WALK(n) \ + SAVE_DATA_FUNC(n, MONSTERINFO_WALK, void, edict_t *self) + +DEFINE_DATA_FUNC(monsterinfo_run, MONSTERINFO_RUN, void, edict_t *self); +#define MONSTERINFO_RUN(n) \ + SAVE_DATA_FUNC(n, MONSTERINFO_RUN, void, edict_t *self) + +DEFINE_DATA_FUNC(monsterinfo_dodge, MONSTERINFO_DODGE, void, edict_t *self, edict_t *attacker, gtime_t eta, trace_t *tr, bool gravity); +#define MONSTERINFO_DODGE(n) \ + SAVE_DATA_FUNC(n, MONSTERINFO_DODGE, void, edict_t *self, edict_t *attacker, gtime_t eta, trace_t *tr, bool gravity) + +DEFINE_DATA_FUNC(monsterinfo_attack, MONSTERINFO_ATTACK, void, edict_t *self); +#define MONSTERINFO_ATTACK(n) \ + SAVE_DATA_FUNC(n, MONSTERINFO_ATTACK, void, edict_t *self) + +DEFINE_DATA_FUNC(monsterinfo_melee, MONSTERINFO_MELEE, void, edict_t *self); +#define MONSTERINFO_MELEE(n) \ + SAVE_DATA_FUNC(n, MONSTERINFO_MELEE, void, edict_t *self) + +DEFINE_DATA_FUNC(monsterinfo_sight, MONSTERINFO_SIGHT, void, edict_t *self, edict_t *other); +#define MONSTERINFO_SIGHT(n) \ + SAVE_DATA_FUNC(n, MONSTERINFO_SIGHT, void, edict_t *self, edict_t *other) + +DEFINE_DATA_FUNC(monsterinfo_checkattack, MONSTERINFO_CHECKATTACK, bool, edict_t *self); +#define MONSTERINFO_CHECKATTACK(n) \ + SAVE_DATA_FUNC(n, MONSTERINFO_CHECKATTACK, bool, edict_t *self) + +DEFINE_DATA_FUNC(monsterinfo_setskin, MONSTERINFO_SETSKIN, void, edict_t *self); +#define MONSTERINFO_SETSKIN(n) \ + SAVE_DATA_FUNC(n, MONSTERINFO_SETSKIN, void, edict_t *self) + +DEFINE_DATA_FUNC(monsterinfo_blocked, MONSTERINFO_BLOCKED, bool, edict_t *self, float dist); +#define MONSTERINFO_BLOCKED(n) \ + SAVE_DATA_FUNC(n, MONSTERINFO_BLOCKED, bool, edict_t *self, float dist) + +DEFINE_DATA_FUNC(monsterinfo_physicschange, MONSTERINFO_PHYSCHANGED, void, edict_t *self); +#define MONSTERINFO_PHYSCHANGED(n) \ + SAVE_DATA_FUNC(n, MONSTERINFO_PHYSCHANGED, void, edict_t *self) + +DEFINE_DATA_FUNC(monsterinfo_duck, MONSTERINFO_DUCK, bool, edict_t *self, gtime_t eta); +#define MONSTERINFO_DUCK(n) \ + SAVE_DATA_FUNC(n, MONSTERINFO_DUCK, bool, edict_t *self, gtime_t eta) + +DEFINE_DATA_FUNC(monsterinfo_unduck, MONSTERINFO_UNDUCK, void, edict_t *self); +#define MONSTERINFO_UNDUCK(n) \ + SAVE_DATA_FUNC(n, MONSTERINFO_UNDUCK, void, edict_t *self) + +DEFINE_DATA_FUNC(monsterinfo_sidestep, MONSTERINFO_SIDESTEP, bool, edict_t *self); +#define MONSTERINFO_SIDESTEP(n) \ + SAVE_DATA_FUNC(n, MONSTERINFO_SIDESTEP, bool, edict_t *self) + +// combat styles, for navigation +enum combat_style_t +{ + COMBAT_UNKNOWN, // automatically choose based on attack functions + COMBAT_MELEE, // should attempt to get up close for melee + COMBAT_MIXED, // has mixed melee/ranged; runs to get up close if far enough away + COMBAT_RANGED // don't bother pathing if we can see the player +}; + +struct reinforcement_t +{ + const char *classname; + int32_t strength; + vec3_t mins, maxs; +}; + +struct reinforcement_list_t +{ + reinforcement_t *reinforcements; + uint32_t num_reinforcements; +}; + +constexpr size_t MAX_REINFORCEMENTS = 5; // max number of spawns we can do at once. + +constexpr gtime_t HOLD_FOREVER = gtime_t::from_ms(std::numeric_limits::max()); + +struct monsterinfo_t +{ + // [Paril-KEX] allow some moves to be done instantaneously, but + // others can wait the full frame. + // NB: always use `M_SetAnimation` as it handles edge cases. + save_mmove_t active_move, next_move; + monster_ai_flags_t aiflags; // PGM - unsigned, since we're close to the max + int32_t nextframe; // if next_move is set, this is ignored until a frame is ran + float scale; + + save_monsterinfo_stand_t stand; + save_monsterinfo_idle_t idle; + save_monsterinfo_search_t search; + save_monsterinfo_walk_t walk; + save_monsterinfo_run_t run; + save_monsterinfo_dodge_t dodge; + save_monsterinfo_attack_t attack; + save_monsterinfo_melee_t melee; + save_monsterinfo_sight_t sight; + save_monsterinfo_checkattack_t checkattack; + save_monsterinfo_setskin_t setskin; + save_monsterinfo_physicschange_t physics_change; + + gtime_t pausetime; + gtime_t attack_finished; + gtime_t fire_wait; + + vec3_t saved_goal; + gtime_t search_time; + gtime_t trail_time; + vec3_t last_sighting; + monster_attack_state_t attack_state; + bool lefty; + gtime_t idle_time; + int32_t linkcount; + + item_id_t power_armor_type; + int32_t power_armor_power; + + // for monster revive + item_id_t initial_power_armor_type; + int32_t max_power_armor_power; + int32_t weapon_sound, engine_sound; + + // ROGUE + save_monsterinfo_blocked_t blocked; + gtime_t last_hint_time; // last time the monster checked for hintpaths. + edict_t *goal_hint; // which hint_path we're trying to get to + int32_t medicTries; + edict_t *badMedic1, *badMedic2; // these medics have declared this monster "unhealable" + edict_t *healer; // this is who is healing this monster + save_monsterinfo_duck_t duck; + save_monsterinfo_unduck_t unduck; + save_monsterinfo_sidestep_t sidestep; + float base_height; + gtime_t next_duck_time; + gtime_t duck_wait_time; + edict_t *last_player_enemy; + // blindfire stuff .. the boolean says whether the monster will do it, and blind_fire_time is the timing + // (set in the monster) of the next shot + bool blindfire; // will the monster blindfire? + bool can_jump; // will the monster jump? + bool had_visibility; // Paril: used for blindfire + float drop_height, jump_height; + gtime_t blind_fire_delay; + vec3_t blind_fire_target; + // used by the spawners to not spawn too much and keep track of #s of monsters spawned + int32_t monster_slots; // nb: for spawned monsters, this is how many slots we took from our commander + int32_t monster_used; + edict_t *commander; + // powerup timers, used by widow, our friend + gtime_t quad_time; + gtime_t invincible_time; + gtime_t double_time; + // ROGUE + + // Paril + gtime_t surprise_time; + item_id_t armor_type; + int32_t armor_power; + bool close_sight_tripped; + gtime_t melee_debounce_time; // don't melee until this time has passed + gtime_t strafe_check_time; // time until we should reconsider strafing + int32_t base_health; // health that we had on spawn, before any co-op adjustments + int32_t health_scaling; // number of players we've been scaled up to + gtime_t next_move_time; // high tick rate + gtime_t bad_move_time; // don't try straight moves until this is over + gtime_t bump_time; // don't slide against walls for a bit + gtime_t random_change_time; // high tickrate + gtime_t path_blocked_counter; // break out of paths when > a certain time + gtime_t path_wait_time; // don't try nav nodes until this is over + PathInfo nav_path; // if AI_PATHING, this is where we are trying to reach + gtime_t nav_path_cache_time; // cache nav_path result for this much time + combat_style_t combat_style; // pathing style + + edict_t *damage_attacker; + edict_t *damage_inflictor; + int32_t damage_blood, damage_knockback; + vec3_t damage_from; + mod_t damage_mod; + + // alternate flying mechanics + float fly_max_distance, fly_min_distance; // how far we should try to stay + float fly_acceleration; // accel/decel speed + float fly_speed; // max speed from flying + vec3_t fly_ideal_position; // ideally where we want to end up to hover, relative to our target if not pinned + gtime_t fly_position_time; // if <= level.time, we can try changing positions + bool fly_buzzard, fly_above; // orbit around all sides of their enemy, not just the sides + bool fly_pinned; // whether we're currently pinned to ideal position (made absolute) + bool fly_thrusters; // slightly different flight mechanics, for melee attacks + gtime_t fly_recovery_time; // time to try a new dir to get away from hazards + vec3_t fly_recovery_dir; + + gtime_t checkattack_time; + int32_t start_frame; + gtime_t dodge_time; + int32_t move_block_counter; + gtime_t move_block_change_time; + gtime_t react_to_damage_time; + + reinforcement_list_t reinforcements; + std::array chosen_reinforcements; // readied for spawn; 255 is value for none + + gtime_t jump_time; + + // NOTE: if adding new elements, make sure to add them + // in g_save.cpp too! +}; + +// non-monsterinfo save stuff +using save_prethink_t = save_data_t; +#define PRETHINK(n) \ + void n(edict_t *self); \ + static const save_data_list_t save__##n(#n, SAVE_FUNC_PRETHINK, reinterpret_cast(n)); \ + auto n + +using save_think_t = save_data_t; +#define THINK(n) \ + void n(edict_t *self); \ + static const save_data_list_t save__##n(#n, SAVE_FUNC_THINK, reinterpret_cast(n)); \ + auto n + +using save_touch_t = save_data_t; +#define TOUCH(n) \ + void n(edict_t *self, edict_t *other, const trace_t &tr, bool other_touching_self); \ + static const save_data_list_t save__##n(#n, SAVE_FUNC_TOUCH, reinterpret_cast(n)); \ + auto n + +using save_use_t = save_data_t; +#define USE(n) \ + void n(edict_t *self, edict_t *other, edict_t *activator); \ + static const save_data_list_t save__##n(#n, SAVE_FUNC_USE, reinterpret_cast(n)); \ + auto n + +using save_pain_t = save_data_t; +#define PAIN(n) \ + void n(edict_t *self, edict_t *other, float kick, int damage, const mod_t &mod); \ + static const save_data_list_t save__##n(#n, SAVE_FUNC_PAIN, reinterpret_cast(n)); \ + auto n + +using save_die_t = save_data_t; +#define DIE(n) \ + void n(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod); \ + static const save_data_list_t save__##n(#n, SAVE_FUNC_DIE, reinterpret_cast(n)); \ + auto n + +// ROGUE +// this determines how long to wait after a duck to duck again. +// if we finish a duck-up, this gets cut in half. +constexpr gtime_t DUCK_INTERVAL = 5000_ms; +// ROGUE + +extern game_locals_t game; +extern level_locals_t level; +extern game_export_t globals; +extern spawn_temp_t st; + +extern int sm_meat_index; +extern int snd_fry; + +extern edict_t *g_edicts; + +#include +extern std::mt19937 mt_rand; + +// uniform float [0, 1) +[[nodiscard]] inline float frandom() +{ + return std::uniform_real_distribution()(mt_rand); +} + +// uniform float [min_inclusive, max_exclusive) +[[nodiscard]] inline float frandom(float min_inclusive, float max_exclusive) +{ + return std::uniform_real_distribution(min_inclusive, max_exclusive)(mt_rand); +} + +// uniform float [0, max_exclusive) +[[nodiscard]] inline float frandom(float max_exclusive) +{ + return std::uniform_real_distribution(0, max_exclusive)(mt_rand); +} + +// uniform time [min_inclusive, max_exclusive) +[[nodiscard]] inline gtime_t random_time(gtime_t min_inclusive, gtime_t max_exclusive) +{ + return gtime_t::from_ms(std::uniform_int_distribution(min_inclusive.milliseconds(), max_exclusive.milliseconds())(mt_rand)); +} + +// uniform time [0, max_exclusive) +[[nodiscard]] inline gtime_t random_time(gtime_t max_exclusive) +{ + return gtime_t::from_ms(std::uniform_int_distribution(0, max_exclusive.milliseconds())(mt_rand)); +} + +// uniform float [-1, 1) +// note: closed on min but not max +// to match vanilla behavior +[[nodiscard]] inline float crandom() +{ + return std::uniform_real_distribution(-1.f, 1.f)(mt_rand); +} + +// uniform float (-1, 1) +[[nodiscard]] inline float crandom_open() +{ + return std::uniform_real_distribution(std::nextafterf(-1.f, 0.f), 1.f)(mt_rand); +} + +// raw unsigned int32 value from random +[[nodiscard]] inline uint32_t irandom() +{ + return mt_rand(); +} + +// uniform int [min, max) +// always returns min if min == (max - 1) +// undefined behavior if min > (max - 1) +[[nodiscard]] inline int32_t irandom(int32_t min_inclusive, int32_t max_exclusive) +{ + if (min_inclusive == max_exclusive - 1) + return min_inclusive; + + return std::uniform_int_distribution(min_inclusive, max_exclusive - 1)(mt_rand); +} + +// uniform int [0, max) +// always returns 0 if max <= 0 +// note for Q2 code: +// - to fix rand()%x, do irandom(x) +// - to fix rand()&x, do irandom(x + 1) +[[nodiscard]] inline int32_t irandom(int32_t max_exclusive) +{ + if (max_exclusive <= 0) + return 0; + + return irandom(0, max_exclusive); +} + +// uniform random index from given container +template +[[nodiscard]] inline int32_t random_index(const T &container) +{ + return irandom(std::size(container)); +} + +// uniform random element from given container +template +[[nodiscard]] inline auto random_element(T &container) -> decltype(*std::begin(container)) +{ + return *(std::begin(container) + random_index(container)); +} + +// flip a coin +[[nodiscard]]inline bool brandom() +{ + return irandom(2) == 0; +} + +extern cvar_t *deathmatch; +extern cvar_t *coop; +extern cvar_t *skill; +extern cvar_t *fraglimit; +extern cvar_t *timelimit; +// ZOID +extern cvar_t *capturelimit; +extern cvar_t *g_quick_weapon_switch; +extern cvar_t *g_instant_weapon_switch; +// ZOID +extern cvar_t *password; +extern cvar_t *spectator_password; +extern cvar_t *needpass; +extern cvar_t *g_select_empty; +extern cvar_t *sv_dedicated; + +extern cvar_t *filterban; + +extern cvar_t *sv_gravity; +extern cvar_t *sv_maxvelocity; + +extern cvar_t *gun_x, *gun_y, *gun_z; +extern cvar_t *sv_rollspeed; +extern cvar_t *sv_rollangle; + +extern cvar_t *run_pitch; +extern cvar_t *run_roll; +extern cvar_t *bob_up; +extern cvar_t *bob_pitch; +extern cvar_t *bob_roll; + +extern cvar_t *sv_cheats; +extern cvar_t *g_debug_monster_paths; +extern cvar_t *g_debug_monster_kills; +extern cvar_t *maxspectators; + +extern cvar_t *bot_debug_follow_actor; +extern cvar_t *bot_debug_move_to_point; + +extern cvar_t *flood_msgs; +extern cvar_t *flood_persecond; +extern cvar_t *flood_waitdelay; + +extern cvar_t *sv_maplist; + +extern cvar_t *g_skipViewModifiers; + +extern cvar_t *sv_stopspeed; // PGM - this was a define in g_phys.c + +extern cvar_t *g_strict_saves; +extern cvar_t *g_coop_health_scaling; +extern cvar_t *g_weapon_respawn_time; + +extern cvar_t* g_no_health; +extern cvar_t* g_no_items; +extern cvar_t* g_dm_weapons_stay; +extern cvar_t* g_dm_no_fall_damage; +extern cvar_t* g_dm_instant_items; +extern cvar_t* g_dm_same_level; +extern cvar_t* g_friendly_fire; +extern cvar_t* g_dm_force_respawn; +extern cvar_t* g_dm_force_respawn_time; +extern cvar_t* g_dm_spawn_farthest; +extern cvar_t* g_no_armor; +extern cvar_t* g_dm_allow_exit; +extern cvar_t* g_infinite_ammo; +extern cvar_t* g_dm_no_quad_drop; +extern cvar_t* g_dm_no_quadfire_drop; +extern cvar_t* g_no_mines; +extern cvar_t* g_dm_no_stack_double; +extern cvar_t* g_no_nukes; +extern cvar_t* g_no_spheres; +extern cvar_t* g_teamplay_armor_protect; +extern cvar_t* g_allow_techs; + +extern cvar_t* g_start_items; +extern cvar_t* g_map_list; +extern cvar_t* g_map_list_shuffle; +extern cvar_t *g_lag_compensation; + +// ROGUE +extern cvar_t *gamerules; +extern cvar_t *huntercam; +extern cvar_t *g_dm_strong_mines; +extern cvar_t *g_dm_random_items; +// ROGUE + +// [Kex] +extern cvar_t* g_instagib; +extern cvar_t* g_coop_player_collision; +extern cvar_t* g_coop_squad_respawn; +extern cvar_t* g_coop_enable_lives; +extern cvar_t* g_coop_num_lives; +extern cvar_t* g_coop_instanced_items; +extern cvar_t* g_allow_grapple; +extern cvar_t* g_grapple_fly_speed; +extern cvar_t* g_grapple_pull_speed; +extern cvar_t* g_grapple_damage; + +extern cvar_t *sv_airaccelerate; + +extern cvar_t *g_damage_scale; +extern cvar_t *g_disable_player_collision; +extern cvar_t *ai_damage_scale; +extern cvar_t *ai_model_scale; +extern cvar_t *ai_allow_dm_spawn; +extern cvar_t *ai_movement_disabled; + +#define world (&g_edicts[0]) + +uint32_t GetUnicastKey(); + +// item spawnflags +constexpr spawnflags_t SPAWNFLAG_ITEM_TRIGGER_SPAWN = 0x00000001_spawnflag; +constexpr spawnflags_t SPAWNFLAG_ITEM_NO_TOUCH = 0x00000002_spawnflag; +constexpr spawnflags_t SPAWNFLAG_ITEM_TOSS_SPAWN = 0x00000004_spawnflag; +constexpr spawnflags_t SPAWNFLAG_ITEM_MAX = 0x00000008_spawnflag; +// 8 bits reserved for editor flags & power cube bits +// (see SPAWNFLAG_NOT_EASY above) +constexpr spawnflags_t SPAWNFLAG_ITEM_DROPPED = 0x00010000_spawnflag; +constexpr spawnflags_t SPAWNFLAG_ITEM_DROPPED_PLAYER = 0x00020000_spawnflag; +constexpr spawnflags_t SPAWNFLAG_ITEM_TARGETS_USED = 0x00040000_spawnflag; + +extern gitem_t itemlist[IT_TOTAL]; + +// +// g_cmds.c +// +bool CheckFlood(edict_t *ent); +void Cmd_Help_f(edict_t *ent); +void Cmd_Score_f(edict_t *ent); + +// +// g_items.c +// +void PrecacheItem(gitem_t *it); +void InitItems(); +void SetItemNames(); +gitem_t *FindItem(const char *pickup_name); +gitem_t *FindItemByClassname(const char *classname); +edict_t *Drop_Item(edict_t *ent, gitem_t *item); +void SetRespawn(edict_t *ent, gtime_t delay, bool hide_self = true); +void ChangeWeapon(edict_t *ent); +void SpawnItem(edict_t *ent, gitem_t *item); +void Think_Weapon(edict_t *ent); +item_id_t ArmorIndex(edict_t *ent); +item_id_t PowerArmorType(edict_t *ent); +gitem_t *GetItemByIndex(item_id_t index); +gitem_t *GetItemByAmmo(ammo_t ammo); +gitem_t *GetItemByPowerup(powerup_t powerup); +bool Add_Ammo(edict_t *ent, gitem_t *item, int count); +void G_CheckPowerArmor(edict_t *ent); +void Touch_Item(edict_t *ent, edict_t *other, const trace_t &tr, bool other_touching_self); +void droptofloor(edict_t *ent); +void P_ToggleFlashlight(edict_t *ent, bool state); +bool Entity_IsVisibleToPlayer(edict_t* ent, edict_t* player); +void Compass_Update(edict_t *ent, bool first); + +// +// g_utils.c +// +bool KillBox(edict_t *ent, bool from_spawning, mod_id_t mod = MOD_TELEFRAG, bool bsp_clipping = true); +edict_t *G_Find(edict_t *from, std::function matcher); + +// utility template for getting the type of a field +template +struct member_object_type { }; +template +struct member_object_type { using type = T1; }; +template +using member_object_type_t = typename member_object_type>::type; + +template +edict_t *G_FindByString(edict_t *from, const std::string_view &value) +{ + static_assert(std::is_same_v, const char *>, "can only use string member functions"); + + return G_Find(from, [&](edict_t *e) { + return e->*M && strlen(e->*M) == value.length() && !Q_strncasecmp(e->*M, value.data(), value.length()); + }); +} + +edict_t *findradius(edict_t *from, const vec3_t &org, float rad); +edict_t *G_PickTarget(const char *targetname); +void G_UseTargets(edict_t *ent, edict_t *activator); +void G_PrintActivationMessage(edict_t *ent, edict_t *activator, bool coop_global); +void G_SetMovedir(vec3_t &angles, vec3_t &movedir); + +void G_InitEdict(edict_t *e); +edict_t *G_Spawn(); +void G_FreeEdict(edict_t *e); + +void G_TouchTriggers(edict_t *ent); +void G_TouchProjectiles(edict_t *ent, vec3_t previous_origin); + +char *G_CopyString(const char *in, int32_t tag); +char *G_CopyString(const char *in, size_t len, int32_t tag); + +// ROGUE +edict_t *findradius2(edict_t *from, const vec3_t &org, float rad); +// ROGUE + +void G_PlayerNotifyGoal(edict_t *player); + +// +// g_spawn.c +// +void ED_CallSpawn(edict_t *ent); +char *ED_NewString(char *string); + +// +// g_target.c +// +void target_laser_think(edict_t *self); +void target_laser_off(edict_t *self); + +constexpr spawnflags_t SPAWNFLAG_LASER_ON = 0x0001_spawnflag; +constexpr spawnflags_t SPAWNFLAG_LASER_RED = 0x0002_spawnflag; +constexpr spawnflags_t SPAWNFLAG_LASER_GREEN = 0x0004_spawnflag; +constexpr spawnflags_t SPAWNFLAG_LASER_BLUE = 0x0008_spawnflag; +constexpr spawnflags_t SPAWNFLAG_LASER_YELLOW = 0x0010_spawnflag; +constexpr spawnflags_t SPAWNFLAG_LASER_ORANGE = 0x0020_spawnflag; +constexpr spawnflags_t SPAWNFLAG_LASER_FAT = 0x0040_spawnflag; +constexpr spawnflags_t SPAWNFLAG_LASER_ZAP = 0x80000000_spawnflag; +constexpr spawnflags_t SPAWNFLAG_LASER_LIGHTNING = 0x10000_spawnflag; + +constexpr spawnflags_t SPAWNFLAG_HEALTHBAR_PVS_ONLY = 1_spawnflag; + +// damage flags +enum damageflags_t +{ + DAMAGE_NONE = 0, // no damage flags + DAMAGE_RADIUS = 0x00000001, // damage was indirect + DAMAGE_NO_ARMOR = 0x00000002, // armour does not protect from this damage + DAMAGE_ENERGY = 0x00000004, // damage is from an energy based weapon + DAMAGE_NO_KNOCKBACK = 0x00000008, // do not affect velocity, just view angles + DAMAGE_BULLET = 0x00000010, // damage is from a bullet (used for ricochets) + DAMAGE_NO_PROTECTION = 0x00000020, // armor, shields, invulnerability, and godmode have no effect + // ROGUE + DAMAGE_DESTROY_ARMOR = 0x00000040, // damage is done to armor and health. + DAMAGE_NO_REG_ARMOR = 0x00000080, // damage skips regular armor + DAMAGE_NO_POWER_ARMOR = 0x00000100,// damage skips power armor + // ROGUE + DAMAGE_NO_INDICATOR = 0x00000200 // for clients: no damage indicators +}; + +MAKE_ENUM_BITFLAGS(damageflags_t); + +// +// g_combat.c +// +bool OnSameTeam(edict_t *ent1, edict_t *ent2); +bool CanDamage(edict_t *targ, edict_t *inflictor); +bool CheckTeamDamage(edict_t *targ, edict_t *attacker); +void T_Damage(edict_t *targ, edict_t *inflictor, edict_t *attacker, const vec3_t &dir, const vec3_t &point, + const vec3_t &normal, int damage, int knockback, damageflags_t dflags, mod_t mod); +void T_RadiusDamage(edict_t *inflictor, edict_t *attacker, float damage, edict_t *ignore, float radius, damageflags_t dflags, mod_t mod); +void Killed(edict_t *targ, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, mod_t mod); + +// ROGUE +void T_RadiusNukeDamage(edict_t *inflictor, edict_t *attacker, float damage, edict_t *ignore, float radius, mod_t mod); +void T_RadiusClassDamage(edict_t *inflictor, edict_t *attacker, float damage, char *ignoreClass, float radius, + mod_t mod); +void cleanupHealTarget(edict_t *ent); +// ROGUE + +constexpr int32_t DEFAULT_BULLET_HSPREAD = 300; +constexpr int32_t DEFAULT_BULLET_VSPREAD = 500; +constexpr int32_t DEFAULT_SHOTGUN_HSPREAD = 1000; +constexpr int32_t DEFAULT_SHOTGUN_VSPREAD = 500; +constexpr int32_t DEFAULT_DEATHMATCH_SHOTGUN_COUNT = 12; +constexpr int32_t DEFAULT_SHOTGUN_COUNT = 12; +constexpr int32_t DEFAULT_SSHOTGUN_COUNT = 20; + +// +// g_func.c +// +void train_use(edict_t *self, edict_t *other, edict_t *activator); +void func_train_find(edict_t *self); +edict_t *plat_spawn_inside_trigger(edict_t *ent); +void Move_Calc(edict_t *ent, const vec3_t &dest, void(*endfunc)(edict_t *self)); +void G_SetMoveinfoSounds(edict_t *self, const char *default_start, const char *default_mid, const char *default_end); + +constexpr spawnflags_t SPAWNFLAG_TRAIN_START_ON = 1_spawnflag; + +constexpr spawnflags_t SPAWNFLAG_WATER_SMART = 2_spawnflag; + +constexpr spawnflags_t SPAWNFLAG_TRAIN_MOVE_TEAMCHAIN = 8_spawnflag; + +constexpr spawnflags_t SPAWNFLAG_DOOR_REVERSE = 2_spawnflag; + +// +// g_monster.c +// +void monster_muzzleflash(edict_t *self, const vec3_t &start, monster_muzzleflash_id_t id); +void monster_fire_bullet(edict_t *self, const vec3_t &start, const vec3_t &dir, int damage, int kick, int hspread, + int vspread, monster_muzzleflash_id_t flashtype); +void monster_fire_shotgun(edict_t *self, const vec3_t &start, const vec3_t &aimdir, int damage, int kick, int hspread, + int vspread, int count, monster_muzzleflash_id_t flashtype); +void monster_fire_blaster(edict_t *self, const vec3_t &start, const vec3_t &dir, int damage, int speed, + monster_muzzleflash_id_t flashtype, effects_t effect); +void monster_fire_flechette(edict_t *self, const vec3_t &start, const vec3_t &dir, int damage, int speed, + monster_muzzleflash_id_t flashtype); +void monster_fire_grenade(edict_t *self, const vec3_t &start, const vec3_t &aimdir, int damage, int speed, + monster_muzzleflash_id_t flashtype, float right_adjust, float up_adjust); +void monster_fire_rocket(edict_t *self, const vec3_t &start, const vec3_t &dir, int damage, int speed, + monster_muzzleflash_id_t flashtype); +void monster_fire_railgun(edict_t *self, const vec3_t &start, const vec3_t &aimdir, int damage, int kick, + monster_muzzleflash_id_t flashtype); +void monster_fire_bfg(edict_t *self, const vec3_t &start, const vec3_t &aimdir, int damage, int speed, int kick, + float damage_radius, monster_muzzleflash_id_t flashtype); +bool M_CheckClearShot(edict_t *self, const vec3_t &offset); +bool M_CheckClearShot(edict_t *self, const vec3_t &offset, vec3_t &start); +vec3_t M_ProjectFlashSource(edict_t *self, const vec3_t &offset, const vec3_t &forward, const vec3_t &right); +bool M_droptofloor_generic(vec3_t &origin, const vec3_t &mins, const vec3_t &maxs, bool ceiling, edict_t *ignore, contents_t mask, bool allow_partial); +bool M_droptofloor(edict_t *ent); +void monster_think(edict_t *self); +void monster_dead_think(edict_t *self); +void monster_dead(edict_t *self); +void walkmonster_start(edict_t *self); +void swimmonster_start(edict_t *self); +void flymonster_start(edict_t *self); +void monster_death_use(edict_t *self); +void M_CatagorizePosition(edict_t *self, const vec3_t &in_point, water_level_t &waterlevel, contents_t &watertype); +void M_WorldEffects(edict_t *ent); +bool M_CheckAttack(edict_t *self); +void M_CheckGround(edict_t *ent, contents_t mask); +void monster_use(edict_t *self, edict_t *other, edict_t *activator); +void M_ProcessPain(edict_t *e); +bool M_ShouldReactToPain(edict_t *self, const mod_t &mod); +void M_SetAnimation(edict_t *self, const save_mmove_t &move, bool instant = true); +bool M_AllowSpawn( edict_t * self ); + +// Paril: used in N64. causes them to be mad at the player +// regardless of circumstance. +constexpr size_t HACKFLAG_ATTACK_PLAYER = 1; +// used in N64, appears to change their behavior for the end scene. +constexpr size_t HACKFLAG_END_CUTSCENE = 4; + +bool monster_start(edict_t *self); +void monster_start_go(edict_t *self); +// RAFAEL +void monster_fire_ionripper(edict_t *self, const vec3_t &start, const vec3_t &dir, int damage, int speed, + monster_muzzleflash_id_t flashtype, effects_t effect); +void monster_fire_heat(edict_t *self, const vec3_t &start, const vec3_t &dir, int damage, int speed, + monster_muzzleflash_id_t flashtype, float lerp_factor); +void monster_fire_dabeam(edict_t *self, int damage, bool secondary, void(*update_func)(edict_t *self)); +void dabeam_update(edict_t *self, bool damage); +void monster_fire_blueblaster(edict_t *self, const vec3_t &start, const vec3_t &dir, int damage, int speed, + monster_muzzleflash_id_t flashtype, effects_t effect); +void G_Monster_CheckCoopHealthScaling(); +// RAFAEL +// ROGUE +void monster_fire_blaster2(edict_t *self, const vec3_t &start, const vec3_t &dir, int damage, int speed, + monster_muzzleflash_id_t flashtype, effects_t effect); +void monster_fire_tracker(edict_t *self, const vec3_t &start, const vec3_t &dir, int damage, int speed, edict_t *enemy, + monster_muzzleflash_id_t flashtype); +void monster_fire_heatbeam(edict_t *self, const vec3_t &start, const vec3_t &dir, const vec3_t &offset, int damage, + int kick, monster_muzzleflash_id_t flashtype); +void stationarymonster_start(edict_t *self); +void monster_done_dodge(edict_t *self); +// ROGUE + +stuck_result_t G_FixStuckObject(edict_t *self, vec3_t check); + +// this is for the count of monsters +int32_t M_SlotsLeft(edict_t *self); + +// shared with monsters +constexpr spawnflags_t SPAWNFLAG_MONSTER_AMBUSH = 1_spawnflag; +constexpr spawnflags_t SPAWNFLAG_MONSTER_TRIGGER_SPAWN = 2_spawnflag; +constexpr spawnflags_t SPAWNFLAG_MONSTER_DEAD = 16_spawnflag_bit; +constexpr spawnflags_t SPAWNFLAG_MONSTER_SUPER_STEP = 17_spawnflag_bit; +constexpr spawnflags_t SPAWNFLAG_MONSTER_NO_DROP = 18_spawnflag_bit; +constexpr spawnflags_t SPAWNFLAG_MONSTER_SCENIC = 19_spawnflag_bit; + +// fixbot spawnflags +constexpr spawnflags_t SPAWNFLAG_FIXBOT_FIXIT = 4_spawnflag; +constexpr spawnflags_t SPAWNFLAG_FIXBOT_TAKEOFF = 8_spawnflag; +constexpr spawnflags_t SPAWNFLAG_FIXBOT_LANDING = 16_spawnflag; +constexpr spawnflags_t SPAWNFLAG_FIXBOT_WORKING = 32_spawnflag; + +// +// g_misc.c +// +void ThrowClientHead(edict_t *self, int damage); +void gib_die(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod); +edict_t *ThrowGib(edict_t *self, const char *gibname, int damage, gib_type_t type, float scale); +void BecomeExplosion1(edict_t *self); +void misc_viper_use(edict_t *self, edict_t *other, edict_t *activator); +void misc_strogg_ship_use(edict_t *self, edict_t *other, edict_t *activator); +void VelocityForDamage(int damage, vec3_t &v); +void ClipGibVelocity(edict_t *ent); + +constexpr spawnflags_t SPAWNFLAG_PATH_CORNER_TELEPORT = 1_spawnflag; + +constexpr spawnflags_t SPAWNFLAG_POINT_COMBAT_HOLD = 1_spawnflag; + +// max chars for a clock string; +// " 0:00:00" is the longest string possible +// plus null terminator. +constexpr size_t CLOCK_MESSAGE_SIZE = 9; + +// +// g_ai.c +// +edict_t *AI_GetSightClient(edict_t *self); + +void ai_stand(edict_t *self, float dist); +void ai_move(edict_t *self, float dist); +void ai_walk(edict_t *self, float dist); +void ai_turn(edict_t *self, float dist); +void ai_run(edict_t *self, float dist); +void ai_charge(edict_t *self, float dist); + +constexpr float RANGE_MELEE = 20; // bboxes basically touching +constexpr float RANGE_NEAR = 440; +constexpr float RANGE_MID = 940; + +// [Paril-KEX] adjusted to return an actual distance, measured +// in a way that is consistent regardless of what is fighting what +float range_to(edict_t *self, edict_t *other); + +void FoundTarget(edict_t *self); +void HuntTarget(edict_t *self, bool animate_state = true); +bool infront(edict_t *self, edict_t *other); +bool visible(edict_t *self, edict_t *other, bool through_glass = true); +bool FacingIdeal(edict_t *self); + + +// +// g_weapon.c +// +bool fire_hit(edict_t *self, vec3_t aim, int damage, int kick); +void fire_bullet(edict_t *self, const vec3_t &start, const vec3_t &aimdir, int damage, int kick, int hspread, + int vspread, mod_t mod); +void fire_shotgun(edict_t *self, const vec3_t &start, const vec3_t &aimdir, int damage, int kick, int hspread, + int vspread, int count, mod_t mod); +void blaster_touch(edict_t *self, edict_t *other, const trace_t &tr, bool other_touching_self); +void fire_blaster(edict_t *self, const vec3_t &start, const vec3_t &aimdir, int damage, int speed, effects_t effect, + mod_t mod); +void Grenade_Explode(edict_t *ent); +void fire_grenade(edict_t *self, const vec3_t &start, const vec3_t &aimdir, int damage, int speed, gtime_t timer, + float damage_radius, float right_adjust, float up_adjust, bool monster); +void fire_grenade2(edict_t *self, const vec3_t &start, const vec3_t &aimdir, int damage, int speed, gtime_t timer, + float damage_radius, bool held); +void rocket_touch(edict_t *ent, edict_t *other, const trace_t &tr, bool other_touching_self); +edict_t *fire_rocket(edict_t *self, const vec3_t &start, const vec3_t &dir, int damage, int speed, float damage_radius, + int radius_damage); +void fire_rail(edict_t *self, const vec3_t &start, const vec3_t &aimdir, int damage, int kick); +void fire_bfg(edict_t *self, const vec3_t &start, const vec3_t &dir, int damage, int speed, float damage_radius); +// RAFAEL +void fire_ionripper(edict_t *self, const vec3_t &start, const vec3_t &aimdir, int damage, int speed, effects_t effect); +void fire_heat(edict_t *self, const vec3_t &start, const vec3_t &dir, int damage, int speed, float damage_radius, + int radius_damage, float turn_fraction); +void fire_blueblaster(edict_t *self, const vec3_t &start, const vec3_t &aimdir, int damage, int speed, + effects_t effect); +void fire_plasma(edict_t *self, const vec3_t &start, const vec3_t &dir, int damage, int speed, float damage_radius, + int radius_damage); +void fire_trap(edict_t *self, const vec3_t &start, const vec3_t &aimdir, int speed); +// RAFAEL +void fire_disintegrator(edict_t *self, const vec3_t &start, const vec3_t &dir, int speed); +vec3_t P_CurrentKickAngles(edict_t *ent); +vec3_t P_CurrentKickOrigin(edict_t *ent); +void P_AddWeaponKick(edict_t *ent, const vec3_t &origin, const vec3_t &angles); + +// we won't ever pierce more than this many entities for a single trace. +constexpr size_t MAX_PIERCE = 16; + +// base class for pierce args; this stores +// the stuff we are piercing. +struct pierce_args_t +{ + // stuff we pierced + std::array pierced; + std::array pierce_solidities; + size_t num_pierced = 0; + // the last trace that was done, when piercing stopped + trace_t tr; + + // mark entity as pierced + inline bool mark(edict_t *ent); + + // restore entities' previous solidities + inline void restore(); + + // we hit an entity; return false to stop the piercing. + // you can adjust the mask for the re-trace (for water, etc). + virtual bool hit(contents_t &mask, vec3_t &end) = 0; + + virtual ~pierce_args_t() + { + restore(); + } +}; + +void pierce_trace(const vec3_t &start, const vec3_t &end, edict_t *ignore, pierce_args_t &pierce, contents_t mask); + +// +// g_ptrail.c +// +void PlayerTrail_Add(edict_t *player); +void PlayerTrail_Destroy(edict_t *player); +edict_t *PlayerTrail_Pick(edict_t *self, bool next); + +// +// g_client.c +// +constexpr spawnflags_t SPAWNFLAG_CHANGELEVEL_CLEAR_INVENTORY = 8_spawnflag; +constexpr spawnflags_t SPAWNFLAG_CHANGELEVEL_NO_END_OF_UNIT = 16_spawnflag; +constexpr spawnflags_t SPAWNFLAG_CHANGELEVEL_FADE_OUT = 32_spawnflag; +constexpr spawnflags_t SPAWNFLAG_CHANGELEVEL_IMMEDIATE_LEAVE = 64_spawnflag; + +void respawn(edict_t *ent); +void BeginIntermission(edict_t *targ); +void PutClientInServer(edict_t *ent); +void InitClientPersistant(edict_t *ent, gclient_t *client); +void InitClientResp(gclient_t *client); +void InitBodyQue(); +void ClientBeginServerFrame(edict_t *ent); +void ClientUserinfoChanged(edict_t *ent, const char *userinfo); +void P_AssignClientSkinnum(edict_t *ent); +void P_ForceFogTransition(edict_t *ent, bool instant); +void P_SendLevelPOI(edict_t *ent); +unsigned int P_GetLobbyUserNum( const edict_t * player ); +void G_UpdateLevelEntry(); +void G_EndOfUnitMessage(); +bool SelectSpawnPoint(edict_t *ent, vec3_t &origin, vec3_t &angles, bool force_spawn, bool &landmark); + +struct select_spawn_result_t +{ + edict_t *spot; + bool any_valid = false; // set if a spawn point was found, even if it was taken +}; + +select_spawn_result_t SelectDeathmatchSpawnPoint(bool farthest, bool force_spawn, bool fallback_to_ctf_or_start); +void G_PostRespawn(edict_t *self); + +// +// g_player.c +// +void player_die(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod); + +// +// g_svcmds.c +// +void ServerCommand(); +bool SV_FilterPacket(const char *from); + +// +// p_view.c +// +void ClientEndServerFrame(edict_t *ent); +void G_LagCompensate(edict_t *from_player, const vec3_t &start, const vec3_t &dir); +void G_UnLagCompensate(); + +// +// p_hud.c +// +void MoveClientToIntermission(edict_t *client); +void G_SetStats(edict_t *ent); +void G_SetCoopStats(edict_t *ent); +void G_SetSpectatorStats(edict_t *ent); +void G_CheckChaseStats(edict_t *ent); +void ValidateSelectedItem(edict_t *ent); +void DeathmatchScoreboardMessage(edict_t *client, edict_t *killer); +void G_ReportMatchDetails(bool is_end); + +// +// p_weapon.c +// +void PlayerNoise(edict_t *who, const vec3_t &where, player_noise_t type); +void P_ProjectSource(edict_t *ent, const vec3_t &angles, vec3_t distance, vec3_t &result_start, vec3_t &result_dir); +void NoAmmoWeaponChange(edict_t *ent, bool sound); +void G_RemoveAmmo(edict_t *ent); +void G_RemoveAmmo(edict_t *ent, int32_t quantity); +void Weapon_Generic(edict_t *ent, int FRAME_ACTIVATE_LAST, int FRAME_FIRE_LAST, int FRAME_IDLE_LAST, + int FRAME_DEACTIVATE_LAST, const int *pause_frames, const int *fire_frames, + void (*fire)(edict_t *ent)); +void Weapon_Repeating(edict_t *ent, int FRAME_ACTIVATE_LAST, int FRAME_FIRE_LAST, int FRAME_IDLE_LAST, + int FRAME_DEACTIVATE_LAST, const int *pause_frames, void (*fire)(edict_t *ent)); +void Throw_Generic(edict_t *ent, int FRAME_FIRE_LAST, int FRAME_IDLE_LAST, int FRAME_PRIME_SOUND, + const char *prime_sound, int FRAME_THROW_HOLD, int FRAME_THROW_FIRE, const int *pause_frames, + int EXPLODE, const char *primed_sound, void (*fire)(edict_t *ent, bool held), bool extra_idle_frame); +byte P_DamageModifier(edict_t *ent); +bool G_CheckInfiniteAmmo(gitem_t *item); +void Weapon_PowerupSound(edict_t *ent); + +constexpr gtime_t GRENADE_TIMER = 3_sec; +constexpr float GRENADE_MINSPEED = 400.f; +constexpr float GRENADE_MAXSPEED = 800.f; + +extern bool is_quad; +// RAFAEL +extern bool is_quadfire; +// RAFAEL +extern player_muzzle_t is_silenced; +// ROGUE +extern byte damage_multiplier; +// ROGUE + +// +// m_move.c +// +bool M_CheckBottom_Fast_Generic(const vec3_t &absmins, const vec3_t &absmaxs, bool ceiling); +bool M_CheckBottom_Slow_Generic(const vec3_t &origin, const vec3_t &absmins, const vec3_t &absmaxs, edict_t *ignore, contents_t mask, bool ceiling, bool allow_any_step_height); +bool M_CheckBottom(edict_t *ent); +bool SV_CloseEnough(edict_t *ent, edict_t *goal, float dist); +bool M_walkmove(edict_t *ent, float yaw, float dist); +void M_MoveToGoal(edict_t *ent, float dist); +void M_ChangeYaw(edict_t *ent); +bool ai_check_move(edict_t *self, float dist); + +// +// g_phys.c +// +constexpr float sv_friction = 6; +constexpr float sv_waterfriction = 1; + +void G_RunEntity(edict_t *ent); +bool SV_RunThink(edict_t *ent); +void SV_AddRotationalFriction(edict_t *ent); +void SV_AddGravity(edict_t *ent); +void SV_CheckVelocity(edict_t *ent); +void SV_FlyMove(edict_t *ent, float time, contents_t mask); +contents_t G_GetClipMask(edict_t *ent); +void G_Impact(edict_t *e1, const trace_t &trace); + +// +// g_main.c +// +void SaveClientData(); +void FetchClientEntData(edict_t *ent); +void EndDMLevel(); + +// +// g_chase.c +// +void UpdateChaseCam(edict_t *ent); +void ChaseNext(edict_t *ent); +void ChasePrev(edict_t *ent); +void GetChaseTarget(edict_t *ent); + +//==================== +// ROGUE PROTOTYPES +// +// g_newweap.c +// +void fire_flechette(edict_t *self, const vec3_t &start, const vec3_t &dir, int damage, int speed, int kick); +void fire_prox(edict_t *self, const vec3_t &start, const vec3_t &aimdir, int damage, int speed); +void fire_nuke(edict_t *self, const vec3_t &start, const vec3_t &aimdir, int speed); +bool fire_player_melee(edict_t *self, const vec3_t &start, const vec3_t &aim, int reach, int damage, int kick, mod_t mod); +void fire_tesla(edict_t *self, const vec3_t &start, const vec3_t &aimdir, int damage, int speed); +void fire_blaster2(edict_t *self, const vec3_t &start, const vec3_t &aimdir, int damage, int speed, effects_t effect, + bool hyper); +void fire_heatbeam(edict_t *self, const vec3_t &start, const vec3_t &aimdir, const vec3_t &offset, int damage, int kick, + bool monster); +void fire_tracker(edict_t *self, const vec3_t &start, const vec3_t &dir, int damage, int speed, edict_t *enemy); + +// +// g_newai.c +// +bool blocked_checkplat(edict_t *self, float dist); + +enum class blocked_jump_result_t +{ + NO_JUMP, + JUMP_TURN, + JUMP_JUMP_UP, + JUMP_JUMP_DOWN +}; + +blocked_jump_result_t blocked_checkjump(edict_t *self, float dist); +bool monsterlost_checkhint(edict_t *self); +bool inback(edict_t *self, edict_t *other); +float realrange(edict_t *self, edict_t *other); +edict_t *SpawnBadArea(const vec3_t &mins, const vec3_t &maxs, gtime_t lifespan, edict_t *owner); +edict_t *CheckForBadArea(edict_t *ent); +bool MarkTeslaArea(edict_t *self, edict_t *tesla); +void InitHintPaths(); +void PredictAim(edict_t *self, edict_t *target, const vec3_t &start, float bolt_speed, bool eye_height, float offset, vec3_t *aimdir, + vec3_t *aimpoint); +bool M_CalculatePitchToFire(edict_t *self, const vec3_t &target, const vec3_t &start, vec3_t &aim, float speed, float time_remaining, bool mortar, bool destroy_on_touch = false); +bool below(edict_t *self, edict_t *other); +void drawbbox(edict_t *self); +void M_MonsterDodge(edict_t *self, edict_t *attacker, gtime_t eta, trace_t *tr, bool gravity); +void monster_duck_down(edict_t *self); +void monster_duck_hold(edict_t *self); +void monster_duck_up(edict_t *self); +bool has_valid_enemy(edict_t *self); +void TargetTesla(edict_t *self, edict_t *tesla); +void hintpath_stop(edict_t *self); +edict_t *PickCoopTarget(edict_t *self); +int CountPlayers(); +bool monster_jump_finished(edict_t *self); +void BossExplode(edict_t *self); + +// g_rogue_func +void plat2_spawn_danger_area(edict_t *ent); +void plat2_kill_danger_area(edict_t *ent); + +// g_rogue_spawn +edict_t *CreateMonster(const vec3_t &origin, const vec3_t &angles, const char *classname); +edict_t *CreateFlyMonster(const vec3_t &origin, const vec3_t &angles, const vec3_t &mins, const vec3_t &maxs, + const char *classname); +edict_t *CreateGroundMonster(const vec3_t &origin, const vec3_t &angles, const vec3_t &mins, const vec3_t &maxs, + const char *classname, float height); +bool FindSpawnPoint(const vec3_t &startpoint, const vec3_t &mins, const vec3_t &maxs, vec3_t &spawnpoint, + float maxMoveUp, bool drop = true); +bool CheckSpawnPoint(const vec3_t &origin, const vec3_t &mins, const vec3_t &maxs); +bool CheckGroundSpawnPoint(const vec3_t &origin, const vec3_t &entMins, const vec3_t &entMaxs, float height, + float gravity); +void SpawnGrow_Spawn(const vec3_t &startpos, float start_size, float end_size); +void Widowlegs_Spawn(const vec3_t &startpos, const vec3_t &angles); + +// g_rogue_items +bool Pickup_Nuke(edict_t *ent, edict_t *other); +void Use_IR(edict_t *ent, gitem_t *item); +void Use_Double(edict_t *ent, gitem_t *item); +void Use_Nuke(edict_t *ent, gitem_t *item); +void Use_Doppleganger(edict_t *ent, gitem_t *item); +bool Pickup_Doppleganger(edict_t *ent, edict_t *other); +bool Pickup_Sphere(edict_t *ent, edict_t *other); +void Use_Defender(edict_t *ent, gitem_t *item); +void Use_Hunter(edict_t *ent, gitem_t *item); +void Use_Vengeance(edict_t *ent, gitem_t *item); +void Item_TriggeredSpawn(edict_t *self, edict_t *other, edict_t *activator); +void SetTriggeredSpawn(edict_t *ent); + +// +// g_sphere.c +// +void Defender_Launch(edict_t *self); +void Vengeance_Launch(edict_t *self); +void Hunter_Launch(edict_t *self); + +// +// g_newdm.c +// +void InitGameRules(); +item_id_t DoRandomRespawn(edict_t *ent); +void PrecacheForRandomRespawn(); +bool Tag_PickupToken(edict_t *ent, edict_t *other); +void Tag_DropToken(edict_t *ent, gitem_t *item); +void fire_doppleganger(edict_t *ent, const vec3_t &start, const vec3_t &aimdir); + +// +// p_client.c +// +void RemoveAttackingPainDaemons(edict_t *self); +bool G_ShouldPlayersCollide(bool weaponry); +bool P_UseCoopInstancedItems(); + +constexpr spawnflags_t SPAWNFLAG_LANDMARK_KEEP_Z = 1_spawnflag; + +// [Paril-KEX] convenience functions that returns true +// if the powerup should be 'active' (false to disable, +// will flash at 500ms intervals after 3 sec) +[[nodiscard]] constexpr bool G_PowerUpExpiringRelative(gtime_t left) +{ + return left.milliseconds() > 3000 || (left.milliseconds() % 1000) < 500; +} + +[[nodiscard]] constexpr bool G_PowerUpExpiring(gtime_t time) +{ + return G_PowerUpExpiringRelative(time - level.time); +} + +// ZOID +#include "ctf/g_ctf.h" +#include "ctf/p_ctf_menu.h" +// ZOID +//============================================================================ + +// client_t->anim_priority +enum anim_priority_t +{ + ANIM_BASIC, // stand / run + ANIM_WAVE, + ANIM_JUMP, + ANIM_PAIN, + ANIM_ATTACK, + ANIM_DEATH, + + // flags + ANIM_REVERSED = bit_v<8> +}; + +MAKE_ENUM_BITFLAGS(anim_priority_t); + +// height fog data values +struct height_fog_t +{ + // r g b dist + std::array start; + std::array end; + float falloff; + float density; + + inline bool operator==(const height_fog_t &o) const + { + return start == o.start && end == o.end && falloff == o.falloff && density == o.density; + } +}; + +constexpr gtime_t SELECTED_ITEM_TIME = 3_sec; + +enum bmodel_animstyle_t : int32_t +{ + BMODEL_ANIM_FORWARDS, + BMODEL_ANIM_BACKWARDS, + BMODEL_ANIM_RANDOM +}; + +struct bmodel_anim_t +{ + // range, inclusive + int32_t start, end; + bmodel_animstyle_t style; + int32_t speed; // in milliseconds + bool nowrap; + + int32_t alt_start, alt_end; + bmodel_animstyle_t alt_style; + int32_t alt_speed; // in milliseconds + bool alt_nowrap; + + // game-only + bool enabled; + bool alternate, currently_alternate; + gtime_t next_tick; +}; + +// never turn back shield on automatically; this is +// the legacy behavior. +constexpr int32_t AUTO_SHIELD_MANUAL = -1; +// when it is >= 0, the shield will turn back on +// when we have that many cells in our inventory +// if possible. +constexpr int32_t AUTO_SHIELD_AUTO = 0; + +// client data that stays across multiple level loads +struct client_persistant_t +{ + char userinfo[MAX_INFO_STRING]; + char social_id[MAX_INFO_VALUE]; + char netname[MAX_NETNAME]; + handedness_t hand; + auto_switch_t autoswitch; + int32_t autoshield; // see AUTO_SHIELD_* + + bool connected, spawned; // a loadgame will leave valid entities that + // just don't have a connection yet + + // values saved and restored from edicts when changing levels + int32_t health; + int32_t max_health; + ent_flags_t savedFlags; + + item_id_t selected_item; + gtime_t selected_item_time; + std::array inventory; + + // ammo capacities + std::array max_ammo; + + gitem_t *weapon; + gitem_t *lastweapon; + + int32_t power_cubes; // used for tracking the cubes in coop games + int32_t score; // for calculating total unit score in coop games + + int32_t game_help1changed, game_help2changed; + int32_t helpchanged; // flash F1 icon if non 0, play sound + // and increment only if 1, 2, or 3 + gtime_t help_time; + + bool spectator; // client wants to be a spectator + bool bob_skip; // [Paril-KEX] client wants no movement bob + + // [Paril-KEX] fog that we want to achieve; density rgb skyfogfactor + std::array wanted_fog; + height_fog_t wanted_heightfog; + // relative time value, copied from last touched trigger + gtime_t fog_transition_time; + gtime_t megahealth_time; // relative megahealth time value + int32_t lives; // player lives left (1 = no respawns remaining) + uint8_t n64_crouch_warn_times; + gtime_t n64_crouch_warning; +}; + +// client data that stays across deathmatch respawns +struct client_respawn_t +{ + client_persistant_t coop_respawn; // what to set client->pers to on a respawn + gtime_t entertime; // level.time the client entered the game + int32_t score; // frags, etc + vec3_t cmd_angles; // angles sent over in the last command + + bool spectator; // client is a spectator + + // ZOID + ctfteam_t ctf_team; // CTF team + int32_t ctf_state; + gtime_t ctf_lasthurtcarrier; + gtime_t ctf_lastreturnedflag; + gtime_t ctf_flagsince; + gtime_t ctf_lastfraggedcarrier; + bool id_state; + gtime_t lastidtime; + bool voted; // for elections + bool ready; + bool admin; + ghost_t *ghost; // for ghost codes + // ZOID +}; + +// [Paril-KEX] seconds until we are fully invisible after +// making a racket +constexpr gtime_t INVISIBILITY_TIME = 2_sec; + +// max number of individual damage indicators we'll track +constexpr size_t MAX_DAMAGE_INDICATORS = 4; + +struct damage_indicator_t +{ + vec3_t from; + int32_t health, armor, power; +}; + +// time between ladder sounds +constexpr gtime_t LADDER_SOUND_TIME = 300_ms; + +// time after damage that we can't respawn on a player for +constexpr gtime_t COOP_DAMAGE_RESPAWN_TIME = 2000_ms; + +// this structure is cleared on each PutClientInServer(), +// except for 'client->pers' +struct gclient_t +{ + // shared with server; do not touch members until the "private" section + player_state_t ps; // communicated by server to clients + int32_t ping; + + // private to game + client_persistant_t pers; + client_respawn_t resp; + pmove_state_t old_pmove; // for detecting out-of-pmove changes + + bool showscores; // set layout stat + bool showeou; // end of unit screen + bool showinventory; // set layout stat + bool showhelp; + + button_t buttons; + button_t oldbuttons; + button_t latched_buttons; + usercmd_t cmd; // last CMD send + + // weapon cannot fire until this time is up + gtime_t weapon_fire_finished; + // time between processing individual animation frames + gtime_t weapon_think_time; + // if we latched fire between server frames but before + // the weapon fire finish has elapsed, we'll "press" it + // automatically when we have a chance + bool weapon_fire_buffered; + bool weapon_thunk; + + gitem_t *newweapon; + + // sum up damage over an entire frame, so + // shotgun blasts give a single big kick + int32_t damage_armor; // damage absorbed by armor + int32_t damage_parmor; // damage absorbed by power armor + int32_t damage_blood; // damage taken out of health + int32_t damage_knockback; // impact damage + vec3_t damage_from; // origin for vector calculation + + damage_indicator_t damage_indicators[MAX_DAMAGE_INDICATORS]; + uint8_t num_damage_indicators; + + float killer_yaw; // when dead, look at killer + + weaponstate_t weaponstate; + struct { + vec3_t angles, origin; + gtime_t time, total; + } kick; + gtime_t quake_time; + vec3_t kick_origin; + float v_dmg_roll, v_dmg_pitch; + gtime_t v_dmg_time; // damage kicks + gtime_t fall_time; + float fall_value; // for view drop on fall + float damage_alpha; + float bonus_alpha; + vec3_t damage_blend; + vec3_t v_angle, v_forward; // aiming direction + float bobtime; // so off-ground doesn't change it + vec3_t oldviewangles; + vec3_t oldvelocity; + edict_t *oldgroundentity; // [Paril-KEX] + gtime_t flash_time; // [Paril-KEX] for high tickrate + + gtime_t next_drown_time; + water_level_t old_waterlevel; + int32_t breather_sound; + + int32_t machinegun_shots; // for weapon raising + + // animation vars + int32_t anim_end; + anim_priority_t anim_priority; + bool anim_duck; + bool anim_run; + gtime_t anim_time; + + // powerup timers + gtime_t quad_time; + gtime_t invincible_time; + gtime_t breather_time; + gtime_t enviro_time; + gtime_t invisible_time; + + bool grenade_blew_up; + gtime_t grenade_time, grenade_finished_time; + // RAFAEL + gtime_t quadfire_time; + // RAFAEL + int32_t silencer_shots; + int32_t weapon_sound; + + gtime_t pickup_msg_time; + + gtime_t flood_locktill; // locked from talking + gtime_t flood_when[10]; // when messages were said + int32_t flood_whenhead; // head pointer for when said + + gtime_t respawn_time; // can respawn when time > this + + edict_t *chase_target; // player we are chasing + bool update_chase; // need to update chase info? + + //======= + // ROGUE + gtime_t double_time; + gtime_t ir_time; + gtime_t nuke_time; + gtime_t tracker_pain_time; + + edict_t *owned_sphere; // this points to the player's sphere + // ROGUE + //======= + + gtime_t empty_click_sound; + + // ZOID + bool inmenu; // in menu + pmenuhnd_t *menu; // current menu + gtime_t menutime; // time to update menu + bool menudirty; + edict_t *ctf_grapple; // entity of grapple + int32_t ctf_grapplestate; // true if pulling + gtime_t ctf_grapplereleasetime; // time of grapple release + gtime_t ctf_regentime; // regen tech + gtime_t ctf_techsndtime; + gtime_t ctf_lasttechmsg; + // ZOID + + // used for player trails. + edict_t *trail_head, *trail_tail; + // whether to use weapon chains + bool no_weapon_chains; + + // seamless level transitions + bool landmark_free_fall; + const char* landmark_name; + vec3_t landmark_rel_pos; // position relative to landmark, un-rotated from landmark angle + gtime_t landmark_noise_time; + + gtime_t invisibility_fade_time; // [Paril-KEX] at this time, the player will be mostly fully cloaked + gtime_t chase_msg_time; // to prevent CTF message spamming + int32_t menu_sign; // menu sign + vec3_t last_ladder_pos; // for ladder step sounds + gtime_t last_ladder_sound; + coop_respawn_t coop_respawn_state; + gtime_t last_damage_time; + + // [Paril-KEX] these are now per-player, to work better in coop + edict_t *sight_entity; + gtime_t sight_entity_time; + edict_t *sound_entity; + gtime_t sound_entity_time; + edict_t *sound2_entity; + gtime_t sound2_entity_time; + // saved positions for lag compensation + uint8_t num_lag_origins; // 0 to MAX_LAG_ORIGINS, how many we can go back + uint8_t next_lag_origin; // the next one to write to + bool is_lag_compensated; + vec3_t lag_restore_origin; + // for high tickrate weapon angles + vec3_t slow_view_angles; + gtime_t slow_view_angle_time; + + // not saved + bool help_draw_points; + size_t help_draw_index, help_draw_count; + gtime_t help_draw_time; + uint32_t step_frame; + int32_t help_poi_image; + vec3_t help_poi_location; + + // only set temporarily + bool awaiting_respawn; + gtime_t respawn_timeout; // after this time, force a respawn + + // [Paril-KEX] current active fog values; density rgb skyfogfactor + std::array fog; + height_fog_t heightfog; + + gtime_t last_attacker_time; +}; + +// ========================================== +// PLAT 2 +// ========================================== +enum plat2flags_t +{ + PLAT2_NONE = 0, + PLAT2_CALLED = 1, + PLAT2_MOVING = 2, + PLAT2_WAITING = 4 +}; + +MAKE_ENUM_BITFLAGS(plat2flags_t); + +#include + +struct edict_t +{ + edict_t() = delete; + edict_t(const edict_t &) = delete; + edict_t(edict_t &&) = delete; + + // shared with server; do not touch members until the "private" section + entity_state_t s; + gclient_t *client; // nullptr if not a player + // the server expects the first part + // of gclient_t to be a player_state_t + // but the rest of it is opaque + + sv_entity_t sv; // read only info about this entity for the server + + bool inuse; + + // world linkage data + bool linked; + int32_t linkcount; + int32_t areanum, areanum2; + + svflags_t svflags; + vec3_t mins, maxs; + vec3_t absmin, absmax, size; + solid_t solid; + contents_t clipmask; + edict_t *owner; + + //================================ + + // private to game + int32_t spawn_count; // [Paril-KEX] used to differentiate different entities that may be in the same slot + movetype_t movetype; + ent_flags_t flags; + + const char *model; + gtime_t freetime; // sv.time when the object was freed + + // + // only used locally in game, not by server + // + const char *message; + const char *classname; + spawnflags_t spawnflags; + + gtime_t timestamp; + + float angle; // set in qe3, -1 = up, -2 = down + const char *target; + const char *targetname; + const char *killtarget; + const char *team; + const char *pathtarget; + const char *deathtarget; + const char *healthtarget; + const char *itemtarget; // [Paril-KEX] + const char *combattarget; + edict_t *target_ent; + + float speed, accel, decel; + vec3_t movedir; + vec3_t pos1, pos2, pos3; + + vec3_t velocity; + vec3_t avelocity; + int32_t mass; + gtime_t air_finished; + float gravity; // per entity gravity multiplier (1.0 is normal) + // use for lowgrav artifact, flares + + edict_t *goalentity; + edict_t *movetarget; + float yaw_speed; + float ideal_yaw; + + gtime_t nextthink; + save_prethink_t prethink; + save_prethink_t postthink; + save_think_t think; + save_touch_t touch; + save_use_t use; + save_pain_t pain; + save_die_t die; + + gtime_t touch_debounce_time; // are all these legit? do we need more/less of them? + gtime_t pain_debounce_time; + gtime_t damage_debounce_time; + gtime_t fly_sound_debounce_time; // move to clientinfo + gtime_t last_move_time; + + int32_t health; + int32_t max_health; + int32_t gib_health; + gtime_t show_hostile; + + gtime_t powerarmor_time; + + const char *map; // target_changelevel + + int32_t viewheight; // height above origin where eyesight is determined + bool deadflag; + bool takedamage; + int32_t dmg; + int32_t radius_dmg; + float dmg_radius; + int32_t sounds; // make this a spawntemp var? + int32_t count; + + edict_t *chain; + edict_t *enemy; + edict_t *oldenemy; + edict_t *activator; + edict_t *groundentity; + int32_t groundentity_linkcount; + edict_t *teamchain; + edict_t *teammaster; + + edict_t *mynoise; // can go in client only + edict_t *mynoise2; + + int32_t noise_index; + int32_t noise_index2; + float volume; + float attenuation; + + // timing variables + float wait; + float delay; // before firing targets + float random; + + gtime_t teleport_time; + + contents_t watertype; + water_level_t waterlevel; + + vec3_t move_origin; + vec3_t move_angles; + + int32_t style; // also used as areaportal number + + gitem_t *item; // for bonus items + + // common data blocks + moveinfo_t moveinfo; + monsterinfo_t monsterinfo; + + //========= + // ROGUE + plat2flags_t plat2flags; + vec3_t offset; + vec3_t gravityVector; + edict_t *bad_area; + edict_t *hint_chain; + edict_t *monster_hint_chain; + edict_t *target_hint_chain; + int32_t hint_chain_id; + // ROGUE + //========= + + char clock_message[CLOCK_MESSAGE_SIZE]; + + // Paril: we died on this frame, apply knockback even if we're dead + gtime_t dead_time; + // used for dabeam monsters + edict_t *beam, *beam2; + // proboscus for Parasite + edict_t *proboscus; + // for vooping things + edict_t *disintegrator; + gtime_t disintegrator_time; + int32_t hackflags; // n64 + + // fog stuff + struct { + vec3_t color; + float density; + float sky_factor; + + vec3_t color_off; + float density_off; + float sky_factor_off; + } fog; + + struct { + float falloff; + float density; + vec3_t start_color; + float start_dist; + vec3_t end_color; + float end_dist; + + float falloff_off; + float density_off; + vec3_t start_color_off; + float start_dist_off; + vec3_t end_color_off; + float end_dist_off; + } heightfog; + + // instanced coop items + std::bitset item_picked_up_by; + gtime_t slime_debounce_time; + + // [Paril-KEX] + bmodel_anim_t bmodel_anim; + + mod_t lastMOD; + const char *style_on, *style_off; + uint32_t crosslevel_flags; + // NOTE: if adding new elements, make sure to add them + // in g_save.cpp too! +}; + +//============= +// ROGUE +constexpr spawnflags_t SPHERE_DEFENDER = 0x0001_spawnflag; +constexpr spawnflags_t SPHERE_HUNTER = 0x0002_spawnflag; +constexpr spawnflags_t SPHERE_VENGEANCE = 0x0004_spawnflag; +constexpr spawnflags_t SPHERE_DOPPLEGANGER = 0x10000_spawnflag; + +constexpr spawnflags_t SPHERE_TYPE = SPHERE_DEFENDER | SPHERE_HUNTER | SPHERE_VENGEANCE; +constexpr spawnflags_t SPHERE_FLAGS = SPHERE_DOPPLEGANGER; + +// +// deathmatch games +// +enum +{ + RDM_TAG = 2, + RDM_DEATHBALL = 3 +}; + +struct dm_game_rt +{ + void (*GameInit)(); + void (*PostInitSetup)(); + void (*ClientBegin)(edict_t *ent); + bool (*SelectSpawnPoint)(edict_t *ent, vec3_t &origin, vec3_t &angles, bool force_spawn); + void (*PlayerDeath)(edict_t *targ, edict_t *inflictor, edict_t *attacker); + void (*Score)(edict_t *attacker, edict_t *victim, int scoreChange, const mod_t &mod); + void (*PlayerEffects)(edict_t *ent); + void (*DogTag)(edict_t *ent, edict_t *killer, const char **pic); + void (*PlayerDisconnect)(edict_t *ent); + int (*ChangeDamage)(edict_t *targ, edict_t *attacker, int damage, mod_t mod); + int (*ChangeKnockback)(edict_t *targ, edict_t *attacker, int knockback, mod_t mod); + int (*CheckDMRules)(); +}; + +extern dm_game_rt DMGame; + +// ROGUE +//============ + + +// [Paril-KEX] +inline void monster_footstep(edict_t *self) +{ + if (self->groundentity) + self->s.event = EV_OTHER_FOOTSTEP; +} + +// [Kex] helpers +// TFilter must be a type that is invokable with the +// signature bool(edict_t *); it must return true if +// the entity given is valid for the given filter +template +struct entity_iterator_t +{ + using iterator_category = std::random_access_iterator_tag; + using value_type = edict_t *; + using reference = edict_t *; + using pointer = edict_t *; + using difference_type = ptrdiff_t; + +private: + uint32_t index; + uint32_t end_index; // where the end index is located for this iterator + // index < globals.num_edicts are valid + TFilter filter; + + // this doubles as the "end" iterator + inline bool is_out_of_range(uint32_t i) const + { + return i >= end_index; + } + + inline bool is_out_of_range() const + { + return is_out_of_range(index); + } + + inline void throw_if_out_of_range() const + { + if (is_out_of_range()) + throw std::out_of_range("index"); + } + + inline difference_type clamped_index() const + { + if (is_out_of_range()) + return end_index; + + return index; + } + +public: + // note: index is not affected by filter. it is up to + // the caller to ensure this index is filtered. + constexpr entity_iterator_t(uint32_t i, uint32_t end_index = -1) : index(i), end_index((end_index >= globals.num_edicts) ? globals.num_edicts : end_index) { } + + inline reference operator*() { throw_if_out_of_range(); return &g_edicts[index]; } + inline pointer operator->() { throw_if_out_of_range(); return &g_edicts[index]; } + + inline entity_iterator_t &operator++() + { + throw_if_out_of_range(); + return *this = *this + 1; + } + + inline entity_iterator_t &operator--() + { + throw_if_out_of_range(); + return *this = *this - 1; + } + + inline difference_type operator-(const entity_iterator_t &it) const + { + return clamped_index() - it.clamped_index(); + } + + inline entity_iterator_t operator+(const difference_type &offset) const + { + entity_iterator_t it(index + offset, end_index); + + // move in the specified direction, only stopping if we + // run out of range or find a filtered entity + while (!is_out_of_range(it.index) && !filter(*it)) + it.index += offset > 0 ? 1 : -1; + + return it; + } + + // + -1 and - 1 are the same (and - -1 & + 1) + inline entity_iterator_t operator-(const difference_type &offset) const { return *this + (-offset); } + + // comparison. hopefully this won't break anything, but == and != use the + // clamped index (so -1 and num_edicts will be equal technically since they + // are the same "invalid" entity) but <= and >= will affect them properly. + inline bool operator==(const entity_iterator_t &it) const { return clamped_index() == it.clamped_index(); } + inline bool operator!=(const entity_iterator_t &it) const { return clamped_index() != it.clamped_index(); } + inline bool operator<(const entity_iterator_t &it) const { return index < it.index; } + inline bool operator>(const entity_iterator_t &it) const { return index > it.index; } + inline bool operator<=(const entity_iterator_t &it) const { return index <= it.index; } + inline bool operator>=(const entity_iterator_t &it) const { return index >= it.index; } + + inline edict_t *operator[](const difference_type &offset) const { return *(*this + offset); } +}; + +// iterate over range of entities, with the specified filter. +// can be "open-ended" (automatically expand with num_edicts) +// by leaving the max unset. +template +struct entity_iterable_t +{ +private: + uint32_t begin_index, end_index; + TFilter filter; + + // find the first entity that matches the filter, from the specified index, + // in the specified direction + inline uint32_t find_matched_index(uint32_t index, int32_t direction) + { + while (index < globals.num_edicts && !filter(&g_edicts[index])) + index += direction; + + return index; + } + +public: + // iterate all allocated entities that match the filter, + // including ones allocated after this iterator is constructed + inline entity_iterable_t() : begin_index(find_matched_index(0, 1)), end_index(game.maxentities) { } + // iterate all allocated entities that match the filter from the specified begin offset + // including ones allocated after this iterator is constructed + inline entity_iterable_t(uint32_t start) : begin_index(find_matched_index(start, 1)), end_index(game.maxentities) { } + // iterate all allocated entities that match the filter from the specified begin offset + // to the specified INCLUSIVE end offset (or the first entity that matches before it), + // including end itself but not ones that may appear after this iterator is done + inline entity_iterable_t(uint32_t start, uint32_t end) : + begin_index(find_matched_index(start, 1)), + end_index(find_matched_index(end, -1) + 1) + { + } + + inline entity_iterator_t begin() const { return entity_iterator_t(begin_index, end_index); } + inline entity_iterator_t end() const { return end_index; } +}; + +// inuse players that are connected; may not be spawned yet, however +struct active_players_filter_t +{ + inline bool operator()(edict_t *ent) const + { + return (ent->inuse && ent->client && ent->client->pers.connected); + } +}; + +inline entity_iterable_t active_players() +{ + return entity_iterable_t { 1u, game.maxclients }; +} + +struct gib_def_t +{ + size_t count; + const char *gibname; + float scale; + gib_type_t type; + + constexpr gib_def_t(size_t count, const char *gibname) : + count(count), + gibname(gibname), + scale(1.0f), + type(GIB_NONE) + { + } + + constexpr gib_def_t(size_t count, const char *gibname, gib_type_t type) : + count(count), + gibname(gibname), + scale(1.0f), + type(type) + { + } + + constexpr gib_def_t(size_t count, const char *gibname, float scale) : + count(count), + gibname(gibname), + scale(scale), + type(GIB_NONE) + { + } + + constexpr gib_def_t(size_t count, const char *gibname, float scale, gib_type_t type) : + count(count), + gibname(gibname), + scale(scale), + type(type) + { + } + + constexpr gib_def_t(const char *gibname, float scale, gib_type_t type) : + count(1), + gibname(gibname), + scale(scale), + type(type) + { + } + + constexpr gib_def_t(const char *gibname, float scale) : + count(1), + gibname(gibname), + scale(scale), + type(GIB_NONE) + { + } + + constexpr gib_def_t(const char *gibname, gib_type_t type) : + count(1), + gibname(gibname), + scale(1.0f), + type(type) + { + } + + constexpr gib_def_t(const char *gibname) : + count(1), + gibname(gibname), + scale(1.0f), + type(GIB_NONE) + { + } +}; + +// convenience function to throw different gib types +// NOTE: always throw the head gib *last* since self's size is used +// to position the gibs! +inline void ThrowGibs(edict_t *self, int32_t damage, std::initializer_list gibs) +{ + for (auto &gib : gibs) + for (size_t i = 0; i < gib.count; i++) + ThrowGib(self, gib.gibname, damage, gib.type, gib.scale * (self->s.scale ? self->s.scale : 1)); +} + +inline bool M_CheckGib(edict_t *self, const mod_t &mod) +{ + if (self->deadflag) + { + if (mod.id == MOD_CRUSH) + return true; + } + + return self->health <= self->gib_health; +} + +// Fmt support for entities +template<> +struct fmt::formatter +{ + template + constexpr auto parse(ParseContext& ctx) + { + return ctx.begin(); + } + + template + auto format(const edict_t &p, FormatContext &ctx) -> decltype(ctx.out()) + { + if (p.linked) + return fmt::format_to(ctx.out(), FMT_STRING("{} @ {}"), p.classname, (p.absmax + p.absmin) * 0.5f); + return fmt::format_to(ctx.out(), FMT_STRING("{} @ {}"), p.classname, p.s.origin); + } +}; + +// POI tags used by this mod +enum pois_t : uint16_t +{ + POI_OBJECTIVE = MAX_EDICTS, // current objective + POI_RED_FLAG, // red flag/carrier + POI_BLUE_FLAG, // blue flag/carrier + POI_PING, + POI_PING_END = POI_PING + MAX_CLIENTS - 1, +}; + +// implementation of pierce stuff +inline bool pierce_args_t::mark(edict_t *ent) +{ + // ran out of pierces + if (num_pierced == MAX_PIERCE) + return false; + + pierced[num_pierced] = ent; + pierce_solidities[num_pierced] = ent->solid; + num_pierced++; + + ent->solid = SOLID_NOT; + gi.linkentity(ent); + + return true; +} + +// implementation of pierce stuff +inline void pierce_args_t::restore() +{ + for (size_t i = 0; i < num_pierced; i++) + { + auto &ent = pierced[i]; + ent->solid = pierce_solidities[i]; + gi.linkentity(ent); + } + + num_pierced = 0; +} \ No newline at end of file diff --git a/rerelease/g_main.cpp b/rerelease/g_main.cpp new file mode 100644 index 0000000..f794435 --- /dev/null +++ b/rerelease/g_main.cpp @@ -0,0 +1,1051 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. + +#include "g_local.h" +#include "bots/bot_includes.h" + +CHECK_GCLIENT_INTEGRITY; +CHECK_EDICT_INTEGRITY; + +std::mt19937 mt_rand; + +game_locals_t game; +level_locals_t level; + +local_game_import_t gi; + +/*static*/ char local_game_import_t::print_buffer[0x10000]; + +/*static*/ std::array local_game_import_t::buffers; +/*static*/ std::array local_game_import_t::buffer_ptrs; + +game_export_t globals; +spawn_temp_t st; + +int sm_meat_index; +int snd_fry; + +edict_t *g_edicts; + +cvar_t *deathmatch; +cvar_t *coop; +cvar_t *skill; +cvar_t *fraglimit; +cvar_t *timelimit; +// ZOID +cvar_t *capturelimit; +cvar_t *g_quick_weapon_switch; +cvar_t *g_instant_weapon_switch; +// ZOID +cvar_t *password; +cvar_t *spectator_password; +cvar_t *needpass; +static cvar_t *maxclients; +cvar_t *maxspectators; +static cvar_t *maxentities; +cvar_t *g_select_empty; +cvar_t *sv_dedicated; + +cvar_t *filterban; + +cvar_t *sv_maxvelocity; +cvar_t *sv_gravity; + +cvar_t *g_skipViewModifiers; + +cvar_t *sv_rollspeed; +cvar_t *sv_rollangle; +cvar_t *gun_x; +cvar_t *gun_y; +cvar_t *gun_z; + +cvar_t *run_pitch; +cvar_t *run_roll; +cvar_t *bob_up; +cvar_t *bob_pitch; +cvar_t *bob_roll; + +cvar_t *sv_cheats; + +cvar_t *g_debug_monster_paths; +cvar_t *g_debug_monster_kills; + +cvar_t *bot_debug_follow_actor; +cvar_t *bot_debug_move_to_point; + +cvar_t *flood_msgs; +cvar_t *flood_persecond; +cvar_t *flood_waitdelay; + +cvar_t *sv_stopspeed; // PGM (this was a define in g_phys.c) + +cvar_t *g_strict_saves; + +// ROGUE cvars +cvar_t *gamerules; +cvar_t *huntercam; +cvar_t *g_dm_strong_mines; +cvar_t *g_dm_random_items; +// ROGUE + +// [Kex] +cvar_t* g_instagib; +cvar_t* g_coop_player_collision; +cvar_t* g_coop_squad_respawn; +cvar_t* g_coop_enable_lives; +cvar_t* g_coop_num_lives; +cvar_t* g_coop_instanced_items; +cvar_t* g_allow_grapple; +cvar_t* g_grapple_fly_speed; +cvar_t* g_grapple_pull_speed; +cvar_t* g_grapple_damage; +cvar_t* g_coop_health_scaling; +cvar_t* g_weapon_respawn_time; + +// dm"flags" +cvar_t* g_no_health; +cvar_t* g_no_items; +cvar_t* g_dm_weapons_stay; +cvar_t* g_dm_no_fall_damage; +cvar_t* g_dm_instant_items; +cvar_t* g_dm_same_level; +cvar_t* g_friendly_fire; +cvar_t* g_dm_force_respawn; +cvar_t* g_dm_force_respawn_time; +cvar_t* g_dm_spawn_farthest; +cvar_t* g_no_armor; +cvar_t* g_dm_allow_exit; +cvar_t* g_infinite_ammo; +cvar_t* g_dm_no_quad_drop; +cvar_t* g_dm_no_quadfire_drop; +cvar_t* g_no_mines; +cvar_t* g_dm_no_stack_double; +cvar_t* g_no_nukes; +cvar_t* g_no_spheres; +cvar_t* g_teamplay_armor_protect; +cvar_t* g_allow_techs; +cvar_t* g_start_items; +cvar_t* g_map_list; +cvar_t* g_map_list_shuffle; +cvar_t *g_lag_compensation; + +cvar_t *sv_airaccelerate; +cvar_t *g_damage_scale; +cvar_t *g_disable_player_collision; +cvar_t *ai_damage_scale; +cvar_t *ai_model_scale; +cvar_t *ai_allow_dm_spawn; +cvar_t *ai_movement_disabled; + +static cvar_t *g_frames_per_frame; + +void SpawnEntities(const char *mapname, const char *entities, const char *spawnpoint); +void ClientThink(edict_t *ent, usercmd_t *cmd); +edict_t *ClientChooseSlot(const char *userinfo, const char *social_id, bool isBot, edict_t **ignore, size_t num_ignore, bool cinematic); +bool ClientConnect(edict_t *ent, char *userinfo, const char *social_id, bool isBot); +char *WriteGameJson(bool autosave, size_t *out_size); +void ReadGameJson(const char *jsonString); +char *WriteLevelJson(bool transition, size_t *out_size); +void ReadLevelJson(const char *jsonString); +bool G_CanSave(); +void ClientDisconnect(edict_t *ent); +void ClientBegin(edict_t *ent); +void ClientCommand(edict_t *ent); +void G_RunFrame(bool main_loop); +void G_PrepFrame(); +void InitSave(); + +#include + +/* +============ +PreInitGame + +This will be called when the dll is first loaded, which +only happens when a new game is started or a save game +is loaded. +============ +*/ +void PreInitGame() +{ + maxclients = gi.cvar("maxclients", G_Fmt("{}", MAX_SPLIT_PLAYERS).data(), CVAR_SERVERINFO | CVAR_LATCH); + deathmatch = gi.cvar("deathmatch", "0", CVAR_LATCH); + coop = gi.cvar("coop", "0", CVAR_LATCH); + teamplay = gi.cvar("teamplay", "0", CVAR_LATCH); + + // ZOID + CTFInit(); + // ZOID + + // ZOID + // This gamemode only supports deathmatch + if (ctf->integer) + { + if (!deathmatch->integer) + { + gi.Com_Print("Forcing deathmatch.\n"); + gi.cvar_set("deathmatch", "1"); + } + // force coop off + if (coop->integer) + gi.cvar_set("coop", "0"); + // force tdm off + if (teamplay->integer) + gi.cvar_set("teamplay", "0"); + } + if (teamplay->integer) + { + if (!deathmatch->integer) + { + gi.Com_Print("Forcing deathmatch.\n"); + gi.cvar_set("deathmatch", "1"); + } + // force coop off + if (coop->integer) + gi.cvar_set("coop", "0"); + } + // ZOID +} + +/* +============ +InitGame + +Called after PreInitGame when the game has set up cvars. +============ +*/ +void InitGame() +{ + gi.Com_Print("==== InitGame ====\n"); + + InitSave(); + + // seed RNG + mt_rand.seed((uint32_t) std::chrono::system_clock::now().time_since_epoch().count()); + + gun_x = gi.cvar("gun_x", "0", CVAR_NOFLAGS); + gun_y = gi.cvar("gun_y", "0", CVAR_NOFLAGS); + gun_z = gi.cvar("gun_z", "0", CVAR_NOFLAGS); + + // FIXME: sv_ prefix is wrong for these + sv_rollspeed = gi.cvar("sv_rollspeed", "200", CVAR_NOFLAGS); + sv_rollangle = gi.cvar("sv_rollangle", "2", CVAR_NOFLAGS); + sv_maxvelocity = gi.cvar("sv_maxvelocity", "2000", CVAR_NOFLAGS); + sv_gravity = gi.cvar("sv_gravity", "800", CVAR_NOFLAGS); + + g_skipViewModifiers = gi.cvar("g_skipViewModifiers", "0", CVAR_NOSET); + + sv_stopspeed = gi.cvar("sv_stopspeed", "100", CVAR_NOFLAGS); // PGM - was #define in g_phys.c + + // ROGUE + huntercam = gi.cvar("huntercam", "1", CVAR_SERVERINFO | CVAR_LATCH); + g_dm_strong_mines = gi.cvar("g_dm_strong_mines", "0", CVAR_NOFLAGS); + g_dm_random_items = gi.cvar("g_dm_random_items", "0", CVAR_NOFLAGS); + // ROGUE + + // [Kex] Instagib + g_instagib = gi.cvar("g_instagib", "0", CVAR_NOFLAGS); + + // [Paril-KEX] + g_coop_player_collision = gi.cvar("g_coop_player_collision", "0", CVAR_LATCH); + g_coop_squad_respawn = gi.cvar("g_coop_squad_respawn", "1", CVAR_LATCH); + g_coop_enable_lives = gi.cvar("g_coop_enable_lives", "0", CVAR_LATCH); + g_coop_num_lives = gi.cvar("g_coop_num_lives", "2", CVAR_LATCH); + g_coop_instanced_items = gi.cvar("g_coop_instanced_items", "1", CVAR_LATCH); + g_allow_grapple = gi.cvar("g_allow_grapple", "auto", CVAR_NOFLAGS); + g_grapple_fly_speed = gi.cvar("g_grapple_fly_speed", G_Fmt("{}", CTF_DEFAULT_GRAPPLE_SPEED).data(), CVAR_NOFLAGS); + g_grapple_pull_speed = gi.cvar("g_grapple_pull_speed", G_Fmt("{}", CTF_DEFAULT_GRAPPLE_PULL_SPEED).data(), CVAR_NOFLAGS); + g_grapple_damage = gi.cvar("g_grapple_damage", "10", CVAR_NOFLAGS); + + g_debug_monster_paths = gi.cvar("g_debug_monster_paths", "0", CVAR_NOFLAGS); + g_debug_monster_kills = gi.cvar("g_debug_monster_kills", "0", CVAR_LATCH); + + bot_debug_follow_actor = gi.cvar("bot_debug_follow_actor", "0", CVAR_NOFLAGS); + bot_debug_move_to_point = gi.cvar("bot_debug_move_to_point", "0", CVAR_NOFLAGS); + + // noset vars + sv_dedicated = gi.cvar("dedicated", "0", CVAR_NOSET); + + // latched vars + sv_cheats = gi.cvar("cheats", +#if defined(_DEBUG) + "1" +#else + "0" +#endif + , CVAR_SERVERINFO | CVAR_LATCH); + gi.cvar("gamename", GAMEVERSION, CVAR_SERVERINFO | CVAR_LATCH); + + maxspectators = gi.cvar("maxspectators", "4", CVAR_SERVERINFO); + skill = gi.cvar("skill", "1", CVAR_LATCH); + maxentities = gi.cvar("maxentities", G_Fmt("{}", MAX_EDICTS).data(), CVAR_LATCH); + gamerules = gi.cvar("gamerules", "0", CVAR_LATCH); // PGM + + // change anytime vars + fraglimit = gi.cvar("fraglimit", "0", CVAR_SERVERINFO); + timelimit = gi.cvar("timelimit", "0", CVAR_SERVERINFO); + // ZOID + capturelimit = gi.cvar("capturelimit", "0", CVAR_SERVERINFO); + g_quick_weapon_switch = gi.cvar("g_quick_weapon_switch", "1", CVAR_LATCH); + g_instant_weapon_switch = gi.cvar("g_instant_weapon_switch", "0", CVAR_LATCH); + // ZOID + password = gi.cvar("password", "", CVAR_USERINFO); + spectator_password = gi.cvar("spectator_password", "", CVAR_USERINFO); + needpass = gi.cvar("needpass", "0", CVAR_SERVERINFO); + filterban = gi.cvar("filterban", "1", CVAR_NOFLAGS); + + g_select_empty = gi.cvar("g_select_empty", "0", CVAR_ARCHIVE); + + run_pitch = gi.cvar("run_pitch", "0.002", CVAR_NOFLAGS); + run_roll = gi.cvar("run_roll", "0.005", CVAR_NOFLAGS); + bob_up = gi.cvar("bob_up", "0.005", CVAR_NOFLAGS); + bob_pitch = gi.cvar("bob_pitch", "0.002", CVAR_NOFLAGS); + bob_roll = gi.cvar("bob_roll", "0.002", CVAR_NOFLAGS); + + // flood control + flood_msgs = gi.cvar("flood_msgs", "4", CVAR_NOFLAGS); + flood_persecond = gi.cvar("flood_persecond", "4", CVAR_NOFLAGS); + flood_waitdelay = gi.cvar("flood_waitdelay", "10", CVAR_NOFLAGS); + + g_strict_saves = gi.cvar("g_strict_saves", "1", CVAR_NOFLAGS); + + sv_airaccelerate = gi.cvar("sv_airaccelerate", "0", CVAR_NOFLAGS); + + g_damage_scale = gi.cvar("g_damage_scale", "1", CVAR_NOFLAGS); + g_disable_player_collision = gi.cvar("g_disable_player_collision", "0", CVAR_NOFLAGS); + ai_damage_scale = gi.cvar("ai_damage_scale", "1", CVAR_NOFLAGS); + ai_model_scale = gi.cvar("ai_model_scale", "0", CVAR_NOFLAGS); + ai_allow_dm_spawn = gi.cvar("ai_allow_dm_spawn", "0", CVAR_NOFLAGS); + ai_movement_disabled = gi.cvar("ai_movement_disabled", "0", CVAR_NOFLAGS); + + g_frames_per_frame = gi.cvar("g_frames_per_frame", "1", CVAR_NOFLAGS); + + g_coop_health_scaling = gi.cvar("g_coop_health_scaling", "0", CVAR_LATCH); + g_weapon_respawn_time = gi.cvar("g_weapon_respawn_time", "30", CVAR_NOFLAGS); + + // dm "flags" + g_no_health = gi.cvar("g_no_health", "0", CVAR_NOFLAGS); + g_no_items = gi.cvar("g_no_items", "0", CVAR_NOFLAGS); + g_dm_weapons_stay = gi.cvar("g_dm_weapons_stay", "0", CVAR_NOFLAGS); + g_dm_no_fall_damage = gi.cvar("g_dm_no_fall_damage", "0", CVAR_NOFLAGS); + g_dm_instant_items = gi.cvar("g_dm_instant_items", "1", CVAR_NOFLAGS); + g_dm_same_level = gi.cvar("g_dm_same_level", "0", CVAR_NOFLAGS); + g_friendly_fire = gi.cvar("g_friendly_fire", "0", CVAR_NOFLAGS); + g_dm_force_respawn = gi.cvar("g_dm_force_respawn", "0", CVAR_NOFLAGS); + g_dm_force_respawn_time = gi.cvar("g_dm_force_respawn_time", "0", CVAR_NOFLAGS); + g_dm_spawn_farthest = gi.cvar("g_dm_spawn_farthest", "0", CVAR_NOFLAGS); + g_no_armor = gi.cvar("g_no_armor", "0", CVAR_NOFLAGS); + g_dm_allow_exit = gi.cvar("g_dm_allow_exit", "0", CVAR_NOFLAGS); + g_infinite_ammo = gi.cvar("g_infinite_ammo", "0", CVAR_LATCH); + g_dm_no_quad_drop = gi.cvar("g_dm_no_quad_drop", "0", CVAR_NOFLAGS); + g_dm_no_quadfire_drop = gi.cvar("g_dm_no_quadfire_drop", "0", CVAR_NOFLAGS); + g_no_mines = gi.cvar("g_no_mines", "0", CVAR_NOFLAGS); + g_dm_no_stack_double = gi.cvar("g_dm_no_stack_double", "0", CVAR_NOFLAGS); + g_no_nukes = gi.cvar("g_no_nukes", "0", CVAR_NOFLAGS); + g_no_spheres = gi.cvar("g_no_spheres", "0", CVAR_NOFLAGS); + g_teamplay_force_join = gi.cvar("g_teamplay_force_join", "0", CVAR_NOFLAGS); + g_teamplay_armor_protect = gi.cvar("g_teamplay_armor_protect", "0", CVAR_NOFLAGS); + g_allow_techs = gi.cvar("g_allow_techs", "auto", CVAR_NOFLAGS); + + g_start_items = gi.cvar("g_start_items", "", CVAR_LATCH); + g_map_list = gi.cvar("g_map_list", "", CVAR_NOFLAGS); + g_map_list_shuffle = gi.cvar("g_map_list_shuffle", "0", CVAR_NOFLAGS); + g_lag_compensation = gi.cvar("g_lag_compensation", "1", CVAR_NOFLAGS); + + // items + InitItems(); + + game = {}; + + // initialize all entities for this game + game.maxentities = maxentities->integer; + g_edicts = (edict_t *) gi.TagMalloc(game.maxentities * sizeof(g_edicts[0]), TAG_GAME); + globals.edicts = g_edicts; + globals.max_edicts = game.maxentities; + + // initialize all clients for this game + game.maxclients = maxclients->integer; + game.clients = (gclient_t *) gi.TagMalloc(game.maxclients * sizeof(game.clients[0]), TAG_GAME); + globals.num_edicts = game.maxclients + 1; + + //====== + // ROGUE + if (gamerules->integer) + InitGameRules(); // if there are game rules to set up, do so now. + // ROGUE + //====== + + // how far back we should support lag origins for + game.max_lag_origins = 20 * (0.1f / gi.frame_time_s); + game.lag_origins = (vec3_t *) gi.TagMalloc(game.maxclients * sizeof(vec3_t) * game.max_lag_origins, TAG_GAME); +} + +//=================================================================== + +void ShutdownGame() +{ + gi.Com_Print("==== ShutdownGame ====\n"); + + gi.FreeTags(TAG_LEVEL); + gi.FreeTags(TAG_GAME); +} + +static void *G_GetExtension(const char *name) +{ + return nullptr; +} + +const shadow_light_data_t *GetShadowLightData(int32_t entity_number); + +gtime_t FRAME_TIME_S; +gtime_t FRAME_TIME_MS; + +/* +================= +GetGameAPI + +Returns a pointer to the structure with all entry points +and global variables +================= +*/ +Q2GAME_API game_export_t *GetGameAPI(game_import_t *import) +{ + gi = *import; + + FRAME_TIME_S = FRAME_TIME_MS = gtime_t::from_ms(gi.frame_time_ms); + + globals.apiversion = GAME_API_VERSION; + globals.PreInit = PreInitGame; + globals.Init = InitGame; + globals.Shutdown = ShutdownGame; + globals.SpawnEntities = SpawnEntities; + + globals.WriteGameJson = WriteGameJson; + globals.ReadGameJson = ReadGameJson; + globals.WriteLevelJson = WriteLevelJson; + globals.ReadLevelJson = ReadLevelJson; + globals.CanSave = G_CanSave; + + globals.Pmove = Pmove; + + globals.GetExtension = G_GetExtension; + + globals.ClientChooseSlot = ClientChooseSlot; + globals.ClientThink = ClientThink; + globals.ClientConnect = ClientConnect; + globals.ClientUserinfoChanged = ClientUserinfoChanged; + globals.ClientDisconnect = ClientDisconnect; + globals.ClientBegin = ClientBegin; + globals.ClientCommand = ClientCommand; + + globals.RunFrame = G_RunFrame; + globals.PrepFrame = G_PrepFrame; + + globals.ServerCommand = ServerCommand; + globals.Bot_SetWeapon = Bot_SetWeapon; + globals.Bot_TriggerEdict = Bot_TriggerEdict; + globals.Bot_GetItemID = Bot_GetItemID; + globals.Bot_UseItem = Bot_UseItem; + globals.Entity_IsVisibleToPlayer = Entity_IsVisibleToPlayer; + globals.GetShadowLightData = GetShadowLightData; + + globals.edict_size = sizeof(edict_t); + + return &globals; +} + +//====================================================================== + +/* +================= +ClientEndServerFrames +================= +*/ +void ClientEndServerFrames() +{ + edict_t *ent; + + // calc the player views now that all pushing + // and damage has been added + for (uint32_t i = 0; i < game.maxclients; i++) + { + ent = g_edicts + 1 + i; + if (!ent->inuse || !ent->client) + continue; + ClientEndServerFrame(ent); + } +} + +/* +================= +CreateTargetChangeLevel + +Returns the created target changelevel +================= +*/ +edict_t *CreateTargetChangeLevel(const char *map) +{ + edict_t *ent; + + ent = G_Spawn(); + ent->classname = "target_changelevel"; + Q_strlcpy(level.nextmap, map, sizeof(level.nextmap)); + ent->map = level.nextmap; + return ent; +} + +inline std::vector str_split(const std::string_view &str, char by) +{ + std::vector out; + size_t start, end = 0; + + while ((start = str.find_first_not_of(by, end)) != std::string_view::npos) + { + end = str.find(by, start); + out.push_back(std::string{str.substr(start, end - start)}); + } + + return out; +} + +/* +================= +EndDMLevel + +The timelimit or fraglimit has been exceeded +================= +*/ +void EndDMLevel() +{ + edict_t *ent; + + // stay on same level flag + if (g_dm_same_level->integer) + { + BeginIntermission(CreateTargetChangeLevel(level.mapname)); + return; + } + + if (*level.forcemap) + { + BeginIntermission(CreateTargetChangeLevel(level.forcemap)); + return; + } + + // see if it's in the map list + if (*g_map_list->string) + { + const char *str = g_map_list->string; + char first_map[MAX_QPATH] { 0 }; + char *map; + + while (1) + { + map = COM_ParseEx(&str, " "); + + if (!*map) + break; + + if (Q_strcasecmp(map, level.mapname) == 0) + { + // it's in the list, go to the next one + map = COM_ParseEx(&str, " "); + if (!*map) + { + // end of list, go to first one + if (!first_map[0]) // there isn't a first one, same level + { + BeginIntermission(CreateTargetChangeLevel(level.mapname)); + return; + } + else + { + // [Paril-KEX] re-shuffle if necessary + if (g_map_list_shuffle->integer) + { + auto values = str_split(g_map_list->string, ' '); + + if (values.size() == 1) + { + // meh + BeginIntermission(CreateTargetChangeLevel(level.mapname)); + return; + } + + std::shuffle(values.begin(), values.end(), mt_rand); + + // if the current map is the map at the front, push it to the end + if (values[0] == level.mapname) + std::swap(values[0], values[values.size() - 1]); + + gi.cvar_forceset("g_map_list", fmt::format("{}", join_strings(values, " ")).data()); + + BeginIntermission(CreateTargetChangeLevel(values[0].c_str())); + return; + } + + BeginIntermission(CreateTargetChangeLevel(first_map)); + return; + } + } + else + { + BeginIntermission(CreateTargetChangeLevel(map)); + return; + } + } + if (!first_map[0]) + Q_strlcpy(first_map, map, sizeof(first_map)); + } + } + + if (level.nextmap[0]) // go to a specific map + { + BeginIntermission(CreateTargetChangeLevel(level.nextmap)); + return; + } + + // search for a changelevel + ent = G_FindByString<&edict_t::classname>(nullptr, "target_changelevel"); + + if (!ent) + { // the map designer didn't include a changelevel, + // so create a fake ent that goes back to the same level + BeginIntermission(CreateTargetChangeLevel(level.mapname)); + return; + } + + BeginIntermission(ent); +} + +/* +================= +CheckNeedPass +================= +*/ +void CheckNeedPass() +{ + int need; + static int32_t password_modified, spectator_password_modified; + + // if password or spectator_password has changed, update needpass + // as needed + if (Cvar_WasModified(password, password_modified) || Cvar_WasModified(spectator_password, spectator_password_modified)) + { + need = 0; + + if (*password->string && Q_strcasecmp(password->string, "none")) + need |= 1; + if (*spectator_password->string && Q_strcasecmp(spectator_password->string, "none")) + need |= 2; + + gi.cvar_set("needpass", G_Fmt("{}", need).data()); + } +} + +/* +================= +CheckDMRules +================= +*/ +void CheckDMRules() +{ + gclient_t *cl; + + if (level.intermissiontime) + return; + + if (!deathmatch->integer) + return; + + // ZOID + if (ctf->integer && CTFCheckRules()) + { + EndDMLevel(); + return; + } + if (CTFInMatch()) + return; // no checking in match mode + // ZOID + + //======= + // ROGUE + if (gamerules->integer && DMGame.CheckDMRules) + { + if (DMGame.CheckDMRules()) + return; + } + // ROGUE + //======= + + if (timelimit->value) + { + if (level.time >= gtime_t::from_min(timelimit->value)) + { + gi.LocBroadcast_Print(PRINT_HIGH, "$g_timelimit_hit"); + EndDMLevel(); + return; + } + } + + if (fraglimit->integer) + { + // [Paril-KEX] + if (teamplay->integer) + { + CheckEndTDMLevel(); + return; + } + + for (uint32_t i = 0; i < game.maxclients; i++) + { + cl = game.clients + i; + if (!g_edicts[i + 1].inuse) + continue; + + if (cl->resp.score >= fraglimit->integer) + { + gi.LocBroadcast_Print(PRINT_HIGH, "$g_fraglimit_hit"); + EndDMLevel(); + return; + } + } + } +} + +/* +============= +ExitLevel +============= +*/ +void ExitLevel() +{ + // [Paril-KEX] N64 fade + if (level.intermission_fade) + { + level.intermission_fade_time = level.time + 1.3_sec; + level.intermission_fading = true; + return; + } + + ClientEndServerFrames(); + + level.exitintermission = 0; + level.intermissiontime = 0_ms; + + // [Paril-KEX] support for intermission completely wiping players + // back to default stuff + if (level.intermission_clear) + { + level.intermission_clear = false; + + for (uint32_t i = 0; i < game.maxclients; i++) + { + // [Kex] Maintain user info to keep the player skin. + char userinfo[MAX_INFO_STRING]; + memcpy(userinfo, game.clients[i].pers.userinfo, sizeof(userinfo)); + + game.clients[i].pers = game.clients[i].resp.coop_respawn = {}; + g_edicts[i + 1].health = 0; // this should trip the power armor, etc to reset as well + + memcpy(game.clients[i].pers.userinfo, userinfo, sizeof(userinfo)); + memcpy(game.clients[i].resp.coop_respawn.userinfo, userinfo, sizeof(userinfo)); + } + } + + // [Paril-KEX] end of unit, so clear level trackers + if (level.intermission_eou) + { + game.level_entries = {}; + + // give all players their lives back + if (g_coop_enable_lives->integer) + for (auto player : active_players()) + player->client->pers.lives = g_coop_num_lives->integer + 1; + } + + if (CTFNextMap()) + return; + + if (level.changemap == nullptr) + { + gi.Com_Error("Got null changemap when trying to exit level. Was a trigger_changelevel configured correctly?"); + return; + } + + // for N64 mainly, but if we're directly changing to "victorXXX.pcx" then + // end game + size_t start_offset = (level.changemap[0] == '*' ? 1 : 0); + + if (strlen(level.changemap) > (6 + start_offset) && + !Q_strncasecmp(level.changemap + start_offset, "victor", 6) && + !Q_strncasecmp(level.changemap + strlen(level.changemap) - 4, ".pcx", 4)) + gi.AddCommandString(G_Fmt("endgame \"{}\"\n", level.changemap + start_offset).data()); + else + gi.AddCommandString(G_Fmt("gamemap \"{}\"\n", level.changemap).data()); + + level.changemap = nullptr; +} + +static void G_CheckCvars() +{ + if (Cvar_WasModified(sv_airaccelerate, game.airacceleration_modified)) + { + // [Paril-KEX] air accel handled by game DLL now, and allow + // it to be changed in sp/coop + gi.configstring(CS_AIRACCEL, G_Fmt("{}", sv_airaccelerate->integer).data()); + pm_config.airaccel = sv_airaccelerate->integer; + } + + if (Cvar_WasModified(sv_gravity, game.gravity_modified)) + level.gravity = sv_gravity->value; +} + +static bool G_AnyDeadPlayersWithoutLives() +{ + for (auto player : active_players()) + if (player->health <= 0 && !player->client->pers.lives) + return true; + + return false; +} + +/* +================ +G_RunFrame + +Advances the world by 0.1 seconds +================ +*/ +inline void G_RunFrame_(bool main_loop) +{ + level.in_frame = true; + + G_CheckCvars(); + + Bot_UpdateDebug(); + + level.time += FRAME_TIME_MS; + + if (level.intermission_fading) + { + if (level.intermission_fade_time > level.time) + { + float alpha = clamp(1.0f - (level.intermission_fade_time - level.time - 300_ms).seconds(), 0.f, 1.f); + + for (auto player : active_players()) + player->client->ps.screen_blend = { 0, 0, 0, alpha }; + } + else + { + level.intermission_fade = level.intermission_fading = false; + ExitLevel(); + } + + level.in_frame = false; + + return; + } + + edict_t *ent; + + // exit intermissions + + if (level.exitintermission) + { + ExitLevel(); + level.in_frame = false; + return; + } + + // reload the map start save if restart time is set (all players are dead) + if (level.coop_level_restart_time > 0_ms && level.time > level.coop_level_restart_time) + { + ClientEndServerFrames(); + gi.AddCommandString("restart_level\n"); + } + + // clear client coop respawn states; this is done + // early since it may be set multiple times for different + // players + if (coop->integer && (g_coop_enable_lives->integer || g_coop_squad_respawn->integer)) + { + for (auto player : active_players()) + { + if (player->client->respawn_time >= level.time) + player->client->coop_respawn_state = COOP_RESPAWN_WAITING; + else if (g_coop_enable_lives->integer && player->health <= 0 && player->client->pers.lives == 0) + player->client->coop_respawn_state = COOP_RESPAWN_NO_LIVES; + else if (g_coop_enable_lives->integer && G_AnyDeadPlayersWithoutLives()) + player->client->coop_respawn_state = COOP_RESPAWN_NO_LIVES; + else + player->client->coop_respawn_state = COOP_RESPAWN_NONE; + } + } + + // + // treat each object in turn + // even the world gets a chance to think + // + ent = &g_edicts[0]; + for (uint32_t i = 0; i < globals.num_edicts; i++, ent++) + { + if (!ent->inuse) + { + // defer removing client info so that disconnected, etc works + if (i > 0 && i <= game.maxclients) + { + if (ent->timestamp && level.time < ent->timestamp) + { + int32_t playernum = ent - g_edicts - 1; + gi.configstring(CS_PLAYERSKINS + playernum, ""); + ent->timestamp = 0_sec; + } + } + continue; + } + + level.current_entity = ent; + + // Paril: RF_BEAM entities update their old_origin by hand. + if (!(ent->s.renderfx & RF_BEAM)) + ent->s.old_origin = ent->s.origin; + + // if the ground entity moved, make sure we are still on it + if ((ent->groundentity) && (ent->groundentity->linkcount != ent->groundentity_linkcount)) + { + contents_t mask = G_GetClipMask(ent); + + if (!(ent->flags & (FL_SWIM | FL_FLY)) && (ent->svflags & SVF_MONSTER)) + { + ent->groundentity = nullptr; + M_CheckGround(ent, mask); + } + else + { + // if it's still 1 point below us, we're good + trace_t tr = gi.trace(ent->s.origin, ent->mins, ent->maxs, ent->s.origin + ent->gravityVector, ent, + mask); + + if (tr.startsolid || tr.allsolid || tr.ent != ent->groundentity) + ent->groundentity = nullptr; + else + ent->groundentity_linkcount = ent->groundentity->linkcount; + } + } + + Entity_UpdateState( ent ); + + if (i > 0 && i <= game.maxclients) + { + ClientBeginServerFrame(ent); + continue; + } + + G_RunEntity(ent); + } + + // see if it is time to end a deathmatch + CheckDMRules(); + + // see if needpass needs updated + CheckNeedPass(); + + if (coop->integer && (g_coop_enable_lives->integer || g_coop_squad_respawn->integer)) + { + // rarely, we can see a flash of text if all players respawned + // on some other player, so if everybody is now alive we'll reset + // back to empty + bool reset_coop_respawn = true; + + for (auto player : active_players()) + { + if (player->health >= 0) + { + reset_coop_respawn = false; + break; + } + } + + if (reset_coop_respawn) + { + for (auto player : active_players()) + player->client->coop_respawn_state = COOP_RESPAWN_NONE; + } + } + + // build the playerstate_t structures for all players + ClientEndServerFrames(); + + // [Paril-KEX] if not in intermission and player 1 is loaded in + // the game as an entity, increase timer on current entry + if (level.entry && !level.intermissiontime && g_edicts[1].inuse && g_edicts[1].client->pers.connected) + level.entry->time += FRAME_TIME_S; + + // [Paril-KEX] run monster pains now + for (uint32_t i = 0; i < globals.num_edicts + 1 + game.maxclients + BODY_QUEUE_SIZE; i++) + { + edict_t *e = &g_edicts[i]; + + if (!e->inuse || !(e->svflags & SVF_MONSTER)) + continue; + + M_ProcessPain(e); + } + + level.in_frame = false; +} + +inline bool G_AnyPlayerSpawned() +{ + for (auto player : active_players()) + if (player->client && player->client->pers.spawned) + return true; + + return false; +} + +void G_RunFrame(bool main_loop) +{ + if (main_loop && !G_AnyPlayerSpawned()) + return; + + for (int32_t i = 0; i < g_frames_per_frame->integer; i++) + G_RunFrame_(main_loop); + + // match details.. only bother if there's at least 1 player in-game + // and not already end of game + if (G_AnyPlayerSpawned() && !level.intermissiontime) + { + constexpr gtime_t MATCH_REPORT_TIME = 45_sec; + + if (level.time - level.next_match_report > MATCH_REPORT_TIME) + { + level.next_match_report = level.time + MATCH_REPORT_TIME; + G_ReportMatchDetails(false); + } + } +} + +/* +================ +G_PrepFrame + +This has to be done before the world logic, because +player processing happens outside RunFrame +================ +*/ +void G_PrepFrame() +{ + for (uint32_t i = 0; i < globals.num_edicts; i++) + g_edicts[i].s.event = EV_NONE; + + for (auto player : active_players()) + player->client->ps.stats[STAT_HIT_MARKER] = 0; + + globals.server_flags &= ~SERVER_FLAG_INTERMISSION; + + if ( level.intermissiontime ) { + globals.server_flags |= SERVER_FLAG_INTERMISSION; + } +} diff --git a/rerelease/g_misc.cpp b/rerelease/g_misc.cpp new file mode 100644 index 0000000..67b013a --- /dev/null +++ b/rerelease/g_misc.cpp @@ -0,0 +1,2485 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// g_misc.c + +#include "g_local.h" + +/*QUAKED func_group (0 0 0) ? +Used to group brushes together just for editor convenience. +*/ + +//===================================================== + +USE(Use_Areaportal) (edict_t *ent, edict_t *other, edict_t *activator) -> void +{ + ent->count ^= 1; // toggle state + gi.SetAreaPortalState(ent->style, ent->count); +} + +/*QUAKED func_areaportal (0 0 0) ? + +This is a non-visible object that divides the world into +areas that are seperated when this portal is not activated. +Usually enclosed in the middle of a door. +*/ +void SP_func_areaportal(edict_t *ent) +{ + ent->use = Use_Areaportal; + ent->count = 0; // always start closed; +} + +//===================================================== + +/* +================= +Misc functions +================= +*/ +void VelocityForDamage(int damage, vec3_t &v) +{ + v[0] = 100.0f * crandom(); + v[1] = 100.0f * crandom(); + v[2] = frandom(200.0f, 300.0f); + + if (damage < 50) + v = v * 0.7f; + else + v = v * 1.2f; +} + +void ClipGibVelocity(edict_t *ent) +{ + if (ent->velocity[0] < -300) + ent->velocity[0] = -300; + else if (ent->velocity[0] > 300) + ent->velocity[0] = 300; + if (ent->velocity[1] < -300) + ent->velocity[1] = -300; + else if (ent->velocity[1] > 300) + ent->velocity[1] = 300; + if (ent->velocity[2] < 200) + ent->velocity[2] = 200; // always some upwards + else if (ent->velocity[2] > 500) + ent->velocity[2] = 500; +} + +/* +================= +gibs +================= +*/ +DIE(gib_die) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void +{ + if (mod.id == MOD_CRUSH) + G_FreeEdict(self); +} + +TOUCH(gib_touch) (edict_t *self, edict_t *other, const trace_t &tr, bool other_touching_self) -> void +{ + if (tr.plane.normal[2] > 0.7f) + { + self->s.angles[0] = clamp(self->s.angles[0], -5.0f, 5.0f); + self->s.angles[2] = clamp(self->s.angles[2], -5.0f, 5.0f); + } +} + +edict_t *ThrowGib(edict_t *self, const char *gibname, int damage, gib_type_t type, float scale) +{ + edict_t *gib; + vec3_t vd; + vec3_t origin; + vec3_t size; + float vscale; + + if (type & GIB_HEAD) + { + gib = self; + gib->s.event = EV_OTHER_TELEPORT; + // remove setskin so that it doesn't set the skin wrongly later + self->monsterinfo.setskin = nullptr; + } + else + gib = G_Spawn(); + + size = self->size * 0.5f; + // since absmin is bloated by 1, un-bloat it here + origin = (self->absmin + vec3_t { 1, 1, 1 }) + size; + + int32_t i; + + for (i = 0; i < 3; i++) + { + gib->s.origin = origin + vec3_t { crandom(), crandom(), crandom() }.scaled(size); + + // try 3 times to get a good, non-solid position + if (!(gi.pointcontents(gib->s.origin) & MASK_SOLID)) + break; + } + + if (i == 3) + { + // only free us if we're not being turned into the gib, otherwise + // just spawn inside a wall + if (gib != self) + { + G_FreeEdict(gib); + return nullptr; + } + } + + gib->s.modelindex = gi.modelindex(gibname); + gib->s.modelindex2 = 0; + gib->s.scale = scale; + gib->solid = SOLID_NOT; + gib->svflags |= SVF_DEADMONSTER; + gib->svflags &= ~SVF_MONSTER; + gib->clipmask = MASK_SOLID; + gib->s.effects = EF_NONE; + gib->s.renderfx = RF_LOW_PRIORITY; + gib->s.renderfx |= RF_NOSHADOW; + if (!(type & GIB_DEBRIS)) + { + if (type & GIB_ACID) + gib->s.effects |= EF_GREENGIB; + else + gib->s.effects |= EF_GIB; + gib->s.renderfx |= RF_IR_VISIBLE; + } + gib->flags |= FL_NO_KNOCKBACK | FL_NO_DAMAGE_EFFECTS; + gib->takedamage = true; + gib->die = gib_die; + gib->classname = "gib"; + if (type & GIB_SKINNED) + gib->s.skinnum = self->s.skinnum; + else + gib->s.skinnum = 0; + gib->s.frame = 0; + gib->mins = gib->maxs = {}; + gib->s.sound = 0; + gib->monsterinfo.engine_sound = 0; + + if (!(type & GIB_METALLIC)) + { + gib->movetype = MOVETYPE_TOSS; + vscale = (type & GIB_ACID) ? 3.0 : 0.5; + } + else + { + gib->movetype = MOVETYPE_BOUNCE; + vscale = 1.0; + } + + if (type & GIB_DEBRIS) + { + vec3_t v; + v[0] = 100 * crandom(); + v[1] = 100 * crandom(); + v[2] = 100 + 100 * crandom(); + gib->velocity = self->velocity + (v * damage); + } + else + { + VelocityForDamage(damage, vd); + gib->velocity = self->velocity + (vd * vscale); + ClipGibVelocity(gib); + } + + if (type & GIB_UPRIGHT) + { + gib->touch = gib_touch; + gib->flags |= FL_ALWAYS_TOUCH; + } + + gib->avelocity[0] = frandom(600); + gib->avelocity[1] = frandom(600); + gib->avelocity[2] = frandom(600); + + gib->s.angles[0] = frandom(359); + gib->s.angles[1] = frandom(359); + gib->s.angles[2] = frandom(359); + + gib->think = G_FreeEdict; + + if (g_instagib->integer) + gib->nextthink = level.time + random_time(1_sec, 5_sec); + else + gib->nextthink = level.time + random_time(10_sec, 20_sec); + + gi.linkentity(gib); + + gib->watertype = gi.pointcontents(gib->s.origin); + + if (gib->watertype & MASK_WATER) + gib->waterlevel = WATER_FEET; + else + gib->waterlevel = WATER_NONE; + + return gib; +} + +void ThrowClientHead(edict_t *self, int damage) +{ + vec3_t vd; + const char *gibname; + + if (brandom()) + { + gibname = "models/objects/gibs/head2/tris.md2"; + self->s.skinnum = 1; // second skin is player + } + else + { + gibname = "models/objects/gibs/skull/tris.md2"; + self->s.skinnum = 0; + } + + self->s.origin[2] += 32; + self->s.frame = 0; + gi.setmodel(self, gibname); + self->mins = { -16, -16, 0 }; + self->maxs = { 16, 16, 16 }; + + self->takedamage = true; // [Paril-KEX] allow takedamage so we get crushed + self->solid = SOLID_TRIGGER; // [Paril-KEX] make 'trigger' so we still move but don't block shots/explode + self->svflags |= SVF_DEADMONSTER; + self->s.effects = EF_GIB; + // PGM + self->s.renderfx |= RF_IR_VISIBLE; + // PGM + self->s.sound = 0; + self->flags |= FL_NO_KNOCKBACK | FL_NO_DAMAGE_EFFECTS; + + self->movetype = MOVETYPE_BOUNCE; + VelocityForDamage(damage, vd); + self->velocity += vd; + + if (self->client) // bodies in the queue don't have a client anymore + { + self->client->anim_priority = ANIM_DEATH; + self->client->anim_end = self->s.frame; + } + else + { + self->think = nullptr; + self->nextthink = 0_ms; + } + + gi.linkentity(self); +} + +void BecomeExplosion1(edict_t *self) +{ + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_EXPLOSION1); + gi.WritePosition(self->s.origin); + gi.multicast(self->s.origin, MULTICAST_PHS, false); + + G_FreeEdict(self); +} + +void BecomeExplosion2(edict_t *self) +{ + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_EXPLOSION2); + gi.WritePosition(self->s.origin); + gi.multicast(self->s.origin, MULTICAST_PHS, false); + + G_FreeEdict(self); +} + +/*QUAKED path_corner (.5 .3 0) (-8 -8 -8) (8 8 8) TELEPORT +Target: next path corner +Pathtarget: gets used when an entity that has + this path_corner targeted touches it +*/ + +TOUCH(path_corner_touch) (edict_t *self, edict_t *other, const trace_t &tr, bool other_touching_self) -> void +{ + vec3_t v; + edict_t *next; + + if (other->movetarget != self) + return; + + if (other->enemy) + return; + + if (self->pathtarget) + { + const char *savetarget; + + savetarget = self->target; + self->target = self->pathtarget; + G_UseTargets(self, other); + self->target = savetarget; + } + + // see m_move; this is just so we don't needlessly check it + self->flags |= FL_PARTIALGROUND; + + if (self->target) + next = G_PickTarget(self->target); + else + next = nullptr; + + // [Paril-KEX] don't teleport to a point_combat, it means HOLD for them. + if ((next) && !strcmp(next->classname, "path_corner") && next->spawnflags.has(SPAWNFLAG_PATH_CORNER_TELEPORT)) + { + v = next->s.origin; + v[2] += next->mins[2]; + v[2] -= other->mins[2]; + other->s.origin = v; + next = G_PickTarget(next->target); + other->s.event = EV_OTHER_TELEPORT; + } + + other->goalentity = other->movetarget = next; + + if (self->wait) + { + other->monsterinfo.pausetime = level.time + gtime_t::from_sec(self->wait); + other->monsterinfo.stand(other); + return; + } + + if (!other->movetarget) + { + // N64 cutscene behavior + if (other->hackflags & HACKFLAG_END_CUTSCENE) + { + G_FreeEdict(other); + return; + } + + other->monsterinfo.pausetime = HOLD_FOREVER; + other->monsterinfo.stand(other); + } + else + { + v = other->goalentity->s.origin - other->s.origin; + other->ideal_yaw = vectoyaw(v); + } +} + +void SP_path_corner(edict_t *self) +{ + if (!self->targetname) + { + gi.Com_PrintFmt("{} with no targetname\n", *self); + G_FreeEdict(self); + return; + } + + self->solid = SOLID_TRIGGER; + self->touch = path_corner_touch; + self->mins = { -8, -8, -8 }; + self->maxs = { 8, 8, 8 }; + self->svflags |= SVF_NOCLIENT; + gi.linkentity(self); +} + +/*QUAKED point_combat (0.5 0.3 0) (-8 -8 -8) (8 8 8) Hold +Makes this the target of a monster and it will head here +when first activated before going after the activator. If +hold is selected, it will stay here. +*/ +TOUCH(point_combat_touch) (edict_t *self, edict_t *other, const trace_t &tr, bool other_touching_self) -> void +{ + edict_t *activator; + + if (other->movetarget != self) + return; + + if (self->target) + { + other->target = self->target; + other->goalentity = other->movetarget = G_PickTarget(other->target); + if (!other->goalentity) + { + gi.Com_PrintFmt("{} target {} does not exist\n", *self, self->target); + other->movetarget = self; + } + // [Paril-KEX] allow them to be re-used + //self->target = nullptr; + } + else if (self->spawnflags.has(SPAWNFLAG_POINT_COMBAT_HOLD) && !(other->flags & (FL_SWIM | FL_FLY))) + { + // already standing + if (other->monsterinfo.aiflags & AI_STAND_GROUND) + return; + + other->monsterinfo.pausetime = HOLD_FOREVER; + other->monsterinfo.aiflags |= AI_STAND_GROUND | AI_REACHED_HOLD_COMBAT | AI_THIRD_EYE; + other->monsterinfo.stand(other); + } + + if (other->movetarget == self) + { + // [Paril-KEX] if we're holding, keep movetarget set; we will + // use this to make sure we haven't moved too far from where + // we want to "guard". + if (!self->spawnflags.has(SPAWNFLAG_POINT_COMBAT_HOLD)) + { + other->target = nullptr; + other->movetarget = nullptr; + } + + other->goalentity = other->enemy; + other->monsterinfo.aiflags &= ~AI_COMBAT_POINT; + } + + if (self->pathtarget) + { + const char *savetarget; + + savetarget = self->target; + self->target = self->pathtarget; + if (other->enemy && other->enemy->client) + activator = other->enemy; + else if (other->oldenemy && other->oldenemy->client) + activator = other->oldenemy; + else if (other->activator && other->activator->client) + activator = other->activator; + else + activator = other; + G_UseTargets(self, activator); + self->target = savetarget; + } +} + +void SP_point_combat(edict_t *self) +{ + if (deathmatch->integer) + { + G_FreeEdict(self); + return; + } + self->solid = SOLID_TRIGGER; + self->touch = point_combat_touch; + self->mins = { -8, -8, -16 }; + self->maxs = { 8, 8, 16 }; + self->svflags = SVF_NOCLIENT; + gi.linkentity(self); +} + +/*QUAKED info_null (0 0.5 0) (-4 -4 -4) (4 4 4) +Used as a positional target for spotlights, etc. +*/ +void SP_info_null(edict_t *self) +{ + G_FreeEdict(self); +} + +/*QUAKED info_notnull (0 0.5 0) (-4 -4 -4) (4 4 4) +Used as a positional target for lightning. +*/ +void SP_info_notnull(edict_t *self) +{ + self->absmin = self->s.origin; + self->absmax = self->s.origin; +} + +/*QUAKED light (0 1 0) (-8 -8 -8) (8 8 8) START_OFF ALLOW_IN_DM +Non-displayed light. +Default light value is 300. +Default style is 0. +If targeted, will toggle between on and off. +Default _cone value is 10 (used to set size of light for spotlights) +*/ + +constexpr spawnflags_t SPAWNFLAG_LIGHT_START_OFF = 1_spawnflag; +constexpr spawnflags_t SPAWNFLAG_LIGHT_ALLOW_IN_DM = 2_spawnflag; + +USE(light_use) (edict_t *self, edict_t *other, edict_t *activator) -> void +{ + if (self->spawnflags.has(SPAWNFLAG_LIGHT_START_OFF)) + { + gi.configstring(CS_LIGHTS + self->style, self->style_on); + self->spawnflags &= ~SPAWNFLAG_LIGHT_START_OFF; + } + else + { + gi.configstring(CS_LIGHTS + self->style, self->style_off); + self->spawnflags |= SPAWNFLAG_LIGHT_START_OFF; + } +} + +// --------------------------------------------------------------------------------- +// [Sam-KEX] For keeping track of shadow light parameters and setting them up on +// the server side. + +struct shadow_light_info_t +{ + int entity_number; + shadow_light_data_t shadowlight; +}; + +static shadow_light_info_t shadowlightinfo[MAX_SHADOW_LIGHTS]; + +const shadow_light_data_t *GetShadowLightData(int32_t entity_number) +{ + for (int32_t i = 0; i < level.shadow_light_count; i++) + { + if (shadowlightinfo[i].entity_number == entity_number) + return &shadowlightinfo[i].shadowlight; + } + + return nullptr; +} + +void setup_shadow_lights() +{ + for(int i = 0; i < level.shadow_light_count; ++i) + { + edict_t *self = &g_edicts[shadowlightinfo[i].entity_number]; + + shadowlightinfo[i].shadowlight.lighttype = shadow_light_type_t::point; + shadowlightinfo[i].shadowlight.conedirection = {}; + + if(self->target) + { + edict_t* target = G_FindByString<&edict_t::targetname>(nullptr, self->target); + if(target) + { + shadowlightinfo[i].shadowlight.conedirection = (target->s.origin - self->s.origin).normalized(); + shadowlightinfo[i].shadowlight.lighttype = shadow_light_type_t::cone; + } + } + + if (self->itemtarget) + { + edict_t* target = G_FindByString<&edict_t::targetname>(nullptr, self->itemtarget); + if(target) + shadowlightinfo[i].shadowlight.lightstyle = target->style; + } + + gi.configstring(CS_SHADOWLIGHTS + i, G_Fmt("{};{};{:1};{};{:1};{:1};{:1};{};{:1};{:1};{:1};{:1}", + self->s.number, + (int)shadowlightinfo[i].shadowlight.lighttype, + shadowlightinfo[i].shadowlight.radius, + shadowlightinfo[i].shadowlight.resolution, + shadowlightinfo[i].shadowlight.intensity, + shadowlightinfo[i].shadowlight.fade_start, + shadowlightinfo[i].shadowlight.fade_end, + shadowlightinfo[i].shadowlight.lightstyle, + shadowlightinfo[i].shadowlight.coneangle, + shadowlightinfo[i].shadowlight.conedirection[0], + shadowlightinfo[i].shadowlight.conedirection[1], + shadowlightinfo[i].shadowlight.conedirection[2]).data()); + } +} +// --------------------------------------------------------------------------------- + +static void setup_dynamic_light(edict_t* self) +{ + // [Sam-KEX] Shadow stuff + if (st.sl.data.radius > 0) + { + self->s.renderfx = RF_CASTSHADOW; + self->itemtarget = st.sl.lightstyletarget; + + shadowlightinfo[level.shadow_light_count].entity_number = self->s.number; + shadowlightinfo[level.shadow_light_count].shadowlight = st.sl.data; + level.shadow_light_count++; + + self->mins[0] = self->mins[1] = self->mins[2] = 0; + self->maxs[0] = self->maxs[1] = self->maxs[2] = 0; + + gi.linkentity(self); + } +} + +USE(dynamic_light_use) (edict_t *self, edict_t *other, edict_t *activator) -> void +{ + self->svflags ^= SVF_NOCLIENT; +} + +void SP_dynamic_light(edict_t* self) +{ + setup_dynamic_light(self); + + if (self->targetname) + { + self->use = dynamic_light_use; + } + + if (self->spawnflags.has(SPAWNFLAG_LIGHT_START_OFF)) + self->svflags ^= SVF_NOCLIENT; +} + +void SP_light(edict_t *self) +{ + // no targeted lights in deathmatch, because they cause global messages + if((!self->targetname || (deathmatch->integer && !(self->spawnflags.has(SPAWNFLAG_LIGHT_ALLOW_IN_DM)))) && st.sl.data.radius == 0) // [Sam-KEX] + { + G_FreeEdict(self); + return; + } + + if (self->style >= 32) + { + self->use = light_use; + + if (!self->style_on || !*self->style_on) + self->style_on = "m"; + else if (*self->style_on >= '0' && *self->style_on <= '9') + self->style_on = gi.get_configstring(CS_LIGHTS + atoi(self->style_on)); + if (!self->style_off || !*self->style_off) + self->style_off = "a"; + else if (*self->style_off >= '0' && *self->style_off <= '9') + self->style_off = gi.get_configstring(CS_LIGHTS + atoi(self->style_off)); + + if (self->spawnflags.has(SPAWNFLAG_LIGHT_START_OFF)) + gi.configstring(CS_LIGHTS + self->style, self->style_off); + else + gi.configstring(CS_LIGHTS + self->style, self->style_on); + } + + setup_dynamic_light(self); +} + +/*QUAKED func_wall (0 .5 .8) ? TRIGGER_SPAWN TOGGLE START_ON ANIMATED ANIMATED_FAST +This is just a solid wall if not inhibited + +TRIGGER_SPAWN the wall will not be present until triggered + it will then blink in to existance; it will + kill anything that was in it's way + +TOGGLE only valid for TRIGGER_SPAWN walls + this allows the wall to be turned on and off + +START_ON only valid for TRIGGER_SPAWN walls + the wall will initially be present +*/ + +constexpr spawnflags_t SPAWNFLAG_WALL_TRIGGER_SPAWN = 1_spawnflag; +constexpr spawnflags_t SPAWNFLAG_WALL_TOGGLE = 2_spawnflag; +constexpr spawnflags_t SPAWNFLAG_WALL_START_ON = 4_spawnflag; +constexpr spawnflags_t SPAWNFLAG_WALL_ANIMATED = 8_spawnflag; +constexpr spawnflags_t SPAWNFLAG_WALL_ANIMATED_FAST = 16_spawnflag; + +USE(func_wall_use) (edict_t *self, edict_t *other, edict_t *activator) -> void +{ + if (self->solid == SOLID_NOT) + { + self->solid = SOLID_BSP; + self->svflags &= ~SVF_NOCLIENT; + gi.linkentity(self); + KillBox(self, false); + } + else + { + self->solid = SOLID_NOT; + self->svflags |= SVF_NOCLIENT; + gi.linkentity(self); + } + + if (!self->spawnflags.has(SPAWNFLAG_WALL_TOGGLE)) + self->use = nullptr; +} + +void SP_func_wall(edict_t *self) +{ + self->movetype = MOVETYPE_PUSH; + gi.setmodel(self, self->model); + + if (self->spawnflags.has(SPAWNFLAG_WALL_ANIMATED)) + self->s.effects |= EF_ANIM_ALL; + if (self->spawnflags.has(SPAWNFLAG_WALL_ANIMATED_FAST)) + self->s.effects |= EF_ANIM_ALLFAST; + + // just a wall + if (!self->spawnflags.has(SPAWNFLAG_WALL_TRIGGER_SPAWN | SPAWNFLAG_WALL_TOGGLE | SPAWNFLAG_WALL_START_ON)) + { + self->solid = SOLID_BSP; + gi.linkentity(self); + return; + } + + // it must be TRIGGER_SPAWN + if (!(self->spawnflags & SPAWNFLAG_WALL_TRIGGER_SPAWN)) + self->spawnflags |= SPAWNFLAG_WALL_TRIGGER_SPAWN; + + // yell if the spawnflags are odd + if (self->spawnflags.has(SPAWNFLAG_WALL_START_ON)) + { + if (!self->spawnflags.has(SPAWNFLAG_WALL_TOGGLE)) + { + gi.Com_Print("func_wall START_ON without TOGGLE\n"); + self->spawnflags |= SPAWNFLAG_WALL_TOGGLE; + } + } + + self->use = func_wall_use; + if (self->spawnflags.has(SPAWNFLAG_WALL_START_ON)) + { + self->solid = SOLID_BSP; + } + else + { + self->solid = SOLID_NOT; + self->svflags |= SVF_NOCLIENT; + } + gi.linkentity(self); +} + +// [Paril-KEX] +/*QUAKED func_animation (0 .5 .8) ? START_ON +Similar to func_wall, but triggering it will toggle animation +state rather than going on/off. + +START_ON will start in alterate animation +*/ + +constexpr spawnflags_t SPAWNFLAG_ANIMATION_START_ON = 1_spawnflag; + +USE(func_animation_use) (edict_t *self, edict_t *other, edict_t *activator) -> void +{ + self->bmodel_anim.alternate = !self->bmodel_anim.alternate; +} + +void SP_func_animation(edict_t *self) +{ + if (!self->bmodel_anim.enabled) + { + gi.Com_PrintFmt("{} has no animation data\n", *self); + G_FreeEdict(self); + return; + } + + self->movetype = MOVETYPE_PUSH; + gi.setmodel(self, self->model); + self->solid = SOLID_BSP; + + self->use = func_animation_use; + self->bmodel_anim.alternate = self->spawnflags.has(SPAWNFLAG_ANIMATION_START_ON); + + if (self->bmodel_anim.alternate) + self->s.frame = self->bmodel_anim.alt_start; + else + self->s.frame = self->bmodel_anim.start; + + gi.linkentity(self); +} + +/*QUAKED func_object (0 .5 .8) ? TRIGGER_SPAWN ANIMATED ANIMATED_FAST +This is solid bmodel that will fall if it's support it removed. +*/ + +constexpr spawnflags_t SPAWNFLAGS_OBJECT_TRIGGER_SPAWN = 1_spawnflag; +constexpr spawnflags_t SPAWNFLAGS_OBJECT_ANIMATED = 2_spawnflag; +constexpr spawnflags_t SPAWNFLAGS_OBJECT_ANIMATED_FAST = 4_spawnflag; + +TOUCH(func_object_touch) (edict_t *self, edict_t *other, const trace_t &tr, bool other_touching_self) -> void +{ + // only squash thing we fall on top of + if (other_touching_self) + return; + if (tr.plane.normal[2] < 1.0f) + return; + if (other->takedamage == false) + return; + if (other->damage_debounce_time > level.time) + return; + T_Damage(other, self, self, vec3_origin, closest_point_to_box(other->s.origin, self->absmin, self->absmax), tr.plane.normal, self->dmg, 1, DAMAGE_NONE, MOD_CRUSH); + other->damage_debounce_time = level.time + 10_hz; +} + +THINK(func_object_release) (edict_t *self) -> void +{ + self->movetype = MOVETYPE_TOSS; + self->touch = func_object_touch; +} + +USE(func_object_use) (edict_t *self, edict_t *other, edict_t *activator) -> void +{ + self->solid = SOLID_BSP; + self->svflags &= ~SVF_NOCLIENT; + self->use = nullptr; + func_object_release(self); + KillBox(self, false); +} + +void SP_func_object(edict_t *self) +{ + gi.setmodel(self, self->model); + + self->mins[0] += 1; + self->mins[1] += 1; + self->mins[2] += 1; + self->maxs[0] -= 1; + self->maxs[1] -= 1; + self->maxs[2] -= 1; + + if (!self->dmg) + self->dmg = 100; + + if (!(self->spawnflags & SPAWNFLAGS_OBJECT_TRIGGER_SPAWN)) + { + self->solid = SOLID_BSP; + self->movetype = MOVETYPE_PUSH; + self->think = func_object_release; + self->nextthink = level.time + 20_hz; + } + else + { + self->solid = SOLID_NOT; + self->movetype = MOVETYPE_PUSH; + self->use = func_object_use; + self->svflags |= SVF_NOCLIENT; + } + + if (self->spawnflags.has(SPAWNFLAGS_OBJECT_ANIMATED)) + self->s.effects |= EF_ANIM_ALL; + if (self->spawnflags.has(SPAWNFLAGS_OBJECT_ANIMATED_FAST)) + self->s.effects |= EF_ANIM_ALLFAST; + + self->clipmask = MASK_MONSTERSOLID; + self->flags |= FL_NO_STANDING; + + gi.linkentity(self); +} + +/*QUAKED func_explosive (0 .5 .8) ? Trigger_Spawn ANIMATED ANIMATED_FAST INACTIVE ALWAYS_SHOOTABLE +Any brush that you want to explode or break apart. If you want an +ex0plosion, set dmg and it will do a radius explosion of that amount +at the center of the bursh. + +If targeted it will not be shootable. + +INACTIVE - specifies that the entity is not explodable until triggered. If you use this you must +target the entity you want to trigger it. This is the only entity approved to activate it. + +health defaults to 100. + +mass defaults to 75. This determines how much debris is emitted when +it explodes. You get one large chunk per 100 of mass (up to 8) and +one small chunk per 25 of mass (up to 16). So 800 gives the most. +*/ + +constexpr spawnflags_t SPAWNFLAGS_EXPLOSIVE_TRIGGER_SPAWN = 1_spawnflag; +constexpr spawnflags_t SPAWNFLAGS_EXPLOSIVE_ANIMATED = 2_spawnflag; +constexpr spawnflags_t SPAWNFLAGS_EXPLOSIVE_ANIMATED_FAST = 4_spawnflag; +constexpr spawnflags_t SPAWNFLAGS_EXPLOSIVE_INACTIVE = 8_spawnflag; +constexpr spawnflags_t SPAWNFLAGS_EXPLOSIVE_ALWAYS_SHOOTABLE = 16_spawnflag; + +DIE(func_explosive_explode) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void +{ + size_t count; + int mass; + edict_t *master; + bool done = false; + + self->takedamage = false; + + if (self->dmg) + T_RadiusDamage(self, attacker, (float) self->dmg, nullptr, (float) (self->dmg + 40), DAMAGE_NONE, MOD_EXPLOSIVE); + + self->velocity = inflictor->s.origin - self->s.origin; + self->velocity.normalize(); + self->velocity *= 150; + + mass = self->mass; + if (!mass) + mass = 75; + + // big chunks + if (mass >= 100) + { + count = mass / 100; + if (count > 8) + count = 8; + ThrowGibs(self, 1, { + { count, "models/objects/debris1/tris.md2", GIB_METALLIC | GIB_DEBRIS } + }); + } + + // small chunks + count = mass / 25; + if (count > 16) + count = 16; + ThrowGibs(self, 2, { + { count, "models/objects/debris2/tris.md2", GIB_METALLIC | GIB_DEBRIS } + }); + + // PMM - if we're part of a train, clean ourselves out of it + if (self->flags & FL_TEAMSLAVE) + { + if (self->teammaster) + { + master = self->teammaster; + if (master && master->inuse) // because mappers (other than jim (usually)) are stupid.... + { + while (!done) + { + if (master->teamchain == self) + { + master->teamchain = self->teamchain; + done = true; + } + master = master->teamchain; + } + } + } + } + + G_UseTargets(self, attacker); + + self->s.origin = (self->absmin + self->absmax) * 0.5f; + + if (self->noise_index) + gi.positioned_sound(self->s.origin, self, CHAN_AUTO, self->noise_index, 1, ATTN_NORM, 0); + + if (self->dmg) + BecomeExplosion1(self); + else + G_FreeEdict(self); +} + +USE(func_explosive_use) (edict_t *self, edict_t *other, edict_t *activator) -> void +{ + // Paril: pass activator to explode as attacker. this fixes + // "strike" trying to centerprint to the relay. Should be + // a safe change. + func_explosive_explode(self, self, activator, self->health, vec3_origin, MOD_EXPLOSIVE); +} + +// PGM +USE(func_explosive_activate) (edict_t *self, edict_t *other, edict_t *activator) -> void +{ + int approved; + + approved = 0; + // PMM - looked like target and targetname were flipped here + if (other != nullptr && other->target) + { + if (!strcmp(other->target, self->targetname)) + approved = 1; + } + if (!approved && activator != nullptr && activator->target) + { + if (!strcmp(activator->target, self->targetname)) + approved = 1; + } + + if (!approved) + return; + + self->use = func_explosive_use; + if (!self->health) + self->health = 100; + self->die = func_explosive_explode; + self->takedamage = true; +} +// PGM + +USE(func_explosive_spawn) (edict_t *self, edict_t *other, edict_t *activator) -> void +{ + self->solid = SOLID_BSP; + self->svflags &= ~SVF_NOCLIENT; + self->use = nullptr; + gi.linkentity(self); + KillBox(self, false); +} + +void SP_func_explosive(edict_t *self) +{ + if (deathmatch->integer) + { // auto-remove for deathmatch + G_FreeEdict(self); + return; + } + + self->movetype = MOVETYPE_PUSH; + + gi.modelindex("models/objects/debris1/tris.md2"); + gi.modelindex("models/objects/debris2/tris.md2"); + + gi.setmodel(self, self->model); + + if (self->spawnflags.has(SPAWNFLAGS_EXPLOSIVE_TRIGGER_SPAWN)) + { + self->svflags |= SVF_NOCLIENT; + self->solid = SOLID_NOT; + self->use = func_explosive_spawn; + } + // PGM + else if (self->spawnflags.has(SPAWNFLAGS_EXPLOSIVE_INACTIVE)) + { + self->solid = SOLID_BSP; + if (self->targetname) + self->use = func_explosive_activate; + } + // PGM + else + { + self->solid = SOLID_BSP; + if (self->targetname) + self->use = func_explosive_use; + } + + if (self->spawnflags.has(SPAWNFLAGS_EXPLOSIVE_ANIMATED)) + self->s.effects |= EF_ANIM_ALL; + if (self->spawnflags.has(SPAWNFLAGS_EXPLOSIVE_ANIMATED_FAST)) + self->s.effects |= EF_ANIM_ALLFAST; + + // PGM + if (self->spawnflags.has(SPAWNFLAGS_EXPLOSIVE_ALWAYS_SHOOTABLE) || ((self->use != func_explosive_use) && (self->use != func_explosive_activate))) + // PGM + { + if (!self->health) + self->health = 100; + self->die = func_explosive_explode; + self->takedamage = true; + } + + if (self->sounds) + { + if (self->sounds == 1) + self->noise_index = gi.soundindex("world/brkglas.wav"); + else + gi.Com_PrintFmt("{}: invalid \"sounds\" {}\n", *self, self->sounds); + } + + gi.linkentity(self); +} + +/*QUAKED misc_explobox (0 .5 .8) (-16 -16 0) (16 16 40) +Large exploding box. You can override its mass (100), +health (80), and dmg (150). +*/ + +TOUCH(barrel_touch) (edict_t *self, edict_t *other, const trace_t &tr, bool other_touching_self) -> void +{ + float ratio; + vec3_t v; + + if ((!other->groundentity) || (other->groundentity == self)) + return; + else if (!other_touching_self) + return; + + ratio = (float) other->mass / (float) self->mass; + v = self->s.origin - other->s.origin; + M_walkmove(self, vectoyaw(v), 20 * ratio * gi.frame_time_s); +} + +THINK(barrel_explode) (edict_t *self) -> void +{ + self->takedamage = false; + + T_RadiusDamage(self, self->activator, (float) self->dmg, nullptr, (float) (self->dmg + 40), DAMAGE_NONE, MOD_BARREL); + + ThrowGibs(self, (1.5f * self->dmg / 200.f), { + { 2, "models/objects/debris1/tris.md2", GIB_METALLIC | GIB_DEBRIS }, + { 4, "models/objects/debris3/tris.md2", GIB_METALLIC | GIB_DEBRIS }, + { 8, "models/objects/debris2/tris.md2", GIB_METALLIC | GIB_DEBRIS } + }); + + if (self->groundentity) + BecomeExplosion2(self); + else + BecomeExplosion1(self); +} + +THINK(barrel_burn) (edict_t* self) -> void +{ + if (level.time >= self->timestamp) + self->think = barrel_explode; + + self->s.effects |= EF_BARREL_EXPLODING; + self->s.sound = gi.soundindex("weapons/bfg__l1a.wav"); + self->nextthink = level.time + FRAME_TIME_S; +} + +DIE(barrel_delay) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void +{ + // allow "dead" barrels waiting to explode to still receive knockback + if (self->think == barrel_burn || self->think == barrel_explode) + return; + + // allow big booms to immediately blow up barrels (rockets, rail, other explosions) because it feels good and powerful + if (damage >= 90) + { + self->think = barrel_explode; + self->activator = attacker; + } + else + { + self->timestamp = level.time + 750_ms; + self->think = barrel_burn; + self->activator = attacker; + } + +} + +//========= +// PGM - change so barrels will think and hence, blow up +THINK(barrel_think) (edict_t *self) -> void +{ + // the think needs to be first since later stuff may override. + self->think = barrel_think; + self->nextthink = level.time + FRAME_TIME_S; + + M_CatagorizePosition(self, self->s.origin, self->waterlevel, self->watertype); + self->flags |= FL_IMMUNE_SLIME; + self->air_finished = level.time + 100_sec; + M_WorldEffects(self); +} + +THINK(barrel_start) (edict_t *self) -> void +{ + M_droptofloor(self); + self->think = barrel_think; + self->nextthink = level.time + FRAME_TIME_S; +} +// PGM +//========= + +void SP_misc_explobox(edict_t *self) +{ + if (deathmatch->integer) + { // auto-remove for deathmatch + G_FreeEdict(self); + return; + } + + gi.modelindex("models/objects/debris1/tris.md2"); + gi.modelindex("models/objects/debris2/tris.md2"); + gi.modelindex("models/objects/debris3/tris.md2"); + gi.soundindex("weapons/bfg__l1a.wav"); + + self->solid = SOLID_BBOX; + self->movetype = MOVETYPE_STEP; + + self->model = "models/objects/barrels/tris.md2"; + self->s.modelindex = gi.modelindex(self->model); + self->mins = { -16, -16, 0 }; + self->maxs = { 16, 16, 40 }; + + if (!self->mass) + self->mass = 50; + if (!self->health) + self->health = 10; + if (!self->dmg) + self->dmg = 150; + + self->die = barrel_delay; + self->takedamage = true; + self->flags |= FL_TRAP; + + self->touch = barrel_touch; + + // PGM - change so barrels will think and hence, blow up + self->think = barrel_start; + self->nextthink = level.time + 20_hz; + // PGM + + gi.linkentity(self); +} + +// +// miscellaneous specialty items +// + +/*QUAKED misc_blackhole (1 .5 0) (-8 -8 -8) (8 8 8) AUTO_NOISE +model="models/objects/black/tris.md2" +*/ + +constexpr spawnflags_t SPAWNFLAG_BLACKHOLE_AUTO_NOISE = 1_spawnflag; + +USE(misc_blackhole_use) (edict_t *ent, edict_t *other, edict_t *activator) -> void +{ + /* + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_BOSSTPORT); + gi.WritePosition (ent->s.origin); + gi.multicast (ent->s.origin, MULTICAST_PVS); + */ + G_FreeEdict(ent); +} + +THINK(misc_blackhole_think) (edict_t *self) -> void +{ + if (self->timestamp <= level.time) + { + if (++self->s.frame >= 19) + self->s.frame = 0; + + self->timestamp = level.time + 10_hz; + } + + if (self->spawnflags.has(SPAWNFLAG_BLACKHOLE_AUTO_NOISE)) + { + self->s.angles[0] += 50.0f * gi.frame_time_s; + self->s.angles[1] += 50.0f * gi.frame_time_s; + } + + self->nextthink = level.time + FRAME_TIME_MS; +} + +void SP_misc_blackhole(edict_t *ent) +{ + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_NOT; + ent->mins = { -64, -64, 0 }; + ent->maxs = { 64, 64, 8 }; + ent->s.modelindex = gi.modelindex("models/objects/black/tris.md2"); + ent->s.renderfx = RF_TRANSLUCENT; + ent->use = misc_blackhole_use; + ent->think = misc_blackhole_think; + ent->nextthink = level.time + 20_hz; + + if (ent->spawnflags.has(SPAWNFLAG_BLACKHOLE_AUTO_NOISE)) + { + ent->s.sound = gi.soundindex("world/blackhole.wav"); + ent->s.loop_attenuation = ATTN_NORM; + } + + gi.linkentity(ent); +} + +/*QUAKED misc_eastertank (1 .5 0) (-32 -32 -16) (32 32 32) + */ + +THINK(misc_eastertank_think) (edict_t *self) -> void +{ + if (++self->s.frame < 293) + self->nextthink = level.time + 10_hz; + else + { + self->s.frame = 254; + self->nextthink = level.time + 10_hz; + } +} + +void SP_misc_eastertank(edict_t *ent) +{ + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_BBOX; + ent->mins = { -32, -32, -16 }; + ent->maxs = { 32, 32, 32 }; + ent->s.modelindex = gi.modelindex("models/monsters/tank/tris.md2"); + ent->s.frame = 254; + ent->think = misc_eastertank_think; + ent->nextthink = level.time + 20_hz; + gi.linkentity(ent); +} + +/*QUAKED misc_easterchick (1 .5 0) (-32 -32 0) (32 32 32) + */ + +THINK(misc_easterchick_think) (edict_t *self) -> void +{ + if (++self->s.frame < 247) + self->nextthink = level.time + 10_hz; + else + { + self->s.frame = 208; + self->nextthink = level.time + 10_hz; + } +} + +void SP_misc_easterchick(edict_t *ent) +{ + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_BBOX; + ent->mins = { -32, -32, 0 }; + ent->maxs = { 32, 32, 32 }; + ent->s.modelindex = gi.modelindex("models/monsters/bitch/tris.md2"); + ent->s.frame = 208; + ent->think = misc_easterchick_think; + ent->nextthink = level.time + 20_hz; + gi.linkentity(ent); +} + +/*QUAKED misc_easterchick2 (1 .5 0) (-32 -32 0) (32 32 32) + */ + +THINK(misc_easterchick2_think) (edict_t *self) -> void +{ + if (++self->s.frame < 287) + self->nextthink = level.time + 10_hz; + else + { + self->s.frame = 248; + self->nextthink = level.time + 10_hz; + } +} + +void SP_misc_easterchick2(edict_t *ent) +{ + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_BBOX; + ent->mins = { -32, -32, 0 }; + ent->maxs = { 32, 32, 32 }; + ent->s.modelindex = gi.modelindex("models/monsters/bitch/tris.md2"); + ent->s.frame = 248; + ent->think = misc_easterchick2_think; + ent->nextthink = level.time + 20_hz; + gi.linkentity(ent); +} + +/*QUAKED monster_commander_body (1 .5 0) (-32 -32 0) (32 32 48) +Not really a monster, this is the Tank Commander's decapitated body. +There should be a item_commander_head that has this as it's target. +*/ + +THINK(commander_body_think) (edict_t *self) -> void +{ + if (++self->s.frame < 24) + self->nextthink = level.time + 10_hz; + else + self->nextthink = 0_ms; + + if (self->s.frame == 22) + gi.sound(self, CHAN_BODY, gi.soundindex("tank/thud.wav"), 1, ATTN_NORM, 0); +} + +USE(commander_body_use) (edict_t *self, edict_t *other, edict_t *activator) -> void +{ + self->think = commander_body_think; + self->nextthink = level.time + 10_hz; + gi.sound(self, CHAN_BODY, gi.soundindex("tank/pain.wav"), 1, ATTN_NORM, 0); +} + +THINK(commander_body_drop) (edict_t *self) -> void +{ + self->movetype = MOVETYPE_TOSS; + self->s.origin[2] += 2; +} + +void SP_monster_commander_body(edict_t *self) +{ + self->movetype = MOVETYPE_NONE; + self->solid = SOLID_BBOX; + self->model = "models/monsters/commandr/tris.md2"; + self->s.modelindex = gi.modelindex(self->model); + self->mins = { -32, -32, 0 }; + self->maxs = { 32, 32, 48 }; + self->use = commander_body_use; + self->takedamage = true; + self->flags = FL_GODMODE; + gi.linkentity(self); + + gi.soundindex("tank/thud.wav"); + gi.soundindex("tank/pain.wav"); + + self->think = commander_body_drop; + self->nextthink = level.time + 50_hz; +} + +/*QUAKED misc_banner (1 .5 0) (-4 -4 -4) (4 4 4) +The origin is the bottom of the banner. +The banner is 128 tall. +model="models/objects/banner/tris.md2" +*/ +THINK(misc_banner_think) (edict_t *ent) -> void +{ + ent->s.frame = (ent->s.frame + 1) % 16; + ent->nextthink = level.time + 10_hz; +} + +void SP_misc_banner(edict_t *ent) +{ + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_NOT; + ent->s.modelindex = gi.modelindex("models/objects/banner/tris.md2"); + ent->s.frame = irandom(16); + gi.linkentity(ent); + + ent->think = misc_banner_think; + ent->nextthink = level.time + 10_hz; +} + +/*QUAKED misc_deadsoldier (1 .5 0) (-16 -16 0) (16 16 16) ON_BACK ON_STOMACH BACK_DECAP FETAL_POS SIT_DECAP IMPALED +This is the dead player model. Comes in 6 exciting different poses! +*/ + +constexpr spawnflags_t SPAWNFLAGS_DEADSOLDIER_ON_BACK = 1_spawnflag; +constexpr spawnflags_t SPAWNFLAGS_DEADSOLDIER_ON_STOMACH = 2_spawnflag; +constexpr spawnflags_t SPAWNFLAGS_DEADSOLDIER_BACK_DECAP = 4_spawnflag; +constexpr spawnflags_t SPAWNFLAGS_DEADSOLDIER_FETAL_POS = 8_spawnflag; +constexpr spawnflags_t SPAWNFLAGS_DEADSOLDIER_SIT_DECAP = 16_spawnflag; +constexpr spawnflags_t SPAWNFLAGS_DEADSOLDIER_IMPALED = 32_spawnflag; + +DIE(misc_deadsoldier_die) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void +{ + if (self->health > -30) + return; + + gi.sound(self, CHAN_BODY, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0); + ThrowGibs(self, damage, { + { 4, "models/objects/gibs/sm_meat/tris.md2" }, + { "models/objects/gibs/head2/tris.md2", GIB_HEAD } + }); +} + +void SP_misc_deadsoldier(edict_t *ent) +{ + if (deathmatch->integer) + { // auto-remove for deathmatch + G_FreeEdict(ent); + return; + } + + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_BBOX; + ent->s.modelindex = gi.modelindex("models/deadbods/dude/tris.md2"); + + // Defaults to frame 0 + if (ent->spawnflags.has(SPAWNFLAGS_DEADSOLDIER_ON_STOMACH)) + ent->s.frame = 1; + else if (ent->spawnflags.has(SPAWNFLAGS_DEADSOLDIER_BACK_DECAP)) + ent->s.frame = 2; + else if (ent->spawnflags.has(SPAWNFLAGS_DEADSOLDIER_FETAL_POS)) + ent->s.frame = 3; + else if (ent->spawnflags.has(SPAWNFLAGS_DEADSOLDIER_SIT_DECAP)) + ent->s.frame = 4; + else if (ent->spawnflags.has(SPAWNFLAGS_DEADSOLDIER_IMPALED)) + ent->s.frame = 5; + else if (ent->spawnflags.has(SPAWNFLAGS_DEADSOLDIER_ON_BACK)) + ent->s.frame = 0; + else + ent->s.frame = 0; + + ent->mins = { -16, -16, 0 }; + ent->maxs = { 16, 16, 16 }; + ent->deadflag = true; + ent->takedamage = true; + // nb: SVF_MONSTER is here so it bleeds + ent->svflags |= SVF_MONSTER | SVF_DEADMONSTER; + ent->die = misc_deadsoldier_die; + ent->monsterinfo.aiflags |= AI_GOOD_GUY | AI_DO_NOT_COUNT; + + gi.linkentity(ent); +} + +/*QUAKED misc_viper (1 .5 0) (-16 -16 0) (16 16 32) +This is the Viper for the flyby bombing. +It is trigger_spawned, so you must have something use it for it to show up. +There must be a path for it to follow once it is activated. + +"speed" How fast the Viper should fly +*/ + +USE(misc_viper_use) (edict_t *self, edict_t *other, edict_t *activator) -> void +{ + self->svflags &= ~SVF_NOCLIENT; + self->use = train_use; + train_use(self, other, activator); +} + +void SP_misc_viper(edict_t *ent) +{ + if (!ent->target) + { + gi.Com_PrintFmt("{} without a target\n", *ent); + G_FreeEdict(ent); + return; + } + + if (!ent->speed) + ent->speed = 300; + + ent->movetype = MOVETYPE_PUSH; + ent->solid = SOLID_NOT; + ent->s.modelindex = gi.modelindex("models/ships/viper/tris.md2"); + ent->mins = { -16, -16, 0 }; + ent->maxs = { 16, 16, 32 }; + + ent->think = func_train_find; + ent->nextthink = level.time + 10_hz; + ent->use = misc_viper_use; + ent->svflags |= SVF_NOCLIENT; + ent->moveinfo.accel = ent->moveinfo.decel = ent->moveinfo.speed = ent->speed; + + gi.linkentity(ent); +} + +/*QUAKED misc_bigviper (1 .5 0) (-176 -120 -24) (176 120 72) +This is a large stationary viper as seen in Paul's intro +*/ +void SP_misc_bigviper(edict_t *ent) +{ + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_BBOX; + ent->mins = { -176, -120, -24 }; + ent->maxs = { 176, 120, 72 }; + ent->s.modelindex = gi.modelindex("models/ships/bigviper/tris.md2"); + gi.linkentity(ent); +} + +/*QUAKED misc_viper_bomb (1 0 0) (-8 -8 -8) (8 8 8) +"dmg" how much boom should the bomb make? +*/ +TOUCH(misc_viper_bomb_touch) (edict_t *self, edict_t *other, const trace_t &tr, bool other_touching_self) -> void +{ + G_UseTargets(self, self->activator); + + self->s.origin[2] = self->absmin[2] + 1; + T_RadiusDamage(self, self, (float) self->dmg, nullptr, (float) (self->dmg + 40), DAMAGE_NONE, MOD_BOMB); + BecomeExplosion2(self); +} + +PRETHINK(misc_viper_bomb_prethink) (edict_t *self) -> void +{ + self->groundentity = nullptr; + + float diff = (self->timestamp - level.time).seconds(); + if (diff < -1.0f) + diff = -1.0f; + + vec3_t v = self->moveinfo.dir * (1.0f + diff); + v[2] = diff; + + diff = self->s.angles[2]; + self->s.angles = vectoangles(v); + self->s.angles[2] = diff + 10; +} + +USE(misc_viper_bomb_use) (edict_t *self, edict_t *other, edict_t *activator) -> void +{ + edict_t *viper; + + self->solid = SOLID_BBOX; + self->svflags &= ~SVF_NOCLIENT; + self->s.effects |= EF_ROCKET; + self->use = nullptr; + self->movetype = MOVETYPE_TOSS; + self->prethink = misc_viper_bomb_prethink; + self->touch = misc_viper_bomb_touch; + self->activator = activator; + + viper = G_FindByString<&edict_t::classname>(nullptr, "misc_viper"); + self->velocity = viper->moveinfo.dir * viper->moveinfo.speed; + + self->timestamp = level.time; + self->moveinfo.dir = viper->moveinfo.dir; +} + +void SP_misc_viper_bomb(edict_t *self) +{ + self->movetype = MOVETYPE_NONE; + self->solid = SOLID_NOT; + self->mins = { -8, -8, -8 }; + self->maxs = { 8, 8, 8 }; + + self->s.modelindex = gi.modelindex("models/objects/bomb/tris.md2"); + + if (!self->dmg) + self->dmg = 1000; + + self->use = misc_viper_bomb_use; + self->svflags |= SVF_NOCLIENT; + + gi.linkentity(self); +} + +/*QUAKED misc_strogg_ship (1 .5 0) (-16 -16 0) (16 16 32) +This is a Storgg ship for the flybys. +It is trigger_spawned, so you must have something use it for it to show up. +There must be a path for it to follow once it is activated. + +"speed" How fast it should fly +*/ +USE(misc_strogg_ship_use) (edict_t *self, edict_t *other, edict_t *activator) -> void +{ + self->svflags &= ~SVF_NOCLIENT; + self->use = train_use; + train_use(self, other, activator); +} + +void SP_misc_strogg_ship(edict_t *ent) +{ + if (!ent->target) + { + gi.Com_PrintFmt("{} without a target\n", *ent); + G_FreeEdict(ent); + return; + } + + if (!ent->speed) + ent->speed = 300; + + ent->movetype = MOVETYPE_PUSH; + ent->solid = SOLID_NOT; + ent->s.modelindex = gi.modelindex("models/ships/strogg1/tris.md2"); + ent->mins = { -16, -16, 0 }; + ent->maxs = { 16, 16, 32 }; + + ent->think = func_train_find; + ent->nextthink = level.time + 10_hz; + ent->use = misc_strogg_ship_use; + ent->svflags |= SVF_NOCLIENT; + ent->moveinfo.accel = ent->moveinfo.decel = ent->moveinfo.speed = ent->speed; + + gi.linkentity(ent); +} + +/*QUAKED misc_satellite_dish (1 .5 0) (-64 -64 0) (64 64 128) +model="models/objects/satellite/tris.md2" +*/ +THINK(misc_satellite_dish_think) (edict_t *self) -> void +{ + self->s.frame++; + if (self->s.frame < 38) + self->nextthink = level.time + 10_hz; +} + +USE(misc_satellite_dish_use) (edict_t *self, edict_t *other, edict_t *activator) -> void +{ + self->s.frame = 0; + self->think = misc_satellite_dish_think; + self->nextthink = level.time + 10_hz; +} + +void SP_misc_satellite_dish(edict_t *ent) +{ + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_BBOX; + ent->mins = { -64, -64, 0 }; + ent->maxs = { 64, 64, 128 }; + ent->s.modelindex = gi.modelindex("models/objects/satellite/tris.md2"); + ent->use = misc_satellite_dish_use; + gi.linkentity(ent); +} + +/*QUAKED light_mine1 (0 1 0) (-2 -2 -12) (2 2 12) + */ +void SP_light_mine1(edict_t *ent) +{ + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_NOT; + ent->svflags = SVF_DEADMONSTER; + ent->s.modelindex = gi.modelindex("models/objects/minelite/light1/tris.md2"); + gi.linkentity(ent); +} + +/*QUAKED light_mine2 (0 1 0) (-2 -2 -12) (2 2 12) + */ +void SP_light_mine2(edict_t *ent) +{ + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_NOT; + ent->svflags = SVF_DEADMONSTER; + ent->s.modelindex = gi.modelindex("models/objects/minelite/light2/tris.md2"); + gi.linkentity(ent); +} + +/*QUAKED misc_gib_arm (1 0 0) (-8 -8 -8) (8 8 8) +Intended for use with the target_spawner +*/ +void SP_misc_gib_arm(edict_t *ent) +{ + gi.setmodel(ent, "models/objects/gibs/arm/tris.md2"); + ent->solid = SOLID_NOT; + ent->s.effects |= EF_GIB; + ent->takedamage = true; + ent->die = gib_die; + ent->movetype = MOVETYPE_TOSS; + ent->deadflag = true; + ent->avelocity[0] = frandom(200); + ent->avelocity[1] = frandom(200); + ent->avelocity[2] = frandom(200); + ent->think = G_FreeEdict; + ent->nextthink = level.time + 10_sec; + gi.linkentity(ent); +} + +/*QUAKED misc_gib_leg (1 0 0) (-8 -8 -8) (8 8 8) +Intended for use with the target_spawner +*/ +void SP_misc_gib_leg(edict_t *ent) +{ + gi.setmodel(ent, "models/objects/gibs/leg/tris.md2"); + ent->solid = SOLID_NOT; + ent->s.effects |= EF_GIB; + ent->takedamage = true; + ent->die = gib_die; + ent->movetype = MOVETYPE_TOSS; + ent->deadflag = true; + ent->avelocity[0] = frandom(200); + ent->avelocity[1] = frandom(200); + ent->avelocity[2] = frandom(200); + ent->think = G_FreeEdict; + ent->nextthink = level.time + 10_sec; + gi.linkentity(ent); +} + +/*QUAKED misc_gib_head (1 0 0) (-8 -8 -8) (8 8 8) +Intended for use with the target_spawner +*/ +void SP_misc_gib_head(edict_t *ent) +{ + gi.setmodel(ent, "models/objects/gibs/head/tris.md2"); + ent->solid = SOLID_NOT; + ent->s.effects |= EF_GIB; + ent->takedamage = true; + ent->die = gib_die; + ent->movetype = MOVETYPE_TOSS; + ent->deadflag = true; + ent->avelocity[0] = frandom(200); + ent->avelocity[1] = frandom(200); + ent->avelocity[2] = frandom(200); + ent->think = G_FreeEdict; + ent->nextthink = level.time + 10_sec; + gi.linkentity(ent); +} + +//===================================================== + +/*QUAKED target_character (0 0 1) ? +used with target_string (must be on same "team") +"count" is position in the string (starts at 1) +*/ + +void SP_target_character(edict_t *self) +{ + self->movetype = MOVETYPE_PUSH; + gi.setmodel(self, self->model); + self->solid = SOLID_BSP; + self->s.frame = 12; + gi.linkentity(self); + return; +} + +/*QUAKED target_string (0 0 1) (-8 -8 -8) (8 8 8) + */ + +USE(target_string_use) (edict_t *self, edict_t *other, edict_t *activator) -> void +{ + edict_t *e; + int n; + size_t l; + char c; + + l = strlen(self->message); + for (e = self->teammaster; e; e = e->teamchain) + { + if (!e->count) + continue; + n = e->count - 1; + if (n > l) + { + e->s.frame = 12; + continue; + } + + c = self->message[n]; + if (c >= '0' && c <= '9') + e->s.frame = c - '0'; + else if (c == '-') + e->s.frame = 10; + else if (c == ':') + e->s.frame = 11; + else + e->s.frame = 12; + } +} + +void SP_target_string(edict_t *self) +{ + if (!self->message) + self->message = ""; + self->use = target_string_use; +} + +/*QUAKED func_clock (0 0 1) (-8 -8 -8) (8 8 8) TIMER_UP TIMER_DOWN START_OFF MULTI_USE +target a target_string with this + +The default is to be a time of day clock + +TIMER_UP and TIMER_DOWN run for "count" seconds and then fire "pathtarget" +If START_OFF, this entity must be used before it starts + +"style" 0 "xx" + 1 "xx:xx" + 2 "xx:xx:xx" +*/ + +constexpr spawnflags_t SPAWNFLAG_TIMER_UP = 1_spawnflag; +constexpr spawnflags_t SPAWNFLAG_TIMER_DOWN = 2_spawnflag; +constexpr spawnflags_t SPAWNFLAG_TIMER_START_OFF = 4_spawnflag; +constexpr spawnflags_t SPAWNFLAG_TIMER_MULTI_USE = 8_spawnflag; + +static void func_clock_reset(edict_t *self) +{ + self->activator = nullptr; + + if (self->spawnflags.has(SPAWNFLAG_TIMER_UP)) + { + self->health = 0; + self->wait = (float) self->count; + } + else if (self->spawnflags.has(SPAWNFLAG_TIMER_DOWN)) + { + self->health = self->count; + self->wait = 0; + } +} + +static void func_clock_format_countdown(edict_t *self) +{ + if (self->style == 0) + { + G_FmtTo(self->clock_message, "{:2}", self->health); + return; + } + + if (self->style == 1) + { + G_FmtTo(self->clock_message, "{:2}:{:02}", self->health / 60, self->health % 60); + return; + } + + if (self->style == 2) + { + G_FmtTo(self->clock_message, "{:2}:{:02}:{:02}", self->health / 3600, + (self->health - (self->health / 3600) * 3600) / 60, self->health % 60); + return; + } +} + +THINK(func_clock_think) (edict_t *self) -> void +{ + if (!self->enemy) + { + self->enemy = G_FindByString<&edict_t::targetname>(nullptr, self->target); + if (!self->enemy) + return; + } + + if (self->spawnflags.has(SPAWNFLAG_TIMER_UP)) + { + func_clock_format_countdown(self); + self->health++; + } + else if (self->spawnflags.has(SPAWNFLAG_TIMER_DOWN)) + { + func_clock_format_countdown(self); + self->health--; + } + else + { + struct tm *ltime; + time_t gmtime; + + time(&gmtime); + ltime = localtime(&gmtime); + G_FmtTo(self->clock_message, "{:2}:{:02}:{:02}", ltime->tm_hour, ltime->tm_min, + ltime->tm_sec); + } + + self->enemy->message = self->clock_message; + self->enemy->use(self->enemy, self, self); + + if ((self->spawnflags.has(SPAWNFLAG_TIMER_UP) && (self->health > self->wait)) || + (self->spawnflags.has(SPAWNFLAG_TIMER_DOWN) && (self->health < self->wait))) + { + if (self->pathtarget) + { + const char *savetarget; + + savetarget = self->target; + self->target = self->pathtarget; + G_UseTargets(self, self->activator); + self->target = savetarget; + } + + if (!self->spawnflags.has(SPAWNFLAG_TIMER_MULTI_USE)) + return; + + func_clock_reset(self); + + if (self->spawnflags.has(SPAWNFLAG_TIMER_START_OFF)) + return; + } + + self->nextthink = level.time + 1_sec; +} + +USE(func_clock_use) (edict_t *self, edict_t *other, edict_t *activator) -> void +{ + if (!self->spawnflags.has(SPAWNFLAG_TIMER_MULTI_USE)) + self->use = nullptr; + if (self->activator) + return; + self->activator = activator; + self->think(self); +} + +void SP_func_clock(edict_t *self) +{ + if (!self->target) + { + gi.Com_PrintFmt("{} with no target\n", *self); + G_FreeEdict(self); + return; + } + + if (self->spawnflags.has(SPAWNFLAG_TIMER_DOWN) && !self->count) + { + gi.Com_PrintFmt("{} with no count\n", *self); + G_FreeEdict(self); + return; + } + + if (self->spawnflags.has(SPAWNFLAG_TIMER_UP) && (!self->count)) + self->count = 60 * 60; + + func_clock_reset(self); + + self->think = func_clock_think; + + if (self->spawnflags.has(SPAWNFLAG_TIMER_START_OFF)) + self->use = func_clock_use; + else + self->nextthink = level.time + 1_sec; +} + +//================================================================================= + +constexpr spawnflags_t SPAWNFLAG_TELEPORTER_NO_SOUND = 1_spawnflag; +constexpr spawnflags_t SPAWNFLAG_TELEPORTER_NO_TELEPORT_EFFECT = 2_spawnflag; + +TOUCH(teleporter_touch) (edict_t *self, edict_t *other, const trace_t &tr, bool other_touching_self) -> void +{ + edict_t *dest; + + if (!other->client) + return; + dest = G_FindByString<&edict_t::targetname>(nullptr, self->target); + if (!dest) + { + gi.Com_Print("Couldn't find destination\n"); + return; + } + + // ZOID + CTFPlayerResetGrapple(other); + // ZOID + + // unlink to make sure it can't possibly interfere with KillBox + gi.unlinkentity(other); + + other->s.origin = dest->s.origin; + other->s.old_origin = dest->s.origin; + other->s.origin[2] += 10; + + // clear the velocity and hold them in place briefly + other->velocity = {}; + other->client->ps.pmove.pm_time = 160; // hold time + other->client->ps.pmove.pm_flags |= PMF_TIME_TELEPORT; + + // draw the teleport splash at source and on the player + if (!self->spawnflags.has(SPAWNFLAG_TELEPORTER_NO_TELEPORT_EFFECT)) + { + self->owner->s.event = EV_PLAYER_TELEPORT; + other->s.event = EV_PLAYER_TELEPORT; + } + else + { + self->owner->s.event = EV_OTHER_TELEPORT; + other->s.event = EV_OTHER_TELEPORT; + } + + // set angles + other->client->ps.pmove.delta_angles = dest->s.angles - other->client->resp.cmd_angles; + + other->s.angles = {}; + other->client->ps.viewangles = {}; + other->client->v_angle = {}; + AngleVectors(other->client->v_angle, other->client->v_forward, nullptr, nullptr); + + gi.linkentity(other); + + // kill anything at the destination + KillBox(other, !!other->client); + + // [Paril-KEX] move sphere, if we own it + if (other->client->owned_sphere) + { + edict_t *sphere = other->client->owned_sphere; + sphere->s.origin = other->s.origin; + sphere->s.origin[2] = other->absmax[2]; + sphere->s.angles[YAW] = other->s.angles[YAW]; + gi.linkentity(sphere); + } +} + +/*QUAKED misc_teleporter (1 0 0) (-32 -32 -24) (32 32 -16) NO_SOUND NO_TELEPORT_EFFECT N64_EFFECT +Stepping onto this disc will teleport players to the targeted misc_teleporter_dest object. +*/ +constexpr spawnflags_t SPAWNFLAG_TEMEPORTER_N64_EFFECT = 4_spawnflag; + +void SP_misc_teleporter(edict_t *ent) +{ + edict_t *trig; + + gi.setmodel(ent, "models/objects/dmspot/tris.md2"); + ent->s.skinnum = 1; + if (level.is_n64 || ent->spawnflags.has(SPAWNFLAG_TEMEPORTER_N64_EFFECT)) + ent->s.effects = EF_TELEPORTER2; + else + ent->s.effects = EF_TELEPORTER; + if (!(ent->spawnflags & SPAWNFLAG_TELEPORTER_NO_SOUND)) + ent->s.sound = gi.soundindex("world/amb10.wav"); + ent->solid = SOLID_BBOX; + + ent->mins = { -32, -32, -24 }; + ent->maxs = { 32, 32, -16 }; + gi.linkentity(ent); + + // N64 has some of these for visual effects + if (!ent->target) + return; + + trig = G_Spawn(); + trig->touch = teleporter_touch; + trig->solid = SOLID_TRIGGER; + trig->target = ent->target; + trig->owner = ent; + trig->s.origin = ent->s.origin; + trig->mins = { -8, -8, 8 }; + trig->maxs = { 8, 8, 24 }; + gi.linkentity(trig); +} + +/*QUAKED misc_teleporter_dest (1 0 0) (-32 -32 -24) (32 32 -16) +Point teleporters at these. +*/ +void SP_misc_teleporter_dest(edict_t *ent) +{ + // Paril-KEX N64 doesn't display these + if (level.is_n64) + return; + + gi.setmodel(ent, "models/objects/dmspot/tris.md2"); + ent->s.skinnum = 0; + ent->solid = SOLID_BBOX; + // ent->s.effects |= EF_FLIES; + ent->mins = { -32, -32, -24 }; + ent->maxs = { 32, 32, -16 }; + gi.linkentity(ent); +} + +/*QUAKED misc_flare (1.0 1.0 0.0) (-32 -32 -32) (32 32 32) RED GREEN BLUE LOCK_ANGLE +Creates a flare seen in the N64 version. +*/ + +static constexpr spawnflags_t SPAWNFLAG_FLARE_RED = 1_spawnflag; +static constexpr spawnflags_t SPAWNFLAG_FLARE_GREEN = 2_spawnflag; +static constexpr spawnflags_t SPAWNFLAG_FLARE_BLUE = 4_spawnflag; +static constexpr spawnflags_t SPAWNFLAG_FLARE_LOCK_ANGLE = 8_spawnflag; + +USE(misc_flare_use) (edict_t *ent, edict_t *other, edict_t *activator) -> void +{ + ent->svflags ^= SVF_NOCLIENT; + gi.linkentity(ent); +} + +void SP_misc_flare(edict_t* ent) +{ + ent->s.modelindex = 1; + ent->s.renderfx = RF_FLARE; + ent->solid = SOLID_NOT; + ent->s.scale = st.radius; + + if (ent->spawnflags.has(SPAWNFLAG_FLARE_RED)) + ent->s.renderfx |= RF_SHELL_RED; + + if (ent->spawnflags.has(SPAWNFLAG_FLARE_GREEN)) + ent->s.renderfx |= RF_SHELL_GREEN; + + if (ent->spawnflags.has(SPAWNFLAG_FLARE_BLUE)) + ent->s.renderfx |= RF_SHELL_BLUE; + + if (ent->spawnflags.has(SPAWNFLAG_FLARE_LOCK_ANGLE)) + ent->s.renderfx |= RF_FLARE_LOCK_ANGLE; + + if (st.image && *st.image) + { + ent->s.renderfx |= RF_CUSTOMSKIN; + ent->s.frame = gi.imageindex(st.image); + } + + ent->mins = { -32, -32, -32 }; + ent->maxs = { 32, 32, 32 }; + + ent->s.modelindex2 = st.fade_start_dist; + ent->s.modelindex3 = st.fade_end_dist; + + if (ent->targetname) + ent->use = misc_flare_use; + + gi.linkentity(ent); +} + +THINK(misc_hologram_think) (edict_t *ent) -> void +{ + ent->s.angles[1] += 100 * gi.frame_time_s; + ent->nextthink = level.time + FRAME_TIME_MS; + ent->s.alpha = frandom(0.2f, 0.6f); +} + +/*QUAKED misc_hologram (1.0 1.0 0.0) (-16 -16 0) (16 16 32) +Ship hologram seen in the N64 version. +*/ +void SP_misc_hologram(edict_t *ent) +{ + ent->solid = SOLID_NOT; + ent->s.modelindex = gi.modelindex("models/ships/strogg1/tris.md2"); + ent->mins = { -16, -16, 0 }; + ent->maxs = { 16, 16, 32 }; + ent->s.effects = EF_HOLOGRAM; + ent->think = misc_hologram_think; + ent->nextthink = level.time + FRAME_TIME_MS; + ent->s.alpha = frandom(0.2f, 0.6f); + ent->s.scale = 0.75f; + gi.linkentity(ent); +} + + +/*QUAKED misc_fireball (0 .5 .8) (-8 -8 -8) (8 8 8) NO_EXPLODE +Lava Balls. Shamelessly copied from Quake 1, like N64 guys +probably did too. +*/ + +constexpr spawnflags_t SPAWNFLAG_LAVABALL_NO_EXPLODE = 1_spawnflag; + +TOUCH(fire_touch) (edict_t *self, edict_t *other, const trace_t &tr, bool other_touching_self) -> void +{ + if (self->spawnflags.has(SPAWNFLAG_LAVABALL_NO_EXPLODE)) + { + G_FreeEdict(self); + return; + } + + if (other->takedamage) + T_Damage (other, self, self, vec3_origin, self->s.origin, vec3_origin, 20, 0, DAMAGE_NONE, MOD_EXPLOSIVE); + + if (gi.pointcontents(self->s.origin) & CONTENTS_LAVA) + G_FreeEdict(self); + else + BecomeExplosion1(self); +} + +THINK(fire_fly) (edict_t *self) -> void +{ + edict_t *fireball = G_Spawn(); + fireball->s.effects = EF_FIREBALL; + fireball->s.renderfx = RF_MINLIGHT; + fireball->solid = SOLID_BBOX; + fireball->movetype = MOVETYPE_TOSS; + fireball->clipmask = MASK_SHOT; + fireball->velocity[0] = crandom() * 50; + fireball->velocity[1] = crandom() * 50; + fireball->avelocity = { crandom() * 360, crandom() * 360, crandom() * 360 }; + fireball->velocity[2] = (self->speed * 1.75f) + (frandom() * 200); + fireball->classname = "fireball"; + gi.setmodel(fireball, "models/objects/gibs/sm_meat/tris.md2"); + fireball->s.origin = self->s.origin; + fireball->nextthink = level.time + 5_sec; + fireball->think = G_FreeEdict; + fireball->touch = fire_touch; + fireball->spawnflags = self->spawnflags; + gi.linkentity(fireball); + self->nextthink = level.time + random_time(5_sec); +} + +void SP_misc_lavaball(edict_t *self) +{ + self->classname = "fireball"; + self->nextthink = level.time + random_time(5_sec); + self->think = fire_fly; + if (!self->speed) + self->speed = 185; +} + + +void SP_info_landmark(edict_t* self) +{ + self->absmin = self->s.origin; + self->absmax = self->s.origin; +} + +constexpr spawnflags_t SPAWNFLAG_WORLD_TEXT_START_OFF = 1_spawnflag; +constexpr spawnflags_t SPAWNFLAG_WORLD_TEXT_TRIGGER_ONCE = 2_spawnflag; +constexpr spawnflags_t SPAWNFLAG_WORLD_TEXT_REMOVE_ON_TRIGGER = 4_spawnflag; + +USE( info_world_text_use ) ( edict_t * self, edict_t * other, edict_t * activator ) -> void { + if ( self->activator == nullptr ) { + self->activator = activator; + self->think( self ); + } else { + self->nextthink = 0_ms; + self->activator = nullptr; + } + + if (self->spawnflags.has(SPAWNFLAG_WORLD_TEXT_TRIGGER_ONCE)) { + self->use = nullptr; + } + + if ( self->target != nullptr ) { + edict_t * target = G_PickTarget( self->target ); + if ( target != nullptr && target->inuse ) { + if ( target->use ) { + target->use( target, self, self ); + } + } + } + + if (self->spawnflags.has(SPAWNFLAG_WORLD_TEXT_REMOVE_ON_TRIGGER)) { + G_FreeEdict( self ); + } +} + +THINK( info_world_text_think ) ( edict_t * self ) -> void { + rgba_t color = rgba_white; + + switch ( self->sounds ) { + case 0: + color = rgba_white; + break; + + case 1: + color = rgba_red; + break; + + case 2: + color = rgba_blue; + break; + + case 3: + color = rgba_green; + break; + + case 4: + color = rgba_yellow; + break; + + case 5: + color = rgba_black; + break; + + case 6: + color = rgba_cyan; + break; + + case 7: + color = rgba_orange; + break; + + default: + color = rgba_white; + gi.Com_PrintFmt( "{}: invalid color\n", *self); + break; + } + + if ( self->s.angles[ YAW ] == -3.0f ) { + gi.Draw_OrientedWorldText( self->s.origin, self->message, color, self->size[ 2 ], FRAME_TIME_MS.seconds(), true ); + } else { + vec3_t textAngle = { 0.0f, 0.0f, 0.0f }; + textAngle[ YAW ] = anglemod( self->s.angles[ YAW ] ) + 180; + if ( textAngle[ YAW ] > 360.0f ) { + textAngle[ YAW ] -= 360.0f; + } + gi.Draw_StaticWorldText( self->s.origin, textAngle, self->message, color, self->size[2], FRAME_TIME_MS.seconds(), true ); + } + self->nextthink = level.time + FRAME_TIME_MS; +} + +/*QUAKED info_world_text (1.0 1.0 0.0) (-16 -16 0) (16 16 32) +designer placed in world text for debugging. +*/ +void SP_info_world_text( edict_t * self ) { + if ( self->message == nullptr ) { + gi.Com_PrintFmt( "{}: no message\n", *self); + G_FreeEdict( self ); + return; + } // not much point without something to print... + + self->think = info_world_text_think; + self->use = info_world_text_use; + self->size[ 2 ] = st.radius ? st.radius : 0.2f; + + if ( !self->spawnflags.has( SPAWNFLAG_WORLD_TEXT_START_OFF ) ) { + self->nextthink = level.time + FRAME_TIME_MS; + self->activator = self; + } +} + +#include "m_player.h" + +USE( misc_player_mannequin_use ) ( edict_t * self, edict_t * other, edict_t * activator ) -> void { + self->monsterinfo.aiflags |= AI_TARGET_ANGER; + self->enemy = activator; + + switch ( self->count ) { + case GESTURE_FLIP_OFF: + self->s.frame = FRAME_flip01; + self->monsterinfo.nextframe = FRAME_flip12; + break; + + case GESTURE_SALUTE: + self->s.frame = FRAME_salute01; + self->monsterinfo.nextframe = FRAME_salute11; + break; + + case GESTURE_TAUNT: + self->s.frame = FRAME_taunt01; + self->monsterinfo.nextframe = FRAME_taunt17; + break; + + case GESTURE_WAVE: + self->s.frame = FRAME_wave01; + self->monsterinfo.nextframe = FRAME_wave11; + break; + + case GESTURE_POINT: + self->s.frame = FRAME_point01; + self->monsterinfo.nextframe = FRAME_point12; + break; + } +} + +THINK( misc_player_mannequin_think ) ( edict_t * self ) -> void { + if ( self->teleport_time <= level.time ) { + self->s.frame++; + + if ( ( self->monsterinfo.aiflags & AI_TARGET_ANGER ) == 0 ) { + if ( self->s.frame > FRAME_stand40 ) { + self->s.frame = FRAME_stand01; + } + } else { + if ( self->s.frame > self->monsterinfo.nextframe ) { + self->s.frame = FRAME_stand01; + self->monsterinfo.aiflags &= ~AI_TARGET_ANGER; + self->enemy = nullptr; + } + } + + self->teleport_time = level.time + 10_hz; + } + + if ( self->enemy != nullptr ) { + const vec3_t vec = ( self->enemy->s.origin - self->s.origin ); + self->ideal_yaw = vectoyaw( vec ); + M_ChangeYaw( self ); + } + + self->nextthink = level.time + FRAME_TIME_MS; +} + +void SetupMannequinModel( edict_t * self, const int32_t modelType, const char * weapon, const char * skin ) { + const char * modelName = nullptr; + const char * defaultSkin = nullptr; + + switch ( modelType ) { + case 1: { + self->s.skinnum = ( MAX_CLIENTS - 1 ); + modelName = "female"; + defaultSkin = "venus"; + break; + } + + case 2: { + self->s.skinnum = ( MAX_CLIENTS - 2 ); + modelName = "male"; + defaultSkin = "rampage"; + break; + } + + case 3: { + self->s.skinnum = ( MAX_CLIENTS - 3 ); + modelName = "cyborg"; + defaultSkin = "oni911"; + break; + } + + default: { + self->s.skinnum = ( MAX_CLIENTS - 1 ); + modelName = "female"; + defaultSkin = "venus"; + break; + } + } + + if ( modelName != nullptr ) { + self->model = G_Fmt( "players/{}/tris.md2", modelName ).data(); + + const char * weaponName = nullptr; + if ( weapon != nullptr ) { + weaponName = G_Fmt( "players/{}/{}.md2", modelName, weapon ).data(); + } else { + weaponName = G_Fmt( "players/{}/{}.md2", modelName, "w_hyperblaster" ).data(); + } + self->s.modelindex2 = gi.modelindex( weaponName ); + + const char * skinName = nullptr; + if ( skin != nullptr ) { + skinName = G_Fmt( "mannequin\\{}/{}", modelName, skin ).data(); + } else { + skinName = G_Fmt( "mannequin\\{}/{}", modelName, defaultSkin ).data(); + } + gi.configstring( CS_PLAYERSKINS + self->s.skinnum, skinName ); + } +} + +/*QUAKED misc_player_mannequin (1.0 1.0 0.0) (-32 -32 -32) (32 32 32) + Creates a player mannequin that stands around. + + NOTE: this is currently very limited, and only allows one unique model + from each of the three player model types. + + "distance" - Sets the type of gesture mannequin when use when triggered + "height" - Sets the type of model to use ( valid numbers: 1 - 3 ) + "goals" - Name of the weapon to use. + "image" - Name of the player skin to use. + "radius" - How much to scale the model in-game +*/ +void SP_misc_player_mannequin( edict_t * self ) { + self->movetype = MOVETYPE_NONE; + self->solid = SOLID_BBOX; + if (!st.was_key_specified("effects")) + self->s.effects = EF_NONE; + if (!st.was_key_specified("renderfx")) + self->s.renderfx = RF_MINLIGHT; + self->mins = { -16, -16, -24 }; + self->maxs = { 16, 16, 32 }; + self->yaw_speed = 30; + self->ideal_yaw = 0; + self->teleport_time = level.time + 10_hz; + self->s.modelindex = MODELINDEX_PLAYER; + self->count = st.distance; + + SetupMannequinModel( self, st.height, st.goals, st.image ); + + self->s.scale = 1.0f; + if ( ai_model_scale->value > 0.0f ) { + self->s.scale = ai_model_scale->value; + } else if ( st.radius > 0.0f ) { + self->s.scale = st.radius; + } + + self->mins *= self->s.scale; + self->maxs *= self->s.scale; + + self->think = misc_player_mannequin_think; + self->nextthink = level.time + FRAME_TIME_MS; + + if ( self->targetname ) { + self->use = misc_player_mannequin_use; + } + + gi.linkentity( self ); +} + +/*QUAKED misc_model (1 0 0) (-8 -8 -8) (8 8 8) +*/ +void SP_misc_model(edict_t *ent) +{ + gi.setmodel(ent, ent->model); + gi.linkentity(ent); +} \ No newline at end of file diff --git a/rerelease/g_monster.cpp b/rerelease/g_monster.cpp new file mode 100644 index 0000000..cf43543 --- /dev/null +++ b/rerelease/g_monster.cpp @@ -0,0 +1,1630 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +#include "g_local.h" +#include "bots/bot_includes.h" + +// +// monster weapons +// +void monster_muzzleflash(edict_t *self, const vec3_t &start, monster_muzzleflash_id_t id) +{ + if (id <= 255) + gi.WriteByte(svc_muzzleflash2); + else + gi.WriteByte(svc_muzzleflash3); + + gi.WriteEntity(self); + + if (id <= 255) + gi.WriteByte(id); + else + gi.WriteShort(id); + + gi.multicast(start, MULTICAST_PHS, false); +} + +void monster_fire_bullet(edict_t *self, const vec3_t &start, const vec3_t &dir, int damage, int kick, int hspread, + int vspread, monster_muzzleflash_id_t flashtype) +{ + fire_bullet(self, start, dir, damage, kick, hspread, vspread, MOD_UNKNOWN); + monster_muzzleflash(self, start, flashtype); +} + +void monster_fire_shotgun(edict_t *self, const vec3_t &start, const vec3_t &aimdir, int damage, int kick, int hspread, + int vspread, int count, monster_muzzleflash_id_t flashtype) +{ + fire_shotgun(self, start, aimdir, damage, kick, hspread, vspread, count, MOD_UNKNOWN); + monster_muzzleflash(self, start, flashtype); +} + +void monster_fire_blaster(edict_t *self, const vec3_t &start, const vec3_t &dir, int damage, int speed, + monster_muzzleflash_id_t flashtype, effects_t effect) +{ + fire_blaster(self, start, dir, damage, speed, effect, MOD_BLASTER); + monster_muzzleflash(self, start, flashtype); +} + +void monster_fire_flechette(edict_t *self, const vec3_t &start, const vec3_t &dir, int damage, int speed, + monster_muzzleflash_id_t flashtype) +{ + fire_flechette(self, start, dir, damage, speed, damage / 2); + monster_muzzleflash(self, start, flashtype); +} + +void monster_fire_grenade(edict_t *self, const vec3_t &start, const vec3_t &aimdir, int damage, int speed, + monster_muzzleflash_id_t flashtype, float right_adjust, float up_adjust) +{ + fire_grenade(self, start, aimdir, damage, speed, 2.5_sec, damage + 40.f, right_adjust, up_adjust, true); + monster_muzzleflash(self, start, flashtype); +} + +void monster_fire_rocket(edict_t *self, const vec3_t &start, const vec3_t &dir, int damage, int speed, + monster_muzzleflash_id_t flashtype) +{ + fire_rocket(self, start, dir, damage, speed, (float) damage + 20, damage); + monster_muzzleflash(self, start, flashtype); +} + +void monster_fire_railgun(edict_t *self, const vec3_t &start, const vec3_t &aimdir, int damage, int kick, + monster_muzzleflash_id_t flashtype) +{ + if (gi.pointcontents(start) & MASK_SOLID) + return; + + fire_rail(self, start, aimdir, damage, kick); + + monster_muzzleflash(self, start, flashtype); +} + +void monster_fire_bfg(edict_t *self, const vec3_t &start, const vec3_t &aimdir, int damage, int speed, int kick, + float damage_radius, monster_muzzleflash_id_t flashtype) +{ + fire_bfg(self, start, aimdir, damage, speed, damage_radius); + monster_muzzleflash(self, start, flashtype); +} + +// [Paril-KEX] +vec3_t M_ProjectFlashSource(edict_t *self, const vec3_t &offset, const vec3_t &forward, const vec3_t &right) +{ + return G_ProjectSource(self->s.origin, self->s.scale ? (offset * self->s.scale) : offset, forward, right); +} + +// [Paril-KEX] check if shots fired from the given offset +// might be blocked by something +bool M_CheckClearShot(edict_t *self, const vec3_t &offset, vec3_t &start) +{ + // no enemy, just do whatever + if (!self->enemy) + return false; + + vec3_t f, r; + + vec3_t real_angles = { self->s.angles[0], self->ideal_yaw, 0.f }; + + AngleVectors(real_angles, f, r, nullptr); + start = M_ProjectFlashSource(self, offset, f, r); + + vec3_t target; + + bool is_blind = self->monsterinfo.attack_state == AS_BLIND || (self->monsterinfo.aiflags & (AI_MANUAL_STEERING | AI_LOST_SIGHT)); + + if (is_blind) + target = self->monsterinfo.blind_fire_target; + else + target = self->enemy->s.origin + vec3_t{ 0, 0, (float) self->enemy->viewheight }; + + trace_t tr = gi.traceline(start, target, self, MASK_PROJECTILE & ~CONTENTS_DEADMONSTER); + + if (tr.ent == self->enemy || tr.ent->client || (tr.fraction > 0.8f && !tr.startsolid)) + return true; + + if (!is_blind) + { + target = self->enemy->s.origin; + + trace_t tr = gi.traceline(start, target, self, MASK_PROJECTILE & ~CONTENTS_DEADMONSTER); + + if (tr.ent == self->enemy || tr.ent->client || (tr.fraction > 0.8f && !tr.startsolid)) + return true; + } + + return false; +} + +bool M_CheckClearShot(edict_t *self, const vec3_t &offset) +{ + vec3_t start; + return M_CheckClearShot(self, offset, start); +} + +void M_CheckGround(edict_t *ent, contents_t mask) +{ + vec3_t point; + trace_t trace; + + if (ent->flags & (FL_SWIM | FL_FLY)) + return; + + if ((ent->velocity[2] * ent->gravityVector[2]) < -100) // PGM + { + ent->groundentity = nullptr; + return; + } + + // if the hull point one-quarter unit down is solid the entity is on ground + point[0] = ent->s.origin[0]; + point[1] = ent->s.origin[1]; + point[2] = ent->s.origin[2] + (0.25f * ent->gravityVector[2]); // PGM + + trace = gi.trace(ent->s.origin, ent->mins, ent->maxs, point, ent, mask); + + // check steepness + // PGM + if (ent->gravityVector[2] < 0) // normal gravity + { + if (trace.plane.normal[2] < 0.7f && !trace.startsolid) + { + ent->groundentity = nullptr; + return; + } + } + else // inverted gravity + { + if (trace.plane.normal[2] > -0.7f && !trace.startsolid) + { + ent->groundentity = nullptr; + return; + } + } + // PGM + + if (!trace.startsolid && !trace.allsolid) + { + ent->s.origin = trace.endpos; + ent->groundentity = trace.ent; + ent->groundentity_linkcount = trace.ent->linkcount; + ent->velocity[2] = 0; + } +} + +void M_CatagorizePosition(edict_t *self, const vec3_t &in_point, water_level_t &waterlevel, contents_t &watertype) +{ + vec3_t point; + contents_t cont; + + // + // get waterlevel + // + point[0] = in_point[0]; + point[1] = in_point[1]; + if (self->gravityVector[2] > 0) + point[2] = in_point[2] + self->maxs[2] - 1; + else + point[2] = in_point[2] + self->mins[2] + 1; + cont = gi.pointcontents(point); + + if (!(cont & MASK_WATER)) + { + waterlevel = WATER_NONE; + watertype = CONTENTS_NONE; + return; + } + + watertype = cont; + waterlevel = WATER_FEET; + point[2] += 26; + cont = gi.pointcontents(point); + if (!(cont & MASK_WATER)) + return; + + waterlevel = WATER_WAIST; + point[2] += 22; + cont = gi.pointcontents(point); + if (cont & MASK_WATER) + waterlevel = WATER_UNDER; +} + +bool M_ShouldReactToPain(edict_t *self, const mod_t &mod) +{ + if (self->monsterinfo.aiflags & (AI_DUCKED | AI_COMBAT_POINT)) + return false; + + return mod.id == MOD_CHAINFIST || skill->integer < 3; +} + +void M_WorldEffects(edict_t *ent) +{ + int dmg; + + if (ent->health > 0) + { + if (!(ent->flags & FL_SWIM)) + { + if (ent->waterlevel < WATER_UNDER) + { + ent->air_finished = level.time + 12_sec; + } + else if (ent->air_finished < level.time) + { // drown! + if (ent->pain_debounce_time < level.time) + { + dmg = 2 + (int) (2 * floorf((level.time - ent->air_finished).seconds())); + if (dmg > 15) + dmg = 15; + T_Damage(ent, world, world, vec3_origin, ent->s.origin, vec3_origin, dmg, 0, DAMAGE_NO_ARMOR, + MOD_WATER); + ent->pain_debounce_time = level.time + 1_sec; + } + } + } + else + { + if (ent->waterlevel > WATER_NONE) + { + ent->air_finished = level.time + 9_sec; + } + else if (ent->air_finished < level.time) + { // suffocate! + if (ent->pain_debounce_time < level.time) + { + dmg = 2 + (int) (2 * floorf((level.time - ent->air_finished).seconds())); + if (dmg > 15) + dmg = 15; + T_Damage(ent, world, world, vec3_origin, ent->s.origin, vec3_origin, dmg, 0, DAMAGE_NO_ARMOR, + MOD_WATER); + ent->pain_debounce_time = level.time + 1_sec; + } + } + } + } + + if (ent->waterlevel == WATER_NONE) + { + if (ent->flags & FL_INWATER) + { + gi.sound(ent, CHAN_BODY, gi.soundindex("player/watr_out.wav"), 1, ATTN_NORM, 0); + ent->flags &= ~FL_INWATER; + } + } + else + { + if ((ent->watertype & CONTENTS_LAVA) && !(ent->flags & FL_IMMUNE_LAVA)) + { + if (ent->damage_debounce_time < level.time) + { + ent->damage_debounce_time = level.time + 100_ms; + T_Damage(ent, world, world, vec3_origin, ent->s.origin, vec3_origin, 10 * ent->waterlevel, 0, DAMAGE_NONE, + MOD_LAVA); + } + } + if ((ent->watertype & CONTENTS_SLIME) && !(ent->flags & FL_IMMUNE_SLIME)) + { + if (ent->damage_debounce_time < level.time) + { + ent->damage_debounce_time = level.time + 100_ms; + T_Damage(ent, world, world, vec3_origin, ent->s.origin, vec3_origin, 4 * ent->waterlevel, 0, DAMAGE_NONE, + MOD_SLIME); + } + } + + if (!(ent->flags & FL_INWATER)) + { + if (ent->watertype & CONTENTS_LAVA) + { + if ((ent->svflags & SVF_MONSTER) && ent->health > 0) + { + if (frandom() <= 0.5f) + gi.sound(ent, CHAN_BODY, gi.soundindex("player/lava1.wav"), 1, ATTN_NORM, 0); + else + gi.sound(ent, CHAN_BODY, gi.soundindex("player/lava2.wav"), 1, ATTN_NORM, 0); + } + else + gi.sound(ent, CHAN_BODY, gi.soundindex("player/watr_in.wav"), 1, ATTN_NORM, 0); + } + else if (ent->watertype & CONTENTS_SLIME) + gi.sound(ent, CHAN_BODY, gi.soundindex("player/watr_in.wav"), 1, ATTN_NORM, 0); + else if (ent->watertype & CONTENTS_WATER) + gi.sound(ent, CHAN_BODY, gi.soundindex("player/watr_in.wav"), 1, ATTN_NORM, 0); + + ent->flags |= FL_INWATER; + ent->damage_debounce_time = 0_ms; + } + } +} + +bool M_droptofloor_generic(vec3_t &origin, const vec3_t &mins, const vec3_t &maxs, bool ceiling, edict_t *ignore, contents_t mask, bool allow_partial) +{ + vec3_t end; + trace_t trace; + + // PGM + if (gi.trace(origin, mins, maxs, origin, ignore, mask).startsolid) + { + if (!ceiling) + origin[2] += 1; + else + origin[2] -= 1; + } + + if (!ceiling) + { + end = origin; + end[2] -= 256; + } + else + { + end = origin; + end[2] += 256; + } + // PGM + + trace = gi.trace(origin, mins, maxs, end, ignore, mask); + + if (trace.fraction == 1 || trace.allsolid || (!allow_partial && trace.startsolid)) + return false; + + origin = trace.endpos; + + return true; +} + +bool M_droptofloor(edict_t *ent) +{ + contents_t mask = G_GetClipMask(ent); + + if (!ent->spawnflags.has(SPAWNFLAG_MONSTER_NO_DROP)) + { + if (!M_droptofloor_generic(ent->s.origin, ent->mins, ent->maxs, ent->gravityVector[2] > 0, ent, mask, true)) + return false; + } + else + { + if (gi.trace(ent->s.origin, ent->mins, ent->maxs, ent->s.origin, ent, mask).startsolid) + return false; + } + + gi.linkentity(ent); + M_CheckGround(ent, mask); + M_CatagorizePosition(ent, ent->s.origin, ent->waterlevel, ent->watertype); + + return true; +} + +void M_SetEffects(edict_t *ent) +{ + ent->s.effects &= ~(EF_COLOR_SHELL | EF_POWERSCREEN | EF_DOUBLE | EF_QUAD | EF_PENT | EF_FLIES); + ent->s.renderfx &= ~(RF_SHELL_RED | RF_SHELL_GREEN | RF_SHELL_BLUE | RF_SHELL_DOUBLE); + + ent->s.sound = 0; + ent->s.loop_attenuation = 0; + + // we're gibbed + if (ent->s.renderfx & RF_LOW_PRIORITY) + return; + + if (ent->monsterinfo.weapon_sound && ent->health > 0) + { + ent->s.sound = ent->monsterinfo.weapon_sound; + ent->s.loop_attenuation = ATTN_NORM; + } + else if (ent->monsterinfo.engine_sound) + ent->s.sound = ent->monsterinfo.engine_sound; + + if (ent->monsterinfo.aiflags & AI_RESURRECTING) + { + ent->s.effects |= EF_COLOR_SHELL; + ent->s.renderfx |= RF_SHELL_RED; + } + + ent->s.renderfx |= RF_DOT_SHADOW; + + // no power armor/powerup effects if we died + if (ent->health <= 0) + return; + + if (ent->powerarmor_time > level.time) + { + if (ent->monsterinfo.power_armor_type == IT_ITEM_POWER_SCREEN) + { + ent->s.effects |= EF_POWERSCREEN; + } + else if (ent->monsterinfo.power_armor_type == IT_ITEM_POWER_SHIELD) + { + ent->s.effects |= EF_COLOR_SHELL; + ent->s.renderfx |= RF_SHELL_GREEN; + } + } + + // PMM - new monster powerups + if (ent->monsterinfo.quad_time > level.time) + { + if (G_PowerUpExpiring(ent->monsterinfo.quad_time)) + ent->s.effects |= EF_QUAD; + } + + if (ent->monsterinfo.double_time > level.time) + { + if (G_PowerUpExpiring(ent->monsterinfo.double_time)) + ent->s.effects |= EF_DOUBLE; + } + + if (ent->monsterinfo.invincible_time > level.time) + { + if (G_PowerUpExpiring(ent->monsterinfo.invincible_time)) + ent->s.effects |= EF_PENT; + } +} + +bool M_AllowSpawn( edict_t * self ) { + if ( deathmatch->integer && !ai_allow_dm_spawn->integer ) { + return false; + } + return true; +} + +void M_SetAnimation(edict_t *self, const save_mmove_t &move, bool instant) +{ + // [Paril-KEX] free the beams if we switch animations. + if (self->beam) + { + G_FreeEdict(self->beam); + self->beam = nullptr; + } + + if (self->beam2) + { + G_FreeEdict(self->beam2); + self->beam2 = nullptr; + } + + // instant switches will cause active_move to change on the next frame + if (instant) + { + self->monsterinfo.active_move = move; + self->monsterinfo.next_move = nullptr; + return; + } + + // these wait until the frame is ready to be finished + self->monsterinfo.next_move = move; +} + +void M_MoveFrame(edict_t *self) +{ + const mmove_t *move = self->monsterinfo.active_move.pointer(); + + // [Paril-KEX] high tick rate adjustments; + // monsters still only step frames and run thinkfunc's at + // 10hz, but will run aifuncs at full speed with + // distance spread over 10hz + + self->nextthink = level.time + FRAME_TIME_S; + + // time to run next 10hz move yet? + bool run_frame = self->monsterinfo.next_move_time <= level.time; + + // we asked nicely to switch frames when the timer ran up + if (run_frame && self->monsterinfo.next_move.pointer() && self->monsterinfo.active_move != self->monsterinfo.next_move) + { + M_SetAnimation(self, self->monsterinfo.next_move, true); + move = self->monsterinfo.active_move.pointer(); + } + + if (!move) + return; + + // no, but maybe we were explicitly forced into another move (pain, + // death, etc) + if (!run_frame) + run_frame = (self->s.frame < move->firstframe || self->s.frame > move->lastframe); + + if (run_frame) + { + // [Paril-KEX] allow next_move and nextframe to work properly after an endfunc + bool explicit_frame = false; + + if ((self->monsterinfo.nextframe) && (self->monsterinfo.nextframe >= move->firstframe) && + (self->monsterinfo.nextframe <= move->lastframe)) + { + self->s.frame = self->monsterinfo.nextframe; + self->monsterinfo.nextframe = 0; + } + else + { + if (self->s.frame == move->lastframe) + { + if (move->endfunc) + { + move->endfunc(self); + + if (self->monsterinfo.next_move) + { + M_SetAnimation(self, self->monsterinfo.next_move, true); + + if (self->monsterinfo.nextframe) + { + self->s.frame = self->monsterinfo.nextframe; + self->monsterinfo.nextframe = 0; + explicit_frame = true; + } + } + + // regrab move, endfunc is very likely to change it + move = self->monsterinfo.active_move.pointer(); + + // check for death + if (self->svflags & SVF_DEADMONSTER) + return; + } + } + + if (self->s.frame < move->firstframe || self->s.frame > move->lastframe) + { + self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; + self->s.frame = move->firstframe; + } + else if (!explicit_frame) + { + if (!(self->monsterinfo.aiflags & AI_HOLD_FRAME)) + { + self->s.frame++; + if (self->s.frame > move->lastframe) + self->s.frame = move->firstframe; + } + } + } + + if (self->monsterinfo.aiflags & AI_HIGH_TICK_RATE) + self->monsterinfo.next_move_time = level.time; + else + self->monsterinfo.next_move_time = level.time + 10_hz; + + if ((self->monsterinfo.nextframe) && !((self->monsterinfo.nextframe >= move->firstframe) && + (self->monsterinfo.nextframe <= move->lastframe))) + self->monsterinfo.nextframe = 0; + } + + // NB: frame thinkfunc can be called on the same frame + // as the animation changing + + int32_t index = self->s.frame - move->firstframe; + if (move->frame[index].aifunc) + { + if (!(self->monsterinfo.aiflags & AI_HOLD_FRAME)) + { + float dist = move->frame[index].dist * self->monsterinfo.scale; + dist /= gi.tick_rate / 10; + move->frame[index].aifunc(self, dist); + } + else + move->frame[index].aifunc(self, 0); + } + + if (run_frame && move->frame[index].thinkfunc) + move->frame[index].thinkfunc(self); + + if (move->frame[index].lerp_frame != -1) + { + self->s.renderfx |= RF_OLD_FRAME_LERP; + self->s.old_frame = move->frame[index].lerp_frame; + } +} + +void G_MonsterKilled(edict_t *self) +{ + level.killed_monsters++; + + if (coop->integer && self->enemy && self->enemy->client) + self->enemy->client->resp.score++; + + if (g_debug_monster_kills->integer) + { + bool found = false; + + for (auto &ent : level.monsters_registered) + { + if (ent == self) + { + ent = nullptr; + found = true; + break; + } + } + + if (!found) + { +#if defined(_DEBUG) && defined(KEX_PLATFORM_WINPC) + __debugbreak(); +#endif + gi.Center_Print(&g_edicts[1], "found missing monster?"); + } + + if (level.killed_monsters == level.total_monsters) + { + gi.Center_Print(&g_edicts[1], "all monsters dead"); + } + } +} + +void M_ProcessPain(edict_t *e) +{ + if (!e->monsterinfo.damage_blood) + return; + + if (e->health <= 0) + { + // ROGUE + if (e->monsterinfo.aiflags & AI_MEDIC) + { + if (e->enemy && e->enemy->inuse && (e->enemy->svflags & SVF_MONSTER)) // god, I hope so + { + cleanupHealTarget(e->enemy); + } + + // clean up self + e->monsterinfo.aiflags &= ~AI_MEDIC; + } + // ROGUE + + if (!e->deadflag) + { + e->enemy = e->monsterinfo.damage_attacker; + + // ROGUE + // ROGUE - free up slot for spawned monster if it's spawned + if (e->monsterinfo.aiflags & AI_SPAWNED_CARRIER) + { + if (e->monsterinfo.commander && e->monsterinfo.commander->inuse && + !strcmp(e->monsterinfo.commander->classname, "monster_carrier")) + e->monsterinfo.commander->monsterinfo.monster_slots++; + e->monsterinfo.commander = nullptr; + } + if (e->monsterinfo.aiflags & AI_SPAWNED_WIDOW) + { + // need to check this because we can have variable numbers of coop players + if (e->monsterinfo.commander && e->monsterinfo.commander->inuse && + !strncmp(e->monsterinfo.commander->classname, "monster_widow", 13)) + { + if (e->monsterinfo.commander->monsterinfo.monster_used > 0) + e->monsterinfo.commander->monsterinfo.monster_used--; + e->monsterinfo.commander = nullptr; + } + } + + if (!(e->monsterinfo.aiflags & AI_DO_NOT_COUNT) && !(e->spawnflags & SPAWNFLAG_MONSTER_DEAD)) + G_MonsterKilled(e); + + e->touch = nullptr; + monster_death_use(e); + } + + e->die(e, e->monsterinfo.damage_inflictor, e->monsterinfo.damage_attacker, e->monsterinfo.damage_blood, e->monsterinfo.damage_from, e->monsterinfo.damage_mod); + + // [Paril-KEX] medic commander only gets his slots back after the monster is gibbed, since we can revive them + if (e->health <= e->gib_health) + { + if (e->monsterinfo.aiflags & AI_SPAWNED_MEDIC_C) + { + if (e->monsterinfo.commander && e->monsterinfo.commander->inuse && !strcmp(e->monsterinfo.commander->classname, "monster_medic_commander")) + e->monsterinfo.commander->monsterinfo.monster_used -= e->monsterinfo.monster_slots; + + e->monsterinfo.commander = nullptr; + } + } + + if (e->inuse && e->health > e->gib_health && e->s.frame == e->monsterinfo.active_move->lastframe) + { + e->s.frame -= irandom(1, 3); + + if (e->groundentity && e->movetype == MOVETYPE_TOSS && !(e->flags & FL_STATIONARY)) + e->s.angles[1] += brandom() ? 4.5f : -4.5f; + } + } + else + e->pain(e, e->monsterinfo.damage_attacker, (float) e->monsterinfo.damage_knockback, e->monsterinfo.damage_blood, e->monsterinfo.damage_mod); + + if (!e->inuse) + return; + + if (e->monsterinfo.setskin) + e->monsterinfo.setskin(e); + + e->monsterinfo.damage_blood = 0; + e->monsterinfo.damage_knockback = 0; + e->monsterinfo.damage_attacker = e->monsterinfo.damage_inflictor = nullptr; + + // [Paril-KEX] fire health target + if (e->healthtarget) + { + const char *target = e->target; + e->target = e->healthtarget; + G_UseTargets(e, e->enemy); + e->target = target; + } +} + +// +// Monster utility functions +// +THINK(monster_dead_think) (edict_t *self) -> void +{ + // flies + if ((self->monsterinfo.aiflags & AI_STINKY) && !(self->monsterinfo.aiflags & AI_STUNK)) + { + if (!self->fly_sound_debounce_time) + self->fly_sound_debounce_time = level.time + random_time(5_sec, 15_sec); + else if (self->fly_sound_debounce_time < level.time) + { + if (!self->s.sound) + { + self->s.effects |= EF_FLIES; + self->s.sound = gi.soundindex("infantry/inflies1.wav"); + self->fly_sound_debounce_time = level.time + 60_sec; + } + else + { + self->s.effects &= ~EF_FLIES; + self->s.sound = 0; + self->monsterinfo.aiflags |= AI_STUNK; + } + } + } + + if (!self->monsterinfo.damage_blood) + { + if (self->s.frame != self->monsterinfo.active_move->lastframe) + self->s.frame++; + } + + self->nextthink = level.time + 10_hz; +} + +void monster_dead(edict_t *self) +{ + self->think = monster_dead_think; + self->nextthink = level.time + 10_hz; + self->movetype = MOVETYPE_TOSS; + self->svflags |= SVF_DEADMONSTER; + self->monsterinfo.damage_blood = 0; + self->fly_sound_debounce_time = 0_ms; + self->monsterinfo.aiflags &= ~AI_STUNK; + gi.linkentity(self); +} + +static BoxEdictsResult_t M_CheckDodge_BoxEdictsFilter(edict_t *ent, void *data) +{ + edict_t *self = (edict_t *) data; + + // not a valid projectile + if (!(ent->svflags & SVF_PROJECTILE) || !(ent->flags & FL_DODGE)) + return BoxEdictsResult_t::Skip; + + // not moving + if (ent->velocity.lengthSquared() < 16.f) + return BoxEdictsResult_t::Skip; + + // projectile is behind us, we can't see it + if (!infront(self, ent)) + return BoxEdictsResult_t::Skip; + + // will it hit us within 1 second? gives us enough time to dodge + trace_t tr = gi.trace(ent->s.origin, ent->mins, ent->maxs, ent->s.origin + ent->velocity, ent, ent->clipmask); + + if (tr.ent == self) + { + vec3_t v = tr.endpos - ent->s.origin; + gtime_t eta = gtime_t::from_sec(v.length() / ent->velocity.length()); + + self->monsterinfo.dodge(self, ent->owner, eta, &tr, (ent->movetype == MOVETYPE_BOUNCE || ent->movetype == MOVETYPE_TOSS)); + + return BoxEdictsResult_t::End; + } + + return BoxEdictsResult_t::Skip; +} + +// [Paril-KEX] active checking for projectiles to dodge +static void M_CheckDodge(edict_t *self) +{ + // we recently made a valid dodge, don't try again for a bit + if (self->monsterinfo.dodge_time > level.time) + return; + + gi.BoxEdicts(self->absmin - vec3_t{512, 512, 512}, self->absmax + vec3_t{512, 512, 512}, nullptr, 0, AREA_SOLID, M_CheckDodge_BoxEdictsFilter, self); +} + +static bool CheckPathVisibility(const vec3_t &start, const vec3_t &end) +{ + trace_t tr = gi.traceline(start, end, nullptr, MASK_SOLID | CONTENTS_PROJECTILECLIP | CONTENTS_MONSTERCLIP | CONTENTS_PLAYERCLIP); + + bool valid = tr.fraction == 1.0f; + + if (!valid) + { + // try raising some of the points + bool can_raise_start = false, can_raise_end = false; + vec3_t raised_start = start + vec3_t{0.f, 0.f, 16.f}; + vec3_t raised_end = end + vec3_t{0.f, 0.f, 16.f}; + + if (gi.traceline(start, raised_start, nullptr, MASK_SOLID | CONTENTS_PROJECTILECLIP | CONTENTS_MONSTERCLIP | CONTENTS_PLAYERCLIP).fraction == 1.0f) + can_raise_start = true; + + if (gi.traceline(end, raised_end, nullptr, MASK_SOLID | CONTENTS_PROJECTILECLIP | CONTENTS_MONSTERCLIP | CONTENTS_PLAYERCLIP).fraction == 1.0f) + can_raise_end = true; + + // try raised start -> end + if (can_raise_start) + { + tr = gi.traceline(raised_start, end, nullptr, MASK_SOLID | CONTENTS_PROJECTILECLIP | CONTENTS_MONSTERCLIP | CONTENTS_PLAYERCLIP); + + if (tr.fraction == 1.0f) + return true; + } + + // try start -> raised end + if (can_raise_end) + { + tr = gi.traceline(start, raised_end, nullptr, MASK_SOLID | CONTENTS_PROJECTILECLIP | CONTENTS_MONSTERCLIP | CONTENTS_PLAYERCLIP); + + if (tr.fraction == 1.0f) + return true; + } + + // try both raised + if (can_raise_start && can_raise_end) + { + tr = gi.traceline(raised_start, raised_end, nullptr, MASK_SOLID | CONTENTS_PROJECTILECLIP | CONTENTS_MONSTERCLIP | CONTENTS_PLAYERCLIP); + + if (tr.fraction == 1.0f) + return true; + } + + //gi.Draw_Line(start, end, rgba_red, 0.1f, false); + } + + return valid; +} + +THINK(monster_think) (edict_t *self) -> void +{ + // [Paril-KEX] monster sniff testing; if we can make an unobstructed path to the player, murder ourselves. + if (g_debug_monster_kills->integer) + { + if (g_edicts[1].inuse) + { + trace_t enemy_trace = gi.traceline(self->s.origin, g_edicts[1].s.origin, self, MASK_SHOT); + + if (enemy_trace.fraction < 1.0f && enemy_trace.ent == &g_edicts[1]) + T_Damage(self, &g_edicts[1], &g_edicts[1], { 0, 0, -1 }, self->s.origin, { 0, 0, -1 }, 9999, 9999, DAMAGE_NO_PROTECTION, MOD_BFG_BLAST); + else + { + static vec3_t points[64]; + + if (self->disintegrator_time <= level.time) + { + PathRequest request; + request.goal = g_edicts[1].s.origin; + request.moveDist = 4.0f; + request.nodeSearch.ignoreNodeFlags = true; + request.nodeSearch.radius = 9999; + request.pathFlags = PathFlags::All; + request.start = self->s.origin; + request.traversals.dropHeight = 9999; + request.traversals.jumpHeight = 9999; + request.pathPoints.array = points; + request.pathPoints.count = q_countof(points); + + PathInfo info; + + if (gi.GetPathToGoal(request, info)) + { + if (info.returnCode != PathReturnCode::NoStartNode && + info.returnCode != PathReturnCode::NoGoalNode && + info.returnCode != PathReturnCode::NoPathFound && + info.returnCode != PathReturnCode::NoNavAvailable && + info.numPathPoints < q_countof(points)) + { + if (CheckPathVisibility(g_edicts[1].s.origin + vec3_t { 0.f, 0.f, g_edicts[1].mins.z }, points[info.numPathPoints - 1]) && + CheckPathVisibility(self->s.origin + vec3_t { 0.f, 0.f, self->mins.z }, points[0])) + { + size_t i = 0; + + for (; i < info.numPathPoints - 1; i++) + if (!CheckPathVisibility(points[i], points[i + 1])) + break; + + if (i == info.numPathPoints - 1) + T_Damage(self, &g_edicts[1], &g_edicts[1], { 0, 0, 1 }, self->s.origin, { 0, 0, 1 }, 9999, 9999, DAMAGE_NO_PROTECTION, MOD_BFG_BLAST); + else + self->disintegrator_time = level.time + 500_ms; + } + else + self->disintegrator_time = level.time + 500_ms; + } + else + { + self->disintegrator_time = level.time + 1_sec; + } + } + else + { + self->disintegrator_time = level.time + 1_sec; + } + } + } + + if (!self->deadflag && !(self->monsterinfo.aiflags & AI_DO_NOT_COUNT)) + gi.Draw_Bounds(self->absmin, self->absmax, rgba_red, gi.frame_time_s, false); + } + } + + self->s.renderfx &= ~(RF_STAIR_STEP | RF_OLD_FRAME_LERP); + + M_ProcessPain(self); + + // pain/die above freed us + if (!self->inuse || self->think != monster_think) + return; + + if (self->hackflags & HACKFLAG_ATTACK_PLAYER) + { + if (!self->enemy && g_edicts[1].inuse) + { + self->enemy = &g_edicts[1]; + FoundTarget(self); + } + } + + if (self->health > 0 && self->monsterinfo.dodge && !(globals.server_flags & SERVER_FLAG_LOADING)) + M_CheckDodge(self); + + M_MoveFrame(self); + if (self->linkcount != self->monsterinfo.linkcount) + { + self->monsterinfo.linkcount = self->linkcount; + M_CheckGround(self, G_GetClipMask(self)); + } + M_CatagorizePosition(self, self->s.origin, self->waterlevel, self->watertype); + M_WorldEffects(self); + M_SetEffects(self); +} + +/* +================ +monster_use + +Using a monster makes it angry at the current activator +================ +*/ +USE(monster_use) (edict_t *self, edict_t *other, edict_t *activator) -> void +{ + if (self->enemy) + return; + if (self->health <= 0) + return; + if (!activator) + return; + if (activator->flags & FL_NOTARGET) + return; + if (!(activator->client) && !(activator->monsterinfo.aiflags & AI_GOOD_GUY)) + return; + if (activator->flags & FL_DISGUISED) // PGM + return; // PGM + + // delay reaction so if the monster is teleported, its sound is still heard + self->enemy = activator; + FoundTarget(self); +} + +void monster_start_go(edict_t *self); + +THINK(monster_triggered_spawn) (edict_t *self) -> void +{ + self->s.origin[2] += 1; + + self->solid = SOLID_BBOX; + self->movetype = MOVETYPE_STEP; + self->svflags &= ~SVF_NOCLIENT; + self->air_finished = level.time + 12_sec; + gi.linkentity(self); + + KillBox(self, false); + + monster_start_go(self); + + // RAFAEL + if (strcmp(self->classname, "monster_fixbot") == 0) + { + if (self->spawnflags.has(SPAWNFLAG_FIXBOT_LANDING | SPAWNFLAG_FIXBOT_TAKEOFF | SPAWNFLAG_FIXBOT_FIXIT)) + { + self->enemy = nullptr; + return; + } + } + // RAFAEL + + if (self->enemy && !(self->spawnflags & SPAWNFLAG_MONSTER_AMBUSH) && !(self->enemy->flags & FL_NOTARGET) && !(self->monsterinfo.aiflags & AI_GOOD_GUY)) + { + // ROGUE + if (!(self->enemy->flags & FL_DISGUISED)) + // ROGUE + FoundTarget(self); + // ROGUE + else // PMM - just in case, make sure to clear the enemy so FindTarget doesn't get confused + self->enemy = nullptr; + // ROGUE + } + else + { + self->enemy = nullptr; + } +} + +USE(monster_triggered_spawn_use) (edict_t *self, edict_t *other, edict_t *activator) -> void +{ + // we have a one frame delay here so we don't telefrag the guy who activated us + self->think = monster_triggered_spawn; + self->nextthink = level.time + FRAME_TIME_S; + if (activator->client && !(self->hackflags & HACKFLAG_END_CUTSCENE)) + self->enemy = activator; + self->use = monster_use; + + if (self->spawnflags.has(SPAWNFLAG_MONSTER_SCENIC)) + { + M_droptofloor(self); + + self->nextthink = 0_ms; + self->think(self); + + if (self->spawnflags.has(SPAWNFLAG_MONSTER_AMBUSH)) + monster_use(self, other, activator); + + for (int i = 0; i < 30; i++) + { + self->think(self); + self->monsterinfo.next_move_time = 0_ms; + } + } +} + +THINK(monster_triggered_think) (edict_t *self) -> void +{ + if (!(self->monsterinfo.aiflags & AI_DO_NOT_COUNT)) + gi.Draw_Bounds(self->absmin, self->absmax, rgba_blue, gi.frame_time_s, false); + + self->nextthink = level.time + 1_ms; +} + +void monster_triggered_start(edict_t *self) +{ + self->solid = SOLID_NOT; + self->movetype = MOVETYPE_NONE; + self->svflags |= SVF_NOCLIENT; + self->nextthink = 0_ms; + self->use = monster_triggered_spawn_use; + + if (g_debug_monster_kills->integer) + { + self->think = monster_triggered_think; + self->nextthink = level.time + 1_ms; + } + + if (!self->targetname || + (G_FindByString<&edict_t::target>(nullptr, self->targetname) == nullptr && + G_FindByString<&edict_t::pathtarget>(nullptr, self->targetname) == nullptr && + G_FindByString<&edict_t::deathtarget>(nullptr, self->targetname) == nullptr && + G_FindByString<&edict_t::itemtarget>(nullptr, self->targetname) == nullptr && + G_FindByString<&edict_t::healthtarget>(nullptr, self->targetname) == nullptr && + G_FindByString<&edict_t::combattarget>(nullptr, self->targetname) == nullptr)) + { + gi.Com_PrintFmt("{}: is trigger spawned, but has no targetname or no entity to spawn it\n", *self); + } +} + +/* +================ +monster_death_use + +When a monster dies, it fires all of its targets with the current +enemy as activator. +================ +*/ +void monster_death_use(edict_t *self) +{ + self->flags &= ~(FL_FLY | FL_SWIM); + self->monsterinfo.aiflags &= (AI_DOUBLE_TROUBLE | AI_GOOD_GUY | AI_STINKY | AI_SPAWNED_MASK); + + if (self->item) + { + edict_t *dropped = Drop_Item(self, self->item); + + if (self->itemtarget) + { + dropped->target = self->itemtarget; + self->itemtarget = nullptr; + } + + self->item = nullptr; + } + + if (self->deathtarget) + self->target = self->deathtarget; + + if (self->target) + G_UseTargets(self, self->enemy); + + // [Paril-KEX] fire health target + if (self->healthtarget) + { + self->target = self->healthtarget; + G_UseTargets(self, self->enemy); + } +} + +// [Paril-KEX] adjust the monster's health from how +// many active players we have +void G_Monster_ScaleCoopHealth(edict_t *self) +{ + // already scaled + if (self->monsterinfo.health_scaling >= level.coop_scale_players) + return; + + // this is just to fix monsters that change health after spawning... + // looking at you, soldiers + if (!self->monsterinfo.base_health) + self->monsterinfo.base_health = self->max_health; + + int32_t delta = level.coop_scale_players - self->monsterinfo.health_scaling; + int32_t additional_health = delta * (int32_t) (self->monsterinfo.base_health * level.coop_health_scaling); + + self->health = max(1, self->health + additional_health); + self->max_health += additional_health; + + self->monsterinfo.health_scaling = level.coop_scale_players; +} + +struct monster_filter_t +{ + inline bool operator()(edict_t *self) const + { + return self->inuse && (self->flags & FL_COOP_HEALTH_SCALE) && self->health > 0; + } +}; + +// check all active monsters' scaling +void G_Monster_CheckCoopHealthScaling() +{ + for (auto monster : entity_iterable_t()) + G_Monster_ScaleCoopHealth(monster); +} + +//============================================================================ +constexpr spawnflags_t SPAWNFLAG_MONSTER_FUBAR = 4_spawnflag; + +bool monster_start(edict_t *self) +{ + if ( !M_AllowSpawn( self ) ) { + G_FreeEdict( self ); + return false; + } + + if (self->spawnflags.has(SPAWNFLAG_MONSTER_SCENIC)) + self->monsterinfo.aiflags |= AI_GOOD_GUY; + + // [Paril-KEX] n64 + if (self->hackflags & (HACKFLAG_END_CUTSCENE | HACKFLAG_ATTACK_PLAYER)) + self->monsterinfo.aiflags |= AI_DO_NOT_COUNT; + + if (self->spawnflags.has(SPAWNFLAG_MONSTER_FUBAR) && !(self->monsterinfo.aiflags & AI_GOOD_GUY)) + { + self->spawnflags &= ~SPAWNFLAG_MONSTER_FUBAR; + self->spawnflags |= SPAWNFLAG_MONSTER_AMBUSH; + } + + // [Paril-KEX] simplify other checks + if (self->monsterinfo.aiflags & AI_GOOD_GUY) + self->monsterinfo.aiflags |= AI_DO_NOT_COUNT; + + // ROGUE + if (!(self->monsterinfo.aiflags & AI_DO_NOT_COUNT) && !self->spawnflags.has(SPAWNFLAG_MONSTER_DEAD)) + { + if (g_debug_monster_kills->integer) + level.monsters_registered[level.total_monsters] = self; + // ROGUE + level.total_monsters++; + } + + self->nextthink = level.time + FRAME_TIME_S; + self->svflags |= SVF_MONSTER; + self->takedamage = true; + self->air_finished = level.time + 12_sec; + self->use = monster_use; + self->max_health = self->health; + self->clipmask = MASK_MONSTERSOLID; + self->deadflag = false; + self->svflags &= ~SVF_DEADMONSTER; + self->flags &= ~FL_ALIVE_KNOCKBACK_ONLY; + self->flags |= FL_COOP_HEALTH_SCALE; + self->s.old_origin = self->s.origin; + self->monsterinfo.initial_power_armor_type = self->monsterinfo.power_armor_type; + self->monsterinfo.max_power_armor_power = self->monsterinfo.power_armor_power; + + if (!self->monsterinfo.checkattack) + self->monsterinfo.checkattack = M_CheckAttack; + + if ( ai_model_scale->value > 0 ) { + self->s.scale = ai_model_scale->value; + } + + if (self->s.scale) + { + self->monsterinfo.scale *= self->s.scale; + self->mins *= self->s.scale; + self->maxs *= self->s.scale; + self->mass *= self->s.scale; + } + + // set combat style if unset + if (self->monsterinfo.combat_style == COMBAT_UNKNOWN) + { + if (!self->monsterinfo.attack && self->monsterinfo.melee) + self->monsterinfo.combat_style = COMBAT_MELEE; + else + self->monsterinfo.combat_style = COMBAT_MIXED; + } + + if (st.item) + { + self->item = FindItemByClassname(st.item); + if (!self->item) + gi.Com_PrintFmt("{}: bad item: {}\n", *self, st.item); + } + + // randomize what frame they start on + if (self->monsterinfo.active_move) + self->s.frame = + irandom(self->monsterinfo.active_move->firstframe, self->monsterinfo.active_move->lastframe + 1); + + // PMM - get this so I don't have to do it in all of the monsters + self->monsterinfo.base_height = self->maxs[2]; + + // Paril: monsters' old default viewheight (25) + // is all messed up for certain monsters. Calculate + // from maxs to make a bit more sense. + if (!self->viewheight) + self->viewheight = (int) (self->maxs[2] - 8.f); + + // PMM - clear these + self->monsterinfo.quad_time = 0_ms; + self->monsterinfo.double_time = 0_ms; + self->monsterinfo.invincible_time = 0_ms; + + // set base health & set base scaling to 1 player + self->monsterinfo.base_health = self->health; + self->monsterinfo.health_scaling = 1; + + // [Paril-KEX] co-op health scale + G_Monster_ScaleCoopHealth(self); + + return true; +} + +stuck_result_t G_FixStuckObject(edict_t *self, vec3_t check) +{ + contents_t mask = G_GetClipMask(self); + stuck_result_t result = G_FixStuckObject_Generic(check, self->mins, self->maxs, [self, mask] (const vec3_t &start, const vec3_t &mins, const vec3_t &maxs, const vec3_t &end) { + return gi.trace(start, mins, maxs, end, self, mask); + }); + + if (result == stuck_result_t::NO_GOOD_POSITION) + return result; + + self->s.origin = check; + + if (result == stuck_result_t::FIXED) + gi.Com_PrintFmt("fixed stuck {}\n", *self); + + return result; +} + +void monster_start_go(edict_t *self) +{ + // Paril: moved here so this applies to swim/fly monsters too + if (!(self->flags & FL_STATIONARY)) + { + const vec3_t check = self->s.origin; + + // [Paril-KEX] different nudge method; see if any of the bbox sides are clear, + // if so we can see how much headroom we have in that direction and shift us. + // most of the monsters stuck in solids will only be stuck on one side, which + // conveniently leaves only one side not in a solid; this won't fix monsters + // stuck in a corner though. + bool is_stuck = false; + + if ((self->monsterinfo.aiflags & AI_GOOD_GUY) || (self->flags & (FL_FLY | FL_SWIM))) + is_stuck = gi.trace(self->s.origin, self->mins, self->maxs, self->s.origin, self, MASK_MONSTERSOLID).startsolid; + else + is_stuck = !M_droptofloor(self) || !M_walkmove(self, 0, 0); + + if (is_stuck) + { + if (G_FixStuckObject(self, check) != stuck_result_t::NO_GOOD_POSITION) + { + if (self->monsterinfo.aiflags & AI_GOOD_GUY) + is_stuck = gi.trace(self->s.origin, self->mins, self->maxs, self->s.origin, self, MASK_MONSTERSOLID).startsolid; + else if (!(self->flags & (FL_FLY | FL_SWIM))) + M_droptofloor(self); + is_stuck = false; + } + } + + // last ditch effort: brute force + if (is_stuck) + { + // Paril: try nudging them out. this fixes monsters stuck + // in very shallow slopes. + constexpr const int32_t adjust[] = { 0, -1, 1, -2, 2, -4, 4, -8, 8 }; + bool walked = false; + + for (int32_t y = 0; !walked && y < 3; y++) + for (int32_t x = 0; !walked && x < 3; x++) + for (int32_t z = 0; !walked && z < 3; z++) + { + self->s.origin[0] = check[0] + adjust[x]; + self->s.origin[1] = check[1] + adjust[y]; + self->s.origin[2] = check[2] + adjust[z]; + + if (self->monsterinfo.aiflags & AI_GOOD_GUY) + { + is_stuck = gi.trace(self->s.origin, self->mins, self->maxs, self->s.origin, self, MASK_MONSTERSOLID).startsolid; + + if (!is_stuck) + walked = true; + } + else if (!(self->flags & (FL_FLY | FL_SWIM))) + { + M_droptofloor(self); + walked = M_walkmove(self, 0, 0); + } + } + } + + if (is_stuck) + gi.Com_PrintFmt("WARNING: {} stuck in solid\n", *self); + } + + vec3_t v; + + if (self->health <= 0) + return; + + self->s.old_origin = self->s.origin; + + // check for target to combat_point and change to combattarget + if (self->target) + { + bool notcombat; + bool fixup; + edict_t *target; + + target = nullptr; + notcombat = false; + fixup = false; + while ((target = G_FindByString<&edict_t::targetname>(target, self->target)) != nullptr) + { + if (strcmp(target->classname, "point_combat") == 0) + { + self->combattarget = self->target; + fixup = true; + } + else + { + notcombat = true; + } + } + if (notcombat && self->combattarget) + gi.Com_PrintFmt("{}: has target with mixed types\n", *self); + if (fixup) + self->target = nullptr; + } + + // validate combattarget + if (self->combattarget) + { + edict_t *target; + + target = nullptr; + while ((target = G_FindByString<&edict_t::targetname>(target, self->combattarget)) != nullptr) + { + if (strcmp(target->classname, "point_combat") != 0) + { + gi.Com_PrintFmt("{} has a bad combattarget {} ({})\n", *self, self->combattarget, *target); + } + } + } + + // allow spawning dead + bool spawn_dead = self->spawnflags.has(SPAWNFLAG_MONSTER_DEAD); + + if (self->target) + { + self->goalentity = self->movetarget = G_PickTarget(self->target); + if (!self->movetarget) + { + gi.Com_PrintFmt("{}: can't find target {}\n", *self, self->target); + self->target = nullptr; + self->monsterinfo.pausetime = HOLD_FOREVER; + if (!spawn_dead) + self->monsterinfo.stand(self); + } + else if (strcmp(self->movetarget->classname, "path_corner") == 0) + { + v = self->goalentity->s.origin - self->s.origin; + self->ideal_yaw = self->s.angles[YAW] = vectoyaw(v); + if (!spawn_dead) + self->monsterinfo.walk(self); + self->target = nullptr; + } + else + { + self->goalentity = self->movetarget = nullptr; + self->monsterinfo.pausetime = HOLD_FOREVER; + if (!spawn_dead) + self->monsterinfo.stand(self); + } + } + else + { + self->monsterinfo.pausetime = HOLD_FOREVER; + if (!spawn_dead) + self->monsterinfo.stand(self); + } + + if (spawn_dead) + { + // to spawn dead, we'll mimick them dying naturally + self->health = 0; + + vec3_t f = self->s.origin; + + if (self->die) + self->die(self, self, self, 0, vec3_origin, MOD_SUICIDE); + + if (!self->inuse) + return; + + if (self->monsterinfo.setskin) + self->monsterinfo.setskin(self); + + self->monsterinfo.aiflags |= AI_SPAWNED_DEAD; + + auto move = self->monsterinfo.active_move.pointer(); + + for (size_t i = move->firstframe; i < move->lastframe; i++) + { + self->s.frame = i; + + if (move->frame[i - move->firstframe].thinkfunc) + move->frame[i - move->firstframe].thinkfunc(self); + + if (!self->inuse) + return; + } + + if (move->endfunc) + move->endfunc(self); + + if (!self->inuse) + return; + + if (self->monsterinfo.start_frame) + self->s.frame = self->monsterinfo.start_frame; + else + self->s.frame = move->lastframe; + + self->s.origin = f; + gi.linkentity(self); + + self->monsterinfo.aiflags &= ~AI_SPAWNED_DEAD; + } + else + { + self->think = monster_think; + self->nextthink = level.time + FRAME_TIME_S; + self->monsterinfo.aiflags |= AI_SPAWNED_ALIVE; + } +} + +THINK(walkmonster_start_go) (edict_t *self) -> void +{ + if (!self->yaw_speed) + self->yaw_speed = 20; + + if (self->spawnflags.has(SPAWNFLAG_MONSTER_TRIGGER_SPAWN)) + monster_triggered_start(self); + else + monster_start_go(self); +} + +void walkmonster_start(edict_t *self) +{ + self->think = walkmonster_start_go; + monster_start(self); +} + +THINK(flymonster_start_go) (edict_t *self) -> void +{ + if (!self->yaw_speed) + self->yaw_speed = 30; + + if (self->spawnflags.has(SPAWNFLAG_MONSTER_TRIGGER_SPAWN)) + monster_triggered_start(self); + else + monster_start_go(self); +} + +void flymonster_start(edict_t *self) +{ + self->flags |= FL_FLY; + self->think = flymonster_start_go; + monster_start(self); +} + +THINK(swimmonster_start_go) (edict_t *self) -> void +{ + if (!self->yaw_speed) + self->yaw_speed = 30; + + if (self->spawnflags.has(SPAWNFLAG_MONSTER_TRIGGER_SPAWN)) + monster_triggered_start(self); + else + monster_start_go(self); +} + +void swimmonster_start(edict_t *self) +{ + self->flags |= FL_SWIM; + self->think = swimmonster_start_go; + monster_start(self); +} + +USE(trigger_health_relay_use) (edict_t *self, edict_t *other, edict_t *activator) -> void +{ + float percent_health = clamp((float) (other->health) / (float) (other->max_health), 0.f, 1.f); + + // not ready to trigger yet + if (percent_health > self->speed) + return; + + // fire! + G_UseTargets(self, activator); + + // kill self + G_FreeEdict(self); +} + +/*QUAKED trigger_health_relay (1.0 1.0 0.0) (-8 -8 -8) (8 8 8) +Special type of relay that fires when a linked object is reduced +beyond a certain amount of health. + +It will only fire once, and free itself afterwards. +*/ +void SP_trigger_health_relay(edict_t *self) +{ + if (!self->targetname) + { + gi.Com_PrintFmt("{} missing targetname\n", *self); + G_FreeEdict(self); + return; + } + + if (self->speed < 0 || self->speed > 100) + { + gi.Com_PrintFmt("{} has bad \"speed\" (health percentage); must be between 0 and 100, inclusive\n", *self); + G_FreeEdict(self); + return; + } + + self->svflags |= SVF_NOCLIENT; + self->use = trigger_health_relay_use; +} \ No newline at end of file diff --git a/rerelease/g_phys.cpp b/rerelease/g_phys.cpp new file mode 100644 index 0000000..28ad366 --- /dev/null +++ b/rerelease/g_phys.cpp @@ -0,0 +1,1044 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// g_phys.c + +#include "g_local.h" + +/* + + +pushmove objects do not obey gravity, and do not interact with each other or trigger fields, but block normal movement +and push normal objects when they move. + +onground is set for toss objects when they come to a complete rest. it is set for steping or walking objects + +doors, plats, etc are SOLID_BSP, and MOVETYPE_PUSH +bonus items are SOLID_TRIGGER touch, and MOVETYPE_TOSS +corpses are SOLID_NOT and MOVETYPE_TOSS +crates are SOLID_BBOX and MOVETYPE_TOSS +walking monsters are SOLID_SLIDEBOX and MOVETYPE_STEP +flying/floating monsters are SOLID_SLIDEBOX and MOVETYPE_FLY + +solid_edge items only clip against bsp models. + +*/ + +void SV_Physics_NewToss(edict_t *ent); // PGM + +// [Paril-KEX] fetch the clipmask for this entity; certain modifiers +// affect the clipping behavior of objects. +contents_t G_GetClipMask(edict_t *ent) +{ + contents_t mask = ent->clipmask; + + // default masks + if (!mask) + { + if (ent->svflags & SVF_MONSTER) + mask = MASK_MONSTERSOLID; + else if (ent->svflags & SVF_PROJECTILE) + mask = MASK_PROJECTILE; + else + mask = MASK_SHOT & ~CONTENTS_DEADMONSTER; + } + + // non-solid objects (items, etc) shouldn't try to clip + // against players/monsters + if (ent->solid == SOLID_NOT || ent->solid == SOLID_TRIGGER) + mask &= ~(CONTENTS_MONSTER | CONTENTS_PLAYER); + + // monsters/players that are also dead shouldn't clip + // against players/monsters + if ((ent->svflags & (SVF_MONSTER | SVF_PLAYER)) && (ent->svflags & SVF_DEADMONSTER)) + mask &= ~(CONTENTS_MONSTER | CONTENTS_PLAYER); + + return mask; +} + +/* +============ +SV_TestEntityPosition + +============ +*/ +edict_t *SV_TestEntityPosition(edict_t *ent) +{ + trace_t trace; + + trace = gi.trace(ent->s.origin, ent->mins, ent->maxs, ent->s.origin, ent, G_GetClipMask(ent)); + + if (trace.startsolid) + return g_edicts; + + return nullptr; +} + +/* +================ +SV_CheckVelocity +================ +*/ +void SV_CheckVelocity(edict_t *ent) +{ + // + // bound velocity + // + float speed = ent->velocity.length(); + + if (speed > sv_maxvelocity->value) + ent->velocity = (ent->velocity / speed) * sv_maxvelocity->value; +} + +/* +============= +SV_RunThink + +Runs thinking code for this frame if necessary +============= +*/ +bool SV_RunThink(edict_t *ent) +{ + gtime_t thinktime = ent->nextthink; + if (thinktime <= 0_ms) + return true; + if (thinktime > level.time) + return true; + + ent->nextthink = 0_ms; + if (!ent->think) + gi.Com_Error("nullptr ent->think"); + ent->think(ent); + + return false; +} + +/* +================== +G_Impact + +Two entities have touched, so run their touch functions +================== +*/ +void G_Impact(edict_t *e1, const trace_t &trace) +{ + edict_t *e2 = trace.ent; + + if (e1->touch && (e1->solid != SOLID_NOT || (e1->flags & FL_ALWAYS_TOUCH))) + e1->touch(e1, e2, trace, false); + + if (e2->touch && (e2->solid != SOLID_NOT || (e2->flags & FL_ALWAYS_TOUCH))) + e2->touch(e2, e1, trace, true); +} + +/* +============ +SV_FlyMove + +The basic solid body movement clip that slides along multiple planes +============ +*/ +void SV_FlyMove(edict_t *ent, float time, contents_t mask) +{ + ent->groundentity = nullptr; + + touch_list_t touch; + PM_StepSlideMove_Generic(ent->s.origin, ent->velocity, time, ent->mins, ent->maxs, touch, false, [&](const vec3_t &start, const vec3_t &mins, const vec3_t &maxs, const vec3_t &end) + { + return gi.trace(start, mins, maxs, end, ent, mask); + }); + + for (size_t i = 0; i < touch.num; i++) + { + auto &trace = touch.traces[i]; + + if (trace.plane.normal[2] > 0.7f) + { + ent->groundentity = trace.ent; + ent->groundentity_linkcount = trace.ent->linkcount; + } + + // + // run the impact function + // + G_Impact(ent, trace); + + // impact func requested velocity kill + if (ent->flags & FL_KILL_VELOCITY) + { + ent->flags &= ~FL_KILL_VELOCITY; + ent->velocity = {}; + } + } +} + +/* +============ +SV_AddGravity + +============ +*/ +void SV_AddGravity(edict_t *ent) +{ + ent->velocity += ent->gravityVector * (ent->gravity * level.gravity * gi.frame_time_s); +} + +/* +=============================================================================== + +PUSHMOVE + +=============================================================================== +*/ + +/* +============ +SV_PushEntity + +Does not change the entities velocity at all +============ +*/ +trace_t SV_PushEntity(edict_t *ent, const vec3_t &push) +{ + vec3_t start = ent->s.origin; + vec3_t end = start + push; + + trace_t trace = gi.trace(start, ent->mins, ent->maxs, end, ent, G_GetClipMask(ent)); + + ent->s.origin = trace.endpos + (trace.plane.normal * .5f); + gi.linkentity(ent); + + if (trace.fraction != 1.0f || trace.startsolid) + { + G_Impact(ent, trace); + + // if the pushed entity went away and the pusher is still there + if (!trace.ent->inuse && ent->inuse) + { + // move the pusher back and try again + ent->s.origin = start; + gi.linkentity(ent); + return SV_PushEntity(ent, push); + } + } + + // ================ + // PGM + // FIXME - is this needed? + ent->gravity = 1.0; + // PGM + // ================ + + if (ent->inuse) + G_TouchTriggers(ent); + + return trace; +} + +struct pushed_t +{ + edict_t *ent; + vec3_t origin; + vec3_t angles; + bool rotated; + float yaw; +}; + +pushed_t pushed[MAX_EDICTS], *pushed_p; + +edict_t *obstacle; + +/* +============ +SV_Push + +Objects need to be moved back on a failed push, +otherwise riders would continue to slide. +============ +*/ +bool SV_Push(edict_t *pusher, vec3_t &move, vec3_t &amove) +{ + edict_t *check, *block = nullptr; + vec3_t mins, maxs; + pushed_t *p; + vec3_t org, org2, move2, forward, right, up; + + // find the bounding box + mins = pusher->absmin + move; + maxs = pusher->absmax + move; + + // we need this for pushing things later + org = -amove; + AngleVectors(org, forward, right, up); + + // save the pusher's original position + pushed_p->ent = pusher; + pushed_p->origin = pusher->s.origin; + pushed_p->angles = pusher->s.angles; + pushed_p->rotated = false; + pushed_p++; + + // move the pusher to it's final position + pusher->s.origin += move; + pusher->s.angles += amove; + gi.linkentity(pusher); + + // see if any solid entities are inside the final position + check = g_edicts + 1; + for (uint32_t e = 1; e < globals.num_edicts; e++, check++) + { + if (!check->inuse) + continue; + if (check->movetype == MOVETYPE_PUSH || check->movetype == MOVETYPE_STOP || check->movetype == MOVETYPE_NONE || + check->movetype == MOVETYPE_NOCLIP) + continue; + + if (!check->linked) + continue; // not linked in anywhere + + // if the entity is standing on the pusher, it will definitely be moved + if (check->groundentity != pusher) + { + // see if the ent needs to be tested + if (check->absmin[0] >= maxs[0] || check->absmin[1] >= maxs[1] || check->absmin[2] >= maxs[2] || + check->absmax[0] <= mins[0] || check->absmax[1] <= mins[1] || check->absmax[2] <= mins[2]) + continue; + + // see if the ent's bbox is inside the pusher's final position + if (!SV_TestEntityPosition(check)) + continue; + } + + if ((pusher->movetype == MOVETYPE_PUSH) || (check->groundentity == pusher)) + { + // move this entity + pushed_p->ent = check; + pushed_p->origin = check->s.origin; + pushed_p->angles = check->s.angles; + pushed_p->rotated = !!amove[YAW]; + if (pushed_p->rotated) + pushed_p->yaw = + pusher->client ? (float) pusher->client->ps.pmove.delta_angles[YAW] : pusher->s.angles[YAW]; + pushed_p++; + + vec3_t old_position = check->s.origin; + + // try moving the contacted entity + check->s.origin += move; + if (check->client) + { + // Paril: disabled because in vanilla delta_angles are never + // lerped. delta_angles can probably be lerped as long as event + // isn't EV_PLAYER_TELEPORT or a new RDF flag is set + // check->client->ps.pmove.delta_angles[YAW] += amove[YAW]; + } + else + check->s.angles[YAW] += amove[YAW]; + + // figure movement due to the pusher's amove + org = check->s.origin - pusher->s.origin; + org2[0] = org.dot(forward); + org2[1] = -(org.dot(right)); + org2[2] = org.dot(up); + move2 = org2 - org; + check->s.origin += move2; + + // may have pushed them off an edge + if (check->groundentity != pusher) + check->groundentity = nullptr; + + block = SV_TestEntityPosition(check); + + // [Paril-KEX] this is a bit of a hack; allow dead player skulls + // to be a blocker because otherwise elevators/doors get stuck + if (block && check->client && !check->takedamage) + { + check->s.origin = old_position; + block = nullptr; + } + + if (!block) + { // pushed ok + gi.linkentity(check); + // impact? + continue; + } + + // if it is ok to leave in the old position, do it. + // this is only relevent for riding entities, not pushed + check->s.origin = old_position; + block = SV_TestEntityPosition(check); + if (!block) + { + pushed_p--; + continue; + } + } + + // save off the obstacle so we can call the block function + obstacle = check; + + // move back any entities we already moved + // go backwards, so if the same entity was pushed + // twice, it goes back to the original position + for (p = pushed_p - 1; p >= pushed; p--) + { + p->ent->s.origin = p->origin; + p->ent->s.angles = p->angles; + if (p->rotated) + { + //if (p->ent->client) + // p->ent->client->ps.pmove.delta_angles[YAW] = p->yaw; + //else + p->ent->s.angles[YAW] = p->yaw; + } + gi.linkentity(p->ent); + } + return false; + } + + // FIXME: is there a better way to handle this? + // see if anything we moved has touched a trigger + for (p = pushed_p - 1; p >= pushed; p--) + G_TouchTriggers(p->ent); + + return true; +} + +/* +================ +SV_Physics_Pusher + +Bmodel objects don't interact with each other, but +push all box objects +================ +*/ +void SV_Physics_Pusher(edict_t *ent) +{ + vec3_t move, amove; + edict_t *part, *mv; + + // if not a team captain, so movement will be handled elsewhere + if (ent->flags & FL_TEAMSLAVE) + return; + + // make sure all team slaves can move before commiting + // any moves or calling any think functions + // if the move is blocked, all moved objects will be backed out +retry: + pushed_p = pushed; + for (part = ent; part; part = part->teamchain) + { + if (part->velocity[0] || part->velocity[1] || part->velocity[2] || part->avelocity[0] || part->avelocity[1] || + part->avelocity[2]) + { // object is moving + move = part->velocity * gi.frame_time_s; + amove = part->avelocity * gi.frame_time_s; + + if (!SV_Push(part, move, amove)) + break; // move was blocked + } + } + if (pushed_p > &pushed[MAX_EDICTS]) + gi.Com_Error("pushed_p > &pushed[MAX_EDICTS], memory corrupted"); + + if (part) + { + // if the pusher has a "blocked" function, call it + // otherwise, just stay in place until the obstacle is gone + if (part->moveinfo.blocked) + part->moveinfo.blocked(part, obstacle); + + if (!obstacle->inuse) + goto retry; + + // the move failed, bump all nextthink times and back out moves + for (mv = ent; mv; mv = mv->teamchain) + { + if (mv->nextthink > 0_ms) + mv->nextthink += FRAME_TIME_S; + } + } + else + { + // the move succeeded, so call all think functions + for (part = ent; part; part = part->teamchain) + { + // prevent entities that are on trains that have gone away from thinking! + if (part->inuse) + SV_RunThink(part); + } + } +} + +//================================================================== + +/* +============= +SV_Physics_None + +Non moving objects can only think +============= +*/ +void SV_Physics_None(edict_t *ent) +{ + // regular thinking + SV_RunThink(ent); +} + +/* +============= +SV_Physics_Noclip + +A moving object that doesn't obey physics +============= +*/ +void SV_Physics_Noclip(edict_t *ent) +{ + // regular thinking + if (!SV_RunThink(ent) || !ent->inuse) + return; + + ent->s.angles += (ent->avelocity * gi.frame_time_s); + ent->s.origin += (ent->velocity * gi.frame_time_s); + + gi.linkentity(ent); +} + +/* +============================================================================== + +TOSS / BOUNCE + +============================================================================== +*/ + +/* +============= +SV_Physics_Toss + +Toss, bounce, and fly movement. When onground, do nothing. +============= +*/ +void SV_Physics_Toss(edict_t *ent) +{ + trace_t trace; + vec3_t move; + float backoff; + edict_t *slave; + bool wasinwater; + bool isinwater; + vec3_t old_origin; + + // regular thinking + SV_RunThink(ent); + + if (!ent->inuse) + return; + + // if not a team captain, so movement will be handled elsewhere + if (ent->flags & FL_TEAMSLAVE) + return; + + if (ent->velocity[2] > 0) + ent->groundentity = nullptr; + + // check for the groundentity going away + if (ent->groundentity) + if (!ent->groundentity->inuse) + ent->groundentity = nullptr; + + // if onground, return without moving + if (ent->groundentity && ent->gravity > 0.0f) // PGM - gravity hack + { + if (ent->svflags & SVF_MONSTER) + { + M_CatagorizePosition(ent, ent->s.origin, ent->waterlevel, ent->watertype); + M_WorldEffects(ent); + } + + return; + } + + old_origin = ent->s.origin; + + SV_CheckVelocity(ent); + + // add gravity + if (ent->movetype != MOVETYPE_FLY && + ent->movetype != MOVETYPE_FLYMISSILE + // RAFAEL + // move type for rippergun projectile + && ent->movetype != MOVETYPE_WALLBOUNCE + // RAFAEL + ) + SV_AddGravity(ent); + + // move angles + ent->s.angles += (ent->avelocity * gi.frame_time_s); + + // move origin + int num_tries = 5; + float time_left = gi.frame_time_s; + + while (time_left) + { + if (num_tries == 0) + break; + + num_tries--; + move = ent->velocity * time_left; + trace = SV_PushEntity(ent, move); + + if (!ent->inuse) + return; + + if (trace.fraction == 1.f) + break; + // [Paril-KEX] don't build up velocity if we're stuck. + // just assume that the object we hit is our ground. + else if (trace.allsolid) + { + ent->groundentity = trace.ent; + ent->groundentity_linkcount = trace.ent->linkcount; + ent->velocity = {}; + ent->avelocity = {}; + break; + } + + time_left -= time_left * trace.fraction; + + if (ent->movetype == MOVETYPE_TOSS) + ent->velocity = SlideClipVelocity(ent->velocity, trace.plane.normal, 0.5f); + else + { + // RAFAEL + if (ent->movetype == MOVETYPE_WALLBOUNCE) + backoff = 2.0f; + // RAFAEL + else + backoff = 1.6f; + + ent->velocity = ClipVelocity(ent->velocity, trace.plane.normal, backoff); + } + + // RAFAEL + if (ent->movetype == MOVETYPE_WALLBOUNCE) + ent->s.angles = vectoangles(ent->velocity); + // RAFAEL + // stop if on ground + else + { + if (trace.plane.normal[2] > 0.7f) + { + if ((ent->movetype == MOVETYPE_TOSS && ent->velocity.length() < 60.f) || + (ent->movetype != MOVETYPE_TOSS && ent->velocity.scaled(trace.plane.normal).length() < 60.f)) + { + if (!(ent->flags & FL_NO_STANDING) || trace.ent->solid == SOLID_BSP) + { + ent->groundentity = trace.ent; + ent->groundentity_linkcount = trace.ent->linkcount; + } + ent->velocity = {}; + ent->avelocity = {}; + break; + } + + // friction for tossing stuff (gibs, etc) + if (ent->movetype == MOVETYPE_TOSS) + { + ent->velocity *= 0.75f; + ent->avelocity *= 0.75f; + } + } + } + + // only toss "slides" multiple times + if (ent->movetype != MOVETYPE_TOSS) + break; + } + + // check for water transition + wasinwater = (ent->watertype & MASK_WATER); + ent->watertype = gi.pointcontents(ent->s.origin); + isinwater = ent->watertype & MASK_WATER; + + if (isinwater) + ent->waterlevel = WATER_FEET; + else + ent->waterlevel = WATER_NONE; + + if (ent->svflags & SVF_MONSTER) + { + M_CatagorizePosition(ent, ent->s.origin, ent->waterlevel, ent->watertype); + M_WorldEffects(ent); + } + else + { + if (!wasinwater && isinwater) + gi.positioned_sound(old_origin, g_edicts, CHAN_AUTO, gi.soundindex("misc/h2ohit1.wav"), 1, 1, 0); + else if (wasinwater && !isinwater) + gi.positioned_sound(ent->s.origin, g_edicts, CHAN_AUTO, gi.soundindex("misc/h2ohit1.wav"), 1, 1, 0); + } + + // prevent softlocks from keys falling into slime/lava + if (isinwater && ent->watertype & (CONTENTS_SLIME | CONTENTS_LAVA) && ent->item && + (ent->item->flags & IF_KEY) && ent->spawnflags.has(SPAWNFLAG_ITEM_DROPPED)) + ent->velocity = { crandom_open() * 300, crandom_open() * 300, 300.f + (crandom_open() * 300.f) }; + + // move teamslaves + for (slave = ent->teamchain; slave; slave = slave->teamchain) + { + slave->s.origin = ent->s.origin; + gi.linkentity(slave); + } +} + +/* +=============================================================================== + +STEPPING MOVEMENT + +=============================================================================== +*/ + +/* +============= +SV_Physics_Step + +Monsters freefall when they don't have a ground entity, otherwise +all movement is done with discrete steps. + +This is also used for objects that have become still on the ground, but +will fall if the floor is pulled out from under them. +============= +*/ + +void SV_AddRotationalFriction(edict_t *ent) +{ + int n; + float adjustment; + + ent->s.angles += (ent->avelocity * gi.frame_time_s); + adjustment = gi.frame_time_s * sv_stopspeed->value * sv_friction; // PGM now a cvar + + for (n = 0; n < 3; n++) + { + if (ent->avelocity[n] > 0) + { + ent->avelocity[n] -= adjustment; + if (ent->avelocity[n] < 0) + ent->avelocity[n] = 0; + } + else + { + ent->avelocity[n] += adjustment; + if (ent->avelocity[n] > 0) + ent->avelocity[n] = 0; + } + } +} + +void SV_Physics_Step(edict_t *ent) +{ + bool wasonground; + bool hitsound = false; + float *vel; + float speed, newspeed, control; + float friction; + edict_t *groundentity; + contents_t mask = G_GetClipMask(ent); + + // airborne monsters should always check for ground + if (!ent->groundentity) + M_CheckGround(ent, mask); + + groundentity = ent->groundentity; + + SV_CheckVelocity(ent); + + if (groundentity) + wasonground = true; + else + wasonground = false; + + if (ent->avelocity[0] || ent->avelocity[1] || ent->avelocity[2]) + SV_AddRotationalFriction(ent); + + // FIXME: figure out how or why this is happening + if (isnan(ent->velocity[0]) || isnan(ent->velocity[1]) || isnan(ent->velocity[2])) + ent->velocity = {}; + + // add gravity except: + // flying monsters + // swimming monsters who are in the water + if (!wasonground) + if (!(ent->flags & FL_FLY)) + if (!((ent->flags & FL_SWIM) && (ent->waterlevel > WATER_WAIST))) + { + if (ent->velocity[2] < level.gravity * -0.1f) + hitsound = true; + if (ent->waterlevel != WATER_UNDER) + SV_AddGravity(ent); + } + + // friction for flying monsters that have been given vertical velocity + if ((ent->flags & FL_FLY) && (ent->velocity[2] != 0) && !(ent->monsterinfo.aiflags & AI_ALTERNATE_FLY)) + { + speed = fabsf(ent->velocity[2]); + control = speed < sv_stopspeed->value ? sv_stopspeed->value : speed; + friction = sv_friction / 3; + newspeed = speed - (gi.frame_time_s * control * friction); + if (newspeed < 0) + newspeed = 0; + newspeed /= speed; + ent->velocity[2] *= newspeed; + } + + // friction for flying monsters that have been given vertical velocity + if ((ent->flags & FL_SWIM) && (ent->velocity[2] != 0) && !(ent->monsterinfo.aiflags & AI_ALTERNATE_FLY)) + { + speed = fabsf(ent->velocity[2]); + control = speed < sv_stopspeed->value ? sv_stopspeed->value : speed; + newspeed = speed - (gi.frame_time_s * control * sv_waterfriction * (float) ent->waterlevel); + if (newspeed < 0) + newspeed = 0; + newspeed /= speed; + ent->velocity[2] *= newspeed; + } + + if (ent->velocity[2] || ent->velocity[1] || ent->velocity[0]) + { + // apply friction + if ((wasonground || (ent->flags & (FL_SWIM | FL_FLY))) && !(ent->monsterinfo.aiflags & AI_ALTERNATE_FLY)) + { + vel = &ent->velocity.x; + speed = sqrtf(vel[0] * vel[0] + vel[1] * vel[1]); + if (speed) + { + friction = sv_friction; + + // Paril: lower friction for dead monsters + if (ent->deadflag) + friction *= 0.5f; + + control = speed < sv_stopspeed->value ? sv_stopspeed->value : speed; + newspeed = speed - gi.frame_time_s * control * friction; + + if (newspeed < 0) + newspeed = 0; + newspeed /= speed; + + vel[0] *= newspeed; + vel[1] *= newspeed; + } + } + + vec3_t old_origin = ent->s.origin; + + SV_FlyMove(ent, gi.frame_time_s, mask); + + G_TouchProjectiles(ent, old_origin); + + M_CheckGround(ent, mask); + + gi.linkentity(ent); + + // ======== + // PGM - reset this every time they move. + // G_touchtriggers will set it back if appropriate + ent->gravity = 1.0; + // ======== + + G_TouchTriggers(ent); + if (!ent->inuse) + return; + + if (ent->groundentity) + if (!wasonground) + if (hitsound) + ent->s.event = EV_FOOTSTEP; + } + + if (!ent->inuse) // PGM g_touchtrigger free problem + return; + + if (ent->svflags & SVF_MONSTER) + { + M_CatagorizePosition(ent, ent->s.origin, ent->waterlevel, ent->watertype); + M_WorldEffects(ent); + + // [Paril-KEX] last minute hack to fix Stalker upside down gravity + if (wasonground != !!ent->groundentity) + { + if (ent->monsterinfo.physics_change) + ent->monsterinfo.physics_change(ent); + } + } + + // regular thinking + SV_RunThink(ent); +} + +// [Paril-KEX] +inline void G_RunBmodelAnimation(edict_t *ent) +{ + auto &anim = ent->bmodel_anim; + + if (anim.currently_alternate != anim.alternate) + { + anim.currently_alternate = anim.alternate; + anim.next_tick = 0_ms; + } + + if (level.time < anim.next_tick) + return; + + const auto &speed = anim.alternate ? anim.alt_speed : anim.speed; + + anim.next_tick = level.time + gtime_t::from_ms(speed); + + const auto &style = anim.alternate ? anim.alt_style : anim.style; + + const auto &start = anim.alternate ? anim.alt_start : anim.start; + const auto &end = anim.alternate ? anim.alt_end : anim.end; + + switch (style) + { + case BMODEL_ANIM_FORWARDS: + if (end >= start) + ent->s.frame++; + else + ent->s.frame--; + break; + case BMODEL_ANIM_BACKWARDS: + if (end >= start) + ent->s.frame--; + else + ent->s.frame++; + break; + case BMODEL_ANIM_RANDOM: + ent->s.frame = irandom(start, end + 1); + break; + } + + const auto &nowrap = anim.alternate ? anim.alt_nowrap : anim.nowrap; + + if (nowrap) + { + if (end >= start) + ent->s.frame = clamp(ent->s.frame, start, end); + else + ent->s.frame = clamp(ent->s.frame, end, start); + } + else + { + if (ent->s.frame < start) + ent->s.frame = end; + else if (ent->s.frame > end) + ent->s.frame = start; + } +} + +//============================================================================ + +/* +================ +G_RunEntity + +================ +*/ +void G_RunEntity(edict_t *ent) +{ + // PGM + trace_t trace; + vec3_t previous_origin; + bool has_previous_origin = false; + + if (ent->movetype == MOVETYPE_STEP) + { + previous_origin = ent->s.origin; + has_previous_origin = true; + } + // PGM + + if (ent->prethink) + ent->prethink(ent); + + // bmodel animation stuff runs first, so custom entities + // can override them + if (ent->bmodel_anim.enabled) + G_RunBmodelAnimation(ent); + + switch ((int) ent->movetype) + { + case MOVETYPE_PUSH: + case MOVETYPE_STOP: + SV_Physics_Pusher(ent); + break; + case MOVETYPE_NONE: + SV_Physics_None(ent); + break; + case MOVETYPE_NOCLIP: + SV_Physics_Noclip(ent); + break; + case MOVETYPE_STEP: + SV_Physics_Step(ent); + break; + case MOVETYPE_TOSS: + case MOVETYPE_BOUNCE: + case MOVETYPE_FLY: + case MOVETYPE_FLYMISSILE: + // RAFAEL + case MOVETYPE_WALLBOUNCE: + // RAFAEL + SV_Physics_Toss(ent); + break; + // ROGUE + case MOVETYPE_NEWTOSS: + SV_Physics_NewToss(ent); + break; + // ROGUE + default: + gi.Com_ErrorFmt("SV_Physics: bad movetype {}", (int32_t) ent->movetype); + } + + // PGM + if (has_previous_origin && ent->movetype == MOVETYPE_STEP) + { + // if we moved, check and fix origin if needed + if (ent->s.origin != previous_origin) + { + trace = gi.trace(ent->s.origin, ent->mins, ent->maxs, previous_origin, ent, G_GetClipMask(ent)); + if (trace.allsolid || trace.startsolid) + ent->s.origin = previous_origin; + } + } + // PGM + +#if 0 + // disintegrator stuff; only for non-players + if (ent->disintegrator_time) + { + if (ent->disintegrator_time > 100_sec) + { + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_BOSSTPORT); + gi.WritePosition(ent->s.origin); + gi.multicast(ent->s.origin, MULTICAST_PHS, false); + + Killed(ent, ent->disintegrator, ent->disintegrator, 999999, vec3_origin, MOD_NUKE); + G_FreeEdict(ent); + } + + ent->disintegrator_time = max(0_ms, ent->disintegrator_time - (15000_ms / gi.tick_rate)); + + if (ent->disintegrator_time) + ent->s.alpha = max(1 / 255.f, 1.f - (ent->disintegrator_time.seconds() / 100.f)); + else + ent->s.alpha = 1; + } +#endif + + if (ent->postthink) + ent->postthink(ent); +} diff --git a/rerelease/g_save.cpp b/rerelease/g_save.cpp new file mode 100644 index 0000000..44d8b56 --- /dev/null +++ b/rerelease/g_save.cpp @@ -0,0 +1,2473 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. + +#include + +#include "g_local.h" +#include +#ifdef __clang__ +#pragma clang diagnostic push +#pragma GCC diagnostic ignored "-Weverything" +#endif +#include "json/json.h" +#include "json/config.h" +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +// new save format; +// - simple JSON format +// - work via 'type' definitions which declare the type +// that is at the specified offset of a struct +// - backwards & forwards compatible with this same format +// - I wrote this initially when the codebase was in C, so it +// does have some C-isms in here. +constexpr size_t SAVE_FORMAT_VERSION = 1; + +#include + +// Professor Daniel J. Bernstein; https://www.partow.net/programming/hashfunctions/#APHashFunction MIT +struct cstring_hash +{ + inline std::size_t operator()(const char *str) const + { + size_t len = strlen(str); + uint32_t hash = 5381; + uint32_t i = 0; + + for (i = 0; i < len; ++str, ++i) + hash = ((hash << 5) + hash) + (*str); + + return hash; + } +}; + +struct cstring_equal +{ + inline bool operator()(const char *a, const char *b) const + { + return strcmp(a, b) == 0; + } +}; + +struct ptr_tag_hash +{ + inline std::size_t operator()(const std::tuple &v) const + { + return (std::hash()(std::get<0>(v)) * 8747) + std::get<1>(v); + } +}; + +static bool save_data_initialized = false; +static const save_data_list_t *list_head = nullptr; +static std::unordered_map list_hash; +static std::unordered_map list_str_hash; +static std::unordered_map, const save_data_list_t *, ptr_tag_hash> list_from_ptr_hash; + +#include + +void InitSave() +{ + if (save_data_initialized) + return; + + for (const save_data_list_t *link = list_head; link; link = link->next) + { + const void *link_ptr = link; + + if (list_hash.find(link_ptr) != list_hash.end()) + { + auto existing = *list_hash.find(link_ptr); + + // [0] is just to silence warning + assert(false || "invalid save pointer; break here to find which pointer it is"[0]); + + if (g_strict_saves->integer) + gi.Com_ErrorFmt("link pointer {} already linked as {}; fatal error", link_ptr, existing.second->name); + else + gi.Com_PrintFmt("link pointer {} already linked as {}; fatal error", link_ptr, existing.second->name); + } + + if (list_str_hash.find(link->name) != list_str_hash.end()) + { + auto existing = *list_str_hash.find(link->name); + + // [0] is just to silence warning + assert(false || "invalid save pointer; break here to find which pointer it is"[0]); + + if (g_strict_saves->integer) + gi.Com_ErrorFmt("link pointer {} already linked as {}; fatal error", link_ptr, existing.second->name); + else + gi.Com_PrintFmt("link pointer {} already linked as {}; fatal error", link_ptr, existing.second->name); + } + + list_hash.emplace(link_ptr, link); + list_str_hash.emplace(link->name, link); + list_from_ptr_hash.emplace(std::make_tuple(link->ptr, link->tag), link); + } + + save_data_initialized = true; +} + +// initializer for save data +save_data_list_t::save_data_list_t(const char *name_in, save_data_tag_t tag_in, const void *ptr_in) : + name(name_in), + tag(tag_in), + ptr(ptr_in) +{ + if (save_data_initialized) + gi.Com_Error("attempted to create save_data_list at runtime"); + + next = list_head; + list_head = this; +} + +const save_data_list_t *save_data_list_t::fetch(const void *ptr, save_data_tag_t tag) +{ + auto link = list_from_ptr_hash.find(std::make_tuple(ptr, tag)); + + if (link != list_from_ptr_hash.end() && link->second->tag == tag) + return link->second; + + // [0] is just to silence warning + assert(false || "invalid save pointer; break here to find which pointer it is"[0]); + + if (g_strict_saves->integer) + gi.Com_ErrorFmt("value pointer {} was not linked to save tag {}", ptr, (int32_t) tag); + else + gi.Com_PrintFmt("value pointer {} was not linked to save tag {}", ptr, (int32_t) tag); + + return nullptr; +} + +std::string json_error_stack; + +void json_push_stack(const std::string &stack) +{ + json_error_stack += "::" + stack; +} + +void json_pop_stack() +{ + size_t o = json_error_stack.find_last_of("::"); + + if (o != std::string::npos) + json_error_stack.resize(o - 1); +} + +void json_print_error(const char *field, const char *message, bool fatal) +{ + if (fatal || g_strict_saves->integer) + gi.Com_ErrorFmt("Error loading JSON\n{}.{}: {}", json_error_stack, field, message); + + gi.Com_PrintFmt("Warning loading JSON\n{}.{}: {}\n", json_error_stack, field, message); +} + +using save_void_t = save_data_t; + +enum save_type_id_t +{ + // never valid + ST_INVALID, + + // integral types + ST_BOOL, + ST_INT8, + ST_INT16, + ST_INT32, + ST_INT64, + ST_UINT8, + ST_UINT16, + ST_UINT32, + ST_UINT64, + ST_ENUM, // "count" = sizeof(enum_type) + + // floating point + ST_FLOAT, + ST_DOUBLE, + + ST_STRING, // "tag" = memory tag, "count" = fixed length if specified, otherwise dynamic + + ST_FIXED_STRING, // "count" = length + + ST_FIXED_ARRAY, + // for simple types (ones that don't require extra info), "tag" = type and "count" = N + // otherwise, "type_resolver" for nested arrays, structures, string/function arrays, etc + + ST_STRUCT, // "count" = sizeof(struct), "structure" = ptr to save_struct_t to save + + ST_BITSET, // bitset; "count" = N bits + + // special Quake types + ST_ENTITY, // serialized as s.number + ST_ITEM_POINTER, // serialized as classname + ST_ITEM_INDEX, // serialized as classname + ST_TIME, // serialized as milliseconds + ST_DATA, // serialized as name of data ptr from global list; `tag` = list tag + ST_INVENTORY, // serialized as classname => number key/value pairs + ST_REINFORCEMENTS // serialized as array of data +}; + +struct save_struct_t; + +struct save_type_t +{ + save_type_id_t id; + int32_t tag = 0; + size_t count = 0; + save_type_t (*type_resolver)() = nullptr; + const save_struct_t *structure = nullptr; + bool never_empty = false; // this should be persisted even if all empty + bool (*is_empty)(const void *data) = nullptr; // override default check + + void (*read)(void *data, const Json::Value &json, const char *field) = nullptr; // for custom reading + bool (*write)(const void *data, bool null_for_empty, Json::Value &output) = nullptr; // for custom writing +}; + +struct save_field_t +{ + const char *name; + size_t offset; + save_type_t type; + + // for easily chaining with FIELD_AUTO + constexpr save_field_t &set_is_empty(decltype(save_type_t::is_empty) empty) + { + type.is_empty = empty; + return *this; + } +}; + +struct save_struct_t +{ + const char *name; + const std::initializer_list fields; // field list + + std::string debug() const + { + std::stringstream s; + + for (auto &field : fields) + s << field.name << " " << field.offset << " " << field.type.id << " " << field.type.tag << " " + << field.type.count << '\n'; + + return s.str(); + } +}; + +// field header macro +#define SAVE_FIELD(n, f) #f, offsetof(n, f) + +// save struct header macro +#define INTERNAL_SAVE_STRUCT_START2(struct_name) static const save_struct_t struct_name##_savestruct = { #struct_name, { +#define INTERNAL_SAVE_STRUCT_START1(struct_name) INTERNAL_SAVE_STRUCT_START2(struct_name) +#define SAVE_STRUCT_START INTERNAL_SAVE_STRUCT_START1(DECLARE_SAVE_STRUCT) + +#define SAVE_STRUCT_END \ + } \ + }; + +// field header macro for current struct +#define FIELD(f) SAVE_FIELD(DECLARE_SAVE_STRUCT, f) + +template +struct save_type_deducer +{ + static constexpr save_field_t get_save_type(const char *name, size_t offset) + { + static_assert( + !std::is_same_v, + "Can't automatically deduce type for save; implement save_type_deducer or use an explicit FIELD_ macro."); + return {}; + } +}; + +// bool +template<> +struct save_type_deducer +{ + static constexpr save_field_t get_save_type(const char *name, size_t offset) + { + return { name, offset, { ST_BOOL } }; + } +}; + +// integral +template<> +struct save_type_deducer +{ + static constexpr save_field_t get_save_type(const char *name, size_t offset) + { + return { name, offset, { ST_INT8 } }; + } +}; +template<> +struct save_type_deducer +{ + static constexpr save_field_t get_save_type(const char *name, size_t offset) + { + return { name, offset, { ST_INT16 } }; + } +}; +template<> +struct save_type_deducer +{ + static constexpr save_field_t get_save_type(const char *name, size_t offset) + { + return { name, offset, { ST_INT32 } }; + } +}; +template<> +struct save_type_deducer +{ + static constexpr save_field_t get_save_type(const char *name, size_t offset) + { + return { name, offset, { ST_INT64 } }; + } +}; +template<> +struct save_type_deducer +{ + static constexpr save_field_t get_save_type(const char *name, size_t offset) + { + return { name, offset, { ST_UINT8 } }; + } +}; +template<> +struct save_type_deducer +{ + static constexpr save_field_t get_save_type(const char *name, size_t offset) + { + return { name, offset, { ST_UINT16 } }; + } +}; +template<> +struct save_type_deducer +{ + static constexpr save_field_t get_save_type(const char *name, size_t offset) + { + return { name, offset, { ST_UINT32 } }; + } +}; +template<> +struct save_type_deducer +{ + static constexpr save_field_t get_save_type(const char *name, size_t offset) + { + return { name, offset, { ST_UINT64 } }; + } +}; + +// floating point +template<> +struct save_type_deducer +{ + static constexpr save_field_t get_save_type(const char *name, size_t offset) + { + return { name, offset, { ST_FLOAT } }; + } +}; +template<> +struct save_type_deducer +{ + static constexpr save_field_t get_save_type(const char *name, size_t offset) + { + return { name, offset, { ST_DOUBLE } }; + } +}; + +// special types +template<> +struct save_type_deducer +{ + static constexpr save_field_t get_save_type(const char *name, size_t offset) + { + return { name, offset, { ST_ENTITY } }; + } +}; + +template<> +struct save_type_deducer +{ + static constexpr save_field_t get_save_type(const char *name, size_t offset) + { + return { name, offset, { ST_ITEM_POINTER } }; + } +}; + +template<> +struct save_type_deducer +{ + static constexpr save_field_t get_save_type(const char *name, size_t offset) + { + return { name, offset, { ST_ITEM_INDEX } }; + } +}; + +template<> +struct save_type_deducer +{ + static constexpr save_field_t get_save_type(const char *name, size_t offset) + { + return { name, offset, { ST_TIME } }; + } +}; + +template<> +struct save_type_deducer +{ + static constexpr save_field_t get_save_type(const char *name, size_t offset) + { + return { name, offset, { ST_UINT32 } }; + } +}; + +// static strings +template +struct save_type_deducer +{ + static constexpr save_field_t get_save_type(const char *name, size_t offset) + { + return { name, offset, { ST_FIXED_STRING, 0, N } }; + } +}; + +// enums +template +struct save_type_deducer>> +{ + static constexpr save_field_t get_save_type(const char *name, size_t offset) + { + return { name, + offset, + { ST_ENUM, 0, + std::is_same_v, uint64_t> ? 8 + : std::is_same_v, uint32_t> ? 4 + : std::is_same_v, uint16_t> ? 2 + : std::is_same_v, uint8_t> ? 1 + : std::is_same_v, int64_t> ? 8 + : std::is_same_v, int32_t> ? 4 + : std::is_same_v, int16_t> ? 2 + : std::is_same_v, int8_t> ? 1 + : 0 } }; + } +}; + +// vector +template<> +struct save_type_deducer +{ + static constexpr save_field_t get_save_type(const char *name, size_t offset) + { + return { name, offset, { ST_FIXED_ARRAY, ST_FLOAT, 3 } }; + } +}; + +// fixed-size arrays +template +struct save_type_deducer>> +{ + static constexpr save_field_t get_save_type(const char *name, size_t offset) + { + auto type = save_type_deducer>::get_save_type(nullptr, 0).type; + + if (type.id <= ST_BOOL || type.id >= ST_DOUBLE) + return { name, offset, { ST_FIXED_ARRAY, ST_INVALID, 0, []() { return save_type_deducer>::get_save_type(nullptr, 0).type; } } }; + + return { name, + offset, + { ST_FIXED_ARRAY, type.id, N } }; + } +}; + +// std::array +template +struct save_type_deducer> +{ + static constexpr save_field_t get_save_type(const char *name, size_t offset) + { + auto type = save_type_deducer>::get_save_type(nullptr, 0).type; + + if (type.id <= ST_BOOL || type.id >= ST_DOUBLE) + return { name, offset, { ST_FIXED_ARRAY, ST_INVALID, N, []() { return save_type_deducer>::get_save_type(nullptr, 0).type; } } }; + + return { name, offset, { ST_FIXED_ARRAY, type.id, N } }; + } +}; + +// save_data_ref +template +struct save_type_deducer> +{ + static constexpr save_field_t get_save_type(const char *name, size_t offset) + { + return { name, offset, { ST_DATA, Tag, 0, nullptr, nullptr, false, nullptr } }; + } +}; + +// std::bitset +template +struct save_type_deducer> +{ + static constexpr save_field_t get_save_type(const char *name, size_t offset) + { + return { name, offset, { ST_BITSET, 0, N, nullptr, nullptr, false, nullptr, + [](void *data, const Json::Value &json, const char *field) { + std::bitset &as_bitset = *(std::bitset *) data; + + as_bitset.reset(); + + if (!json.isString()) + json_print_error(field, "expected string", false); + else if (strlen(json.asCString()) > N) + json_print_error(field, "bitset length overflow", false); + else + { + const char *str = json.asCString(); + size_t len = strlen(str); + + for (size_t i = 0; i < len; i++) + { + if (str[i] == '0') + continue; + else if (str[i] == '1') + as_bitset[i] = true; + else + json_print_error(field, "bad bitset value", false); + } + } + }, + [](const void *data, bool null_for_empty, Json::Value &output) -> bool { + const std::bitset &as_bitset = *(std::bitset *) data; + + if (as_bitset.none()) + { + if (null_for_empty) + return false; + + output = ""; + return true; + } + + int32_t num_needed; + + for (num_needed = N - 1; num_needed >= 0; num_needed--) + if (as_bitset[num_needed]) + break; + + // 00100000, num_needed = 2 + // num_needed always >= 0 since none() check done above + num_needed++; + + std::string result(num_needed, '0'); + + for (size_t n = 0; n < num_needed; n++) + if (as_bitset[n]) + result[n] = '1'; + + output = result; + + return true; + } + } + }; + } +}; + +// deduce type via template deduction; use this generally +// since it prevents user error and allows seamless type upgrades. +#define FIELD_AUTO(f) save_type_deducer::get_save_type(FIELD(f)) + +// simple macro for a `char*` of TAG_LEVEL allocation +#define FIELD_LEVEL_STRING(f) \ + { \ + FIELD(f), \ + { \ + ST_STRING, TAG_LEVEL \ + } \ + } + +// simple macro for a `char*` of TAG_GAME allocation +#define FIELD_GAME_STRING(f) \ + { \ + FIELD(f), \ + { \ + ST_STRING, TAG_GAME \ + } \ + } + +// simple macro for a struct type +#define FIELD_STRUCT(f, t) \ + { \ + FIELD(f), \ + { \ + ST_STRUCT, 0, sizeof(t), nullptr, &t##_savestruct \ + } \ + } + +// simple macro for a simple field with no parameters +#define FIELD_SIMPLE(f, t) \ + { \ + FIELD(f), \ + { \ + t \ + } \ + } + +// macro for creating save type deducer for +// specified struct type +#define MAKE_STRUCT_SAVE_DEDUCER(t) \ +template<> \ +struct save_type_deducer \ +{ \ + static constexpr save_field_t get_save_type(const char *name, size_t offset) \ + { \ + return { name, offset, { ST_STRUCT, 0, sizeof(t), nullptr, &t##_savestruct } }; \ + } \ +}; + +#define DECLARE_SAVE_STRUCT level_entry_t +SAVE_STRUCT_START + FIELD_AUTO(map_name), + FIELD_AUTO(pretty_name), + FIELD_AUTO(total_secrets), + FIELD_AUTO(found_secrets), + FIELD_AUTO(total_monsters), + FIELD_AUTO(killed_monsters), + FIELD_AUTO(time), + FIELD_AUTO(visit_order) +SAVE_STRUCT_END +#undef DECLARE_SAVE_STRUCT + +MAKE_STRUCT_SAVE_DEDUCER(level_entry_t); + +// clang-format off +#define DECLARE_SAVE_STRUCT game_locals_t +SAVE_STRUCT_START + FIELD_AUTO(helpmessage1), + FIELD_AUTO(helpmessage2), + FIELD_AUTO(help1changed), + FIELD_AUTO(help2changed), + + // clients is set by load/init only + + FIELD_AUTO(spawnpoint), + + FIELD_AUTO(maxclients), + FIELD_AUTO(maxentities), + + FIELD_AUTO(cross_level_flags), + FIELD_AUTO(cross_unit_flags), + + FIELD_AUTO(autosaved), + FIELD_AUTO(level_entries) +SAVE_STRUCT_END +#undef DECLARE_SAVE_STRUCT + +#define DECLARE_SAVE_STRUCT level_locals_t +SAVE_STRUCT_START + FIELD_AUTO(time), + + FIELD_AUTO(level_name), + FIELD_AUTO(mapname), + FIELD_AUTO(nextmap), + + FIELD_AUTO(intermissiontime), + FIELD_LEVEL_STRING(changemap), + FIELD_LEVEL_STRING(achievement), + FIELD_AUTO(exitintermission), + FIELD_AUTO(intermission_clear), + FIELD_AUTO(intermission_origin), + FIELD_AUTO(intermission_angle), + + // pic_health is set by worldspawn + + FIELD_AUTO(total_secrets), + FIELD_AUTO(found_secrets), + + FIELD_AUTO(total_goals), + FIELD_AUTO(found_goals), + + FIELD_AUTO(total_monsters), + FIELD_AUTO(monsters_registered), + FIELD_AUTO(killed_monsters), + + // current_entity not necessary to save + + FIELD_AUTO(body_que), + + FIELD_AUTO(power_cubes), + + // ROGUE + FIELD_AUTO(disguise_violator), + FIELD_AUTO(disguise_violation_time), + // ROGUE + + FIELD_AUTO(coop_level_restart_time), + FIELD_LEVEL_STRING(goals), + FIELD_AUTO(goal_num), + FIELD_AUTO(vwep_offset), + + FIELD_AUTO(valid_poi), + FIELD_AUTO(current_poi), + FIELD_AUTO(current_poi_stage), + FIELD_AUTO(current_poi_image), + FIELD_AUTO(current_dynamic_poi), + + FIELD_LEVEL_STRING(start_items), + FIELD_AUTO(no_grapple), + FIELD_AUTO(gravity), + FIELD_AUTO(hub_map), + FIELD_AUTO(health_bar_entities), + FIELD_AUTO(intermission_server_frame), + FIELD_AUTO(story_active), + FIELD_AUTO(next_auto_save) +SAVE_STRUCT_END +#undef DECLARE_SAVE_STRUCT + +#define DECLARE_SAVE_STRUCT pmove_state_t +SAVE_STRUCT_START + FIELD_AUTO(pm_type), + FIELD_AUTO(origin), + FIELD_AUTO(velocity), + FIELD_AUTO(pm_flags), + FIELD_AUTO(pm_time), + FIELD_AUTO(gravity), + FIELD_AUTO(delta_angles), + FIELD_AUTO(viewheight) +SAVE_STRUCT_END +#undef DECLARE_SAVE_STRUCT + +#define DECLARE_SAVE_STRUCT player_state_t +SAVE_STRUCT_START + FIELD_STRUCT(pmove, pmove_state_t), + + FIELD_AUTO(viewangles), + FIELD_AUTO(viewoffset), + // kick_angles only last 1 frame + + FIELD_AUTO(gunangles), + FIELD_AUTO(gunoffset), + FIELD_AUTO(gunindex), + FIELD_AUTO(gunframe), + FIELD_AUTO(gunskin), + // blend is calculated by ClientEndServerFrame + FIELD_AUTO(fov), + + // rdflags are generated by ClientEndServerFrame + FIELD_AUTO(stats) +SAVE_STRUCT_END +#undef DECLARE_SAVE_STRUCT + +#define DECLARE_SAVE_STRUCT height_fog_t +SAVE_STRUCT_START + FIELD_AUTO(start), + FIELD_AUTO(end), + FIELD_AUTO(falloff), + FIELD_AUTO(density) +SAVE_STRUCT_END +#undef DECLARE_SAVE_STRUCT + +#define DECLARE_SAVE_STRUCT client_persistant_t +SAVE_STRUCT_START + FIELD_AUTO(userinfo), + FIELD_AUTO(social_id), + FIELD_AUTO(netname), + FIELD_AUTO(hand), + + FIELD_AUTO(health), + FIELD_AUTO(max_health), + FIELD_AUTO(savedFlags), + + FIELD_AUTO(selected_item), + FIELD_SIMPLE(inventory, ST_INVENTORY), + + FIELD_AUTO(max_ammo), + + FIELD_AUTO(weapon), + FIELD_AUTO(lastweapon), + + FIELD_AUTO(power_cubes), + FIELD_AUTO(score), + + FIELD_AUTO(game_help1changed), + FIELD_AUTO(game_help2changed), + FIELD_AUTO(helpchanged), + FIELD_AUTO(help_time), + + FIELD_AUTO(spectator), + + // save the wanted fog, but not the current fog + // or transition time so it sends immediately + FIELD_AUTO(wanted_fog), + FIELD_STRUCT(wanted_heightfog, height_fog_t), + FIELD_AUTO(megahealth_time), + FIELD_AUTO(lives), + FIELD_AUTO(n64_crouch_warn_times), + FIELD_AUTO(n64_crouch_warning) +SAVE_STRUCT_END +#undef DECLARE_SAVE_STRUCT + +#define DECLARE_SAVE_STRUCT gclient_t +SAVE_STRUCT_START + FIELD_STRUCT(ps, player_state_t), + // ping... duh + + FIELD_STRUCT(pers, client_persistant_t), + + FIELD_STRUCT(resp.coop_respawn, client_persistant_t), + FIELD_AUTO(resp.entertime), + FIELD_AUTO(resp.score), + FIELD_AUTO(resp.cmd_angles), + FIELD_AUTO(resp.spectator), + // old_pmove is not necessary to persist + + // showscores, showinventory, showhelp not necessary + + // buttons, oldbuttons, latched_buttons not necessary + // weapon_thunk not necessary + + FIELD_AUTO(newweapon), + + // damage_ members are calculated on damage + + FIELD_AUTO(killer_yaw), + + FIELD_AUTO(weaponstate), + FIELD_AUTO(kick.angles), + FIELD_AUTO(kick.origin), + FIELD_AUTO(kick.total), + FIELD_AUTO(kick.time), + FIELD_AUTO(quake_time), + FIELD_AUTO(v_dmg_roll), + FIELD_AUTO(v_dmg_pitch), + FIELD_AUTO(v_dmg_time), + FIELD_AUTO(fall_time), + FIELD_AUTO(fall_value), + FIELD_AUTO(damage_alpha), + FIELD_AUTO(bonus_alpha), + FIELD_AUTO(damage_blend), + FIELD_AUTO(v_angle), + FIELD_AUTO(bobtime), + FIELD_AUTO(oldviewangles), + FIELD_AUTO(oldvelocity), + FIELD_AUTO(oldgroundentity), + + FIELD_AUTO(next_drown_time), + FIELD_AUTO(old_waterlevel), + FIELD_AUTO(breather_sound), + + FIELD_AUTO(machinegun_shots), + + FIELD_AUTO(anim_end), + FIELD_AUTO(anim_priority), + FIELD_AUTO(anim_duck), + FIELD_AUTO(anim_run), + + FIELD_AUTO(quad_time), + FIELD_AUTO(invincible_time), + FIELD_AUTO(breather_time), + FIELD_AUTO(enviro_time), + FIELD_AUTO(invisible_time), + + FIELD_AUTO(grenade_blew_up), + FIELD_AUTO(grenade_time), + FIELD_AUTO(grenade_finished_time), + FIELD_AUTO(quadfire_time), + FIELD_AUTO(silencer_shots), + FIELD_AUTO(weapon_sound), + + FIELD_AUTO(pickup_msg_time), + + // flood stuff is dm only + + FIELD_AUTO(respawn_time), + + // chasecam not required to persist + + FIELD_AUTO(double_time), + FIELD_AUTO(ir_time), + FIELD_AUTO(nuke_time), + FIELD_AUTO(tracker_pain_time), + + // owned_sphere is DM only + + FIELD_AUTO(empty_click_sound), + FIELD_AUTO(trail_head), + FIELD_AUTO(trail_tail), + + FIELD_GAME_STRING(landmark_name), + FIELD_AUTO(landmark_rel_pos), + FIELD_AUTO(landmark_free_fall), + FIELD_AUTO(landmark_noise_time), + FIELD_AUTO(invisibility_fade_time), + FIELD_AUTO(last_ladder_pos), + FIELD_AUTO(last_ladder_sound), + + FIELD_AUTO(sight_entity), + FIELD_AUTO(sight_entity_time), + FIELD_AUTO(sound_entity), + FIELD_AUTO(sound_entity_time), + FIELD_AUTO(sound2_entity), + FIELD_AUTO(sound2_entity_time), +SAVE_STRUCT_END +#undef DECLARE_SAVE_STRUCT +// clang-format on + +static bool edict_t_gravity_is_empty(const void *data) +{ + return *((const float *) data) == 1.f; +} + +static bool edict_t_gravityVector_is_empty(const void *data) +{ + constexpr vec3_t up_vector = { 0, 0, -1 }; + return *(const vec3_t *) data == up_vector; +} + +// clang-format off +#define DECLARE_SAVE_STRUCT edict_t +SAVE_STRUCT_START + // entity_state_t stuff; only one instance + // so no need to do a whole save struct + FIELD_AUTO(s.origin), + FIELD_AUTO(s.angles), + FIELD_AUTO(s.old_origin), + FIELD_AUTO(s.modelindex), + FIELD_AUTO(s.modelindex2), + FIELD_AUTO(s.modelindex3), + FIELD_AUTO(s.modelindex4), + FIELD_AUTO(s.frame), + FIELD_AUTO(s.skinnum), + FIELD_AUTO(s.effects), + FIELD_AUTO(s.renderfx), + // s.solid is set by linkentity + // events are cleared on each frame, no need to save + FIELD_AUTO(s.sound), + FIELD_AUTO(s.alpha), + FIELD_AUTO(s.scale), + FIELD_AUTO(s.instance_bits), + + // server stuff + // client is auto-set + // inuse is implied + FIELD_AUTO(linkcount), + // area, num_clusters, clusternums, headnode, areanum, areanum2 + // are set by linkentity and can't be saved + + FIELD_AUTO(svflags), + FIELD_AUTO(mins), + FIELD_AUTO(maxs), + // absmin, absmax and size are set by linkentity + FIELD_AUTO(solid), + FIELD_AUTO(clipmask), + FIELD_AUTO(owner), + + // game stuff + FIELD_AUTO(spawn_count), + FIELD_AUTO(movetype), + FIELD_AUTO(flags), + + FIELD_LEVEL_STRING(model), + FIELD_AUTO(freetime), + + FIELD_LEVEL_STRING(message), + FIELD_LEVEL_STRING(classname), // FIXME: should allow loading from constants + FIELD_AUTO(spawnflags), + + FIELD_AUTO(timestamp), + + FIELD_AUTO(angle), + FIELD_LEVEL_STRING(target), + FIELD_LEVEL_STRING(targetname), + FIELD_LEVEL_STRING(killtarget), + FIELD_LEVEL_STRING(team), + FIELD_LEVEL_STRING(pathtarget), + FIELD_LEVEL_STRING(deathtarget), + FIELD_LEVEL_STRING(healthtarget), + FIELD_LEVEL_STRING(itemtarget), + FIELD_LEVEL_STRING(combattarget), + FIELD_AUTO(target_ent), + + FIELD_AUTO(speed), + FIELD_AUTO(accel), + FIELD_AUTO(decel), + FIELD_AUTO(movedir), + FIELD_AUTO(pos1), + FIELD_AUTO(pos2), + FIELD_AUTO(pos3), + + FIELD_AUTO(velocity), + FIELD_AUTO(avelocity), + FIELD_AUTO(mass), + FIELD_AUTO(air_finished), + FIELD_AUTO(gravity).set_is_empty(edict_t_gravity_is_empty), + + FIELD_AUTO(goalentity), + FIELD_AUTO(movetarget), + FIELD_AUTO(yaw_speed), + FIELD_AUTO(ideal_yaw), + + FIELD_AUTO(nextthink), + FIELD_AUTO(prethink), + FIELD_AUTO(postthink), + FIELD_AUTO(think), + FIELD_AUTO(touch), + FIELD_AUTO(use), + FIELD_AUTO(pain), + FIELD_AUTO(die), + + FIELD_AUTO(touch_debounce_time), + FIELD_AUTO(pain_debounce_time), + FIELD_AUTO(damage_debounce_time), + FIELD_AUTO(fly_sound_debounce_time), + FIELD_AUTO(last_move_time), + + FIELD_AUTO(health), + FIELD_AUTO(max_health), + FIELD_AUTO(gib_health), + FIELD_AUTO(deadflag), + FIELD_AUTO(show_hostile), + + FIELD_AUTO(powerarmor_time), + + FIELD_LEVEL_STRING(map), + + FIELD_AUTO(viewheight), + FIELD_AUTO(takedamage), + FIELD_AUTO(dmg), + FIELD_AUTO(radius_dmg), + FIELD_AUTO(dmg_radius), + FIELD_AUTO(sounds), + FIELD_AUTO(count), + + FIELD_AUTO(chain), + FIELD_AUTO(enemy), + FIELD_AUTO(oldenemy), + FIELD_AUTO(activator), + FIELD_AUTO(groundentity), + FIELD_AUTO(groundentity_linkcount), + FIELD_AUTO(teamchain), + FIELD_AUTO(teammaster), + + FIELD_AUTO(mynoise), + FIELD_AUTO(mynoise2), + + FIELD_AUTO(noise_index), + FIELD_AUTO(noise_index2), + FIELD_AUTO(volume), + FIELD_AUTO(attenuation), + + FIELD_AUTO(wait), + FIELD_AUTO(delay), + FIELD_AUTO(random), + + FIELD_AUTO(teleport_time), + + FIELD_AUTO(watertype), + FIELD_AUTO(waterlevel), + + FIELD_AUTO(move_origin), + FIELD_AUTO(move_angles), + + FIELD_AUTO(style), + FIELD_LEVEL_STRING(style_on), + FIELD_LEVEL_STRING(style_off), + + FIELD_AUTO(item), + FIELD_AUTO(crosslevel_flags), + + // moveinfo_t + FIELD_AUTO(moveinfo.start_origin), + FIELD_AUTO(moveinfo.start_angles), + FIELD_AUTO(moveinfo.end_origin), + FIELD_AUTO(moveinfo.end_angles), + FIELD_AUTO(moveinfo.end_angles_reversed), + + FIELD_AUTO(moveinfo.sound_start), + FIELD_AUTO(moveinfo.sound_middle), + FIELD_AUTO(moveinfo.sound_end), + + FIELD_AUTO(moveinfo.accel), + FIELD_AUTO(moveinfo.speed), + FIELD_AUTO(moveinfo.decel), + FIELD_AUTO(moveinfo.distance), + + FIELD_AUTO(moveinfo.wait), + + FIELD_AUTO(moveinfo.state), + FIELD_AUTO(moveinfo.reversing), + FIELD_AUTO(moveinfo.dir), + FIELD_AUTO(moveinfo.dest), + FIELD_AUTO(moveinfo.current_speed), + FIELD_AUTO(moveinfo.move_speed), + FIELD_AUTO(moveinfo.next_speed), + FIELD_AUTO(moveinfo.remaining_distance), + FIELD_AUTO(moveinfo.decel_distance), + FIELD_AUTO(moveinfo.endfunc), + FIELD_AUTO(moveinfo.blocked), + + // monsterinfo_t + FIELD_AUTO(monsterinfo.active_move), + FIELD_AUTO(monsterinfo.next_move), + FIELD_AUTO(monsterinfo.aiflags), + FIELD_AUTO(monsterinfo.nextframe), + FIELD_AUTO(monsterinfo.scale), + + FIELD_AUTO(monsterinfo.stand), + FIELD_AUTO(monsterinfo.idle), + FIELD_AUTO(monsterinfo.search), + FIELD_AUTO(monsterinfo.walk), + FIELD_AUTO(monsterinfo.run), + FIELD_AUTO(monsterinfo.dodge), + FIELD_AUTO(monsterinfo.attack), + FIELD_AUTO(monsterinfo.melee), + FIELD_AUTO(monsterinfo.sight), + FIELD_AUTO(monsterinfo.checkattack), + FIELD_AUTO(monsterinfo.setskin), + + FIELD_AUTO(monsterinfo.pausetime), + FIELD_AUTO(monsterinfo.attack_finished), + FIELD_AUTO(monsterinfo.fire_wait), + + FIELD_AUTO(monsterinfo.saved_goal), + FIELD_AUTO(monsterinfo.search_time), + FIELD_AUTO(monsterinfo.trail_time), + FIELD_AUTO(monsterinfo.last_sighting), + FIELD_AUTO(monsterinfo.attack_state), + FIELD_AUTO(monsterinfo.lefty), + FIELD_AUTO(monsterinfo.idle_time), + FIELD_AUTO(monsterinfo.linkcount), + + FIELD_AUTO(monsterinfo.power_armor_type), + FIELD_AUTO(monsterinfo.power_armor_power), + FIELD_AUTO(monsterinfo.initial_power_armor_type), + FIELD_AUTO(monsterinfo.max_power_armor_power), + FIELD_AUTO(monsterinfo.weapon_sound), + FIELD_AUTO(monsterinfo.engine_sound), + + FIELD_AUTO(monsterinfo.blocked), + FIELD_AUTO(monsterinfo.last_hint_time), + FIELD_AUTO(monsterinfo.goal_hint), + FIELD_AUTO(monsterinfo.medicTries), + FIELD_AUTO(monsterinfo.badMedic1), + FIELD_AUTO(monsterinfo.badMedic2), + FIELD_AUTO(monsterinfo.healer), + FIELD_AUTO(monsterinfo.duck), + FIELD_AUTO(monsterinfo.unduck), + FIELD_AUTO(monsterinfo.sidestep), + FIELD_AUTO(monsterinfo.base_height), + FIELD_AUTO(monsterinfo.next_duck_time), + FIELD_AUTO(monsterinfo.duck_wait_time), + FIELD_AUTO(monsterinfo.last_player_enemy), + FIELD_AUTO(monsterinfo.blindfire), + FIELD_AUTO(monsterinfo.can_jump), + FIELD_AUTO(monsterinfo.had_visibility), + FIELD_AUTO(monsterinfo.drop_height), + FIELD_AUTO(monsterinfo.jump_height), + FIELD_AUTO(monsterinfo.blind_fire_delay), + FIELD_AUTO(monsterinfo.blind_fire_target), + FIELD_AUTO(monsterinfo.monster_slots), + FIELD_AUTO(monsterinfo.monster_used), + FIELD_AUTO(monsterinfo.commander), + FIELD_AUTO( monsterinfo.quad_time ), + FIELD_AUTO( monsterinfo.invincible_time ), + FIELD_AUTO( monsterinfo.double_time ), + + FIELD_AUTO( monsterinfo.surprise_time ), + FIELD_AUTO( monsterinfo.armor_type ), + FIELD_AUTO( monsterinfo.armor_power ), + FIELD_AUTO( monsterinfo.close_sight_tripped ), + FIELD_AUTO( monsterinfo.melee_debounce_time ), + FIELD_AUTO( monsterinfo.strafe_check_time ), + FIELD_AUTO( monsterinfo.base_health ), + FIELD_AUTO( monsterinfo.health_scaling ), + FIELD_AUTO( monsterinfo.next_move_time ), + FIELD_AUTO( monsterinfo.bad_move_time ), + FIELD_AUTO( monsterinfo.bump_time ), + FIELD_AUTO( monsterinfo.random_change_time ), + FIELD_AUTO( monsterinfo.path_blocked_counter ), + FIELD_AUTO( monsterinfo.path_wait_time ), + FIELD_AUTO( monsterinfo.combat_style ), + + FIELD_AUTO( monsterinfo.fly_max_distance ), + FIELD_AUTO( monsterinfo.fly_min_distance ), + FIELD_AUTO( monsterinfo.fly_acceleration ), + FIELD_AUTO( monsterinfo.fly_speed ), + FIELD_AUTO( monsterinfo.fly_ideal_position ), + FIELD_AUTO( monsterinfo.fly_position_time ), + FIELD_AUTO( monsterinfo.fly_buzzard ), + FIELD_AUTO( monsterinfo.fly_above ), + FIELD_AUTO( monsterinfo.fly_pinned ), + FIELD_AUTO( monsterinfo.fly_thrusters ), + FIELD_AUTO( monsterinfo.fly_recovery_time ), + FIELD_AUTO( monsterinfo.fly_recovery_dir ), + + FIELD_AUTO( monsterinfo.checkattack_time ), + FIELD_AUTO( monsterinfo.start_frame ), + FIELD_AUTO( monsterinfo.dodge_time ), + FIELD_AUTO( monsterinfo.move_block_counter ), + FIELD_AUTO( monsterinfo.move_block_change_time ), + FIELD_AUTO( monsterinfo.react_to_damage_time ), + FIELD_AUTO(monsterinfo.jump_time), + + FIELD_SIMPLE( monsterinfo.reinforcements, ST_REINFORCEMENTS ), + FIELD_AUTO( monsterinfo.chosen_reinforcements ), + +// back to edict_t + FIELD_AUTO( plat2flags ), + FIELD_AUTO( offset ), + FIELD_AUTO( gravityVector ).set_is_empty( edict_t_gravityVector_is_empty ), + FIELD_AUTO( bad_area ), + FIELD_AUTO( hint_chain ), + FIELD_AUTO( monster_hint_chain ), + FIELD_AUTO( target_hint_chain ), + FIELD_AUTO( hint_chain_id ), + + FIELD_AUTO( clock_message ), + FIELD_AUTO( dead_time ), + FIELD_AUTO( beam ), + FIELD_AUTO( beam2 ), + FIELD_AUTO(proboscus), + FIELD_AUTO( disintegrator ), + FIELD_AUTO( disintegrator_time ), + FIELD_AUTO( hackflags ), + + FIELD_AUTO( fog.color ), + FIELD_AUTO( fog.density ), + FIELD_AUTO( fog.color_off ), + FIELD_AUTO( fog.density_off ), + FIELD_AUTO( fog.sky_factor ), + FIELD_AUTO( fog.sky_factor_off ), + + FIELD_AUTO( heightfog.falloff ), + FIELD_AUTO( heightfog.density ), + FIELD_AUTO( heightfog.start_color ), + FIELD_AUTO( heightfog.start_dist ), + FIELD_AUTO( heightfog.end_color ), + FIELD_AUTO( heightfog.end_dist ), + + FIELD_AUTO( heightfog.falloff_off ), + FIELD_AUTO( heightfog.density_off ), + FIELD_AUTO( heightfog.start_color_off ), + FIELD_AUTO( heightfog.start_dist_off ), + FIELD_AUTO( heightfog.end_color_off ), + FIELD_AUTO( heightfog.end_dist_off ), + + FIELD_AUTO( item_picked_up_by ), + FIELD_AUTO( slime_debounce_time ), + + FIELD_AUTO( bmodel_anim.start ), + FIELD_AUTO( bmodel_anim.end ), + FIELD_AUTO( bmodel_anim.style ), + FIELD_AUTO( bmodel_anim.speed ), + FIELD_AUTO( bmodel_anim.nowrap ), + + FIELD_AUTO( bmodel_anim.alt_start ), + FIELD_AUTO( bmodel_anim.alt_end ), + FIELD_AUTO( bmodel_anim.alt_style ), + FIELD_AUTO( bmodel_anim.alt_speed ), + FIELD_AUTO( bmodel_anim.alt_nowrap ), + + FIELD_AUTO( bmodel_anim.enabled ), + FIELD_AUTO( bmodel_anim.alternate ), + FIELD_AUTO( bmodel_anim.currently_alternate ), + FIELD_AUTO( bmodel_anim.next_tick ), + + FIELD_AUTO( lastMOD.id ), + FIELD_AUTO( lastMOD.friendly_fire ), + +SAVE_STRUCT_END +#undef DECLARE_SAVE_STRUCT +// clang-format on + +size_t get_simple_type_size(save_type_id_t id) +{ + switch (id) + { + case ST_BOOL: + return sizeof(bool); + case ST_INT8: + case ST_UINT8: + return sizeof(uint8_t); + case ST_INT16: + case ST_UINT16: + return sizeof(uint16_t); + case ST_INT32: + case ST_UINT32: + return sizeof(uint32_t); + case ST_INT64: + case ST_UINT64: + case ST_TIME: + return sizeof(uint64_t); + case ST_FLOAT: + return sizeof(float); + case ST_DOUBLE: + return sizeof(double); + case ST_ENTITY: + case ST_ITEM_POINTER: + return sizeof(size_t); + case ST_ITEM_INDEX: + return sizeof(uint32_t); + default: + gi.Com_ErrorFmt("Can't calculate static size for type ID {}", (int32_t) id); + } + + return 0; +} + +size_t get_complex_type_size(const save_type_t &type) +{ + // these are simple types + if ((type.id >= ST_BOOL && type.id <= ST_DOUBLE) || (type.id >= ST_ENTITY && type.id <= ST_TIME)) + return get_simple_type_size(type.id); + + switch (type.id) + { + case ST_STRUCT: + return type.count; + case ST_FIXED_ARRAY: { + save_type_t element_type; + size_t element_size; + + if (type.type_resolver) + { + element_type = type.type_resolver(); + element_size = get_complex_type_size(element_type); + } + else + { + element_size = get_simple_type_size((save_type_id_t) type.tag); + element_type = { (save_type_id_t) type.tag }; + } + + return element_size * type.count; + } + default: + gi.Com_ErrorFmt("Can't calculate static size for type ID {}", (int32_t) type.id); + } + + return 0; +} + +void read_save_struct_json(const Json::Value &json, void *data, const save_struct_t *structure); + +void read_save_type_json(const Json::Value &json, void *data, const save_type_t *type, const char *field) +{ + switch (type->id) + { + case ST_BOOL: + if (!json.isBool()) + json_print_error(field, "expected boolean", false); + else + *((bool *) data) = json.asBool(); + return; + case ST_ENUM: + if (!json.isIntegral()) + json_print_error(field, "expected integer", false); + else if (type->count == 1) + { + if (json.isInt()) + { + if (json.asInt() < INT8_MIN || json.asInt() > INT8_MAX) + json_print_error(field, "int8 out of range", false); + else + *((int8_t *) data) = json.asInt(); + } + else if (json.isUInt()) + { + if (json.asUInt() > UINT8_MAX) + json_print_error(field, "uint8 out of range", false); + else + *((uint8_t *) data) = json.asUInt(); + } + else + json_print_error(field, "int8 out of range (is 64-bit)", false); + return; + } + else if (type->count == 2) + { + if (json.isInt()) + { + if (json.asInt() < INT16_MIN || json.asInt() > INT16_MAX) + json_print_error(field, "int16 out of range", false); + else + *((int16_t *) data) = json.asInt(); + } + else if (json.isUInt()) + { + if (json.asUInt() > UINT16_MAX) + json_print_error(field, "uint16 out of range", false); + else + *((uint16_t *) data) = json.asUInt(); + } + else + json_print_error(field, "int16 out of range (is 64-bit)", false); + return; + } + else if (type->count == 4) + { + if (json.isInt()) + { + if (json.asInt64() < INT32_MIN || json.asInt64() > INT32_MAX) + json_print_error(field, "int32 out of range", false); + else + *((int32_t *) data) = json.asInt(); + } + else if (json.isUInt()) + { + if (json.asUInt64() > UINT32_MAX) + json_print_error(field, "uint32 out of range", false); + else + *((uint32_t *) data) = json.asUInt(); + } + else + json_print_error(field, "int32 out of range (is 64-bit)", false); + return; + } + else if (type->count == 8) + { + if (json.isInt64()) + *((int64_t *) data) = json.asInt64(); + else if (json.isUInt64()) + *((int64_t *) data) = json.asUInt64(); + else if (json.isInt()) + *((int64_t *) data) = json.asInt(); + else if (json.isUInt()) + *((int64_t *) data) = json.asUInt(); + else + json_print_error(field, "int64 not integral", false); + return; + } + + json_print_error(field, "invalid enum size", true); + return; + case ST_INT8: + if (!json.isInt()) + json_print_error(field, "expected integer", false); + else if (json.asInt() < INT8_MIN || json.asInt() > INT8_MAX) + json_print_error(field, "int8 out of range", false); + else + *((int8_t *) data) = json.isInt(); + return; + case ST_INT16: + if (!json.isInt()) + json_print_error(field, "expected integer", false); + else if (json.asInt() < INT16_MIN || json.asInt() > INT16_MAX) + json_print_error(field, "int16 out of range", false); + else + *((int16_t *) data) = json.asInt(); + return; + case ST_INT32: + if (!json.isInt()) + json_print_error(field, "expected integer", false); + else if (json.asInt() < INT32_MIN || json.asInt() > INT32_MAX) + json_print_error(field, "int32 out of range", false); + else + *((int32_t *) data) = json.asInt(); + return; + case ST_INT64: + if (!json.isInt64()) + json_print_error(field, "expected integer", false); + else + *((int64_t *) data) = json.asInt64(); + return; + case ST_UINT8: + if (!json.isUInt()) + json_print_error(field, "expected integer", false); + else if (json.asUInt() > UINT8_MAX) + json_print_error(field, "uint8 out of range", false); + else + *((uint8_t *) data) = json.asUInt(); + return; + case ST_UINT16: + if (!json.isUInt()) + json_print_error(field, "expected integer", false); + else if (json.asUInt() > UINT16_MAX) + json_print_error(field, "uint16 out of range", false); + else + *((uint16_t *) data) = json.asUInt(); + return; + case ST_UINT32: + if (!json.isUInt()) + json_print_error(field, "expected integer", false); + else if (json.asUInt() > UINT32_MAX) + json_print_error(field, "uint32 out of range", false); + else + *((uint32_t *) data) = json.asUInt(); + return; + case ST_UINT64: + if (!json.isUInt64()) + json_print_error(field, "expected integer", false); + else + *((uint64_t *) data) = json.asUInt64(); + return; + case ST_FLOAT: + if (!json.isDouble()) + json_print_error(field, "expected number", false); + else if (isnan(json.asDouble())) + *((float *) data) = std::numeric_limits::quiet_NaN(); + else + *((float *) data) = json.asFloat(); + return; + case ST_DOUBLE: + if (!json.isDouble()) + json_print_error(field, "expected number", false); + else + *((double *) data) = json.asDouble(); + return; + case ST_STRING: + if (json.isNull()) + *((char **) data) = nullptr; + else if (json.isString()) + { + if (type->count && strlen(json.asCString()) >= type->count) + json_print_error(field, "static-length dynamic string overrun", false); + else + { + size_t len = strlen(json.asCString()); + char *str = *((char **) data) = (char *) gi.TagMalloc(type->count ? type->count : (len + 1), type->tag); + strcpy(str, json.asCString()); + str[len] = 0; + } + } + else if (json.isArray()) + { + if (type->count && json.size() >= type->count - 1) + json_print_error(field, "static-length dynamic string overrun", false); + else + { + size_t len = json.size(); + char *str = *((char **) data) = (char *) gi.TagMalloc(type->count ? type->count : (len + 1), type->tag); + + for (Json::Value::ArrayIndex i = 0; i < json.size(); i++) + { + const Json::Value &chr = json[i]; + + if (!chr.isInt()) + json_print_error(field, "expected number", false); + else if (chr.asInt() < 0 || chr.asInt() > UINT8_MAX) + json_print_error(field, "char out of range", false); + + str[i] = chr.asInt(); + } + + str[len] = 0; + } + } + else + json_print_error(field, "expected string, array or null", false); + return; + case ST_FIXED_STRING: + if (json.isString()) + { + if (type->count && strlen(json.asCString()) >= type->count) + json_print_error(field, "fixed length string overrun", false); + else + strcpy((char *) data, json.asCString()); + } + else if (json.isArray()) + { + if (type->count && json.size() >= type->count - 1) + json_print_error(field, "fixed length string overrun", false); + else + { + Json::Value::ArrayIndex i; + + for (i = 0; i < json.size(); i++) + { + const Json::Value &chr = json[i]; + + if (!chr.isInt()) + json_print_error(field, "expected number", false); + else if (chr.asInt() < 0 || chr.asInt() > UINT8_MAX) + json_print_error(field, "char out of range", false); + + ((char *) data)[i] = chr.asInt(); + } + + ((char *) data)[i] = 0; + } + } + else + json_print_error(field, "expected string or array", false); + return; + case ST_FIXED_ARRAY: + if (!json.isArray()) + json_print_error(field, "expected array", false); + else if (type->count != json.size()) + json_print_error(field, "fixed array length mismatch", false); + else + { + uint8_t *element = (uint8_t *) data; + size_t element_size; + save_type_t element_type; + + if (type->type_resolver) + { + element_type = type->type_resolver(); + element_size = get_complex_type_size(element_type); + } + else + { + element_size = get_simple_type_size((save_type_id_t) type->tag); + element_type = { (save_type_id_t) type->tag }; + } + + for (Json::Value::ArrayIndex i = 0; i < type->count; i++, element += element_size) + { + const Json::Value &v = json[i]; + read_save_type_json(v, element, &element_type, + fmt::format("[{}]", i).c_str()); + } + } + + return; + case ST_BITSET: + type->read(data, json, field); + return; + case ST_STRUCT: + if (!json.isNull()) + { + json_push_stack(field); + read_save_struct_json(json, data, type->structure); + json_pop_stack(); + } + return; + case ST_ENTITY: + if (json.isNull()) + *((edict_t **) data) = nullptr; + else if (!json.isUInt()) + json_print_error(field, "expected null or integer", false); + else if (json.asUInt() >= globals.max_edicts) + json_print_error(field, "entity index out of range", false); + else + *((edict_t **) data) = globals.edicts + json.asUInt(); + + return; + case ST_ITEM_POINTER: + case ST_ITEM_INDEX: { + gitem_t *item; + + if (json.isNull()) + item = nullptr; + else if (json.isString()) + { + const char *classname = json.asCString(); + item = FindItemByClassname(classname); + + if (item == nullptr) + { + json_print_error(field, G_Fmt("item {} missing", classname).data(), false); + return; + } + } + else + { + json_print_error(field, "expected null or string", false); + return; + } + + if (type->id == ST_ITEM_POINTER) + *((gitem_t **) data) = item; + else + *((int32_t *) data) = item ? item->id : 0; + return; + } + case ST_TIME: + if (!json.isInt64()) + json_print_error(field, "expected integer", false); + else + *((gtime_t *) data) = gtime_t::from_ms(json.asInt64()); + return; + case ST_DATA: + if (json.isNull()) + *((void **) data) = nullptr; + else if (!json.isString()) + json_print_error(field, "expected null or string", false); + else + { + const char *name = json.asCString(); + auto link = list_str_hash.find(name); + + if (link == list_str_hash.end()) + json_print_error( + field, G_Fmt("unknown pointer {} in list {}", name, type->tag).data(), false); + else + (*reinterpret_cast(data)) = save_void_t(link->second); + } + return; + case ST_INVENTORY: + if (!json.isObject()) + json_print_error(field, "expected object", false); + else + { + int32_t *inventory_ptr = (int32_t *) data; + + //for (auto key : json.getMemberNames()) + for (auto it = json.begin(); it != json.end(); it++) + { + //const char *classname = key.c_str(); + const char *dummy; + const char *classname = it.memberName(&dummy); + const Json::Value &value = *it; + + if (!value.isInt()) + { + json_push_stack(classname); + json_print_error(field, "expected integer", false); + json_pop_stack(); + continue; + } + + gitem_t *item = FindItemByClassname(classname); + + if (!item) + { + json_push_stack(classname); + json_print_error(field, G_Fmt("can't find item {}", classname).data(), false); + json_pop_stack(); + continue; + } + + inventory_ptr[item->id] = value.asInt(); + } + return; + } + return; + case ST_REINFORCEMENTS: + if (!json.isArray()) + json_print_error(field, "expected array", false); + else + { + reinforcement_list_t *list_ptr = (reinforcement_list_t *) data; + + list_ptr->num_reinforcements = json.size(); + list_ptr->reinforcements = (reinforcement_t *) gi.TagMalloc(sizeof(reinforcement_t) * list_ptr->num_reinforcements, TAG_LEVEL); + + reinforcement_t *p = list_ptr->reinforcements; + + for (Json::Value::ArrayIndex i = 0; i < json.size(); i++, p++) + { + const Json::Value &value = json[i]; + + if (!value.isObject()) + { + json_push_stack(fmt::format("{}", i)); + json_print_error(field, "expected object", false); + json_pop_stack(); + continue; + } + + // quick type checks + + if (!value["classname"].isString()) + { + json_push_stack(fmt::format("{}.classname", i)); + json_print_error(field, "expected string", false); + json_pop_stack(); + continue; + } + + if (!value["mins"].isArray() || value["mins"].size() != 3) + { + json_push_stack(fmt::format("{}.mins", i)); + json_print_error(field, "expected array[3]", false); + json_pop_stack(); + continue; + } + + if (!value["maxs"].isArray() || value["maxs"].size() != 3) + { + json_push_stack(fmt::format("{}.maxs", i)); + json_print_error(field, "expected array[3]", false); + json_pop_stack(); + continue; + } + + if (!value["strength"].isInt()) + { + json_push_stack(fmt::format("{}.strength", i)); + json_print_error(field, "expected int", false); + json_pop_stack(); + continue; + } + + p->classname = G_CopyString(value["classname"].asCString(), TAG_LEVEL); + p->strength = value["strength"].asInt(); + + for (int32_t x = 0; x < 3; x++) + { + p->mins[x] = value["mins"][x].asInt(); + p->maxs[x] = value["maxs"][x].asInt(); + } + } + } + return; + default: + gi.Com_ErrorFmt("Can't read type ID {}", (int32_t) type->id); + break; + } +} + +bool write_save_struct_json(const void *data, const save_struct_t *structure, bool null_for_empty, Json::Value &output); + +#define TYPED_DATA_IS_EMPTY(type, expr) (type->is_empty ? type->is_empty(data) : (expr)) + +inline bool string_is_high(const char *c) +{ + for (size_t i = 0; i < strlen(c); i++) + if (c[i] & 128) + return true; + + return false; +} + +inline Json::Value string_to_bytes(const char *c) +{ + Json::Value array(Json::arrayValue); + + for (size_t i = 0; i < strlen(c); i++) + array.append((int32_t) (unsigned char) c[i]); + + return array; +} + +// fetch a JSON value for the specified data. +// if allow_empty is true, false will be returned for +// values that are the same as zero'd memory, to save +// space in the resulting JSON. output will be +// unmodified in that case. +bool write_save_type_json(const void *data, const save_type_t *type, bool null_for_empty, Json::Value &output) +{ + switch (type->id) + { + case ST_BOOL: + if (null_for_empty && TYPED_DATA_IS_EMPTY(type, !*(const bool *) data)) + return false; + + output = Json::Value(*(const bool *) data); + return true; + case ST_ENUM: + if (type->count == 1) + { + if (null_for_empty && TYPED_DATA_IS_EMPTY(type, !*(const int8_t *) data)) + return false; + + output = Json::Value(*(const int8_t *) data); + return true; + } + else if (type->count == 2) + { + if (null_for_empty && TYPED_DATA_IS_EMPTY(type, !*(const int16_t *) data)) + return false; + + output = Json::Value(*(const int16_t *) data); + return true; + } + else if (type->count == 4) + { + if (null_for_empty && TYPED_DATA_IS_EMPTY(type, !*(const int32_t *) data)) + return false; + + output = Json::Value(*(const int32_t *) data); + return true; + } + else if (type->count == 8) + { + if (null_for_empty && TYPED_DATA_IS_EMPTY(type, !*(const int64_t *) data)) + return false; + + output = Json::Value(*(const int64_t *) data); + return true; + } + gi.Com_Error("invalid enum length"); + break; + case ST_INT8: + if (null_for_empty && TYPED_DATA_IS_EMPTY(type, !*(const int8_t *) data)) + return false; + + output = Json::Value(*(const int8_t *) data); + return true; + case ST_INT16: + if (null_for_empty && TYPED_DATA_IS_EMPTY(type, !*(const int16_t *) data)) + return false; + + output = Json::Value(*(const int16_t *) data); + return true; + case ST_INT32: + if (null_for_empty && TYPED_DATA_IS_EMPTY(type, !*(const int32_t *) data)) + return false; + + output = Json::Value(*(const int32_t *) data); + return true; + case ST_INT64: + if (null_for_empty && TYPED_DATA_IS_EMPTY(type, !*(const int64_t *) data)) + return false; + + output = Json::Value(*(const int64_t *) data); + return true; + case ST_UINT8: + if (null_for_empty && TYPED_DATA_IS_EMPTY(type, !*(const uint8_t *) data)) + return false; + + output = Json::Value(*(const uint8_t *) data); + return true; + case ST_UINT16: + if (null_for_empty && TYPED_DATA_IS_EMPTY(type, !*(const uint16_t *) data)) + return false; + + output = Json::Value(*(const uint16_t *) data); + return true; + case ST_UINT32: + if (null_for_empty && TYPED_DATA_IS_EMPTY(type, !*(const uint32_t *) data)) + return false; + + output = Json::Value(*(const uint32_t *) data); + return true; + case ST_UINT64: + if (null_for_empty && TYPED_DATA_IS_EMPTY(type, !*(const uint64_t *) data)) + return false; + + output = Json::Value(*(const uint64_t *) data); + return true; + case ST_FLOAT: + if (null_for_empty && TYPED_DATA_IS_EMPTY(type, !*(const float *) data)) + return false; + + output = Json::Value(static_cast(*(const float *) data)); + return true; + case ST_DOUBLE: + if (null_for_empty && TYPED_DATA_IS_EMPTY(type, !*(const double *) data)) + return false; + + output = Json::Value(*(const double *) data); + return true; + case ST_STRING: { + const char *const *str = reinterpret_cast(data); + if (null_for_empty && TYPED_DATA_IS_EMPTY(type, *str == nullptr)) + return false; + output = *str == nullptr ? Json::Value::nullSingleton() : string_is_high(*str) ? Json::Value(string_to_bytes(*str)) : Json::Value(Json::StaticString(*str)); + return true; + } + case ST_FIXED_STRING: + if (null_for_empty && TYPED_DATA_IS_EMPTY(type, !strlen((const char *) data))) + return false; + output = string_is_high((const char *) data) ? Json::Value(string_to_bytes((const char *) data)) : Json::Value(Json::StaticString((const char *) data)); + return true; + case ST_FIXED_ARRAY: { + const uint8_t *element = (const uint8_t *) data; + size_t i; + size_t element_size; + save_type_t element_type; + + if (type->type_resolver) + { + element_type = type->type_resolver(); + element_size = get_complex_type_size(element_type); + } + else + { + element_size = get_simple_type_size((save_type_id_t) type->tag); + element_type = { (save_type_id_t) type->tag }; + } + + if (null_for_empty) + { + if (type->is_empty) + { + if (type->is_empty(data)) + return false; + } + else + { + for (i = 0; i < type->count; i++, element += element_size) + { + Json::Value value; + bool valid_value = write_save_type_json(element, &element_type, !element_type.never_empty, value); + + if (valid_value) + break; + } + + if (i == type->count) + return false; + } + } + + element = (const uint8_t *) data; + Json::Value v(Json::arrayValue); + + for (i = 0; i < type->count; i++, element += element_size) + { + Json::Value value; + write_save_type_json(element, &element_type, false, value); + v.append(std::move(value)); + } + + output = v; + return true; + } + case ST_BITSET: + return type->write(data, null_for_empty, output); + case ST_STRUCT: { + if (type->is_empty && type->is_empty(data)) + return false; + + Json::Value obj; + bool valid_value = write_save_struct_json(data, type->structure, true, obj); + + if (null_for_empty && (!valid_value || !obj.size())) + return false; + + output = obj; + return true; + } + case ST_ENTITY: { + const edict_t *entity = *reinterpret_cast(data); + + if (null_for_empty && TYPED_DATA_IS_EMPTY(type, entity == nullptr)) + return false; + + if (!entity) + { + output = Json::Value::nullSingleton(); + return true; + } + + output = Json::Value(entity->s.number); + return true; + } + case ST_ITEM_POINTER: { + const gitem_t *item = *reinterpret_cast(data); + + if (item != nullptr && item->id != 0) + if (!strlen(item->classname)) + gi.Com_ErrorFmt("Attempt to persist invalid item {} (index {})", item->pickup_name, (int32_t) item->id); + + if (null_for_empty && TYPED_DATA_IS_EMPTY(type, item == nullptr)) + return false; + + if (item == nullptr) + { + output = Json::Value::nullSingleton(); + return true; + } + + output = Json::Value(Json::StaticString(item->classname)); + return true; + } + case ST_ITEM_INDEX: { + const item_id_t index = *reinterpret_cast(data); + + if (index < IT_NULL || index >= IT_TOTAL) + gi.Com_ErrorFmt("Attempt to persist invalid item index {}", (int32_t) index); + + const gitem_t *item = GetItemByIndex(index); + + if (index) + if (!strlen(item->classname)) + gi.Com_ErrorFmt("Attempt to persist invalid item {} (index {})", item->pickup_name, (int32_t) item->id); + + if (null_for_empty && TYPED_DATA_IS_EMPTY(type, item == nullptr)) + return false; + + if (item == nullptr) + { + output = Json::Value::nullSingleton(); + return true; + } + + output = Json::Value(Json::StaticString(item->classname)); + return true; + } + case ST_TIME: { + const gtime_t &time = *(const gtime_t *) data; + + if (null_for_empty && TYPED_DATA_IS_EMPTY(type, !time)) + return false; + + output = Json::Value(time.milliseconds()); + return true; + } + case ST_DATA: { + const save_void_t &ptr = *reinterpret_cast(data); + + if (null_for_empty && TYPED_DATA_IS_EMPTY(type, !ptr)) + return false; + + if (!ptr) + { + output = Json::Value::nullSingleton(); + return true; + } + + if (!ptr.save_list()) + { + gi.Com_ErrorFmt("Attempt to persist invalid data pointer {} in list {}", ptr.pointer(), type->tag); + return false; + } + + output = Json::Value(Json::StaticString(ptr.save_list()->name)); + return true; + } + case ST_INVENTORY: { + Json::Value inventory = Json::Value(Json::objectValue); + const int32_t *inventory_ptr = (const int32_t *) data; + + for (item_id_t i = static_cast(IT_NULL + 1); i < IT_TOTAL; i = static_cast(i + 1)) + { + gitem_t *item = GetItemByIndex(i); + + if (!item || !item->classname) + { + if (inventory_ptr[i]) + gi.Com_ErrorFmt("Item index {} is in inventory but has no classname", (int32_t) i); + + continue; + } + + if (inventory_ptr[i]) + inventory[item->classname] = Json::Value(inventory_ptr[i]); + } + + if (null_for_empty && inventory.empty()) + return false; + + output = inventory; + return true; + } + case ST_REINFORCEMENTS: { + const reinforcement_list_t *reinforcement_ptr = (const reinforcement_list_t *) data; + + if (null_for_empty && !reinforcement_ptr->num_reinforcements) + return false; + + Json::Value reinforcements = Json::Value(Json::arrayValue); + reinforcements.resize(reinforcement_ptr->num_reinforcements); + + for (uint32_t i = 0; i < reinforcement_ptr->num_reinforcements; i++) + { + const reinforcement_t *reinforcement = &reinforcement_ptr->reinforcements[i]; + + Json::Value obj = Json::Value(Json::objectValue); + + obj["classname"] = Json::StaticString(reinforcement->classname); + obj["mins"] = Json::Value(Json::arrayValue); + obj["maxs"] = Json::Value(Json::arrayValue); + for (int32_t x = 0; x < 3; x++) + { + obj["mins"][x] = reinforcement->mins[x]; + obj["maxs"][x] = reinforcement->maxs[x]; + } + obj["strength"] = reinforcement->strength; + + reinforcements[i] = obj; + } + + output = reinforcements; + return true; + } + default: + gi.Com_ErrorFmt("Can't persist type ID {}", (int32_t) type->id); + } + + return false; +} + +// write the specified data+structure to the JSON object +// referred to by `json`. +bool write_save_struct_json(const void *data, const save_struct_t *structure, bool null_for_empty, Json::Value &output) +{ + Json::Value obj(Json::objectValue); + + for (auto &field : structure->fields) + { + const void *p = ((const uint8_t *) data) + field.offset; + Json::Value value; + bool valid_value = write_save_type_json(p, &field.type, !field.type.never_empty, value); + + if (valid_value) + obj[field.name].swap(value); + } + + if (null_for_empty && obj.empty()) + return false; + + output = obj; + return true; +} + +// read the specified data+structure from the JSON object +// referred to by `json`. +void read_save_struct_json(const Json::Value &json, void *data, const save_struct_t *structure) +{ + if (!json.isObject()) + { + json_print_error("", "expected object", false); + return; + } + + //for (auto key : json.getMemberNames()) + for (auto it = json.begin(); it != json.end(); it++) + { + //const char *classname = key.c_str(); + const char *dummy; + const char *key = it.memberName(&dummy); + const Json::Value &value = *it;//json[key]; + const save_field_t *field; + + for (field = structure->fields.begin(); field != structure->fields.end(); field++) + { + if (strcmp(key, field->name) == 0) + { + void *p = ((uint8_t *) data) + field->offset; + read_save_type_json(value, p, &field->type, field->name); + break; + } + } + + if (!field->name) + json_print_error(key, "unknown field", false); + } +} + +#include +#include + +static Json::Value parseJson(const char *jsonString) +{ + Json::CharReaderBuilder reader; + reader["allowSpecialFloats"] = true; + Json::Value json; + JSONCPP_STRING errs; + std::stringstream ss(jsonString, std::ios_base::in | std::ios_base::binary); + + if (!Json::parseFromStream(reader, ss, &json, &errs)) + gi.Com_ErrorFmt("Couldn't decode JSON: {}", errs.c_str()); + + if (!json.isObject()) + gi.Com_Error("expected object at root"); + + return json; +} + +static char *saveJson(const Json::Value &json, size_t *out_size) +{ + Json::StreamWriterBuilder builder; + builder["indentation"] = "\t"; + builder["useSpecialFloats"] = true; + const std::unique_ptr writer(builder.newStreamWriter()); + std::stringstream ss(std::ios_base::out | std::ios_base::binary); + writer->write(json, &ss); + *out_size = ss.tellp(); + char *const out = static_cast(gi.TagMalloc(*out_size + 1, TAG_GAME)); + // FIXME: some day... + std::string v = ss.str(); + memcpy(out, v.c_str(), *out_size); + out[*out_size] = '\0'; + return out; +} + +// new entry point for WriteGame. +// returns pointer to TagMalloc'd JSON string. +char *WriteGameJson(bool autosave, size_t *out_size) +{ + if (!autosave) + SaveClientData(); + + Json::Value json(Json::objectValue); + + json["save_version"] = SAVE_FORMAT_VERSION; + // TODO: engine version ID? + + // write game + game.autosaved = autosave; + write_save_struct_json(&game, &game_locals_t_savestruct, false, json["game"]); + game.autosaved = false; + + // write clients + Json::Value clients(Json::arrayValue); + for (size_t i = 0; i < game.maxclients; i++) + { + Json::Value v; + write_save_struct_json(&game.clients[i], &gclient_t_savestruct, false, v); + clients.append(std::move(v)); + } + json["clients"] = std::move(clients); + + return saveJson(json, out_size); +} + +void G_PrecacheInventoryItems(); + +// new entry point for ReadGame. +// takes in pointer to JSON data. does +// not store or modify it. +void ReadGameJson(const char *jsonString) +{ + gi.FreeTags(TAG_GAME); + + Json::Value json = parseJson(jsonString); + + uint32_t max_entities = game.maxentities; + uint32_t max_clients = game.maxclients; + + game = {}; + g_edicts = (edict_t *) gi.TagMalloc(max_entities * sizeof(g_edicts[0]), TAG_GAME); + game.clients = (gclient_t *) gi.TagMalloc(max_clients * sizeof(game.clients[0]), TAG_GAME); + globals.edicts = g_edicts; + + // read game + json_push_stack("game"); + read_save_struct_json(json["game"], &game, &game_locals_t_savestruct); + json_pop_stack(); + + // read clients + const Json::Value &clients = json["clients"]; + + if (!clients.isArray()) + gi.Com_Error("expected \"clients\" to be array"); + else if (clients.size() != game.maxclients) + gi.Com_Error("mismatched client size"); + + size_t i = 0; + + for (auto &v : clients) + { + json_push_stack(fmt::format("clients[{}]", i)); + read_save_struct_json(v, &game.clients[i++], &gclient_t_savestruct); + json_pop_stack(); + } + + G_PrecacheInventoryItems(); +} + +// new entry point for WriteLevel. +// returns pointer to TagMalloc'd JSON string. +char *WriteLevelJson(bool transition, size_t *out_size) +{ + // update current level entry now, just so we can + // use gamemap to test EOU + G_UpdateLevelEntry(); + + Json::Value json(Json::objectValue); + + json["save_version"] = SAVE_FORMAT_VERSION; + + // write level + write_save_struct_json(&level, &level_locals_t_savestruct, false, json["level"]); + + // write entities + Json::Value entities(Json::objectValue); + char number[16]; + + for (uint32_t i = 0; i < globals.num_edicts; i++) + { + if (!globals.edicts[i].inuse) + continue; + // clear all the client inuse flags before saving so that + // when the level is re-entered, the clients will spawn + // at spawn points instead of occupying body shells + else if (transition && i >= 1 && i <= game.maxclients) + continue; + + auto result = std::to_chars(number, number + sizeof(number) - 1, i); + + if (result.ec == std::errc()) + *result.ptr = '\0'; + else + gi.Com_ErrorFmt("error formatting number: {}", std::make_error_code(result.ec).message()); + + write_save_struct_json(&globals.edicts[i], &edict_t_savestruct, false, entities[number]); + } + + json["entities"] = std::move(entities); + + return saveJson(json, out_size); +} + +// new entry point for ReadLevel. +// takes in pointer to JSON data. does +// not store or modify it. +void ReadLevelJson(const char *jsonString) +{ + // free any dynamic memory allocated by loading the level + // base state + gi.FreeTags(TAG_LEVEL); + + Json::Value json = parseJson(jsonString); + + // wipe all the entities + memset(g_edicts, 0, game.maxentities * sizeof(g_edicts[0])); + globals.num_edicts = game.maxclients + 1; + + // read level + json_push_stack("level"); + read_save_struct_json(json["level"], &level, &level_locals_t_savestruct); + json_pop_stack(); + + // read entities + const Json::Value &entities = json["entities"]; + + if (!entities.isObject()) + gi.Com_Error("expected \"entities\" to be object"); + + //for (auto key : json.getMemberNames()) + for (auto it = entities.begin(); it != entities.end(); it++) + { + //const char *classname = key.c_str(); + const char *dummy; + const char *id = it.memberName(&dummy); + const Json::Value &value = *it;//json[key]; + uint32_t number = strtoul(id, nullptr, 10); + + if (number >= globals.num_edicts) + globals.num_edicts = number + 1; + + edict_t *ent = &g_edicts[number]; + G_InitEdict(ent); + json_push_stack(fmt::format("entities[{}]", number)); + read_save_struct_json(value, ent, &edict_t_savestruct); + json_pop_stack(); + gi.linkentity(ent); + } + + // mark all clients as unconnected + for (size_t i = 0; i < game.maxclients; i++) + { + edict_t *ent = &g_edicts[i + 1]; + ent->client = game.clients + i; + ent->client->pers.connected = false; + ent->client->pers.spawned = false; + } + + // do any load time things at this point + for (size_t i = 0; i < globals.num_edicts; i++) + { + edict_t *ent = &g_edicts[i]; + + if (!ent->inuse) + continue; + + // fire any cross-level/unit triggers + if (ent->classname) + if (strcmp(ent->classname, "target_crosslevel_target") == 0 || + strcmp(ent->classname, "target_crossunit_target") == 0) + ent->nextthink = level.time + gtime_t::from_sec(ent->delay); + } + + G_PrecacheInventoryItems(); +} + +// [Paril-KEX] +bool G_CanSave() +{ + if (game.maxclients == 1 && g_edicts[1].health <= 0) + { + gi.LocClient_Print(&g_edicts[1], PRINT_CENTER, "$g_no_save_dead"); + return false; + } + + return true; +} \ No newline at end of file diff --git a/rerelease/g_spawn.cpp b/rerelease/g_spawn.cpp new file mode 100644 index 0000000..0021fef --- /dev/null +++ b/rerelease/g_spawn.cpp @@ -0,0 +1,1714 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. + +#include "g_local.h" + +struct spawn_t +{ + const char *name; + void (*spawn)(edict_t *ent); +}; + +void SP_info_player_start(edict_t *ent); +void SP_info_player_deathmatch(edict_t *ent); +void SP_info_player_coop(edict_t *ent); +void SP_info_player_intermission(edict_t *ent); + +void SP_func_plat(edict_t *ent); +void SP_func_rotating(edict_t *ent); +void SP_func_button(edict_t *ent); +void SP_func_door(edict_t *ent); +void SP_func_door_secret(edict_t *ent); +void SP_func_door_rotating(edict_t *ent); +void SP_func_water(edict_t *ent); +void SP_func_train(edict_t *ent); +void SP_func_conveyor(edict_t *self); +void SP_func_wall(edict_t *self); +void SP_func_object(edict_t *self); +void SP_func_explosive(edict_t *self); +void SP_func_timer(edict_t *self); +void SP_func_areaportal(edict_t *ent); +void SP_func_clock(edict_t *ent); +void SP_func_killbox(edict_t *ent); +void SP_func_eye(edict_t *ent); // [Paril-KEX] +void SP_func_animation(edict_t *ent); // [Paril-KEX] +void SP_func_spinning(edict_t *ent); // [Paril-KEX] + +void SP_trigger_always(edict_t *ent); +void SP_trigger_once(edict_t *ent); +void SP_trigger_multiple(edict_t *ent); +void SP_trigger_relay(edict_t *ent); +void SP_trigger_push(edict_t *ent); +void SP_trigger_hurt(edict_t *ent); +void SP_trigger_key(edict_t *ent); +void SP_trigger_counter(edict_t *ent); +void SP_trigger_elevator(edict_t *ent); +void SP_trigger_gravity(edict_t *ent); +void SP_trigger_monsterjump(edict_t *ent); +void SP_trigger_flashlight(edict_t *self); // [Paril-KEX] +void SP_trigger_fog(edict_t *self); // [Paril-KEX] +void SP_trigger_coop_relay(edict_t *self); // [Paril-KEX] +void SP_trigger_health_relay(edict_t *self); // [Paril-KEX] + +void SP_target_temp_entity(edict_t *ent); +void SP_target_speaker(edict_t *ent); +void SP_target_explosion(edict_t *ent); +void SP_target_changelevel(edict_t *ent); +void SP_target_secret(edict_t *ent); +void SP_target_goal(edict_t *ent); +void SP_target_splash(edict_t *ent); +void SP_target_spawner(edict_t *ent); +void SP_target_blaster(edict_t *ent); +void SP_target_crosslevel_trigger(edict_t *ent); +void SP_target_crosslevel_target(edict_t *ent); +void SP_target_crossunit_trigger(edict_t *ent); // [Paril-KEX] +void SP_target_crossunit_target(edict_t *ent); // [Paril-KEX] +void SP_target_laser(edict_t *self); +void SP_target_help(edict_t *ent); +void SP_target_actor(edict_t *ent); +void SP_target_lightramp(edict_t *self); +void SP_target_earthquake(edict_t *ent); +void SP_target_character(edict_t *ent); +void SP_target_string(edict_t *ent); +void SP_target_camera(edict_t* self); // [Sam-KEX] +void SP_target_gravity(edict_t* self); // [Sam-KEX] +void SP_target_soundfx(edict_t* self); // [Sam-KEX] +void SP_target_light(edict_t *self); // [Paril-KEX] +void SP_target_poi(edict_t *ent); // [Paril-KEX] +void SP_target_music(edict_t *ent); +void SP_target_healthbar(edict_t *self); // [Paril-KEX] +void SP_target_autosave(edict_t *self); // [Paril-KEX] +void SP_target_sky(edict_t *self); // [Paril-KEX] +void SP_target_achievement(edict_t *self); // [Paril-KEX] +void SP_target_story(edict_t *self); // [Paril-KEX] + +void SP_worldspawn(edict_t *ent); + +void SP_dynamic_light(edict_t* self); +void SP_light(edict_t *self); +void SP_light_mine1(edict_t *ent); +void SP_light_mine2(edict_t *ent); +void SP_info_null(edict_t *self); +void SP_info_notnull(edict_t *self); +void SP_info_landmark (edict_t* self); // [Paril-KEX] +void SP_info_world_text(edict_t * self); +void SP_misc_player_mannequin(edict_t * self); +void SP_misc_model(edict_t *self); // [Paril-KEX] +void SP_path_corner(edict_t *self); +void SP_point_combat(edict_t *self); +void SP_info_nav_lock(edict_t *self); // [Paril-KEX] + +void SP_misc_explobox(edict_t *self); +void SP_misc_banner(edict_t *self); +void SP_misc_satellite_dish(edict_t *self); +void SP_misc_actor(edict_t *self); +void SP_misc_gib_arm(edict_t *self); +void SP_misc_gib_leg(edict_t *self); +void SP_misc_gib_head(edict_t *self); +void SP_misc_insane(edict_t *self); +void SP_misc_deadsoldier(edict_t *self); +void SP_misc_viper(edict_t *self); +void SP_misc_viper_bomb(edict_t *self); +void SP_misc_bigviper(edict_t *self); +void SP_misc_strogg_ship(edict_t *self); +void SP_misc_teleporter(edict_t *self); +void SP_misc_teleporter_dest(edict_t *self); +void SP_misc_blackhole(edict_t *self); +void SP_misc_eastertank(edict_t *self); +void SP_misc_easterchick(edict_t *self); +void SP_misc_easterchick2(edict_t *self); + +void SP_misc_flare(edict_t* ent); // [Sam-KEX] +void SP_misc_hologram(edict_t *ent); +void SP_misc_lavaball(edict_t *ent); + +void SP_monster_berserk(edict_t *self); +void SP_monster_gladiator(edict_t *self); +void SP_monster_gunner(edict_t *self); +void SP_monster_infantry(edict_t *self); +void SP_monster_soldier_light(edict_t *self); +void SP_monster_soldier(edict_t *self); +void SP_monster_soldier_ss(edict_t *self); +void SP_monster_tank(edict_t *self); +void SP_monster_medic(edict_t *self); +void SP_monster_flipper(edict_t *self); +void SP_monster_chick(edict_t *self); +void SP_monster_parasite(edict_t *self); +void SP_monster_flyer(edict_t *self); +void SP_monster_brain(edict_t *self); +void SP_monster_floater(edict_t *self); +void SP_monster_hover(edict_t *self); +void SP_monster_mutant(edict_t *self); +void SP_monster_supertank(edict_t *self); +void SP_monster_boss2(edict_t *self); +void SP_monster_jorg(edict_t *self); +void SP_monster_boss3_stand(edict_t *self); +void SP_monster_makron(edict_t *self); +// Paril +void SP_monster_tank_stand(edict_t *self); +void SP_monster_guardian(edict_t *self); +void SP_monster_arachnid(edict_t *self); +void SP_monster_guncmdr(edict_t *self); + +void SP_monster_commander_body(edict_t *self); + +void SP_turret_breach(edict_t *self); +void SP_turret_base(edict_t *self); +void SP_turret_driver(edict_t *self); + +// RAFAEL 14-APR-98 +void SP_monster_soldier_hypergun(edict_t *self); +void SP_monster_soldier_lasergun(edict_t *self); +void SP_monster_soldier_ripper(edict_t *self); +void SP_monster_fixbot(edict_t *self); +void SP_monster_gekk(edict_t *self); +void SP_monster_chick_heat(edict_t *self); +void SP_monster_gladb(edict_t *self); +void SP_monster_boss5(edict_t *self); +void SP_rotating_light(edict_t *self); +void SP_object_repair(edict_t *self); +void SP_misc_crashviper(edict_t *ent); +void SP_misc_viper_missile(edict_t *self); +void SP_misc_amb4(edict_t *ent); +void SP_target_mal_laser(edict_t *ent); +void SP_misc_transport(edict_t *ent); +// END 14-APR-98 + +void SP_misc_nuke(edict_t *ent); + +//=========== +// ROGUE +void SP_func_plat2(edict_t *ent); +void SP_func_door_secret2(edict_t *ent); +void SP_func_force_wall(edict_t *ent); +void SP_info_player_coop_lava(edict_t *self); +void SP_info_teleport_destination(edict_t *self); +void SP_trigger_teleport(edict_t *self); +void SP_trigger_disguise(edict_t *self); +void SP_monster_stalker(edict_t *self); +void SP_monster_turret(edict_t *self); +void SP_target_steam(edict_t *self); +void SP_target_anger(edict_t *self); +void SP_target_killplayers(edict_t *self); +// PMM - still experimental! +void SP_target_blacklight(edict_t *self); +void SP_target_orb(edict_t *self); +// pmm +void SP_hint_path(edict_t *self); +void SP_monster_carrier(edict_t *self); +void SP_monster_widow(edict_t *self); +void SP_monster_widow2(edict_t *self); +void SP_dm_tag_token(edict_t *self); +void SP_dm_dball_goal(edict_t *self); +void SP_dm_dball_ball(edict_t *self); +void SP_dm_dball_team1_start(edict_t *self); +void SP_dm_dball_team2_start(edict_t *self); +void SP_dm_dball_ball_start(edict_t *self); +void SP_dm_dball_speed_change(edict_t *self); +void SP_monster_kamikaze(edict_t *self); +void SP_turret_invisible_brain(edict_t *self); +void SP_misc_nuke_core(edict_t *self); +// ROGUE +//=========== +// ZOID +void SP_trigger_ctf_teleport(edict_t *self); +void SP_info_ctf_teleport_destination(edict_t *self); +// ZOID + +void SP_monster_shambler(edict_t* self); + +// clang-format off +static const std::initializer_list spawns = { + { "info_player_start", SP_info_player_start }, + { "info_player_deathmatch", SP_info_player_deathmatch }, + { "info_player_coop", SP_info_player_coop }, + { "info_player_intermission", SP_info_player_intermission }, + + { "func_plat", SP_func_plat }, + { "func_button", SP_func_button }, + { "func_door", SP_func_door }, + { "func_door_secret", SP_func_door_secret }, + { "func_door_rotating", SP_func_door_rotating }, + { "func_rotating", SP_func_rotating }, + { "func_train", SP_func_train }, + { "func_water", SP_func_water }, + { "func_conveyor", SP_func_conveyor }, + { "func_areaportal", SP_func_areaportal }, + { "func_clock", SP_func_clock }, + { "func_wall", SP_func_wall }, + { "func_object", SP_func_object }, + { "func_timer", SP_func_timer }, + { "func_explosive", SP_func_explosive }, + { "func_killbox", SP_func_killbox }, + { "func_eye", SP_func_eye }, + { "func_animation", SP_func_animation }, + { "func_spinning", SP_func_spinning }, + + { "trigger_always", SP_trigger_always }, + { "trigger_once", SP_trigger_once }, + { "trigger_multiple", SP_trigger_multiple }, + { "trigger_relay", SP_trigger_relay }, + { "trigger_push", SP_trigger_push }, + { "trigger_hurt", SP_trigger_hurt }, + { "trigger_key", SP_trigger_key }, + { "trigger_counter", SP_trigger_counter }, + { "trigger_elevator", SP_trigger_elevator }, + { "trigger_gravity", SP_trigger_gravity }, + { "trigger_monsterjump", SP_trigger_monsterjump }, + { "trigger_flashlight", SP_trigger_flashlight }, // [Paril-KEX] + { "trigger_fog", SP_trigger_fog }, // [Paril-KEX] + { "trigger_coop_relay", SP_trigger_coop_relay }, // [Paril-KEX] + { "trigger_health_relay", SP_trigger_health_relay }, // [Paril-KEX] + + { "target_temp_entity", SP_target_temp_entity }, + { "target_speaker", SP_target_speaker }, + { "target_explosion", SP_target_explosion }, + { "target_changelevel", SP_target_changelevel }, + { "target_secret", SP_target_secret }, + { "target_goal", SP_target_goal }, + { "target_splash", SP_target_splash }, + { "target_spawner", SP_target_spawner }, + { "target_blaster", SP_target_blaster }, + { "target_crosslevel_trigger", SP_target_crosslevel_trigger }, + { "target_crosslevel_target", SP_target_crosslevel_target }, + { "target_crossunit_trigger", SP_target_crossunit_trigger }, // [Paril-KEX] + { "target_crossunit_target", SP_target_crossunit_target }, // [Paril-KEX] + { "target_laser", SP_target_laser }, + { "target_help", SP_target_help }, + { "target_actor", SP_target_actor }, + { "target_lightramp", SP_target_lightramp }, + { "target_earthquake", SP_target_earthquake }, + { "target_character", SP_target_character }, + { "target_string", SP_target_string }, + { "target_camera", SP_target_camera }, // [Sam-KEX] + { "target_gravity", SP_target_gravity }, // [Sam-KEX] + { "target_soundfx", SP_target_soundfx }, // [Sam-KEX] + { "target_light", SP_target_light }, // [Paril-KEX] + { "target_poi", SP_target_poi }, // [Paril-KEX] + { "target_music", SP_target_music }, + { "target_healthbar", SP_target_healthbar }, // [Paril-KEX] + { "target_autosave", SP_target_autosave }, // [Paril-KEX] + { "target_sky", SP_target_sky }, // [Paril-KEX] + { "target_achievement", SP_target_achievement }, // [Paril-KEX] + { "target_story", SP_target_story }, // [Paril-KEX] + + { "worldspawn", SP_worldspawn }, + + { "dynamic_light", SP_dynamic_light }, + { "light", SP_light }, + { "light_mine1", SP_light_mine1 }, + { "light_mine2", SP_light_mine2 }, + { "info_null", SP_info_null }, + { "func_group", SP_info_null }, + { "info_notnull", SP_info_notnull }, + { "info_landmark", SP_info_landmark }, + { "info_world_text", SP_info_world_text }, + { "path_corner", SP_path_corner }, + { "point_combat", SP_point_combat }, + { "info_nav_lock", SP_info_nav_lock }, + + { "misc_explobox", SP_misc_explobox }, + { "misc_banner", SP_misc_banner }, + { "misc_satellite_dish", SP_misc_satellite_dish }, + { "misc_actor", SP_misc_actor }, + { "misc_player_mannequin", SP_misc_player_mannequin }, + { "misc_model", SP_misc_model }, // [Paril-KEX] + { "misc_gib_arm", SP_misc_gib_arm }, + { "misc_gib_leg", SP_misc_gib_leg }, + { "misc_gib_head", SP_misc_gib_head }, + { "misc_insane", SP_misc_insane }, + { "misc_deadsoldier", SP_misc_deadsoldier }, + { "misc_viper", SP_misc_viper }, + { "misc_viper_bomb", SP_misc_viper_bomb }, + { "misc_bigviper", SP_misc_bigviper }, + { "misc_strogg_ship", SP_misc_strogg_ship }, + { "misc_teleporter", SP_misc_teleporter }, + { "misc_teleporter_dest", SP_misc_teleporter_dest }, + { "misc_blackhole", SP_misc_blackhole }, + { "misc_eastertank", SP_misc_eastertank }, + { "misc_easterchick", SP_misc_easterchick }, + { "misc_easterchick2", SP_misc_easterchick2 }, + { "misc_flare", SP_misc_flare }, // [Sam-KEX] + { "misc_hologram", SP_misc_hologram }, // Paril + { "misc_lavaball", SP_misc_lavaball }, // Paril + + { "monster_berserk", SP_monster_berserk }, + { "monster_gladiator", SP_monster_gladiator }, + { "monster_gunner", SP_monster_gunner }, + { "monster_infantry", SP_monster_infantry }, + { "monster_soldier_light", SP_monster_soldier_light }, + { "monster_soldier", SP_monster_soldier }, + { "monster_soldier_ss", SP_monster_soldier_ss }, + { "monster_tank", SP_monster_tank }, + { "monster_tank_commander", SP_monster_tank }, + { "monster_medic", SP_monster_medic }, + { "monster_flipper", SP_monster_flipper }, + { "monster_chick", SP_monster_chick }, + { "monster_parasite", SP_monster_parasite }, + { "monster_flyer", SP_monster_flyer }, + { "monster_brain", SP_monster_brain }, + { "monster_floater", SP_monster_floater }, + { "monster_hover", SP_monster_hover }, + { "monster_mutant", SP_monster_mutant }, + { "monster_supertank", SP_monster_supertank }, + { "monster_boss2", SP_monster_boss2 }, + { "monster_boss3_stand", SP_monster_boss3_stand }, + { "monster_jorg", SP_monster_jorg }, + // Paril: allow spawning makron + { "monster_makron", SP_monster_makron }, + // Paril: N64 + { "monster_tank_stand", SP_monster_tank_stand }, + // Paril: PSX + { "monster_guardian", SP_monster_guardian }, + { "monster_arachnid", SP_monster_arachnid }, + { "monster_guncmdr", SP_monster_guncmdr }, + + { "monster_commander_body", SP_monster_commander_body }, + + { "turret_breach", SP_turret_breach }, + { "turret_base", SP_turret_base }, + { "turret_driver", SP_turret_driver }, + + // RAFAEL + { "func_object_repair", SP_object_repair }, + { "rotating_light", SP_rotating_light }, + { "target_mal_laser", SP_target_mal_laser }, + { "misc_crashviper", SP_misc_crashviper }, + { "misc_viper_missile", SP_misc_viper_missile }, + { "misc_amb4", SP_misc_amb4 }, + { "misc_transport", SP_misc_transport }, + { "misc_nuke", SP_misc_nuke }, + { "monster_soldier_hypergun", SP_monster_soldier_hypergun }, + { "monster_soldier_lasergun", SP_monster_soldier_lasergun }, + { "monster_soldier_ripper", SP_monster_soldier_ripper }, + { "monster_fixbot", SP_monster_fixbot }, + { "monster_gekk", SP_monster_gekk }, + { "monster_chick_heat", SP_monster_chick_heat }, + { "monster_gladb", SP_monster_gladb }, + { "monster_boss5", SP_monster_boss5 }, + // RAFAEL + + //============== + // ROGUE + { "func_plat2", SP_func_plat2 }, + { "func_door_secret2", SP_func_door_secret2 }, + { "func_force_wall", SP_func_force_wall }, + { "trigger_teleport", SP_trigger_teleport }, + { "trigger_disguise", SP_trigger_disguise }, + { "info_teleport_destination", SP_info_teleport_destination }, + { "info_player_coop_lava", SP_info_player_coop_lava }, + { "monster_stalker", SP_monster_stalker }, + { "monster_turret", SP_monster_turret }, + { "target_steam", SP_target_steam }, + { "target_anger", SP_target_anger }, + { "target_killplayers", SP_target_killplayers }, + // PMM - experiment + { "target_blacklight", SP_target_blacklight }, + { "target_orb", SP_target_orb }, + // pmm + { "monster_daedalus", SP_monster_hover }, + { "hint_path", SP_hint_path }, + { "monster_carrier", SP_monster_carrier }, + { "monster_widow", SP_monster_widow }, + { "monster_widow2", SP_monster_widow2 }, + { "monster_medic_commander", SP_monster_medic }, + { "dm_tag_token", SP_dm_tag_token }, + { "dm_dball_goal", SP_dm_dball_goal }, + { "dm_dball_ball", SP_dm_dball_ball }, + { "dm_dball_team1_start", SP_dm_dball_team1_start }, + { "dm_dball_team2_start", SP_dm_dball_team2_start }, + { "dm_dball_ball_start", SP_dm_dball_ball_start }, + { "dm_dball_speed_change", SP_dm_dball_speed_change }, + { "monster_kamikaze", SP_monster_kamikaze }, + { "turret_invisible_brain", SP_turret_invisible_brain }, + { "misc_nuke_core", SP_misc_nuke_core }, + // ROGUE + //============== + // ZOID + { "trigger_ctf_teleport", SP_trigger_ctf_teleport }, + { "info_ctf_teleport_destination", SP_info_ctf_teleport_destination }, + { "misc_ctf_banner", SP_misc_ctf_banner }, + { "misc_ctf_small_banner", SP_misc_ctf_small_banner }, + { "info_player_team1", SP_info_player_team1 }, + { "info_player_team2", SP_info_player_team2 }, + // ZOID + + { "monster_shambler", SP_monster_shambler } +}; +// clang-format on + +/* +=============== +ED_CallSpawn + +Finds the spawn function for the entity and calls it +=============== +*/ +void ED_CallSpawn(edict_t *ent) +{ + gitem_t *item; + int i; + + if (!ent->classname) + { + gi.Com_Print("ED_CallSpawn: nullptr classname\n"); + G_FreeEdict(ent); + return; + } + + // PGM - do this before calling the spawn function so it can be overridden. + ent->gravityVector[0] = 0.0; + ent->gravityVector[1] = 0.0; + ent->gravityVector[2] = -1.0; + // PGM + + ent->sv.init = false; + + // FIXME - PMM classnames hack + if (!strcmp(ent->classname, "weapon_nailgun")) + ent->classname = GetItemByIndex(IT_WEAPON_ETF_RIFLE)->classname; + if (!strcmp(ent->classname, "ammo_nails")) + ent->classname = GetItemByIndex(IT_AMMO_FLECHETTES)->classname; + if (!strcmp(ent->classname, "weapon_heatbeam")) + ent->classname = GetItemByIndex(IT_WEAPON_PLASMABEAM)->classname; + // pmm + + // check item spawn functions + for (i = 0, item = itemlist; i < IT_TOTAL; i++, item++) + { + if (!item->classname) + continue; + if (!strcmp(item->classname, ent->classname)) + { + // found it + // before spawning, pick random item replacement + if (g_dm_random_items->integer) + { + ent->item = item; + item_id_t new_item = DoRandomRespawn(ent); + + if (new_item) + { + item = GetItemByIndex(new_item); + ent->classname = item->classname; + } + } + + SpawnItem(ent, item); + return; + } + } + + // check normal spawn functions + for (auto &s : spawns) + { + if (!strcmp(s.name, ent->classname)) + { // found it + s.spawn(ent); + + // Paril: swap classname with stored constant if we didn't change it + if (strcmp(ent->classname, s.name) == 0) + ent->classname = s.name; + return; + } + } + + gi.Com_PrintFmt("{} doesn't have a spawn function\n", *ent); + G_FreeEdict(ent); +} + +/* +============= +ED_NewString +============= +*/ +char *ED_NewString(const char *string) +{ + char *newb, *new_p; + int i; + size_t l; + + l = strlen(string) + 1; + + newb = (char *) gi.TagMalloc(l, TAG_LEVEL); + + new_p = newb; + + for (i = 0; i < l; i++) + { + if (string[i] == '\\' && i < l - 1) + { + i++; + if (string[i] == 'n') + *new_p++ = '\n'; + else + *new_p++ = '\\'; + } + else + *new_p++ = string[i]; + } + + return newb; +} + +// +// fields are used for spawning from the entity string +// + +struct field_t +{ + const char *name; + void (*load_func) (edict_t *e, const char *s) = nullptr; +}; + +// utility template for getting the type of a field +template +struct member_object_container_type { }; +template +struct member_object_container_type { using type = T2; }; +template +using member_object_container_type_t = typename member_object_container_type>::type; + +struct type_loaders_t +{ + template, int> = 0> + static T load(const char *s) + { + return ED_NewString(s); + } + + template, int> = 0> + static T load(const char *s) + { + return atoi(s); + } + + template, int> = 0> + static T load(const char *s) + { + return spawnflags_t(atoi(s)); + } + + template, int> = 0> + static T load(const char *s) + { + return atof(s); + } + + template, int> = 0> + static T load(const char *s) + { + if constexpr (sizeof(T) > 4) + return static_cast(atoll(s)); + else + return static_cast(atoi(s)); + } + + template, int> = 0> + static T load(const char *s) + { + vec3_t vec; + static char vec_buffer[32]; + const char *token = COM_Parse(&s, vec_buffer, sizeof(vec_buffer)); + vec.x = atof(token); + token = COM_Parse(&s); + vec.y = atof(token); + token = COM_Parse(&s); + vec.z = atof(token); + return vec; + } +}; + +#define AUTO_LOADER_FUNC(M) \ + [](edict_t *e, const char *s) { \ + e->M = type_loaders_t::loadM)>(s); \ + } + +static int32_t ED_LoadColor(const char *value) +{ + // space means rgba as values + if (strchr(value, ' ')) + { + static char color_buffer[32]; + std::array raw_values { 0, 0, 0, 1.0f }; + bool is_float = true; + + for (auto &v : raw_values) + { + const char *token = COM_Parse(&value, color_buffer, sizeof(color_buffer)); + + if (*token) + { + v = atof(token); + + if (v > 1.0f) + is_float = false; + } + } + + if (is_float) + for (auto &v : raw_values) + v *= 255.f; + + return ((int32_t) raw_values[3]) | (((int32_t) raw_values[2]) << 8) | (((int32_t) raw_values[1]) << 16) | (((int32_t) raw_values[0]) << 24); + } + + // integral + return atoi(value); +} + +#define FIELD_COLOR(n, x) \ + { n, [](edict_t *e, const char *s) { \ + e->x = ED_LoadColor(s); \ + } } + +// clang-format off +// fields that get copied directly to edict_t +#define FIELD_AUTO(x) \ + { #x, AUTO_LOADER_FUNC(x) } + +#define FIELD_AUTO_NAMED(n, x) \ + { n, AUTO_LOADER_FUNC(x) } + +static const std::initializer_list entity_fields = { + FIELD_AUTO(classname), + FIELD_AUTO(model), + FIELD_AUTO(spawnflags), + FIELD_AUTO(speed), + FIELD_AUTO(accel), + FIELD_AUTO(decel), + FIELD_AUTO(target), + FIELD_AUTO(targetname), + FIELD_AUTO(pathtarget), + FIELD_AUTO(deathtarget), + FIELD_AUTO(healthtarget), + FIELD_AUTO(itemtarget), + FIELD_AUTO(killtarget), + FIELD_AUTO(combattarget), + FIELD_AUTO(message), + FIELD_AUTO(team), + FIELD_AUTO(wait), + FIELD_AUTO(delay), + FIELD_AUTO(random), + FIELD_AUTO(move_origin), + FIELD_AUTO(move_angles), + FIELD_AUTO(style), + FIELD_AUTO(style_on), + FIELD_AUTO(style_off), + FIELD_AUTO(crosslevel_flags), + FIELD_AUTO(count), + FIELD_AUTO(health), + FIELD_AUTO(sounds), + { "light" }, + FIELD_AUTO(dmg), + FIELD_AUTO(mass), + FIELD_AUTO(volume), + FIELD_AUTO(attenuation), + FIELD_AUTO(map), + FIELD_AUTO_NAMED("origin", s.origin), + FIELD_AUTO_NAMED("angles", s.angles), + { "angle", [](edict_t *e, const char *value) { + e->s.angles = {}; + e->s.angles[YAW] = atof(value); + } }, + FIELD_COLOR("rgba", s.skinnum), // [Sam-KEX] + FIELD_AUTO(hackflags), // [Paril-KEX] n64 + FIELD_AUTO_NAMED("alpha", s.alpha), // [Paril-KEX] + FIELD_AUTO_NAMED("scale", s.scale), // [Paril-KEX] + { "mangle" }, // editor field + FIELD_AUTO_NAMED("dead_frame", monsterinfo.start_frame), // [Paril-KEX] + FIELD_AUTO_NAMED("frame", s.frame), + FIELD_AUTO_NAMED("effects", s.effects), + FIELD_AUTO_NAMED("renderfx", s.renderfx), + + // [Paril-KEX] fog keys + FIELD_AUTO_NAMED("fog_color", fog.color), + FIELD_AUTO_NAMED("fog_color_off", fog.color_off), + FIELD_AUTO_NAMED("fog_density", fog.density), + FIELD_AUTO_NAMED("fog_density_off", fog.density_off), + FIELD_AUTO_NAMED("fog_sky_factor", fog.sky_factor), + FIELD_AUTO_NAMED("fog_sky_factor_off", fog.sky_factor_off), + + FIELD_AUTO_NAMED("heightfog_falloff", heightfog.falloff), + FIELD_AUTO_NAMED("heightfog_density", heightfog.density), + FIELD_AUTO_NAMED("heightfog_start_color", heightfog.start_color), + FIELD_AUTO_NAMED("heightfog_start_dist", heightfog.start_dist), + FIELD_AUTO_NAMED("heightfog_end_color", heightfog.end_color), + FIELD_AUTO_NAMED("heightfog_end_dist", heightfog.end_dist), + + FIELD_AUTO_NAMED("heightfog_falloff_off", heightfog.falloff_off), + FIELD_AUTO_NAMED("heightfog_density_off", heightfog.density_off), + FIELD_AUTO_NAMED("heightfog_start_color_off", heightfog.start_color_off), + FIELD_AUTO_NAMED("heightfog_start_dist_off", heightfog.start_dist_off), + FIELD_AUTO_NAMED("heightfog_end_color_off", heightfog.end_color_off), + FIELD_AUTO_NAMED("heightfog_end_dist_off", heightfog.end_dist_off), + + // [Paril-KEX] func_eye stuff + FIELD_AUTO_NAMED("eye_position", move_origin), + FIELD_AUTO_NAMED("vision_cone", yaw_speed), + + // [Paril-KEX] for trigger_coop_relay + FIELD_AUTO_NAMED("message2", map), + FIELD_AUTO(mins), + FIELD_AUTO(maxs), + + // [Paril-KEX] customizable bmodel animations + FIELD_AUTO_NAMED("bmodel_anim_start", bmodel_anim.start), + FIELD_AUTO_NAMED("bmodel_anim_end", bmodel_anim.end), + FIELD_AUTO_NAMED("bmodel_anim_style", bmodel_anim.style), + FIELD_AUTO_NAMED("bmodel_anim_speed", bmodel_anim.speed), + FIELD_AUTO_NAMED("bmodel_anim_nowrap", bmodel_anim.nowrap), + + FIELD_AUTO_NAMED("bmodel_anim_alt_start", bmodel_anim.alt_start), + FIELD_AUTO_NAMED("bmodel_anim_alt_end", bmodel_anim.alt_end), + FIELD_AUTO_NAMED("bmodel_anim_alt_style", bmodel_anim.alt_style), + FIELD_AUTO_NAMED("bmodel_anim_alt_speed", bmodel_anim.alt_speed), + FIELD_AUTO_NAMED("bmodel_anim_alt_nowrap", bmodel_anim.alt_nowrap), + + // [Paril-KEX] customizable power armor stuff + FIELD_AUTO_NAMED("power_armor_power", monsterinfo.power_armor_power), + { "power_armor_type", [](edict_t *s, const char *v) + { + int32_t type = atoi(v); + + if (type == 0) + s->monsterinfo.power_armor_type = IT_NULL; + else if (type == 1) + s->monsterinfo.power_armor_type = IT_ITEM_POWER_SCREEN; + else + s->monsterinfo.power_armor_type = IT_ITEM_POWER_SHIELD; + } + }, + + FIELD_AUTO_NAMED("monster_slots", monsterinfo.monster_slots) +}; + +#undef AUTO_LOADER_FUNC + +#define AUTO_LOADER_FUNC(M) \ + [](spawn_temp_t *e, const char *s) { \ + e->M = type_loaders_t::loadM)>(s); \ + } + +struct temp_field_t +{ + const char *name; + void (*load_func) (spawn_temp_t *e, const char *s) = nullptr; +}; + +// temp spawn vars -- only valid when the spawn function is called +// (copied to `st`) +static const std::initializer_list temp_fields = { + FIELD_AUTO(lip), + FIELD_AUTO(distance), + FIELD_AUTO(height), + FIELD_AUTO(noise), + FIELD_AUTO(pausetime), + FIELD_AUTO(item), + + FIELD_AUTO(gravity), + FIELD_AUTO(sky), + FIELD_AUTO(skyrotate), + FIELD_AUTO(skyaxis), + FIELD_AUTO(skyautorotate), + FIELD_AUTO(minyaw), + FIELD_AUTO(maxyaw), + FIELD_AUTO(minpitch), + FIELD_AUTO(maxpitch), + FIELD_AUTO(nextmap), + FIELD_AUTO(music), // [Edward-KEX] + FIELD_AUTO(instantitems), + FIELD_AUTO(radius), // [Paril-KEX] + FIELD_AUTO(hub_map), + FIELD_AUTO(achievement), + + FIELD_AUTO_NAMED("shadowlightradius", sl.data.radius), + FIELD_AUTO_NAMED("shadowlightresolution", sl.data.resolution), + FIELD_AUTO_NAMED("shadowlightintensity", sl.data.intensity), + FIELD_AUTO_NAMED("shadowlightstartfadedistance", sl.data.fade_start), + FIELD_AUTO_NAMED("shadowlightendfadedistance", sl.data.fade_end), + FIELD_AUTO_NAMED("shadowlightstyle", sl.data.lightstyle), + FIELD_AUTO_NAMED("shadowlightconeangle", sl.data.coneangle), + FIELD_AUTO_NAMED("shadowlightstyletarget", sl.lightstyletarget), + + FIELD_AUTO(goals), + + FIELD_AUTO(image), + + FIELD_AUTO(fade_start_dist), + FIELD_AUTO(fade_end_dist), + FIELD_AUTO(start_items), + FIELD_AUTO(no_grapple), + FIELD_AUTO(health_multiplier), + + FIELD_AUTO(reinforcements), + FIELD_AUTO(noise_start), + FIELD_AUTO(noise_middle), + FIELD_AUTO(noise_end), + + FIELD_AUTO(loop_count) +}; +// clang-format on + +/* +=============== +ED_ParseField + +Takes a key/value pair and sets the binary values +in an edict +=============== +*/ +void ED_ParseField(const char *key, const char *value, edict_t *ent) +{ + // check st first + for (auto &f : temp_fields) + { + if (Q_strcasecmp(f.name, key)) + continue; + + st.keys_specified.emplace(f.name); + + // found it + if (f.load_func) + f.load_func(&st, value); + + return; + } + + // now entity + for (auto &f : entity_fields) + { + if (Q_strcasecmp(f.name, key)) + continue; + + st.keys_specified.emplace(f.name); + + // [Paril-KEX] + if (!strcmp(f.name, "bmodel_anim_start") || !strcmp(f.name, "bmodel_anim_end")) + ent->bmodel_anim.enabled = true; + + // found it + if (f.load_func) + f.load_func(ent, value); + + return; + } + + gi.Com_PrintFmt("{} is not a valid field\n", key); +} + +/* +==================== +ED_ParseEdict + +Parses an edict out of the given string, returning the new position +ed should be a properly initialized empty edict. +==================== +*/ +const char *ED_ParseEdict(const char *data, edict_t *ent) +{ + bool init; + char keyname[256]; + const char *com_token; + + init = false; + st = {}; + + // go through all the dictionary pairs + while (1) + { + // parse key + com_token = COM_Parse(&data); + if (com_token[0] == '}') + break; + if (!data) + gi.Com_Error("ED_ParseEntity: EOF without closing brace"); + + Q_strlcpy(keyname, com_token, sizeof(keyname)); + + // parse value + com_token = COM_Parse(&data); + if (!data) + gi.Com_Error("ED_ParseEntity: EOF without closing brace"); + + if (com_token[0] == '}') + gi.Com_Error("ED_ParseEntity: closing brace without data"); + + init = true; + + // keynames with a leading underscore are used for utility comments, + // and are immediately discarded by quake + if (keyname[0] == '_') + { + // [Sam-KEX] Hack for setting RGBA for shadow-casting lights + if(!strcmp(keyname, "_color")) + ent->s.skinnum = ED_LoadColor(com_token); + + continue; + } + + ED_ParseField(keyname, com_token, ent); + } + + if (!init) + memset(ent, 0, sizeof(*ent)); + + return data; +} + +/* +================ +G_FindTeams + +Chain together all entities with a matching team field. + +All but the first will have the FL_TEAMSLAVE flag set. +All but the last will have the teamchain field set to the next one +================ +*/ + +// adjusts teams so that trains that move their children +// are in the front of the team +void G_FixTeams() +{ + edict_t *e, *e2, *chain; + uint32_t i, j; + uint32_t c; + + c = 0; + for (i = 1, e = g_edicts + i; i < globals.num_edicts; i++, e++) + { + if (!e->inuse) + continue; + if (!e->team) + continue; + if (!strcmp(e->classname, "func_train") && e->spawnflags.has(SPAWNFLAG_TRAIN_MOVE_TEAMCHAIN)) + { + if (e->flags & FL_TEAMSLAVE) + { + chain = e; + e->teammaster = e; + e->teamchain = nullptr; + e->flags &= ~FL_TEAMSLAVE; + e->flags |= FL_TEAMMASTER; + c++; + for (j = 1, e2 = g_edicts + j; j < globals.num_edicts; j++, e2++) + { + if (e2 == e) + continue; + if (!e2->inuse) + continue; + if (!e2->team) + continue; + if (!strcmp(e->team, e2->team)) + { + chain->teamchain = e2; + e2->teammaster = e; + e2->teamchain = nullptr; + chain = e2; + e2->flags |= FL_TEAMSLAVE; + e2->flags &= ~FL_TEAMMASTER; + e2->movetype = MOVETYPE_PUSH; + e2->speed = e->speed; + } + } + } + } + } + + gi.Com_PrintFmt("{} teams repaired\n", c); +} + +void G_FindTeams() +{ + edict_t *e, *e2, *chain; + uint32_t i, j; + uint32_t c, c2; + + c = 0; + c2 = 0; + for (i = 1, e = g_edicts + i; i < globals.num_edicts; i++, e++) + { + if (!e->inuse) + continue; + if (!e->team) + continue; + if (e->flags & FL_TEAMSLAVE) + continue; + chain = e; + e->teammaster = e; + e->flags |= FL_TEAMMASTER; + c++; + c2++; + for (j = i + 1, e2 = e + 1; j < globals.num_edicts; j++, e2++) + { + if (!e2->inuse) + continue; + if (!e2->team) + continue; + if (e2->flags & FL_TEAMSLAVE) + continue; + if (!strcmp(e->team, e2->team)) + { + c2++; + chain->teamchain = e2; + e2->teammaster = e; + chain = e2; + e2->flags |= FL_TEAMSLAVE; + } + } + } + + // ROGUE + G_FixTeams(); + // ROGUE + + gi.Com_PrintFmt("{} teams with {} entities\n", c, c2); +} + +// inhibit entities from game based on cvars & spawnflags +inline bool G_InhibitEntity(edict_t *ent) +{ + // dm-only + if (deathmatch->integer) + return ent->spawnflags.has(SPAWNFLAG_NOT_DEATHMATCH); + + // coop flags + if (coop->integer && ent->spawnflags.has(SPAWNFLAG_NOT_COOP)) + return true; + else if (!coop->integer && ent->spawnflags.has(SPAWNFLAG_COOP_ONLY)) + return true; + + // skill + return ((skill->integer == 0) && ent->spawnflags.has(SPAWNFLAG_NOT_EASY)) || + ((skill->integer == 1) && ent->spawnflags.has(SPAWNFLAG_NOT_MEDIUM)) || + ((skill->integer >= 2) && ent->spawnflags.has(SPAWNFLAG_NOT_HARD)); +} + +void setup_shadow_lights(); + +// [Paril-KEX] +void G_PrecacheInventoryItems() +{ + if (deathmatch->integer) + return; + + for (size_t i = 0; i < game.maxclients; i++) + { + gclient_t *cl = g_edicts[i + 1].client; + + if (!cl) + continue; + + for (item_id_t id = IT_NULL; id != IT_TOTAL; id = static_cast(id + 1)) + if (cl->pers.inventory[id]) + PrecacheItem(GetItemByIndex(id)); + } +} + +// [Paril-KEX] +static void G_PrecacheStartItems() +{ + if (!*g_start_items->string) + return; + + char token_copy[MAX_TOKEN_CHARS]; + const char *token; + const char *ptr = g_start_items->string; + + while (*(token = COM_ParseEx(&ptr, ";"))) + { + Q_strlcpy(token_copy, token, sizeof(token_copy)); + const char *ptr_copy = token_copy; + + const char *item_name = COM_Parse(&ptr_copy); + gitem_t *item = FindItemByClassname(item_name); + + if (!item || !item->pickup) + gi.Com_ErrorFmt("Invalid g_start_item entry: {}\n", item_name); + + if (*ptr_copy) + COM_Parse(&ptr_copy); + + PrecacheItem(item); + } +} + +/* +============== +SpawnEntities + +Creates a server's entity / program execution context by +parsing textual entity definitions out of an ent file. +============== +*/ +void SpawnEntities(const char *mapname, const char *entities, const char *spawnpoint) +{ + edict_t *ent; + int inhibit; + const char *com_token; + + int skill_level = clamp(skill->integer, 0, 3); + if (skill->integer != skill_level) + gi.cvar_forceset("skill", G_Fmt("{}", skill_level).data()); + + SaveClientData(); + + gi.FreeTags(TAG_LEVEL); + + memset(&level, 0, sizeof(level)); + memset(g_edicts, 0, game.maxentities * sizeof(g_edicts[0])); + + // all other flags are not important atm + globals.server_flags &= SERVER_FLAG_LOADING; + + Q_strlcpy(level.mapname, mapname, sizeof(level.mapname)); + // Paril: fixes a bug where autosaves will start you at + // the wrong spawnpoint if they happen to be non-empty + // (mine2 -> mine3) + if (!game.autosaved) + Q_strlcpy(game.spawnpoint, spawnpoint, sizeof(game.spawnpoint)); + + level.is_n64 = strncmp(level.mapname, "q64/", 4) == 0; + + level.coop_scale_players = 0; + level.coop_health_scaling = clamp(g_coop_health_scaling->value, 0.f, 1.f); + + // set client fields on player ents + for (uint32_t i = 0; i < game.maxclients; i++) + { + g_edicts[i + 1].client = game.clients + i; + + // "disconnect" all players since the level is switching + game.clients[i].pers.connected = false; + game.clients[i].pers.spawned = false; + } + + ent = nullptr; + inhibit = 0; + + // reserve some spots for dead player bodies for coop / deathmatch + InitBodyQue(); + + // parse ents + while (1) + { + // parse the opening brace + com_token = COM_Parse(&entities); + if (!entities) + break; + if (com_token[0] != '{') + gi.Com_ErrorFmt("ED_LoadFromFile: found \"{}\" when expecting {{", com_token); + + if (!ent) + ent = g_edicts; + else + ent = G_Spawn(); + entities = ED_ParseEdict(entities, ent); + + // remove things (except the world) from different skill levels or deathmatch + if (ent != g_edicts) + { + if (G_InhibitEntity(ent)) + { + G_FreeEdict(ent); + inhibit++; + continue; + } + + ent->spawnflags &= ~SPAWNFLAG_EDITOR_MASK; + } + + if (!ent) + gi.Com_Error("invalid/empty entity string!"); + + // PGM - do this before calling the spawn function so it can be overridden. + ent->gravityVector[0] = 0.0; + ent->gravityVector[1] = 0.0; + ent->gravityVector[2] = -1.0; + // PGM + ED_CallSpawn(ent); + + ent->s.renderfx |= RF_IR_VISIBLE; // PGM + } + + gi.Com_PrintFmt("{} entities inhibited\n", inhibit); + + // precache start_items + G_PrecacheStartItems(); + + // precache player inventory items + G_PrecacheInventoryItems(); + + G_FindTeams(); + + // ZOID + CTFSpawn(); + // ZOID + + // ROGUE + if (deathmatch->integer) + { + if (g_dm_random_items->integer) + PrecacheForRandomRespawn(); + } + else + { + InitHintPaths(); // if there aren't hintpaths on this map, enable quick aborts + } + // ROGUE + + // ROGUE -- allow dm games to do init stuff right before game starts. + if (deathmatch->integer && gamerules->integer) + { + if (DMGame.PostInitSetup) + DMGame.PostInitSetup(); + } + // ROGUE + + setup_shadow_lights(); +} + +//=================================================================== + +#include "g_statusbar.h" + +// create & set the statusbar string for the current gamemode +static void G_InitStatusbar() +{ + statusbar_t sb; + + // ---- shared stuff that every gamemode uses ---- + sb.yb(-24); + + // health + sb.xv(0).hnum().xv(50).pic(STAT_HEALTH_ICON); + + // ammo + sb.ifstat(STAT_AMMO_ICON).xv(100).anum().xv(150).pic(STAT_AMMO_ICON).endifstat(); + + // armor + sb.ifstat(STAT_ARMOR_ICON).xv(200).rnum().xv(250).pic(STAT_ARMOR_ICON).endifstat(); + + // selected item + sb.ifstat(STAT_SELECTED_ICON).xv(296).pic(STAT_SELECTED_ICON).endifstat(); + + sb.yb(-50); + + // picked up item + sb.ifstat(STAT_PICKUP_ICON).xv(0).pic(STAT_PICKUP_ICON).xv(26).yb(-42).loc_stat_string(STAT_PICKUP_STRING).yb(-50).endifstat(); + + // selected item name + sb.ifstat(STAT_SELECTED_ITEM_NAME).yb(-34).xv(319).loc_stat_rstring(STAT_SELECTED_ITEM_NAME).yb(-58).endifstat(); + + // timer + sb.ifstat(STAT_TIMER_ICON).xv(262).num(2, STAT_TIMER).xv(296).pic(STAT_TIMER_ICON).endifstat(); + + sb.yb(-50); + + // help / weapon icon + sb.ifstat(STAT_HELPICON).xv(150).pic(STAT_HELPICON).endifstat(); + + // ---- gamemode-specific stuff ---- + if (!deathmatch->integer) + { + // SP/coop + // key display + // move up if the timer is active + // FIXME: ugly af + sb.ifstat(STAT_TIMER_ICON).yb(-76).endifstat(); + sb.ifstat(STAT_SELECTED_ITEM_NAME) + .yb(-58) + .ifstat(STAT_TIMER_ICON) + .yb(-84) + .endifstat() + .endifstat(); + sb.ifstat(STAT_KEY_A).xv(296).pic(STAT_KEY_A).endifstat(); + sb.ifstat(STAT_KEY_B).xv(272).pic(STAT_KEY_B).endifstat(); + sb.ifstat(STAT_KEY_C).xv(248).pic(STAT_KEY_C).endifstat(); + + if (coop->integer) + { + // top of screen coop respawn display + sb.ifstat(STAT_COOP_RESPAWN).xv(0).yt(0).loc_stat_cstring2(STAT_COOP_RESPAWN).endifstat(); + + // coop lives + sb.ifstat(STAT_LIVES).xr(-16).yt(2).lives_num(STAT_LIVES).xr(0).yt(28).loc_rstring("$g_lives").endifstat(); + } + + sb.ifstat(STAT_HEALTH_BARS).yt(24).health_bars().endifstat(); + } + else if (G_TeamplayEnabled()) + { + CTFPrecache(); + + // ctf/tdm + // red team + sb.yb(-110).ifstat(STAT_CTF_TEAM1_PIC).xr(-26).pic(STAT_CTF_TEAM1_PIC).endifstat().xr(-78).num(3, STAT_CTF_TEAM1_CAPS); + // joined overlay + sb.ifstat(STAT_CTF_JOINED_TEAM1_PIC).yb(-112).xr(-28).pic(STAT_CTF_JOINED_TEAM1_PIC).endifstat(); + + // blue team + sb.yb(-83).ifstat(STAT_CTF_TEAM2_PIC).xr(-26).pic(STAT_CTF_TEAM2_PIC).endifstat().xr(-78).num(3, STAT_CTF_TEAM2_CAPS); + // joined overlay + sb.ifstat(STAT_CTF_JOINED_TEAM2_PIC).yb(-85).xr(-28).pic(STAT_CTF_JOINED_TEAM2_PIC).endifstat(); + + if (ctf->integer) + { + // have flag graph + sb.ifstat(STAT_CTF_FLAG_PIC).yt(26).xr(-24).pic(STAT_CTF_FLAG_PIC).endifstat(); + } + + // id view state + sb.ifstat(STAT_CTF_ID_VIEW).xv(112).yb(-58).stat_pname(STAT_CTF_ID_VIEW).endifstat(); + + // id view color + sb.ifstat(STAT_CTF_ID_VIEW_COLOR).xv(96).yb(-58).pic(STAT_CTF_ID_VIEW_COLOR).endifstat(); + + if (ctf->integer) + { + // match + sb.ifstat(STAT_CTF_MATCH).xl(0).yb(-78).stat_string(STAT_CTF_MATCH).endifstat(); + } + + // team info + sb.ifstat(STAT_CTF_TEAMINFO).xl(0).yb(-88).stat_string(STAT_CTF_TEAMINFO).endifstat(); + } + else + { + // dm + // frags + sb.xr(-50).yt(2).num(3, STAT_FRAGS); + + // spectator + sb.ifstat(STAT_SPECTATOR).xv(0).yb(-58).string2("SPECTATOR MODE").endifstat(); + + // chase cam + sb.ifstat(STAT_CHASE).xv(0).yb(-68).string("CHASING").xv(64).stat_string(STAT_CHASE).endifstat(); + } + + // ---- more shared stuff ---- + if (deathmatch->integer) + { + // tech + sb.ifstat(STAT_CTF_TECH).yb(-137).xr(-26).pic(STAT_CTF_TECH).endifstat(); + } + else + { + sb.story(); + } + + gi.configstring(CS_STATUSBAR, sb.sb.str().c_str()); +} + + +/*QUAKED worldspawn (0 0 0) ? + +Only used for the world. +"sky" environment map name +"skyaxis" vector axis for rotating sky +"skyrotate" speed of rotation in degrees/second +"sounds" music cd track number +"gravity" 800 is default gravity +"message" text to print at user logon +*/ +void SP_worldspawn(edict_t *ent) +{ + ent->movetype = MOVETYPE_PUSH; + ent->solid = SOLID_BSP; + ent->inuse = true; // since the world doesn't use G_Spawn() + ent->s.modelindex = MODELINDEX_WORLD; + ent->gravity = 1.0f; + + if (st.hub_map) + { + level.hub_map = true; + + // clear helps + game.help1changed = game.help2changed = 0; + *game.helpmessage1 = *game.helpmessage2 = '\0'; + + for (size_t i = 0; i < game.maxclients; i++) + { + game.clients[i].pers.game_help1changed = game.clients[i].pers.game_help2changed = 0; + game.clients[i].resp.coop_respawn.game_help1changed = game.clients[i].resp.coop_respawn.game_help2changed = 0; + } + } + + if (st.achievement && st.achievement[0]) + level.achievement = st.achievement; + + //--------------- + + // set configstrings for items + SetItemNames(); + + if (st.nextmap) + Q_strlcpy(level.nextmap, st.nextmap, sizeof(level.nextmap)); + + // make some data visible to the server + + if (ent->message && ent->message[0]) + { + gi.configstring(CS_NAME, ent->message); + Q_strlcpy(level.level_name, ent->message, sizeof(level.level_name)); + } + else + Q_strlcpy(level.level_name, level.mapname, sizeof(level.level_name)); + + if (st.sky && st.sky[0]) + gi.configstring(CS_SKY, st.sky); + else + gi.configstring(CS_SKY, "unit1_"); + + gi.configstring(CS_SKYROTATE, G_Fmt("{} {}", st.skyrotate, st.skyautorotate).data()); + + gi.configstring(CS_SKYAXIS, G_Fmt("{}", st.skyaxis).data()); + + if (st.music && st.music[0]) + { + gi.configstring(CS_CDTRACK, st.music); + } + else + { + gi.configstring(CS_CDTRACK, G_Fmt("{}", ent->sounds).data()); + } + + if (level.is_n64) + gi.configstring(CS_CD_LOOP_COUNT, "0"); + else if (st.was_key_specified("loop_count")) + gi.configstring(CS_CD_LOOP_COUNT, G_Fmt("{}", st.loop_count).data()); + else + gi.configstring(CS_CD_LOOP_COUNT, ""); + + if (st.instantitems > 0 || level.is_n64) + { + level.instantitems = true; + } + + // [Paril-KEX] + if (!deathmatch->integer) + gi.configstring(CS_GAME_STYLE, G_Fmt("{}", (int32_t) game_style_t::GAME_STYLE_PVE).data()); + else if (teamplay->integer || ctf->integer) + gi.configstring(CS_GAME_STYLE, G_Fmt("{}", (int32_t) game_style_t::GAME_STYLE_TDM).data()); + else + gi.configstring(CS_GAME_STYLE, G_Fmt("{}", (int32_t) game_style_t::GAME_STYLE_FFA).data()); + + // [Paril-KEX] + if (st.goals) + { + level.goals = st.goals; + game.help1changed++; + } + + if (st.start_items) + level.start_items = st.start_items; + + if (st.no_grapple) + level.no_grapple = st.no_grapple; + + gi.configstring(CS_MAXCLIENTS, G_Fmt("{}", game.maxclients).data()); + + if (level.is_n64) + { + gi.configstring(CONFIG_N64_PHYSICS, "1"); + pm_config.n64_physics = true; + } + + // statusbar prog + G_InitStatusbar(); + + // [Paril-KEX] air accel handled by game DLL now, and allow + // it to be changed in sp/coop + gi.configstring(CS_AIRACCEL, G_Fmt("{}", sv_airaccelerate->integer).data()); + pm_config.airaccel = sv_airaccelerate->integer; + + game.airacceleration_modified = sv_airaccelerate->modified_count; + + //--------------- + + // help icon for statusbar + gi.imageindex("i_help"); + level.pic_health = gi.imageindex("i_health"); + gi.imageindex("help"); + gi.imageindex("field_3"); + + if (!st.gravity) + { + level.gravity = 800.f; + gi.cvar_set("sv_gravity", "800"); + } + else + { + level.gravity = atof(st.gravity); + gi.cvar_set("sv_gravity", st.gravity); + } + + snd_fry = gi.soundindex("player/fry.wav"); // standing in lava / slime + + PrecacheItem(GetItemByIndex(IT_ITEM_COMPASS)); + PrecacheItem(GetItemByIndex(IT_WEAPON_BLASTER)); + + if (g_dm_random_items->integer) + for (item_id_t i = static_cast(IT_NULL + 1); i < IT_TOTAL; i = static_cast(i + 1)) + PrecacheItem(GetItemByIndex(i)); + + gi.soundindex("player/lava1.wav"); + gi.soundindex("player/lava2.wav"); + + gi.soundindex("misc/pc_up.wav"); + gi.soundindex("misc/talk1.wav"); + + // gibs + gi.soundindex("misc/udeath.wav"); + + gi.soundindex("items/respawn1.wav"); + gi.soundindex("misc/mon_power2.wav"); + + // sexed sounds + gi.soundindex("*death1.wav"); + gi.soundindex("*death2.wav"); + gi.soundindex("*death3.wav"); + gi.soundindex("*death4.wav"); + gi.soundindex("*fall1.wav"); + gi.soundindex("*fall2.wav"); + gi.soundindex("*gurp1.wav"); // drowning damage + gi.soundindex("*gurp2.wav"); + gi.soundindex("*jump1.wav"); // player jump + gi.soundindex("*pain25_1.wav"); + gi.soundindex("*pain25_2.wav"); + gi.soundindex("*pain50_1.wav"); + gi.soundindex("*pain50_2.wav"); + gi.soundindex("*pain75_1.wav"); + gi.soundindex("*pain75_2.wav"); + gi.soundindex("*pain100_1.wav"); + gi.soundindex("*pain100_2.wav"); + + // sexed models + for (auto &item : itemlist) + item.vwep_index = 0; + + for (auto &item : itemlist) + { + if (!item.vwep_model) + continue; + + for (auto &check : itemlist) + { + if (check.vwep_model && !Q_strcasecmp(item.vwep_model, check.vwep_model) && check.vwep_index) + { + item.vwep_index = check.vwep_index; + break; + } + } + + if (item.vwep_index) + continue; + + item.vwep_index = gi.modelindex(item.vwep_model); + + if (!level.vwep_offset) + level.vwep_offset = item.vwep_index; + } + + //------------------- + + gi.soundindex("player/gasp1.wav"); // gasping for air + gi.soundindex("player/gasp2.wav"); // head breaking surface, not gasping + + gi.soundindex("player/watr_in.wav"); // feet hitting water + gi.soundindex("player/watr_out.wav"); // feet leaving water + + gi.soundindex("player/watr_un.wav"); // head going underwater + + gi.soundindex("player/u_breath1.wav"); + gi.soundindex("player/u_breath2.wav"); + + gi.soundindex("items/pkup.wav"); // bonus item pickup + gi.soundindex("world/land.wav"); // landing thud + gi.soundindex("misc/h2ohit1.wav"); // landing splash + + gi.soundindex("items/damage.wav"); + gi.soundindex("items/protect.wav"); + gi.soundindex("items/protect4.wav"); + gi.soundindex("weapons/noammo.wav"); + gi.soundindex("weapons/lowammo.wav"); + gi.soundindex("weapons/change.wav"); + + gi.soundindex("infantry/inflies1.wav"); + + sm_meat_index = gi.modelindex("models/objects/gibs/sm_meat/tris.md2"); + gi.modelindex("models/objects/gibs/arm/tris.md2"); + gi.modelindex("models/objects/gibs/bone/tris.md2"); + gi.modelindex("models/objects/gibs/bone2/tris.md2"); + gi.modelindex("models/objects/gibs/chest/tris.md2"); + gi.modelindex("models/objects/gibs/skull/tris.md2"); + gi.modelindex("models/objects/gibs/head2/tris.md2"); + gi.modelindex("models/objects/gibs/sm_metal/tris.md2"); + + gi.imageindex("loc_ping"); + + // + // Setup light animation tables. 'a' is total darkness, 'z' is doublebright. + // + + // 0 normal + gi.configstring(CS_LIGHTS + 0, "m"); + + // 1 FLICKER (first variety) + gi.configstring(CS_LIGHTS + 1, "mmnmmommommnonmmonqnmmo"); + + // 2 SLOW STRONG PULSE + gi.configstring(CS_LIGHTS + 2, "abcdefghijklmnopqrstuvwxyzyxwvutsrqponmlkjihgfedcba"); + + // 3 CANDLE (first variety) + gi.configstring(CS_LIGHTS + 3, "mmmmmaaaaammmmmaaaaaabcdefgabcdefg"); + + // 4 FAST STROBE + gi.configstring(CS_LIGHTS + 4, "mamamamamama"); + + // 5 GENTLE PULSE 1 + gi.configstring(CS_LIGHTS + 5, "jklmnopqrstuvwxyzyxwvutsrqponmlkj"); + + // 6 FLICKER (second variety) + gi.configstring(CS_LIGHTS + 6, "nmonqnmomnmomomno"); + + // 7 CANDLE (second variety)`map + gi.configstring(CS_LIGHTS + 7, "mmmaaaabcdefgmmmmaaaammmaamm"); + + // 8 CANDLE (third variety) + gi.configstring(CS_LIGHTS + 8, "mmmaaammmaaammmabcdefaaaammmmabcdefmmmaaaa"); + + // 9 SLOW STROBE (fourth variety) + gi.configstring(CS_LIGHTS + 9, "aaaaaaaazzzzzzzz"); + + // 10 FLUORESCENT FLICKER + gi.configstring(CS_LIGHTS + 10, "mmamammmmammamamaaamammma"); + + // 11 SLOW PULSE NOT FADE TO BLACK + gi.configstring(CS_LIGHTS + 11, "abcdefghijklmnopqrrqponmlkjihgfedcba"); + + // [Paril-KEX] 12 N64's 2 (fast strobe) + gi.configstring(CS_LIGHTS + 12, "zzazazzzzazzazazaaazazzza"); + + // [Paril-KEX] 13 N64's 3 (half of strong pulse) + gi.configstring(CS_LIGHTS + 13, "abcdefghijklmnopqrstuvwxyz"); + + // [Paril-KEX] 14 N64's 4 (fast strobe) + gi.configstring(CS_LIGHTS + 14, "abcdefghijklmnopqrstuvwxyzyxwvutsrqponmlkjihgfedcba"); + + // styles 32-62 are assigned by the light program for switchable lights + + // 63 testing + gi.configstring(CS_LIGHTS + 63, "a"); + + // coop respawn strings + if (coop->integer) + { + gi.configstring(CONFIG_COOP_RESPAWN_STRING + 0, "$g_coop_respawn_in_combat"); + gi.configstring(CONFIG_COOP_RESPAWN_STRING + 1, "$g_coop_respawn_bad_area"); + gi.configstring(CONFIG_COOP_RESPAWN_STRING + 2, "$g_coop_respawn_blocked"); + gi.configstring(CONFIG_COOP_RESPAWN_STRING + 3, "$g_coop_respawn_waiting"); + gi.configstring(CONFIG_COOP_RESPAWN_STRING + 4, "$g_coop_respawn_no_lives"); + } +} diff --git a/rerelease/g_statusbar.h b/rerelease/g_statusbar.h new file mode 100644 index 0000000..03ddc4a --- /dev/null +++ b/rerelease/g_statusbar.h @@ -0,0 +1,63 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. + +#include + +// easy statusbar wrapper +struct statusbar_t +{ + std::stringstream sb; + + inline auto &yb(int32_t offset) { sb << "yb " << offset << ' '; return *this; } + inline auto &yt(int32_t offset) { sb << "yt " << offset << ' '; return *this; } + inline auto &yv(int32_t offset) { sb << "yv " << offset << ' '; return *this; } + inline auto &xl(int32_t offset) { sb << "xl " << offset << ' '; return *this; } + inline auto &xr(int32_t offset) { sb << "xr " << offset << ' '; return *this; } + inline auto &xv(int32_t offset) { sb << "xv " << offset << ' '; return *this; } + + inline auto &ifstat(player_stat_t stat) { sb << "if " << stat << ' '; return *this; } + inline auto &endifstat() { sb << "endif "; return *this; } + + inline auto &pic(player_stat_t stat) { sb << "pic " << stat << ' '; return *this; } + inline auto &picn(const char *icon) { sb << "picn " << icon << ' '; return *this; } + + inline auto &anum() { sb << "anum "; return *this; } + inline auto &rnum() { sb << "rnum "; return *this; } + inline auto &hnum() { sb << "hnum "; return *this; } + inline auto &num(int32_t width, player_stat_t stat) { sb << "num " << width << ' ' << stat << ' '; return *this; } + + inline auto &loc_stat_string(player_stat_t stat) { sb << "loc_stat_string " << stat << ' '; return *this; } + inline auto &loc_stat_rstring(player_stat_t stat) { sb << "loc_stat_rstring " << stat << ' '; return *this; } + inline auto &stat_string(player_stat_t stat) { sb << "stat_string " << stat << ' '; return *this; } + inline auto &loc_stat_cstring2(player_stat_t stat) { sb << "loc_stat_cstring2 " << stat << ' '; return *this; } + inline auto &string2(const char *str) + { + if (str[0] != '"' && (strchr(str, ' ') || strchr(str, '\n'))) + sb << "string2 \"" << str << "\" "; + else + sb << "string2 " << str << ' '; + return *this; + } + inline auto &string(const char *str) + { + if (str[0] != '"' && (strchr(str, ' ') || strchr(str, '\n'))) + sb << "string \"" << str << "\" "; + else + sb << "string " << str << ' '; + return *this; + } + inline auto &loc_rstring(const char *str) + { + if (str[0] != '"' && (strchr(str, ' ') || strchr(str, '\n'))) + sb << "loc_rstring 0 \"" << str << "\" "; + else + sb << "loc_rstring 0 " << str << ' '; + return *this; + } + + inline auto &lives_num(player_stat_t stat) { sb << "lives_num " << stat << ' '; return *this; } + inline auto &stat_pname(player_stat_t stat) { sb << "stat_pname " << stat << ' '; return *this; } + + inline auto &health_bars() { sb << "health_bars "; return *this; } + inline auto &story() { sb << "story "; return *this; } +}; \ No newline at end of file diff --git a/rerelease/g_svcmds.cpp b/rerelease/g_svcmds.cpp new file mode 100644 index 0000000..17e7402 --- /dev/null +++ b/rerelease/g_svcmds.cpp @@ -0,0 +1,302 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. + +#include "g_local.h" + +void Svcmd_Test_f() +{ + gi.LocClient_Print(nullptr, PRINT_HIGH, "Svcmd_Test_f()\n"); +} + +/* +============================================================================== + +PACKET FILTERING + + +You can add or remove addresses from the filter list with: + +addip +removeip + +The ip address is specified in dot format, and any unspecified digits will match any value, so you can specify an entire +class C network with "addip 192.246.40". + +Removeip will only remove an address specified exactly the same way. You cannot addip a subnet, then removeip a single +host. + +listip +Prints the current list of filters. + +writeip +Dumps "addip " commands to listip.cfg so it can be execed at a later date. The filter lists are not saved and +restored by default, because I beleive it would cause too much confusion. + +filterban <0 or 1> + +If 1 (the default), then ip addresses matching the current list will be prohibited from entering the game. This is the +default setting. + +If 0, then only addresses matching the list will be allowed. This lets you easily set up a private game, or a game that +only allows players from your local network. + + +============================================================================== +*/ + +struct ipfilter_t +{ + unsigned mask; + unsigned compare; +}; + +constexpr size_t MAX_IPFILTERS = 1024; + +ipfilter_t ipfilters[MAX_IPFILTERS]; +int numipfilters; + +/* +================= +StringToFilter +================= +*/ +static bool StringToFilter(const char *s, ipfilter_t *f) +{ + char num[128]; + int i, j; + byte b[4]; + byte m[4]; + + for (i = 0; i < 4; i++) + { + b[i] = 0; + m[i] = 0; + } + + for (i = 0; i < 4; i++) + { + if (*s < '0' || *s > '9') + { + gi.LocClient_Print(nullptr, PRINT_HIGH, "Bad filter address: {}\n", s); + return false; + } + + j = 0; + while (*s >= '0' && *s <= '9') + { + num[j++] = *s++; + } + num[j] = 0; + b[i] = atoi(num); + if (b[i] != 0) + m[i] = 255; + + if (!*s) + break; + s++; + } + + f->mask = *(unsigned *) m; + f->compare = *(unsigned *) b; + + return true; +} + +/* +================= +SV_FilterPacket +================= +*/ +bool SV_FilterPacket(const char *from) +{ + int i; + unsigned in; + byte m[4]; + const char *p; + + i = 0; + p = from; + while (*p && i < 4) + { + m[i] = 0; + while (*p >= '0' && *p <= '9') + { + m[i] = m[i] * 10 + (*p - '0'); + p++; + } + if (!*p || *p == ':') + break; + i++; + p++; + } + + in = *(unsigned *) m; + + for (i = 0; i < numipfilters; i++) + if ((in & ipfilters[i].mask) == ipfilters[i].compare) + return filterban->integer; + + return !filterban->integer; +} + +/* +================= +SV_AddIP_f +================= +*/ +void SVCmd_AddIP_f() +{ + int i; + + if (gi.argc() < 3) + { + gi.LocClient_Print(nullptr, PRINT_HIGH, "Usage: addip \n"); + return; + } + + for (i = 0; i < numipfilters; i++) + if (ipfilters[i].compare == 0xffffffff) + break; // free spot + if (i == numipfilters) + { + if (numipfilters == MAX_IPFILTERS) + { + gi.LocClient_Print(nullptr, PRINT_HIGH, "IP filter list is full\n"); + return; + } + numipfilters++; + } + + if (!StringToFilter(gi.argv(2), &ipfilters[i])) + ipfilters[i].compare = 0xffffffff; +} + +/* +================= +SV_RemoveIP_f +================= +*/ +void SVCmd_RemoveIP_f() +{ + ipfilter_t f; + int i, j; + + if (gi.argc() < 3) + { + gi.LocClient_Print(nullptr, PRINT_HIGH, "Usage: sv removeip \n"); + return; + } + + if (!StringToFilter(gi.argv(2), &f)) + return; + + for (i = 0; i < numipfilters; i++) + if (ipfilters[i].mask == f.mask && ipfilters[i].compare == f.compare) + { + for (j = i + 1; j < numipfilters; j++) + ipfilters[j - 1] = ipfilters[j]; + numipfilters--; + gi.LocClient_Print(nullptr, PRINT_HIGH, "Removed.\n"); + return; + } + gi.LocClient_Print(nullptr, PRINT_HIGH, "Didn't find {}.\n", gi.argv(2)); +} + +/* +================= +SV_ListIP_f +================= +*/ +void SVCmd_ListIP_f() +{ + int i; + byte b[4]; + + gi.LocClient_Print(nullptr, PRINT_HIGH, "Filter list:\n"); + for (i = 0; i < numipfilters; i++) + { + *(unsigned *) b = ipfilters[i].compare; + gi.LocClient_Print(nullptr, PRINT_HIGH, "{}.{}.{}.{}\n", b[0], b[1], b[2], b[3]); + } +} + +// [Paril-KEX] +void SVCmd_NextMap_f() +{ + gi.LocBroadcast_Print(PRINT_HIGH, "$g_map_ended_by_server"); + EndDMLevel(); +} + +/* +================= +SV_WriteIP_f +================= +*/ +void SVCmd_WriteIP_f(void) +{ + // KEX_FIXME: Sys_FOpen isn't available atm, just commenting this out since i don't think we even need this functionality - sponge + /* + FILE* f; + + byte b[4]; + int i; + cvar_t* game; + + game = gi.cvar("game", "", 0); + + std::string name; + if (!*game->string) + name = std::string(GAMEVERSION) + "/listip.cfg"; + else + name = std::string(game->string) + "/listip.cfg"; + + gi.LocClient_Print(nullptr, PRINT_HIGH, "Writing {}.\n", name.c_str()); + + f = Sys_FOpen(name.c_str(), "wb"); + if (!f) + { + gi.LocClient_Print(nullptr, PRINT_HIGH, "Couldn't open {}\n", name.c_str()); + return; + } + + fprintf(f, "set filterban %d\n", filterban->integer); + + for (i = 0; i < numipfilters; i++) + { + *(unsigned*)b = ipfilters[i].compare; + fprintf(f, "sv addip %i.%i.%i.%i\n", b[0], b[1], b[2], b[3]); + } + + fclose(f); + */ +} + +/* +================= +ServerCommand + +ServerCommand will be called when an "sv" command is issued. +The game can issue gi.argc() / gi.argv() commands to get the rest +of the parameters +================= +*/ +void ServerCommand() +{ + const char *cmd; + + cmd = gi.argv(1); + if (Q_strcasecmp(cmd, "test") == 0) + Svcmd_Test_f(); + else if (Q_strcasecmp(cmd, "addip") == 0) + SVCmd_AddIP_f(); + else if (Q_strcasecmp(cmd, "removeip") == 0) + SVCmd_RemoveIP_f(); + else if (Q_strcasecmp(cmd, "listip") == 0) + SVCmd_ListIP_f(); + else if (Q_strcasecmp(cmd, "writeip") == 0) + SVCmd_WriteIP_f(); + else if (Q_strcasecmp(cmd, "nextmap") == 0) + SVCmd_NextMap_f(); + else + gi.LocClient_Print(nullptr, PRINT_HIGH, "Unknown server command \"{}\"\n", cmd); +} diff --git a/rerelease/g_target.cpp b/rerelease/g_target.cpp new file mode 100644 index 0000000..27cdd42 --- /dev/null +++ b/rerelease/g_target.cpp @@ -0,0 +1,2059 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +#include "g_local.h" + +/*QUAKED target_temp_entity (1 0 0) (-8 -8 -8) (8 8 8) +Fire an origin based temp entity event to the clients. +"style" type byte +*/ +USE(Use_Target_Tent) (edict_t *ent, edict_t *other, edict_t *activator) -> void +{ + gi.WriteByte(svc_temp_entity); + gi.WriteByte(ent->style); + gi.WritePosition(ent->s.origin); + gi.multicast(ent->s.origin, MULTICAST_PVS, false); +} + +void SP_target_temp_entity(edict_t *ent) +{ + if (level.is_n64 && ent->style == 27) + ent->style = TE_TELEPORT_EFFECT; + + ent->use = Use_Target_Tent; +} + +//========================================================== + +//========================================================== + +/*QUAKED target_speaker (1 0 0) (-8 -8 -8) (8 8 8) looped-on looped-off reliable +"noise" wav file to play +"attenuation" +-1 = none, send to whole level +1 = normal fighting sounds +2 = idle sound level +3 = ambient sound level +"volume" 0.0 to 1.0 + +Normal sounds play each time the target is used. The reliable flag can be set for crucial voiceovers. + +[Paril-KEX] looped sounds are by default atten 3 / vol 1, and the use function toggles it on/off. +*/ + +constexpr spawnflags_t SPAWNFLAG_SPEAKER_LOOPED_ON = 1_spawnflag; +constexpr spawnflags_t SPAWNFLAG_SPEAKER_LOOPED_OFF = 2_spawnflag; +constexpr spawnflags_t SPAWNFLAG_SPEAKER_RELIABLE = 4_spawnflag; +constexpr spawnflags_t SPAWNFLAG_SPEAKER_NO_STEREO = 8_spawnflag; + +USE(Use_Target_Speaker) (edict_t *ent, edict_t *other, edict_t *activator) -> void +{ + soundchan_t chan; + + if (ent->spawnflags.has(SPAWNFLAG_SPEAKER_LOOPED_ON | SPAWNFLAG_SPEAKER_LOOPED_OFF)) + { // looping sound toggles + if (ent->s.sound) + ent->s.sound = 0; // turn it off + else + ent->s.sound = ent->noise_index; // start it + } + else + { // normal sound + if (ent->spawnflags.has(SPAWNFLAG_SPEAKER_RELIABLE)) + chan = CHAN_VOICE | CHAN_RELIABLE; + else + chan = CHAN_VOICE; + // use a positioned_sound, because this entity won't normally be + // sent to any clients because it is invisible + gi.positioned_sound(ent->s.origin, ent, chan, ent->noise_index, ent->volume, ent->attenuation, 0); + } +} + +void SP_target_speaker(edict_t *ent) +{ + if (!st.noise) + { + gi.Com_PrintFmt("{}: no noise set\n", *ent); + return; + } + + if (!strstr(st.noise, ".wav")) + ent->noise_index = gi.soundindex(G_Fmt("{}.wav", st.noise).data()); + else + ent->noise_index = gi.soundindex(st.noise); + + if (!ent->volume) + ent->volume = ent->s.loop_volume = 1.0; + + if (!ent->attenuation) + { + if (ent->spawnflags.has(SPAWNFLAG_SPEAKER_LOOPED_OFF | SPAWNFLAG_SPEAKER_LOOPED_ON)) + ent->attenuation = ATTN_STATIC; + else + ent->attenuation = ATTN_NORM; + } + else if (ent->attenuation == -1) // use -1 so 0 defaults to 1 + { + if (ent->spawnflags.has(SPAWNFLAG_SPEAKER_LOOPED_OFF | SPAWNFLAG_SPEAKER_LOOPED_ON)) + { + ent->attenuation = ATTN_LOOP_NONE; + ent->svflags |= SVF_NOCULL; + } + else + ent->attenuation = ATTN_NONE; + } + + ent->s.loop_attenuation = ent->attenuation; + + // check for prestarted looping sound + if (ent->spawnflags.has(SPAWNFLAG_SPEAKER_LOOPED_ON)) + ent->s.sound = ent->noise_index; + + if (ent->spawnflags.has(SPAWNFLAG_SPEAKER_NO_STEREO)) + ent->s.renderfx |= RF_NO_STEREO; + + ent->use = Use_Target_Speaker; + + // must link the entity so we get areas and clusters so + // the server can determine who to send updates to + gi.linkentity(ent); +} + +//========================================================== + +constexpr spawnflags_t SPAWNFLAG_HELP_HELP1 = 1_spawnflag; +constexpr spawnflags_t SPAWNFLAG_SET_POI = 2_spawnflag; + +extern void target_poi_use(edict_t* ent, edict_t* other, edict_t* activator); +USE(Use_Target_Help) (edict_t *ent, edict_t *other, edict_t *activator) -> void +{ + if (ent->spawnflags.has(SPAWNFLAG_HELP_HELP1)) + { + if (strcmp(game.helpmessage1, ent->message)) + { + Q_strlcpy(game.helpmessage1, ent->message, sizeof(game.helpmessage1)); + game.help1changed++; + } + } + else + { + if (strcmp(game.helpmessage2, ent->message)) + { + Q_strlcpy(game.helpmessage2, ent->message, sizeof(game.helpmessage2)); + game.help2changed++; + } + } + + if (ent->spawnflags.has(SPAWNFLAG_SET_POI)) + { + target_poi_use(ent, other, activator); + } +} + +/*QUAKED target_help (1 0 1) (-16 -16 -24) (16 16 24) help1 setpoi +When fired, the "message" key becomes the current personal computer string, and the message light will be set on all clients status bars. +*/ +void SP_target_help(edict_t *ent) +{ + if (deathmatch->integer) + { // auto-remove for deathmatch + G_FreeEdict(ent); + return; + } + + if (!ent->message) + { + gi.Com_PrintFmt("{}: no message\n", *ent); + G_FreeEdict(ent); + return; + } + + ent->use = Use_Target_Help; + + if (ent->spawnflags.has(SPAWNFLAG_SET_POI)) + { + if (st.image) + ent->noise_index = gi.imageindex(st.image); + else + ent->noise_index = gi.imageindex("friend"); + } +} + +//========================================================== + +/*QUAKED target_secret (1 0 1) (-8 -8 -8) (8 8 8) +Counts a secret found. +These are single use targets. +*/ +USE(use_target_secret) (edict_t *ent, edict_t *other, edict_t *activator) -> void +{ + gi.sound(ent, CHAN_VOICE, ent->noise_index, 1, ATTN_NORM, 0); + + level.found_secrets++; + + G_UseTargets(ent, activator); + G_FreeEdict(ent); +} + +THINK(G_VerifyTargetted) (edict_t *ent) -> void +{ + if (!ent->targetname || !*ent->targetname) + gi.Com_PrintFmt("WARNING: missing targetname on {}\n", *ent); + else if (!G_FindByString<&edict_t::target>(nullptr, ent->targetname)) + gi.Com_PrintFmt("WARNING: doesn't appear to be anything targeting {}\n", *ent); +} + +void SP_target_secret(edict_t *ent) +{ + if (deathmatch->integer) + { // auto-remove for deathmatch + G_FreeEdict(ent); + return; + } + + ent->think = G_VerifyTargetted; + ent->nextthink = level.time + 10_ms; + + ent->use = use_target_secret; + if (!st.noise) + st.noise = "misc/secret.wav"; + ent->noise_index = gi.soundindex(st.noise); + ent->svflags = SVF_NOCLIENT; + level.total_secrets++; +} + +//========================================================== +// [Paril-KEX] notify this player of a goal change +void G_PlayerNotifyGoal(edict_t *player) +{ + // no goals in DM + if (deathmatch->integer) + return; + + if (!player->client->pers.spawned) + return; + else if ((level.time - player->client->resp.entertime) < 300_ms) + return; + + // N64 goals + if (level.goals) + { + // if the goal has updated, commit it first + if (game.help1changed != game.help2changed) + { + const char *current_goal = level.goals; + + // skip ahead by the number of goals we've finished + for (int32_t i = 0; i < level.goal_num; i++) + { + while (*current_goal && *current_goal != '\t') + current_goal++; + + if (!*current_goal) + gi.Com_Error("invalid n64 goals; tell Paril\n"); + + current_goal++; + } + + // find the end of this goal + const char *goal_end = current_goal; + + while (*goal_end && *goal_end != '\t') + goal_end++; + + Q_strlcpy(game.helpmessage1, current_goal, min((size_t) (goal_end - current_goal + 1), sizeof(game.helpmessage1))); + + game.help2changed = game.help1changed; + } + + if (player->client->pers.game_help1changed != game.help1changed) + { + gi.LocClient_Print(player, PRINT_TYPEWRITER, game.helpmessage1); + gi.local_sound(player, player, CHAN_AUTO | CHAN_RELIABLE, gi.soundindex("misc/talk.wav"), 1.0f, ATTN_NONE, 0.0f, GetUnicastKey()); + + player->client->pers.game_help1changed = game.help1changed; + } + + // no regular goals + return; + } + + if (player->client->pers.game_help1changed != game.help1changed) + { + player->client->pers.game_help1changed = game.help1changed; + player->client->pers.helpchanged = 1; + player->client->pers.help_time = level.time + 5_sec; + + if (*game.helpmessage1) + // [Sam-KEX] Print objective to screen + gi.LocClient_Print(player, PRINT_TYPEWRITER, "$g_primary_mission_objective", game.helpmessage1); + } + + if (player->client->pers.game_help2changed != game.help2changed) + { + player->client->pers.game_help2changed = game.help2changed; + player->client->pers.helpchanged = 1; + player->client->pers.help_time = level.time + 5_sec; + + if (*game.helpmessage2) + // [Sam-KEX] Print objective to screen + gi.LocClient_Print(player, PRINT_TYPEWRITER, "$g_secondary_mission_objective", game.helpmessage2); + } +} + +/*QUAKED target_goal (1 0 1) (-8 -8 -8) (8 8 8) KEEP_MUSIC +Counts a goal completed. +These are single use targets. +*/ +constexpr spawnflags_t SPAWNFLAG_GOAL_KEEP_MUSIC = 1_spawnflag; + +USE(use_target_goal) (edict_t *ent, edict_t *other, edict_t *activator) -> void +{ + gi.sound(ent, CHAN_VOICE, ent->noise_index, 1, ATTN_NORM, 0); + + level.found_goals++; + + if (level.found_goals == level.total_goals && !ent->spawnflags.has(SPAWNFLAG_GOAL_KEEP_MUSIC)) + { + if (ent->sounds) + gi.configstring (CS_CDTRACK, G_Fmt("{}", ent->sounds).data() ); + else + gi.configstring(CS_CDTRACK, "0"); + } + + // [Paril-KEX] n64 goals + if (level.goals) + { + level.goal_num++; + game.help1changed++; + + for (auto player : active_players()) + G_PlayerNotifyGoal(player); + } + + G_UseTargets(ent, activator); + G_FreeEdict(ent); +} + +void SP_target_goal(edict_t *ent) +{ + if (deathmatch->integer) + { // auto-remove for deathmatch + G_FreeEdict(ent); + return; + } + + ent->use = use_target_goal; + if (!st.noise) + st.noise = "misc/secret.wav"; + ent->noise_index = gi.soundindex(st.noise); + ent->svflags = SVF_NOCLIENT; + level.total_goals++; +} + +//========================================================== + +/*QUAKED target_explosion (1 0 0) (-8 -8 -8) (8 8 8) +Spawns an explosion temporary entity when used. + +"delay" wait this long before going off +"dmg" how much radius damage should be done, defaults to 0 +*/ +THINK(target_explosion_explode) (edict_t *self) -> void +{ + float save; + + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_EXPLOSION1); + gi.WritePosition(self->s.origin); + gi.multicast(self->s.origin, MULTICAST_PHS, false); + + T_RadiusDamage(self, self->activator, (float) self->dmg, nullptr, (float) self->dmg + 40, DAMAGE_NONE, MOD_EXPLOSIVE); + + save = self->delay; + self->delay = 0; + G_UseTargets(self, self->activator); + self->delay = save; +} + +USE(use_target_explosion) (edict_t *self, edict_t *other, edict_t *activator) -> void +{ + self->activator = activator; + + if (!self->delay) + { + target_explosion_explode(self); + return; + } + + self->think = target_explosion_explode; + self->nextthink = level.time + gtime_t::from_sec(self->delay); +} + +void SP_target_explosion(edict_t *ent) +{ + ent->use = use_target_explosion; + ent->svflags = SVF_NOCLIENT; +} + +//========================================================== + +/*QUAKED target_changelevel (1 0 0) (-8 -8 -8) (8 8 8) END_OF_UNIT UNKNOWN UNKNOWN CLEAR_INVENTORY NO_END_OF_UNIT FADE_OUT IMMEDIATE_LEAVE +Changes level to "map" when fired +*/ +USE(use_target_changelevel) (edict_t *self, edict_t *other, edict_t *activator) -> void +{ + if (level.intermissiontime) + return; // already activated + + if (!deathmatch->integer && !coop->integer) + { + if (g_edicts[1].health <= 0) + return; + } + + // if noexit, do a ton of damage to other + if (deathmatch->integer && !g_dm_allow_exit->integer && other != world) + { + T_Damage(other, self, self, vec3_origin, other->s.origin, vec3_origin, 10 * other->max_health, 1000, DAMAGE_NONE, MOD_EXIT); + return; + } + + // if multiplayer, let everyone know who hit the exit + if (deathmatch->integer) + { + if (level.time < 10_sec) + return; + + if (activator && activator->client) + gi.LocBroadcast_Print(PRINT_HIGH, "$g_exited_level", activator->client->pers.netname); + } + + // if going to a new unit, clear cross triggers + if (strstr(self->map, "*")) + game.cross_level_flags &= ~(SFL_CROSS_TRIGGER_MASK); + + // if map has a landmark, store position instead of using spawn next map + if (activator && activator->client && !deathmatch->integer) + { + activator->client->landmark_name = nullptr; + activator->client->landmark_rel_pos = vec3_origin; + + self->target_ent = G_PickTarget(self->target); + if (self->target_ent && activator && activator->client) + { + activator->client->landmark_name = G_CopyString(self->target_ent->targetname, TAG_GAME); + + // get relative vector to landmark pos, and unrotate by the landmark angles in preparation to be + // rotated by the next map + activator->client->landmark_rel_pos = activator->s.origin - self->target_ent->s.origin; + + activator->client->landmark_rel_pos = RotatePointAroundVector({ 1, 0, 0 }, activator->client->landmark_rel_pos, -self->target_ent->s.angles[0]); + activator->client->landmark_rel_pos = RotatePointAroundVector({ 0, 1, 0 }, activator->client->landmark_rel_pos, -self->target_ent->s.angles[2]); + activator->client->landmark_rel_pos = RotatePointAroundVector({ 0, 0, 1 }, activator->client->landmark_rel_pos, -self->target_ent->s.angles[1]); + + activator->client->oldvelocity = RotatePointAroundVector({ 1, 0, 0 }, activator->client->oldvelocity, -self->target_ent->s.angles[0]); + activator->client->oldvelocity = RotatePointAroundVector({ 0, 1, 0 }, activator->client->oldvelocity, -self->target_ent->s.angles[2]); + activator->client->oldvelocity = RotatePointAroundVector({ 0, 0, 1 }, activator->client->oldvelocity, -self->target_ent->s.angles[1]); + + // unrotate our view angles for the next map too + activator->client->oldviewangles = activator->client->ps.viewangles - self->target_ent->s.angles; + } + } + + BeginIntermission(self); +} + +void SP_target_changelevel(edict_t *ent) +{ + if (!ent->map) + { + gi.Com_PrintFmt("{}: no map\n", *ent); + G_FreeEdict(ent); + return; + } + + ent->use = use_target_changelevel; + ent->svflags = SVF_NOCLIENT; +} + +//========================================================== + +/*QUAKED target_splash (1 0 0) (-8 -8 -8) (8 8 8) +Creates a particle splash effect when used. + +Set "sounds" to one of the following: + 1) sparks + 2) blue water + 3) brown water + 4) slime + 5) lava + 6) blood + +"count" how many pixels in the splash +"dmg" if set, does a radius damage at this location when it splashes + useful for lava/sparks +*/ + +USE(use_target_splash) (edict_t *self, edict_t *other, edict_t *activator) -> void +{ + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_SPLASH); + gi.WriteByte(self->count); + gi.WritePosition(self->s.origin); + gi.WriteDir(self->movedir); + gi.WriteByte(self->sounds); + gi.multicast(self->s.origin, MULTICAST_PVS, false); + + if (self->dmg) + T_RadiusDamage(self, activator, (float) self->dmg, nullptr, (float) self->dmg + 40, DAMAGE_NONE, MOD_SPLASH); +} + +void SP_target_splash(edict_t *self) +{ + self->use = use_target_splash; + G_SetMovedir(self->s.angles, self->movedir); + + if (!self->count) + self->count = 32; + + // N64 "sparks" are blue, not yellow. + if (level.is_n64 && self->sounds == 1) + self->sounds = 7; + + self->svflags = SVF_NOCLIENT; +} + +//========================================================== + +/*QUAKED target_spawner (1 0 0) (-8 -8 -8) (8 8 8) +Set target to the type of entity you want spawned. +Useful for spawning monsters and gibs in the factory levels. + +For monsters: + Set direction to the facing you want it to have. + +For gibs: + Set direction if you want it moving and + speed how fast it should be moving otherwise it + will just be dropped +*/ +void ED_CallSpawn(edict_t *ent); + +USE(use_target_spawner) (edict_t *self, edict_t *other, edict_t *activator) -> void +{ + edict_t *ent; + + ent = G_Spawn(); + ent->classname = self->target; + // RAFAEL + ent->flags = self->flags; + // RAFAEL + ent->s.origin = self->s.origin; + ent->s.angles = self->s.angles; + st = {}; + + // [Paril-KEX] although I fixed these in our maps, this is just + // in case anybody else does this by accident. Don't count these monsters + // so they don't inflate the monster count. + ent->monsterinfo.aiflags |= AI_DO_NOT_COUNT; + + ED_CallSpawn(ent); + gi.linkentity(ent); + + KillBox(ent, false); + if (self->speed) + ent->velocity = self->movedir; + + ent->s.renderfx |= RF_IR_VISIBLE; // PGM +} + +void SP_target_spawner(edict_t *self) +{ + self->use = use_target_spawner; + self->svflags = SVF_NOCLIENT; + if (self->speed) + { + G_SetMovedir(self->s.angles, self->movedir); + self->movedir *= self->speed; + } +} + +//========================================================== + +/*QUAKED target_blaster (1 0 0) (-8 -8 -8) (8 8 8) NOTRAIL NOEFFECTS +Fires a blaster bolt in the set direction when triggered. + +dmg default is 15 +speed default is 1000 +*/ + +constexpr spawnflags_t SPAWNFLAG_BLASTER_NOTRAIL = 1_spawnflag; +constexpr spawnflags_t SPAWNFLAG_BLASTER_NOEFFECTS = 2_spawnflag; + +USE(use_target_blaster) (edict_t *self, edict_t *other, edict_t *activator) -> void +{ + effects_t effect; + + if (self->spawnflags.has(SPAWNFLAG_BLASTER_NOEFFECTS)) + effect = EF_NONE; + else if (self->spawnflags.has(SPAWNFLAG_BLASTER_NOTRAIL)) + effect = EF_HYPERBLASTER; + else + effect = EF_BLASTER; + + fire_blaster(self, self->s.origin, self->movedir, self->dmg, (int) self->speed, effect, MOD_TARGET_BLASTER); + gi.sound(self, CHAN_VOICE, self->noise_index, 1, ATTN_NORM, 0); +} + +void SP_target_blaster(edict_t *self) +{ + self->use = use_target_blaster; + G_SetMovedir(self->s.angles, self->movedir); + self->noise_index = gi.soundindex("weapons/laser2.wav"); + + if (!self->dmg) + self->dmg = 15; + if (!self->speed) + self->speed = 1000; + + self->svflags = SVF_NOCLIENT; +} + +//========================================================== + +/*QUAKED target_crosslevel_trigger (.5 .5 .5) (-8 -8 -8) (8 8 8) trigger1 trigger2 trigger3 trigger4 trigger5 trigger6 trigger7 trigger8 +Once this trigger is touched/used, any trigger_crosslevel_target with the same trigger number is automatically used when a level is started within the same unit. It is OK to check multiple triggers. Message, delay, target, and killtarget also work. +*/ +USE(trigger_crosslevel_trigger_use) (edict_t *self, edict_t *other, edict_t *activator) -> void +{ + game.cross_level_flags |= self->spawnflags.value; + G_FreeEdict(self); +} + +void SP_target_crosslevel_trigger(edict_t *self) +{ + self->svflags = SVF_NOCLIENT; + self->use = trigger_crosslevel_trigger_use; +} + +/*QUAKED target_crosslevel_target (.5 .5 .5) (-8 -8 -8) (8 8 8) trigger1 trigger2 trigger3 trigger4 trigger5 trigger6 trigger7 trigger8 - - - - - - - - trigger9 trigger10 trigger11 trigger12 trigger13 trigger14 trigger15 trigger16 +Triggered by a trigger_crosslevel elsewhere within a unit. If multiple triggers are checked, all must be true. Delay, target and +killtarget also work. + +"delay" delay before using targets if the trigger has been activated (default 1) +*/ +THINK(target_crosslevel_target_think) (edict_t *self) -> void +{ + if (self->spawnflags.value == (game.cross_level_flags & SFL_CROSS_TRIGGER_MASK & self->spawnflags.value)) + { + G_UseTargets(self, self); + G_FreeEdict(self); + } +} + +void SP_target_crosslevel_target(edict_t *self) +{ + if (!self->delay) + self->delay = 1; + self->svflags = SVF_NOCLIENT; + + self->think = target_crosslevel_target_think; + self->nextthink = level.time + gtime_t::from_sec(self->delay); +} + +//========================================================== + +/*QUAKED target_laser (0 .5 .8) (-8 -8 -8) (8 8 8) START_ON RED GREEN BLUE YELLOW ORANGE FAT WINDOWSTOP +When triggered, fires a laser. You can either set a target or a direction. + +WINDOWSTOP - stops at CONTENTS_WINDOW +*/ + +//====== +// PGM +constexpr spawnflags_t SPAWNFLAG_LASER_STOPWINDOW = 0x0080_spawnflag; +// PGM +//====== + + +struct laser_pierce_t : pierce_args_t +{ + edict_t *self; + int32_t count; + bool damaged_thing = false; + + inline laser_pierce_t(edict_t *self, int32_t count) : + pierce_args_t(), + self(self), + count(count) + { + } + + // we hit an entity; return false to stop the piercing. + // you can adjust the mask for the re-trace (for water, etc). + virtual bool hit(contents_t &mask, vec3_t &end) override + { + // hurt it if we can + if (self->dmg > 0 && (tr.ent->takedamage) && !(tr.ent->flags & FL_IMMUNE_LASER) && self->damage_debounce_time <= level.time) + { + damaged_thing = true; + T_Damage(tr.ent, self, self->activator, self->movedir, tr.endpos, vec3_origin, self->dmg, 1, DAMAGE_ENERGY, MOD_TARGET_LASER); + } + + // if we hit something that's not a monster or player or is immune to lasers, we're done + // ROGUE + if (!(tr.ent->svflags & SVF_MONSTER) && (!tr.ent->client) && !(tr.ent->flags & FL_DAMAGEABLE)) + // ROGUE + { + if (self->spawnflags.has(SPAWNFLAG_LASER_ZAP)) + { + self->spawnflags &= ~SPAWNFLAG_LASER_ZAP; + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_LASER_SPARKS); + gi.WriteByte(count); + gi.WritePosition(tr.endpos); + gi.WriteDir(tr.plane.normal); + gi.WriteByte(self->s.skinnum); + gi.multicast(tr.endpos, MULTICAST_PVS, false); + } + + return false; + } + + if (!mark(tr.ent)) + return false; + + return true; + } +}; + +THINK(target_laser_think) (edict_t *self) -> void +{ + int32_t count; + + if (self->spawnflags.has(SPAWNFLAG_LASER_ZAP)) + count = 8; + else + count = 4; + + if (self->enemy) + { + vec3_t last_movedir = self->movedir; + vec3_t point = (self->enemy->absmin + self->enemy->absmax) * 0.5f; + self->movedir = point - self->s.origin; + self->movedir.normalize(); + if (self->movedir != last_movedir) + self->spawnflags |= SPAWNFLAG_LASER_ZAP; + } + + vec3_t start = self->s.origin; + vec3_t end = start + (self->movedir * 2048); + + laser_pierce_t args { + self, + count + }; + + contents_t mask = self->spawnflags.has(SPAWNFLAG_LASER_STOPWINDOW) ? MASK_SHOT : (CONTENTS_SOLID | CONTENTS_MONSTER | CONTENTS_PLAYER | CONTENTS_DEADMONSTER); + + pierce_trace(start, end, self, args, mask); + + self->s.old_origin = args.tr.endpos; + + if (args.damaged_thing) + self->damage_debounce_time = level.time + 10_hz; + + self->nextthink = level.time + FRAME_TIME_S; + gi.linkentity(self); +} + +void target_laser_on(edict_t *self) +{ + if (!self->activator) + self->activator = self; + self->spawnflags |= SPAWNFLAG_LASER_ZAP | SPAWNFLAG_LASER_ON; + self->svflags &= ~SVF_NOCLIENT; + self->flags |= FL_TRAP; + target_laser_think(self); +} + +void target_laser_off(edict_t *self) +{ + self->spawnflags &= ~SPAWNFLAG_LASER_ON; + self->svflags |= SVF_NOCLIENT; + self->flags &= ~FL_TRAP; + self->nextthink = 0_ms; +} + +USE(target_laser_use) (edict_t *self, edict_t *other, edict_t *activator) -> void +{ + self->activator = activator; + if (self->spawnflags.has(SPAWNFLAG_LASER_ON)) + target_laser_off(self); + else + target_laser_on(self); +} + +THINK(target_laser_start) (edict_t *self) -> void +{ + edict_t *ent; + + self->movetype = MOVETYPE_NONE; + self->solid = SOLID_NOT; + self->s.renderfx |= RF_BEAM; + self->s.modelindex = MODELINDEX_WORLD; // must be non-zero + + // [Sam-KEX] On Q2N64, spawnflag of 128 turns it into a lightning bolt + if (level.is_n64) + { + // Paril: fix for N64 + if (self->spawnflags.has(SPAWNFLAG_LASER_STOPWINDOW)) + { + self->spawnflags &= ~SPAWNFLAG_LASER_STOPWINDOW; + self->spawnflags |= SPAWNFLAG_LASER_LIGHTNING; + } + } + + if (self->spawnflags.has(SPAWNFLAG_LASER_LIGHTNING)) + { + self->s.renderfx |= RF_BEAM_LIGHTNING; // tell renderer it is lightning + + if (!self->s.skinnum) + self->s.skinnum = 0xf3f3f1f1; // default lightning color + } + + // set the beam diameter + // [Paril-KEX] lab has this set prob before lightning was implemented + if (!level.is_n64 && self->spawnflags.has(SPAWNFLAG_LASER_FAT)) + self->s.frame = 16; + else + self->s.frame = 4; + + // set the color + if (!self->s.skinnum) + { + if (self->spawnflags.has(SPAWNFLAG_LASER_RED)) + self->s.skinnum = 0xf2f2f0f0; + else if (self->spawnflags.has(SPAWNFLAG_LASER_GREEN)) + self->s.skinnum = 0xd0d1d2d3; + else if (self->spawnflags.has(SPAWNFLAG_LASER_BLUE)) + self->s.skinnum = 0xf3f3f1f1; + else if (self->spawnflags.has(SPAWNFLAG_LASER_YELLOW)) + self->s.skinnum = 0xdcdddedf; + else if (self->spawnflags.has(SPAWNFLAG_LASER_ORANGE)) + self->s.skinnum = 0xe0e1e2e3; + } + + if (!self->enemy) + { + if (self->target) + { + ent = G_FindByString<&edict_t::targetname>(nullptr, self->target); + if (!ent) + gi.Com_PrintFmt("{}: {} is a bad target\n", *self, self->target); + else + { + self->enemy = ent; + + // N64 fix + // FIXME: which map was this for again? oops + if (level.is_n64 && !strcmp(self->enemy->classname, "func_train") && !(self->enemy->spawnflags & SPAWNFLAG_TRAIN_START_ON)) + self->enemy->use(self->enemy, self, self); + } + } + else + { + G_SetMovedir(self->s.angles, self->movedir); + } + } + self->use = target_laser_use; + self->think = target_laser_think; + + if (!self->dmg) + self->dmg = 1; + + self->mins = { -8, -8, -8 }; + self->maxs = { 8, 8, 8 }; + gi.linkentity(self); + + if (self->spawnflags.has(SPAWNFLAG_LASER_ON)) + target_laser_on(self); + else + target_laser_off(self); +} + +void SP_target_laser(edict_t *self) +{ + // let everything else get spawned before we start firing + self->think = target_laser_start; + self->flags |= FL_TRAP_LASER_FIELD; + self->nextthink = level.time + 1_sec; +} + +//========================================================== + +/*QUAKED target_lightramp (0 .5 .8) (-8 -8 -8) (8 8 8) TOGGLE +speed How many seconds the ramping will take +message two letters; starting lightlevel and ending lightlevel +*/ + +constexpr spawnflags_t SPAWNFLAG_LIGHTRAMP_TOGGLE = 1_spawnflag; + +THINK(target_lightramp_think) (edict_t *self) -> void +{ + char style[2]; + + style[0] = (char) ('a' + self->movedir[0] + ((level.time - self->timestamp) / gi.frame_time_s).seconds() * self->movedir[2]); + style[1] = 0; + + gi.configstring(CS_LIGHTS + self->enemy->style, style); + + if ((level.time - self->timestamp).seconds() < self->speed) + { + self->nextthink = level.time + FRAME_TIME_S; + } + else if (self->spawnflags.has(SPAWNFLAG_LIGHTRAMP_TOGGLE)) + { + char temp; + + temp = (char) self->movedir[0]; + self->movedir[0] = self->movedir[1]; + self->movedir[1] = temp; + self->movedir[2] *= -1; + } +} + +USE(target_lightramp_use) (edict_t *self, edict_t *other, edict_t *activator) -> void +{ + if (!self->enemy) + { + edict_t *e; + + // check all the targets + e = nullptr; + while (1) + { + e = G_FindByString<&edict_t::targetname>(e, self->target); + if (!e) + break; + if (strcmp(e->classname, "light") != 0) + { + gi.Com_PrintFmt("{}: target {} ({}) is not a light\n", *self, self->target, *e); + } + else + { + self->enemy = e; + } + } + + if (!self->enemy) + { + gi.Com_PrintFmt("{}: target {} not found\n", *self, self->target); + G_FreeEdict(self); + return; + } + } + + self->timestamp = level.time; + target_lightramp_think(self); +} + +void SP_target_lightramp(edict_t *self) +{ + if (!self->message || strlen(self->message) != 2 || self->message[0] < 'a' || self->message[0] > 'z' || self->message[1] < 'a' || self->message[1] > 'z' || self->message[0] == self->message[1]) + { + gi.Com_PrintFmt("{}: bad ramp ({})\n", *self, self->message ? self->message : "null string"); + G_FreeEdict(self); + return; + } + + if (deathmatch->integer) + { + G_FreeEdict(self); + return; + } + + if (!self->target) + { + gi.Com_PrintFmt("{}: no target\n", *self); + G_FreeEdict(self); + return; + } + + self->svflags |= SVF_NOCLIENT; + self->use = target_lightramp_use; + self->think = target_lightramp_think; + + self->movedir[0] = (float) (self->message[0] - 'a'); + self->movedir[1] = (float) (self->message[1] - 'a'); + self->movedir[2] = (self->movedir[1] - self->movedir[0]) / (self->speed / gi.frame_time_s); +} + +//========================================================== + +/*QUAKED target_earthquake (1 0 0) (-8 -8 -8) (8 8 8) SILENT TOGGLE UNKNOWN_ROGUE ONE_SHOT +When triggered, this initiates a level-wide earthquake. +All players are affected with a screen shake. +"speed" severity of the quake (default:200) +"count" duration of the quake (default:5) +*/ + +constexpr spawnflags_t SPAWNFLAGS_EARTHQUAKE_SILENT = 1_spawnflag; +constexpr spawnflags_t SPAWNFLAGS_EARTHQUAKE_TOGGLE = 2_spawnflag; +[[maybe_unused]] constexpr spawnflags_t SPAWNFLAGS_EARTHQUAKE_UNKNOWN_ROGUE = 4_spawnflag; +constexpr spawnflags_t SPAWNFLAGS_EARTHQUAKE_ONE_SHOT = 8_spawnflag; + +THINK(target_earthquake_think) (edict_t *self) -> void +{ + uint32_t i; + edict_t *e; + + if (!(self->spawnflags & SPAWNFLAGS_EARTHQUAKE_SILENT)) // PGM + { // PGM + if (self->last_move_time < level.time) + { + gi.positioned_sound(self->s.origin, self, CHAN_VOICE, self->noise_index, 1.0, ATTN_NONE, 0); + self->last_move_time = level.time + 6.5_sec; + } + } // PGM + + for (i = 1, e = g_edicts + i; i < globals.num_edicts; i++, e++) + { + if (!e->inuse) + continue; + if (!e->client) + break; + + e->client->quake_time = level.time + 1000_ms; + } + + if (level.time < self->timestamp) + self->nextthink = level.time + 10_hz; +} + +USE(target_earthquake_use) (edict_t *self, edict_t *other, edict_t *activator) -> void +{ + if (self->spawnflags.has(SPAWNFLAGS_EARTHQUAKE_ONE_SHOT)) + { + uint32_t i; + edict_t *e; + + for (i = 1, e = g_edicts + i; i < globals.num_edicts; i++, e++) + { + if (!e->inuse) + continue; + if (!e->client) + break; + + e->client->v_dmg_pitch = -self->speed * 0.1f; + e->client->v_dmg_time = level.time + DAMAGE_TIME(); + } + + return; + } + + self->timestamp = level.time + gtime_t::from_sec(self->count); + + if (self->spawnflags.has(SPAWNFLAGS_EARTHQUAKE_TOGGLE)) + { + if (self->style) + self->nextthink = 0_ms; + else + self->nextthink = level.time + FRAME_TIME_S; + + self->style = !self->style; + } + else + { + self->nextthink = level.time + FRAME_TIME_S; + self->last_move_time = 0_ms; + } + + self->activator = activator; +} + +void SP_target_earthquake(edict_t *self) +{ + if (!self->targetname) + gi.Com_PrintFmt("{}: untargeted\n", *self); + + if (level.is_n64) + { + self->spawnflags |= SPAWNFLAGS_EARTHQUAKE_TOGGLE; + self->speed = 5; + } + + if (!self->count) + self->count = 5; + + if (!self->speed) + self->speed = 200; + + self->svflags |= SVF_NOCLIENT; + self->think = target_earthquake_think; + self->use = target_earthquake_use; + + if (!(self->spawnflags & SPAWNFLAGS_EARTHQUAKE_SILENT)) // PGM + self->noise_index = gi.soundindex("world/quake.wav"); +} + +/*QUAKED target_camera (1 0 0) (-8 -8 -8) (8 8 8) +[Sam-KEX] Creates a camera path as seen in the N64 version. +*/ + +constexpr size_t HACKFLAG_TELEPORT_OUT = 2; +constexpr size_t HACKFLAG_SKIPPABLE = 64; +constexpr size_t HACKFLAG_END_OF_UNIT = 128; + +static void camera_lookat_pathtarget(edict_t* self, vec3_t origin, vec3_t* dest) +{ + if(self->pathtarget) + { + edict_t* pt = nullptr; + pt = G_FindByString<&edict_t::targetname>(pt, self->pathtarget); + if(pt) + { + float yaw, pitch; + vec3_t delta = pt->s.origin - origin; + + float d = delta[0] * delta[0] + delta[1] * delta[1]; + if(d == 0.0f) + { + yaw = 0.0f; + pitch = (delta[2] > 0.0f) ? 90.0f : -90.0f; + } + else + { + yaw = atan2(delta[1], delta[0]) * (180.0f / PIf); + pitch = atan2(delta[2], sqrt(d)) * (180.0f / PIf); + } + + (*dest)[YAW] = yaw; + (*dest)[PITCH] = -pitch; + (*dest)[ROLL] = 0; + } + } +} + +THINK(update_target_camera) (edict_t *self) -> void +{ + bool do_skip = false; + + // only allow skipping after 2 seconds + if ((self->hackflags & HACKFLAG_SKIPPABLE) && level.time > 2_sec) + { + for (uint32_t i = 0; i < game.maxclients; i++) + { + edict_t *client = g_edicts + 1 + i; + if (!client->inuse || !client->client->pers.connected) + continue; + + if (client->client->buttons & BUTTON_ANY) + { + do_skip = true; + break; + } + } + } + + if (!do_skip && self->movetarget) + { + self->moveinfo.remaining_distance -= (self->moveinfo.move_speed * gi.frame_time_s) * 0.8f; + + if(self->moveinfo.remaining_distance <= 0) + { + if (self->movetarget->hackflags & HACKFLAG_TELEPORT_OUT) + { + if (self->enemy) + { + self->enemy->s.event = EV_PLAYER_TELEPORT; + self->enemy->hackflags = HACKFLAG_TELEPORT_OUT; + self->enemy->pain_debounce_time = self->enemy->timestamp = gtime_t::from_sec(self->movetarget->wait); + } + } + + self->s.origin = self->movetarget->s.origin; + self->nextthink = level.time + gtime_t::from_sec(self->movetarget->wait); + if (self->movetarget->target) + { + self->movetarget = G_PickTarget(self->movetarget->target); + + if (self->movetarget) + { + self->moveinfo.move_speed = self->movetarget->speed ? self->movetarget->speed : 55; + self->moveinfo.remaining_distance = (self->movetarget->s.origin - self->s.origin).normalize(); + self->moveinfo.distance = self->moveinfo.remaining_distance; + } + } + else + self->movetarget = nullptr; + + return; + } + else + { + float frac = 1.0f - (self->moveinfo.remaining_distance / self->moveinfo.distance); + + if (self->enemy && (self->enemy->hackflags & HACKFLAG_TELEPORT_OUT)) + self->enemy->s.alpha = max(1.f / 255.f, frac); + + vec3_t delta = self->movetarget->s.origin - self->s.origin; + delta *= frac; + vec3_t newpos = self->s.origin + delta; + + camera_lookat_pathtarget(self, newpos, &level.intermission_angle); + level.intermission_origin = newpos; + + // move all clients to the intermission point + for (uint32_t i = 0; i < game.maxclients; i++) + { + edict_t* client = g_edicts + 1 + i; + if (!client->inuse) + { + continue; + } + + MoveClientToIntermission(client); + } + } + } + else + { + if (self->killtarget) + { + // destroy dummy player + if (self->enemy) + G_FreeEdict(self->enemy); + + edict_t* t = nullptr; + level.intermissiontime = 0_ms; + level.level_intermission_set = true; + + while ((t = G_FindByString<&edict_t::targetname>(t, self->killtarget))) + { + t->use(t, self, self->activator); + } + + level.intermissiontime = level.time; + level.intermission_server_frame = gi.ServerFrame(); + + // end of unit requires a wait + if (level.changemap && !strchr(level.changemap, '*')) + level.exitintermission = true; + } + + self->think = nullptr; + return; + } + + self->nextthink = level.time + FRAME_TIME_S; +} + +void G_SetClientFrame(edict_t *ent); + +extern float xyspeed; + +THINK(target_camera_dummy_think) (edict_t *self) -> void +{ + // bit of a hack, but this will let the dummy + // move like a player + self->client = self->owner->client; + xyspeed = sqrtf(self->velocity[0] * self->velocity[0] + self->velocity[1] * self->velocity[1]); + G_SetClientFrame(self); + self->client = nullptr; + + // alpha fade out for voops + if (self->hackflags & HACKFLAG_TELEPORT_OUT) + { + self->timestamp = max(0_ms, self->timestamp - 10_hz); + self->s.alpha = max(1.f / 255.f, (self->timestamp.seconds() / self->pain_debounce_time.seconds())); + } + + self->nextthink = level.time + 10_hz; +} + +USE(use_target_camera) (edict_t *self, edict_t *other, edict_t *activator) -> void +{ + if (self->sounds) + gi.configstring (CS_CDTRACK, G_Fmt("{}", self->sounds).data() ); + + if (!self->target) + return; + + self->movetarget = G_PickTarget(self->target); + + if (!self->movetarget) + return; + + level.intermissiontime = level.time; + level.intermission_server_frame = gi.ServerFrame(); + level.exitintermission = 0; + + // spawn fake player dummy where we were + if (activator->client) + { + edict_t *dummy = self->enemy = G_Spawn(); + dummy->owner = activator; + dummy->clipmask = activator->clipmask; + dummy->s.origin = activator->s.origin; + dummy->s.angles = activator->s.angles; + dummy->groundentity = activator->groundentity; + dummy->groundentity_linkcount = dummy->groundentity ? dummy->groundentity->linkcount : 0; + dummy->think = target_camera_dummy_think; + dummy->nextthink = level.time + 10_hz; + dummy->solid = SOLID_BBOX; + dummy->movetype = MOVETYPE_STEP; + dummy->mins = activator->mins; + dummy->maxs = activator->maxs; + dummy->s.modelindex = dummy->s.modelindex2 = MODELINDEX_PLAYER; + dummy->s.skinnum = activator->s.skinnum; + dummy->velocity = activator->velocity; + dummy->s.renderfx = RF_MINLIGHT; + dummy->s.frame = activator->s.frame; + gi.linkentity(dummy); + } + + camera_lookat_pathtarget(self, self->s.origin, &level.intermission_angle); + level.intermission_origin = self->s.origin; + + // move all clients to the intermission point + for (uint32_t i = 0; i < game.maxclients; i++) + { + edict_t* client = g_edicts + 1 + i; + if (!client->inuse) + { + continue; + } + + // respawn any dead clients + if (client->health <= 0) + respawn(client); + + MoveClientToIntermission(client); + } + + self->activator = activator; + self->think = update_target_camera; + self->nextthink = level.time + gtime_t::from_sec(self->wait); + self->moveinfo.move_speed = self->speed; + + self->moveinfo.remaining_distance = (self->movetarget->s.origin - self->s.origin).normalize(); + self->moveinfo.distance = self->moveinfo.remaining_distance; + + if (self->hackflags & HACKFLAG_END_OF_UNIT) + G_EndOfUnitMessage(); +} + +void SP_target_camera(edict_t* self) +{ + if (deathmatch->integer) + { // auto-remove for deathmatch + G_FreeEdict(self); + return; + } + + self->use = use_target_camera; + self->svflags = SVF_NOCLIENT; +} + +/*QUAKED target_gravity (1 0 0) (-8 -8 -8) (8 8 8) NOTRAIL NOEFFECTS +[Sam-KEX] Changes gravity, as seen in the N64 version +*/ + +USE(use_target_gravity) (edict_t *self, edict_t *other, edict_t *activator) -> void +{ + gi.cvar_set("sv_gravity", G_Fmt("{}", self->gravity).data()); + level.gravity = self->gravity; +} + +void SP_target_gravity(edict_t* self) +{ + self->use = use_target_gravity; + self->gravity = atof(st.gravity); +} + +/*QUAKED target_soundfx (1 0 0) (-8 -8 -8) (8 8 8) NOTRAIL NOEFFECTS +[Sam-KEX] Plays a sound fx, as seen in the N64 version +*/ + +THINK(update_target_soundfx) (edict_t *self) -> void +{ + gi.positioned_sound(self->s.origin, self, CHAN_VOICE, self->noise_index, self->volume, self->attenuation, 0); +} + +USE(use_target_soundfx) (edict_t *self, edict_t *other, edict_t *activator) -> void +{ + self->think = update_target_soundfx; + self->nextthink = level.time + gtime_t::from_sec(self->delay); +} + +void SP_target_soundfx(edict_t* self) +{ + if (!self->volume) + self->volume = 1.0; + + if (!self->attenuation) + self->attenuation = 1.0; + else if (self->attenuation == -1) // use -1 so 0 defaults to 1 + self->attenuation = 0; + + self->noise_index = atoi(st.noise); + + switch(self->noise_index) + { + case 1: + self->noise_index = gi.soundindex("world/x_alarm.wav"); + break; + case 2: + self->noise_index = gi.soundindex("world/flyby1.wav"); + break; + case 4: + self->noise_index = gi.soundindex("world/amb12.wav"); + break; + case 5: + self->noise_index = gi.soundindex("world/amb17.wav"); + break; + case 7: + self->noise_index = gi.soundindex("world/bigpump2.wav"); + break; + default: + gi.Com_PrintFmt("{}: unknown noise {}\n", *self, self->noise_index); + return; + } + + self->use = use_target_soundfx; +} + +/*QUAKED target_light (1 0 0) (-8 -8 -8) (8 8 8) START_ON NO_LERP FLICKER +[Paril-KEX] dynamic light entity that follows a lightstyle. + +*/ + +constexpr spawnflags_t SPAWNFLAG_TARGET_LIGHT_START_ON = 1_spawnflag; +constexpr spawnflags_t SPAWNFLAG_TARGET_LIGHT_NO_LERP = 2_spawnflag; // not used in N64, but I'll use it for this +constexpr spawnflags_t SPAWNFLAG_TARGET_LIGHT_FLICKER = 4_spawnflag; + +THINK(target_light_flicker_think) (edict_t *self) -> void +{ + if (brandom()) + self->svflags ^= SVF_NOCLIENT; + + self->nextthink = level.time + 10_hz; +} + +// think function handles interpolation from start to finish. +THINK(target_light_think) (edict_t *self) -> void +{ + if (self->spawnflags.has(SPAWNFLAG_TARGET_LIGHT_FLICKER)) + target_light_flicker_think(self); + + const char *style = gi.get_configstring(CS_LIGHTS + self->style); + self->delay += self->speed; + + int32_t index = ((int32_t) self->delay) % strlen(style); + char style_value = style[index]; + float current_lerp = (float) (style_value - 'a') / (float) ('z' - 'a'); + float lerp; + + if (!(self->spawnflags & SPAWNFLAG_TARGET_LIGHT_NO_LERP)) + { + int32_t next_index = (index + 1) % strlen(style); + char next_style_value = style[next_index]; + + float next_lerp = (float) (next_style_value - 'a') / (float) ('z' - 'a'); + + float mod_lerp = fmod(self->delay, 1.0f); + lerp = (next_lerp * mod_lerp) + (current_lerp * (1.f - mod_lerp)); + } + else + lerp = current_lerp; + + int my_rgb = self->count; + int target_rgb = self->chain->s.skinnum; + + int my_b = ((my_rgb >> 8 ) & 0xff); + int my_g = ((my_rgb >> 16) & 0xff); + int my_r = ((my_rgb >> 24) & 0xff); + + int target_b = ((target_rgb >> 8 ) & 0xff); + int target_g = ((target_rgb >> 16) & 0xff); + int target_r = ((target_rgb >> 24) & 0xff); + + float backlerp = 1.0f - lerp; + + int b = (target_b * lerp) + (my_b * backlerp); + int g = (target_g * lerp) + (my_g * backlerp); + int r = (target_r * lerp) + (my_r * backlerp); + + self->s.skinnum = (b << 8) | (g << 16) | (r << 24); + + self->nextthink = level.time + 10_hz; +} + +USE(target_light_use) (edict_t *self, edict_t *other, edict_t *activator) -> void +{ + self->health = !self->health; + + if (self->health) + self->svflags &= ~SVF_NOCLIENT; + else + self->svflags |= SVF_NOCLIENT; + + if (!self->health) + { + self->think = nullptr; + self->nextthink = 0_ms; + return; + } + + // has dynamic light "target" + if (self->chain) + { + self->think = target_light_think; + self->nextthink = level.time + 10_hz; + } + else if (self->spawnflags.has(SPAWNFLAG_TARGET_LIGHT_FLICKER)) + { + self->think = target_light_flicker_think; + self->nextthink = level.time + 10_hz; + } +} + +void SP_target_light(edict_t *self) +{ + self->s.modelindex = 1; + self->s.renderfx = RF_CUSTOM_LIGHT; + self->s.frame = st.radius ? st.radius : 150; + self->count = self->s.skinnum; + self->svflags |= SVF_NOCLIENT; + self->health = 0; + + if (self->target) + self->chain = G_PickTarget(self->target); + + if (self->spawnflags.has(SPAWNFLAG_TARGET_LIGHT_START_ON)) + target_light_use(self, self, self); + + if (!self->speed) + self->speed = 1.0f; + else + self->speed = 0.1f / self->speed; + + if (level.is_n64) + self->style += 10; + + self->use = target_light_use; + + gi.linkentity(self); +} + +/*QUAKED target_poi (1 0 0) (-4 -4 -4) (4 4 4) NEAREST DUMMY DYNAMIC +[Paril-KEX] point of interest for help in player navigation. +Without any additional setup, targeting this entity will switch +the current POI in the level to the one this is linked to. + +"count": if set, this value is the 'stage' linked to this POI. A POI +with this set that is activated will only take effect if the current +level's stage value is <= this value, and if it is, will also set +the current level's stage value to this value. + +"style": only used for teamed POIs; the POI with the lowest style will +be activated when checking for which POI to activate. This is mainly +useful during development, to easily insert or change the order of teamed +POIs without needing to manually move the entity definitions around. + +"team": if set, this will create a team of POIs. Teamed POIs act like +a single unit; activating any of them will do the same thing. When activated, +it will filter through all of the POIs on the team selecting the one that +best fits the current situation. This includes checking "count" and "style" +values. You can also set the NEAREST spawnflag on any of the teamed POIs, +which will additionally cause activation to prefer the nearest one to the player. +Killing a POI via killtarget will remove it from the chain, allowing you to +adjust valid POIs at runtime. + +The DUMMY spawnflag is to allow you to use a single POI as a team member +that can be activated, if you're using killtargets to remove POIs. + +The DYNAMIC spawnflag is for very specific circumstances where you want +to direct the player to the nearest teamed POI, but want the path to pick +the nearest at any given time rather than only when activated. + +The DISABLED flag is mainly intended to work with DYNAMIC & teams; the POI +will be disabled until it is targeted, and afterwards will be enabled until +it is killed. +*/ + +constexpr spawnflags_t SPAWNFLAG_POI_NEAREST = 1_spawnflag; +constexpr spawnflags_t SPAWNFLAG_POI_DUMMY = 2_spawnflag; +constexpr spawnflags_t SPAWNFLAG_POI_DYNAMIC = 4_spawnflag; +constexpr spawnflags_t SPAWNFLAG_POI_DISABLED = 8_spawnflag; + +static float distance_to_poi(vec3_t start, vec3_t end) +{ + PathRequest request; + request.start = start; + request.goal = end; + request.moveDist = 64.f; + request.pathFlags = PathFlags::All; + request.nodeSearch.ignoreNodeFlags = true; + request.nodeSearch.minHeight = 128.0f; + request.nodeSearch.maxHeight = 128.0f; + request.nodeSearch.radius = 1024.0f; + request.pathPoints.count = 0; + + PathInfo info; + + if (gi.GetPathToGoal(request, info)) + return info.pathDistSqr; + + return std::numeric_limits::infinity(); +} + +USE(target_poi_use) (edict_t *ent, edict_t *other, edict_t *activator) -> void +{ + // we were disabled, so remove the disable check + if (ent->spawnflags.has(SPAWNFLAG_POI_DISABLED)) + ent->spawnflags &= ~SPAWNFLAG_POI_DISABLED; + + // early stage check + if (ent->count && level.current_poi_stage > ent->count) + return; + + // teamed POIs work a bit differently + if (ent->team) + { + edict_t *poi_master = ent->teammaster; + + // unset ent, since we need to find one that matches + ent = nullptr; + + float best_distance = std::numeric_limits::infinity(); + int32_t best_style = std::numeric_limits::max(); + + edict_t *dummy_fallback = nullptr; + + for (edict_t *poi = poi_master; poi; poi = poi->teamchain) + { + // currently disabled + if (poi->spawnflags.has(SPAWNFLAG_POI_DISABLED)) + continue; + + // ignore dummy POI + if (poi->spawnflags.has(SPAWNFLAG_POI_DUMMY)) + { + dummy_fallback = poi; + continue; + } + // POI is not part of current stage + else if (poi->count && level.current_poi_stage > poi->count) + continue; + // POI isn't the right style + else if (poi->style > best_style) + continue; + + float dist = distance_to_poi(activator->s.origin, poi->s.origin); + + // we have one already and it's farther away, don't bother + if (poi_master->spawnflags.has(SPAWNFLAG_POI_NEAREST) && + ent && + dist > best_distance) + continue; + + // found a better style; overwrite dist + if (poi->style < best_style) + { + // unless we weren't reachable... + if (poi_master->spawnflags.has(SPAWNFLAG_POI_NEAREST) && std::isinf(dist)) + continue; + + best_style = poi->style; + if (poi_master->spawnflags.has(SPAWNFLAG_POI_NEAREST)) + best_distance = dist; + ent = poi; + continue; + } + + // if we're picking by nearest, check distance + if (poi_master->spawnflags.has(SPAWNFLAG_POI_NEAREST)) + { + if (dist < best_distance) + { + best_distance = dist; + ent = poi; + continue; + } + } + else + { + // not picking by distance, so it's order of appearance + ent = poi; + } + } + + // no valid POI found; this isn't always an error, + // some valid techniques may require this to happen. + if (!ent) + { + if (dummy_fallback && dummy_fallback->spawnflags.has(SPAWNFLAG_POI_DYNAMIC)) + ent = dummy_fallback; + else + return; + } + } + else + { + if (ent->count) + { + if (level.current_poi_stage <= ent->count) + level.current_poi_stage = ent->count; + else + return; // this POI is not part of our current stage + } + } + + // dummy POI; not valid + if (!strcmp(ent->classname, "target_poi") && ent->spawnflags.has(SPAWNFLAG_POI_DUMMY) && !ent->spawnflags.has(SPAWNFLAG_POI_DYNAMIC)) + return; + + level.valid_poi = true; + level.current_poi = ent->s.origin; + level.current_poi_image = ent->noise_index; + + if (!strcmp(ent->classname, "target_poi") && ent->spawnflags.has(SPAWNFLAG_POI_DYNAMIC)) + { + level.current_dynamic_poi = nullptr; + + // pick the dummy POI, since it isn't supposed to get freed + // FIXME maybe store the team string instead? + + for (edict_t *m = ent->teammaster; m; m = m->teamchain) + if (m->spawnflags.has(SPAWNFLAG_POI_DUMMY)) + { + level.current_dynamic_poi = m; + break; + } + + if (!level.current_dynamic_poi) + gi.Com_PrintFmt("can't activate poi for {}; need DUMMY in chain\n", *ent); + } + else + level.current_dynamic_poi = nullptr; +} + +THINK(target_poi_setup) (edict_t *self) -> void +{ + if (self->team) + { + // copy dynamic/nearest over to all teammates + if (self->spawnflags.has((SPAWNFLAG_POI_NEAREST | SPAWNFLAG_POI_DYNAMIC))) + for (edict_t *m = self->teammaster; m; m = m->teamchain) + m->spawnflags |= self->spawnflags & (SPAWNFLAG_POI_NEAREST | SPAWNFLAG_POI_DYNAMIC); + + for (edict_t *m = self->teammaster; m; m = m->teamchain) + { + if (strcmp(m->classname, "target_poi")) + gi.Com_PrintFmt("WARNING: {} is teamed with target_poi's; unintentional\n", *m); + } + } +} + +void SP_target_poi(edict_t *self) +{ + if (deathmatch->integer) + { // auto-remove for deathmatch + G_FreeEdict(self); + return; + } + + if (st.image) + self->noise_index = gi.imageindex(st.image); + else + self->noise_index = gi.imageindex("friend"); + + self->use = target_poi_use; + self->svflags |= SVF_NOCLIENT; + self->think = target_poi_setup; + self->nextthink = level.time + 1_ms; + + if (!self->team) + { + if (self->spawnflags.has(SPAWNFLAG_POI_NEAREST)) + gi.Com_PrintFmt("{} has useless spawnflag 'NEAREST'\n", *self); + if (self->spawnflags.has(SPAWNFLAG_POI_DYNAMIC)) + gi.Com_PrintFmt("{} has useless spawnflag 'DYNAMIC'\n", *self); + } +} + +/*QUAKED target_music (1 0 0) (-8 -8 -8) (8 8 8) +Change music when used +*/ + +USE(use_target_music) (edict_t* ent, edict_t* other, edict_t* activator) -> void +{ + gi.configstring(CS_CDTRACK, G_Fmt("{}", ent->sounds).data()); +} + +void SP_target_music(edict_t* self) +{ + self->use = use_target_music; +} + +/*QUAKED target_healthbar (0 1 0) (-8 -8 -8) (8 8 8) PVS_ONLY +* +* Hook up health bars to monsters. +* "delay" is how long to show the health bar for after death. +* "message" is their name +*/ + +USE(use_target_healthbar) (edict_t *ent, edict_t *other, edict_t *activator) -> void +{ + edict_t *target = G_PickTarget(ent->target); + + if (!target || ent->health != target->spawn_count) + { + if (target) + gi.Com_PrintFmt("{}: target {} changed from what it used to be\n", *ent, *target); + else + gi.Com_PrintFmt("{}: no target\n", *ent); + G_FreeEdict(ent); + return; + } + + for (size_t i = 0; i < MAX_HEALTH_BARS; i++) + { + if (level.health_bar_entities[i]) + continue; + + ent->enemy = target; + level.health_bar_entities[i] = ent; + gi.configstring(CONFIG_HEALTH_BAR_NAME, ent->message); + return; + } + + gi.Com_PrintFmt("{}: too many health bars\n", *ent); + G_FreeEdict(ent); +} + +THINK(check_target_healthbar) (edict_t *ent) -> void +{ + edict_t *target = G_PickTarget(ent->target); + if (!target || !(target->svflags & SVF_MONSTER)) + { + if ( target != nullptr ) { + gi.Com_PrintFmt( "{}: target {} does not appear to be a monster\n", *ent, *target ); + } + G_FreeEdict(ent); + return; + } + + // just for sanity check + ent->health = target->spawn_count; +} + +void SP_target_healthbar(edict_t *self) +{ + if (deathmatch->integer) + { + G_FreeEdict(self); + return; + } + + if (!self->target || !*self->target) + { + gi.Com_PrintFmt("{}: missing target\n", *self); + G_FreeEdict(self); + return; + } + + if (!self->message) + { + gi.Com_PrintFmt("{}: missing message\n", *self); + G_FreeEdict(self); + return; + } + + self->use = use_target_healthbar; + self->think = check_target_healthbar; + self->nextthink = level.time + 25_ms; +} + +/*QUAKED target_autosave (0 1 0) (-8 -8 -8) (8 8 8) +* +* Auto save on command. +*/ + +USE(use_target_autosave) (edict_t *ent, edict_t *other, edict_t *activator) -> void +{ + gtime_t save_time = gtime_t::from_sec(gi.cvar("g_athena_auto_save_min_time", "60", CVAR_NOSET)->value); + + if (level.time - level.next_auto_save > save_time) + { + gi.AddCommandString("autosave\n"); + level.next_auto_save = level.time; + } +} + +void SP_target_autosave(edict_t *self) +{ + if (deathmatch->integer) + { + G_FreeEdict(self); + return; + } + + self->use = use_target_autosave; +} + +/*QUAKED target_sky (0 1 0) (-8 -8 -8) (8 8 8) +* +* Change sky parameters. +"sky" environment map name +"skyaxis" vector axis for rotating sky +"skyrotate" speed of rotation in degrees/second +*/ + +USE(use_target_sky) (edict_t *self, edict_t *other, edict_t *activator) -> void +{ + if (self->map) + gi.configstring(CS_SKY, self->map); + + if (self->count & 3) + { + float rotate; + int32_t autorotate; + + sscanf(gi.get_configstring(CS_SKYROTATE), "%f %i", &rotate, &autorotate); + + if (self->count & 1) + rotate = self->accel; + + if (self->count & 2) + autorotate = self->style; + + gi.configstring(CS_SKYROTATE, G_Fmt("{} {}", rotate, autorotate).data()); + } + + if (self->count & 4) + gi.configstring(CS_SKYAXIS, G_Fmt("{}", self->movedir).data()); +} + +void SP_target_sky(edict_t *self) +{ + self->use = use_target_sky; + if (st.was_key_specified("sky")) + self->map = st.sky; + if (st.was_key_specified("skyaxis")) + { + self->count |= 4; + self->movedir = st.skyaxis; + } + if (st.was_key_specified("skyrotate")) + { + self->count |= 1; + self->accel = st.skyrotate; + } + if (st.was_key_specified("skyautorotate")) + { + self->count |= 2; + self->style = st.skyautorotate; + } +} + +//========================================================== + +/*QUAKED target_crossunit_trigger (.5 .5 .5) (-8 -8 -8) (8 8 8) trigger1 trigger2 trigger3 trigger4 trigger5 trigger6 trigger7 trigger8 +Once this trigger is touched/used, any trigger_crossunit_target with the same trigger number is automatically used when a level is started within the same unit. It is OK to check multiple triggers. Message, delay, target, and killtarget also work. +*/ +USE(trigger_crossunit_trigger_use) (edict_t *self, edict_t *other, edict_t *activator) -> void +{ + game.cross_unit_flags |= self->spawnflags.value; + G_FreeEdict(self); +} + +void SP_target_crossunit_trigger(edict_t *self) +{ + if (deathmatch->integer) + { + G_FreeEdict(self); + return; + } + + self->svflags = SVF_NOCLIENT; + self->use = trigger_crossunit_trigger_use; +} + +/*QUAKED target_crossunit_target (.5 .5 .5) (-8 -8 -8) (8 8 8) trigger1 trigger2 trigger3 trigger4 trigger5 trigger6 trigger7 trigger8 - - - - - - - - trigger9 trigger10 trigger11 trigger12 trigger13 trigger14 trigger15 trigger16 +Triggered by a trigger_crossunit elsewhere within a unit. If multiple triggers are checked, all must be true. Delay, target and +killtarget also work. + +"delay" delay before using targets if the trigger has been activated (default 1) +*/ +THINK(target_crossunit_target_think) (edict_t *self) -> void +{ + if (self->spawnflags.value == (game.cross_unit_flags & SFL_CROSS_TRIGGER_MASK & self->spawnflags.value)) + { + G_UseTargets(self, self); + G_FreeEdict(self); + } +} + +void SP_target_crossunit_target(edict_t *self) +{ + if (deathmatch->integer) + { + G_FreeEdict(self); + return; + } + + if (!self->delay) + self->delay = 1; + self->svflags = SVF_NOCLIENT; + + self->think = target_crossunit_target_think; + self->nextthink = level.time + gtime_t::from_sec(self->delay); +} + +/*QUAKED target_achievement (.5 .5 .5) (-8 -8 -8) (8 8 8) +Give an achievement. + +"achievement" cheevo to give +*/ +USE(use_target_achievement) (edict_t *self, edict_t *other, edict_t *activator) -> void +{ + gi.WriteByte(svc_achievement); + gi.WriteString(self->map); + gi.multicast(vec3_origin, MULTICAST_ALL, true); +} + +void SP_target_achievement(edict_t *self) +{ + if (deathmatch->integer) + { + G_FreeEdict(self); + return; + } + + self->map = st.achievement; + self->use = use_target_achievement; +} + +USE(use_target_story) (edict_t *self, edict_t *other, edict_t *activator) -> void +{ + if (self->message && *self->message) + level.story_active = true; + else + level.story_active = false; + + gi.configstring(CONFIG_STORY, self->message ? self->message : ""); +} + +void SP_target_story(edict_t *self) +{ + if (deathmatch->integer) + { + G_FreeEdict(self); + return; + } + + self->use = use_target_story; +} \ No newline at end of file diff --git a/rerelease/g_trigger.cpp b/rerelease/g_trigger.cpp new file mode 100644 index 0000000..89d5f84 --- /dev/null +++ b/rerelease/g_trigger.cpp @@ -0,0 +1,1333 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +#include "g_local.h" + +// PGM - some of these are mine, some id's. I added the define's. +constexpr spawnflags_t SPAWNFLAG_TRIGGER_MONSTER = 0x01_spawnflag; +constexpr spawnflags_t SPAWNFLAG_TRIGGER_NOT_PLAYER = 0x02_spawnflag; +constexpr spawnflags_t SPAWNFLAG_TRIGGER_TRIGGERED = 0x04_spawnflag; +constexpr spawnflags_t SPAWNFLAG_TRIGGER_TOGGLE = 0x08_spawnflag; +constexpr spawnflags_t SPAWNFLAG_TRIGGER_LATCHED = 0x10_spawnflag; +constexpr spawnflags_t SPAWNFLAG_TRIGGER_CLIP = 0x20_spawnflag; +// PGM + +void InitTrigger(edict_t *self) +{ + if (st.was_key_specified("angle") || st.was_key_specified("angles") || self->s.angles) + G_SetMovedir(self->s.angles, self->movedir); + + self->solid = SOLID_TRIGGER; + self->movetype = MOVETYPE_NONE; + // [Paril-KEX] adjusted to allow mins/maxs to be defined + // by hand instead + if (self->model) + gi.setmodel(self, self->model); + self->svflags = SVF_NOCLIENT; +} + +// the wait time has passed, so set back up for another activation +THINK(multi_wait) (edict_t *ent) -> void +{ + ent->nextthink = 0_ms; +} + +// the trigger was just activated +// ent->activator should be set to the activator so it can be held through a delay +// so wait for the delay time before firing +void multi_trigger(edict_t *ent) +{ + if (ent->nextthink) + return; // already been triggered + + G_UseTargets(ent, ent->activator); + + if (ent->wait > 0) + { + ent->think = multi_wait; + ent->nextthink = level.time + gtime_t::from_sec(ent->wait); + } + else + { // we can't just remove (self) here, because this is a touch function + // called while looping through area links... + ent->touch = nullptr; + ent->nextthink = level.time + FRAME_TIME_S; + ent->think = G_FreeEdict; + } +} + +USE(Use_Multi) (edict_t *ent, edict_t *other, edict_t *activator) -> void +{ + // PGM + if (ent->spawnflags.has(SPAWNFLAG_TRIGGER_TOGGLE)) + { + if (ent->solid == SOLID_TRIGGER) + ent->solid = SOLID_NOT; + else + ent->solid = SOLID_TRIGGER; + gi.linkentity(ent); + } + else + { + ent->activator = activator; + multi_trigger(ent); + } + // PGM +} + +TOUCH(Touch_Multi) (edict_t *self, edict_t *other, const trace_t &tr, bool other_touching_self) -> void +{ + if (other->client) + { + if (self->spawnflags.has(SPAWNFLAG_TRIGGER_NOT_PLAYER)) + return; + } + else if (other->svflags & SVF_MONSTER) + { + if (!self->spawnflags.has(SPAWNFLAG_TRIGGER_MONSTER)) + return; + } + else + return; + + if (self->spawnflags.has(SPAWNFLAG_TRIGGER_CLIP)) + { + trace_t clip = gi.clip(self, other->s.origin, other->mins, other->maxs, other->s.origin, G_GetClipMask(other)); + + if (clip.fraction == 1.0f) + return; + } + + if (self->movedir) + { + vec3_t forward; + + AngleVectors(other->s.angles, forward, nullptr, nullptr); + if (forward.dot(self->movedir) < 0) + return; + } + + self->activator = other; + multi_trigger(self); +} + +/*QUAKED trigger_multiple (.5 .5 .5) ? MONSTER NOT_PLAYER TRIGGERED TOGGLE LATCHED +Variable sized repeatable trigger. Must be targeted at one or more entities. +If "delay" is set, the trigger waits some time after activating before firing. +"wait" : Seconds between triggerings. (.2 default) + +TOGGLE - using this trigger will activate/deactivate it. trigger will begin inactive. + +sounds +1) secret +2) beep beep +3) large switch +4) +set "message" to text string +*/ +USE(trigger_enable) (edict_t *self, edict_t *other, edict_t *activator) -> void +{ + self->solid = SOLID_TRIGGER; + self->use = Use_Multi; + gi.linkentity(self); +} + +static BoxEdictsResult_t latched_trigger_filter(edict_t *other, void *data) +{ + edict_t *self = (edict_t *) data; + + if (other->client) + { + if (self->spawnflags.has(SPAWNFLAG_TRIGGER_NOT_PLAYER)) + return BoxEdictsResult_t::Skip; + } + else if (other->svflags & SVF_MONSTER) + { + if (!self->spawnflags.has(SPAWNFLAG_TRIGGER_MONSTER)) + return BoxEdictsResult_t::Skip; + } + else + return BoxEdictsResult_t::Skip; + + if (self->movedir) + { + vec3_t forward; + + AngleVectors(other->s.angles, forward, nullptr, nullptr); + if (forward.dot(self->movedir) < 0) + return BoxEdictsResult_t::Skip; + } + + self->activator = other; + return BoxEdictsResult_t::Keep | BoxEdictsResult_t::End; +} + +THINK(latched_trigger_think) (edict_t *self) -> void +{ + self->nextthink = level.time + 1_ms; + + bool any_inside = !!gi.BoxEdicts(self->absmin, self->absmax, nullptr, 0, AREA_SOLID, latched_trigger_filter, self); + + if (!!self->count != any_inside) + { + G_UseTargets(self, self->activator); + self->count = any_inside ? 1 : 0; + } +} + +void SP_trigger_multiple(edict_t *ent) +{ + if (ent->sounds == 1) + ent->noise_index = gi.soundindex("misc/secret.wav"); + else if (ent->sounds == 2) + ent->noise_index = gi.soundindex("misc/talk.wav"); + else if (ent->sounds == 3) + ent->noise_index = gi.soundindex("misc/trigger1.wav"); + + if (!ent->wait) + ent->wait = 0.2f; + + InitTrigger(ent); + + if (ent->spawnflags.has(SPAWNFLAG_TRIGGER_LATCHED)) + { + if (ent->spawnflags.has(SPAWNFLAG_TRIGGER_TRIGGERED | SPAWNFLAG_TRIGGER_TOGGLE)) + gi.Com_PrintFmt("{}: latched and triggered/toggle are not supported\n", *ent); + + ent->think = latched_trigger_think; + ent->nextthink = level.time + 1_ms; + ent->use = Use_Multi; + return; + } + else + ent->touch = Touch_Multi; + + // PGM + if (ent->spawnflags.has(SPAWNFLAG_TRIGGER_TRIGGERED | SPAWNFLAG_TRIGGER_TOGGLE)) + // PGM + { + ent->solid = SOLID_NOT; + ent->use = trigger_enable; + } + else + { + ent->solid = SOLID_TRIGGER; + ent->use = Use_Multi; + } + + gi.linkentity(ent); + + if (ent->spawnflags.has(SPAWNFLAG_TRIGGER_CLIP)) + ent->svflags |= SVF_HULL; +} + +/*QUAKED trigger_once (.5 .5 .5) ? x x TRIGGERED +Triggers once, then removes itself. +You must set the key "target" to the name of another object in the level that has a matching "targetname". + +If TRIGGERED, this trigger must be triggered before it is live. + +sounds + 1) secret + 2) beep beep + 3) large switch + 4) + +"message" string to be displayed when triggered +*/ + +void SP_trigger_once(edict_t *ent) +{ + // make old maps work because I messed up on flag assignments here + // triggered was on bit 1 when it should have been on bit 4 + if (ent->spawnflags.has(SPAWNFLAG_TRIGGER_MONSTER)) + { + ent->spawnflags &= ~SPAWNFLAG_TRIGGER_MONSTER; + ent->spawnflags |= SPAWNFLAG_TRIGGER_TRIGGERED; + gi.Com_PrintFmt("{}: fixed TRIGGERED flag\n", *ent); + } + + ent->wait = -1; + SP_trigger_multiple(ent); +} + +/*QUAKED trigger_relay (.5 .5 .5) (-8 -8 -8) (8 8 8) +This fixed size trigger cannot be touched, it can only be fired by other events. +*/ +constexpr spawnflags_t SPAWNFLAGS_TRIGGER_RELAY_NO_SOUND = 1_spawnflag; + +USE(trigger_relay_use) (edict_t *self, edict_t *other, edict_t *activator) -> void +{ + if (self->crosslevel_flags && !(self->crosslevel_flags == (game.cross_level_flags & SFL_CROSS_TRIGGER_MASK & self->crosslevel_flags))) + return; + + G_UseTargets(self, activator); +} + +void SP_trigger_relay(edict_t *self) +{ + self->use = trigger_relay_use; + + if (self->spawnflags.has(SPAWNFLAGS_TRIGGER_RELAY_NO_SOUND)) + self->noise_index = -1; +} + +/* +============================================================================== + +trigger_key + +============================================================================== +*/ + +/*QUAKED trigger_key (.5 .5 .5) (-8 -8 -8) (8 8 8) +A relay trigger that only fires it's targets if player has the proper key. +Use "item" to specify the required key, for example "key_data_cd" +*/ +USE(trigger_key_use) (edict_t *self, edict_t *other, edict_t *activator) -> void +{ + item_id_t index; + + if (!self->item) + return; + if (!activator->client) + return; + + index = self->item->id; + if (!activator->client->pers.inventory[index]) + { + if (level.time < self->touch_debounce_time) + return; + self->touch_debounce_time = level.time + 5_sec; + gi.LocCenter_Print(activator, "$g_you_need", self->item->pickup_name_definite); + gi.sound(activator, CHAN_AUTO, gi.soundindex("misc/keytry.wav"), 1, ATTN_NORM, 0); + return; + } + + gi.sound(activator, CHAN_AUTO, gi.soundindex("misc/keyuse.wav"), 1, ATTN_NORM, 0); + if (coop->integer) + { + edict_t *ent; + + if (self->item->id == IT_KEY_POWER_CUBE || self->item->id == IT_KEY_EXPLOSIVE_CHARGES) + { + int cube; + + for (cube = 0; cube < 8; cube++) + if (activator->client->pers.power_cubes & (1 << cube)) + break; + for (uint32_t player = 1; player <= game.maxclients; player++) + { + ent = &g_edicts[player]; + if (!ent->inuse) + continue; + if (!ent->client) + continue; + if (ent->client->pers.power_cubes & (1 << cube)) + { + ent->client->pers.inventory[index]--; + ent->client->pers.power_cubes &= ~(1 << cube); + + // [Paril-KEX] don't allow respawning players to keep + // used keys + if (!P_UseCoopInstancedItems()) + { + ent->client->resp.coop_respawn.inventory[index] = 0; + ent->client->resp.coop_respawn.power_cubes &= ~(1 << cube); + } + } + } + } + else + { + for (uint32_t player = 1; player <= game.maxclients; player++) + { + ent = &g_edicts[player]; + if (!ent->inuse) + continue; + if (!ent->client) + continue; + ent->client->pers.inventory[index] = 0; + + // [Paril-KEX] don't allow respawning players to keep + // used keys + if (!P_UseCoopInstancedItems()) + ent->client->resp.coop_respawn.inventory[index] = 0; + } + } + } + else + { + activator->client->pers.inventory[index]--; + } + + G_UseTargets(self, activator); + + self->use = nullptr; +} + +void SP_trigger_key(edict_t *self) +{ + if (!st.item) + { + gi.Com_PrintFmt("{}: no key item\n", *self); + return; + } + self->item = FindItemByClassname(st.item); + + if (!self->item) + { + gi.Com_PrintFmt("{}: item {} not found\n", *self, st.item); + return; + } + + if (!self->target) + { + gi.Com_PrintFmt("{}: no target\n", *self); + return; + } + + gi.soundindex("misc/keytry.wav"); + gi.soundindex("misc/keyuse.wav"); + + self->use = trigger_key_use; +} + +/* +============================================================================== + +trigger_counter + +============================================================================== +*/ + +/*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. +*/ + +constexpr spawnflags_t SPAWNFLAG_COUNTER_NOMESSAGE = 1_spawnflag; + +USE(trigger_counter_use) (edict_t *self, edict_t *other, edict_t *activator) -> void +{ + if (self->count == 0) + return; + + self->count--; + + if (self->count) + { + if (!(self->spawnflags & SPAWNFLAG_COUNTER_NOMESSAGE)) + { + gi.LocCenter_Print(activator, "$g_more_to_go", self->count); + gi.sound(activator, CHAN_AUTO, gi.soundindex("misc/talk1.wav"), 1, ATTN_NORM, 0); + } + return; + } + + if (!(self->spawnflags & SPAWNFLAG_COUNTER_NOMESSAGE)) + { + gi.LocCenter_Print(activator, "$g_sequence_completed"); + gi.sound(activator, CHAN_AUTO, gi.soundindex("misc/talk1.wav"), 1, ATTN_NORM, 0); + } + self->activator = activator; + multi_trigger(self); +} + +void SP_trigger_counter(edict_t *self) +{ + self->wait = -1; + if (!self->count) + self->count = 2; + + self->use = trigger_counter_use; +} + +/* +============================================================================== + +trigger_always + +============================================================================== +*/ + +/*QUAKED trigger_always (.5 .5 .5) (-8 -8 -8) (8 8 8) +This trigger will always fire. It is activated by the world. +*/ +void SP_trigger_always(edict_t *ent) +{ + // we must have some delay to make sure our use targets are present + if (!ent->delay) + ent->delay = 0.2f; + G_UseTargets(ent, ent); +} + +/* +============================================================================== + +trigger_push + +============================================================================== +*/ + +// PGM +constexpr spawnflags_t SPAWNFLAG_PUSH_ONCE = 0x01_spawnflag; +constexpr spawnflags_t SPAWNFLAG_PUSH_PLUS = 0x02_spawnflag; +constexpr spawnflags_t SPAWNFLAG_PUSH_SILENT = 0x04_spawnflag; +constexpr spawnflags_t SPAWNFLAG_PUSH_START_OFF = 0x08_spawnflag; +constexpr spawnflags_t SPAWNFLAG_PUSH_CLIP = 0x10_spawnflag; +// PGM + +static int windsound; + +TOUCH(trigger_push_touch) (edict_t *self, edict_t *other, const trace_t &tr, bool other_touching_self) -> void +{ + if (self->spawnflags.has(SPAWNFLAG_PUSH_CLIP)) + { + trace_t clip = gi.clip(self, other->s.origin, other->mins, other->maxs, other->s.origin, G_GetClipMask(other)); + + if (clip.fraction == 1.0f) + return; + } + + if (strcmp(other->classname, "grenade") == 0) + { + other->velocity = self->movedir * (self->speed * 10); + } + else if (other->health > 0) + { + other->velocity = self->movedir * (self->speed * 10); + + if (other->client) + { + // don't take falling damage immediately from this + other->client->oldvelocity = other->velocity; + other->client->oldgroundentity = other->groundentity; + if ( + !(self->spawnflags & SPAWNFLAG_PUSH_SILENT) && + (other->fly_sound_debounce_time < level.time)) + { + other->fly_sound_debounce_time = level.time + 1.5_sec; + gi.sound(other, CHAN_AUTO, windsound, 1, ATTN_NORM, 0); + } + } + } + + if (self->spawnflags.has(SPAWNFLAG_PUSH_ONCE)) + G_FreeEdict(self); +} + +//====== +// PGM +USE(trigger_push_use) (edict_t *self, edict_t *other, edict_t *activator) -> void +{ + if (self->solid == SOLID_NOT) + self->solid = SOLID_TRIGGER; + else + self->solid = SOLID_NOT; + gi.linkentity(self); +} +// PGM +//====== + +// RAFAEL +void trigger_push_active(edict_t *self); + +void trigger_effect(edict_t *self) +{ + vec3_t origin; + int i; + + origin = (self->absmin + self->absmax) * 0.5f; + + for (i = 0; i < 10; i++) + { + origin[2] += (self->speed * 0.01f) * (i + frandom()); + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_TUNNEL_SPARKS); + gi.WriteByte(1); + gi.WritePosition(origin); + gi.WriteDir(vec3_origin); + gi.WriteByte(irandom(0x74, 0x7C)); + gi.multicast(self->s.origin, MULTICAST_PVS, false); + } +} + +THINK(trigger_push_inactive) (edict_t *self) -> void +{ + if (self->delay > level.time.seconds()) + { + self->nextthink = level.time + 100_ms; + } + else + { + self->touch = trigger_push_touch; + self->think = trigger_push_active; + self->nextthink = level.time + 100_ms; + self->delay = (self->nextthink + gtime_t::from_sec(self->wait)).seconds(); + } +} + +THINK(trigger_push_active) (edict_t *self) -> void +{ + if (self->delay > level.time.seconds()) + { + self->nextthink = level.time + 100_ms; + trigger_effect(self); + } + else + { + self->touch = nullptr; + self->think = trigger_push_inactive; + self->nextthink = level.time + 100_ms; + self->delay = (self->nextthink + gtime_t::from_sec(self->wait)).seconds(); + } +} +// RAFAEL + +/*QUAKED trigger_push (.5 .5 .5) ? PUSH_ONCE PUSH_PLUS PUSH_SILENT START_OFF CLIP +Pushes the player +"speed" defaults to 1000 +"wait" defaults to 10, must use PUSH_PLUS + +If targeted, it will toggle on and off when used. + +START_OFF - toggled trigger_push begins in off setting +SILENT - doesn't make wind noise +*/ +void SP_trigger_push(edict_t *self) +{ + InitTrigger(self); + if (!(self->spawnflags & SPAWNFLAG_PUSH_SILENT)) + windsound = gi.soundindex("misc/windfly.wav"); + self->touch = trigger_push_touch; + + // RAFAEL + if (self->spawnflags.has(SPAWNFLAG_PUSH_PLUS)) + { + if (!self->wait) + self->wait = 10; + + self->think = trigger_push_active; + self->nextthink = level.time + 100_ms; + self->delay = (self->nextthink + gtime_t::from_sec(self->wait)).seconds(); + } + // RAFAEL + + if (!self->speed) + self->speed = 1000; + + // PGM + if (self->targetname) // toggleable + { + self->use = trigger_push_use; + if (self->spawnflags.has(SPAWNFLAG_PUSH_START_OFF)) + self->solid = SOLID_NOT; + } + else if (self->spawnflags.has(SPAWNFLAG_PUSH_START_OFF)) + { + gi.Com_Print("trigger_push is START_OFF but not targeted.\n"); + self->svflags = SVF_NONE; + self->touch = nullptr; + self->solid = SOLID_BSP; + self->movetype = MOVETYPE_PUSH; + } + // PGM + + gi.linkentity(self); + + if (self->spawnflags.has(SPAWNFLAG_PUSH_CLIP)) + self->svflags |= SVF_HULL; +} + +/* +============================================================================== + +trigger_hurt + +============================================================================== +*/ + +/*QUAKED trigger_hurt (.5 .5 .5) ? START_OFF TOGGLE SILENT NO_PROTECTION SLOW NO_PLAYERS NO_MONSTERS +Any entity that touches this will be hurt. + +It does dmg points of damage each server frame + +SILENT supresses playing the sound +SLOW changes the damage rate to once per second +NO_PROTECTION *nothing* stops the damage + +"dmg" default 5 (whole numbers only) + +*/ + +constexpr spawnflags_t SPAWNFLAG_HURT_START_OFF = 1_spawnflag; +constexpr spawnflags_t SPAWNFLAG_HURT_TOGGLE = 2_spawnflag; +constexpr spawnflags_t SPAWNFLAG_HURT_SILENT = 4_spawnflag; +constexpr spawnflags_t SPAWNFLAG_HURT_NO_PROTECTION = 8_spawnflag; +constexpr spawnflags_t SPAWNFLAG_HURT_SLOW = 16_spawnflag; +constexpr spawnflags_t SPAWNFLAG_HURT_NO_PLAYERS = 32_spawnflag; +constexpr spawnflags_t SPAWNFLAG_HURT_NO_MONSTERS = 64_spawnflag; +constexpr spawnflags_t SPAWNFLAG_HURT_CLIPPED = 128_spawnflag; + +USE(hurt_use) (edict_t *self, edict_t *other, edict_t *activator) -> void +{ + if (self->solid == SOLID_NOT) + self->solid = SOLID_TRIGGER; + else + self->solid = SOLID_NOT; + gi.linkentity(self); + + if (!(self->spawnflags & SPAWNFLAG_HURT_TOGGLE)) + self->use = nullptr; +} + +TOUCH(hurt_touch) (edict_t *self, edict_t *other, const trace_t &tr, bool other_touching_self) -> void +{ + damageflags_t dflags; + + if (!other->takedamage) + return; + else if (!(other->svflags & SVF_MONSTER) && !(other->flags & FL_DAMAGEABLE) && (!other->client) && (strcmp(other->classname, "misc_explobox") != 0)) + return; + else if (self->spawnflags.has(SPAWNFLAG_HURT_NO_MONSTERS) && (other->svflags & SVF_MONSTER)) + return; + else if (self->spawnflags.has(SPAWNFLAG_HURT_NO_PLAYERS) && (other->client)) + return; + + if (self->timestamp > level.time) + return; + + if (self->spawnflags.has(SPAWNFLAG_HURT_CLIPPED)) + { + trace_t clip = gi.clip(self, other->s.origin, other->mins, other->maxs, other->s.origin, G_GetClipMask(other)); + + if (clip.fraction == 1.0f) + return; + } + + if (self->spawnflags.has(SPAWNFLAG_HURT_SLOW)) + self->timestamp = level.time + 1_sec; + else + self->timestamp = level.time + 10_hz; + + if (!(self->spawnflags & SPAWNFLAG_HURT_SILENT)) + { + if (self->fly_sound_debounce_time < level.time) + { + gi.sound(other, CHAN_AUTO, self->noise_index, 1, ATTN_NORM, 0); + self->fly_sound_debounce_time = level.time + 1_sec; + } + } + + if (self->spawnflags.has(SPAWNFLAG_HURT_NO_PROTECTION)) + dflags = DAMAGE_NO_PROTECTION; + else + dflags = DAMAGE_NONE; + + T_Damage(other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, self->dmg, dflags, MOD_TRIGGER_HURT); +} + +void SP_trigger_hurt(edict_t *self) +{ + InitTrigger(self); + + self->noise_index = gi.soundindex("world/electro.wav"); + self->touch = hurt_touch; + + if (!self->dmg) + self->dmg = 5; + + if (self->spawnflags.has(SPAWNFLAG_HURT_START_OFF)) + self->solid = SOLID_NOT; + else + self->solid = SOLID_TRIGGER; + + if (self->spawnflags.has(SPAWNFLAG_HURT_TOGGLE)) + self->use = hurt_use; + + gi.linkentity(self); + + if (self->spawnflags.has(SPAWNFLAG_HURT_CLIPPED)) + self->svflags |= SVF_HULL; +} + +/* +============================================================================== + +trigger_gravity + +============================================================================== +*/ + +/*QUAKED trigger_gravity (.5 .5 .5) ? TOGGLE START_OFF +Changes the touching entites gravity to the value of "gravity". +1.0 is standard gravity for the level. + +TOGGLE - trigger_gravity can be turned on and off +START_OFF - trigger_gravity starts turned off (implies TOGGLE) +*/ + +constexpr spawnflags_t SPAWNFLAG_GRAVITY_TOGGLE = 1_spawnflag; +constexpr spawnflags_t SPAWNFLAG_GRAVITY_START_OFF = 2_spawnflag; +constexpr spawnflags_t SPAWNFLAG_GRAVITY_CLIPPED = 4_spawnflag; + +// PGM +USE(trigger_gravity_use) (edict_t *self, edict_t *other, edict_t *activator) -> void +{ + if (self->solid == SOLID_NOT) + self->solid = SOLID_TRIGGER; + else + self->solid = SOLID_NOT; + gi.linkentity(self); +} +// PGM + +TOUCH(trigger_gravity_touch) (edict_t *self, edict_t *other, const trace_t &tr, bool other_touching_self) -> void +{ + if (self->spawnflags.has(SPAWNFLAG_GRAVITY_CLIPPED)) + { + trace_t clip = gi.clip(self, other->s.origin, other->mins, other->maxs, other->s.origin, G_GetClipMask(other)); + + if (clip.fraction == 1.0f) + return; + } + + other->gravity = self->gravity; +} + +void SP_trigger_gravity(edict_t *self) +{ + if (!st.gravity || !*st.gravity) + { + gi.Com_PrintFmt("{}: no gravity set\n", *self); + G_FreeEdict(self); + return; + } + + InitTrigger(self); + + // PGM + self->gravity = (float) atof(st.gravity); + + if (self->spawnflags.has(SPAWNFLAG_GRAVITY_TOGGLE)) + self->use = trigger_gravity_use; + + if (self->spawnflags.has(SPAWNFLAG_GRAVITY_START_OFF)) + { + self->use = trigger_gravity_use; + self->solid = SOLID_NOT; + } + + self->touch = trigger_gravity_touch; + // PGM + + gi.linkentity(self); + + if (self->spawnflags.has(SPAWNFLAG_GRAVITY_CLIPPED)) + self->svflags |= SVF_HULL; +} + +/* +============================================================================== + +trigger_monsterjump + +============================================================================== +*/ + +/*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 + +TOGGLE - trigger_monsterjump can be turned on and off +START_OFF - trigger_monsterjump starts turned off (implies TOGGLE) +*/ + +constexpr spawnflags_t SPAWNFLAG_MONSTERJUMP_TOGGLE = 1_spawnflag; +constexpr spawnflags_t SPAWNFLAG_MONSTERJUMP_START_OFF = 2_spawnflag; +constexpr spawnflags_t SPAWNFLAG_MONSTERJUMP_CLIPPED = 4_spawnflag; + +USE(trigger_monsterjump_use) (edict_t* self, edict_t* other, edict_t* activator) -> void +{ + if (self->solid == SOLID_NOT) + self->solid = SOLID_TRIGGER; + else + self->solid = SOLID_NOT; + gi.linkentity(self); +} + +TOUCH(trigger_monsterjump_touch) (edict_t *self, edict_t *other, const trace_t &tr, bool other_touching_self) -> void +{ + if (other->flags & (FL_FLY | FL_SWIM)) + return; + if (other->svflags & SVF_DEADMONSTER) + return; + if (!(other->svflags & SVF_MONSTER)) + return; + + if (self->spawnflags.has(SPAWNFLAG_MONSTERJUMP_CLIPPED)) + { + trace_t clip = gi.clip(self, other->s.origin, other->mins, other->maxs, other->s.origin, G_GetClipMask(other)); + + if (clip.fraction == 1.0f) + return; + } + + // set XY even if not on ground, so the jump will clear lips + other->velocity[0] = self->movedir[0] * self->speed; + other->velocity[1] = self->movedir[1] * self->speed; + + if (!other->groundentity) + return; + + other->groundentity = nullptr; + other->velocity[2] = self->movedir[2]; +} + +void SP_trigger_monsterjump(edict_t *self) +{ + if (!self->speed) + self->speed = 200; + if (!st.height) + st.height = 200; + if (self->s.angles[YAW] == 0) + self->s.angles[YAW] = 360; + InitTrigger(self); + self->touch = trigger_monsterjump_touch; + self->movedir[2] = (float) st.height; + + if (self->spawnflags.has(SPAWNFLAG_MONSTERJUMP_TOGGLE)) + self->use = trigger_monsterjump_use; + + if (self->spawnflags.has(SPAWNFLAG_MONSTERJUMP_START_OFF)) + { + self->use = trigger_monsterjump_use; + self->solid = SOLID_NOT; + } + + gi.linkentity(self); + + if (self->spawnflags.has(SPAWNFLAG_MONSTERJUMP_CLIPPED)) + self->svflags |= SVF_HULL; +} + +/* +============================================================================== + +trigger_flashlight + +============================================================================== +*/ + +/*QUAKED trigger_flashlight (.5 .5 .5) ? +Players moving against this trigger will have their flashlight turned on or off. +"style" default to 0, set to 1 to always turn flashlight on, 2 to always turn off, + otherwise "angles" are used to control on/off state +*/ + +constexpr spawnflags_t SPAWNFLAG_FLASHLIGHT_CLIPPED = 1_spawnflag; + +TOUCH(trigger_flashlight_touch) (edict_t *self, edict_t *other, const trace_t &tr, bool other_touching_self) -> void +{ + if (!other->client) + return; + + if (self->spawnflags.has(SPAWNFLAG_FLASHLIGHT_CLIPPED)) + { + trace_t clip = gi.clip(self, other->s.origin, other->mins, other->maxs, other->s.origin, G_GetClipMask(other)); + + if (clip.fraction == 1.0f) + return; + } + + if (self->style == 1) + { + P_ToggleFlashlight(other, true); + } + else if (self->style == 2) + { + P_ToggleFlashlight(other, false); + } + else if (other->velocity.lengthSquared() > 32.f) + { + vec3_t forward = other->velocity.normalized(); + P_ToggleFlashlight(other, forward.dot(self->movedir) > 0); + } +} + +void SP_trigger_flashlight(edict_t *self) +{ + if (self->s.angles[YAW] == 0) + self->s.angles[YAW] = 360; + InitTrigger(self); + self->touch = trigger_flashlight_touch; + self->movedir[2] = (float) st.height; + + if (self->spawnflags.has(SPAWNFLAG_FLASHLIGHT_CLIPPED)) + self->svflags |= SVF_HULL; + gi.linkentity(self); +} + + +/* +============================================================================== + +trigger_fog + +============================================================================== +*/ + +/*QUAKED trigger_fog (.5 .5 .5) ? AFFECT_FOG AFFECT_HEIGHTFOG INSTANTANEOUS FORCE BLEND +Players moving against this trigger will have their fog settings changed. +Fog/heightfog will be adjusted if the spawnflags are set. Instantaneous +ignores any delays. Force causes it to ignore movement dir and always use +the "on" values. Blend causes it to change towards how far you are into the trigger +with respect to angles. +"target" can target an info_notnull to pull the keys below from. +"delay" default to 0.5; time in seconds a change in fog will occur over +"wait" default to 0.0; time in seconds before a re-trigger can be executed + +"fog_density"; density value of fog, 0-1 +"fog_color"; color value of fog, 3d vector with values between 0-1 (r g b) +"fog_density_off"; transition density value of fog, 0-1 +"fog_color_off"; transition color value of fog, 3d vector with values between 0-1 (r g b) +"fog_sky_factor"; sky factor value of fog, 0-1 +"fog_sky_factor_off"; transition sky factor value of fog, 0-1 + +"heightfog_falloff"; falloff value of heightfog, 0-1 +"heightfog_density"; density value of heightfog, 0-1 +"heightfog_start_color"; the start color for the fog (r g b, 0-1) +"heightfog_start_dist"; the start distance for the fog (units) +"heightfog_end_color"; the start color for the fog (r g b, 0-1) +"heightfog_end_dist"; the end distance for the fog (units) + +"heightfog_falloff_off"; transition falloff value of heightfog, 0-1 +"heightfog_density_off"; transition density value of heightfog, 0-1 +"heightfog_start_color_off"; transition the start color for the fog (r g b, 0-1) +"heightfog_start_dist_off"; transition the start distance for the fog (units) +"heightfog_end_color_off"; transition the start color for the fog (r g b, 0-1) +"heightfog_end_dist_off"; transition the end distance for the fog (units) +*/ + +constexpr spawnflags_t SPAWNFLAG_FOG_AFFECT_FOG = 1_spawnflag; +constexpr spawnflags_t SPAWNFLAG_FOG_AFFECT_HEIGHTFOG = 2_spawnflag; +constexpr spawnflags_t SPAWNFLAG_FOG_INSTANTANEOUS = 4_spawnflag; +constexpr spawnflags_t SPAWNFLAG_FOG_FORCE = 8_spawnflag; +constexpr spawnflags_t SPAWNFLAG_FOG_BLEND = 16_spawnflag; + +TOUCH(trigger_fog_touch) (edict_t *self, edict_t *other, const trace_t &tr, bool other_touching_self) -> void +{ + if (!other->client) + return; + + if (self->timestamp > level.time) + return; + + self->timestamp = level.time + gtime_t::from_sec(self->wait); + + edict_t *fog_value_storage = self; + + if (self->movetarget) + fog_value_storage = self->movetarget; + + if (self->spawnflags.has(SPAWNFLAG_FOG_INSTANTANEOUS)) + other->client->pers.fog_transition_time = 0_ms; + else + other->client->pers.fog_transition_time = gtime_t::from_sec(fog_value_storage->delay); + + if (self->spawnflags.has(SPAWNFLAG_FOG_BLEND)) + { + vec3_t center = (self->absmin + self->absmax) * 0.5f; + vec3_t half_size = (self->size * 0.5f) + (other->size * 0.5f); + vec3_t start = (-self->movedir).scaled(half_size); + vec3_t end = (self->movedir).scaled(half_size); + vec3_t player_dist = (other->s.origin - center).scaled(vec3_t{fabs(self->movedir[0]),fabs(self->movedir[1]),fabs(self->movedir[2])}); + + float dist = (player_dist - start).length(); + dist /= (start - end).length(); + dist = clamp(dist, 0.f, 1.f); + + if (self->spawnflags.has(SPAWNFLAG_FOG_AFFECT_FOG)) + { + other->client->pers.wanted_fog = { + lerp(fog_value_storage->fog.density, fog_value_storage->fog.density_off, dist), + lerp(fog_value_storage->fog.color[0], fog_value_storage->fog.color_off[0], dist), + lerp(fog_value_storage->fog.color[1], fog_value_storage->fog.color_off[1], dist), + lerp(fog_value_storage->fog.color[2], fog_value_storage->fog.color_off[2], dist), + lerp(fog_value_storage->fog.sky_factor, fog_value_storage->fog.sky_factor_off, dist) + }; + } + + if (self->spawnflags.has(SPAWNFLAG_FOG_AFFECT_HEIGHTFOG)) + { + other->client->pers.wanted_heightfog = { + { + lerp(fog_value_storage->heightfog.start_color[0], fog_value_storage->heightfog.start_color_off[0], dist), + lerp(fog_value_storage->heightfog.start_color[1], fog_value_storage->heightfog.start_color_off[1], dist), + lerp(fog_value_storage->heightfog.start_color[2], fog_value_storage->heightfog.start_color_off[2], dist), + lerp(fog_value_storage->heightfog.start_dist, fog_value_storage->heightfog.start_dist_off, dist) + }, + { + lerp(fog_value_storage->heightfog.end_color[0], fog_value_storage->heightfog.end_color_off[0], dist), + lerp(fog_value_storage->heightfog.end_color[1], fog_value_storage->heightfog.end_color_off[1], dist), + lerp(fog_value_storage->heightfog.end_color[2], fog_value_storage->heightfog.end_color_off[2], dist), + lerp(fog_value_storage->heightfog.end_dist, fog_value_storage->heightfog.end_dist_off, dist) + }, + lerp(fog_value_storage->heightfog.falloff, fog_value_storage->heightfog.falloff_off, dist), + lerp(fog_value_storage->heightfog.density, fog_value_storage->heightfog.density_off, dist) + }; + } + + return; + } + + bool use_on = true; + + if (!self->spawnflags.has(SPAWNFLAG_FOG_FORCE)) + { + float len; + vec3_t forward = other->velocity.normalized(len); + + // not moving enough to trip; this is so we don't trip + // the wrong direction when on an elevator, etc. + if (len <= 0.0001f) + return; + + use_on = forward.dot(self->movedir) > 0; + } + + if (self->spawnflags.has(SPAWNFLAG_FOG_AFFECT_FOG)) + { + if (use_on) + { + other->client->pers.wanted_fog = { + fog_value_storage->fog.density, + fog_value_storage->fog.color[0], + fog_value_storage->fog.color[1], + fog_value_storage->fog.color[2], + fog_value_storage->fog.sky_factor + }; + } + else + { + other->client->pers.wanted_fog = { + fog_value_storage->fog.density_off, + fog_value_storage->fog.color_off[0], + fog_value_storage->fog.color_off[1], + fog_value_storage->fog.color_off[2], + fog_value_storage->fog.sky_factor_off + }; + } + } + + if (self->spawnflags.has(SPAWNFLAG_FOG_AFFECT_HEIGHTFOG)) + { + if (use_on) + { + other->client->pers.wanted_heightfog = { + { + fog_value_storage->heightfog.start_color[0], + fog_value_storage->heightfog.start_color[1], + fog_value_storage->heightfog.start_color[2], + fog_value_storage->heightfog.start_dist + }, + { + fog_value_storage->heightfog.end_color[0], + fog_value_storage->heightfog.end_color[1], + fog_value_storage->heightfog.end_color[2], + fog_value_storage->heightfog.end_dist + }, + fog_value_storage->heightfog.falloff, + fog_value_storage->heightfog.density + }; + } + else + { + other->client->pers.wanted_heightfog = { + { + fog_value_storage->heightfog.start_color_off[0], + fog_value_storage->heightfog.start_color_off[1], + fog_value_storage->heightfog.start_color_off[2], + fog_value_storage->heightfog.start_dist_off + }, + { + fog_value_storage->heightfog.end_color_off[0], + fog_value_storage->heightfog.end_color_off[1], + fog_value_storage->heightfog.end_color_off[2], + fog_value_storage->heightfog.end_dist_off + }, + fog_value_storage->heightfog.falloff_off, + fog_value_storage->heightfog.density_off + }; + } + } +} + +void SP_trigger_fog(edict_t *self) +{ + if (self->s.angles[YAW] == 0) + self->s.angles[YAW] = 360; + + InitTrigger(self); + + if (!(self->spawnflags & (SPAWNFLAG_FOG_AFFECT_FOG | SPAWNFLAG_FOG_AFFECT_HEIGHTFOG))) + gi.Com_PrintFmt("WARNING: {} with no fog spawnflags set\n", *self); + + if (self->target) + { + self->movetarget = G_PickTarget(self->target); + + if (self->movetarget) + { + if (!self->movetarget->delay) + self->movetarget->delay = 0.5f; + } + } + + if (!self->delay) + self->delay = 0.5f; + + self->touch = trigger_fog_touch; +} + +/*QUAKED trigger_coop_relay (.5 .5 .5) ? AUTO_FIRE +Like a trigger_relay, but all players must be touching its +mins/maxs in order to fire, otherwise a message will be printed. + +AUTO_FIRE: check every `wait` seconds for containment instead of +requiring to be fired by something else. Frees itself after firing. + +"message"; message to print to the one activating the relay if + not all players are inside the bounds +"message2"; message to print to players not inside the trigger + if they aren't in the bounds +*/ + +constexpr spawnflags_t SPAWNFLAG_COOP_RELAY_AUTO_FIRE = 1_spawnflag; + +inline bool trigger_coop_relay_filter(edict_t *player) +{ + return (player->health <= 0 || player->deadflag || player->movetype == MOVETYPE_NOCLIP || + player->client->resp.spectator || player->s.modelindex != MODELINDEX_PLAYER); +} + +static bool trigger_coop_relay_can_use(edict_t *self, edict_t *activator) +{ + // not coop, so act like a standard trigger_relay minus the message + if (!coop->integer) + return true; + + // coop; scan for all alive players, print appropriate message + // to those in/out of range + bool can_use = true; + + for (auto player : active_players()) + { + // dead or spectator, don't count them + if (trigger_coop_relay_filter(player)) + continue; + + if (boxes_intersect(player->absmin, player->absmax, self->absmin, self->absmax)) + continue; + + if (self->timestamp < level.time) + gi.LocCenter_Print(player, self->map); + can_use = false; + } + + return can_use; +} + +USE(trigger_coop_relay_use) (edict_t *self, edict_t *other, edict_t *activator) -> void +{ + if (!trigger_coop_relay_can_use(self, activator)) + { + if (self->timestamp < level.time) + gi.LocCenter_Print(activator, self->message); + + self->timestamp = level.time + 5_sec; + return; + } + + const char *msg = self->message; + self->message = nullptr; + G_UseTargets(self, activator); + self->message = msg; +} + +static BoxEdictsResult_t trigger_coop_relay_player_filter(edict_t *ent, void *data) +{ + if (!ent->client) + return BoxEdictsResult_t::Skip; + else if (trigger_coop_relay_filter(ent)) + return BoxEdictsResult_t::Skip; + + return BoxEdictsResult_t::Keep; +} + +THINK(trigger_coop_relay_think) (edict_t *self) -> void +{ + std::array players; + size_t num_active = 0; + + for (auto player : active_players()) + if (!trigger_coop_relay_filter(player)) + num_active++; + + size_t n = gi.BoxEdicts(self->absmin, self->absmax, players.data(), num_active, AREA_SOLID, trigger_coop_relay_player_filter, nullptr); + + if (n == num_active) + { + const char *msg = self->message; + self->message = nullptr; + G_UseTargets(self, &globals.edicts[1]); + self->message = msg; + + G_FreeEdict(self); + return; + } + else if (n && self->timestamp < level.time) + { + for (size_t i = 0; i < n; i++) + gi.LocCenter_Print(players[i], self->message); + + for (auto player : active_players()) + if (std::find(players.begin(), players.end(), player) == players.end()) + gi.LocCenter_Print(player, self->map); + + self->timestamp = level.time + 5_sec; + } + + self->nextthink = level.time + gtime_t::from_sec(self->wait); +} + +void SP_trigger_coop_relay(edict_t *self) +{ + if (self->targetname && self->spawnflags.has(SPAWNFLAG_COOP_RELAY_AUTO_FIRE)) + gi.Com_PrintFmt("{}: targetname and auto-fire are mutually exclusive\n", *self); + + InitTrigger(self); + + if (!self->message) + self->message = "$g_coop_wait_for_players"; + + if (!self->map) + self->map = "$g_coop_players_waiting_for_you"; + + if (!self->wait) + self->wait = 1; + + if (self->spawnflags.has(SPAWNFLAG_COOP_RELAY_AUTO_FIRE)) + { + self->think = trigger_coop_relay_think; + self->nextthink = level.time + gtime_t::from_sec(self->wait); + } + else + self->use = trigger_coop_relay_use; + self->svflags |= SVF_NOCLIENT; + gi.linkentity(self); +} \ No newline at end of file diff --git a/rerelease/g_turret.cpp b/rerelease/g_turret.cpp new file mode 100644 index 0000000..2eb406f --- /dev/null +++ b/rerelease/g_turret.cpp @@ -0,0 +1,661 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// g_turret.c + +#include "g_local.h" + +constexpr spawnflags_t SPAWNFLAG_TURRET_BREACH_FIRE = 65536_spawnflag; + +void AnglesNormalize(vec3_t &vec) +{ + while (vec[0] > 360) + vec[0] -= 360; + while (vec[0] < 0) + vec[0] += 360; + while (vec[1] > 360) + vec[1] -= 360; + while (vec[1] < 0) + vec[1] += 360; +} + +MOVEINFO_BLOCKED(turret_blocked) (edict_t *self, edict_t *other) -> void +{ + edict_t *attacker; + + if (other->takedamage) + { + if (self->teammaster->owner) + attacker = self->teammaster->owner; + else + attacker = self->teammaster; + T_Damage(other, self, attacker, vec3_origin, other->s.origin, vec3_origin, self->teammaster->dmg, 10, DAMAGE_NONE, MOD_CRUSH); + } +} + +/*QUAKED turret_breach (0 0 0) ? +This portion of the turret can change both pitch and yaw. +The model should be made with a flat pitch. +It (and the associated base) need to be oriented towards 0. +Use "angle" to set the starting angle. + +"speed" default 50 +"dmg" default 10 +"angle" point this forward +"target" point this at an info_notnull at the muzzle tip +"minpitch" min acceptable pitch angle : default -30 +"maxpitch" max acceptable pitch angle : default 30 +"minyaw" min acceptable yaw angle : default 0 +"maxyaw" max acceptable yaw angle : default 360 +*/ + +void turret_breach_fire(edict_t *self) +{ + vec3_t f, r, u; + vec3_t start; + int damage; + int speed; + + AngleVectors(self->s.angles, f, r, u); + start = self->s.origin + (f * self->move_origin[0]); + start += (r * self->move_origin[1]); + start += (u * self->move_origin[2]); + + if (self->count) + damage = self->count; + else + damage = (int) frandom(100, 150); + speed = 550 + 50 * skill->integer; + edict_t *rocket = fire_rocket(self->teammaster->owner->activator ? self->teammaster->owner->activator : self->teammaster->owner, start, f, damage, speed, 150, damage); + rocket->s.scale = self->teammaster->dmg_radius; + + gi.positioned_sound(start, self, CHAN_WEAPON, gi.soundindex("weapons/rocklf1a.wav"), 1, ATTN_NORM, 0); +} + +THINK(turret_breach_think) (edict_t *self) -> void +{ + edict_t *ent; + vec3_t current_angles; + vec3_t delta; + + current_angles = self->s.angles; + AnglesNormalize(current_angles); + + AnglesNormalize(self->move_angles); + if (self->move_angles[PITCH] > 180) + self->move_angles[PITCH] -= 360; + + // clamp angles to mins & maxs + if (self->move_angles[PITCH] > self->pos1[PITCH]) + self->move_angles[PITCH] = self->pos1[PITCH]; + else if (self->move_angles[PITCH] < self->pos2[PITCH]) + self->move_angles[PITCH] = self->pos2[PITCH]; + + if ((self->move_angles[YAW] < self->pos1[YAW]) || (self->move_angles[YAW] > self->pos2[YAW])) + { + float dmin, dmax; + + dmin = fabsf(self->pos1[YAW] - self->move_angles[YAW]); + if (dmin < -180) + dmin += 360; + else if (dmin > 180) + dmin -= 360; + dmax = fabsf(self->pos2[YAW] - self->move_angles[YAW]); + if (dmax < -180) + dmax += 360; + else if (dmax > 180) + dmax -= 360; + if (fabsf(dmin) < fabsf(dmax)) + self->move_angles[YAW] = self->pos1[YAW]; + else + self->move_angles[YAW] = self->pos2[YAW]; + } + + delta = self->move_angles - current_angles; + if (delta[0] < -180) + delta[0] += 360; + else if (delta[0] > 180) + delta[0] -= 360; + if (delta[1] < -180) + delta[1] += 360; + else if (delta[1] > 180) + delta[1] -= 360; + delta[2] = 0; + + if (delta[0] > self->speed * gi.frame_time_s) + delta[0] = self->speed * gi.frame_time_s; + if (delta[0] < -1 * self->speed * gi.frame_time_s) + delta[0] = -1 * self->speed * gi.frame_time_s; + if (delta[1] > self->speed * gi.frame_time_s) + delta[1] = self->speed * gi.frame_time_s; + if (delta[1] < -1 * self->speed * gi.frame_time_s) + delta[1] = -1 * self->speed * gi.frame_time_s; + + for (ent = self->teammaster; ent; ent = ent->teamchain) + { + if (ent->noise_index) + { + if (delta[0] || delta[1]) + { + ent->s.sound = ent->noise_index; + ent->s.loop_attenuation = ATTN_NORM; + } + else + ent->s.sound = 0; + } + } + + self->avelocity = delta * (1.0f / gi.frame_time_s); + + self->nextthink = level.time + FRAME_TIME_S; + + for (ent = self->teammaster; ent; ent = ent->teamchain) + ent->avelocity[1] = self->avelocity[1]; + + // if we have a driver, adjust his velocities + if (self->owner) + { + float angle; + float target_z; + float diff; + vec3_t target; + vec3_t dir; + + // angular is easy, just copy ours + self->owner->avelocity[0] = self->avelocity[0]; + self->owner->avelocity[1] = self->avelocity[1]; + + // x & y + angle = self->s.angles[1] + self->owner->move_origin[1]; + angle *= (float) (PI * 2 / 360); + target[0] = self->s.origin[0] + cosf(angle) * self->owner->move_origin[0]; + target[1] = self->s.origin[1] + sinf(angle) * self->owner->move_origin[0]; + target[2] = self->owner->s.origin[2]; + + dir = target - self->owner->s.origin; + self->owner->velocity[0] = dir[0] * 1.0f / gi.frame_time_s; + self->owner->velocity[1] = dir[1] * 1.0f / gi.frame_time_s; + + // z + angle = self->s.angles[PITCH] * (float) (PI * 2 / 360); + target_z = self->s.origin[2] + self->owner->move_origin[0] * tan(angle) + self->owner->move_origin[2]; + + diff = target_z - self->owner->s.origin[2]; + self->owner->velocity[2] = diff * 1.0f / gi.frame_time_s; + + if (self->spawnflags.has(SPAWNFLAG_TURRET_BREACH_FIRE)) + { + turret_breach_fire(self); + self->spawnflags &= ~SPAWNFLAG_TURRET_BREACH_FIRE; + } + } +} + +THINK(turret_breach_finish_init) (edict_t *self) -> void +{ + // get and save info for muzzle location + if (!self->target) + { + gi.Com_PrintFmt("{}: needs a target\n", *self); + } + else + { + self->target_ent = G_PickTarget(self->target); + if (self->target_ent) + { + self->move_origin = self->target_ent->s.origin - self->s.origin; + G_FreeEdict(self->target_ent); + } + else + gi.Com_PrintFmt("{}: could not find target entity \"{}\"\n", *self, self->target); + } + + self->teammaster->dmg = self->dmg; + self->teammaster->dmg_radius = self->dmg_radius; // scale + self->think = turret_breach_think; + self->think(self); +} + +void SP_turret_breach(edict_t *self) +{ + self->solid = SOLID_BSP; + self->movetype = MOVETYPE_PUSH; + + if (st.noise) + self->noise_index = gi.soundindex(st.noise); + + gi.setmodel(self, self->model); + + if (!self->speed) + self->speed = 50; + if (!self->dmg) + self->dmg = 10; + + if (!st.minpitch) + st.minpitch = -30; + if (!st.maxpitch) + st.maxpitch = 30; + if (!st.maxyaw) + st.maxyaw = 360; + + self->pos1[PITCH] = -1 * st.minpitch; + self->pos1[YAW] = st.minyaw; + self->pos2[PITCH] = -1 * st.maxpitch; + self->pos2[YAW] = st.maxyaw; + + // scale used for rocket scale + self->dmg_radius = self->s.scale; + self->s.scale = 0; + + self->ideal_yaw = self->s.angles[YAW]; + self->move_angles[YAW] = self->ideal_yaw; + + self->moveinfo.blocked = turret_blocked; + + self->think = turret_breach_finish_init; + self->nextthink = level.time + FRAME_TIME_S; + gi.linkentity(self); +} + +/*QUAKED turret_base (0 0 0) ? +This portion of the turret changes yaw only. +MUST be teamed with a turret_breach. +*/ + +void SP_turret_base(edict_t *self) +{ + self->solid = SOLID_BSP; + self->movetype = MOVETYPE_PUSH; + + if (st.noise) + self->noise_index = gi.soundindex(st.noise); + + gi.setmodel(self, self->model); + self->moveinfo.blocked = turret_blocked; + gi.linkentity(self); +} + +/*QUAKED turret_driver (1 .5 0) (-16 -16 -24) (16 16 32) +Must NOT be on the team with the rest of the turret parts. +Instead it must target the turret_breach. +*/ + +void infantry_die(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod); +void infantry_stand(edict_t *self); +void infantry_pain(edict_t *self, edict_t *other, float kick, int damage, const mod_t &mod); +void infantry_setskin(edict_t *self); + +DIE(turret_driver_die) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void +{ + if (!self->deadflag) + { + edict_t *ent; + + // level the gun + self->target_ent->move_angles[0] = 0; + + // remove the driver from the end of them team chain + for (ent = self->target_ent->teammaster; ent->teamchain != self; ent = ent->teamchain) + ; + ent->teamchain = nullptr; + self->teammaster = nullptr; + self->flags &= ~FL_TEAMSLAVE; + + self->target_ent->owner = nullptr; + self->target_ent->teammaster->owner = nullptr; + + self->target_ent->moveinfo.blocked = nullptr; + + // clear pitch + self->s.angles[0] = 0; + self->movetype = MOVETYPE_STEP; + + self->think = monster_think; + } + + infantry_die(self, inflictor, attacker, damage, point, mod); + + G_FixStuckObject(self, self->s.origin); + AngleVectors(self->s.angles, self->velocity, nullptr, nullptr); + self->velocity *= -50; + self->velocity.z += 110.f; +} + +bool FindTarget(edict_t *self); + +THINK(turret_driver_think) (edict_t *self) -> void +{ + vec3_t target; + vec3_t dir; + + self->nextthink = level.time + FRAME_TIME_S; + + if (self->enemy && (!self->enemy->inuse || self->enemy->health <= 0)) + self->enemy = nullptr; + + if (!self->enemy) + { + if (!FindTarget(self)) + return; + self->monsterinfo.trail_time = level.time; + self->monsterinfo.aiflags &= ~AI_LOST_SIGHT; + } + else + { + if (visible(self, self->enemy)) + { + if (self->monsterinfo.aiflags & AI_LOST_SIGHT) + { + self->monsterinfo.trail_time = level.time; + self->monsterinfo.aiflags &= ~AI_LOST_SIGHT; + } + } + else + { + self->monsterinfo.aiflags |= AI_LOST_SIGHT; + return; + } + } + + // let the turret know where we want it to aim + target = self->enemy->s.origin; + target[2] += self->enemy->viewheight; + dir = target - self->target_ent->s.origin; + self->target_ent->move_angles = vectoangles(dir); + + // decide if we should shoot + if (level.time < self->monsterinfo.attack_finished) + return; + + gtime_t reaction_time = gtime_t::from_sec(3 - skill->integer); + if ((level.time - self->monsterinfo.trail_time) < reaction_time) + return; + + self->monsterinfo.attack_finished = level.time + reaction_time + 1_sec; + // FIXME how do we really want to pass this along? + self->target_ent->spawnflags |= SPAWNFLAG_TURRET_BREACH_FIRE; +} + +THINK(turret_driver_link) (edict_t *self) -> void +{ + vec3_t vec; + edict_t *ent; + + self->think = turret_driver_think; + self->nextthink = level.time + FRAME_TIME_S; + + self->target_ent = G_PickTarget(self->target); + self->target_ent->owner = self; + self->target_ent->teammaster->owner = self; + self->s.angles = self->target_ent->s.angles; + + vec[0] = self->target_ent->s.origin[0] - self->s.origin[0]; + vec[1] = self->target_ent->s.origin[1] - self->s.origin[1]; + vec[2] = 0; + self->move_origin[0] = vec.length(); + + vec = self->s.origin - self->target_ent->s.origin; + vec = vectoangles(vec); + AnglesNormalize(vec); + self->move_origin[1] = vec[1]; + + self->move_origin[2] = self->s.origin[2] - self->target_ent->s.origin[2]; + + // add the driver to the end of them team chain + for (ent = self->target_ent->teammaster; ent->teamchain; ent = ent->teamchain) + ; + ent->teamchain = self; + self->teammaster = self->target_ent->teammaster; + self->flags |= FL_TEAMSLAVE; +} + +void InfantryPrecache(); + +void SP_turret_driver(edict_t *self) +{ + if (deathmatch->integer) + { + G_FreeEdict(self); + return; + } + + InfantryPrecache(); + + self->movetype = MOVETYPE_PUSH; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex("models/monsters/infantry/tris.md2"); + self->mins = { -16, -16, -24 }; + self->maxs = { 16, 16, 32 }; + + self->health = self->max_health = 100; + self->gib_health = -40; + self->mass = 200; + self->viewheight = 24; + + self->pain = infantry_pain; + self->die = turret_driver_die; + self->monsterinfo.stand = infantry_stand; + + self->flags |= FL_NO_KNOCKBACK; + + if (g_debug_monster_kills->integer) + level.monsters_registered[level.total_monsters] = self; + level.total_monsters++; + + self->svflags |= SVF_MONSTER; + self->takedamage = true; + self->use = monster_use; + self->clipmask = MASK_MONSTERSOLID; + self->s.old_origin = self->s.origin; + self->monsterinfo.aiflags |= AI_STAND_GROUND; + self->monsterinfo.setskin = infantry_setskin; + + if (st.item) + { + self->item = FindItemByClassname(st.item); + if (!self->item) + gi.Com_PrintFmt("{}: bad item: {}\n", *self, st.item); + } + + self->think = turret_driver_link; + self->nextthink = level.time + FRAME_TIME_S; + + gi.linkentity(self); +} + +//============ +// ROGUE + +// invisible turret drivers so we can have unmanned turrets. +// originally designed to shoot at func_trains and such, so they +// fire at the center of the bounding box, rather than the entity's +// origin. + +constexpr spawnflags_t SPAWNFLAG_TURRET_BRAIN_IGNORE_SIGHT = 1_spawnflag; + +THINK(turret_brain_think) (edict_t *self) -> void +{ + vec3_t target; + vec3_t dir; + vec3_t endpos; + trace_t trace; + + self->nextthink = level.time + FRAME_TIME_S; + + if (self->enemy) + { + if (!self->enemy->inuse) + self->enemy = nullptr; + else if (self->enemy->takedamage && self->enemy->health <= 0) + self->enemy = nullptr; + } + + if (!self->enemy) + { + if (!FindTarget(self)) + return; + self->monsterinfo.trail_time = level.time; + self->monsterinfo.aiflags &= ~AI_LOST_SIGHT; + } + + endpos = self->enemy->absmax + self->enemy->absmin; + endpos *= 0.5f; + + if (!self->spawnflags.has(SPAWNFLAG_TURRET_BRAIN_IGNORE_SIGHT)) + { + trace = gi.traceline(self->target_ent->s.origin, endpos, self->target_ent, MASK_SHOT); + if (trace.fraction == 1 || trace.ent == self->enemy) + { + if (self->monsterinfo.aiflags & AI_LOST_SIGHT) + { + self->monsterinfo.trail_time = level.time; + self->monsterinfo.aiflags &= ~AI_LOST_SIGHT; + } + } + else + { + self->monsterinfo.aiflags |= AI_LOST_SIGHT; + return; + } + } + + // let the turret know where we want it to aim + target = endpos; + dir = target - self->target_ent->s.origin; + self->target_ent->move_angles = vectoangles(dir); + + // decide if we should shoot + if (level.time < self->monsterinfo.attack_finished) + return; + + gtime_t reaction_time; + + if (self->delay) + reaction_time = gtime_t::from_sec(self->delay); + else + reaction_time = gtime_t::from_sec(3 - skill->integer); + + if ((level.time - self->monsterinfo.trail_time) < reaction_time) + return; + + self->monsterinfo.attack_finished = level.time + reaction_time + 1_sec; + // FIXME how do we really want to pass this along? + self->target_ent->spawnflags |= SPAWNFLAG_TURRET_BREACH_FIRE; +} + +// ================= +// ================= +THINK(turret_brain_link) (edict_t *self) -> void +{ + vec3_t vec; + edict_t *ent; + + if (self->killtarget) + { + self->enemy = G_PickTarget(self->killtarget); + } + + self->think = turret_brain_think; + self->nextthink = level.time + FRAME_TIME_S; + + self->target_ent = G_PickTarget(self->target); + self->target_ent->owner = self; + self->target_ent->teammaster->owner = self; + self->s.angles = self->target_ent->s.angles; + + vec[0] = self->target_ent->s.origin[0] - self->s.origin[0]; + vec[1] = self->target_ent->s.origin[1] - self->s.origin[1]; + vec[2] = 0; + self->move_origin[0] = vec.length(); + + vec = self->s.origin - self->target_ent->s.origin; + vec = vectoangles(vec); + AnglesNormalize(vec); + self->move_origin[1] = vec[1]; + + self->move_origin[2] = self->s.origin[2] - self->target_ent->s.origin[2]; + + // add the driver to the end of them team chain + for (ent = self->target_ent->teammaster; ent->teamchain; ent = ent->teamchain) + ent->activator = self->activator; // pass along activator to breach, etc + + ent->teamchain = self; + self->teammaster = self->target_ent->teammaster; + self->flags |= FL_TEAMSLAVE; +} + +// ================= +// ================= +USE(turret_brain_deactivate) (edict_t *self, edict_t *other, edict_t *activator) -> void +{ + self->think = nullptr; + self->nextthink = 0_ms; +} + +// ================= +// ================= +USE(turret_brain_activate) (edict_t *self, edict_t *other, edict_t *activator) -> void +{ + if (!self->enemy) + self->enemy = activator; + + // wait at least 3 seconds to fire. + if (self->wait) + self->monsterinfo.attack_finished = level.time + gtime_t::from_sec(self->wait); + else + self->monsterinfo.attack_finished = level.time + 3_sec; + self->use = turret_brain_deactivate; + + // Paril NOTE: rhangar1 has a turret_invisible_brain that breaks the + // hangar ceiling; once the final rocket explodes the barrier, + // it attempts to print "Barrier neutralized." to the rocket owner + // who happens to be this brain rather than the player that activated + // the turret. this resolves this by passing it along to fire_rocket. + self->activator = activator; + + self->think = turret_brain_link; + self->nextthink = level.time + FRAME_TIME_S; +} + +/*QUAKED turret_invisible_brain (1 .5 0) (-16 -16 -16) (16 16 16) +Invisible brain to drive the turret. + +Does not search for targets. If targeted, can only be turned on once +and then off once. After that they are completely disabled. + +"delay" the delay between firing (default ramps for skill level) +"Target" the turret breach +"Killtarget" the item you want it to attack. +Target the brain if you want it activated later, instead of immediately. It will wait 3 seconds +before firing to acquire the target. +*/ +void SP_turret_invisible_brain(edict_t *self) +{ + if (!self->killtarget) + { + gi.Com_Print("turret_invisible_brain with no killtarget!\n"); + G_FreeEdict(self); + return; + } + if (!self->target) + { + gi.Com_Print("turret_invisible_brain with no target!\n"); + G_FreeEdict(self); + return; + } + + if (self->targetname) + { + self->use = turret_brain_activate; + } + else + { + self->think = turret_brain_link; + self->nextthink = level.time + FRAME_TIME_S; + } + + self->movetype = MOVETYPE_PUSH; + gi.linkentity(self); +} + +// ROGUE +//============ diff --git a/rerelease/g_utils.cpp b/rerelease/g_utils.cpp new file mode 100644 index 0000000..52fdca2 --- /dev/null +++ b/rerelease/g_utils.cpp @@ -0,0 +1,546 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// g_utils.c -- misc utility functions for game module + +#include "g_local.h" + +/* +============= +G_Find + +Searches all active entities for the next one that validates the given callback. + +Searches beginning at the edict after from, or the beginning if nullptr +nullptr will be returned if the end of the list is reached. +============= +*/ +edict_t *G_Find(edict_t *from, std::function matcher) +{ + if (!from) + from = g_edicts; + else + from++; + + for (; from < &g_edicts[globals.num_edicts]; from++) + { + if (!from->inuse) + continue; + if (matcher(from)) + return from; + } + + return nullptr; +} + +/* +================= +findradius + +Returns entities that have origins within a spherical area + +findradius (origin, radius) +================= +*/ +edict_t *findradius(edict_t *from, const vec3_t &org, float rad) +{ + vec3_t eorg; + int j; + + if (!from) + from = g_edicts; + else + from++; + for (; from < &g_edicts[globals.num_edicts]; from++) + { + if (!from->inuse) + continue; + if (from->solid == SOLID_NOT) + continue; + for (j = 0; j < 3; j++) + eorg[j] = org[j] - (from->s.origin[j] + (from->mins[j] + from->maxs[j]) * 0.5f); + if (eorg.length() > rad) + continue; + return from; + } + + return nullptr; +} + +/* +============= +G_PickTarget + +Searches all active entities for the next one that holds +the matching string at fieldofs in the structure. + +Searches beginning at the edict after from, or the beginning if nullptr +nullptr will be returned if the end of the list is reached. + +============= +*/ +constexpr size_t MAXCHOICES = 8; + +edict_t *G_PickTarget(const char *targetname) +{ + edict_t *ent = nullptr; + int num_choices = 0; + edict_t *choice[MAXCHOICES]; + + if (!targetname) + { + gi.Com_Print("G_PickTarget called with nullptr targetname\n"); + return nullptr; + } + + while (1) + { + ent = G_FindByString<&edict_t::targetname>(ent, targetname); + if (!ent) + break; + choice[num_choices++] = ent; + if (num_choices == MAXCHOICES) + break; + } + + if (!num_choices) + { + gi.Com_PrintFmt("G_PickTarget: target {} not found\n", targetname); + return nullptr; + } + + return choice[irandom(num_choices)]; +} + +THINK(Think_Delay) (edict_t *ent) -> void +{ + G_UseTargets(ent, ent->activator); + G_FreeEdict(ent); +} + +void G_PrintActivationMessage(edict_t *ent, edict_t *activator, bool coop_global) +{ + // + // print the message + // + if ((ent->message) && !(activator->svflags & SVF_MONSTER)) + { + if (coop_global && coop->integer) + gi.LocBroadcast_Print(PRINT_CENTER, "{}", ent->message); + else + gi.LocCenter_Print(activator, "{}", ent->message); + + // [Paril-KEX] allow non-noisy centerprints + if (ent->noise_index >= 0) + { + if (ent->noise_index) + gi.sound(activator, CHAN_AUTO, ent->noise_index, 1, ATTN_NORM, 0); + else + gi.sound(activator, CHAN_AUTO, gi.soundindex("misc/talk1.wav"), 1, ATTN_NORM, 0); + } + } +} + +void G_MonsterKilled(edict_t *self); + +/* +============================== +G_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. + +Search for (string)targetname in all entities that +match (string)self.target and call their .use function + +============================== +*/ +void G_UseTargets(edict_t *ent, edict_t *activator) +{ + edict_t *t; + + // + // check for a delay + // + if (ent->delay) + { + // create a temp object to fire at a later time + t = G_Spawn(); + t->classname = "DelayedUse"; + t->nextthink = level.time + gtime_t::from_sec(ent->delay); + t->think = Think_Delay; + t->activator = activator; + if (!activator) + gi.Com_Print("Think_Delay with no activator\n"); + t->message = ent->message; + t->target = ent->target; + t->killtarget = ent->killtarget; + return; + } + + // + // print the message + // + G_PrintActivationMessage(ent, activator, true); + + // + // kill killtargets + // + if (ent->killtarget) + { + t = nullptr; + while ((t = G_FindByString<&edict_t::targetname>(t, ent->killtarget))) + { + if (t->teammaster) + { + // PMM - if this entity is part of a chain, cleanly remove it + if (t->flags & FL_TEAMSLAVE) + { + for (edict_t *master = t->teammaster; master; master = master->teamchain) + { + if (master->teamchain == t) + { + master->teamchain = t->teamchain; + break; + } + } + } + // [Paril-KEX] remove teammaster too + else if (t->flags & FL_TEAMMASTER) + { + t->teammaster->flags &= ~FL_TEAMMASTER; + + edict_t *new_master = t->teammaster->teamchain; + + if (new_master) + { + new_master->flags |= FL_TEAMMASTER; + new_master->flags &= ~FL_TEAMSLAVE; + + for (edict_t *m = new_master; m; m = m->teamchain) + m->teammaster = new_master; + } + } + } + + // [Paril-KEX] if we killtarget a monster, clean up properly + if (t->svflags & SVF_MONSTER) + { + if (!t->deadflag && !(t->monsterinfo.aiflags & AI_DO_NOT_COUNT) && !(t->spawnflags & SPAWNFLAG_MONSTER_DEAD)) + G_MonsterKilled(t); + } + + // PMM + G_FreeEdict(t); + + if (!ent->inuse) + { + gi.Com_Print("entity was removed while using killtargets\n"); + return; + } + } + } + + // + // fire targets + // + if (ent->target) + { + t = nullptr; + while ((t = G_FindByString<&edict_t::targetname>(t, ent->target))) + { + // doors fire area portals in a specific way + if (!Q_strcasecmp(t->classname, "func_areaportal") && + (!Q_strcasecmp(ent->classname, "func_door") || !Q_strcasecmp(ent->classname, "func_door_rotating") + || !Q_strcasecmp(ent->classname, "func_door_secret") || !Q_strcasecmp(ent->classname, "func_water"))) + continue; + + if (t == ent) + { + gi.Com_Print("WARNING: Entity used itself.\n"); + } + else + { + if (t->use) + t->use(t, ent, activator); + } + if (!ent->inuse) + { + gi.Com_Print("entity was removed while using targets\n"); + return; + } + } + } +} + +constexpr vec3_t VEC_UP = { 0, -1, 0 }; +constexpr vec3_t MOVEDIR_UP = { 0, 0, 1 }; +constexpr vec3_t VEC_DOWN = { 0, -2, 0 }; +constexpr vec3_t MOVEDIR_DOWN = { 0, 0, -1 }; + +void G_SetMovedir(vec3_t &angles, vec3_t &movedir) +{ + if (angles == VEC_UP) + { + movedir = MOVEDIR_UP; + } + else if (angles == VEC_DOWN) + { + movedir = MOVEDIR_DOWN; + } + else + { + AngleVectors(angles, movedir, nullptr, nullptr); + } + + angles = {}; +} + +char *G_CopyString(const char *in, int32_t tag) +{ + if(!in) + return nullptr; + const size_t amt = strlen(in) + 1; + char *const out = static_cast(gi.TagMalloc(amt, tag)); + Q_strlcpy(out, in, amt); + return out; +} + +void G_InitEdict(edict_t *e) +{ + // ROGUE + // FIXME - + // this fixes a bug somewhere that is setting "nextthink" for an entity that has + // already been released. nextthink is being set to FRAME_TIME_S after level.time, + // since freetime = nextthink - FRAME_TIME_S + if (e->nextthink) + e->nextthink = 0_ms; + // ROGUE + + e->inuse = true; + e->sv.init = false; + e->classname = "noclass"; + e->gravity = 1.0; + e->s.number = e - g_edicts; + + // PGM - do this before calling the spawn function so it can be overridden. + e->gravityVector[0] = 0.0; + e->gravityVector[1] = 0.0; + e->gravityVector[2] = -1.0; + // PGM +} + +/* +================= +G_Spawn + +Either finds a free edict, or allocates a new one. +Try to avoid reusing an entity that was recently freed, because it +can cause the client to think the entity morphed into something else +instead of being removed and recreated, which can cause interpolated +angles and bad trails. +================= +*/ +edict_t *G_Spawn() +{ + uint32_t i; + edict_t *e; + + e = &g_edicts[game.maxclients + 1]; + for (i = game.maxclients + 1; i < globals.num_edicts; i++, e++) + { + // the first couple seconds of server time can involve a lot of + // freeing and allocating, so relax the replacement policy + if (!e->inuse && (e->freetime < 2_sec || level.time - e->freetime > 500_ms)) + { + G_InitEdict(e); + return e; + } + } + + if (i == game.maxentities) + gi.Com_Error("ED_Alloc: no free edicts"); + + globals.num_edicts++; + G_InitEdict(e); + return e; +} + +/* +================= +G_FreeEdict + +Marks the edict as free +================= +*/ +THINK(G_FreeEdict) (edict_t *ed) -> void +{ + // already freed + if (!ed->inuse) + return; + + gi.unlinkentity(ed); // unlink from world + + if ((ed - g_edicts) <= (ptrdiff_t) (game.maxclients + BODY_QUEUE_SIZE)) + { +#ifdef _DEBUG + gi.Com_Print("tried to free special edict\n"); +#endif + return; + } + + gi.Bot_UnRegisterEdict( ed ); + + int32_t id = ed->spawn_count + 1; + memset(ed, 0, sizeof(*ed)); + ed->s.number = ed - g_edicts; + ed->classname = "freed"; + ed->freetime = level.time; + ed->inuse = false; + ed->spawn_count = id; + ed->sv.init = false; +} + +BoxEdictsResult_t G_TouchTriggers_BoxFilter(edict_t *hit, void *) +{ + if (!hit->touch) + return BoxEdictsResult_t::Skip; + + return BoxEdictsResult_t::Keep; +} + +/* +============ +G_TouchTriggers + +============ +*/ +void G_TouchTriggers(edict_t *ent) +{ + int i, num; + static edict_t *touch[MAX_EDICTS]; + edict_t *hit; + + // dead things don't activate triggers! + if ((ent->client || (ent->svflags & SVF_MONSTER)) && (ent->health <= 0)) + return; + + num = gi.BoxEdicts(ent->absmin, ent->absmax, touch, MAX_EDICTS, AREA_TRIGGERS, G_TouchTriggers_BoxFilter, nullptr); + + // be careful, it is possible to have an entity in this + // list removed before we get to it (killtriggered) + for (i = 0; i < num; i++) + { + hit = touch[i]; + if (!hit->inuse) + continue; + if (!hit->touch) + continue; + hit->touch(hit, ent, null_trace, true); + } +} + +// [Paril-KEX] scan for projectiles between our movement positions +// to see if we need to collide against them +void G_TouchProjectiles(edict_t *ent, vec3_t previous_origin) +{ + // a bit ugly, but we'll store projectiles we are ignoring here. + static std::vector skipped; + + while (true) + { + trace_t tr = gi.trace(previous_origin, ent->mins, ent->maxs, ent->s.origin, ent, ent->clipmask | CONTENTS_PROJECTILE); + + if (tr.fraction == 1.0f) + break; + else if (!(tr.ent->svflags & SVF_PROJECTILE)) + break; + + // always skip this projectile since certain conditions may cause the projectile + // to not disappear immediately + tr.ent->svflags &= ~SVF_PROJECTILE; + skipped.push_back(tr.ent); + + // if we're both players and it's coop, allow the projectile to "pass" through + if (ent->client && tr.ent->owner && tr.ent->owner->client && !G_ShouldPlayersCollide(true)) + continue; + + G_Impact(ent, tr); + } + + for (auto &skip : skipped) + skip->svflags |= SVF_PROJECTILE; + + skipped.clear(); +} + +/* +============================================================================== + +Kill box + +============================================================================== +*/ + +/* +================= +KillBox + +Kills all entities that would touch the proposed new positioning +of ent. +================= +*/ + +BoxEdictsResult_t KillBox_BoxFilter(edict_t *hit, void *) +{ + if (!hit->solid || !hit->takedamage || hit->solid == SOLID_TRIGGER) + return BoxEdictsResult_t::Skip; + + return BoxEdictsResult_t::Keep; +} + +bool KillBox(edict_t *ent, bool from_spawning, mod_id_t mod, bool bsp_clipping) +{ + // don't telefrag as spectator... + if (ent->movetype == MOVETYPE_NOCLIP) + return true; + + contents_t mask = CONTENTS_MONSTER | CONTENTS_PLAYER; + + // [Paril-KEX] don't gib other players in coop if we're not colliding + if (from_spawning && ent->client && coop->integer && !G_ShouldPlayersCollide(false)) + mask &= ~CONTENTS_PLAYER; + + int i, num; + static edict_t *touch[MAX_EDICTS]; + edict_t *hit; + + num = gi.BoxEdicts(ent->absmin, ent->absmax, touch, MAX_EDICTS, AREA_SOLID, KillBox_BoxFilter, nullptr); + + for (i = 0; i < num; i++) + { + hit = touch[i]; + + if (hit == ent) + continue; + else if (!hit->inuse || !hit->takedamage || !hit->solid || hit->solid == SOLID_TRIGGER) + continue; + else if (hit->client && !(mask & CONTENTS_PLAYER)) + continue; + + if ((ent->solid == SOLID_BSP || (ent->svflags & SVF_HULL)) && bsp_clipping) + { + trace_t clip = gi.clip(ent, hit->s.origin, hit->mins, hit->maxs, hit->s.origin, G_GetClipMask(hit)); + + if (clip.fraction == 1.0f) + continue; + } + + T_Damage(hit, ent, ent, vec3_origin, ent->s.origin, vec3_origin, 100000, 0, DAMAGE_NO_PROTECTION, mod); + } + + return true; // all clear +} diff --git a/rerelease/g_weapon.cpp b/rerelease/g_weapon.cpp new file mode 100644 index 0000000..5baa523 --- /dev/null +++ b/rerelease/g_weapon.cpp @@ -0,0 +1,1217 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +#include "g_local.h" + +/* +================= +fire_hit + +Used for all impact (hit/punch/slash) attacks +================= +*/ +bool fire_hit(edict_t *self, vec3_t aim, int damage, int kick) +{ + trace_t tr; + vec3_t forward, right, up; + vec3_t v; + vec3_t point; + float range; + vec3_t dir; + + // see if enemy is in range + range = distance_between_boxes(self->enemy->absmin, self->enemy->absmax, self->absmin, self->absmax); + if (range > aim[0]) + return false; + + if (!(aim[1] > self->mins[0] && aim[1] < self->maxs[0])) + { + // this is a side hit so adjust the "right" value out to the edge of their bbox + if (aim[1] < 0) + aim[1] = self->enemy->mins[0]; + else + aim[1] = self->enemy->maxs[0]; + } + + point = closest_point_to_box(self->s.origin, self->enemy->absmin, self->enemy->absmax); + + // check that we can hit the point on the bbox + tr = gi.traceline(self->s.origin, point, self, MASK_PROJECTILE); + + if (tr.fraction < 1) + { + if (!tr.ent->takedamage) + return false; + // if it will hit any client/monster then hit the one we wanted to hit + if ((tr.ent->svflags & SVF_MONSTER) || (tr.ent->client)) + tr.ent = self->enemy; + } + + // check that we can hit the player from the point + tr = gi.traceline(point, self->enemy->s.origin, self, MASK_PROJECTILE); + + if (tr.fraction < 1) + { + if (!tr.ent->takedamage) + return false; + // if it will hit any client/monster then hit the one we wanted to hit + if ((tr.ent->svflags & SVF_MONSTER) || (tr.ent->client)) + tr.ent = self->enemy; + } + + AngleVectors(self->s.angles, forward, right, up); + point = self->s.origin + (forward * range); + point += (right * aim[1]); + point += (up * aim[2]); + dir = point - self->enemy->s.origin; + + // do the damage + T_Damage(tr.ent, self, self, dir, point, vec3_origin, damage, kick / 2, DAMAGE_NO_KNOCKBACK, MOD_HIT); + + if (!(tr.ent->svflags & SVF_MONSTER) && (!tr.ent->client)) + return false; + + // do our special form of knockback here + v = (self->enemy->absmin + self->enemy->absmax) * 0.5f; + v -= point; + v.normalize(); + self->enemy->velocity += v * kick; + if (self->enemy->velocity[2] > 0) + self->enemy->groundentity = nullptr; + return true; +} + +// helper routine for piercing traces; +// mask = the input mask for finding what to hit +// you can adjust the mask for the re-trace (for water, etc). +// note that you must take care in your pierce callback to mark +// the entities that are being pierced. +void pierce_trace(const vec3_t &start, const vec3_t &end, edict_t *ignore, pierce_args_t &pierce, contents_t mask) +{ + int loop_count = MAX_EDICTS; + vec3_t own_start, own_end; + own_start = start; + own_end = end; + + while (--loop_count) + { + pierce.tr = gi.traceline(start, own_end, ignore, mask); + + // didn't hit anything, so we're done + if (!pierce.tr.ent || pierce.tr.fraction == 1.0f) + return; + + // hit callback said we're done + if (!pierce.hit(mask, own_end)) + return; + + own_start = pierce.tr.endpos; + } + + gi.Com_Print("runaway pierce_trace\n"); +} + +struct fire_lead_pierce_t : pierce_args_t +{ + edict_t *self; + vec3_t start; + vec3_t aimdir; + int damage; + int kick; + int hspread; + int vspread; + mod_t mod; + int te_impact; + contents_t mask; + bool water = false; + vec3_t water_start = {}; + edict_t *chain = nullptr; + + inline fire_lead_pierce_t(edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int hspread, int vspread, mod_t mod, int te_impact, contents_t mask) : + pierce_args_t(), + self(self), + start(start), + aimdir(aimdir), + damage(damage), + kick(kick), + hspread(hspread), + vspread(vspread), + mod(mod), + te_impact(te_impact), + mask(mask) + { + } + + // we hit an entity; return false to stop the piercing. + // you can adjust the mask for the re-trace (for water, etc). + bool hit(contents_t &mask, vec3_t &end) override + { + // see if we hit water + if (tr.contents & MASK_WATER) + { + int color; + + water = true; + water_start = tr.endpos; + + // CHECK: is this compare ever true? + if (te_impact != -1 && start != tr.endpos) + { + if (tr.contents & CONTENTS_WATER) + { + // FIXME: this effectively does nothing.. + if (strcmp(tr.surface->name, "brwater") == 0) + color = SPLASH_BROWN_WATER; + else + color = SPLASH_BLUE_WATER; + } + else if (tr.contents & CONTENTS_SLIME) + color = SPLASH_SLIME; + else if (tr.contents & CONTENTS_LAVA) + color = SPLASH_LAVA; + else + color = SPLASH_UNKNOWN; + + if (color != SPLASH_UNKNOWN) + { + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_SPLASH); + gi.WriteByte(8); + gi.WritePosition(tr.endpos); + gi.WriteDir(tr.plane.normal); + gi.WriteByte(color); + gi.multicast(tr.endpos, MULTICAST_PVS, false); + } + + // change bullet's course when it enters water + vec3_t dir, forward, right, up; + dir = end - start; + dir = vectoangles(dir); + AngleVectors(dir, forward, right, up); + float r = crandom() * hspread * 2; + float u = crandom() * vspread * 2; + end = water_start + (forward * 8192); + end += (right * r); + end += (up * u); + } + + // re-trace ignoring water this time + mask &= ~MASK_WATER; + return true; + } + + // did we hit an hurtable entity? + if (tr.ent->takedamage) + { + T_Damage(tr.ent, self, self, aimdir, tr.endpos, tr.plane.normal, damage, kick, mod.id == MOD_TESLA ? DAMAGE_ENERGY : DAMAGE_BULLET, mod); + + // only deadmonster is pierceable, or actual dead monsters + // that haven't been made non-solid yet + if ((tr.ent->svflags & SVF_DEADMONSTER) || + (tr.ent->health <= 0 && (tr.ent->svflags & SVF_MONSTER))) + { + if (!mark(tr.ent)) + return false; + + return true; + } + } + else + { + // send gun puff / flash + // don't mark the sky + if (te_impact != -1 && !(tr.surface && ((tr.surface->flags & SURF_SKY) || strncmp(tr.surface->name, "sky", 3) == 0))) + { + gi.WriteByte(svc_temp_entity); + gi.WriteByte(te_impact); + gi.WritePosition(tr.endpos); + gi.WriteDir(tr.plane.normal); + gi.multicast(tr.endpos, MULTICAST_PVS, false); + + if (self->client) + PlayerNoise(self, tr.endpos, PNOISE_IMPACT); + } + } + + // hit a solid, so we're stopping here + + return false; + } +}; + +/* +================= +fire_lead + +This is an internal support routine used for bullet/pellet based weapons. +================= +*/ +static void fire_lead(edict_t *self, const vec3_t &start, const vec3_t &aimdir, int damage, int kick, int te_impact, int hspread, int vspread, mod_t mod) +{ + fire_lead_pierce_t args = { + self, + start, + aimdir, + damage, + kick, + hspread, + vspread, + mod, + te_impact, + MASK_PROJECTILE | MASK_WATER + }; + + // [Paril-KEX] + if (self->client && !G_ShouldPlayersCollide(true)) + args.mask &= ~CONTENTS_PLAYER; + + // special case: we started in water. + if (gi.pointcontents(start) & MASK_WATER) + { + args.water = true; + args.water_start = start; + args.mask &= ~MASK_WATER; + } + + // check initial firing position + pierce_trace(self->s.origin, start, self, args, args.mask); + + // we're clear, so do the second pierce + if (args.tr.fraction == 1.f) + { + args.restore(); + + vec3_t end, dir, forward, right, up; + dir = vectoangles(aimdir); + AngleVectors(dir, forward, right, up); + + float r = crandom() * hspread; + float u = crandom() * vspread; + end = start + (forward * 8192); + end += (right * r); + end += (up * u); + + pierce_trace(args.tr.endpos, end, self, args, args.mask); + } + + // if went through water, determine where the end is and make a bubble trail + if (args.water && te_impact != -1) + { + vec3_t pos, dir; + + dir = args.tr.endpos - args.water_start; + dir.normalize(); + pos = args.tr.endpos + (dir * -2); + if (gi.pointcontents(pos) & MASK_WATER) + args.tr.endpos = pos; + else + args.tr = gi.traceline(pos, args.water_start, args.tr.ent != world ? args.tr.ent : nullptr, MASK_WATER); + + pos = args.water_start + args.tr.endpos; + pos *= 0.5f; + + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_BUBBLETRAIL); + gi.WritePosition(args.water_start); + gi.WritePosition(args.tr.endpos); + gi.multicast(pos, MULTICAST_PVS, false); + } +} + +/* +================= +fire_bullet + +Fires a single round. Used for machinegun and chaingun. Would be fine for +pistols, rifles, etc.... +================= +*/ +void fire_bullet(edict_t *self, const vec3_t &start, const vec3_t &aimdir, int damage, int kick, int hspread, int vspread, mod_t mod) +{ + fire_lead(self, start, aimdir, damage, kick, mod.id == MOD_TESLA ? -1 : TE_GUNSHOT, hspread, vspread, mod); +} + +/* +================= +fire_shotgun + +Shoots shotgun pellets. Used by shotgun and super shotgun. +================= +*/ +void fire_shotgun(edict_t *self, const vec3_t &start, const vec3_t &aimdir, int damage, int kick, int hspread, int vspread, int count, mod_t mod) +{ + for (int i = 0; i < count; i++) + fire_lead(self, start, aimdir, damage, kick, TE_SHOTGUN, hspread, vspread, mod); +} + +/* +================= +fire_blaster + +Fires a single blaster bolt. Used by the blaster and hyper blaster. +================= +*/ +TOUCH(blaster_touch) (edict_t *self, edict_t *other, const trace_t &tr, bool other_touching_self) -> void +{ + if (other == self->owner) + return; + + if (tr.surface && (tr.surface->flags & SURF_SKY)) + { + G_FreeEdict(self); + return; + } + + // PMM - crash prevention + if (self->owner && self->owner->client) + PlayerNoise(self->owner, self->s.origin, PNOISE_IMPACT); + + if (other->takedamage) + T_Damage(other, self, self->owner, self->velocity, self->s.origin, tr.plane.normal, self->dmg, 1, DAMAGE_ENERGY, static_cast(self->style)); + else + { + gi.WriteByte(svc_temp_entity); + gi.WriteByte( ( self->style != MOD_BLUEBLASTER ) ? TE_BLASTER : TE_BLUEHYPERBLASTER ); + gi.WritePosition(self->s.origin); + gi.WriteDir(tr.plane.normal); + gi.multicast(self->s.origin, MULTICAST_PHS, false); + } + + G_FreeEdict(self); +} + +void fire_blaster(edict_t *self, const vec3_t &start, const vec3_t &dir, int damage, int speed, effects_t effect, mod_t mod) +{ + edict_t *bolt; + trace_t tr; + + bolt = G_Spawn(); + bolt->svflags = SVF_PROJECTILE; + bolt->s.origin = start; + bolt->s.old_origin = start; + bolt->s.angles = vectoangles(dir); + bolt->velocity = dir * speed; + bolt->movetype = MOVETYPE_FLYMISSILE; + bolt->clipmask = MASK_PROJECTILE; + // [Paril-KEX] + if (self->client && !G_ShouldPlayersCollide(true)) + bolt->clipmask &= ~CONTENTS_PLAYER; + bolt->flags |= FL_DODGE; + bolt->solid = SOLID_BBOX; + bolt->s.effects |= effect; + bolt->s.modelindex = gi.modelindex("models/objects/laser/tris.md2"); + bolt->s.sound = gi.soundindex("misc/lasfly.wav"); + bolt->owner = self; + bolt->touch = blaster_touch; + bolt->nextthink = level.time + 2_sec; + bolt->think = G_FreeEdict; + bolt->dmg = damage; + bolt->classname = "bolt"; + bolt->style = mod.id; + gi.linkentity(bolt); + + tr = gi.traceline(self->s.origin, bolt->s.origin, bolt, bolt->clipmask); + if (tr.fraction < 1.0f) + { + bolt->s.origin = tr.endpos + (tr.plane.normal * 1.f); + bolt->touch(bolt, tr.ent, tr, false); + } +} + +constexpr spawnflags_t SPAWNFLAG_GRENADE_HAND = 1_spawnflag; +constexpr spawnflags_t SPAWNFLAG_GRENADE_HELD = 2_spawnflag; + +/* +================= +fire_grenade +================= +*/ +THINK(Grenade_Explode) (edict_t *ent) -> void +{ + vec3_t origin; + mod_t mod; + + if (ent->owner->client) + PlayerNoise(ent->owner, ent->s.origin, PNOISE_IMPACT); + + // FIXME: if we are onground then raise our Z just a bit since we are a point? + if (ent->enemy) + { + float points; + vec3_t v; + vec3_t dir; + + v = ent->enemy->mins + ent->enemy->maxs; + v = ent->enemy->s.origin + (v * 0.5f); + v = ent->s.origin - v; + points = ent->dmg - 0.5f * v.length(); + dir = ent->enemy->s.origin - ent->s.origin; + if (ent->spawnflags.has(SPAWNFLAG_GRENADE_HAND)) + mod = MOD_HANDGRENADE; + else + mod = MOD_GRENADE; + T_Damage(ent->enemy, ent, ent->owner, dir, ent->s.origin, vec3_origin, (int) points, (int) points, DAMAGE_RADIUS, mod); + } + + if (ent->spawnflags.has(SPAWNFLAG_GRENADE_HELD)) + mod = MOD_HELD_GRENADE; + else if (ent->spawnflags.has(SPAWNFLAG_GRENADE_HAND)) + mod = MOD_HG_SPLASH; + else + mod = MOD_G_SPLASH; + T_RadiusDamage(ent, ent->owner, (float) ent->dmg, ent->enemy, ent->dmg_radius, DAMAGE_NONE, mod); + + origin = ent->s.origin + (ent->velocity * -0.02f); + gi.WriteByte(svc_temp_entity); + if (ent->waterlevel) + { + if (ent->groundentity) + gi.WriteByte(TE_GRENADE_EXPLOSION_WATER); + else + gi.WriteByte(TE_ROCKET_EXPLOSION_WATER); + } + else + { + if (ent->groundentity) + gi.WriteByte(TE_GRENADE_EXPLOSION); + else + gi.WriteByte(TE_ROCKET_EXPLOSION); + } + gi.WritePosition(origin); + gi.multicast(ent->s.origin, MULTICAST_PHS, false); + + G_FreeEdict(ent); +} + +TOUCH(Grenade_Touch) (edict_t *ent, edict_t *other, const trace_t &tr, bool other_touching_self) -> void +{ + if (other == ent->owner) + return; + + if (tr.surface && (tr.surface->flags & SURF_SKY)) + { + G_FreeEdict(ent); + return; + } + + if (!other->takedamage) + { + if (ent->spawnflags.has(SPAWNFLAG_GRENADE_HAND)) + { + if (frandom() > 0.5f) + gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/hgrenb1a.wav"), 1, ATTN_NORM, 0); + else + gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/hgrenb2a.wav"), 1, ATTN_NORM, 0); + } + else + { + gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/grenlb1b.wav"), 1, ATTN_NORM, 0); + } + return; + } + + ent->enemy = other; + Grenade_Explode(ent); +} + +THINK(Grenade4_Think) (edict_t *self) -> void +{ + if (level.time >= self->timestamp) + { + Grenade_Explode(self); + return; + } + + if (self->velocity) + { + float p = self->s.angles.x; + float z = self->s.angles.z; + float speed_frac = clamp(self->velocity.lengthSquared() / (self->speed * self->speed), 0.f, 1.f); + self->s.angles = vectoangles(self->velocity); + self->s.angles.x = LerpAngle(p, self->s.angles.x, speed_frac); + self->s.angles.z = z + (gi.frame_time_s * 360 * speed_frac); + } + + self->nextthink = level.time + FRAME_TIME_S; +} + +void fire_grenade(edict_t *self, const vec3_t &start, const vec3_t &aimdir, int damage, int speed, gtime_t timer, float damage_radius, float right_adjust, float up_adjust, bool monster) +{ + edict_t *grenade; + vec3_t dir; + vec3_t forward, right, up; + + dir = vectoangles(aimdir); + AngleVectors(dir, forward, right, up); + + grenade = G_Spawn(); + grenade->s.origin = start; + grenade->velocity = aimdir * speed; + + if (up_adjust) + { + float gravityAdjustment = level.gravity / 800.f; + grenade->velocity += up * up_adjust * gravityAdjustment; + } + + if (right_adjust) + grenade->velocity += right * right_adjust; + + grenade->movetype = MOVETYPE_BOUNCE; + grenade->clipmask = MASK_PROJECTILE; + // [Paril-KEX] + if (self->client && !G_ShouldPlayersCollide(true)) + grenade->clipmask &= ~CONTENTS_PLAYER; + grenade->solid = SOLID_BBOX; + grenade->svflags |= SVF_PROJECTILE; + grenade->flags |= ( FL_DODGE | FL_TRAP ); + grenade->s.effects |= EF_GRENADE; + grenade->speed = speed; + if (monster) + { + grenade->avelocity = { crandom() * 360, crandom() * 360, crandom() * 360 }; + grenade->s.modelindex = gi.modelindex("models/objects/grenade/tris.md2"); + grenade->nextthink = level.time + timer; + grenade->think = Grenade_Explode; + grenade->s.effects |= EF_GRENADE_LIGHT; + } + else + { + grenade->s.modelindex = gi.modelindex("models/objects/grenade4/tris.md2"); + grenade->s.angles = vectoangles(grenade->velocity); + grenade->nextthink = level.time + FRAME_TIME_S; + grenade->timestamp = level.time + timer; + grenade->think = Grenade4_Think; + grenade->s.renderfx |= RF_MINLIGHT; + } + grenade->owner = self; + grenade->touch = Grenade_Touch; + grenade->dmg = damage; + grenade->dmg_radius = damage_radius; + grenade->classname = "grenade"; + + gi.linkentity(grenade); +} + +void fire_grenade2(edict_t *self, const vec3_t &start, const vec3_t &aimdir, int damage, int speed, gtime_t timer, float damage_radius, bool held) +{ + edict_t *grenade; + vec3_t dir; + vec3_t forward, right, up; + + dir = vectoangles(aimdir); + AngleVectors(dir, forward, right, up); + + grenade = G_Spawn(); + grenade->s.origin = start; + grenade->velocity = aimdir * speed; + + float gravityAdjustment = level.gravity / 800.f; + + grenade->velocity += up * (200 + crandom() * 10.0f) * gravityAdjustment; + grenade->velocity += right * (crandom() * 10.0f); + + grenade->avelocity = { crandom() * 360, crandom() * 360, crandom() * 360 }; + grenade->movetype = MOVETYPE_BOUNCE; + grenade->clipmask = MASK_PROJECTILE; + // [Paril-KEX] + if (self->client && !G_ShouldPlayersCollide(true)) + grenade->clipmask &= ~CONTENTS_PLAYER; + grenade->solid = SOLID_BBOX; + grenade->svflags |= SVF_PROJECTILE; + grenade->flags |= ( FL_DODGE | FL_TRAP ); + grenade->s.effects |= EF_GRENADE; + + grenade->s.modelindex = gi.modelindex("models/objects/grenade3/tris.md2"); + grenade->owner = self; + grenade->touch = Grenade_Touch; + grenade->nextthink = level.time + timer; + grenade->think = Grenade_Explode; + grenade->dmg = damage; + grenade->dmg_radius = damage_radius; + grenade->classname = "hand_grenade"; + grenade->spawnflags = SPAWNFLAG_GRENADE_HAND; + if (held) + grenade->spawnflags |= SPAWNFLAG_GRENADE_HELD; + grenade->s.sound = gi.soundindex("weapons/hgrenc1b.wav"); + + if (timer <= 0_ms) + Grenade_Explode(grenade); + else + { + gi.sound(self, CHAN_WEAPON, gi.soundindex("weapons/hgrent1a.wav"), 1, ATTN_NORM, 0); + gi.linkentity(grenade); + } +} + +/* +================= +fire_rocket +================= +*/ +TOUCH(rocket_touch) (edict_t *ent, edict_t *other, const trace_t &tr, bool other_touching_self) -> void +{ + vec3_t origin; + + if (other == ent->owner) + return; + + if (tr.surface && (tr.surface->flags & SURF_SKY)) + { + G_FreeEdict(ent); + return; + } + + if (ent->owner->client) + PlayerNoise(ent->owner, ent->s.origin, PNOISE_IMPACT); + + // calculate position for the explosion entity + origin = ent->s.origin + tr.plane.normal; + + if (other->takedamage) + { + T_Damage(other, ent, ent->owner, ent->velocity, ent->s.origin, tr.plane.normal, ent->dmg, 0, DAMAGE_NONE, MOD_ROCKET); + } + else + { + // don't throw any debris in net games + if (!deathmatch->integer && !coop->integer) + { + if (tr.surface && !(tr.surface->flags & (SURF_WARP | SURF_TRANS33 | SURF_TRANS66 | SURF_FLOWING))) + { + ThrowGibs(ent, 2, { + { (size_t) irandom(5), "models/objects/debris2/tris.md2", GIB_METALLIC | GIB_DEBRIS } + }); + } + } + } + + T_RadiusDamage(ent, ent->owner, (float) ent->radius_dmg, other, ent->dmg_radius, DAMAGE_NONE, MOD_R_SPLASH); + + gi.WriteByte(svc_temp_entity); + if (ent->waterlevel) + gi.WriteByte(TE_ROCKET_EXPLOSION_WATER); + else + gi.WriteByte(TE_ROCKET_EXPLOSION); + gi.WritePosition(origin); + gi.multicast(ent->s.origin, MULTICAST_PHS, false); + + G_FreeEdict(ent); +} + +edict_t *fire_rocket(edict_t *self, const vec3_t &start, const vec3_t &dir, int damage, int speed, float damage_radius, int radius_damage) +{ + edict_t *rocket; + + rocket = G_Spawn(); + rocket->s.origin = start; + rocket->s.angles = vectoangles(dir); + rocket->velocity = dir * speed; + rocket->movetype = MOVETYPE_FLYMISSILE; + rocket->svflags |= SVF_PROJECTILE; + rocket->flags |= FL_DODGE; + rocket->clipmask = MASK_PROJECTILE; + // [Paril-KEX] + if (self->client && !G_ShouldPlayersCollide(true)) + rocket->clipmask &= ~CONTENTS_PLAYER; + rocket->solid = SOLID_BBOX; + rocket->s.effects |= EF_ROCKET; + rocket->s.modelindex = gi.modelindex("models/objects/rocket/tris.md2"); + rocket->owner = self; + rocket->touch = rocket_touch; + rocket->nextthink = level.time + gtime_t::from_sec(8000.f / speed); + rocket->think = G_FreeEdict; + rocket->dmg = damage; + rocket->radius_dmg = radius_damage; + rocket->dmg_radius = damage_radius; + rocket->s.sound = gi.soundindex("weapons/rockfly.wav"); + rocket->classname = "rocket"; + + gi.linkentity(rocket); + + return rocket; +} + +using search_callback_t = decltype(game_import_t::inPVS); + +bool binary_positional_search_r(const vec3_t &viewer, const vec3_t &start, const vec3_t &end, search_callback_t cb, int32_t split_num) +{ + // check half-way point + vec3_t mid = (start + end) * 0.5f; + + if (cb(viewer, mid, true)) + return true; + + // no more splits + if (!split_num) + return false; + + // recursively check both sides + return binary_positional_search_r(viewer, start, mid, cb, split_num - 1) || binary_positional_search_r(viewer, mid, end, cb, split_num - 1); +} + +// [Paril-KEX] simple binary search through a line to see if any points along +// the line (in a binary split) pass the callback +bool binary_positional_search(const vec3_t &viewer, const vec3_t &start, const vec3_t &end, search_callback_t cb, int32_t num_splits) +{ + // check start/end first + if (cb(viewer, start, true) || cb(viewer, end, true)) + return true; + + // recursive split + return binary_positional_search_r(viewer, start, end, cb, num_splits); +} + +struct fire_rail_pierce_t : pierce_args_t +{ + edict_t *self; + vec3_t aimdir; + int damage; + int kick; + bool water = false; + + inline fire_rail_pierce_t(edict_t *self, vec3_t aimdir, int damage, int kick) : + pierce_args_t(), + self(self), + aimdir(aimdir), + damage(damage), + kick(kick) + { + } + + // we hit an entity; return false to stop the piercing. + // you can adjust the mask for the re-trace (for water, etc). + bool hit(contents_t &mask, vec3_t &end) override + { + if (tr.contents & (CONTENTS_SLIME | CONTENTS_LAVA)) + { + mask &= ~(CONTENTS_SLIME | CONTENTS_LAVA); + water = true; + return true; + } + else + { + // try to kill it first + if ((tr.ent != self) && (tr.ent->takedamage)) + T_Damage(tr.ent, self, self, aimdir, tr.endpos, tr.plane.normal, damage, kick, DAMAGE_NONE, MOD_RAILGUN); + + // dead, so we don't need to care about checking pierce + if (!tr.ent->inuse || (!tr.ent->solid || tr.ent->solid == SOLID_TRIGGER)) + return true; + + // ZOID--added so rail goes through SOLID_BBOX entities (gibs, etc) + if ((tr.ent->svflags & SVF_MONSTER) || (tr.ent->client) || + // ROGUE + (tr.ent->flags & FL_DAMAGEABLE) || + // ROGUE + (tr.ent->solid == SOLID_BBOX)) + { + if (!mark(tr.ent)) + return false; + + return true; + } + } + + return false; + } +}; + +// [Paril-KEX] get the current unique unicast key +uint32_t GetUnicastKey() +{ + static uint32_t key = 1; + + if (!key) + return key = 1; + + return key++; +} + +/* +================= +fire_rail +================= +*/ +void fire_rail(edict_t *self, const vec3_t &start, const vec3_t &aimdir, int damage, int kick) +{ + fire_rail_pierce_t args = { + self, + aimdir, + damage, + kick + }; + + contents_t mask = MASK_PROJECTILE | CONTENTS_SLIME | CONTENTS_LAVA; + + // [Paril-KEX] + if (self->client && !G_ShouldPlayersCollide(true)) + mask &= ~CONTENTS_PLAYER; + + vec3_t end = start + (aimdir * 8192); + + pierce_trace(start, end, self, args, mask); + + uint32_t unicast_key = GetUnicastKey(); + + // send gun puff / flash + // [Paril-KEX] this often makes double noise, so trying + // a slightly different approach... + for (auto player : active_players()) + { + vec3_t org = player->s.origin + player->client->ps.viewoffset + vec3_t{ 0, 0, (float) player->client->ps.pmove.viewheight }; + + if (binary_positional_search(org, start, args.tr.endpos, gi.inPHS, 3)) + { + gi.WriteByte(svc_temp_entity); + gi.WriteByte(g_instagib->integer ? TE_RAILTRAIL2 : TE_RAILTRAIL); + gi.WritePosition(start); + gi.WritePosition(args.tr.endpos); + gi.unicast(player, false, unicast_key); + } + } + + if (self->client) + PlayerNoise(self, args.tr.endpos, PNOISE_IMPACT); +} + +static vec3_t bfg_laser_pos(vec3_t p, float dist) +{ + float theta = frandom(2 * PIf); + float phi = acos(crandom()); + + vec3_t d { + sin(phi) * cos(theta), + sin(phi) * sin(theta), + cos(phi) + }; + + return p + (d * dist); +} + +THINK(bfg_laser_update) (edict_t *self) -> void +{ + if (level.time > self->timestamp || !self->owner->inuse) + { + G_FreeEdict(self); + return; + } + + self->s.origin = self->owner->s.origin; + self->nextthink = level.time + 1_ms; + gi.linkentity(self); +} + +static void bfg_spawn_laser(edict_t *self) +{ + vec3_t end = bfg_laser_pos(self->s.origin, 256); + trace_t tr = gi.traceline(self->s.origin, end, self, MASK_OPAQUE); + + if (tr.fraction == 1.0f) + return; + + edict_t *laser = G_Spawn(); + laser->s.frame = 3; + laser->s.renderfx = RF_BEAM_LIGHTNING; + laser->movetype = MOVETYPE_NONE; + laser->solid = SOLID_NOT; + laser->s.modelindex = MODELINDEX_WORLD; // must be non-zero + laser->s.origin = self->s.origin; + laser->s.old_origin = tr.endpos; + laser->s.skinnum = 0xD0D0D0D0; + laser->think = bfg_laser_update; + laser->nextthink = level.time + 1_ms; + laser->timestamp = level.time + 300_ms; + laser->owner = self; + gi.linkentity(laser); +} + +/* +================= +fire_bfg +================= +*/ +THINK(bfg_explode) (edict_t *self) -> void +{ + edict_t *ent; + float points; + vec3_t v; + float dist; + + bfg_spawn_laser(self); + + if (self->s.frame == 0) + { + // the BFG effect + ent = nullptr; + while ((ent = findradius(ent, self->s.origin, self->dmg_radius)) != nullptr) + { + if (!ent->takedamage) + continue; + if (ent == self->owner) + continue; + if (!CanDamage(ent, self)) + continue; + if (!CanDamage(ent, self->owner)) + continue; + // ROGUE - make tesla hurt by bfg + if (!(ent->svflags & SVF_MONSTER) && !(ent->flags & FL_DAMAGEABLE) && (!ent->client) && (strcmp(ent->classname, "misc_explobox") != 0)) + continue; + // ZOID + // don't target players in CTF + if (CheckTeamDamage(ent, self->owner)) + continue; + // ZOID + + v = ent->mins + ent->maxs; + v = ent->s.origin + (v * 0.5f); + vec3_t centroid = v; + v = self->s.origin - centroid; + dist = v.length(); + points = self->radius_dmg * (1.0f - sqrtf(dist / self->dmg_radius)); + + T_Damage(ent, self, self->owner, self->velocity, centroid, vec3_origin, (int) points, 0, DAMAGE_ENERGY, MOD_BFG_EFFECT); + + // Paril: draw BFG lightning laser to enemies + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_BFG_ZAP); + gi.WritePosition(self->s.origin); + gi.WritePosition(centroid); + gi.multicast(self->s.origin, MULTICAST_PHS, false); + } + } + + self->nextthink = level.time + 10_hz; + self->s.frame++; + if (self->s.frame == 5) + self->think = G_FreeEdict; +} + +TOUCH(bfg_touch) (edict_t *self, edict_t *other, const trace_t &tr, bool other_touching_self) -> void +{ + if (other == self->owner) + return; + + if (tr.surface && (tr.surface->flags & SURF_SKY)) + { + G_FreeEdict(self); + return; + } + + if (self->owner->client) + PlayerNoise(self->owner, self->s.origin, PNOISE_IMPACT); + + // core explosion - prevents firing it into the wall/floor + if (other->takedamage) + T_Damage(other, self, self->owner, self->velocity, self->s.origin, tr.plane.normal, 200, 0, DAMAGE_ENERGY, MOD_BFG_BLAST); + T_RadiusDamage(self, self->owner, 200, other, 100, DAMAGE_ENERGY, MOD_BFG_BLAST); + + gi.sound(self, CHAN_VOICE, gi.soundindex("weapons/bfg__x1b.wav"), 1, ATTN_NORM, 0); + self->solid = SOLID_NOT; + self->touch = nullptr; + self->s.origin += self->velocity * (-1 * gi.frame_time_s); + self->velocity = {}; + self->s.modelindex = gi.modelindex("sprites/s_bfg3.sp2"); + self->s.frame = 0; + self->s.sound = 0; + self->s.effects &= ~EF_ANIM_ALLFAST; + self->think = bfg_explode; + self->nextthink = level.time + 10_hz; + self->enemy = other; + + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_BFG_BIGEXPLOSION); + gi.WritePosition(self->s.origin); + gi.multicast(self->s.origin, MULTICAST_PHS, false); +} + + +struct bfg_laser_pierce_t : pierce_args_t +{ + edict_t *self; + vec3_t dir; + int damage; + + inline bfg_laser_pierce_t(edict_t *self, vec3_t dir, int damage) : + pierce_args_t(), + self(self), + dir(dir), + damage(damage) + { + } + + // we hit an entity; return false to stop the piercing. + // you can adjust the mask for the re-trace (for water, etc). + bool hit(contents_t &mask, vec3_t &end) override + { + // hurt it if we can + if ((tr.ent->takedamage) && !(tr.ent->flags & FL_IMMUNE_LASER) && (tr.ent != self->owner)) + T_Damage(tr.ent, self, self->owner, dir, tr.endpos, vec3_origin, damage, 1, DAMAGE_ENERGY, MOD_BFG_LASER); + + // if we hit something that's not a monster or player we're done + if (!(tr.ent->svflags & SVF_MONSTER) && !(tr.ent->flags & FL_DAMAGEABLE) && (!tr.ent->client)) + { + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_LASER_SPARKS); + gi.WriteByte(4); + gi.WritePosition(tr.endpos); + gi.WriteDir(tr.plane.normal); + gi.WriteByte(self->s.skinnum); + gi.multicast(tr.endpos, MULTICAST_PVS, false); + return false; + } + + if (!mark(tr.ent)) + return false; + + return true; + } +}; + +THINK(bfg_think) (edict_t *self) -> void +{ + edict_t *ent; + vec3_t point; + vec3_t dir; + vec3_t start; + vec3_t end; + int dmg; + trace_t tr; + + if (deathmatch->integer) + dmg = 5; + else + dmg = 10; + + bfg_spawn_laser(self); + + ent = nullptr; + while ((ent = findradius(ent, self->s.origin, 256)) != nullptr) + { + if (ent == self) + continue; + + if (ent == self->owner) + continue; + + if (!ent->takedamage) + continue; + + // ROGUE - make tesla hurt by bfg + if (!(ent->svflags & SVF_MONSTER) && !(ent->flags & FL_DAMAGEABLE) && (!ent->client) && (strcmp(ent->classname, "misc_explobox") != 0)) + continue; + // ZOID + // don't target players in CTF + if (CheckTeamDamage(ent, self->owner)) + continue; + // ZOID + + point = (ent->absmin + ent->absmax) * 0.5f; + + dir = point - self->s.origin; + dir.normalize(); + + start = self->s.origin; + end = start + (dir * 2048); + + // [Paril-KEX] don't fire a laser if we're blocked by the world + tr = gi.traceline(start, point, nullptr, MASK_SOLID); + + if (tr.fraction < 1.0f) + continue; + + bfg_laser_pierce_t args { + self, + dir, + dmg + }; + + pierce_trace(start, end, self, args, CONTENTS_SOLID | CONTENTS_MONSTER | CONTENTS_PLAYER | CONTENTS_DEADMONSTER); + + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_BFG_LASER); + gi.WritePosition(self->s.origin); + gi.WritePosition(tr.endpos); + gi.multicast(self->s.origin, MULTICAST_PHS, false); + } + + self->nextthink = level.time + 10_hz; +} + +void fire_bfg(edict_t *self, const vec3_t &start, const vec3_t &dir, int damage, int speed, float damage_radius) +{ + edict_t *bfg; + + bfg = G_Spawn(); + bfg->s.origin = start; + bfg->s.angles = vectoangles(dir); + bfg->velocity = dir * speed; + bfg->movetype = MOVETYPE_FLYMISSILE; + bfg->clipmask = MASK_PROJECTILE; + bfg->svflags = SVF_PROJECTILE; + // [Paril-KEX] + if (self->client && !G_ShouldPlayersCollide(true)) + bfg->clipmask &= ~CONTENTS_PLAYER; + bfg->solid = SOLID_BBOX; + bfg->s.effects |= EF_BFG | EF_ANIM_ALLFAST; + bfg->s.modelindex = gi.modelindex("sprites/s_bfg1.sp2"); + bfg->owner = self; + bfg->touch = bfg_touch; + bfg->nextthink = level.time + gtime_t::from_sec(8000.f / speed); + bfg->think = G_FreeEdict; + bfg->radius_dmg = damage; + bfg->dmg_radius = damage_radius; + bfg->classname = "bfg blast"; + bfg->s.sound = gi.soundindex("weapons/bfg__l1a.wav"); + + bfg->think = bfg_think; + bfg->nextthink = level.time + FRAME_TIME_S; + bfg->teammaster = bfg; + bfg->teamchain = nullptr; + + gi.linkentity(bfg); +} + +TOUCH(disintegrator_touch) (edict_t *self, edict_t *other, const trace_t &tr, bool other_touching_self) -> void +{ + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_WIDOWSPLASH); + gi.WritePosition(self->s.origin - (self->velocity * 0.01f)); + gi.multicast(self->s.origin, MULTICAST_PHS, false); + + G_FreeEdict(self); + + if (other->svflags & (SVF_MONSTER | SVF_PLAYER)) + { + other->disintegrator_time += 50_sec; + other->disintegrator = self->owner; + } +} + +void fire_disintegrator(edict_t *self, const vec3_t &start, const vec3_t &forward, int speed) +{ + edict_t *bfg; + + bfg = G_Spawn(); + bfg->s.origin = start; + bfg->s.angles = vectoangles(forward); + bfg->velocity = forward * speed; + bfg->movetype = MOVETYPE_FLYMISSILE; + bfg->clipmask = MASK_PROJECTILE; + // [Paril-KEX] + if (self->client && !G_ShouldPlayersCollide(true)) + bfg->clipmask &= ~CONTENTS_PLAYER; + bfg->solid = SOLID_BBOX; + bfg->s.effects |= EF_TAGTRAIL | EF_ANIM_ALL; + bfg->s.renderfx |= RF_TRANSLUCENT; + bfg->svflags |= SVF_PROJECTILE; + bfg->flags |= FL_DODGE; + bfg->s.modelindex = gi.modelindex("sprites/s_bfg1.sp2"); + bfg->owner = self; + bfg->touch = disintegrator_touch; + bfg->nextthink = level.time + gtime_t::from_sec(8000.f / speed); + bfg->think = G_FreeEdict; + bfg->classname = "disint ball"; + bfg->s.sound = gi.soundindex("weapons/bfg__l1a.wav"); + + gi.linkentity(bfg); +} \ No newline at end of file diff --git a/rerelease/game.h b/rerelease/game.h new file mode 100644 index 0000000..f4396fb --- /dev/null +++ b/rerelease/game.h @@ -0,0 +1,2315 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. + +// game.h - game API stuff +#pragma once + +#include +#include + +// compatibility with legacy float[3] stuff for engine +#ifdef GAME_INCLUDE + using gvec3_t = vec3_t; + using gvec3_ptr_t = vec3_t *; + using gvec3_cptr_t = const vec3_t *; + using gvec3_ref_t = vec3_t &; + using gvec3_cref_t = const vec3_t &; + using gvec4_t = std::array; +#else + using gvec3_t = float[3]; + using gvec3_ptr_t = gvec3_t; + using gvec3_ref_t = gvec3_t; + using gvec3_cref_t = const gvec3_t; + using gvec3_cptr_t = const gvec3_t; + using gvec4_t = float[4]; +#endif + +constexpr size_t MAX_SPLIT_PLAYERS = 8; + +struct rgba_t +{ + uint8_t r, g, b, a; +}; + +struct vec2_t +{ + float x, y; +}; + +constexpr rgba_t rgba_red { 255, 0, 0, 255 }; +constexpr rgba_t rgba_blue { 0, 0, 255, 255 }; +constexpr rgba_t rgba_green { 0, 255, 0, 255 }; +constexpr rgba_t rgba_yellow { 255, 255, 0, 255 }; +constexpr rgba_t rgba_white { 255, 255, 255, 255 }; +constexpr rgba_t rgba_black { 0, 0, 0, 255 }; +constexpr rgba_t rgba_cyan { 0, 255, 255, 255 }; +constexpr rgba_t rgba_magenta { 255, 0, 255, 255 }; +constexpr rgba_t rgba_orange { 116, 61, 50, 255 }; + +constexpr size_t MAX_NETNAME = 32; + +constexpr float STEPSIZE = 18.0f; + +// ugly hack to support bitflags on enums +// and use enable_if to prevent confusing cascading +// errors if you use the operators wrongly +#define MAKE_ENUM_BITFLAGS(T) \ + constexpr T operator~(const T &v) \ + { \ + return static_cast(~static_cast>(v)); \ + } \ + constexpr T operator|(const T &v, const T &v2) \ + { \ + return static_cast(static_cast>(v) | static_cast>(v2)); \ + } \ + constexpr T operator&(const T &v, const T &v2) \ + { \ + return static_cast(static_cast>(v) & static_cast>(v2)); \ + } \ + constexpr T operator^(const T &v, const T &v2) \ + { \ + return static_cast(static_cast>(v) ^ static_cast>(v2)); \ + } \ + template>> \ + constexpr T &operator|=(T &v, const T &v2) \ + { \ + v = v | v2; \ + return v; \ + } \ + template>> \ + constexpr T &operator&=(T &v, const T &v2) \ + { \ + v = v & v2; \ + return v; \ + } \ + template>> \ + constexpr T &operator^=(T &v, const T &v2) \ + { \ + v = v ^ v2; \ + return v; \ + } + +using byte = uint8_t; + +// bit simplification +template +using bit_t = std::conditional_t= 32, uint64_t, uint32_t>; + +// template is better for this because you can see +// it in the hover-over preview +template +constexpr bit_t bit_v = 1ull << n; + +#if defined(KEX_Q2GAME_EXPORTS) + #define Q2GAME_API extern "C" __declspec( dllexport ) +#elif defined(KEX_Q2GAME_IMPORTS) + #define Q2GAME_API extern "C" __declspec( dllimport ) +#else + #define Q2GAME_API +#endif + +// game.h -- game dll information visible to server +// PARIL_NEW_API - value likely not used by any other Q2-esque engine in the wild +constexpr int32_t GAME_API_VERSION = 2022; +constexpr int32_t CGAME_API_VERSION = 2022; + +// forward declarations +struct edict_t; +struct gclient_t; + +constexpr size_t MAX_STRING_CHARS = 1024; // max length of a string passed to Cmd_TokenizeString +constexpr size_t MAX_STRING_TOKENS = 80; // max tokens resulting from Cmd_TokenizeString +constexpr size_t MAX_TOKEN_CHARS = 512; // max length of an individual token + +constexpr size_t MAX_QPATH = 64; // max length of a quake game pathname +constexpr size_t MAX_OSPATH = 128; // max length of a filesystem pathname + +// +// per-level limits +// +constexpr size_t MAX_CLIENTS = 256; // absolute limit +constexpr size_t MAX_EDICTS = 8192; // upper limit, due to svc_sound encoding as 15 bits +constexpr size_t MAX_LIGHTSTYLES = 256; +constexpr size_t MAX_MODELS = 8192; // these are sent over the net as shorts +constexpr size_t MAX_SOUNDS = 2048; // so they cannot be blindly increased +constexpr size_t MAX_IMAGES = 512; +constexpr size_t MAX_ITEMS = 256; +constexpr size_t MAX_GENERAL = (MAX_CLIENTS * 2); // general config strings + +// [Sam-KEX] +constexpr size_t MAX_SHADOW_LIGHTS = 256; + +// game print flags +enum print_type_t +{ + PRINT_LOW = 0, // pickup messages + PRINT_MEDIUM = 1, // death messages + PRINT_HIGH = 2, // critical messages + PRINT_CHAT = 3, // chat messages + PRINT_TYPEWRITER = 4, // centerprint but typed out one char at a time + PRINT_CENTER = 5, // centerprint without a separate function (loc variants only) + PRINT_TTS = 6, // PRINT_HIGH but will speak for players with narration on + + PRINT_BROADCAST = (1 << 3), // Bitflag, add to message to broadcast print to all clients. + PRINT_NO_NOTIFY = (1 << 4) // Bitflag, don't put on notify +}; + +MAKE_ENUM_BITFLAGS(print_type_t); + +// [Paril-KEX] max number of arguments (not including the base) for +// localization prints +constexpr size_t MAX_LOCALIZATION_ARGS = 8; + +// destination class for gi.multicast() +enum multicast_t +{ + MULTICAST_ALL, + MULTICAST_PHS, + MULTICAST_PVS +}; + +/* +========================================================== + +CVARS (console variables) + +========================================================== +*/ + +enum cvar_flags_t : uint32_t +{ + CVAR_NOFLAGS = 0, + CVAR_ARCHIVE = bit_v<0>, // set to cause it to be saved to config + CVAR_USERINFO = bit_v<1>, // added to userinfo when changed + CVAR_SERVERINFO = bit_v<2>, // added to serverinfo when changed + CVAR_NOSET = bit_v<3>, // don't allow change from console at all, + // but can be set from the command line + CVAR_LATCH = bit_v<4>, // save changes until server restart + CVAR_USER_PROFILE = bit_v<5>, // like CVAR_USERINFO but not sent to server +}; +MAKE_ENUM_BITFLAGS(cvar_flags_t); + +// nothing outside the Cvar_*() functions should modify these fields! +struct cvar_t +{ + char *name; + char *string; + char *latched_string; // for CVAR_LATCH vars + cvar_flags_t flags; + int32_t modified_count; // changed each time the cvar is changed, but never zero + float value; + cvar_t *next; + int32_t integer; // integral value +}; + +// convenience function to check if the given cvar ptr has been +// modified from its previous modified value, and automatically +// assigns modified to cvar's current value +inline bool Cvar_WasModified(const cvar_t *cvar, int32_t &modified) +{ + if (cvar->modified_count != modified) + { + modified = cvar->modified_count; + return true; + } + + return false; +} + +/* +============================================================== + +COLLISION DETECTION + +============================================================== +*/ + +// lower bits are stronger, and will eat weaker brushes completely +enum contents_t : uint32_t +{ + CONTENTS_NONE = 0, + CONTENTS_SOLID = bit_v<0>, // an eye is never valid in a solid + CONTENTS_WINDOW = bit_v<1>, // translucent, but not watery + CONTENTS_AUX = bit_v<2>, + CONTENTS_LAVA = bit_v<3>, + CONTENTS_SLIME = bit_v<4>, + CONTENTS_WATER = bit_v<5>, + CONTENTS_MIST = bit_v<6>, + + // remaining contents are non-visible, and don't eat brushes + + CONTENTS_PROJECTILECLIP = bit_v<14>, // [Paril-KEX] projectiles will collide with this + + CONTENTS_AREAPORTAL = bit_v<15>, + + CONTENTS_PLAYERCLIP = bit_v<16>, + CONTENTS_MONSTERCLIP = bit_v<17>, + + // currents can be added to any other contents, and may be mixed + CONTENTS_CURRENT_0 = bit_v<18>, + CONTENTS_CURRENT_90 = bit_v<19>, + CONTENTS_CURRENT_180 = bit_v<20>, + CONTENTS_CURRENT_270 = bit_v<21>, + CONTENTS_CURRENT_UP = bit_v<22>, + CONTENTS_CURRENT_DOWN = bit_v<23>, + + CONTENTS_ORIGIN = bit_v<24>, // removed before bsping an entity + + CONTENTS_MONSTER = bit_v<25>, // should never be on a brush, only in game + CONTENTS_DEADMONSTER = bit_v<26>, + + CONTENTS_DETAIL = bit_v<27>, // brushes to be added after vis leafs + CONTENTS_TRANSLUCENT = bit_v<28>, // auto set if any surface has trans + CONTENTS_LADDER = bit_v<29>, + + CONTENTS_PLAYER = bit_v<30>, // [Paril-KEX] should never be on a brush, only in game; player + CONTENTS_PROJECTILE = bit_v<31> // [Paril-KEX] should never be on a brush, only in game; projectiles. + // used to solve deadmonster collision issues. +}; + +MAKE_ENUM_BITFLAGS(contents_t); + +constexpr contents_t LAST_VISIBLE_CONTENTS = CONTENTS_MIST; + +enum surfflags_t : uint32_t +{ + SURF_NONE = 0, + SURF_LIGHT = bit_v<0>, // value will hold the light strength + SURF_SLICK = bit_v<1>, // effects game physics + SURF_SKY = bit_v<2>, // don't draw, but add to skybox + SURF_WARP = bit_v<3>, // turbulent water warp + SURF_TRANS33 = bit_v<4>, + SURF_TRANS66 = bit_v<5>, + SURF_FLOWING = bit_v<6>, // scroll towards angle + SURF_NODRAW = bit_v<7>, // don't bother referencing the texture + SURF_ALPHATEST = bit_v<25>, // [Paril-KEX] alpha test using widely supported flag + SURF_N64_UV = bit_v<28>, // [Sam-KEX] Stretches texture UVs + SURF_N64_SCROLL_X = bit_v<29>, // [Sam-KEX] Texture scroll X-axis + SURF_N64_SCROLL_Y = bit_v<30>, // [Sam-KEX] Texture scroll Y-axis + SURF_N64_SCROLL_FLIP = bit_v<31> // [Sam-KEX] Flip direction of texture scroll +}; + +MAKE_ENUM_BITFLAGS(surfflags_t); + +// content masks +constexpr contents_t MASK_ALL = static_cast(-1); +constexpr contents_t MASK_SOLID = (CONTENTS_SOLID | CONTENTS_WINDOW); +constexpr contents_t MASK_PLAYERSOLID = (CONTENTS_SOLID | CONTENTS_PLAYERCLIP | CONTENTS_WINDOW | CONTENTS_MONSTER | CONTENTS_PLAYER); +constexpr contents_t MASK_DEADSOLID = (CONTENTS_SOLID | CONTENTS_PLAYERCLIP | CONTENTS_WINDOW); +constexpr contents_t MASK_MONSTERSOLID = (CONTENTS_SOLID | CONTENTS_MONSTERCLIP | CONTENTS_WINDOW | CONTENTS_MONSTER | CONTENTS_PLAYER); +constexpr contents_t MASK_WATER = (CONTENTS_WATER | CONTENTS_LAVA | CONTENTS_SLIME); +constexpr contents_t MASK_OPAQUE = (CONTENTS_SOLID | CONTENTS_SLIME | CONTENTS_LAVA); +constexpr contents_t MASK_SHOT = (CONTENTS_SOLID | CONTENTS_MONSTER | CONTENTS_PLAYER | CONTENTS_WINDOW | CONTENTS_DEADMONSTER); +constexpr contents_t MASK_CURRENT = (CONTENTS_CURRENT_0 | CONTENTS_CURRENT_90 | CONTENTS_CURRENT_180 | CONTENTS_CURRENT_270 | CONTENTS_CURRENT_UP | CONTENTS_CURRENT_DOWN); +constexpr contents_t MASK_BLOCK_SIGHT = ( CONTENTS_SOLID | CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_MONSTER | CONTENTS_PLAYER ); +constexpr contents_t MASK_NAV_SOLID = ( CONTENTS_SOLID | CONTENTS_PLAYERCLIP | CONTENTS_WINDOW ); +constexpr contents_t MASK_LADDER_NAV_SOLID = ( CONTENTS_SOLID | CONTENTS_WINDOW ); +constexpr contents_t MASK_WALK_NAV_SOLID = ( CONTENTS_SOLID | CONTENTS_PLAYERCLIP | CONTENTS_WINDOW | CONTENTS_MONSTERCLIP ); +constexpr contents_t MASK_PROJECTILE = MASK_SHOT | CONTENTS_PROJECTILECLIP; + +// gi.BoxEdicts() can return a list of either solid or trigger entities +// FIXME: eliminate AREA_ distinction? +enum solidity_area_t +{ + AREA_SOLID = 1, + AREA_TRIGGERS = 2 +}; + +// plane_t structure +// !!! if this is changed, it must be changed in asm code too !!! +struct cplane_t +{ + gvec3_t normal; + float dist; + byte type; // for fast side tests + byte signbits; // signx + (signy<<1) + (signz<<1) + byte pad[2]; +}; + +// [Paril-KEX] +constexpr size_t MAX_MATERIAL_NAME = 16; + +struct csurface_t +{ + char name[32]; + surfflags_t flags; + int32_t value; + + // [Paril-KEX] + uint32_t id; // unique texinfo ID, offset by 1 (0 is 'null') + char material[MAX_MATERIAL_NAME]; +}; + +// a trace is returned when a box is swept through the world +struct trace_t +{ + bool allsolid; // if true, plane is not valid + bool startsolid; // if true, the initial point was in a solid area + float fraction; // time completed, 1.0 = didn't hit anything + gvec3_t endpos; // final position + cplane_t plane; // surface normal at impact + csurface_t *surface; // surface hit + contents_t contents; // contents on other side of surface hit + edict_t *ent; // not set by CM_*() functions + + // [Paril-KEX] the second-best surface hit from a trace + cplane_t plane2; // second surface normal at impact + csurface_t *surface2; // second surface hit +}; + +// pmove_state_t is the information necessary for client side movement +// prediction +enum pmtype_t +{ + // can accelerate and turn + PM_NORMAL, + PM_GRAPPLE, // [Paril-KEX] pull towards velocity, no gravity + PM_NOCLIP, + PM_SPECTATOR, // [Paril-KEX] clip against walls, but not entities + // no acceleration or turning + PM_DEAD, + PM_GIB, // different bounding box + PM_FREEZE +}; + +// pmove->pm_flags +enum pmflags_t : uint16_t +{ + PMF_NONE = 0, + PMF_DUCKED = bit_v<0>, + PMF_JUMP_HELD = bit_v<1>, + PMF_ON_GROUND = bit_v<2>, + PMF_TIME_WATERJUMP = bit_v<3>, // pm_time is waterjump + PMF_TIME_LAND = bit_v<4>, // pm_time is time before rejump + PMF_TIME_TELEPORT = bit_v<5>, // pm_time is non-moving time + PMF_NO_POSITIONAL_PREDICTION = bit_v<6>, // temporarily disables positional prediction (used for grappling hook) + PMF_ON_LADDER = bit_v<7>, // signal to game that we are on a ladder + PMF_NO_ANGULAR_PREDICTION = bit_v<8>, // temporary disables angular prediction + PMF_IGNORE_PLAYER_COLLISION = bit_v<9>, // don't collide with other players + PMF_TIME_TRICK = bit_v<10>, // pm_time is trick jump time +}; + +MAKE_ENUM_BITFLAGS(pmflags_t); + +// this structure needs to be communicated bit-accurate +// from the server to the client to guarantee that +// prediction stays in sync. +// if any part of the game code modifies this struct, it +// will result in a prediction error of some degree. +struct pmove_state_t +{ + pmtype_t pm_type; + + vec3_t origin; + vec3_t velocity; + pmflags_t pm_flags; // ducked, jump_held, etc + uint16_t pm_time; + int16_t gravity; + gvec3_t delta_angles; // add to command angles to get view direction + // changed by spawns, rotating objects, and teleporters + int8_t viewheight; // view height, added to origin[2] + viewoffset[2], for crouching +}; + +// +// button bits +// +enum button_t : uint8_t +{ + BUTTON_NONE = 0, + BUTTON_ATTACK = bit_v<0>, + BUTTON_USE = bit_v<1>, + BUTTON_HOLSTER = bit_v<2>, // [Paril-KEX] + BUTTON_JUMP = bit_v<3>, + BUTTON_CROUCH = bit_v<4>, + BUTTON_ANY = bit_v<7> // any key whatsoever +}; + +MAKE_ENUM_BITFLAGS(button_t); + +// usercmd_t is sent to the server each client frame +struct usercmd_t +{ + byte msec; + button_t buttons; + gvec3_t angles; + float forwardmove, sidemove; + uint32_t server_frame; // for integrity, etc +}; + +enum water_level_t : uint8_t +{ + WATER_NONE, + WATER_FEET, + WATER_WAIST, + WATER_UNDER +}; + +// player_state_t->refdef flags +enum refdef_flags_t : uint8_t +{ + RDF_NONE = 0, + RDF_UNDERWATER = bit_v<0>, // warp the screen as appropriate + RDF_NOWORLDMODEL = bit_v<1>, // used for player configuration screen + + // ROGUE + RDF_IRGOGGLES = bit_v<2>, + RDF_UVGOGGLES = bit_v<3>, + // ROGUE + + RDF_NO_WEAPON_LERP = bit_v<4> +}; + +constexpr size_t MAXTOUCH = 32; + +struct touch_list_t +{ + uint32_t num = 0; + std::array traces; +}; + +struct pmove_t +{ + // state (in / out) + pmove_state_t s; + + // command (in) + usercmd_t cmd; + bool snapinitial; // if s has been changed outside pmove + + // results (out) + touch_list_t touch; + + gvec3_t viewangles; // clamped + + gvec3_t mins, maxs; // bounding box size + + edict_t *groundentity; + cplane_t groundplane; + contents_t watertype; + water_level_t waterlevel; + + edict_t *player; // opaque handle + + // clip against world & entities + trace_t (*trace)(gvec3_cref_t start, gvec3_cptr_t mins, gvec3_cptr_t maxs, gvec3_cref_t end, const edict_t* passent, contents_t contentmask); + // [Paril-KEX] clip against world only + trace_t (*clip)(gvec3_cref_t start, gvec3_cptr_t mins, gvec3_cptr_t maxs, gvec3_cref_t end, contents_t contentmask); + + contents_t (*pointcontents)(gvec3_cref_t point); + + // [KEX] variables (in) + vec3_t viewoffset; // last viewoffset (for accurate calculation of blending) + + // [KEX] results (out) + gvec4_t screen_blend; + refdef_flags_t rdflags; // merged with rdflags from server + bool jump_sound; // play jump sound + float impact_delta; // impact delta, for falling damage +}; + + +// entity_state_t->effects +// Effects are things handled on the client side (lights, particles, frame animations) +// that happen constantly on the given entity. +// An entity that has effects will be sent to the client +// even if it has a zero index model. +enum effects_t : uint64_t +{ + EF_NONE = 0, // no effects + EF_ROTATE = bit_v<0>, // rotate (bonus items) + EF_GIB = bit_v<1>, // leave a trail + EF_BOB = bit_v<2>, // bob (bonus items) + EF_BLASTER = bit_v<3>, // redlight + trail + EF_ROCKET = bit_v<4>, // redlight + trail + EF_GRENADE = bit_v<5>, + EF_HYPERBLASTER = bit_v<6>, + EF_BFG = bit_v<7>, + EF_COLOR_SHELL = bit_v<8>, + EF_POWERSCREEN = bit_v<9>, + EF_ANIM01 = bit_v<10>, // automatically cycle between frames 0 and 1 at 2 hz + EF_ANIM23 = bit_v<11>, // automatically cycle between frames 2 and 3 at 2 hz + EF_ANIM_ALL = bit_v<12>, // automatically cycle through all frames at 2hz + EF_ANIM_ALLFAST = bit_v<13>, // automatically cycle through all frames at 10hz + EF_FLIES = bit_v<14>, + EF_QUAD = bit_v<15>, + EF_PENT = bit_v<16>, + EF_TELEPORTER = bit_v<17>, // particle fountain + EF_FLAG1 = bit_v<18>, + EF_FLAG2 = bit_v<19>, + // RAFAEL + EF_IONRIPPER = bit_v<20>, + EF_GREENGIB = bit_v<21>, + EF_BLUEHYPERBLASTER = bit_v<22>, + EF_SPINNINGLIGHTS = bit_v<23>, + EF_PLASMA = bit_v<24>, + EF_TRAP = bit_v<25>, + + // ROGUE + EF_TRACKER = bit_v<26>, + EF_DOUBLE = bit_v<27>, + EF_SPHERETRANS = bit_v<28>, + EF_TAGTRAIL = bit_v<29>, + EF_HALF_DAMAGE = bit_v<30>, + EF_TRACKERTRAIL = bit_v<31>, + // ROGUE + + EF_DUALFIRE = bit_v<32>, // [KEX] dualfire damage color shell + EF_HOLOGRAM = bit_v<33>, // [Paril-KEX] N64 hologram + EF_FLASHLIGHT = bit_v<34>, // [Paril-KEX] project flashlight, only for players + EF_BARREL_EXPLODING= bit_v<35>, + EF_TELEPORTER2 = bit_v<36>, // [Paril-KEX] n64 teleporter + EF_GRENADE_LIGHT = bit_v<37> +}; + +MAKE_ENUM_BITFLAGS(effects_t); + +constexpr effects_t EF_FIREBALL = EF_ROCKET | EF_GIB; + +// entity_state_t->renderfx flags +enum renderfx_t : uint32_t +{ + RF_NONE = 0, + RF_MINLIGHT = bit_v<0>, // always have some light (viewmodel) + RF_VIEWERMODEL = bit_v<1>, // don't draw through eyes, only mirrors + RF_WEAPONMODEL = bit_v<2>, // only draw through eyes + RF_FULLBRIGHT = bit_v<3>, // always draw full intensity + RF_DEPTHHACK = bit_v<4>, // for view weapon Z crunching + RF_TRANSLUCENT = bit_v<5>, + RF_NO_ORIGIN_LERP = bit_v<6>, // no interpolation for origins + RF_BEAM = bit_v<7>, + RF_CUSTOMSKIN = bit_v<8>, // [Paril-KEX] implemented; set skinnum (or frame for RF_FLARE) to specify + // an image in CS_IMAGES to use as skin. + RF_GLOW = bit_v<9>, // pulse lighting for bonus items + RF_SHELL_RED = bit_v<10>, + RF_SHELL_GREEN = bit_v<11>, + RF_SHELL_BLUE = bit_v<12>, + RF_NOSHADOW = bit_v<13>, + RF_CASTSHADOW = bit_v<14>, // [Sam-KEX] + + // ROGUE + RF_IR_VISIBLE = bit_v<15>, + RF_SHELL_DOUBLE = bit_v<16>, + RF_SHELL_HALF_DAM = bit_v<17>, + RF_USE_DISGUISE = bit_v<18>, + // ROGUE + + RF_SHELL_LITE_GREEN = bit_v<19>, + RF_CUSTOM_LIGHT = bit_v<20>, // [Paril-KEX] custom point dlight that is designed to strobe/be turned off; s.frame is radius, s.skinnum is color + RF_FLARE = bit_v<21>, // [Sam-KEX] + RF_OLD_FRAME_LERP = bit_v<22>, // [Paril-KEX] force model to lerp from oldframe in entity state; otherwise it uses last frame client received + RF_DOT_SHADOW = bit_v<23>, // [Paril-KEX] draw blobby shadow + RF_LOW_PRIORITY = bit_v<24>, // [Paril-KEX] low priority object; if we can't be added to the scene, don't bother replacing entities, + // and we can be replaced if anything non-low-priority needs room + RF_NO_LOD = bit_v<25>, // [Paril-KEX] never LOD + RF_NO_STEREO = RF_WEAPONMODEL, // [Paril-KEX] this is a bit dumb, but, for looping noises if this is set there's no stereo + RF_STAIR_STEP = bit_v<26>, // [Paril-KEX] re-tuned, now used to handle stair steps for monsters + + RF_FLARE_LOCK_ANGLE = RF_MINLIGHT +}; + +MAKE_ENUM_BITFLAGS(renderfx_t); + +constexpr renderfx_t RF_BEAM_LIGHTNING = RF_BEAM | RF_GLOW; // [Paril-KEX] make a lightning bolt instead of a laser + +MAKE_ENUM_BITFLAGS(refdef_flags_t); + +// +// muzzle flashes / player effects +// +enum player_muzzle_t : uint8_t +{ + MZ_BLASTER = 0, + MZ_MACHINEGUN = 1, + MZ_SHOTGUN = 2, + MZ_CHAINGUN1 = 3, + MZ_CHAINGUN2 = 4, + MZ_CHAINGUN3 = 5, + MZ_RAILGUN = 6, + MZ_ROCKET = 7, + MZ_GRENADE = 8, + MZ_LOGIN = 9, + MZ_LOGOUT = 10, + MZ_RESPAWN = 11, + MZ_BFG = 12, + MZ_SSHOTGUN = 13, + MZ_HYPERBLASTER = 14, + MZ_ITEMRESPAWN = 15, + // RAFAEL + MZ_IONRIPPER = 16, + MZ_BLUEHYPERBLASTER = 17, + MZ_PHALANX = 18, + MZ_BFG2 = 19, + MZ_PHALANX2 = 20, + + // ROGUE + MZ_ETF_RIFLE = 30, + MZ_PROX = 31, // [Paril-KEX] + MZ_ETF_RIFLE_2 = 32, // [Paril-KEX] unused, so using it for the other barrel + MZ_HEATBEAM = 33, + MZ_BLASTER2 = 34, + MZ_TRACKER = 35, + MZ_NUKE1 = 36, + MZ_NUKE2 = 37, + MZ_NUKE4 = 38, + MZ_NUKE8 = 39, + // ROGUE + + MZ_SILENCED = bit_v<7>, // bit flag ORed with one of the above numbers + MZ_NONE = 0 // "no" bitflags +}; + +MAKE_ENUM_BITFLAGS(player_muzzle_t); + +// +// monster muzzle flashes +// NOTE: this needs to match the m_flash table! +// +enum monster_muzzleflash_id_t : uint16_t +{ + MZ2_UNUSED_0, + + MZ2_TANK_BLASTER_1, + MZ2_TANK_BLASTER_2, + MZ2_TANK_BLASTER_3, + MZ2_TANK_MACHINEGUN_1, + MZ2_TANK_MACHINEGUN_2, + MZ2_TANK_MACHINEGUN_3, + MZ2_TANK_MACHINEGUN_4, + MZ2_TANK_MACHINEGUN_5, + MZ2_TANK_MACHINEGUN_6, + MZ2_TANK_MACHINEGUN_7, + MZ2_TANK_MACHINEGUN_8, + MZ2_TANK_MACHINEGUN_9, + MZ2_TANK_MACHINEGUN_10, + MZ2_TANK_MACHINEGUN_11, + MZ2_TANK_MACHINEGUN_12, + MZ2_TANK_MACHINEGUN_13, + MZ2_TANK_MACHINEGUN_14, + MZ2_TANK_MACHINEGUN_15, + MZ2_TANK_MACHINEGUN_16, + MZ2_TANK_MACHINEGUN_17, + MZ2_TANK_MACHINEGUN_18, + MZ2_TANK_MACHINEGUN_19, + MZ2_TANK_ROCKET_1, + MZ2_TANK_ROCKET_2, + MZ2_TANK_ROCKET_3, + + MZ2_INFANTRY_MACHINEGUN_1, + MZ2_INFANTRY_MACHINEGUN_2, + MZ2_INFANTRY_MACHINEGUN_3, + MZ2_INFANTRY_MACHINEGUN_4, + MZ2_INFANTRY_MACHINEGUN_5, + MZ2_INFANTRY_MACHINEGUN_6, + MZ2_INFANTRY_MACHINEGUN_7, + MZ2_INFANTRY_MACHINEGUN_8, + MZ2_INFANTRY_MACHINEGUN_9, + MZ2_INFANTRY_MACHINEGUN_10, + MZ2_INFANTRY_MACHINEGUN_11, + MZ2_INFANTRY_MACHINEGUN_12, + MZ2_INFANTRY_MACHINEGUN_13, + + MZ2_SOLDIER_BLASTER_1, + MZ2_SOLDIER_BLASTER_2, + MZ2_SOLDIER_SHOTGUN_1, + MZ2_SOLDIER_SHOTGUN_2, + MZ2_SOLDIER_MACHINEGUN_1, + MZ2_SOLDIER_MACHINEGUN_2, + + MZ2_GUNNER_MACHINEGUN_1, + MZ2_GUNNER_MACHINEGUN_2, + MZ2_GUNNER_MACHINEGUN_3, + MZ2_GUNNER_MACHINEGUN_4, + MZ2_GUNNER_MACHINEGUN_5, + MZ2_GUNNER_MACHINEGUN_6, + MZ2_GUNNER_MACHINEGUN_7, + MZ2_GUNNER_MACHINEGUN_8, + MZ2_GUNNER_GRENADE_1, + MZ2_GUNNER_GRENADE_2, + MZ2_GUNNER_GRENADE_3, + MZ2_GUNNER_GRENADE_4, + + MZ2_CHICK_ROCKET_1, + + MZ2_FLYER_BLASTER_1, + MZ2_FLYER_BLASTER_2, + + MZ2_MEDIC_BLASTER_1, + + MZ2_GLADIATOR_RAILGUN_1, + + MZ2_HOVER_BLASTER_1, + + MZ2_ACTOR_MACHINEGUN_1, + + MZ2_SUPERTANK_MACHINEGUN_1, + MZ2_SUPERTANK_MACHINEGUN_2, + MZ2_SUPERTANK_MACHINEGUN_3, + MZ2_SUPERTANK_MACHINEGUN_4, + MZ2_SUPERTANK_MACHINEGUN_5, + MZ2_SUPERTANK_MACHINEGUN_6, + MZ2_SUPERTANK_ROCKET_1, + MZ2_SUPERTANK_ROCKET_2, + MZ2_SUPERTANK_ROCKET_3, + + MZ2_BOSS2_MACHINEGUN_L1, + MZ2_BOSS2_MACHINEGUN_L2, + MZ2_BOSS2_MACHINEGUN_L3, + MZ2_BOSS2_MACHINEGUN_L4, + MZ2_BOSS2_MACHINEGUN_L5, + MZ2_BOSS2_ROCKET_1, + MZ2_BOSS2_ROCKET_2, + MZ2_BOSS2_ROCKET_3, + MZ2_BOSS2_ROCKET_4, + + MZ2_FLOAT_BLASTER_1, + + MZ2_SOLDIER_BLASTER_3, + MZ2_SOLDIER_SHOTGUN_3, + MZ2_SOLDIER_MACHINEGUN_3, + MZ2_SOLDIER_BLASTER_4, + MZ2_SOLDIER_SHOTGUN_4, + MZ2_SOLDIER_MACHINEGUN_4, + MZ2_SOLDIER_BLASTER_5, + MZ2_SOLDIER_SHOTGUN_5, + MZ2_SOLDIER_MACHINEGUN_5, + MZ2_SOLDIER_BLASTER_6, + MZ2_SOLDIER_SHOTGUN_6, + MZ2_SOLDIER_MACHINEGUN_6, + MZ2_SOLDIER_BLASTER_7, + MZ2_SOLDIER_SHOTGUN_7, + MZ2_SOLDIER_MACHINEGUN_7, + MZ2_SOLDIER_BLASTER_8, + MZ2_SOLDIER_SHOTGUN_8, + MZ2_SOLDIER_MACHINEGUN_8, + + // --- Xian shit below --- + MZ2_MAKRON_BFG, + MZ2_MAKRON_BLASTER_1, + MZ2_MAKRON_BLASTER_2, + MZ2_MAKRON_BLASTER_3, + MZ2_MAKRON_BLASTER_4, + MZ2_MAKRON_BLASTER_5, + MZ2_MAKRON_BLASTER_6, + MZ2_MAKRON_BLASTER_7, + MZ2_MAKRON_BLASTER_8, + MZ2_MAKRON_BLASTER_9, + MZ2_MAKRON_BLASTER_10, + MZ2_MAKRON_BLASTER_11, + MZ2_MAKRON_BLASTER_12, + MZ2_MAKRON_BLASTER_13, + MZ2_MAKRON_BLASTER_14, + MZ2_MAKRON_BLASTER_15, + MZ2_MAKRON_BLASTER_16, + MZ2_MAKRON_BLASTER_17, + MZ2_MAKRON_RAILGUN_1, + MZ2_JORG_MACHINEGUN_L1, + MZ2_JORG_MACHINEGUN_L2, + MZ2_JORG_MACHINEGUN_L3, + MZ2_JORG_MACHINEGUN_L4, + MZ2_JORG_MACHINEGUN_L5, + MZ2_JORG_MACHINEGUN_L6, + MZ2_JORG_MACHINEGUN_R1, + MZ2_JORG_MACHINEGUN_R2, + MZ2_JORG_MACHINEGUN_R3, + MZ2_JORG_MACHINEGUN_R4, + MZ2_JORG_MACHINEGUN_R5, + MZ2_JORG_MACHINEGUN_R6, + MZ2_JORG_BFG_1, + MZ2_BOSS2_MACHINEGUN_R1, + MZ2_BOSS2_MACHINEGUN_R2, + MZ2_BOSS2_MACHINEGUN_R3, + MZ2_BOSS2_MACHINEGUN_R4, + MZ2_BOSS2_MACHINEGUN_R5, + + // ROGUE + MZ2_CARRIER_MACHINEGUN_L1, + MZ2_CARRIER_MACHINEGUN_R1, + MZ2_CARRIER_GRENADE, + MZ2_TURRET_MACHINEGUN, + MZ2_TURRET_ROCKET, + MZ2_TURRET_BLASTER, + MZ2_STALKER_BLASTER, + MZ2_DAEDALUS_BLASTER, + MZ2_MEDIC_BLASTER_2, + MZ2_CARRIER_RAILGUN, + MZ2_WIDOW_DISRUPTOR, + MZ2_WIDOW_BLASTER, + MZ2_WIDOW_RAIL, + MZ2_WIDOW_PLASMABEAM, // PMM - not used + MZ2_CARRIER_MACHINEGUN_L2, + MZ2_CARRIER_MACHINEGUN_R2, + MZ2_WIDOW_RAIL_LEFT, + MZ2_WIDOW_RAIL_RIGHT, + MZ2_WIDOW_BLASTER_SWEEP1, + MZ2_WIDOW_BLASTER_SWEEP2, + MZ2_WIDOW_BLASTER_SWEEP3, + MZ2_WIDOW_BLASTER_SWEEP4, + MZ2_WIDOW_BLASTER_SWEEP5, + MZ2_WIDOW_BLASTER_SWEEP6, + MZ2_WIDOW_BLASTER_SWEEP7, + MZ2_WIDOW_BLASTER_SWEEP8, + MZ2_WIDOW_BLASTER_SWEEP9, + MZ2_WIDOW_BLASTER_100, + MZ2_WIDOW_BLASTER_90, + MZ2_WIDOW_BLASTER_80, + MZ2_WIDOW_BLASTER_70, + MZ2_WIDOW_BLASTER_60, + MZ2_WIDOW_BLASTER_50, + MZ2_WIDOW_BLASTER_40, + MZ2_WIDOW_BLASTER_30, + MZ2_WIDOW_BLASTER_20, + MZ2_WIDOW_BLASTER_10, + MZ2_WIDOW_BLASTER_0, + MZ2_WIDOW_BLASTER_10L, + MZ2_WIDOW_BLASTER_20L, + MZ2_WIDOW_BLASTER_30L, + MZ2_WIDOW_BLASTER_40L, + MZ2_WIDOW_BLASTER_50L, + MZ2_WIDOW_BLASTER_60L, + MZ2_WIDOW_BLASTER_70L, + MZ2_WIDOW_RUN_1, + MZ2_WIDOW_RUN_2, + MZ2_WIDOW_RUN_3, + MZ2_WIDOW_RUN_4, + MZ2_WIDOW_RUN_5, + MZ2_WIDOW_RUN_6, + MZ2_WIDOW_RUN_7, + MZ2_WIDOW_RUN_8, + MZ2_CARRIER_ROCKET_1, + MZ2_CARRIER_ROCKET_2, + MZ2_CARRIER_ROCKET_3, + MZ2_CARRIER_ROCKET_4, + MZ2_WIDOW2_BEAMER_1, + MZ2_WIDOW2_BEAMER_2, + MZ2_WIDOW2_BEAMER_3, + MZ2_WIDOW2_BEAMER_4, + MZ2_WIDOW2_BEAMER_5, + MZ2_WIDOW2_BEAM_SWEEP_1, + MZ2_WIDOW2_BEAM_SWEEP_2, + MZ2_WIDOW2_BEAM_SWEEP_3, + MZ2_WIDOW2_BEAM_SWEEP_4, + MZ2_WIDOW2_BEAM_SWEEP_5, + MZ2_WIDOW2_BEAM_SWEEP_6, + MZ2_WIDOW2_BEAM_SWEEP_7, + MZ2_WIDOW2_BEAM_SWEEP_8, + MZ2_WIDOW2_BEAM_SWEEP_9, + MZ2_WIDOW2_BEAM_SWEEP_10, + MZ2_WIDOW2_BEAM_SWEEP_11, + // ROGUE + + // [Paril-KEX] + MZ2_SOLDIER_RIPPER_1, + MZ2_SOLDIER_RIPPER_2, + MZ2_SOLDIER_RIPPER_3, + MZ2_SOLDIER_RIPPER_4, + MZ2_SOLDIER_RIPPER_5, + MZ2_SOLDIER_RIPPER_6, + MZ2_SOLDIER_RIPPER_7, + MZ2_SOLDIER_RIPPER_8, + + MZ2_SOLDIER_HYPERGUN_1, + MZ2_SOLDIER_HYPERGUN_2, + MZ2_SOLDIER_HYPERGUN_3, + MZ2_SOLDIER_HYPERGUN_4, + MZ2_SOLDIER_HYPERGUN_5, + MZ2_SOLDIER_HYPERGUN_6, + MZ2_SOLDIER_HYPERGUN_7, + MZ2_SOLDIER_HYPERGUN_8, + MZ2_GUARDIAN_BLASTER, + MZ2_ARACHNID_RAIL1, + MZ2_ARACHNID_RAIL2, + MZ2_ARACHNID_RAIL_UP1, + MZ2_ARACHNID_RAIL_UP2, + + MZ2_INFANTRY_MACHINEGUN_14, // run-attack + MZ2_INFANTRY_MACHINEGUN_15, // run-attack + MZ2_INFANTRY_MACHINEGUN_16, // run-attack + MZ2_INFANTRY_MACHINEGUN_17, // run-attack + MZ2_INFANTRY_MACHINEGUN_18, // run-attack + MZ2_INFANTRY_MACHINEGUN_19, // run-attack + MZ2_INFANTRY_MACHINEGUN_20, // run-attack + MZ2_INFANTRY_MACHINEGUN_21, // run-attack + + MZ2_GUNCMDR_CHAINGUN_1, // straight + MZ2_GUNCMDR_CHAINGUN_2, // dodging + + MZ2_GUNCMDR_GRENADE_MORTAR_1, + MZ2_GUNCMDR_GRENADE_MORTAR_2, + MZ2_GUNCMDR_GRENADE_MORTAR_3, + MZ2_GUNCMDR_GRENADE_FRONT_1, + MZ2_GUNCMDR_GRENADE_FRONT_2, + MZ2_GUNCMDR_GRENADE_FRONT_3, + MZ2_GUNCMDR_GRENADE_CROUCH_1, + MZ2_GUNCMDR_GRENADE_CROUCH_2, + MZ2_GUNCMDR_GRENADE_CROUCH_3, + + // prone + MZ2_SOLDIER_BLASTER_9, + MZ2_SOLDIER_SHOTGUN_9, + MZ2_SOLDIER_MACHINEGUN_9, + MZ2_SOLDIER_RIPPER_9, + MZ2_SOLDIER_HYPERGUN_9, + + // alternate frontwards grenades + MZ2_GUNNER_GRENADE2_1, + MZ2_GUNNER_GRENADE2_2, + MZ2_GUNNER_GRENADE2_3, + MZ2_GUNNER_GRENADE2_4, + + MZ2_INFANTRY_MACHINEGUN_22, + + // supertonk + MZ2_SUPERTANK_GRENADE_1, + MZ2_SUPERTANK_GRENADE_2, + + // hover & daedalus other side + MZ2_HOVER_BLASTER_2, + MZ2_DAEDALUS_BLASTER_2, + + // medic (commander) sweeps + MZ2_MEDIC_HYPERBLASTER1_1, + MZ2_MEDIC_HYPERBLASTER1_2, + MZ2_MEDIC_HYPERBLASTER1_3, + MZ2_MEDIC_HYPERBLASTER1_4, + MZ2_MEDIC_HYPERBLASTER1_5, + MZ2_MEDIC_HYPERBLASTER1_6, + MZ2_MEDIC_HYPERBLASTER1_7, + MZ2_MEDIC_HYPERBLASTER1_8, + MZ2_MEDIC_HYPERBLASTER1_9, + MZ2_MEDIC_HYPERBLASTER1_10, + MZ2_MEDIC_HYPERBLASTER1_11, + MZ2_MEDIC_HYPERBLASTER1_12, + + MZ2_MEDIC_HYPERBLASTER2_1, + MZ2_MEDIC_HYPERBLASTER2_2, + MZ2_MEDIC_HYPERBLASTER2_3, + MZ2_MEDIC_HYPERBLASTER2_4, + MZ2_MEDIC_HYPERBLASTER2_5, + MZ2_MEDIC_HYPERBLASTER2_6, + MZ2_MEDIC_HYPERBLASTER2_7, + MZ2_MEDIC_HYPERBLASTER2_8, + MZ2_MEDIC_HYPERBLASTER2_9, + MZ2_MEDIC_HYPERBLASTER2_10, + MZ2_MEDIC_HYPERBLASTER2_11, + MZ2_MEDIC_HYPERBLASTER2_12, + + // only used for compile time checks + MZ2_LAST +}; + +// temp entity events +// +// Temp entity events are for things that happen +// at a location seperate from any existing entity. +// Temporary entity messages are explicitly constructed +// and broadcast. +enum temp_event_t : uint8_t +{ + TE_GUNSHOT, + TE_BLOOD, + TE_BLASTER, + TE_RAILTRAIL, + TE_SHOTGUN, + TE_EXPLOSION1, + TE_EXPLOSION2, + TE_ROCKET_EXPLOSION, + TE_GRENADE_EXPLOSION, + TE_SPARKS, + TE_SPLASH, + TE_BUBBLETRAIL, + TE_SCREEN_SPARKS, + TE_SHIELD_SPARKS, + TE_BULLET_SPARKS, + TE_LASER_SPARKS, + TE_PARASITE_ATTACK, + TE_ROCKET_EXPLOSION_WATER, + TE_GRENADE_EXPLOSION_WATER, + TE_MEDIC_CABLE_ATTACK, + TE_BFG_EXPLOSION, + TE_BFG_BIGEXPLOSION, + TE_BOSSTPORT, // used as '22' in a map, so DON'T RENUMBER!!! + TE_BFG_LASER, + TE_GRAPPLE_CABLE, + TE_WELDING_SPARKS, + TE_GREENBLOOD, + TE_BLUEHYPERBLASTER_DUMMY, // [Paril-KEX] leaving for compatibility, do not use; use TE_BLUEHYPERBLASTER + TE_PLASMA_EXPLOSION, + TE_TUNNEL_SPARKS, + // ROGUE + TE_BLASTER2, + TE_RAILTRAIL2, + TE_FLAME, + TE_LIGHTNING, + TE_DEBUGTRAIL, + TE_PLAIN_EXPLOSION, + TE_FLASHLIGHT, + TE_FORCEWALL, + TE_HEATBEAM, + TE_MONSTER_HEATBEAM, + TE_STEAM, + TE_BUBBLETRAIL2, + TE_MOREBLOOD, + TE_HEATBEAM_SPARKS, + TE_HEATBEAM_STEAM, + TE_CHAINFIST_SMOKE, + TE_ELECTRIC_SPARKS, + TE_TRACKER_EXPLOSION, + TE_TELEPORT_EFFECT, + TE_DBALL_GOAL, + TE_WIDOWBEAMOUT, + TE_NUKEBLAST, + TE_WIDOWSPLASH, + TE_EXPLOSION1_BIG, + TE_EXPLOSION1_NP, + TE_FLECHETTE, + // ROGUE + + // [Paril-KEX] + TE_BLUEHYPERBLASTER, + TE_BFG_ZAP, + TE_BERSERK_SLAM, + TE_GRAPPLE_CABLE_2, + TE_POWER_SPLASH, + TE_LIGHTNING_BEAM, + TE_EXPLOSION1_NL, + TE_EXPLOSION2_NL, +}; + +enum splash_color_t : uint8_t +{ + SPLASH_UNKNOWN = 0, + SPLASH_SPARKS = 1, + SPLASH_BLUE_WATER = 2, + SPLASH_BROWN_WATER = 3, + SPLASH_SLIME = 4, + SPLASH_LAVA = 5, + SPLASH_BLOOD = 6, + + // [Paril-KEX] N64 electric sparks that go zap + SPLASH_ELECTRIC = 7 +}; + +// sound channels +// channel 0 never willingly overrides +// other channels (1-7) always override a playing sound on that channel +enum soundchan_t : uint8_t +{ + CHAN_AUTO = 0, + CHAN_WEAPON = 1, + CHAN_VOICE = 2, + CHAN_ITEM = 3, + CHAN_BODY = 4, + CHAN_AUX = 5, + CHAN_FOOTSTEP = 6, + CHAN_AUX3 = 7, + + // modifier flags + CHAN_NO_PHS_ADD = bit_v<3>, // send to all clients, not just ones in PHS (ATTN 0 will also do this) + CHAN_RELIABLE = bit_v<4>, // send by reliable message, not datagram + CHAN_FORCE_POS = bit_v<5>, // always use position sent in packet +}; + +MAKE_ENUM_BITFLAGS(soundchan_t); + +// sound attenuation values +constexpr float ATTN_LOOP_NONE = -1; // full volume the entire level, for loop only +constexpr float ATTN_NONE = 0; // full volume the entire level, for sounds only +constexpr float ATTN_NORM = 1; +constexpr float ATTN_IDLE = 2; +constexpr float ATTN_STATIC = 3; // diminish very rapidly with distance + +// total stat count +constexpr size_t MAX_STATS = 64; + +/* +ROGUE - VERSIONS +1234 08/13/1998 Activision +1235 08/14/1998 Id Software +1236 08/15/1998 Steve Tietze +1237 08/15/1998 Phil Dobranski +1238 08/15/1998 John Sheley +1239 08/17/1998 Barrett Alexander +1230 08/17/1998 Brandon Fish +1245 08/17/1998 Don MacAskill +1246 08/17/1998 David "Zoid" Kirsch +1247 08/17/1998 Manu Smith +1248 08/17/1998 Geoff Scully +1249 08/17/1998 Andy Van Fossen +1240 08/20/1998 Activision Build 2 +1256 08/20/1998 Ranger Clan +1257 08/20/1998 Ensemble Studios +1258 08/21/1998 Robert Duffy +1259 08/21/1998 Stephen Seachord +1250 08/21/1998 Stephen Heaslip +1267 08/21/1998 Samir Sandesara +1268 08/21/1998 Oliver Wyman +1269 08/21/1998 Steven Marchegiano +1260 08/21/1998 Build #2 for Nihilistic +1278 08/21/1998 Build #2 for Ensemble +1279 08/26/1998 Build for Ron Solo - DEFUNCT +1270 08/26/1998 Build #3 for Activision +1289 08/26/1998 Build for Don MacAskill +1280 08/26/1998 Build for Robert Duffy +1290 08/26/1998 Build #2 for Rangers +1345 08/28/1998 Build #4 for Activision +2345 08/26/1998 Build for Zoid + +9999 08/20/1998 Internal Use +*/ + +// ROGUE +/* +========================================================== + + ELEMENTS COMMUNICATED ACROSS THE NET + +========================================================== +*/ + +//============================================= +// INFO STRINGS +// +// NB: the Q2 protocol does not dictate the type +// of strings being used, so it's kind of a crapshoot. +// Kex's protocol assumes info strings are always UTF8. +//============================================= + +// +// key / value info strings +// +constexpr size_t MAX_INFO_KEY = 64; +constexpr size_t MAX_INFO_VALUE = 256; +constexpr size_t MAX_INFO_STRING = 2048; + +// CONFIG STRINGS + +// bound by number of things we can fit in two stats +constexpr size_t MAX_WHEEL_ITEMS = 32; + +// CS_WHEEL_xxx are special configstrings that +// map individual weapon and ammo ids to each other, separated by a pipe | +// the format for CS_WHEEL_WEAPONS is: +// ||||||| +// if the weapon does not take ammo, the index will be -1 +// the format for CS_WHEEL_AMMO is: +// | +// the indices here are not related to the IT_ or AMMO_ +// indices, and are just as they appear in the configstrings. +// the format for CS_WHEEL_POWERUP is: +// ||||| + +enum game_style_t : uint8_t +{ + GAME_STYLE_PVE, + GAME_STYLE_FFA, + GAME_STYLE_TDM +}; + +// +// config strings are a general means of communication from +// the server to all connected clients. +// Each config string can be at most CS_MAX_STRING_LENGTH characters. +// +enum +{ + CS_NAME, + CS_CDTRACK, + CS_SKY, + CS_SKYAXIS, // %f %f %f format + CS_SKYROTATE, + CS_STATUSBAR, // display program string + + CS_AIRACCEL = 59, // air acceleration control + CS_MAXCLIENTS, + CS_MAPCHECKSUM, // for catching cheater maps + + CS_MODELS, + CS_SOUNDS = (CS_MODELS + MAX_MODELS), + CS_IMAGES = (CS_SOUNDS + MAX_SOUNDS), + CS_LIGHTS = (CS_IMAGES + MAX_IMAGES), + CS_SHADOWLIGHTS = (CS_LIGHTS + MAX_LIGHTSTYLES), // [Sam-KEX] + CS_ITEMS = (CS_SHADOWLIGHTS + MAX_SHADOW_LIGHTS), + CS_PLAYERSKINS = (CS_ITEMS + MAX_ITEMS), + CS_GENERAL = (CS_PLAYERSKINS + MAX_CLIENTS), + CS_WHEEL_WEAPONS = (CS_GENERAL + MAX_GENERAL), // [Paril-KEX] see MAX_WHEEL_ITEMS + CS_WHEEL_AMMO = (CS_WHEEL_WEAPONS + MAX_WHEEL_ITEMS), // [Paril-KEX] see MAX_WHEEL_ITEMS + CS_WHEEL_POWERUPS = (CS_WHEEL_AMMO + MAX_WHEEL_ITEMS), // [Paril-KEX] see MAX_WHEEL_ITEMS + CS_CD_LOOP_COUNT = (CS_WHEEL_POWERUPS + MAX_WHEEL_ITEMS), // [Paril-KEX] override default loop count + CS_GAME_STYLE, // [Paril-KEX] see game_style_t + MAX_CONFIGSTRINGS +}; + +static_assert(MAX_CONFIGSTRINGS <= 0x7FFF, "configstrings too big"); + +// [Sam-KEX] New define for max config string length +constexpr size_t CS_MAX_STRING_LENGTH = 96; +constexpr size_t CS_MAX_STRING_LENGTH_OLD = 64; + +// certain configstrings are allowed to be larger +// than CS_MAX_STRING_LENGTH; this gets the absolute size +// for the given configstring at the specified id +// since vanilla didn't do a very good job of size checking +constexpr size_t CS_SIZE(int32_t in) +{ + if (in >= CS_STATUSBAR && in < CS_AIRACCEL) + return CS_MAX_STRING_LENGTH * (CS_AIRACCEL - in); + else if (in >= CS_GENERAL && in < CS_WHEEL_WEAPONS) + return CS_MAX_STRING_LENGTH * (MAX_CONFIGSTRINGS - in); + + return CS_MAX_STRING_LENGTH; +} + +constexpr size_t MAX_MODELS_OLD = 256, MAX_SOUNDS_OLD = 256, MAX_IMAGES_OLD = 256; + +enum +{ + CS_NAME_OLD, + CS_CDTRACK_OLD, + CS_SKY_OLD, + CS_SKYAXIS_OLD, // %f %f %f format + CS_SKYROTATE_OLD, + CS_STATUSBAR_OLD, // display program string + + CS_AIRACCEL_OLD = 29, // air acceleration control + CS_MAXCLIENTS_OLD, + CS_MAPCHECKSUM_OLD, // for catching cheater maps + + CS_MODELS_OLD, + CS_SOUNDS_OLD = (CS_MODELS_OLD + MAX_MODELS_OLD), + CS_IMAGES_OLD = (CS_SOUNDS_OLD + MAX_SOUNDS_OLD), + CS_LIGHTS_OLD = (CS_IMAGES_OLD + MAX_IMAGES_OLD), + CS_ITEMS_OLD = (CS_LIGHTS_OLD + MAX_LIGHTSTYLES), + CS_PLAYERSKINS_OLD = (CS_ITEMS_OLD + MAX_ITEMS), + CS_GENERAL_OLD = (CS_PLAYERSKINS_OLD + MAX_CLIENTS), + MAX_CONFIGSTRINGS_OLD = (CS_GENERAL_OLD + MAX_GENERAL) +}; + +// remaps old configstring IDs to new ones +// for old DLL & demo support +struct configstring_remap_t +{ + // start position in the configstring list + // to write into + size_t start; + // max length to write into; [start+length-1] should always + // be set to '\0' + size_t length; +}; + +constexpr configstring_remap_t CS_REMAP(int32_t id) +{ + // direct mapping + if (id < CS_STATUSBAR_OLD) + return { id * CS_MAX_STRING_LENGTH, CS_MAX_STRING_LENGTH }; + // statusbar needs a bit of special handling, since we have a different + // max configstring length and these are just segments of a longer string + else if (id < CS_AIRACCEL_OLD) + return { (CS_STATUSBAR * CS_MAX_STRING_LENGTH) + ((id - CS_STATUSBAR_OLD) * CS_MAX_STRING_LENGTH_OLD), (CS_AIRACCEL - CS_STATUSBAR) * CS_MAX_STRING_LENGTH }; + // offset + else if (id < CS_MODELS_OLD) + return { (id + (CS_AIRACCEL - CS_AIRACCEL_OLD)) * CS_MAX_STRING_LENGTH, CS_MAX_STRING_LENGTH }; + else if (id < CS_SOUNDS_OLD) + return { (id + (CS_MODELS - CS_MODELS_OLD)) * CS_MAX_STRING_LENGTH, CS_MAX_STRING_LENGTH }; + else if (id < CS_IMAGES_OLD) + return { (id + (CS_SOUNDS - CS_SOUNDS_OLD)) * CS_MAX_STRING_LENGTH, CS_MAX_STRING_LENGTH }; + else if (id < CS_LIGHTS_OLD) + return { (id + (CS_IMAGES - CS_IMAGES_OLD)) * CS_MAX_STRING_LENGTH, CS_MAX_STRING_LENGTH }; + else if (id < CS_ITEMS_OLD) + return { (id + (CS_LIGHTS - CS_LIGHTS_OLD)) * CS_MAX_STRING_LENGTH, CS_MAX_STRING_LENGTH }; + else if (id < CS_PLAYERSKINS_OLD) + return { (id + (CS_ITEMS - CS_ITEMS_OLD)) * CS_MAX_STRING_LENGTH, CS_MAX_STRING_LENGTH }; + else if (id < CS_GENERAL_OLD) + return { (id + (CS_PLAYERSKINS - CS_PLAYERSKINS_OLD)) * CS_MAX_STRING_LENGTH, CS_MAX_STRING_LENGTH }; + + // general also needs some special handling because it's both + // offset *and* allowed to overflow + return { (id + (CS_GENERAL - CS_GENERAL_OLD)) * CS_MAX_STRING_LENGTH_OLD, (MAX_CONFIGSTRINGS - CS_GENERAL) * CS_MAX_STRING_LENGTH }; +} + +static_assert(CS_REMAP(CS_MODELS_OLD).start == (CS_MODELS * 96), "check CS_REMAP"); +static_assert(CS_REMAP(CS_SOUNDS_OLD).start == (CS_SOUNDS * 96), "check CS_REMAP"); +static_assert(CS_REMAP(CS_IMAGES_OLD).start == (CS_IMAGES * 96), "check CS_REMAP"); +static_assert(CS_REMAP(CS_LIGHTS_OLD).start == (CS_LIGHTS * 96), "check CS_REMAP"); +static_assert(CS_REMAP(CS_PLAYERSKINS_OLD).start == (CS_PLAYERSKINS * 96), "check CS_REMAP"); +static_assert(CS_REMAP(CS_ITEMS_OLD).start == (CS_ITEMS * 96), "check CS_REMAP"); +static_assert(CS_REMAP(CS_GENERAL_OLD).start == (CS_GENERAL * 64), "check CS_REMAP"); +static_assert(CS_REMAP(CS_AIRACCEL_OLD).start == (CS_AIRACCEL * 96), "check CS_REMAP"); + +//============================================== + +// entity_state_t->event values +// ertity events are for effects that take place reletive +// to an existing entities origin. Very network efficient. +// All muzzle flashes really should be converted to events... +enum entity_event_t : uint8_t +{ + EV_NONE, + EV_ITEM_RESPAWN, + EV_FOOTSTEP, + EV_FALLSHORT, + EV_FALL, + EV_FALLFAR, + EV_PLAYER_TELEPORT, + EV_OTHER_TELEPORT, + + // [Paril-KEX] + EV_OTHER_FOOTSTEP, + EV_LADDER_STEP, +}; + +// [Paril-KEX] player s.skinnum's encode additional data +union player_skinnum_t +{ + int32_t skinnum; + struct { + uint8_t client_num; // client index + uint8_t vwep_index; // vwep index + int8_t viewheight; // viewheight + uint8_t team_index : 4; // team #; note that teams are 1-indexed here, with 0 meaning no team + // (spectators in CTF would be 0, for instance) + uint8_t poi_icon : 4; // poi icon; 0 default friendly, 1 dead, others unused + }; +}; + +// entity_state_t is the information conveyed from the server +// in an update message about entities that the client will +// need to render in some way +struct entity_state_t +{ + uint32_t number; // edict index + + gvec3_t origin; + gvec3_t angles; + gvec3_t old_origin; // for lerping + int32_t modelindex; + int32_t modelindex2, modelindex3, modelindex4; // weapons, CTF flags, etc + int32_t frame; + int32_t skinnum; + effects_t effects; // PGM - we're filling it, so it needs to be unsigned + renderfx_t renderfx; + uint32_t solid; // for client side prediction + int32_t sound; // for looping sounds, to guarantee shutoff + entity_event_t event; // impulse events -- muzzle flashes, footsteps, etc + // events only go out for a single frame, they + // are automatically cleared each frame + float alpha; // [Paril-KEX] alpha scalar; 0 is a "default" value, which will respect other + // settings (default 1.0 for most things, EF_TRANSLUCENT will default this + // to 0.3, etc) + float scale; // [Paril-KEX] model scale scalar; 0 is a "default" value, like with alpha. + uint8_t instance_bits; // [Paril-KEX] players that *can't* see this entity will have a bit of 1. handled by + // the server, do not set directly. + // [Paril-KEX] allow specifying volume/attn for looping noises; note that + // zero will be defaults (1.0 and 3.0 respectively); -1 attenuation is used + // for "none" (similar to target_speaker) for no phs/pvs looping noises + float loop_volume; + float loop_attenuation; + // [Paril-KEX] for proper client-side owner collision skipping + int32_t owner; + // [Paril-KEX] for custom interpolation stuff + int32_t old_frame; +}; + +//============================================== + +// player_state_t is the information needed in addition to pmove_state_t +// to rendered a view. There will only be 10 player_state_t sent each second, +// but the number of pmove_state_t changes will be relative to client +// frame rates +struct player_state_t +{ + pmove_state_t pmove; // for prediction + + // these fields do not need to be communicated bit-precise + + gvec3_t viewangles; // for fixed views + gvec3_t viewoffset; // add to pmovestate->origin + gvec3_t kick_angles; // add to view direction to get render angles + // set by weapon kicks, pain effects, etc + + gvec3_t gunangles; + gvec3_t gunoffset; + int32_t gunindex; + int32_t gunskin; // [Paril-KEX] gun skin # + int32_t gunframe; + int32_t gunrate; // [Paril-KEX] tickrate of gun animations; 0 and 10 are equivalent + + std::array screen_blend; // rgba full screen effect + std::array damage_blend; // [Paril-KEX] rgba damage blend effect + + float fov; // horizontal field of view + + refdef_flags_t rdflags; // refdef flags + + std::array stats; // fast status bar updates + + uint8_t team_id; // team identifier +}; + +// protocol bytes that can be directly added to messages +enum server_command_t : uint8_t +{ + svc_bad, + + svc_muzzleflash, + svc_muzzleflash2, + svc_temp_entity, + svc_layout, + svc_inventory, + + svc_nop, + svc_disconnect, + svc_reconnect, + svc_sound, // + svc_print, // [byte] id [string] null terminated string + svc_stufftext, // [string] stuffed into client's console buffer, should be \n terminated + svc_serverdata, // [long] protocol ... + svc_configstring, // [short] [string] + svc_spawnbaseline, + svc_centerprint, // [string] to put in center of the screen + svc_download, // [short] size [size bytes] + svc_playerinfo, // variable + svc_packetentities, // [...] + svc_deltapacketentities, // [...] + svc_frame, + + svc_splitclient, + + svc_configblast, // [Kex] A compressed version of svc_configstring + svc_spawnbaselineblast, // [Kex] A compressed version of svc_spawnbaseline + svc_level_restart, // [Paril-KEX] level was soft-rebooted + svc_damage, // [Paril-KEX] damage indicators + svc_locprint, // [Kex] localized + libfmt version of print + svc_fog, // [Paril-KEX] change current fog values + svc_waitingforplayers, // [Kex-Edward] Inform clients that the server is waiting for remaining players + svc_bot_chat, // [Kex] bot specific chat + svc_poi, // [Paril-KEX] point of interest + svc_help_path, // [Paril-KEX] help path + svc_muzzleflash3, // [Paril-KEX] muzzleflashes, but ushort id + svc_achievement, // [Paril-KEX] + + svc_last // only for checks +}; + +enum svc_poi_flags +{ + POI_FLAG_NONE = 0, + POI_FLAG_HIDE_ON_AIM = 1, // hide the POI if we get close to it with our aim +}; + +// data for svc_fog +struct svc_fog_data_t +{ + enum bits_t : uint16_t + { + // global fog + BIT_DENSITY = bit_v<0>, + BIT_R = bit_v<1>, + BIT_G = bit_v<2>, + BIT_B = bit_v<3>, + BIT_TIME = bit_v<4>, // if set, the transition takes place over N milliseconds + + // height fog + BIT_HEIGHTFOG_FALLOFF = bit_v<5>, + BIT_HEIGHTFOG_DENSITY = bit_v<6>, + BIT_MORE_BITS = bit_v<7>, // read additional bit + BIT_HEIGHTFOG_START_R = bit_v<8>, + BIT_HEIGHTFOG_START_G = bit_v<9>, + BIT_HEIGHTFOG_START_B = bit_v<10>, + BIT_HEIGHTFOG_START_DIST= bit_v<11>, + BIT_HEIGHTFOG_END_R = bit_v<12>, + BIT_HEIGHTFOG_END_G = bit_v<13>, + BIT_HEIGHTFOG_END_B = bit_v<14>, + BIT_HEIGHTFOG_END_DIST = bit_v<15> + }; + + bits_t bits; + float density; // bits & BIT_DENSITY + uint8_t skyfactor; // bits & BIT_DENSITY + uint8_t red; // bits & BIT_R + uint8_t green; // bits & BIT_G + uint8_t blue; // bits & BIT_B + uint16_t time; // bits & BIT_TIME + + float hf_falloff; // bits & BIT_HEIGHTFOG_FALLOFF + float hf_density; // bits & BIT_HEIGHTFOG_DENSITY + uint8_t hf_start_r; // bits & (BIT_MORE_BITS | BIT_HEIGHTFOG_START_R) + uint8_t hf_start_g; // bits & (BIT_MORE_BITS | BIT_HEIGHTFOG_START_G) + uint8_t hf_start_b; // bits & (BIT_MORE_BITS | BIT_HEIGHTFOG_START_B) + int32_t hf_start_dist; // bits & (BIT_MORE_BITS | BIT_HEIGHTFOG_START_DIST) + uint8_t hf_end_r; // bits & (BIT_MORE_BITS | BIT_HEIGHTFOG_END_R) + uint8_t hf_end_g; // bits & (BIT_MORE_BITS | BIT_HEIGHTFOG_END_G) + uint8_t hf_end_b; // bits & (BIT_MORE_BITS | BIT_HEIGHTFOG_END_B) + int32_t hf_end_dist; // bits & (BIT_MORE_BITS | BIT_HEIGHTFOG_END_DIST) +}; + +MAKE_ENUM_BITFLAGS(svc_fog_data_t::bits_t); + +// bit masks +static constexpr svc_fog_data_t::bits_t BITS_GLOBAL_FOG = (svc_fog_data_t::BIT_DENSITY | svc_fog_data_t::BIT_R | svc_fog_data_t::BIT_G | svc_fog_data_t::BIT_B); +static constexpr svc_fog_data_t::bits_t BITS_HEIGHTFOG = (svc_fog_data_t::BIT_HEIGHTFOG_FALLOFF | svc_fog_data_t::BIT_HEIGHTFOG_DENSITY | svc_fog_data_t::BIT_HEIGHTFOG_START_R | svc_fog_data_t::BIT_HEIGHTFOG_START_G | + svc_fog_data_t::BIT_HEIGHTFOG_START_B | svc_fog_data_t::BIT_HEIGHTFOG_START_DIST | svc_fog_data_t::BIT_HEIGHTFOG_END_R | svc_fog_data_t::BIT_HEIGHTFOG_END_G | + svc_fog_data_t::BIT_HEIGHTFOG_END_B | svc_fog_data_t::BIT_HEIGHTFOG_END_DIST); + +// edict->svflags +enum svflags_t : uint32_t +{ + SVF_NONE = 0, // no serverflags + SVF_NOCLIENT = bit_v<0>, // don't send entity to clients, even if it has effects + SVF_DEADMONSTER = bit_v<1>, // treat as CONTENTS_DEADMONSTER for collision + SVF_MONSTER = bit_v<2>, // treat as CONTENTS_MONSTER for collision + SVF_PLAYER = bit_v<3>, // [Paril-KEX] treat as CONTENTS_PLAYER for collision + SVF_BOT = bit_v<4>, // entity is controlled by a bot AI. + SVF_NOBOTS = bit_v<5>, // don't allow bots to use/interact with entity + SVF_RESPAWNING = bit_v<6>, // entity will respawn on it's next think. + SVF_PROJECTILE = bit_v<7>, // treat as CONTENTS_PROJECTILE for collision + SVF_INSTANCED = bit_v<8>, // entity has different visibility per player + SVF_DOOR = bit_v<9>, // entity is a door of some kind + SVF_NOCULL = bit_v<10>, // always send, even if we normally wouldn't + SVF_HULL = bit_v<11> // always use hull when appropriate (triggers, etc; for gi.clip) +}; +MAKE_ENUM_BITFLAGS(svflags_t); + +// edict->solid values +enum solid_t : uint8_t +{ + SOLID_NOT, // no interaction with other objects + SOLID_TRIGGER, // only touch when inside, after moving + SOLID_BBOX, // touch on edge + SOLID_BSP // bsp clip, touch on edge +}; + +// bitflags for STAT_LAYOUTS +enum layout_flags_t : int16_t +{ + LAYOUTS_LAYOUT = bit_v<0>, // svc_layout is active; escape remapped to putaway + LAYOUTS_INVENTORY = bit_v<1>, // inventory is active; escape remapped to putaway + LAYOUTS_HIDE_HUD = bit_v<2>, // hide entire hud, for cameras, etc + LAYOUTS_INTERMISSION = bit_v<3>, // intermission is being drawn; collapse splitscreen into 1 view + LAYOUTS_HELP = bit_v<4>, // help is active; escape remapped to putaway + LAYOUTS_HIDE_CROSSHAIR = bit_v<5> // hide crosshair only +}; +MAKE_ENUM_BITFLAGS(layout_flags_t); + +enum GoalReturnCode { + Error = 0, + Started, + InProgress, + Finished +}; + +enum gesture_type { + GESTURE_NONE = -1, + GESTURE_FLIP_OFF, + GESTURE_SALUTE, + GESTURE_TAUNT, + GESTURE_WAVE, + GESTURE_POINT, + GESTURE_POINT_NO_PING, + GESTURE_MAX +}; + +enum class PathReturnCode { + ReachedGoal = 0, // we're at our destination + ReachedPathEnd, // we're as close to the goal as we can get with a path + TraversalPending, // the upcoming path segment is a traversal + RawPathFound, // user wanted ( and got ) just a raw path ( no processing ) + InProgress, // pathing in progress + StartPathErrors, // any code after this one indicates an error of some kind. + InvalidStart, // start position is invalid. + InvalidGoal, // goal position is invalid. + NoNavAvailable, // no nav file available for this map. + NoStartNode, // can't find a nav node near the start position + NoGoalNode, // can't find a nav node near the goal position + NoPathFound, // can't find a path from the start to the goal + MissingWalkOrSwimFlag // MUST have at least Walk or Water path flags set! +}; + +enum class PathLinkType { + Walk, // can walk between the path points + WalkOffLedge, // will walk off a ledge going between path points + LongJump, // will need to perform a long jump between path points + BarrierJump, // will need to jump over a low barrier between path points + Elevator // will need to use an elevator between path points +}; + +enum PathFlags : uint32_t { + All = static_cast( -1 ), + Water = bit_v<0>, // swim to your goal ( useful for fish/gekk/etc. ) + Walk = bit_v<1>, // walk to your goal + WalkOffLedge = bit_v<2>, // allow walking over ledges + LongJump = bit_v<3>, // allow jumping over gaps + BarrierJump = bit_v<4>, // allow jumping over low barriers + Elevator = bit_v<5> // allow using elevators +}; +MAKE_ENUM_BITFLAGS(PathFlags); + +struct PathRequest { + gvec3_t start = { 0.0f, 0.0f, 0.0f }; + gvec3_t goal = { 0.0f, 0.0f, 0.0f }; + PathFlags pathFlags = PathFlags::Walk; + float moveDist = 0.0f; + + struct DebugSettings { + float drawTime = 0.0f; // if > 0, how long ( in seconds ) to draw path in world + } debugging; + + struct NodeSettings { + bool ignoreNodeFlags = false; // true = ignore node flags when considering nodes + float minHeight = 0.0f; // 0 <= use default values + float maxHeight = 0.0f; // 0 <= use default values + float radius = 0.0f; // 0 <= use default values + } nodeSearch; + + struct TraversalSettings { + float dropHeight = 0.0f; // 0 = don't drop down + float jumpHeight = 0.0f; // 0 = don't jump up + } traversals; + + struct PathArray { + mutable gvec3_t * array = nullptr; // array to store raw path points + int64_t count = 0; // number of elements in array + } pathPoints; +}; + +struct PathInfo { + int32_t numPathPoints = 0; + float pathDistSqr = 0.0f; + gvec3_t firstMovePoint = { 0.0f, 0.0f, 0.0f }; + gvec3_t secondMovePoint = { 0.0f, 0.0f, 0.0f }; + PathLinkType pathLinkType = PathLinkType::Walk; + PathReturnCode returnCode = PathReturnCode::StartPathErrors; +}; + +//=============================================================== + +constexpr int32_t MODELINDEX_WORLD = 1; // special index for world +constexpr int32_t MODELINDEX_PLAYER = MAX_MODELS_OLD - 1; // special index for player models + +// short stubs only used by the engine; the game DLL's version +// must be compatible with this. +#ifndef GAME_INCLUDE +struct gclient_t +#else +struct gclient_shared_t +#endif +{ + player_state_t ps; // communicated by server to clients + int32_t ping; + // the game dll can add anything it wants after + // this point in the structure +}; + +static constexpr int32_t Team_None = 0; +static constexpr int32_t Item_UnknownRespawnTime = INT_MAX; +static constexpr int32_t Item_Invalid = -1; +static constexpr int32_t Item_Null = 0; + +enum sv_ent_flags_t : uint64_t { + SVFL_NONE = 0, // no flags + SVFL_ONGROUND = bit_v< 0 >, + SVFL_HAS_DMG_BOOST = bit_v< 1 >, + SVFL_HAS_PROTECTION = bit_v< 2 >, + SVFL_HAS_INVISIBILITY = bit_v< 3 >, + SVFL_IS_JUMPING = bit_v< 4 >, + SVFL_IS_CROUCHING = bit_v< 5 >, + SVFL_IS_ITEM = bit_v< 6 >, + SVFL_IS_OBJECTIVE = bit_v< 7 >, + SVFL_HAS_TELEPORTED = bit_v< 8 >, + SVFL_TAKES_DAMAGE = bit_v< 9 >, + SVFL_IS_HIDDEN = bit_v< 10 >, + SVFL_IS_NOCLIP = bit_v< 11 >, + SVFL_IN_WATER = bit_v< 12 >, + SVFL_NO_TARGET = bit_v< 13 >, + SVFL_GOD_MODE = bit_v< 14 >, + SVFL_IS_FLIPPING_OFF = bit_v< 15 >, + SVFL_IS_SALUTING = bit_v< 16 >, + SVFL_IS_TAUNTING = bit_v< 17 >, + SVFL_IS_WAVING = bit_v< 18 >, + SVFL_IS_POINTING = bit_v< 19 >, + SVFL_ON_LADDER = bit_v< 20 >, + SVFL_MOVESTATE_TOP = bit_v< 21 >, + SVFL_MOVESTATE_BOTTOM = bit_v< 22 >, + SVFL_MOVESTATE_MOVING = bit_v< 23 >, + SVFL_IS_LOCKED_DOOR = bit_v< 24 >, + SVFL_CAN_GESTURE = bit_v< 25 >, + SVFL_WAS_TELEFRAGGED = bit_v< 26 >, + SVFL_TRAP_DANGER = bit_v< 27 >, + SVFL_ACTIVE = bit_v< 28 >, + SVFL_IS_SPECTATOR = bit_v< 29 >, + SVFL_IN_TEAM = bit_v< 30 > +}; +MAKE_ENUM_BITFLAGS( sv_ent_flags_t ); + +#include + +static constexpr int Max_Armor_Types = 3; + +struct armorInfo_t { + int32_t item_id = Item_Null; + int32_t max_count = 0; +}; + +// Used by AI/Tools on the engine side... +struct sv_entity_t { + bool init; + sv_ent_flags_t ent_flags; + button_t buttons; + uint32_t spawnflags; + int32_t item_id; + int32_t armor_type; + int32_t armor_value; + int32_t health; + int32_t max_health; + int32_t starting_health; + int32_t weapon; + int32_t team; + int32_t lobby_usernum; + int32_t respawntime; + int32_t viewheight; + int32_t last_attackertime; + water_level_t waterlevel; + gvec3_t viewangles; + gvec3_t viewforward; + gvec3_t velocity; + gvec3_t start_origin; + gvec3_t end_origin; + edict_t * enemy; + edict_t * ground_entity; + const char * classname; + const char * targetname; + char netname[ MAX_NETNAME ]; + int32_t inventory[ MAX_ITEMS ] = { 0 }; + armorInfo_t armor_info[ Max_Armor_Types ]; + std::bitset pickedup_list; +}; + +#ifndef GAME_INCLUDE +struct edict_t +#else +struct edict_shared_t +#endif +{ + entity_state_t s; + gclient_t *client; // nullptr if not a player + // the server expects the first part + // of gclient_t to be a player_state_t + // but the rest of it is opaque + + sv_entity_t sv; // read only info about this entity for the server + + bool inuse; + + // world linkage data + bool linked; + int32_t linkcount; + int32_t areanum, areanum2; + + svflags_t svflags; + vec3_t mins, maxs; + vec3_t absmin, absmax, size; + solid_t solid; + contents_t clipmask; + edict_t *owner; +}; + +#define CHECK_INTEGRITY(from_type, to_type, member) \ + static_assert(offsetof(from_type, member) == offsetof(to_type, member) && \ + sizeof(from_type::member) == sizeof(to_type::member), \ + "structure malformed; not compatible with server: check member \"" #member "\"") + +#define CHECK_GCLIENT_INTEGRITY \ + CHECK_INTEGRITY(gclient_t, gclient_shared_t, ps); \ + CHECK_INTEGRITY(gclient_t, gclient_shared_t, ping) + +#define CHECK_EDICT_INTEGRITY \ + CHECK_INTEGRITY(edict_t, edict_shared_t, s); \ + CHECK_INTEGRITY(edict_t, edict_shared_t, client); \ + CHECK_INTEGRITY(edict_t, edict_shared_t, sv); \ + CHECK_INTEGRITY(edict_t, edict_shared_t, inuse); \ + CHECK_INTEGRITY(edict_t, edict_shared_t, linked); \ + CHECK_INTEGRITY(edict_t, edict_shared_t, linkcount); \ + CHECK_INTEGRITY(edict_t, edict_shared_t, areanum); \ + CHECK_INTEGRITY(edict_t, edict_shared_t, areanum2); \ + CHECK_INTEGRITY(edict_t, edict_shared_t, svflags); \ + CHECK_INTEGRITY(edict_t, edict_shared_t, mins); \ + CHECK_INTEGRITY(edict_t, edict_shared_t, maxs); \ + CHECK_INTEGRITY(edict_t, edict_shared_t, absmin); \ + CHECK_INTEGRITY(edict_t, edict_shared_t, absmax); \ + CHECK_INTEGRITY(edict_t, edict_shared_t, size); \ + CHECK_INTEGRITY(edict_t, edict_shared_t, solid); \ + CHECK_INTEGRITY(edict_t, edict_shared_t, clipmask); \ + CHECK_INTEGRITY(edict_t, edict_shared_t, owner) + +//=============================================================== + +// file system stuff +using fs_handle_t = uint64_t; + +enum fs_search_flags_t +{ + FS_SEARCH_NONE = 0, + + // flags for individual file filtering; note that if none + // of these are set, they will all apply. + FS_SEARCH_FOR_DIRECTORIES = bit_v<0>, // only get directories + FS_SEARCH_FOR_FILES = bit_v<1> // only get files +}; + +MAKE_ENUM_BITFLAGS(fs_search_flags_t); + +enum class BoxEdictsResult_t +{ + Keep, // keep the given entity in the result and keep looping + Skip, // skip the given entity + + End = 64, // stop searching any further + + Flags = End +}; + +MAKE_ENUM_BITFLAGS(BoxEdictsResult_t); + +using BoxEdictsFilter_t = BoxEdictsResult_t (*)(edict_t *, void *); + +// +// functions provided by the main engine +// +struct game_import_t +{ + uint32_t tick_rate; + float frame_time_s; + uint32_t frame_time_ms; + + // broadcast to all clients + void (*Broadcast_Print)(print_type_t printlevel, const char *message); + + // print to appropriate places (console, log file, etc) + void (*Com_Print)(const char *msg); + + // print directly to a single client (or nullptr for server console) + void (*Client_Print)(edict_t *ent, print_type_t printlevel, const char *message); + + // center-print to player (legacy function) + void (*Center_Print)(edict_t *ent, const char *message); + + void (*sound)(edict_t *ent, soundchan_t channel, int soundindex, float volume, float attenuation, float timeofs); + void (*positioned_sound)(gvec3_cref_t origin, edict_t *ent, soundchan_t channel, int soundindex, float volume, float attenuation, float timeofs); + // [Paril-KEX] like sound, but only send to the player indicated by the parameter; + // this is mainly to handle split screen properly + void (*local_sound)(edict_t *target, gvec3_cptr_t origin, edict_t *ent, soundchan_t channel, int soundindex, float volume, float attenuation, float timeofs, uint32_t dupe_key); + + // config strings hold all the index strings, the lightstyles, + // and misc data like the sky definition and cdtrack. + // All of the current configstrings are sent to clients when + // they connect, and changes are sent to all connected clients. + void (*configstring)(int num, const char *string); + const char *(*get_configstring)(int num); + + void (*Com_Error)(const char *message); + + // the *index functions create configstrings and some internal server state + int (*modelindex)(const char *name); + int (*soundindex)(const char *name); + // [Paril-KEX] imageindex can precache both pics for the HUD and + // textures used for RF_CUSTOMSKIN; to register an image as a texture, + // the path must be relative to the mod dir and end in an extension + // ie models/my_model/skin.tga + int (*imageindex)(const char *name); + + void (*setmodel)(edict_t *ent, const char *name); + + // collision detection + trace_t (*trace)(gvec3_cref_t start, gvec3_cptr_t mins, gvec3_cptr_t maxs, gvec3_cref_t end, const edict_t *passent, contents_t contentmask); + // [Paril-KEX] clip the box against the specified entity + trace_t (*clip)(edict_t *entity, gvec3_cref_t start, gvec3_cptr_t mins, gvec3_cptr_t maxs, gvec3_cref_t end, contents_t contentmask); + contents_t (*pointcontents)(gvec3_cref_t point); + bool (*inPVS)(gvec3_cref_t p1, gvec3_cref_t p2, bool portals); + bool (*inPHS)(gvec3_cref_t p1, gvec3_cref_t p2, bool portals); + void (*SetAreaPortalState)(int portalnum, bool open); + bool (*AreasConnected)(int area1, int area2); + + // an entity will never be sent to a client or used for collision + // if it is not passed to linkentity. If the size, position, or + // solidity changes, it must be relinked. + void (*linkentity)(edict_t *ent); + void (*unlinkentity)(edict_t *ent); // call before removing an interactive edict + + // return a list of entities that touch the input absmin/absmax. + // if maxcount is 0, it will return a count but not attempt to fill "list". + // if maxcount > 0, once it reaches maxcount, it will keep going but not fill + // any more of list (the return count will cap at maxcount). + // the filter function can remove unnecessary entities from the final list; it is illegal + // to modify world links in this callback. + size_t (*BoxEdicts)(gvec3_cref_t mins, gvec3_cref_t maxs, edict_t **list, size_t maxcount, solidity_area_t areatype, BoxEdictsFilter_t filter, void *filter_data); + + // network messaging + void (*multicast)(gvec3_cref_t origin, multicast_t to, bool reliable); + // [Paril-KEX] `dupe_key` is a key unique to a group of calls to unicast + // that will prevent sending the message on this frame with the same key + // to the same player (for splitscreen players). + void (*unicast)(edict_t *ent, bool reliable, uint32_t dupe_key); + + void (*WriteChar)(int c); + void (*WriteByte)(int c); + void (*WriteShort)(int c); + void (*WriteLong)(int c); + void (*WriteFloat)(float f); + void (*WriteString)(const char *s); + void (*WritePosition)(gvec3_cref_t pos); + void (*WriteDir)(gvec3_cref_t pos); // single byte encoded, very coarse + void (*WriteAngle)(float f); // legacy 8-bit angle + void (*WriteEntity)(const edict_t *e); + + // managed memory allocation + void *(*TagMalloc)(size_t size, int tag); + void (*TagFree)(void *block); + void (*FreeTags)(int tag); + + // console variable interaction + cvar_t *(*cvar)(const char *var_name, const char *value, cvar_flags_t flags); + cvar_t *(*cvar_set)(const char *var_name, const char *value); + cvar_t *(*cvar_forceset)(const char *var_name, const char *value); + + // ClientCommand and ServerCommand parameter access + int (*argc)(); + const char *(*argv)(int n); + const char *(*args)(); // concatenation of all argv >= 1 + + // add commands to the server console as if they were typed in + // for map changing, etc + void (*AddCommandString)(const char *text); + + void (*DebugGraph)(float value, int color); + + // Fetch named extension from engine. + void *(*GetExtension)(const char *name); + + // === [KEX] Additional APIs === + + // bots + void (*Bot_RegisterEdict)(const edict_t * edict); + void (*Bot_UnRegisterEdict)(const edict_t * edict); + GoalReturnCode (*Bot_MoveToPoint)(const edict_t * bot, gvec3_cref_t point, const float moveTolerance); + GoalReturnCode (*Bot_FollowActor)(const edict_t * bot, const edict_t * actor); + + // pathfinding - returns true if a path was found + bool (*GetPathToGoal)(const PathRequest & request, PathInfo & info); + + // localization + void (*Loc_Print)(edict_t* ent, print_type_t level, const char* base, const char** args, size_t num_args); + + // drawing + void (*Draw_Line)(gvec3_cref_t start, gvec3_cref_t end, const rgba_t &color, const float lifeTime, const bool depthTest); + void (*Draw_Point)(gvec3_cref_t point, const float size, const rgba_t &color, const float lifeTime, const bool depthTest); + void (*Draw_Circle)(gvec3_cref_t origin, const float radius, const rgba_t &color, const float lifeTime, const bool depthTest); + void (*Draw_Bounds)(gvec3_cref_t mins, gvec3_cref_t maxs, const rgba_t &color, const float lifeTime, const bool depthTest); + void (*Draw_Sphere)(gvec3_cref_t origin, const float radius, const rgba_t &color, const float lifeTime, const bool depthTest); + void (*Draw_OrientedWorldText)(gvec3_cref_t origin, const char * text, const rgba_t &color, const float size, const float lifeTime, const bool depthTest); + void (*Draw_StaticWorldText )(gvec3_cref_t origin, gvec3_cref_t angles, const char * text, const rgba_t & color, const float size, const float lifeTime, const bool depthTest); + void (*Draw_Cylinder)(gvec3_cref_t origin, const float halfHeight, const float radius, const rgba_t &color, const float lifeTime, const bool depthTest); + void (*Draw_Ray)(gvec3_cref_t origin, gvec3_cref_t direction, const float length, const float size, const rgba_t &color, const float lifeTime, const bool depthTest); + + // scoreboard + void (*ReportMatchDetails_Multicast)(bool is_end); + + // get server frame # + uint32_t (*ServerFrame)(); + + // misc utils + void (*SendToClipBoard)(const char * text); + + // info string stuff + size_t (*Info_ValueForKey) (const char *s, const char *key, char *buffer, size_t buffer_len); + bool (*Info_RemoveKey) (char *s, const char *key); + bool (*Info_SetValueForKey) (char *s, const char *key, const char *value); +}; + +enum class shadow_light_type_t +{ + point, + cone +}; + +struct shadow_light_data_t +{ + shadow_light_type_t lighttype; + float radius; + int resolution; + float intensity = 1; + float fade_start; + float fade_end; + int lightstyle = -1; + float coneangle = 45; + vec3_t conedirection; +}; + +enum server_flags_t +{ + SERVER_FLAGS_NONE = 0, + SERVER_FLAG_SLOW_TIME = bit_v<0>, + SERVER_FLAG_INTERMISSION = bit_v<1>, + SERVER_FLAG_LOADING = bit_v<2> +}; + +MAKE_ENUM_BITFLAGS(server_flags_t); + +// +// functions exported by the game subsystem +// +struct game_export_t +{ + int apiversion; + + // the init function will only be called when a game starts, + // not each time a level is loaded. Persistant data for clients + // and the server can be allocated in init + void (*PreInit)(); // [Paril-KEX] called before InitGame, to potentially change maxclients + void (*Init)(); + void (*Shutdown)(); + + // each new level entered will cause a call to SpawnEntities + void (*SpawnEntities)(const char *mapname, const char *entstring, const char *spawnpoint); + + // Read/Write Game is for storing persistant cross level information + // about the world state and the clients. + // WriteGame is called every time a level is exited. + // ReadGame is called on a loadgame. + + // returns pointer to tagmalloc'd allocated string. + // tagfree after use + char *(*WriteGameJson)(bool autosave, size_t *out_size); + void (*ReadGameJson)(const char *json); + + // ReadLevel is called after the default map information has been + // loaded with SpawnEntities + // returns pointer to tagmalloc'd allocated string. + // tagfree after use + char *(*WriteLevelJson)(bool transition, size_t *out_size); + void (*ReadLevelJson)(const char *json); + + // [Paril-KEX] game can tell the server whether a save is allowed + // currently or not. + bool (*CanSave)(); + + // [Paril-KEX] choose a free gclient_t slot for the given social ID; for + // coop slot re-use. Return nullptr if none is available. You can not + // return a slot that is currently in use by another client; that must + // throw a fatal error. + edict_t *(*ClientChooseSlot) (const char *userinfo, const char *social_id, bool isBot, edict_t **ignore, size_t num_ignore, bool cinematic); + bool (*ClientConnect)(edict_t *ent, char *userinfo, const char *social_id, bool isBot); + void (*ClientBegin)(edict_t *ent); + void (*ClientUserinfoChanged)(edict_t *ent, const char *userinfo); + void (*ClientDisconnect)(edict_t *ent); + void (*ClientCommand)(edict_t *ent); + void (*ClientThink)(edict_t *ent, usercmd_t *cmd); + + void (*RunFrame)(bool main_loop); + // [Paril-KEX] allow the game DLL to clear per-frame stuff + void (*PrepFrame)(); + + // ServerCommand will be called when an "sv " command is issued on the + // server console. + // The game can issue gi.argc() / gi.argv() commands to get the rest + // of the parameters + void (*ServerCommand)(); + + // + // global variables shared between game and server + // + + // The edict array is allocated in the game dll so it + // can vary in size from one game to another. + // + // The size will be fixed when ge->Init() is called + edict_t *edicts; + size_t edict_size; + uint32_t num_edicts; // current number, <= max_edicts + uint32_t max_edicts; + + // [Paril-KEX] special flags to indicate something to the server + server_flags_t server_flags; + + // [KEX]: Pmove as export + void (*Pmove)(pmove_t *pmove); // player movement code called by server & client + + // Fetch named extension from game DLL. + void *(*GetExtension)(const char *name); + + void (*Bot_SetWeapon)(edict_t * botEdict, const int weaponIndex, const bool instantSwitch); + void (*Bot_TriggerEdict)(edict_t * botEdict, edict_t * edict); + void (*Bot_UseItem)(edict_t * botEdict, const int32_t itemID); + int32_t (*Bot_GetItemID)(const char * classname); + + // [KEX]: Checks entity visibility instancing + bool (*Entity_IsVisibleToPlayer)(edict_t* ent, edict_t* player); + + // Fetch info from the shadow light, for culling + const shadow_light_data_t *(*GetShadowLightData)(int32_t entity_number); +}; + +// generic rectangle +struct vrect_t +{ + int32_t x, y, width, height; +}; + +enum class text_align_t +{ + LEFT, + CENTER, + RIGHT +}; + +// transient data from server +struct cg_server_data_t +{ + char layout[1024]; + std::array inventory; +}; + +constexpr int32_t PROTOCOL_VERSION_3XX = 34; +constexpr int32_t PROTOCOL_VERSION_DEMOS = 2022; +constexpr int32_t PROTOCOL_VERSION = 2023; + +// +// functions provided by main engine for client +// +struct cgame_import_t +{ + uint32_t tick_rate; + float frame_time_s; + uint32_t frame_time_ms; + + // print to appropriate places (console, log file, etc) + void (*Com_Print)(const char *msg); + + // config strings hold all the index strings, the lightstyles, + // and misc data like the sky definition and cdtrack. + // All of the current configstrings are sent to clients when + // they connect, and changes are sent to all connected clients. + const char *(*get_configstring)(int num); + + void (*Com_Error)(const char *message); + + // managed memory allocation + void *(*TagMalloc)(size_t size, int tag); + void (*TagFree)(void *block); + void (*FreeTags)(int tag); + + // console variable interaction + cvar_t *(*cvar)(const char *var_name, const char *value, cvar_flags_t flags); + cvar_t *(*cvar_set)(const char *var_name, const char *value); + cvar_t *(*cvar_forceset)(const char *var_name, const char *value); + + // add commands to the server console as if they were typed in + // for map changing, etc + void (*AddCommandString)(const char *text); + + // Fetch named extension from engine. + void *(*GetExtension)(const char *name); + + // Check whether current frame is valid + bool (*CL_FrameValid) (); + + // Get client frame time delta + float (*CL_FrameTime) (); + + // [Paril-KEX] cgame-specific stuff + uint64_t (*CL_ClientTime) (); + uint64_t (*CL_ClientRealTime) (); + int32_t (*CL_ServerFrame) (); + int32_t (*CL_ServerProtocol) (); + const char *(*CL_GetClientName) (int32_t index); + const char *(*CL_GetClientPic) (int32_t index); + const char *(*CL_GetClientDogtag) (int32_t index); + const char *(*CL_GetKeyBinding) (const char *binding); // fetch key bind for key, or empty string + bool (*Draw_RegisterPic) (const char *name); + void (*Draw_GetPicSize) (int *w, int *h, const char *name); // will return 0 0 if not found + void (*SCR_DrawChar)(int x, int y, int scale, int num, bool shadow); + void (*SCR_DrawPic) (int x, int y, int w, int h, const char *name); + void (*SCR_DrawColorPic)(int x, int y, int w, int h, const char* name, const rgba_t &color); + + // [Paril-KEX] kfont stuff + void(*SCR_SetAltTypeface)(bool enabled); + void (*SCR_DrawFontString)(const char *str, int x, int y, int scale, const rgba_t &color, bool shadow, text_align_t align); + vec2_t (*SCR_MeasureFontString)(const char *str, int scale); + float (*SCR_FontLineHeight)(int scale); + + // [Paril-KEX] for legacy text input (not used in lobbies) + bool (*CL_GetTextInput)(const char **msg, bool *is_team); + + // [Paril-KEX] FIXME this probably should be an export instead... + int32_t (*CL_GetWarnAmmoCount)(int32_t weapon_id); + + // === [KEX] Additional APIs === + // returns a *temporary string* ptr to a localized input + const char* (*Localize) (const char *base, const char **args, size_t num_args); + + // [Paril-KEX] Draw binding, for centerprint; returns y offset + int32_t (*SCR_DrawBind) (int32_t isplit, const char *binding, const char *purpose, int x, int y, int scale); + + // [Paril-KEX] + bool (*CL_InAutoDemoLoop) (); +}; + +// +// functions exported for client by game subsystem +// +struct cgame_export_t +{ + int apiversion; + + // the init/shutdown functions will be called between levels/connections + // (cgame does not run in menus) + void (*Init)(); + void (*Shutdown)(); + + // [Paril-KEX] hud drawing + void (*DrawHUD) (int32_t isplit, const cg_server_data_t *data, vrect_t hud_vrect, vrect_t hud_safe, int32_t scale, int32_t playernum, const player_state_t *ps); + // [Paril-KEX] precache special pics used by hud + void (*TouchPics) (); + + // [Paril-KEX] layout flags; see layout_flags_t + layout_flags_t (*LayoutFlags) (const player_state_t *ps); + + // [Paril-KEX] fetch the current wheel weapon ID in use + int32_t (*GetActiveWeaponWheelWeapon) (const player_state_t *ps); + + // [Paril-KEX] fetch owned weapon IDs + uint32_t (*GetOwnedWeaponWheelWeapons) (const player_state_t *ps); + + // [Paril-KEX] fetch ammo count for given ammo id + int16_t (*GetWeaponWheelAmmoCount)(const player_state_t *ps, int32_t ammo_id); + + // [Paril-KEX] fetch powerup count for given powerup id + int16_t (*GetPowerupWheelCount)(const player_state_t *ps, int32_t powerup_id); + + // [Paril-KEX] fetch how much damage was registered by these stats + int16_t (*GetHitMarkerDamage)(const player_state_t *ps); + + // [KEX]: Pmove as export + void (*Pmove)(pmove_t *pmove); // player movement code called by server & client + + // [Paril-KEX] allow cgame to react to configstring changes + void (*ParseConfigString)(int32_t i, const char *s); + + // [Paril-KEX] parse centerprint-like messages + void (*ParseCenterPrint)(const char *str, int isplit, bool instant); + + // [Paril-KEX] tell the cgame to clear notify stuff + void (*ClearNotify)(int32_t isplit); + + // [Paril-KEX] tell the cgame to clear centerprint state + void (*ClearCenterprint)(int32_t isplit); + + // [Paril-KEX] be notified by the game DLL of a message of some sort + void (*NotifyMessage)(int32_t isplit, const char *msg, bool is_chat); + + // [Paril-KEX] + void (*GetMonsterFlashOffset)(monster_muzzleflash_id_t id, gvec3_ref_t offset); + + // Fetch named extension from cgame DLL. + void *(*GetExtension)(const char *name); +}; + +// EOF diff --git a/rerelease/game.sln b/rerelease/game.sln new file mode 100644 index 0000000..94487d1 --- /dev/null +++ b/rerelease/game.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.7.33808.371 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "game", "game.vcxproj", "{C994B5EA-3058-403C-953D-3673C2C4D64E}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {C994B5EA-3058-403C-953D-3673C2C4D64E}.Debug|x64.ActiveCfg = Debug|x64 + {C994B5EA-3058-403C-953D-3673C2C4D64E}.Debug|x64.Build.0 = Debug|x64 + {C994B5EA-3058-403C-953D-3673C2C4D64E}.Release|x64.ActiveCfg = Release|x64 + {C994B5EA-3058-403C-953D-3673C2C4D64E}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {5E7547B5-6A49-45CD-97AA-956B8460495D} + EndGlobalSection +EndGlobal diff --git a/rerelease/game.vcxproj b/rerelease/game.vcxproj new file mode 100644 index 0000000..704f3da --- /dev/null +++ b/rerelease/game.vcxproj @@ -0,0 +1,246 @@ + + + + + Debug + x64 + + + Release + x64 + + + + 16.0 + Win32Proj + {c994b5ea-3058-403c-953d-3673c2c4d64e} + game + 10.0 + + + + DynamicLibrary + true + v143 + Unicode + + + DynamicLibrary + false + v143 + true + Unicode + + + + + + + + + + + + + + + ../ + $(ProjectName)_x64 + + + ../ + $(ProjectName)_x64 + + + true + + + true + + + true + + + + Level3 + true + KEX_Q2_GAME;KEX_Q2GAME_EXPORTS;NO_FMT_SOURCE;KEX_Q2GAME_DYNAMIC;_CRT_SECURE_NO_WARNINGS;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpp17 + 4267;4244 + true + MultiThreadedDebug + + + NotSet + true + + + + + Level3 + true + true + true + KEX_Q2_GAME;KEX_Q2GAME_EXPORTS;NO_FMT_SOURCE;KEX_Q2GAME_DYNAMIC;_CRT_SECURE_NO_WARNINGS;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpp17 + 4267;4244 + true + MultiThreaded + + + NotSet + true + true + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/rerelease/game.vcxproj.filters b/rerelease/game.vcxproj.filters new file mode 100644 index 0000000..20132db --- /dev/null +++ b/rerelease/game.vcxproj.filters @@ -0,0 +1,265 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + bots + + + bots + + + bots + + + bots + + + bots + + + ctf + + + ctf + + + rogue + + + rogue + + + rogue + + + rogue + + + rogue + + + xatrix + + + xatrix + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + bots + + + bots + + + bots + + + bots + + + ctf + + + ctf + + + rogue + + + rogue + + + rogue + + + rogue + + + rogue + + + rogue + + + rogue + + + rogue + + + rogue + + + rogue + + + rogue + + + rogue + + + rogue + + + rogue + + + rogue + + + rogue + + + rogue + + + rogue + + + rogue + + + rogue + + + rogue + + + rogue + + + rogue + + + xatrix + + + xatrix + + + xatrix + + + xatrix + + + xatrix + + + xatrix + + + xatrix + + + xatrix + + + xatrix + + + + + {185665ce-b604-4d9a-b22b-02a83797d112} + + + {2b0fdaa0-3de9-4bbd-9bc6-3cadf798c291} + + + {76ef0be8-1c1d-472a-ada1-3aa1b354e022} + + + {6565427e-a805-4dc7-ba57-3ce0b62e4336} + + + \ No newline at end of file diff --git a/rerelease/m_actor.cpp b/rerelease/m_actor.cpp new file mode 100644 index 0000000..31fd7fd --- /dev/null +++ b/rerelease/m_actor.cpp @@ -0,0 +1,564 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// g_actor.c + +#include "g_local.h" +#include "m_actor.h" +#include "m_flash.h" + +constexpr const char *actor_names[] = { + "Hellrot", + "Tokay", + "Killme", + "Disruptor", + "Adrianator", + "Rambear", + "Titus", + "Bitterman" +}; + +mframe_t actor_frames_stand[] = { + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand } +}; +MMOVE_T(actor_move_stand) = { FRAME_stand101, FRAME_stand140, actor_frames_stand, nullptr }; + +MONSTERINFO_STAND(actor_stand) (edict_t *self) -> void +{ + M_SetAnimation(self, &actor_move_stand); + + // randomize on startup + if (level.time < 1_sec) + self->s.frame = irandom(self->monsterinfo.active_move->firstframe, self->monsterinfo.active_move->lastframe + 1); +} + +mframe_t actor_frames_walk[] = { + { ai_walk }, + { ai_walk, 6 }, + { ai_walk, 10 }, + { ai_walk, 3 }, + { ai_walk, 2 }, + { ai_walk, 7 }, + { ai_walk, 10 }, + { ai_walk, 1 } +}; +MMOVE_T(actor_move_walk) = { FRAME_walk01, FRAME_walk08, actor_frames_walk, nullptr }; + +MONSTERINFO_WALK(actor_walk) (edict_t *self) -> void +{ + M_SetAnimation(self, &actor_move_walk); +} + +mframe_t actor_frames_run[] = { + { ai_run, 4 }, + { ai_run, 15 }, + { ai_run, 15 }, + { ai_run, 8 }, + { ai_run, 20 }, + { ai_run, 15 } +}; +MMOVE_T(actor_move_run) = { FRAME_run02, FRAME_run07, actor_frames_run, nullptr }; + +MONSTERINFO_RUN(actor_run) (edict_t *self) -> void +{ + if ((level.time < self->pain_debounce_time) && (!self->enemy)) + { + if (self->movetarget) + actor_walk(self); + else + actor_stand(self); + return; + } + + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + { + actor_stand(self); + return; + } + + M_SetAnimation(self, &actor_move_run); +} + +mframe_t actor_frames_pain1[] = { + { ai_move, -5 }, + { ai_move, 4 }, + { ai_move, 1 } +}; +MMOVE_T(actor_move_pain1) = { FRAME_pain101, FRAME_pain103, actor_frames_pain1, actor_run }; + +mframe_t actor_frames_pain2[] = { + { ai_move, -4 }, + { ai_move, 4 }, + { ai_move } +}; +MMOVE_T(actor_move_pain2) = { FRAME_pain201, FRAME_pain203, actor_frames_pain2, actor_run }; + +mframe_t actor_frames_pain3[] = { + { ai_move, -1 }, + { ai_move, 1 }, + { ai_move, 0 } +}; +MMOVE_T(actor_move_pain3) = { FRAME_pain301, FRAME_pain303, actor_frames_pain3, actor_run }; + +mframe_t actor_frames_flipoff[] = { + { ai_turn }, + { ai_turn }, + { ai_turn }, + { ai_turn }, + { ai_turn }, + { ai_turn }, + { ai_turn }, + { ai_turn }, + { ai_turn }, + { ai_turn }, + { ai_turn }, + { ai_turn }, + { ai_turn }, + { ai_turn } +}; +MMOVE_T(actor_move_flipoff) = { FRAME_flip01, FRAME_flip14, actor_frames_flipoff, actor_run }; + +mframe_t actor_frames_taunt[] = { + { ai_turn }, + { ai_turn }, + { ai_turn }, + { ai_turn }, + { ai_turn }, + { ai_turn }, + { ai_turn }, + { ai_turn }, + { ai_turn }, + { ai_turn }, + { ai_turn }, + { ai_turn }, + { ai_turn }, + { ai_turn }, + { ai_turn }, + { ai_turn }, + { ai_turn } +}; +MMOVE_T(actor_move_taunt) = { FRAME_taunt01, FRAME_taunt17, actor_frames_taunt, actor_run }; + +const char *messages[] = { + "Watch it", + "#$@*&", + "Idiot", + "Check your targets" +}; + +PAIN(actor_pain) (edict_t *self, edict_t *other, float kick, int damage, const mod_t &mod) -> void +{ + int n; + + if (level.time < self->pain_debounce_time) + return; + + self->pain_debounce_time = level.time + 3_sec; + // gi.sound (self, CHAN_VOICE, actor.sound_pain, 1, ATTN_NORM, 0); + + if ((other->client) && (frandom() < 0.4f)) + { + vec3_t v; + const char *name; + + v = other->s.origin - self->s.origin; + self->ideal_yaw = vectoyaw(v); + if (frandom() < 0.5f) + M_SetAnimation(self, &actor_move_flipoff); + else + M_SetAnimation(self, &actor_move_taunt); + name = actor_names[(self - g_edicts) % q_countof(actor_names)]; + gi.LocClient_Print(other, PRINT_CHAT, "{}: {}!\n", name, random_element(messages)); + return; + } + + n = irandom(3); + if (n == 0) + M_SetAnimation(self, &actor_move_pain1); + else if (n == 1) + M_SetAnimation(self, &actor_move_pain2); + else + M_SetAnimation(self, &actor_move_pain3); +} + +MONSTERINFO_SETSKIN(actor_setskin) (edict_t *self) -> void +{ + if (self->health < (self->max_health / 2)) + self->s.skinnum = 1; + else + self->s.skinnum = 0; +} + +void actorMachineGun(edict_t *self) +{ + vec3_t start, target; + vec3_t forward, right; + + AngleVectors(self->s.angles, forward, right, nullptr); + start = G_ProjectSource(self->s.origin, monster_flash_offset[MZ2_ACTOR_MACHINEGUN_1], forward, right); + if (self->enemy) + { + if (self->enemy->health > 0) + { + target = self->enemy->s.origin + (self->enemy->velocity * -0.2f); + target[2] += self->enemy->viewheight; + } + else + { + target = self->enemy->absmin; + target[2] += (self->enemy->size[2] / 2) + 1; + } + forward = target - start; + forward.normalize(); + } + else + { + AngleVectors(self->s.angles, forward, nullptr, nullptr); + } + monster_fire_bullet(self, start, forward, 3, 4, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, MZ2_ACTOR_MACHINEGUN_1); +} + +void actor_dead(edict_t *self) +{ + self->mins = { -16, -16, -24 }; + self->maxs = { 16, 16, -8 }; + self->movetype = MOVETYPE_TOSS; + self->svflags |= SVF_DEADMONSTER; + self->nextthink = 0_ms; + gi.linkentity(self); +} + +mframe_t actor_frames_death1[] = { + { ai_move }, + { ai_move }, + { ai_move, -13 }, + { ai_move, 14 }, + { ai_move, 3 }, + { ai_move, -2 }, + { ai_move, 1 } +}; +MMOVE_T(actor_move_death1) = { FRAME_death101, FRAME_death107, actor_frames_death1, actor_dead }; + +mframe_t actor_frames_death2[] = { + { ai_move }, + { ai_move, 7 }, + { ai_move, -6 }, + { ai_move, -5 }, + { ai_move, 1 }, + { ai_move }, + { ai_move, -1 }, + { ai_move, -2 }, + { ai_move, -1 }, + { ai_move, -9 }, + { ai_move, -13 }, + { ai_move, -13 }, + { ai_move } +}; +MMOVE_T(actor_move_death2) = { FRAME_death201, FRAME_death213, actor_frames_death2, actor_dead }; + +DIE(actor_die) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void +{ + // check for gib + if (self->health <= -80) + { + // gi.sound (self, CHAN_VOICE, actor.sound_gib, 1, ATTN_NORM, 0); + ThrowGibs(self, damage, { + { 2, "models/objects/gibs/bone/tris.md2" }, + { 4, "models/objects/gibs/sm_meat/tris.md2" }, + { "models/objects/gibs/head2/tris.md2", GIB_HEAD } + }); + self->deadflag = true; + return; + } + + if (self->deadflag) + return; + + // regular death + // gi.sound (self, CHAN_VOICE, actor.sound_die, 1, ATTN_NORM, 0); + self->deadflag = true; + self->takedamage = true; + + if (brandom()) + M_SetAnimation(self, &actor_move_death1); + else + M_SetAnimation(self, &actor_move_death2); +} + +void actor_fire(edict_t *self) +{ + actorMachineGun(self); + + if (level.time >= self->monsterinfo.fire_wait) + self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; + else + self->monsterinfo.aiflags |= AI_HOLD_FRAME; +} + +mframe_t actor_frames_attack[] = { + { ai_charge, -2, actor_fire }, + { ai_charge, -2 }, + { ai_charge, 3 }, + { ai_charge, 2 } +}; +MMOVE_T(actor_move_attack) = { FRAME_attak01, FRAME_attak04, actor_frames_attack, actor_run }; + +MONSTERINFO_ATTACK(actor_attack) (edict_t *self) -> void +{ + M_SetAnimation(self, &actor_move_attack); + self->monsterinfo.fire_wait = level.time + random_time(1_sec, 2.6_sec); +} + +USE(actor_use) (edict_t *self, edict_t *other, edict_t *activator) -> void +{ + vec3_t v; + + self->goalentity = self->movetarget = G_PickTarget(self->target); + if ((!self->movetarget) || (strcmp(self->movetarget->classname, "target_actor") != 0)) + { + gi.Com_PrintFmt("{}: bad target {}\n", *self, self->target); + self->target = nullptr; + self->monsterinfo.pausetime = HOLD_FOREVER; + self->monsterinfo.stand(self); + return; + } + + v = self->goalentity->s.origin - self->s.origin; + self->ideal_yaw = self->s.angles[YAW] = vectoyaw(v); + self->monsterinfo.walk(self); + self->target = nullptr; +} + +/*QUAKED misc_actor (1 .5 0) (-16 -16 -24) (16 16 32) + */ + +void SP_misc_actor(edict_t *self) +{ + if ( !M_AllowSpawn( self ) ) { + G_FreeEdict(self); + return; + } + + if (!self->targetname) + { + gi.Com_PrintFmt("{}: no targetname\n", *self); + G_FreeEdict(self); + return; + } + + if (!self->target) + { + gi.Com_PrintFmt("{}: no target\n", *self); + G_FreeEdict(self); + return; + } + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex("players/male/tris.md2"); + self->mins = { -16, -16, -24 }; + self->maxs = { 16, 16, 32 }; + + if (!self->health) + self->health = 100; + self->mass = 200; + + self->pain = actor_pain; + self->die = actor_die; + + self->monsterinfo.stand = actor_stand; + self->monsterinfo.walk = actor_walk; + self->monsterinfo.run = actor_run; + self->monsterinfo.attack = actor_attack; + self->monsterinfo.melee = nullptr; + self->monsterinfo.sight = nullptr; + self->monsterinfo.setskin = actor_setskin; + + self->monsterinfo.aiflags |= AI_GOOD_GUY; + + gi.linkentity(self); + + M_SetAnimation(self, &actor_move_stand); + self->monsterinfo.scale = MODEL_SCALE; + + walkmonster_start(self); + + // actors always start in a dormant state, they *must* be used to get going + self->use = actor_use; +} + +/*QUAKED target_actor (.5 .3 0) (-8 -8 -8) (8 8 8) JUMP SHOOT ATTACK x HOLD BRUTAL +JUMP jump in set direction upon reaching this target +SHOOT take a single shot at the pathtarget +ATTACK attack pathtarget until it or actor is dead + +"target" next target_actor +"pathtarget" target of any action to be taken at this point +"wait" amount of time actor should pause at this point +"message" actor will "say" this to the player + +for JUMP only: +"speed" speed thrown forward (default 200) +"height" speed thrown upwards (default 200) +*/ + +constexpr spawnflags_t SPAWNFLAG_TARGET_ACTOR_JUMP = 1_spawnflag; +constexpr spawnflags_t SPAWNFLAG_TARGET_ACTOR_SHOOT = 2_spawnflag; +constexpr spawnflags_t SPAWNFLAG_TARGET_ACTOR_ATTACK = 4_spawnflag; +constexpr spawnflags_t SPAWNFLAG_TARGET_ACTOR_HOLD = 16_spawnflag; +constexpr spawnflags_t SPAWNFLAG_TARGET_ACTOR_BRUTAL = 32_spawnflag; + +TOUCH(target_actor_touch) (edict_t *self, edict_t *other, const trace_t &tr, bool other_touching_self) -> void +{ + vec3_t v; + + if (other->movetarget != self) + return; + + if (other->enemy) + return; + + other->goalentity = other->movetarget = nullptr; + + if (self->message) + { + edict_t *ent; + + for (uint32_t n = 1; n <= game.maxclients; n++) + { + ent = &g_edicts[n]; + if (!ent->inuse) + continue; + gi.LocClient_Print(ent, PRINT_CHAT, "{}: {}\n", actor_names[(other - g_edicts) % q_countof(actor_names)], self->message); + } + } + + if (self->spawnflags.has(SPAWNFLAG_TARGET_ACTOR_JUMP)) // jump + { + other->velocity[0] = self->movedir[0] * self->speed; + other->velocity[1] = self->movedir[1] * self->speed; + + if (other->groundentity) + { + other->groundentity = nullptr; + other->velocity[2] = self->movedir[2]; + gi.sound(other, CHAN_VOICE, gi.soundindex("player/male/jump1.wav"), 1, ATTN_NORM, 0); + } + } + + if (self->spawnflags.has(SPAWNFLAG_TARGET_ACTOR_SHOOT)) // shoot + { + } + else if (self->spawnflags.has(SPAWNFLAG_TARGET_ACTOR_ATTACK)) // attack + { + other->enemy = G_PickTarget(self->pathtarget); + if (other->enemy) + { + other->goalentity = other->enemy; + if (self->spawnflags.has(SPAWNFLAG_TARGET_ACTOR_BRUTAL)) + other->monsterinfo.aiflags |= AI_BRUTAL; + if (self->spawnflags.has(SPAWNFLAG_TARGET_ACTOR_HOLD)) + { + other->monsterinfo.aiflags |= AI_STAND_GROUND; + actor_stand(other); + } + else + { + actor_run(other); + } + } + } + + if (!self->spawnflags.has((SPAWNFLAG_TARGET_ACTOR_ATTACK | SPAWNFLAG_TARGET_ACTOR_SHOOT)) && (self->pathtarget)) + { + const char *savetarget; + + savetarget = self->target; + self->target = self->pathtarget; + G_UseTargets(self, other); + self->target = savetarget; + } + + other->movetarget = G_PickTarget(self->target); + + if (!other->goalentity) + other->goalentity = other->movetarget; + + if (!other->movetarget && !other->enemy) + { + other->monsterinfo.pausetime = HOLD_FOREVER; + other->monsterinfo.stand(other); + } + else if (other->movetarget == other->goalentity) + { + v = other->movetarget->s.origin - other->s.origin; + other->ideal_yaw = vectoyaw(v); + } +} + +void SP_target_actor(edict_t *self) +{ + if (!self->targetname) + gi.Com_PrintFmt("{}: no targetname\n", *self); + + self->solid = SOLID_TRIGGER; + self->touch = target_actor_touch; + self->mins = { -8, -8, -8 }; + self->maxs = { 8, 8, 8 }; + self->svflags = SVF_NOCLIENT; + + if (self->spawnflags.has(SPAWNFLAG_TARGET_ACTOR_JUMP)) + { + if (!self->speed) + self->speed = 200; + if (!st.height) + st.height = 200; + if (self->s.angles[YAW] == 0) + self->s.angles[YAW] = 360; + G_SetMovedir(self->s.angles, self->movedir); + self->movedir[2] = (float) st.height; + } + + gi.linkentity(self); +} diff --git a/rerelease/m_actor.h b/rerelease/m_actor.h new file mode 100644 index 0000000..c9f6b1f --- /dev/null +++ b/rerelease/m_actor.h @@ -0,0 +1,492 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// G:\quake2\baseq2\models/player_y + +// This file generated by ModelGen - Do NOT Modify + +enum +{ + FRAME_attak01, + FRAME_attak02, + FRAME_attak03, + FRAME_attak04, + FRAME_death101, + FRAME_death102, + FRAME_death103, + FRAME_death104, + FRAME_death105, + FRAME_death106, + FRAME_death107, + FRAME_death201, + FRAME_death202, + FRAME_death203, + FRAME_death204, + FRAME_death205, + FRAME_death206, + FRAME_death207, + FRAME_death208, + FRAME_death209, + FRAME_death210, + FRAME_death211, + FRAME_death212, + FRAME_death213, + FRAME_death301, + FRAME_death302, + FRAME_death303, + FRAME_death304, + FRAME_death305, + FRAME_death306, + FRAME_death307, + FRAME_death308, + FRAME_death309, + FRAME_death310, + FRAME_death311, + FRAME_death312, + FRAME_death313, + FRAME_death314, + FRAME_death315, + FRAME_flip01, + FRAME_flip02, + FRAME_flip03, + FRAME_flip04, + FRAME_flip05, + FRAME_flip06, + FRAME_flip07, + FRAME_flip08, + FRAME_flip09, + FRAME_flip10, + FRAME_flip11, + FRAME_flip12, + FRAME_flip13, + FRAME_flip14, + FRAME_grenad01, + FRAME_grenad02, + FRAME_grenad03, + FRAME_grenad04, + FRAME_grenad05, + FRAME_grenad06, + FRAME_grenad07, + FRAME_grenad08, + FRAME_grenad09, + FRAME_grenad10, + FRAME_grenad11, + FRAME_grenad12, + FRAME_grenad13, + FRAME_grenad14, + FRAME_grenad15, + FRAME_jump01, + FRAME_jump02, + FRAME_jump03, + FRAME_jump04, + FRAME_jump05, + FRAME_jump06, + FRAME_pain101, + FRAME_pain102, + FRAME_pain103, + FRAME_pain201, + FRAME_pain202, + FRAME_pain203, + FRAME_pain301, + FRAME_pain302, + FRAME_pain303, + FRAME_push01, + FRAME_push02, + FRAME_push03, + FRAME_push04, + FRAME_push05, + FRAME_push06, + FRAME_push07, + FRAME_push08, + FRAME_push09, + FRAME_run01, + FRAME_run02, + FRAME_run03, + FRAME_run04, + FRAME_run05, + FRAME_run06, + FRAME_run07, + FRAME_run08, + FRAME_run09, + FRAME_run10, + FRAME_run11, + FRAME_run12, + FRAME_runs01, + FRAME_runs02, + FRAME_runs03, + FRAME_runs04, + FRAME_runs05, + FRAME_runs06, + FRAME_runs07, + FRAME_runs08, + FRAME_runs09, + FRAME_runs10, + FRAME_runs11, + FRAME_runs12, + FRAME_salute01, + FRAME_salute02, + FRAME_salute03, + FRAME_salute04, + FRAME_salute05, + FRAME_salute06, + FRAME_salute07, + FRAME_salute08, + FRAME_salute09, + FRAME_salute10, + FRAME_salute11, + FRAME_salute12, + FRAME_stand101, + FRAME_stand102, + FRAME_stand103, + FRAME_stand104, + FRAME_stand105, + FRAME_stand106, + FRAME_stand107, + FRAME_stand108, + FRAME_stand109, + FRAME_stand110, + FRAME_stand111, + FRAME_stand112, + FRAME_stand113, + FRAME_stand114, + FRAME_stand115, + FRAME_stand116, + FRAME_stand117, + FRAME_stand118, + FRAME_stand119, + FRAME_stand120, + FRAME_stand121, + FRAME_stand122, + FRAME_stand123, + FRAME_stand124, + FRAME_stand125, + FRAME_stand126, + FRAME_stand127, + FRAME_stand128, + FRAME_stand129, + FRAME_stand130, + FRAME_stand131, + FRAME_stand132, + FRAME_stand133, + FRAME_stand134, + FRAME_stand135, + FRAME_stand136, + FRAME_stand137, + FRAME_stand138, + FRAME_stand139, + FRAME_stand140, + FRAME_stand201, + FRAME_stand202, + FRAME_stand203, + FRAME_stand204, + FRAME_stand205, + FRAME_stand206, + FRAME_stand207, + FRAME_stand208, + FRAME_stand209, + FRAME_stand210, + FRAME_stand211, + FRAME_stand212, + FRAME_stand213, + FRAME_stand214, + FRAME_stand215, + FRAME_stand216, + FRAME_stand217, + FRAME_stand218, + FRAME_stand219, + FRAME_stand220, + FRAME_stand221, + FRAME_stand222, + FRAME_stand223, + FRAME_swim01, + FRAME_swim02, + FRAME_swim03, + FRAME_swim04, + FRAME_swim05, + FRAME_swim06, + FRAME_swim07, + FRAME_swim08, + FRAME_swim09, + FRAME_swim10, + FRAME_swim11, + FRAME_swim12, + FRAME_sw_atk01, + FRAME_sw_atk02, + FRAME_sw_atk03, + FRAME_sw_atk04, + FRAME_sw_atk05, + FRAME_sw_atk06, + FRAME_sw_pan01, + FRAME_sw_pan02, + FRAME_sw_pan03, + FRAME_sw_pan04, + FRAME_sw_pan05, + FRAME_sw_std01, + FRAME_sw_std02, + FRAME_sw_std03, + FRAME_sw_std04, + FRAME_sw_std05, + FRAME_sw_std06, + FRAME_sw_std07, + FRAME_sw_std08, + FRAME_sw_std09, + FRAME_sw_std10, + FRAME_sw_std11, + FRAME_sw_std12, + FRAME_sw_std13, + FRAME_sw_std14, + FRAME_sw_std15, + FRAME_sw_std16, + FRAME_sw_std17, + FRAME_sw_std18, + FRAME_sw_std19, + FRAME_sw_std20, + FRAME_taunt01, + FRAME_taunt02, + FRAME_taunt03, + FRAME_taunt04, + FRAME_taunt05, + FRAME_taunt06, + FRAME_taunt07, + FRAME_taunt08, + FRAME_taunt09, + FRAME_taunt10, + FRAME_taunt11, + FRAME_taunt12, + FRAME_taunt13, + FRAME_taunt14, + FRAME_taunt15, + FRAME_taunt16, + FRAME_taunt17, + FRAME_walk01, + FRAME_walk02, + FRAME_walk03, + FRAME_walk04, + FRAME_walk05, + FRAME_walk06, + FRAME_walk07, + FRAME_walk08, + FRAME_walk09, + FRAME_walk10, + FRAME_walk11, + FRAME_wave01, + FRAME_wave02, + FRAME_wave03, + FRAME_wave04, + FRAME_wave05, + FRAME_wave06, + FRAME_wave07, + FRAME_wave08, + FRAME_wave09, + FRAME_wave10, + FRAME_wave11, + FRAME_wave12, + FRAME_wave13, + FRAME_wave14, + FRAME_wave15, + FRAME_wave16, + FRAME_wave17, + FRAME_wave18, + FRAME_wave19, + FRAME_wave20, + FRAME_wave21, + FRAME_bl_atk01, + FRAME_bl_atk02, + FRAME_bl_atk03, + FRAME_bl_atk04, + FRAME_bl_atk05, + FRAME_bl_atk06, + FRAME_bl_flp01, + FRAME_bl_flp02, + FRAME_bl_flp13, + FRAME_bl_flp14, + FRAME_bl_flp15, + FRAME_bl_jmp01, + FRAME_bl_jmp02, + FRAME_bl_jmp03, + FRAME_bl_jmp04, + FRAME_bl_jmp05, + FRAME_bl_jmp06, + FRAME_bl_pn101, + FRAME_bl_pn102, + FRAME_bl_pn103, + FRAME_bl_pn201, + FRAME_bl_pn202, + FRAME_bl_pn203, + FRAME_bl_pn301, + FRAME_bl_pn302, + FRAME_bl_pn303, + FRAME_bl_psh08, + FRAME_bl_psh09, + FRAME_bl_run01, + FRAME_bl_run02, + FRAME_bl_run03, + FRAME_bl_run04, + FRAME_bl_run05, + FRAME_bl_run06, + FRAME_bl_run07, + FRAME_bl_run08, + FRAME_bl_run09, + FRAME_bl_run10, + FRAME_bl_run11, + FRAME_bl_run12, + FRAME_bl_rns03, + FRAME_bl_rns04, + FRAME_bl_rns05, + FRAME_bl_rns06, + FRAME_bl_rns07, + FRAME_bl_rns08, + FRAME_bl_rns09, + FRAME_bl_sal10, + FRAME_bl_sal11, + FRAME_bl_sal12, + FRAME_bl_std01, + FRAME_bl_std02, + FRAME_bl_std03, + FRAME_bl_std04, + FRAME_bl_std05, + FRAME_bl_std06, + FRAME_bl_std07, + FRAME_bl_std08, + FRAME_bl_std09, + FRAME_bl_std10, + FRAME_bl_std11, + FRAME_bl_std12, + FRAME_bl_std13, + FRAME_bl_std14, + FRAME_bl_std15, + FRAME_bl_std16, + FRAME_bl_std17, + FRAME_bl_std18, + FRAME_bl_std19, + FRAME_bl_std20, + FRAME_bl_std21, + FRAME_bl_std22, + FRAME_bl_std23, + FRAME_bl_std24, + FRAME_bl_std25, + FRAME_bl_std26, + FRAME_bl_std27, + FRAME_bl_std28, + FRAME_bl_std29, + FRAME_bl_std30, + FRAME_bl_std31, + FRAME_bl_std32, + FRAME_bl_std33, + FRAME_bl_std34, + FRAME_bl_std35, + FRAME_bl_std36, + FRAME_bl_std37, + FRAME_bl_std38, + FRAME_bl_std39, + FRAME_bl_std40, + FRAME_bl_swm01, + FRAME_bl_swm02, + FRAME_bl_swm03, + FRAME_bl_swm04, + FRAME_bl_swm05, + FRAME_bl_swm06, + FRAME_bl_swm07, + FRAME_bl_swm08, + FRAME_bl_swm09, + FRAME_bl_swm10, + FRAME_bl_swm11, + FRAME_bl_swm12, + FRAME_bl_swk01, + FRAME_bl_swk02, + FRAME_bl_swk03, + FRAME_bl_swk04, + FRAME_bl_swk05, + FRAME_bl_swk06, + FRAME_bl_swp01, + FRAME_bl_swp02, + FRAME_bl_swp03, + FRAME_bl_swp04, + FRAME_bl_swp05, + FRAME_bl_sws01, + FRAME_bl_sws02, + FRAME_bl_sws03, + FRAME_bl_sws04, + FRAME_bl_sws05, + FRAME_bl_sws06, + FRAME_bl_sws07, + FRAME_bl_sws08, + FRAME_bl_sws09, + FRAME_bl_sws10, + FRAME_bl_sws11, + FRAME_bl_sws12, + FRAME_bl_sws13, + FRAME_bl_sws14, + FRAME_bl_tau14, + FRAME_bl_tau15, + FRAME_bl_tau16, + FRAME_bl_tau17, + FRAME_bl_wlk01, + FRAME_bl_wlk02, + FRAME_bl_wlk03, + FRAME_bl_wlk04, + FRAME_bl_wlk05, + FRAME_bl_wlk06, + FRAME_bl_wlk07, + FRAME_bl_wlk08, + FRAME_bl_wlk09, + FRAME_bl_wlk10, + FRAME_bl_wlk11, + FRAME_bl_wav19, + FRAME_bl_wav20, + FRAME_bl_wav21, + FRAME_cr_atk01, + FRAME_cr_atk02, + FRAME_cr_atk03, + FRAME_cr_atk04, + FRAME_cr_atk05, + FRAME_cr_atk06, + FRAME_cr_atk07, + FRAME_cr_atk08, + FRAME_cr_pan01, + FRAME_cr_pan02, + FRAME_cr_pan03, + FRAME_cr_pan04, + FRAME_cr_std01, + FRAME_cr_std02, + FRAME_cr_std03, + FRAME_cr_std04, + FRAME_cr_std05, + FRAME_cr_std06, + FRAME_cr_std07, + FRAME_cr_std08, + FRAME_cr_wlk01, + FRAME_cr_wlk02, + FRAME_cr_wlk03, + FRAME_cr_wlk04, + FRAME_cr_wlk05, + FRAME_cr_wlk06, + FRAME_cr_wlk07, + FRAME_crbl_a01, + FRAME_crbl_a02, + FRAME_crbl_a03, + FRAME_crbl_a04, + FRAME_crbl_a05, + FRAME_crbl_a06, + FRAME_crbl_a07, + FRAME_crbl_p01, + FRAME_crbl_p02, + FRAME_crbl_p03, + FRAME_crbl_p04, + FRAME_crbl_s01, + FRAME_crbl_s02, + FRAME_crbl_s03, + FRAME_crbl_s04, + FRAME_crbl_s05, + FRAME_crbl_s06, + FRAME_crbl_s07, + FRAME_crbl_s08, + FRAME_crbl_w01, + FRAME_crbl_w02, + FRAME_crbl_w03, + FRAME_crbl_w04, + FRAME_crbl_w05, + FRAME_crbl_w06, + FRAME_crbl_w07 +}; + +constexpr float MODEL_SCALE = 1.000000f; diff --git a/rerelease/m_arachnid.cpp b/rerelease/m_arachnid.cpp new file mode 100644 index 0000000..3c5a062 --- /dev/null +++ b/rerelease/m_arachnid.cpp @@ -0,0 +1,385 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +/* +============================================================================== + +TANK + +============================================================================== +*/ + +#include "g_local.h" +#include "m_arachnid.h" +#include "m_flash.h" + +static int sound_pain; +static int sound_death; +static int sound_sight; + +MONSTERINFO_SIGHT(arachnid_sight) (edict_t *self, edict_t *other) -> void +{ + gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); +} + +// +// stand +// + +mframe_t arachnid_frames_stand[] = { + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand } +}; +MMOVE_T(arachnid_move_stand) = { FRAME_idle1, FRAME_idle13, arachnid_frames_stand, nullptr }; + +MONSTERINFO_STAND(arachnid_stand) (edict_t *self) -> void +{ + M_SetAnimation(self, &arachnid_move_stand); +} + +// +// walk +// + +static int sound_step; + +void arachnid_footstep(edict_t *self) +{ + gi.sound(self, CHAN_BODY, sound_step, 0.5f, ATTN_IDLE, 0.0f); +} + +mframe_t arachnid_frames_walk[] = { + { ai_walk, 8, arachnid_footstep }, + { ai_walk, 8 }, + { ai_walk, 8 }, + { ai_walk, 8 }, + { ai_walk, 8 }, + { ai_walk, 8, arachnid_footstep }, + { ai_walk, 8 }, + { ai_walk, 8 }, + { ai_walk, 8 }, + { ai_walk, 8 } +}; +MMOVE_T(arachnid_move_walk) = { FRAME_walk1, FRAME_walk10, arachnid_frames_walk, nullptr }; + +MONSTERINFO_WALK(arachnid_walk) (edict_t *self) -> void +{ + M_SetAnimation(self, &arachnid_move_walk); +} + +// +// run +// + +mframe_t arachnid_frames_run[] = { + { ai_run, 8, arachnid_footstep }, + { ai_run, 8 }, + { ai_run, 8 }, + { ai_run, 8 }, + { ai_run, 8 }, + { ai_run, 8, arachnid_footstep }, + { ai_run, 8 }, + { ai_run, 8 }, + { ai_run, 8 }, + { ai_run, 8 } +}; +MMOVE_T(arachnid_move_run) = { FRAME_walk1, FRAME_walk10, arachnid_frames_run, nullptr }; + +MONSTERINFO_RUN(arachnid_run) (edict_t *self) -> void +{ + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + { + M_SetAnimation(self, &arachnid_move_stand); + return; + } + + M_SetAnimation(self, &arachnid_move_run); +} + +// +// pain +// + +mframe_t arachnid_frames_pain1[] = { + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move } +}; +MMOVE_T(arachnid_move_pain1) = { FRAME_pain11, FRAME_pain15, arachnid_frames_pain1, arachnid_run }; + +mframe_t arachnid_frames_pain2[] = { + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move } +}; +MMOVE_T(arachnid_move_pain2) = { FRAME_pain21, FRAME_pain26, arachnid_frames_pain2, arachnid_run }; + +PAIN(arachnid_pain) (edict_t *self, edict_t *other, float kick, int damage, const mod_t &mod) -> void +{ + if (level.time < self->pain_debounce_time) + return; + + self->pain_debounce_time = level.time + 3_sec; + gi.sound(self, CHAN_VOICE, sound_pain, 1, ATTN_NORM, 0); + + if (!M_ShouldReactToPain(self, mod)) + return; // no pain anims in nightmare + + float r = frandom(); + + if (r < 0.5f) + M_SetAnimation(self, &arachnid_move_pain1); + else + M_SetAnimation(self, &arachnid_move_pain2); +} + +static int sound_charge; + +void arachnid_charge_rail(edict_t *self) +{ + if (!self->enemy || !self->enemy->inuse) + return; + + gi.sound(self, CHAN_WEAPON, sound_charge, 1.f, ATTN_NORM, 0.f); + self->pos1 = self->enemy->s.origin; + self->pos1[2] += self->enemy->viewheight; +} + +void arachnid_rail(edict_t *self) +{ + vec3_t start; + vec3_t dir; + vec3_t forward, right; + monster_muzzleflash_id_t id; + + switch (self->s.frame) + { + case FRAME_rails4: + default: + id = MZ2_ARACHNID_RAIL1; + break; + case FRAME_rails8: + id = MZ2_ARACHNID_RAIL2; + break; + case FRAME_rails_up7: + id = MZ2_ARACHNID_RAIL_UP1; + break; + case FRAME_rails_up11: + id = MZ2_ARACHNID_RAIL_UP2; + break; + } + + AngleVectors(self->s.angles, forward, right, nullptr); + start = M_ProjectFlashSource(self, monster_flash_offset[id], forward, right); + + // calc direction to where we targeted + dir = self->pos1 - start; + dir.normalize(); + + monster_fire_railgun(self, start, dir, 35, 100, id); +} + +mframe_t arachnid_frames_attack1[] = { + { ai_charge, 0, arachnid_charge_rail }, + { ai_charge }, + { ai_charge }, + { ai_charge, 0, arachnid_rail }, + { ai_charge, 0, arachnid_charge_rail }, + { ai_charge }, + { ai_charge }, + { ai_charge, 0, arachnid_rail }, + { ai_charge }, + { ai_charge }, + { ai_charge } +}; +MMOVE_T(arachnid_attack1) = { FRAME_rails1, FRAME_rails11, arachnid_frames_attack1, arachnid_run }; + +mframe_t arachnid_frames_attack_up1[] = { + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge, 0, arachnid_charge_rail }, + { ai_charge }, + { ai_charge }, + { ai_charge, 0, arachnid_rail }, + { ai_charge, 0, arachnid_charge_rail }, + { ai_charge }, + { ai_charge }, + { ai_charge, 0, arachnid_rail }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, +}; +MMOVE_T(arachnid_attack_up1) = { FRAME_rails_up1, FRAME_rails_up16, arachnid_frames_attack_up1, arachnid_run }; + +static int sound_melee, sound_melee_hit; + +void arachnid_melee_charge(edict_t *self) +{ + gi.sound(self, CHAN_WEAPON, sound_melee, 1.f, ATTN_NORM, 0.f); +} + +void arachnid_melee_hit(edict_t *self) +{ + if (!fire_hit(self, { MELEE_DISTANCE, 0, 0 }, 15, 50)) + self->monsterinfo.melee_debounce_time = level.time + 1000_ms; +} + +mframe_t arachnid_frames_melee[] = { + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge, 0, arachnid_melee_charge }, + { ai_charge }, + { ai_charge, 0, arachnid_melee_hit }, + { ai_charge }, + { ai_charge }, + { ai_charge, 0, arachnid_melee_charge }, + { ai_charge }, + { ai_charge, 0, arachnid_melee_hit }, + { ai_charge } +}; +MMOVE_T(arachnid_melee) = { FRAME_melee_atk1, FRAME_melee_atk12, arachnid_frames_melee, arachnid_run }; + +MONSTERINFO_ATTACK(arachnid_attack) (edict_t *self) -> void +{ + if (!self->enemy || !self->enemy->inuse) + return; + + if (self->monsterinfo.melee_debounce_time < level.time && range_to(self, self->enemy) < MELEE_DISTANCE) + M_SetAnimation(self, &arachnid_melee); + else if ((self->enemy->s.origin[2] - self->s.origin[2]) > 150.f) + M_SetAnimation(self, &arachnid_attack_up1); + else + M_SetAnimation(self, &arachnid_attack1); +} + +// +// death +// + +void arachnid_dead(edict_t *self) +{ + self->mins = { -16, -16, -24 }; + self->maxs = { 16, 16, -8 }; + self->movetype = MOVETYPE_TOSS; + self->svflags |= SVF_DEADMONSTER; + self->nextthink = 0_ms; + gi.linkentity(self); +} + +mframe_t arachnid_frames_death1[] = { + { ai_move, 0 }, + { ai_move, -1.23f }, + { ai_move, -1.23f }, + { ai_move, -1.23f }, + { ai_move, -1.23f }, + { ai_move, -1.64f }, + { ai_move, -1.64f }, + { ai_move, -2.45f }, + { ai_move, -8.63f }, + { ai_move, -4.0f }, + { ai_move, -4.5f }, + { ai_move, -6.8f }, + { ai_move, -8.0f }, + { ai_move, -5.4f }, + { ai_move, -3.4f }, + { ai_move, -1.9f }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move } +}; +MMOVE_T(arachnid_move_death) = { FRAME_death1, FRAME_death20, arachnid_frames_death1, arachnid_dead }; + +DIE(arachnid_die) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void +{ + // check for gib + if (M_CheckGib(self, mod)) + { + gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0); + ThrowGibs(self, damage, { + { 2, "models/objects/gibs/bone/tris.md2" }, + { 4, "models/objects/gibs/sm_meat/tris.md2" }, + { "models/objects/gibs/head2/tris.md2", GIB_HEAD } + }); + self->deadflag = true; + return; + } + + if (self->deadflag) + return; + + // regular death + gi.sound(self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0); + self->deadflag = true; + self->takedamage = true; + + M_SetAnimation(self, &arachnid_move_death); +} + +// +// monster_arachnid +// + +/*QUAKED monster_arachnid (1 .5 0) (-48 -48 -20) (48 48 48) Ambush Trigger_Spawn Sight + */ +void SP_monster_arachnid(edict_t *self) +{ + if ( !M_AllowSpawn( self ) ) { + G_FreeEdict( self ); + return; + } + + sound_step = gi.soundindex("insane/insane11.wav"); + sound_charge = gi.soundindex("gladiator/railgun.wav"); + sound_melee = gi.soundindex("gladiator/melee3.wav"); + sound_melee_hit = gi.soundindex("gladiator/melee2.wav"); + sound_pain = gi.soundindex("arachnid/pain.wav"); + sound_death = gi.soundindex("arachnid/death.wav"); + sound_sight = gi.soundindex("arachnid/sight.wav"); + + self->s.modelindex = gi.modelindex("models/monsters/arachnid/tris.md2"); + self->mins = { -48, -48, -20 }; + self->maxs = { 48, 48, 48 }; + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + + self->health = 1000 * st.health_multiplier; + self->gib_health = -200; + + self->monsterinfo.scale = MODEL_SCALE; + + self->mass = 450; + + self->pain = arachnid_pain; + self->die = arachnid_die; + self->monsterinfo.stand = arachnid_stand; + self->monsterinfo.walk = arachnid_walk; + self->monsterinfo.run = arachnid_run; + self->monsterinfo.attack = arachnid_attack; + self->monsterinfo.sight = arachnid_sight; + + gi.linkentity(self); + + M_SetAnimation(self, &arachnid_move_stand); + + walkmonster_start(self); +} diff --git a/rerelease/m_arachnid.h b/rerelease/m_arachnid.h new file mode 100644 index 0000000..e3420f7 --- /dev/null +++ b/rerelease/m_arachnid.h @@ -0,0 +1,142 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// G:\quake2\baseq2\models/monsters/arachnid + +// This file generated by ModelGen - Do NOT Modify + +enum +{ + FRAME_rails1, + FRAME_rails2, + FRAME_rails3, + FRAME_rails4, + FRAME_rails5, + FRAME_rails6, + FRAME_rails7, + FRAME_rails8, + FRAME_rails9, + FRAME_rails10, + FRAME_rails11, + FRAME_death1, + FRAME_death2, + FRAME_death3, + FRAME_death4, + FRAME_death5, + FRAME_death6, + FRAME_death7, + FRAME_death8, + FRAME_death9, + FRAME_death10, + FRAME_death11, + FRAME_death12, + FRAME_death13, + FRAME_death14, + FRAME_death15, + FRAME_death16, + FRAME_death17, + FRAME_death18, + FRAME_death19, + FRAME_death20, + FRAME_melee_atk1, + FRAME_melee_atk2, + FRAME_melee_atk3, + FRAME_melee_atk4, + FRAME_melee_atk5, + FRAME_melee_atk6, + FRAME_melee_atk7, + FRAME_melee_atk8, + FRAME_melee_atk9, + FRAME_melee_atk10, + FRAME_melee_atk11, + FRAME_melee_atk12, + FRAME_pain11, + FRAME_pain12, + FRAME_pain13, + FRAME_pain14, + FRAME_pain15, + FRAME_idle1, + FRAME_idle2, + FRAME_idle3, + FRAME_idle4, + FRAME_idle5, + FRAME_idle6, + FRAME_idle7, + FRAME_idle8, + FRAME_idle9, + FRAME_idle10, + FRAME_idle11, + FRAME_idle12, + FRAME_idle13, + FRAME_walk1, + FRAME_walk2, + FRAME_walk3, + FRAME_walk4, + FRAME_walk5, + FRAME_walk6, + FRAME_walk7, + FRAME_walk8, + FRAME_walk9, + FRAME_walk10, + FRAME_turn1, + FRAME_turn2, + FRAME_turn3, + FRAME_melee_out1, + FRAME_melee_out2, + FRAME_melee_out3, + FRAME_pain21, + FRAME_pain22, + FRAME_pain23, + FRAME_pain24, + FRAME_pain25, + FRAME_pain26, + FRAME_melee_pain1, + FRAME_melee_pain2, + FRAME_melee_pain3, + FRAME_melee_pain4, + FRAME_melee_pain5, + FRAME_melee_pain6, + FRAME_melee_pain7, + FRAME_melee_pain8, + FRAME_melee_pain9, + FRAME_melee_pain10, + FRAME_melee_pain11, + FRAME_melee_pain12, + FRAME_melee_pain13, + FRAME_melee_pain14, + FRAME_melee_pain15, + FRAME_melee_pain16, + FRAME_melee_in1, + FRAME_melee_in2, + FRAME_melee_in3, + FRAME_melee_in4, + FRAME_melee_in5, + FRAME_melee_in6, + FRAME_melee_in7, + FRAME_melee_in8, + FRAME_melee_in9, + FRAME_melee_in10, + FRAME_melee_in11, + FRAME_melee_in12, + FRAME_melee_in13, + FRAME_melee_in14, + FRAME_melee_in15, + FRAME_melee_in16, + FRAME_rails_up1, + FRAME_rails_up2, + FRAME_rails_up3, + FRAME_rails_up4, + FRAME_rails_up5, + FRAME_rails_up6, + FRAME_rails_up7, + FRAME_rails_up8, + FRAME_rails_up9, + FRAME_rails_up10, + FRAME_rails_up11, + FRAME_rails_up12, + FRAME_rails_up13, + FRAME_rails_up14, + FRAME_rails_up15, + FRAME_rails_up16 +}; + +#define MODEL_SCALE 1.000000f \ No newline at end of file diff --git a/rerelease/m_berserk.cpp b/rerelease/m_berserk.cpp new file mode 100644 index 0000000..1ce0a98 --- /dev/null +++ b/rerelease/m_berserk.cpp @@ -0,0 +1,822 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +/* +============================================================================== + +BERSERK + +============================================================================== +*/ + +#include "g_local.h" +#include "m_berserk.h" + +constexpr spawnflags_t SPAWNFLAG_BERSERK_NOJUMPING = 8_spawnflag; + +static int sound_pain; +static int sound_die; +static int sound_idle; +static int sound_idle2; +static int sound_punch; +static int sound_sight; +static int sound_search; +static int sound_thud; +static int sound_jump; + +MONSTERINFO_SIGHT(berserk_sight) (edict_t *self, edict_t *other) -> void +{ + gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); +} + +MONSTERINFO_SEARCH(berserk_search) (edict_t *self) -> void +{ + if (brandom()) + gi.sound(self, CHAN_VOICE, sound_idle2, 1, ATTN_NORM, 0); + else + gi.sound(self, CHAN_VOICE, sound_search, 1, ATTN_NORM, 0); +} + +void berserk_fidget(edict_t *self); + +mframe_t berserk_frames_stand[] = { + { ai_stand, 0, berserk_fidget }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand } +}; +MMOVE_T(berserk_move_stand) = { FRAME_stand1, FRAME_stand5, berserk_frames_stand, nullptr }; + +MONSTERINFO_STAND(berserk_stand) (edict_t *self) -> void +{ + M_SetAnimation(self, &berserk_move_stand); +} + +mframe_t berserk_frames_stand_fidget[] = { + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand } +}; +MMOVE_T(berserk_move_stand_fidget) = { FRAME_standb1, FRAME_standb20, berserk_frames_stand_fidget, berserk_stand }; + +void berserk_fidget(edict_t *self) +{ + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + return; + else if (self->enemy) + return; + if (frandom() > 0.15f) + return; + + M_SetAnimation(self, &berserk_move_stand_fidget); + gi.sound(self, CHAN_WEAPON, sound_idle, 1, ATTN_IDLE, 0); +} + +mframe_t berserk_frames_walk[] = { + { ai_walk, 9.1f }, + { ai_walk, 6.3f }, + { ai_walk, 4.9f }, + { ai_walk, 6.7f, monster_footstep }, + { ai_walk, 6.0f }, + { ai_walk, 8.2f }, + { ai_walk, 7.2f }, + { ai_walk, 6.1f }, + { ai_walk, 4.9f }, + { ai_walk, 4.7f, monster_footstep }, + { ai_walk, 4.7f } +}; +MMOVE_T(berserk_move_walk) = { FRAME_walkc1, FRAME_walkc11, berserk_frames_walk, nullptr }; + +MONSTERINFO_WALK(berserk_walk) (edict_t *self) -> void +{ + M_SetAnimation(self, &berserk_move_walk); +} + +/* + + ***************************** + SKIPPED THIS FOR NOW! + ***************************** + + Running -> Arm raised in air + +void() berserk_runb1 =[ $r_att1 , berserk_runb2 ] {ai_run(21);}; +void() berserk_runb2 =[ $r_att2 , berserk_runb3 ] {ai_run(11);}; +void() berserk_runb3 =[ $r_att3 , berserk_runb4 ] {ai_run(21);}; +void() berserk_runb4 =[ $r_att4 , berserk_runb5 ] {ai_run(25);}; +void() berserk_runb5 =[ $r_att5 , berserk_runb6 ] {ai_run(18);}; +void() berserk_runb6 =[ $r_att6 , berserk_runb7 ] {ai_run(19);}; +// running with arm in air : start loop +void() berserk_runb7 =[ $r_att7 , berserk_runb8 ] {ai_run(21);}; +void() berserk_runb8 =[ $r_att8 , berserk_runb9 ] {ai_run(11);}; +void() berserk_runb9 =[ $r_att9 , berserk_runb10 ] {ai_run(21);}; +void() berserk_runb10 =[ $r_att10 , berserk_runb11 ] {ai_run(25);}; +void() berserk_runb11 =[ $r_att11 , berserk_runb12 ] {ai_run(18);}; +void() berserk_runb12 =[ $r_att12 , berserk_runb7 ] {ai_run(19);}; +// running with arm in air : end loop +*/ + +mframe_t berserk_frames_run1[] = { + { ai_run, 21 }, + { ai_run, 11, monster_footstep }, + { ai_run, 21 }, + { ai_run, 25, monster_done_dodge }, + { ai_run, 18, monster_footstep }, + { ai_run, 19 } +}; +MMOVE_T(berserk_move_run1) = { FRAME_run1, FRAME_run6, berserk_frames_run1, nullptr }; + +MONSTERINFO_RUN(berserk_run) (edict_t *self) -> void +{ + monster_done_dodge(self); + + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + M_SetAnimation(self, &berserk_move_stand); + else + M_SetAnimation(self, &berserk_move_run1); +} + +void berserk_attack_spike(edict_t *self) +{ + constexpr vec3_t aim = { MELEE_DISTANCE, 0, -24 }; + + if (!fire_hit(self, aim, irandom(5, 11), 80)) // Faster attack -- upwards and backwards + self->monsterinfo.melee_debounce_time = level.time + 1.2_sec; +} + +void berserk_swing(edict_t *self) +{ + gi.sound(self, CHAN_WEAPON, sound_punch, 1, ATTN_NORM, 0); +} + +mframe_t berserk_frames_attack_spike[] = { + { ai_charge }, + { ai_charge }, + { ai_charge, 0, berserk_swing }, + { ai_charge, 0, berserk_attack_spike }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge } +}; +MMOVE_T(berserk_move_attack_spike) = { FRAME_att_c1, FRAME_att_c8, berserk_frames_attack_spike, berserk_run }; + +void berserk_attack_club(edict_t *self) +{ + vec3_t aim = { MELEE_DISTANCE, self->mins[0], -4 }; + + if (!fire_hit(self, aim, irandom(15, 21), 400)) // Slower attack + self->monsterinfo.melee_debounce_time = level.time + 2.5_sec; +} + +mframe_t berserk_frames_attack_club[] = { + { ai_charge }, + { ai_charge }, + { ai_charge, 0, monster_footstep }, + { ai_charge }, + { ai_charge, 0, berserk_swing }, + { ai_charge }, + { ai_charge, 0, berserk_attack_club }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge } +}; +MMOVE_T(berserk_move_attack_club) = { FRAME_att_c9, FRAME_att_c20, berserk_frames_attack_club, berserk_run }; + + +/* +============ +T_RadiusDamage +============ +*/ +void T_SlamRadiusDamage(vec3_t point, edict_t *inflictor, edict_t *attacker, float damage, float kick, edict_t *ignore, float radius, mod_t mod) +{ + float points; + edict_t *ent = nullptr; + vec3_t v; + vec3_t dir; + + while ((ent = findradius(ent, inflictor->s.origin, radius)) != nullptr) + { + if (ent == ignore) + continue; + if (!ent->takedamage) + continue; + if (!CanDamage(ent, inflictor)) + continue; + + v = closest_point_to_box(point, ent->s.origin + ent->mins, ent->s.origin + ent->maxs) - point; + points = damage - 0.5f * v.length(); + if (ent == attacker) + points = points * 0.5f; + points = max(1.f, points); + + dir = (ent->s.origin - point).normalized(); + + // keep the point at their feet so they always get knocked up + point[2] = ent->absmin[2]; + T_Damage(ent, inflictor, attacker, dir, point, dir, (int) points, (int) kick, + DAMAGE_RADIUS, mod); + + if (ent->client) + ent->velocity.z = max(270.f, ent->velocity.z); + } +} + +static void berserk_attack_slam(edict_t *self) +{ + gi.sound(self, CHAN_WEAPON, sound_thud, 1, ATTN_NORM, 0); + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_BERSERK_SLAM); + vec3_t f, r, start; + AngleVectors(self->s.angles, f, r, nullptr); + start = M_ProjectFlashSource(self, { 20.f, -14.3f, -21.f }, f, r); + trace_t tr = gi.traceline(self->s.origin, start, self, MASK_SOLID); + gi.WritePosition(tr.endpos); + gi.WriteDir({ 0.f, 0.f, 1.f }); + gi.multicast(tr.endpos, MULTICAST_PHS, false); + self->gravity = 1.0f; + self->velocity = {}; + self->flags |= FL_KILL_VELOCITY; + + T_SlamRadiusDamage(tr.endpos, self, self, 35, 150.f, self, 275, MOD_UNKNOWN); +} + +TOUCH(berserk_jump_touch) (edict_t *self, edict_t *other, const trace_t &tr, bool other_touching_self) -> void +{ + if (self->health <= 0) + { + self->touch = nullptr; + return; + } + + if (self->groundentity) + { + self->s.frame = FRAME_slam18; + + if (self->touch) + berserk_attack_slam(self); + + self->touch = nullptr; + } +} + +static void berserk_high_gravity(edict_t *self) +{ + if (self->velocity[2] < 0) + self->gravity = 2.25f * (800.f / level.gravity); + else + self->gravity = 5.25f * (800.f / level.gravity); +} + +void berserk_jump_takeoff(edict_t *self) +{ + vec3_t forward; + + if (!self->enemy) + return; + + // immediately turn to where we need to go + float length = (self->s.origin - self->enemy->s.origin).length(); + float fwd_speed = length * 1.95f; + vec3_t dir; + PredictAim(self, self->enemy, self->s.origin, fwd_speed, false, 0.f, &dir, nullptr); + self->s.angles[1] = vectoyaw(dir); + AngleVectors(self->s.angles, forward, nullptr, nullptr); + self->s.origin[2] += 1; + self->velocity = forward * fwd_speed; + self->velocity[2] = 450; + self->groundentity = nullptr; + self->monsterinfo.aiflags |= AI_DUCKED; + self->monsterinfo.attack_finished = level.time + 3_sec; + self->touch = berserk_jump_touch; + gi.sound(self, CHAN_WEAPON, sound_jump, 1, ATTN_NORM, 0); + berserk_high_gravity(self); +} + +void berserk_check_landing(edict_t *self) +{ + berserk_high_gravity(self); + + if (self->groundentity) + { + self->monsterinfo.attack_finished = 0_ms; + self->monsterinfo.unduck(self); + self->s.frame = FRAME_slam18; + if (self->touch) + { + berserk_attack_slam(self); + self->touch = nullptr; + } + self->flags &= ~FL_KILL_VELOCITY; + return; + } + + if (level.time > self->monsterinfo.attack_finished) + self->monsterinfo.nextframe = FRAME_slam2; + else + self->monsterinfo.nextframe = FRAME_slam5; +} + +mframe_t berserk_frames_attack_strike[] = { + { ai_charge }, + { ai_charge, 0, berserk_jump_takeoff }, + { ai_move, 0, berserk_high_gravity }, + { ai_move, 0, berserk_high_gravity }, + { ai_move, 0, berserk_check_landing }, + { ai_move, 0, monster_footstep }, + { ai_move }, + { ai_move, 0, monster_footstep }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move, 0, monster_footstep } +}; +MMOVE_T(berserk_move_attack_strike) = { FRAME_slam1, FRAME_slam23, berserk_frames_attack_strike, berserk_run }; + +extern const mmove_t berserk_move_run_attack1; + +MONSTERINFO_MELEE(berserk_melee) (edict_t *self) -> void +{ + if (self->monsterinfo.melee_debounce_time > level.time) + return; + // if we're *almost* ready to land down the hammer from run-attack + // don't switch us + else if (self->monsterinfo.active_move == &berserk_move_run_attack1 && self->s.frame >= FRAME_r_att13) + { + self->monsterinfo.attack_state = AS_STRAIGHT; + self->monsterinfo.attack_finished = 0_ms; + return; + } + + monster_done_dodge(self); + + if (brandom()) + M_SetAnimation(self, &berserk_move_attack_spike); + else + M_SetAnimation(self, &berserk_move_attack_club); +} + +static void berserk_run_attack_speed(edict_t *self) +{ + if (self->enemy && range_to(self, self->enemy) < MELEE_DISTANCE) + { + self->monsterinfo.nextframe = self->s.frame + 6; + monster_done_dodge(self); + } +} + +static void berserk_run_swing(edict_t *self) +{ + berserk_swing(self); + self->monsterinfo.melee_debounce_time = level.time + 0.6_sec; + + if (self->monsterinfo.attack_state == AS_SLIDING) + { + self->monsterinfo.attack_state = AS_STRAIGHT; + monster_done_dodge(self); + } +} + +mframe_t berserk_frames_run_attack1[] = { + { ai_run, 21, berserk_run_attack_speed }, + { ai_run, 11, [](edict_t *self) { berserk_run_attack_speed(self); monster_footstep(self); } }, + { ai_run, 21, berserk_run_attack_speed }, + { ai_run, 25, [](edict_t *self) { berserk_run_attack_speed(self); monster_done_dodge(self); } }, + { ai_run, 18, [](edict_t *self) { berserk_run_attack_speed(self); monster_footstep(self); } }, + { ai_run, 19, berserk_run_attack_speed }, + { ai_run, 21 }, + { ai_run, 11, monster_footstep }, + { ai_run, 21 }, + { ai_run, 25 }, + { ai_run, 18, monster_footstep }, + { ai_run, 19 }, + { ai_run, 21, berserk_run_swing }, + { ai_run, 11, monster_footstep }, + { ai_run, 21 }, + { ai_run, 25 }, + { ai_run, 18, monster_footstep }, + { ai_run, 19, berserk_attack_club } +}; +MMOVE_T(berserk_move_run_attack1) = { FRAME_r_att1, FRAME_r_att18, berserk_frames_run_attack1, berserk_run }; + +MONSTERINFO_ATTACK(berserk_attack) (edict_t *self) -> void +{ + if (self->monsterinfo.melee_debounce_time <= level.time && (range_to(self, self->enemy) < MELEE_DISTANCE)) + berserk_melee(self); + // only jump if they are far enough away for it to make sense (otherwise + // it gets annoying to have them keep hopping over and over again) + else if (!self->spawnflags.has(SPAWNFLAG_BERSERK_NOJUMPING) && (self->timestamp < level.time && brandom()) && range_to(self, self->enemy) > 150.f) + { + M_SetAnimation(self, &berserk_move_attack_strike); + // don't do this for a while, otherwise we just keep doing it + self->timestamp = level.time + 5_sec; + } + else if (self->monsterinfo.active_move == &berserk_move_run1 && (range_to(self, self->enemy) <= RANGE_NEAR)) + { + M_SetAnimation(self, &berserk_move_run_attack1); + self->monsterinfo.nextframe = FRAME_r_att1 + (self->s.frame - FRAME_run1) + 1; + } +} + +mframe_t berserk_frames_pain1[] = { + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move } +}; +MMOVE_T(berserk_move_pain1) = { FRAME_painc1, FRAME_painc4, berserk_frames_pain1, berserk_run }; + +mframe_t berserk_frames_pain2[] = { + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move, 0, monster_footstep }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move, 0, monster_footstep }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move, 0, monster_footstep } +}; +MMOVE_T(berserk_move_pain2) = { FRAME_painb1, FRAME_painb20, berserk_frames_pain2, berserk_run }; + +extern const mmove_t berserk_move_jump, berserk_move_jump2; + +PAIN(berserk_pain) (edict_t *self, edict_t *other, float kick, int damage, const mod_t &mod) -> void +{ + // if we're jumping, don't pain + if ((self->monsterinfo.active_move == &berserk_move_jump) || + (self->monsterinfo.active_move == &berserk_move_jump2) || + (self->monsterinfo.active_move == &berserk_move_attack_strike)) + { + return; + } + + if (level.time < self->pain_debounce_time) + return; + + self->pain_debounce_time = level.time + 3_sec; + gi.sound(self, CHAN_VOICE, sound_pain, 1, ATTN_NORM, 0); + + if (!M_ShouldReactToPain(self, mod)) + return; // no pain anims in nightmare + + monster_done_dodge(self); + + if ((damage <= 50) || (frandom() < 0.5f)) + M_SetAnimation(self, &berserk_move_pain1); + else + M_SetAnimation(self, &berserk_move_pain2); +} + +MONSTERINFO_SETSKIN(berserk_setskin) (edict_t *self) -> void +{ + if (self->health < (self->max_health / 2)) + self->s.skinnum = 1; + else + self->s.skinnum = 0; +} + +void berserk_dead(edict_t *self) +{ + self->mins = { -16, -16, -24 }; + self->maxs = { 16, 16, -8 }; + monster_dead(self); +} + +static void berserk_shrink(edict_t *self) +{ + self->maxs[2] = 0; + self->svflags |= SVF_DEADMONSTER; + gi.linkentity(self); +} + +mframe_t berserk_frames_death1[] = { + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move, 0, monster_footstep }, + { ai_move }, + { ai_move, 0, berserk_shrink }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move } +}; +MMOVE_T(berserk_move_death1) = { FRAME_death1, FRAME_death13, berserk_frames_death1, berserk_dead }; + +mframe_t berserk_frames_death2[] = { + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move, 0, berserk_shrink }, + { ai_move, 0, monster_footstep }, + { ai_move }, + { ai_move }, + { ai_move } +}; +MMOVE_T(berserk_move_death2) = { FRAME_deathc1, FRAME_deathc8, berserk_frames_death2, berserk_dead }; + +DIE(berserk_die) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void +{ + if (M_CheckGib(self, mod)) + { + gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0); + + self->s.skinnum = 0; + + ThrowGibs(self, damage, { + { 2, "models/objects/gibs/bone/tris.md2" }, + { 3, "models/objects/gibs/sm_meat/tris.md2" }, + { 1, "models/objects/gibs/gear/tris.md2" }, + { "models/monsters/berserk/gibs/chest.md2", GIB_SKINNED }, + { "models/monsters/berserk/gibs/hammer.md2", GIB_SKINNED | GIB_UPRIGHT }, + { "models/monsters/berserk/gibs/thigh.md2", GIB_SKINNED }, + { "models/monsters/berserk/gibs/head.md2", GIB_HEAD | GIB_SKINNED } + }); + self->deadflag = true; + return; + } + + if (self->deadflag) + return; + + gi.sound(self, CHAN_VOICE, sound_die, 1, ATTN_NORM, 0); + self->deadflag = true; + self->takedamage = true; + + if (damage >= 50) + M_SetAnimation(self, &berserk_move_death1); + else + M_SetAnimation(self, &berserk_move_death2); +} + +//=========== +// PGM +void berserk_jump_now(edict_t *self) +{ + vec3_t forward, up; + + AngleVectors(self->s.angles, forward, nullptr, up); + self->velocity += (forward * 100); + self->velocity += (up * 300); +} + +void berserk_jump2_now(edict_t *self) +{ + vec3_t forward, up; + + AngleVectors(self->s.angles, forward, nullptr, up); + self->velocity += (forward * 150); + self->velocity += (up * 400); +} + +void berserk_jump_wait_land(edict_t *self) +{ + if (self->groundentity == nullptr) + { + self->monsterinfo.nextframe = self->s.frame; + + if (monster_jump_finished(self)) + self->monsterinfo.nextframe = self->s.frame + 1; + } + else + self->monsterinfo.nextframe = self->s.frame + 1; +} + +mframe_t berserk_frames_jump[] = { + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move, 0, berserk_jump_now }, + { ai_move }, + { ai_move }, + { ai_move, 0, berserk_jump_wait_land }, + { ai_move }, + { ai_move } +}; +MMOVE_T(berserk_move_jump) = { FRAME_jump1, FRAME_jump9, berserk_frames_jump, berserk_run }; + +mframe_t berserk_frames_jump2[] = { + { ai_move, -8 }, + { ai_move, -4 }, + { ai_move, -4 }, + { ai_move, 0, berserk_jump2_now }, + { ai_move }, + { ai_move }, + { ai_move, 0, berserk_jump_wait_land }, + { ai_move }, + { ai_move } +}; +MMOVE_T(berserk_move_jump2) = { FRAME_jump1, FRAME_jump9, berserk_frames_jump2, berserk_run }; + +void berserk_jump(edict_t *self, blocked_jump_result_t result) +{ + if (!self->enemy) + return; + + if (result == blocked_jump_result_t::JUMP_JUMP_UP) + M_SetAnimation(self, &berserk_move_jump2); + else + M_SetAnimation(self, &berserk_move_jump); +} + +MONSTERINFO_BLOCKED(berserk_blocked) (edict_t *self, float dist) -> bool +{ + if (auto result = blocked_checkjump(self, dist); result != blocked_jump_result_t::NO_JUMP) + { + if (result != blocked_jump_result_t::JUMP_TURN) + berserk_jump(self, result); + + return true; + } + + if (blocked_checkplat(self, dist)) + return true; + + return false; +} +// PGM +//=========== + +MONSTERINFO_SIDESTEP(berserk_sidestep) (edict_t *self) -> bool +{ + // if we're jumping or in long pain, don't dodge + if ((self->monsterinfo.active_move == &berserk_move_jump) || + (self->monsterinfo.active_move == &berserk_move_jump2) || + (self->monsterinfo.active_move == &berserk_move_attack_strike) || + (self->monsterinfo.active_move == &berserk_move_pain2)) + return false; + + if (self->monsterinfo.active_move != &berserk_move_run1) + M_SetAnimation(self, &berserk_move_run1); + + return true; +} + +mframe_t berserk_frames_duck[] = { + { ai_move, 0, monster_duck_down }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move, 0, monster_duck_hold }, + { ai_move }, + { ai_move }, + { ai_move, 0, monster_duck_up }, + { ai_move }, + { ai_move } +}; +MMOVE_T(berserk_move_duck) = { FRAME_duck1, FRAME_duck10, berserk_frames_duck, berserk_run }; + +mframe_t berserk_frames_duck2[] = { + { ai_move, 21, monster_duck_down }, + { ai_move, 28 }, + { ai_move, 20 }, + { ai_move, 12, monster_footstep }, + { ai_move, 7 }, + { ai_move, 0, monster_footstep }, + { ai_move }, + { ai_move, 0, monster_duck_hold }, + { ai_move, 0 }, + { ai_move, 0 }, + { ai_move, 0 }, + { ai_move, 0 }, + { ai_move, 0, monster_footstep }, + { ai_move, 0, monster_duck_up }, + { ai_move }, + { ai_move }, + { ai_move, 0, monster_footstep }, +}; +MMOVE_T(berserk_move_duck2) = { FRAME_fall2, FRAME_fall18, berserk_frames_duck2, berserk_run }; + +MONSTERINFO_DUCK(berserk_duck) (edict_t *self, gtime_t eta) -> bool +{ + // berserk only dives forward, and very rarely + if (frandom() >= 0.05f) + { + return false; + } + + // if we're jumping, don't dodge + if ((self->monsterinfo.active_move == &berserk_move_jump) || + (self->monsterinfo.active_move == &berserk_move_jump2)) + { + return false; + } + + M_SetAnimation(self, &berserk_move_duck2); + + return true; +} + +/*QUAKED monster_berserk (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight + */ +void SP_monster_berserk(edict_t *self) +{ + if ( !M_AllowSpawn( self ) ) { + G_FreeEdict( self ); + return; + } + + // pre-caches + sound_pain = gi.soundindex("berserk/berpain2.wav"); + sound_die = gi.soundindex("berserk/berdeth2.wav"); + sound_idle = gi.soundindex("berserk/beridle1.wav"); + sound_idle2 = gi.soundindex("berserk/idle.wav"); + sound_punch = gi.soundindex("berserk/attack.wav"); + sound_search = gi.soundindex("berserk/bersrch1.wav"); + sound_sight = gi.soundindex("berserk/sight.wav"); + sound_thud = gi.soundindex("mutant/thud1.wav"); + sound_jump = gi.soundindex("berserk/jump.wav"); + + self->s.modelindex = gi.modelindex("models/monsters/berserk/tris.md2"); + + gi.modelindex("models/monsters/berserk/gibs/head.md2"); + gi.modelindex("models/monsters/berserk/gibs/chest.md2"); + gi.modelindex("models/monsters/berserk/gibs/hammer.md2"); + gi.modelindex("models/monsters/berserk/gibs/thigh.md2"); + + self->mins = { -16, -16, -24 }; + self->maxs = { 16, 16, 32 }; + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + + self->health = 240 * st.health_multiplier; + self->gib_health = -60; + self->mass = 250; + + self->pain = berserk_pain; + self->die = berserk_die; + + self->monsterinfo.stand = berserk_stand; + self->monsterinfo.walk = berserk_walk; + self->monsterinfo.run = berserk_run; + // pmm + self->monsterinfo.dodge = M_MonsterDodge; + self->monsterinfo.duck = berserk_duck; + self->monsterinfo.unduck = monster_duck_up; + self->monsterinfo.sidestep = berserk_sidestep; + self->monsterinfo.blocked = berserk_blocked; // PGM + // pmm + self->monsterinfo.attack = berserk_attack; + self->monsterinfo.melee = berserk_melee; + self->monsterinfo.sight = berserk_sight; + self->monsterinfo.search = berserk_search; + self->monsterinfo.setskin = berserk_setskin; + + M_SetAnimation(self, &berserk_move_stand); + self->monsterinfo.scale = MODEL_SCALE; + + self->monsterinfo.combat_style = COMBAT_MELEE; + self->monsterinfo.can_jump = !self->spawnflags.has(SPAWNFLAG_BERSERK_NOJUMPING); + self->monsterinfo.drop_height = 256; + self->monsterinfo.jump_height = 40; + + gi.linkentity(self); + + walkmonster_start(self); +} diff --git a/rerelease/m_berserk.h b/rerelease/m_berserk.h new file mode 100644 index 0000000..405f22f --- /dev/null +++ b/rerelease/m_berserk.h @@ -0,0 +1,266 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// G:\quake2\baseq2\models/monsters/berserk + +// This file generated by ModelGen - Do NOT Modify + +enum +{ + FRAME_stand1, + FRAME_stand2, + FRAME_stand3, + FRAME_stand4, + FRAME_stand5, + FRAME_standb1, + FRAME_standb2, + FRAME_standb3, + FRAME_standb4, + FRAME_standb5, + FRAME_standb6, + FRAME_standb7, + FRAME_standb8, + FRAME_standb9, + FRAME_standb10, + FRAME_standb11, + FRAME_standb12, + FRAME_standb13, + FRAME_standb14, + FRAME_standb15, + FRAME_standb16, + FRAME_standb17, + FRAME_standb18, + FRAME_standb19, + FRAME_standb20, + FRAME_walkc1, + FRAME_walkc2, + FRAME_walkc3, + FRAME_walkc4, + FRAME_walkc5, + FRAME_walkc6, + FRAME_walkc7, + FRAME_walkc8, + FRAME_walkc9, + FRAME_walkc10, + FRAME_walkc11, + FRAME_run1, + FRAME_run2, + FRAME_run3, + FRAME_run4, + FRAME_run5, + FRAME_run6, + FRAME_att_a1, + FRAME_att_a2, + FRAME_att_a3, + FRAME_att_a4, + FRAME_att_a5, + FRAME_att_a6, + FRAME_att_a7, + FRAME_att_a8, + FRAME_att_a9, + FRAME_att_a10, + FRAME_att_a11, + FRAME_att_a12, + FRAME_att_a13, + FRAME_att_b1, + FRAME_att_b2, + FRAME_att_b3, + FRAME_att_b4, + FRAME_att_b5, + FRAME_att_b6, + FRAME_att_b7, + FRAME_att_b8, + FRAME_att_b9, + FRAME_att_b10, + FRAME_att_b11, + FRAME_att_b12, + FRAME_att_b13, + FRAME_att_b14, + FRAME_att_b15, + FRAME_att_b16, + FRAME_att_b17, + FRAME_att_b18, + FRAME_att_b19, + FRAME_att_b20, + FRAME_att_b21, + FRAME_att_c1, + FRAME_att_c2, + FRAME_att_c3, + FRAME_att_c4, + FRAME_att_c5, + FRAME_att_c6, + FRAME_att_c7, + FRAME_att_c8, + FRAME_att_c9, + FRAME_att_c10, + FRAME_att_c11, + FRAME_att_c12, + FRAME_att_c13, + FRAME_att_c14, + FRAME_att_c15, + FRAME_att_c16, + FRAME_att_c17, + FRAME_att_c18, + FRAME_att_c19, + FRAME_att_c20, + FRAME_att_c21, + FRAME_att_c22, + FRAME_att_c23, + FRAME_att_c24, + FRAME_att_c25, + FRAME_att_c26, + FRAME_att_c27, + FRAME_att_c28, + FRAME_att_c29, + FRAME_att_c30, + FRAME_att_c31, + FRAME_att_c32, + FRAME_att_c33, + FRAME_att_c34, + FRAME_r_att1, + FRAME_r_att2, + FRAME_r_att3, + FRAME_r_att4, + FRAME_r_att5, + FRAME_r_att6, + FRAME_r_att7, + FRAME_r_att8, + FRAME_r_att9, + FRAME_r_att10, + FRAME_r_att11, + FRAME_r_att12, + FRAME_r_att13, + FRAME_r_att14, + FRAME_r_att15, + FRAME_r_att16, + FRAME_r_att17, + FRAME_r_att18, + FRAME_r_attb1, + FRAME_r_attb2, + FRAME_r_attb3, + FRAME_r_attb4, + FRAME_r_attb5, + FRAME_r_attb6, + FRAME_r_attb7, + FRAME_r_attb8, + FRAME_r_attb9, + FRAME_r_attb10, + FRAME_r_attb11, + FRAME_r_attb12, + FRAME_r_attb13, + FRAME_r_attb14, + FRAME_r_attb15, + FRAME_r_attb16, + FRAME_r_attb17, + FRAME_r_attb18, + FRAME_slam1, + FRAME_slam2, + FRAME_slam3, + FRAME_slam4, + FRAME_slam5, + FRAME_slam6, + FRAME_slam7, + FRAME_slam8, + FRAME_slam9, + FRAME_slam10, + FRAME_slam11, + FRAME_slam12, + FRAME_slam13, + FRAME_slam14, + FRAME_slam15, + FRAME_slam16, + FRAME_slam17, + FRAME_slam18, + FRAME_slam19, + FRAME_slam20, + FRAME_slam21, + FRAME_slam22, + FRAME_slam23, + FRAME_duck1, + FRAME_duck2, + FRAME_duck3, + FRAME_duck4, + FRAME_duck5, + FRAME_duck6, + FRAME_duck7, + FRAME_duck8, + FRAME_duck9, + FRAME_duck10, + FRAME_fall1, + FRAME_fall2, + FRAME_fall3, + FRAME_fall4, + FRAME_fall5, + FRAME_fall6, + FRAME_fall7, + FRAME_fall8, + FRAME_fall9, + FRAME_fall10, + FRAME_fall11, + FRAME_fall12, + FRAME_fall13, + FRAME_fall14, + FRAME_fall15, + FRAME_fall16, + FRAME_fall17, + FRAME_fall18, + FRAME_fall19, + FRAME_fall20, + FRAME_painc1, + FRAME_painc2, + FRAME_painc3, + FRAME_painc4, + FRAME_painb1, + FRAME_painb2, + FRAME_painb3, + FRAME_painb4, + FRAME_painb5, + FRAME_painb6, + FRAME_painb7, + FRAME_painb8, + FRAME_painb9, + FRAME_painb10, + FRAME_painb11, + FRAME_painb12, + FRAME_painb13, + FRAME_painb14, + FRAME_painb15, + FRAME_painb16, + FRAME_painb17, + FRAME_painb18, + FRAME_painb19, + FRAME_painb20, + FRAME_death1, + FRAME_death2, + FRAME_death3, + FRAME_death4, + FRAME_death5, + FRAME_death6, + FRAME_death7, + FRAME_death8, + FRAME_death9, + FRAME_death10, + FRAME_death11, + FRAME_death12, + FRAME_death13, + FRAME_deathc1, + FRAME_deathc2, + FRAME_deathc3, + FRAME_deathc4, + FRAME_deathc5, + FRAME_deathc6, + FRAME_deathc7, + FRAME_deathc8, + // PGM + FRAME_jump1, + FRAME_jump2, + FRAME_jump3, + FRAME_jump4, + FRAME_jump5, + FRAME_jump6, + FRAME_jump7, + FRAME_jump8, + FRAME_jump9 + // PGM +}; + +constexpr float MODEL_SCALE = 1.000000f; diff --git a/rerelease/m_boss2.cpp b/rerelease/m_boss2.cpp new file mode 100644 index 0000000..4c7ecee --- /dev/null +++ b/rerelease/m_boss2.cpp @@ -0,0 +1,768 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +/* +============================================================================== + +boss2 + +============================================================================== +*/ + +#include "g_local.h" +#include "m_boss2.h" +#include "m_flash.h" + +// [Paril-KEX] +constexpr spawnflags_t SPAWNFLAG_BOSS2_N64 = 8_spawnflag; + +bool infront(edict_t *self, edict_t *other); + +static int sound_pain1; +static int sound_pain2; +static int sound_pain3; +static int sound_death; +static int sound_search1; + +MONSTERINFO_SEARCH(boss2_search) (edict_t *self) -> void +{ + if (frandom() < 0.5f) + gi.sound(self, CHAN_VOICE, sound_search1, 1, ATTN_NONE, 0); +} + +void boss2_run(edict_t *self); +void boss2_dead(edict_t *self); +void boss2_attack_mg(edict_t *self); +void boss2_reattack_mg(edict_t *self); + +constexpr int32_t BOSS2_ROCKET_SPEED = 750; + +void Boss2PredictiveRocket(edict_t *self) +{ + vec3_t forward, right; + vec3_t start; + vec3_t dir; + + AngleVectors(self->s.angles, forward, right, nullptr); + + // 1 + start = M_ProjectFlashSource(self, monster_flash_offset[MZ2_BOSS2_ROCKET_1], forward, right); + PredictAim(self, self->enemy, start, BOSS2_ROCKET_SPEED, false, -0.10f, &dir, nullptr); + monster_fire_rocket(self, start, dir, 50, BOSS2_ROCKET_SPEED, MZ2_BOSS2_ROCKET_1); + + // 2 + start = M_ProjectFlashSource(self, monster_flash_offset[MZ2_BOSS2_ROCKET_2], forward, right); + PredictAim(self, self->enemy, start, BOSS2_ROCKET_SPEED, false, -0.05f, &dir, nullptr); + monster_fire_rocket(self, start, dir, 50, BOSS2_ROCKET_SPEED, MZ2_BOSS2_ROCKET_2); + + // 3 + start = M_ProjectFlashSource(self, monster_flash_offset[MZ2_BOSS2_ROCKET_3], forward, right); + PredictAim(self, self->enemy, start, BOSS2_ROCKET_SPEED, false, 0.05f, &dir, nullptr); + monster_fire_rocket(self, start, dir, 50, BOSS2_ROCKET_SPEED, MZ2_BOSS2_ROCKET_3); + + // 4 + start = M_ProjectFlashSource(self, monster_flash_offset[MZ2_BOSS2_ROCKET_4], forward, right); + PredictAim(self, self->enemy, start, BOSS2_ROCKET_SPEED, false, 0.10f, &dir, nullptr); + monster_fire_rocket(self, start, dir, 50, BOSS2_ROCKET_SPEED, MZ2_BOSS2_ROCKET_4); +} + +void Boss2Rocket(edict_t *self) +{ + vec3_t forward, right; + vec3_t start; + vec3_t dir; + vec3_t vec; + + if (self->enemy) + { + if (self->enemy->client && frandom() < 0.9f) + { + Boss2PredictiveRocket(self); + return; + } + } + + AngleVectors(self->s.angles, forward, right, nullptr); + + // 1 + start = M_ProjectFlashSource(self, monster_flash_offset[MZ2_BOSS2_ROCKET_1], forward, right); + vec = self->enemy->s.origin; + vec[2] -= 15; + dir = vec - start; + dir.normalize(); + dir += (right * 0.4f); + dir.normalize(); + monster_fire_rocket(self, start, dir, 50, 500, MZ2_BOSS2_ROCKET_1); + + // 2 + start = M_ProjectFlashSource(self, monster_flash_offset[MZ2_BOSS2_ROCKET_2], forward, right); + vec = self->enemy->s.origin; + dir = vec - start; + dir.normalize(); + dir += (right * 0.025f); + dir.normalize(); + monster_fire_rocket(self, start, dir, 50, 500, MZ2_BOSS2_ROCKET_2); + + // 3 + start = M_ProjectFlashSource(self, monster_flash_offset[MZ2_BOSS2_ROCKET_3], forward, right); + vec = self->enemy->s.origin; + dir = vec - start; + dir.normalize(); + dir += (right * -0.025f); + dir.normalize(); + monster_fire_rocket(self, start, dir, 50, 500, MZ2_BOSS2_ROCKET_3); + + // 4 + start = M_ProjectFlashSource(self, monster_flash_offset[MZ2_BOSS2_ROCKET_4], forward, right); + vec = self->enemy->s.origin; + vec[2] -= 15; + dir = vec - start; + dir.normalize(); + dir += (right * -0.4f); + dir.normalize(); + monster_fire_rocket(self, start, dir, 50, 500, MZ2_BOSS2_ROCKET_4); +} + +void Boss2Rocket64(edict_t *self) +{ + vec3_t forward, right; + vec3_t start; + vec3_t dir; + vec3_t vec; + float time, dist; + + AngleVectors(self->s.angles, forward, right, nullptr); + start = M_ProjectFlashSource(self, monster_flash_offset[MZ2_BOSS2_ROCKET_1], forward, right); + + float scale = self->s.scale ? self->s.scale : 1; + + start[2] += 10.f * scale; + start -= right * 2.f * scale; + start -= right * ((self->count++ % 4) * 8.f * scale); + + if (self->enemy && self->enemy->client && frandom() < 0.9f) + { + // 1 + dir = self->enemy->s.origin - start; + dist = dir.length(); + time = dist / BOSS2_ROCKET_SPEED; + vec = self->enemy->s.origin + (self->enemy->velocity * (time - 0.3f)); + } + else + { + // 1 + vec = self->enemy->s.origin; + vec[2] -= 15; + } + + dir = vec - start; + dir.normalize(); + + monster_fire_rocket(self, start, dir, 35, BOSS2_ROCKET_SPEED, MZ2_BOSS2_ROCKET_1); +} + +void boss2_firebullet_right(edict_t *self) +{ + vec3_t forward, right, start; + AngleVectors(self->s.angles, forward, right, nullptr); + start = M_ProjectFlashSource(self, monster_flash_offset[MZ2_BOSS2_MACHINEGUN_R1], forward, right); + PredictAim(self, self->enemy, start, 0, true, -0.2f, &forward, nullptr); + monster_fire_bullet(self, start, forward, 6, 4, DEFAULT_BULLET_HSPREAD * 3, DEFAULT_BULLET_VSPREAD, MZ2_BOSS2_MACHINEGUN_R1); +} + +void boss2_firebullet_left(edict_t *self) +{ + vec3_t forward, right, start; + AngleVectors(self->s.angles, forward, right, nullptr); + start = M_ProjectFlashSource(self, monster_flash_offset[MZ2_BOSS2_MACHINEGUN_L1], forward, right); + PredictAim(self, self->enemy, start, 0, true, -0.2f, &forward, nullptr); + monster_fire_bullet(self, start, forward, 6, 4, DEFAULT_BULLET_HSPREAD * 3, DEFAULT_BULLET_VSPREAD, MZ2_BOSS2_MACHINEGUN_L1); +} + +void Boss2MachineGun(edict_t *self) +{ + boss2_firebullet_left(self); + boss2_firebullet_right(self); +} + +mframe_t boss2_frames_stand[] = { + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand } +}; +MMOVE_T(boss2_move_stand) = { FRAME_stand30, FRAME_stand50, boss2_frames_stand, nullptr }; + +mframe_t boss2_frames_walk[] = { + { ai_walk, 10 }, + { ai_walk, 10 }, + { ai_walk, 10 }, + { ai_walk, 10 }, + { ai_walk, 10 }, + { ai_walk, 10 }, + { ai_walk, 10 }, + { ai_walk, 10 }, + { ai_walk, 10 }, + { ai_walk, 10 }, + { ai_walk, 10 }, + { ai_walk, 10 }, + { ai_walk, 10 }, + { ai_walk, 10 }, + { ai_walk, 10 }, + { ai_walk, 10 }, + { ai_walk, 10 }, + { ai_walk, 10 }, + { ai_walk, 10 }, + { ai_walk, 10 } +}; +MMOVE_T(boss2_move_walk) = { FRAME_walk1, FRAME_walk20, boss2_frames_walk, nullptr }; + +mframe_t boss2_frames_run[] = { + { ai_run, 10 }, + { ai_run, 10 }, + { ai_run, 10 }, + { ai_run, 10 }, + { ai_run, 10 }, + { ai_run, 10 }, + { ai_run, 10 }, + { ai_run, 10 }, + { ai_run, 10 }, + { ai_run, 10 }, + { ai_run, 10 }, + { ai_run, 10 }, + { ai_run, 10 }, + { ai_run, 10 }, + { ai_run, 10 }, + { ai_run, 10 }, + { ai_run, 10 }, + { ai_run, 10 }, + { ai_run, 10 }, + { ai_run, 10 } +}; +MMOVE_T(boss2_move_run) = { FRAME_walk1, FRAME_walk20, boss2_frames_run, nullptr }; + +mframe_t boss2_frames_attack_pre_mg[] = { + { ai_charge, 2 }, + { ai_charge, 2 }, + { ai_charge, 2 }, + { ai_charge, 2 }, + { ai_charge, 2 }, + { ai_charge, 2 }, + { ai_charge, 2 }, + { ai_charge, 2 }, + { ai_charge, 2, boss2_attack_mg } +}; +MMOVE_T(boss2_move_attack_pre_mg) = { FRAME_attack1, FRAME_attack9, boss2_frames_attack_pre_mg, nullptr }; + +// Loop this +mframe_t boss2_frames_attack_mg[] = { + { ai_charge, 2, Boss2MachineGun }, + { ai_charge, 2, Boss2MachineGun }, + { ai_charge, 2, Boss2MachineGun }, + { ai_charge, 2, Boss2MachineGun }, + { ai_charge, 2, Boss2MachineGun }, + { ai_charge, 2, boss2_reattack_mg } +}; +MMOVE_T(boss2_move_attack_mg) = { FRAME_attack10, FRAME_attack15, boss2_frames_attack_mg, nullptr }; + +// [Paril-KEX] +void Boss2HyperBlaster(edict_t *self) +{ + vec3_t forward, right, target; + vec3_t start; + monster_muzzleflash_id_t id = (self->s.frame & 1) ? MZ2_BOSS2_MACHINEGUN_L2 : MZ2_BOSS2_MACHINEGUN_R2; + + AngleVectors(self->s.angles, forward, right, nullptr); + start = M_ProjectFlashSource(self, monster_flash_offset[id], forward, right); + target = self->enemy->s.origin; + target[2] += self->enemy->viewheight; + forward = target - start; + forward.normalize(); + + monster_fire_blaster(self, start, forward, 2, 1000, id, (self->s.frame % 4) ? EF_NONE : EF_HYPERBLASTER); +} + +mframe_t boss2_frames_attack_hb[] = { + { ai_charge, 2, Boss2HyperBlaster }, + { ai_charge, 2, Boss2HyperBlaster }, + { ai_charge, 2, Boss2HyperBlaster }, + { ai_charge, 2, Boss2HyperBlaster }, + { ai_charge, 2, Boss2HyperBlaster }, + { ai_charge, 2, [](edict_t *self) -> void { Boss2HyperBlaster(self); boss2_reattack_mg(self); } } +}; +MMOVE_T(boss2_move_attack_hb) = { FRAME_attack10, FRAME_attack15, boss2_frames_attack_hb, nullptr }; + +mframe_t boss2_frames_attack_post_mg[] = { + { ai_charge, 2 }, + { ai_charge, 2 }, + { ai_charge, 2 }, + { ai_charge, 2 } +}; +MMOVE_T(boss2_move_attack_post_mg) = { FRAME_attack16, FRAME_attack19, boss2_frames_attack_post_mg, boss2_run }; + +mframe_t boss2_frames_attack_rocket[] = { + { ai_charge, 2 }, + { ai_charge, 2 }, + { ai_charge, 2 }, + { ai_charge, 2 }, + { ai_charge, 2 }, + { ai_charge, 2 }, + { ai_charge, 2 }, + { ai_charge, 2 }, + { ai_charge, 2 }, + { ai_charge, 2 }, + { ai_charge, 2 }, + { ai_charge, 2 }, + { ai_move, -5, Boss2Rocket }, + { ai_charge, 2 }, + { ai_charge, 2 }, + { ai_charge, 2 }, + { ai_charge, 2 }, + { ai_charge, 2 }, + { ai_charge, 2 }, + { ai_charge, 2 }, + { ai_charge, 2 } +}; +MMOVE_T(boss2_move_attack_rocket) = { FRAME_attack20, FRAME_attack40, boss2_frames_attack_rocket, boss2_run }; + +// [Paril-KEX] n64 rocket behavior +mframe_t boss2_frames_attack_rocket2[] = { + { ai_charge, 2, Boss2Rocket64 }, + { ai_charge, 2 }, + { ai_charge, 2 }, + { ai_charge, 2 }, + { ai_charge, 2, Boss2Rocket64 }, + { ai_charge, 2 }, + { ai_charge, 2 }, + { ai_charge, 2 }, + { ai_charge, 2, Boss2Rocket64 }, + { ai_charge, 2 }, + { ai_charge, 2 }, + { ai_charge, 2 }, + { ai_charge, 2, Boss2Rocket64 }, + { ai_charge, 2 }, + { ai_charge, 2 }, + { ai_charge, 2 }, + { ai_charge, 2, Boss2Rocket64 }, + { ai_charge, 2 }, + { ai_charge, 2 }, + { ai_charge, 2 } +}; +MMOVE_T(boss2_move_attack_rocket2) = { FRAME_attack20, FRAME_attack39, boss2_frames_attack_rocket2, boss2_run }; + +mframe_t boss2_frames_pain_heavy[] = { + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move } +}; +MMOVE_T(boss2_move_pain_heavy) = { FRAME_pain2, FRAME_pain19, boss2_frames_pain_heavy, boss2_run }; + +mframe_t boss2_frames_pain_light[] = { + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move } +}; +MMOVE_T(boss2_move_pain_light) = { FRAME_pain20, FRAME_pain23, boss2_frames_pain_light, boss2_run }; + +static void boss2_shrink(edict_t *self) +{ + self->maxs.z = 50.f; + gi.linkentity(self); +} + +mframe_t boss2_frames_death[] = { + { ai_move, 0, BossExplode }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move, 0, boss2_shrink }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move } +}; +MMOVE_T(boss2_move_death) = { FRAME_death2, FRAME_death50, boss2_frames_death, boss2_dead }; + +MONSTERINFO_STAND(boss2_stand) (edict_t *self) -> void +{ + M_SetAnimation(self, &boss2_move_stand); +} + +MONSTERINFO_RUN(boss2_run) (edict_t *self) -> void +{ + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + M_SetAnimation(self, &boss2_move_stand); + else + M_SetAnimation(self, &boss2_move_run); +} + +MONSTERINFO_WALK(boss2_walk) (edict_t *self) -> void +{ + M_SetAnimation(self, &boss2_move_walk); +} + +MONSTERINFO_ATTACK(boss2_attack) (edict_t *self) -> void +{ + vec3_t vec; + float range; + + vec = self->enemy->s.origin - self->s.origin; + range = vec.length(); + + if (range <= 125 || frandom() <= 0.6f) + M_SetAnimation(self, self->spawnflags.has(SPAWNFLAG_BOSS2_N64) ? &boss2_move_attack_hb : &boss2_move_attack_pre_mg); + else + M_SetAnimation(self, self->spawnflags.has(SPAWNFLAG_BOSS2_N64) ? &boss2_move_attack_rocket2 : &boss2_move_attack_rocket); +} + +void boss2_attack_mg(edict_t *self) +{ + M_SetAnimation(self, self->spawnflags.has(SPAWNFLAG_BOSS2_N64) ? &boss2_move_attack_hb : &boss2_move_attack_mg); +} + +void boss2_reattack_mg(edict_t *self) +{ + if (infront(self, self->enemy) && frandom() <= 0.7f) + boss2_attack_mg(self); + else + M_SetAnimation(self, &boss2_move_attack_post_mg); +} + +PAIN(boss2_pain) (edict_t *self, edict_t *other, float kick, int damage, const mod_t &mod) -> void +{ + if (level.time < self->pain_debounce_time) + return; + + self->pain_debounce_time = level.time + 3_sec; + + // American wanted these at no attenuation + if (damage < 10) + gi.sound(self, CHAN_VOICE, sound_pain3, 1, ATTN_NONE, 0); + else if (damage < 30) + gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NONE, 0); + else + gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NONE, 0); + + if (!M_ShouldReactToPain(self, mod)) + return; // no pain anims in nightmare + + if (damage < 10) + M_SetAnimation(self, &boss2_move_pain_light); + else if (damage < 30) + M_SetAnimation(self, &boss2_move_pain_light); + else + M_SetAnimation(self, &boss2_move_pain_heavy); +} + +MONSTERINFO_SETSKIN(boss2_setskin) (edict_t *self) -> void +{ + if (self->health < (self->max_health / 2)) + self->s.skinnum = 1; + else + self->s.skinnum = 0; +} + +static void boss2_gib(edict_t *self) +{ + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_EXPLOSION1_BIG); + gi.WritePosition(self->s.origin); + gi.multicast(self->s.origin, MULTICAST_PHS, false); + + self->s.sound = 0; + self->s.skinnum /= 2; + + self->gravityVector.z = -1.0f; + + ThrowGibs(self, 500, { + { 2, "models/objects/gibs/sm_meat/tris.md2" }, + { 2, "models/objects/gibs/sm_metal/tris.md2", GIB_METALLIC }, + { "models/monsters/boss2/gibs/chest.md2", GIB_SKINNED }, + { 2, "models/monsters/boss2/gibs/chaingun.md2", GIB_SKINNED | GIB_UPRIGHT }, + { "models/monsters/boss2/gibs/cpu.md2", GIB_SKINNED | GIB_UPRIGHT }, + { "models/monsters/boss2/gibs/engine.md2", GIB_SKINNED }, + { "models/monsters/boss2/gibs/rocket.md2", GIB_SKINNED | GIB_UPRIGHT }, + { "models/monsters/boss2/gibs/spine.md2", GIB_SKINNED }, + { 2, "models/monsters/boss2/gibs/wing.md2", GIB_SKINNED | GIB_UPRIGHT }, + { "models/monsters/boss2/gibs/larm.md2", GIB_SKINNED | GIB_UPRIGHT }, + { "models/monsters/boss2/gibs/rarm.md2", GIB_SKINNED | GIB_UPRIGHT }, + { "models/monsters/boss2/gibs/larm.md2", 2.0f, GIB_SKINNED | GIB_UPRIGHT }, + { "models/monsters/boss2/gibs/rarm.md2", 2.0f, GIB_SKINNED | GIB_UPRIGHT }, + { "models/monsters/boss2/gibs/larm.md2", 1.35f, GIB_SKINNED | GIB_UPRIGHT }, + { "models/monsters/boss2/gibs/rarm.md2", 1.35f, GIB_SKINNED | GIB_UPRIGHT }, + { "models/monsters/boss2/gibs/head.md2", GIB_SKINNED | GIB_METALLIC | GIB_HEAD } + }); +} + +void boss2_dead(edict_t *self) +{ + // no blowy on deady + if (self->spawnflags.has(SPAWNFLAG_MONSTER_DEAD)) + { + self->deadflag = false; + self->takedamage = true; + return; + } + + boss2_gib(self); +} + +DIE(boss2_die) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void +{ + if (self->spawnflags.has(SPAWNFLAG_MONSTER_DEAD)) + { + // check for gib + if (M_CheckGib(self, mod)) + { + boss2_gib(self); + self->deadflag = true; + return; + } + + if (self->deadflag) + return; + } + else + { + gi.sound(self, CHAN_VOICE, sound_death, 1, ATTN_NONE, 0); + self->deadflag = true; + self->takedamage = false; + self->count = 0; + self->velocity = {}; + self->gravityVector.z *= 0.30f; + } + + M_SetAnimation(self, &boss2_move_death); +} + +MONSTERINFO_CHECKATTACK(Boss2_CheckAttack) (edict_t *self) -> bool +{ + vec3_t spot1, spot2; + vec3_t temp; + float chance; + trace_t tr; + float enemy_yaw; + + if (self->enemy->health > 0) + { + // see if any entities are in the way of the shot + spot1 = self->s.origin; + spot1[2] += self->viewheight; + spot2 = self->enemy->s.origin; + spot2[2] += self->enemy->viewheight; + + tr = gi.traceline(spot1, spot2, self, CONTENTS_SOLID | CONTENTS_PLAYER | CONTENTS_MONSTER | CONTENTS_SLIME | CONTENTS_LAVA); + + // do we have a clear shot? + if (tr.ent != self->enemy && !(tr.ent->svflags & SVF_PLAYER)) + { + // PGM - we want them to go ahead and shoot at info_notnulls if they can. + if (self->enemy->solid != SOLID_NOT || tr.fraction < 1.0f) // PGM + return false; + } + } + + float enemy_range = range_to(self, self->enemy); + temp = self->enemy->s.origin - self->s.origin; + enemy_yaw = vectoyaw(temp); + + self->ideal_yaw = enemy_yaw; + + // melee attack + if (enemy_range <= RANGE_MELEE) + { + if (self->monsterinfo.melee) + self->monsterinfo.attack_state = AS_MELEE; + else + self->monsterinfo.attack_state = AS_MISSILE; + return true; + } + + // missile attack + if (!self->monsterinfo.attack) + return false; + + if (level.time < self->monsterinfo.attack_finished) + return false; + + if (enemy_range > RANGE_MID) + return false; + + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + { + chance = 0.4f; + } + else if (enemy_range <= RANGE_MELEE) + { + chance = 0.8f; + } + else if (enemy_range <= RANGE_NEAR) + { + chance = 0.8f; + } + else + { + chance = 0.8f; + } + + // PGM - go ahead and shoot every time if it's a info_notnull + if ((frandom() < chance) || (self->enemy->solid == SOLID_NOT)) + { + self->monsterinfo.attack_state = AS_MISSILE; + self->monsterinfo.attack_finished = level.time + random_time(2_sec); + return true; + } + + if (self->flags & FL_FLY) + { + if (frandom() < 0.3f) + self->monsterinfo.attack_state = AS_SLIDING; + else + self->monsterinfo.attack_state = AS_STRAIGHT; + } + + return false; +} + +/*QUAKED monster_boss2 (1 .5 0) (-56 -56 0) (56 56 80) Ambush Trigger_Spawn Sight Hyperblaster + */ +void SP_monster_boss2(edict_t *self) +{ + if ( !M_AllowSpawn( self ) ) { + G_FreeEdict( self ); + return; + } + + sound_pain1 = gi.soundindex("bosshovr/bhvpain1.wav"); + sound_pain2 = gi.soundindex("bosshovr/bhvpain2.wav"); + sound_pain3 = gi.soundindex("bosshovr/bhvpain3.wav"); + sound_death = gi.soundindex("bosshovr/bhvdeth1.wav"); + sound_search1 = gi.soundindex("bosshovr/bhvunqv1.wav"); + + gi.soundindex("tank/rocket.wav"); + + if (self->spawnflags.has(SPAWNFLAG_BOSS2_N64)) + gi.soundindex("flyer/flyatck3.wav"); + else + gi.soundindex("infantry/infatck1.wav"); + + self->monsterinfo.weapon_sound = gi.soundindex("bosshovr/bhvengn1.wav"); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex("models/monsters/boss2/tris.md2"); + + gi.modelindex("models/monsters/boss2/gibs/chaingun.md2"); + gi.modelindex("models/monsters/boss2/gibs/chest.md2"); + gi.modelindex("models/monsters/boss2/gibs/cpu.md2"); + gi.modelindex("models/monsters/boss2/gibs/engine.md2"); + gi.modelindex("models/monsters/boss2/gibs/head.md2"); + gi.modelindex("models/monsters/boss2/gibs/larm.md2"); + gi.modelindex("models/monsters/boss2/gibs/rarm.md2"); + gi.modelindex("models/monsters/boss2/gibs/rocket.md2"); + gi.modelindex("models/monsters/boss2/gibs/spine.md2"); + gi.modelindex("models/monsters/boss2/gibs/wing.md2"); + + self->mins = { -56, -56, 0 }; + self->maxs = { 56, 56, 80 }; + + self->health = 2000 * st.health_multiplier; + self->gib_health = -200; + self->mass = 1000; + + self->yaw_speed = 50; + + self->flags |= FL_IMMUNE_LASER; + + self->pain = boss2_pain; + self->die = boss2_die; + + self->monsterinfo.stand = boss2_stand; + self->monsterinfo.walk = boss2_walk; + self->monsterinfo.run = boss2_run; + self->monsterinfo.attack = boss2_attack; + self->monsterinfo.search = boss2_search; + self->monsterinfo.checkattack = Boss2_CheckAttack; + self->monsterinfo.setskin = boss2_setskin; + gi.linkentity(self); + + M_SetAnimation(self, &boss2_move_stand); + self->monsterinfo.scale = MODEL_SCALE; + + // [Paril-KEX] + self->monsterinfo.aiflags |= AI_IGNORE_SHOTS; + + flymonster_start(self); +} diff --git a/rerelease/m_boss2.h b/rerelease/m_boss2.h new file mode 100644 index 0000000..df571d5 --- /dev/null +++ b/rerelease/m_boss2.h @@ -0,0 +1,192 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// G:\quake2\baseq2\models/monsters/boss2 + +// This file generated by ModelGen - Do NOT Modify + +enum +{ + FRAME_stand30, + FRAME_stand31, + FRAME_stand32, + FRAME_stand33, + FRAME_stand34, + FRAME_stand35, + FRAME_stand36, + FRAME_stand37, + FRAME_stand38, + FRAME_stand39, + FRAME_stand40, + FRAME_stand41, + FRAME_stand42, + FRAME_stand43, + FRAME_stand44, + FRAME_stand45, + FRAME_stand46, + FRAME_stand47, + FRAME_stand48, + FRAME_stand49, + FRAME_stand50, + FRAME_stand1, + FRAME_stand2, + FRAME_stand3, + FRAME_stand4, + FRAME_stand5, + FRAME_stand6, + FRAME_stand7, + FRAME_stand8, + FRAME_stand9, + FRAME_stand10, + FRAME_stand11, + FRAME_stand12, + FRAME_stand13, + FRAME_stand14, + FRAME_stand15, + FRAME_stand16, + FRAME_stand17, + FRAME_stand18, + FRAME_stand19, + FRAME_stand20, + FRAME_stand21, + FRAME_stand22, + FRAME_stand23, + FRAME_stand24, + FRAME_stand25, + FRAME_stand26, + FRAME_stand27, + FRAME_stand28, + FRAME_stand29, + FRAME_walk1, + FRAME_walk2, + FRAME_walk3, + FRAME_walk4, + FRAME_walk5, + FRAME_walk6, + FRAME_walk7, + FRAME_walk8, + FRAME_walk9, + FRAME_walk10, + FRAME_walk11, + FRAME_walk12, + FRAME_walk13, + FRAME_walk14, + FRAME_walk15, + FRAME_walk16, + FRAME_walk17, + FRAME_walk18, + FRAME_walk19, + FRAME_walk20, + FRAME_attack1, + FRAME_attack2, + FRAME_attack3, + FRAME_attack4, + FRAME_attack5, + FRAME_attack6, + FRAME_attack7, + FRAME_attack8, + FRAME_attack9, + FRAME_attack10, + FRAME_attack11, + FRAME_attack12, + FRAME_attack13, + FRAME_attack14, + FRAME_attack15, + FRAME_attack16, + FRAME_attack17, + FRAME_attack18, + FRAME_attack19, + FRAME_attack20, + FRAME_attack21, + FRAME_attack22, + FRAME_attack23, + FRAME_attack24, + FRAME_attack25, + FRAME_attack26, + FRAME_attack27, + FRAME_attack28, + FRAME_attack29, + FRAME_attack30, + FRAME_attack31, + FRAME_attack32, + FRAME_attack33, + FRAME_attack34, + FRAME_attack35, + FRAME_attack36, + FRAME_attack37, + FRAME_attack38, + FRAME_attack39, + FRAME_attack40, + FRAME_pain2, + FRAME_pain3, + FRAME_pain4, + FRAME_pain5, + FRAME_pain6, + FRAME_pain7, + FRAME_pain8, + FRAME_pain9, + FRAME_pain10, + FRAME_pain11, + FRAME_pain12, + FRAME_pain13, + FRAME_pain14, + FRAME_pain15, + FRAME_pain16, + FRAME_pain17, + FRAME_pain18, + FRAME_pain19, + FRAME_pain20, + FRAME_pain21, + FRAME_pain22, + FRAME_pain23, + FRAME_death2, + FRAME_death3, + FRAME_death4, + FRAME_death5, + FRAME_death6, + FRAME_death7, + FRAME_death8, + FRAME_death9, + FRAME_death10, + FRAME_death11, + FRAME_death12, + FRAME_death13, + FRAME_death14, + FRAME_death15, + FRAME_death16, + FRAME_death17, + FRAME_death18, + FRAME_death19, + FRAME_death20, + FRAME_death21, + FRAME_death22, + FRAME_death23, + FRAME_death24, + FRAME_death25, + FRAME_death26, + FRAME_death27, + FRAME_death28, + FRAME_death29, + FRAME_death30, + FRAME_death31, + FRAME_death32, + FRAME_death33, + FRAME_death34, + FRAME_death35, + FRAME_death36, + FRAME_death37, + FRAME_death38, + FRAME_death39, + FRAME_death40, + FRAME_death41, + FRAME_death42, + FRAME_death43, + FRAME_death44, + FRAME_death45, + FRAME_death46, + FRAME_death47, + FRAME_death48, + FRAME_death49, + FRAME_death50 +}; + +constexpr float MODEL_SCALE = 1.000000f; diff --git a/rerelease/m_boss3.cpp b/rerelease/m_boss3.cpp new file mode 100644 index 0000000..68647b4 --- /dev/null +++ b/rerelease/m_boss3.cpp @@ -0,0 +1,61 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +/* +============================================================================== + +boss3 + +============================================================================== +*/ + +#include "g_local.h" +#include "m_boss32.h" + +USE(Use_Boss3) (edict_t *self, edict_t *other, edict_t *activator) -> void +{ + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_BOSSTPORT); + gi.WritePosition(self->s.origin); + gi.multicast(self->s.origin, MULTICAST_PHS, false); + + // just hide, don't kill ent so we can trigger it again + self->svflags |= SVF_NOCLIENT; + self->solid = SOLID_NOT; +} + +THINK(Think_Boss3Stand) (edict_t *self) -> void +{ + if (self->s.frame == FRAME_stand260) + self->s.frame = FRAME_stand201; + else + self->s.frame++; + self->nextthink = level.time + 10_hz; +} + +/*QUAKED monster_boss3_stand (1 .5 0) (-32 -32 0) (32 32 90) + +Just stands and cycles in one place until targeted, then teleports away. +*/ +void SP_monster_boss3_stand(edict_t *self) +{ + if ( !M_AllowSpawn( self ) ) { + G_FreeEdict( self ); + return; + } + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->model = "models/monsters/boss3/rider/tris.md2"; + self->s.modelindex = gi.modelindex(self->model); + self->s.frame = FRAME_stand201; + + gi.soundindex("misc/bigtele.wav"); + + self->mins = { -32, -32, 0 }; + self->maxs = { 32, 32, 90 }; + + self->use = Use_Boss3; + self->think = Think_Boss3Stand; + self->nextthink = level.time + FRAME_TIME_S; + gi.linkentity(self); +} diff --git a/rerelease/m_boss31.cpp b/rerelease/m_boss31.cpp new file mode 100644 index 0000000..b164afa --- /dev/null +++ b/rerelease/m_boss31.cpp @@ -0,0 +1,720 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +/* +============================================================================== + +jorg + +============================================================================== +*/ + +#include "g_local.h" +#include "m_boss31.h" +#include "m_flash.h" + +void SP_monster_makron(edict_t *self); + +static int sound_pain1; +static int sound_pain2; +static int sound_pain3; +static int sound_idle; +static int sound_death; +static int sound_search1; +static int sound_search2; +static int sound_search3; +static int sound_attack1, sound_attack1_loop, sound_attack1_end; +static int sound_attack2, sound_bfg_fire; +static int sound_firegun; +static int sound_step_left; +static int sound_step_right; +static int sound_death_hit; + +void MakronToss(edict_t *self); + +void jorg_attack1_end_sound(edict_t *self) +{ + if (self->monsterinfo.weapon_sound) + { + gi.sound(self, CHAN_WEAPON, sound_attack1_end, 1, ATTN_NORM, 0); + self->monsterinfo.weapon_sound = 0; + } +} + +MONSTERINFO_SEARCH(jorg_search) (edict_t *self) -> void +{ + float r; + + r = frandom(); + + if (r <= 0.3f) + gi.sound(self, CHAN_VOICE, sound_search1, 1, ATTN_NORM, 0); + else if (r <= 0.6f) + gi.sound(self, CHAN_VOICE, sound_search2, 1, ATTN_NORM, 0); + else + gi.sound(self, CHAN_VOICE, sound_search3, 1, ATTN_NORM, 0); +} + +void jorg_dead(edict_t *self); +void jorgBFG(edict_t *self); +void jorg_firebullet(edict_t *self); +void jorg_reattack1(edict_t *self); +void jorg_attack1(edict_t *self); +void jorg_idle(edict_t *self); +void jorg_step_left(edict_t *self); +void jorg_step_right(edict_t *self); +void jorg_death_hit(edict_t *self); + +// +// stand +// + +mframe_t jorg_frames_stand[] = { + { ai_stand, 0, jorg_idle }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, // 10 + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, // 20 + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, // 30 + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand, 19 }, + { ai_stand, 11, jorg_step_left }, + { ai_stand }, + { ai_stand }, + { ai_stand, 6 }, + { ai_stand, 9, jorg_step_right }, + { ai_stand }, // 40 + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand, -2, nullptr }, + { ai_stand, -17, jorg_step_left }, + { ai_stand }, + { ai_stand, -12 }, // 50 + { ai_stand, -14, jorg_step_right } // 51 +}; +MMOVE_T(jorg_move_stand) = { FRAME_stand01, FRAME_stand51, jorg_frames_stand, nullptr }; + +void jorg_idle (edict_t *self) +{ + gi.sound(self, CHAN_VOICE, sound_idle, 1, ATTN_NORM, 0); +} + +void jorg_death_hit(edict_t *self) +{ + gi.sound(self, CHAN_BODY, sound_death_hit, 1, ATTN_NORM, 0); +} + +void jorg_step_left(edict_t *self) +{ + gi.sound(self, CHAN_BODY, sound_step_left, 1, ATTN_NORM, 0); +} + +void jorg_step_right(edict_t *self) +{ + gi.sound(self, CHAN_BODY, sound_step_right, 1, ATTN_NORM, 0); +} + +MONSTERINFO_STAND(jorg_stand) (edict_t *self) -> void +{ + M_SetAnimation(self, &jorg_move_stand); + + jorg_attack1_end_sound(self); +} + +mframe_t jorg_frames_run[] = { + { ai_run, 17, jorg_step_left }, + { ai_run }, + { ai_run }, + { ai_run }, + { ai_run, 12 }, + { ai_run, 8 }, + { ai_run, 10 }, + { ai_run, 33, jorg_step_right }, + { ai_run }, + { ai_run }, + { ai_run }, + { ai_run, 9 }, + { ai_run, 9 }, + { ai_run, 9 } +}; +MMOVE_T(jorg_move_run) = { FRAME_walk06, FRAME_walk19, jorg_frames_run, nullptr }; + +// +// walk +// +#if 0 +mframe_t jorg_frames_start_walk[] = { + { ai_walk, 5 }, + { ai_walk, 6 }, + { ai_walk, 7 }, + { ai_walk, 9 }, + { ai_walk, 15 } +}; +MMOVE_T(jorg_move_start_walk) = { FRAME_walk01, FRAME_walk05, jorg_frames_start_walk, nullptr }; +#endif + +mframe_t jorg_frames_walk[] = { + { ai_walk, 17 }, + { ai_walk }, + { ai_walk }, + { ai_walk }, + { ai_walk, 12 }, + { ai_walk, 8 }, + { ai_walk, 10 }, + { ai_walk, 33 }, + { ai_walk }, + { ai_walk }, + { ai_walk }, + { ai_walk, 9 }, + { ai_walk, 9 }, + { ai_walk, 9 } +}; +MMOVE_T(jorg_move_walk) = { FRAME_walk06, FRAME_walk19, jorg_frames_walk, nullptr }; + +#if 0 +mframe_t jorg_frames_end_walk[] = { + { ai_walk, 11 }, + { ai_walk }, + { ai_walk }, + { ai_walk }, + { ai_walk, 8 }, + { ai_walk, -8 } +}; +MMOVE_T(jorg_move_end_walk) = { FRAME_walk20, FRAME_walk25, jorg_frames_end_walk, nullptr }; +#endif + +MONSTERINFO_WALK(jorg_walk) (edict_t *self) -> void +{ + M_SetAnimation(self, &jorg_move_walk); +} + +MONSTERINFO_RUN(jorg_run) (edict_t *self) -> void +{ + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + M_SetAnimation(self, &jorg_move_stand); + else + M_SetAnimation(self, &jorg_move_run); + + jorg_attack1_end_sound(self); +} + +mframe_t jorg_frames_pain3[] = { + { ai_move, -28 }, + { ai_move, -6 }, + { ai_move, -3, jorg_step_left }, + { ai_move, -9 }, + { ai_move, 0, jorg_step_right }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move, -7 }, + { ai_move, 1 }, + { ai_move, -11 }, + { ai_move, -4 }, + { ai_move }, + { ai_move }, + { ai_move, 10 }, + { ai_move, 11 }, + { ai_move }, + { ai_move, 10 }, + { ai_move, 3 }, + { ai_move, 10 }, + { ai_move, 7, jorg_step_left }, + { ai_move, 17 }, + { ai_move }, + { ai_move }, + { ai_move, 0, jorg_step_right } +}; +MMOVE_T(jorg_move_pain3) = { FRAME_pain301, FRAME_pain325, jorg_frames_pain3, jorg_run }; + +mframe_t jorg_frames_pain2[] = { + { ai_move }, + { ai_move }, + { ai_move } +}; +MMOVE_T(jorg_move_pain2) = { FRAME_pain201, FRAME_pain203, jorg_frames_pain2, jorg_run }; + +mframe_t jorg_frames_pain1[] = { + { ai_move }, + { ai_move }, + { ai_move } +}; +MMOVE_T(jorg_move_pain1) = { FRAME_pain101, FRAME_pain103, jorg_frames_pain1, jorg_run }; + +mframe_t jorg_frames_death1[] = { + { ai_move, 0, BossExplode }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move, -2 }, + { ai_move, -5 }, + { ai_move, -8 }, + { ai_move, -15, jorg_step_left }, + { ai_move }, // 10 + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move, -11 }, + { ai_move, -25 }, + { ai_move, -10, jorg_step_right }, + { ai_move }, + { ai_move }, // 20 + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move, -21 }, + { ai_move, -10 }, + { ai_move, -16, jorg_step_left }, + { ai_move }, + { ai_move }, + { ai_move }, // 30 + { ai_move }, + { ai_move }, + { ai_move, 22 }, + { ai_move, 33, jorg_step_left }, + { ai_move }, + { ai_move }, + { ai_move, 28 }, + { ai_move, 28, jorg_step_right }, + { ai_move }, + { ai_move }, // 40 + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move, -19 }, + { ai_move, 0, jorg_death_hit }, + { ai_move }, + { ai_move } // 50 +}; +MMOVE_T(jorg_move_death) = { FRAME_death01, FRAME_death50, jorg_frames_death1, jorg_dead }; + +mframe_t jorg_frames_attack2[] = { + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge, 0, jorgBFG }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move } +}; +MMOVE_T(jorg_move_attack2) = { FRAME_attak201, FRAME_attak213, jorg_frames_attack2, jorg_run }; + +mframe_t jorg_frames_start_attack1[] = { + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge } +}; +MMOVE_T(jorg_move_start_attack1) = { FRAME_attak101, FRAME_attak108, jorg_frames_start_attack1, jorg_attack1 }; + +mframe_t jorg_frames_attack1[] = { + { ai_charge, 0, jorg_firebullet }, + { ai_charge, 0, jorg_firebullet }, + { ai_charge, 0, jorg_firebullet }, + { ai_charge, 0, jorg_firebullet }, + { ai_charge, 0, jorg_firebullet }, + { ai_charge, 0, jorg_firebullet } +}; +MMOVE_T(jorg_move_attack1) = { FRAME_attak109, FRAME_attak114, jorg_frames_attack1, jorg_reattack1 }; + +mframe_t jorg_frames_end_attack1[] = { + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move } +}; +MMOVE_T(jorg_move_end_attack1) = { FRAME_attak115, FRAME_attak118, jorg_frames_end_attack1, jorg_run }; + +void jorg_reattack1(edict_t *self) +{ + if (visible(self, self->enemy)) + { + if (frandom() < 0.9f) + M_SetAnimation(self, &jorg_move_attack1); + else + { + M_SetAnimation(self, &jorg_move_end_attack1); + jorg_attack1_end_sound(self); + } + } + else + { + M_SetAnimation(self, &jorg_move_end_attack1); + jorg_attack1_end_sound(self); + } +} + +void jorg_attack1(edict_t *self) +{ + M_SetAnimation(self, &jorg_move_attack1); +} + +PAIN(jorg_pain) (edict_t *self, edict_t *other, float kick, int damage, const mod_t &mod) -> void +{ + if (level.time < self->pain_debounce_time) + return; + + // Lessen the chance of him going into his pain frames if he takes little damage + if (mod.id != MOD_CHAINFIST) + { + if (damage <= 40) + if (frandom() <= 0.6f) + return; + + /* + If he's entering his attack1 or using attack1, lessen the chance of him + going into pain + */ + + if ((self->s.frame >= FRAME_attak101) && (self->s.frame <= FRAME_attak108)) + if (frandom() <= 0.005f) + return; + + if ((self->s.frame >= FRAME_attak109) && (self->s.frame <= FRAME_attak114)) + if (frandom() <= 0.00005f) + return; + + if ((self->s.frame >= FRAME_attak201) && (self->s.frame <= FRAME_attak208)) + if (frandom() <= 0.005f) + return; + } + + self->pain_debounce_time = level.time + 3_sec; + + bool do_pain3 = false; + + if (damage > 50) + { + if (damage <= 100) + { + gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0); + } + else + { + if (frandom() <= 0.3f) + { + do_pain3 = true; + gi.sound(self, CHAN_VOICE, sound_pain3, 1, ATTN_NORM, 0); + } + } + } + + if (!M_ShouldReactToPain(self, mod)) + return; // no pain anims in nightmare + + jorg_attack1_end_sound(self); + + if (damage <= 50) + M_SetAnimation(self, &jorg_move_pain1); + else if (damage <= 100) + M_SetAnimation(self, &jorg_move_pain2); + else if (do_pain3) + M_SetAnimation(self, &jorg_move_pain3); +} + +MONSTERINFO_SETSKIN(jorg_setskin) (edict_t *self) -> void +{ + if (self->health < (self->max_health / 2)) + self->s.skinnum = 1; + else + self->s.skinnum = 0; +} + +void jorgBFG(edict_t *self) +{ + vec3_t forward, right; + vec3_t start; + vec3_t dir; + vec3_t vec; + + AngleVectors(self->s.angles, forward, right, nullptr); + start = M_ProjectFlashSource(self, monster_flash_offset[MZ2_JORG_BFG_1], forward, right); + + vec = self->enemy->s.origin; + vec[2] += self->enemy->viewheight; + dir = vec - start; + dir.normalize(); + gi.sound(self, CHAN_WEAPON, sound_bfg_fire, 1, ATTN_NORM, 0); + monster_fire_bfg(self, start, dir, 50, 300, 100, 200, MZ2_JORG_BFG_1); +} + +void jorg_firebullet_right(edict_t *self) +{ + vec3_t forward, right, start; + AngleVectors(self->s.angles, forward, right, nullptr); + start = M_ProjectFlashSource(self, monster_flash_offset[MZ2_JORG_MACHINEGUN_R1], forward, right); + PredictAim(self, self->enemy, start, 0, false, -0.2f, &forward, nullptr); + monster_fire_bullet(self, start, forward, 6, 4, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, MZ2_JORG_MACHINEGUN_R1); +} + +void jorg_firebullet_left(edict_t *self) +{ + vec3_t forward, right, start; + AngleVectors(self->s.angles, forward, right, nullptr); + start = M_ProjectFlashSource(self, monster_flash_offset[MZ2_JORG_MACHINEGUN_L1], forward, right); + PredictAim(self, self->enemy, start, 0, false, 0.2f, &forward, nullptr); + monster_fire_bullet(self, start, forward, 6, 4, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, MZ2_JORG_MACHINEGUN_L1); +} + +void jorg_firebullet(edict_t *self) +{ + jorg_firebullet_left(self); + jorg_firebullet_right(self); +}; + +MONSTERINFO_ATTACK(jorg_attack) (edict_t *self) -> void +{ + if (frandom() <= 0.75f) + { + gi.sound(self, CHAN_WEAPON, sound_attack1, 1, ATTN_NORM, 0); + self->monsterinfo.weapon_sound = gi.soundindex("boss3/w_loop.wav"); + M_SetAnimation(self, &jorg_move_start_attack1); + } + else + { + gi.sound(self, CHAN_VOICE, sound_attack2, 1, ATTN_NORM, 0); + M_SetAnimation(self, &jorg_move_attack2); + } +} + +void jorg_dead(edict_t *self) +{ + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_EXPLOSION1_BIG); + gi.WritePosition(self->s.origin); + gi.multicast(self->s.origin, MULTICAST_PHS, false); + + self->s.sound = 0; + self->s.skinnum /= 2; + + ThrowGibs(self, 500, { + { 2, "models/objects/gibs/sm_meat/tris.md2" }, + { 2, "models/objects/gibs/sm_metal/tris.md2", GIB_METALLIC }, + { "models/monsters/boss3/jorg/gibs/chest.md2", GIB_SKINNED }, + { 2, "models/monsters/boss3/jorg/gibs/foot.md2", GIB_SKINNED }, + { 2, "models/monsters/boss3/jorg/gibs/gun.md2", GIB_SKINNED | GIB_UPRIGHT }, + { 2, "models/monsters/boss3/jorg/gibs/thigh.md2", GIB_SKINNED | GIB_UPRIGHT }, + { "models/monsters/boss3/jorg/gibs/spine.md2", GIB_SKINNED | GIB_UPRIGHT }, + { 4, "models/monsters/boss3/jorg/gibs/tube.md2", GIB_SKINNED }, + { 6, "models/monsters/boss3/jorg/gibs/spike.md2", GIB_SKINNED }, + { "models/monsters/boss3/jorg/gibs/head.md2", GIB_SKINNED | GIB_METALLIC | GIB_HEAD } + }); + + MakronToss(self); +} + +DIE(jorg_die) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void +{ + gi.sound(self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0); + jorg_attack1_end_sound(self); + self->deadflag = true; + self->takedamage = false; + self->count = 0; + M_SetAnimation(self, &jorg_move_death); +} + +MONSTERINFO_CHECKATTACK(Jorg_CheckAttack) (edict_t *self) -> bool +{ + vec3_t spot1, spot2; + vec3_t temp; + float chance; + trace_t tr; + float enemy_yaw; + + if (self->enemy->health > 0) + { + // see if any entities are in the way of the shot + spot1 = self->s.origin; + spot1[2] += self->viewheight; + spot2 = self->enemy->s.origin; + spot2[2] += self->enemy->viewheight; + + tr = gi.traceline(spot1, spot2, self, CONTENTS_SOLID | CONTENTS_MONSTER | CONTENTS_PLAYER | CONTENTS_SLIME | CONTENTS_LAVA); + + // do we have a clear shot? + if (tr.ent != self->enemy && !(tr.ent->svflags & SVF_PLAYER)) + return false; + } + + float enemy_range = range_to(self, self->enemy); + temp = self->enemy->s.origin - self->s.origin; + enemy_yaw = vectoyaw(temp); + + self->ideal_yaw = enemy_yaw; + + // melee attack + if (enemy_range <= RANGE_MELEE) + { + if (self->monsterinfo.melee) + self->monsterinfo.attack_state = AS_MELEE; + else + self->monsterinfo.attack_state = AS_MISSILE; + return true; + } + + // missile attack + if (!self->monsterinfo.attack) + return false; + + if (level.time < self->monsterinfo.attack_finished) + return false; + + if (enemy_range > RANGE_MID) + return false; + + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + { + chance = 0.4f; + } + else if (enemy_range <= RANGE_MELEE) + { + chance = 0.8f; + } + else if (enemy_range <= RANGE_NEAR) + { + chance = 0.4f; + } + else + { + chance = 0.2f; + } + + if (frandom() < chance) + { + self->monsterinfo.attack_state = AS_MISSILE; + self->monsterinfo.attack_finished = level.time + random_time(2_sec); + return true; + } + + if (self->flags & FL_FLY) + { + if (frandom() < 0.3f) + self->monsterinfo.attack_state = AS_SLIDING; + else + self->monsterinfo.attack_state = AS_STRAIGHT; + } + + return false; +} + +void MakronPrecache(); + +/*QUAKED monster_jorg (1 .5 0) (-80 -80 0) (90 90 140) Ambush Trigger_Spawn Sight + */ +void SP_monster_jorg(edict_t *self) +{ + if ( !M_AllowSpawn( self ) ) { + G_FreeEdict( self ); + return; + } + + sound_pain1 = gi.soundindex("boss3/bs3pain1.wav"); + sound_pain2 = gi.soundindex("boss3/bs3pain2.wav"); + sound_pain3 = gi.soundindex("boss3/bs3pain3.wav"); + sound_death = gi.soundindex("boss3/bs3deth1.wav"); + sound_attack1 = gi.soundindex("boss3/bs3atck1.wav"); + sound_attack1_loop = gi.soundindex("boss3/bs3atck1_loop.wav"); + sound_attack1_end = gi.soundindex("boss3/bs3atck1_end.wav"); + sound_attack2 = gi.soundindex("boss3/bs3atck2.wav"); + sound_search1 = gi.soundindex("boss3/bs3srch1.wav"); + sound_search2 = gi.soundindex("boss3/bs3srch2.wav"); + sound_search3 = gi.soundindex("boss3/bs3srch3.wav"); + sound_idle = gi.soundindex("boss3/bs3idle1.wav"); + sound_step_left = gi.soundindex("boss3/step1.wav"); + sound_step_right = gi.soundindex("boss3/step2.wav"); + sound_firegun = gi.soundindex("boss3/xfire.wav"); + sound_death_hit = gi.soundindex("boss3/d_hit.wav"); + sound_bfg_fire = gi.soundindex("makron/bfg_fire.wav"); + + MakronPrecache(); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex("models/monsters/boss3/jorg/tris.md2"); + self->s.modelindex2 = gi.modelindex("models/monsters/boss3/rider/tris.md2"); + + gi.modelindex("models/monsters/boss3/jorg/gibs/chest.md2"); + gi.modelindex("models/monsters/boss3/jorg/gibs/foot.md2"); + gi.modelindex("models/monsters/boss3/jorg/gibs/gun.md2"); + gi.modelindex("models/monsters/boss3/jorg/gibs/head.md2"); + gi.modelindex("models/monsters/boss3/jorg/gibs/spike.md2"); + gi.modelindex("models/monsters/boss3/jorg/gibs/spine.md2"); + gi.modelindex("models/monsters/boss3/jorg/gibs/thigh.md2"); + gi.modelindex("models/monsters/boss3/jorg/gibs/tube.md2"); + + self->mins = { -80, -80, 0 }; + self->maxs = { 80, 80, 140 }; + + self->health = 8000 * st.health_multiplier; + self->gib_health = -2000; + self->mass = 1000; + + self->pain = jorg_pain; + self->die = jorg_die; + self->monsterinfo.stand = jorg_stand; + self->monsterinfo.walk = jorg_walk; + self->monsterinfo.run = jorg_run; + self->monsterinfo.dodge = nullptr; + self->monsterinfo.attack = jorg_attack; + self->monsterinfo.search = jorg_search; + self->monsterinfo.melee = nullptr; + self->monsterinfo.sight = nullptr; + self->monsterinfo.checkattack = Jorg_CheckAttack; + self->monsterinfo.setskin = jorg_setskin; + gi.linkentity(self); + + M_SetAnimation(self, &jorg_move_stand); + self->monsterinfo.scale = MODEL_SCALE; + + walkmonster_start(self); + // PMM + self->monsterinfo.aiflags |= AI_IGNORE_SHOTS; + // pmm + self->monsterinfo.aiflags |= AI_DOUBLE_TROUBLE; +} diff --git a/rerelease/m_boss31.h b/rerelease/m_boss31.h new file mode 100644 index 0000000..5dedce5 --- /dev/null +++ b/rerelease/m_boss31.h @@ -0,0 +1,199 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// G:\quake2\baseq2\models/monsters/boss3/jorg + +// This file generated by ModelGen - Do NOT Modify + +enum +{ + FRAME_attak101, + FRAME_attak102, + FRAME_attak103, + FRAME_attak104, + FRAME_attak105, + FRAME_attak106, + FRAME_attak107, + FRAME_attak108, + FRAME_attak109, + FRAME_attak110, + FRAME_attak111, + FRAME_attak112, + FRAME_attak113, + FRAME_attak114, + FRAME_attak115, + FRAME_attak116, + FRAME_attak117, + FRAME_attak118, + FRAME_attak201, + FRAME_attak202, + FRAME_attak203, + FRAME_attak204, + FRAME_attak205, + FRAME_attak206, + FRAME_attak207, + FRAME_attak208, + FRAME_attak209, + FRAME_attak210, + FRAME_attak211, + FRAME_attak212, + FRAME_attak213, + FRAME_death01, + FRAME_death02, + FRAME_death03, + FRAME_death04, + FRAME_death05, + FRAME_death06, + FRAME_death07, + FRAME_death08, + FRAME_death09, + FRAME_death10, + FRAME_death11, + FRAME_death12, + FRAME_death13, + FRAME_death14, + FRAME_death15, + FRAME_death16, + FRAME_death17, + FRAME_death18, + FRAME_death19, + FRAME_death20, + FRAME_death21, + FRAME_death22, + FRAME_death23, + FRAME_death24, + FRAME_death25, + FRAME_death26, + FRAME_death27, + FRAME_death28, + FRAME_death29, + FRAME_death30, + FRAME_death31, + FRAME_death32, + FRAME_death33, + FRAME_death34, + FRAME_death35, + FRAME_death36, + FRAME_death37, + FRAME_death38, + FRAME_death39, + FRAME_death40, + FRAME_death41, + FRAME_death42, + FRAME_death43, + FRAME_death44, + FRAME_death45, + FRAME_death46, + FRAME_death47, + FRAME_death48, + FRAME_death49, + FRAME_death50, + FRAME_pain101, + FRAME_pain102, + FRAME_pain103, + FRAME_pain201, + FRAME_pain202, + FRAME_pain203, + FRAME_pain301, + FRAME_pain302, + FRAME_pain303, + FRAME_pain304, + FRAME_pain305, + FRAME_pain306, + FRAME_pain307, + FRAME_pain308, + FRAME_pain309, + FRAME_pain310, + FRAME_pain311, + FRAME_pain312, + FRAME_pain313, + FRAME_pain314, + FRAME_pain315, + FRAME_pain316, + FRAME_pain317, + FRAME_pain318, + FRAME_pain319, + FRAME_pain320, + FRAME_pain321, + FRAME_pain322, + FRAME_pain323, + FRAME_pain324, + FRAME_pain325, + FRAME_stand01, + FRAME_stand02, + FRAME_stand03, + FRAME_stand04, + FRAME_stand05, + FRAME_stand06, + FRAME_stand07, + FRAME_stand08, + FRAME_stand09, + FRAME_stand10, + FRAME_stand11, + FRAME_stand12, + FRAME_stand13, + FRAME_stand14, + FRAME_stand15, + FRAME_stand16, + FRAME_stand17, + FRAME_stand18, + FRAME_stand19, + FRAME_stand20, + FRAME_stand21, + FRAME_stand22, + FRAME_stand23, + FRAME_stand24, + FRAME_stand25, + FRAME_stand26, + FRAME_stand27, + FRAME_stand28, + FRAME_stand29, + FRAME_stand30, + FRAME_stand31, + FRAME_stand32, + FRAME_stand33, + FRAME_stand34, + FRAME_stand35, + FRAME_stand36, + FRAME_stand37, + FRAME_stand38, + FRAME_stand39, + FRAME_stand40, + FRAME_stand41, + FRAME_stand42, + FRAME_stand43, + FRAME_stand44, + FRAME_stand45, + FRAME_stand46, + FRAME_stand47, + FRAME_stand48, + FRAME_stand49, + FRAME_stand50, + FRAME_stand51, + FRAME_walk01, + FRAME_walk02, + FRAME_walk03, + FRAME_walk04, + FRAME_walk05, + FRAME_walk06, + FRAME_walk07, + FRAME_walk08, + FRAME_walk09, + FRAME_walk10, + FRAME_walk11, + FRAME_walk12, + FRAME_walk13, + FRAME_walk14, + FRAME_walk15, + FRAME_walk16, + FRAME_walk17, + FRAME_walk18, + FRAME_walk19, + FRAME_walk20, + FRAME_walk21, + FRAME_walk22, + FRAME_walk23, + FRAME_walk24, + FRAME_walk25 +}; + +constexpr float MODEL_SCALE = 1.000000f; diff --git a/rerelease/m_boss32.cpp b/rerelease/m_boss32.cpp new file mode 100644 index 0000000..976224a --- /dev/null +++ b/rerelease/m_boss32.cpp @@ -0,0 +1,905 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +/* +============================================================================== + +Makron -- Final Boss + +============================================================================== +*/ + +#include "g_local.h" +#include "m_boss32.h" +#include "m_flash.h" + +void MakronRailgun(edict_t *self); +void MakronSaveloc(edict_t *self); +void MakronHyperblaster(edict_t *self); +void makron_step_left(edict_t *self); +void makron_step_right(edict_t *self); +void makronBFG(edict_t *self); +void makron_dead(edict_t *self); + +static int sound_pain4; +static int sound_pain5; +static int sound_pain6; +static int sound_death; +static int sound_step_left; +static int sound_step_right; +static int sound_attack_bfg; +static int sound_brainsplorch; +static int sound_prerailgun; +static int sound_popup; +static int sound_taunt1; +static int sound_taunt2; +static int sound_taunt3; +static int sound_hit; + +void makron_taunt(edict_t *self) +{ + float r; + + r = frandom(); + if (r <= 0.3f) + gi.sound(self, CHAN_AUTO, sound_taunt1, 1, ATTN_NONE, 0); + else if (r <= 0.6f) + gi.sound(self, CHAN_AUTO, sound_taunt2, 1, ATTN_NONE, 0); + else + gi.sound(self, CHAN_AUTO, sound_taunt3, 1, ATTN_NONE, 0); +} + +// +// stand +// + +mframe_t makron_frames_stand[] = { + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, // 10 + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, // 20 + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, // 30 + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, // 40 + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, // 50 + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand } // 60 +}; +MMOVE_T(makron_move_stand) = { FRAME_stand201, FRAME_stand260, makron_frames_stand, nullptr }; + +MONSTERINFO_STAND(makron_stand) (edict_t *self) -> void +{ + M_SetAnimation(self, &makron_move_stand); +} + +mframe_t makron_frames_run[] = { + { ai_run, 3, makron_step_left }, + { ai_run, 12 }, + { ai_run, 8 }, + { ai_run, 8 }, + { ai_run, 8, makron_step_right }, + { ai_run, 6 }, + { ai_run, 12 }, + { ai_run, 9 }, + { ai_run, 6 }, + { ai_run, 12 } +}; +MMOVE_T(makron_move_run) = { FRAME_walk204, FRAME_walk213, makron_frames_run, nullptr }; + +void makron_hit(edict_t *self) +{ + gi.sound(self, CHAN_AUTO, sound_hit, 1, ATTN_NONE, 0); +} + +void makron_popup(edict_t *self) +{ + gi.sound(self, CHAN_BODY, sound_popup, 1, ATTN_NONE, 0); +} + +void makron_step_left(edict_t *self) +{ + gi.sound(self, CHAN_BODY, sound_step_left, 1, ATTN_NORM, 0); +} + +void makron_step_right(edict_t *self) +{ + gi.sound(self, CHAN_BODY, sound_step_right, 1, ATTN_NORM, 0); +} + +void makron_brainsplorch(edict_t *self) +{ + gi.sound(self, CHAN_VOICE, sound_brainsplorch, 1, ATTN_NORM, 0); +} + +void makron_prerailgun(edict_t *self) +{ + gi.sound(self, CHAN_WEAPON, sound_prerailgun, 1, ATTN_NORM, 0); +} + +mframe_t makron_frames_walk[] = { + { ai_walk, 3, makron_step_left }, + { ai_walk, 12 }, + { ai_walk, 8 }, + { ai_walk, 8 }, + { ai_walk, 8, makron_step_right }, + { ai_walk, 6 }, + { ai_walk, 12 }, + { ai_walk, 9 }, + { ai_walk, 6 }, + { ai_walk, 12 } +}; +MMOVE_T(makron_move_walk) = { FRAME_walk204, FRAME_walk213, makron_frames_run, nullptr }; + +MONSTERINFO_WALK(makron_walk) (edict_t *self) -> void +{ + M_SetAnimation(self, &makron_move_walk); +} + +MONSTERINFO_RUN(makron_run) (edict_t *self) -> void +{ + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + M_SetAnimation(self, &makron_move_stand); + else + M_SetAnimation(self, &makron_move_run); +} + +mframe_t makron_frames_pain6[] = { + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, // 10 + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move, 0, makron_popup }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, // 20 + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move, 0, makron_taunt }, + { ai_move }, + { ai_move }, + { ai_move } +}; +MMOVE_T(makron_move_pain6) = { FRAME_pain601, FRAME_pain627, makron_frames_pain6, makron_run }; + +mframe_t makron_frames_pain5[] = { + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move } +}; +MMOVE_T(makron_move_pain5) = { FRAME_pain501, FRAME_pain504, makron_frames_pain5, makron_run }; + +mframe_t makron_frames_pain4[] = { + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move } +}; +MMOVE_T(makron_move_pain4) = { FRAME_pain401, FRAME_pain404, makron_frames_pain4, makron_run }; + +/* +--- +Makron Torso. This needs to be spawned in +--- +*/ + +THINK(makron_torso_think) (edict_t *self) -> void +{ + if (++self->s.frame >= 365) + self->s.frame = 346; + + self->nextthink = level.time + 10_hz; + + if (self->s.angles[0] > 0) + self->s.angles[0] = max(0.f, self->s.angles[0] - 15); +} + +void makron_torso(edict_t *ent) +{ + ent->s.frame = 346; + ent->s.modelindex = gi.modelindex("models/monsters/boss3/rider/tris.md2"); + ent->s.skinnum = 1; + ent->think = makron_torso_think; + ent->nextthink = level.time + 10_hz; + ent->s.sound = gi.soundindex("makron/spine.wav"); + ent->movetype = MOVETYPE_TOSS; + ent->s.effects = EF_GIB; + vec3_t forward, up; + AngleVectors(ent->s.angles, forward, nullptr, up); + ent->velocity += (up * 120); + ent->velocity += (forward * -120); + ent->s.origin += (forward * -10); + ent->s.angles[0] = 90; + ent->avelocity = {}; + gi.linkentity(ent); +} + +void makron_spawn_torso(edict_t *self) +{ + edict_t *tempent = ThrowGib(self, "models/monsters/boss3/rider/tris.md2", 0, GIB_NONE, self->s.scale); + tempent->s.origin = self->s.origin; + tempent->s.angles = self->s.angles; + self->maxs[2] -= tempent->maxs[2]; + tempent->s.origin[2] += self->maxs[2] - 15; + makron_torso(tempent); +} + +mframe_t makron_frames_death2[] = { + { ai_move, -15 }, + { ai_move, 3 }, + { ai_move, -12 }, + { ai_move, 0, makron_step_left }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, // 10 + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move, 11 }, + { ai_move, 12 }, + { ai_move, 11, makron_step_right }, + { ai_move }, + { ai_move }, // 20 + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, // 30 + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move, 5 }, + { ai_move, 7 }, + { ai_move, 6, makron_step_left }, + { ai_move }, + { ai_move }, + { ai_move, -1 }, + { ai_move, 2 }, // 40 + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, // 50 + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move, -6 }, + { ai_move, -4 }, + { ai_move, -6, makron_step_right }, + { ai_move, -4 }, + { ai_move, -4, makron_step_left }, + { ai_move }, + { ai_move }, // 60 + { ai_move }, + { ai_move }, + { ai_move, -2 }, + { ai_move, -5 }, + { ai_move, -3, makron_step_right }, + { ai_move, -8 }, + { ai_move, -3, makron_step_left }, + { ai_move, -7 }, + { ai_move, -4 }, + { ai_move, -4, makron_step_right }, // 70 + { ai_move, -6 }, + { ai_move, -7 }, + { ai_move, 0, makron_step_left }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, // 80 + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move, -2 }, + { ai_move }, + { ai_move }, + { ai_move, 2 }, + { ai_move }, // 90 + { ai_move, 27, makron_hit }, + { ai_move, 26 }, + { ai_move, 0, makron_brainsplorch }, + { ai_move }, + { ai_move } // 95 +}; +MMOVE_T(makron_move_death2) = { FRAME_death201, FRAME_death295, makron_frames_death2, makron_dead }; + +#if 0 +mframe_t makron_frames_death3[] = { + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move } +}; +MMOVE_T(makron_move_death3) = { FRAME_death301, FRAME_death320, makron_frames_death3, nullptr }; +#endif + +mframe_t makron_frames_sight[] = { + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move } +}; +MMOVE_T(makron_move_sight) = { FRAME_active01, FRAME_active13, makron_frames_sight, makron_run }; + +void makronBFG(edict_t *self) +{ + vec3_t forward, right; + vec3_t start; + vec3_t dir; + vec3_t vec; + + AngleVectors(self->s.angles, forward, right, nullptr); + start = M_ProjectFlashSource(self, monster_flash_offset[MZ2_MAKRON_BFG], forward, right); + + vec = self->enemy->s.origin; + vec[2] += self->enemy->viewheight; + dir = vec - start; + dir.normalize(); + gi.sound(self, CHAN_VOICE, sound_attack_bfg, 1, ATTN_NORM, 0); + monster_fire_bfg(self, start, dir, 50, 300, 100, 300, MZ2_MAKRON_BFG); +} + +mframe_t makron_frames_attack3[] = { + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge, 0, makronBFG }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move } +}; +MMOVE_T(makron_move_attack3) = { FRAME_attak301, FRAME_attak308, makron_frames_attack3, makron_run }; + +mframe_t makron_frames_attack4[] = { + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_move, 0, MakronHyperblaster }, // fire + { ai_move, 0, MakronHyperblaster }, // fire + { ai_move, 0, MakronHyperblaster }, // fire + { ai_move, 0, MakronHyperblaster }, // fire + { ai_move, 0, MakronHyperblaster }, // fire + { ai_move, 0, MakronHyperblaster }, // fire + { ai_move, 0, MakronHyperblaster }, // fire + { ai_move, 0, MakronHyperblaster }, // fire + { ai_move, 0, MakronHyperblaster }, // fire + { ai_move, 0, MakronHyperblaster }, // fire + { ai_move, 0, MakronHyperblaster }, // fire + { ai_move, 0, MakronHyperblaster }, // fire + { ai_move, 0, MakronHyperblaster }, // fire + { ai_move, 0, MakronHyperblaster }, // fire + { ai_move, 0, MakronHyperblaster }, // fire + { ai_move, 0, MakronHyperblaster }, // fire + { ai_move, 0, MakronHyperblaster }, // fire + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move } +}; +MMOVE_T(makron_move_attack4) = { FRAME_attak401, FRAME_attak426, makron_frames_attack4, makron_run }; + +mframe_t makron_frames_attack5[] = { + { ai_charge, 0, makron_prerailgun }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge, 0, MakronSaveloc }, + { ai_move, 0, MakronRailgun }, // Fire railgun + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move } +}; +MMOVE_T(makron_move_attack5) = { FRAME_attak501, FRAME_attak516, makron_frames_attack5, makron_run }; + +void MakronSaveloc(edict_t *self) +{ + self->pos1 = self->enemy->s.origin; // save for aiming the shot + self->pos1[2] += self->enemy->viewheight; +}; + +void MakronRailgun(edict_t *self) +{ + vec3_t start; + vec3_t dir; + vec3_t forward, right; + + AngleVectors(self->s.angles, forward, right, nullptr); + start = M_ProjectFlashSource(self, monster_flash_offset[MZ2_MAKRON_RAILGUN_1], forward, right); + + // calc direction to where we targted + dir = self->pos1 - start; + dir.normalize(); + + monster_fire_railgun(self, start, dir, 50, 100, MZ2_MAKRON_RAILGUN_1); +} + +void MakronHyperblaster(edict_t *self) +{ + vec3_t dir; + vec3_t vec; + vec3_t start; + vec3_t forward, right; + + monster_muzzleflash_id_t flash_number = (monster_muzzleflash_id_t) (MZ2_MAKRON_BLASTER_1 + (self->s.frame - FRAME_attak405)); + + AngleVectors(self->s.angles, forward, right, nullptr); + start = M_ProjectFlashSource(self, monster_flash_offset[flash_number], forward, right); + + if (self->enemy) + { + vec = self->enemy->s.origin; + vec[2] += self->enemy->viewheight; + vec -= start; + vec = vectoangles(vec); + dir[0] = vec[0]; + } + else + { + dir[0] = 0; + } + if (self->s.frame <= FRAME_attak413) + dir[1] = self->s.angles[1] - 10 * (self->s.frame - FRAME_attak413); + else + dir[1] = self->s.angles[1] + 10 * (self->s.frame - FRAME_attak421); + dir[2] = 0; + + AngleVectors(dir, forward, nullptr, nullptr); + + monster_fire_blaster(self, start, forward, 15, 1000, flash_number, EF_BLASTER); +} + +PAIN(makron_pain) (edict_t *self, edict_t *other, float kick, int damage, const mod_t &mod) -> void +{ + if (self->monsterinfo.active_move == &makron_move_sight) + return; + + if (level.time < self->pain_debounce_time) + return; + + // Lessen the chance of him going into his pain frames + if (mod.id != MOD_CHAINFIST && damage <= 25) + if (frandom() < 0.2f) + return; + + self->pain_debounce_time = level.time + 3_sec; + + bool do_pain6 = false; + + if (damage <= 40) + gi.sound(self, CHAN_VOICE, sound_pain4, 1, ATTN_NONE, 0); + else if (damage <= 110) + gi.sound(self, CHAN_VOICE, sound_pain5, 1, ATTN_NONE, 0); + else + { + if (damage <= 150) + { + if (frandom() <= 0.45f) + { + do_pain6 = true; + gi.sound(self, CHAN_VOICE, sound_pain6, 1, ATTN_NONE, 0); + } + } + else + { + if (frandom() <= 0.35f) + { + do_pain6 = true; + gi.sound(self, CHAN_VOICE, sound_pain6, 1, ATTN_NONE, 0); + } + } + } + + if (!M_ShouldReactToPain(self, mod)) + return; // no pain anims in nightmare + + if (damage <= 40) + M_SetAnimation(self, &makron_move_pain4); + else if (damage <= 110) + M_SetAnimation(self, &makron_move_pain5); + else if (do_pain6) + M_SetAnimation(self, &makron_move_pain6); +} + +MONSTERINFO_SETSKIN(makron_setskin) (edict_t *self) -> void +{ + if (self->health < (self->max_health / 2)) + self->s.skinnum = 1; + else + self->s.skinnum = 0; +} + +MONSTERINFO_SIGHT(makron_sight) (edict_t *self, edict_t *other) -> void +{ + M_SetAnimation(self, &makron_move_sight); +} + +MONSTERINFO_ATTACK(makron_attack) (edict_t *self) -> void +{ + float r; + + r = frandom(); + + if (r <= 0.3f) + M_SetAnimation(self, &makron_move_attack3); + else if (r <= 0.6f) + M_SetAnimation(self, &makron_move_attack4); + else + M_SetAnimation(self, &makron_move_attack5); +} + +// +// death +// + +void makron_dead(edict_t *self) +{ + self->mins = { -60, -60, 0 }; + self->maxs = { 60, 60, 24 }; + self->movetype = MOVETYPE_TOSS; + self->svflags |= SVF_DEADMONSTER; + gi.linkentity(self); + monster_dead(self); +} + +DIE(makron_die) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void +{ + self->s.sound = 0; + + // check for gib + if (M_CheckGib(self, mod)) + { + gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0); + ThrowGibs(self, damage, { + { "models/objects/gibs/sm_meat/tris.md2" }, + { 4, "models/objects/gibs/sm_metal/tris.md2", GIB_METALLIC }, + { "models/objects/gibs/gear/tris.md2", GIB_METALLIC | GIB_HEAD } + }); + self->deadflag = true; + return; + } + + if (self->deadflag) + return; + + // regular death + gi.sound(self, CHAN_VOICE, sound_death, 1, ATTN_NONE, 0); + self->deadflag = true; + self->takedamage = true; + self->svflags |= SVF_DEADMONSTER; + + M_SetAnimation(self, &makron_move_death2); + + makron_spawn_torso(self); + + self->mins = { -60, -60, 0 }; + self->maxs = { 60, 60, 48 }; +} + +MONSTERINFO_CHECKATTACK(Makron_CheckAttack) (edict_t *self) -> bool +{ + vec3_t spot1, spot2; + vec3_t temp; + float chance; + trace_t tr; + float enemy_yaw; + + if (self->enemy->health > 0) + { + // see if any entities are in the way of the shot + spot1 = self->s.origin; + spot1[2] += self->viewheight; + spot2 = self->enemy->s.origin; + spot2[2] += self->enemy->viewheight; + + tr = gi.traceline(spot1, spot2, self, CONTENTS_SOLID | CONTENTS_PLAYER | CONTENTS_MONSTER | CONTENTS_SLIME | CONTENTS_LAVA); + + // do we have a clear shot? + if (tr.ent != self->enemy && !(tr.ent->svflags & SVF_PLAYER)) + return false; + } + + float enemy_range = range_to(self, self->enemy); + temp = self->enemy->s.origin - self->s.origin; + enemy_yaw = vectoyaw(temp); + + self->ideal_yaw = enemy_yaw; + + // melee attack + if (enemy_range <= RANGE_MELEE) + { + if (self->monsterinfo.melee) + self->monsterinfo.attack_state = AS_MELEE; + else + self->monsterinfo.attack_state = AS_MISSILE; + return true; + } + + // missile attack + if (!self->monsterinfo.attack) + return false; + + if (level.time < self->monsterinfo.attack_finished) + return false; + + if (enemy_range > RANGE_MID) + return false; + + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + { + chance = 0.4f; + } + else if (enemy_range <= RANGE_MELEE) + { + chance = 0.8f; + } + else if (enemy_range <= RANGE_NEAR) + { + chance = 0.4f; + } + else + { + chance = 0.2f; + } + + if (frandom() < chance) + { + self->monsterinfo.attack_state = AS_MISSILE; + self->monsterinfo.attack_finished = level.time + random_time(2_sec); + return true; + } + + if (self->flags & FL_FLY) + { + if (frandom() < 0.3f) + self->monsterinfo.attack_state = AS_SLIDING; + else + self->monsterinfo.attack_state = AS_STRAIGHT; + } + + return false; +} + +// +// monster_makron +// + +void MakronPrecache() +{ + sound_pain4 = gi.soundindex("makron/pain3.wav"); + sound_pain5 = gi.soundindex("makron/pain2.wav"); + sound_pain6 = gi.soundindex("makron/pain1.wav"); + sound_death = gi.soundindex("makron/death.wav"); + sound_step_left = gi.soundindex("makron/step1.wav"); + sound_step_right = gi.soundindex("makron/step2.wav"); + sound_attack_bfg = gi.soundindex("makron/bfg_fire.wav"); + sound_brainsplorch = gi.soundindex("makron/brain1.wav"); + sound_prerailgun = gi.soundindex("makron/rail_up.wav"); + sound_popup = gi.soundindex("makron/popup.wav"); + sound_taunt1 = gi.soundindex("makron/voice4.wav"); + sound_taunt2 = gi.soundindex("makron/voice3.wav"); + sound_taunt3 = gi.soundindex("makron/voice.wav"); + sound_hit = gi.soundindex("makron/bhit.wav"); + + gi.modelindex("models/monsters/boss3/rider/tris.md2"); +} + +/*QUAKED monster_makron (1 .5 0) (-30 -30 0) (30 30 90) Ambush Trigger_Spawn Sight + */ +void SP_monster_makron(edict_t *self) +{ + if ( !M_AllowSpawn( self ) ) { + G_FreeEdict( self ); + return; + } + + MakronPrecache(); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex("models/monsters/boss3/rider/tris.md2"); + self->mins = { -30, -30, 0 }; + self->maxs = { 30, 30, 90 }; + + self->health = 3000 * st.health_multiplier; + self->gib_health = -2000; + self->mass = 500; + + self->pain = makron_pain; + self->die = makron_die; + self->monsterinfo.stand = makron_stand; + self->monsterinfo.walk = makron_walk; + self->monsterinfo.run = makron_run; + self->monsterinfo.dodge = nullptr; + self->monsterinfo.attack = makron_attack; + self->monsterinfo.melee = nullptr; + self->monsterinfo.sight = makron_sight; + self->monsterinfo.checkattack = Makron_CheckAttack; + self->monsterinfo.setskin = makron_setskin; + + gi.linkentity(self); + + // M_SetAnimation(self, &makron_move_stand); + M_SetAnimation(self, &makron_move_sight); + self->monsterinfo.scale = MODEL_SCALE; + + walkmonster_start(self); + + // PMM + self->monsterinfo.aiflags |= AI_IGNORE_SHOTS; + // pmm +} + +/* +================= +MakronSpawn + +================= +*/ +THINK(MakronSpawn) (edict_t *self) -> void +{ + vec3_t vec; + edict_t *player; + + SP_monster_makron(self); + self->think(self); + + // jump at player + if (self->enemy && self->enemy->inuse && self->enemy->health > 0) + player = self->enemy; + else + player = AI_GetSightClient(self); + + if (!player) + return; + + vec = player->s.origin - self->s.origin; + self->s.angles[YAW] = vectoyaw(vec); + vec.normalize(); + self->velocity = vec * 400; + self->velocity[2] = 200; + self->groundentity = nullptr; + self->enemy = player; + FoundTarget(self); + self->monsterinfo.sight(self, self->enemy); + self->s.frame = self->monsterinfo.nextframe = FRAME_active01; // FIXME: why???? +} + +/* +================= +MakronToss + +Jorg is just about dead, so set up to launch Makron out +================= +*/ +void MakronToss(edict_t *self) +{ + edict_t *ent = G_Spawn(); + ent->classname = "monster_makron"; + ent->target = self->target; + ent->s.origin = self->s.origin; + ent->enemy = self->enemy; + + MakronSpawn(ent); + + // [Paril-KEX] set health bar over to Makron when we throw him out + for (size_t i = 0; i < 2; i++) + if (level.health_bar_entities[i] && level.health_bar_entities[i]->enemy == self) + level.health_bar_entities[i]->enemy = ent; +} diff --git a/rerelease/m_boss32.h b/rerelease/m_boss32.h new file mode 100644 index 0000000..9ea7de2 --- /dev/null +++ b/rerelease/m_boss32.h @@ -0,0 +1,502 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// G:\quake2\baseq2\models/monsters/boss3/rider + +// This file generated by ModelGen - Do NOT Modify + +enum +{ + FRAME_attak101, + FRAME_attak102, + FRAME_attak103, + FRAME_attak104, + FRAME_attak105, + FRAME_attak106, + FRAME_attak107, + FRAME_attak108, + FRAME_attak109, + FRAME_attak110, + FRAME_attak111, + FRAME_attak112, + FRAME_attak113, + FRAME_attak114, + FRAME_attak115, + FRAME_attak116, + FRAME_attak117, + FRAME_attak118, + FRAME_attak201, + FRAME_attak202, + FRAME_attak203, + FRAME_attak204, + FRAME_attak205, + FRAME_attak206, + FRAME_attak207, + FRAME_attak208, + FRAME_attak209, + FRAME_attak210, + FRAME_attak211, + FRAME_attak212, + FRAME_attak213, + FRAME_death01, + FRAME_death02, + FRAME_death03, + FRAME_death04, + FRAME_death05, + FRAME_death06, + FRAME_death07, + FRAME_death08, + FRAME_death09, + FRAME_death10, + FRAME_death11, + FRAME_death12, + FRAME_death13, + FRAME_death14, + FRAME_death15, + FRAME_death16, + FRAME_death17, + FRAME_death18, + FRAME_death19, + FRAME_death20, + FRAME_death21, + FRAME_death22, + FRAME_death23, + FRAME_death24, + FRAME_death25, + FRAME_death26, + FRAME_death27, + FRAME_death28, + FRAME_death29, + FRAME_death30, + FRAME_death31, + FRAME_death32, + FRAME_death33, + FRAME_death34, + FRAME_death35, + FRAME_death36, + FRAME_death37, + FRAME_death38, + FRAME_death39, + FRAME_death40, + FRAME_death41, + FRAME_death42, + FRAME_death43, + FRAME_death44, + FRAME_death45, + FRAME_death46, + FRAME_death47, + FRAME_death48, + FRAME_death49, + FRAME_death50, + FRAME_pain101, + FRAME_pain102, + FRAME_pain103, + FRAME_pain201, + FRAME_pain202, + FRAME_pain203, + FRAME_pain301, + FRAME_pain302, + FRAME_pain303, + FRAME_pain304, + FRAME_pain305, + FRAME_pain306, + FRAME_pain307, + FRAME_pain308, + FRAME_pain309, + FRAME_pain310, + FRAME_pain311, + FRAME_pain312, + FRAME_pain313, + FRAME_pain314, + FRAME_pain315, + FRAME_pain316, + FRAME_pain317, + FRAME_pain318, + FRAME_pain319, + FRAME_pain320, + FRAME_pain321, + FRAME_pain322, + FRAME_pain323, + FRAME_pain324, + FRAME_pain325, + FRAME_stand01, + FRAME_stand02, + FRAME_stand03, + FRAME_stand04, + FRAME_stand05, + FRAME_stand06, + FRAME_stand07, + FRAME_stand08, + FRAME_stand09, + FRAME_stand10, + FRAME_stand11, + FRAME_stand12, + FRAME_stand13, + FRAME_stand14, + FRAME_stand15, + FRAME_stand16, + FRAME_stand17, + FRAME_stand18, + FRAME_stand19, + FRAME_stand20, + FRAME_stand21, + FRAME_stand22, + FRAME_stand23, + FRAME_stand24, + FRAME_stand25, + FRAME_stand26, + FRAME_stand27, + FRAME_stand28, + FRAME_stand29, + FRAME_stand30, + FRAME_stand31, + FRAME_stand32, + FRAME_stand33, + FRAME_stand34, + FRAME_stand35, + FRAME_stand36, + FRAME_stand37, + FRAME_stand38, + FRAME_stand39, + FRAME_stand40, + FRAME_stand41, + FRAME_stand42, + FRAME_stand43, + FRAME_stand44, + FRAME_stand45, + FRAME_stand46, + FRAME_stand47, + FRAME_stand48, + FRAME_stand49, + FRAME_stand50, + FRAME_stand51, + FRAME_walk01, + FRAME_walk02, + FRAME_walk03, + FRAME_walk04, + FRAME_walk05, + FRAME_walk06, + FRAME_walk07, + FRAME_walk08, + FRAME_walk09, + FRAME_walk10, + FRAME_walk11, + FRAME_walk12, + FRAME_walk13, + FRAME_walk14, + FRAME_walk15, + FRAME_walk16, + FRAME_walk17, + FRAME_walk18, + FRAME_walk19, + FRAME_walk20, + FRAME_walk21, + FRAME_walk22, + FRAME_walk23, + FRAME_walk24, + FRAME_walk25, + FRAME_active01, + FRAME_active02, + FRAME_active03, + FRAME_active04, + FRAME_active05, + FRAME_active06, + FRAME_active07, + FRAME_active08, + FRAME_active09, + FRAME_active10, + FRAME_active11, + FRAME_active12, + FRAME_active13, + FRAME_attak301, + FRAME_attak302, + FRAME_attak303, + FRAME_attak304, + FRAME_attak305, + FRAME_attak306, + FRAME_attak307, + FRAME_attak308, + FRAME_attak401, + FRAME_attak402, + FRAME_attak403, + FRAME_attak404, + FRAME_attak405, + FRAME_attak406, + FRAME_attak407, + FRAME_attak408, + FRAME_attak409, + FRAME_attak410, + FRAME_attak411, + FRAME_attak412, + FRAME_attak413, + FRAME_attak414, + FRAME_attak415, + FRAME_attak416, + FRAME_attak417, + FRAME_attak418, + FRAME_attak419, + FRAME_attak420, + FRAME_attak421, + FRAME_attak422, + FRAME_attak423, + FRAME_attak424, + FRAME_attak425, + FRAME_attak426, + FRAME_attak501, + FRAME_attak502, + FRAME_attak503, + FRAME_attak504, + FRAME_attak505, + FRAME_attak506, + FRAME_attak507, + FRAME_attak508, + FRAME_attak509, + FRAME_attak510, + FRAME_attak511, + FRAME_attak512, + FRAME_attak513, + FRAME_attak514, + FRAME_attak515, + FRAME_attak516, + FRAME_death201, + FRAME_death202, + FRAME_death203, + FRAME_death204, + FRAME_death205, + FRAME_death206, + FRAME_death207, + FRAME_death208, + FRAME_death209, + FRAME_death210, + FRAME_death211, + FRAME_death212, + FRAME_death213, + FRAME_death214, + FRAME_death215, + FRAME_death216, + FRAME_death217, + FRAME_death218, + FRAME_death219, + FRAME_death220, + FRAME_death221, + FRAME_death222, + FRAME_death223, + FRAME_death224, + FRAME_death225, + FRAME_death226, + FRAME_death227, + FRAME_death228, + FRAME_death229, + FRAME_death230, + FRAME_death231, + FRAME_death232, + FRAME_death233, + FRAME_death234, + FRAME_death235, + FRAME_death236, + FRAME_death237, + FRAME_death238, + FRAME_death239, + FRAME_death240, + FRAME_death241, + FRAME_death242, + FRAME_death243, + FRAME_death244, + FRAME_death245, + FRAME_death246, + FRAME_death247, + FRAME_death248, + FRAME_death249, + FRAME_death250, + FRAME_death251, + FRAME_death252, + FRAME_death253, + FRAME_death254, + FRAME_death255, + FRAME_death256, + FRAME_death257, + FRAME_death258, + FRAME_death259, + FRAME_death260, + FRAME_death261, + FRAME_death262, + FRAME_death263, + FRAME_death264, + FRAME_death265, + FRAME_death266, + FRAME_death267, + FRAME_death268, + FRAME_death269, + FRAME_death270, + FRAME_death271, + FRAME_death272, + FRAME_death273, + FRAME_death274, + FRAME_death275, + FRAME_death276, + FRAME_death277, + FRAME_death278, + FRAME_death279, + FRAME_death280, + FRAME_death281, + FRAME_death282, + FRAME_death283, + FRAME_death284, + FRAME_death285, + FRAME_death286, + FRAME_death287, + FRAME_death288, + FRAME_death289, + FRAME_death290, + FRAME_death291, + FRAME_death292, + FRAME_death293, + FRAME_death294, + FRAME_death295, + FRAME_death301, + FRAME_death302, + FRAME_death303, + FRAME_death304, + FRAME_death305, + FRAME_death306, + FRAME_death307, + FRAME_death308, + FRAME_death309, + FRAME_death310, + FRAME_death311, + FRAME_death312, + FRAME_death313, + FRAME_death314, + FRAME_death315, + FRAME_death316, + FRAME_death317, + FRAME_death318, + FRAME_death319, + FRAME_death320, + FRAME_jump01, + FRAME_jump02, + FRAME_jump03, + FRAME_jump04, + FRAME_jump05, + FRAME_jump06, + FRAME_jump07, + FRAME_jump08, + FRAME_jump09, + FRAME_jump10, + FRAME_jump11, + FRAME_jump12, + FRAME_jump13, + FRAME_pain401, + FRAME_pain402, + FRAME_pain403, + FRAME_pain404, + FRAME_pain501, + FRAME_pain502, + FRAME_pain503, + FRAME_pain504, + FRAME_pain601, + FRAME_pain602, + FRAME_pain603, + FRAME_pain604, + FRAME_pain605, + FRAME_pain606, + FRAME_pain607, + FRAME_pain608, + FRAME_pain609, + FRAME_pain610, + FRAME_pain611, + FRAME_pain612, + FRAME_pain613, + FRAME_pain614, + FRAME_pain615, + FRAME_pain616, + FRAME_pain617, + FRAME_pain618, + FRAME_pain619, + FRAME_pain620, + FRAME_pain621, + FRAME_pain622, + FRAME_pain623, + FRAME_pain624, + FRAME_pain625, + FRAME_pain626, + FRAME_pain627, + FRAME_stand201, + FRAME_stand202, + FRAME_stand203, + FRAME_stand204, + FRAME_stand205, + FRAME_stand206, + FRAME_stand207, + FRAME_stand208, + FRAME_stand209, + FRAME_stand210, + FRAME_stand211, + FRAME_stand212, + FRAME_stand213, + FRAME_stand214, + FRAME_stand215, + FRAME_stand216, + FRAME_stand217, + FRAME_stand218, + FRAME_stand219, + FRAME_stand220, + FRAME_stand221, + FRAME_stand222, + FRAME_stand223, + FRAME_stand224, + FRAME_stand225, + FRAME_stand226, + FRAME_stand227, + FRAME_stand228, + FRAME_stand229, + FRAME_stand230, + FRAME_stand231, + FRAME_stand232, + FRAME_stand233, + FRAME_stand234, + FRAME_stand235, + FRAME_stand236, + FRAME_stand237, + FRAME_stand238, + FRAME_stand239, + FRAME_stand240, + FRAME_stand241, + FRAME_stand242, + FRAME_stand243, + FRAME_stand244, + FRAME_stand245, + FRAME_stand246, + FRAME_stand247, + FRAME_stand248, + FRAME_stand249, + FRAME_stand250, + FRAME_stand251, + FRAME_stand252, + FRAME_stand253, + FRAME_stand254, + FRAME_stand255, + FRAME_stand256, + FRAME_stand257, + FRAME_stand258, + FRAME_stand259, + FRAME_stand260, + FRAME_walk201, + FRAME_walk202, + FRAME_walk203, + FRAME_walk204, + FRAME_walk205, + FRAME_walk206, + FRAME_walk207, + FRAME_walk208, + FRAME_walk209, + FRAME_walk210, + FRAME_walk211, + FRAME_walk212, + FRAME_walk213, + FRAME_walk214, + FRAME_walk215, + FRAME_walk216, + FRAME_walk217 +}; + +constexpr float MODEL_SCALE = 1.000000f; diff --git a/rerelease/m_brain.cpp b/rerelease/m_brain.cpp new file mode 100644 index 0000000..364bd30 --- /dev/null +++ b/rerelease/m_brain.cpp @@ -0,0 +1,798 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +/* +============================================================================== + +brain + +============================================================================== +*/ + +#include "g_local.h" +#include "m_brain.h" + +static int sound_chest_open; +static int sound_tentacles_extend; +static int sound_tentacles_retract; +static int sound_death; +static int sound_idle1; +static int sound_idle2; +static int sound_idle3; +static int sound_pain1; +static int sound_pain2; +static int sound_sight; +static int sound_search; +static int sound_melee1; +static int sound_melee2; +static int sound_melee3; + +MONSTERINFO_SIGHT(brain_sight) (edict_t *self, edict_t *other) -> void +{ + gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); +} + +MONSTERINFO_SEARCH(brain_search) (edict_t *self) -> void +{ + gi.sound(self, CHAN_VOICE, sound_search, 1, ATTN_NORM, 0); +} + +void brain_run(edict_t *self); +void brain_dead(edict_t *self); + +constexpr spawnflags_t SPAWNFLAG_BRAIN_NO_LASERS = 8_spawnflag; + +// +// STAND +// + +mframe_t brain_frames_stand[] = { + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand } +}; +MMOVE_T(brain_move_stand) = { FRAME_stand01, FRAME_stand30, brain_frames_stand, nullptr }; + +MONSTERINFO_STAND(brain_stand) (edict_t *self) -> void +{ + M_SetAnimation(self, &brain_move_stand); +} + +// +// IDLE +// + +mframe_t brain_frames_idle[] = { + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand } +}; +MMOVE_T(brain_move_idle) = { FRAME_stand31, FRAME_stand60, brain_frames_idle, brain_stand }; + +MONSTERINFO_IDLE(brain_idle) (edict_t *self) -> void +{ + gi.sound(self, CHAN_AUTO, sound_idle3, 1, ATTN_IDLE, 0); + M_SetAnimation(self, &brain_move_idle); +} + +// +// WALK +// +mframe_t brain_frames_walk1[] = { + { ai_walk, 7 }, + { ai_walk, 2 }, + { ai_walk, 3 }, + { ai_walk, 3, monster_footstep }, + { ai_walk, 1 }, + { ai_walk }, + { ai_walk }, + { ai_walk, 9 }, + { ai_walk, -4 }, + { ai_walk, -1, monster_footstep }, + { ai_walk, 2 } +}; +MMOVE_T(brain_move_walk1) = { FRAME_walk101, FRAME_walk111, brain_frames_walk1, nullptr }; + +MONSTERINFO_WALK(brain_walk) (edict_t *self) -> void +{ + M_SetAnimation(self, &brain_move_walk1); +} + +#if 0 +mframe_t brain_frames_defense[] = { + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move } +}; +MMOVE_T(brain_move_defense) = { FRAME_defens01, FRAME_defens08, brain_frames_defense, nullptr }; +#endif + +mframe_t brain_frames_pain3[] = { + { ai_move, -2 }, + { ai_move, 2 }, + { ai_move, 1 }, + { ai_move, 3 }, + { ai_move }, + { ai_move, -4 } +}; +MMOVE_T(brain_move_pain3) = { FRAME_pain301, FRAME_pain306, brain_frames_pain3, brain_run }; + +mframe_t brain_frames_pain2[] = { + { ai_move, -2 }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move, 3 }, + { ai_move, 1 }, + { ai_move, -2 } +}; +MMOVE_T(brain_move_pain2) = { FRAME_pain201, FRAME_pain208, brain_frames_pain2, brain_run }; + +mframe_t brain_frames_pain1[] = { + { ai_move, -6 }, + { ai_move, -2 }, + { ai_move, -6, monster_footstep }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move, 2 }, + { ai_move }, + { ai_move, 2 }, + { ai_move, 1 }, + { ai_move, 7 }, + { ai_move }, + { ai_move, 3, monster_footstep }, + { ai_move, -1 } +}; +MMOVE_T(brain_move_pain1) = { FRAME_pain101, FRAME_pain121, brain_frames_pain1, brain_run }; + +mframe_t brain_frames_duck[] = { + { ai_move }, + { ai_move, -2, [](edict_t *self) { monster_duck_down(self); monster_footstep(self); } }, + { ai_move, 17, monster_duck_hold }, + { ai_move, -3 }, + { ai_move, -1, monster_duck_up }, + { ai_move, -5 }, + { ai_move, -6 }, + { ai_move, -6, monster_footstep } +}; +MMOVE_T(brain_move_duck) = { FRAME_duck01, FRAME_duck08, brain_frames_duck, brain_run }; + +static void brain_shrink(edict_t *self) +{ + self->maxs[2] = 0; + self->svflags |= SVF_DEADMONSTER; + gi.linkentity(self); +} + +mframe_t brain_frames_death2[] = { + { ai_move }, + { ai_move, 0, monster_footstep }, + { ai_move, 0, brain_shrink }, + { ai_move, 9 }, + { ai_move } +}; +MMOVE_T(brain_move_death2) = { FRAME_death201, FRAME_death205, brain_frames_death2, brain_dead }; + +mframe_t brain_frames_death1[] = { + { ai_move }, + { ai_move, 0, monster_footstep }, + { ai_move, -2 }, + { ai_move, 9, [](edict_t *self) { brain_shrink(self); monster_footstep(self); } }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move, 0, monster_footstep }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move, 0, monster_footstep }, + { ai_move }, + { ai_move } +}; +MMOVE_T(brain_move_death1) = { FRAME_death101, FRAME_death118, brain_frames_death1, brain_dead }; + +// +// MELEE +// + +void brain_swing_right(edict_t *self) +{ + gi.sound(self, CHAN_BODY, sound_melee1, 1, ATTN_NORM, 0); +} + +void brain_hit_right(edict_t *self) +{ + vec3_t aim = { MELEE_DISTANCE, self->maxs[0], 8 }; + if (fire_hit(self, aim, irandom(15, 20), 40)) + gi.sound(self, CHAN_WEAPON, sound_melee3, 1, ATTN_NORM, 0); + else + self->monsterinfo.melee_debounce_time = level.time + 3_sec; +} + +void brain_swing_left(edict_t *self) +{ + gi.sound(self, CHAN_BODY, sound_melee2, 1, ATTN_NORM, 0); +} + +void brain_hit_left(edict_t *self) +{ + vec3_t aim = { MELEE_DISTANCE, self->mins[0], 8 }; + if (fire_hit(self, aim, irandom(15, 20), 40)) + gi.sound(self, CHAN_WEAPON, sound_melee3, 1, ATTN_NORM, 0); + else + self->monsterinfo.melee_debounce_time = level.time + 3_sec; +} + +mframe_t brain_frames_attack1[] = { + { ai_charge, 8 }, + { ai_charge, 3 }, + { ai_charge, 5 }, + { ai_charge, 0, monster_footstep }, + { ai_charge, -3, brain_swing_right }, + { ai_charge }, + { ai_charge, -5 }, + { ai_charge, -7, brain_hit_right }, + { ai_charge }, + { ai_charge, 6, brain_swing_left }, + { ai_charge, 1 }, + { ai_charge, 2, brain_hit_left }, + { ai_charge, -3 }, + { ai_charge, 6 }, + { ai_charge, -1 }, + { ai_charge, -3 }, + { ai_charge, 2 }, + { ai_charge, -11, monster_footstep } +}; +MMOVE_T(brain_move_attack1) = { FRAME_attak101, FRAME_attak118, brain_frames_attack1, brain_run }; + +constexpr spawnflags_t SPAWNFLAG_BRAIN_TENTACLES_HIT = 65536_spawnflag; + +void brain_chest_open(edict_t *self) +{ + self->spawnflags &= ~SPAWNFLAG_BRAIN_TENTACLES_HIT; + self->monsterinfo.power_armor_type = IT_NULL; + gi.sound(self, CHAN_BODY, sound_chest_open, 1, ATTN_NORM, 0); +} + +void brain_tentacle_attack(edict_t *self) +{ + vec3_t aim = { MELEE_DISTANCE, 0, 8 }; + if (fire_hit(self, aim, irandom(10, 15), -600)) + self->spawnflags |= SPAWNFLAG_BRAIN_TENTACLES_HIT; + else + self->monsterinfo.melee_debounce_time = level.time + 3_sec; + gi.sound(self, CHAN_WEAPON, sound_tentacles_retract, 1, ATTN_NORM, 0); +} + +void brain_chest_closed(edict_t *self) +{ + self->monsterinfo.power_armor_type = IT_ITEM_POWER_SCREEN; + if (self->spawnflags.has(SPAWNFLAG_BRAIN_TENTACLES_HIT)) + { + self->spawnflags &= ~SPAWNFLAG_BRAIN_TENTACLES_HIT; + M_SetAnimation(self, &brain_move_attack1); + } +} + +mframe_t brain_frames_attack2[] = { + { ai_charge, 5 }, + { ai_charge, -4 }, + { ai_charge, -4 }, + { ai_charge, -3 }, + { ai_charge, 0, brain_chest_open }, + { ai_charge }, + { ai_charge, 13, brain_tentacle_attack }, + { ai_charge }, + { ai_charge, 2 }, + { ai_charge }, + { ai_charge, -9, brain_chest_closed }, + { ai_charge }, + { ai_charge, 4 }, + { ai_charge, 3 }, + { ai_charge, 2 }, + { ai_charge, -3 }, + { ai_charge, -6 } +}; +MMOVE_T(brain_move_attack2) = { FRAME_attak201, FRAME_attak217, brain_frames_attack2, brain_run }; + +MONSTERINFO_MELEE(brain_melee) (edict_t *self) -> void +{ + if (frandom() <= 0.5f) + M_SetAnimation(self, &brain_move_attack1); + else + M_SetAnimation(self, &brain_move_attack2); +} + +// RAFAEL +static bool brain_tounge_attack_ok(const vec3_t &start, const vec3_t &end) +{ + vec3_t dir, angles; + + // check for max distance + dir = start - end; + if (dir.length() > 512) + return false; + + // check for min/max pitch + angles = vectoangles(dir); + if (angles[0] < -180) + angles[0] += 360; + if (fabsf(angles[0]) > 30) + return false; + + return true; +} + +void brain_tounge_attack(edict_t *self) +{ + vec3_t offset, start, f, r, end, dir; + trace_t tr; + int damage; + + AngleVectors(self->s.angles, f, r, nullptr); + // offset = { 24, 0, 6 }; + offset = { 24, 0, 16 }; + start = M_ProjectFlashSource(self, offset, f, r); + + end = self->enemy->s.origin; + if (!brain_tounge_attack_ok(start, end)) + { + end[2] = self->enemy->s.origin[2] + self->enemy->maxs[2] - 8; + if (!brain_tounge_attack_ok(start, end)) + { + end[2] = self->enemy->s.origin[2] + self->enemy->mins[2] + 8; + if (!brain_tounge_attack_ok(start, end)) + return; + } + } + end = self->enemy->s.origin; + + tr = gi.traceline(start, end, self, MASK_PROJECTILE); + if (tr.ent != self->enemy) + return; + + damage = 5; + gi.sound(self, CHAN_WEAPON, sound_tentacles_retract, 1, ATTN_NORM, 0); + + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_PARASITE_ATTACK); + gi.WriteEntity(self); + gi.WritePosition(start); + gi.WritePosition(end); + gi.multicast(self->s.origin, MULTICAST_PVS, false); + + dir = start - end; + T_Damage(self->enemy, self, self, dir, self->enemy->s.origin, vec3_origin, damage, 0, DAMAGE_NO_KNOCKBACK, MOD_BRAINTENTACLE); + + // pull the enemy in + vec3_t forward; + self->s.origin[2] += 1; + AngleVectors(self->s.angles, forward, nullptr, nullptr); + self->enemy->velocity = forward * -1200; +} + +// Brian right eye center +constexpr vec3_t brain_reye[] = { + { 0.746700f, 0.238370f, 34.167690f }, + { -1.076390f, 0.238370f, 33.386372f }, + { -1.335500f, 5.334300f, 32.177170f }, + { -0.175360f, 8.846370f, 30.635479f }, + { -2.757590f, 7.804610f, 30.150860f }, + { -5.575090f, 5.152840f, 30.056160f }, + { -7.017550f, 3.262470f, 30.552521f }, + { -7.915740f, 0.638800f, 33.176189f }, + { -3.915390f, 8.285730f, 33.976349f }, + { -0.913540f, 10.933030f, 34.141811f }, + { -0.369900f, 8.923900f, 34.189079f } +}; + +// Brain left eye center +constexpr vec3_t brain_leye[] = { + { -3.364710f, 0.327750f, 33.938381f }, + { -5.140450f, 0.493480f, 32.659851f }, + { -5.341980f, 5.646980f, 31.277901f }, + { -4.134480f, 9.277440f, 29.925621f }, + { -6.598340f, 6.815090f, 29.322620f }, + { -8.610840f, 2.529650f, 29.251591f }, + { -9.231360f, 0.093280f, 29.747959f }, + { -11.004110f, 1.936930f, 32.395260f }, + { -7.878310f, 7.648190f, 33.148151f }, + { -4.947370f, 11.430050f, 33.313610f }, + { -4.332820f, 9.444570f, 33.526340f } +}; + +PRETHINK(brain_right_eye_laser_update) (edict_t *laser) -> void +{ + edict_t *self = laser->owner; + + vec3_t start, forward, right, up, dir; + + // check for max distance + AngleVectors(self->s.angles, forward, right, up); + + // dis is my right eye + start = self->s.origin + (right * brain_reye[self->s.frame - FRAME_walk101].x); + start += forward * brain_reye[self->s.frame - FRAME_walk101].y; + start += up * brain_reye[self->s.frame - FRAME_walk101].z; + + PredictAim(self, self->enemy, start, 0, false, frandom(0.1f, 0.2f), &dir, nullptr); + + laser->s.origin = start; + laser->movedir = dir; + gi.linkentity(laser); +} + +PRETHINK(brain_left_eye_laser_update) (edict_t *laser) -> void +{ + edict_t *self = laser->owner; + + vec3_t start, forward, right, up, dir; + + // check for max distance + AngleVectors(self->s.angles, forward, right, up); + + // dis is my right eye + start = self->s.origin + (right * brain_leye[self->s.frame - FRAME_walk101].x); + start += forward * brain_leye[self->s.frame - FRAME_walk101].y; + start += up * brain_leye[self->s.frame - FRAME_walk101].z; + + PredictAim(self, self->enemy, start, 0, false, frandom(0.1f, 0.2f), &dir, nullptr); + + laser->s.origin = start; + laser->movedir = dir; + gi.linkentity(laser); + dabeam_update(laser, false); +} + +void brain_laserbeam(edict_t *self) +{ + // dis is my right eye + monster_fire_dabeam(self, 1, false, brain_right_eye_laser_update); + + // dis is me left eye + monster_fire_dabeam(self, 1, true, brain_left_eye_laser_update); +} + +void brain_laserbeam_reattack(edict_t *self) +{ + if (frandom() < 0.5f) + if (visible(self, self->enemy)) + if (self->enemy->health > 0) + self->s.frame = FRAME_walk101; +} + +mframe_t brain_frames_attack3[] = { + { ai_charge, 5 }, + { ai_charge, -4 }, + { ai_charge, -4 }, + { ai_charge, -3 }, + { ai_charge, 0, brain_chest_open }, + { ai_charge, 0, brain_tounge_attack }, + { ai_charge, 13 }, + { ai_charge, 0, brain_tentacle_attack }, + { ai_charge, 2 }, + { ai_charge, 0, brain_tounge_attack }, + { ai_charge, -9, brain_chest_closed }, + { ai_charge }, + { ai_charge, 4 }, + { ai_charge, 3 }, + { ai_charge, 2 }, + { ai_charge, -3 }, + { ai_charge, -6 } +}; +MMOVE_T(brain_move_attack3) = { FRAME_attak201, FRAME_attak217, brain_frames_attack3, brain_run }; + +mframe_t brain_frames_attack4[] = { + { ai_charge, 9, brain_laserbeam }, + { ai_charge, 2, brain_laserbeam }, + { ai_charge, 3, brain_laserbeam }, + { ai_charge, 3, brain_laserbeam }, + { ai_charge, 1, brain_laserbeam }, + { ai_charge, 0, brain_laserbeam }, + { ai_charge, 0, brain_laserbeam }, + { ai_charge, 10, brain_laserbeam }, + { ai_charge, -4, brain_laserbeam }, + { ai_charge, -1, brain_laserbeam }, + { ai_charge, 2, brain_laserbeam_reattack } +}; +MMOVE_T(brain_move_attack4) = { FRAME_walk101, FRAME_walk111, brain_frames_attack4, brain_run }; + +// RAFAEL +MONSTERINFO_ATTACK(brain_attack) (edict_t *self) -> void +{ + float r = range_to(self, self->enemy); + if (r <= RANGE_NEAR) + { + if (frandom() < 0.5f) + M_SetAnimation(self, &brain_move_attack3); + else if (!self->spawnflags.has(SPAWNFLAG_BRAIN_NO_LASERS)) + M_SetAnimation(self, &brain_move_attack4); + } + else if (!self->spawnflags.has(SPAWNFLAG_BRAIN_NO_LASERS)) + M_SetAnimation(self, &brain_move_attack4); +} +// RAFAEL + +// +// RUN +// + +mframe_t brain_frames_run[] = { + { ai_run, 9 }, + { ai_run, 2 }, + { ai_run, 3 }, + { ai_run, 3 }, + { ai_run, 1 }, + { ai_run }, + { ai_run }, + { ai_run, 10 }, + { ai_run, -4 }, + { ai_run, -1 }, + { ai_run, 2 } +}; +MMOVE_T(brain_move_run) = { FRAME_walk101, FRAME_walk111, brain_frames_run, nullptr }; + +MONSTERINFO_RUN(brain_run) (edict_t *self) -> void +{ + self->monsterinfo.power_armor_type = IT_ITEM_POWER_SCREEN; + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + M_SetAnimation(self, &brain_move_stand); + else + M_SetAnimation(self, &brain_move_run); +} + +PAIN(brain_pain) (edict_t *self, edict_t *other, float kick, int damage, const mod_t &mod) -> void +{ + float r; + + if (level.time < self->pain_debounce_time) + return; + + self->pain_debounce_time = level.time + 3_sec; + + r = frandom(); + + if (r < 0.33f) + gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); + else if (r < 0.66f) + gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0); + else + gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); + + if (!M_ShouldReactToPain(self, mod)) + return; // no pain anims in nightmare + + if (r < 0.33f) + M_SetAnimation(self, &brain_move_pain1); + else if (r < 0.66f) + M_SetAnimation(self, &brain_move_pain2); + else + M_SetAnimation(self, &brain_move_pain3); + + // PMM - clear duck flag + if (self->monsterinfo.aiflags & AI_DUCKED) + monster_duck_up(self); +} + +MONSTERINFO_SETSKIN(brain_setskin) (edict_t *self) -> void +{ + if (self->health < (self->max_health / 2)) + self->s.skinnum = 1; + else + self->s.skinnum = 0; +} + +void brain_dead(edict_t *self) +{ + self->mins = { -16, -16, -24 }; + self->maxs = { 16, 16, -8 }; + monster_dead(self); +} + +DIE(brain_die) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void +{ + self->s.effects = EF_NONE; + self->monsterinfo.power_armor_type = IT_NULL; + + // check for gib + if (M_CheckGib(self, mod)) + { + gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0); + + self->s.skinnum /= 2; + + if (self->beam) + { + G_FreeEdict(self->beam); + self->beam = nullptr; + } + if (self->beam2) + { + G_FreeEdict(self->beam2); + self->beam2 = nullptr; + } + + ThrowGibs(self, damage, { + { 1, "models/objects/gibs/bone/tris.md2" }, + { 2, "models/objects/gibs/sm_meat/tris.md2" }, + { 2, "models/monsters/brain/gibs/arm.md2", GIB_SKINNED | GIB_UPRIGHT }, + { "models/monsters/brain/gibs/boot.md2", GIB_SKINNED | GIB_UPRIGHT }, + { "models/monsters/brain/gibs/pelvis.md2", GIB_SKINNED }, + { "models/monsters/brain/gibs/chest.md2", GIB_SKINNED }, + { 2, "models/monsters/brain/gibs/door.md2", GIB_SKINNED | GIB_UPRIGHT }, + { "models/monsters/brain/gibs/head.md2", GIB_SKINNED | GIB_HEAD } + }); + self->deadflag = true; + return; + } + + if (self->deadflag) + return; + + // regular death + gi.sound(self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0); + self->deadflag = true; + self->takedamage = true; + if (frandom() <= 0.5f) + M_SetAnimation(self, &brain_move_death1); + else + M_SetAnimation(self, &brain_move_death2); +} + +MONSTERINFO_DUCK(brain_duck) (edict_t *self, gtime_t eta) -> bool +{ + M_SetAnimation(self, &brain_move_duck); + + return true; +} + +/*QUAKED monster_brain (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight + */ +void SP_monster_brain(edict_t *self) +{ + if ( !M_AllowSpawn( self ) ) { + G_FreeEdict( self ); + return; + } + + sound_chest_open = gi.soundindex("brain/brnatck1.wav"); + sound_tentacles_extend = gi.soundindex("brain/brnatck2.wav"); + sound_tentacles_retract = gi.soundindex("brain/brnatck3.wav"); + sound_death = gi.soundindex("brain/brndeth1.wav"); + sound_idle1 = gi.soundindex("brain/brnidle1.wav"); + sound_idle2 = gi.soundindex("brain/brnidle2.wav"); + sound_idle3 = gi.soundindex("brain/brnlens1.wav"); + sound_pain1 = gi.soundindex("brain/brnpain1.wav"); + sound_pain2 = gi.soundindex("brain/brnpain2.wav"); + sound_sight = gi.soundindex("brain/brnsght1.wav"); + sound_search = gi.soundindex("brain/brnsrch1.wav"); + sound_melee1 = gi.soundindex("brain/melee1.wav"); + sound_melee2 = gi.soundindex("brain/melee2.wav"); + sound_melee3 = gi.soundindex("brain/melee3.wav"); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex("models/monsters/brain/tris.md2"); + + gi.modelindex("models/monsters/brain/gibs/arm.md2"); + gi.modelindex("models/monsters/brain/gibs/boot.md2"); + gi.modelindex("models/monsters/brain/gibs/chest.md2"); + gi.modelindex("models/monsters/brain/gibs/door.md2"); + gi.modelindex("models/monsters/brain/gibs/head.md2"); + gi.modelindex("models/monsters/brain/gibs/pelvis.md2"); + + self->mins = { -16, -16, -24 }; + self->maxs = { 16, 16, 32 }; + + self->health = 300 * st.health_multiplier; + self->gib_health = -150; + self->mass = 400; + + self->pain = brain_pain; + self->die = brain_die; + + self->monsterinfo.stand = brain_stand; + self->monsterinfo.walk = brain_walk; + self->monsterinfo.run = brain_run; + // PMM + self->monsterinfo.dodge = M_MonsterDodge; + self->monsterinfo.duck = brain_duck; + self->monsterinfo.unduck = monster_duck_up; + // pmm + // RAFAEL + self->monsterinfo.attack = brain_attack; + // RAFAEL + self->monsterinfo.melee = brain_melee; + self->monsterinfo.sight = brain_sight; + self->monsterinfo.search = brain_search; + self->monsterinfo.idle = brain_idle; + self->monsterinfo.setskin = brain_setskin; + + if (!st.was_key_specified("power_armor_type")) + self->monsterinfo.power_armor_type = IT_ITEM_POWER_SCREEN; + if (!st.was_key_specified("power_armor_power")) + self->monsterinfo.power_armor_power = 100; + + gi.linkentity(self); + + M_SetAnimation(self, &brain_move_stand); + self->monsterinfo.scale = MODEL_SCALE; + + walkmonster_start(self); +} diff --git a/rerelease/m_brain.h b/rerelease/m_brain.h new file mode 100644 index 0000000..b8a226c --- /dev/null +++ b/rerelease/m_brain.h @@ -0,0 +1,233 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// G:\quake2\baseq2\models/monsters/brain + +// This file generated by ModelGen - Do NOT Modify + +enum +{ + FRAME_walk101, + FRAME_walk102, + FRAME_walk103, + FRAME_walk104, + FRAME_walk105, + FRAME_walk106, + FRAME_walk107, + FRAME_walk108, + FRAME_walk109, + FRAME_walk110, + FRAME_walk111, + FRAME_walk112, + FRAME_walk113, + FRAME_walk201, + FRAME_walk202, + FRAME_walk203, + FRAME_walk204, + FRAME_walk205, + FRAME_walk206, + FRAME_walk207, + FRAME_walk208, + FRAME_walk209, + FRAME_walk210, + FRAME_walk211, + FRAME_walk212, + FRAME_walk213, + FRAME_walk214, + FRAME_walk215, + FRAME_walk216, + FRAME_walk217, + FRAME_walk218, + FRAME_walk219, + FRAME_walk220, + FRAME_walk221, + FRAME_walk222, + FRAME_walk223, + FRAME_walk224, + FRAME_walk225, + FRAME_walk226, + FRAME_walk227, + FRAME_walk228, + FRAME_walk229, + FRAME_walk230, + FRAME_walk231, + FRAME_walk232, + FRAME_walk233, + FRAME_walk234, + FRAME_walk235, + FRAME_walk236, + FRAME_walk237, + FRAME_walk238, + FRAME_walk239, + FRAME_walk240, + FRAME_attak101, + FRAME_attak102, + FRAME_attak103, + FRAME_attak104, + FRAME_attak105, + FRAME_attak106, + FRAME_attak107, + FRAME_attak108, + FRAME_attak109, + FRAME_attak110, + FRAME_attak111, + FRAME_attak112, + FRAME_attak113, + FRAME_attak114, + FRAME_attak115, + FRAME_attak116, + FRAME_attak117, + FRAME_attak118, + FRAME_attak201, + FRAME_attak202, + FRAME_attak203, + FRAME_attak204, + FRAME_attak205, + FRAME_attak206, + FRAME_attak207, + FRAME_attak208, + FRAME_attak209, + FRAME_attak210, + FRAME_attak211, + FRAME_attak212, + FRAME_attak213, + FRAME_attak214, + FRAME_attak215, + FRAME_attak216, + FRAME_attak217, + FRAME_pain101, + FRAME_pain102, + FRAME_pain103, + FRAME_pain104, + FRAME_pain105, + FRAME_pain106, + FRAME_pain107, + FRAME_pain108, + FRAME_pain109, + FRAME_pain110, + FRAME_pain111, + FRAME_pain112, + FRAME_pain113, + FRAME_pain114, + FRAME_pain115, + FRAME_pain116, + FRAME_pain117, + FRAME_pain118, + FRAME_pain119, + FRAME_pain120, + FRAME_pain121, + FRAME_pain201, + FRAME_pain202, + FRAME_pain203, + FRAME_pain204, + FRAME_pain205, + FRAME_pain206, + FRAME_pain207, + FRAME_pain208, + FRAME_pain301, + FRAME_pain302, + FRAME_pain303, + FRAME_pain304, + FRAME_pain305, + FRAME_pain306, + FRAME_death101, + FRAME_death102, + FRAME_death103, + FRAME_death104, + FRAME_death105, + FRAME_death106, + FRAME_death107, + FRAME_death108, + FRAME_death109, + FRAME_death110, + FRAME_death111, + FRAME_death112, + FRAME_death113, + FRAME_death114, + FRAME_death115, + FRAME_death116, + FRAME_death117, + FRAME_death118, + FRAME_death201, + FRAME_death202, + FRAME_death203, + FRAME_death204, + FRAME_death205, + FRAME_duck01, + FRAME_duck02, + FRAME_duck03, + FRAME_duck04, + FRAME_duck05, + FRAME_duck06, + FRAME_duck07, + FRAME_duck08, + FRAME_defens01, + FRAME_defens02, + FRAME_defens03, + FRAME_defens04, + FRAME_defens05, + FRAME_defens06, + FRAME_defens07, + FRAME_defens08, + FRAME_stand01, + FRAME_stand02, + FRAME_stand03, + FRAME_stand04, + FRAME_stand05, + FRAME_stand06, + FRAME_stand07, + FRAME_stand08, + FRAME_stand09, + FRAME_stand10, + FRAME_stand11, + FRAME_stand12, + FRAME_stand13, + FRAME_stand14, + FRAME_stand15, + FRAME_stand16, + FRAME_stand17, + FRAME_stand18, + FRAME_stand19, + FRAME_stand20, + FRAME_stand21, + FRAME_stand22, + FRAME_stand23, + FRAME_stand24, + FRAME_stand25, + FRAME_stand26, + FRAME_stand27, + FRAME_stand28, + FRAME_stand29, + FRAME_stand30, + FRAME_stand31, + FRAME_stand32, + FRAME_stand33, + FRAME_stand34, + FRAME_stand35, + FRAME_stand36, + FRAME_stand37, + FRAME_stand38, + FRAME_stand39, + FRAME_stand40, + FRAME_stand41, + FRAME_stand42, + FRAME_stand43, + FRAME_stand44, + FRAME_stand45, + FRAME_stand46, + FRAME_stand47, + FRAME_stand48, + FRAME_stand49, + FRAME_stand50, + FRAME_stand51, + FRAME_stand52, + FRAME_stand53, + FRAME_stand54, + FRAME_stand55, + FRAME_stand56, + FRAME_stand57, + FRAME_stand58, + FRAME_stand59, + FRAME_stand60 +}; + +constexpr float MODEL_SCALE = 1.000000f; diff --git a/rerelease/m_chick.cpp b/rerelease/m_chick.cpp new file mode 100644 index 0000000..9a62e44 --- /dev/null +++ b/rerelease/m_chick.cpp @@ -0,0 +1,869 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +/* +============================================================================== + +chick + +============================================================================== +*/ + +#include "g_local.h" +#include "m_chick.h" +#include "m_flash.h" + +void chick_stand(edict_t *self); +void chick_run(edict_t *self); +void chick_reslash(edict_t *self); +void chick_rerocket(edict_t *self); +void chick_attack1(edict_t *self); + +static int sound_missile_prelaunch; +static int sound_missile_launch; +static int sound_melee_swing; +static int sound_melee_hit; +static int sound_missile_reload; +static int sound_death1; +static int sound_death2; +static int sound_fall_down; +static int sound_idle1; +static int sound_idle2; +static int sound_pain1; +static int sound_pain2; +static int sound_pain3; +static int sound_sight; +static int sound_search; + +void ChickMoan(edict_t *self) +{ + if (frandom() < 0.5f) + gi.sound(self, CHAN_VOICE, sound_idle1, 1, ATTN_IDLE, 0); + else + gi.sound(self, CHAN_VOICE, sound_idle2, 1, ATTN_IDLE, 0); +} + +mframe_t chick_frames_fidget[] = { + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand, 0, ChickMoan }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand } +}; +MMOVE_T(chick_move_fidget) = { FRAME_stand201, FRAME_stand230, chick_frames_fidget, chick_stand }; + +void chick_fidget(edict_t *self) +{ + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + return; + else if (self->enemy) + return; + if (frandom() <= 0.3f) + M_SetAnimation(self, &chick_move_fidget); +} + +mframe_t chick_frames_stand[] = { + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand, 0, chick_fidget }, +}; +MMOVE_T(chick_move_stand) = { FRAME_stand101, FRAME_stand130, chick_frames_stand, nullptr }; + +MONSTERINFO_STAND(chick_stand) (edict_t *self) -> void +{ + M_SetAnimation(self, &chick_move_stand); +} + +mframe_t chick_frames_start_run[] = { + { ai_run, 1 }, + { ai_run }, + { ai_run, 0, monster_footstep }, + { ai_run, -1 }, + { ai_run, -1, monster_footstep }, + { ai_run }, + { ai_run, 1 }, + { ai_run, 3 }, + { ai_run, 6 }, + { ai_run, 3 } +}; +MMOVE_T(chick_move_start_run) = { FRAME_walk01, FRAME_walk10, chick_frames_start_run, chick_run }; + +mframe_t chick_frames_run[] = { + { ai_run, 6 }, + { ai_run, 8, monster_footstep }, + { ai_run, 13 }, + { ai_run, 5, monster_done_dodge }, // make sure to clear dodge bit + { ai_run, 7 }, + { ai_run, 4 }, + { ai_run, 11, monster_footstep }, + { ai_run, 5 }, + { ai_run, 9 }, + { ai_run, 7 } +}; + +MMOVE_T(chick_move_run) = { FRAME_walk11, FRAME_walk20, chick_frames_run, nullptr }; + +mframe_t chick_frames_walk[] = { + { ai_walk, 6 }, + { ai_walk, 8, monster_footstep }, + { ai_walk, 13 }, + { ai_walk, 5 }, + { ai_walk, 7 }, + { ai_walk, 4 }, + { ai_walk, 11, monster_footstep }, + { ai_walk, 5 }, + { ai_walk, 9 }, + { ai_walk, 7 } +}; + +MMOVE_T(chick_move_walk) = { FRAME_walk11, FRAME_walk20, chick_frames_walk, nullptr }; + +MONSTERINFO_WALK(chick_walk) (edict_t *self) -> void +{ + M_SetAnimation(self, &chick_move_walk); +} + +MONSTERINFO_RUN(chick_run) (edict_t *self) -> void +{ + monster_done_dodge(self); + + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + { + M_SetAnimation(self, &chick_move_stand); + return; + } + + if (self->monsterinfo.active_move == &chick_move_walk || + self->monsterinfo.active_move == &chick_move_start_run) + { + M_SetAnimation(self, &chick_move_run); + } + else + { + M_SetAnimation(self, &chick_move_start_run); + } +} + +mframe_t chick_frames_pain1[] = { + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move } +}; +MMOVE_T(chick_move_pain1) = { FRAME_pain101, FRAME_pain105, chick_frames_pain1, chick_run }; + +mframe_t chick_frames_pain2[] = { + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move } +}; +MMOVE_T(chick_move_pain2) = { FRAME_pain201, FRAME_pain205, chick_frames_pain2, chick_run }; + +mframe_t chick_frames_pain3[] = { + { ai_move }, + { ai_move, 0, monster_footstep }, + { ai_move, -6 }, + { ai_move, 3, monster_footstep }, + { ai_move, 11 }, + { ai_move, 3, monster_footstep }, + { ai_move }, + { ai_move }, + { ai_move, 4 }, + { ai_move, 1 }, + { ai_move }, + { ai_move, -3 }, + { ai_move, -4 }, + { ai_move, 5 }, + { ai_move, 7 }, + { ai_move, -2 }, + { ai_move, 3 }, + { ai_move, -5 }, + { ai_move, -2 }, + { ai_move, -8 }, + { ai_move, 2, monster_footstep } +}; +MMOVE_T(chick_move_pain3) = { FRAME_pain301, FRAME_pain321, chick_frames_pain3, chick_run }; + +PAIN(chick_pain) (edict_t *self, edict_t *other, float kick, int damage, const mod_t &mod) -> void +{ + float r; + + monster_done_dodge(self); + + if (level.time < self->pain_debounce_time) + return; + + self->pain_debounce_time = level.time + 3_sec; + + r = frandom(); + if (r < 0.33f) + gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); + else if (r < 0.66f) + gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0); + else + gi.sound(self, CHAN_VOICE, sound_pain3, 1, ATTN_NORM, 0); + + if (!M_ShouldReactToPain(self, mod)) + return; // no pain anims in nightmare + + // PMM - clear this from blindfire + self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING; + + if (damage <= 10) + M_SetAnimation(self, &chick_move_pain1); + else if (damage <= 25) + M_SetAnimation(self, &chick_move_pain2); + else + M_SetAnimation(self, &chick_move_pain3); + + // PMM - clear duck flag + if (self->monsterinfo.aiflags & AI_DUCKED) + monster_duck_up(self); +} + +MONSTERINFO_SETSKIN(chick_setpain) (edict_t *self) -> void +{ + if (self->health < (self->max_health / 2)) + self->s.skinnum |= 1; + else + self->s.skinnum &= ~1; +} + +void chick_dead(edict_t *self) +{ + self->mins = { -16, -16, 0 }; + self->maxs = { 16, 16, 8 }; + monster_dead(self); +} + +static void chick_shrink(edict_t *self) +{ + self->maxs[2] = 12; + self->svflags |= SVF_DEADMONSTER; + gi.linkentity(self); +} + +mframe_t chick_frames_death2[] = { + { ai_move, -6 }, + { ai_move }, + { ai_move, -1 }, + { ai_move, -5, monster_footstep }, + { ai_move }, + { ai_move, -1 }, + { ai_move, -2 }, + { ai_move, 1 }, + { ai_move, 10 }, + { ai_move, 2 }, + { ai_move, 3, monster_footstep }, + { ai_move, 1 }, + { ai_move, 2 }, + { ai_move }, + { ai_move, 3 }, + { ai_move, 3 }, + { ai_move, 1, monster_footstep }, + { ai_move, -3 }, + { ai_move, -5 }, + { ai_move, 4 }, + { ai_move, 15, chick_shrink }, + { ai_move, 14, monster_footstep }, + { ai_move, 1 } +}; +MMOVE_T(chick_move_death2) = { FRAME_death201, FRAME_death223, chick_frames_death2, chick_dead }; + +mframe_t chick_frames_death1[] = { + { ai_move }, + { ai_move, 0, monster_footstep }, + { ai_move, -7 }, + { ai_move, 4, monster_footstep }, + { ai_move, 11, chick_shrink }, + { ai_move }, + { ai_move, 0, monster_footstep }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move, 0, monster_footstep }, + { ai_move } +}; +MMOVE_T(chick_move_death1) = { FRAME_death101, FRAME_death112, chick_frames_death1, chick_dead }; + +DIE(chick_die) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void +{ + int n; + + // check for gib + if (M_CheckGib(self, mod)) + { + gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0); + + self->s.skinnum /= 2; + + ThrowGibs(self, damage, { + { 2, "models/objects/gibs/bone/tris.md2" }, + { 3, "models/objects/gibs/sm_meat/tris.md2" }, + { "models/monsters/bitch/gibs/arm.md2", GIB_SKINNED | GIB_UPRIGHT }, + { "models/monsters/bitch/gibs/foot.md2", GIB_SKINNED | GIB_UPRIGHT }, + { "models/monsters/bitch/gibs/tube.md2", GIB_SKINNED | GIB_UPRIGHT }, + { "models/monsters/bitch/gibs/chest.md2", GIB_SKINNED }, + { "models/monsters/bitch/gibs/head.md2", GIB_HEAD | GIB_SKINNED } + }); + self->deadflag = true; + + return; + } + + if (self->deadflag) + return; + + // regular death + self->deadflag = true; + self->takedamage = true; + + n = brandom(); + + if (n == 0) + { + M_SetAnimation(self, &chick_move_death1); + gi.sound(self, CHAN_VOICE, sound_death1, 1, ATTN_NORM, 0); + } + else + { + M_SetAnimation(self, &chick_move_death2); + gi.sound(self, CHAN_VOICE, sound_death2, 1, ATTN_NORM, 0); + } +} + +// PMM - changes to duck code for new dodge + +mframe_t chick_frames_duck[] = { + { ai_move, 0, monster_duck_down }, + { ai_move, 1 }, + { ai_move, 4, monster_duck_hold }, + { ai_move, -4 }, + { ai_move, -5, monster_duck_up }, + { ai_move, 3 }, + { ai_move, 1 } +}; +MMOVE_T(chick_move_duck) = { FRAME_duck01, FRAME_duck07, chick_frames_duck, chick_run }; + +void ChickSlash(edict_t *self) +{ + vec3_t aim = { MELEE_DISTANCE, self->mins[0], 10 }; + gi.sound(self, CHAN_WEAPON, sound_melee_swing, 1, ATTN_NORM, 0); + fire_hit(self, aim, irandom(10, 16), 100); +} + +void ChickRocket(edict_t *self) +{ + vec3_t forward, right; + vec3_t start; + vec3_t dir; + vec3_t vec; + trace_t trace; // PMM - check target + int rocketSpeed; + // pmm - blindfire + vec3_t target; + bool blindfire = false; + + if (self->monsterinfo.aiflags & AI_MANUAL_STEERING) + blindfire = true; + else + blindfire = false; + + if (!self->enemy || !self->enemy->inuse) // PGM + return; // PGM + + AngleVectors(self->s.angles, forward, right, nullptr); + start = M_ProjectFlashSource(self, monster_flash_offset[MZ2_CHICK_ROCKET_1], forward, right); + + // [Paril-KEX] + if (self->s.skinnum > 1) + rocketSpeed = 500; + else + rocketSpeed = 650; + + // PMM + if (blindfire) + target = self->monsterinfo.blind_fire_target; + else + target = self->enemy->s.origin; + // pmm + // PGM + // PMM - blindfire shooting + if (blindfire) + { + vec = target; + dir = vec - start; + } + // pmm + // don't shoot at feet if they're above where i'm shooting from. + else if (frandom() < 0.33f || (start[2] < self->enemy->absmin[2])) + { + vec = target; + vec[2] += self->enemy->viewheight; + dir = vec - start; + } + else + { + vec = target; + vec[2] = self->enemy->absmin[2] + 1; + dir = vec - start; + } + // PGM + + //====== + // PMM - lead target (not when blindfiring) + // 20, 35, 50, 65 chance of leading + if ((!blindfire) && (frandom() < 0.35f)) + PredictAim(self, self->enemy, start, rocketSpeed, false, 0.f, &dir, &vec); + // PMM - lead target + //====== + + dir.normalize(); + + // pmm blindfire doesn't check target (done in checkattack) + // paranoia, make sure we're not shooting a target right next to us + trace = gi.traceline(start, vec, self, MASK_PROJECTILE); + if (blindfire) + { + // blindfire has different fail criteria for the trace + if (!(trace.startsolid || trace.allsolid || (trace.fraction < 0.5f))) + { + // RAFAEL + if (self->s.skinnum > 1) + monster_fire_heat(self, start, dir, 50, rocketSpeed, MZ2_CHICK_ROCKET_1, 0.075f); + else + // RAFAEL + monster_fire_rocket(self, start, dir, 50, rocketSpeed, MZ2_CHICK_ROCKET_1); + } + else + { + // geez, this is bad. she's avoiding about 80% of her blindfires due to hitting things. + // hunt around for a good shot + // try shifting the target to the left a little (to help counter her large offset) + vec = target; + vec += (right * -10); + dir = vec - start; + dir.normalize(); + trace = gi.traceline(start, vec, self, MASK_PROJECTILE); + if (!(trace.startsolid || trace.allsolid || (trace.fraction < 0.5f))) + { + // RAFAEL + if (self->s.skinnum > 1) + monster_fire_heat(self, start, dir, 50, rocketSpeed, MZ2_CHICK_ROCKET_1, 0.075f); + else + // RAFAEL + monster_fire_rocket(self, start, dir, 50, rocketSpeed, MZ2_CHICK_ROCKET_1); + } + else + { + // ok, that failed. try to the right + vec = target; + vec += (right * 10); + dir = vec - start; + dir.normalize(); + trace = gi.traceline(start, vec, self, MASK_PROJECTILE); + if (!(trace.startsolid || trace.allsolid || (trace.fraction < 0.5f))) + { + // RAFAEL + if (self->s.skinnum > 1) + monster_fire_heat(self, start, dir, 50, rocketSpeed, MZ2_CHICK_ROCKET_1, 0.075f); + else + // RAFAEL + monster_fire_rocket(self, start, dir, 50, rocketSpeed, MZ2_CHICK_ROCKET_1); + } + } + } + } + else + { + if (trace.fraction > 0.5f || trace.ent->solid != SOLID_BSP) + { + // RAFAEL + if (self->s.skinnum > 1) + monster_fire_heat(self, start, dir, 50, rocketSpeed, MZ2_CHICK_ROCKET_1, 0.15f); + else + // RAFAEL + monster_fire_rocket(self, start, dir, 50, rocketSpeed, MZ2_CHICK_ROCKET_1); + } + } +} + +void Chick_PreAttack1(edict_t *self) +{ + gi.sound(self, CHAN_VOICE, sound_missile_prelaunch, 1, ATTN_NORM, 0); + + if (self->monsterinfo.aiflags & AI_MANUAL_STEERING) + { + vec3_t aim = self->monsterinfo.blind_fire_target - self->s.origin; + self->ideal_yaw = vectoyaw(aim); + } +} + +void ChickReload(edict_t *self) +{ + gi.sound(self, CHAN_VOICE, sound_missile_reload, 1, ATTN_NORM, 0); +} + +mframe_t chick_frames_start_attack1[] = { + { ai_charge, 0, Chick_PreAttack1 }, + { ai_charge }, + { ai_charge }, + { ai_charge, 4 }, + { ai_charge }, + { ai_charge, -3 }, + { ai_charge, 3 }, + { ai_charge, 5 }, + { ai_charge, 7, monster_footstep }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge, 0, chick_attack1 } +}; +MMOVE_T(chick_move_start_attack1) = { FRAME_attak101, FRAME_attak113, chick_frames_start_attack1, nullptr }; + +mframe_t chick_frames_attack1[] = { + { ai_charge, 19, ChickRocket }, + { ai_charge, -6, monster_footstep }, + { ai_charge, -5 }, + { ai_charge, -2 }, + { ai_charge, -7, monster_footstep }, + { ai_charge }, + { ai_charge, 1 }, + { ai_charge, 10, ChickReload }, + { ai_charge, 4 }, + { ai_charge, 5, monster_footstep }, + { ai_charge, 6 }, + { ai_charge, 6 }, + { ai_charge, 4 }, + { ai_charge, 3, [](edict_t *self) { chick_rerocket(self); monster_footstep(self); } } +}; +MMOVE_T(chick_move_attack1) = { FRAME_attak114, FRAME_attak127, chick_frames_attack1, nullptr }; + +mframe_t chick_frames_end_attack1[] = { + { ai_charge, -3 }, + { ai_charge }, + { ai_charge, -6 }, + { ai_charge, -4 }, + { ai_charge, -2, monster_footstep } +}; +MMOVE_T(chick_move_end_attack1) = { FRAME_attak128, FRAME_attak132, chick_frames_end_attack1, chick_run }; + +void chick_rerocket(edict_t *self) +{ + if (self->monsterinfo.aiflags & AI_MANUAL_STEERING) + { + self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING; + M_SetAnimation(self, &chick_move_end_attack1); + return; + } + + if (!M_CheckClearShot(self, monster_flash_offset[MZ2_CHICK_ROCKET_1])) + { + M_SetAnimation(self, &chick_move_end_attack1); + return; + } + + if (self->enemy->health > 0) + { + if (range_to(self, self->enemy) > RANGE_MELEE) + if (visible(self, self->enemy)) + if (frandom() <= 0.7f) + { + M_SetAnimation(self, &chick_move_attack1); + return; + } + } + M_SetAnimation(self, &chick_move_end_attack1); +} + +void chick_attack1(edict_t *self) +{ + M_SetAnimation(self, &chick_move_attack1); +} + +mframe_t chick_frames_slash[] = { + { ai_charge, 1 }, + { ai_charge, 7, ChickSlash }, + { ai_charge, -7, monster_footstep }, + { ai_charge, 1 }, + { ai_charge, -1 }, + { ai_charge, 1 }, + { ai_charge }, + { ai_charge, 1 }, + { ai_charge, -2, chick_reslash } +}; +MMOVE_T(chick_move_slash) = { FRAME_attak204, FRAME_attak212, chick_frames_slash, nullptr }; + +mframe_t chick_frames_end_slash[] = { + { ai_charge, -6 }, + { ai_charge, -1 }, + { ai_charge, -6 }, + { ai_charge, 0, monster_footstep } +}; +MMOVE_T(chick_move_end_slash) = { FRAME_attak213, FRAME_attak216, chick_frames_end_slash, chick_run }; + +void chick_reslash(edict_t *self) +{ + if (self->enemy->health > 0) + { + if (range_to(self, self->enemy) <= RANGE_MELEE) + { + if (frandom() <= 0.9f) + { + M_SetAnimation(self, &chick_move_slash); + return; + } + else + { + M_SetAnimation(self, &chick_move_end_slash); + return; + } + } + } + M_SetAnimation(self, &chick_move_end_slash); +} + +void chick_slash(edict_t *self) +{ + M_SetAnimation(self, &chick_move_slash); +} + +mframe_t chick_frames_start_slash[] = { + { ai_charge, 1 }, + { ai_charge, 8 }, + { ai_charge, 3 } +}; +MMOVE_T(chick_move_start_slash) = { FRAME_attak201, FRAME_attak203, chick_frames_start_slash, chick_slash }; + +MONSTERINFO_MELEE(chick_melee) (edict_t *self) -> void +{ + M_SetAnimation(self, &chick_move_start_slash); +} + +MONSTERINFO_ATTACK(chick_attack) (edict_t *self) -> void +{ + if (!M_CheckClearShot(self, monster_flash_offset[MZ2_CHICK_ROCKET_1])) + return; + + float r, chance; + + monster_done_dodge(self); + + // PMM + if (self->monsterinfo.attack_state == AS_BLIND) + { + // setup shot probabilities + if (self->monsterinfo.blind_fire_delay < 1.0_sec) + chance = 1.0; + else if (self->monsterinfo.blind_fire_delay < 7.5_sec) + chance = 0.4f; + else + chance = 0.1f; + + r = frandom(); + + // minimum of 5.5 seconds, plus 0-1, after the shots are done + self->monsterinfo.blind_fire_delay += random_time(5.5_sec, 6.5_sec); + + // don't shoot at the origin + if (!self->monsterinfo.blind_fire_target) + return; + + // don't shoot if the dice say not to + if (r > chance) + return; + + // turn on manual steering to signal both manual steering and blindfire + self->monsterinfo.aiflags |= AI_MANUAL_STEERING; + M_SetAnimation(self, &chick_move_start_attack1); + self->monsterinfo.attack_finished = level.time + random_time(2_sec); + return; + } + // pmm + + M_SetAnimation(self, &chick_move_start_attack1); +} + +MONSTERINFO_SIGHT(chick_sight) (edict_t *self, edict_t *other) -> void +{ + gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); +} + +//=========== +// PGM +MONSTERINFO_BLOCKED(chick_blocked) (edict_t *self, float dist) -> bool +{ + if (blocked_checkplat(self, dist)) + return true; + + return false; +} +// PGM +//=========== + +MONSTERINFO_DUCK(chick_duck) (edict_t *self, gtime_t eta) -> bool +{ + if ((self->monsterinfo.active_move == &chick_move_start_attack1) || + (self->monsterinfo.active_move == &chick_move_attack1)) + { + // if we're shooting don't dodge + self->monsterinfo.unduck(self); + return false; + } + + M_SetAnimation(self, &chick_move_duck); + + return true; +} + +MONSTERINFO_SIDESTEP(chick_sidestep) (edict_t *self) -> bool +{ + if ((self->monsterinfo.active_move == &chick_move_start_attack1) || + (self->monsterinfo.active_move == &chick_move_attack1) || + (self->monsterinfo.active_move == &chick_move_pain3)) + { + // if we're shooting, don't dodge + return false; + } + + if (self->monsterinfo.active_move != &chick_move_run) + M_SetAnimation(self, &chick_move_run); + + return true; +} + +/*QUAKED monster_chick (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight + */ +void SP_monster_chick(edict_t *self) +{ + if ( !M_AllowSpawn( self ) ) { + G_FreeEdict( self ); + return; + } + + sound_missile_prelaunch = gi.soundindex("chick/chkatck1.wav"); + sound_missile_launch = gi.soundindex("chick/chkatck2.wav"); + sound_melee_swing = gi.soundindex("chick/chkatck3.wav"); + sound_melee_hit = gi.soundindex("chick/chkatck4.wav"); + sound_missile_reload = gi.soundindex("chick/chkatck5.wav"); + sound_death1 = gi.soundindex("chick/chkdeth1.wav"); + sound_death2 = gi.soundindex("chick/chkdeth2.wav"); + sound_fall_down = gi.soundindex("chick/chkfall1.wav"); + sound_idle1 = gi.soundindex("chick/chkidle1.wav"); + sound_idle2 = gi.soundindex("chick/chkidle2.wav"); + sound_pain1 = gi.soundindex("chick/chkpain1.wav"); + sound_pain2 = gi.soundindex("chick/chkpain2.wav"); + sound_pain3 = gi.soundindex("chick/chkpain3.wav"); + sound_sight = gi.soundindex("chick/chksght1.wav"); + sound_search = gi.soundindex("chick/chksrch1.wav"); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex("models/monsters/bitch/tris.md2"); + + gi.modelindex("models/monsters/bitch/gibs/arm.md2"); + gi.modelindex("models/monsters/bitch/gibs/chest.md2"); + gi.modelindex("models/monsters/bitch/gibs/foot.md2"); + gi.modelindex("models/monsters/bitch/gibs/head.md2"); + gi.modelindex("models/monsters/bitch/gibs/tube.md2"); + + self->mins = { -16, -16, 0 }; + self->maxs = { 16, 16, 56 }; + + self->health = 175 * st.health_multiplier; + self->gib_health = -70; + self->mass = 200; + + self->pain = chick_pain; + self->die = chick_die; + + self->monsterinfo.stand = chick_stand; + self->monsterinfo.walk = chick_walk; + self->monsterinfo.run = chick_run; + // pmm + self->monsterinfo.dodge = M_MonsterDodge; + self->monsterinfo.duck = chick_duck; + self->monsterinfo.unduck = monster_duck_up; + self->monsterinfo.sidestep = chick_sidestep; + self->monsterinfo.blocked = chick_blocked; // PGM + // pmm + self->monsterinfo.attack = chick_attack; + self->monsterinfo.melee = chick_melee; + self->monsterinfo.sight = chick_sight; + self->monsterinfo.setskin = chick_setpain; + + gi.linkentity(self); + + M_SetAnimation(self, &chick_move_stand); + self->monsterinfo.scale = MODEL_SCALE; + + // PMM + self->monsterinfo.blindfire = true; + // pmm + walkmonster_start(self); +} + +// RAFAEL +/*QUAKED monster_chick_heat (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight + */ +void SP_monster_chick_heat(edict_t *self) +{ + SP_monster_chick(self); + self->s.skinnum = 2; + gi.soundindex("weapons/railgr1a.wav"); +} +// RAFAEL diff --git a/rerelease/m_chick.h b/rerelease/m_chick.h new file mode 100644 index 0000000..94f691c --- /dev/null +++ b/rerelease/m_chick.h @@ -0,0 +1,299 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// G:\quake2\baseq2\models/monsters/bitch + +// This file generated by qdata - Do NOT Modify + +enum +{ + FRAME_attak101, + FRAME_attak102, + FRAME_attak103, + FRAME_attak104, + FRAME_attak105, + FRAME_attak106, + FRAME_attak107, + FRAME_attak108, + FRAME_attak109, + FRAME_attak110, + FRAME_attak111, + FRAME_attak112, + FRAME_attak113, + FRAME_attak114, + FRAME_attak115, + FRAME_attak116, + FRAME_attak117, + FRAME_attak118, + FRAME_attak119, + FRAME_attak120, + FRAME_attak121, + FRAME_attak122, + FRAME_attak123, + FRAME_attak124, + FRAME_attak125, + FRAME_attak126, + FRAME_attak127, + FRAME_attak128, + FRAME_attak129, + FRAME_attak130, + FRAME_attak131, + FRAME_attak132, + FRAME_attak201, + FRAME_attak202, + FRAME_attak203, + FRAME_attak204, + FRAME_attak205, + FRAME_attak206, + FRAME_attak207, + FRAME_attak208, + FRAME_attak209, + FRAME_attak210, + FRAME_attak211, + FRAME_attak212, + FRAME_attak213, + FRAME_attak214, + FRAME_attak215, + FRAME_attak216, + FRAME_death101, + FRAME_death102, + FRAME_death103, + FRAME_death104, + FRAME_death105, + FRAME_death106, + FRAME_death107, + FRAME_death108, + FRAME_death109, + FRAME_death110, + FRAME_death111, + FRAME_death112, + FRAME_death201, + FRAME_death202, + FRAME_death203, + FRAME_death204, + FRAME_death205, + FRAME_death206, + FRAME_death207, + FRAME_death208, + FRAME_death209, + FRAME_death210, + FRAME_death211, + FRAME_death212, + FRAME_death213, + FRAME_death214, + FRAME_death215, + FRAME_death216, + FRAME_death217, + FRAME_death218, + FRAME_death219, + FRAME_death220, + FRAME_death221, + FRAME_death222, + FRAME_death223, + FRAME_duck01, + FRAME_duck02, + FRAME_duck03, + FRAME_duck04, + FRAME_duck05, + FRAME_duck06, + FRAME_duck07, + FRAME_pain101, + FRAME_pain102, + FRAME_pain103, + FRAME_pain104, + FRAME_pain105, + FRAME_pain201, + FRAME_pain202, + FRAME_pain203, + FRAME_pain204, + FRAME_pain205, + FRAME_pain301, + FRAME_pain302, + FRAME_pain303, + FRAME_pain304, + FRAME_pain305, + FRAME_pain306, + FRAME_pain307, + FRAME_pain308, + FRAME_pain309, + FRAME_pain310, + FRAME_pain311, + FRAME_pain312, + FRAME_pain313, + FRAME_pain314, + FRAME_pain315, + FRAME_pain316, + FRAME_pain317, + FRAME_pain318, + FRAME_pain319, + FRAME_pain320, + FRAME_pain321, + FRAME_stand101, + FRAME_stand102, + FRAME_stand103, + FRAME_stand104, + FRAME_stand105, + FRAME_stand106, + FRAME_stand107, + FRAME_stand108, + FRAME_stand109, + FRAME_stand110, + FRAME_stand111, + FRAME_stand112, + FRAME_stand113, + FRAME_stand114, + FRAME_stand115, + FRAME_stand116, + FRAME_stand117, + FRAME_stand118, + FRAME_stand119, + FRAME_stand120, + FRAME_stand121, + FRAME_stand122, + FRAME_stand123, + FRAME_stand124, + FRAME_stand125, + FRAME_stand126, + FRAME_stand127, + FRAME_stand128, + FRAME_stand129, + FRAME_stand130, + FRAME_stand201, + FRAME_stand202, + FRAME_stand203, + FRAME_stand204, + FRAME_stand205, + FRAME_stand206, + FRAME_stand207, + FRAME_stand208, + FRAME_stand209, + FRAME_stand210, + FRAME_stand211, + FRAME_stand212, + FRAME_stand213, + FRAME_stand214, + FRAME_stand215, + FRAME_stand216, + FRAME_stand217, + FRAME_stand218, + FRAME_stand219, + FRAME_stand220, + FRAME_stand221, + FRAME_stand222, + FRAME_stand223, + FRAME_stand224, + FRAME_stand225, + FRAME_stand226, + FRAME_stand227, + FRAME_stand228, + FRAME_stand229, + FRAME_stand230, + FRAME_walk01, + FRAME_walk02, + FRAME_walk03, + FRAME_walk04, + FRAME_walk05, + FRAME_walk06, + FRAME_walk07, + FRAME_walk08, + FRAME_walk09, + FRAME_walk10, + FRAME_walk11, + FRAME_walk12, + FRAME_walk13, + FRAME_walk14, + FRAME_walk15, + FRAME_walk16, + FRAME_walk17, + FRAME_walk18, + FRAME_walk19, + FRAME_walk20, + FRAME_walk21, + FRAME_walk22, + FRAME_walk23, + FRAME_walk24, + FRAME_walk25, + FRAME_walk26, + FRAME_walk27, + FRAME_recln201, + FRAME_recln202, + FRAME_recln203, + FRAME_recln204, + FRAME_recln205, + FRAME_recln206, + FRAME_recln207, + FRAME_recln208, + FRAME_recln209, + FRAME_recln210, + FRAME_recln211, + FRAME_recln212, + FRAME_recln213, + FRAME_recln214, + FRAME_recln215, + FRAME_recln216, + FRAME_recln217, + FRAME_recln218, + FRAME_recln219, + FRAME_recln220, + FRAME_recln221, + FRAME_recln222, + FRAME_recln223, + FRAME_recln224, + FRAME_recln225, + FRAME_recln226, + FRAME_recln227, + FRAME_recln228, + FRAME_recln229, + FRAME_recln230, + FRAME_recln231, + FRAME_recln232, + FRAME_recln233, + FRAME_recln234, + FRAME_recln235, + FRAME_recln236, + FRAME_recln237, + FRAME_recln238, + FRAME_recln239, + FRAME_recln240, + FRAME_recln101, + FRAME_recln102, + FRAME_recln103, + FRAME_recln104, + FRAME_recln105, + FRAME_recln106, + FRAME_recln107, + FRAME_recln108, + FRAME_recln109, + FRAME_recln110, + FRAME_recln111, + FRAME_recln112, + FRAME_recln113, + FRAME_recln114, + FRAME_recln115, + FRAME_recln116, + FRAME_recln117, + FRAME_recln118, + FRAME_recln119, + FRAME_recln120, + FRAME_recln121, + FRAME_recln122, + FRAME_recln123, + FRAME_recln124, + FRAME_recln125, + FRAME_recln126, + FRAME_recln127, + FRAME_recln128, + FRAME_recln129, + FRAME_recln130, + FRAME_recln131, + FRAME_recln132, + FRAME_recln133, + FRAME_recln134, + FRAME_recln135, + FRAME_recln136, + FRAME_recln137, + FRAME_recln138, + FRAME_recln139, + FRAME_recln140 +}; + +constexpr float MODEL_SCALE = 1.000000f; diff --git a/rerelease/m_flash.h b/rerelease/m_flash.h new file mode 100644 index 0000000..a931ee1 --- /dev/null +++ b/rerelease/m_flash.h @@ -0,0 +1,619 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// m_flash.h + +// this file is included in both the game dll and quake2, +// the game needs it to source shot locations, the client +// needs it to position muzzle flashes +constexpr vec3_t monster_flash_offset[] = { + // flash 0 is not used + { 0.0f, 0.0f, 0.0f }, + + // MZ2_TANK_BLASTER_1 1 + { 28.7f, -18.5f, 28.7f }, + // MZ2_TANK_BLASTER_2 2 + { 24.6f, -21.5f, 30.1f }, + // MZ2_TANK_BLASTER_3 3 + { 19.8f, -23.9f, 32.1f }, + // MZ2_TANK_MACHINEGUN_1 4 + { 22.9f, -0.7f, 25.3f }, + // MZ2_TANK_MACHINEGUN_2 5 + { 22.2f, 6.2f, 22.3f }, + // MZ2_TANK_MACHINEGUN_3 6 + { 19.4f, 13.1f, 18.6f }, + // MZ2_TANK_MACHINEGUN_4 7 + { 19.4f, 18.8f, 18.6f }, + // MZ2_TANK_MACHINEGUN_5 8 + { 17.9f, 25.0f, 18.6f }, + // MZ2_TANK_MACHINEGUN_6 9 + { 14.1f, 30.5f, 20.6f }, + // MZ2_TANK_MACHINEGUN_7 10 + { 9.3f, 35.3f, 22.1f }, + // MZ2_TANK_MACHINEGUN_8 11 + { 4.7f, 38.4f, 22.1f }, + // MZ2_TANK_MACHINEGUN_9 12 + { -1.1f, 40.4f, 24.1f }, + // MZ2_TANK_MACHINEGUN_10 13 + { -6.5f, 41.2f, 24.1f }, + // MZ2_TANK_MACHINEGUN_11 14 + { 3.2f, 40.1f, 24.7f }, + // MZ2_TANK_MACHINEGUN_12 15 + { 11.7f, 36.7f, 26.0f }, + // MZ2_TANK_MACHINEGUN_13 16 + { 18.9f, 31.3f, 26.0f }, + // MZ2_TANK_MACHINEGUN_14 17 + { 24.4f, 24.4f, 26.4f }, + // MZ2_TANK_MACHINEGUN_15 18 + { 27.1f, 17.1f, 27.2f }, + // MZ2_TANK_MACHINEGUN_16 19 + { 28.5f, 9.1f, 28.0f }, + // MZ2_TANK_MACHINEGUN_17 20 + { 27.1f, 2.2f, 28.0f }, + // MZ2_TANK_MACHINEGUN_18 21 + { 24.9f, -2.8f, 28.0f }, + // MZ2_TANK_MACHINEGUN_19 22 + { 21.6f, -7.0f, 26.4f }, + // MZ2_TANK_ROCKET_1 23 + { 6.2f, 29.1f, 49.1f }, + // MZ2_TANK_ROCKET_2 24 + { 6.9f, 23.8f, 49.1f }, + // MZ2_TANK_ROCKET_3 25 + { 8.3f, 17.8f, 49.5f }, + + // MZ2_INFANTRY_MACHINEGUN_1 26 + { 26.6f, 7.1f, 13.1f }, + // MZ2_INFANTRY_MACHINEGUN_2 27 + { 18.2f, 7.5f, 15.4f }, + // MZ2_INFANTRY_MACHINEGUN_3 28 + { 17.2f, 10.3f, 17.9f }, + // MZ2_INFANTRY_MACHINEGUN_4 29 + { 17.0f, 12.8f, 20.1f }, + // MZ2_INFANTRY_MACHINEGUN_5 30 + { 15.1f, 14.1f, 21.8f }, + // MZ2_INFANTRY_MACHINEGUN_6 31 + { 11.8f, 17.2f, 23.1f }, + // MZ2_INFANTRY_MACHINEGUN_7 32 + { 11.4f, 20.2f, 21.0f }, + // MZ2_INFANTRY_MACHINEGUN_8 33 + { 9.0f, 23.0f, 18.9f }, + // MZ2_INFANTRY_MACHINEGUN_9 34 + { 13.9f, 18.6f, 17.7f }, + // MZ2_INFANTRY_MACHINEGUN_10 35 + { 15.4f, 15.6f, 15.8f }, + // MZ2_INFANTRY_MACHINEGUN_11 36 + { 10.2f, 15.2f, 25.1f }, + // MZ2_INFANTRY_MACHINEGUN_12 37 + { -1.9f, 15.1f, 28.2f }, + // MZ2_INFANTRY_MACHINEGUN_13 38 + { -12.4f, 13.0f, 20.2f }, + + // MZ2_SOLDIER_BLASTER_1 39 + { 10.6f * 1.2f, 7.7f * 1.2f, 7.8f * 1.2f }, + // MZ2_SOLDIER_BLASTER_2 40 + { 25.1f * 1.2f, 3.6f * 1.2f, 19.0f * 1.2f }, + // MZ2_SOLDIER_SHOTGUN_1 41 + { 10.6f * 1.2f, 7.7f * 1.2f, 7.8f * 1.2f }, + // MZ2_SOLDIER_SHOTGUN_2 42 + { 25.1f * 1.2f, 3.6f * 1.2f, 19.0f * 1.2f }, + // MZ2_SOLDIER_MACHINEGUN_1 43 + { 10.6f * 1.2f, 7.7f * 1.2f, 7.8f * 1.2f }, + // MZ2_SOLDIER_MACHINEGUN_2 44 + { 25.1f * 1.2f, 3.6f * 1.2f, 19.0f * 1.2f }, + + // MZ2_GUNNER_MACHINEGUN_1 45 + { 30.1f * 1.15f, 3.9f * 1.15f, 19.6f * 1.15f }, + // MZ2_GUNNER_MACHINEGUN_2 46 + { 29.1f * 1.15f, 2.5f * 1.15f, 20.7f * 1.15f }, + // MZ2_GUNNER_MACHINEGUN_3 47 + { 28.2f * 1.15f, 2.5f * 1.15f, 22.2f * 1.15f }, + // MZ2_GUNNER_MACHINEGUN_4 48 + { 28.2f * 1.15f, 3.6f * 1.15f, 22.0f * 1.15f }, + // MZ2_GUNNER_MACHINEGUN_5 49 + { 26.9f * 1.15f, 2.0f * 1.15f, 23.4f * 1.15f }, + // MZ2_GUNNER_MACHINEGUN_6 50 + { 26.5f * 1.15f, 0.6f * 1.15f, 20.8f * 1.15f }, + // MZ2_GUNNER_MACHINEGUN_7 51 + { 26.9f * 1.15f, 0.5f * 1.15f, 21.5f * 1.15f }, + // MZ2_GUNNER_MACHINEGUN_8 52 + { 29.0f * 1.15f, 2.4f * 1.15f, 19.5f * 1.15f }, + // MZ2_GUNNER_GRENADE_1 53 + { 4.6f * 1.15f, -16.8f * 1.15f, 7.3f * 1.15f }, + // MZ2_GUNNER_GRENADE_2 54 + { 4.6f * 1.15f, -16.8f * 1.15f, 7.3f * 1.15f }, + // MZ2_GUNNER_GRENADE_3 55 + { 4.6f * 1.15f, -16.8f * 1.15f, 7.3f * 1.15f }, + // MZ2_GUNNER_GRENADE_4 56 + { 4.6f * 1.15f, -16.8f * 1.15f, 7.3f * 1.15f }, + + // MZ2_CHICK_ROCKET_1 57 + // -24.8f, -9.0f, 39.0f, + { 24.8f, -9.0f, 39.0f }, // PGM - this was incorrect in Q2 + + // MZ2_FLYER_BLASTER_1 58 + { 14.1f, 13.4f, -7.0f }, + // MZ2_FLYER_BLASTER_2 59 + { 14.1f, -13.4f, -7.0f }, + + // MZ2_MEDIC_BLASTER_1 60 + { 44.0f, 3.0f, 14.4f }, + + // MZ2_GLADIATOR_RAILGUN_1 61 + { 30.0f, 18.0f, 28.0f }, + + // MZ2_HOVER_BLASTER_1 62 + { 1.7f, 7.0f, 11.3f }, + + // MZ2_ACTOR_MACHINEGUN_1 63 + { 18.4f, 7.4f, 9.6f }, + + // MZ2_SUPERTANK_MACHINEGUN_1 64 + { 30.0f, 39.0f, 85.5f }, + // MZ2_SUPERTANK_MACHINEGUN_2 65 + { 30.0f, 39.0f, 85.5f }, + // MZ2_SUPERTANK_MACHINEGUN_3 66 + { 30.0f, 39.0f, 85.5f }, + // MZ2_SUPERTANK_MACHINEGUN_4 67 + { 30.0f, 39.0f, 85.5f }, + // MZ2_SUPERTANK_MACHINEGUN_5 68 + { 30.0f, 39.0f, 85.5f }, + // MZ2_SUPERTANK_MACHINEGUN_6 69 + { 30.0f, 39.0f, 85.5f }, + // MZ2_SUPERTANK_ROCKET_1 70 + { 16.0f, -22.5f, 108.7f }, + // MZ2_SUPERTANK_ROCKET_2 71 + { 16.0f, -33.4f, 106.7f }, + // MZ2_SUPERTANK_ROCKET_3 72 + { 16.0f, -42.8f, 104.7f }, + + // --- Start Xian Stuff --- + // MZ2_BOSS2_MACHINEGUN_L1 73 + { 32.f, -40.f, 70.f }, + // MZ2_BOSS2_MACHINEGUN_L2 74 + { 32.f, -40.f, 70.f }, + // MZ2_BOSS2_MACHINEGUN_L3 75 + { 32.f, -40.f, 70.f }, + // MZ2_BOSS2_MACHINEGUN_L4 76 + { 32.f, -40.f, 70.f }, + // MZ2_BOSS2_MACHINEGUN_L5 77 + { 32.f, -40.f, 70.f }, + // --- End Xian Stuff + + // MZ2_BOSS2_ROCKET_1 78 + { 22.0f, 16.0f, 10.0f }, + // MZ2_BOSS2_ROCKET_2 79 + { 22.0f, 8.0f, 10.0f }, + // MZ2_BOSS2_ROCKET_3 80 + { 22.0f, -8.0f, 10.0f }, + // MZ2_BOSS2_ROCKET_4 81 + { 22.0f, -16.0f, 10.0f }, + + // MZ2_FLOAT_BLASTER_1 82 + { 32.5f, -0.8f, 10.f }, + + // MZ2_SOLDIER_BLASTER_3 83 + { 20.8f * 1.2f, 10.1f * 1.2f, -2.7f * 1.2f }, + // MZ2_SOLDIER_SHOTGUN_3 84 + { 20.8f * 1.2f, 10.1f * 1.2f, -2.7f * 1.2f }, + // MZ2_SOLDIER_MACHINEGUN_3 85 + { 20.8f * 1.2f, 10.1f * 1.2f, -2.7f * 1.2f }, + // MZ2_SOLDIER_BLASTER_4 86 + { 7.6f * 1.2f, 9.3f * 1.2f, 0.8f * 1.2f }, + // MZ2_SOLDIER_SHOTGUN_4 87 + { 7.6f * 1.2f, 9.3f * 1.2f, 0.8f * 1.2f }, + // MZ2_SOLDIER_MACHINEGUN_4 88 + { 7.6f * 1.2f, 9.3f * 1.2f, 0.8f * 1.2f }, + // MZ2_SOLDIER_BLASTER_5 89 + { 30.5f * 1.2f, 9.9f * 1.2f, -18.7f * 1.2f }, + // MZ2_SOLDIER_SHOTGUN_5 90 + { 30.5f * 1.2f, 9.9f * 1.2f, -18.7f * 1.2f }, + // MZ2_SOLDIER_MACHINEGUN_5 91 + { 30.5f * 1.2f, 9.9f * 1.2f, -18.7f * 1.2f }, + // MZ2_SOLDIER_BLASTER_6 92 + { 27.6f * 1.2f, 3.4f * 1.2f, -10.4f * 1.2f }, + // MZ2_SOLDIER_SHOTGUN_6 93 + { 27.6f * 1.2f, 3.4f * 1.2f, -10.4f * 1.2f }, + // MZ2_SOLDIER_MACHINEGUN_6 94 + { 27.6f * 1.2f, 3.4f * 1.2f, -10.4f * 1.2f }, + // MZ2_SOLDIER_BLASTER_7 95 + { 28.9f * 1.2f, 4.6f * 1.2f, -8.1f * 1.2f }, + // MZ2_SOLDIER_SHOTGUN_7 96 + { 28.9f * 1.2f, 4.6f * 1.2f, -8.1f * 1.2f }, + // MZ2_SOLDIER_MACHINEGUN_7 97 + { 28.9f * 1.2f, 4.6f * 1.2f, -8.1f * 1.2f }, + // MZ2_SOLDIER_BLASTER_8 98 + { 31.5f * 1.2f, 9.6f * 1.2f, 10.1f * 1.2f }, + // MZ2_SOLDIER_SHOTGUN_8 99 + { 34.5f * 1.2f, 9.6f * 1.2f, 6.1f * 1.2f }, + // MZ2_SOLDIER_MACHINEGUN_8 100 + { 34.5f * 1.2f, 9.6f * 1.2f, 6.1f * 1.2f }, + + // --- Xian shit below --- + // MZ2_MAKRON_BFG 101 + { 17.f, -19.5f, 62.9f }, + // MZ2_MAKRON_BLASTER_1 102 + { -3.6f, -24.1f, 59.5f }, + // MZ2_MAKRON_BLASTER_2 103 + { -1.6f, -19.3f, 59.5f }, + // MZ2_MAKRON_BLASTER_3 104 + { -0.1f, -14.4f, 59.5f }, + // MZ2_MAKRON_BLASTER_4 105 + { 2.0f, -7.6f, 59.5f }, + // MZ2_MAKRON_BLASTER_5 106 + { 3.4f, 1.3f, 59.5f }, + // MZ2_MAKRON_BLASTER_6 107 + { 3.7f, 11.1f, 59.5f }, + // MZ2_MAKRON_BLASTER_7 108 + { -0.3f, 22.3f, 59.5f }, + // MZ2_MAKRON_BLASTER_8 109 + { -6.f, 33.f, 59.5f }, + // MZ2_MAKRON_BLASTER_9 110 + { -9.3f, 36.4f, 59.5f }, + // MZ2_MAKRON_BLASTER_10 111 + { -7.f, 35.f, 59.5f }, + // MZ2_MAKRON_BLASTER_11 112 + { -2.1f, 29.f, 59.5f }, + // MZ2_MAKRON_BLASTER_12 113 + { 3.9f, 17.3f, 59.5f }, + // MZ2_MAKRON_BLASTER_13 114 + { 6.1f, 5.8f, 59.5f }, + // MZ2_MAKRON_BLASTER_14 115 + { 5.9f, -4.4f, 59.5f }, + // MZ2_MAKRON_BLASTER_15 116 + { 4.2f, -14.1f, 59.5f }, + // MZ2_MAKRON_BLASTER_16 117 + { 2.4f, -18.8f, 59.5f }, + // MZ2_MAKRON_BLASTER_17 118 + { -1.8f, -25.5f, 59.5f }, + // MZ2_MAKRON_RAILGUN_1 119 + { 18.1f, 7.8f, 74.4f }, + + // MZ2_JORG_MACHINEGUN_L1 120 + { 78.5f, -47.1f, 96.f }, + // MZ2_JORG_MACHINEGUN_L2 121 + { 78.5f, -47.1f, 96.f }, + // MZ2_JORG_MACHINEGUN_L3 122 + { 78.5f, -47.1f, 96.f }, + // MZ2_JORG_MACHINEGUN_L4 123 + { 78.5f, -47.1f, 96.f }, + // MZ2_JORG_MACHINEGUN_L5 124 + { 78.5f, -47.1f, 96.f }, + // MZ2_JORG_MACHINEGUN_L6 125 + { 78.5f, -47.1f, 96.f }, + // MZ2_JORG_MACHINEGUN_R1 126 + { 78.5f, 46.7f, 96.f }, + // MZ2_JORG_MACHINEGUN_R2 127 + { 78.5f, 46.7f, 96.f }, + // MZ2_JORG_MACHINEGUN_R3 128 + { 78.5f, 46.7f, 96.f }, + // MZ2_JORG_MACHINEGUN_R4 129 + { 78.5f, 46.7f, 96.f }, + // MZ2_JORG_MACHINEGUN_R5 130 + { 78.5f, 46.7f, 96.f }, + // MZ2_JORG_MACHINEGUN_R6 131 + { 78.5f, 46.7f, 96.f }, + // MZ2_JORG_BFG_1 132 + { 6.3f, -9.f, 111.2f }, + + // MZ2_BOSS2_MACHINEGUN_R1 73 + { 32.f, 40.f, 70.f }, + // MZ2_BOSS2_MACHINEGUN_R2 74 + { 32.f, 40.f, 70.f }, + // MZ2_BOSS2_MACHINEGUN_R3 75 + { 32.f, 40.f, 70.f }, + // MZ2_BOSS2_MACHINEGUN_R4 76 + { 32.f, 40.f, 70.f }, + // MZ2_BOSS2_MACHINEGUN_R5 77 + { 32.f, 40.f, 70.f }, + + // --- End Xian Shit --- + + // ROGUE + // note that the above really ends at 137 + // carrier machineguns + // MZ2_CARRIER_MACHINEGUN_L1 + { 56.f, -32.f, 32.f }, + // MZ2_CARRIER_MACHINEGUN_R1 + { 56.f, 32.f, 32.f }, + // MZ2_CARRIER_GRENADE + { 42.f, 24.f, 50.f }, + // MZ2_TURRET_MACHINEGUN 141 + { 20.f, 0.f, 0.f }, + // MZ2_TURRET_ROCKET 142 + { 20.f, 0.f, 0.f }, + // MZ2_TURRET_BLASTER 143 + { 20.f, 0.f, 0.f }, + // MZ2_STALKER_BLASTER 144 + { 24.f, 0.f, 6.f }, + // MZ2_DAEDALUS_BLASTER 145 + { 1.7f, 7.0f, 11.3f }, + // MZ2_MEDIC_BLASTER_2 146 + { 44.0f, 3.0f, 14.4f }, + // MZ2_CARRIER_RAILGUN 147 + { 32.f, 0.f, 6.f }, + // MZ2_WIDOW_DISRUPTOR 148 + { 64.72f, 14.50f, 88.81f }, + // MZ2_WIDOW_BLASTER 149 + { 56.f, 32.f, 32.f }, + // MZ2_WIDOW_RAIL 150 + { 62.f, -20.f, 84.f }, + // MZ2_WIDOW_PLASMABEAM 151 // PMM - not used! + { 32.f, 0.f, 6.f }, + // MZ2_CARRIER_MACHINEGUN_L2 152 + { 61.f, -32.f, 12.f }, + // MZ2_CARRIER_MACHINEGUN_R2 153 + { 61.f, 32.f, 12.f }, + // MZ2_WIDOW_RAIL_LEFT 154 + { 17.f, -62.f, 91.f }, + // MZ2_WIDOW_RAIL_RIGHT 155 + { 68.f, 12.f, 86.f }, + // MZ2_WIDOW_BLASTER_SWEEP1 156 pmm - the sweeps need to be in sequential order + { 47.5f, 56.f, 89.f }, + // MZ2_WIDOW_BLASTER_SWEEP2 157 + { 54.f, 52.f, 91.f }, + // MZ2_WIDOW_BLASTER_SWEEP3 158 + { 58.f, 40.f, 91.f }, + // MZ2_WIDOW_BLASTER_SWEEP4 159 + { 68.f, 30.f, 88.f }, + // MZ2_WIDOW_BLASTER_SWEEP5 160 + { 74.f, 20.f, 88.f }, + // MZ2_WIDOW_BLASTER_SWEEP6 161 + { 73.f, 11.f, 87.f }, + // MZ2_WIDOW_BLASTER_SWEEP7 162 + { 73.f, 3.f, 87.f }, + // MZ2_WIDOW_BLASTER_SWEEP8 163 + { 70.f, -12.f, 87.f }, + // MZ2_WIDOW_BLASTER_SWEEP9 164 + { 67.f, -20.f, 90.f }, + // MZ2_WIDOW_BLASTER_100 165 + { -20.f, 76.f, 90.f }, + // MZ2_WIDOW_BLASTER_90 166 + { -8.f, 74.f, 90.f }, + // MZ2_WIDOW_BLASTER_80 167 + { 0.f, 72.f, 90.f }, + // MZ2_WIDOW_BLASTER_70 168 d06 + { 10.f, 71.f, 89.f }, + // MZ2_WIDOW_BLASTER_60 169 d07 + { 23.f, 70.f, 87.f }, + // MZ2_WIDOW_BLASTER_50 170 d08 + { 32.f, 64.f, 85.f }, + // MZ2_WIDOW_BLASTER_40 171 + { 40.f, 58.f, 84.f }, + // MZ2_WIDOW_BLASTER_30 172 d10 + { 48.f, 50.f, 83.f }, + // MZ2_WIDOW_BLASTER_20 173 + { 54.f, 42.f, 82.f }, + // MZ2_WIDOW_BLASTER_10 174 d12 + { 56.f, 34.f, 82.f }, + // MZ2_WIDOW_BLASTER_0 175 + { 58.f, 26.f, 82.f }, + // MZ2_WIDOW_BLASTER_10L 176 d14 + { 60.f, 16.f, 82.f }, + // MZ2_WIDOW_BLASTER_20L 177 + { 59.f, 6.f, 81.f }, + // MZ2_WIDOW_BLASTER_30L 178 d16 + { 58.f, -2.f, 80.f }, + // MZ2_WIDOW_BLASTER_40L 179 + { 57.f, -10.f, 79.f }, + // MZ2_WIDOW_BLASTER_50L 180 d18 + { 54.f, -18.f, 78.f }, + // MZ2_WIDOW_BLASTER_60L 181 + { 42.f, -32.f, 80.f }, + // MZ2_WIDOW_BLASTER_70L 182 d20 + { 36.f, -40.f, 78.f }, + // MZ2_WIDOW_RUN_1 183 + { 68.4f, 10.88f, 82.08f }, + // MZ2_WIDOW_RUN_2 184 + { 68.51f, 8.64f, 85.14f }, + // MZ2_WIDOW_RUN_3 185 + { 68.66f, 6.38f, 88.78f }, + // MZ2_WIDOW_RUN_4 186 + { 68.73f, 5.1f, 84.47f }, + // MZ2_WIDOW_RUN_5 187 + { 68.82f, 4.79f, 80.52f }, + // MZ2_WIDOW_RUN_6 188 + { 68.77f, 6.11f, 85.37f }, + // MZ2_WIDOW_RUN_7 189 + { 68.67f, 7.99f, 90.24f }, + // MZ2_WIDOW_RUN_8 190 + { 68.55f, 9.54f, 87.36f }, + // MZ2_CARRIER_ROCKET_1 191 + { 0.f, 0.f, -5.f }, + // MZ2_CARRIER_ROCKET_2 192 + { 0.f, 0.f, -5.f }, + // MZ2_CARRIER_ROCKET_3 193 + { 0.f, 0.f, -5.f }, + // MZ2_CARRIER_ROCKET_4 194 + { 0.f, 0.f, -5.f }, + // MZ2_WIDOW2_BEAMER_1 195 + // 72.13f, -17.63f, 93.77f, + { 69.00f, -17.63f, 93.77f }, + // MZ2_WIDOW2_BEAMER_2 196 + // 71.46f, -17.08f, 89.82f, + { 69.00f, -17.08f, 89.82f }, + // MZ2_WIDOW2_BEAMER_3 197 + // 71.47f, -18.40f, 90.70f, + { 69.00f, -18.40f, 90.70f }, + // MZ2_WIDOW2_BEAMER_4 198 + // 71.96f, -18.34f, 94.32f, + { 69.00f, -18.34f, 94.32f }, + // MZ2_WIDOW2_BEAMER_5 199 + // 72.25f, -18.30f, 97.98f, + { 69.00f, -18.30f, 97.98f }, + // MZ2_WIDOW2_BEAM_SWEEP_1 200 + { 45.04f, -59.02f, 92.24f }, + // MZ2_WIDOW2_BEAM_SWEEP_2 201 + { 50.68f, -54.70f, 91.96f }, + // MZ2_WIDOW2_BEAM_SWEEP_3 202 + { 56.57f, -47.72f, 91.65f }, + // MZ2_WIDOW2_BEAM_SWEEP_4 203 + { 61.75f, -38.75f, 91.38f }, + // MZ2_WIDOW2_BEAM_SWEEP_5 204 + { 65.55f, -28.76f, 91.24f }, + // MZ2_WIDOW2_BEAM_SWEEP_6 205 + { 67.79f, -18.90f, 91.22f }, + // MZ2_WIDOW2_BEAM_SWEEP_7 206 + { 68.60f, -9.52f, 91.23f }, + // MZ2_WIDOW2_BEAM_SWEEP_8 207 + { 68.08f, 0.18f, 91.32f }, + // MZ2_WIDOW2_BEAM_SWEEP_9 208 + { 66.14f, 9.79f, 91.44f }, + // MZ2_WIDOW2_BEAM_SWEEP_10 209 + { 62.77f, 18.91f, 91.65f }, + // MZ2_WIDOW2_BEAM_SWEEP_11 210 + { 58.29f, 27.11f, 92.00f }, + + // MZ2_SOLDIER_RIPPER_1 211 + { 10.6f * 1.2f, 7.7f * 1.2f, 7.8f * 1.2f }, + // MZ2_SOLDIER_RIPPER_2 212 + { 25.1f * 1.2f, 3.6f * 1.2f, 19.0f * 1.2f }, + // MZ2_SOLDIER_RIPPER_3 213 + { 20.8f * 1.2f, 10.1f * 1.2f, -2.7f * 1.2f }, + // MZ2_SOLDIER_RIPPER_4 214 + { 7.6f * 1.2f, 9.3f * 1.2f, 0.8f * 1.2f }, + // MZ2_SOLDIER_RIPPER_5 215 + { 30.5f * 1.2f, 9.9f * 1.2f, -18.7f * 1.2f }, + // MZ2_SOLDIER_RIPPER_6 216 + { 27.6f * 1.2f, 3.4f * 1.2f, -10.4f * 1.2f }, + // MZ2_SOLDIER_RIPPER_7 217 + { 28.9f * 1.2f, 4.6f * 1.2f, -8.1f * 1.2f }, + // MZ2_SOLDIER_RIPPER_8 218 + { 31.5f * 1.2f, 9.6f * 1.2f, 10.1f * 1.2f }, + + // MZ2_SOLDIER_HYPERGUN_1 219 + { 10.6f * 1.2f, 7.7f * 1.2f, 7.8f * 1.2f }, + // MZ2_SOLDIER_HYPERGUN_2 220 + { 25.1f * 1.2f, 3.6f * 1.2f, 19.0f * 1.2f }, + // MZ2_SOLDIER_HYPERGUN_3 221 + { 20.8f * 1.2f, 10.1f * 1.2f, -2.7f * 1.2f }, + // MZ2_SOLDIER_HYPERGUN_4 222 + { 7.6f * 1.2f, 9.3f * 1.2f, 0.8f * 1.2f }, + // MZ2_SOLDIER_HYPERGUN_5 223 + { 30.5f * 1.2f, 9.9f * 1.2f, -18.7f * 1.2f }, + // MZ2_SOLDIER_HYPERGUN_6 224 + { 27.6f * 1.2f, 3.4f * 1.2f, -10.4f * 1.2f }, + // MZ2_SOLDIER_HYPERGUN_7 225 + { 28.9f * 1.2f, 4.6f * 1.2f, -8.1f * 1.2f }, + // MZ2_SOLDIER_HYPERGUN_8 226 + { 31.5f * 1.2f, 9.6f * 1.2f, 10.1f * 1.2f }, + + // MZ2_GUARDIAN_BLASTER 227 + { 88.f, 50.f, 60.f }, + + // MZ2_ARACHNID_RAIL1 228 + { 58.f, 20.f, 17.2f }, + // MZ2_ARACHNID_RAIL2 229 + { 64.f, -22.f, 24.f }, + // MZ2_ARACHNID_RAIL_UP1 230 + { 37.f, 13.f, 72.f }, + // MZ2_ARACHNID_RAIL_UP2 231 + { 58.f, -25.f, 72.f }, + + // MZ2_INFANTRY_MACHINEGUN_14 232 + { 34.f, 11.f, 13.f }, + // MZ2_INFANTRY_MACHINEGUN_15 233 + { 28.f, 13.f, 10.5f }, + // MZ2_INFANTRY_MACHINEGUN_16 234 + { 29.f, 13.f, 8.5f }, + // MZ2_INFANTRY_MACHINEGUN_17 235 + { 30.f, 12.5f, 12.f }, + // MZ2_INFANTRY_MACHINEGUN_18 236 + { 29.f, 12.5f, 14.7f }, + // MZ2_INFANTRY_MACHINEGUN_19 237 + { 30.f, 6.5f, 12.f }, + // MZ2_INFANTRY_MACHINEGUN_20 238 + { 29.f, 1.5f, 8.5f }, + // MZ2_INFANTRY_MACHINEGUN_21 239 + { 29.f, 6.0f, 10.f }, + + // MZ2_GUNCMDR_CHAINGUN_1 240 + { 25.0f, 11.f, 21.f }, + // MZ2_GUNCMDR_CHAINGUN_2 241 + { 26.5f, 5.f, 21.f }, + + // MZ2_GUNCMDR_GRENADE_MORTAR_1 242 + { 27.f, 6.5f, 4.0f }, + // MZ2_GUNCMDR_GRENADE_MORTAR_2 243 + { 28.f, 4.f, 4.0f }, + // MZ2_GUNCMDR_GRENADE_MORTAR_3 244 + { 27.f, 1.7f, 4.0f }, + + // MZ2_GUNCMDR_GRENADE_FRONT_1 245 + { 21.7f, -1.5f, 22.5f }, + // MZ2_GUNCMDR_GRENADE_FRONT_2 246 + { 22.f, 0.f, 20.5f }, + // MZ2_GUNCMDR_GRENADE_FRONT_3 247 + { 22.5f, 3.7f, 20.5f }, + + // MZ2_GUNCMDR_GRENADE_CROUCH_1 248 + { 8.0f, 40.0f, 18.0f }, + // MZ2_GUNCMDR_GRENADE_CROUCH_2 249 + { 29.0f, 16.0f, 19.0f }, + // MZ2_GUNCMDR_GRENADE_CROUCH_3 250 + { 4.7f, -30.0f, 20.0f }, + + // MZ2_SOLDIER_BLASTER_9 251 + { 36.33f, 12.24f, -17.39f }, + // MZ2_SOLDIER_SHOTGUN_9 252 + { 36.33f, 12.24f, -17.39f }, + // MZ2_SOLDIER_MACHINEGUN_9 253 + { 36.33f, 12.24f, -17.39f }, + // MZ2_SOLDIER_RIPPER_9 254 + { 36.33f, 12.24f, -17.39f }, + // MZ2_SOLDIER_HYPERGUN_9 255 + { 36.33f, 12.24f, -17.39f }, + + // MZ2_GUNNER_GRENADE2_1 + { 36.f, -6.2f, 19.59f }, + // MZ2_GUNNER_GRENADE2_2 + { 36.f, -6.2f, 19.59f }, + // MZ2_GUNNER_GRENADE2_3 + { 36.f, -6.2f, 19.59f }, + // MZ2_GUNNER_GRENADE2_4 + { 36.f, -6.2f, 19.59f }, + + // MZ2_INFANTRY_MACHINEGUN_22 + { 14.8f, 10.5f, 8.82f }, + + // MZ2_SUPERTANK_GRENADE_1 + { 31.31f, -37.f, 54.32f }, + // MZ2_SUPERTANK_GRENADE_2 + { 31.31f, 37.f, 54.32f }, + + // MZ2_HOVER_BLASTER_2 + { 1.7f, -7.0f, 11.3f }, + // MZ2_DAEDALUS_BLASTER_2 + { 1.7f, -7.0f, 11.3f }, + + // MZ2_MEDIC_HYPERBLASTER1_1-12 + { 33.0f + 1.f, 12.5f, 15.0f }, + { 32.4f + 1.f, 11.2f, 15.0f }, + { 35.6f + 1.f, 7.4f, 15.0f }, + { 34.0f + 1.f, 4.1f, 15.0f }, + { 36.6f + 1.f, 1.0f, 15.0f }, + { 34.7f + 1.f, -1.9f, 15.0f }, + { 36.6f + 1.f, -0.5f, 15.0f }, + { 34.2f + 1.f, 2.8f, 15.0f }, + { 36.5f + 1.f, 3.8f, 15.0f }, + { 33.5f + 1.f, 6.9f, 15.0f }, + { 32.7f + 1.f, 9.9f, 15.0f }, + { 34.5f + 1.f, 11.0f, 15.0f }, + + // MZ2_MEDIC_HYPERBLASTER2_1-12 + { 33.0f + 1.f, 12.5f, 15.0f }, + { 32.4f + 1.f, 11.2f, 15.0f }, + { 35.6f + 1.f, 7.4f, 15.0f }, + { 34.0f + 1.f, 4.1f, 15.0f }, + { 36.6f + 1.f, 1.0f, 15.0f }, + { 34.7f + 1.f, -1.9f, 15.0f }, + { 36.6f + 1.f, -0.5f, 15.0f }, + { 34.2f + 1.f, 2.8f, 15.0f }, + { 36.5f + 1.f, 3.8f, 15.0f }, + { 33.5f + 1.f, 6.9f, 15.0f }, + { 32.7f + 1.f, 9.9f, 15.0f }, + { 34.5f + 1.f, 11.0f, 15.0f }, + + // end of table + { 0.0f, 0.0f, 0.0f } +}; + +static_assert(q_countof(monster_flash_offset) - 1 == MZ2_LAST); \ No newline at end of file diff --git a/rerelease/m_flipper.cpp b/rerelease/m_flipper.cpp new file mode 100644 index 0000000..d0e6ed4 --- /dev/null +++ b/rerelease/m_flipper.cpp @@ -0,0 +1,384 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +/* +============================================================================== + +FLIPPER + +============================================================================== +*/ + +#include "g_local.h" +#include "m_flipper.h" + +static int sound_chomp; +static int sound_attack; +static int sound_pain1; +static int sound_pain2; +static int sound_death; +static int sound_idle; +static int sound_search; +static int sound_sight; + +mframe_t flipper_frames_stand[] = { + { ai_stand } +}; + +MMOVE_T(flipper_move_stand) = { FRAME_flphor01, FRAME_flphor01, flipper_frames_stand, nullptr }; + +MONSTERINFO_STAND(flipper_stand) (edict_t *self) -> void +{ + M_SetAnimation(self, &flipper_move_stand); +} + +constexpr float FLIPPER_RUN_SPEED = 24; + +mframe_t flipper_frames_run[] = { + { ai_run, FLIPPER_RUN_SPEED }, // 6 + { ai_run, FLIPPER_RUN_SPEED }, + { ai_run, FLIPPER_RUN_SPEED }, + { ai_run, FLIPPER_RUN_SPEED }, + { ai_run, FLIPPER_RUN_SPEED }, // 10 + + { ai_run, FLIPPER_RUN_SPEED }, + { ai_run, FLIPPER_RUN_SPEED }, + { ai_run, FLIPPER_RUN_SPEED }, + { ai_run, FLIPPER_RUN_SPEED }, + { ai_run, FLIPPER_RUN_SPEED }, + { ai_run, FLIPPER_RUN_SPEED }, + { ai_run, FLIPPER_RUN_SPEED }, + { ai_run, FLIPPER_RUN_SPEED }, + { ai_run, FLIPPER_RUN_SPEED }, + { ai_run, FLIPPER_RUN_SPEED }, // 20 + + { ai_run, FLIPPER_RUN_SPEED }, + { ai_run, FLIPPER_RUN_SPEED }, + { ai_run, FLIPPER_RUN_SPEED }, + { ai_run, FLIPPER_RUN_SPEED }, + { ai_run, FLIPPER_RUN_SPEED }, + { ai_run, FLIPPER_RUN_SPEED }, + { ai_run, FLIPPER_RUN_SPEED }, + { ai_run, FLIPPER_RUN_SPEED }, + { ai_run, FLIPPER_RUN_SPEED } // 29 +}; +MMOVE_T(flipper_move_run_loop) = { FRAME_flpver06, FRAME_flpver29, flipper_frames_run, nullptr }; + +void flipper_run_loop(edict_t *self) +{ + M_SetAnimation(self, &flipper_move_run_loop); +} + +mframe_t flipper_frames_run_start[] = { + { ai_run, 8 }, + { ai_run, 8 }, + { ai_run, 8 }, + { ai_run, 8 }, + { ai_run, 8 }, + { ai_run, 8 } +}; +MMOVE_T(flipper_move_run_start) = { FRAME_flpver01, FRAME_flpver06, flipper_frames_run_start, flipper_run_loop }; + +void flipper_run(edict_t *self) +{ + M_SetAnimation(self, &flipper_move_run_start); +} + +/* Standard Swimming */ +mframe_t flipper_frames_walk[] = { + { ai_walk, 4 }, + { ai_walk, 4 }, + { ai_walk, 4 }, + { ai_walk, 4 }, + { ai_walk, 4 }, + { ai_walk, 4 }, + { ai_walk, 4 }, + { ai_walk, 4 }, + { ai_walk, 4 }, + { ai_walk, 4 }, + { ai_walk, 4 }, + { ai_walk, 4 }, + { ai_walk, 4 }, + { ai_walk, 4 }, + { ai_walk, 4 }, + { ai_walk, 4 }, + { ai_walk, 4 }, + { ai_walk, 4 }, + { ai_walk, 4 }, + { ai_walk, 4 }, + { ai_walk, 4 }, + { ai_walk, 4 }, + { ai_walk, 4 }, + { ai_walk, 4 } +}; +MMOVE_T(flipper_move_walk) = { FRAME_flphor01, FRAME_flphor24, flipper_frames_walk, nullptr }; + +MONSTERINFO_WALK(flipper_walk) (edict_t *self) -> void +{ + M_SetAnimation(self, &flipper_move_walk); +} + +mframe_t flipper_frames_start_run[] = { + { ai_run }, + { ai_run }, + { ai_run }, + { ai_run }, + { ai_run, 8, flipper_run } +}; +MMOVE_T(flipper_move_start_run) = { FRAME_flphor01, FRAME_flphor05, flipper_frames_start_run, nullptr }; + +MONSTERINFO_RUN(flipper_start_run) (edict_t *self) -> void +{ + M_SetAnimation(self, &flipper_move_start_run); +} + +mframe_t flipper_frames_pain2[] = { + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move } +}; +MMOVE_T(flipper_move_pain2) = { FRAME_flppn101, FRAME_flppn105, flipper_frames_pain2, flipper_run }; + +mframe_t flipper_frames_pain1[] = { + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move } +}; +MMOVE_T(flipper_move_pain1) = { FRAME_flppn201, FRAME_flppn205, flipper_frames_pain1, flipper_run }; + +void flipper_bite(edict_t *self) +{ + vec3_t aim = { MELEE_DISTANCE, 0, 0 }; + fire_hit(self, aim, 5, 0); +} + +void flipper_preattack(edict_t *self) +{ + gi.sound(self, CHAN_WEAPON, sound_chomp, 1, ATTN_NORM, 0); +} + +mframe_t flipper_frames_attack[] = { + { ai_charge, 0, flipper_preattack }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge, 0, flipper_bite }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge, 0, flipper_bite }, + { ai_charge } +}; +MMOVE_T(flipper_move_attack) = { FRAME_flpbit01, FRAME_flpbit20, flipper_frames_attack, flipper_run }; + +MONSTERINFO_MELEE(flipper_melee) (edict_t *self) -> void +{ + M_SetAnimation(self, &flipper_move_attack); +} + +PAIN(flipper_pain) (edict_t *self, edict_t *other, float kick, int damage, const mod_t &mod) -> void +{ + int n; + + if (level.time < self->pain_debounce_time) + return; + + self->pain_debounce_time = level.time + 3_sec; + n = brandom(); + + if (n == 0) + gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); + else + gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0); + + if (!M_ShouldReactToPain(self, mod)) + return; // no pain anims in nightmare + + if (n == 0) + M_SetAnimation(self, &flipper_move_pain1); + else + M_SetAnimation(self, &flipper_move_pain2); +} + +MONSTERINFO_SETSKIN(flipper_setskin) (edict_t *self) -> void +{ + if (self->health < (self->max_health / 2)) + self->s.skinnum = 1; + else + self->s.skinnum = 0; +} + +void flipper_dead(edict_t *self) +{ + self->mins = { -16, -16, -8 }; + self->maxs = { 16, 16, 8 }; + monster_dead(self); +} + +mframe_t flipper_frames_death[] = { + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move } +}; +MMOVE_T(flipper_move_death) = { FRAME_flpdth01, FRAME_flpdth56, flipper_frames_death, flipper_dead }; + +MONSTERINFO_SIGHT(flipper_sight) (edict_t *self, edict_t *other) -> void +{ + gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); +} + +DIE(flipper_die) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void +{ + // check for gib + if (M_CheckGib(self, mod)) + { + gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0); + ThrowGibs(self, damage, { + { 2, "models/objects/gibs/bone/tris.md2" }, + { 2, "models/objects/gibs/sm_meat/tris.md2" }, + { "models/objects/gibs/head2/tris.md2", GIB_HEAD } + }); + self->deadflag = true; + return; + } + + if (self->deadflag) + return; + + // regular death + gi.sound(self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0); + self->deadflag = true; + self->takedamage = true; + self->svflags |= SVF_DEADMONSTER; + M_SetAnimation(self, &flipper_move_death); +} + +static void flipper_set_fly_parameters(edict_t *self) +{ + self->monsterinfo.fly_thrusters = false; + self->monsterinfo.fly_acceleration = 30.f; + self->monsterinfo.fly_speed = 110.f; + // only melee, so get in close + self->monsterinfo.fly_min_distance = 10.f; + self->monsterinfo.fly_max_distance = 10.f; +} + +/*QUAKED monster_flipper (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight + */ +void SP_monster_flipper(edict_t *self) +{ + if ( !M_AllowSpawn( self ) ) { + G_FreeEdict( self ); + return; + } + + sound_pain1 = gi.soundindex("flipper/flppain1.wav"); + sound_pain2 = gi.soundindex("flipper/flppain2.wav"); + sound_death = gi.soundindex("flipper/flpdeth1.wav"); + sound_chomp = gi.soundindex("flipper/flpatck1.wav"); + sound_attack = gi.soundindex("flipper/flpatck2.wav"); + sound_idle = gi.soundindex("flipper/flpidle1.wav"); + sound_search = gi.soundindex("flipper/flpsrch1.wav"); + sound_sight = gi.soundindex("flipper/flpsght1.wav"); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex("models/monsters/flipper/tris.md2"); + self->mins = { -16, -16, -8 }; + self->maxs = { 16, 16, 20 }; + + self->health = 50 * st.health_multiplier; + self->gib_health = -30; + self->mass = 100; + + self->pain = flipper_pain; + self->die = flipper_die; + + self->monsterinfo.stand = flipper_stand; + self->monsterinfo.walk = flipper_walk; + self->monsterinfo.run = flipper_start_run; + self->monsterinfo.melee = flipper_melee; + self->monsterinfo.sight = flipper_sight; + self->monsterinfo.setskin = flipper_setskin; + + gi.linkentity(self); + + M_SetAnimation(self, &flipper_move_stand); + self->monsterinfo.scale = MODEL_SCALE; + + self->monsterinfo.aiflags |= AI_ALTERNATE_FLY; + flipper_set_fly_parameters(self); + + swimmonster_start(self); +} diff --git a/rerelease/m_flipper.h b/rerelease/m_flipper.h new file mode 100644 index 0000000..feac61a --- /dev/null +++ b/rerelease/m_flipper.h @@ -0,0 +1,171 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// G:\quake2\baseq2\models/monsters/flipper + +// This file generated by ModelGen - Do NOT Modify + +enum +{ + FRAME_flpbit01, + FRAME_flpbit02, + FRAME_flpbit03, + FRAME_flpbit04, + FRAME_flpbit05, + FRAME_flpbit06, + FRAME_flpbit07, + FRAME_flpbit08, + FRAME_flpbit09, + FRAME_flpbit10, + FRAME_flpbit11, + FRAME_flpbit12, + FRAME_flpbit13, + FRAME_flpbit14, + FRAME_flpbit15, + FRAME_flpbit16, + FRAME_flpbit17, + FRAME_flpbit18, + FRAME_flpbit19, + FRAME_flpbit20, + FRAME_flptal01, + FRAME_flptal02, + FRAME_flptal03, + FRAME_flptal04, + FRAME_flptal05, + FRAME_flptal06, + FRAME_flptal07, + FRAME_flptal08, + FRAME_flptal09, + FRAME_flptal10, + FRAME_flptal11, + FRAME_flptal12, + FRAME_flptal13, + FRAME_flptal14, + FRAME_flptal15, + FRAME_flptal16, + FRAME_flptal17, + FRAME_flptal18, + FRAME_flptal19, + FRAME_flptal20, + FRAME_flptal21, + FRAME_flphor01, + FRAME_flphor02, + FRAME_flphor03, + FRAME_flphor04, + FRAME_flphor05, + FRAME_flphor06, + FRAME_flphor07, + FRAME_flphor08, + FRAME_flphor09, + FRAME_flphor10, + FRAME_flphor11, + FRAME_flphor12, + FRAME_flphor13, + FRAME_flphor14, + FRAME_flphor15, + FRAME_flphor16, + FRAME_flphor17, + FRAME_flphor18, + FRAME_flphor19, + FRAME_flphor20, + FRAME_flphor21, + FRAME_flphor22, + FRAME_flphor23, + FRAME_flphor24, + FRAME_flpver01, + FRAME_flpver02, + FRAME_flpver03, + FRAME_flpver04, + FRAME_flpver05, + FRAME_flpver06, + FRAME_flpver07, + FRAME_flpver08, + FRAME_flpver09, + FRAME_flpver10, + FRAME_flpver11, + FRAME_flpver12, + FRAME_flpver13, + FRAME_flpver14, + FRAME_flpver15, + FRAME_flpver16, + FRAME_flpver17, + FRAME_flpver18, + FRAME_flpver19, + FRAME_flpver20, + FRAME_flpver21, + FRAME_flpver22, + FRAME_flpver23, + FRAME_flpver24, + FRAME_flpver25, + FRAME_flpver26, + FRAME_flpver27, + FRAME_flpver28, + FRAME_flpver29, + FRAME_flppn101, + FRAME_flppn102, + FRAME_flppn103, + FRAME_flppn104, + FRAME_flppn105, + FRAME_flppn201, + FRAME_flppn202, + FRAME_flppn203, + FRAME_flppn204, + FRAME_flppn205, + FRAME_flpdth01, + FRAME_flpdth02, + FRAME_flpdth03, + FRAME_flpdth04, + FRAME_flpdth05, + FRAME_flpdth06, + FRAME_flpdth07, + FRAME_flpdth08, + FRAME_flpdth09, + FRAME_flpdth10, + FRAME_flpdth11, + FRAME_flpdth12, + FRAME_flpdth13, + FRAME_flpdth14, + FRAME_flpdth15, + FRAME_flpdth16, + FRAME_flpdth17, + FRAME_flpdth18, + FRAME_flpdth19, + FRAME_flpdth20, + FRAME_flpdth21, + FRAME_flpdth22, + FRAME_flpdth23, + FRAME_flpdth24, + FRAME_flpdth25, + FRAME_flpdth26, + FRAME_flpdth27, + FRAME_flpdth28, + FRAME_flpdth29, + FRAME_flpdth30, + FRAME_flpdth31, + FRAME_flpdth32, + FRAME_flpdth33, + FRAME_flpdth34, + FRAME_flpdth35, + FRAME_flpdth36, + FRAME_flpdth37, + FRAME_flpdth38, + FRAME_flpdth39, + FRAME_flpdth40, + FRAME_flpdth41, + FRAME_flpdth42, + FRAME_flpdth43, + FRAME_flpdth44, + FRAME_flpdth45, + FRAME_flpdth46, + FRAME_flpdth47, + FRAME_flpdth48, + FRAME_flpdth49, + FRAME_flpdth50, + FRAME_flpdth51, + FRAME_flpdth52, + FRAME_flpdth53, + FRAME_flpdth54, + FRAME_flpdth55, + FRAME_flpdth56 +}; + +constexpr float MODEL_SCALE = 1.000000f; diff --git a/rerelease/m_float.cpp b/rerelease/m_float.cpp new file mode 100644 index 0000000..a18654a --- /dev/null +++ b/rerelease/m_float.cpp @@ -0,0 +1,716 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +/* +============================================================================== + +floater + +============================================================================== +*/ + +#include "g_local.h" +#include "m_float.h" +#include "m_flash.h" + +static int sound_attack2; +static int sound_attack3; +static int sound_death1; +static int sound_idle; +static int sound_pain1; +static int sound_pain2; +static int sound_sight; + +MONSTERINFO_SIGHT(floater_sight) (edict_t *self, edict_t *other) -> void +{ + gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); +} + +MONSTERINFO_IDLE(floater_idle) (edict_t *self) -> void +{ + gi.sound(self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0); +} + +void floater_dead(edict_t *self); +void floater_run(edict_t *self); +void floater_wham(edict_t *self); +void floater_zap(edict_t *self); + +void floater_fire_blaster(edict_t *self) +{ + vec3_t start; + vec3_t forward, right; + vec3_t end; + vec3_t dir; + + if (!self->enemy || !self->enemy->inuse) // PGM + return; // PGM + + AngleVectors(self->s.angles, forward, right, nullptr); + start = M_ProjectFlashSource(self, monster_flash_offset[MZ2_FLOAT_BLASTER_1], forward, right); + + end = self->enemy->s.origin; + end[2] += self->enemy->viewheight; + dir = end - start; + dir.normalize(); + + monster_fire_blaster(self, start, dir, 1, 1000, MZ2_FLOAT_BLASTER_1, (self->s.frame % 4) ? EF_NONE : EF_HYPERBLASTER); +} + +mframe_t floater_frames_stand1[] = { + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand } +}; +MMOVE_T(floater_move_stand1) = { FRAME_stand101, FRAME_stand152, floater_frames_stand1, nullptr }; + +mframe_t floater_frames_stand2[] = { + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand } +}; +MMOVE_T(floater_move_stand2) = { FRAME_stand201, FRAME_stand252, floater_frames_stand2, nullptr }; + +mframe_t floater_frames_pop[] = { + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {} +}; +MMOVE_T(floater_move_pop) = { FRAME_actvat05, FRAME_actvat31, floater_frames_pop, floater_run }; + +mframe_t floater_frames_disguise[] = { + { ai_stand } +}; +MMOVE_T(floater_move_disguise) = { FRAME_actvat01, FRAME_actvat01, floater_frames_disguise, nullptr }; + +MONSTERINFO_STAND(floater_stand) (edict_t *self) -> void +{ + if (self->monsterinfo.active_move == &floater_move_disguise) + M_SetAnimation(self, &floater_move_disguise); + else if (frandom() <= 0.5f) + M_SetAnimation(self, &floater_move_stand1); + else + M_SetAnimation(self, &floater_move_stand2); +} + +mframe_t floater_frames_attack1[] = { + { ai_charge }, // Blaster attack + { ai_charge }, + { ai_charge }, + { ai_charge, 0, floater_fire_blaster }, // BOOM (0, -25.8, 32.5) -- LOOP Starts + { ai_charge, 0, floater_fire_blaster }, + { ai_charge, 0, floater_fire_blaster }, + { ai_charge, 0, floater_fire_blaster }, + { ai_charge, 0, floater_fire_blaster }, + { ai_charge, 0, floater_fire_blaster }, + { ai_charge, 0, floater_fire_blaster }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge } // -- LOOP Ends +}; +MMOVE_T(floater_move_attack1) = { FRAME_attak101, FRAME_attak114, floater_frames_attack1, floater_run }; + +// PMM - circle strafe frames +mframe_t floater_frames_attack1a[] = { + { ai_charge, 10 }, // Blaster attack + { ai_charge, 10 }, + { ai_charge, 10 }, + { ai_charge, 10, floater_fire_blaster }, // BOOM (0, -25.8, 32.5) -- LOOP Starts + { ai_charge, 10, floater_fire_blaster }, + { ai_charge, 10, floater_fire_blaster }, + { ai_charge, 10, floater_fire_blaster }, + { ai_charge, 10, floater_fire_blaster }, + { ai_charge, 10, floater_fire_blaster }, + { ai_charge, 10, floater_fire_blaster }, + { ai_charge, 10 }, + { ai_charge, 10 }, + { ai_charge, 10 }, + { ai_charge, 10 } // -- LOOP Ends +}; +MMOVE_T(floater_move_attack1a) = { FRAME_attak101, FRAME_attak114, floater_frames_attack1a, floater_run }; +// pmm + +mframe_t floater_frames_attack2[] = { + { ai_charge }, // Claws + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge, 0, floater_wham }, // WHAM (0, -45, 29.6) -- LOOP Starts + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, // -- LOOP Ends + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge } +}; +MMOVE_T(floater_move_attack2) = { FRAME_attak201, FRAME_attak225, floater_frames_attack2, floater_run }; + +mframe_t floater_frames_attack3[] = { + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge, 0, floater_zap }, // -- LOOP Starts + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, // -- LOOP Ends + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge } +}; +MMOVE_T(floater_move_attack3) = { FRAME_attak301, FRAME_attak334, floater_frames_attack3, floater_run }; + +#if 0 +mframe_t floater_frames_death[] = { + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move } +}; +MMOVE_T(floater_move_death) = { FRAME_death01, FRAME_death13, floater_frames_death, floater_dead }; +#endif + +mframe_t floater_frames_pain1[] = { + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move } +}; +MMOVE_T(floater_move_pain1) = { FRAME_pain101, FRAME_pain107, floater_frames_pain1, floater_run }; + +mframe_t floater_frames_pain2[] = { + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move } +}; +MMOVE_T(floater_move_pain2) = { FRAME_pain201, FRAME_pain208, floater_frames_pain2, floater_run }; + +#if 0 +mframe_t floater_frames_pain3[] = { + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move } +}; +MMOVE_T(floater_move_pain3) = { FRAME_pain301, FRAME_pain312, floater_frames_pain3, floater_run }; +#endif + +mframe_t floater_frames_walk[] = { + { ai_walk, 5 }, + { ai_walk, 5 }, + { ai_walk, 5 }, + { ai_walk, 5 }, + { ai_walk, 5 }, + { ai_walk, 5 }, + { ai_walk, 5 }, + { ai_walk, 5 }, + { ai_walk, 5 }, + { ai_walk, 5 }, + { ai_walk, 5 }, + { ai_walk, 5 }, + { ai_walk, 5 }, + { ai_walk, 5 }, + { ai_walk, 5 }, + { ai_walk, 5 }, + { ai_walk, 5 }, + { ai_walk, 5 }, + { ai_walk, 5 }, + { ai_walk, 5 }, + { ai_walk, 5 }, + { ai_walk, 5 }, + { ai_walk, 5 }, + { ai_walk, 5 }, + { ai_walk, 5 }, + { ai_walk, 5 }, + { ai_walk, 5 }, + { ai_walk, 5 }, + { ai_walk, 5 }, + { ai_walk, 5 }, + { ai_walk, 5 }, + { ai_walk, 5 }, + { ai_walk, 5 }, + { ai_walk, 5 }, + { ai_walk, 5 }, + { ai_walk, 5 }, + { ai_walk, 5 }, + { ai_walk, 5 }, + { ai_walk, 5 }, + { ai_walk, 5 }, + { ai_walk, 5 }, + { ai_walk, 5 }, + { ai_walk, 5 }, + { ai_walk, 5 }, + { ai_walk, 5 }, + { ai_walk, 5 }, + { ai_walk, 5 }, + { ai_walk, 5 }, + { ai_walk, 5 }, + { ai_walk, 5 }, + { ai_walk, 5 }, + { ai_walk, 5 } +}; +MMOVE_T(floater_move_walk) = { FRAME_stand101, FRAME_stand152, floater_frames_walk, nullptr }; + +mframe_t floater_frames_run[] = { + { ai_run, 13 }, + { ai_run, 13 }, + { ai_run, 13 }, + { ai_run, 13 }, + { ai_run, 13 }, + { ai_run, 13 }, + { ai_run, 13 }, + { ai_run, 13 }, + { ai_run, 13 }, + { ai_run, 13 }, + { ai_run, 13 }, + { ai_run, 13 }, + { ai_run, 13 }, + { ai_run, 13 }, + { ai_run, 13 }, + { ai_run, 13 }, + { ai_run, 13 }, + { ai_run, 13 }, + { ai_run, 13 }, + { ai_run, 13 }, + { ai_run, 13 }, + { ai_run, 13 }, + { ai_run, 13 }, + { ai_run, 13 }, + { ai_run, 13 }, + { ai_run, 13 }, + { ai_run, 13 }, + { ai_run, 13 }, + { ai_run, 13 }, + { ai_run, 13 }, + { ai_run, 13 }, + { ai_run, 13 }, + { ai_run, 13 }, + { ai_run, 13 }, + { ai_run, 13 }, + { ai_run, 13 }, + { ai_run, 13 }, + { ai_run, 13 }, + { ai_run, 13 }, + { ai_run, 13 }, + { ai_run, 13 }, + { ai_run, 13 }, + { ai_run, 13 }, + { ai_run, 13 }, + { ai_run, 13 }, + { ai_run, 13 }, + { ai_run, 13 }, + { ai_run, 13 }, + { ai_run, 13 }, + { ai_run, 13 }, + { ai_run, 13 }, + { ai_run, 13 } +}; +MMOVE_T(floater_move_run) = { FRAME_stand101, FRAME_stand152, floater_frames_run, nullptr }; + +MONSTERINFO_RUN(floater_run) (edict_t *self) -> void +{ + if (self->monsterinfo.active_move == &floater_move_disguise) + M_SetAnimation(self, &floater_move_pop); + else if (self->monsterinfo.aiflags & AI_STAND_GROUND) + M_SetAnimation(self, &floater_move_stand1); + else + M_SetAnimation(self, &floater_move_run); +} + +MONSTERINFO_WALK(floater_walk) (edict_t *self) -> void +{ + M_SetAnimation(self, &floater_move_walk); +} + +void floater_wham(edict_t *self) +{ + constexpr vec3_t aim = { MELEE_DISTANCE, 0, 0 }; + gi.sound(self, CHAN_WEAPON, sound_attack3, 1, ATTN_NORM, 0); + + if (!fire_hit(self, aim, irandom(5, 11), -50)) + self->monsterinfo.melee_debounce_time = level.time + 3_sec; +} + +void floater_zap(edict_t *self) +{ + vec3_t forward, right; + vec3_t origin; + vec3_t dir; + vec3_t offset; + + dir = self->enemy->s.origin - self->s.origin; + + AngleVectors(self->s.angles, forward, right, nullptr); + // FIXME use a flash and replace these two lines with the commented one + offset = { 18.5f, -0.9f, 10 }; + origin = M_ProjectFlashSource(self, offset, forward, right); + + gi.sound(self, CHAN_WEAPON, sound_attack2, 1, ATTN_NORM, 0); + + // FIXME use the flash, Luke + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_SPLASH); + gi.WriteByte(32); + gi.WritePosition(origin); + gi.WriteDir(dir); + gi.WriteByte(SPLASH_SPARKS); + gi.multicast(origin, MULTICAST_PVS, false); + + T_Damage(self->enemy, self, self, dir, self->enemy->s.origin, vec3_origin, irandom(5, 11), -10, DAMAGE_ENERGY, MOD_UNKNOWN); +} + +MONSTERINFO_ATTACK(floater_attack) (edict_t *self) -> void +{ + float chance = 0.5f; + + if (frandom() > chance) + { + self->monsterinfo.attack_state = AS_STRAIGHT; + M_SetAnimation(self, &floater_move_attack1); + } + else // circle strafe + { + if (frandom() <= 0.5f) // switch directions + self->monsterinfo.lefty = !self->monsterinfo.lefty; + self->monsterinfo.attack_state = AS_SLIDING; + M_SetAnimation(self, &floater_move_attack1a); + } +} + +MONSTERINFO_MELEE(floater_melee) (edict_t *self) -> void +{ + if (frandom() < 0.5f) + M_SetAnimation(self, &floater_move_attack3); + else + M_SetAnimation(self, &floater_move_attack2); +} + +PAIN(floater_pain) (edict_t *self, edict_t *other, float kick, int damage, const mod_t &mod) -> void +{ + int n; + + if (level.time < self->pain_debounce_time) + return; + + // no pain anims if poppin' + if (self->monsterinfo.active_move == &floater_move_disguise || + self->monsterinfo.active_move == &floater_move_pop) + return; + + n = irandom(3); + if (n == 0) + gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); + else + gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0); + + self->pain_debounce_time = level.time + 3_sec; + + if (!M_ShouldReactToPain(self, mod)) + return; // no pain anims in nightmare + + if (n == 0) + M_SetAnimation(self, &floater_move_pain1); + else + M_SetAnimation(self, &floater_move_pain2); +} + +MONSTERINFO_SETSKIN(floater_setskin) (edict_t *self) -> void +{ + if (self->health < (self->max_health / 2)) + self->s.skinnum = 1; + else + self->s.skinnum = 0; +} + +void floater_dead(edict_t *self) +{ + self->mins = { -16, -16, -24 }; + self->maxs = { 16, 16, -8 }; + self->movetype = MOVETYPE_TOSS; + self->svflags |= SVF_DEADMONSTER; + self->nextthink = 0_ms; + gi.linkentity(self); +} + +DIE(floater_die) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void +{ + gi.sound(self, CHAN_VOICE, sound_death1, 1, ATTN_NORM, 0); + + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_EXPLOSION1); + gi.WritePosition(self->s.origin); + gi.multicast(self->s.origin, MULTICAST_PHS, false); + + self->s.skinnum /= 2; + + ThrowGibs(self, 55, { + { 2, "models/objects/gibs/sm_metal/tris.md2" }, + { 3, "models/objects/gibs/sm_meat/tris.md2" }, + { "models/monsters/float/gibs/piece.md2", GIB_SKINNED }, + { "models/monsters/float/gibs/gun.md2", GIB_SKINNED }, + { "models/monsters/float/gibs/base.md2", GIB_SKINNED }, + { "models/monsters/float/gibs/jar.md2", GIB_SKINNED | GIB_HEAD } + }); +} + +static void float_set_fly_parameters(edict_t *self) +{ + self->monsterinfo.fly_thrusters = false; + self->monsterinfo.fly_acceleration = 10.f; + self->monsterinfo.fly_speed = 100.f; + // Technician gets in closer because he has two melee attacks + self->monsterinfo.fly_min_distance = 20.f; + self->monsterinfo.fly_max_distance = 200.f; +} + +constexpr spawnflags_t SPAWNFLAG_FLOATER_DISGUISE = 8_spawnflag; + +/*QUAKED monster_floater (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight Disguise + */ +void SP_monster_floater(edict_t *self) +{ + if ( !M_AllowSpawn( self ) ) { + G_FreeEdict( self ); + return; + } + + sound_attack2 = gi.soundindex("floater/fltatck2.wav"); + sound_attack3 = gi.soundindex("floater/fltatck3.wav"); + sound_death1 = gi.soundindex("floater/fltdeth1.wav"); + sound_idle = gi.soundindex("floater/fltidle1.wav"); + sound_pain1 = gi.soundindex("floater/fltpain1.wav"); + sound_pain2 = gi.soundindex("floater/fltpain2.wav"); + sound_sight = gi.soundindex("floater/fltsght1.wav"); + + gi.soundindex("floater/fltatck1.wav"); + + self->monsterinfo.engine_sound = gi.soundindex("floater/fltsrch1.wav"); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex("models/monsters/float/tris.md2"); + + gi.modelindex("models/monsters/float/gibs/base.md2"); + gi.modelindex("models/monsters/float/gibs/gun.md2"); + gi.modelindex("models/monsters/float/gibs/jar.md2"); + gi.modelindex("models/monsters/float/gibs/piece.md2"); + + self->mins = { -24, -24, -24 }; + self->maxs = { 24, 24, 48 }; + + self->health = 200 * st.health_multiplier; + self->gib_health = -80; + self->mass = 300; + + self->pain = floater_pain; + self->die = floater_die; + + self->monsterinfo.stand = floater_stand; + self->monsterinfo.walk = floater_walk; + self->monsterinfo.run = floater_run; + self->monsterinfo.attack = floater_attack; + self->monsterinfo.melee = floater_melee; + self->monsterinfo.sight = floater_sight; + self->monsterinfo.idle = floater_idle; + self->monsterinfo.setskin = floater_setskin; + + gi.linkentity(self); + + if (self->spawnflags.has(SPAWNFLAG_FLOATER_DISGUISE)) + M_SetAnimation(self, &floater_move_disguise); + else if (frandom() <= 0.5f) + M_SetAnimation(self, &floater_move_stand1); + else + M_SetAnimation(self, &floater_move_stand2); + + self->monsterinfo.scale = MODEL_SCALE; + + self->monsterinfo.aiflags |= AI_ALTERNATE_FLY; + float_set_fly_parameters(self); + + flymonster_start(self); +} diff --git a/rerelease/m_float.h b/rerelease/m_float.h new file mode 100644 index 0000000..28231ee --- /dev/null +++ b/rerelease/m_float.h @@ -0,0 +1,259 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// G:\quake2\baseq2\models/monsters/float + +// This file generated by ModelGen - Do NOT Modify + +enum +{ + FRAME_actvat01, + FRAME_actvat02, + FRAME_actvat03, + FRAME_actvat04, + FRAME_actvat05, + FRAME_actvat06, + FRAME_actvat07, + FRAME_actvat08, + FRAME_actvat09, + FRAME_actvat10, + FRAME_actvat11, + FRAME_actvat12, + FRAME_actvat13, + FRAME_actvat14, + FRAME_actvat15, + FRAME_actvat16, + FRAME_actvat17, + FRAME_actvat18, + FRAME_actvat19, + FRAME_actvat20, + FRAME_actvat21, + FRAME_actvat22, + FRAME_actvat23, + FRAME_actvat24, + FRAME_actvat25, + FRAME_actvat26, + FRAME_actvat27, + FRAME_actvat28, + FRAME_actvat29, + FRAME_actvat30, + FRAME_actvat31, + FRAME_attak101, + FRAME_attak102, + FRAME_attak103, + FRAME_attak104, + FRAME_attak105, + FRAME_attak106, + FRAME_attak107, + FRAME_attak108, + FRAME_attak109, + FRAME_attak110, + FRAME_attak111, + FRAME_attak112, + FRAME_attak113, + FRAME_attak114, + FRAME_attak201, + FRAME_attak202, + FRAME_attak203, + FRAME_attak204, + FRAME_attak205, + FRAME_attak206, + FRAME_attak207, + FRAME_attak208, + FRAME_attak209, + FRAME_attak210, + FRAME_attak211, + FRAME_attak212, + FRAME_attak213, + FRAME_attak214, + FRAME_attak215, + FRAME_attak216, + FRAME_attak217, + FRAME_attak218, + FRAME_attak219, + FRAME_attak220, + FRAME_attak221, + FRAME_attak222, + FRAME_attak223, + FRAME_attak224, + FRAME_attak225, + FRAME_attak301, + FRAME_attak302, + FRAME_attak303, + FRAME_attak304, + FRAME_attak305, + FRAME_attak306, + FRAME_attak307, + FRAME_attak308, + FRAME_attak309, + FRAME_attak310, + FRAME_attak311, + FRAME_attak312, + FRAME_attak313, + FRAME_attak314, + FRAME_attak315, + FRAME_attak316, + FRAME_attak317, + FRAME_attak318, + FRAME_attak319, + FRAME_attak320, + FRAME_attak321, + FRAME_attak322, + FRAME_attak323, + FRAME_attak324, + FRAME_attak325, + FRAME_attak326, + FRAME_attak327, + FRAME_attak328, + FRAME_attak329, + FRAME_attak330, + FRAME_attak331, + FRAME_attak332, + FRAME_attak333, + FRAME_attak334, + FRAME_death01, + FRAME_death02, + FRAME_death03, + FRAME_death04, + FRAME_death05, + FRAME_death06, + FRAME_death07, + FRAME_death08, + FRAME_death09, + FRAME_death10, + FRAME_death11, + FRAME_death12, + FRAME_death13, + FRAME_pain101, + FRAME_pain102, + FRAME_pain103, + FRAME_pain104, + FRAME_pain105, + FRAME_pain106, + FRAME_pain107, + FRAME_pain201, + FRAME_pain202, + FRAME_pain203, + FRAME_pain204, + FRAME_pain205, + FRAME_pain206, + FRAME_pain207, + FRAME_pain208, + FRAME_pain301, + FRAME_pain302, + FRAME_pain303, + FRAME_pain304, + FRAME_pain305, + FRAME_pain306, + FRAME_pain307, + FRAME_pain308, + FRAME_pain309, + FRAME_pain310, + FRAME_pain311, + FRAME_pain312, + FRAME_stand101, + FRAME_stand102, + FRAME_stand103, + FRAME_stand104, + FRAME_stand105, + FRAME_stand106, + FRAME_stand107, + FRAME_stand108, + FRAME_stand109, + FRAME_stand110, + FRAME_stand111, + FRAME_stand112, + FRAME_stand113, + FRAME_stand114, + FRAME_stand115, + FRAME_stand116, + FRAME_stand117, + FRAME_stand118, + FRAME_stand119, + FRAME_stand120, + FRAME_stand121, + FRAME_stand122, + FRAME_stand123, + FRAME_stand124, + FRAME_stand125, + FRAME_stand126, + FRAME_stand127, + FRAME_stand128, + FRAME_stand129, + FRAME_stand130, + FRAME_stand131, + FRAME_stand132, + FRAME_stand133, + FRAME_stand134, + FRAME_stand135, + FRAME_stand136, + FRAME_stand137, + FRAME_stand138, + FRAME_stand139, + FRAME_stand140, + FRAME_stand141, + FRAME_stand142, + FRAME_stand143, + FRAME_stand144, + FRAME_stand145, + FRAME_stand146, + FRAME_stand147, + FRAME_stand148, + FRAME_stand149, + FRAME_stand150, + FRAME_stand151, + FRAME_stand152, + FRAME_stand201, + FRAME_stand202, + FRAME_stand203, + FRAME_stand204, + FRAME_stand205, + FRAME_stand206, + FRAME_stand207, + FRAME_stand208, + FRAME_stand209, + FRAME_stand210, + FRAME_stand211, + FRAME_stand212, + FRAME_stand213, + FRAME_stand214, + FRAME_stand215, + FRAME_stand216, + FRAME_stand217, + FRAME_stand218, + FRAME_stand219, + FRAME_stand220, + FRAME_stand221, + FRAME_stand222, + FRAME_stand223, + FRAME_stand224, + FRAME_stand225, + FRAME_stand226, + FRAME_stand227, + FRAME_stand228, + FRAME_stand229, + FRAME_stand230, + FRAME_stand231, + FRAME_stand232, + FRAME_stand233, + FRAME_stand234, + FRAME_stand235, + FRAME_stand236, + FRAME_stand237, + FRAME_stand238, + FRAME_stand239, + FRAME_stand240, + FRAME_stand241, + FRAME_stand242, + FRAME_stand243, + FRAME_stand244, + FRAME_stand245, + FRAME_stand246, + FRAME_stand247, + FRAME_stand248, + FRAME_stand249, + FRAME_stand250, + FRAME_stand251, + FRAME_stand252 +}; + +constexpr float MODEL_SCALE = 1.000000f; diff --git a/rerelease/m_flyer.cpp b/rerelease/m_flyer.cpp new file mode 100644 index 0000000..10edc8f --- /dev/null +++ b/rerelease/m_flyer.cpp @@ -0,0 +1,784 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +/* +============================================================================== + +flyer + +============================================================================== +*/ + +#include "g_local.h" +#include "m_flyer.h" +#include "m_flash.h" + +static int sound_sight; +static int sound_idle; +static int sound_pain1; +static int sound_pain2; +static int sound_slash; +static int sound_sproing; +static int sound_die; + +void flyer_check_melee(edict_t *self); +void flyer_loop_melee(edict_t *self); +void flyer_setstart(edict_t *self); + +// ROGUE - kamikaze stuff +void flyer_kamikaze(edict_t *self); +void flyer_kamikaze_check(edict_t *self); +void flyer_die(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod); + +MONSTERINFO_SIGHT(flyer_sight) (edict_t *self, edict_t *other) -> void +{ + gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); +} + +MONSTERINFO_IDLE(flyer_idle) (edict_t *self) -> void +{ + gi.sound(self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0); +} + +void flyer_pop_blades(edict_t *self) +{ + gi.sound(self, CHAN_VOICE, sound_sproing, 1, ATTN_NORM, 0); +} + +mframe_t flyer_frames_stand[] = { + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand } +}; +MMOVE_T(flyer_move_stand) = { FRAME_stand01, FRAME_stand45, flyer_frames_stand, nullptr }; + +mframe_t flyer_frames_walk[] = { + { ai_walk, 5 }, + { ai_walk, 5 }, + { ai_walk, 5 }, + { ai_walk, 5 }, + { ai_walk, 5 }, + { ai_walk, 5 }, + { ai_walk, 5 }, + { ai_walk, 5 }, + { ai_walk, 5 }, + { ai_walk, 5 }, + { ai_walk, 5 }, + { ai_walk, 5 }, + { ai_walk, 5 }, + { ai_walk, 5 }, + { ai_walk, 5 }, + { ai_walk, 5 }, + { ai_walk, 5 }, + { ai_walk, 5 }, + { ai_walk, 5 }, + { ai_walk, 5 }, + { ai_walk, 5 }, + { ai_walk, 5 }, + { ai_walk, 5 }, + { ai_walk, 5 }, + { ai_walk, 5 }, + { ai_walk, 5 }, + { ai_walk, 5 }, + { ai_walk, 5 }, + { ai_walk, 5 }, + { ai_walk, 5 }, + { ai_walk, 5 }, + { ai_walk, 5 }, + { ai_walk, 5 }, + { ai_walk, 5 }, + { ai_walk, 5 }, + { ai_walk, 5 }, + { ai_walk, 5 }, + { ai_walk, 5 }, + { ai_walk, 5 }, + { ai_walk, 5 }, + { ai_walk, 5 }, + { ai_walk, 5 }, + { ai_walk, 5 }, + { ai_walk, 5 }, + { ai_walk, 5 } +}; +MMOVE_T(flyer_move_walk) = { FRAME_stand01, FRAME_stand45, flyer_frames_walk, nullptr }; + +mframe_t flyer_frames_run[] = { + { ai_run, 10 }, + { ai_run, 10 }, + { ai_run, 10 }, + { ai_run, 10 }, + { ai_run, 10 }, + { ai_run, 10 }, + { ai_run, 10 }, + { ai_run, 10 }, + { ai_run, 10 }, + { ai_run, 10 }, + { ai_run, 10 }, + { ai_run, 10 }, + { ai_run, 10 }, + { ai_run, 10 }, + { ai_run, 10 }, + { ai_run, 10 }, + { ai_run, 10 }, + { ai_run, 10 }, + { ai_run, 10 }, + { ai_run, 10 }, + { ai_run, 10 }, + { ai_run, 10 }, + { ai_run, 10 }, + { ai_run, 10 }, + { ai_run, 10 }, + { ai_run, 10 }, + { ai_run, 10 }, + { ai_run, 10 }, + { ai_run, 10 }, + { ai_run, 10 }, + { ai_run, 10 }, + { ai_run, 10 }, + { ai_run, 10 }, + { ai_run, 10 }, + { ai_run, 10 }, + { ai_run, 10 }, + { ai_run, 10 }, + { ai_run, 10 }, + { ai_run, 10 }, + { ai_run, 10 }, + { ai_run, 10 }, + { ai_run, 10 }, + { ai_run, 10 }, + { ai_run, 10 }, + { ai_run, 10 } +}; +MMOVE_T(flyer_move_run) = { FRAME_stand01, FRAME_stand45, flyer_frames_run, nullptr }; + +mframe_t flyer_frames_kamizake[] = { + { ai_charge, 40, flyer_kamikaze_check }, + { ai_charge, 40, flyer_kamikaze_check }, + { ai_charge, 40, flyer_kamikaze_check }, + { ai_charge, 40, flyer_kamikaze_check }, + { ai_charge, 40, flyer_kamikaze_check } +}; +MMOVE_T(flyer_move_kamikaze) = { FRAME_rollr02, FRAME_rollr06, flyer_frames_kamizake, flyer_kamikaze }; + +MONSTERINFO_RUN(flyer_run) (edict_t *self) -> void +{ + if (self->mass > 50) + M_SetAnimation(self, &flyer_move_kamikaze); + else if (self->monsterinfo.aiflags & AI_STAND_GROUND) + M_SetAnimation(self, &flyer_move_stand); + else + M_SetAnimation(self, &flyer_move_run); +} + +MONSTERINFO_WALK(flyer_walk) (edict_t *self) -> void +{ + if (self->mass > 50) + flyer_run(self); + else + M_SetAnimation(self, &flyer_move_walk); +} + +MONSTERINFO_STAND(flyer_stand) (edict_t *self) -> void +{ + if (self->mass > 50) + flyer_run(self); + else + M_SetAnimation(self, &flyer_move_stand); +} + +// ROGUE - kamikaze stuff + +void flyer_kamikaze_explode(edict_t *self) +{ + vec3_t dir; + + if (self->monsterinfo.commander && self->monsterinfo.commander->inuse && + !strcmp(self->monsterinfo.commander->classname, "monster_carrier")) + self->monsterinfo.commander->monsterinfo.monster_slots++; + + if (self->enemy) + { + dir = self->enemy->s.origin - self->s.origin; + T_Damage(self->enemy, self, self, dir, self->s.origin, vec3_origin, (int) 50, (int) 50, DAMAGE_RADIUS, MOD_UNKNOWN); + } + + flyer_die(self, nullptr, nullptr, 0, dir, MOD_EXPLOSIVE); +} + +void flyer_kamikaze(edict_t *self) +{ + M_SetAnimation(self, &flyer_move_kamikaze); +} + +void flyer_kamikaze_check(edict_t *self) +{ + float dist; + + // PMM - this needed because we could have gone away before we get here (blocked code) + if (!self->inuse) + return; + + if ((!self->enemy) || (!self->enemy->inuse)) + { + flyer_kamikaze_explode(self); + return; + } + + self->s.angles[0] = vectoangles(self->enemy->s.origin - self->s.origin).x; + + self->goalentity = self->enemy; + + dist = realrange(self, self->enemy); + + if (dist < 90) + flyer_kamikaze_explode(self); +} + +#if 0 +mframe_t flyer_frames_rollright[] = { + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move } +}; +MMOVE_T(flyer_move_rollright) = { FRAME_rollr01, FRAME_rollr09, flyer_frames_rollright, nullptr }; + +mframe_t flyer_frames_rollleft[] = { + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move } +}; +MMOVE_T(flyer_move_rollleft) = { FRAME_rollf01, FRAME_rollf09, flyer_frames_rollleft, nullptr }; +#endif + +mframe_t flyer_frames_pain3[] = { + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move } +}; +MMOVE_T(flyer_move_pain3) = { FRAME_pain301, FRAME_pain304, flyer_frames_pain3, flyer_run }; + +mframe_t flyer_frames_pain2[] = { + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move } +}; +MMOVE_T(flyer_move_pain2) = { FRAME_pain201, FRAME_pain204, flyer_frames_pain2, flyer_run }; + +mframe_t flyer_frames_pain1[] = { + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move } +}; +MMOVE_T(flyer_move_pain1) = { FRAME_pain101, FRAME_pain109, flyer_frames_pain1, flyer_run }; + +#if 0 +mframe_t flyer_frames_defense[] = { + { ai_move }, + { ai_move }, + { ai_move }, // Hold this frame + { ai_move }, + { ai_move }, + { ai_move } +}; +MMOVE_T(flyer_move_defense) = { FRAME_defens01, FRAME_defens06, flyer_frames_defense, nullptr }; + +mframe_t flyer_frames_bankright[] = { + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move } +}; +MMOVE_T(flyer_move_bankright) = { FRAME_bankr01, FRAME_bankr07, flyer_frames_bankright, nullptr }; + +mframe_t flyer_frames_bankleft[] = { + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move } +}; +MMOVE_T(flyer_move_bankleft) = { FRAME_bankl01, FRAME_bankl07, flyer_frames_bankleft, nullptr }; +#endif + +void flyer_fire(edict_t *self, monster_muzzleflash_id_t flash_number) +{ + vec3_t start; + vec3_t forward, right; + vec3_t end; + vec3_t dir; + + if (!self->enemy || !self->enemy->inuse) // PGM + return; // PGM + + AngleVectors(self->s.angles, forward, right, nullptr); + start = M_ProjectFlashSource(self, monster_flash_offset[flash_number], forward, right); + + end = self->enemy->s.origin; + end[2] += self->enemy->viewheight; + dir = end - start; + dir.normalize(); + + monster_fire_blaster(self, start, dir, 1, 1000, flash_number, (self->s.frame % 4) ? EF_NONE : EF_HYPERBLASTER); +} + +void flyer_fireleft(edict_t *self) +{ + flyer_fire(self, MZ2_FLYER_BLASTER_1); +} + +void flyer_fireright(edict_t *self) +{ + flyer_fire(self, MZ2_FLYER_BLASTER_2); +} + +mframe_t flyer_frames_attack2[] = { + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge, -10, flyer_fireleft }, // left gun + { ai_charge, -10, flyer_fireright }, // right gun + { ai_charge, -10, flyer_fireleft }, // left gun + { ai_charge, -10, flyer_fireright }, // right gun + { ai_charge, -10, flyer_fireleft }, // left gun + { ai_charge, -10, flyer_fireright }, // right gun + { ai_charge, -10, flyer_fireleft }, // left gun + { ai_charge, -10, flyer_fireright }, // right gun + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge } +}; +MMOVE_T(flyer_move_attack2) = { FRAME_attak201, FRAME_attak217, flyer_frames_attack2, flyer_run }; + +// PMM +// circle strafe frames + +mframe_t flyer_frames_attack3[] = { + { ai_charge, 10 }, + { ai_charge, 10 }, + { ai_charge, 10 }, + { ai_charge, 10, flyer_fireleft }, // left gun + { ai_charge, 10, flyer_fireright }, // right gun + { ai_charge, 10, flyer_fireleft }, // left gun + { ai_charge, 10, flyer_fireright }, // right gun + { ai_charge, 10, flyer_fireleft }, // left gun + { ai_charge, 10, flyer_fireright }, // right gun + { ai_charge, 10, flyer_fireleft }, // left gun + { ai_charge, 10, flyer_fireright }, // right gun + { ai_charge, 10 }, + { ai_charge, 10 }, + { ai_charge, 10 }, + { ai_charge, 10 }, + { ai_charge, 10 }, + { ai_charge, 10 } +}; +MMOVE_T(flyer_move_attack3) = { FRAME_attak201, FRAME_attak217, flyer_frames_attack3, flyer_run }; +// pmm + +void flyer_slash_left(edict_t *self) +{ + vec3_t aim = { MELEE_DISTANCE, self->mins[0], 0 }; + if (!fire_hit(self, aim, 5, 0)) + self->monsterinfo.melee_debounce_time = level.time + 1.5_sec; + gi.sound(self, CHAN_WEAPON, sound_slash, 1, ATTN_NORM, 0); +} + +void flyer_slash_right(edict_t *self) +{ + vec3_t aim = { MELEE_DISTANCE, self->maxs[0], 0 }; + if (!fire_hit(self, aim, 5, 0)) + self->monsterinfo.melee_debounce_time = level.time + 1.5_sec; + gi.sound(self, CHAN_WEAPON, sound_slash, 1, ATTN_NORM, 0); +} + +mframe_t flyer_frames_start_melee[] = { + { ai_charge, 0, flyer_pop_blades }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge } +}; +MMOVE_T(flyer_move_start_melee) = { FRAME_attak101, FRAME_attak106, flyer_frames_start_melee, flyer_loop_melee }; + +mframe_t flyer_frames_end_melee[] = { + { ai_charge }, + { ai_charge }, + { ai_charge } +}; +MMOVE_T(flyer_move_end_melee) = { FRAME_attak119, FRAME_attak121, flyer_frames_end_melee, flyer_run }; + +mframe_t flyer_frames_loop_melee[] = { + { ai_charge }, // Loop Start + { ai_charge }, + { ai_charge, 0, flyer_slash_left }, // Left Wing Strike + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge, 0, flyer_slash_right }, // Right Wing Strike + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge } // Loop Ends + +}; +MMOVE_T(flyer_move_loop_melee) = { FRAME_attak107, FRAME_attak118, flyer_frames_loop_melee, flyer_check_melee }; + +void flyer_loop_melee(edict_t *self) +{ + M_SetAnimation(self, &flyer_move_loop_melee); +} + +static void flyer_set_fly_parameters(edict_t *self, bool melee) +{ + if (melee) + { + // engage thrusters for a slice + self->monsterinfo.fly_pinned = false; + self->monsterinfo.fly_thrusters = true; + self->monsterinfo.fly_position_time = 0_sec; + self->monsterinfo.fly_acceleration = 20.f; + self->monsterinfo.fly_speed = 210.f; + self->monsterinfo.fly_min_distance = 0.f; + self->monsterinfo.fly_max_distance = 10.f; + } + else + { + self->monsterinfo.fly_thrusters = false; + self->monsterinfo.fly_acceleration = 15.f; + self->monsterinfo.fly_speed = 165.f; + self->monsterinfo.fly_min_distance = 45.f; + self->monsterinfo.fly_max_distance = 200.f; + } +} + +MONSTERINFO_ATTACK(flyer_attack) (edict_t *self) -> void +{ + if (self->mass > 50) + { + flyer_run(self); + return; + } + + float range = range_to(self, self->enemy); + + if (self->enemy && visible(self, self->enemy) && range <= 225.f && frandom() > (range / 225.f) * 0.35f) + { + // fly-by slicing! + self->monsterinfo.attack_state = AS_STRAIGHT; + M_SetAnimation(self, &flyer_move_start_melee); + flyer_set_fly_parameters(self, true); + } + else + { + self->monsterinfo.attack_state = AS_STRAIGHT; + M_SetAnimation(self, &flyer_move_attack2); + } + + // [Paril-KEX] for alternate fly mode, sometimes we'll pin us + // down, kind of like a pseudo-stand ground + if (!self->monsterinfo.fly_pinned && brandom() && self->enemy && visible(self, self->enemy)) + { + self->monsterinfo.fly_pinned = true; + self->monsterinfo.fly_position_time = max(self->monsterinfo.fly_position_time, self->monsterinfo.fly_position_time + 1.7_sec); // make sure there's enough time for attack2/3 + + if (brandom()) + self->monsterinfo.fly_ideal_position = self->s.origin + (self->velocity * frandom()); // pin to our current position + else + self->monsterinfo.fly_ideal_position += self->enemy->s.origin; // make un-relative + } + + // if we're currently pinned, fly_position_time will unpin us eventually +} + +MONSTERINFO_MELEE(flyer_melee) (edict_t *self) -> void +{ + if (self->mass > 50) + flyer_run(self); + else + { + M_SetAnimation(self, &flyer_move_start_melee); + flyer_set_fly_parameters(self, true); + } +} + +void flyer_check_melee(edict_t *self) +{ + if (range_to(self, self->enemy) <= RANGE_MELEE) + { + if (self->monsterinfo.melee_debounce_time <= level.time) + { + M_SetAnimation(self, &flyer_move_loop_melee); + return; + } + } + + M_SetAnimation(self, &flyer_move_end_melee); + flyer_set_fly_parameters(self, false); +} + +PAIN(flyer_pain) (edict_t *self, edict_t *other, float kick, int damage, const mod_t &mod) -> void +{ + int n; + + // pmm - kamikaze's don't feel pain + if (self->mass != 50) + return; + // pmm + + if (level.time < self->pain_debounce_time) + return; + + self->pain_debounce_time = level.time + 3_sec; + + n = irandom(3); + if (n == 0) + gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); + else if (n == 1) + gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0); + else + gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); + + if (!M_ShouldReactToPain(self, mod)) + return; // no pain anims in nightmare + + flyer_set_fly_parameters(self, false); + + if (n == 0) + M_SetAnimation(self, &flyer_move_pain1); + else if (n == 1) + M_SetAnimation(self, &flyer_move_pain2); + else + M_SetAnimation(self, &flyer_move_pain3); +} + +MONSTERINFO_SETSKIN(flyer_setskin) (edict_t *self) -> void +{ + if (self->health < (self->max_health / 2)) + self->s.skinnum = 1; + else + self->s.skinnum = 0; +} + +DIE(flyer_die) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void +{ + gi.sound(self, CHAN_VOICE, sound_die, 1, ATTN_NORM, 0); + + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_EXPLOSION1); + gi.WritePosition(self->s.origin); + gi.multicast(self->s.origin, MULTICAST_PHS, false); + + self->s.skinnum /= 2; + + ThrowGibs(self, 55, { + { 2, "models/objects/gibs/sm_metal/tris.md2" }, + { 2, "models/objects/gibs/sm_meat/tris.md2" }, + { "models/monsters/flyer/gibs/base.md2", GIB_SKINNED }, + { 2, "models/monsters/flyer/gibs/gun.md2", GIB_SKINNED }, + { 2, "models/monsters/flyer/gibs/wing.md2", GIB_SKINNED }, + { "models/monsters/flyer/gibs/head.md2", GIB_SKINNED | GIB_HEAD } + }); + + self->touch = nullptr; +} + +// PMM - kamikaze code .. blow up if blocked +MONSTERINFO_BLOCKED(flyer_blocked) (edict_t *self, float dist) -> bool +{ + // kamikaze = 100, normal = 50 + if (self->mass == 100) + { + flyer_kamikaze_check(self); + + // if the above didn't blow us up (i.e. I got blocked by the player) + if (self->inuse) + T_Damage(self, self, self, vec3_origin, self->s.origin, vec3_origin, 9999, 100, DAMAGE_NONE, MOD_UNKNOWN); + + return true; + } + + return false; +} + +TOUCH(kamikaze_touch) (edict_t *ent, edict_t *other, const trace_t &tr, bool other_touching_self) -> void +{ + T_Damage(ent, ent, ent, ent->velocity.normalized(), ent->s.origin, ent->velocity.normalized(), 9999, 100, DAMAGE_NONE, MOD_UNKNOWN); +} + +TOUCH(flyer_touch) (edict_t *ent, edict_t *other, const trace_t &tr, bool other_touching_self) -> void +{ + if ((other->monsterinfo.aiflags & AI_ALTERNATE_FLY) && (other->flags & FL_FLY) && + (ent->monsterinfo.duck_wait_time < level.time)) + { + ent->monsterinfo.duck_wait_time = level.time + 1_sec; + ent->monsterinfo.fly_thrusters = false; + + vec3_t dir = (ent->s.origin - other->s.origin).normalized(); + ent->velocity = dir * 500.f; + + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_SPLASH); + gi.WriteByte(32); + gi.WritePosition(tr.endpos); + gi.WriteDir(dir); + gi.WriteByte(SPLASH_SPARKS); + gi.multicast(tr.endpos, MULTICAST_PVS, false); + } +} + +/*QUAKED monster_flyer (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight + */ +void SP_monster_flyer(edict_t *self) +{ + if ( !M_AllowSpawn( self ) ) { + G_FreeEdict( self ); + return; + } + + sound_sight = gi.soundindex("flyer/flysght1.wav"); + sound_idle = gi.soundindex("flyer/flysrch1.wav"); + sound_pain1 = gi.soundindex("flyer/flypain1.wav"); + sound_pain2 = gi.soundindex("flyer/flypain2.wav"); + sound_slash = gi.soundindex("flyer/flyatck2.wav"); + sound_sproing = gi.soundindex("flyer/flyatck1.wav"); + sound_die = gi.soundindex("flyer/flydeth1.wav"); + + gi.soundindex("flyer/flyatck3.wav"); + + self->s.modelindex = gi.modelindex("models/monsters/flyer/tris.md2"); + + gi.modelindex("models/monsters/flyer/gibs/base.md2"); + gi.modelindex("models/monsters/flyer/gibs/wing.md2"); + gi.modelindex("models/monsters/flyer/gibs/gun.md2"); + gi.modelindex("models/monsters/flyer/gibs/head.md2"); + + self->mins = { -16, -16, -24 }; + // PMM - shortened to 16 from 32 + self->maxs = { 16, 16, 16 }; + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + + self->viewheight = 12; + + self->monsterinfo.engine_sound = gi.soundindex("flyer/flyidle1.wav"); + + self->health = 50 * st.health_multiplier; + self->mass = 50; + + self->pain = flyer_pain; + self->die = flyer_die; + + self->monsterinfo.stand = flyer_stand; + self->monsterinfo.walk = flyer_walk; + self->monsterinfo.run = flyer_run; + self->monsterinfo.attack = flyer_attack; + self->monsterinfo.melee = flyer_melee; + self->monsterinfo.sight = flyer_sight; + self->monsterinfo.idle = flyer_idle; + self->monsterinfo.blocked = flyer_blocked; + self->monsterinfo.setskin = flyer_setskin; + + gi.linkentity(self); + + M_SetAnimation(self, &flyer_move_stand); + self->monsterinfo.scale = MODEL_SCALE; + + if (self->s.effects & EF_ROCKET) + { + // PMM - normal flyer has mass of 50 + self->mass = 100; + self->yaw_speed = 5; + self->touch = kamikaze_touch; + } + else + { + self->monsterinfo.aiflags |= AI_ALTERNATE_FLY; + self->monsterinfo.fly_buzzard = true; + flyer_set_fly_parameters(self, false); + self->touch = flyer_touch; + } + + flymonster_start(self); +} + +// PMM - suicide fliers +void SP_monster_kamikaze(edict_t *self) +{ + if ( !M_AllowSpawn( self ) ) { + G_FreeEdict( self ); + return; + } + + self->s.effects |= EF_ROCKET; + + SP_monster_flyer(self); +} diff --git a/rerelease/m_flyer.h b/rerelease/m_flyer.h new file mode 100644 index 0000000..82a619d --- /dev/null +++ b/rerelease/m_flyer.h @@ -0,0 +1,161 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// G:\quake2\baseq2\models/monsters/flyer + +// This file generated by ModelGen - Do NOT Modify +enum +{ + FRAME_start01, + FRAME_start02, + FRAME_start03, + FRAME_start04, + FRAME_start05, + FRAME_start06, + FRAME_stop01, + FRAME_stop02, + FRAME_stop03, + FRAME_stop04, + FRAME_stop05, + FRAME_stop06, + FRAME_stop07, + FRAME_stand01, + FRAME_stand02, + FRAME_stand03, + FRAME_stand04, + FRAME_stand05, + FRAME_stand06, + FRAME_stand07, + FRAME_stand08, + FRAME_stand09, + FRAME_stand10, + FRAME_stand11, + FRAME_stand12, + FRAME_stand13, + FRAME_stand14, + FRAME_stand15, + FRAME_stand16, + FRAME_stand17, + FRAME_stand18, + FRAME_stand19, + FRAME_stand20, + FRAME_stand21, + FRAME_stand22, + FRAME_stand23, + FRAME_stand24, + FRAME_stand25, + FRAME_stand26, + FRAME_stand27, + FRAME_stand28, + FRAME_stand29, + FRAME_stand30, + FRAME_stand31, + FRAME_stand32, + FRAME_stand33, + FRAME_stand34, + FRAME_stand35, + FRAME_stand36, + FRAME_stand37, + FRAME_stand38, + FRAME_stand39, + FRAME_stand40, + FRAME_stand41, + FRAME_stand42, + FRAME_stand43, + FRAME_stand44, + FRAME_stand45, + FRAME_attak101, + FRAME_attak102, + FRAME_attak103, + FRAME_attak104, + FRAME_attak105, + FRAME_attak106, + FRAME_attak107, + FRAME_attak108, + FRAME_attak109, + FRAME_attak110, + FRAME_attak111, + FRAME_attak112, + FRAME_attak113, + FRAME_attak114, + FRAME_attak115, + FRAME_attak116, + FRAME_attak117, + FRAME_attak118, + FRAME_attak119, + FRAME_attak120, + FRAME_attak121, + FRAME_attak201, + FRAME_attak202, + FRAME_attak203, + FRAME_attak204, + FRAME_attak205, + FRAME_attak206, + FRAME_attak207, + FRAME_attak208, + FRAME_attak209, + FRAME_attak210, + FRAME_attak211, + FRAME_attak212, + FRAME_attak213, + FRAME_attak214, + FRAME_attak215, + FRAME_attak216, + FRAME_attak217, + FRAME_bankl01, + FRAME_bankl02, + FRAME_bankl03, + FRAME_bankl04, + FRAME_bankl05, + FRAME_bankl06, + FRAME_bankl07, + FRAME_bankr01, + FRAME_bankr02, + FRAME_bankr03, + FRAME_bankr04, + FRAME_bankr05, + FRAME_bankr06, + FRAME_bankr07, + FRAME_rollf01, + FRAME_rollf02, + FRAME_rollf03, + FRAME_rollf04, + FRAME_rollf05, + FRAME_rollf06, + FRAME_rollf07, + FRAME_rollf08, + FRAME_rollf09, + FRAME_rollr01, + FRAME_rollr02, + FRAME_rollr03, + FRAME_rollr04, + FRAME_rollr05, + FRAME_rollr06, + FRAME_rollr07, + FRAME_rollr08, + FRAME_rollr09, + FRAME_defens01, + FRAME_defens02, + FRAME_defens03, + FRAME_defens04, + FRAME_defens05, + FRAME_defens06, + FRAME_pain101, + FRAME_pain102, + FRAME_pain103, + FRAME_pain104, + FRAME_pain105, + FRAME_pain106, + FRAME_pain107, + FRAME_pain108, + FRAME_pain109, + FRAME_pain201, + FRAME_pain202, + FRAME_pain203, + FRAME_pain204, + FRAME_pain301, + FRAME_pain302, + FRAME_pain303, + FRAME_pain304 +}; + +constexpr float MODEL_SCALE = 1.000000f; diff --git a/rerelease/m_gladiator.cpp b/rerelease/m_gladiator.cpp new file mode 100644 index 0000000..fbfca30 --- /dev/null +++ b/rerelease/m_gladiator.cpp @@ -0,0 +1,489 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +/* +============================================================================== + +GLADIATOR + +============================================================================== +*/ + +#include "g_local.h" +#include "m_gladiator.h" +#include "m_flash.h" + +static int sound_pain1; +static int sound_pain2; +static int sound_die; +static int sound_die2; +static int sound_gun; +static int sound_gunb; +static int sound_cleaver_swing; +static int sound_cleaver_hit; +static int sound_cleaver_miss; +static int sound_idle; +static int sound_search; +static int sound_sight; + +MONSTERINFO_IDLE(gladiator_idle) (edict_t *self) -> void +{ + gi.sound(self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0); +} + +MONSTERINFO_SIGHT(gladiator_sight) (edict_t *self, edict_t *other) -> void +{ + gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); +} + +MONSTERINFO_SEARCH(gladiator_search) (edict_t *self) -> void +{ + gi.sound(self, CHAN_VOICE, sound_search, 1, ATTN_NORM, 0); +} + +void gladiator_cleaver_swing(edict_t *self) +{ + gi.sound(self, CHAN_WEAPON, sound_cleaver_swing, 1, ATTN_NORM, 0); +} + +mframe_t gladiator_frames_stand[] = { + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand } +}; +MMOVE_T(gladiator_move_stand) = { FRAME_stand1, FRAME_stand7, gladiator_frames_stand, nullptr }; + +MONSTERINFO_STAND(gladiator_stand) (edict_t *self) -> void +{ + M_SetAnimation(self, &gladiator_move_stand); +} + +mframe_t gladiator_frames_walk[] = { + { ai_walk, 15 }, + { ai_walk, 7 }, + { ai_walk, 6 }, + { ai_walk, 5 }, + { ai_walk, 2, monster_footstep }, + { ai_walk }, + { ai_walk, 2 }, + { ai_walk, 8 }, + { ai_walk, 12 }, + { ai_walk, 8 }, + { ai_walk, 5 }, + { ai_walk, 5 }, + { ai_walk, 2, monster_footstep }, + { ai_walk, 2 }, + { ai_walk, 1 }, + { ai_walk, 8 } +}; +MMOVE_T(gladiator_move_walk) = { FRAME_walk1, FRAME_walk16, gladiator_frames_walk, nullptr }; + +MONSTERINFO_WALK(gladiator_walk) (edict_t *self) -> void +{ + M_SetAnimation(self, &gladiator_move_walk); +} + +mframe_t gladiator_frames_run[] = { + { ai_run, 23 }, + { ai_run, 14 }, + { ai_run, 14, monster_footstep }, + { ai_run, 21 }, + { ai_run, 12 }, + { ai_run, 13, monster_footstep } +}; +MMOVE_T(gladiator_move_run) = { FRAME_run1, FRAME_run6, gladiator_frames_run, nullptr }; + +MONSTERINFO_RUN(gladiator_run) (edict_t *self) -> void +{ + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + M_SetAnimation(self, &gladiator_move_stand); + else + M_SetAnimation(self, &gladiator_move_run); +} + +void GladiatorMelee(edict_t *self) +{ + vec3_t aim = { MELEE_DISTANCE, self->mins[0], -4 }; + if (fire_hit(self, aim, irandom(20, 25), 300)) + gi.sound(self, CHAN_AUTO, sound_cleaver_hit, 1, ATTN_NORM, 0); + else + { + gi.sound(self, CHAN_AUTO, sound_cleaver_miss, 1, ATTN_NORM, 0); + self->monsterinfo.melee_debounce_time = level.time + 1.5_sec; + } +} + +mframe_t gladiator_frames_attack_melee[] = { + { ai_charge }, + { ai_charge }, + { ai_charge, 0, gladiator_cleaver_swing }, + { ai_charge }, + { ai_charge, 0, GladiatorMelee }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge, 0, gladiator_cleaver_swing }, + { ai_charge }, + { ai_charge }, + { ai_charge, 0, GladiatorMelee }, + { ai_charge }, + { ai_charge } +}; +MMOVE_T(gladiator_move_attack_melee) = { FRAME_melee3, FRAME_melee16, gladiator_frames_attack_melee, gladiator_run }; + +MONSTERINFO_MELEE(gladiator_melee) (edict_t *self) -> void +{ + M_SetAnimation(self, &gladiator_move_attack_melee); +} + +void GladiatorGun(edict_t *self) +{ + vec3_t start; + vec3_t dir; + vec3_t forward, right; + + AngleVectors(self->s.angles, forward, right, nullptr); + start = M_ProjectFlashSource(self, monster_flash_offset[MZ2_GLADIATOR_RAILGUN_1], forward, right); + + // calc direction to where we targted + dir = self->pos1 - start; + dir.normalize(); + + monster_fire_railgun(self, start, dir, 50, 100, MZ2_GLADIATOR_RAILGUN_1); +} + +mframe_t gladiator_frames_attack_gun[] = { + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge, 0, GladiatorGun }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge, 0, monster_footstep }, + { ai_charge } +}; +MMOVE_T(gladiator_move_attack_gun) = { FRAME_attack1, FRAME_attack9, gladiator_frames_attack_gun, gladiator_run }; + +// RAFAEL +void gladbGun(edict_t *self) +{ + vec3_t start; + vec3_t dir; + vec3_t forward, right; + + AngleVectors(self->s.angles, forward, right, nullptr); + start = M_ProjectFlashSource(self, monster_flash_offset[MZ2_GLADIATOR_RAILGUN_1], forward, right); + + // calc direction to where we targeted + dir = self->pos1 - start; + dir.normalize(); + + int damage = 35; + int radius_damage = 45; + + if (self->s.frame > FRAME_attack3) + { + damage /= 2; + radius_damage /= 2; + } + + fire_plasma(self, start, dir, damage, 725, radius_damage, radius_damage); + + // save for aiming the shot + self->pos1 = self->enemy->s.origin; + self->pos1[2] += self->enemy->viewheight; +} + +void gladbGun_check(edict_t *self) +{ + if (skill->integer == 3) + gladbGun(self); +} + +mframe_t gladb_frames_attack_gun[] = { + { ai_charge }, + { ai_charge }, + { ai_charge, 0, gladbGun }, + { ai_charge }, + { ai_charge }, + { ai_charge, 0, gladbGun }, + { ai_charge }, + { ai_charge }, + { ai_charge, 0, gladbGun_check } +}; +MMOVE_T(gladb_move_attack_gun) = { FRAME_attack1, FRAME_attack9, gladb_frames_attack_gun, gladiator_run }; +// RAFAEL + +MONSTERINFO_ATTACK(gladiator_attack) (edict_t *self) -> void +{ + float range; + vec3_t v; + + // a small safe zone + v = self->s.origin - self->enemy->s.origin; + range = v.length(); + if (range <= (MELEE_DISTANCE + 32) && self->monsterinfo.melee_debounce_time <= level.time) + return; + else if (!M_CheckClearShot(self, monster_flash_offset[MZ2_GLADIATOR_RAILGUN_1])) + return; + + // charge up the railgun + self->pos1 = self->enemy->s.origin; // save for aiming the shot + self->pos1[2] += self->enemy->viewheight; + // RAFAEL + if (self->style == 1) + { + gi.sound(self, CHAN_WEAPON, sound_gunb, 1, ATTN_NORM, 0); + M_SetAnimation(self, &gladb_move_attack_gun); + } + else + { + // RAFAEL + gi.sound(self, CHAN_WEAPON, sound_gun, 1, ATTN_NORM, 0); + M_SetAnimation(self, &gladiator_move_attack_gun); + // RAFAEL + } + // RAFAEL +} + +mframe_t gladiator_frames_pain[] = { + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move } +}; +MMOVE_T(gladiator_move_pain) = { FRAME_pain2, FRAME_pain5, gladiator_frames_pain, gladiator_run }; + +mframe_t gladiator_frames_pain_air[] = { + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move } +}; +MMOVE_T(gladiator_move_pain_air) = { FRAME_painup2, FRAME_painup6, gladiator_frames_pain_air, gladiator_run }; + +PAIN(gladiator_pain) (edict_t *self, edict_t *other, float kick, int damage, const mod_t &mod) -> void +{ + if (level.time < self->pain_debounce_time) + { + if ((self->velocity[2] > 100) && (self->monsterinfo.active_move == &gladiator_move_pain)) + M_SetAnimation(self, &gladiator_move_pain_air); + return; + } + + self->pain_debounce_time = level.time + 3_sec; + + if (frandom() < 0.5f) + gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); + else + gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0); + + if (!M_ShouldReactToPain(self, mod)) + return; // no pain anims in nightmare + + if (self->velocity[2] > 100) + M_SetAnimation(self, &gladiator_move_pain_air); + else + M_SetAnimation(self, &gladiator_move_pain); +} + +MONSTERINFO_SETSKIN(gladiator_setskin) (edict_t *self) -> void +{ + if (self->health < (self->max_health / 2)) + self->s.skinnum |= 1; + else + self->s.skinnum &= ~1; +} + +void gladiator_dead(edict_t *self) +{ + self->mins = { -16, -16, -24 }; + self->maxs = { 16, 16, -8 }; + monster_dead(self); +} + +static void gladiator_shrink(edict_t *self) +{ + self->maxs[2] = 0; + self->svflags |= SVF_DEADMONSTER; + gi.linkentity(self); +} + +mframe_t gladiator_frames_death[] = { + { ai_move }, + { ai_move }, + { ai_move, 0, gladiator_shrink }, + { ai_move, 0, monster_footstep }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move } +}; +MMOVE_T(gladiator_move_death) = { FRAME_death2, FRAME_death22, gladiator_frames_death, gladiator_dead }; + +DIE(gladiator_die) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void +{ + // check for gib + if (M_CheckGib(self, mod)) + { + gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0); + + self->s.skinnum /= 2; + + ThrowGibs(self, damage, { + { 2, "models/objects/gibs/bone/tris.md2" }, + { 2, "models/objects/gibs/sm_meat/tris.md2" }, + { 2, "models/monsters/gladiatr/gibs/thigh.md2", GIB_SKINNED }, + { "models/monsters/gladiatr/gibs/larm.md2", GIB_SKINNED | GIB_UPRIGHT }, + { "models/monsters/gladiatr/gibs/rarm.md2", GIB_SKINNED | GIB_UPRIGHT }, + { "models/monsters/gladiatr/gibs/chest.md2", GIB_SKINNED }, + { "models/monsters/gladiatr/gibs/head.md2", GIB_SKINNED | GIB_HEAD } + }); + self->deadflag = true; + return; + } + + if (self->deadflag) + return; + + // regular death + gi.sound(self, CHAN_BODY, sound_die, 1, ATTN_NORM, 0); + + if (brandom()) + gi.sound(self, CHAN_VOICE, sound_die2, 1, ATTN_NORM, 0); + + self->deadflag = true; + self->takedamage = true; + + M_SetAnimation(self, &gladiator_move_death); +} + +//=========== +// PGM +MONSTERINFO_BLOCKED(gladiator_blocked) (edict_t *self, float dist) -> bool +{ + if (blocked_checkplat(self, dist)) + return true; + + return false; +} +// PGM +//=========== + +/*QUAKED monster_gladiator (1 .5 0) (-32 -32 -24) (32 32 64) Ambush Trigger_Spawn Sight + */ +void SP_monster_gladiator(edict_t *self) +{ + if ( !M_AllowSpawn( self ) ) { + G_FreeEdict( self ); + return; + } + + sound_pain1 = gi.soundindex("gladiator/pain.wav"); + sound_pain2 = gi.soundindex("gladiator/gldpain2.wav"); + sound_die = gi.soundindex("gladiator/glddeth2.wav"); + sound_die2 = gi.soundindex("gladiator/death.wav"); + sound_cleaver_swing = gi.soundindex("gladiator/melee1.wav"); + sound_cleaver_hit = gi.soundindex("gladiator/melee2.wav"); + sound_cleaver_miss = gi.soundindex("gladiator/melee3.wav"); + sound_idle = gi.soundindex("gladiator/gldidle1.wav"); + sound_search = gi.soundindex("gladiator/gldsrch1.wav"); + sound_sight = gi.soundindex("gladiator/sight.wav"); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex("models/monsters/gladiatr/tris.md2"); + + gi.modelindex("models/monsters/gladiatr/gibs/chest.md2"); + gi.modelindex("models/monsters/gladiatr/gibs/head.md2"); + gi.modelindex("models/monsters/gladiatr/gibs/larm.md2"); + gi.modelindex("models/monsters/gladiatr/gibs/rarm.md2"); + gi.modelindex("models/monsters/gladiatr/gibs/thigh.md2"); + + // RAFAEL + if (strcmp(self->classname, "monster_gladb") == 0) + { + sound_gunb = gi.soundindex("weapons/plasshot.wav"); + + self->health = 250 * st.health_multiplier; + self->mass = 350; + + if (!st.was_key_specified("power_armor_type")) + self->monsterinfo.power_armor_type = IT_ITEM_POWER_SHIELD; + if (!st.was_key_specified("power_armor_power")) + self->monsterinfo.power_armor_power = 250; + + self->s.skinnum = 2; + + self->style = 1; + + self->monsterinfo.weapon_sound = gi.soundindex("weapons/phaloop.wav"); + } + else + { + // RAFAEL + sound_gun = gi.soundindex("gladiator/railgun.wav"); + + self->health = 400 * st.health_multiplier; + self->mass = 400; + // RAFAEL + + self->monsterinfo.weapon_sound = gi.soundindex("weapons/rg_hum.wav"); + } + // RAFAEL + + self->gib_health = -175; + + self->mins = { -32, -32, -24 }; + self->maxs = { 32, 32, 42 }; + + self->pain = gladiator_pain; + self->die = gladiator_die; + + self->monsterinfo.stand = gladiator_stand; + self->monsterinfo.walk = gladiator_walk; + self->monsterinfo.run = gladiator_run; + self->monsterinfo.dodge = nullptr; + self->monsterinfo.attack = gladiator_attack; + self->monsterinfo.melee = gladiator_melee; + self->monsterinfo.sight = gladiator_sight; + self->monsterinfo.idle = gladiator_idle; + self->monsterinfo.search = gladiator_search; + self->monsterinfo.blocked = gladiator_blocked; // PGM + self->monsterinfo.setskin = gladiator_setskin; + + gi.linkentity(self); + M_SetAnimation(self, &gladiator_move_stand); + self->monsterinfo.scale = MODEL_SCALE; + + walkmonster_start(self); +} + +// +// monster_gladb +// RAFAEL +// +/*QUAKED monster_gladb (1 .5 0) (-32 -32 -24) (32 32 64) Ambush Trigger_Spawn Sight + */ +void SP_monster_gladb(edict_t *self) +{ + SP_monster_gladiator(self); +} \ No newline at end of file diff --git a/rerelease/m_gladiator.h b/rerelease/m_gladiator.h new file mode 100644 index 0000000..729f90d --- /dev/null +++ b/rerelease/m_gladiator.h @@ -0,0 +1,101 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// G:\quake2\baseq2\models/monsters/gladiatr + +// This file generated by ModelGen - Do NOT Modify + +enum +{ + FRAME_stand1, + FRAME_stand2, + FRAME_stand3, + FRAME_stand4, + FRAME_stand5, + FRAME_stand6, + FRAME_stand7, + FRAME_walk1, + FRAME_walk2, + FRAME_walk3, + FRAME_walk4, + FRAME_walk5, + FRAME_walk6, + FRAME_walk7, + FRAME_walk8, + FRAME_walk9, + FRAME_walk10, + FRAME_walk11, + FRAME_walk12, + FRAME_walk13, + FRAME_walk14, + FRAME_walk15, + FRAME_walk16, + FRAME_run1, + FRAME_run2, + FRAME_run3, + FRAME_run4, + FRAME_run5, + FRAME_run6, + FRAME_melee1, + FRAME_melee2, + FRAME_melee3, + FRAME_melee4, + FRAME_melee5, + FRAME_melee6, + FRAME_melee7, + FRAME_melee8, + FRAME_melee9, + FRAME_melee10, + FRAME_melee11, + FRAME_melee12, + FRAME_melee13, + FRAME_melee14, + FRAME_melee15, + FRAME_melee16, + FRAME_melee17, + FRAME_attack1, + FRAME_attack2, + FRAME_attack3, + FRAME_attack4, + FRAME_attack5, + FRAME_attack6, + FRAME_attack7, + FRAME_attack8, + FRAME_attack9, + FRAME_pain1, + FRAME_pain2, + FRAME_pain3, + FRAME_pain4, + FRAME_pain5, + FRAME_pain6, + FRAME_death1, + FRAME_death2, + FRAME_death3, + FRAME_death4, + FRAME_death5, + FRAME_death6, + FRAME_death7, + FRAME_death8, + FRAME_death9, + FRAME_death10, + FRAME_death11, + FRAME_death12, + FRAME_death13, + FRAME_death14, + FRAME_death15, + FRAME_death16, + FRAME_death17, + FRAME_death18, + FRAME_death19, + FRAME_death20, + FRAME_death21, + FRAME_death22, + FRAME_painup1, + FRAME_painup2, + FRAME_painup3, + FRAME_painup4, + FRAME_painup5, + FRAME_painup6, + FRAME_painup7 +}; + +constexpr float MODEL_SCALE = 1.000000f; diff --git a/rerelease/m_guardian.cpp b/rerelease/m_guardian.cpp new file mode 100644 index 0000000..f186def --- /dev/null +++ b/rerelease/m_guardian.cpp @@ -0,0 +1,523 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +/* +============================================================================== + +GUARDIAN + +============================================================================== +*/ + +#include "g_local.h" +#include "m_guardian.h" +#include "m_flash.h" + +// +// stand +// + +mframe_t guardian_frames_stand[] = { + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand } +}; +MMOVE_T(guardian_move_stand) = { FRAME_idle1, FRAME_idle52, guardian_frames_stand, nullptr }; + +MONSTERINFO_STAND(guardian_stand) (edict_t *self) -> void +{ + M_SetAnimation(self, &guardian_move_stand); +} + +// +// walk +// + +static int sound_step; + +void guardian_footstep(edict_t *self) +{ + gi.sound(self, CHAN_BODY, sound_step, 1.f, ATTN_NORM, 0.0f); +} + +mframe_t guardian_frames_walk[] = { + { ai_walk, 8 }, + { ai_walk, 8 }, + { ai_walk, 8 }, + { ai_walk, 8 }, + { ai_walk, 8 }, + { ai_walk, 8 }, + { ai_walk, 8 }, + { ai_walk, 8, guardian_footstep }, + { ai_walk, 8 }, + { ai_walk, 8 }, + { ai_walk, 8 }, + { ai_walk, 8 }, + { ai_walk, 8 }, + { ai_walk, 8 }, + { ai_walk, 8 }, + { ai_walk, 8 }, + { ai_walk, 8 }, + { ai_walk, 8, guardian_footstep }, + { ai_walk, 8 } +}; +MMOVE_T(guardian_move_walk) = { FRAME_walk1, FRAME_walk19, guardian_frames_walk, nullptr }; + +MONSTERINFO_WALK(guardian_walk) (edict_t *self) -> void +{ + M_SetAnimation(self, &guardian_move_walk); +} + +// +// run +// + +mframe_t guardian_frames_run[] = { + { ai_run, 8 }, + { ai_run, 8 }, + { ai_run, 8 }, + { ai_run, 8 }, + { ai_run, 8 }, + { ai_run, 8 }, + { ai_run, 8 }, + { ai_run, 8, guardian_footstep }, + { ai_run, 8 }, + { ai_run, 8 }, + { ai_run, 8 }, + { ai_run, 8 }, + { ai_run, 8 }, + { ai_run, 8 }, + { ai_run, 8 }, + { ai_run, 8 }, + { ai_run, 8 }, + { ai_run, 8, guardian_footstep }, + { ai_run, 8 } +}; +MMOVE_T(guardian_move_run) = { FRAME_walk1, FRAME_walk19, guardian_frames_run, nullptr }; + +MONSTERINFO_RUN(guardian_run) (edict_t *self) -> void +{ + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + { + M_SetAnimation(self, &guardian_move_stand); + return; + } + + M_SetAnimation(self, &guardian_move_run); +} + +// +// pain +// + +mframe_t guardian_frames_pain1[] = { + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move } +}; +MMOVE_T(guardian_move_pain1) = { FRAME_pain1_1, FRAME_pain1_8, guardian_frames_pain1, guardian_run }; + +PAIN(guardian_pain) (edict_t *self, edict_t *other, float kick, int damage, const mod_t &mod) -> void +{ + if (mod.id != MOD_CHAINFIST && damage <= 10) + return; + + if (level.time < self->pain_debounce_time) + return; + + if (mod.id != MOD_CHAINFIST && damage <= 75) + if (frandom() > 0.2f) + return; + + // don't go into pain while attacking + if ((self->s.frame >= FRAME_atk1_spin1) && (self->s.frame <= FRAME_atk1_spin15)) + return; + if ((self->s.frame >= FRAME_atk2_fire1) && (self->s.frame <= FRAME_atk2_fire4)) + return; + if ((self->s.frame >= FRAME_kick_in1) && (self->s.frame <= FRAME_kick_in13)) + return; + + self->pain_debounce_time = level.time + 3_sec; + //gi.sound(self, CHAN_VOICE, sound_pain, 1, ATTN_NORM, 0); + + if (!M_ShouldReactToPain(self, mod)) + return; // no pain anims in nightmare + + M_SetAnimation(self, &guardian_move_pain1); + self->monsterinfo.weapon_sound = 0; +} + +mframe_t guardian_frames_atk1_out[] = { + { ai_charge }, + { ai_charge }, + { ai_charge } +}; +MMOVE_T(guardian_atk1_out) = { FRAME_atk1_out1, FRAME_atk1_out3, guardian_frames_atk1_out, guardian_run }; + +void guardian_atk1_finish(edict_t *self) +{ + M_SetAnimation(self, &guardian_atk1_out); + self->monsterinfo.weapon_sound = 0; +} + +static int sound_charge; +static int sound_spin_loop; + +void guardian_atk1_charge(edict_t *self) +{ + self->monsterinfo.weapon_sound = sound_spin_loop; + gi.sound(self, CHAN_WEAPON, sound_charge, 1.f, ATTN_NORM, 0.f); +} + +void guardian_fire_blaster(edict_t *self) +{ + vec3_t forward, right, target; + vec3_t start; + monster_muzzleflash_id_t id = MZ2_GUARDIAN_BLASTER; + + AngleVectors(self->s.angles, forward, right, nullptr); + start = M_ProjectFlashSource(self, monster_flash_offset[id], forward, right); + target = self->enemy->s.origin; + target[2] += self->enemy->viewheight; + for (int i = 0; i < 3; i++) + target[i] += crandom_open() * 5.f; + forward = target - start; + forward.normalize(); + + monster_fire_blaster(self, start, forward, 2, 1000, id, (self->s.frame % 4) ? EF_NONE : EF_HYPERBLASTER); + + if (self->enemy && self->enemy->health > 0 && + self->s.frame == FRAME_atk1_spin12 && self->timestamp > level.time && visible(self, self->enemy)) + self->monsterinfo.nextframe = FRAME_atk1_spin5; +} + +mframe_t guardian_frames_atk1_spin[] = { + { ai_charge, 0, guardian_atk1_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge, 0, guardian_fire_blaster }, + { ai_charge, 0, guardian_fire_blaster }, + { ai_charge, 0, guardian_fire_blaster }, + { ai_charge, 0, guardian_fire_blaster }, + { ai_charge, 0, guardian_fire_blaster }, + { ai_charge, 0, guardian_fire_blaster }, + { ai_charge, 0, guardian_fire_blaster }, + { ai_charge, 0, guardian_fire_blaster }, + { ai_charge, 0 }, + { ai_charge, 0 }, + { ai_charge, 0 } +}; +MMOVE_T(guardian_move_atk1_spin) = { FRAME_atk1_spin1, FRAME_atk1_spin15, guardian_frames_atk1_spin, guardian_atk1_finish }; + +void guardian_atk1(edict_t *self) +{ + M_SetAnimation(self, &guardian_move_atk1_spin); + self->timestamp = level.time + 650_ms + random_time(1.5_sec); +} + +mframe_t guardian_frames_atk1_in[] = { + { ai_charge }, + { ai_charge }, + { ai_charge } +}; +MMOVE_T(guardian_move_atk1_in) = { FRAME_atk1_in1, FRAME_atk1_in3, guardian_frames_atk1_in, guardian_atk1 }; + +mframe_t guardian_frames_atk2_out[] = { + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge, 0, guardian_footstep }, + { ai_charge }, + { ai_charge } +}; +MMOVE_T(guardian_move_atk2_out) = { FRAME_atk2_out1, FRAME_atk2_out7, guardian_frames_atk2_out, guardian_run }; + +void guardian_atk2_out(edict_t *self) +{ + M_SetAnimation(self, &guardian_move_atk2_out); +} + +static int sound_laser; + +constexpr vec3_t laser_positions[] = { + { 125.0f, -70.f, 60.f }, + { 112.0f, -62.f, 60.f } +}; + +PRETHINK(guardian_fire_update) (edict_t *laser) -> void +{ + edict_t *self = laser->owner; + + vec3_t forward, right, target; + vec3_t start; + + AngleVectors(self->s.angles, forward, right, nullptr); + start = M_ProjectFlashSource(self, laser_positions[1 - (self->s.frame & 1)], forward, right); + target = self->enemy->s.origin + self->enemy->mins; + for (int i = 0; i < 3; i++) + target[i] += frandom() * self->enemy->size[i]; + forward = target - start; + forward.normalize(); + + laser->s.origin = start; + laser->movedir = forward; + gi.linkentity(laser); + dabeam_update(laser, false); +} + +void guardian_laser_fire(edict_t *self) +{ + gi.sound(self, CHAN_WEAPON, sound_laser, 1.f, ATTN_NORM, 0.f); + monster_fire_dabeam(self, 25, self->s.frame & 1, guardian_fire_update); +} + +mframe_t guardian_frames_atk2_fire[] = { + { ai_charge, 0, guardian_laser_fire }, + { ai_charge, 0, guardian_laser_fire }, + { ai_charge, 0, guardian_laser_fire }, + { ai_charge, 0, guardian_laser_fire } +}; +MMOVE_T(guardian_move_atk2_fire) = { FRAME_atk2_fire1, FRAME_atk2_fire4, guardian_frames_atk2_fire, guardian_atk2_out }; + +void guardian_atk2(edict_t *self) +{ + M_SetAnimation(self, &guardian_move_atk2_fire); +} + +mframe_t guardian_frames_atk2_in[] = { + { ai_charge, 0, guardian_footstep }, + { ai_charge }, + { ai_charge }, + { ai_charge, 0, guardian_footstep }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge, 0, guardian_footstep }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge } +}; +MMOVE_T(guardian_move_atk2_in) = { FRAME_atk2_in1, FRAME_atk2_in12, guardian_frames_atk2_in, guardian_atk2 }; + +void guardian_kick(edict_t *self) +{ + if (!fire_hit(self, { MELEE_DISTANCE, 0, -80 }, 85, 700)) + self->monsterinfo.melee_debounce_time = level.time + 1000_ms; +} + +mframe_t guardian_frames_kick[] = { + { ai_charge }, + { ai_charge, 0, guardian_footstep }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge, 0, guardian_kick }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge, 0, guardian_footstep }, + { ai_charge }, + { ai_charge } +}; +MMOVE_T(guardian_move_kick) = { FRAME_kick_in1, FRAME_kick_in13, guardian_frames_kick, guardian_run }; + +MONSTERINFO_ATTACK(guardian_attack) (edict_t *self) -> void +{ + if (!self->enemy || !self->enemy->inuse) + return; + + float r = range_to(self, self->enemy); + + if (r > RANGE_NEAR) + M_SetAnimation(self, &guardian_move_atk2_in); + else if (self->monsterinfo.melee_debounce_time < level.time && r < 120.f) + M_SetAnimation(self, &guardian_move_kick); + else + M_SetAnimation(self, &guardian_move_atk1_in); +} + +// +// death +// + +void guardian_explode(edict_t *self) +{ + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_EXPLOSION1_BIG); + gi.WritePosition((self->s.origin + self->mins) + vec3_t { frandom() * self->size[0], frandom() * self->size[1], frandom() * self->size[2] }); + gi.multicast(self->s.origin, MULTICAST_ALL, false); +} + +constexpr const char *gibs[] = { + "models/monsters/guardian/gib1.md2", + "models/monsters/guardian/gib2.md2", + "models/monsters/guardian/gib3.md2", + "models/monsters/guardian/gib4.md2", + "models/monsters/guardian/gib5.md2", + "models/monsters/guardian/gib6.md2", + "models/monsters/guardian/gib7.md2" +}; + +void guardian_dead(edict_t *self) +{ + for (int i = 0; i < 3; i++) + guardian_explode(self); + + ThrowGibs(self, 125, { + { 2, "models/objects/gibs/sm_meat/tris.md2" }, + { 4, "models/objects/gibs/sm_metal/tris.md2", GIB_METALLIC }, + { 2, gibs[0], GIB_METALLIC }, + { 2, gibs[1], GIB_METALLIC }, + { 2, gibs[2], GIB_METALLIC }, + { 2, gibs[3], GIB_METALLIC }, + { 2, gibs[4], GIB_METALLIC }, + { 2, gibs[5], GIB_METALLIC }, + { gibs[6], GIB_METALLIC | GIB_HEAD } + }); +} + +mframe_t guardian_frames_death1[FRAME_death26 - FRAME_death1 + 1] = { + { ai_move, 0, BossExplode }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move } +}; +MMOVE_T(guardian_move_death) = { FRAME_death1, FRAME_death26, guardian_frames_death1, guardian_dead }; + +DIE(guardian_die) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void +{ + // regular death + //gi.sound(self, CHAN_VOICE, sound_die, 1, ATTN_NORM, 0); + self->monsterinfo.weapon_sound = 0; + self->deadflag = true; + self->takedamage = true; + + M_SetAnimation(self, &guardian_move_death); +} + +// +// monster_tank +// + +/*QUAKED monster_guardian (1 .5 0) (-96 -96 -66) (96 96 62) Ambush Trigger_Spawn Sight + */ +void SP_monster_guardian(edict_t *self) +{ + if ( !M_AllowSpawn( self ) ) { + G_FreeEdict( self ); + return; + } + + sound_step = gi.soundindex("zortemp/step.wav"); + sound_charge = gi.soundindex("weapons/hyprbu1a.wav"); + sound_spin_loop = gi.soundindex("weapons/hyprbl1a.wav"); + sound_laser = gi.soundindex("weapons/laser2.wav"); + + for (auto &gib : gibs) + gi.modelindex(gib); + + self->s.modelindex = gi.modelindex("models/monsters/guardian/tris.md2"); + self->mins = { -96, -96, -66 }; + self->maxs = { 96, 96, 62 }; + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + + self->health = 2500 * st.health_multiplier; + self->gib_health = -200; + + self->monsterinfo.scale = MODEL_SCALE; + + self->mass = 850; + + self->pain = guardian_pain; + self->die = guardian_die; + self->monsterinfo.stand = guardian_stand; + self->monsterinfo.walk = guardian_walk; + self->monsterinfo.run = guardian_run; + self->monsterinfo.attack = guardian_attack; + + gi.linkentity(self); + + M_SetAnimation(self, &guardian_move_stand); + + walkmonster_start(self); +} diff --git a/rerelease/m_guardian.h b/rerelease/m_guardian.h new file mode 100644 index 0000000..2414901 --- /dev/null +++ b/rerelease/m_guardian.h @@ -0,0 +1,225 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// G:\quake2\baseq2\models/monsters/guardian + +// This file generated by ModelGen - Do NOT Modify + +enum { + FRAME_sleep1, + FRAME_sleep2, + FRAME_sleep3, + FRAME_sleep4, + FRAME_sleep5, + FRAME_sleep6, + FRAME_sleep7, + FRAME_sleep8, + FRAME_sleep9, + FRAME_sleep10, + FRAME_sleep11, + FRAME_sleep12, + FRAME_sleep13, + FRAME_sleep14, + FRAME_death1, + FRAME_death2, + FRAME_death3, + FRAME_death4, + FRAME_death5, + FRAME_death6, + FRAME_death7, + FRAME_death8, + FRAME_death9, + FRAME_death10, + FRAME_death11, + FRAME_death12, + FRAME_death13, + FRAME_death14, + FRAME_death15, + FRAME_death16, + FRAME_death17, + FRAME_death18, + FRAME_death19, + FRAME_death20, + FRAME_death21, + FRAME_death22, + FRAME_death23, + FRAME_death24, + FRAME_death25, + FRAME_death26, + FRAME_atk1_out1, + FRAME_atk1_out2, + FRAME_atk1_out3, + FRAME_atk2_out1, + FRAME_atk2_out2, + FRAME_atk2_out3, + FRAME_atk2_out4, + FRAME_atk2_out5, + FRAME_atk2_out6, + FRAME_atk2_out7, + FRAME_kick_out1, + FRAME_kick_out2, + FRAME_kick_out3, + FRAME_kick_out4, + FRAME_kick_out5, + FRAME_kick_out6, + FRAME_kick_out7, + FRAME_kick_out8, + FRAME_kick_out9, + FRAME_kick_out10, + FRAME_kick_out11, + FRAME_kick_out12, + FRAME_pain1_1, + FRAME_pain1_2, + FRAME_pain1_3, + FRAME_pain1_4, + FRAME_pain1_5, + FRAME_pain1_6, + FRAME_pain1_7, + FRAME_pain1_8, + FRAME_idle1, + FRAME_idle2, + FRAME_idle3, + FRAME_idle4, + FRAME_idle5, + FRAME_idle6, + FRAME_idle7, + FRAME_idle8, + FRAME_idle9, + FRAME_idle10, + FRAME_idle11, + FRAME_idle12, + FRAME_idle13, + FRAME_idle14, + FRAME_idle15, + FRAME_idle16, + FRAME_idle17, + FRAME_idle18, + FRAME_idle19, + FRAME_idle20, + FRAME_idle21, + FRAME_idle22, + FRAME_idle23, + FRAME_idle24, + FRAME_idle25, + FRAME_idle26, + FRAME_idle27, + FRAME_idle28, + FRAME_idle29, + FRAME_idle30, + FRAME_idle31, + FRAME_idle32, + FRAME_idle33, + FRAME_idle34, + FRAME_idle35, + FRAME_idle36, + FRAME_idle37, + FRAME_idle38, + FRAME_idle39, + FRAME_idle40, + FRAME_idle41, + FRAME_idle42, + FRAME_idle43, + FRAME_idle44, + FRAME_idle45, + FRAME_idle46, + FRAME_idle47, + FRAME_idle48, + FRAME_idle49, + FRAME_idle50, + FRAME_idle51, + FRAME_idle52, + FRAME_atk1_in1, + FRAME_atk1_in2, + FRAME_atk1_in3, + FRAME_kick_in1, + FRAME_kick_in2, + FRAME_kick_in3, + FRAME_kick_in4, + FRAME_kick_in5, + FRAME_kick_in6, + FRAME_kick_in7, + FRAME_kick_in8, + FRAME_kick_in9, + FRAME_kick_in10, + FRAME_kick_in11, + FRAME_kick_in12, + FRAME_kick_in13, + FRAME_walk1, + FRAME_walk2, + FRAME_walk3, + FRAME_walk4, + FRAME_walk5, + FRAME_walk6, + FRAME_walk7, + FRAME_walk8, + FRAME_walk9, + FRAME_walk10, + FRAME_walk11, + FRAME_walk12, + FRAME_walk13, + FRAME_walk14, + FRAME_walk15, + FRAME_walk16, + FRAME_walk17, + FRAME_walk18, + FRAME_walk19, + FRAME_wake1, + FRAME_wake2, + FRAME_wake3, + FRAME_wake4, + FRAME_wake5, + FRAME_atk1_spin1, + FRAME_atk1_spin2, + FRAME_atk1_spin3, + FRAME_atk1_spin4, + FRAME_atk1_spin5, + FRAME_atk1_spin6, + FRAME_atk1_spin7, + FRAME_atk1_spin8, + FRAME_atk1_spin9, + FRAME_atk1_spin10, + FRAME_atk1_spin11, + FRAME_atk1_spin12, + FRAME_atk1_spin13, + FRAME_atk1_spin14, + FRAME_atk1_spin15, + FRAME_atk2_fire1, + FRAME_atk2_fire2, + FRAME_atk2_fire3, + FRAME_atk2_fire4, + FRAME_turnl_1, + FRAME_turnl_2, + FRAME_turnl_3, + FRAME_turnl_4, + FRAME_turnl_5, + FRAME_turnl_6, + FRAME_turnl_7, + FRAME_turnl_8, + FRAME_turnl_9, + FRAME_turnl_10, + FRAME_turnl_11, + FRAME_turnr_1, + FRAME_turnr_2, + FRAME_turnr_3, + FRAME_turnr_4, + FRAME_turnr_5, + FRAME_turnr_6, + FRAME_turnr_7, + FRAME_turnr_8, + FRAME_turnr_9, + FRAME_turnr_10, + FRAME_turnr_11, + FRAME_atk2_in1, + FRAME_atk2_in2, + FRAME_atk2_in3, + FRAME_atk2_in4, + FRAME_atk2_in5, + FRAME_atk2_in6, + FRAME_atk2_in7, + FRAME_atk2_in8, + FRAME_atk2_in9, + FRAME_atk2_in10, + FRAME_atk2_in11, + FRAME_atk2_in12 +}; + +constexpr float MODEL_SCALE = 1.000000; \ No newline at end of file diff --git a/rerelease/m_guncmdr.cpp b/rerelease/m_guncmdr.cpp new file mode 100644 index 0000000..7f97d77 --- /dev/null +++ b/rerelease/m_guncmdr.cpp @@ -0,0 +1,1473 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +/* +============================================================================== + +GUNNER + +============================================================================== +*/ + +#include "g_local.h" +#include "m_gunner.h" +#include "m_flash.h" + +constexpr spawnflags_t SPAWNFLAG_GUNCMDR_NOJUMPING = 8_spawnflag; + +static int sound_pain; +static int sound_pain2; +static int sound_death; +static int sound_idle; +static int sound_open; +static int sound_search; +static int sound_sight; + +void guncmdr_idlesound(edict_t *self) +{ + gi.sound(self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0); +} + +MONSTERINFO_SIGHT(guncmdr_sight) (edict_t *self, edict_t *other) -> void +{ + gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); +} + +MONSTERINFO_SEARCH(guncmdr_search) (edict_t *self) -> void +{ + gi.sound(self, CHAN_VOICE, sound_search, 1, ATTN_NORM, 0); +} + +void GunnerGrenade(edict_t *self); +void GunnerFire(edict_t *self); +void guncmdr_fire_chain(edict_t *self); +void guncmdr_refire_chain(edict_t *self); + +void guncmdr_stand(edict_t *self); + +mframe_t guncmdr_frames_fidget[] = { + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand, 0, guncmdr_idlesound }, + { ai_stand }, + { ai_stand }, + + { ai_stand }, + { ai_stand, 0, guncmdr_idlesound }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand } +}; +MMOVE_T(guncmdr_move_fidget) = { FRAME_c_stand201, FRAME_c_stand254, guncmdr_frames_fidget, guncmdr_stand }; + +void guncmdr_fidget(edict_t *self) +{ + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + return; + else if (self->enemy) + return; + if (frandom() <= 0.05f) + M_SetAnimation(self, &guncmdr_move_fidget); +} + +mframe_t guncmdr_frames_stand[] = { + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand, 0, guncmdr_fidget }, + + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand, 0, guncmdr_fidget }, + + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand, 0, guncmdr_fidget }, + + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand, 0, guncmdr_fidget } +}; +MMOVE_T(guncmdr_move_stand) = { FRAME_c_stand101, FRAME_c_stand140, guncmdr_frames_stand, nullptr }; + +MONSTERINFO_STAND(guncmdr_stand) (edict_t *self) -> void +{ + M_SetAnimation(self, &guncmdr_move_stand); +} + +mframe_t guncmdr_frames_walk[] = { + { ai_walk, 1.5f, monster_footstep }, + { ai_walk, 2.5f }, + { ai_walk, 3.0f }, + { ai_walk, 2.5f }, + { ai_walk, 2.3f }, + { ai_walk, 3.0f }, + { ai_walk, 2.8f, monster_footstep }, + { ai_walk, 3.6f }, + { ai_walk, 2.8f }, + { ai_walk, 2.5f }, + + { ai_walk, 2.3f }, + { ai_walk, 4.3f }, + { ai_walk, 3.0f, monster_footstep }, + { ai_walk, 1.5f }, + { ai_walk, 2.5f }, + { ai_walk, 3.3f }, + { ai_walk, 2.8f }, + { ai_walk, 3.0f }, + { ai_walk, 2.0f, monster_footstep }, + { ai_walk, 2.0f }, + + { ai_walk, 3.3f }, + { ai_walk, 3.6f }, + { ai_walk, 3.4f }, + { ai_walk, 2.8f }, +}; +MMOVE_T(guncmdr_move_walk) = { FRAME_c_walk101, FRAME_c_walk124, guncmdr_frames_walk, nullptr }; + +MONSTERINFO_WALK(guncmdr_walk) (edict_t *self) -> void +{ + M_SetAnimation(self, &guncmdr_move_walk); +} + +mframe_t guncmdr_frames_run[] = { + { ai_run, 15.f, monster_done_dodge }, + { ai_run, 16.f, monster_footstep }, + { ai_run, 20.f }, + { ai_run, 18.f }, + { ai_run, 24.f, monster_footstep }, + { ai_run, 13.5f } +}; + +MMOVE_T(guncmdr_move_run) = { FRAME_c_run101, FRAME_c_run106, guncmdr_frames_run, nullptr }; + +MONSTERINFO_RUN(guncmdr_run) (edict_t *self) -> void +{ + monster_done_dodge(self); + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + M_SetAnimation(self, &guncmdr_move_stand); + else + M_SetAnimation(self, &guncmdr_move_run); +} + +// standing pains + +mframe_t guncmdr_frames_pain1[] = { + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, +}; +MMOVE_T(guncmdr_move_pain1) = { FRAME_c_pain101, FRAME_c_pain104, guncmdr_frames_pain1, guncmdr_run }; + +mframe_t guncmdr_frames_pain2[] = { + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move } +}; +MMOVE_T(guncmdr_move_pain2) = { FRAME_c_pain201, FRAME_c_pain204, guncmdr_frames_pain2, guncmdr_run }; + +mframe_t guncmdr_frames_pain3[] = { + { ai_move, -3.0f }, + { ai_move }, + { ai_move }, + { ai_move }, +}; +MMOVE_T(guncmdr_move_pain3) = { FRAME_c_pain301, FRAME_c_pain304, guncmdr_frames_pain3, guncmdr_run }; + +mframe_t guncmdr_frames_pain4[] = { + { ai_move, -17.1f }, + { ai_move, -3.2f }, + { ai_move, 0.9f }, + { ai_move, 3.6f }, + { ai_move, -2.6f }, + { ai_move, 1.0f }, + { ai_move, -5.1f }, + { ai_move, -6.7f }, + { ai_move, -8.8f }, + { ai_move }, + + { ai_move }, + { ai_move, -2.1f }, + { ai_move, -2.3f }, + { ai_move, -2.5f }, + { ai_move } +}; +MMOVE_T(guncmdr_move_pain4) = { FRAME_c_pain401, FRAME_c_pain415, guncmdr_frames_pain4, guncmdr_run }; + +void guncmdr_dead(edict_t *); + +mframe_t guncmdr_frames_death1[] = { + { ai_move }, + { ai_move }, + { ai_move, 4.0f }, // scoot + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move } +}; +MMOVE_T(guncmdr_move_death1) = { FRAME_c_death101, FRAME_c_death118, guncmdr_frames_death1, guncmdr_dead }; + +void guncmdr_pain5_to_death1(edict_t *self) +{ + if (self->health < 0) + M_SetAnimation(self, &guncmdr_move_death1, false); +} + +mframe_t guncmdr_frames_death2[] = { + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move } +}; +MMOVE_T(guncmdr_move_death2) = { FRAME_c_death201, FRAME_c_death204, guncmdr_frames_death2, guncmdr_dead }; + +void guncmdr_pain5_to_death2(edict_t *self) +{ + if (self->health < 0 && brandom()) + M_SetAnimation(self, &guncmdr_move_death2, false); +} + +mframe_t guncmdr_frames_pain5[] = { + { ai_move, -29.f }, + { ai_move, -5.f }, + { ai_move, -5.f }, + { ai_move, -3.f }, + { ai_move }, + { ai_move, 0, guncmdr_pain5_to_death2 }, + { ai_move, 9.f }, + { ai_move, 3.f }, + { ai_move, 0, guncmdr_pain5_to_death1 }, + { ai_move }, + + { ai_move }, + { ai_move, -4.6f }, + { ai_move, -4.8f }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move, 9.5f }, + { ai_move, 3.4f }, + { ai_move }, + { ai_move }, + + { ai_move, -2.4f }, + { ai_move, -9.0f }, + { ai_move, -5.0f }, + { ai_move, -3.6f }, +}; +MMOVE_T(guncmdr_move_pain5) = { FRAME_c_pain501, FRAME_c_pain524, guncmdr_frames_pain5, guncmdr_run }; + +void guncmdr_dead(edict_t *self) +{ + self->mins = vec3_t { -16, -16, -24 } * self->s.scale; + self->maxs = vec3_t { 16, 16, -8 } * self->s.scale; + monster_dead(self); +} + +static void guncmdr_shrink(edict_t *self) +{ + self->maxs[2] = -4 * self->s.scale; + self->svflags |= SVF_DEADMONSTER; + gi.linkentity(self); +} + +mframe_t guncmdr_frames_death6[] = { + { ai_move, 0, guncmdr_shrink }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move } +}; +MMOVE_T(guncmdr_move_death6) = { FRAME_c_death601, FRAME_c_death614, guncmdr_frames_death6, guncmdr_dead }; + +static void guncmdr_pain6_to_death6(edict_t *self) +{ + if (self->health < 0) + M_SetAnimation(self, &guncmdr_move_death6, false); +} + +mframe_t guncmdr_frames_pain6[] = { + { ai_move, 16.f }, + { ai_move, 16.f }, + { ai_move, 12.f }, + { ai_move, 5.5f, monster_duck_down }, + { ai_move, 3.0f }, + { ai_move, -4.7f }, + { ai_move, -6.0f, guncmdr_pain6_to_death6 }, + { ai_move }, + { ai_move, 1.8f }, + { ai_move, 0.7f }, + + { ai_move }, + { ai_move, -2.1f }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + + { ai_move }, + { ai_move, -6.1f }, + { ai_move, 10.5f }, + { ai_move, 4.3f }, + { ai_move, 4.7f, monster_duck_up }, + { ai_move, 1.4f }, + { ai_move }, + { ai_move, -3.2f }, + { ai_move, 2.3f }, + { ai_move, -4.4f }, + + { ai_move, -4.4f }, + { ai_move, -2.4f } +}; +MMOVE_T(guncmdr_move_pain6) = { FRAME_c_pain601, FRAME_c_pain632, guncmdr_frames_pain6, guncmdr_run }; + +mframe_t guncmdr_frames_pain7[] = { + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move } +}; +MMOVE_T(guncmdr_move_pain7) = { FRAME_c_pain701, FRAME_c_pain714, guncmdr_frames_pain7, guncmdr_run }; + +extern const mmove_t guncmdr_move_jump; +extern const mmove_t guncmdr_move_jump2; +extern const mmove_t guncmdr_move_duck_attack; + +bool guncmdr_sidestep(edict_t *self); + +PAIN(guncmdr_pain) (edict_t *self, edict_t *other, float kick, int damage, const mod_t &mod) -> void +{ + monster_done_dodge(self); + + if (self->monsterinfo.active_move == &guncmdr_move_jump || + self->monsterinfo.active_move == &guncmdr_move_jump2 || + self->monsterinfo.active_move == &guncmdr_move_duck_attack) + return; + + if (level.time < self->pain_debounce_time) + { + if (frandom() < 0.3) + self->monsterinfo.dodge(self, other, FRAME_TIME_S, nullptr, false); + + return; + } + + self->pain_debounce_time = level.time + 3_sec; + + if (brandom()) + gi.sound(self, CHAN_VOICE, sound_pain, 1, ATTN_NORM, 0); + else + gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0); + + if (!M_ShouldReactToPain(self, mod)) + { + if (frandom() < 0.3) + self->monsterinfo.dodge(self, other, FRAME_TIME_S, nullptr, false); + + return; // no pain anims in nightmare + } + + vec3_t forward; + AngleVectors(self->s.angles, forward, nullptr, nullptr); + + vec3_t dif = (other->s.origin - self->s.origin); + dif.z = 0; + dif.normalize(); + + // small pain + if (damage < 35) + { + int r = irandom(0, 4); + + if (r == 0) + M_SetAnimation(self, &guncmdr_move_pain3); + else if (r == 1) + M_SetAnimation(self, &guncmdr_move_pain2); + else if (r == 2) + M_SetAnimation(self, &guncmdr_move_pain1); + else + M_SetAnimation(self, &guncmdr_move_pain7); + } + // large pain from behind (aka Paril) + else if (dif.dot(forward) < -0.40f) + { + M_SetAnimation(self, &guncmdr_move_pain6); + + self->pain_debounce_time += 1.5_sec; + } + else + { + if (brandom()) + M_SetAnimation(self, &guncmdr_move_pain4); + else + M_SetAnimation(self, &guncmdr_move_pain5); + + self->pain_debounce_time += 1.5_sec; + } + + self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING; + + // PMM - clear duck flag + if (self->monsterinfo.aiflags & AI_DUCKED) + monster_duck_up(self); +} + +MONSTERINFO_SETSKIN(guncmdr_setskin) (edict_t *self) -> void +{ + if (self->health < (self->max_health / 2)) + self->s.skinnum |= 1; + else + self->s.skinnum &= ~1; +} + +mframe_t guncmdr_frames_death3[] = { + { ai_move, 20.f }, + { ai_move, 10.f }, + { ai_move, 10.f, [](edict_t *self) { monster_footstep(self); guncmdr_shrink(self); } }, + { ai_move, 0.f, monster_footstep }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + + { ai_move } +}; +MMOVE_T(guncmdr_move_death3) = { FRAME_c_death301, FRAME_c_death321, guncmdr_frames_death3, guncmdr_dead }; + +mframe_t guncmdr_frames_death7[] = { + { ai_move, 30.f }, + { ai_move, 20.f }, + { ai_move, 16.f, [](edict_t *self) { monster_footstep(self); guncmdr_shrink(self); } }, + { ai_move, 5.f, monster_footstep }, + { ai_move, -6.f }, + { ai_move, -7.f, monster_footstep }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move, 0.f, monster_footstep }, + { ai_move }, + { ai_move }, + { ai_move, 0.f, monster_footstep }, + { ai_move }, + { ai_move }, +}; +MMOVE_T(guncmdr_move_death7) = { FRAME_c_death701, FRAME_c_death730, guncmdr_frames_death7, guncmdr_dead }; + +mframe_t guncmdr_frames_death4[] = { + { ai_move, -20.f }, + { ai_move, -16.f }, + { ai_move, -26.f, [](edict_t *self) { monster_footstep(self); guncmdr_shrink(self); } }, + { ai_move, 0.f, monster_footstep }, + { ai_move, -12.f }, + { ai_move, 16.f }, + { ai_move, 9.2f }, + { ai_move }, + { ai_move }, + { ai_move }, + + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move } +}; +MMOVE_T(guncmdr_move_death4) = { FRAME_c_death401, FRAME_c_death436, guncmdr_frames_death4, guncmdr_dead }; + +mframe_t guncmdr_frames_death5[] = { + { ai_move, -14.f }, + { ai_move, -2.7f }, + { ai_move, -2.5f }, + { ai_move, -4.6f, monster_footstep }, + { ai_move, -4.0f, monster_footstep }, + { ai_move, -1.5f }, + { ai_move, 2.3f }, + { ai_move, 2.5f }, + { ai_move }, + { ai_move }, + + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move, 3.5f }, + { ai_move, 12.9f, monster_footstep }, + { ai_move, 3.8f }, + { ai_move }, + { ai_move }, + { ai_move }, + + { ai_move, -2.1f }, + { ai_move, -1.3f }, + { ai_move }, + { ai_move }, + { ai_move, 3.4f }, + { ai_move, 5.7f }, + { ai_move, 11.2f }, + { ai_move, 0, monster_footstep } +}; +MMOVE_T(guncmdr_move_death5) = { FRAME_c_death501, FRAME_c_death528, guncmdr_frames_death5, guncmdr_dead }; + +DIE(guncmdr_die) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void +{ + // check for gib + if (M_CheckGib(self, mod)) + { + gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0); + + const char *head_gib = (self->monsterinfo.active_move != &guncmdr_move_death5) ? "models/objects/gibs/sm_meat/tris.md2" : "models/monsters/gunner/gibs/head.md2"; + + self->s.skinnum /= 2; + + ThrowGibs(self, damage, { + { 2, "models/objects/gibs/bone/tris.md2" }, + { 2, "models/objects/gibs/sm_meat/tris.md2" }, + { 1, "models/objects/gibs/gear/tris.md2" }, + { "models/monsters/gunner/gibs/chest.md2", GIB_SKINNED }, + { "models/monsters/gunner/gibs/garm.md2", GIB_SKINNED | GIB_UPRIGHT }, + { "models/monsters/gunner/gibs/gun.md2", GIB_SKINNED | GIB_UPRIGHT }, + { "models/monsters/gunner/gibs/foot.md2", GIB_SKINNED }, + { head_gib, GIB_SKINNED | GIB_HEAD } + }); + self->deadflag = true; + return; + } + + if (self->deadflag) + return; + + // regular death + gi.sound(self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0); + self->deadflag = true; + self->takedamage = true; + + // these animations cleanly transitions to death, so just keep going + if (self->monsterinfo.active_move == &guncmdr_move_pain5 && + self->s.frame < FRAME_c_pain508) + return; + else if (self->monsterinfo.active_move == &guncmdr_move_pain6 && + self->s.frame < FRAME_c_pain607) + return; + + vec3_t forward; + AngleVectors(self->s.angles, forward, nullptr, nullptr); + + vec3_t dif = (inflictor->s.origin - self->s.origin); + dif.z = 0; + dif.normalize(); + + // off with da head + if (fabsf((self->s.origin[2] + self->viewheight) - point[2]) <= 4 && + self->velocity.z < 65.f) + { + M_SetAnimation(self, &guncmdr_move_death5); + + edict_t *head = ThrowGib(self, "models/monsters/gunner/gibs/head.md2", damage, GIB_NONE, self->s.scale); + + if (head) + { + head->s.angles = self->s.angles; + head->s.origin = self->s.origin + vec3_t{0, 0, 24.f}; + vec3_t headDir = (self->s.origin - inflictor->s.origin); + head->velocity = headDir / headDir.length() * 100.0f; + head->velocity[2] = 200.0f; + head->avelocity *= 0.15f; + gi.linkentity(head); + } + } + // damage came from behind; use backwards death + else if (dif.dot(forward) < -0.40f) + { + int r = irandom(0, self->monsterinfo.active_move == &guncmdr_move_pain6 ? 2 : 3); + + if (r == 0) + M_SetAnimation(self, &guncmdr_move_death3); + else if (r == 1) + M_SetAnimation(self, &guncmdr_move_death7); + else if (r == 2) + M_SetAnimation(self, &guncmdr_move_pain6); + } + else + { + int r = irandom(0, self->monsterinfo.active_move == &guncmdr_move_pain5 ? 1 : 2); + + if (r == 0) + M_SetAnimation(self, &guncmdr_move_death4); + else + M_SetAnimation(self, &guncmdr_move_pain5); + } +} + +void guncmdr_opengun(edict_t *self) +{ + gi.sound(self, CHAN_VOICE, sound_open, 1, ATTN_IDLE, 0); +} + +void GunnerCmdrFire(edict_t *self) +{ + vec3_t start; + vec3_t forward, right; + vec3_t aim; + monster_muzzleflash_id_t flash_number; + + if (!self->enemy || !self->enemy->inuse) // PGM + return; // PGM + + if (self->s.frame >= FRAME_c_attack401 && self->s.frame <= FRAME_c_attack505) + flash_number = MZ2_GUNCMDR_CHAINGUN_2; + else + flash_number = MZ2_GUNCMDR_CHAINGUN_1; + + AngleVectors(self->s.angles, forward, right, nullptr); + start = M_ProjectFlashSource(self, monster_flash_offset[flash_number], forward, right); + PredictAim(self, self->enemy, start, 800, false, frandom() * 0.3f, &aim, nullptr); + for (int i = 0; i < 3; i++) + aim[i] += crandom_open() * 0.025f; + monster_fire_flechette(self, start, aim, 4, 800, flash_number); +} + +mframe_t guncmdr_frames_attack_chain[] = { + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge, 0, guncmdr_opengun }, + { ai_charge } +}; +MMOVE_T(guncmdr_move_attack_chain) = { FRAME_c_attack101, FRAME_c_attack106, guncmdr_frames_attack_chain, guncmdr_fire_chain }; + +mframe_t guncmdr_frames_fire_chain[] = { + { ai_charge, 0, GunnerCmdrFire }, + { ai_charge, 0, GunnerCmdrFire }, + { ai_charge, 0, GunnerCmdrFire }, + { ai_charge, 0, GunnerCmdrFire }, + { ai_charge, 0, GunnerCmdrFire }, + { ai_charge, 0, GunnerCmdrFire } +}; +MMOVE_T(guncmdr_move_fire_chain) = { FRAME_c_attack107, FRAME_c_attack112, guncmdr_frames_fire_chain, guncmdr_refire_chain }; + +mframe_t guncmdr_frames_fire_chain_run[] = { + { ai_charge, 15.f, GunnerCmdrFire }, + { ai_charge, 16.f, GunnerCmdrFire }, + { ai_charge, 20.f, GunnerCmdrFire }, + { ai_charge, 18.f, GunnerCmdrFire }, + { ai_charge, 24.f, GunnerCmdrFire }, + { ai_charge, 13.5f, GunnerCmdrFire } +}; +MMOVE_T(guncmdr_move_fire_chain_run) = { FRAME_c_run201, FRAME_c_run206, guncmdr_frames_fire_chain_run, guncmdr_refire_chain }; + +mframe_t guncmdr_frames_fire_chain_dodge_right[] = { + { ai_charge, 5.1f * 2.0f, GunnerCmdrFire }, + { ai_charge, 9.0f * 2.0f, GunnerCmdrFire }, + { ai_charge, 3.5f * 2.0f, GunnerCmdrFire }, + { ai_charge, 3.6f * 2.0f, GunnerCmdrFire }, + { ai_charge, -1.0f * 2.0f, GunnerCmdrFire } +}; +MMOVE_T(guncmdr_move_fire_chain_dodge_right) = { FRAME_c_attack401, FRAME_c_attack405, guncmdr_frames_fire_chain_dodge_right, guncmdr_refire_chain }; + +mframe_t guncmdr_frames_fire_chain_dodge_left[] = { + { ai_charge, 5.1f * 2.0f, GunnerCmdrFire }, + { ai_charge, 9.0f * 2.0f, GunnerCmdrFire }, + { ai_charge, 3.5f * 2.0f, GunnerCmdrFire }, + { ai_charge, 3.6f * 2.0f, GunnerCmdrFire }, + { ai_charge, -1.0f * 2.0f, GunnerCmdrFire } +}; +MMOVE_T(guncmdr_move_fire_chain_dodge_left) = { FRAME_c_attack501, FRAME_c_attack505, guncmdr_frames_fire_chain_dodge_left, guncmdr_refire_chain }; + +mframe_t guncmdr_frames_endfire_chain[] = { + { ai_charge }, + { ai_charge }, + { ai_charge, 0, guncmdr_opengun }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge } +}; +MMOVE_T(guncmdr_move_endfire_chain) = { FRAME_c_attack118, FRAME_c_attack124, guncmdr_frames_endfire_chain, guncmdr_run }; + +constexpr float MORTAR_SPEED = 850.f; +constexpr float GRENADE_SPEED = 600.f; + +void GunnerCmdrGrenade(edict_t *self) +{ + vec3_t start; + vec3_t forward, right, up; + vec3_t aim; + monster_muzzleflash_id_t flash_number; + float spread; + float pitch = 0; + // PMM + vec3_t target; + bool blindfire = false; + + if (!self->enemy || !self->enemy->inuse) // PGM + return; // PGM + + // pmm + if (self->monsterinfo.aiflags & AI_MANUAL_STEERING) + blindfire = true; + + if (self->s.frame == FRAME_c_attack205) + { + spread = -0.1f; + flash_number = MZ2_GUNCMDR_GRENADE_MORTAR_1; + } + else if (self->s.frame == FRAME_c_attack208) + { + spread = 0.f; + flash_number = MZ2_GUNCMDR_GRENADE_MORTAR_2; + } + else if (self->s.frame == FRAME_c_attack211) + { + spread = 0.1f; + flash_number = MZ2_GUNCMDR_GRENADE_MORTAR_3; + } + else if (self->s.frame == FRAME_c_attack304) + { + spread = -0.1f; + flash_number = MZ2_GUNCMDR_GRENADE_FRONT_1; + } + else if (self->s.frame == FRAME_c_attack307) + { + spread = 0.f; + flash_number = MZ2_GUNCMDR_GRENADE_FRONT_2; + } + else if (self->s.frame == FRAME_c_attack310) + { + spread = 0.1f; + flash_number = MZ2_GUNCMDR_GRENADE_FRONT_3; + } + else if (self->s.frame == FRAME_c_attack911) + { + spread = 0.25f; + flash_number = MZ2_GUNCMDR_GRENADE_CROUCH_1; + } + else if (self->s.frame == FRAME_c_attack912) + { + spread = 0.f; + flash_number = MZ2_GUNCMDR_GRENADE_CROUCH_2; + } + else if (self->s.frame == FRAME_c_attack913) + { + spread = -0.25f; + flash_number = MZ2_GUNCMDR_GRENADE_CROUCH_3; + } + + // pmm + // if we're shooting blind and we still can't see our enemy + if ((blindfire) && (!visible(self, self->enemy))) + { + // and we have a valid blind_fire_target + if (!self->monsterinfo.blind_fire_target) + return; + + target = self->monsterinfo.blind_fire_target; + } + else + target = self->enemy->s.origin; + // pmm + + AngleVectors(self->s.angles, forward, right, up); // PGM + start = M_ProjectFlashSource(self, monster_flash_offset[flash_number], forward, right); + + // PGM + if (self->enemy && !(flash_number >= MZ2_GUNCMDR_GRENADE_CROUCH_1 && flash_number <= MZ2_GUNCMDR_GRENADE_CROUCH_3)) + { + float dist; + + aim = target - self->s.origin; + dist = aim.length(); + + // aim up if they're on the same level as me and far away. + if ((dist > 512) && (aim[2] < 64) && (aim[2] > -64)) + { + aim[2] += (dist - 512); + } + + aim.normalize(); + pitch = aim[2]; + if (pitch > 0.4f) + pitch = 0.4f; + else if (pitch < -0.5f) + pitch = -0.5f; + + if ((self->enemy->absmin.z - self->absmax.z) > 16.f && flash_number >= MZ2_GUNCMDR_GRENADE_MORTAR_1 && flash_number <= MZ2_GUNCMDR_GRENADE_MORTAR_3) + pitch += 0.5f; + } + // PGM + + if (flash_number >= MZ2_GUNCMDR_GRENADE_FRONT_1 && flash_number <= MZ2_GUNCMDR_GRENADE_FRONT_3) + pitch -= 0.05f; + + if (!(flash_number >= MZ2_GUNCMDR_GRENADE_CROUCH_1 && flash_number <= MZ2_GUNCMDR_GRENADE_CROUCH_3)) + { + aim = forward + (right * spread); + aim += (up * pitch); + aim.normalize(); + } + else + { + PredictAim(self, self->enemy, start, 800, false, 0.f, &aim, nullptr); + aim += right * spread; + aim.normalize(); + } + + if (flash_number >= MZ2_GUNCMDR_GRENADE_CROUCH_1 && flash_number <= MZ2_GUNCMDR_GRENADE_CROUCH_3) + { + constexpr float inner_spread = 0.125f; + + for (int32_t i = 0; i < 3; i++) + fire_ionripper(self, start, aim + (right * (-(inner_spread * 2) + (inner_spread * (i + 1)))), 15, 800, EF_IONRIPPER); + + monster_muzzleflash(self, start, flash_number); + } + else + { + // mortar fires farther + float speed; + + if (flash_number >= MZ2_GUNCMDR_GRENADE_MORTAR_1 && flash_number <= MZ2_GUNCMDR_GRENADE_MORTAR_3) + speed = MORTAR_SPEED; + else + speed = GRENADE_SPEED; + + // try search for best pitch + if (M_CalculatePitchToFire(self, target, start, aim, speed, 2.5f, (flash_number >= MZ2_GUNCMDR_GRENADE_MORTAR_1 && flash_number <= MZ2_GUNCMDR_GRENADE_MORTAR_3))) + monster_fire_grenade(self, start, aim, 50, speed, flash_number, (crandom_open() * 10.0f), frandom() * 10.f); + else + // normal shot + monster_fire_grenade(self, start, aim, 50, speed, flash_number, (crandom_open() * 10.0f), 200.f + (crandom_open() * 10.0f)); + } +} + +mframe_t guncmdr_frames_attack_mortar[] = { + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge, 0, GunnerCmdrGrenade }, + { ai_charge }, + { ai_charge }, + { ai_charge, 0, GunnerCmdrGrenade }, + { ai_charge }, + { ai_charge }, + + { ai_charge, 0, GunnerCmdrGrenade }, + { ai_charge }, + { ai_charge }, + { ai_charge, 0, monster_duck_up }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge } +}; +MMOVE_T(guncmdr_move_attack_mortar) = { FRAME_c_attack201, FRAME_c_attack221, guncmdr_frames_attack_mortar, guncmdr_run }; + +void guncmdr_grenade_mortar_resume(edict_t *self) +{ + M_SetAnimation(self, &guncmdr_move_attack_mortar); + self->monsterinfo.attack_state = AS_STRAIGHT; + self->s.frame = self->count; +} + +mframe_t guncmdr_frames_attack_mortar_dodge[] = { + { ai_charge, 11.f }, + { ai_charge, 12.f }, + { ai_charge, 16.f }, + { ai_charge, 16.f }, + { ai_charge, 12.f }, + { ai_charge, 11.f } +}; +MMOVE_T(guncmdr_move_attack_mortar_dodge) = { FRAME_c_duckstep01, FRAME_c_duckstep06, guncmdr_frames_attack_mortar_dodge, guncmdr_grenade_mortar_resume }; + +mframe_t guncmdr_frames_attack_back[] = { + //{ ai_charge }, + { ai_charge, -2.f }, + { ai_charge, -1.5f }, + { ai_charge, -0.5f, GunnerCmdrGrenade }, + { ai_charge, -6.0f }, + { ai_charge, -4.f }, + { ai_charge, -2.5f, GunnerCmdrGrenade }, + { ai_charge, -7.0f }, + { ai_charge, -3.5f }, + { ai_charge, -1.1f, GunnerCmdrGrenade }, + + { ai_charge, -4.6f }, + { ai_charge, 1.9f }, + { ai_charge, 1.0f }, + { ai_charge, -4.5f }, + { ai_charge, 3.2f }, + { ai_charge, 4.4f }, + { ai_charge, -6.5f }, + { ai_charge, -6.1f }, + { ai_charge, 3.0f }, + { ai_charge, -0.7f }, + { ai_charge, -1.0f } +}; +MMOVE_T(guncmdr_move_attack_grenade_back) = { FRAME_c_attack302, FRAME_c_attack321, guncmdr_frames_attack_back, guncmdr_run }; + +void guncmdr_grenade_back_dodge_resume(edict_t *self) +{ + M_SetAnimation(self, &guncmdr_move_attack_grenade_back); + self->monsterinfo.attack_state = AS_STRAIGHT; + self->s.frame = self->count; +} + +mframe_t guncmdr_frames_attack_grenade_back_dodge_right[] = { + { ai_charge, 5.1f * 2.0f }, + { ai_charge, 9.0f * 2.0f }, + { ai_charge, 3.5f * 2.0f }, + { ai_charge, 3.6f * 2.0f }, + { ai_charge, -1.0f * 2.0f } +}; +MMOVE_T(guncmdr_move_attack_grenade_back_dodge_right) = { FRAME_c_attack601, FRAME_c_attack605, guncmdr_frames_attack_grenade_back_dodge_right, guncmdr_grenade_back_dodge_resume }; + +mframe_t guncmdr_frames_attack_grenade_back_dodge_left[] = { + { ai_charge, 5.1f * 2.0f }, + { ai_charge, 9.0f * 2.0f }, + { ai_charge, 3.5f * 2.0f }, + { ai_charge, 3.6f * 2.0f }, + { ai_charge, -1.0f * 2.0f } +}; +MMOVE_T(guncmdr_move_attack_grenade_back_dodge_left) = { FRAME_c_attack701, FRAME_c_attack705, guncmdr_frames_attack_grenade_back_dodge_left, guncmdr_grenade_back_dodge_resume }; + +static void guncmdr_kick_finished(edict_t *self) +{ + self->monsterinfo.melee_debounce_time = level.time + 3_sec; + self->monsterinfo.attack(self); +} + +static void guncmdr_kick(edict_t *self) +{ + if (fire_hit(self, vec3_t { MELEE_DISTANCE, 0.f, -32.f }, 15.f, 400.f)) + { + if (self->enemy && self->enemy->client && self->enemy->velocity.z < 270.f) + self->enemy->velocity.z = 270.f; + } +} + +mframe_t guncmdr_frames_attack_kick[] = { + { ai_charge, -7.7f }, + { ai_charge, -4.9f }, + { ai_charge, 12.6f, guncmdr_kick }, + { ai_charge }, + { ai_charge, -3.0f }, + { ai_charge }, + { ai_charge, -4.1f }, + { ai_charge, 8.6f }, + //{ ai_charge, -3.5f } +}; +MMOVE_T(guncmdr_move_attack_kick) = { FRAME_c_attack801, FRAME_c_attack808, guncmdr_frames_attack_kick, guncmdr_kick_finished }; + +// don't ever try grenades if we get this close +constexpr float RANGE_GRENADE = 100.f; + +// always use mortar at this range +constexpr float RANGE_GRENADE_MORTAR = 525.f; + +// at this range, run towards the enemy +constexpr float RANGE_CHAINGUN_RUN = 400.f; + +MONSTERINFO_ATTACK(guncmdr_attack) (edict_t *self) -> void +{ + monster_done_dodge(self); + + float d = range_to(self, self->enemy); + + vec3_t forward, right, aim; + AngleVectors(self->s.angles, forward, right, nullptr); // PGM + + // always use chaingun on tesla + // kick close enemies + if (!self->bad_area && d < RANGE_MELEE && self->monsterinfo.melee_debounce_time < level.time) + M_SetAnimation(self, &guncmdr_move_attack_kick); + else if (self->bad_area || ((d <= RANGE_GRENADE || brandom()) && M_CheckClearShot(self, monster_flash_offset[MZ2_GUNCMDR_CHAINGUN_1]))) + M_SetAnimation(self, &guncmdr_move_attack_chain); + else if ((d >= RANGE_GRENADE_MORTAR || + fabs(self->absmin.z - self->enemy->absmax.z) > 64.f // enemy is far below or above us, always try mortar + ) && M_CheckClearShot(self, monster_flash_offset[MZ2_GUNCMDR_GRENADE_MORTAR_1]) && + M_CalculatePitchToFire(self, self->enemy->s.origin, M_ProjectFlashSource(self, monster_flash_offset[MZ2_GUNCMDR_GRENADE_MORTAR_1], forward, right), + aim = (self->enemy->s.origin - self->s.origin).normalized(), MORTAR_SPEED, 2.5f, true) + ) + { + M_SetAnimation(self, &guncmdr_move_attack_mortar); + monster_duck_down(self); + } + else if (M_CheckClearShot(self, monster_flash_offset[MZ2_GUNCMDR_GRENADE_FRONT_1]) && !(self->monsterinfo.aiflags & AI_STAND_GROUND) && + M_CalculatePitchToFire(self, self->enemy->s.origin, M_ProjectFlashSource(self, monster_flash_offset[MZ2_GUNCMDR_GRENADE_FRONT_1], forward, right), + aim = (self->enemy->s.origin - self->s.origin).normalized(), GRENADE_SPEED, 2.5f, false)) + M_SetAnimation(self, &guncmdr_move_attack_grenade_back); + else if (self->monsterinfo.aiflags & AI_STAND_GROUND) + M_SetAnimation(self, &guncmdr_move_attack_chain); +} + +void guncmdr_fire_chain(edict_t *self) +{ + if (!(self->monsterinfo.aiflags & AI_STAND_GROUND) && self->enemy && range_to(self, self->enemy) > RANGE_CHAINGUN_RUN && ai_check_move(self, 8.0f)) + M_SetAnimation(self, &guncmdr_move_fire_chain_run); + else + M_SetAnimation(self, &guncmdr_move_fire_chain); +} + +void guncmdr_refire_chain(edict_t *self) +{ + monster_done_dodge(self); + self->monsterinfo.attack_state = AS_STRAIGHT; + + if (self->enemy->health > 0) + if (visible(self, self->enemy)) + if (frandom() <= 0.5f) + { + if (!(self->monsterinfo.aiflags & AI_STAND_GROUND) && self->enemy && range_to(self, self->enemy) > RANGE_CHAINGUN_RUN && ai_check_move(self, 8.0f)) + M_SetAnimation(self, &guncmdr_move_fire_chain_run, false); + else + M_SetAnimation(self, &guncmdr_move_fire_chain, false); + return; + } + M_SetAnimation(self, &guncmdr_move_endfire_chain, false); +} + +//=========== +// PGM +void guncmdr_jump_now(edict_t *self) +{ + vec3_t forward, up; + + AngleVectors(self->s.angles, forward, nullptr, up); + self->velocity += (forward * 100); + self->velocity += (up * 300); +} + +void guncmdr_jump2_now(edict_t *self) +{ + vec3_t forward, up; + + AngleVectors(self->s.angles, forward, nullptr, up); + self->velocity += (forward * 150); + self->velocity += (up * 400); +} + +void guncmdr_jump_wait_land(edict_t *self) +{ + if (self->groundentity == nullptr) + { + self->monsterinfo.nextframe = self->s.frame; + + if (monster_jump_finished(self)) + self->monsterinfo.nextframe = self->s.frame + 1; + } + else + self->monsterinfo.nextframe = self->s.frame + 1; +} + +mframe_t guncmdr_frames_jump[] = { + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move, 0, guncmdr_jump_now }, + { ai_move }, + { ai_move }, + { ai_move, 0, guncmdr_jump_wait_land }, + { ai_move }, + { ai_move }, + { ai_move } +}; +MMOVE_T(guncmdr_move_jump) = { FRAME_c_jump01, FRAME_c_jump10, guncmdr_frames_jump, guncmdr_run }; + +mframe_t guncmdr_frames_jump2[] = { + { ai_move, -8 }, + { ai_move, -4 }, + { ai_move, -4 }, + { ai_move, 0, guncmdr_jump2_now }, + { ai_move }, + { ai_move }, + { ai_move, 0, guncmdr_jump_wait_land }, + { ai_move }, + { ai_move }, + { ai_move } +}; +MMOVE_T(guncmdr_move_jump2) = { FRAME_c_jump01, FRAME_c_jump10, guncmdr_frames_jump2, guncmdr_run }; + +void guncmdr_jump(edict_t *self, blocked_jump_result_t result) +{ + if (!self->enemy) + return; + + monster_done_dodge(self); + + if (result == blocked_jump_result_t::JUMP_JUMP_UP) + M_SetAnimation(self, &guncmdr_move_jump2); + else + M_SetAnimation(self, &guncmdr_move_jump); +} + +void T_SlamRadiusDamage(vec3_t point, edict_t *inflictor, edict_t *attacker, float damage, float kick, edict_t *ignore, float radius, mod_t mod); + +static void GunnerCmdrCounter(edict_t *self) +{ + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_BERSERK_SLAM); + vec3_t f, r, start; + AngleVectors(self->s.angles, f, r, nullptr); + start = M_ProjectFlashSource(self, { 20.f, 0.f, 14.f }, f, r); + trace_t tr = gi.traceline(self->s.origin, start, self, MASK_SOLID); + gi.WritePosition(tr.endpos); + gi.WriteDir(f); + gi.multicast(tr.endpos, MULTICAST_PHS, false); + + T_SlamRadiusDamage(tr.endpos, self, self, 15, 250.f, self, 200.f, MOD_UNKNOWN); +} + +//=========== +// PGM +mframe_t guncmdr_frames_duck_attack[] = { + { ai_move, 3.6f }, + { ai_move, 5.6f, monster_duck_down }, + { ai_move, 8.4f }, + { ai_move, 2.0f, monster_duck_hold }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + + //{ ai_charge, 0, GunnerCmdrGrenade }, + //{ ai_charge, 9.5f, GunnerCmdrGrenade }, + //{ ai_charge, -1.5f, GunnerCmdrGrenade }, + + { ai_charge, 0 }, + { ai_charge, 9.5f, GunnerCmdrCounter }, + { ai_charge, -1.5f }, + { ai_charge }, + { ai_charge, 0, monster_duck_up }, + { ai_charge }, + { ai_charge, 11.f }, + { ai_charge, 2.0f }, + { ai_charge, 5.6f } +}; +MMOVE_T(guncmdr_move_duck_attack) = { FRAME_c_attack901, FRAME_c_attack919, guncmdr_frames_duck_attack, guncmdr_run }; + +MONSTERINFO_DUCK(guncmdr_duck) (edict_t *self, gtime_t eta) -> bool +{ + if ((self->monsterinfo.active_move == &guncmdr_move_jump2) || + (self->monsterinfo.active_move == &guncmdr_move_jump)) + { + return false; + } + + if ((self->monsterinfo.active_move == &guncmdr_move_fire_chain_dodge_left) || + (self->monsterinfo.active_move == &guncmdr_move_fire_chain_dodge_right) || + (self->monsterinfo.active_move == &guncmdr_move_attack_grenade_back_dodge_right) || + (self->monsterinfo.active_move == &guncmdr_move_attack_grenade_back_dodge_right) || + (self->monsterinfo.active_move == &guncmdr_move_attack_mortar_dodge)) + { + // if we're dodging, don't duck + self->monsterinfo.unduck(self); + return false; + } + + M_SetAnimation(self, &guncmdr_move_duck_attack); + + return true; +} + +MONSTERINFO_SIDESTEP(guncmdr_sidestep) (edict_t *self) -> bool +{ + // use special dodge during the main firing anim + if (self->monsterinfo.active_move == &guncmdr_move_fire_chain || + self->monsterinfo.active_move == &guncmdr_move_fire_chain_run) + { + M_SetAnimation(self, !self->monsterinfo.lefty ? &guncmdr_move_fire_chain_dodge_right : &guncmdr_move_fire_chain_dodge_left, false); + return true; + } + + // for backwards mortar, back up where we are in the animation and do a quick dodge + if (self->monsterinfo.active_move == &guncmdr_move_attack_grenade_back) + { + self->count = self->s.frame; + M_SetAnimation(self, !self->monsterinfo.lefty ? &guncmdr_move_attack_grenade_back_dodge_right : &guncmdr_move_attack_grenade_back_dodge_left, false); + return true; + } + + // use crouch-move for mortar dodge + if (self->monsterinfo.active_move == &guncmdr_move_attack_mortar) + { + self->count = self->s.frame; + M_SetAnimation(self, &guncmdr_move_attack_mortar_dodge, false); + return true; + } + + // regular sidestep during run + if (self->monsterinfo.active_move == &guncmdr_move_run) + { + M_SetAnimation(self, &guncmdr_move_run, true); + return true; + } + + return false; +} + +MONSTERINFO_BLOCKED(guncmdr_blocked) (edict_t *self, float dist) -> bool +{ + if (blocked_checkplat(self, dist)) + return true; + + if (auto result = blocked_checkjump(self, dist); result != blocked_jump_result_t::NO_JUMP) + { + if (result != blocked_jump_result_t::JUMP_TURN) + guncmdr_jump(self, result); + + return true; + } + + return false; +} +// PGM +//=========== + +/*QUAKED monster_guncmdr (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight NoJumping +model="models/monsters/guncmdr/tris.md2" +*/ +void SP_monster_guncmdr(edict_t *self) +{ + if ( !M_AllowSpawn( self ) ) { + G_FreeEdict( self ); + return; + } + + sound_death = gi.soundindex("guncmdr/gcdrdeath1.wav"); + sound_pain = gi.soundindex("guncmdr/gcdrpain2.wav"); + sound_pain2 = gi.soundindex("guncmdr/gcdrpain1.wav"); + sound_idle = gi.soundindex("guncmdr/gcdridle1.wav"); + sound_open = gi.soundindex("guncmdr/gcdratck1.wav"); + sound_search = gi.soundindex("guncmdr/gcdrsrch1.wav"); + sound_sight = gi.soundindex("guncmdr/sight1.wav"); + + gi.soundindex("guncmdr/gcdratck2.wav"); + gi.soundindex("guncmdr/gcdratck3.wav"); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex("models/monsters/gunner/tris.md2"); + + gi.modelindex("models/monsters/gunner/gibs/chest.md2"); + gi.modelindex("models/monsters/gunner/gibs/foot.md2"); + gi.modelindex("models/monsters/gunner/gibs/garm.md2"); + gi.modelindex("models/monsters/gunner/gibs/gun.md2"); + gi.modelindex("models/monsters/gunner/gibs/head.md2"); + + self->s.scale = 1.25f; + self->mins = vec3_t { -16, -16, -24 }; + self->maxs = vec3_t { 16, 16, 36 }; + self->s.skinnum = 2; + + self->health = 325 * st.health_multiplier; + self->gib_health = -175; + self->mass = 255; + + self->pain = guncmdr_pain; + self->die = guncmdr_die; + + self->monsterinfo.stand = guncmdr_stand; + self->monsterinfo.walk = guncmdr_walk; + self->monsterinfo.run = guncmdr_run; + // pmm + self->monsterinfo.dodge = M_MonsterDodge; + self->monsterinfo.duck = guncmdr_duck; + self->monsterinfo.unduck = monster_duck_up; + self->monsterinfo.sidestep = guncmdr_sidestep; + self->monsterinfo.blocked = guncmdr_blocked; // PGM + // pmm + self->monsterinfo.attack = guncmdr_attack; + self->monsterinfo.melee = nullptr; + self->monsterinfo.sight = guncmdr_sight; + self->monsterinfo.search = guncmdr_search; + self->monsterinfo.setskin = guncmdr_setskin; + + gi.linkentity(self); + + M_SetAnimation(self, &guncmdr_move_stand); + self->monsterinfo.scale = MODEL_SCALE; + + if (!st.was_key_specified("power_armor_power")) + self->monsterinfo.power_armor_power = 200; + if (!st.was_key_specified("power_armor_type")) + self->monsterinfo.power_armor_type = IT_ITEM_POWER_SHIELD; + + // PMM + //self->monsterinfo.blindfire = true; + self->monsterinfo.can_jump = !self->spawnflags.has(SPAWNFLAG_GUNCMDR_NOJUMPING); + self->monsterinfo.drop_height = 192; + self->monsterinfo.jump_height = 40; + + walkmonster_start(self); +} diff --git a/rerelease/m_gunner.cpp b/rerelease/m_gunner.cpp new file mode 100644 index 0000000..1bdcc49 --- /dev/null +++ b/rerelease/m_gunner.cpp @@ -0,0 +1,920 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +/* +============================================================================== + +GUNNER + +============================================================================== +*/ + +#include "g_local.h" +#include "m_gunner.h" +#include "m_flash.h" + +static int sound_pain; +static int sound_pain2; +static int sound_death; +static int sound_idle; +static int sound_open; +static int sound_search; +static int sound_sight; + +void gunner_idlesound(edict_t *self) +{ + gi.sound(self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0); +} + +MONSTERINFO_SIGHT(gunner_sight) (edict_t *self, edict_t *other) -> void +{ + gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); +} + +MONSTERINFO_SEARCH(gunner_search) (edict_t *self) -> void +{ + gi.sound(self, CHAN_VOICE, sound_search, 1, ATTN_NORM, 0); +} + +void GunnerGrenade(edict_t *self); +void GunnerFire(edict_t *self); +void gunner_fire_chain(edict_t *self); +void gunner_refire_chain(edict_t *self); + +void gunner_stand(edict_t *self); + +mframe_t gunner_frames_fidget[] = { + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand, 0, gunner_idlesound }, + { ai_stand }, + + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + + { ai_stand } +}; +MMOVE_T(gunner_move_fidget) = { FRAME_stand31, FRAME_stand70, gunner_frames_fidget, gunner_stand }; + +void gunner_fidget(edict_t *self) +{ + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + return; + else if (self->enemy) + return; + if (frandom() <= 0.05f) + M_SetAnimation(self, &gunner_move_fidget); +} + +mframe_t gunner_frames_stand[] = { + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand, 0, gunner_fidget }, + + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand, 0, gunner_fidget }, + + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand, 0, gunner_fidget } +}; +MMOVE_T(gunner_move_stand) = { FRAME_stand01, FRAME_stand30, gunner_frames_stand, nullptr }; + +MONSTERINFO_STAND(gunner_stand) (edict_t *self) -> void +{ + M_SetAnimation(self, &gunner_move_stand); +} + +mframe_t gunner_frames_walk[] = { + { ai_walk }, + { ai_walk, 3 }, + { ai_walk, 4 }, + { ai_walk, 5 }, + { ai_walk, 7 }, + { ai_walk, 2, monster_footstep }, + { ai_walk, 6 }, + { ai_walk, 4 }, + { ai_walk, 2 }, + { ai_walk, 7 }, + { ai_walk, 5 }, + { ai_walk, 7 }, + { ai_walk, 4, monster_footstep } +}; +MMOVE_T(gunner_move_walk) = { FRAME_walk07, FRAME_walk19, gunner_frames_walk, nullptr }; + +MONSTERINFO_WALK(gunner_walk) (edict_t *self) -> void +{ + M_SetAnimation(self, &gunner_move_walk); +} + +mframe_t gunner_frames_run[] = { + { ai_run, 26 }, + { ai_run, 9, monster_footstep }, + { ai_run, 9 }, + { ai_run, 9, monster_done_dodge }, + { ai_run, 15 }, + { ai_run, 10, monster_footstep }, + { ai_run, 13 }, + { ai_run, 6 } +}; + +MMOVE_T(gunner_move_run) = { FRAME_run01, FRAME_run08, gunner_frames_run, nullptr }; + +MONSTERINFO_RUN(gunner_run) (edict_t *self) -> void +{ + monster_done_dodge(self); + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + M_SetAnimation(self, &gunner_move_stand); + else + M_SetAnimation(self, &gunner_move_run); +} + +mframe_t gunner_frames_runandshoot[] = { + { ai_run, 32 }, + { ai_run, 15 }, + { ai_run, 10 }, + { ai_run, 18 }, + { ai_run, 8 }, + { ai_run, 20 } +}; + +MMOVE_T(gunner_move_runandshoot) = { FRAME_runs01, FRAME_runs06, gunner_frames_runandshoot, nullptr }; + +void gunner_runandshoot(edict_t *self) +{ + M_SetAnimation(self, &gunner_move_runandshoot); +} + +mframe_t gunner_frames_pain3[] = { + { ai_move, -3 }, + { ai_move, 1 }, + { ai_move, 1 }, + { ai_move }, + { ai_move, 1 } +}; +MMOVE_T(gunner_move_pain3) = { FRAME_pain301, FRAME_pain305, gunner_frames_pain3, gunner_run }; + +mframe_t gunner_frames_pain2[] = { + { ai_move, -2 }, + { ai_move, 11 }, + { ai_move, 6, monster_footstep }, + { ai_move, 2 }, + { ai_move, -1 }, + { ai_move, -7 }, + { ai_move, -2 }, + { ai_move, -7, monster_footstep } +}; +MMOVE_T(gunner_move_pain2) = { FRAME_pain201, FRAME_pain208, gunner_frames_pain2, gunner_run }; + +mframe_t gunner_frames_pain1[] = { + { ai_move, 2 }, + { ai_move }, + { ai_move, -5 }, + { ai_move, 3 }, + { ai_move, -1, monster_footstep }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move, 1 }, + { ai_move, 1 }, + { ai_move, 2 }, + { ai_move, 1, monster_footstep }, + { ai_move }, + { ai_move, -2 }, + { ai_move, -2 }, + { ai_move }, + { ai_move, 0, monster_footstep } +}; +MMOVE_T(gunner_move_pain1) = { FRAME_pain101, FRAME_pain118, gunner_frames_pain1, gunner_run }; + +extern const mmove_t gunner_move_jump; +extern const mmove_t gunner_move_jump2; + +PAIN(gunner_pain) (edict_t *self, edict_t *other, float kick, int damage, const mod_t &mod) -> void +{ + monster_done_dodge(self); + + if (self->monsterinfo.active_move == &gunner_move_jump || + self->monsterinfo.active_move == &gunner_move_jump2) + return; + + if (level.time < self->pain_debounce_time) + return; + + self->pain_debounce_time = level.time + 3_sec; + + if (brandom()) + gi.sound(self, CHAN_VOICE, sound_pain, 1, ATTN_NORM, 0); + else + gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0); + + if (!M_ShouldReactToPain(self, mod)) + return; // no pain anims in nightmare + + if (damage <= 10) + M_SetAnimation(self, &gunner_move_pain3); + else if (damage <= 25) + M_SetAnimation(self, &gunner_move_pain2); + else + M_SetAnimation(self, &gunner_move_pain1); + + self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING; + + // PMM - clear duck flag + if (self->monsterinfo.aiflags & AI_DUCKED) + monster_duck_up(self); +} + +MONSTERINFO_SETSKIN(gunner_setskin) (edict_t *self) -> void +{ + if (self->health < (self->max_health / 2)) + self->s.skinnum = 1; + else + self->s.skinnum = 0; +} + +void gunner_dead(edict_t *self) +{ + self->mins = { -16, -16, -24 }; + self->maxs = { 16, 16, -8 }; + monster_dead(self); +} + +static void gunner_shrink(edict_t *self) +{ + self->maxs[2] = -4; + self->svflags |= SVF_DEADMONSTER; + gi.linkentity(self); +} + +mframe_t gunner_frames_death[] = { + { ai_move }, + { ai_move }, + { ai_move, 0, monster_footstep }, + { ai_move, -7, gunner_shrink }, + { ai_move, -3 }, + { ai_move, -5 }, + { ai_move, 8 }, + { ai_move, 6 }, + { ai_move, 0, monster_footstep }, + { ai_move }, + { ai_move } +}; +MMOVE_T(gunner_move_death) = { FRAME_death01, FRAME_death11, gunner_frames_death, gunner_dead }; + +DIE(gunner_die) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void +{ + // check for gib + if (M_CheckGib(self, mod)) + { + gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0); + + self->s.skinnum /= 2; + + ThrowGibs(self, damage, { + { 2, "models/objects/gibs/bone/tris.md2" }, + { 2, "models/objects/gibs/sm_meat/tris.md2" }, + { "models/monsters/gunner/gibs/chest.md2", GIB_SKINNED }, + { "models/monsters/gunner/gibs/garm.md2", GIB_SKINNED | GIB_UPRIGHT }, + { "models/monsters/gunner/gibs/gun.md2", GIB_SKINNED | GIB_UPRIGHT }, + { "models/monsters/gunner/gibs/foot.md2", GIB_SKINNED }, + { "models/monsters/gunner/gibs/head.md2", GIB_SKINNED | GIB_HEAD } + }); + + self->deadflag = true; + return; + } + + if (self->deadflag) + return; + + // regular death + gi.sound(self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0); + self->deadflag = true; + self->takedamage = true; + M_SetAnimation(self, &gunner_move_death); +} + +// PMM - changed to duck code for new dodge + +mframe_t gunner_frames_duck[] = { + { ai_move, 1, monster_duck_down }, + { ai_move, 1 }, + { ai_move, 1, monster_duck_hold }, + { ai_move }, + { ai_move, -1 }, + { ai_move, -1 }, + { ai_move, 0, monster_duck_up }, + { ai_move, -1 } +}; +MMOVE_T(gunner_move_duck) = { FRAME_duck01, FRAME_duck08, gunner_frames_duck, gunner_run }; + +// PMM - gunner dodge moved below so I know about attack sequences + +void gunner_opengun(edict_t *self) +{ + gi.sound(self, CHAN_VOICE, sound_open, 1, ATTN_IDLE, 0); +} + +void GunnerFire(edict_t *self) +{ + vec3_t start; + vec3_t forward, right; + vec3_t aim; + monster_muzzleflash_id_t flash_number; + + if (!self->enemy || !self->enemy->inuse) // PGM + return; // PGM + + flash_number = static_cast(MZ2_GUNNER_MACHINEGUN_1 + (self->s.frame - FRAME_attak216)); + + AngleVectors(self->s.angles, forward, right, nullptr); + start = M_ProjectFlashSource(self, monster_flash_offset[flash_number], forward, right); + PredictAim(self, self->enemy, start, 0, true, -0.2f, &aim, nullptr); + monster_fire_bullet(self, start, aim, 3, 4, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, flash_number); +} + +bool gunner_grenade_check(edict_t *self) +{ + vec3_t dir; + + if (!self->enemy) + return false; + + vec3_t start; + + if (!M_CheckClearShot(self, monster_flash_offset[MZ2_GUNNER_GRENADE_1], start)) + return false; + + vec3_t target; + + // check for flag telling us that we're blindfiring + if (self->monsterinfo.aiflags & AI_MANUAL_STEERING) + target = self->monsterinfo.blind_fire_target; + else + target = self->enemy->s.origin; + + // see if we're too close + dir = target - start; + + if (dir.length() < 100) + return false; + + // check to see that we can trace to the player before we start + // tossing grenades around. + vec3_t aim = dir.normalized(); + return M_CalculatePitchToFire(self, target, start, aim, 600, 2.5f, false); +} + +void GunnerGrenade(edict_t *self) +{ + vec3_t start; + vec3_t forward, right, up; + vec3_t aim; + monster_muzzleflash_id_t flash_number; + float spread; + float pitch = 0; + // PMM + vec3_t target; + bool blindfire = false; + + if (!self->enemy || !self->enemy->inuse) // PGM + return; // PGM + + // pmm + if (self->monsterinfo.aiflags & AI_MANUAL_STEERING) + blindfire = true; + + if (self->s.frame == FRAME_attak105 || self->s.frame == FRAME_attak309) + { + spread = -0.10f; + flash_number = MZ2_GUNNER_GRENADE_1; + } + else if (self->s.frame == FRAME_attak108 || self->s.frame == FRAME_attak312) + { + spread = -0.05f; + flash_number = MZ2_GUNNER_GRENADE_2; + } + else if (self->s.frame == FRAME_attak111 || self->s.frame == FRAME_attak315) + { + spread = 0.05f; + flash_number = MZ2_GUNNER_GRENADE_3; + } + else // (self->s.frame == FRAME_attak114) + { + self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING; + spread = 0.10f; + flash_number = MZ2_GUNNER_GRENADE_4; + } + + if (self->s.frame >= FRAME_attak301 && self->s.frame <= FRAME_attak324) + flash_number = static_cast(MZ2_GUNNER_GRENADE2_1 + (MZ2_GUNNER_GRENADE_4 - flash_number)); + + // pmm + // if we're shooting blind and we still can't see our enemy + if ((blindfire) && (!visible(self, self->enemy))) + { + // and we have a valid blind_fire_target + if (!self->monsterinfo.blind_fire_target) + return; + + target = self->monsterinfo.blind_fire_target; + } + else + target = self->enemy->s.origin; + // pmm + + AngleVectors(self->s.angles, forward, right, up); // PGM + start = M_ProjectFlashSource(self, monster_flash_offset[flash_number], forward, right); + + // PGM + if (self->enemy) + { + float dist; + + aim = target - self->s.origin; + dist = aim.length(); + + // aim up if they're on the same level as me and far away. + if ((dist > 512) && (aim[2] < 64) && (aim[2] > -64)) + { + aim[2] += (dist - 512); + } + + aim.normalize(); + pitch = aim[2]; + if (pitch > 0.4f) + pitch = 0.4f; + else if (pitch < -0.5f) + pitch = -0.5f; + } + // PGM + + aim = forward + (right * spread); + aim += (up * pitch); + + // try search for best pitch + if (M_CalculatePitchToFire(self, target, start, aim, 600, 2.5f, false)) + monster_fire_grenade(self, start, aim, 50, 600, flash_number, (crandom_open() * 10.0f), frandom() * 10.f); + else + // normal shot + monster_fire_grenade(self, start, aim, 50, 600, flash_number, (crandom_open() * 10.0f), 200.f + (crandom_open() * 10.0f)); +} + +mframe_t gunner_frames_attack_chain[] = { + { ai_charge, 0, gunner_opengun }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge } +}; +MMOVE_T(gunner_move_attack_chain) = { FRAME_attak209, FRAME_attak215, gunner_frames_attack_chain, gunner_fire_chain }; + +mframe_t gunner_frames_fire_chain[] = { + { ai_charge, 0, GunnerFire }, + { ai_charge, 0, GunnerFire }, + { ai_charge, 0, GunnerFire }, + { ai_charge, 0, GunnerFire }, + { ai_charge, 0, GunnerFire }, + { ai_charge, 0, GunnerFire }, + { ai_charge, 0, GunnerFire }, + { ai_charge, 0, GunnerFire } +}; +MMOVE_T(gunner_move_fire_chain) = { FRAME_attak216, FRAME_attak223, gunner_frames_fire_chain, gunner_refire_chain }; + +mframe_t gunner_frames_endfire_chain[] = { + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge, 0, monster_footstep } +}; +MMOVE_T(gunner_move_endfire_chain) = { FRAME_attak224, FRAME_attak230, gunner_frames_endfire_chain, gunner_run }; + +void gunner_blind_check(edict_t *self) +{ + vec3_t aim; + + if (self->monsterinfo.aiflags & AI_MANUAL_STEERING) + { + aim = self->monsterinfo.blind_fire_target - self->s.origin; + self->ideal_yaw = vectoyaw(aim); + } +} + +mframe_t gunner_frames_attack_grenade[] = { + { ai_charge, 0, gunner_blind_check }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge, 0, GunnerGrenade }, + { ai_charge }, + { ai_charge }, + { ai_charge, 0, GunnerGrenade }, + { ai_charge }, + { ai_charge }, + { ai_charge, 0, GunnerGrenade }, + { ai_charge }, + { ai_charge }, + { ai_charge, 0, GunnerGrenade }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge } +}; +MMOVE_T(gunner_move_attack_grenade) = { FRAME_attak101, FRAME_attak121, gunner_frames_attack_grenade, gunner_run }; + +mframe_t gunner_frames_attack_grenade2[] = { + //{ ai_charge }, + //{ ai_charge }, + //{ ai_charge }, + //{ ai_charge }, + + { ai_charge, 0, gunner_blind_check }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge, 0, GunnerGrenade }, + { ai_charge }, + { ai_charge }, + { ai_charge, 0, GunnerGrenade }, + { ai_charge }, + { ai_charge }, + { ai_charge, 0, GunnerGrenade }, + { ai_charge }, + { ai_charge }, + { ai_charge, 0, GunnerGrenade }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge } +}; +MMOVE_T(gunner_move_attack_grenade2) = { FRAME_attak305, FRAME_attak324, gunner_frames_attack_grenade2, gunner_run }; + +MONSTERINFO_ATTACK(gunner_attack) (edict_t *self) -> void +{ + float chance, r; + + monster_done_dodge(self); + + // PMM + if (self->monsterinfo.attack_state == AS_BLIND) + { + if (self->timestamp > level.time) + return; + + // setup shot probabilities + if (self->monsterinfo.blind_fire_delay < 1_sec) + chance = 1.0f; + else if (self->monsterinfo.blind_fire_delay < 7.5_sec) + chance = 0.4f; + else + chance = 0.1f; + + r = frandom(); + + // minimum of 4.1 seconds, plus 0-3, after the shots are done + self->monsterinfo.blind_fire_delay += 4.1_sec + random_time(3_sec); + + // don't shoot at the origin + if (!self->monsterinfo.blind_fire_target) + return; + + // don't shoot if the dice say not to + if (r > chance) + return; + + // turn on manual steering to signal both manual steering and blindfire + self->monsterinfo.aiflags |= AI_MANUAL_STEERING; + + if (gunner_grenade_check(self)) + { + // if the check passes, go for the attack + M_SetAnimation(self, brandom() ? &gunner_move_attack_grenade2 : &gunner_move_attack_grenade); + self->monsterinfo.attack_finished = level.time + random_time(2_sec); + } + else + // turn off blindfire flag + self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING; + + self->timestamp = level.time + random_time(2_sec, 3_sec); + + return; + } + // pmm + + // PGM - gunner needs to use his chaingun if he's being attacked by a tesla. + if (self->bad_area || self->timestamp > level.time || + (range_to(self, self->enemy) <= RANGE_NEAR * 0.35f && M_CheckClearShot(self, monster_flash_offset[MZ2_GUNNER_MACHINEGUN_1]))) + { + M_SetAnimation(self, &gunner_move_attack_chain); + } + else + { + if (self->timestamp <= level.time && frandom() <= 0.5f && gunner_grenade_check(self)) + { + M_SetAnimation(self, brandom() ? &gunner_move_attack_grenade2 : &gunner_move_attack_grenade); + self->timestamp = level.time + random_time(2_sec, 3_sec); + } + else if (M_CheckClearShot(self, monster_flash_offset[MZ2_GUNNER_MACHINEGUN_1])) + M_SetAnimation(self, &gunner_move_attack_chain); + } +} + +void gunner_fire_chain(edict_t *self) +{ + M_SetAnimation(self, &gunner_move_fire_chain); +} + +void gunner_refire_chain(edict_t *self) +{ + if (self->enemy->health > 0) + if (visible(self, self->enemy)) + if (frandom() <= 0.5f) + { + M_SetAnimation(self, &gunner_move_fire_chain, false); + return; + } + M_SetAnimation(self, &gunner_move_endfire_chain, false); +} + +//=========== +// PGM +void gunner_jump_now(edict_t *self) +{ + vec3_t forward, up; + + AngleVectors(self->s.angles, forward, nullptr, up); + self->velocity += (forward * 100); + self->velocity += (up * 300); +} + +void gunner_jump2_now(edict_t *self) +{ + vec3_t forward, up; + + AngleVectors(self->s.angles, forward, nullptr, up); + self->velocity += (forward * 150); + self->velocity += (up * 400); +} + +void gunner_jump_wait_land(edict_t *self) +{ + if (self->groundentity == nullptr) + { + self->monsterinfo.nextframe = self->s.frame; + + if (monster_jump_finished(self)) + self->monsterinfo.nextframe = self->s.frame + 1; + } + else + self->monsterinfo.nextframe = self->s.frame + 1; +} + +mframe_t gunner_frames_jump[] = { + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move, 0, gunner_jump_now }, + { ai_move }, + { ai_move }, + { ai_move, 0, gunner_jump_wait_land }, + { ai_move }, + { ai_move }, + { ai_move } +}; +MMOVE_T(gunner_move_jump) = { FRAME_jump01, FRAME_jump10, gunner_frames_jump, gunner_run }; + +mframe_t gunner_frames_jump2[] = { + { ai_move, -8 }, + { ai_move, -4 }, + { ai_move, -4 }, + { ai_move, 0, gunner_jump2_now }, + { ai_move }, + { ai_move }, + { ai_move, 0, gunner_jump_wait_land }, + { ai_move }, + { ai_move }, + { ai_move } +}; +MMOVE_T(gunner_move_jump2) = { FRAME_jump01, FRAME_jump10, gunner_frames_jump2, gunner_run }; + +void gunner_jump(edict_t *self, blocked_jump_result_t result) +{ + if (!self->enemy) + return; + + monster_done_dodge(self); + + if (result == blocked_jump_result_t::JUMP_JUMP_UP) + M_SetAnimation(self, &gunner_move_jump2); + else + M_SetAnimation(self, &gunner_move_jump); +} + +//=========== +// PGM +MONSTERINFO_BLOCKED(gunner_blocked) (edict_t *self, float dist) -> bool +{ + if (blocked_checkplat(self, dist)) + return true; + + if (auto result = blocked_checkjump(self, dist); result != blocked_jump_result_t::NO_JUMP) + { + if (result != blocked_jump_result_t::JUMP_TURN) + gunner_jump(self, result); + return true; + } + + return false; +} +// PGM +//=========== + +// PMM - new duck code +MONSTERINFO_DUCK(gunner_duck) (edict_t *self, gtime_t eta) -> bool +{ + if ((self->monsterinfo.active_move == &gunner_move_jump2) || + (self->monsterinfo.active_move == &gunner_move_jump)) + { + return false; + } + + if ((self->monsterinfo.active_move == &gunner_move_attack_chain) || + (self->monsterinfo.active_move == &gunner_move_fire_chain) || + (self->monsterinfo.active_move == &gunner_move_attack_grenade) || + (self->monsterinfo.active_move == &gunner_move_attack_grenade2)) + { + // if we're shooting don't dodge + self->monsterinfo.unduck(self); + return false; + } + + if (frandom() > 0.5f) + GunnerGrenade(self); + + M_SetAnimation(self, &gunner_move_duck); + + return true; +} + +MONSTERINFO_SIDESTEP(gunner_sidestep) (edict_t *self) -> bool +{ + if ((self->monsterinfo.active_move == &gunner_move_jump2) || + (self->monsterinfo.active_move == &gunner_move_jump) || + (self->monsterinfo.active_move == &gunner_move_pain1)) + return false; + + if ((self->monsterinfo.active_move == &gunner_move_attack_chain) || + (self->monsterinfo.active_move == &gunner_move_fire_chain) || + (self->monsterinfo.active_move == &gunner_move_attack_grenade) || + (self->monsterinfo.active_move == &gunner_move_attack_grenade2)) + { + // if we're shooting, don't dodge + return false; + } + + if (self->monsterinfo.active_move != &gunner_move_run) + M_SetAnimation(self, &gunner_move_run); + + return true; +} + +constexpr spawnflags_t SPAWNFLAG_GUNNER_NOJUMPING = 8_spawnflag; + +/*QUAKED monster_gunner (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight NoJumping +model="models/monsters/gunner/tris.md2" +*/ +void SP_monster_gunner(edict_t *self) +{ + if ( !M_AllowSpawn( self ) ) { + G_FreeEdict( self ); + return; + } + + sound_death = gi.soundindex("gunner/death1.wav"); + sound_pain = gi.soundindex("gunner/gunpain2.wav"); + sound_pain2 = gi.soundindex("gunner/gunpain1.wav"); + sound_idle = gi.soundindex("gunner/gunidle1.wav"); + sound_open = gi.soundindex("gunner/gunatck1.wav"); + sound_search = gi.soundindex("gunner/gunsrch1.wav"); + sound_sight = gi.soundindex("gunner/sight1.wav"); + + gi.soundindex("gunner/gunatck2.wav"); + gi.soundindex("gunner/gunatck3.wav"); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex("models/monsters/gunner/tris.md2"); + + gi.modelindex("models/monsters/gunner/gibs/chest.md2"); + gi.modelindex("models/monsters/gunner/gibs/foot.md2"); + gi.modelindex("models/monsters/gunner/gibs/garm.md2"); + gi.modelindex("models/monsters/gunner/gibs/gun.md2"); + gi.modelindex("models/monsters/gunner/gibs/head.md2"); + + self->mins = { -16, -16, -24 }; + self->maxs = { 16, 16, 36 }; + + self->health = 175 * st.health_multiplier; + self->gib_health = -70; + self->mass = 200; + + self->pain = gunner_pain; + self->die = gunner_die; + + self->monsterinfo.stand = gunner_stand; + self->monsterinfo.walk = gunner_walk; + self->monsterinfo.run = gunner_run; + // pmm + self->monsterinfo.dodge = M_MonsterDodge; + self->monsterinfo.duck = gunner_duck; + self->monsterinfo.unduck = monster_duck_up; + self->monsterinfo.sidestep = gunner_sidestep; + self->monsterinfo.blocked = gunner_blocked; // PGM + // pmm + self->monsterinfo.attack = gunner_attack; + self->monsterinfo.melee = nullptr; + self->monsterinfo.sight = gunner_sight; + self->monsterinfo.search = gunner_search; + self->monsterinfo.setskin = gunner_setskin; + + gi.linkentity(self); + + M_SetAnimation(self, &gunner_move_stand); + self->monsterinfo.scale = MODEL_SCALE; + + // PMM + self->monsterinfo.blindfire = true; + self->monsterinfo.can_jump = !self->spawnflags.has(SPAWNFLAG_GUNNER_NOJUMPING); + self->monsterinfo.drop_height = 192; + self->monsterinfo.jump_height = 40; + + walkmonster_start(self); +} diff --git a/rerelease/m_gunner.h b/rerelease/m_gunner.h new file mode 100644 index 0000000..e1a3e9f --- /dev/null +++ b/rerelease/m_gunner.h @@ -0,0 +1,809 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// E:\G Drive\md2f\quake2\baseq2\models/monsters/gunner + +// This file generated by qdata - Do NOT Modify + +enum { + FRAME_stand01, + FRAME_stand02, + FRAME_stand03, + FRAME_stand04, + FRAME_stand05, + FRAME_stand06, + FRAME_stand07, + FRAME_stand08, + FRAME_stand09, + FRAME_stand10, + FRAME_stand11, + FRAME_stand12, + FRAME_stand13, + FRAME_stand14, + FRAME_stand15, + FRAME_stand16, + FRAME_stand17, + FRAME_stand18, + FRAME_stand19, + FRAME_stand20, + FRAME_stand21, + FRAME_stand22, + FRAME_stand23, + FRAME_stand24, + FRAME_stand25, + FRAME_stand26, + FRAME_stand27, + FRAME_stand28, + FRAME_stand29, + FRAME_stand30, + FRAME_stand31, + FRAME_stand32, + FRAME_stand33, + FRAME_stand34, + FRAME_stand35, + FRAME_stand36, + FRAME_stand37, + FRAME_stand38, + FRAME_stand39, + FRAME_stand40, + FRAME_stand41, + FRAME_stand42, + FRAME_stand43, + FRAME_stand44, + FRAME_stand45, + FRAME_stand46, + FRAME_stand47, + FRAME_stand48, + FRAME_stand49, + FRAME_stand50, + FRAME_stand51, + FRAME_stand52, + FRAME_stand53, + FRAME_stand54, + FRAME_stand55, + FRAME_stand56, + FRAME_stand57, + FRAME_stand58, + FRAME_stand59, + FRAME_stand60, + FRAME_stand61, + FRAME_stand62, + FRAME_stand63, + FRAME_stand64, + FRAME_stand65, + FRAME_stand66, + FRAME_stand67, + FRAME_stand68, + FRAME_stand69, + FRAME_stand70, + FRAME_walk01, + FRAME_walk02, + FRAME_walk03, + FRAME_walk04, + FRAME_walk05, + FRAME_walk06, + FRAME_walk07, + FRAME_walk08, + FRAME_walk09, + FRAME_walk10, + FRAME_walk11, + FRAME_walk12, + FRAME_walk13, + FRAME_walk14, + FRAME_walk15, + FRAME_walk16, + FRAME_walk17, + FRAME_walk18, + FRAME_walk19, + FRAME_walk20, + FRAME_walk21, + FRAME_walk22, + FRAME_walk23, + FRAME_walk24, + FRAME_run01, + FRAME_run02, + FRAME_run03, + FRAME_run04, + FRAME_run05, + FRAME_run06, + FRAME_run07, + FRAME_run08, + FRAME_runs01, + FRAME_runs02, + FRAME_runs03, + FRAME_runs04, + FRAME_runs05, + FRAME_runs06, + FRAME_attak101, + FRAME_attak102, + FRAME_attak103, + FRAME_attak104, + FRAME_attak105, + FRAME_attak106, + FRAME_attak107, + FRAME_attak108, + FRAME_attak109, + FRAME_attak110, + FRAME_attak111, + FRAME_attak112, + FRAME_attak113, + FRAME_attak114, + FRAME_attak115, + FRAME_attak116, + FRAME_attak117, + FRAME_attak118, + FRAME_attak119, + FRAME_attak120, + FRAME_attak121, + FRAME_attak201, + FRAME_attak202, + FRAME_attak203, + FRAME_attak204, + FRAME_attak205, + FRAME_attak206, + FRAME_attak207, + FRAME_attak208, + FRAME_attak209, + FRAME_attak210, + FRAME_attak211, + FRAME_attak212, + FRAME_attak213, + FRAME_attak214, + FRAME_attak215, + FRAME_attak216, + FRAME_attak217, + FRAME_attak218, + FRAME_attak219, + FRAME_attak220, + FRAME_attak221, + FRAME_attak222, + FRAME_attak223, + FRAME_attak224, + FRAME_attak225, + FRAME_attak226, + FRAME_attak227, + FRAME_attak228, + FRAME_attak229, + FRAME_attak230, + FRAME_pain101, + FRAME_pain102, + FRAME_pain103, + FRAME_pain104, + FRAME_pain105, + FRAME_pain106, + FRAME_pain107, + FRAME_pain108, + FRAME_pain109, + FRAME_pain110, + FRAME_pain111, + FRAME_pain112, + FRAME_pain113, + FRAME_pain114, + FRAME_pain115, + FRAME_pain116, + FRAME_pain117, + FRAME_pain118, + FRAME_pain201, + FRAME_pain202, + FRAME_pain203, + FRAME_pain204, + FRAME_pain205, + FRAME_pain206, + FRAME_pain207, + FRAME_pain208, + FRAME_pain301, + FRAME_pain302, + FRAME_pain303, + FRAME_pain304, + FRAME_pain305, + FRAME_death01, + FRAME_death02, + FRAME_death03, + FRAME_death04, + FRAME_death05, + FRAME_death06, + FRAME_death07, + FRAME_death08, + FRAME_death09, + FRAME_death10, + FRAME_death11, + FRAME_duck01, + FRAME_duck02, + FRAME_duck03, + FRAME_duck04, + FRAME_duck05, + FRAME_duck06, + FRAME_duck07, + FRAME_duck08, + FRAME_jump01, + FRAME_jump02, + FRAME_jump03, + FRAME_jump04, + FRAME_jump05, + FRAME_jump06, + FRAME_jump07, + FRAME_jump08, + FRAME_jump09, + FRAME_jump10, + FRAME_shield01, + FRAME_shield02, + FRAME_shield03, + FRAME_shield04, + FRAME_shield05, + FRAME_shield06, + FRAME_attak301, + FRAME_attak302, + FRAME_attak303, + FRAME_attak304, + FRAME_attak305, + FRAME_attak306, + FRAME_attak307, + FRAME_attak308, + FRAME_attak309, + FRAME_attak310, + FRAME_attak311, + FRAME_attak312, + FRAME_attak313, + FRAME_attak314, + FRAME_attak315, + FRAME_attak316, + FRAME_attak317, + FRAME_attak318, + FRAME_attak319, + FRAME_attak320, + FRAME_attak321, + FRAME_attak322, + FRAME_attak323, + FRAME_attak324, + FRAME_c_stand101, + FRAME_c_stand102, + FRAME_c_stand103, + FRAME_c_stand104, + FRAME_c_stand105, + FRAME_c_stand106, + FRAME_c_stand107, + FRAME_c_stand108, + FRAME_c_stand109, + FRAME_c_stand110, + FRAME_c_stand111, + FRAME_c_stand112, + FRAME_c_stand113, + FRAME_c_stand114, + FRAME_c_stand115, + FRAME_c_stand116, + FRAME_c_stand117, + FRAME_c_stand118, + FRAME_c_stand119, + FRAME_c_stand120, + FRAME_c_stand121, + FRAME_c_stand122, + FRAME_c_stand123, + FRAME_c_stand124, + FRAME_c_stand125, + FRAME_c_stand126, + FRAME_c_stand127, + FRAME_c_stand128, + FRAME_c_stand129, + FRAME_c_stand130, + FRAME_c_stand131, + FRAME_c_stand132, + FRAME_c_stand133, + FRAME_c_stand134, + FRAME_c_stand135, + FRAME_c_stand136, + FRAME_c_stand137, + FRAME_c_stand138, + FRAME_c_stand139, + FRAME_c_stand140, + FRAME_c_stand201, + FRAME_c_stand202, + FRAME_c_stand203, + FRAME_c_stand204, + FRAME_c_stand205, + FRAME_c_stand206, + FRAME_c_stand207, + FRAME_c_stand208, + FRAME_c_stand209, + FRAME_c_stand210, + FRAME_c_stand211, + FRAME_c_stand212, + FRAME_c_stand213, + FRAME_c_stand214, + FRAME_c_stand215, + FRAME_c_stand216, + FRAME_c_stand217, + FRAME_c_stand218, + FRAME_c_stand219, + FRAME_c_stand220, + FRAME_c_stand221, + FRAME_c_stand222, + FRAME_c_stand223, + FRAME_c_stand224, + FRAME_c_stand225, + FRAME_c_stand226, + FRAME_c_stand227, + FRAME_c_stand228, + FRAME_c_stand229, + FRAME_c_stand230, + FRAME_c_stand231, + FRAME_c_stand232, + FRAME_c_stand233, + FRAME_c_stand234, + FRAME_c_stand235, + FRAME_c_stand236, + FRAME_c_stand237, + FRAME_c_stand238, + FRAME_c_stand239, + FRAME_c_stand240, + FRAME_c_stand241, + FRAME_c_stand242, + FRAME_c_stand243, + FRAME_c_stand244, + FRAME_c_stand245, + FRAME_c_stand246, + FRAME_c_stand247, + FRAME_c_stand248, + FRAME_c_stand249, + FRAME_c_stand250, + FRAME_c_stand251, + FRAME_c_stand252, + FRAME_c_stand253, + FRAME_c_stand254, + FRAME_c_attack101, + FRAME_c_attack102, + FRAME_c_attack103, + FRAME_c_attack104, + FRAME_c_attack105, + FRAME_c_attack106, + FRAME_c_attack107, + FRAME_c_attack108, + FRAME_c_attack109, + FRAME_c_attack110, + FRAME_c_attack111, + FRAME_c_attack112, + FRAME_c_attack113, + FRAME_c_attack114, + FRAME_c_attack115, + FRAME_c_attack116, + FRAME_c_attack117, + FRAME_c_attack118, + FRAME_c_attack119, + FRAME_c_attack120, + FRAME_c_attack121, + FRAME_c_attack122, + FRAME_c_attack123, + FRAME_c_attack124, + FRAME_c_jump01, + FRAME_c_jump02, + FRAME_c_jump03, + FRAME_c_jump04, + FRAME_c_jump05, + FRAME_c_jump06, + FRAME_c_jump07, + FRAME_c_jump08, + FRAME_c_jump09, + FRAME_c_jump10, + FRAME_c_attack201, + FRAME_c_attack202, + FRAME_c_attack203, + FRAME_c_attack204, + FRAME_c_attack205, + FRAME_c_attack206, + FRAME_c_attack207, + FRAME_c_attack208, + FRAME_c_attack209, + FRAME_c_attack210, + FRAME_c_attack211, + FRAME_c_attack212, + FRAME_c_attack213, + FRAME_c_attack214, + FRAME_c_attack215, + FRAME_c_attack216, + FRAME_c_attack217, + FRAME_c_attack218, + FRAME_c_attack219, + FRAME_c_attack220, + FRAME_c_attack221, + FRAME_c_attack301, + FRAME_c_attack302, + FRAME_c_attack303, + FRAME_c_attack304, + FRAME_c_attack305, + FRAME_c_attack306, + FRAME_c_attack307, + FRAME_c_attack308, + FRAME_c_attack309, + FRAME_c_attack310, + FRAME_c_attack311, + FRAME_c_attack312, + FRAME_c_attack313, + FRAME_c_attack314, + FRAME_c_attack315, + FRAME_c_attack316, + FRAME_c_attack317, + FRAME_c_attack318, + FRAME_c_attack319, + FRAME_c_attack320, + FRAME_c_attack321, + FRAME_c_attack401, + FRAME_c_attack402, + FRAME_c_attack403, + FRAME_c_attack404, + FRAME_c_attack405, + FRAME_c_attack501, + FRAME_c_attack502, + FRAME_c_attack503, + FRAME_c_attack504, + FRAME_c_attack505, + FRAME_c_attack601, + FRAME_c_attack602, + FRAME_c_attack603, + FRAME_c_attack604, + FRAME_c_attack605, + FRAME_c_attack701, + FRAME_c_attack702, + FRAME_c_attack703, + FRAME_c_attack704, + FRAME_c_attack705, + FRAME_c_pain101, + FRAME_c_pain102, + FRAME_c_pain103, + FRAME_c_pain104, + FRAME_c_pain201, + FRAME_c_pain202, + FRAME_c_pain203, + FRAME_c_pain204, + FRAME_c_pain301, + FRAME_c_pain302, + FRAME_c_pain303, + FRAME_c_pain304, + FRAME_c_pain401, + FRAME_c_pain402, + FRAME_c_pain403, + FRAME_c_pain404, + FRAME_c_pain405, + FRAME_c_pain406, + FRAME_c_pain407, + FRAME_c_pain408, + FRAME_c_pain409, + FRAME_c_pain410, + FRAME_c_pain411, + FRAME_c_pain412, + FRAME_c_pain413, + FRAME_c_pain414, + FRAME_c_pain415, + FRAME_c_pain501, + FRAME_c_pain502, + FRAME_c_pain503, + FRAME_c_pain504, + FRAME_c_pain505, + FRAME_c_pain506, + FRAME_c_pain507, + FRAME_c_pain508, + FRAME_c_pain509, + FRAME_c_pain510, + FRAME_c_pain511, + FRAME_c_pain512, + FRAME_c_pain513, + FRAME_c_pain514, + FRAME_c_pain515, + FRAME_c_pain516, + FRAME_c_pain517, + FRAME_c_pain518, + FRAME_c_pain519, + FRAME_c_pain520, + FRAME_c_pain521, + FRAME_c_pain522, + FRAME_c_pain523, + FRAME_c_pain524, + FRAME_c_death101, + FRAME_c_death102, + FRAME_c_death103, + FRAME_c_death104, + FRAME_c_death105, + FRAME_c_death106, + FRAME_c_death107, + FRAME_c_death108, + FRAME_c_death109, + FRAME_c_death110, + FRAME_c_death111, + FRAME_c_death112, + FRAME_c_death113, + FRAME_c_death114, + FRAME_c_death115, + FRAME_c_death116, + FRAME_c_death117, + FRAME_c_death118, + FRAME_c_death201, + FRAME_c_death202, + FRAME_c_death203, + FRAME_c_death204, + FRAME_c_death301, + FRAME_c_death302, + FRAME_c_death303, + FRAME_c_death304, + FRAME_c_death305, + FRAME_c_death306, + FRAME_c_death307, + FRAME_c_death308, + FRAME_c_death309, + FRAME_c_death310, + FRAME_c_death311, + FRAME_c_death312, + FRAME_c_death313, + FRAME_c_death314, + FRAME_c_death315, + FRAME_c_death316, + FRAME_c_death317, + FRAME_c_death318, + FRAME_c_death319, + FRAME_c_death320, + FRAME_c_death321, + FRAME_c_death401, + FRAME_c_death402, + FRAME_c_death403, + FRAME_c_death404, + FRAME_c_death405, + FRAME_c_death406, + FRAME_c_death407, + FRAME_c_death408, + FRAME_c_death409, + FRAME_c_death410, + FRAME_c_death411, + FRAME_c_death412, + FRAME_c_death413, + FRAME_c_death414, + FRAME_c_death415, + FRAME_c_death416, + FRAME_c_death417, + FRAME_c_death418, + FRAME_c_death419, + FRAME_c_death420, + FRAME_c_death421, + FRAME_c_death422, + FRAME_c_death423, + FRAME_c_death424, + FRAME_c_death425, + FRAME_c_death426, + FRAME_c_death427, + FRAME_c_death428, + FRAME_c_death429, + FRAME_c_death430, + FRAME_c_death431, + FRAME_c_death432, + FRAME_c_death433, + FRAME_c_death434, + FRAME_c_death435, + FRAME_c_death436, + FRAME_c_death501, + FRAME_c_death502, + FRAME_c_death503, + FRAME_c_death504, + FRAME_c_death505, + FRAME_c_death506, + FRAME_c_death507, + FRAME_c_death508, + FRAME_c_death509, + FRAME_c_death510, + FRAME_c_death511, + FRAME_c_death512, + FRAME_c_death513, + FRAME_c_death514, + FRAME_c_death515, + FRAME_c_death516, + FRAME_c_death517, + FRAME_c_death518, + FRAME_c_death519, + FRAME_c_death520, + FRAME_c_death521, + FRAME_c_death522, + FRAME_c_death523, + FRAME_c_death524, + FRAME_c_death525, + FRAME_c_death526, + FRAME_c_death527, + FRAME_c_death528, + FRAME_c_run101, + FRAME_c_run102, + FRAME_c_run103, + FRAME_c_run104, + FRAME_c_run105, + FRAME_c_run106, + FRAME_c_run201, + FRAME_c_run202, + FRAME_c_run203, + FRAME_c_run204, + FRAME_c_run205, + FRAME_c_run206, + FRAME_c_run301, + FRAME_c_run302, + FRAME_c_run303, + FRAME_c_run304, + FRAME_c_run305, + FRAME_c_run306, + FRAME_c_walk101, + FRAME_c_walk102, + FRAME_c_walk103, + FRAME_c_walk104, + FRAME_c_walk105, + FRAME_c_walk106, + FRAME_c_walk107, + FRAME_c_walk108, + FRAME_c_walk109, + FRAME_c_walk110, + FRAME_c_walk111, + FRAME_c_walk112, + FRAME_c_walk113, + FRAME_c_walk114, + FRAME_c_walk115, + FRAME_c_walk116, + FRAME_c_walk117, + FRAME_c_walk118, + FRAME_c_walk119, + FRAME_c_walk120, + FRAME_c_walk121, + FRAME_c_walk122, + FRAME_c_walk123, + FRAME_c_walk124, + FRAME_c_pain601, + FRAME_c_pain602, + FRAME_c_pain603, + FRAME_c_pain604, + FRAME_c_pain605, + FRAME_c_pain606, + FRAME_c_pain607, + FRAME_c_pain608, + FRAME_c_pain609, + FRAME_c_pain610, + FRAME_c_pain611, + FRAME_c_pain612, + FRAME_c_pain613, + FRAME_c_pain614, + FRAME_c_pain615, + FRAME_c_pain616, + FRAME_c_pain617, + FRAME_c_pain618, + FRAME_c_pain619, + FRAME_c_pain620, + FRAME_c_pain621, + FRAME_c_pain622, + FRAME_c_pain623, + FRAME_c_pain624, + FRAME_c_pain625, + FRAME_c_pain626, + FRAME_c_pain627, + FRAME_c_pain628, + FRAME_c_pain629, + FRAME_c_pain630, + FRAME_c_pain631, + FRAME_c_pain632, + FRAME_c_death601, + FRAME_c_death602, + FRAME_c_death603, + FRAME_c_death604, + FRAME_c_death605, + FRAME_c_death606, + FRAME_c_death607, + FRAME_c_death608, + FRAME_c_death609, + FRAME_c_death610, + FRAME_c_death611, + FRAME_c_death612, + FRAME_c_death613, + FRAME_c_death614, + FRAME_c_death701, + FRAME_c_death702, + FRAME_c_death703, + FRAME_c_death704, + FRAME_c_death705, + FRAME_c_death706, + FRAME_c_death707, + FRAME_c_death708, + FRAME_c_death709, + FRAME_c_death710, + FRAME_c_death711, + FRAME_c_death712, + FRAME_c_death713, + FRAME_c_death714, + FRAME_c_death715, + FRAME_c_death716, + FRAME_c_death717, + FRAME_c_death718, + FRAME_c_death719, + FRAME_c_death720, + FRAME_c_death721, + FRAME_c_death722, + FRAME_c_death723, + FRAME_c_death724, + FRAME_c_death725, + FRAME_c_death726, + FRAME_c_death727, + FRAME_c_death728, + FRAME_c_death729, + FRAME_c_death730, + FRAME_c_pain701, + FRAME_c_pain702, + FRAME_c_pain703, + FRAME_c_pain704, + FRAME_c_pain705, + FRAME_c_pain706, + FRAME_c_pain707, + FRAME_c_pain708, + FRAME_c_pain709, + FRAME_c_pain710, + FRAME_c_pain711, + FRAME_c_pain712, + FRAME_c_pain713, + FRAME_c_pain714, + FRAME_c_attack801, + FRAME_c_attack802, + FRAME_c_attack803, + FRAME_c_attack804, + FRAME_c_attack805, + FRAME_c_attack806, + FRAME_c_attack807, + FRAME_c_attack808, + FRAME_c_attack809, + FRAME_c_attack901, + FRAME_c_attack902, + FRAME_c_attack903, + FRAME_c_attack904, + FRAME_c_attack905, + FRAME_c_attack906, + FRAME_c_attack907, + FRAME_c_attack908, + FRAME_c_attack909, + FRAME_c_attack910, + FRAME_c_attack911, + FRAME_c_attack912, + FRAME_c_attack913, + FRAME_c_attack914, + FRAME_c_attack915, + FRAME_c_attack916, + FRAME_c_attack917, + FRAME_c_attack918, + FRAME_c_attack919, + FRAME_c_duck01, + FRAME_c_duck02, + FRAME_c_duckstep01, + FRAME_c_duckstep02, + FRAME_c_duckstep03, + FRAME_c_duckstep04, + FRAME_c_duckstep05, + FRAME_c_duckstep06, + FRAME_c_duckpain01, + FRAME_c_duckpain02, + FRAME_c_duckpain03, + FRAME_c_duckpain04, + FRAME_c_duckpain05, + FRAME_c_duckdeath01, + FRAME_c_duckdeath02, + FRAME_c_duckdeath03, + FRAME_c_duckdeath04, + FRAME_c_duckdeath05, + FRAME_c_duckdeath06, + FRAME_c_duckdeath07, + FRAME_c_duckdeath08, + FRAME_c_duckdeath09, + FRAME_c_duckdeath10, + FRAME_c_duckdeath11, + FRAME_c_duckdeath12, + FRAME_c_duckdeath13, + FRAME_c_duckdeath14, + FRAME_c_duckdeath15, + FRAME_c_duckdeath16, + FRAME_c_duckdeath17, + FRAME_c_duckdeath18, + FRAME_c_duckdeath19, + FRAME_c_duckdeath20, + FRAME_c_duckdeath21, + FRAME_c_duckdeath22, + FRAME_c_duckdeath23, + FRAME_c_duckdeath24, + FRAME_c_duckdeath25, + FRAME_c_duckdeath26, + FRAME_c_duckdeath27, + FRAME_c_duckdeath28, + FRAME_c_duckdeath29 +}; + +constexpr float MODEL_SCALE = 1.150000f; diff --git a/rerelease/m_hover.cpp b/rerelease/m_hover.cpp new file mode 100644 index 0000000..31459bc --- /dev/null +++ b/rerelease/m_hover.cpp @@ -0,0 +1,662 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +/* +============================================================================== + +hover + +============================================================================== +*/ + +#include "g_local.h" +#include "m_hover.h" +#include "m_flash.h" + +static int sound_pain1; +static int sound_pain2; +static int sound_death1; +static int sound_death2; +static int sound_sight; +static int sound_search1; +static int sound_search2; + +// ROGUE +// daedalus sounds +static int daed_sound_pain1; +static int daed_sound_pain2; +static int daed_sound_death1; +static int daed_sound_death2; +static int daed_sound_sight; +static int daed_sound_search1; +static int daed_sound_search2; +// ROGUE + +MONSTERINFO_SIGHT(hover_sight) (edict_t *self, edict_t *other) -> void +{ + // PMM - daedalus sounds + if (self->mass < 225) + gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); + else + gi.sound(self, CHAN_VOICE, daed_sound_sight, 1, ATTN_NORM, 0); +} + +MONSTERINFO_SEARCH(hover_search) (edict_t *self) -> void +{ + // PMM - daedalus sounds + if (self->mass < 225) + { + if (frandom() < 0.5f) + gi.sound(self, CHAN_VOICE, sound_search1, 1, ATTN_NORM, 0); + else + gi.sound(self, CHAN_VOICE, sound_search2, 1, ATTN_NORM, 0); + } + else + { + if (frandom() < 0.5f) + gi.sound(self, CHAN_VOICE, daed_sound_search1, 1, ATTN_NORM, 0); + else + gi.sound(self, CHAN_VOICE, daed_sound_search2, 1, ATTN_NORM, 0); + } +} + +void hover_run(edict_t *self); +void hover_dead(edict_t *self); +void hover_attack(edict_t *self); +void hover_reattack(edict_t *self); +void hover_fire_blaster(edict_t *self); + +mframe_t hover_frames_stand[] = { + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand } +}; +MMOVE_T(hover_move_stand) = { FRAME_stand01, FRAME_stand30, hover_frames_stand, nullptr }; + +mframe_t hover_frames_pain3[] = { + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move } +}; +MMOVE_T(hover_move_pain3) = { FRAME_pain301, FRAME_pain309, hover_frames_pain3, hover_run }; + +mframe_t hover_frames_pain2[] = { + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move } +}; +MMOVE_T(hover_move_pain2) = { FRAME_pain201, FRAME_pain212, hover_frames_pain2, hover_run }; + +mframe_t hover_frames_pain1[] = { + { ai_move }, + { ai_move }, + { ai_move, 2 }, + { ai_move, -8 }, + { ai_move, -4 }, + { ai_move, -6 }, + { ai_move, -4 }, + { ai_move, -3 }, + { ai_move, 1 }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move, 3 }, + { ai_move, 1 }, + { ai_move }, + { ai_move, 2 }, + { ai_move, 3 }, + { ai_move, 2 }, + { ai_move, 7 }, + { ai_move, 1 }, + { ai_move }, + { ai_move }, + { ai_move, 2 }, + { ai_move }, + { ai_move }, + { ai_move, 5 }, + { ai_move, 3 }, + { ai_move, 4 } +}; +MMOVE_T(hover_move_pain1) = { FRAME_pain101, FRAME_pain128, hover_frames_pain1, hover_run }; + +mframe_t hover_frames_walk[] = { + { ai_walk, 4 }, + { ai_walk, 4 }, + { ai_walk, 4 }, + { ai_walk, 4 }, + { ai_walk, 4 }, + { ai_walk, 4 }, + { ai_walk, 4 }, + { ai_walk, 4 }, + { ai_walk, 4 }, + { ai_walk, 4 }, + { ai_walk, 4 }, + { ai_walk, 4 }, + { ai_walk, 4 }, + { ai_walk, 4 }, + { ai_walk, 4 }, + { ai_walk, 4 }, + { ai_walk, 4 }, + { ai_walk, 4 }, + { ai_walk, 4 }, + { ai_walk, 4 }, + { ai_walk, 4 }, + { ai_walk, 4 }, + { ai_walk, 4 }, + { ai_walk, 4 }, + { ai_walk, 4 }, + { ai_walk, 4 }, + { ai_walk, 4 }, + { ai_walk, 4 }, + { ai_walk, 4 }, + { ai_walk, 4 }, + { ai_walk, 4 }, + { ai_walk, 4 }, + { ai_walk, 4 }, + { ai_walk, 4 }, + { ai_walk, 4 } +}; +MMOVE_T(hover_move_walk) = { FRAME_forwrd01, FRAME_forwrd35, hover_frames_walk, nullptr }; + +mframe_t hover_frames_run[] = { + { ai_run, 10 }, + { ai_run, 10 }, + { ai_run, 10 }, + { ai_run, 10 }, + { ai_run, 10 }, + { ai_run, 10 }, + { ai_run, 10 }, + { ai_run, 10 }, + { ai_run, 10 }, + { ai_run, 10 }, + { ai_run, 10 }, + { ai_run, 10 }, + { ai_run, 10 }, + { ai_run, 10 }, + { ai_run, 10 }, + { ai_run, 10 }, + { ai_run, 10 }, + { ai_run, 10 }, + { ai_run, 10 }, + { ai_run, 10 }, + { ai_run, 10 }, + { ai_run, 10 }, + { ai_run, 10 }, + { ai_run, 10 }, + { ai_run, 10 }, + { ai_run, 10 }, + { ai_run, 10 }, + { ai_run, 10 }, + { ai_run, 10 }, + { ai_run, 10 }, + { ai_run, 10 }, + { ai_run, 10 }, + { ai_run, 10 }, + { ai_run, 10 }, + { ai_run, 10 } +}; +MMOVE_T(hover_move_run) = { FRAME_forwrd01, FRAME_forwrd35, hover_frames_run, nullptr }; + +static void hover_gib(edict_t *self) +{ + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_EXPLOSION1); + gi.WritePosition(self->s.origin); + gi.multicast(self->s.origin, MULTICAST_PHS, false); + + self->s.skinnum /= 2; + + ThrowGibs(self, 150, { + { 2, "models/objects/gibs/sm_meat/tris.md2" }, + { 2, "models/objects/gibs/sm_metal/tris.md2", GIB_METALLIC }, + { "models/monsters/hover/gibs/chest.md2", GIB_SKINNED }, + { 2, "models/monsters/hover/gibs/ring.md2", GIB_SKINNED | GIB_METALLIC }, + { 2, "models/monsters/hover/gibs/foot.md2", GIB_SKINNED }, + { "models/monsters/hover/gibs/head.md2", GIB_SKINNED | GIB_HEAD }, + }); +} + +THINK(hover_deadthink) (edict_t *self) -> void +{ + if (!self->groundentity && level.time < self->timestamp) + { + self->nextthink = level.time + FRAME_TIME_S; + return; + } + + hover_gib(self); +} + +void hover_dying(edict_t *self) +{ + if (self->groundentity) + { + hover_deadthink(self); + return; + } + + if (brandom()) + return; + + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_PLAIN_EXPLOSION); + gi.WritePosition(self->s.origin); + gi.multicast(self->s.origin, MULTICAST_PHS, false); + + if (brandom()) + ThrowGibs(self, 120, { + { "models/objects/gibs/sm_meat/tris.md2" } + }); + else + ThrowGibs(self, 120, { + { "models/objects/gibs/sm_metal/tris.md2", GIB_METALLIC } + }); +} + +mframe_t hover_frames_death1[] = { + { ai_move }, + { ai_move, 0.f, hover_dying }, + { ai_move }, + { ai_move, 0.f, hover_dying }, + { ai_move }, + { ai_move, 0.f, hover_dying }, + { ai_move, -10, hover_dying }, + { ai_move, 3 }, + { ai_move, 5, hover_dying }, + { ai_move, 4, hover_dying }, + { ai_move, 7 } +}; +MMOVE_T(hover_move_death1) = { FRAME_death101, FRAME_death111, hover_frames_death1, hover_dead }; + +mframe_t hover_frames_start_attack[] = { + { ai_charge, 1 }, + { ai_charge, 1 }, + { ai_charge, 1 } +}; +MMOVE_T(hover_move_start_attack) = { FRAME_attak101, FRAME_attak103, hover_frames_start_attack, hover_attack }; + +mframe_t hover_frames_attack1[] = { + { ai_charge, -10, hover_fire_blaster }, + { ai_charge, -10, hover_fire_blaster }, + { ai_charge, 0, hover_reattack }, +}; +MMOVE_T(hover_move_attack1) = { FRAME_attak104, FRAME_attak106, hover_frames_attack1, nullptr }; + +mframe_t hover_frames_end_attack[] = { + { ai_charge, 1 }, + { ai_charge, 1 } +}; +MMOVE_T(hover_move_end_attack) = { FRAME_attak107, FRAME_attak108, hover_frames_end_attack, hover_run }; + +/* PMM - circle strafing code */ +#if 0 +mframe_t hover_frames_start_attack2[] = { + { ai_charge, 15 }, + { ai_charge, 15 }, + { ai_charge, 15 } +}; +MMOVE_T(hover_move_start_attack2) = { FRAME_attak101, FRAME_attak103, hover_frames_start_attack2, hover_attack }; +#endif + +mframe_t hover_frames_attack2[] = { + { ai_charge, 10, hover_fire_blaster }, + { ai_charge, 10, hover_fire_blaster }, + { ai_charge, 10, hover_reattack }, +}; +MMOVE_T(hover_move_attack2) = { FRAME_attak104, FRAME_attak106, hover_frames_attack2, nullptr }; + +#if 0 +mframe_t hover_frames_end_attack2[] = { + { ai_charge, 15 }, + { ai_charge, 15 } +}; +MMOVE_T(hover_move_end_attack2) = { FRAME_attak107, FRAME_attak108, hover_frames_end_attack2, hover_run }; +#endif + +// end of circle strafe + +void hover_reattack(edict_t *self) +{ + if (self->enemy->health > 0) + if (visible(self, self->enemy)) + if (frandom() <= 0.6f) + { + if (self->monsterinfo.attack_state == AS_STRAIGHT) + { + M_SetAnimation(self, &hover_move_attack1); + return; + } + else if (self->monsterinfo.attack_state == AS_SLIDING) + { + M_SetAnimation(self, &hover_move_attack2); + return; + } + else + gi.Com_PrintFmt("hover_reattack: unexpected state {}\n", (int32_t) self->monsterinfo.attack_state); + } + M_SetAnimation(self, &hover_move_end_attack); +} + +void hover_fire_blaster(edict_t *self) +{ + vec3_t start; + vec3_t forward, right; + vec3_t end; + vec3_t dir; + + if (!self->enemy || !self->enemy->inuse) // PGM + return; // PGM + + AngleVectors(self->s.angles, forward, right, nullptr); + vec3_t o = monster_flash_offset[(self->s.frame & 1) ? MZ2_HOVER_BLASTER_2 : MZ2_HOVER_BLASTER_1]; + start = M_ProjectFlashSource(self, o, forward, right); + + end = self->enemy->s.origin; + end[2] += self->enemy->viewheight; + dir = end - start; + dir.normalize(); + + // PGM - daedalus fires blaster2 + if (self->mass < 200) + monster_fire_blaster(self, start, dir, 1, 1000, (self->s.frame & 1) ? MZ2_HOVER_BLASTER_2 : MZ2_HOVER_BLASTER_1, (self->s.frame % 4) ? EF_NONE : EF_HYPERBLASTER); + else + monster_fire_blaster2(self, start, dir, 1, 1000, (self->s.frame & 1) ? MZ2_DAEDALUS_BLASTER_2 : MZ2_DAEDALUS_BLASTER, (self->s.frame % 4) ? EF_NONE : EF_BLASTER); + // PGM +} + +MONSTERINFO_STAND(hover_stand) (edict_t *self) -> void +{ + M_SetAnimation(self, &hover_move_stand); +} + +MONSTERINFO_RUN(hover_run) (edict_t *self) -> void +{ + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + M_SetAnimation(self, &hover_move_stand); + else + M_SetAnimation(self, &hover_move_run); +} + +MONSTERINFO_WALK(hover_walk) (edict_t *self) -> void +{ + M_SetAnimation(self, &hover_move_walk); +} + +MONSTERINFO_ATTACK(hover_start_attack) (edict_t *self) -> void +{ + M_SetAnimation(self, &hover_move_start_attack); +} + +void hover_attack(edict_t *self) +{ + float chance = 0.5f; + + if (self->mass > 150) // the daedalus strafes more + chance += 0.1f; + + if (frandom() > chance) + { + M_SetAnimation(self, &hover_move_attack1); + self->monsterinfo.attack_state = AS_STRAIGHT; + } + else // circle strafe + { + if (frandom() <= 0.5f) // switch directions + self->monsterinfo.lefty = !self->monsterinfo.lefty; + M_SetAnimation(self, &hover_move_attack2); + self->monsterinfo.attack_state = AS_SLIDING; + } +} + +PAIN(hover_pain) (edict_t *self, edict_t *other, float kick, int damage, const mod_t &mod) -> void +{ + if (level.time < self->pain_debounce_time) + return; + + self->pain_debounce_time = level.time + 3_sec; + + float r = frandom(); + + //==== + if (r < 0.5f) + { + // PMM - daedalus sounds + if (self->mass < 225) + gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); + else + gi.sound(self, CHAN_VOICE, daed_sound_pain1, 1, ATTN_NORM, 0); + } + else + { + // PMM - daedalus sounds + if (self->mass < 225) + gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0); + else + gi.sound(self, CHAN_VOICE, daed_sound_pain2, 1, ATTN_NORM, 0); + } + // PGM + //==== + + if (!M_ShouldReactToPain(self, mod)) + return; // no pain anims in nightmare + + r = frandom(); + + if (damage <= 25) + { + if (r < 0.5f) + M_SetAnimation(self, &hover_move_pain3); + else + M_SetAnimation(self, &hover_move_pain2); + } + else + { + //==== + // PGM pain sequence is WAY too long + if (r < 0.3f) + M_SetAnimation(self, &hover_move_pain1); + else + M_SetAnimation(self, &hover_move_pain2); + // PGM + //==== + } +} + +MONSTERINFO_SETSKIN(hover_setskin) (edict_t *self) -> void +{ + if (self->health < (self->max_health / 2)) + self->s.skinnum |= 1; // PGM support for skins 2 & 3. + else + self->s.skinnum &= ~1; // PGM support for skins 2 & 3. +} + +void hover_dead(edict_t *self) +{ + self->mins = { -16, -16, -24 }; + self->maxs = { 16, 16, -8 }; + self->movetype = MOVETYPE_TOSS; + self->think = hover_deadthink; + self->nextthink = level.time + FRAME_TIME_S; + self->timestamp = level.time + 15_sec; + gi.linkentity(self); +} + +DIE(hover_die) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void +{ + self->s.effects = EF_NONE; + self->monsterinfo.power_armor_type = IT_NULL; + + if (M_CheckGib(self, mod)) + { + hover_gib(self); + return; + } + + if (self->deadflag) + return; + + // regular death + // PMM - daedalus sounds + if (self->mass < 225) + { + if (frandom() < 0.5f) + gi.sound(self, CHAN_VOICE, sound_death1, 1, ATTN_NORM, 0); + else + gi.sound(self, CHAN_VOICE, sound_death2, 1, ATTN_NORM, 0); + } + else + { + if (frandom() < 0.5f) + gi.sound(self, CHAN_VOICE, daed_sound_death1, 1, ATTN_NORM, 0); + else + gi.sound(self, CHAN_VOICE, daed_sound_death2, 1, ATTN_NORM, 0); + } + self->deadflag = true; + self->takedamage = true; + M_SetAnimation(self, &hover_move_death1); +} + +static void hover_set_fly_parameters(edict_t *self) +{ + self->monsterinfo.fly_thrusters = false; + self->monsterinfo.fly_acceleration = 20.f; + self->monsterinfo.fly_speed = 120.f; + // Icarus prefers to keep its distance, but flies slower than the flyer. + // he never pins because of this. + self->monsterinfo.fly_min_distance = 150.f; + self->monsterinfo.fly_max_distance = 350.f; +} + +/*QUAKED monster_hover (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight + */ +/*QUAKED monster_daedalus (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight +This is the improved icarus monster. +*/ +void SP_monster_hover(edict_t *self) +{ + if ( !M_AllowSpawn( self ) ) { + G_FreeEdict( self ); + return; + } + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex("models/monsters/hover/tris.md2"); + + gi.modelindex("models/monsters/hover/gibs/chest.md2"); + gi.modelindex("models/monsters/hover/gibs/foot.md2"); + gi.modelindex("models/monsters/hover/gibs/head.md2"); + gi.modelindex("models/monsters/hover/gibs/ring.md2"); + + self->mins = { -24, -24, -24 }; + self->maxs = { 24, 24, 32 }; + + self->health = 240 * st.health_multiplier; + self->gib_health = -100; + self->mass = 150; + + self->pain = hover_pain; + self->die = hover_die; + + self->monsterinfo.stand = hover_stand; + self->monsterinfo.walk = hover_walk; + self->monsterinfo.run = hover_run; + self->monsterinfo.attack = hover_start_attack; + self->monsterinfo.sight = hover_sight; + self->monsterinfo.search = hover_search; + self->monsterinfo.setskin = hover_setskin; + + // PGM + if (strcmp(self->classname, "monster_daedalus") == 0) + { + self->health = 450 * st.health_multiplier; + self->mass = 225; + self->yaw_speed = 23; + if (!st.was_key_specified("power_armor_type")) + self->monsterinfo.power_armor_type = IT_ITEM_POWER_SCREEN; + if (!st.was_key_specified("power_armor_power")) + self->monsterinfo.power_armor_power = 100; + // PMM - daedalus sounds + self->monsterinfo.engine_sound = gi.soundindex("daedalus/daedidle1.wav"); + daed_sound_pain1 = gi.soundindex("daedalus/daedpain1.wav"); + daed_sound_pain2 = gi.soundindex("daedalus/daedpain2.wav"); + daed_sound_death1 = gi.soundindex("daedalus/daeddeth1.wav"); + daed_sound_death2 = gi.soundindex("daedalus/daeddeth2.wav"); + daed_sound_sight = gi.soundindex("daedalus/daedsght1.wav"); + daed_sound_search1 = gi.soundindex("daedalus/daedsrch1.wav"); + daed_sound_search2 = gi.soundindex("daedalus/daedsrch2.wav"); + gi.soundindex("tank/tnkatck3.wav"); + // pmm + } + else + { + self->yaw_speed = 18; + sound_pain1 = gi.soundindex("hover/hovpain1.wav"); + sound_pain2 = gi.soundindex("hover/hovpain2.wav"); + sound_death1 = gi.soundindex("hover/hovdeth1.wav"); + sound_death2 = gi.soundindex("hover/hovdeth2.wav"); + sound_sight = gi.soundindex("hover/hovsght1.wav"); + sound_search1 = gi.soundindex("hover/hovsrch1.wav"); + sound_search2 = gi.soundindex("hover/hovsrch2.wav"); + gi.soundindex("hover/hovatck1.wav"); + + self->monsterinfo.engine_sound = gi.soundindex("hover/hovidle1.wav"); + } + // PGM + + gi.linkentity(self); + + M_SetAnimation(self, &hover_move_stand); + self->monsterinfo.scale = MODEL_SCALE; + + flymonster_start(self); + + // PGM + if (strcmp(self->classname, "monster_daedalus") == 0) + self->s.skinnum = 2; + // PGM + + self->monsterinfo.aiflags |= AI_ALTERNATE_FLY; + hover_set_fly_parameters(self); +} diff --git a/rerelease/m_hover.h b/rerelease/m_hover.h new file mode 100644 index 0000000..b783364 --- /dev/null +++ b/rerelease/m_hover.h @@ -0,0 +1,216 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// G:\quake2\baseq2\models/monsters/hover + +// This file generated by ModelGen - Do NOT Modify + +enum +{ + FRAME_stand01, + FRAME_stand02, + FRAME_stand03, + FRAME_stand04, + FRAME_stand05, + FRAME_stand06, + FRAME_stand07, + FRAME_stand08, + FRAME_stand09, + FRAME_stand10, + FRAME_stand11, + FRAME_stand12, + FRAME_stand13, + FRAME_stand14, + FRAME_stand15, + FRAME_stand16, + FRAME_stand17, + FRAME_stand18, + FRAME_stand19, + FRAME_stand20, + FRAME_stand21, + FRAME_stand22, + FRAME_stand23, + FRAME_stand24, + FRAME_stand25, + FRAME_stand26, + FRAME_stand27, + FRAME_stand28, + FRAME_stand29, + FRAME_stand30, + FRAME_forwrd01, + FRAME_forwrd02, + FRAME_forwrd03, + FRAME_forwrd04, + FRAME_forwrd05, + FRAME_forwrd06, + FRAME_forwrd07, + FRAME_forwrd08, + FRAME_forwrd09, + FRAME_forwrd10, + FRAME_forwrd11, + FRAME_forwrd12, + FRAME_forwrd13, + FRAME_forwrd14, + FRAME_forwrd15, + FRAME_forwrd16, + FRAME_forwrd17, + FRAME_forwrd18, + FRAME_forwrd19, + FRAME_forwrd20, + FRAME_forwrd21, + FRAME_forwrd22, + FRAME_forwrd23, + FRAME_forwrd24, + FRAME_forwrd25, + FRAME_forwrd26, + FRAME_forwrd27, + FRAME_forwrd28, + FRAME_forwrd29, + FRAME_forwrd30, + FRAME_forwrd31, + FRAME_forwrd32, + FRAME_forwrd33, + FRAME_forwrd34, + FRAME_forwrd35, + FRAME_stop101, + FRAME_stop102, + FRAME_stop103, + FRAME_stop104, + FRAME_stop105, + FRAME_stop106, + FRAME_stop107, + FRAME_stop108, + FRAME_stop109, + FRAME_stop201, + FRAME_stop202, + FRAME_stop203, + FRAME_stop204, + FRAME_stop205, + FRAME_stop206, + FRAME_stop207, + FRAME_stop208, + FRAME_takeof01, + FRAME_takeof02, + FRAME_takeof03, + FRAME_takeof04, + FRAME_takeof05, + FRAME_takeof06, + FRAME_takeof07, + FRAME_takeof08, + FRAME_takeof09, + FRAME_takeof10, + FRAME_takeof11, + FRAME_takeof12, + FRAME_takeof13, + FRAME_takeof14, + FRAME_takeof15, + FRAME_takeof16, + FRAME_takeof17, + FRAME_takeof18, + FRAME_takeof19, + FRAME_takeof20, + FRAME_takeof21, + FRAME_takeof22, + FRAME_takeof23, + FRAME_takeof24, + FRAME_takeof25, + FRAME_takeof26, + FRAME_takeof27, + FRAME_takeof28, + FRAME_takeof29, + FRAME_takeof30, + FRAME_land01, + FRAME_pain101, + FRAME_pain102, + FRAME_pain103, + FRAME_pain104, + FRAME_pain105, + FRAME_pain106, + FRAME_pain107, + FRAME_pain108, + FRAME_pain109, + FRAME_pain110, + FRAME_pain111, + FRAME_pain112, + FRAME_pain113, + FRAME_pain114, + FRAME_pain115, + FRAME_pain116, + FRAME_pain117, + FRAME_pain118, + FRAME_pain119, + FRAME_pain120, + FRAME_pain121, + FRAME_pain122, + FRAME_pain123, + FRAME_pain124, + FRAME_pain125, + FRAME_pain126, + FRAME_pain127, + FRAME_pain128, + FRAME_pain201, + FRAME_pain202, + FRAME_pain203, + FRAME_pain204, + FRAME_pain205, + FRAME_pain206, + FRAME_pain207, + FRAME_pain208, + FRAME_pain209, + FRAME_pain210, + FRAME_pain211, + FRAME_pain212, + FRAME_pain301, + FRAME_pain302, + FRAME_pain303, + FRAME_pain304, + FRAME_pain305, + FRAME_pain306, + FRAME_pain307, + FRAME_pain308, + FRAME_pain309, + FRAME_death101, + FRAME_death102, + FRAME_death103, + FRAME_death104, + FRAME_death105, + FRAME_death106, + FRAME_death107, + FRAME_death108, + FRAME_death109, + FRAME_death110, + FRAME_death111, + FRAME_backwd01, + FRAME_backwd02, + FRAME_backwd03, + FRAME_backwd04, + FRAME_backwd05, + FRAME_backwd06, + FRAME_backwd07, + FRAME_backwd08, + FRAME_backwd09, + FRAME_backwd10, + FRAME_backwd11, + FRAME_backwd12, + FRAME_backwd13, + FRAME_backwd14, + FRAME_backwd15, + FRAME_backwd16, + FRAME_backwd17, + FRAME_backwd18, + FRAME_backwd19, + FRAME_backwd20, + FRAME_backwd21, + FRAME_backwd22, + FRAME_backwd23, + FRAME_backwd24, + FRAME_attak101, + FRAME_attak102, + FRAME_attak103, + FRAME_attak104, + FRAME_attak105, + FRAME_attak106, + FRAME_attak107, + FRAME_attak108 +}; + +constexpr float MODEL_SCALE = 1.000000f; diff --git a/rerelease/m_infantry.cpp b/rerelease/m_infantry.cpp new file mode 100644 index 0000000..18f9f20 --- /dev/null +++ b/rerelease/m_infantry.cpp @@ -0,0 +1,928 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +/* +============================================================================== + +INFANTRY + +============================================================================== +*/ + +#include "g_local.h" +#include "m_infantry.h" +#include "m_flash.h" + +void InfantryMachineGun(edict_t *self); + +static int sound_pain1; +static int sound_pain2; +static int sound_die1; +static int sound_die2; + +static int sound_gunshot; +static int sound_weapon_cock; +static int sound_punch_swing; +static int sound_punch_hit; +static int sound_sight; +static int sound_search; +static int sound_idle; + +// range at which we'll try to initiate a run-attack to close distance +constexpr float RANGE_RUN_ATTACK = RANGE_NEAR * 0.75f; + +mframe_t infantry_frames_stand[] = { + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand } +}; +MMOVE_T(infantry_move_stand) = { FRAME_stand50, FRAME_stand71, infantry_frames_stand, nullptr }; + +MONSTERINFO_STAND(infantry_stand) (edict_t *self) -> void +{ + M_SetAnimation(self, &infantry_move_stand); +} + +mframe_t infantry_frames_fidget[] = { + { ai_stand, 1 }, + { ai_stand }, + { ai_stand, 1 }, + { ai_stand, 3 }, + { ai_stand, 6 }, + { ai_stand, 3, monster_footstep }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand, 1 }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand, 1 }, + { ai_stand }, + { ai_stand, -1 }, + { ai_stand }, + { ai_stand }, + { ai_stand, 1 }, + { ai_stand }, + { ai_stand, -2 }, + { ai_stand, 1 }, + { ai_stand, 1 }, + { ai_stand, 1 }, + { ai_stand, -1 }, + { ai_stand }, + { ai_stand }, + { ai_stand, -1 }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand, -1 }, + { ai_stand }, + { ai_stand }, + { ai_stand, 1 }, + { ai_stand }, + { ai_stand }, + { ai_stand, -1 }, + { ai_stand, -1 }, + { ai_stand }, + { ai_stand, -3 }, + { ai_stand, -2 }, + { ai_stand, -3 }, + { ai_stand, -3, monster_footstep }, + { ai_stand, -2 } +}; +MMOVE_T(infantry_move_fidget) = { FRAME_stand01, FRAME_stand49, infantry_frames_fidget, infantry_stand }; + +MONSTERINFO_IDLE(infantry_fidget) (edict_t *self) -> void +{ + if (self->enemy) + return; + + M_SetAnimation(self, &infantry_move_fidget); + gi.sound(self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0); +} + +mframe_t infantry_frames_walk[] = { + { ai_walk, 5, monster_footstep }, + { ai_walk, 4 }, + { ai_walk, 4 }, + { ai_walk, 5 }, + { ai_walk, 4 }, + { ai_walk, 5 }, + { ai_walk, 6, monster_footstep }, + { ai_walk, 4 }, + { ai_walk, 4 }, + { ai_walk, 4 }, + { ai_walk, 4 }, + { ai_walk, 5 } +}; +MMOVE_T(infantry_move_walk) = { FRAME_walk03, FRAME_walk14, infantry_frames_walk, nullptr }; + +MONSTERINFO_WALK(infantry_walk) (edict_t *self) -> void +{ + M_SetAnimation(self, &infantry_move_walk); +} + +mframe_t infantry_frames_run[] = { + { ai_run, 10 }, + { ai_run, 15, monster_footstep }, + { ai_run, 5 }, + { ai_run, 7, monster_done_dodge }, + { ai_run, 18 }, + { ai_run, 20, monster_footstep }, + { ai_run, 2 }, + { ai_run, 6 } +}; +MMOVE_T(infantry_move_run) = { FRAME_run01, FRAME_run08, infantry_frames_run, nullptr }; + +MONSTERINFO_RUN(infantry_run) (edict_t *self) -> void +{ + monster_done_dodge(self); + + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + M_SetAnimation(self, &infantry_move_stand); + else + M_SetAnimation(self, &infantry_move_run); +} + +mframe_t infantry_frames_pain1[] = { + { ai_move, -3 }, + { ai_move, -2 }, + { ai_move, -1 }, + { ai_move, -2 }, + { ai_move, -1, monster_footstep }, + { ai_move, 1 }, + { ai_move, -1 }, + { ai_move, 1 }, + { ai_move, 6 }, + { ai_move, 2, monster_footstep } +}; +MMOVE_T(infantry_move_pain1) = { FRAME_pain101, FRAME_pain110, infantry_frames_pain1, infantry_run }; + +mframe_t infantry_frames_pain2[] = { + { ai_move, -3 }, + { ai_move, -3 }, + { ai_move }, + { ai_move, -1 }, + { ai_move, -2, monster_footstep }, + { ai_move }, + { ai_move }, + { ai_move, 2 }, + { ai_move, 5 }, + { ai_move, 2, monster_footstep } +}; +MMOVE_T(infantry_move_pain2) = { FRAME_pain201, FRAME_pain210, infantry_frames_pain2, infantry_run }; + +extern const mmove_t infantry_move_jump; +extern const mmove_t infantry_move_jump2; + +PAIN(infantry_pain) (edict_t *self, edict_t *other, float kick, int damage, const mod_t &mod) -> void +{ + int n; + + // allow turret to pain + if ((self->monsterinfo.active_move == &infantry_move_jump || + self->monsterinfo.active_move == &infantry_move_jump2) && self->think == monster_think) + return; + + monster_done_dodge(self); + + if (level.time < self->pain_debounce_time) + { + if (self->think == monster_think && frandom() < 0.33f) + self->monsterinfo.dodge(self, other, FRAME_TIME_S, nullptr, false); + + return; + } + + self->pain_debounce_time = level.time + 3_sec; + + n = brandom(); + + if (n == 0) + gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); + else + gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0); + + if (self->think != monster_think) + return; + + if (!M_ShouldReactToPain(self, mod)) + { + if (self->think == monster_think && frandom() < 0.33f) + self->monsterinfo.dodge(self, other, FRAME_TIME_S, nullptr, false); + + return; // no pain anims in nightmare + } + + if (n == 0) + M_SetAnimation(self, &infantry_move_pain1); + else + M_SetAnimation(self, &infantry_move_pain2); + + // PMM - clear duck flag + if (self->monsterinfo.aiflags & AI_DUCKED) + monster_duck_up(self); +} + +MONSTERINFO_SETSKIN(infantry_setskin) (edict_t *self) -> void +{ + if (self->health < (self->max_health / 2)) + self->s.skinnum = 1; + else + self->s.skinnum = 0; +} + +constexpr vec3_t aimangles[] = { + { 0.0f, 5.0f, 0.0f }, + { 10.0f, 15.0f, 0.0f }, + { 20.0f, 25.0f, 0.0f }, + { 25.0f, 35.0f, 0.0f }, + { 30.0f, 40.0f, 0.0f }, + { 30.0f, 45.0f, 0.0f }, + { 25.0f, 50.0f, 0.0f }, + { 20.0f, 40.0f, 0.0f }, + { 15.0f, 35.0f, 0.0f }, + { 40.0f, 35.0f, 0.0f }, + { 70.0f, 35.0f, 0.0f }, + { 90.0f, 35.0f, 0.0f } +}; + +void InfantryMachineGun(edict_t *self) +{ + vec3_t start; + vec3_t forward, right; + vec3_t vec; + monster_muzzleflash_id_t flash_number; + + if (!self->enemy || !self->enemy->inuse) // PGM + return; // PGM + + bool is_run_attack = (self->s.frame >= FRAME_run201 && self->s.frame <= FRAME_run208); + + if (self->s.frame == FRAME_attak103 || self->s.frame == FRAME_attak311 || is_run_attack || self->s.frame == FRAME_attak416) + { + if (is_run_attack) + flash_number = static_cast(MZ2_INFANTRY_MACHINEGUN_14 + (self->s.frame - MZ2_INFANTRY_MACHINEGUN_14)); + else if (self->s.frame == FRAME_attak416) + flash_number = MZ2_INFANTRY_MACHINEGUN_22; + else + flash_number = MZ2_INFANTRY_MACHINEGUN_1; + AngleVectors(self->s.angles, forward, right, nullptr); + start = M_ProjectFlashSource(self, monster_flash_offset[flash_number], forward, right); + + if (self->enemy) + PredictAim(self, self->enemy, start, 0, true, -0.2f, &forward, nullptr); + else + { + AngleVectors(self->s.angles, forward, right, nullptr); + } + } + else + { + flash_number = static_cast(MZ2_INFANTRY_MACHINEGUN_2 + (self->s.frame - FRAME_death211)); + + AngleVectors(self->s.angles, forward, right, nullptr); + start = M_ProjectFlashSource(self, monster_flash_offset[flash_number], forward, right); + + vec = self->s.angles - aimangles[flash_number - MZ2_INFANTRY_MACHINEGUN_2]; + AngleVectors(vec, forward, nullptr, nullptr); + } + + monster_fire_bullet(self, start, forward, 3, 4, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, flash_number); +} + +MONSTERINFO_SIGHT(infantry_sight) (edict_t *self, edict_t *other) -> void +{ + if (brandom()) + gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); + else + gi.sound(self, CHAN_VOICE, sound_search, 1, ATTN_NORM, 0); +} + +void infantry_dead(edict_t *self) +{ + self->mins = { -16, -16, -24 }; + self->maxs = { 16, 16, -8 }; + monster_dead(self); +} + +static void infantry_shrink(edict_t *self) +{ + self->maxs[2] = 0; + self->svflags |= SVF_DEADMONSTER; + gi.linkentity(self); +} + +mframe_t infantry_frames_death1[] = { + { ai_move, -4, nullptr, FRAME_death102 }, + { ai_move }, + { ai_move }, + { ai_move, -1 }, + { ai_move, -4, monster_footstep }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move, -1, monster_footstep }, + { ai_move, 3 }, + { ai_move, 1 }, + { ai_move, 1 }, + { ai_move, -2 }, + { ai_move, 2 }, + { ai_move, 2 }, + { ai_move, 9, [](edict_t *self) { infantry_shrink(self); monster_footstep(self); } }, + { ai_move, 9 }, + { ai_move, 5, monster_footstep }, + { ai_move, -3 }, + { ai_move, -3 } +}; +MMOVE_T(infantry_move_death1) = { FRAME_death101, FRAME_death120, infantry_frames_death1, infantry_dead }; + +// Off with his head +mframe_t infantry_frames_death2[] = { + { ai_move, 0, nullptr, FRAME_death202 }, + { ai_move, 1 }, + { ai_move, 5 }, + { ai_move, -1 }, + { ai_move }, + { ai_move, 1, monster_footstep }, + { ai_move, 1, monster_footstep }, + { ai_move, 4 }, + { ai_move, 3 }, + { ai_move }, + { ai_move, -2, InfantryMachineGun }, + { ai_move, -2, InfantryMachineGun }, + { ai_move, -3, InfantryMachineGun }, + { ai_move, -1, InfantryMachineGun }, + { ai_move, -2, InfantryMachineGun }, + { ai_move, 0, InfantryMachineGun }, + { ai_move, 2, InfantryMachineGun }, + { ai_move, 2, InfantryMachineGun }, + { ai_move, 3, InfantryMachineGun }, + { ai_move, -10, InfantryMachineGun }, + { ai_move, -7, InfantryMachineGun }, + { ai_move, -8, InfantryMachineGun }, + { ai_move, -6, [](edict_t *self) { infantry_shrink(self); monster_footstep(self); } }, + { ai_move, 4 }, + { ai_move } +}; +MMOVE_T(infantry_move_death2) = { FRAME_death201, FRAME_death225, infantry_frames_death2, infantry_dead }; + +mframe_t infantry_frames_death3[] = { + { ai_move, 0 }, + { ai_move }, + { ai_move, 0, [](edict_t *self) { infantry_shrink(self); monster_footstep(self); } }, + { ai_move, -6 }, + { ai_move, -11, [](edict_t *self) { self->monsterinfo.nextframe = FRAME_death307; } }, + { ai_move, -3 }, + { ai_move, -11 }, + { ai_move, 0, monster_footstep }, + { ai_move } +}; +MMOVE_T(infantry_move_death3) = { FRAME_death301, FRAME_death309, infantry_frames_death3, infantry_dead }; + +DIE(infantry_die) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void +{ + int n; + + // check for gib + if (M_CheckGib(self, mod)) + { + gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0); + + const char *head_gib = (self->monsterinfo.active_move != &infantry_move_death3) ? "models/objects/gibs/sm_meat/tris.md2" : "models/monsters/infantry/gibs/head.md2"; + + self->s.skinnum /= 2; + + ThrowGibs(self, damage, { + { "models/objects/gibs/bone/tris.md2" }, + { 3, "models/objects/gibs/sm_meat/tris.md2" }, + { "models/monsters/infantry/gibs/chest.md2", GIB_SKINNED }, + { "models/monsters/infantry/gibs/gun.md2", GIB_SKINNED | GIB_UPRIGHT }, + { 2, "models/monsters/infantry/gibs/foot.md2", GIB_SKINNED }, + { 2, "models/monsters/infantry/gibs/arm.md2", GIB_SKINNED }, + { head_gib, GIB_HEAD | GIB_SKINNED } + }); + self->deadflag = true; + return; + } + + if (self->deadflag) + return; + + // regular death + self->deadflag = true; + self->takedamage = true; + + n = irandom(3); + + if (n == 0) + { + M_SetAnimation(self, &infantry_move_death1); + gi.sound(self, CHAN_VOICE, sound_die2, 1, ATTN_NORM, 0); + } + else if (n == 1) + { + M_SetAnimation(self, &infantry_move_death2); + gi.sound(self, CHAN_VOICE, sound_die1, 1, ATTN_NORM, 0); + } + else + { + M_SetAnimation(self, &infantry_move_death3); + gi.sound(self, CHAN_VOICE, sound_die2, 1, ATTN_NORM, 0); + } + + // don't always pop a head gib, it gets old + if (n != 2 && frandom() <= 0.25f) + { + edict_t *head = ThrowGib(self, "models/monsters/infantry/gibs/head.md2", damage, GIB_NONE, self->s.scale); + + if (head) + { + head->s.angles = self->s.angles; + head->s.origin = self->s.origin + vec3_t{0, 0, 32.f}; + vec3_t headDir = (self->s.origin - inflictor->s.origin); + head->velocity = headDir / headDir.length() * 100.0f; + head->velocity[2] = 200.0f; + head->avelocity *= 0.15f; + head->s.skinnum = 0; + gi.linkentity(head); + } + } +} + +mframe_t infantry_frames_duck[] = { + { ai_move, -2, monster_duck_down }, + { ai_move, -5, monster_duck_hold }, + { ai_move, 3 }, + { ai_move, 4, monster_duck_up }, + { ai_move } +}; +MMOVE_T(infantry_move_duck) = { FRAME_duck01, FRAME_duck05, infantry_frames_duck, infantry_run }; + +// PMM - dodge code moved below so I can see the attack frames + +extern const mmove_t infantry_move_attack4; + +void infantry_set_firetime(edict_t *self) +{ + self->monsterinfo.fire_wait = level.time + random_time(0.7_sec, 2_sec); + + if (!(self->monsterinfo.aiflags & AI_STAND_GROUND) && self->enemy && range_to(self, self->enemy) >= RANGE_RUN_ATTACK && ai_check_move(self, 8.0f)) + M_SetAnimation(self, &infantry_move_attack4, false); +} + +void infantry_cock_gun(edict_t *self) +{ + gi.sound(self, CHAN_WEAPON, sound_weapon_cock, 1, ATTN_NORM, 0); + + // gun cocked + self->count = 1; +} + +void infantry_fire(edict_t *self); + +// cock-less attack, used if he has already cocked his gun +mframe_t infantry_frames_attack1[] = { + { ai_charge }, + { ai_charge, 6, [](edict_t *self) { infantry_set_firetime(self); monster_footstep(self); } }, + { ai_charge, 0, infantry_fire }, + { ai_charge }, + { ai_charge, 1 }, + { ai_charge, -7 }, + { ai_charge, -6, [](edict_t *self) { self->monsterinfo.nextframe = FRAME_attak114; monster_footstep(self); } }, + // dead frames start + { ai_charge, -1 }, + { ai_charge, 0, infantry_cock_gun }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + // dead frames end + { ai_charge, -1 }, + { ai_charge, -1 } +}; +MMOVE_T(infantry_move_attack1) = { FRAME_attak101, FRAME_attak115, infantry_frames_attack1, infantry_run }; + +// old animation, full cock + shoot +mframe_t infantry_frames_attack3[] = { + { ai_charge, 4, NULL }, + { ai_charge, -1, NULL }, + { ai_charge, -1, NULL }, + { ai_charge, 0, infantry_cock_gun }, + { ai_charge, -1, NULL }, + { ai_charge, 1, NULL }, + { ai_charge, 1, NULL }, + { ai_charge, 2, NULL }, + { ai_charge, -2, NULL }, + { ai_charge, -3, [](edict_t *self) { infantry_set_firetime(self); monster_footstep(self); } }, + { ai_charge, 1, infantry_fire }, + { ai_charge, 5, NULL }, + { ai_charge, -1, NULL }, + { ai_charge, -2, NULL }, + { ai_charge, -3, NULL }, +}; +MMOVE_T(infantry_move_attack3) = { FRAME_attak301, FRAME_attak315, infantry_frames_attack3, infantry_run }; + +// even older animation, full cock + shoot +mframe_t infantry_frames_attack5[] = { + // skipped frames + { ai_charge, 0, NULL }, + { ai_charge, 0, NULL }, + { ai_charge, 0, NULL }, + { ai_charge, 0, NULL }, + + { ai_charge, 0, NULL }, + { ai_charge, 0, nullptr }, + { ai_charge, 0, monster_footstep }, + { ai_charge, 0, infantry_cock_gun }, + { ai_charge, 0, NULL }, + { ai_charge, 0, NULL }, + { ai_charge, 0, [](edict_t *self) { self->monsterinfo.nextframe = self->s.frame + 1; } }, + { ai_charge, 0, NULL }, // skipped frame + { ai_charge, 0, NULL }, + { ai_charge, 0, nullptr }, + { ai_charge, 0, infantry_set_firetime }, + { ai_charge, 0, infantry_fire }, + + // skipped frames + { ai_charge, 0, NULL }, + { ai_charge, 0, NULL }, + { ai_charge, 0, NULL }, + + { ai_charge, 0, NULL }, + { ai_charge, 0, NULL }, + { ai_charge, 0, NULL }, + { ai_charge, 0, monster_footstep } +}; +MMOVE_T(infantry_move_attack5) = { FRAME_attak401, FRAME_attak423, infantry_frames_attack5, infantry_run }; + +extern const mmove_t infantry_move_attack4; + +void infantry_fire(edict_t *self) +{ + InfantryMachineGun(self); + + // we fired, so we must cock again before firing + self->count = 0; + + // check if we ran out of firing time + if (self->monsterinfo.active_move == &infantry_move_attack4) + { + if (level.time >= self->monsterinfo.fire_wait) + { + monster_done_dodge(self); + M_SetAnimation(self, &infantry_move_attack1, false); + self->monsterinfo.nextframe = FRAME_attak114; + } + // got close to an edge + else if (!ai_check_move(self, 8.0f)) + { + M_SetAnimation(self, &infantry_move_attack1, false); + self->monsterinfo.nextframe = FRAME_attak103; + monster_done_dodge(self); + self->monsterinfo.attack_state = AS_STRAIGHT; + } + } + else if ((self->s.frame >= FRAME_attak101 && self->s.frame <= FRAME_attak115) || + (self->s.frame >= FRAME_attak301 && self->s.frame <= FRAME_attak315) || + (self->s.frame >= FRAME_attak401 && self->s.frame <= FRAME_attak424)) + { + if (level.time >= self->monsterinfo.fire_wait) + { + self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; + + if (self->s.frame == FRAME_attak416) + self->monsterinfo.nextframe = FRAME_attak420; + } + else + self->monsterinfo.aiflags |= AI_HOLD_FRAME; + } +} + +void infantry_swing(edict_t *self) +{ + gi.sound(self, CHAN_WEAPON, sound_punch_swing, 1, ATTN_NORM, 0); +} + +void infantry_smack(edict_t *self) +{ + vec3_t aim = { MELEE_DISTANCE, 0, 0 }; + + if (fire_hit(self, aim, irandom(5, 10), 50)) + gi.sound(self, CHAN_WEAPON, sound_punch_hit, 1, ATTN_NORM, 0); + else + self->monsterinfo.melee_debounce_time = level.time + 1.5_sec; +} + +mframe_t infantry_frames_attack2[] = { + { ai_charge, 3 }, + { ai_charge, 6 }, + { ai_charge, 0, infantry_swing }, + { ai_charge, 8, monster_footstep }, + { ai_charge, 5 }, + { ai_charge, 8, infantry_smack }, + { ai_charge, 6 }, + { ai_charge, 3 } +}; +MMOVE_T(infantry_move_attack2) = { FRAME_attak201, FRAME_attak208, infantry_frames_attack2, infantry_run }; + +// [Paril-KEX] run-attack, inspired by q2test +void infantry_attack4_refire(edict_t *self) +{ + // ran out of firing time + if (level.time >= self->monsterinfo.fire_wait) + { + monster_done_dodge(self); + M_SetAnimation(self, &infantry_move_attack1, false); + self->monsterinfo.nextframe = FRAME_attak114; + } + // we got too close, or we can't move forward, switch us back to regular attack + else if ((self->monsterinfo.aiflags & AI_STAND_GROUND) || (self->enemy && (range_to(self, self->enemy) < RANGE_RUN_ATTACK || !ai_check_move(self, 8.0f)))) + { + M_SetAnimation(self, &infantry_move_attack1, false); + self->monsterinfo.nextframe = FRAME_attak103; + monster_done_dodge(self); + self->monsterinfo.attack_state = AS_STRAIGHT; + } + else + self->monsterinfo.nextframe = FRAME_run201; + + infantry_fire(self); +} + +mframe_t infantry_frames_attack4[] = { + { ai_charge, 16, infantry_fire }, + { ai_charge, 16, [](edict_t *self) { monster_footstep(self); infantry_fire(self); } }, + { ai_charge, 13, infantry_fire }, + { ai_charge, 10, infantry_fire }, + { ai_charge, 16, infantry_fire }, + { ai_charge, 16, [](edict_t *self) { monster_footstep(self); infantry_fire(self); } }, + { ai_charge, 16, infantry_fire }, + { ai_charge, 16, infantry_attack4_refire } +}; +MMOVE_T(infantry_move_attack4) = { FRAME_run201, FRAME_run208, infantry_frames_attack4, infantry_run, 0.5f }; + +MONSTERINFO_ATTACK(infantry_attack) (edict_t *self) -> void +{ + monster_done_dodge(self); + + float r = range_to(self, self->enemy); + + if (r <= RANGE_MELEE && self->monsterinfo.melee_debounce_time <= level.time) + M_SetAnimation(self, &infantry_move_attack2); + else if (M_CheckClearShot(self, monster_flash_offset[MZ2_INFANTRY_MACHINEGUN_1])) + { + if (self->count) + M_SetAnimation(self, &infantry_move_attack1); + else + { + M_SetAnimation(self, frandom() <= 0.1f ? &infantry_move_attack5 : &infantry_move_attack3); + self->monsterinfo.nextframe = FRAME_attak405; + } + } +} + +//=========== +// PGM +void infantry_jump_now(edict_t *self) +{ + vec3_t forward, up; + + AngleVectors(self->s.angles, forward, nullptr, up); + self->velocity += (forward * 100); + self->velocity += (up * 300); +} + +void infantry_jump2_now(edict_t *self) +{ + vec3_t forward, up; + + AngleVectors(self->s.angles, forward, nullptr, up); + self->velocity += (forward * 150); + self->velocity += (up * 400); +} + +void infantry_jump_wait_land(edict_t *self) +{ + if (self->groundentity == nullptr) + { + self->monsterinfo.nextframe = self->s.frame; + + if (monster_jump_finished(self)) + self->monsterinfo.nextframe = self->s.frame + 1; + } + else + self->monsterinfo.nextframe = self->s.frame + 1; +} + +mframe_t infantry_frames_jump[] = { + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move, 0, infantry_jump_now }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move, 0, infantry_jump_wait_land }, + { ai_move }, + { ai_move } +}; +MMOVE_T(infantry_move_jump) = { FRAME_jump01, FRAME_jump10, infantry_frames_jump, infantry_run }; + +mframe_t infantry_frames_jump2[] = { + { ai_move, -8 }, + { ai_move, -4 }, + { ai_move, -4 }, + { ai_move, 0, infantry_jump2_now }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move, 0, infantry_jump_wait_land }, + { ai_move }, + { ai_move } +}; +MMOVE_T(infantry_move_jump2) = { FRAME_jump01, FRAME_jump10, infantry_frames_jump2, infantry_run }; + +void infantry_jump(edict_t *self, blocked_jump_result_t result) +{ + if (!self->enemy) + return; + + monster_done_dodge(self); + + if (result == blocked_jump_result_t::JUMP_JUMP_UP) + M_SetAnimation(self, &infantry_move_jump2); + else + M_SetAnimation(self, &infantry_move_jump); +} + +MONSTERINFO_BLOCKED(infantry_blocked) (edict_t *self, float dist) -> bool +{ + if (auto result = blocked_checkjump(self, dist); result != blocked_jump_result_t::NO_JUMP) + { + if (result != blocked_jump_result_t::JUMP_TURN) + infantry_jump(self, result); + return true; + } + + if (blocked_checkplat(self, dist)) + return true; + + return false; +} + +MONSTERINFO_DUCK(infantry_duck) (edict_t *self, gtime_t eta) -> bool +{ + // if we're jumping, don't dodge + if ((self->monsterinfo.active_move == &infantry_move_jump) || + (self->monsterinfo.active_move == &infantry_move_jump2)) + { + return false; + } + + // don't duck during our firing or melee frames + if (self->s.frame == FRAME_attak103 || + self->s.frame == FRAME_attak315 || + (self->monsterinfo.active_move == &infantry_move_attack2)) + { + self->monsterinfo.unduck(self); + return false; + } + + M_SetAnimation(self, &infantry_move_duck); + + return true; +} + +MONSTERINFO_SIDESTEP(infantry_sidestep) (edict_t *self) -> bool +{ + // if we're jumping, don't dodge + if ((self->monsterinfo.active_move == &infantry_move_jump) || + (self->monsterinfo.active_move == &infantry_move_jump2)) + { + return false; + } + + if (self->monsterinfo.active_move == &infantry_move_run) + return true; + + // Don't sidestep if we're already sidestepping, and def not unless we're actually shooting + // or if we already cocked + if (self->monsterinfo.active_move != &infantry_move_attack4 && + self->monsterinfo.next_move != &infantry_move_attack4 && + ((self->s.frame == FRAME_attak103 || + self->s.frame == FRAME_attak311 || + self->s.frame == FRAME_attak416) && + !self->count)) + { + // give us a fire time boost so we don't end up firing for 1 frame + self->monsterinfo.fire_wait += random_time(300_ms, 600_ms); + + M_SetAnimation(self, &infantry_move_attack4, false); + } + + return true; +} + +void InfantryPrecache() +{ + sound_pain1 = gi.soundindex("infantry/infpain1.wav"); + sound_pain2 = gi.soundindex("infantry/infpain2.wav"); + sound_die1 = gi.soundindex("infantry/infdeth1.wav"); + sound_die2 = gi.soundindex("infantry/infdeth2.wav"); + + sound_gunshot = gi.soundindex("infantry/infatck1.wav"); + sound_weapon_cock = gi.soundindex("infantry/infatck3.wav"); + sound_punch_swing = gi.soundindex("infantry/infatck2.wav"); + sound_punch_hit = gi.soundindex("infantry/melee2.wav"); + + sound_sight = gi.soundindex("infantry/infsght1.wav"); + sound_search = gi.soundindex("infantry/infsrch1.wav"); + sound_idle = gi.soundindex("infantry/infidle1.wav"); +} + +constexpr spawnflags_t SPAWNFLAG_INFANTRY_NOJUMPING = 8_spawnflag; + +/*QUAKED monster_infantry (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight NoJumping + */ +void SP_monster_infantry(edict_t *self) +{ + if ( !M_AllowSpawn( self ) ) { + G_FreeEdict( self ); + return; + } + + InfantryPrecache(); + + self->monsterinfo.aiflags |= AI_STINKY; + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex("models/monsters/infantry/tris.md2"); + + gi.modelindex("models/monsters/infantry/gibs/head.md2"); + gi.modelindex("models/monsters/infantry/gibs/chest.md2"); + gi.modelindex("models/monsters/infantry/gibs/gun.md2"); + gi.modelindex("models/monsters/infantry/gibs/arm.md2"); + gi.modelindex("models/monsters/infantry/gibs/foot.md2"); + + self->mins = { -16, -16, -24 }; + self->maxs = { 16, 16, 32 }; + + self->health = 100 * st.health_multiplier; + self->gib_health = -65; + self->mass = 200; + + self->pain = infantry_pain; + self->die = infantry_die; + + self->monsterinfo.combat_style = COMBAT_MIXED; + + self->monsterinfo.stand = infantry_stand; + self->monsterinfo.walk = infantry_walk; + self->monsterinfo.run = infantry_run; + // pmm + self->monsterinfo.dodge = M_MonsterDodge; + self->monsterinfo.duck = infantry_duck; + self->monsterinfo.unduck = monster_duck_up; + self->monsterinfo.sidestep = infantry_sidestep; + self->monsterinfo.blocked = infantry_blocked; + // pmm + self->monsterinfo.attack = infantry_attack; + self->monsterinfo.melee = nullptr; + self->monsterinfo.sight = infantry_sight; + self->monsterinfo.idle = infantry_fidget; + self->monsterinfo.setskin = infantry_setskin; + + gi.linkentity(self); + + M_SetAnimation(self, &infantry_move_stand); + self->monsterinfo.scale = MODEL_SCALE; + self->monsterinfo.can_jump = !self->spawnflags.has(SPAWNFLAG_INFANTRY_NOJUMPING); + self->monsterinfo.drop_height = 192; + self->monsterinfo.jump_height = 40; + + walkmonster_start(self); +} diff --git a/rerelease/m_infantry.h b/rerelease/m_infantry.h new file mode 100644 index 0000000..4912f83 --- /dev/null +++ b/rerelease/m_infantry.h @@ -0,0 +1,280 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// G:\quake2\baseq2\models/monsters/infantry + +// This file generated by ModelGen - Do NOT Modify + +enum +{ + FRAME_gun02, + FRAME_stand01, + FRAME_stand02, + FRAME_stand03, + FRAME_stand04, + FRAME_stand05, + FRAME_stand06, + FRAME_stand07, + FRAME_stand08, + FRAME_stand09, + FRAME_stand10, + FRAME_stand11, + FRAME_stand12, + FRAME_stand13, + FRAME_stand14, + FRAME_stand15, + FRAME_stand16, + FRAME_stand17, + FRAME_stand18, + FRAME_stand19, + FRAME_stand20, + FRAME_stand21, + FRAME_stand22, + FRAME_stand23, + FRAME_stand24, + FRAME_stand25, + FRAME_stand26, + FRAME_stand27, + FRAME_stand28, + FRAME_stand29, + FRAME_stand30, + FRAME_stand31, + FRAME_stand32, + FRAME_stand33, + FRAME_stand34, + FRAME_stand35, + FRAME_stand36, + FRAME_stand37, + FRAME_stand38, + FRAME_stand39, + FRAME_stand40, + FRAME_stand41, + FRAME_stand42, + FRAME_stand43, + FRAME_stand44, + FRAME_stand45, + FRAME_stand46, + FRAME_stand47, + FRAME_stand48, + FRAME_stand49, + FRAME_stand50, + FRAME_stand51, + FRAME_stand52, + FRAME_stand53, + FRAME_stand54, + FRAME_stand55, + FRAME_stand56, + FRAME_stand57, + FRAME_stand58, + FRAME_stand59, + FRAME_stand60, + FRAME_stand61, + FRAME_stand62, + FRAME_stand63, + FRAME_stand64, + FRAME_stand65, + FRAME_stand66, + FRAME_stand67, + FRAME_stand68, + FRAME_stand69, + FRAME_stand70, + FRAME_stand71, + FRAME_walk01, + FRAME_walk02, + FRAME_walk03, + FRAME_walk04, + FRAME_walk05, + FRAME_walk06, + FRAME_walk07, + FRAME_walk08, + FRAME_walk09, + FRAME_walk10, + FRAME_walk11, + FRAME_walk12, + FRAME_walk13, + FRAME_walk14, + FRAME_walk15, + FRAME_walk16, + FRAME_walk17, + FRAME_walk18, + FRAME_walk19, + FRAME_walk20, + FRAME_run01, + FRAME_run02, + FRAME_run03, + FRAME_run04, + FRAME_run05, + FRAME_run06, + FRAME_run07, + FRAME_run08, + FRAME_pain101, + FRAME_pain102, + FRAME_pain103, + FRAME_pain104, + FRAME_pain105, + FRAME_pain106, + FRAME_pain107, + FRAME_pain108, + FRAME_pain109, + FRAME_pain110, + FRAME_pain201, + FRAME_pain202, + FRAME_pain203, + FRAME_pain204, + FRAME_pain205, + FRAME_pain206, + FRAME_pain207, + FRAME_pain208, + FRAME_pain209, + FRAME_pain210, + FRAME_duck01, + FRAME_duck02, + FRAME_duck03, + FRAME_duck04, + FRAME_duck05, + FRAME_death101, + FRAME_death102, + FRAME_death103, + FRAME_death104, + FRAME_death105, + FRAME_death106, + FRAME_death107, + FRAME_death108, + FRAME_death109, + FRAME_death110, + FRAME_death111, + FRAME_death112, + FRAME_death113, + FRAME_death114, + FRAME_death115, + FRAME_death116, + FRAME_death117, + FRAME_death118, + FRAME_death119, + FRAME_death120, + FRAME_death201, + FRAME_death202, + FRAME_death203, + FRAME_death204, + FRAME_death205, + FRAME_death206, + FRAME_death207, + FRAME_death208, + FRAME_death209, + FRAME_death210, + FRAME_death211, + FRAME_death212, + FRAME_death213, + FRAME_death214, + FRAME_death215, + FRAME_death216, + FRAME_death217, + FRAME_death218, + FRAME_death219, + FRAME_death220, + FRAME_death221, + FRAME_death222, + FRAME_death223, + FRAME_death224, + FRAME_death225, + FRAME_death301, + FRAME_death302, + FRAME_death303, + FRAME_death304, + FRAME_death305, + FRAME_death306, + FRAME_death307, + FRAME_death308, + FRAME_death309, + FRAME_block01, + FRAME_block02, + FRAME_block03, + FRAME_block04, + FRAME_block05, + FRAME_attak101, + FRAME_attak102, + FRAME_attak103, + FRAME_attak104, + FRAME_attak105, + FRAME_attak106, + FRAME_attak107, + FRAME_attak108, + FRAME_attak109, + FRAME_attak110, + FRAME_attak111, + FRAME_attak112, + FRAME_attak113, + FRAME_attak114, + FRAME_attak115, + FRAME_attak201, + FRAME_attak202, + FRAME_attak203, + FRAME_attak204, + FRAME_attak205, + FRAME_attak206, + FRAME_attak207, + FRAME_attak208, + // ROGUE + FRAME_jump01, + FRAME_jump02, + FRAME_jump03, + FRAME_jump04, + FRAME_jump05, + FRAME_jump06, + FRAME_jump07, + FRAME_jump08, + FRAME_jump09, + FRAME_jump10, + // ROGUE + // [Paril-KEX] old attack, for demos + FRAME_attak301, + FRAME_attak302, + FRAME_attak303, + FRAME_attak304, + FRAME_attak305, + FRAME_attak306, + FRAME_attak307, + FRAME_attak308, + FRAME_attak309, + FRAME_attak310, + FRAME_attak311, + FRAME_attak312, + FRAME_attak313, + FRAME_attak314, + FRAME_attak315, + // [Paril-KEX] run attack + FRAME_run201, + FRAME_run202, + FRAME_run203, + FRAME_run204, + FRAME_run205, + FRAME_run206, + FRAME_run207, + FRAME_run208, + // [Paril-KEX] + FRAME_attak401, + FRAME_attak402, + FRAME_attak403, + FRAME_attak404, + FRAME_attak405, + FRAME_attak406, + FRAME_attak407, + FRAME_attak408, + FRAME_attak409, + FRAME_attak410, + FRAME_attak411, + FRAME_attak412, + FRAME_attak413, + FRAME_attak414, + FRAME_attak415, + FRAME_attak416, + FRAME_attak417, + FRAME_attak418, + FRAME_attak419, + FRAME_attak420, + FRAME_attak421, + FRAME_attak422, + FRAME_attak423, + FRAME_attak424 +}; + +constexpr float MODEL_SCALE = 1.000000f; diff --git a/rerelease/m_insane.cpp b/rerelease/m_insane.cpp new file mode 100644 index 0000000..acdcc1a --- /dev/null +++ b/rerelease/m_insane.cpp @@ -0,0 +1,692 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +/* +============================================================================== + +insane + +============================================================================== +*/ + +#include "g_local.h" +#include "m_insane.h" + +constexpr spawnflags_t SPAWNFLAG_INSANE_CRAWL = 4_spawnflag; +constexpr spawnflags_t SPAWNFLAG_INSANE_CRUCIFIED = 8_spawnflag; +constexpr spawnflags_t SPAWNFLAG_INSANE_STAND_GROUND = 16_spawnflag; +constexpr spawnflags_t SPAWNFLAG_INSANE_ALWAYS_STAND = 32_spawnflag; +constexpr spawnflags_t SPAWNFLAG_INSANE_QUIET = 64_spawnflag; + +static int sound_fist; +static int sound_shake; +static int sound_moan; +static int sound_scream[8]; + +void insane_fist(edict_t *self) +{ + gi.sound(self, CHAN_VOICE, sound_fist, 1, ATTN_IDLE, 0); +} + +void insane_shake(edict_t *self) +{ + if (!self->spawnflags.has(SPAWNFLAG_INSANE_QUIET)) + gi.sound(self, CHAN_VOICE, sound_shake, 1, ATTN_IDLE, 0); +} + +extern const mmove_t insane_move_cross, insane_move_struggle_cross; + +void insane_moan(edict_t *self) +{ + if (self->spawnflags.has(SPAWNFLAG_INSANE_QUIET)) + return; + + // Paril: don't moan every second + if (self->monsterinfo.attack_finished < level.time) + { + gi.sound(self, CHAN_VOICE, sound_moan, 1, ATTN_IDLE, 0); + self->monsterinfo.attack_finished = level.time + random_time(1_sec, 3_sec); + } +} + +void insane_scream(edict_t *self) +{ + if (self->spawnflags.has(SPAWNFLAG_INSANE_QUIET)) + return; + + // Paril: don't moan every second + if (self->monsterinfo.attack_finished < level.time) + { + gi.sound(self, CHAN_VOICE, random_element(sound_scream), 1, ATTN_IDLE, 0); + self->monsterinfo.attack_finished = level.time + random_time(1_sec, 3_sec); + } +} + +void insane_stand(edict_t *self); +void insane_dead(edict_t *self); +void insane_cross(edict_t *self); +void insane_walk(edict_t *self); +void insane_run(edict_t *self); +void insane_checkdown(edict_t *self); +void insane_checkup(edict_t *self); +void insane_onground(edict_t *self); + +// Paril: unused atm because it breaks N64. +// may fix later +void insane_shrink(edict_t *self) +{ + self->maxs[2] = 0; + self->svflags |= SVF_DEADMONSTER; + gi.linkentity(self); +} + +mframe_t insane_frames_stand_normal[] = { + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand, 0, insane_checkdown } +}; +MMOVE_T(insane_move_stand_normal) = { FRAME_stand60, FRAME_stand65, insane_frames_stand_normal, insane_stand }; + +mframe_t insane_frames_stand_insane[] = { + { ai_stand, 0, insane_shake }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand, 0, insane_checkdown } +}; +MMOVE_T(insane_move_stand_insane) = { FRAME_stand65, FRAME_stand94, insane_frames_stand_insane, insane_stand }; + +mframe_t insane_frames_uptodown[] = { + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move, 0, insane_moan }, + { ai_move },//, 0, monster_duck_down }, + { ai_move }, + + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + + { ai_move, 2.7f }, + { ai_move, 4.1f }, + { ai_move, 6 }, + { ai_move, 7.6f }, + { ai_move, 3.6f }, + { ai_move }, + { ai_move }, + { ai_move, 0, insane_fist }, + { ai_move }, + { ai_move }, + + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move, 0, insane_fist }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move } +}; +MMOVE_T(insane_move_uptodown) = { FRAME_stand1, FRAME_stand40, insane_frames_uptodown, insane_onground }; + +mframe_t insane_frames_downtoup[] = { + { ai_move, -0.7f }, // 41 + { ai_move, -1.2f }, // 42 + { ai_move, -1.5f }, // 43 + { ai_move, -4.5f }, // 44 + { ai_move, -3.5f }, // 45 + { ai_move, -0.2f }, // 46 + { ai_move }, // 47 + { ai_move, -1.3f }, // 48 + { ai_move, -3 }, // 49 + { ai_move, -2 }, // 50 + { ai_move },//, 0, monster_duck_up }, // 51 + { ai_move }, // 52 + { ai_move }, // 53 + { ai_move, -3.3f }, // 54 + { ai_move, -1.6f }, // 55 + { ai_move, -0.3f }, // 56 + { ai_move }, // 57 + { ai_move }, // 58 + { ai_move } // 59 +}; +MMOVE_T(insane_move_downtoup) = { FRAME_stand41, FRAME_stand59, insane_frames_downtoup, insane_stand }; + +mframe_t insane_frames_jumpdown[] = { + { ai_move, 0.2f }, + { ai_move, 11.5f }, + { ai_move, 5.1f }, + { ai_move, 7.1f }, + { ai_move } +}; +MMOVE_T(insane_move_jumpdown) = { FRAME_stand96, FRAME_stand100, insane_frames_jumpdown, insane_onground }; + +mframe_t insane_frames_down[] = { + { ai_move }, // 100 + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, // 110 + { ai_move, -1.7f }, + { ai_move, -1.6f }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move, 0, insane_fist }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, // 120 + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, // 130 + { ai_move }, + { ai_move }, + { ai_move, 0, insane_moan }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, // 140 + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, // 150 + { ai_move, 0.5f }, + { ai_move }, + { ai_move, -0.2f, insane_scream }, + { ai_move }, + { ai_move, 0.2f }, + { ai_move, 0.4f }, + { ai_move, 0.6f }, + { ai_move, 0.8f }, + { ai_move, 0.7f }, + { ai_move, 0, insane_checkup } // 160 +}; +MMOVE_T(insane_move_down) = { FRAME_stand100, FRAME_stand160, insane_frames_down, insane_onground }; + +mframe_t insane_frames_walk_normal[] = { + { ai_walk, 0, insane_scream }, + { ai_walk, 2.5f }, + { ai_walk, 3.5f }, + { ai_walk, 1.7f }, + { ai_walk, 2.3f }, + { ai_walk, 2.4f }, + { ai_walk, 2.2f, monster_footstep }, + { ai_walk, 4.2f }, + { ai_walk, 5.6f }, + { ai_walk, 3.3f }, + { ai_walk, 2.4f }, + { ai_walk, 0.9f }, + { ai_walk, 0, monster_footstep } +}; +MMOVE_T(insane_move_walk_normal) = { FRAME_walk27, FRAME_walk39, insane_frames_walk_normal, insane_walk }; +MMOVE_T(insane_move_run_normal) = { FRAME_walk27, FRAME_walk39, insane_frames_walk_normal, insane_run }; + +mframe_t insane_frames_walk_insane[] = { + { ai_walk, 0, insane_scream }, // walk 1 + { ai_walk, 3.4f }, // walk 2 + { ai_walk, 3.6f }, // 3 + { ai_walk, 2.9f }, // 4 + { ai_walk, 2.2f }, // 5 + { ai_walk, 2.6f, monster_footstep }, // 6 + { ai_walk }, // 7 + { ai_walk, 0.7f }, // 8 + { ai_walk, 4.8f }, // 9 + { ai_walk, 5.3f }, // 10 + { ai_walk, 1.1f }, // 11 + { ai_walk, 2, monster_footstep }, // 12 + { ai_walk, 0.5f }, // 13 + { ai_walk }, // 14 + { ai_walk }, // 15 + { ai_walk, 4.9f }, // 16 + { ai_walk, 6.7f }, // 17 + { ai_walk, 3.8f }, // 18 + { ai_walk, 2, monster_footstep }, // 19 + { ai_walk, 0.2f }, // 20 + { ai_walk }, // 21 + { ai_walk, 3.4f }, // 22 + { ai_walk, 6.4f }, // 23 + { ai_walk, 5 }, // 24 + { ai_walk, 1.8f, monster_footstep }, // 25 + { ai_walk } // 26 +}; +MMOVE_T(insane_move_walk_insane) = { FRAME_walk1, FRAME_walk26, insane_frames_walk_insane, insane_walk }; +MMOVE_T(insane_move_run_insane) = { FRAME_walk1, FRAME_walk26, insane_frames_walk_insane, insane_run }; + +mframe_t insane_frames_stand_pain[] = { + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move, 0, monster_footstep }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move, 0, monster_footstep } +}; +MMOVE_T(insane_move_stand_pain) = { FRAME_st_pain2, FRAME_st_pain12, insane_frames_stand_pain, insane_run }; + +mframe_t insane_frames_stand_death[] = { + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move, 0, monster_footstep }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move, 0, monster_footstep }, + { ai_move }, + { ai_move, 0, monster_footstep }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move } +}; +MMOVE_T(insane_move_stand_death) = { FRAME_st_death2, FRAME_st_death18, insane_frames_stand_death, insane_dead }; + +mframe_t insane_frames_crawl[] = { + { ai_walk, 0, insane_scream }, + { ai_walk, 1.5f }, + { ai_walk, 2.1f }, + { ai_walk, 3.6f }, + { ai_walk, 2, monster_footstep }, + { ai_walk, 0.9f }, + { ai_walk, 3 }, + { ai_walk, 3.4f }, + { ai_walk, 2.4f, monster_footstep } +}; +MMOVE_T(insane_move_crawl) = { FRAME_crawl1, FRAME_crawl9, insane_frames_crawl, nullptr }; +MMOVE_T(insane_move_runcrawl) = { FRAME_crawl1, FRAME_crawl9, insane_frames_crawl, nullptr }; + +mframe_t insane_frames_crawl_pain[] = { + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move } +}; +MMOVE_T(insane_move_crawl_pain) = { FRAME_cr_pain2, FRAME_cr_pain10, insane_frames_crawl_pain, insane_run }; + +mframe_t insane_frames_crawl_death[] = { + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move } +}; +MMOVE_T(insane_move_crawl_death) = { FRAME_cr_death10, FRAME_cr_death16, insane_frames_crawl_death, insane_dead }; + +mframe_t insane_frames_cross[] = { + { ai_move, 0, insane_moan }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move } +}; +MMOVE_T(insane_move_cross) = { FRAME_cross1, FRAME_cross15, insane_frames_cross, insane_cross }; + +mframe_t insane_frames_struggle_cross[] = { + { ai_move, 0, insane_scream }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move } +}; +MMOVE_T(insane_move_struggle_cross) = { FRAME_cross16, FRAME_cross30, insane_frames_struggle_cross, insane_cross }; + +void insane_cross(edict_t *self) +{ + if (frandom() < 0.8f) + M_SetAnimation(self, &insane_move_cross); + else + M_SetAnimation(self, &insane_move_struggle_cross); +} + +MONSTERINFO_WALK(insane_walk) (edict_t *self) -> void +{ + if (self->spawnflags.has(SPAWNFLAG_INSANE_STAND_GROUND)) // Hold Ground? + if (self->s.frame == FRAME_cr_pain10) + { + M_SetAnimation(self, &insane_move_down); + //monster_duck_down(self); + return; + } + if (self->spawnflags.has(SPAWNFLAG_INSANE_CRAWL)) + M_SetAnimation(self, &insane_move_crawl); + else if (frandom() <= 0.5f) + M_SetAnimation(self, &insane_move_walk_normal); + else + M_SetAnimation(self, &insane_move_walk_insane); +} + +MONSTERINFO_RUN(insane_run) (edict_t *self) -> void +{ + if (self->spawnflags.has(SPAWNFLAG_INSANE_STAND_GROUND)) // Hold Ground? + if (self->s.frame == FRAME_cr_pain10) + { + M_SetAnimation(self, &insane_move_down); + //monster_duck_down(self); + return; + } + if (self->spawnflags.has(SPAWNFLAG_INSANE_CRAWL) || (self->s.frame >= FRAME_cr_pain2 && self->s.frame <= FRAME_cr_pain10) || (self->s.frame >= FRAME_crawl1 && self->s.frame <= FRAME_crawl9) || + (self->s.frame >= FRAME_stand99 && self->s.frame <= FRAME_stand160)) // Crawling? + M_SetAnimation(self, &insane_move_runcrawl); + else if (frandom() <= 0.5f) // Else, mix it up + { + M_SetAnimation(self, &insane_move_run_normal); + //monster_duck_up(self); + } + else + { + M_SetAnimation(self, &insane_move_run_insane); + //monster_duck_up(self); + } +} + +PAIN(insane_pain) (edict_t *self, edict_t *other, float kick, int damage, const mod_t &mod) -> void +{ + int l, r; + + if (level.time < self->pain_debounce_time) + return; + + self->pain_debounce_time = level.time + 3_sec; + + r = 1 + brandom(); + if (self->health < 25) + l = 25; + else if (self->health < 50) + l = 50; + else if (self->health < 75) + l = 75; + else + l = 100; + gi.sound(self, CHAN_VOICE, gi.soundindex(G_Fmt("player/male/pain{}_{}.wav", l, r).data()), 1, ATTN_IDLE, 0); + + // Don't go into pain frames if crucified. + if (self->spawnflags.has(SPAWNFLAG_INSANE_CRUCIFIED)) + { + M_SetAnimation(self, &insane_move_struggle_cross); + return; + } + + if (((self->s.frame >= FRAME_crawl1) && (self->s.frame <= FRAME_crawl9)) || ((self->s.frame >= FRAME_stand99) && (self->s.frame <= FRAME_stand160)) || ((self->s.frame >= FRAME_stand1 && self->s.frame <= FRAME_stand40))) + { + M_SetAnimation(self, &insane_move_crawl_pain); + } + else + { + M_SetAnimation(self, &insane_move_stand_pain); + //monster_duck_up(self); + } +} + +void insane_onground(edict_t *self) +{ + M_SetAnimation(self, &insane_move_down); + //monster_duck_down(self); +} + +void insane_checkdown(edict_t *self) +{ + // if ( (self->s.frame == FRAME_stand94) || (self->s.frame == FRAME_stand65) ) + if (self->spawnflags.has(SPAWNFLAG_INSANE_ALWAYS_STAND)) // Always stand + return; + if (frandom() < 0.3f) + { + if (frandom() < 0.5f) + M_SetAnimation(self, &insane_move_uptodown); + else + M_SetAnimation(self, &insane_move_jumpdown); + } +} + +void insane_checkup(edict_t *self) +{ + // If Hold_Ground and Crawl are set + if (self->spawnflags.has_all(SPAWNFLAG_INSANE_CRAWL | SPAWNFLAG_INSANE_STAND_GROUND)) + return; + if (frandom() < 0.5f) + { + M_SetAnimation(self, &insane_move_downtoup); + //monster_duck_up(self); + } +} + +MONSTERINFO_STAND(insane_stand) (edict_t *self) -> void +{ + if (self->spawnflags.has(SPAWNFLAG_INSANE_CRUCIFIED)) // If crucified + { + M_SetAnimation(self, &insane_move_cross); + self->monsterinfo.aiflags |= AI_STAND_GROUND; + } + // If Hold_Ground and Crawl are set + else if (self->spawnflags.has_all(SPAWNFLAG_INSANE_CRAWL | SPAWNFLAG_INSANE_STAND_GROUND)) + { + M_SetAnimation(self, &insane_move_down); + //monster_duck_down(self); + } + else if (frandom() < 0.5f) + M_SetAnimation(self, &insane_move_stand_normal); + else + M_SetAnimation(self, &insane_move_stand_insane); +} + +void insane_dead(edict_t *self) +{ + if (self->spawnflags.has(SPAWNFLAG_INSANE_CRUCIFIED)) + { + self->flags |= FL_FLY; + } + else + { + self->mins = { -16, -16, -24 }; + self->maxs = { 16, 16, -8 }; + self->movetype = MOVETYPE_TOSS; + } + monster_dead(self); +} + +DIE(insane_die) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void +{ + if (M_CheckGib(self, mod)) + { + gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_IDLE, 0); + ThrowGibs(self, damage, { + { 2, "models/objects/gibs/bone/tris.md2" }, + { 4, "models/objects/gibs/sm_meat/tris.md2" }, + { "models/objects/gibs/head2/tris.md2", GIB_HEAD } + }); + self->deadflag = true; + return; + } + + if (self->deadflag) + return; + + gi.sound(self, CHAN_VOICE, gi.soundindex(G_Fmt("player/male/death{}.wav", irandom(1, 5)).data()), 1, ATTN_IDLE, 0); + + self->deadflag = true; + self->takedamage = true; + + if (self->spawnflags.has(SPAWNFLAG_INSANE_CRUCIFIED)) + { + insane_dead(self); + } + else + { + if (((self->s.frame >= FRAME_crawl1) && (self->s.frame <= FRAME_crawl9)) || ((self->s.frame >= FRAME_stand99) && (self->s.frame <= FRAME_stand160))) + M_SetAnimation(self, &insane_move_crawl_death); + else + M_SetAnimation(self, &insane_move_stand_death); + } +} + +/*QUAKED misc_insane (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn CRAWL CRUCIFIED STAND_GROUND ALWAYS_STAND QUIET + */ +void SP_misc_insane(edict_t *self) +{ + // static int skin = 0; //@@ + + if ( !M_AllowSpawn( self ) ) { + G_FreeEdict( self ); + return; + } + + sound_fist = gi.soundindex("insane/insane11.wav"); + if (!self->spawnflags.has(SPAWNFLAG_INSANE_QUIET)) + { + sound_shake = gi.soundindex("insane/insane5.wav"); + sound_moan = gi.soundindex("insane/insane7.wav"); + sound_scream[0] = gi.soundindex("insane/insane1.wav"); + sound_scream[1] = gi.soundindex("insane/insane2.wav"); + sound_scream[2] = gi.soundindex("insane/insane3.wav"); + sound_scream[3] = gi.soundindex("insane/insane4.wav"); + sound_scream[4] = gi.soundindex("insane/insane6.wav"); + sound_scream[5] = gi.soundindex("insane/insane8.wav"); + sound_scream[6] = gi.soundindex("insane/insane9.wav"); + sound_scream[7] = gi.soundindex("insane/insane10.wav"); + } + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex("models/monsters/insane/tris.md2"); + + self->mins = { -16, -16, -24 }; + self->maxs = { 16, 16, 32 }; + + self->health = 100 * st.health_multiplier; + self->gib_health = -50; + self->mass = 300; + + self->pain = insane_pain; + self->die = insane_die; + + self->monsterinfo.stand = insane_stand; + self->monsterinfo.walk = insane_walk; + self->monsterinfo.run = insane_run; + self->monsterinfo.dodge = nullptr; + self->monsterinfo.attack = nullptr; + self->monsterinfo.melee = nullptr; + self->monsterinfo.sight = nullptr; + self->monsterinfo.aiflags |= AI_GOOD_GUY; + + //@@ + // self->s.skinnum = skin; + // skin++; + // if (skin > 12) + // skin = 0; + + gi.linkentity(self); + + if (self->spawnflags.has(SPAWNFLAG_INSANE_STAND_GROUND)) // Stand Ground + self->monsterinfo.aiflags |= AI_STAND_GROUND; + + M_SetAnimation(self, &insane_move_stand_normal); + + self->monsterinfo.scale = MODEL_SCALE; + + if (self->spawnflags.has(SPAWNFLAG_INSANE_CRUCIFIED)) // Crucified ? + { + self->flags |= FL_NO_KNOCKBACK | FL_STATIONARY; + stationarymonster_start(self); + } + else + walkmonster_start(self); + + self->s.skinnum = irandom(3); +} diff --git a/rerelease/m_insane.h b/rerelease/m_insane.h new file mode 100644 index 0000000..e15c659 --- /dev/null +++ b/rerelease/m_insane.h @@ -0,0 +1,293 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// G:\quake2\baseq2\models/monsters/insane + +// This file generated by ModelGen - Do NOT Modify + +enum +{ + FRAME_stand1, + FRAME_stand2, + FRAME_stand3, + FRAME_stand4, + FRAME_stand5, + FRAME_stand6, + FRAME_stand7, + FRAME_stand8, + FRAME_stand9, + FRAME_stand10, + FRAME_stand11, + FRAME_stand12, + FRAME_stand13, + FRAME_stand14, + FRAME_stand15, + FRAME_stand16, + FRAME_stand17, + FRAME_stand18, + FRAME_stand19, + FRAME_stand20, + FRAME_stand21, + FRAME_stand22, + FRAME_stand23, + FRAME_stand24, + FRAME_stand25, + FRAME_stand26, + FRAME_stand27, + FRAME_stand28, + FRAME_stand29, + FRAME_stand30, + FRAME_stand31, + FRAME_stand32, + FRAME_stand33, + FRAME_stand34, + FRAME_stand35, + FRAME_stand36, + FRAME_stand37, + FRAME_stand38, + FRAME_stand39, + FRAME_stand40, + FRAME_stand41, + FRAME_stand42, + FRAME_stand43, + FRAME_stand44, + FRAME_stand45, + FRAME_stand46, + FRAME_stand47, + FRAME_stand48, + FRAME_stand49, + FRAME_stand50, + FRAME_stand51, + FRAME_stand52, + FRAME_stand53, + FRAME_stand54, + FRAME_stand55, + FRAME_stand56, + FRAME_stand57, + FRAME_stand58, + FRAME_stand59, + FRAME_stand60, + FRAME_stand61, + FRAME_stand62, + FRAME_stand63, + FRAME_stand64, + FRAME_stand65, + FRAME_stand66, + FRAME_stand67, + FRAME_stand68, + FRAME_stand69, + FRAME_stand70, + FRAME_stand71, + FRAME_stand72, + FRAME_stand73, + FRAME_stand74, + FRAME_stand75, + FRAME_stand76, + FRAME_stand77, + FRAME_stand78, + FRAME_stand79, + FRAME_stand80, + FRAME_stand81, + FRAME_stand82, + FRAME_stand83, + FRAME_stand84, + FRAME_stand85, + FRAME_stand86, + FRAME_stand87, + FRAME_stand88, + FRAME_stand89, + FRAME_stand90, + FRAME_stand91, + FRAME_stand92, + FRAME_stand93, + FRAME_stand94, + FRAME_stand95, + FRAME_stand96, + FRAME_stand97, + FRAME_stand98, + FRAME_stand99, + FRAME_stand100, + FRAME_stand101, + FRAME_stand102, + FRAME_stand103, + FRAME_stand104, + FRAME_stand105, + FRAME_stand106, + FRAME_stand107, + FRAME_stand108, + FRAME_stand109, + FRAME_stand110, + FRAME_stand111, + FRAME_stand112, + FRAME_stand113, + FRAME_stand114, + FRAME_stand115, + FRAME_stand116, + FRAME_stand117, + FRAME_stand118, + FRAME_stand119, + FRAME_stand120, + FRAME_stand121, + FRAME_stand122, + FRAME_stand123, + FRAME_stand124, + FRAME_stand125, + FRAME_stand126, + FRAME_stand127, + FRAME_stand128, + FRAME_stand129, + FRAME_stand130, + FRAME_stand131, + FRAME_stand132, + FRAME_stand133, + FRAME_stand134, + FRAME_stand135, + FRAME_stand136, + FRAME_stand137, + FRAME_stand138, + FRAME_stand139, + FRAME_stand140, + FRAME_stand141, + FRAME_stand142, + FRAME_stand143, + FRAME_stand144, + FRAME_stand145, + FRAME_stand146, + FRAME_stand147, + FRAME_stand148, + FRAME_stand149, + FRAME_stand150, + FRAME_stand151, + FRAME_stand152, + FRAME_stand153, + FRAME_stand154, + FRAME_stand155, + FRAME_stand156, + FRAME_stand157, + FRAME_stand158, + FRAME_stand159, + FRAME_stand160, + FRAME_walk27, + FRAME_walk28, + FRAME_walk29, + FRAME_walk30, + FRAME_walk31, + FRAME_walk32, + FRAME_walk33, + FRAME_walk34, + FRAME_walk35, + FRAME_walk36, + FRAME_walk37, + FRAME_walk38, + FRAME_walk39, + FRAME_walk1, + FRAME_walk2, + FRAME_walk3, + FRAME_walk4, + FRAME_walk5, + FRAME_walk6, + FRAME_walk7, + FRAME_walk8, + FRAME_walk9, + FRAME_walk10, + FRAME_walk11, + FRAME_walk12, + FRAME_walk13, + FRAME_walk14, + FRAME_walk15, + FRAME_walk16, + FRAME_walk17, + FRAME_walk18, + FRAME_walk19, + FRAME_walk20, + FRAME_walk21, + FRAME_walk22, + FRAME_walk23, + FRAME_walk24, + FRAME_walk25, + FRAME_walk26, + FRAME_st_pain2, + FRAME_st_pain3, + FRAME_st_pain4, + FRAME_st_pain5, + FRAME_st_pain6, + FRAME_st_pain7, + FRAME_st_pain8, + FRAME_st_pain9, + FRAME_st_pain10, + FRAME_st_pain11, + FRAME_st_pain12, + FRAME_st_death2, + FRAME_st_death3, + FRAME_st_death4, + FRAME_st_death5, + FRAME_st_death6, + FRAME_st_death7, + FRAME_st_death8, + FRAME_st_death9, + FRAME_st_death10, + FRAME_st_death11, + FRAME_st_death12, + FRAME_st_death13, + FRAME_st_death14, + FRAME_st_death15, + FRAME_st_death16, + FRAME_st_death17, + FRAME_st_death18, + FRAME_crawl1, + FRAME_crawl2, + FRAME_crawl3, + FRAME_crawl4, + FRAME_crawl5, + FRAME_crawl6, + FRAME_crawl7, + FRAME_crawl8, + FRAME_crawl9, + FRAME_cr_pain2, + FRAME_cr_pain3, + FRAME_cr_pain4, + FRAME_cr_pain5, + FRAME_cr_pain6, + FRAME_cr_pain7, + FRAME_cr_pain8, + FRAME_cr_pain9, + FRAME_cr_pain10, + FRAME_cr_death10, + FRAME_cr_death11, + FRAME_cr_death12, + FRAME_cr_death13, + FRAME_cr_death14, + FRAME_cr_death15, + FRAME_cr_death16, + FRAME_cross1, + FRAME_cross2, + FRAME_cross3, + FRAME_cross4, + FRAME_cross5, + FRAME_cross6, + FRAME_cross7, + FRAME_cross8, + FRAME_cross9, + FRAME_cross10, + FRAME_cross11, + FRAME_cross12, + FRAME_cross13, + FRAME_cross14, + FRAME_cross15, + FRAME_cross16, + FRAME_cross17, + FRAME_cross18, + FRAME_cross19, + FRAME_cross20, + FRAME_cross21, + FRAME_cross22, + FRAME_cross23, + FRAME_cross24, + FRAME_cross25, + FRAME_cross26, + FRAME_cross27, + FRAME_cross28, + FRAME_cross29, + FRAME_cross30 +}; + +constexpr float MODEL_SCALE = 1.000000f; diff --git a/rerelease/m_medic.cpp b/rerelease/m_medic.cpp new file mode 100644 index 0000000..85eab25 --- /dev/null +++ b/rerelease/m_medic.cpp @@ -0,0 +1,1643 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +/* +============================================================================== + +MEDIC + +============================================================================== +*/ + +#include "g_local.h" +#include "m_medic.h" +#include "m_flash.h" + +constexpr float MEDIC_MIN_DISTANCE = 32; +constexpr float MEDIC_MAX_HEAL_DISTANCE = 400; +constexpr gtime_t MEDIC_TRY_TIME = 10_sec; + +// FIXME - +// +// owner moved to monsterinfo.healer instead +// +// For some reason, the healed monsters are rarely ending up in the floor +// +// 5/15/1998 I think I fixed these, keep an eye on them + +void M_SetEffects(edict_t *ent); +bool FindTarget(edict_t *self); +void FoundTarget(edict_t *self); +void ED_CallSpawn(edict_t *ent); + +static int sound_idle1; +static int sound_pain1; +static int sound_pain2; +static int sound_die; +static int sound_sight; +static int sound_search; +static int sound_hook_launch; +static int sound_hook_hit; +static int sound_hook_heal; +static int sound_hook_retract; + +// PMM - commander sounds +static int commander_sound_idle1; +static int commander_sound_pain1; +static int commander_sound_pain2; +static int commander_sound_die; +static int commander_sound_sight; +static int commander_sound_search; +static int commander_sound_hook_launch; +static int commander_sound_hook_hit; +static int commander_sound_hook_heal; +static int commander_sound_hook_retract; +static int commander_sound_spawn; + +constexpr const char *default_reinforcements = "monster_soldier_light 1;monster_soldier 2;monster_soldier_ss 2;monster_infantry 3;monster_gunner 4;monster_medic 5;monster_gladiator 6"; +constexpr int32_t default_monster_slots_base = 3; + +static const float inverse_log_slots = pow(2, MAX_REINFORCEMENTS); + +constexpr std::array reinforcement_position = { + vec3_t { 80, 0, 0 }, + vec3_t { 40, 60, 0 }, + vec3_t { 40, -60, 0 }, + vec3_t { 0, 80, 0 }, + vec3_t { 0, -80, 0 } +}; + +// filter out the reinforcement indices we can pick given the space we have left +static void M_PickValidReinforcements(edict_t *self, int32_t space, std::vector &output) +{ + output.clear(); + + for (uint8_t i = 0; i < self->monsterinfo.reinforcements.num_reinforcements; i++) + if (self->monsterinfo.reinforcements.reinforcements[i].strength <= space) + output.push_back(i); +} + +// pick an array of reinforcements to use; note that this does not modify `self` +std::array M_PickReinforcements(edict_t *self, int32_t &num_chosen, int32_t max_slots = 0) +{ + static std::vector available; + std::array chosen; + chosen.fill(255); + + // decide how many things we want to spawn; + // this is on a logarithmic scale + // so we don't spawn too much too often. + int32_t num_slots = max(1, (int32_t) log2(frandom(inverse_log_slots))); + + // we only have this many slots left to use + int32_t remaining = self->monsterinfo.monster_slots - self->monsterinfo.monster_used; + + for (num_chosen = 0; num_chosen < num_slots; num_chosen++) + { + // ran out of slots! + if ((max_slots && num_chosen == max_slots) || !remaining) + break; + + // get everything we could choose + M_PickValidReinforcements(self, remaining, available); + + // can't pick any + if (!available.size()) + break; + + // select monster, TODO fairly + chosen[num_chosen] = random_element(available); + + remaining -= self->monsterinfo.reinforcements.reinforcements[chosen[num_chosen]].strength; + } + + return chosen; +} + +void M_SetupReinforcements(const char *reinforcements, reinforcement_list_t &list) +{ + // count up the semicolons + list.num_reinforcements = 0; + + if (!*reinforcements) + return; + + list.num_reinforcements++; + + for (size_t i = 0; i < strlen(reinforcements); i++) + if (reinforcements[i] == ';') + list.num_reinforcements++; + + // allocate + list.reinforcements = (reinforcement_t *) gi.TagMalloc(sizeof(reinforcement_t) * list.num_reinforcements, TAG_LEVEL); + + // parse + const char *p = reinforcements; + reinforcement_t *r = list.reinforcements; + + st = {}; + + while (true) + { + const char *token = COM_ParseEx(&p, "; "); + + if (!*token || r == list.reinforcements + list.num_reinforcements) + break; + + r->classname = G_CopyString(token, TAG_LEVEL); + + token = COM_ParseEx(&p, "; "); + + r->strength = atoi(token); + + edict_t *newEnt = G_Spawn(); + + newEnt->classname = r->classname; + + newEnt->monsterinfo.aiflags |= AI_DO_NOT_COUNT; + + ED_CallSpawn(newEnt); + + r->mins = newEnt->mins; + r->maxs = newEnt->maxs; + + G_FreeEdict(newEnt); + + r++; + } +} + +void cleanupHeal(edict_t *self, bool change_frame) +{ + // clean up target, if we have one and it's legit + if (self->enemy && self->enemy->inuse) + cleanupHealTarget(self->enemy); + + if (self->oldenemy && self->oldenemy->inuse && self->oldenemy->health > 0) + { + self->enemy = self->oldenemy; + HuntTarget(self, false); + } + else + { + self->enemy = self->goalentity = nullptr; + self->oldenemy = nullptr; + if (!FindTarget(self)) + { + // no valid enemy, so stop acting + self->monsterinfo.pausetime = HOLD_FOREVER; + self->monsterinfo.stand(self); + return; + } + } + + if (change_frame) + self->monsterinfo.nextframe = FRAME_attack52; +} + +void abortHeal(edict_t *self, bool change_frame, bool gib, bool mark) +{ + int hurt; + constexpr vec3_t pain_normal = { 0, 0, 1 }; + + cleanupHealTarget(self->enemy); + + // gib em! + if ((mark) && (self->enemy) && (self->enemy->inuse)) + { + // if the first badMedic slot is filled by a medic, skip it and use the second one + if ((self->enemy->monsterinfo.badMedic1) && (self->enemy->monsterinfo.badMedic1->inuse) && (!strncmp(self->enemy->monsterinfo.badMedic1->classname, "monster_medic", 13))) + { + self->enemy->monsterinfo.badMedic2 = self; + } + else + { + self->enemy->monsterinfo.badMedic1 = self; + } + } + if ((gib) && (self->enemy) && (self->enemy->inuse)) + { + if (self->enemy->gib_health) + hurt = -self->enemy->gib_health; + else + hurt = 500; + + T_Damage(self->enemy, self, self, vec3_origin, self->enemy->s.origin, + pain_normal, hurt, 0, DAMAGE_NONE, MOD_UNKNOWN); + } + // clean up self + + // clean up target + cleanupHeal(self, change_frame); + + self->monsterinfo.aiflags &= ~AI_MEDIC; + self->monsterinfo.medicTries = 0; +} + +bool canReach(edict_t *self, edict_t *other) +{ + vec3_t spot1; + vec3_t spot2; + trace_t trace; + + spot1 = self->s.origin; + spot1[2] += self->viewheight; + spot2 = other->s.origin; + spot2[2] += other->viewheight; + trace = gi.traceline(spot1, spot2, self, MASK_PROJECTILE | MASK_WATER); + return trace.fraction == 1.0f || trace.ent == other; +} + +edict_t *medic_FindDeadMonster(edict_t *self) +{ + float radius; + edict_t *ent = nullptr; + edict_t *best = nullptr; + + if (self->monsterinfo.react_to_damage_time > level.time) + return nullptr; + + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + radius = MEDIC_MAX_HEAL_DISTANCE; + else + radius = 1024; + + while ((ent = findradius(ent, self->s.origin, radius)) != nullptr) + { + if (ent == self) + continue; + if (!(ent->svflags & SVF_MONSTER)) + continue; + if (ent->monsterinfo.aiflags & AI_GOOD_GUY) + continue; + // check to make sure we haven't bailed on this guy already + if ((ent->monsterinfo.badMedic1 == self) || (ent->monsterinfo.badMedic2 == self)) + continue; + if (ent->monsterinfo.healer) + // FIXME - this is correcting a bug that is somewhere else + // if the healer is a monster, and it's in medic mode .. continue .. otherwise + // we will override the healer, if it passes all the other tests + if ((ent->monsterinfo.healer->inuse) && (ent->monsterinfo.healer->health > 0) && + (ent->monsterinfo.healer->svflags & SVF_MONSTER) && (ent->monsterinfo.healer->monsterinfo.aiflags & AI_MEDIC)) + continue; + if (ent->health > 0) + continue; + if ((ent->nextthink) && (ent->think != monster_dead_think)) + continue; + if (!visible(self, ent)) + continue; + if (!strncmp(ent->classname, "player", 6)) // stop it from trying to heal player_noise entities + continue; + // FIXME - there's got to be a better way .. + // make sure we don't spawn people right on top of us + if (realrange(self, ent) <= MEDIC_MIN_DISTANCE) + continue; + if (!best) + { + best = ent; + continue; + } + if (ent->max_health <= best->max_health) + continue; + best = ent; + } + + if (best) + self->timestamp = level.time + MEDIC_TRY_TIME; + + return best; +} + +MONSTERINFO_IDLE(medic_idle) (edict_t *self) -> void +{ + edict_t *ent; + + // PMM - commander sounds + if (self->mass == 400) + gi.sound(self, CHAN_VOICE, sound_idle1, 1, ATTN_IDLE, 0); + else + gi.sound(self, CHAN_VOICE, commander_sound_idle1, 1, ATTN_IDLE, 0); + + if (!self->oldenemy) + { + ent = medic_FindDeadMonster(self); + if (ent) + { + self->oldenemy = self->enemy; + self->enemy = ent; + self->enemy->monsterinfo.healer = self; + self->monsterinfo.aiflags |= AI_MEDIC; + FoundTarget(self); + } + } +} + +MONSTERINFO_SEARCH(medic_search) (edict_t *self) -> void +{ + edict_t *ent; + + // PMM - commander sounds + if (self->mass == 400) + gi.sound(self, CHAN_VOICE, sound_search, 1, ATTN_IDLE, 0); + else + gi.sound(self, CHAN_VOICE, commander_sound_search, 1, ATTN_IDLE, 0); + + if (!self->oldenemy) + { + ent = medic_FindDeadMonster(self); + if (ent) + { + self->oldenemy = self->enemy; + self->enemy = ent; + self->enemy->monsterinfo.healer = self; + self->monsterinfo.aiflags |= AI_MEDIC; + FoundTarget(self); + } + } +} + +MONSTERINFO_SIGHT(medic_sight) (edict_t *self, edict_t *other) -> void +{ + // PMM - commander sounds + if (self->mass == 400) + gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); + else + gi.sound(self, CHAN_VOICE, commander_sound_sight, 1, ATTN_NORM, 0); +} + +mframe_t medic_frames_stand[] = { + { ai_stand, 0, medic_idle }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, +}; +MMOVE_T(medic_move_stand) = { FRAME_wait1, FRAME_wait90, medic_frames_stand, nullptr }; + +MONSTERINFO_STAND(medic_stand) (edict_t *self) -> void +{ + M_SetAnimation(self, &medic_move_stand); +} + +mframe_t medic_frames_walk[] = { + { ai_walk, 6.2f }, + { ai_walk, 18.1f, monster_footstep }, + { ai_walk, 1 }, + { ai_walk, 9 }, + { ai_walk, 10 }, + { ai_walk, 9 }, + { ai_walk, 11 }, + { ai_walk, 11.6f, monster_footstep }, + { ai_walk, 2 }, + { ai_walk, 9.9f }, + { ai_walk, 14 }, + { ai_walk, 9.3f } +}; +MMOVE_T(medic_move_walk) = { FRAME_walk1, FRAME_walk12, medic_frames_walk, nullptr }; + +MONSTERINFO_WALK(medic_walk) (edict_t *self) -> void +{ + M_SetAnimation(self, &medic_move_walk); +} + +mframe_t medic_frames_run[] = { + { ai_run, 18 }, + { ai_run, 22.5f, monster_footstep }, + { ai_run, 25.4f, monster_done_dodge }, + { ai_run, 23.4f, monster_footstep }, + { ai_run, 24 }, + { ai_run, 35.6f } +}; +MMOVE_T(medic_move_run) = { FRAME_run1, FRAME_run6, medic_frames_run, nullptr }; + +MONSTERINFO_RUN(medic_run) (edict_t *self) -> void +{ + monster_done_dodge(self); + + if (!(self->monsterinfo.aiflags & AI_MEDIC)) + { + edict_t *ent; + + ent = medic_FindDeadMonster(self); + if (ent) + { + self->oldenemy = self->enemy; + self->enemy = ent; + self->enemy->monsterinfo.healer = self; + self->monsterinfo.aiflags |= AI_MEDIC; + FoundTarget(self); + return; + } + } + + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + M_SetAnimation(self, &medic_move_stand); + else + M_SetAnimation(self, &medic_move_run); +} + +mframe_t medic_frames_pain1[] = { + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move } +}; +MMOVE_T(medic_move_pain1) = { FRAME_paina2, FRAME_paina6, medic_frames_pain1, medic_run }; + +mframe_t medic_frames_pain2[] = { + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move, 0, monster_footstep }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move, 0, monster_footstep } +}; +MMOVE_T(medic_move_pain2) = { FRAME_painb2, FRAME_painb13, medic_frames_pain2, medic_run }; + +PAIN(medic_pain) (edict_t *self, edict_t *other, float kick, int damage, const mod_t &mod) -> void +{ + monster_done_dodge(self); + + if (level.time < self->pain_debounce_time) + return; + + self->pain_debounce_time = level.time + 3_sec; + + float r = frandom(); + + if (self->mass > 400) + { + if (damage < 35) + { + gi.sound(self, CHAN_VOICE, commander_sound_pain1, 1, ATTN_NORM, 0); + + if (mod.id != MOD_CHAINFIST) + return; + } + + gi.sound(self, CHAN_VOICE, commander_sound_pain2, 1, ATTN_NORM, 0); + } + else if (r < 0.5f) + gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); + else + gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0); + + if (!M_ShouldReactToPain(self, mod)) + return; // no pain anims in nightmare + + // if we're healing someone, we ignore pain + if (mod.id != MOD_CHAINFIST && (self->monsterinfo.aiflags & AI_MEDIC)) + return; + + if (self->mass > 400) + { + self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING; + self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; + + if (r < (min(((float) damage * 0.005f), 0.5f))) // no more than 50% chance of big pain + M_SetAnimation(self, &medic_move_pain2); + else + M_SetAnimation(self, &medic_move_pain1); + } + else if (r < 0.5f) + M_SetAnimation(self, &medic_move_pain1); + else + M_SetAnimation(self, &medic_move_pain2); + + // PMM - clear duck flag + if (self->monsterinfo.aiflags & AI_DUCKED) + monster_duck_up(self); + + abortHeal(self, false, false, false); +} + +MONSTERINFO_SETSKIN(medic_setskin) (edict_t *self) -> void +{ + if ((self->health < (self->max_health / 2))) + self->s.skinnum |= 1; + else + self->s.skinnum &= ~1; +} + +void medic_fire_blaster(edict_t *self) +{ + vec3_t start; + vec3_t forward, right; + vec3_t end; + vec3_t dir; + effects_t effect; + int damage = 2; + monster_muzzleflash_id_t mz; + + // paranoia checking + if (!(self->enemy && self->enemy->inuse)) + return; + + if ((self->s.frame == FRAME_attack9) || (self->s.frame == FRAME_attack12)) + { + effect = EF_BLASTER; + damage = 6; + mz = (self->mass > 400) ? MZ2_MEDIC_BLASTER_2 : MZ2_MEDIC_BLASTER_1; + } + else + { + static constexpr vec3_t hb_offsets[] = { + { 33.0f, 12.5f, 15.0f }, + { 32.4f, 11.2f, 15.0f }, + { 35.6f, 7.4f, 15.0f }, + { 34.0f, 4.1f, 15.0f }, + { 36.6f, 1.0f, 15.0f }, + { 34.7f, -1.9f, 15.0f }, + { 36.6f, -0.5f, 15.0f }, + { 34.2f, 2.8f, 15.0f }, + { 36.5f, 3.8f, 15.0f }, + { 33.5f, 6.9f, 15.0f }, + { 32.7f, 9.9f, 15.0f }, + { 34.5f, 11.0f, 15.0f } + }; + + effect = (self->s.frame % 4) ? EF_NONE : EF_HYPERBLASTER; + mz = static_cast(((self->mass > 400) ? MZ2_MEDIC_HYPERBLASTER2_1 : MZ2_MEDIC_HYPERBLASTER1_1) + (self->s.frame - FRAME_attack19)); + } + + AngleVectors(self->s.angles, forward, right, nullptr); + const vec3_t &offset = monster_flash_offset[mz]; + start = M_ProjectFlashSource(self, offset, forward, right); + + end = self->enemy->s.origin; + end[2] += self->enemy->viewheight; + dir = end - start; + dir.normalize(); + + if ( !strcmp( self->enemy->classname, "tesla_mine" ) ) + damage = 3; + + // medic commander shoots blaster2 + if (self->mass > 400) + monster_fire_blaster2(self, start, dir, damage, 1000, mz, effect); + else + monster_fire_blaster(self, start, dir, damage, 1000, mz, effect); +} + +void medic_dead(edict_t *self) +{ + self->mins = { -16, -16, -24 }; + self->maxs = { 16, 16, -8 }; + monster_dead(self); +} + +static void medic_shrink(edict_t *self) +{ + self->maxs[2] = -2; + self->svflags |= SVF_DEADMONSTER; + gi.linkentity(self); +} + +mframe_t medic_frames_death[] = { + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move, -18.f, monster_footstep }, + { ai_move, -10.f, medic_shrink }, + { ai_move, -6.f }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move, 0, monster_footstep }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move } +}; +MMOVE_T(medic_move_death) = { FRAME_death2, FRAME_death30, medic_frames_death, medic_dead }; + +DIE(medic_die) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void +{ + // if we had a pending patient, he was already freed up in Killed + + // check for gib + if (M_CheckGib(self, mod)) + { + gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0); + + self->s.skinnum /= 2; + + ThrowGibs(self, damage, { + { 2, "models/objects/gibs/bone/tris.md2" }, + { "models/objects/gibs/sm_meat/tris.md2" }, + { "models/objects/gibs/sm_metal/tris.md2", GIB_METALLIC }, + { "models/monsters/medic/gibs/chest.md2", GIB_SKINNED }, + { 2, "models/monsters/medic/gibs/leg.md2", GIB_SKINNED | GIB_UPRIGHT }, + { "models/monsters/medic/gibs/hook.md2", GIB_SKINNED | GIB_UPRIGHT }, + { "models/monsters/medic/gibs/gun.md2", GIB_SKINNED | GIB_UPRIGHT }, + { "models/monsters/medic/gibs/head.md2", GIB_SKINNED | GIB_HEAD } + }); + + self->deadflag = true; + return; + } + + if (self->deadflag) + return; + + // regular death + // PMM + if (self->mass == 400) + gi.sound(self, CHAN_VOICE, sound_die, 1, ATTN_NORM, 0); + else + gi.sound(self, CHAN_VOICE, commander_sound_die, 1, ATTN_NORM, 0); + // + self->deadflag = true; + self->takedamage = true; + + M_SetAnimation(self, &medic_move_death); +} + +mframe_t medic_frames_duck[] = { + { ai_move, -1 }, + { ai_move, -1, monster_duck_down }, + { ai_move, -1, monster_duck_hold }, + { ai_move, -1 }, + { ai_move, -1 }, + { ai_move, -1 }, // PMM - duck up used to be here + { ai_move, -1 }, + { ai_move, -1 }, + { ai_move, -1 }, + { ai_move, -1 }, + { ai_move, -1 }, + { ai_move, -1 }, + { ai_move, -1, monster_duck_up } +}; +MMOVE_T(medic_move_duck) = { FRAME_duck2, FRAME_duck14, medic_frames_duck, medic_run }; + +// PMM -- moved dodge code to after attack code so I can reference attack frames + +mframe_t medic_frames_attackHyperBlaster[] = { + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge, 0, medic_fire_blaster }, + { ai_charge, 0, medic_fire_blaster }, + { ai_charge, 0, medic_fire_blaster }, + { ai_charge, 0, medic_fire_blaster }, + { ai_charge, 0, medic_fire_blaster }, + { ai_charge, 0, medic_fire_blaster }, + { ai_charge, 0, medic_fire_blaster }, + { ai_charge, 0, medic_fire_blaster }, + { ai_charge, 0, medic_fire_blaster }, + { ai_charge, 0, medic_fire_blaster }, + { ai_charge, 0, medic_fire_blaster }, + { ai_charge, 0, medic_fire_blaster }, + { ai_charge }, + { ai_charge }, + // [Paril-KEX] end on 36 as intended + { ai_charge, 2.f }, // 33 + { ai_charge, 3.f, monster_footstep }, +}; +MMOVE_T(medic_move_attackHyperBlaster) = { FRAME_attack15, FRAME_attack34, medic_frames_attackHyperBlaster, medic_run }; + +static void medic_quick_attack(edict_t *self) +{ + if (frandom() < 0.5f) + { + M_SetAnimation(self, &medic_move_attackHyperBlaster, false); + self->monsterinfo.nextframe = FRAME_attack16; + } +} + +void medic_continue(edict_t *self) +{ + if (visible(self, self->enemy)) + if (frandom() <= 0.95f) + M_SetAnimation(self, &medic_move_attackHyperBlaster, false); +} + +mframe_t medic_frames_attackBlaster[] = { + { ai_charge, 5 }, + { ai_charge, 3 }, + { ai_charge, 2 }, + { ai_charge, 0, medic_quick_attack }, + { ai_charge, 0, monster_footstep }, + { ai_charge }, + { ai_charge, 0, medic_fire_blaster }, + { ai_charge }, + { ai_charge }, + { ai_charge, 0, medic_fire_blaster }, + { ai_charge }, + { ai_charge, 0, medic_continue } // Change to medic_continue... Else, go to frame 32 +}; +MMOVE_T(medic_move_attackBlaster) = { FRAME_attack3, FRAME_attack14, medic_frames_attackBlaster, medic_run }; + +void medic_hook_launch(edict_t *self) +{ + // PMM - commander sounds + if (self->mass == 400) + gi.sound(self, CHAN_WEAPON, sound_hook_launch, 1, ATTN_NORM, 0); + else + gi.sound(self, CHAN_WEAPON, commander_sound_hook_launch, 1, ATTN_NORM, 0); +} + +constexpr vec3_t medic_cable_offsets[] = { + { 45.0f, -9.2f, 15.5f }, + { 48.4f, -9.7f, 15.2f }, + { 47.8f, -9.8f, 15.8f }, + { 47.3f, -9.3f, 14.3f }, + { 45.4f, -10.1f, 13.1f }, + { 41.9f, -12.7f, 12.0f }, + { 37.8f, -15.8f, 11.2f }, + { 34.3f, -18.4f, 10.7f }, + { 32.7f, -19.7f, 10.4f }, + { 32.7f, -19.7f, 10.4f } +}; + +void medic_cable_attack(edict_t *self) +{ + vec3_t offset, start, end, f, r; + trace_t tr; + vec3_t dir; + float distance; + + if ((!self->enemy) || (!self->enemy->inuse) || (self->enemy->s.effects & EF_GIB)) + { + abortHeal(self, false, false, false); + return; + } + + // we switched back to a player; let the animation finish + if (self->enemy->client) + return; + + // see if our enemy has changed to a client, or our target has more than 0 health, + // abort it .. we got switched to someone else due to damage + if (self->enemy->health > 0) + { + abortHeal(self, false, false, false); + return; + } + + AngleVectors(self->s.angles, f, r, nullptr); + offset = medic_cable_offsets[self->s.frame - FRAME_attack42]; + start = M_ProjectFlashSource(self, offset, f, r); + + // check for max distance + // not needed, done in checkattack + // check for min distance + dir = start - self->enemy->s.origin; + distance = dir.length(); + if (distance < MEDIC_MIN_DISTANCE) + { + abortHeal(self, true, true, false); + return; + } + + tr = gi.traceline(start, self->enemy->s.origin, self, MASK_SOLID); + if (tr.fraction != 1.0f && tr.ent != self->enemy) + { + if (tr.ent == world) + { + // give up on second try + if (self->monsterinfo.medicTries > 1) + { + abortHeal(self, true, false, true); + return; + } + self->monsterinfo.medicTries++; + cleanupHeal(self, 1); + return; + } + abortHeal(self, true, false, false); + return; + } + + if (self->s.frame == FRAME_attack43) + { + // PMM - commander sounds + if (self->mass == 400) + gi.sound(self->enemy, CHAN_AUTO, sound_hook_hit, 1, ATTN_NORM, 0); + else + gi.sound(self->enemy, CHAN_AUTO, commander_sound_hook_hit, 1, ATTN_NORM, 0); + + self->enemy->monsterinfo.aiflags |= AI_RESURRECTING; + self->enemy->takedamage = false; + M_SetEffects(self->enemy); + } + else if (self->s.frame == FRAME_attack50) + { + vec3_t maxs; + self->enemy->spawnflags = SPAWNFLAG_NONE; + self->enemy->monsterinfo.aiflags &= AI_STINKY | AI_SPAWNED_MASK; + self->enemy->target = nullptr; + self->enemy->targetname = nullptr; + self->enemy->combattarget = nullptr; + self->enemy->deathtarget = nullptr; + self->enemy->healthtarget = nullptr; + self->enemy->itemtarget = nullptr; + self->enemy->monsterinfo.healer = self; + + maxs = self->enemy->maxs; + maxs[2] += 48; // compensate for change when they die + + tr = gi.trace(self->enemy->s.origin, self->enemy->mins, maxs, self->enemy->s.origin, self->enemy, MASK_MONSTERSOLID); + + if (tr.startsolid || tr.allsolid) + { + abortHeal(self, true, true, false); + return; + } + else if (tr.ent != world) + { + abortHeal(self, true, true, false); + return; + } + else + { + self->enemy->monsterinfo.aiflags |= AI_DO_NOT_COUNT; + + // backup & restore health stuff, because of multipliers + int32_t old_max_health = self->enemy->max_health; + item_id_t old_power_armor_type = self->enemy->monsterinfo.initial_power_armor_type; + int32_t old_power_armor_power = self->enemy->monsterinfo.max_power_armor_power; + int32_t old_base_health = self->enemy->monsterinfo.base_health; + int32_t old_health_scaling = self->enemy->monsterinfo.health_scaling; + auto reinforcements = self->enemy->monsterinfo.reinforcements; + int32_t monster_slots = self->enemy->monsterinfo.monster_slots; + int32_t monster_used = self->enemy->monsterinfo.monster_used; + int32_t old_gib_health = self->enemy->gib_health; + + st = {}; + st.keys_specified.emplace("reinforcements"); + st.reinforcements = ""; + + ED_CallSpawn(self->enemy); + + self->enemy->monsterinfo.reinforcements = reinforcements; + self->enemy->monsterinfo.monster_slots = monster_slots; + self->enemy->monsterinfo.monster_used = monster_used; + + self->enemy->gib_health = old_gib_health / 2; + self->enemy->health = self->enemy->max_health = old_max_health; + self->enemy->monsterinfo.power_armor_power = self->enemy->monsterinfo.max_power_armor_power = old_power_armor_power; + self->enemy->monsterinfo.power_armor_type = self->enemy->monsterinfo.initial_power_armor_type = old_power_armor_type; + self->enemy->monsterinfo.base_health = old_base_health; + self->enemy->monsterinfo.health_scaling = old_health_scaling; + + if (self->enemy->monsterinfo.setskin) + self->enemy->monsterinfo.setskin(self->enemy); + + if (self->enemy->think) + { + self->enemy->nextthink = level.time; + self->enemy->think(self->enemy); + } + self->enemy->monsterinfo.aiflags &= ~AI_RESURRECTING; + self->enemy->monsterinfo.aiflags |= AI_IGNORE_SHOTS | AI_DO_NOT_COUNT; + // turn off flies + self->enemy->s.effects &= ~EF_FLIES; + self->enemy->monsterinfo.healer = nullptr; + + if ((self->oldenemy) && (self->oldenemy->inuse) && (self->oldenemy->health > 0)) + { + self->enemy->enemy = self->oldenemy; + FoundTarget(self->enemy); + } + else + { + self->enemy->enemy = nullptr; + if (!FindTarget(self->enemy)) + { + // no valid enemy, so stop acting + self->enemy->monsterinfo.pausetime = HOLD_FOREVER; + self->enemy->monsterinfo.stand(self->enemy); + } + self->enemy = nullptr; + self->oldenemy = nullptr; + if (!FindTarget(self)) + { + // no valid enemy, so stop acting + self->monsterinfo.pausetime = HOLD_FOREVER; + self->monsterinfo.stand(self); + return; + } + } + + cleanupHeal(self, false); + return; + } + } + else + { + if (self->s.frame == FRAME_attack44) + { + // PMM - medic commander sounds + if (self->mass == 400) + gi.sound(self, CHAN_WEAPON, sound_hook_heal, 1, ATTN_NORM, 0); + else + gi.sound(self, CHAN_WEAPON, commander_sound_hook_heal, 1, ATTN_NORM, 0); + } + } + + // adjust start for beam origin being in middle of a segment + start += (f * 8); + + // adjust end z for end spot since the monster is currently dead + end = self->enemy->s.origin; + end[2] = (self->enemy->absmin[2] + self->enemy->absmax[2]) / 2; + + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_MEDIC_CABLE_ATTACK); + gi.WriteEntity(self); + gi.WritePosition(start); + gi.WritePosition(end); + gi.multicast(self->s.origin, MULTICAST_PVS, false); +} + +void medic_hook_retract(edict_t *self) +{ + if (self->mass == 400) + gi.sound(self, CHAN_WEAPON, sound_hook_retract, 1, ATTN_NORM, 0); + else + gi.sound(self, CHAN_WEAPON, sound_hook_retract, 1, ATTN_NORM, 0); + + self->monsterinfo.aiflags &= ~AI_MEDIC; + + if (self->oldenemy && self->oldenemy->inuse && self->oldenemy->health > 0) + { + self->enemy = self->oldenemy; + HuntTarget(self, false); + } + else + { + self->enemy = self->goalentity = nullptr; + self->oldenemy = nullptr; + if (!FindTarget(self)) + { + // no valid enemy, so stop acting + self->monsterinfo.pausetime = HOLD_FOREVER; + self->monsterinfo.stand(self); + return; + } + } +} + +mframe_t medic_frames_attackCable[] = { + // ROGUE - negated 36-40 so he scoots back from his target a little + // ROGUE - switched 33-36 to ai_charge + // ROGUE - changed frame 52 to 60 to compensate for changes in 36-40 + // [Paril-KEX] started on 36 as they intended + { ai_charge, -4.7f }, // 37 + { ai_charge, -5.f }, + { ai_charge, -6.f }, + { ai_charge, -4.f }, // 40 + { ai_charge, 0, monster_footstep }, + { ai_move, 0, medic_hook_launch }, // 42 + { ai_move, 0, medic_cable_attack }, // 43 + { ai_move, 0, medic_cable_attack }, + { ai_move, 0, medic_cable_attack }, + { ai_move, 0, medic_cable_attack }, + { ai_move, 0, medic_cable_attack }, + { ai_move, 0, medic_cable_attack }, + { ai_move, 0, medic_cable_attack }, + { ai_move, 0, medic_cable_attack }, + { ai_move, 0, medic_cable_attack }, // 51 + { ai_move, 0, medic_hook_retract }, // 52 + { ai_move, -1.5f }, + { ai_move, -1.2f, monster_footstep }, + { ai_move, -3.f } +}; +MMOVE_T(medic_move_attackCable) = { FRAME_attack37, FRAME_attack55, medic_frames_attackCable, medic_run }; + +void medic_start_spawn(edict_t *self) +{ + gi.sound(self, CHAN_WEAPON, commander_sound_spawn, 1, ATTN_NORM, 0); + self->monsterinfo.nextframe = FRAME_attack48; +} + +void medic_determine_spawn(edict_t *self) +{ + vec3_t f, r, offset, startpoint, spawnpoint; + int count; + int num_success = 0; + + AngleVectors(self->s.angles, f, r, nullptr); + + int num_summoned; + self->monsterinfo.chosen_reinforcements = M_PickReinforcements(self, num_summoned); + + for (count = 0; count < num_summoned; count++) + { + offset = reinforcement_position[count]; + + if (self->s.scale) + offset *= self->s.scale; + + startpoint = M_ProjectFlashSource(self, offset, f, r); + // a little off the ground + startpoint[2] += 10 * (self->s.scale ? self->s.scale : 1.0f); + + auto &reinforcement = self->monsterinfo.reinforcements.reinforcements[self->monsterinfo.chosen_reinforcements[count]]; + + if (FindSpawnPoint(startpoint, reinforcement.mins, reinforcement.maxs, spawnpoint, 32)) + { + if (CheckGroundSpawnPoint(spawnpoint, reinforcement.mins, reinforcement.maxs, 256, -1)) + { + num_success++; + // we found a spot, we're done here + count = num_summoned; + } + } + } + + // see if we have any success by spinning around + if (num_success == 0) + { + for (count = 0; count < num_summoned; count++) + { + offset = reinforcement_position[count]; + + if (self->s.scale) + offset *= self->s.scale; + + // check behind + offset[0] *= -1.0f; + offset[1] *= -1.0f; + startpoint = M_ProjectFlashSource(self, offset, f, r); + // a little off the ground + startpoint[2] += 10; + + auto &reinforcement = self->monsterinfo.reinforcements.reinforcements[self->monsterinfo.chosen_reinforcements[count]]; + + if (FindSpawnPoint(startpoint, reinforcement.mins, reinforcement.maxs, spawnpoint, 32)) + { + if (CheckGroundSpawnPoint(spawnpoint, reinforcement.mins, reinforcement.maxs, 256, -1)) + { + num_success++; + // we found a spot, we're done here + count = num_summoned; + } + } + } + + if (num_success) + { + self->monsterinfo.aiflags |= AI_MANUAL_STEERING; + self->ideal_yaw = anglemod(self->s.angles[YAW]) + 180; + if (self->ideal_yaw > 360.0f) + self->ideal_yaw -= 360.0f; + } + } + + if (num_success == 0) + self->monsterinfo.nextframe = FRAME_attack53; +} + +void medic_spawngrows(edict_t *self) +{ + vec3_t f, r, offset, startpoint, spawnpoint; + int count; + int num_summoned; // should be 1, 3, or 5 + int num_success = 0; + float current_yaw; + + // if we've been directed to turn around + if (self->monsterinfo.aiflags & AI_MANUAL_STEERING) + { + current_yaw = anglemod(self->s.angles[YAW]); + if (fabsf(current_yaw - self->ideal_yaw) > 0.1f) + { + self->monsterinfo.aiflags |= AI_HOLD_FRAME; + return; + } + + // done turning around + self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; + self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING; + } + + AngleVectors(self->s.angles, f, r, nullptr); + + num_summoned = 0; + + for (int32_t i = 0; i < MAX_REINFORCEMENTS; i++, num_summoned++) + if (self->monsterinfo.chosen_reinforcements[i] == 255) + break; + + for (count = 0; count < num_summoned; count++) + { + offset = reinforcement_position[count]; + + if (self->s.scale) + offset *= self->s.scale; + + startpoint = M_ProjectFlashSource(self, offset, f, r); + // a little off the ground + startpoint[2] += 10 * (self->s.scale ? self->s.scale : 1.0f); + + auto &reinforcement = self->monsterinfo.reinforcements.reinforcements[self->monsterinfo.chosen_reinforcements[count]]; + + if (FindSpawnPoint(startpoint, reinforcement.mins, reinforcement.maxs, spawnpoint, 32)) + { + if (CheckGroundSpawnPoint(spawnpoint, reinforcement.mins, reinforcement.maxs, 256, -1)) + { + num_success++; + float radius = (reinforcement.maxs - reinforcement.mins).length() * 0.5f; + SpawnGrow_Spawn(spawnpoint + (reinforcement.mins + reinforcement.maxs), radius, radius * 2.f); + } + } + } + + if (num_success == 0) + self->monsterinfo.nextframe = FRAME_attack53; +} + +void medic_finish_spawn(edict_t *self) +{ + edict_t *ent; + vec3_t f, r, offset, startpoint, spawnpoint; + int count; + int num_summoned; // should be 1, 3, or 5 + edict_t *designated_enemy; + + AngleVectors(self->s.angles, f, r, nullptr); + + num_summoned = 0; + + for (int32_t i = 0; i < MAX_REINFORCEMENTS; i++, num_summoned++) + if (self->monsterinfo.chosen_reinforcements[i] == 255) + break; + + for (count = 0; count < num_summoned; count++) + { + auto &reinforcement = self->monsterinfo.reinforcements.reinforcements[self->monsterinfo.chosen_reinforcements[count]]; + offset = reinforcement_position[count]; + + if (self->s.scale) + offset *= self->s.scale; + + startpoint = M_ProjectFlashSource(self, offset, f, r); + + // a little off the ground + startpoint[2] += 10 * (self->s.scale ? self->s.scale : 1.0f); + + ent = nullptr; + if (FindSpawnPoint(startpoint, reinforcement.mins, reinforcement.maxs, spawnpoint, 32)) + { + if (CheckSpawnPoint(spawnpoint, reinforcement.mins, reinforcement.maxs)) + ent = CreateGroundMonster(spawnpoint, self->s.angles, reinforcement.mins, reinforcement.maxs, reinforcement.classname, 256); + } + + if (!ent) + continue; + + if (ent->think) + { + ent->nextthink = level.time; + ent->think(ent); + } + + ent->monsterinfo.aiflags |= AI_IGNORE_SHOTS | AI_DO_NOT_COUNT | AI_SPAWNED_MEDIC_C; + ent->monsterinfo.commander = self; + ent->monsterinfo.monster_slots = reinforcement.strength; + self->monsterinfo.monster_used += reinforcement.strength; + + if (self->monsterinfo.aiflags & AI_MEDIC) + designated_enemy = self->oldenemy; + else + designated_enemy = self->enemy; + + if (coop->integer) + { + designated_enemy = PickCoopTarget(ent); + if (designated_enemy) + { + // try to avoid using my enemy + if (designated_enemy == self->enemy) + { + designated_enemy = PickCoopTarget(ent); + if (!designated_enemy) + designated_enemy = self->enemy; + } + } + else + designated_enemy = self->enemy; + } + + if ((designated_enemy) && (designated_enemy->inuse) && (designated_enemy->health > 0)) + { + ent->enemy = designated_enemy; + FoundTarget(ent); + } + else + { + ent->enemy = nullptr; + ent->monsterinfo.stand(ent); + } + } +} + +mframe_t medic_frames_callReinforcements[] = { + // ROGUE - 33-36 now ai_charge + { ai_charge, 2 }, // 33 + { ai_charge, 3 }, + { ai_charge, 5 }, + { ai_charge, 4.4f }, // 36 + { ai_charge, 4.7f }, + { ai_charge, 5 }, + { ai_charge, 6 }, + { ai_charge, 4 }, // 40 + { ai_charge, 0, monster_footstep }, + { ai_move, 0, medic_start_spawn }, // 42 + { ai_move }, // 43 -- 43 through 47 are skipped + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move, 0, medic_determine_spawn }, // 48 + { ai_charge, 0, medic_spawngrows }, // 49 + { ai_move }, // 50 + { ai_move }, // 51 + { ai_move, -15, medic_finish_spawn }, // 52 + { ai_move, -1.5f }, + { ai_move, -1.2f }, + { ai_move, -3, monster_footstep } +}; +MMOVE_T(medic_move_callReinforcements) = { FRAME_attack33, FRAME_attack55, medic_frames_callReinforcements, medic_run }; + +MONSTERINFO_ATTACK(medic_attack) (edict_t *self) -> void +{ + monster_done_dodge(self); + + float enemy_range = range_to(self, self->enemy); + + // signal from checkattack to spawn + if (self->monsterinfo.aiflags & AI_BLOCKED) + { + M_SetAnimation(self, &medic_move_callReinforcements); + self->monsterinfo.aiflags &= ~AI_BLOCKED; + } + + float r = frandom(); + if (self->monsterinfo.aiflags & AI_MEDIC) + { + if ((self->mass > 400) && (r > 0.8f) && M_SlotsLeft(self)) + M_SetAnimation(self, &medic_move_callReinforcements); + else + M_SetAnimation(self, &medic_move_attackCable); + } + else + { + if (self->monsterinfo.attack_state == AS_BLIND) + { + M_SetAnimation(self, &medic_move_callReinforcements); + return; + } + if ((self->mass > 400) && (r > 0.2f) && (enemy_range > RANGE_MELEE) && M_SlotsLeft(self)) + M_SetAnimation(self, &medic_move_callReinforcements); + else + M_SetAnimation(self, &medic_move_attackBlaster); + } +} + +MONSTERINFO_CHECKATTACK(medic_checkattack) (edict_t *self) -> bool +{ + if (self->monsterinfo.aiflags & AI_MEDIC) + { + // if our target went away + if ((!self->enemy) || (!self->enemy->inuse)) + { + abortHeal(self, true, false, false); + return false; + } + + // if we ran out of time, give up + if (self->timestamp < level.time) + { + abortHeal(self, true, false, true); + self->timestamp = 0_ms; + return false; + } + + if (realrange(self, self->enemy) < MEDIC_MAX_HEAL_DISTANCE + 10) + { + medic_attack(self); + return true; + } + else + { + self->monsterinfo.attack_state = AS_STRAIGHT; + return false; + } + } + + if (self->enemy->client && !visible(self, self->enemy) && M_SlotsLeft(self)) + { + self->monsterinfo.attack_state = AS_BLIND; + return true; + } + + // give a LARGE bias to spawning things when we have room + // use AI_BLOCKED as a signal to attack to spawn + if (self->monsterinfo.monster_slots && (frandom() < 0.8f) && (M_SlotsLeft(self) > self->monsterinfo.monster_slots * 0.8f) && (realrange(self, self->enemy) > 150)) + { + self->monsterinfo.aiflags |= AI_BLOCKED; + self->monsterinfo.attack_state = AS_MISSILE; + return true; + } + + // ROGUE + // since his idle animation looks kinda bad in combat, always attack + // when he's on a combat point + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + { + self->monsterinfo.attack_state = AS_MISSILE; + return true; + } + + return M_CheckAttack(self); +} + +void MedicCommanderCache() +{ + gi.modelindex("models/items/spawngro3/tris.md2"); +} + +MONSTERINFO_DUCK(medic_duck) (edict_t *self, gtime_t eta) -> bool +{ + // don't dodge if you're healing + if (self->monsterinfo.aiflags & AI_MEDIC) + return false; + + if ((self->monsterinfo.active_move == &medic_move_attackHyperBlaster) || + (self->monsterinfo.active_move == &medic_move_attackCable) || + (self->monsterinfo.active_move == &medic_move_attackBlaster) || + (self->monsterinfo.active_move == &medic_move_callReinforcements)) + { + // he ignores skill + self->monsterinfo.unduck(self); + return false; + } + + M_SetAnimation(self, &medic_move_duck); + + return true; +} + +MONSTERINFO_SIDESTEP(medic_sidestep) (edict_t *self) -> bool +{ + if ((self->monsterinfo.active_move == &medic_move_attackHyperBlaster) || + (self->monsterinfo.active_move == &medic_move_attackCable) || + (self->monsterinfo.active_move == &medic_move_attackBlaster) || + (self->monsterinfo.active_move == &medic_move_callReinforcements)) + { + // if we're shooting, don't dodge + return false; + } + + if (self->monsterinfo.active_move != &medic_move_run) + M_SetAnimation(self, &medic_move_run); + + return true; +} + +//=========== +// PGM +MONSTERINFO_BLOCKED(medic_blocked) (edict_t *self, float dist) -> bool +{ + if (blocked_checkplat(self, dist)) + return true; + + return false; +} +// PGM +//=========== + +/*QUAKED monster_medic_commander (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight + */ +/*QUAKED monster_medic (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight +model="models/monsters/medic/tris.md2" +*/ +void SP_monster_medic(edict_t *self) +{ + if ( !M_AllowSpawn( self ) ) { + G_FreeEdict( self ); + return; + } + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex("models/monsters/medic/tris.md2"); + + gi.modelindex("models/monsters/medic/gibs/chest.md2"); + gi.modelindex("models/monsters/medic/gibs/gun.md2"); + gi.modelindex("models/monsters/medic/gibs/head.md2"); + gi.modelindex("models/monsters/medic/gibs/hook.md2"); + gi.modelindex("models/monsters/medic/gibs/leg.md2"); + + self->mins = { -24, -24, -24 }; + self->maxs = { 24, 24, 32 }; + + // PMM + if (strcmp(self->classname, "monster_medic_commander") == 0) + { + self->health = 600 * st.health_multiplier; + self->gib_health = -130; + self->mass = 600; + self->yaw_speed = 40; // default is 20 + MedicCommanderCache(); + } + else + { + // PMM + self->health = 300 * st.health_multiplier; + self->gib_health = -130; + self->mass = 400; + } + + self->pain = medic_pain; + self->die = medic_die; + + self->monsterinfo.stand = medic_stand; + self->monsterinfo.walk = medic_walk; + self->monsterinfo.run = medic_run; + // pmm + self->monsterinfo.dodge = M_MonsterDodge; + self->monsterinfo.duck = medic_duck; + self->monsterinfo.unduck = monster_duck_up; + self->monsterinfo.sidestep = medic_sidestep; + self->monsterinfo.blocked = medic_blocked; + // pmm + self->monsterinfo.attack = medic_attack; + self->monsterinfo.melee = nullptr; + self->monsterinfo.sight = medic_sight; + self->monsterinfo.idle = medic_idle; + self->monsterinfo.search = medic_search; + self->monsterinfo.checkattack = medic_checkattack; + self->monsterinfo.setskin = medic_setskin; + + gi.linkentity(self); + + M_SetAnimation(self, &medic_move_stand); + self->monsterinfo.scale = MODEL_SCALE; + + walkmonster_start(self); + + // PMM + self->monsterinfo.aiflags |= AI_IGNORE_SHOTS; + + if (self->mass > 400) + { + self->s.skinnum = 2; + + // commander sounds + commander_sound_idle1 = gi.soundindex("medic_commander/medidle.wav"); + commander_sound_pain1 = gi.soundindex("medic_commander/medpain1.wav"); + commander_sound_pain2 = gi.soundindex("medic_commander/medpain2.wav"); + commander_sound_die = gi.soundindex("medic_commander/meddeth.wav"); + commander_sound_sight = gi.soundindex("medic_commander/medsght.wav"); + commander_sound_search = gi.soundindex("medic_commander/medsrch.wav"); + commander_sound_hook_launch = gi.soundindex("medic_commander/medatck2c.wav"); + commander_sound_hook_hit = gi.soundindex("medic_commander/medatck3a.wav"); + commander_sound_hook_heal = gi.soundindex("medic_commander/medatck4a.wav"); + commander_sound_hook_retract = gi.soundindex("medic_commander/medatck5a.wav"); + commander_sound_spawn = gi.soundindex("medic_commander/monsterspawn1.wav"); + gi.soundindex("tank/tnkatck3.wav"); + + const char *reinforcements = default_reinforcements; + + if (!st.was_key_specified("monster_slots")) + self->monsterinfo.monster_slots = default_monster_slots_base; + if (st.was_key_specified("reinforcements")) + reinforcements = st.reinforcements; + + if (self->monsterinfo.monster_slots && reinforcements && *reinforcements) + { + if (skill->integer) + self->monsterinfo.monster_slots += floor(self->monsterinfo.monster_slots * (skill->value / 2.f)); + + M_SetupReinforcements(reinforcements, self->monsterinfo.reinforcements); + } + } + else + { + sound_idle1 = gi.soundindex("medic/idle.wav"); + sound_pain1 = gi.soundindex("medic/medpain1.wav"); + sound_pain2 = gi.soundindex("medic/medpain2.wav"); + sound_die = gi.soundindex("medic/meddeth1.wav"); + sound_sight = gi.soundindex("medic/medsght1.wav"); + sound_search = gi.soundindex("medic/medsrch1.wav"); + sound_hook_launch = gi.soundindex("medic/medatck2.wav"); + sound_hook_hit = gi.soundindex("medic/medatck3.wav"); + sound_hook_heal = gi.soundindex("medic/medatck4.wav"); + sound_hook_retract = gi.soundindex("medic/medatck5.wav"); + gi.soundindex("medic/medatck1.wav"); + + self->s.skinnum = 0; + } + // pmm +} diff --git a/rerelease/m_medic.h b/rerelease/m_medic.h new file mode 100644 index 0000000..388cd00 --- /dev/null +++ b/rerelease/m_medic.h @@ -0,0 +1,248 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// G:\quake2\baseq2\models/monsters/medic + +// This file generated by ModelGen - Do NOT Modify + +enum +{ + FRAME_walk1, + FRAME_walk2, + FRAME_walk3, + FRAME_walk4, + FRAME_walk5, + FRAME_walk6, + FRAME_walk7, + FRAME_walk8, + FRAME_walk9, + FRAME_walk10, + FRAME_walk11, + FRAME_walk12, + FRAME_wait1, + FRAME_wait2, + FRAME_wait3, + FRAME_wait4, + FRAME_wait5, + FRAME_wait6, + FRAME_wait7, + FRAME_wait8, + FRAME_wait9, + FRAME_wait10, + FRAME_wait11, + FRAME_wait12, + FRAME_wait13, + FRAME_wait14, + FRAME_wait15, + FRAME_wait16, + FRAME_wait17, + FRAME_wait18, + FRAME_wait19, + FRAME_wait20, + FRAME_wait21, + FRAME_wait22, + FRAME_wait23, + FRAME_wait24, + FRAME_wait25, + FRAME_wait26, + FRAME_wait27, + FRAME_wait28, + FRAME_wait29, + FRAME_wait30, + FRAME_wait31, + FRAME_wait32, + FRAME_wait33, + FRAME_wait34, + FRAME_wait35, + FRAME_wait36, + FRAME_wait37, + FRAME_wait38, + FRAME_wait39, + FRAME_wait40, + FRAME_wait41, + FRAME_wait42, + FRAME_wait43, + FRAME_wait44, + FRAME_wait45, + FRAME_wait46, + FRAME_wait47, + FRAME_wait48, + FRAME_wait49, + FRAME_wait50, + FRAME_wait51, + FRAME_wait52, + FRAME_wait53, + FRAME_wait54, + FRAME_wait55, + FRAME_wait56, + FRAME_wait57, + FRAME_wait58, + FRAME_wait59, + FRAME_wait60, + FRAME_wait61, + FRAME_wait62, + FRAME_wait63, + FRAME_wait64, + FRAME_wait65, + FRAME_wait66, + FRAME_wait67, + FRAME_wait68, + FRAME_wait69, + FRAME_wait70, + FRAME_wait71, + FRAME_wait72, + FRAME_wait73, + FRAME_wait74, + FRAME_wait75, + FRAME_wait76, + FRAME_wait77, + FRAME_wait78, + FRAME_wait79, + FRAME_wait80, + FRAME_wait81, + FRAME_wait82, + FRAME_wait83, + FRAME_wait84, + FRAME_wait85, + FRAME_wait86, + FRAME_wait87, + FRAME_wait88, + FRAME_wait89, + FRAME_wait90, + FRAME_run1, + FRAME_run2, + FRAME_run3, + FRAME_run4, + FRAME_run5, + FRAME_run6, + FRAME_paina1, + FRAME_paina2, + FRAME_paina3, + FRAME_paina4, + FRAME_paina5, + FRAME_paina6, + FRAME_paina7, + FRAME_paina8, + FRAME_painb1, + FRAME_painb2, + FRAME_painb3, + FRAME_painb4, + FRAME_painb5, + FRAME_painb6, + FRAME_painb7, + FRAME_painb8, + FRAME_painb9, + FRAME_painb10, + FRAME_painb11, + FRAME_painb12, + FRAME_painb13, + FRAME_painb14, + FRAME_painb15, + FRAME_duck1, + FRAME_duck2, + FRAME_duck3, + FRAME_duck4, + FRAME_duck5, + FRAME_duck6, + FRAME_duck7, + FRAME_duck8, + FRAME_duck9, + FRAME_duck10, + FRAME_duck11, + FRAME_duck12, + FRAME_duck13, + FRAME_duck14, + FRAME_duck15, + FRAME_duck16, + FRAME_death1, + FRAME_death2, + FRAME_death3, + FRAME_death4, + FRAME_death5, + FRAME_death6, + FRAME_death7, + FRAME_death8, + FRAME_death9, + FRAME_death10, + FRAME_death11, + FRAME_death12, + FRAME_death13, + FRAME_death14, + FRAME_death15, + FRAME_death16, + FRAME_death17, + FRAME_death18, + FRAME_death19, + FRAME_death20, + FRAME_death21, + FRAME_death22, + FRAME_death23, + FRAME_death24, + FRAME_death25, + FRAME_death26, + FRAME_death27, + FRAME_death28, + FRAME_death29, + FRAME_death30, + FRAME_attack1, + FRAME_attack2, + FRAME_attack3, + FRAME_attack4, + FRAME_attack5, + FRAME_attack6, + FRAME_attack7, + FRAME_attack8, + FRAME_attack9, + FRAME_attack10, + FRAME_attack11, + FRAME_attack12, + FRAME_attack13, + FRAME_attack14, + FRAME_attack15, + FRAME_attack16, + FRAME_attack17, + FRAME_attack18, + FRAME_attack19, + FRAME_attack20, + FRAME_attack21, + FRAME_attack22, + FRAME_attack23, + FRAME_attack24, + FRAME_attack25, + FRAME_attack26, + FRAME_attack27, + FRAME_attack28, + FRAME_attack29, + FRAME_attack30, + FRAME_attack31, + FRAME_attack32, + FRAME_attack33, + FRAME_attack34, + FRAME_attack35, + FRAME_attack36, + FRAME_attack37, + FRAME_attack38, + FRAME_attack39, + FRAME_attack40, + FRAME_attack41, + FRAME_attack42, + FRAME_attack43, + FRAME_attack44, + FRAME_attack45, + FRAME_attack46, + FRAME_attack47, + FRAME_attack48, + FRAME_attack49, + FRAME_attack50, + FRAME_attack51, + FRAME_attack52, + FRAME_attack53, + FRAME_attack54, + FRAME_attack55, + FRAME_attack56, + FRAME_attack57, + FRAME_attack58, + FRAME_attack59, + FRAME_attack60 +}; + +constexpr float MODEL_SCALE = 1.000000f; diff --git a/rerelease/m_move.cpp b/rerelease/m_move.cpp new file mode 100644 index 0000000..d431341 --- /dev/null +++ b/rerelease/m_move.cpp @@ -0,0 +1,1497 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// m_move.c -- monster movement + +#include "g_local.h" + +// this is used for communications out of sv_movestep to say what entity +// is blocking us +edict_t *new_bad; // pmm + +/* +============= +M_CheckBottom + +Returns false if any part of the bottom of the entity is off an edge that +is not a staircase. + +============= +*/ +bool M_CheckBottom_Fast_Generic(const vec3_t &absmins, const vec3_t &absmaxs, bool ceiling) +{ + // PGM + // FIXME - this will only handle 0,0,1 and 0,0,-1 gravity vectors + vec3_t start; + + start[2] = absmins[2] - 1; + if (ceiling) + start[2] = absmaxs[2] + 1; + // PGM + + for (int x = 0; x <= 1; x++) + for (int y = 0; y <= 1; y++) + { + start[0] = x ? absmaxs[0] : absmins[0]; + start[1] = y ? absmaxs[1] : absmins[1]; + if (gi.pointcontents(start) != CONTENTS_SOLID) + return false; + } + + return true; // we got out easy +} + +bool M_CheckBottom_Slow_Generic(const vec3_t &origin, const vec3_t &mins, const vec3_t &maxs, edict_t *ignore, contents_t mask, bool ceiling, bool allow_any_step_height) +{ + vec3_t start; + + // + // check it for real... + // + vec3_t step_quadrant_size = (maxs - mins) * 0.5f; + step_quadrant_size.z = 0; + + vec3_t half_step_quadrant = step_quadrant_size * 0.5f; + vec3_t half_step_quadrant_mins = -half_step_quadrant; + + vec3_t stop; + + start[0] = stop[0] = origin.x; + start[1] = stop[1] = origin.y; + + // PGM + if (!ceiling) + { + start[2] = origin.z + mins.z; + stop[2] = start[2] - STEPSIZE * 2; + } + else + { + start[2] = origin.z + maxs.z; + stop[2] = start[2] + STEPSIZE * 2; + } + // PGM + + vec3_t mins_no_z = mins; + vec3_t maxs_no_z = maxs; + mins_no_z.z = maxs_no_z.z = 0; + + trace_t trace = gi.trace(start, mins_no_z, maxs_no_z, stop, ignore, mask); + + if (trace.fraction == 1.0f) + return false; + + // [Paril-KEX] + if (allow_any_step_height) + return true; + + start[0] = stop[0] = origin.x + ((mins.x + maxs.x) * 0.5f); + start[1] = stop[1] = origin.y + ((mins.y + maxs.y) * 0.5f); + + float mid = trace.endpos[2]; + + // the corners must be within 16 of the midpoint + for (int32_t x = 0; x <= 1; x++) + for (int32_t y = 0; y <= 1; y++) + { + vec3_t quadrant_start = start; + + if (x) + quadrant_start.x += half_step_quadrant.x; + else + quadrant_start.x -= half_step_quadrant.x; + + if (y) + quadrant_start.y += half_step_quadrant.y; + else + quadrant_start.y -= half_step_quadrant.y; + + vec3_t quadrant_end = quadrant_start; + quadrant_end.z = stop.z; + + trace = gi.trace(quadrant_start, half_step_quadrant_mins, half_step_quadrant, quadrant_end, ignore, mask); + + // PGM + // FIXME - this will only handle 0,0,1 and 0,0,-1 gravity vectors + if (ceiling) + { + if (trace.fraction == 1.0f || trace.endpos[2] - mid > (STEPSIZE)) + return false; + } + else + { + if (trace.fraction == 1.0f || mid - trace.endpos[2] > (STEPSIZE)) + return false; + } + // PGM + } + + return true; +} + +bool M_CheckBottom(edict_t *ent) +{ + // if all of the points under the corners are solid world, don't bother + // with the tougher checks + + if (M_CheckBottom_Fast_Generic(ent->s.origin + ent->mins, ent->s.origin + ent->maxs, ent->gravityVector[2] > 0)) + return true; // we got out easy + + contents_t mask = (ent->svflags & SVF_MONSTER) ? MASK_MONSTERSOLID : (MASK_SOLID | CONTENTS_MONSTER | CONTENTS_PLAYER); + return M_CheckBottom_Slow_Generic(ent->s.origin, ent->mins, ent->maxs, ent, mask, ent->gravityVector[2] > 0, ent->spawnflags.has(SPAWNFLAG_MONSTER_SUPER_STEP)); +} + +//============ +// ROGUE +bool IsBadAhead(edict_t *self, edict_t *bad, const vec3_t &move) +{ + vec3_t dir; + vec3_t forward; + float dp_bad, dp_move; + vec3_t move_copy; + + move_copy = move; + + dir = bad->s.origin - self->s.origin; + dir.normalize(); + AngleVectors(self->s.angles, forward, nullptr, nullptr); + dp_bad = forward.dot(dir); + + move_copy.normalize(); + AngleVectors(self->s.angles, forward, nullptr, nullptr); + dp_move = forward.dot(move_copy); + + if ((dp_bad < 0) && (dp_move < 0)) + return true; + if ((dp_bad > 0) && (dp_move > 0)) + return true; + + return false; +} + +static vec3_t G_IdealHoverPosition(edict_t *ent) +{ + if ((!ent->enemy && !(ent->monsterinfo.aiflags & AI_MEDIC)) || (ent->monsterinfo.aiflags & (AI_COMBAT_POINT | AI_SOUND_TARGET | AI_HINT_PATH | AI_PATHING))) + return { 0, 0, 0 }; // go right for the center + + // pick random direction + float theta = frandom(2 * PIf); + float phi; + + // buzzards pick half sphere + if (ent->monsterinfo.fly_above) + phi = acos(0.7f + frandom(0.3f)); + else if (ent->monsterinfo.fly_buzzard || (ent->monsterinfo.aiflags & AI_MEDIC)) + phi = acos(frandom()); + // non-buzzards pick a level around the center + else + phi = acos(crandom() * 0.06f); + + vec3_t d { + sin(phi) * cos(theta), + sin(phi) * sin(theta), + cos(phi) + }; + + return d * frandom(ent->monsterinfo.fly_min_distance, ent->monsterinfo.fly_max_distance); +} + +inline bool SV_flystep_testvisposition(vec3_t start, vec3_t end, vec3_t starta, vec3_t startb, edict_t *ent) +{ + trace_t tr = gi.traceline(start, end, ent, MASK_SOLID | CONTENTS_MONSTERCLIP); + + if (tr.fraction == 1.0f) + { + tr = gi.trace(starta, ent->mins, ent->maxs, startb, ent, MASK_SOLID | CONTENTS_MONSTERCLIP); + + if (tr.fraction == 1.0f) + return true; + } + + return false; +} + +static bool SV_alternate_flystep(edict_t *ent, vec3_t move, bool relink, edict_t *current_bad) +{ + // swimming monsters just follow their velocity in the air + if ((ent->flags & FL_SWIM) && ent->waterlevel < WATER_UNDER) + return true; + + if (ent->monsterinfo.fly_position_time <= level.time || + (ent->enemy && ent->monsterinfo.fly_pinned && !visible(ent, ent->enemy))) + { + ent->monsterinfo.fly_pinned = false; + ent->monsterinfo.fly_position_time = level.time + random_time(3_sec, 10_sec); + ent->monsterinfo.fly_ideal_position = G_IdealHoverPosition(ent); + } + + vec3_t towards_origin, towards_velocity = {}; + + float current_speed; + vec3_t dir = ent->velocity.normalized(current_speed); + + // FIXME + if (isnan(dir[0]) || isnan(dir[1]) || isnan(dir[2])) + { +#if defined(_DEBUG) && defined(_WIN32) + __debugbreak(); +#endif + return false; + } + + if (ent->monsterinfo.aiflags & AI_PATHING) + towards_origin = (ent->monsterinfo.nav_path.returnCode == PathReturnCode::TraversalPending) ? + ent->monsterinfo.nav_path.secondMovePoint : ent->monsterinfo.nav_path.firstMovePoint; + else if (ent->enemy && !(ent->monsterinfo.aiflags & (AI_COMBAT_POINT | AI_SOUND_TARGET | AI_LOST_SIGHT))) + { + towards_origin = ent->enemy->s.origin; + towards_velocity = ent->enemy->velocity; + } + else if (ent->goalentity) + towards_origin = ent->goalentity->s.origin; + else // what we're going towards probably died or something + { + // change speed + if (current_speed) + { + if (current_speed > 0) + current_speed = max(0.f, current_speed - ent->monsterinfo.fly_acceleration); + else if (current_speed < 0) + current_speed = min(0.f, current_speed + ent->monsterinfo.fly_acceleration); + + ent->velocity = dir * current_speed; + } + + return true; + } + + vec3_t wanted_pos; + + if (ent->monsterinfo.fly_pinned) + wanted_pos = ent->monsterinfo.fly_ideal_position; + else if (ent->monsterinfo.aiflags & (AI_PATHING | AI_COMBAT_POINT | AI_SOUND_TARGET | AI_LOST_SIGHT)) + wanted_pos = towards_origin; + else + wanted_pos = (towards_origin + (towards_velocity * 0.25f)) + ent->monsterinfo.fly_ideal_position; + + // find a place we can fit in from here + trace_t tr = gi.trace(towards_origin, { -8.f, -8.f, -8.f }, { 8.f, 8.f, 8.f }, wanted_pos, ent, MASK_SOLID | CONTENTS_MONSTERCLIP); + + if (!tr.allsolid) + wanted_pos = tr.endpos; + + float dist_to_wanted; + vec3_t dest_diff = (wanted_pos - ent->s.origin); + + if (dest_diff.z > ent->mins.z && dest_diff.z < ent->maxs.z) + dest_diff.z = 0; + + vec3_t wanted_dir = dest_diff.normalized(dist_to_wanted); + + if (!(ent->monsterinfo.aiflags & AI_MANUAL_STEERING)) + ent->ideal_yaw = vectoyaw((towards_origin - ent->s.origin).normalized()); + + // check if we're blocked from moving this way from where we are + tr = gi.trace(ent->s.origin, ent->mins, ent->maxs, ent->s.origin + (wanted_dir * ent->monsterinfo.fly_acceleration), ent, MASK_SOLID | CONTENTS_MONSTERCLIP); + + vec3_t aim_fwd, aim_rgt, aim_up; + vec3_t yaw_angles = { 0, ent->s.angles.y, 0 }; + + AngleVectors(yaw_angles, aim_fwd, aim_rgt, aim_up); + + // it's a fairly close block, so we may want to shift more dramatically + if (tr.fraction < 0.25f) + { + bool bottom_visible = SV_flystep_testvisposition(ent->s.origin + vec3_t{0, 0, ent->mins.z}, wanted_pos, + ent->s.origin, ent->s.origin + vec3_t{0, 0, ent->mins.z - ent->monsterinfo.fly_acceleration}, ent); + bool top_visible = SV_flystep_testvisposition(ent->s.origin + vec3_t{0, 0, ent->maxs.z}, wanted_pos, + ent->s.origin, ent->s.origin + vec3_t{0, 0, ent->maxs.z + ent->monsterinfo.fly_acceleration}, ent); + + // top & bottom are same, so we need to try right/left + if (bottom_visible == top_visible) + { + bool left_visible = gi.traceline(ent->s.origin + aim_fwd.scaled(ent->maxs) - aim_rgt.scaled(ent->maxs), wanted_pos, ent, MASK_SOLID | CONTENTS_MONSTERCLIP).fraction == 1.0f; + bool right_visible = gi.traceline(ent->s.origin + aim_fwd.scaled(ent->maxs) + aim_rgt.scaled(ent->maxs), wanted_pos, ent, MASK_SOLID | CONTENTS_MONSTERCLIP).fraction == 1.0f; + + if (left_visible != right_visible) + { + if (right_visible) + wanted_dir += aim_rgt; + else + wanted_dir -= aim_rgt; + } + else + // we're probably stuck, push us directly away + wanted_dir = tr.plane.normal; + } + else + { + if (top_visible) + wanted_dir += aim_up; + else + wanted_dir -= aim_up; + } + + wanted_dir.normalize(); + } + + // the closer we are to zero, the more we can change dir. + // if we're pushed past our max speed we shouldn't + // turn at all. + float turn_factor; + + if (((ent->monsterinfo.fly_thrusters && !ent->monsterinfo.fly_pinned) || ent->monsterinfo.aiflags & (AI_PATHING | AI_COMBAT_POINT | AI_LOST_SIGHT)) && dir.dot(wanted_dir) > 0.0f) + turn_factor = 0.45f; + else + turn_factor = min(1.f, 0.84f + (0.08f * (current_speed / ent->monsterinfo.fly_speed))); + + vec3_t final_dir = dir ? dir : wanted_dir; + + // FIXME + if (isnan(final_dir[0]) || isnan(final_dir[1]) || isnan(final_dir[2])) + { +#if defined(_DEBUG) && defined(_WIN32) + __debugbreak(); +#endif + return false; + } + + // swimming monsters don't exit water voluntarily, and + // flying monsters don't enter water voluntarily (but will + // try to leave it) + bool bad_movement_direction = false; + + //if (!(ent->monsterinfo.aiflags & AI_COMBAT_POINT)) + { + if (ent->flags & FL_SWIM) + bad_movement_direction = !(gi.pointcontents(ent->s.origin + (wanted_dir * current_speed)) & CONTENTS_WATER); + else if ((ent->flags & FL_FLY) && ent->waterlevel < WATER_UNDER) + bad_movement_direction = gi.pointcontents(ent->s.origin + (wanted_dir * current_speed)) & CONTENTS_WATER; + } + + if (bad_movement_direction) + { + if (ent->monsterinfo.fly_recovery_time < level.time) + { + ent->monsterinfo.fly_recovery_dir = vec3_t{ crandom(), crandom(), crandom() }.normalized(); + ent->monsterinfo.fly_recovery_time = level.time + 1_sec; + } + + wanted_dir = ent->monsterinfo.fly_recovery_dir; + } + + if (dir && turn_factor > 0) + final_dir = slerp(dir, wanted_dir, 1.0f - turn_factor).normalized(); + + // the closer we are to the wanted position, we want to slow + // down so we don't fly past it. + float speed_factor; + + if (!ent->enemy || (ent->monsterinfo.fly_thrusters && !ent->monsterinfo.fly_pinned) || (ent->monsterinfo.aiflags & (AI_PATHING | AI_COMBAT_POINT | AI_LOST_SIGHT))) + speed_factor = 1.f; + else if (aim_fwd.dot(wanted_dir) < -0.25 && dir) + speed_factor = 0.f; + else + speed_factor = min(1.f, dist_to_wanted / ent->monsterinfo.fly_speed); + + if (bad_movement_direction) + speed_factor = -speed_factor; + + float accel = ent->monsterinfo.fly_acceleration; + + // if we're flying away from our destination, apply reverse thrusters + if (final_dir.dot(wanted_dir) < 0.25f) + accel *= 2.0f; + + float wanted_speed = ent->monsterinfo.fly_speed * speed_factor; + + if (ent->monsterinfo.aiflags & AI_MANUAL_STEERING) + wanted_speed = 0; + + // change speed + if (current_speed > wanted_speed) + current_speed = max(wanted_speed, current_speed - accel); + else if (current_speed < wanted_speed) + current_speed = min(wanted_speed, current_speed + accel); + + // FIXME + if (isnan(final_dir[0]) || isnan(final_dir[1]) || isnan(final_dir[2]) || + isnan(current_speed)) + { +#if defined(_DEBUG) && defined(_WIN32) + __debugbreak(); +#endif + return false; + } + + // commit + ent->velocity = final_dir * current_speed; + + // for buzzards, set their pitch + if (ent->enemy && (ent->monsterinfo.fly_buzzard || (ent->monsterinfo.aiflags & AI_MEDIC))) + { + vec3_t d = (ent->s.origin - towards_origin).normalized(); + d = vectoangles(d); + ent->s.angles[PITCH] = LerpAngle(ent->s.angles[PITCH], -d[PITCH], gi.frame_time_s * 4.0f); + } + else + ent->s.angles[PITCH] = 0; + + return true; +} + +// flying monsters don't step up +static bool SV_flystep(edict_t *ent, vec3_t move, bool relink, edict_t *current_bad) +{ + if (ent->monsterinfo.aiflags & AI_ALTERNATE_FLY) + { + if (SV_alternate_flystep(ent, move, relink, current_bad)) + return true; + } + + // try the move + vec3_t oldorg = ent->s.origin; + vec3_t neworg = ent->s.origin + move; + + // fixme: move to monsterinfo + // we want the carrier to stay a certain distance off the ground, to help prevent him + // from shooting his fliers, who spawn in below him + float minheight; + + if (!strcmp(ent->classname, "monster_carrier")) + minheight = 104; + else + minheight = 40; + + // try one move with vertical motion, then one without + for (int i = 0; i < 2; i++) + { + vec3_t new_move = move; + + if (i == 0 && ent->enemy) + { + if (!ent->goalentity) + ent->goalentity = ent->enemy; + + vec3_t &goal_position = (ent->monsterinfo.aiflags & AI_PATHING) ? ent->monsterinfo.nav_path.firstMovePoint : ent->goalentity->s.origin; + + float dz = ent->s.origin[2] - goal_position[2]; + float dist = move.length(); + + if (ent->goalentity->client) + { + if (dz > minheight) + { + // pmm + new_move *= 0.5f; + new_move[2] -= dist; + } + if (!((ent->flags & FL_SWIM) && (ent->waterlevel < WATER_WAIST))) + if (dz < (minheight - 10)) + { + new_move *= 0.5f; + new_move[2] += dist; + } + } + else + { + // RAFAEL + if (strcmp(ent->classname, "monster_fixbot") == 0) + { + if (ent->s.frame >= 105 && ent->s.frame <= 120) + { + if (dz > 12) + new_move[2]--; + else if (dz < -12) + new_move[2]++; + } + else if (ent->s.frame >= 31 && ent->s.frame <= 88) + { + if (dz > 12) + new_move[2] -= 12; + else if (dz < -12) + new_move[2] += 12; + } + else + { + if (dz > 12) + new_move[2] -= 8; + else if (dz < -12) + new_move[2] += 8; + } + } + else + { + // RAFAEL + if (dz > 0) + { + new_move *= 0.5f; + new_move[2] -= min(dist, dz); + } + else if (dz < 0) + { + new_move *= 0.5f; + new_move[2] += -max(-dist, dz); + } + // RAFAEL + } + // RAFAEL + } + } + + neworg = ent->s.origin + new_move; + + trace_t trace = gi.trace(ent->s.origin, ent->mins, ent->maxs, neworg, ent, MASK_MONSTERSOLID); + + // fly monsters don't enter water voluntarily + if (ent->flags & FL_FLY) + { + if (!ent->waterlevel) + { + vec3_t test { trace.endpos[0], trace.endpos[1], trace.endpos[2] + ent->mins[2] + 1 }; + contents_t contents = gi.pointcontents(test); + if (contents & MASK_WATER) + return false; + } + } + + // swim monsters don't exit water voluntarily + if (ent->flags & FL_SWIM) + { + if (ent->waterlevel < WATER_WAIST) + { + vec3_t test { trace.endpos[0], trace.endpos[1], trace.endpos[2] + ent->mins[2] + 1 }; + contents_t contents = gi.pointcontents(test); + if (!(contents & MASK_WATER)) + return false; + } + } + + // ROGUE + if ((trace.fraction == 1) && (!trace.allsolid) && (!trace.startsolid)) + // ROGUE + { + ent->s.origin = trace.endpos; + //===== + // PGM + if (!current_bad && CheckForBadArea(ent)) + ent->s.origin = oldorg; + else + { + if (relink) + { + gi.linkentity(ent); + G_TouchTriggers(ent); + } + + return true; + } + // PGM + //===== + } + + G_Impact(ent, trace); + + if (!ent->enemy) + break; + } + + return false; +} + +/* +============= +SV_movestep + +Called by monster program code. +The move will be adjusted for slopes and stairs, but if the move isn't +possible, no move is done, false is returned, and +pr_global_struct->trace_normal is set to the normal of the blocking wall +============= +*/ +// FIXME since we need to test end position contents here, can we avoid doing +// it again later in catagorize position? +bool SV_movestep(edict_t *ent, vec3_t move, bool relink) +{ + //====== + // PGM + edict_t *current_bad = nullptr; + + // PMM - who cares about bad areas if you're dead? + if (ent->health > 0) + { + current_bad = CheckForBadArea(ent); + if (current_bad) + { + ent->bad_area = current_bad; + + if (ent->enemy && !strcmp(ent->enemy->classname, "tesla_mine")) + { + // if the tesla is in front of us, back up... + if (IsBadAhead(ent, current_bad, move)) + move *= -1; + } + } + else if (ent->bad_area) + { + // if we're no longer in a bad area, get back to business. + ent->bad_area = nullptr; + if (ent->oldenemy) // && ent->bad_area->owner == ent->enemy) + { + ent->enemy = ent->oldenemy; + ent->goalentity = ent->oldenemy; + FoundTarget(ent); + } + } + } + // PGM + //====== + + // flying monsters don't step up + if (ent->flags & (FL_SWIM | FL_FLY)) + return SV_flystep(ent, move, relink, current_bad); + + // try the move + vec3_t oldorg = ent->s.origin; + + float stepsize; + + // push down from a step height above the wished position + if (ent->spawnflags.has(SPAWNFLAG_MONSTER_SUPER_STEP)) + stepsize = 64.f; + else if (!(ent->monsterinfo.aiflags & AI_NOSTEP)) + stepsize = STEPSIZE; + else + stepsize = 1; + + stepsize += 0.75f; + + contents_t mask = (ent->svflags & SVF_MONSTER) ? MASK_MONSTERSOLID : (MASK_SOLID | CONTENTS_MONSTER | CONTENTS_PLAYER); + + vec3_t start_up = oldorg + ent->gravityVector * (-1 * stepsize); + + start_up = gi.trace(oldorg, ent->mins, ent->maxs, start_up, ent, mask).endpos; + + vec3_t end_up = start_up + move; + + trace_t up_trace = gi.trace(start_up, ent->mins, ent->maxs, end_up, ent, mask); + + if (up_trace.startsolid) + { + start_up += ent->gravityVector * (-1 * stepsize); + up_trace = gi.trace(start_up, ent->mins, ent->maxs, end_up, ent, mask); + } + + vec3_t start_fwd = oldorg; + vec3_t end_fwd = start_fwd + move; + + trace_t fwd_trace = gi.trace(start_fwd, ent->mins, ent->maxs, end_fwd, ent, mask); + + if (fwd_trace.startsolid) + { + start_up += ent->gravityVector * (-1 * stepsize); + fwd_trace = gi.trace(start_fwd, ent->mins, ent->maxs, end_fwd, ent, mask); + } + + // pick the one that went farther + trace_t &chosen_forward = (up_trace.fraction > fwd_trace.fraction) ? up_trace : fwd_trace; + + if (chosen_forward.startsolid || chosen_forward.allsolid) + return false; + + int32_t steps = 1; + bool stepped = false; + + if (up_trace.fraction > fwd_trace.fraction) + steps = 2; + + // step us down + vec3_t end = chosen_forward.endpos + (ent->gravityVector * (steps * stepsize)); + trace_t trace = gi.trace(chosen_forward.endpos, ent->mins, ent->maxs, end, ent, mask); + + if (fabsf(ent->s.origin.z - trace.endpos.z) > 8.f) + stepped = true; + + // Paril: improved the water handling here. + // monsters are okay with stepping into water + // up to their waist. + if (ent->waterlevel <= WATER_WAIST) + { + water_level_t end_waterlevel; + contents_t end_watertype; + M_CatagorizePosition(ent, trace.endpos, end_waterlevel, end_watertype); + + // don't go into deep liquids or + // slime/lava voluntarily + if (end_watertype & (CONTENTS_SLIME | CONTENTS_LAVA) || + end_waterlevel > WATER_WAIST) + return false; + } + + if (trace.fraction == 1) + { + // if monster had the ground pulled out, go ahead and fall + if (ent->flags & FL_PARTIALGROUND) + { + ent->s.origin += move; + if (relink) + { + gi.linkentity(ent); + G_TouchTriggers(ent); + } + ent->groundentity = nullptr; + return true; + } + else if (!ent->spawnflags.has(SPAWNFLAG_MONSTER_SUPER_STEP)) + return false; // walked off an edge + } + + // [Paril-KEX] if we didn't move at all (or barely moved), don't count it + if ((trace.endpos - oldorg).length() < move.length() * 0.05f) + { + ent->monsterinfo.bad_move_time = level.time + 1000_ms; + + if (ent->monsterinfo.bump_time < level.time && chosen_forward.fraction < 1.0f) + { + // adjust ideal_yaw to move against the object we hit and try again + vec3_t dir = SlideClipVelocity(AngleVectors(vec3_t{0.f, ent->ideal_yaw, 0.f}).forward, chosen_forward.plane.normal, 1.0f); + float new_yaw = vectoyaw(dir); + + if (dir.lengthSquared() > 0.1f && ent->ideal_yaw != new_yaw) + { + ent->ideal_yaw = new_yaw; + ent->monsterinfo.random_change_time = level.time + 100_ms; + ent->monsterinfo.bump_time = level.time + 200_ms; + return true; + } + } + + return false; + } + + // check point traces down for dangling corners + ent->s.origin = trace.endpos; + + // PGM + // PMM - don't bother with bad areas if we're dead + if (ent->health > 0) + { + // use AI_BLOCKED to tell the calling layer that we're now mad at a tesla + new_bad = CheckForBadArea(ent); + if (!current_bad && new_bad) + { + if (new_bad->owner) + { + if (!strcmp(new_bad->owner->classname, "tesla_mine")) + { + if ((!(ent->enemy)) || (!(ent->enemy->inuse))) + { + TargetTesla(ent, new_bad->owner); + ent->monsterinfo.aiflags |= AI_BLOCKED; + } + else if (!strcmp(ent->enemy->classname, "tesla_mine")) + { + } + else if ((ent->enemy) && (ent->enemy->client)) + { + if (!visible(ent, ent->enemy)) + { + TargetTesla(ent, new_bad->owner); + ent->monsterinfo.aiflags |= AI_BLOCKED; + } + } + else + { + TargetTesla(ent, new_bad->owner); + ent->monsterinfo.aiflags |= AI_BLOCKED; + } + } + } + + ent->s.origin = oldorg; + return false; + } + } + // PGM + + if (!M_CheckBottom(ent)) + { + if (ent->flags & FL_PARTIALGROUND) + { // entity had floor mostly pulled out from underneath it + // and is trying to correct + if (relink) + { + gi.linkentity(ent); + G_TouchTriggers(ent); + } + return true; + } + + // walked off an edge that wasn't a stairway + ent->s.origin = oldorg; + return false; + } + + if (ent->spawnflags.has(SPAWNFLAG_MONSTER_SUPER_STEP)) + { + if (!ent->groundentity || ent->groundentity->solid == SOLID_BSP) + { + if (!(trace.ent->solid == SOLID_BSP)) + { + // walked off an edge + ent->s.origin = oldorg; + M_CheckGround(ent, G_GetClipMask(ent)); + return false; + } + } + } + + // [Paril-KEX] + M_CheckGround(ent, G_GetClipMask(ent)); + + if (!ent->groundentity) + { + // walked off an edge + ent->s.origin = oldorg; + M_CheckGround(ent, G_GetClipMask(ent)); + return false; + } + + if (ent->flags & FL_PARTIALGROUND) + { + ent->flags &= ~FL_PARTIALGROUND; + } + ent->groundentity = trace.ent; + ent->groundentity_linkcount = trace.ent->linkcount; + + // the move is ok + if (relink) + { + gi.linkentity(ent); + G_TouchTriggers(ent); + } + + if (stepped) + ent->s.renderfx |= RF_STAIR_STEP; + + if (trace.fraction < 1.f) + G_Impact(ent, trace); + + return true; +} + +// check if a movement would succeed +bool ai_check_move(edict_t *self, float dist) +{ + if ( ai_movement_disabled->integer ) { + return false; + } + + float yaw = self->s.angles[YAW] * PIf * 2 / 360; + vec3_t move = { + cosf(yaw) * dist, + sinf(yaw) * dist, + 0 + }; + + vec3_t old_origin = self->s.origin; + + if (!SV_movestep(self, move, false)) + return false; + + self->s.origin = old_origin; + gi.linkentity(self); + return true; +} + +//============================================================================ + +/* +=============== +M_ChangeYaw + +=============== +*/ +void M_ChangeYaw(edict_t *ent) +{ + float ideal; + float current; + float move; + float speed; + + current = anglemod(ent->s.angles[YAW]); + ideal = ent->ideal_yaw; + + if (current == ideal) + return; + + move = ideal - current; + // [Paril-KEX] high tick rate + speed = ent->yaw_speed / (gi.tick_rate / 10); + + if (ideal > current) + { + if (move >= 180) + move = move - 360; + } + else + { + if (move <= -180) + move = move + 360; + } + if (move > 0) + { + if (move > speed) + move = speed; + } + else + { + if (move < -speed) + move = -speed; + } + + ent->s.angles[YAW] = anglemod(current + move); +} + +/* +====================== +SV_StepDirection + +Turns to the movement direction, and walks the current distance if +facing it. + +====================== +*/ +bool SV_StepDirection(edict_t *ent, float yaw, float dist, bool allow_no_turns) +{ + vec3_t move, oldorigin; + + if (!ent->inuse) + return true; // PGM g_touchtrigger free problem + + float old_ideal_yaw = ent->ideal_yaw; + float old_current_yaw = ent->s.angles[YAW]; + + ent->ideal_yaw = yaw; + M_ChangeYaw(ent); + + yaw = yaw * PIf * 2 / 360; + move[0] = cosf(yaw) * dist; + move[1] = sinf(yaw) * dist; + move[2] = 0; + + oldorigin = ent->s.origin; + if (SV_movestep(ent, move, false)) + { + ent->monsterinfo.aiflags &= ~AI_BLOCKED; + if (!ent->inuse) + return true; // PGM g_touchtrigger free problem + + if (strncmp(ent->classname, "monster_widow", 13)) + { + if (!FacingIdeal(ent)) + { + // not turned far enough, so don't take the step + // but still turn + ent->s.origin = oldorigin; + M_CheckGround(ent, G_GetClipMask(ent)); + return allow_no_turns; // [Paril-KEX] + } + } + gi.linkentity(ent); + G_TouchTriggers(ent); + G_TouchProjectiles(ent, oldorigin); + return true; + } + gi.linkentity(ent); + G_TouchTriggers(ent); + ent->ideal_yaw = old_ideal_yaw; + ent->s.angles[YAW] = old_current_yaw; + return false; +} + +/* +====================== +SV_FixCheckBottom + +====================== +*/ +void SV_FixCheckBottom(edict_t *ent) +{ + ent->flags |= FL_PARTIALGROUND; +} + +/* +================ +SV_NewChaseDir + +================ +*/ +constexpr float DI_NODIR = -1; + +bool SV_NewChaseDir(edict_t *actor, vec3_t pos, float dist) +{ + float deltax, deltay; + float d[3]; + float tdir, olddir, turnaround; + + olddir = anglemod(truncf(actor->ideal_yaw / 45) * 45); + turnaround = anglemod(olddir - 180); + + deltax = pos[0] - actor->s.origin[0]; + deltay = pos[1] - actor->s.origin[1]; + if (deltax > 10) + d[1] = 0; + else if (deltax < -10) + d[1] = 180; + else + d[1] = DI_NODIR; + if (deltay < -10) + d[2] = 270; + else if (deltay > 10) + d[2] = 90; + else + d[2] = DI_NODIR; + + // try direct route + if (d[1] != DI_NODIR && d[2] != DI_NODIR) + { + if (d[1] == 0) + tdir = d[2] == 90 ? 45.f : 315.f; + else + tdir = d[2] == 90 ? 135.f : 215.f; + + if (tdir != turnaround && SV_StepDirection(actor, tdir, dist, false)) + return true; + } + + // try other directions + if (brandom() || fabsf(deltay) > fabsf(deltax)) + { + tdir = d[1]; + d[1] = d[2]; + d[2] = tdir; + } + + if (d[1] != DI_NODIR && d[1] != turnaround && SV_StepDirection(actor, d[1], dist, false)) + return true; + + if (d[2] != DI_NODIR && d[2] != turnaround && SV_StepDirection(actor, d[2], dist, false)) + return true; + + // ROGUE + if (actor->monsterinfo.blocked) + { + if ((actor->inuse) && (actor->health > 0) && !(actor->monsterinfo.aiflags & AI_TARGET_ANGER)) + { + // if block "succeeds", the actor will not move or turn. + if (actor->monsterinfo.blocked(actor, dist)) + { + actor->monsterinfo.move_block_counter = -2; + return true; + } + + // we couldn't step; instead of running endlessly in our current + // spot, try switching to node navigation temporarily to get to + // where we need to go. + if (!(actor->monsterinfo.aiflags & (AI_LOST_SIGHT | AI_COMBAT_POINT | AI_TARGET_ANGER | AI_PATHING | AI_TEMP_MELEE_COMBAT | AI_NO_PATH_FINDING))) + { + if (++actor->monsterinfo.move_block_counter > 2) + { + actor->monsterinfo.aiflags |= AI_TEMP_MELEE_COMBAT; + actor->monsterinfo.move_block_change_time = level.time + 3_sec; + actor->monsterinfo.move_block_counter = 0; + } + } + } + } + // ROGUE + + /* there is no direct path to the player, so pick another direction */ + + if (olddir != DI_NODIR && SV_StepDirection(actor, olddir, dist, false)) + return true; + + if (brandom()) /*randomly determine direction of search*/ + { + for (tdir = 0; tdir <= 315; tdir += 45) + if (tdir != turnaround && SV_StepDirection(actor, tdir, dist, false)) + return true; + } + else + { + for (tdir = 315; tdir >= 0; tdir -= 45) + if (tdir != turnaround && SV_StepDirection(actor, tdir, dist, false)) + return true; + } + + if (turnaround != DI_NODIR && SV_StepDirection(actor, turnaround, dist, false)) + return true; + + actor->ideal_yaw = frandom(0, 360); // can't move; pick a random yaw... + + // if a bridge was pulled out from underneath a monster, it may not have + // a valid standing position at all + + if (!M_CheckBottom(actor)) + SV_FixCheckBottom(actor); + + return false; +} + +/* +====================== +SV_CloseEnough + +====================== +*/ +bool SV_CloseEnough(edict_t *ent, edict_t *goal, float dist) +{ + int i; + + for (i = 0; i < 3; i++) + { + if (goal->absmin[i] > ent->absmax[i] + dist) + return false; + if (goal->absmax[i] < ent->absmin[i] - dist) + return false; + } + return true; +} + +static bool M_NavPathToGoal(edict_t *self, float dist, const vec3_t &goal) +{ + // mark us as *trying* now (nav_pos is valid) + self->monsterinfo.aiflags |= AI_PATHING; + + vec3_t &path_to = (self->monsterinfo.nav_path.returnCode == PathReturnCode::TraversalPending) ? + self->monsterinfo.nav_path.secondMovePoint : self->monsterinfo.nav_path.firstMovePoint; + + if ((self->monsterinfo.nav_path.returnCode != PathReturnCode::TraversalPending && (path_to - self->s.origin).length() <= (self->size.length() * 0.5f)) || + self->monsterinfo.nav_path_cache_time <= level.time) + { + PathRequest request; + if (self->enemy) + request.goal = self->enemy->s.origin; + else + request.goal = self->goalentity->s.origin; + request.moveDist = dist; + if (g_debug_monster_paths->integer == 1) + request.debugging.drawTime = gi.frame_time_s; + request.start = self->s.origin; + request.pathFlags = PathFlags::Walk; + + if (self->monsterinfo.can_jump || (self->flags & FL_FLY)) + { + if (self->monsterinfo.jump_height) + { + request.pathFlags |= PathFlags::BarrierJump; + request.traversals.jumpHeight = self->monsterinfo.jump_height; + } + if (self->monsterinfo.drop_height) + { + request.pathFlags |= PathFlags::WalkOffLedge; + request.traversals.dropHeight = self->monsterinfo.drop_height; + } + } + + if (self->flags & FL_FLY) + { + request.nodeSearch.maxHeight = request.nodeSearch.minHeight = 8192.f; + request.pathFlags |= PathFlags::LongJump; + } + + if (!gi.GetPathToGoal(request, self->monsterinfo.nav_path)) + { + // fatal error, don't bother ever trying nodes + if (self->monsterinfo.nav_path.returnCode == PathReturnCode::NoNavAvailable) + self->monsterinfo.aiflags |= AI_NO_PATH_FINDING; + return false; + } + + self->monsterinfo.nav_path_cache_time = level.time + 2_sec; + } + + float yaw; + float old_yaw = self->s.angles[YAW]; + float old_ideal_yaw = self->ideal_yaw; + + if (self->monsterinfo.random_change_time >= level.time && + !(self->monsterinfo.aiflags & AI_ALTERNATE_FLY)) + yaw = self->ideal_yaw; + else + yaw = vectoyaw((path_to - self->s.origin).normalized()); + + if ( !SV_StepDirection( self, yaw, dist, true ) ) { + + if (!self->inuse) + return false; + + if (self->monsterinfo.blocked && !(self->monsterinfo.aiflags & AI_TARGET_ANGER)) + { + if ((self->inuse) && (self->health > 0)) + { + // if we're blocked, the blocked function will be deferred to for yaw + self->s.angles[YAW] = old_yaw; + self->ideal_yaw = old_ideal_yaw; + if (self->monsterinfo.blocked(self, dist)) + return true; + } + } + + // try the first point + if (self->monsterinfo.random_change_time >= level.time) + yaw = self->ideal_yaw; + else + yaw = vectoyaw((self->monsterinfo.nav_path.firstMovePoint - self->s.origin).normalized()); + + if ( !SV_StepDirection( self, yaw, dist, true ) ) { + + // we got blocked, but all is not lost yet; do a similar bump around-ish behavior + // to try to regain our composure + if (self->monsterinfo.aiflags & AI_BLOCKED) + { + self->monsterinfo.aiflags &= ~AI_BLOCKED; + return true; + } + + if (self->monsterinfo.random_change_time < level.time && self->inuse) + { + self->monsterinfo.random_change_time = level.time + 1500_ms; + if (SV_NewChaseDir(self, path_to, dist)) + return true; + } + + self->monsterinfo.path_blocked_counter += FRAME_TIME_S * 3; + } + + if (self->monsterinfo.path_blocked_counter > 1.5_sec) + return false; + } + + return true; +} + +/* +============= +M_MoveToPath + +Advanced movement code that use the bots pathfinder if allowed and conditions are right. +Feel free to add any other conditions needed. +============= +*/ +static bool M_MoveToPath(edict_t *self, float dist) +{ + if (self->flags & FL_STATIONARY) + return false; + else if (self->monsterinfo.aiflags & AI_NO_PATH_FINDING) + return false; + else if (self->monsterinfo.path_wait_time > level.time) + return false; + else if (!self->enemy) + return false; + else if (self->enemy->client && self->enemy->client->invisible_time > level.time && self->enemy->client->invisibility_fade_time <= level.time) + return false; + else if (self->monsterinfo.attack_state >= AS_MISSILE) + return true; + + combat_style_t style = self->monsterinfo.combat_style; + + if (self->monsterinfo.aiflags & AI_TEMP_MELEE_COMBAT) + style = COMBAT_MELEE; + + if ( visible(self, self->enemy, false) ) { + if ( (self->flags & (FL_SWIM | FL_FLY)) || style == COMBAT_RANGED ) { + // do the normal "shoot, walk, shoot" behavior... + return false; + } else if ( style == COMBAT_MELEE ) { + // path pretty close to the enemy, then let normal Quake movement take over. + if ( range_to(self, self->enemy) > 240.f || + fabs(self->s.origin.z - self->enemy->s.origin.z) > max(self->maxs.z, -self->mins.z) ) { + if ( M_NavPathToGoal( self, dist, self->enemy->s.origin ) ) { + return true; + } + self->monsterinfo.aiflags &= ~AI_TEMP_MELEE_COMBAT; + } else { + self->monsterinfo.aiflags &= ~AI_TEMP_MELEE_COMBAT; + return false; + } + } else if ( style == COMBAT_MIXED ) { + // most mixed combat AI have fairly short range attacks, so try to path within mid range. + if ( range_to(self, self->enemy) > RANGE_NEAR || + fabs(self->s.origin.z - self->enemy->s.origin.z) > max(self->maxs.z, -self->mins.z) * 2.0f ) { + if ( M_NavPathToGoal( self, dist, self->enemy->s.origin ) ) { + return true; + } + } else { + return false; + } + } + } else { + // we can't see our enemy, let's see if we can path to them + if ( M_NavPathToGoal( self, dist, self->enemy->s.origin ) ) { + return true; + } + } + + if (!self->inuse) + return false; + + if (self->monsterinfo.nav_path.returnCode > PathReturnCode::StartPathErrors) + { + self->monsterinfo.path_wait_time = level.time + 10_sec; + return false; + } + + self->monsterinfo.path_blocked_counter += FRAME_TIME_S * 3; + + if (self->monsterinfo.path_blocked_counter > 5_sec) + { + self->monsterinfo.path_blocked_counter = 0_ms; + self->monsterinfo.path_wait_time = level.time + 5_sec; + + return false; + } + + return true; +} + +/* +====================== +M_MoveToGoal +====================== +*/ +void M_MoveToGoal(edict_t *ent, float dist) +{ + if ( ai_movement_disabled->integer ) { + if ( !FacingIdeal( ent ) ) { + M_ChangeYaw( ent ); + } // mal: don't move, but still face toward target + return; + } + + edict_t *goal; + + goal = ent->goalentity; + + if (!ent->groundentity && !(ent->flags & (FL_FLY | FL_SWIM))) + return; + // ??? + else if (!goal) + return; + + // [Paril-KEX] try paths if we can't see the enemy + if (!(ent->monsterinfo.aiflags & AI_COMBAT_POINT) && ent->monsterinfo.attack_state < AS_MISSILE) + { + if (M_MoveToPath(ent, dist)) + { + ent->monsterinfo.path_blocked_counter = max(0_ms, ent->monsterinfo.path_blocked_counter - FRAME_TIME_S); + return; + } + } + + ent->monsterinfo.aiflags &= ~AI_PATHING; + + //if (goal) + // gi.Draw_Point(goal->s.origin, 1.f, rgba_red, gi.frame_time_ms, false); + + // [Paril-KEX] dumb hack; in some n64 maps, the corners are way too high and + // I'm too lazy to fix them individually in maps, so here's a game fix.. + if (!(goal->flags & FL_PARTIALGROUND) && !(ent->flags & (FL_FLY | FL_SWIM)) && + goal->classname && (!strcmp(goal->classname, "path_corner") || !strcmp(goal->classname, "point_combat"))) + { + vec3_t p = goal->s.origin; + p.z = ent->s.origin.z; + + if (boxes_intersect(ent->absmin, ent->absmax, p, p)) + { + // mark this so we don't do it again later + goal->flags |= FL_PARTIALGROUND; + + if (!boxes_intersect(ent->absmin, ent->absmax, goal->s.origin, goal->s.origin)) + { + // move it if we would have touched it if the corner was lower + goal->s.origin.z = p.z; + gi.linkentity(goal); + } + } + } + + // [Paril-KEX] if we have a straight shot to our target, just move + // straight instead of trying to stick to invisible guide lines + if ((ent->monsterinfo.bad_move_time <= level.time || (ent->monsterinfo.aiflags & AI_CHARGING)) && goal) + { + if (!FacingIdeal(ent)) + { + M_ChangeYaw(ent); + return; + } + + trace_t tr = gi.traceline(ent->s.origin, goal->s.origin, ent, MASK_MONSTERSOLID); + + if (tr.fraction == 1.0f || tr.ent == goal) + { + if (SV_StepDirection(ent, vectoyaw((goal->s.origin - ent->s.origin).normalized()), dist, false)) + return; + } + + // we didn't make a step, so don't try this for a while + // *unless* we're going to a path corner + if (goal->classname && strcmp(goal->classname, "path_corner") && strcmp(goal->classname, "point_combat")) + { + ent->monsterinfo.bad_move_time = level.time + 5_sec; + ent->monsterinfo.aiflags &= ~AI_CHARGING; + } + } + + // bump around... + if ((ent->monsterinfo.random_change_time <= level.time // random change time is up + && irandom(4) == 1 // random bump around + && !(ent->monsterinfo.aiflags & AI_CHARGING) // PMM - charging monsters (AI_CHARGING) don't deflect unless they have to + && !((ent->monsterinfo.aiflags & AI_ALTERNATE_FLY) && ent->enemy && !(ent->monsterinfo.aiflags & AI_LOST_SIGHT))) // alternate fly monsters don't do this either unless they have to + || !SV_StepDirection(ent, ent->ideal_yaw, dist, ent->monsterinfo.bad_move_time > level.time)) + { + if (ent->monsterinfo.aiflags & AI_BLOCKED) + { + ent->monsterinfo.aiflags &= ~AI_BLOCKED; + return; + } + ent->monsterinfo.random_change_time = level.time + random_time(500_ms, 1000_ms); + SV_NewChaseDir(ent, goal->s.origin, dist); + ent->monsterinfo.move_block_counter = 0; + } + else + ent->monsterinfo.bad_move_time -= 250_ms; + + //vec3_t dir = AngleVectors({ 0.f, ent->ideal_yaw, 0.f }).forward; + //gi.Draw_Line(ent->s.origin, ent->s.origin + (dir * 24), rgba_blue, gi.frame_time_ms, false); +} + +/* +=============== +M_walkmove +=============== +*/ +bool M_walkmove(edict_t *ent, float yaw, float dist) +{ + if ( ai_movement_disabled->integer ) { + return false; + } + + vec3_t move; + // PMM + bool retval; + + if (!ent->groundentity && !(ent->flags & (FL_FLY | FL_SWIM))) + return false; + + yaw = yaw * PIf * 2 / 360; + + move[0] = cosf(yaw) * dist; + move[1] = sinf(yaw) * dist; + move[2] = 0; + + // PMM + retval = SV_movestep(ent, move, true); + ent->monsterinfo.aiflags &= ~AI_BLOCKED; + return retval; +} diff --git a/rerelease/m_mutant.cpp b/rerelease/m_mutant.cpp new file mode 100644 index 0000000..7b6294b --- /dev/null +++ b/rerelease/m_mutant.cpp @@ -0,0 +1,739 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +/* +============================================================================== + +mutant + +============================================================================== +*/ + +#include "g_local.h" +#include "m_mutant.h" + +constexpr spawnflags_t SPAWNFLAG_MUTANT_NOJUMPING = 8_spawnflag; + +static int sound_swing; +static int sound_hit; +static int sound_hit2; +static int sound_death; +static int sound_idle; +static int sound_pain1; +static int sound_pain2; +static int sound_sight; +static int sound_search; +static int sound_step1; +static int sound_step2; +static int sound_step3; +static int sound_thud; + +// +// SOUNDS +// + +void mutant_step(edict_t *self) +{ + int n = irandom(3); + if (n == 0) + gi.sound(self, CHAN_BODY, sound_step1, 1, ATTN_NORM, 0); + else if (n == 1) + gi.sound(self, CHAN_BODY, sound_step2, 1, ATTN_NORM, 0); + else + gi.sound(self, CHAN_BODY, sound_step3, 1, ATTN_NORM, 0); +} + +MONSTERINFO_SIGHT(mutant_sight) (edict_t *self, edict_t *other) -> void +{ + gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); +} + +MONSTERINFO_SEARCH(mutant_search) (edict_t *self) -> void +{ + gi.sound(self, CHAN_VOICE, sound_search, 1, ATTN_NORM, 0); +} + +void mutant_swing(edict_t *self) +{ + gi.sound(self, CHAN_VOICE, sound_swing, 1, ATTN_NORM, 0); +} + +// +// STAND +// + +mframe_t mutant_frames_stand[] = { + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, // 10 + + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, // 20 + + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, // 30 + + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, // 40 + + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, // 50 + + { ai_stand } +}; +MMOVE_T(mutant_move_stand) = { FRAME_stand101, FRAME_stand151, mutant_frames_stand, nullptr }; + +MONSTERINFO_STAND(mutant_stand) (edict_t *self) -> void +{ + M_SetAnimation(self, &mutant_move_stand); +} + +// +// IDLE +// + +void mutant_idle_loop(edict_t *self) +{ + if (frandom() < 0.75f) + self->monsterinfo.nextframe = FRAME_stand155; +} + +mframe_t mutant_frames_idle[] = { + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, // scratch loop start + { ai_stand }, + { ai_stand }, + { ai_stand, 0, mutant_idle_loop }, // scratch loop end + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand } +}; +MMOVE_T(mutant_move_idle) = { FRAME_stand152, FRAME_stand164, mutant_frames_idle, mutant_stand }; + +MONSTERINFO_IDLE(mutant_idle) (edict_t *self) -> void +{ + M_SetAnimation(self, &mutant_move_idle); + gi.sound(self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0); +} + +// +// WALK +// + +mframe_t mutant_frames_walk[] = { + { ai_walk, 3 }, + { ai_walk, 1 }, + { ai_walk, 5 }, + { ai_walk, 10 }, + { ai_walk, 13 }, + { ai_walk, 10 }, + { ai_walk }, + { ai_walk, 5 }, + { ai_walk, 6 }, + { ai_walk, 16 }, + { ai_walk, 15 }, + { ai_walk, 6 } +}; +MMOVE_T(mutant_move_walk) = { FRAME_walk05, FRAME_walk16, mutant_frames_walk, nullptr }; + +void mutant_walk_loop(edict_t *self) +{ + M_SetAnimation(self, &mutant_move_walk); +} + +mframe_t mutant_frames_start_walk[] = { + { ai_walk, 5 }, + { ai_walk, 5 }, + { ai_walk, -2 }, + { ai_walk, 1 } +}; +MMOVE_T(mutant_move_start_walk) = { FRAME_walk01, FRAME_walk04, mutant_frames_start_walk, mutant_walk_loop }; + +MONSTERINFO_WALK(mutant_walk) (edict_t *self) -> void +{ + M_SetAnimation(self, &mutant_move_start_walk); +} + +// +// RUN +// + +mframe_t mutant_frames_run[] = { + { ai_run, 40 }, + { ai_run, 40, mutant_step }, + { ai_run, 24 }, + { ai_run, 5, mutant_step }, + { ai_run, 17 }, + { ai_run, 10 } +}; +MMOVE_T(mutant_move_run) = { FRAME_run03, FRAME_run08, mutant_frames_run, nullptr }; + +MONSTERINFO_RUN(mutant_run) (edict_t *self) -> void +{ + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + M_SetAnimation(self, &mutant_move_stand); + else + M_SetAnimation(self, &mutant_move_run); +} + +// +// MELEE +// + +void mutant_hit_left(edict_t *self) +{ + vec3_t aim = { MELEE_DISTANCE, self->mins[0], 8 }; + if (fire_hit(self, aim, irandom(5, 15), 100)) + gi.sound(self, CHAN_WEAPON, sound_hit, 1, ATTN_NORM, 0); + else + { + gi.sound(self, CHAN_WEAPON, sound_swing, 1, ATTN_NORM, 0); + self->monsterinfo.melee_debounce_time = level.time + 1.5_sec; + } +} + +void mutant_hit_right(edict_t *self) +{ + vec3_t aim = { MELEE_DISTANCE, self->maxs[0], 8 }; + if (fire_hit(self, aim, irandom(5, 15), 100)) + gi.sound(self, CHAN_WEAPON, sound_hit2, 1, ATTN_NORM, 0); + else + { + gi.sound(self, CHAN_WEAPON, sound_swing, 1, ATTN_NORM, 0); + self->monsterinfo.melee_debounce_time = level.time + 1.5_sec; + } +} + +void mutant_check_refire(edict_t *self) +{ + if (!self->enemy || !self->enemy->inuse || self->enemy->health <= 0) + return; + + if ((self->monsterinfo.melee_debounce_time <= level.time) && ((frandom() < 0.5f) || (range_to(self, self->enemy) <= RANGE_MELEE))) + self->monsterinfo.nextframe = FRAME_attack09; +} + +mframe_t mutant_frames_attack[] = { + { ai_charge }, + { ai_charge }, + { ai_charge, 0, mutant_hit_left }, + { ai_charge }, + { ai_charge }, + { ai_charge, 0, mutant_hit_right }, + { ai_charge, 0, mutant_check_refire } +}; +MMOVE_T(mutant_move_attack) = { FRAME_attack09, FRAME_attack15, mutant_frames_attack, mutant_run }; + +MONSTERINFO_MELEE(mutant_melee) (edict_t *self) -> void +{ + M_SetAnimation(self, &mutant_move_attack); +} + +// +// ATTACK +// + +TOUCH(mutant_jump_touch) (edict_t *self, edict_t *other, const trace_t &tr, bool other_touching_self) -> void +{ + if (self->health <= 0) + { + self->touch = nullptr; + return; + } + + if (self->style == 1 && other->takedamage) + { + if (self->velocity.length() > 400) + { + vec3_t point; + vec3_t normal; + int damage; + + normal = self->velocity; + normal.normalize(); + point = self->s.origin + (normal * self->maxs[0]); + damage = (int) frandom(40, 50); + T_Damage(other, self, self, self->velocity, point, normal, damage, damage, DAMAGE_NONE, MOD_UNKNOWN); + self->style = 0; + } + } + + if (!M_CheckBottom(self)) + { + if (self->groundentity) + { + self->monsterinfo.nextframe = FRAME_attack02; + self->touch = nullptr; + } + return; + } + + self->touch = nullptr; +} + +void mutant_jump_takeoff(edict_t *self) +{ + vec3_t forward; + + gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); + AngleVectors(self->s.angles, forward, nullptr, nullptr); + self->s.origin[2] += 1; + self->velocity = forward * 400; + self->velocity[2] = 150; + self->groundentity = nullptr; + self->monsterinfo.aiflags |= AI_DUCKED; + self->monsterinfo.attack_finished = level.time + 3_sec; + self->style = 1; + self->touch = mutant_jump_touch; +} + +void mutant_check_landing(edict_t *self) +{ + monster_jump_finished(self); + + if (self->groundentity) + { + gi.sound(self, CHAN_WEAPON, sound_thud, 1, ATTN_NORM, 0); + self->monsterinfo.attack_finished = level.time + random_time(500_ms, 1.5_sec); + + if (self->monsterinfo.unduck) + self->monsterinfo.unduck(self); + + if (range_to(self, self->enemy) <= RANGE_MELEE * 2.f) + self->monsterinfo.melee(self); + + return; + } + + if (level.time > self->monsterinfo.attack_finished) + self->monsterinfo.nextframe = FRAME_attack02; + else + self->monsterinfo.nextframe = FRAME_attack05; +} + +mframe_t mutant_frames_jump[] = { + { ai_charge }, + { ai_charge, 17 }, + { ai_charge, 15, mutant_jump_takeoff }, + { ai_charge, 15 }, + { ai_charge, 15, mutant_check_landing }, + { ai_charge }, + { ai_charge, 3 }, + { ai_charge } +}; +MMOVE_T(mutant_move_jump) = { FRAME_attack01, FRAME_attack08, mutant_frames_jump, mutant_run }; + +MONSTERINFO_ATTACK(mutant_jump) (edict_t *self) -> void +{ + M_SetAnimation(self, &mutant_move_jump); +} + +// +// CHECKATTACK +// + +bool mutant_check_melee(edict_t *self) +{ + return range_to(self, self->enemy) <= RANGE_MELEE && self->monsterinfo.melee_debounce_time <= level.time; +} + +bool mutant_check_jump(edict_t *self) +{ + vec3_t v; + float distance; + + // Paril: no harm in letting them jump down if you're below them + // if (self->absmin[2] > (self->enemy->absmin[2] + 0.75 * self->enemy->size[2])) + // return false; + + // don't jump if there's no way we can reach standing height + if (self->absmin[2] + 125 < self->enemy->absmin[2]) + return false; + + v[0] = self->s.origin[0] - self->enemy->s.origin[0]; + v[1] = self->s.origin[1] - self->enemy->s.origin[1]; + v[2] = 0; + distance = v.length(); + + // if we're not trying to avoid a melee, then don't jump + if (distance < 100 && self->monsterinfo.melee_debounce_time <= level.time) + return false; + // only use it to close distance gaps + if (distance > 265) + return false; + + return self->monsterinfo.attack_finished < level.time && brandom(); +} + +MONSTERINFO_CHECKATTACK(mutant_checkattack) (edict_t *self) -> bool +{ + if (!self->enemy || self->enemy->health <= 0) + return false; + + if (mutant_check_melee(self)) + { + self->monsterinfo.attack_state = AS_MELEE; + return true; + } + + if (!self->spawnflags.has(SPAWNFLAG_MUTANT_NOJUMPING) && mutant_check_jump(self)) + { + self->monsterinfo.attack_state = AS_MISSILE; + return true; + } + + return false; +} + +// +// PAIN +// + +mframe_t mutant_frames_pain1[] = { + { ai_move, 4 }, + { ai_move, -3 }, + { ai_move, -8 }, + { ai_move, 2 }, + { ai_move, 5 } +}; +MMOVE_T(mutant_move_pain1) = { FRAME_pain101, FRAME_pain105, mutant_frames_pain1, mutant_run }; + +mframe_t mutant_frames_pain2[] = { + { ai_move, -24 }, + { ai_move, 11 }, + { ai_move, 5 }, + { ai_move, -2 }, + { ai_move, 6 }, + { ai_move, 4 } +}; +MMOVE_T(mutant_move_pain2) = { FRAME_pain201, FRAME_pain206, mutant_frames_pain2, mutant_run }; + +mframe_t mutant_frames_pain3[] = { + { ai_move, -22 }, + { ai_move, 3 }, + { ai_move, 3 }, + { ai_move, 2 }, + { ai_move, 1 }, + { ai_move, 1 }, + { ai_move, 6 }, + { ai_move, 3 }, + { ai_move, 2 }, + { ai_move }, + { ai_move, 1 } +}; +MMOVE_T(mutant_move_pain3) = { FRAME_pain301, FRAME_pain311, mutant_frames_pain3, mutant_run }; + +PAIN(mutant_pain) (edict_t *self, edict_t *other, float kick, int damage, const mod_t &mod) -> void +{ + float r; + + if (level.time < self->pain_debounce_time) + return; + + self->pain_debounce_time = level.time + 3_sec; + + r = frandom(); + if (r < 0.33f) + gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); + else if (r < 0.66f) + gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0); + else + gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); + + if (!M_ShouldReactToPain(self, mod)) + return; // no pain anims in nightmare + + if (r < 0.33f) + M_SetAnimation(self, &mutant_move_pain1); + else if (r < 0.66f) + M_SetAnimation(self, &mutant_move_pain2); + else + M_SetAnimation(self, &mutant_move_pain3); +} + +MONSTERINFO_SETSKIN(mutant_setskin) (edict_t *self) -> void +{ + if (self->health < (self->max_health / 2)) + self->s.skinnum = 1; + else + self->s.skinnum = 0; +} + +// +// DEATH +// + +// FIXME: expanded dead box a bit, but rotation of dead body +// means it'll never always fit unless you move the whole box based on angle +void mutant_dead(edict_t *self) +{ + self->mins = { 0, -48, -24 }; + self->maxs = { 64, 16, -8 }; + monster_dead(self); +} + +void mutant_shrink(edict_t *self) +{ + self->maxs[2] = 0; + self->svflags |= SVF_DEADMONSTER; + gi.linkentity(self); +} + +mframe_t mutant_frames_death1[] = { + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move, 0, mutant_shrink }, + { ai_move }, + { ai_move }, + { ai_move } +}; +MMOVE_T(mutant_move_death1) = { FRAME_death101, FRAME_death109, mutant_frames_death1, mutant_dead }; + +mframe_t mutant_frames_death2[] = { + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move, 0, mutant_shrink }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move } +}; +MMOVE_T(mutant_move_death2) = { FRAME_death201, FRAME_death210, mutant_frames_death2, mutant_dead }; + +DIE(mutant_die) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void +{ + if (M_CheckGib(self, mod)) + { + gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0); + + self->s.skinnum /= 2; + + ThrowGibs(self, damage, { + { 2, "models/objects/gibs/bone/tris.md2" }, + { 4, "models/objects/gibs/sm_meat/tris.md2" }, + { 2, "models/monsters/mutant/gibs/hand.md2", GIB_SKINNED | GIB_UPRIGHT }, + { 2, "models/monsters/mutant/gibs/foot.md2", GIB_SKINNED }, + { "models/monsters/mutant/gibs/chest.md2", GIB_SKINNED }, + { "models/monsters/mutant/gibs/head.md2", GIB_SKINNED | GIB_HEAD } + }); + + self->deadflag = true; + return; + } + + if (self->deadflag) + return; + + gi.sound(self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0); + self->deadflag = true; + self->takedamage = true; + + if (frandom() < 0.5f) + M_SetAnimation(self, &mutant_move_death1); + else + M_SetAnimation(self, &mutant_move_death2); +} + +//================ +// ROGUE +void mutant_jump_down(edict_t *self) +{ + vec3_t forward, up; + + AngleVectors(self->s.angles, forward, nullptr, up); + self->velocity += (forward * 100); + self->velocity += (up * 300); +} + +void mutant_jump_up(edict_t *self) +{ + vec3_t forward, up; + + AngleVectors(self->s.angles, forward, nullptr, up); + self->velocity += (forward * 200); + self->velocity += (up * 450); +} + +void mutant_jump_wait_land(edict_t *self) +{ + if (!monster_jump_finished(self) && self->groundentity == nullptr) + self->monsterinfo.nextframe = self->s.frame; + else + self->monsterinfo.nextframe = self->s.frame + 1; +} + +mframe_t mutant_frames_jump_up[] = { + { ai_move, -8 }, + { ai_move, -8, mutant_jump_up }, + { ai_move, 0, mutant_jump_wait_land }, + { ai_move }, + { ai_move } +}; +MMOVE_T(mutant_move_jump_up) = { FRAME_jump01, FRAME_jump05, mutant_frames_jump_up, mutant_run }; + +mframe_t mutant_frames_jump_down[] = { + { ai_move }, + { ai_move, 0, mutant_jump_down }, + { ai_move, 0, mutant_jump_wait_land }, + { ai_move }, + { ai_move } +}; +MMOVE_T(mutant_move_jump_down) = { FRAME_jump01, FRAME_jump05, mutant_frames_jump_down, mutant_run }; + +void mutant_jump_updown(edict_t *self, blocked_jump_result_t result) +{ + if (!self->enemy) + return; + + if (result == blocked_jump_result_t::JUMP_JUMP_UP) + M_SetAnimation(self, &mutant_move_jump_up); + else + M_SetAnimation(self, &mutant_move_jump_down); +} + +/* +=== +Blocked +=== +*/ +MONSTERINFO_BLOCKED(mutant_blocked) (edict_t *self, float dist) -> bool +{ + if (auto result = blocked_checkjump(self, dist); result != blocked_jump_result_t::NO_JUMP) + { + if (result != blocked_jump_result_t::JUMP_TURN) + mutant_jump_updown(self, result); + return true; + } + + if (blocked_checkplat(self, dist)) + return true; + + return false; +} +// ROGUE +//================ + +// +// SPAWN +// + +/*QUAKED monster_mutant (1 .5 0) (-32 -32 -24) (32 32 32) Ambush Trigger_Spawn Sight NoJumping +model="models/monsters/mutant/tris.md2" +*/ +void SP_monster_mutant(edict_t *self) +{ + if ( !M_AllowSpawn( self ) ) { + G_FreeEdict( self ); + return; + } + + sound_swing = gi.soundindex("mutant/mutatck1.wav"); + sound_hit = gi.soundindex("mutant/mutatck2.wav"); + sound_hit2 = gi.soundindex("mutant/mutatck3.wav"); + sound_death = gi.soundindex("mutant/mutdeth1.wav"); + sound_idle = gi.soundindex("mutant/mutidle1.wav"); + sound_pain1 = gi.soundindex("mutant/mutpain1.wav"); + sound_pain2 = gi.soundindex("mutant/mutpain2.wav"); + sound_sight = gi.soundindex("mutant/mutsght1.wav"); + sound_search = gi.soundindex("mutant/mutsrch1.wav"); + sound_step1 = gi.soundindex("mutant/step1.wav"); + sound_step2 = gi.soundindex("mutant/step2.wav"); + sound_step3 = gi.soundindex("mutant/step3.wav"); + sound_thud = gi.soundindex("mutant/thud1.wav"); + + self->monsterinfo.aiflags |= AI_STINKY; + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex("models/monsters/mutant/tris.md2"); + + gi.modelindex("models/monsters/mutant/gibs/head.md2"); + gi.modelindex("models/monsters/mutant/gibs/chest.md2"); + gi.modelindex("models/monsters/mutant/gibs/hand.md2"); + gi.modelindex("models/monsters/mutant/gibs/foot.md2"); + + self->mins = { -18, -18, -24 }; + self->maxs = { 18, 18, 30 }; + + self->health = 300 * st.health_multiplier; + self->gib_health = -120; + self->mass = 300; + + self->pain = mutant_pain; + self->die = mutant_die; + + self->monsterinfo.stand = mutant_stand; + self->monsterinfo.walk = mutant_walk; + self->monsterinfo.run = mutant_run; + self->monsterinfo.dodge = nullptr; + self->monsterinfo.attack = mutant_jump; + self->monsterinfo.melee = mutant_melee; + self->monsterinfo.sight = mutant_sight; + self->monsterinfo.search = mutant_search; + self->monsterinfo.idle = mutant_idle; + self->monsterinfo.checkattack = mutant_checkattack; + self->monsterinfo.blocked = mutant_blocked; // PGM + self->monsterinfo.setskin = mutant_setskin; + + gi.linkentity(self); + + M_SetAnimation(self, &mutant_move_stand); + + self->monsterinfo.combat_style = COMBAT_MELEE; + + self->monsterinfo.scale = MODEL_SCALE; + self->monsterinfo.can_jump = !(self->spawnflags & SPAWNFLAG_MUTANT_NOJUMPING); + self->monsterinfo.drop_height = 256; + self->monsterinfo.jump_height = 68; + + walkmonster_start(self); +} diff --git a/rerelease/m_mutant.h b/rerelease/m_mutant.h new file mode 100644 index 0000000..5379e8d --- /dev/null +++ b/rerelease/m_mutant.h @@ -0,0 +1,167 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// G:\quake2\baseq2\models/monsters/mutant + +// This file generated by ModelGen - Do NOT Modify + +enum +{ + FRAME_attack01, + FRAME_attack02, + FRAME_attack03, + FRAME_attack04, + FRAME_attack05, + FRAME_attack06, + FRAME_attack07, + FRAME_attack08, + FRAME_attack09, + FRAME_attack10, + FRAME_attack11, + FRAME_attack12, + FRAME_attack13, + FRAME_attack14, + FRAME_attack15, + FRAME_death101, + FRAME_death102, + FRAME_death103, + FRAME_death104, + FRAME_death105, + FRAME_death106, + FRAME_death107, + FRAME_death108, + FRAME_death109, + FRAME_death201, + FRAME_death202, + FRAME_death203, + FRAME_death204, + FRAME_death205, + FRAME_death206, + FRAME_death207, + FRAME_death208, + FRAME_death209, + FRAME_death210, + FRAME_pain101, + FRAME_pain102, + FRAME_pain103, + FRAME_pain104, + FRAME_pain105, + FRAME_pain201, + FRAME_pain202, + FRAME_pain203, + FRAME_pain204, + FRAME_pain205, + FRAME_pain206, + FRAME_pain301, + FRAME_pain302, + FRAME_pain303, + FRAME_pain304, + FRAME_pain305, + FRAME_pain306, + FRAME_pain307, + FRAME_pain308, + FRAME_pain309, + FRAME_pain310, + FRAME_pain311, + FRAME_run03, + FRAME_run04, + FRAME_run05, + FRAME_run06, + FRAME_run07, + FRAME_run08, + FRAME_stand101, + FRAME_stand102, + FRAME_stand103, + FRAME_stand104, + FRAME_stand105, + FRAME_stand106, + FRAME_stand107, + FRAME_stand108, + FRAME_stand109, + FRAME_stand110, + FRAME_stand111, + FRAME_stand112, + FRAME_stand113, + FRAME_stand114, + FRAME_stand115, + FRAME_stand116, + FRAME_stand117, + FRAME_stand118, + FRAME_stand119, + FRAME_stand120, + FRAME_stand121, + FRAME_stand122, + FRAME_stand123, + FRAME_stand124, + FRAME_stand125, + FRAME_stand126, + FRAME_stand127, + FRAME_stand128, + FRAME_stand129, + FRAME_stand130, + FRAME_stand131, + FRAME_stand132, + FRAME_stand133, + FRAME_stand134, + FRAME_stand135, + FRAME_stand136, + FRAME_stand137, + FRAME_stand138, + FRAME_stand139, + FRAME_stand140, + FRAME_stand141, + FRAME_stand142, + FRAME_stand143, + FRAME_stand144, + FRAME_stand145, + FRAME_stand146, + FRAME_stand147, + FRAME_stand148, + FRAME_stand149, + FRAME_stand150, + FRAME_stand151, + FRAME_stand152, + FRAME_stand153, + FRAME_stand154, + FRAME_stand155, + FRAME_stand156, + FRAME_stand157, + FRAME_stand158, + FRAME_stand159, + FRAME_stand160, + FRAME_stand161, + FRAME_stand162, + FRAME_stand163, + FRAME_stand164, + FRAME_walk01, + FRAME_walk02, + FRAME_walk03, + FRAME_walk04, + FRAME_walk05, + FRAME_walk06, + FRAME_walk07, + FRAME_walk08, + FRAME_walk09, + FRAME_walk10, + FRAME_walk11, + FRAME_walk12, + FRAME_walk13, + FRAME_walk14, + FRAME_walk15, + FRAME_walk16, + FRAME_walk17, + FRAME_walk18, + FRAME_walk19, + FRAME_walk20, + FRAME_walk21, + FRAME_walk22, + FRAME_walk23, + // ROGUE + FRAME_jump01, + FRAME_jump02, + FRAME_jump03, + FRAME_jump04, + FRAME_jump05 + // ROGUE +}; + +constexpr float MODEL_SCALE = 1.000000f; diff --git a/rerelease/m_parasite.cpp b/rerelease/m_parasite.cpp new file mode 100644 index 0000000..8ec6390 --- /dev/null +++ b/rerelease/m_parasite.cpp @@ -0,0 +1,965 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +/* +============================================================================== + +parasite + +============================================================================== +*/ + +#include "g_local.h" +#include "m_parasite.h" + +constexpr float g_athena_parasite_miss_chance = 0.1f; +constexpr float g_athena_parasite_proboscis_speed = 1250; +constexpr float g_athena_parasite_proboscis_retract_modifier = 2.0f; + +static int sound_pain1; +static int sound_pain2; +static int sound_die; +static int sound_launch; +static int sound_impact; +static int sound_suck; +static int sound_reelin; +static int sound_sight; +static int sound_tap; +static int sound_scratch; +static int sound_search; + +void parasite_stand(edict_t *self); +void parasite_start_run(edict_t *self); +void parasite_run(edict_t *self); +void parasite_walk(edict_t *self); +void parasite_end_fidget(edict_t *self); +void parasite_do_fidget(edict_t *self); +void parasite_refidget(edict_t *self); + +void parasite_launch(edict_t *self) +{ + gi.sound(self, CHAN_WEAPON, sound_launch, 1, ATTN_NORM, 0); +} + +void parasite_reel_in(edict_t *self) +{ + gi.sound(self, CHAN_WEAPON, sound_reelin, 1, ATTN_NORM, 0); +} + +MONSTERINFO_SIGHT(parasite_sight) (edict_t *self, edict_t *other) -> void +{ + gi.sound(self, CHAN_WEAPON, sound_sight, 1, ATTN_NORM, 0); +} + +void parasite_tap(edict_t *self) +{ + gi.sound(self, CHAN_WEAPON, sound_tap, 0.75f, 2.75f, 0); +} + +void parasite_scratch(edict_t *self) +{ + gi.sound(self, CHAN_WEAPON, sound_scratch, 0.75f, 2.75f, 0); +} + +#if 0 +void parasite_search(edict_t *self) +{ + gi.sound(self, CHAN_WEAPON, sound_search, 1, ATTN_IDLE, 0); +} +#endif + +mframe_t parasite_frames_start_fidget[] = { + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand } +}; +MMOVE_T(parasite_move_start_fidget) = { FRAME_stand18, FRAME_stand21, parasite_frames_start_fidget, parasite_do_fidget }; + +mframe_t parasite_frames_fidget[] = { + { ai_stand, 0, parasite_scratch }, + { ai_stand }, + { ai_stand }, + { ai_stand, 0, parasite_scratch }, + { ai_stand }, + { ai_stand } +}; +MMOVE_T(parasite_move_fidget) = { FRAME_stand22, FRAME_stand27, parasite_frames_fidget, parasite_refidget }; + +mframe_t parasite_frames_end_fidget[] = { + { ai_stand, 0, parasite_scratch }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand } +}; +MMOVE_T(parasite_move_end_fidget) = { FRAME_stand28, FRAME_stand35, parasite_frames_end_fidget, parasite_stand }; + +void parasite_end_fidget(edict_t *self) +{ + M_SetAnimation(self, ¶site_move_end_fidget); +} + +void parasite_do_fidget(edict_t *self) +{ + M_SetAnimation(self, ¶site_move_fidget); +} + +void parasite_refidget(edict_t *self) +{ + if (frandom() <= 0.8f) + M_SetAnimation(self, ¶site_move_fidget); + else + M_SetAnimation(self, ¶site_move_end_fidget); +} + +MONSTERINFO_IDLE(parasite_idle) (edict_t *self) -> void +{ + if (self->enemy) + return; + + M_SetAnimation(self, ¶site_move_start_fidget); +} + +mframe_t parasite_frames_stand[] = { + { ai_stand }, + { ai_stand }, + { ai_stand, 0, parasite_tap }, + { ai_stand }, + { ai_stand, 0, parasite_tap }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand, 0, parasite_tap }, + { ai_stand }, + { ai_stand, 0, parasite_tap }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand, 0, parasite_tap }, + { ai_stand }, + { ai_stand, 0, parasite_tap } +}; +MMOVE_T(parasite_move_stand) = { FRAME_stand01, FRAME_stand17, parasite_frames_stand, parasite_stand }; + +MONSTERINFO_STAND(parasite_stand) (edict_t *self) -> void +{ + M_SetAnimation(self, ¶site_move_stand); +} + +mframe_t parasite_frames_run[] = { + { ai_run, 30 }, + { ai_run, 30 }, + { ai_run, 22, monster_footstep }, + { ai_run, 19, monster_footstep }, + { ai_run, 24 }, + { ai_run, 28, monster_footstep }, + { ai_run, 25, monster_footstep } +}; +MMOVE_T(parasite_move_run) = { FRAME_run03, FRAME_run09, parasite_frames_run, nullptr }; + +mframe_t parasite_frames_start_run[] = { + { ai_run }, + { ai_run, 30 }, +}; +MMOVE_T(parasite_move_start_run) = { FRAME_run01, FRAME_run02, parasite_frames_start_run, parasite_run }; + +#if 0 +mframe_t parasite_frames_stop_run[] = { + { ai_run, 20 }, + { ai_run, 20 }, + { ai_run, 12 }, + { ai_run, 10 }, + { ai_run }, + { ai_run } +}; +MMOVE_T(parasite_move_stop_run) = { FRAME_run10, FRAME_run15, parasite_frames_stop_run, nullptr }; +#endif + +MONSTERINFO_RUN(parasite_start_run) (edict_t *self) -> void +{ + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + M_SetAnimation(self, ¶site_move_stand); + else + M_SetAnimation(self, ¶site_move_start_run); +} + +static void proboscis_retract(edict_t *self); + +void parasite_run(edict_t *self) +{ + if (self->proboscus && self->proboscus->style != 2) + proboscis_retract(self->proboscus); + + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + M_SetAnimation(self, ¶site_move_stand); + else + M_SetAnimation(self, ¶site_move_run); +} + +mframe_t parasite_frames_walk[] = { + { ai_walk, 30 }, + { ai_walk, 30 }, + { ai_walk, 22, monster_footstep }, + { ai_walk, 19, monster_footstep }, + { ai_walk, 24 }, + { ai_walk, 28, monster_footstep }, + { ai_walk, 25, monster_footstep } +}; +MMOVE_T(parasite_move_walk) = { FRAME_run03, FRAME_run09, parasite_frames_walk, parasite_walk }; + +mframe_t parasite_frames_start_walk[] = { + { ai_walk, 0 }, + { ai_walk, 30, parasite_walk } +}; +MMOVE_T(parasite_move_start_walk) = { FRAME_run01, FRAME_run02, parasite_frames_start_walk, nullptr }; + +#if 0 +mframe_t parasite_frames_stop_walk[] = { + { ai_walk, 20 }, + { ai_walk, 20 }, + { ai_walk, 12 }, + { ai_walk, 10 }, + { ai_walk }, + { ai_walk } +}; +MMOVE_T(parasite_move_stop_walk) = { FRAME_run10, FRAME_run15, parasite_frames_stop_walk, nullptr }; +#endif + +MONSTERINFO_WALK(parasite_start_walk) (edict_t *self) -> void +{ + M_SetAnimation(self, ¶site_move_start_walk); +} + +void parasite_walk(edict_t *self) +{ + M_SetAnimation(self, ¶site_move_walk); +} + +// hard reset on proboscis; like we never existed +THINK(proboscis_reset) (edict_t *self) -> void +{ + self->owner->proboscus = nullptr; + G_FreeEdict(self->proboscus); + G_FreeEdict(self); +} + +DIE(proboscis_die) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void +{ + if (mod.id == MOD_CRUSH) + proboscis_reset(self); +} + +extern const mmove_t parasite_move_fire_proboscis; + +static void parasite_break_wait(edict_t *self) +{ + // prob exploded? + if (self->proboscus && self->proboscus->style != 3) + self->monsterinfo.nextframe = FRAME_break19; + else if (brandom()) + { + // don't get hurt + parasite_reel_in(self); + self->monsterinfo.nextframe = FRAME_break31; + } +} + +static void proboscis_retract(edict_t *self) +{ + // start retract animation + if (self->owner->monsterinfo.active_move == ¶site_move_fire_proboscis) + self->owner->monsterinfo.nextframe = FRAME_drain12; + + // mark as retracting + self->movetype = MOVETYPE_NONE; + self->solid = SOLID_NOT; + // come back real hard + if (self->style != 2) + self->speed *= g_athena_parasite_proboscis_retract_modifier; + self->style = 2; + gi.linkentity(self); +} + +static void parasite_break_retract(edict_t *self) +{ + if (self->proboscus) + proboscis_retract(self->proboscus); +} + +static void parasite_break_sound(edict_t *self) +{ + if (frandom() < 0.5f) + gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); + else + gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0); + + self->pain_debounce_time = level.time + 3_sec; +} + +void proboscis_segment_draw(edict_t *self); + +static void parasite_charge_proboscis(edict_t *self, float dist) +{ + if (self->s.frame >= FRAME_break01 && self->s.frame <= FRAME_break32) + ai_move(self, dist); + else + ai_charge(self, dist); + + if (self->proboscus) + proboscis_segment_draw(self->proboscus->proboscus); +} + +static void parasite_break_noise(edict_t *self) +{ + gi.sound(self, CHAN_VOICE, sound_search, 1, ATTN_NORM, 0); +} + +constexpr mframe_t parasite_frames_break[] = { + { parasite_charge_proboscis }, + { parasite_charge_proboscis, -3, parasite_break_noise }, + { parasite_charge_proboscis, 1 }, + { parasite_charge_proboscis, 2 }, + { parasite_charge_proboscis, -3 }, + { parasite_charge_proboscis, 1 }, + { parasite_charge_proboscis, 1 }, + { parasite_charge_proboscis, 3 }, + { parasite_charge_proboscis, 0, parasite_break_noise }, + { parasite_charge_proboscis, -18 }, + { parasite_charge_proboscis, 3 }, + { parasite_charge_proboscis, 9 }, + { parasite_charge_proboscis, 6 }, + { parasite_charge_proboscis }, + { parasite_charge_proboscis, -18 }, + { parasite_charge_proboscis }, + { parasite_charge_proboscis, 8, parasite_break_retract }, + { parasite_charge_proboscis, 9 }, + { parasite_charge_proboscis, 0, parasite_break_wait }, + { parasite_charge_proboscis, -18, parasite_break_sound }, + { parasite_charge_proboscis }, + { parasite_charge_proboscis }, // airborne + { parasite_charge_proboscis }, // airborne + { parasite_charge_proboscis }, // slides + { parasite_charge_proboscis }, // slides + { parasite_charge_proboscis }, // slides + { parasite_charge_proboscis }, // slides + { parasite_charge_proboscis, 4 }, + { parasite_charge_proboscis, 11 }, + { parasite_charge_proboscis, -2 }, + { parasite_charge_proboscis, -5 }, + { parasite_charge_proboscis, 1 } +}; +MMOVE_T(parasite_move_break) = { FRAME_break01, FRAME_break32, parasite_frames_break, parasite_start_run }; + +TOUCH(proboscis_touch) (edict_t *self, edict_t *other, const trace_t &tr, bool other_touching_self) -> void +{ + // owner isn't trying to probe any more, don't touch anything + if (self->owner->monsterinfo.active_move != ¶site_move_fire_proboscis) + return; + + vec3_t p; + + // hit what we want to succ + if ((other->svflags & SVF_PLAYER) || other == self->owner->enemy) + { + if (tr.startsolid) + p = tr.endpos; + else + p = tr.endpos - ((self->s.origin - tr.endpos).normalized() * 12); + + self->owner->monsterinfo.nextframe = FRAME_drain06; + self->movetype = MOVETYPE_NONE; + self->solid = SOLID_NOT; + self->style = 1; + // stick to this guy + self->move_origin = p - other->s.origin; + self->enemy = other; + self->s.alpha = 0.35f; + gi.sound(self, CHAN_WEAPON, sound_suck, 1, ATTN_NORM, 0); + } + else + { + p = tr.endpos + tr.plane.normal; + // hit monster, don't suck but do small damage + // and retract immediately + if (other->svflags & (SVF_MONSTER | SVF_DEADMONSTER)) + proboscis_retract(self); + else + { + // hit wall; stick to it and do break animation + self->owner->monsterinfo.active_move = ¶site_move_break; + self->movetype = MOVETYPE_NONE; + self->solid = SOLID_NOT; + self->style = 1; + self->owner->s.angles[YAW] = self->s.angles[YAW]; + } + } + + if (other->takedamage) + T_Damage(other, self, self->owner, tr.plane.normal, tr.endpos, tr.plane.normal, 5, 0, DAMAGE_NONE, MOD_UNKNOWN); + + gi.positioned_sound(tr.endpos, self->owner, CHAN_AUTO, sound_impact, 1, ATTN_NORM, 0); + + self->s.origin = p; + self->nextthink = level.time + FRAME_TIME_S; // start doing stuff on next frame + gi.linkentity(self); +} + +// from break01 +constexpr vec3_t parasite_break_offsets[] = { + { 7.0f, 0, 7.0f }, + { 6.3f, 14.5f, 4.0f }, + { 8.5f, 0, 5.6f }, + { 5.0f, -15.25f, 4.0f, }, + { 9.5f, -1.8f, 5.9f }, + { 6.2f, 14.f, 4.0f }, + { 12.25f, 7.5f, 1.4f }, + { 13.8f, 0, -2.4f }, + { 13.8f, 0, -4.0f }, + { 0.1f, 0, -0.7f }, + { 5.0f, 0, 3.7f }, + { 11.f, 0, 4.f }, + { 13.5f, 0, -4.0f }, + { 13.5f, 0, -4.0f }, + { 0.2f, 0, -0.7f }, + { 3.9f, 0, 3.6f }, + { 8.5f, 0, 5.0f }, + { 14.0f, 0, -4.f }, + { 14.0f, 0, -4.f }, + { 0.1f, 0, -0.5f } +}; + +// from drain01 +constexpr vec3_t parasite_drain_offsets[] = { + { -1.7f, 0, 1.2f }, + { -2.2f, 0, -0.6f }, + { 7.7f, 0, 7.2f }, + { 7.2f, 0, 5.7f }, + { 6.2f, 0, 7.8f }, + { 4.7f, 0, 6.7f }, + { 5.0f, 0, 9.0f }, + { 5.0f, 0, 7.0f }, + { 5.0f, 0, 10.5f }, + { 4.5f, 0, 9.7f }, + { 1.5f, 0, 12.0f }, + { 2.9f, 0, 11.0f }, + { 2.1f, 0, 7.6f }, +}; + +vec3_t parasite_get_proboscis_start(edict_t *self) +{ + vec3_t f, r, start; + AngleVectors(self->s.angles, f, r, nullptr); + vec3_t offset; + if (self->s.frame >= FRAME_break01 && self->s.frame < FRAME_break01 + q_countof(parasite_break_offsets)) + offset = parasite_break_offsets[self->s.frame - FRAME_break01]; + else if (self->s.frame >= FRAME_drain01 && self->s.frame < FRAME_drain01 + q_countof(parasite_drain_offsets)) + offset = parasite_drain_offsets[self->s.frame - FRAME_drain01]; + else + offset = { 8, 0, 6 }; + start = M_ProjectFlashSource(self, offset, f, r); + return start; +} + +THINK(proboscis_think) (edict_t *self) -> void +{ + self->nextthink = level.time + FRAME_TIME_S; // start doing stuff on next frame + + // retracting; keep pulling until we hit the parasite + if (self->style == 2) + { + vec3_t start = parasite_get_proboscis_start(self->owner); + vec3_t dir = (self->s.origin - start); + float dist = dir.normalize(); + + if (dist <= (self->speed * 2) * gi.frame_time_s) + { + // reached target; free self on next frame, let parasite know + self->style = 3; + self->think = proboscis_reset; + self->s.origin = start; + gi.linkentity(self); + return; + } + + // pull us in + self->s.origin -= dir * (self->speed * gi.frame_time_s); + gi.linkentity(self); + } + // stuck on target; do damage, suck health + // and check if target goes away + else if (self->style == 1) + { + if (!self->enemy) + { + // stuck in wall + } + else if (!self->enemy->inuse || self->enemy->health <= 0 || !self->enemy->takedamage) + { + // target gone, retract early + proboscis_retract(self); + } + else + { + // update our position + self->s.origin = self->enemy->s.origin + self->move_origin; + + vec3_t start = parasite_get_proboscis_start(self->owner); + + self->s.angles = vectoangles((self->s.origin - start).normalized()); + + // see if we got cut by the world + trace_t tr = gi.traceline(start, self->s.origin, nullptr, MASK_SOLID); + + if (tr.fraction != 1.0f) + { + // blocked, so retract + proboscis_retract(self); + self->s.origin = self->s.old_origin; + } + else + { + // succ & drain + if (self->timestamp <= level.time) + { + T_Damage(self->enemy, self, self->owner, tr.plane.normal, tr.endpos, tr.plane.normal, 2, 0, DAMAGE_NONE, MOD_UNKNOWN); + self->owner->health = min(self->owner->max_health, self->owner->health + 2); + self->owner->monsterinfo.setskin(self->owner); + self->timestamp = level.time + 10_hz; + } + } + + gi.linkentity(self); + } + } + // flying + else if (self->style == 0) + { + // owner gone away? + if (!self->owner->enemy || !self->owner->enemy->inuse || self->owner->enemy->health <= 0) + { + proboscis_retract(self); + return; + } + + // if we're well behind our target and missed by 2x velocity, + // be smart enough to pull in automatically + vec3_t to_target = (self->s.origin - self->owner->enemy->s.origin); + float dist_to_target = to_target.normalize(); + + if (dist_to_target > (self->speed * 2) / 15.f) + { + vec3_t from_owner = (self->s.origin - self->owner->s.origin).normalized(); + float dot = to_target.dot(from_owner); + + if (dot > 0.f) + { + proboscis_retract(self); + return; + } + } + } +} + +PRETHINK(proboscis_segment_draw) (edict_t *self) -> void +{ + vec3_t start = parasite_get_proboscis_start(self->owner->owner); + + self->s.origin = start; + self->s.old_origin = self->owner->s.origin - ((self->owner->s.origin - start).normalized() * 8.f); + gi.linkentity(self); +} + +static void fire_proboscis(edict_t *self, vec3_t start, vec3_t dir, float speed) +{ + edict_t *tip = G_Spawn(); + tip->s.angles = vectoangles(dir); + tip->s.modelindex = gi.modelindex("models/monsters/parasite/tip/tris.md2"); + tip->movetype = MOVETYPE_FLYMISSILE; + tip->owner = self; + self->proboscus = tip; + tip->clipmask = MASK_PROJECTILE & ~CONTENTS_DEADMONSTER; + tip->s.origin = tip->s.old_origin = start; + tip->speed = speed; + tip->velocity = dir * speed; + tip->solid = SOLID_BBOX; + tip->takedamage = true; + tip->flags |= FL_NO_DAMAGE_EFFECTS | FL_NO_KNOCKBACK; + tip->die = proboscis_die; + tip->touch = proboscis_touch; + tip->think = proboscis_think; + tip->nextthink = level.time + FRAME_TIME_S; // start doing stuff on next frame + tip->svflags |= SVF_PROJECTILE; + + edict_t *segment = G_Spawn(); + segment->s.modelindex = gi.modelindex("models/monsters/parasite/segment/tris.md2"); + segment->s.renderfx = RF_BEAM; + segment->postthink = proboscis_segment_draw; + + tip->proboscus = segment; + segment->owner = tip; + + trace_t tr = gi.traceline(tip->s.origin, tip->s.origin + (tip->velocity * gi.frame_time_s), self, tip->clipmask); + if (tr.startsolid) + { + tr.plane.normal = -dir; + tr.endpos = start; + tip->touch(tip, tr.ent, tr, false); + } + else if (tr.fraction < 1.0f) + tip->touch(tip, tr.ent, tr, false); + + segment->s.origin = start; + segment->s.old_origin = tip->s.origin + ((tip->s.origin - start).normalized() * 8.f); + + gi.linkentity(tip); + gi.linkentity(segment); +} + +static void parasite_fire_proboscis(edict_t *self) +{ + if (self->proboscus && self->proboscus->style != 2) + proboscis_reset(self->proboscus); + + vec3_t start = parasite_get_proboscis_start(self); + + vec3_t dir; + PredictAim(self, self->enemy, start, g_athena_parasite_proboscis_speed, false, crandom_open() * g_athena_parasite_miss_chance, &dir, nullptr); + + fire_proboscis(self, start, dir, g_athena_parasite_proboscis_speed); +} + +static void parasite_proboscis_wait(edict_t *self) +{ + // loop frames while we wait + if (self->s.frame == FRAME_drain04) + self->monsterinfo.nextframe = FRAME_drain05; + else + self->monsterinfo.nextframe = FRAME_drain04; +} + +static void parasite_proboscis_pull_wait(edict_t *self) +{ + // prob exploded? + if (!self->proboscus || self->proboscus->style == 3) + { + self->monsterinfo.nextframe = FRAME_drain14; + return; + } + + // being pulled in, so wait until we get destroyed + if (self->s.frame == FRAME_drain12) + self->monsterinfo.nextframe = FRAME_drain13; + else + self->monsterinfo.nextframe = FRAME_drain12; + + if (self->proboscus->style != 2) + proboscis_retract(self->proboscus); +} + +mframe_t parasite_frames_fire_proboscis[] = { + { parasite_charge_proboscis, 0, parasite_launch }, + { parasite_charge_proboscis }, + { parasite_charge_proboscis, 15, parasite_fire_proboscis }, // Target hits + { parasite_charge_proboscis, 0, parasite_proboscis_wait }, // drain + { parasite_charge_proboscis, 0, parasite_proboscis_wait }, // drain + { parasite_charge_proboscis, 0 }, // drain + { parasite_charge_proboscis, 0 }, // drain + { parasite_charge_proboscis, -2 }, // drain + { parasite_charge_proboscis, -2 }, // drain + { parasite_charge_proboscis, -3 }, // drain + { parasite_charge_proboscis, -2 }, // drain + { parasite_charge_proboscis, 0, parasite_proboscis_pull_wait }, // drain + { parasite_charge_proboscis, -1, parasite_proboscis_pull_wait }, // drain + { parasite_charge_proboscis, 0, parasite_reel_in }, // let go + { parasite_charge_proboscis, -2 }, + { parasite_charge_proboscis, -2 }, + { parasite_charge_proboscis, -3 }, + { parasite_charge_proboscis } +}; +MMOVE_T(parasite_move_fire_proboscis) = { FRAME_drain01, FRAME_drain18, parasite_frames_fire_proboscis, parasite_start_run }; + +MONSTERINFO_ATTACK(parasite_attack) (edict_t *self) -> void +{ + if (!M_CheckClearShot(self, parasite_drain_offsets[0])) + return; + + if (self->proboscus && self->proboscus->style != 2) + proboscis_retract(self->proboscus); + + M_SetAnimation(self, ¶site_move_fire_proboscis); +} + +//================ +// ROGUE +void parasite_jump_down(edict_t *self) +{ + vec3_t forward, up; + + AngleVectors(self->s.angles, forward, nullptr, up); + self->velocity += (forward * 100); + self->velocity += (up * 300); +} + +void parasite_jump_up(edict_t *self) +{ + vec3_t forward, up; + + AngleVectors(self->s.angles, forward, nullptr, up); + self->velocity += (forward * 200); + self->velocity += (up * 450); +} + +void parasite_jump_wait_land(edict_t *self) +{ + if (self->groundentity == nullptr) + { + self->monsterinfo.nextframe = self->s.frame; + + if (monster_jump_finished(self)) + self->monsterinfo.nextframe = self->s.frame + 1; + } + else + self->monsterinfo.nextframe = self->s.frame + 1; +} + +mframe_t parasite_frames_jump_up[] = { + { ai_move, -8 }, + { ai_move, -8 }, + { ai_move, -8 }, + { ai_move, -8, parasite_jump_up }, + { ai_move }, + { ai_move }, + { ai_move, 0, parasite_jump_wait_land }, + { ai_move } +}; +MMOVE_T(parasite_move_jump_up) = { FRAME_jump01, FRAME_jump08, parasite_frames_jump_up, parasite_run }; + +mframe_t parasite_frames_jump_down[] = { + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move, 0, parasite_jump_down }, + { ai_move }, + { ai_move }, + { ai_move, 0, parasite_jump_wait_land }, + { ai_move } +}; +MMOVE_T(parasite_move_jump_down) = { FRAME_jump01, FRAME_jump08, parasite_frames_jump_down, parasite_run }; + +void parasite_jump(edict_t *self, blocked_jump_result_t result) +{ + if (!self->enemy) + return; + + if (result == blocked_jump_result_t::JUMP_JUMP_UP) + M_SetAnimation(self, ¶site_move_jump_up); + else + M_SetAnimation(self, ¶site_move_jump_down); +} + +/* +=== +Blocked +=== +*/ +MONSTERINFO_BLOCKED(parasite_blocked) (edict_t *self, float dist) -> bool +{ + if (auto result = blocked_checkjump(self, dist); result != blocked_jump_result_t::NO_JUMP) + { + if (result != blocked_jump_result_t::JUMP_TURN) + parasite_jump(self, result); + return true; + } + + if (blocked_checkplat(self, dist)) + return true; + + return false; +} +// ROGUE +//================ + +/* +=== +Death Stuff Starts +=== +*/ + +void parasite_dead(edict_t *self) +{ + self->mins = { -16, -16, -24 }; + self->maxs = { 16, 16, -8 }; + monster_dead(self); +} + +static void parasite_shrink(edict_t *self) +{ + self->maxs[2] = 0; + self->svflags |= SVF_DEADMONSTER; + gi.linkentity(self); +} + +mframe_t parasite_frames_death[] = { + { ai_move, 0, nullptr, FRAME_stand01 }, + { ai_move }, + { ai_move }, + { ai_move, 0, parasite_shrink }, + { ai_move, 0, monster_footstep }, + { ai_move }, + { ai_move } +}; +MMOVE_T(parasite_move_death) = { FRAME_death101, FRAME_death107, parasite_frames_death, parasite_dead }; + +DIE(parasite_die) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void +{ + if (self->proboscus && self->proboscus->style != 2) + proboscis_reset(self->proboscus); + + // check for gib + if (M_CheckGib(self, mod)) + { + gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0); + + self->s.skinnum /= 2; + + ThrowGibs(self, damage, { + { 1, "models/objects/gibs/bone/tris.md2" }, + { 3, "models/objects/gibs/sm_meat/tris.md2" }, + { "models/monsters/parasite/gibs/chest.md2", GIB_SKINNED }, + { 2, "models/monsters/parasite/gibs/bleg.md2", GIB_SKINNED | GIB_UPRIGHT }, + { 2, "models/monsters/parasite/gibs/fleg.md2", GIB_SKINNED | GIB_UPRIGHT }, + { "models/monsters/parasite/gibs/head.md2", GIB_SKINNED | GIB_HEAD } + }); + + self->deadflag = true; + return; + } + + if (self->deadflag) + return; + + // regular death + gi.sound(self, CHAN_VOICE, sound_die, 1, ATTN_NORM, 0); + self->deadflag = true; + self->takedamage = true; + M_SetAnimation(self, ¶site_move_death); +} + +/* +=== +End Death Stuff +=== +*/ + +mframe_t parasite_frames_pain1[] = { + { ai_move, 0, nullptr, FRAME_stand01 }, + { ai_move }, + { ai_move, 0, [](edict_t *self) { self->monsterinfo.nextframe = FRAME_pain105; } }, + { ai_move, 0, monster_footstep }, + { ai_move }, + { ai_move }, + { ai_move, 6, monster_footstep }, + { ai_move, 16 }, + { ai_move, -6, monster_footstep }, + { ai_move, -7 }, + { ai_move } +}; +MMOVE_T(parasite_move_pain1) = { FRAME_pain101, FRAME_pain111, parasite_frames_pain1, parasite_start_run }; + +PAIN(parasite_pain) (edict_t *self, edict_t *other, float kick, int damage, const mod_t &mod) -> void +{ + if (level.time < self->pain_debounce_time) + return; + + if (self->proboscus && self->proboscus->style != 2) + proboscis_retract(self->proboscus); + + self->pain_debounce_time = level.time + 3_sec; + + if (frandom() < 0.5f) + gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); + else + gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0); + + if (!M_ShouldReactToPain(self, mod)) + return; // no pain anims in nightmare + + M_SetAnimation(self, ¶site_move_pain1); +} + +MONSTERINFO_SETSKIN(parasite_setskin) (edict_t *self) -> void +{ + if (self->health < (self->max_health / 2)) + self->s.skinnum = 1; + else + self->s.skinnum = 0; +} + +constexpr spawnflags_t SPAWNFLAG_PARASITE_NOJUMPING = 8_spawnflag; + +/*QUAKED monster_parasite (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight NoJumping + */ +void SP_monster_parasite(edict_t *self) +{ + if ( !M_AllowSpawn( self ) ) { + G_FreeEdict( self ); + return; + } + + sound_pain1 = gi.soundindex("parasite/parpain1.wav"); + sound_pain2 = gi.soundindex("parasite/parpain2.wav"); + sound_die = gi.soundindex("parasite/pardeth1.wav"); + sound_launch = gi.soundindex("parasite/paratck1.wav"); + sound_impact = gi.soundindex("parasite/paratck2.wav"); + sound_suck = gi.soundindex("parasite/paratck3.wav"); + sound_reelin = gi.soundindex("parasite/paratck4.wav"); + sound_sight = gi.soundindex("parasite/parsght1.wav"); + sound_tap = gi.soundindex("parasite/paridle1.wav"); + sound_scratch = gi.soundindex("parasite/paridle2.wav"); + sound_search = gi.soundindex("parasite/parsrch1.wav"); + + gi.modelindex("models/monsters/parasite/tip/tris.md2"); + gi.modelindex("models/monsters/parasite/segment/tris.md2"); + + self->s.modelindex = gi.modelindex("models/monsters/parasite/tris.md2"); + + gi.modelindex("models/monsters/parasite/gibs/head.md2"); + gi.modelindex("models/monsters/parasite/gibs/chest.md2"); + gi.modelindex("models/monsters/parasite/gibs/bleg.md2"); + gi.modelindex("models/monsters/parasite/gibs/fleg.md2"); + + self->mins = { -16, -16, -24 }; + self->maxs = { 16, 16, 24 }; + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + + self->health = 175 * st.health_multiplier; + self->gib_health = -50; + self->mass = 250; + + self->pain = parasite_pain; + self->die = parasite_die; + + self->monsterinfo.stand = parasite_stand; + self->monsterinfo.walk = parasite_start_walk; + self->monsterinfo.run = parasite_start_run; + self->monsterinfo.attack = parasite_attack; + self->monsterinfo.sight = parasite_sight; + self->monsterinfo.idle = parasite_idle; + self->monsterinfo.blocked = parasite_blocked; // PGM + self->monsterinfo.setskin = parasite_setskin; + + gi.linkentity(self); + + M_SetAnimation(self, ¶site_move_stand); + self->monsterinfo.scale = MODEL_SCALE; + self->yaw_speed = 30; + self->monsterinfo.can_jump = !self->spawnflags.has(SPAWNFLAG_PARASITE_NOJUMPING); + self->monsterinfo.drop_height = 256; + self->monsterinfo.jump_height = 68; + + walkmonster_start(self); +} diff --git a/rerelease/m_parasite.h b/rerelease/m_parasite.h new file mode 100644 index 0000000..8847fe5 --- /dev/null +++ b/rerelease/m_parasite.h @@ -0,0 +1,139 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// G:\quake2\baseq2\models/monsters/parasite + +// This file generated by ModelGen - Do NOT Modify + +enum +{ + FRAME_break01, + FRAME_break02, + FRAME_break03, + FRAME_break04, + FRAME_break05, + FRAME_break06, + FRAME_break07, + FRAME_break08, + FRAME_break09, + FRAME_break10, + FRAME_break11, + FRAME_break12, + FRAME_break13, + FRAME_break14, + FRAME_break15, + FRAME_break16, + FRAME_break17, + FRAME_break18, + FRAME_break19, + FRAME_break20, + FRAME_break21, + FRAME_break22, + FRAME_break23, + FRAME_break24, + FRAME_break25, + FRAME_break26, + FRAME_break27, + FRAME_break28, + FRAME_break29, + FRAME_break30, + FRAME_break31, + FRAME_break32, + FRAME_death101, + FRAME_death102, + FRAME_death103, + FRAME_death104, + FRAME_death105, + FRAME_death106, + FRAME_death107, + FRAME_drain01, + FRAME_drain02, + FRAME_drain03, + FRAME_drain04, + FRAME_drain05, + FRAME_drain06, + FRAME_drain07, + FRAME_drain08, + FRAME_drain09, + FRAME_drain10, + FRAME_drain11, + FRAME_drain12, + FRAME_drain13, + FRAME_drain14, + FRAME_drain15, + FRAME_drain16, + FRAME_drain17, + FRAME_drain18, + FRAME_pain101, + FRAME_pain102, + FRAME_pain103, + FRAME_pain104, + FRAME_pain105, + FRAME_pain106, + FRAME_pain107, + FRAME_pain108, + FRAME_pain109, + FRAME_pain110, + FRAME_pain111, + FRAME_run01, + FRAME_run02, + FRAME_run03, + FRAME_run04, + FRAME_run05, + FRAME_run06, + FRAME_run07, + FRAME_run08, + FRAME_run09, + FRAME_run10, + FRAME_run11, + FRAME_run12, + FRAME_run13, + FRAME_run14, + FRAME_run15, + FRAME_stand01, + FRAME_stand02, + FRAME_stand03, + FRAME_stand04, + FRAME_stand05, + FRAME_stand06, + FRAME_stand07, + FRAME_stand08, + FRAME_stand09, + FRAME_stand10, + FRAME_stand11, + FRAME_stand12, + FRAME_stand13, + FRAME_stand14, + FRAME_stand15, + FRAME_stand16, + FRAME_stand17, + FRAME_stand18, + FRAME_stand19, + FRAME_stand20, + FRAME_stand21, + FRAME_stand22, + FRAME_stand23, + FRAME_stand24, + FRAME_stand25, + FRAME_stand26, + FRAME_stand27, + FRAME_stand28, + FRAME_stand29, + FRAME_stand30, + FRAME_stand31, + FRAME_stand32, + FRAME_stand33, + FRAME_stand34, + FRAME_stand35, + // ROGUE + FRAME_jump01, + FRAME_jump02, + FRAME_jump03, + FRAME_jump04, + FRAME_jump05, + FRAME_jump06, + FRAME_jump07, + FRAME_jump08 + // ROGUE +}; + +constexpr float MODEL_SCALE = 1.000000f; diff --git a/rerelease/m_player.h b/rerelease/m_player.h new file mode 100644 index 0000000..444c1cd --- /dev/null +++ b/rerelease/m_player.h @@ -0,0 +1,209 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// G:\quake2\baseq2\models/player_x/frames + +// This file generated by qdata - Do NOT Modify + +enum +{ + FRAME_stand01, + FRAME_stand02, + FRAME_stand03, + FRAME_stand04, + FRAME_stand05, + FRAME_stand06, + FRAME_stand07, + FRAME_stand08, + FRAME_stand09, + FRAME_stand10, + FRAME_stand11, + FRAME_stand12, + FRAME_stand13, + FRAME_stand14, + FRAME_stand15, + FRAME_stand16, + FRAME_stand17, + FRAME_stand18, + FRAME_stand19, + FRAME_stand20, + FRAME_stand21, + FRAME_stand22, + FRAME_stand23, + FRAME_stand24, + FRAME_stand25, + FRAME_stand26, + FRAME_stand27, + FRAME_stand28, + FRAME_stand29, + FRAME_stand30, + FRAME_stand31, + FRAME_stand32, + FRAME_stand33, + FRAME_stand34, + FRAME_stand35, + FRAME_stand36, + FRAME_stand37, + FRAME_stand38, + FRAME_stand39, + FRAME_stand40, + FRAME_run1, + FRAME_run2, + FRAME_run3, + FRAME_run4, + FRAME_run5, + FRAME_run6, + FRAME_attack1, + FRAME_attack2, + FRAME_attack3, + FRAME_attack4, + FRAME_attack5, + FRAME_attack6, + FRAME_attack7, + FRAME_attack8, + FRAME_pain101, + FRAME_pain102, + FRAME_pain103, + FRAME_pain104, + FRAME_pain201, + FRAME_pain202, + FRAME_pain203, + FRAME_pain204, + FRAME_pain301, + FRAME_pain302, + FRAME_pain303, + FRAME_pain304, + FRAME_jump1, + FRAME_jump2, + FRAME_jump3, + FRAME_jump4, + FRAME_jump5, + FRAME_jump6, + FRAME_flip01, + FRAME_flip02, + FRAME_flip03, + FRAME_flip04, + FRAME_flip05, + FRAME_flip06, + FRAME_flip07, + FRAME_flip08, + FRAME_flip09, + FRAME_flip10, + FRAME_flip11, + FRAME_flip12, + FRAME_salute01, + FRAME_salute02, + FRAME_salute03, + FRAME_salute04, + FRAME_salute05, + FRAME_salute06, + FRAME_salute07, + FRAME_salute08, + FRAME_salute09, + FRAME_salute10, + FRAME_salute11, + FRAME_taunt01, + FRAME_taunt02, + FRAME_taunt03, + FRAME_taunt04, + FRAME_taunt05, + FRAME_taunt06, + FRAME_taunt07, + FRAME_taunt08, + FRAME_taunt09, + FRAME_taunt10, + FRAME_taunt11, + FRAME_taunt12, + FRAME_taunt13, + FRAME_taunt14, + FRAME_taunt15, + FRAME_taunt16, + FRAME_taunt17, + FRAME_wave01, + FRAME_wave02, + FRAME_wave03, + FRAME_wave04, + FRAME_wave05, + FRAME_wave06, + FRAME_wave07, + FRAME_wave08, + FRAME_wave09, + FRAME_wave10, + FRAME_wave11, + FRAME_point01, + FRAME_point02, + FRAME_point03, + FRAME_point04, + FRAME_point05, + FRAME_point06, + FRAME_point07, + FRAME_point08, + FRAME_point09, + FRAME_point10, + FRAME_point11, + FRAME_point12, + FRAME_crstnd01, + FRAME_crstnd02, + FRAME_crstnd03, + FRAME_crstnd04, + FRAME_crstnd05, + FRAME_crstnd06, + FRAME_crstnd07, + FRAME_crstnd08, + FRAME_crstnd09, + FRAME_crstnd10, + FRAME_crstnd11, + FRAME_crstnd12, + FRAME_crstnd13, + FRAME_crstnd14, + FRAME_crstnd15, + FRAME_crstnd16, + FRAME_crstnd17, + FRAME_crstnd18, + FRAME_crstnd19, + FRAME_crwalk1, + FRAME_crwalk2, + FRAME_crwalk3, + FRAME_crwalk4, + FRAME_crwalk5, + FRAME_crwalk6, + FRAME_crattak1, + FRAME_crattak2, + FRAME_crattak3, + FRAME_crattak4, + FRAME_crattak5, + FRAME_crattak6, + FRAME_crattak7, + FRAME_crattak8, + FRAME_crattak9, + FRAME_crpain1, + FRAME_crpain2, + FRAME_crpain3, + FRAME_crpain4, + FRAME_crdeath1, + FRAME_crdeath2, + FRAME_crdeath3, + FRAME_crdeath4, + FRAME_crdeath5, + FRAME_death101, + FRAME_death102, + FRAME_death103, + FRAME_death104, + FRAME_death105, + FRAME_death106, + FRAME_death201, + FRAME_death202, + FRAME_death203, + FRAME_death204, + FRAME_death205, + FRAME_death206, + FRAME_death301, + FRAME_death302, + FRAME_death303, + FRAME_death304, + FRAME_death305, + FRAME_death306, + FRAME_death307, + FRAME_death308 +}; + +constexpr float MODEL_SCALE = 1.000000f; diff --git a/rerelease/m_rider.h b/rerelease/m_rider.h new file mode 100644 index 0000000..d9dc660 --- /dev/null +++ b/rerelease/m_rider.h @@ -0,0 +1,71 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// G:\quake2\baseq2\models/monsters/boss3/rider + +// This file generated by ModelGen - Do NOT Modify + +enum +{ + FRAME_stand201, + FRAME_stand202, + FRAME_stand203, + FRAME_stand204, + FRAME_stand205, + FRAME_stand206, + FRAME_stand207, + FRAME_stand208, + FRAME_stand209, + FRAME_stand210, + FRAME_stand211, + FRAME_stand212, + FRAME_stand213, + FRAME_stand214, + FRAME_stand215, + FRAME_stand216, + FRAME_stand217, + FRAME_stand218, + FRAME_stand219, + FRAME_stand220, + FRAME_stand221, + FRAME_stand222, + FRAME_stand223, + FRAME_stand224, + FRAME_stand225, + FRAME_stand226, + FRAME_stand227, + FRAME_stand228, + FRAME_stand229, + FRAME_stand230, + FRAME_stand231, + FRAME_stand232, + FRAME_stand233, + FRAME_stand234, + FRAME_stand235, + FRAME_stand236, + FRAME_stand237, + FRAME_stand238, + FRAME_stand239, + FRAME_stand240, + FRAME_stand241, + FRAME_stand242, + FRAME_stand243, + FRAME_stand244, + FRAME_stand245, + FRAME_stand246, + FRAME_stand247, + FRAME_stand248, + FRAME_stand249, + FRAME_stand250, + FRAME_stand251, + FRAME_stand252, + FRAME_stand253, + FRAME_stand254, + FRAME_stand255, + FRAME_stand256, + FRAME_stand257, + FRAME_stand258, + FRAME_stand259, + FRAME_stand260 +}; + +constexpr float MODEL_SCALE = 1.000000; diff --git a/rerelease/m_shambler.cpp b/rerelease/m_shambler.cpp new file mode 100644 index 0000000..56f3d07 --- /dev/null +++ b/rerelease/m_shambler.cpp @@ -0,0 +1,598 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +/* +============================================================================== + +SHAMBLER + +============================================================================== +*/ + +#include "g_local.h" +#include "m_shambler.h" +#include "m_flash.h" + +static int sound_pain; +static int sound_idle; +static int sound_die; +static int sound_sight; +static int sound_windup; +static int sound_melee1; +static int sound_melee2; +static int sound_smack; +static int sound_boom; + +// +// misc +// + +MONSTERINFO_SIGHT(shambler_sight) (edict_t* self, edict_t* other) -> void +{ + gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); +} + +constexpr vec3_t lightning_left_hand[] = { + { 44, 36, 25 }, + { 10, 44, 57 }, + { -1, 40, 70 }, + { -10, 34, 75 }, + { 7.4f, 24, 89 } +}; + +constexpr vec3_t lightning_right_hand[] = { + { 28, -38, 25 }, + { 31, -7, 70 }, + { 20, 0, 80 }, + { 16, 1.2f, 81 }, + { 27, -11, 83 } +}; + +static void shambler_lightning_update(edict_t *self) +{ + edict_t *lightning = self->beam; + + if (self->s.frame >= FRAME_magic01 + q_countof(lightning_left_hand)) + { + G_FreeEdict(lightning); + self->beam = nullptr; + return; + } + + vec3_t f, r; + AngleVectors(self->s.angles, f, r, nullptr); + lightning->s.origin = M_ProjectFlashSource(self, lightning_left_hand[self->s.frame - FRAME_magic01], f, r); + lightning->s.old_origin = M_ProjectFlashSource(self, lightning_right_hand[self->s.frame - FRAME_magic01], f, r); + gi.linkentity(lightning); +} + +void shambler_windup(edict_t* self) +{ + gi.sound(self, CHAN_WEAPON, sound_windup, 1, ATTN_NORM, 0); + + edict_t *lightning = self->beam = G_Spawn(); + lightning->s.modelindex = gi.modelindex("models/proj/lightning/tris.md2"); + lightning->s.renderfx |= RF_BEAM; + lightning->owner = self; + shambler_lightning_update(self); +} + +MONSTERINFO_IDLE(shambler_idle) (edict_t* self) -> void +{ + gi.sound(self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0); +} + +void shambler_maybe_idle(edict_t* self) +{ + if (frandom() > 0.8) + gi.sound(self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0); +} + +// +// stand +// + +mframe_t shambler_frames_stand[] = { + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand } +}; +MMOVE_T(shambler_move_stand) = { FRAME_stand01, FRAME_stand17, shambler_frames_stand, nullptr }; + +MONSTERINFO_STAND(shambler_stand) (edict_t* self) -> void +{ + M_SetAnimation(self, &shambler_move_stand); +} + +// +// walk +// + +void shambler_walk(edict_t* self); + +mframe_t shambler_frames_walk[] = { + { ai_walk, 10 }, // FIXME: add footsteps? + { ai_walk, 9 }, + { ai_walk, 9 }, + { ai_walk, 5 }, + { ai_walk, 6 }, + { ai_walk, 12 }, + { ai_walk, 8 }, + { ai_walk, 3 }, + { ai_walk, 13 }, + { ai_walk, 9 }, + { ai_walk, 7, shambler_maybe_idle }, + { ai_walk, 5 }, +}; +MMOVE_T(shambler_move_walk) = { FRAME_walk01, FRAME_walk12, shambler_frames_walk, nullptr }; + +MONSTERINFO_WALK(shambler_walk) (edict_t* self) -> void +{ + M_SetAnimation(self, &shambler_move_walk); +} + +// +// run +// + +void shambler_run(edict_t* self); + +mframe_t shambler_frames_run[] = { + { ai_run, 20 }, // FIXME: add footsteps? + { ai_run, 24 }, + { ai_run, 20 }, + { ai_run, 20 }, + { ai_run, 24 }, + { ai_run, 20, shambler_maybe_idle }, +}; +MMOVE_T(shambler_move_run) = { FRAME_run01, FRAME_run06, shambler_frames_run, nullptr }; + +MONSTERINFO_RUN(shambler_run) (edict_t* self) -> void +{ + if (self->enemy && self->enemy->client) + self->monsterinfo.aiflags |= AI_BRUTAL; + else + self->monsterinfo.aiflags &= ~AI_BRUTAL; + + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + { + M_SetAnimation(self, &shambler_move_stand); + return; + } + + M_SetAnimation(self, &shambler_move_run); +} + +// +// pain +// + +// FIXME: needs halved explosion damage + +mframe_t shambler_frames_pain[] = { + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, +}; +MMOVE_T(shambler_move_pain) = { FRAME_pain01, FRAME_pain06, shambler_frames_pain, shambler_run }; + +PAIN(shambler_pain) (edict_t* self, edict_t* other, float kick, int damage, const mod_t &mod) -> void +{ + if (level.time < self->timestamp) + return; + + self->timestamp = level.time + 1_ms; + gi.sound(self, CHAN_AUTO, sound_pain, 1, ATTN_NORM, 0); + + if (mod.id != MOD_CHAINFIST && damage <= 30 && frandom() > 0.2f) + return; + + // If hard or nightmare, don't go into pain while attacking + if (skill->integer >= 2) + { + if ((self->s.frame >= FRAME_smash01) && (self->s.frame <= FRAME_smash12)) + return; + + if ((self->s.frame >= FRAME_swingl01) && (self->s.frame <= FRAME_swingl09)) + return; + + if ((self->s.frame >= FRAME_swingr01) && (self->s.frame <= FRAME_swingr09)) + return; + } + + if (!M_ShouldReactToPain(self, mod)) + return; // no pain anims in nightmare + + if (level.time < self->pain_debounce_time) + return; + + self->pain_debounce_time = level.time + 2_sec; + M_SetAnimation(self, &shambler_move_pain); +} + +MONSTERINFO_SETSKIN(shambler_setskin) (edict_t* self) -> void +{ + // FIXME: create pain skin? + //if (self->health < (self->max_health / 2)) + // self->s.skinnum |= 1; + //else + // self->s.skinnum &= ~1; +} + +// +// attacks +// + +/* +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 ShamblerSaveLoc(edict_t* self) +{ + self->pos1 = self->enemy->s.origin; // save for aiming the shot + self->pos1[2] += self->enemy->viewheight; + self->monsterinfo.nextframe = FRAME_magic09; + + gi.sound(self, CHAN_WEAPON, sound_boom, 1, ATTN_NORM, 0); + shambler_lightning_update(self); +} + +constexpr spawnflags_t SPAWNFLAG_SHAMBLER_PRECISE = 1_spawnflag; + +vec3_t FindShamblerOffset(edict_t *self) +{ + vec3_t offset = { 0, 0, 48.f }; + + for (int i = 0; i < 8; i++) + { + if (M_CheckClearShot(self, offset)) + return offset; + + offset.z -= 4.f; + } + + return { 0, 0, 48.f }; +} + +void ShamblerCastLightning(edict_t* self) +{ + if (!self->enemy) + return; + + vec3_t start; + vec3_t dir; + vec3_t forward, right; + vec3_t offset = FindShamblerOffset(self); + + AngleVectors(self->s.angles, forward, right, nullptr); + start = M_ProjectFlashSource(self, offset, forward, right); + + // calc direction to where we targted + PredictAim(self, self->enemy, start, 0, false, self->spawnflags.has(SPAWNFLAG_SHAMBLER_PRECISE) ? 0.f : 0.1f, &dir, nullptr); + + vec3_t end = start + (dir * 8192); + trace_t tr = gi.traceline(start, end, self, MASK_PROJECTILE | CONTENTS_SLIME | CONTENTS_LAVA); + + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_LIGHTNING); + gi.WriteEntity(self); // source entity + gi.WriteEntity(world); // destination entity + gi.WritePosition(start); + gi.WritePosition(tr.endpos); + gi.multicast(start, MULTICAST_PVS, false); + + fire_bullet(self, start, dir, irandom(8, 12), 15, 0, 0, MOD_TESLA); +} + +mframe_t shambler_frames_magic[] = { + { ai_charge, 0, shambler_windup }, + { ai_charge, 0, shambler_lightning_update }, + { ai_charge, 0, shambler_lightning_update }, + { ai_move, 0, shambler_lightning_update }, + { ai_move, 0, shambler_lightning_update }, + { ai_move, 0, ShamblerSaveLoc}, + { ai_move }, + { ai_charge }, + { ai_move, 0, ShamblerCastLightning }, + { ai_move, 0, ShamblerCastLightning }, + { ai_move, 0, ShamblerCastLightning }, + { ai_move }, +}; + +MMOVE_T(shambler_attack_magic) = { FRAME_magic01, FRAME_magic12, shambler_frames_magic, shambler_run }; + +MONSTERINFO_ATTACK(shambler_attack) (edict_t* self) -> void +{ + M_SetAnimation(self, &shambler_attack_magic); +} + +// +// melee +// + +void shambler_melee1(edict_t* self) +{ + gi.sound(self, CHAN_WEAPON, sound_melee1, 1, ATTN_NORM, 0); +} + +void shambler_melee2(edict_t* self) +{ + gi.sound(self, CHAN_WEAPON, sound_melee2, 1, ATTN_NORM, 0); +} + +void sham_swingl9(edict_t* self); +void sham_swingr9(edict_t* self); + +void sham_smash10(edict_t* self) +{ + if (!self->enemy) + return; + + ai_charge(self, 0); + + if (!CanDamage(self->enemy, self)) + return; + + vec3_t aim = { MELEE_DISTANCE, self->mins[0], -4 }; + bool hit = fire_hit(self, aim, irandom(110, 120), 120); // Slower attack + + if (hit) + gi.sound(self, CHAN_WEAPON, sound_smack, 1, ATTN_NORM, 0); + + // SpawnMeatSpray(self.origin + v_forward * 16, crandom() * 100 * v_right); + // SpawnMeatSpray(self.origin + v_forward * 16, crandom() * 100 * v_right); +}; + +void ShamClaw(edict_t* self) +{ + if (!self->enemy) + return; + + ai_charge(self, 10); + + if (!CanDamage(self->enemy, self)) + return; + + vec3_t aim = { MELEE_DISTANCE, self->mins[0], -4 }; + bool hit = fire_hit(self, aim, irandom(70, 80), 80); // Slower attack + + if (hit) + gi.sound(self, CHAN_WEAPON, sound_smack, 1, ATTN_NORM, 0); + + // 250 if left, -250 if right + /* + if (side) + { + makevectorsfixed(self.angles); + SpawnMeatSpray(self.origin + v_forward * 16, side * v_right); + } + */ +}; + +mframe_t shambler_frames_smash[] = { + { ai_charge, 2, shambler_melee1 }, + { ai_charge, 6 }, + { ai_charge, 6 }, + { ai_charge, 5 }, + { ai_charge, 4 }, + { ai_charge, 1 }, + { ai_charge, 0 }, + { ai_charge, 0 }, + { ai_charge, 0 }, + { ai_charge, 0, sham_smash10 }, + { ai_charge, 5 }, + { ai_charge, 4 }, +}; + +MMOVE_T(shambler_attack_smash) = { FRAME_smash01, FRAME_smash12, shambler_frames_smash, shambler_run }; + +mframe_t shambler_frames_swingl[] = { + { ai_charge, 5, shambler_melee1 }, + { ai_charge, 3 }, + { ai_charge, 7 }, + { ai_charge, 3 }, + { ai_charge, 7 }, + { ai_charge, 9 }, + { ai_charge, 5, ShamClaw }, + { ai_charge, 4 }, + { ai_charge, 8, sham_swingl9 }, +}; + +MMOVE_T(shambler_attack_swingl) = { FRAME_swingl01, FRAME_swingl09, shambler_frames_swingl, shambler_run }; + +mframe_t shambler_frames_swingr[] = { + { ai_charge, 1, shambler_melee2 }, + { ai_charge, 8 }, + { ai_charge, 14 }, + { ai_charge, 7 }, + { ai_charge, 3 }, + { ai_charge, 6 }, + { ai_charge, 6, ShamClaw }, + { ai_charge, 3 }, + { ai_charge, 8, sham_swingr9 }, +}; + +MMOVE_T(shambler_attack_swingr) = { FRAME_swingr01, FRAME_swingr09, shambler_frames_swingr, shambler_run }; + +void sham_swingl9(edict_t* self) +{ + ai_charge(self, 8); + + if (brandom() && self->enemy && range_to(self, self->enemy) < MELEE_DISTANCE) + M_SetAnimation(self, &shambler_attack_swingr); +} + +void sham_swingr9(edict_t* self) +{ + ai_charge(self, 1); + ai_charge(self, 10); + + if (brandom() && self->enemy && range_to(self, self->enemy) < MELEE_DISTANCE) + M_SetAnimation(self, &shambler_attack_swingl); +} + +MONSTERINFO_MELEE(shambler_melee) (edict_t* self) -> void +{ + float chance = frandom(); + if (chance > 0.6 || self->health == 600) + M_SetAnimation(self, &shambler_attack_smash); + else if (chance > 0.3) + M_SetAnimation(self, &shambler_attack_swingl); + else + M_SetAnimation(self, &shambler_attack_swingr); +} + +// +// death +// + +void shambler_dead(edict_t* self) +{ + self->mins = { -16, -16, -24 }; + self->maxs = { 16, 16, -0 }; + monster_dead(self); +} + +static void shambler_shrink(edict_t* self) +{ + self->maxs[2] = 0; + self->svflags |= SVF_DEADMONSTER; + gi.linkentity(self); +} + +mframe_t shambler_frames_death[] = { + { ai_move, 0 }, + { ai_move, 0 }, + { ai_move, 0, shambler_shrink }, + { ai_move, 0 }, + { ai_move, 0 }, + { ai_move, 0 }, + { ai_move, 0 }, + { ai_move, 0 }, + { ai_move, 0 }, + { ai_move, 0 }, + { ai_move, 0 }, // FIXME: thud? +}; +MMOVE_T(shambler_move_death) = { FRAME_death01, FRAME_death11, shambler_frames_death, shambler_dead }; + +DIE(shambler_die) (edict_t* self, edict_t* inflictor, edict_t* attacker, int damage, const vec3_t& point, const mod_t &mod) -> void +{ + if (self->beam) + { + G_FreeEdict(self->beam); + self->beam = nullptr; + } + + if (self->beam2) + { + G_FreeEdict(self->beam2); + self->beam2 = nullptr; + } + + // check for gib + if (M_CheckGib(self, mod)) + { + gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0); + // FIXME: better gibs for shambler, shambler head + ThrowGibs(self, damage, { + { "models/objects/gibs/sm_meat/tris.md2" }, + { "models/objects/gibs/chest/tris.md2" }, + { "models/objects/gibs/head2/tris.md2", GIB_HEAD } + }); + self->deadflag = true; + return; + } + + if (self->deadflag) + return; + + // regular death + gi.sound(self, CHAN_VOICE, sound_die, 1, ATTN_NORM, 0); + self->deadflag = true; + self->takedamage = true; + + M_SetAnimation(self, &shambler_move_death); +} + +void SP_monster_shambler(edict_t* self) +{ + if ( !M_AllowSpawn( self ) ) { + G_FreeEdict( self ); + return; + } + + self->s.modelindex = gi.modelindex("models/monsters/shambler/tris.md2"); + self->mins = { -32, -32, -24 }; + self->maxs = { 32, 32, 64 }; + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + + gi.modelindex("models/proj/lightning/tris.md2"); + sound_pain = gi.soundindex("shambler/shurt2.wav"); + sound_idle = gi.soundindex("shambler/sidle.wav"); + sound_die = gi.soundindex("shambler/sdeath.wav"); + sound_windup = gi.soundindex("shambler/sattck1.wav"); + sound_melee1 = gi.soundindex("shambler/melee1.wav"); + sound_melee2 = gi.soundindex("shambler/melee2.wav"); + sound_sight = gi.soundindex("shambler/ssight.wav"); + sound_smack = gi.soundindex("shambler/smack.wav"); + sound_boom = gi.soundindex("shambler/sboom.wav"); + + self->health = 600 * st.health_multiplier; + self->gib_health = -60; + + self->mass = 500; + + self->pain = shambler_pain; + self->die = shambler_die; + self->monsterinfo.stand = shambler_stand; + self->monsterinfo.walk = shambler_walk; + self->monsterinfo.run = shambler_run; + self->monsterinfo.dodge = nullptr; + self->monsterinfo.attack = shambler_attack; + self->monsterinfo.melee = shambler_melee; + self->monsterinfo.sight = shambler_sight; + self->monsterinfo.idle = shambler_idle; + self->monsterinfo.blocked = nullptr; + self->monsterinfo.setskin = shambler_setskin; + + gi.linkentity(self); + + if (self->spawnflags.has(SPAWNFLAG_SHAMBLER_PRECISE)) + self->monsterinfo.aiflags |= AI_IGNORE_SHOTS; + + M_SetAnimation(self, &shambler_move_stand); + self->monsterinfo.scale = MODEL_SCALE; + + walkmonster_start(self); +} diff --git a/rerelease/m_shambler.h b/rerelease/m_shambler.h new file mode 100644 index 0000000..6f84b8f --- /dev/null +++ b/rerelease/m_shambler.h @@ -0,0 +1,106 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// G:\quake2\baseq2\models/monsters/tank + +// This file generated by qdata - Do NOT Modify + +enum +{ + FRAME_stand01, + FRAME_stand02, + FRAME_stand03, + FRAME_stand04, + FRAME_stand05, + FRAME_stand06, + FRAME_stand07, + FRAME_stand08, + FRAME_stand09, + FRAME_stand10, + FRAME_stand11, + FRAME_stand12, + FRAME_stand13, + FRAME_stand14, + FRAME_stand15, + FRAME_stand16, + FRAME_stand17, + FRAME_walk01, + FRAME_walk02, + FRAME_walk03, + FRAME_walk04, + FRAME_walk05, + FRAME_walk06, + FRAME_walk07, + FRAME_walk08, + FRAME_walk09, + FRAME_walk10, + FRAME_walk11, + FRAME_walk12, + FRAME_run01, + FRAME_run02, + FRAME_run03, + FRAME_run04, + FRAME_run05, + FRAME_run06, + FRAME_smash01, + FRAME_smash02, + FRAME_smash03, + FRAME_smash04, + FRAME_smash05, + FRAME_smash06, + FRAME_smash07, + FRAME_smash08, + FRAME_smash09, + FRAME_smash10, + FRAME_smash11, + FRAME_smash12, + FRAME_swingr01, + FRAME_swingr02, + FRAME_swingr03, + FRAME_swingr04, + FRAME_swingr05, + FRAME_swingr06, + FRAME_swingr07, + FRAME_swingr08, + FRAME_swingr09, + FRAME_swingl01, + FRAME_swingl02, + FRAME_swingl03, + FRAME_swingl04, + FRAME_swingl05, + FRAME_swingl06, + FRAME_swingl07, + FRAME_swingl08, + FRAME_swingl09, + FRAME_magic01, + FRAME_magic02, + FRAME_magic03, + FRAME_magic04, + FRAME_magic05, + FRAME_magic06, + FRAME_magic07, + FRAME_magic08, + FRAME_magic09, + FRAME_magic10, + FRAME_magic11, + FRAME_magic12, + FRAME_pain01, + FRAME_pain02, + FRAME_pain03, + FRAME_pain04, + FRAME_pain05, + FRAME_pain06, + FRAME_death01, + FRAME_death02, + FRAME_death03, + FRAME_death04, + FRAME_death05, + FRAME_death06, + FRAME_death07, + FRAME_death08, + FRAME_death09, + FRAME_death10, + FRAME_death11 +}; + + +constexpr float MODEL_SCALE = 1.000000f; \ No newline at end of file diff --git a/rerelease/m_soldier.cpp b/rerelease/m_soldier.cpp new file mode 100644 index 0000000..94ed7f6 --- /dev/null +++ b/rerelease/m_soldier.cpp @@ -0,0 +1,2042 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +/* +============================================================================== + +SOLDIER + +============================================================================== +*/ + +#include "g_local.h" +#include "m_soldier.h" +#include "m_flash.h" + +static int sound_idle; +static int sound_sight1; +static int sound_sight2; +static int sound_pain_light; +static int sound_pain; +static int sound_pain_ss; +static int sound_death_light; +static int sound_death; +static int sound_death_ss; +static int sound_cock; + +void soldier_start_charge(edict_t *self) +{ + self->monsterinfo.aiflags |= AI_CHARGING; +} + +void soldier_stop_charge(edict_t *self) +{ + self->monsterinfo.aiflags &= ~AI_CHARGING; +} + +void soldier_idle(edict_t *self) +{ + if (frandom() > 0.8f) + gi.sound(self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0); +} + +void soldier_cock(edict_t *self) +{ + if (self->s.frame == FRAME_stand322) + gi.sound(self, CHAN_WEAPON, sound_cock, 1, ATTN_IDLE, 0); + else + gi.sound(self, CHAN_WEAPON, sound_cock, 1, ATTN_NORM, 0); + + // [Paril-KEX] reset cockness + self->dmg = 0; +} + +// RAFAEL +void soldierh_hyper_laser_sound_start(edict_t *self) +{ + if (self->style == 1) + { + if (self->count >= 2 && self->count < 4) + self->monsterinfo.weapon_sound = gi.soundindex("weapons/hyprbl1a.wav"); + } +} + +void soldierh_hyper_laser_sound_end(edict_t *self) +{ + if (self->monsterinfo.weapon_sound) + { + if (self->count >= 2 && self->count < 4) + gi.sound(self, CHAN_AUTO, gi.soundindex("weapons/hyprbd1a.wav"), 1, ATTN_NORM, 0); + + self->monsterinfo.weapon_sound = 0; + } +} +// RAFAEL + +// STAND + +void soldier_stand(edict_t *self); + +mframe_t soldier_frames_stand1[] = { + { ai_stand, 0, soldier_idle }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand } +}; +MMOVE_T(soldier_move_stand1) = { FRAME_stand101, FRAME_stand130, soldier_frames_stand1, soldier_stand }; + +mframe_t soldier_frames_stand2[] = { + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand, 0, monster_footstep }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand, 0, monster_footstep } +}; +MMOVE_T(soldier_move_stand2) = { FRAME_stand201, FRAME_stand240, soldier_frames_stand2, soldier_stand }; + +mframe_t soldier_frames_stand3[] = { + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + + { ai_stand }, + { ai_stand, 0, soldier_cock }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand } +}; +MMOVE_T(soldier_move_stand3) = { FRAME_stand301, FRAME_stand339, soldier_frames_stand3, soldier_stand }; + +MONSTERINFO_STAND(soldier_stand) (edict_t *self) -> void +{ + float r = frandom(); + + if ((self->monsterinfo.active_move != &soldier_move_stand1) || (r < 0.6f)) + M_SetAnimation(self, &soldier_move_stand1); + else if (r < 0.8f) + M_SetAnimation(self, &soldier_move_stand2); + else + M_SetAnimation(self, &soldier_move_stand3); + soldierh_hyper_laser_sound_end(self); +} + +// +// WALK +// + +void soldier_walk1_random(edict_t *self) +{ + if (frandom() > 0.1f) + self->monsterinfo.nextframe = FRAME_walk101; +} + +mframe_t soldier_frames_walk1[] = { + { ai_walk, 3 }, + { ai_walk, 6 }, + { ai_walk, 2 }, + { ai_walk, 2, monster_footstep }, + { ai_walk, 2 }, + { ai_walk, 1 }, + { ai_walk, 6 }, + { ai_walk, 5 }, + { ai_walk, 3, monster_footstep }, + { ai_walk, -1, soldier_walk1_random }, + { ai_walk }, + { ai_walk }, + { ai_walk }, + { ai_walk }, + { ai_walk }, + { ai_walk }, + { ai_walk }, + { ai_walk }, + { ai_walk }, + { ai_walk }, + { ai_walk }, + { ai_walk }, + { ai_walk }, + { ai_walk }, + { ai_walk }, + { ai_walk }, + { ai_walk }, + { ai_walk }, + { ai_walk }, + { ai_walk }, + { ai_walk }, + { ai_walk }, + { ai_walk } +}; +MMOVE_T(soldier_move_walk1) = { FRAME_walk101, FRAME_walk133, soldier_frames_walk1, nullptr }; + +mframe_t soldier_frames_walk2[] = { + { ai_walk, 4, monster_footstep }, + { ai_walk, 4 }, + { ai_walk, 9 }, + { ai_walk, 8 }, + { ai_walk, 5 }, + { ai_walk, 1, monster_footstep }, + { ai_walk, 3 }, + { ai_walk, 7 }, + { ai_walk, 6 }, + { ai_walk, 7 } +}; +MMOVE_T(soldier_move_walk2) = { FRAME_walk209, FRAME_walk218, soldier_frames_walk2, nullptr }; + +MONSTERINFO_WALK(soldier_walk) (edict_t *self) -> void +{ + // [Paril-KEX] during N64 cutscene, always use fast walk or we bog down the line + if (!(self->hackflags & HACKFLAG_END_CUTSCENE) && frandom() < 0.5f) + M_SetAnimation(self, &soldier_move_walk1); + else + M_SetAnimation(self, &soldier_move_walk2); +} + +// +// RUN +// + +void soldier_run(edict_t *self); + +mframe_t soldier_frames_start_run[] = { + { ai_run, 7 }, + { ai_run, 5 } +}; +MMOVE_T(soldier_move_start_run) = { FRAME_run01, FRAME_run02, soldier_frames_start_run, soldier_run }; + +mframe_t soldier_frames_run[] = { + { ai_run, 10 }, + { ai_run, 11, [](edict_t *self) { monster_done_dodge(self); monster_footstep(self); } }, + { ai_run, 11 }, + { ai_run, 16 }, + { ai_run, 10, monster_footstep }, + { ai_run, 15, monster_done_dodge } +}; +MMOVE_T(soldier_move_run) = { FRAME_run03, FRAME_run08, soldier_frames_run, nullptr }; + +MONSTERINFO_RUN(soldier_run) (edict_t *self) -> void +{ + monster_done_dodge(self); + soldierh_hyper_laser_sound_end(self); + + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + { + M_SetAnimation(self, &soldier_move_stand1); + return; + } + + if (self->monsterinfo.active_move == &soldier_move_walk1 || + self->monsterinfo.active_move == &soldier_move_walk2 || + self->monsterinfo.active_move == &soldier_move_start_run || + self->monsterinfo.active_move == &soldier_move_run) + { + M_SetAnimation(self, &soldier_move_run); + } + else + { + M_SetAnimation(self, &soldier_move_start_run); + } +} + +// +// PAIN +// + +mframe_t soldier_frames_pain1[] = { + { ai_move, -3 }, + { ai_move, 4 }, + { ai_move, 1 }, + { ai_move, 1 }, + { ai_move } +}; +MMOVE_T(soldier_move_pain1) = { FRAME_pain101, FRAME_pain105, soldier_frames_pain1, soldier_run }; + +mframe_t soldier_frames_pain2[] = { + { ai_move, -13 }, + { ai_move, -1 }, + { ai_move, 2 }, + { ai_move, 4 }, + { ai_move, 2 }, + { ai_move, 3 }, + { ai_move, 2 } +}; +MMOVE_T(soldier_move_pain2) = { FRAME_pain201, FRAME_pain207, soldier_frames_pain2, soldier_run }; + +mframe_t soldier_frames_pain3[] = { + { ai_move, -8 }, + { ai_move, 10 }, + { ai_move, -4, monster_footstep }, + { ai_move, -1 }, + { ai_move, -3 }, + { ai_move }, + { ai_move, 3 }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move, 1 }, + { ai_move }, + { ai_move, 1 }, + { ai_move, 2 }, + { ai_move, 4 }, + { ai_move, 3 }, + { ai_move, 2, monster_footstep } +}; +MMOVE_T(soldier_move_pain3) = { FRAME_pain301, FRAME_pain318, soldier_frames_pain3, soldier_run }; + +mframe_t soldier_frames_pain4[] = { + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move, -10 }, + { ai_move, -6 }, + { ai_move, 8 }, + { ai_move, 4 }, + { ai_move, 1 }, + { ai_move }, + { ai_move, 2 }, + { ai_move, 5 }, + { ai_move, 2 }, + { ai_move, -1 }, + { ai_move, -1 }, + { ai_move, 3 }, + { ai_move, 2 }, + { ai_move } +}; +MMOVE_T(soldier_move_pain4) = { FRAME_pain401, FRAME_pain417, soldier_frames_pain4, soldier_run }; + +PAIN(soldier_pain) (edict_t *self, edict_t *other, float kick, int damage, const mod_t &mod) -> void +{ + float r; + int n; + + monster_done_dodge(self); + soldier_stop_charge(self); + + // if we're blind firing, this needs to be turned off here + self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING; + + if (level.time < self->pain_debounce_time) + { + if ((self->velocity[2] > 100) && ((self->monsterinfo.active_move == &soldier_move_pain1) || (self->monsterinfo.active_move == &soldier_move_pain2) || (self->monsterinfo.active_move == &soldier_move_pain3))) + { + // PMM - clear duck flag + if (self->monsterinfo.aiflags & AI_DUCKED) + monster_duck_up(self); + M_SetAnimation(self, &soldier_move_pain4); + soldierh_hyper_laser_sound_end(self); + } + return; + } + + self->pain_debounce_time = level.time + 3_sec; + + n = self->count | 1; + if (n == 1) + gi.sound(self, CHAN_VOICE, sound_pain_light, 1, ATTN_NORM, 0); + else if (n == 3) + gi.sound(self, CHAN_VOICE, sound_pain, 1, ATTN_NORM, 0); + else + gi.sound(self, CHAN_VOICE, sound_pain_ss, 1, ATTN_NORM, 0); + + if (self->velocity[2] > 100) + { + // PMM - clear duck flag + if (self->monsterinfo.aiflags & AI_DUCKED) + monster_duck_up(self); + M_SetAnimation(self, &soldier_move_pain4); + soldierh_hyper_laser_sound_end(self); + return; + } + + if (!M_ShouldReactToPain(self, mod)) + return; // no pain anims in nightmare + + r = frandom(); + + if (r < 0.33f) + M_SetAnimation(self, &soldier_move_pain1); + else if (r < 0.66f) + M_SetAnimation(self, &soldier_move_pain2); + else + M_SetAnimation(self, &soldier_move_pain3); + + // PMM - clear duck flag + if (self->monsterinfo.aiflags & AI_DUCKED) + monster_duck_up(self); + soldierh_hyper_laser_sound_end(self); +} + +MONSTERINFO_SETSKIN(soldier_setskin) (edict_t *self) -> void +{ + if (self->health < (self->max_health / 2)) + self->s.skinnum |= 1; + else + self->s.skinnum &= ~1; +} + +// +// ATTACK +// + +constexpr monster_muzzleflash_id_t blaster_flash[] = { MZ2_SOLDIER_BLASTER_1, MZ2_SOLDIER_BLASTER_2, MZ2_SOLDIER_BLASTER_3, MZ2_SOLDIER_BLASTER_4, MZ2_SOLDIER_BLASTER_5, MZ2_SOLDIER_BLASTER_6, MZ2_SOLDIER_BLASTER_7, MZ2_SOLDIER_BLASTER_8, MZ2_SOLDIER_BLASTER_9 }; +constexpr monster_muzzleflash_id_t shotgun_flash[] = { MZ2_SOLDIER_SHOTGUN_1, MZ2_SOLDIER_SHOTGUN_2, MZ2_SOLDIER_SHOTGUN_3, MZ2_SOLDIER_SHOTGUN_4, MZ2_SOLDIER_SHOTGUN_5, MZ2_SOLDIER_SHOTGUN_6, MZ2_SOLDIER_SHOTGUN_7, MZ2_SOLDIER_SHOTGUN_8, MZ2_SOLDIER_SHOTGUN_9 }; +constexpr monster_muzzleflash_id_t machinegun_flash[] = { MZ2_SOLDIER_MACHINEGUN_1, MZ2_SOLDIER_MACHINEGUN_2, MZ2_SOLDIER_MACHINEGUN_3, MZ2_SOLDIER_MACHINEGUN_4, MZ2_SOLDIER_MACHINEGUN_5, MZ2_SOLDIER_MACHINEGUN_6, MZ2_SOLDIER_MACHINEGUN_7, MZ2_SOLDIER_MACHINEGUN_8, MZ2_SOLDIER_MACHINEGUN_9 }; + +void soldier_fire_vanilla(edict_t *self, int flash_number, bool angle_limited) +{ + vec3_t start; + vec3_t forward, right, up; + vec3_t aim; + vec3_t dir; + vec3_t end; + float r, u; + monster_muzzleflash_id_t flash_index; + vec3_t aim_norm; + float angle; + vec3_t aim_good; + + if (self->count < 2) + flash_index = blaster_flash[flash_number]; + else if (self->count < 4) + flash_index = shotgun_flash[flash_number]; + else + flash_index = machinegun_flash[flash_number]; + + AngleVectors(self->s.angles, forward, right, nullptr); + start = M_ProjectFlashSource(self, monster_flash_offset[flash_index], forward, right); + + if (flash_number == 5 || flash_number == 6) // he's dead + { + if (self->spawnflags.has(SPAWNFLAG_MONSTER_DEAD)) + return; + + aim = forward; + } + else + { + if ((!self->enemy) || (!self->enemy->inuse)) + { + self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; + return; + } + + // PMM + if (self->monsterinfo.attack_state == AS_BLIND) + end = self->monsterinfo.blind_fire_target; + else + end = self->enemy->s.origin; + // pmm + end[2] += self->enemy->viewheight; + aim = end - start; + aim_good = end; + // PMM + if (angle_limited) + { + aim_norm = aim; + aim_norm.normalize(); + angle = aim_norm.dot(forward); + if (angle < 0.5f) // ~25 degree angle + { + if (level.time >= self->monsterinfo.fire_wait) + self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; + else + self->monsterinfo.aiflags |= AI_HOLD_FRAME; + + return; + } + } + //-PMM + dir = vectoangles(aim); + AngleVectors(dir, forward, right, up); + + r = crandom() * 1000; + u = crandom() * 500; + + end = start + (forward * 8192); + end += (right * r); + end += (up * u); + + aim = end - start; + aim.normalize(); + } + + if (self->count <= 1) + { + monster_fire_blaster(self, start, aim, 5, 600, flash_index, EF_BLASTER); + } + else if (self->count <= 3) + { + monster_fire_shotgun(self, start, aim, 2, 1, 1500, 750, 9, flash_index); + // [Paril-KEX] indicates to soldier that he must cock + self->dmg = 1; + } + else + { + // PMM - changed to wait from pausetime to not interfere with dodge code + if (!(self->monsterinfo.aiflags & AI_HOLD_FRAME)) + self->monsterinfo.fire_wait = level.time + random_time(300_ms, 1.1_sec); + + monster_fire_bullet(self, start, aim, 2, 4, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, flash_index); + + if (level.time >= self->monsterinfo.fire_wait) + self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; + else + self->monsterinfo.aiflags |= AI_HOLD_FRAME; + } +} + +PRETHINK(soldierh_laser_update) (edict_t *laser) -> void +{ + edict_t *self = laser->owner; + + vec3_t forward, right, up; + vec3_t start; + vec3_t tempvec; + + AngleVectors(self->s.angles, forward, right, up); + start = self->s.origin; + tempvec = monster_flash_offset[self->radius_dmg]; + start += (forward * tempvec[0]); + start += (right * tempvec[1]); + start += (up * (tempvec[2] + 6)); + + if (!self->deadflag) + PredictAim(self, self->enemy, start, 0, false, frandom(0.1f, 0.2f), &forward, nullptr); + + laser->s.origin = start; + laser->movedir = forward; + gi.linkentity(laser); + dabeam_update(laser, false); +} + +// RAFAEL +void soldierh_laserbeam(edict_t *self, int flash_index) +{ + self->radius_dmg = flash_index; + monster_fire_dabeam(self, 1, false, soldierh_laser_update); +} + +constexpr monster_muzzleflash_id_t ripper_flash[] = { MZ2_SOLDIER_RIPPER_1, MZ2_SOLDIER_RIPPER_2, MZ2_SOLDIER_RIPPER_3, MZ2_SOLDIER_RIPPER_4, MZ2_SOLDIER_RIPPER_5, MZ2_SOLDIER_RIPPER_6, MZ2_SOLDIER_RIPPER_7, MZ2_SOLDIER_RIPPER_8, MZ2_SOLDIER_RIPPER_9 }; +constexpr monster_muzzleflash_id_t hyper_flash[] = { MZ2_SOLDIER_HYPERGUN_1, MZ2_SOLDIER_HYPERGUN_2, MZ2_SOLDIER_HYPERGUN_3, MZ2_SOLDIER_HYPERGUN_4, MZ2_SOLDIER_HYPERGUN_5, MZ2_SOLDIER_HYPERGUN_6, MZ2_SOLDIER_HYPERGUN_7, MZ2_SOLDIER_HYPERGUN_8, MZ2_SOLDIER_HYPERGUN_9 }; + +void soldier_fire_xatrix(edict_t *self, int flash_number, bool angle_limited) +{ + vec3_t start; + vec3_t forward, right, up; + vec3_t aim; + vec3_t dir; + vec3_t end; + float r, u; + monster_muzzleflash_id_t flash_index; + vec3_t aim_norm; + float angle; + vec3_t aim_good; + + if (self->count < 2) + flash_index = ripper_flash[flash_number]; // ripper + else if (self->count < 4) + flash_index = hyper_flash[flash_number]; // hyperblaster + else + flash_index = machinegun_flash[flash_number]; // laserbeam + + AngleVectors(self->s.angles, forward, right, nullptr); + start = M_ProjectFlashSource(self, monster_flash_offset[flash_index], forward, right); + + if (flash_number == 5 || flash_number == 6) + { + if (self->spawnflags.has(SPAWNFLAG_MONSTER_DEAD)) + return; + + aim = forward; + } + else + { + // PMM + if (self->monsterinfo.attack_state == AS_BLIND) + end = self->monsterinfo.blind_fire_target; + else + end = self->enemy->s.origin; + // pmm + end[2] += self->enemy->viewheight; + + aim = end - start; + aim_good = end; + + // PMM + if (angle_limited) + { + aim_norm = aim; + aim_norm.normalize(); + angle = aim_norm.dot(forward); + + if (angle < 0.5f) // ~25 degree angle + { + if (level.time >= self->monsterinfo.fire_wait) + self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; + else + self->monsterinfo.aiflags |= AI_HOLD_FRAME; + + return; + } + } + //-PMM + + dir = vectoangles(aim); + AngleVectors(dir, forward, right, up); + + r = crandom() * 100; + u = crandom() * 50; + end = start + (forward * 8192); + end += (right * r); + end += (up * u); + + aim = end - start; + aim.normalize(); + } + + if (self->count <= 1) + { + // RAFAEL 24-APR-98 + // droped the damage from 15 to 5 + monster_fire_ionripper(self, start, aim, 5, 600, flash_index, EF_IONRIPPER); + } + else if (self->count <= 3) + { + monster_fire_blueblaster(self, start, aim, 1, 600, flash_index, EF_BLUEHYPERBLASTER); + } + else + { + // PMM - changed to wait from pausetime to not interfere with dodge code + if (!(self->monsterinfo.aiflags & AI_HOLD_FRAME)) + self->monsterinfo.fire_wait = level.time + random_time(300_ms, 1.1_sec); + + soldierh_laserbeam(self, flash_index); + + if (level.time >= self->monsterinfo.fire_wait) + self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; + else + self->monsterinfo.aiflags |= AI_HOLD_FRAME; + } +} +// RAFAEL + +void soldier_fire(edict_t *self, int flash_number, bool angle_limited) +{ + // RAFAEL + if (self->style == 1) + soldier_fire_xatrix(self, flash_number, angle_limited); + else + // RAFAEL + soldier_fire_vanilla(self, flash_number, angle_limited); +} + +// ATTACK1 (blaster/shotgun) + +void soldier_fire1(edict_t *self) +{ + soldier_fire(self, 0, false); +} + +void soldier_attack1_refire1(edict_t *self) +{ + // [Paril-KEX] + if (self->count <= 0) + self->monsterinfo.nextframe = FRAME_attak110; + + // PMM - blindfire + if (self->monsterinfo.aiflags & AI_MANUAL_STEERING) + { + self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING; + return; + } + // pmm + + if (!self->enemy) + return; + + if (self->count > 1) + return; + + if (self->enemy->health <= 0) + return; + + if (((frandom() < 0.5f) && visible(self, self->enemy)) || (range_to(self, self->enemy) <= RANGE_MELEE)) + self->monsterinfo.nextframe = FRAME_attak102; + else + self->monsterinfo.nextframe = FRAME_attak110; +} + +void soldier_attack1_refire2(edict_t *self) +{ + if (!self->enemy) + return; + + if (self->count < 2) + return; + + if (self->enemy->health <= 0) + return; + + if (((self->radius_dmg || frandom() < 0.5f) && visible(self, self->enemy)) || (range_to(self, self->enemy) <= RANGE_MELEE)) + { + self->monsterinfo.nextframe = FRAME_attak102; + self->radius_dmg = 0; + } +} + +static void soldier_attack1_shotgun_check(edict_t *self) +{ + if (self->dmg) + { + self->monsterinfo.nextframe = FRAME_attak106; + // [Paril-KEX] indicate that we should force a refire + self->radius_dmg = 1; + } +} + +static void soldier_blind_check(edict_t *self) +{ + if (self->monsterinfo.aiflags & AI_MANUAL_STEERING) + { + vec3_t aim = self->monsterinfo.blind_fire_target - self->s.origin; + self->ideal_yaw = vectoyaw(aim); + } +} + +mframe_t soldier_frames_attack1[] = { + { ai_charge, 0, soldier_blind_check }, + { ai_charge, 0, soldier_attack1_shotgun_check }, + { ai_charge, 0, soldier_fire1 }, + { ai_charge }, + { ai_charge }, + { ai_charge, 0, soldier_attack1_refire1 }, + { ai_charge }, + { ai_charge, 0, soldier_cock }, + { ai_charge, 0, soldier_attack1_refire2 }, + { ai_charge }, + { ai_charge }, + { ai_charge } +}; +MMOVE_T(soldier_move_attack1) = { FRAME_attak101, FRAME_attak112, soldier_frames_attack1, soldier_run }; + +// ATTACK1 (blaster/shotgun) +void soldierh_hyper_refire1(edict_t *self) +{ + if (!self->enemy) + return; + + if (self->count >= 2 && self->count < 4) + { + if (frandom() < 0.7f && visible(self, self->enemy)) + self->s.frame = FRAME_attak103; + } +} + +void soldierh_hyperripper1(edict_t *self) +{ + if (self->count < 4) + soldier_fire(self, 0, false); +} + +mframe_t soldierh_frames_attack1[] = { + { ai_charge, 0, soldier_blind_check }, + { ai_charge, 0, soldierh_hyper_laser_sound_start }, + { ai_charge, 0, soldier_fire1 }, + { ai_charge, 0, soldierh_hyperripper1 }, + { ai_charge, 0, soldierh_hyperripper1 }, + { ai_charge, 0, soldier_attack1_refire1 }, + { ai_charge, 0, soldierh_hyper_refire1 }, + { ai_charge, 0, soldier_cock }, + { ai_charge, 0, soldier_attack1_refire2 }, + { ai_charge, 0, soldierh_hyper_laser_sound_end }, + { ai_charge }, + { ai_charge } +}; +MMOVE_T(soldierh_move_attack1) = { FRAME_attak101, FRAME_attak112, soldierh_frames_attack1, soldier_run }; + +// ATTACK2 (blaster/shotgun) + +void soldier_fire2(edict_t *self) +{ + soldier_fire(self, 1, false); +} + +void soldier_attack2_refire1(edict_t *self) +{ + if (self->count <= 0) + self->monsterinfo.nextframe = FRAME_attak216; + + if (!self->enemy) + return; + + if (self->count > 1) + return; + + if (self->enemy->health <= 0) + return; + + if (((frandom() < 0.5f) && visible(self, self->enemy)) || (range_to(self, self->enemy) <= RANGE_MELEE)) + self->monsterinfo.nextframe = FRAME_attak204; +} + +void soldier_attack2_refire2(edict_t *self) +{ + if (!self->enemy) + return; + + if (self->count < 2) + return; + + if (self->enemy->health <= 0) + return; + + // RAFAEL + if (((self->radius_dmg || frandom() < 0.5f) && visible(self, self->enemy)) || ((self->style == 0 || self->count < 4) && (range_to(self, self->enemy) <= RANGE_MELEE))) + { + // RAFAEL + self->monsterinfo.nextframe = FRAME_attak204; + self->radius_dmg = 0; + } +} + +static void soldier_attack2_shotgun_check(edict_t *self) +{ + if (self->dmg) + { + self->monsterinfo.nextframe = FRAME_attak210; + // [Paril-KEX] indicate that we should force a refire + self->radius_dmg = 1; + } +} + +mframe_t soldier_frames_attack2[] = { + { ai_charge }, + { ai_charge }, + { ai_charge, 0, soldier_attack2_shotgun_check }, + { ai_charge }, + { ai_charge, 0, soldier_fire2 }, + { ai_charge }, + { ai_charge }, + { ai_charge, 0, soldier_attack2_refire1 }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge, 0, soldier_cock }, + { ai_charge }, + { ai_charge, 0, soldier_attack2_refire2 }, + { ai_charge }, + { ai_charge }, + { ai_charge } +}; +MMOVE_T(soldier_move_attack2) = { FRAME_attak201, FRAME_attak218, soldier_frames_attack2, soldier_run }; + +// RAFAEL +void soldierh_hyper_refire2(edict_t *self) +{ + if (!self->enemy) + return; + + if (self->count < 2) + return; + else if (self->count < 4) + { + if (frandom() < 0.7f && visible(self, self->enemy)) + self->s.frame = FRAME_attak205; + } +} + +void soldierh_hyperripper2(edict_t *self) +{ + if (self->count < 4) + soldier_fire(self, 1, false); +} + +mframe_t soldierh_frames_attack2[] = { + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge, 0, soldierh_hyper_laser_sound_start }, + { ai_charge, 0, soldier_fire2 }, + { ai_charge, 0, soldierh_hyperripper2 }, + { ai_charge, 0, soldierh_hyperripper2 }, + { ai_charge, 0, soldier_attack2_refire1 }, + { ai_charge, 0, soldierh_hyper_refire2 }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge, 0, soldier_cock }, + { ai_charge }, + { ai_charge, 0, soldier_attack2_refire2 }, + { ai_charge, 0, soldierh_hyper_laser_sound_end }, + { ai_charge }, + { ai_charge } +}; +MMOVE_T(soldierh_move_attack2) = { FRAME_attak201, FRAME_attak218, soldierh_frames_attack2, soldier_run }; +// RAFAEL + +// ATTACK3 (duck and shoot) +void soldier_fire3(edict_t *self) +{ + soldier_fire(self, 2, false); +} + +void soldierh_hyperripper3(edict_t *self) +{ + if (self->s.skinnum >= 6 && self->count < 4) + soldier_fire(self, 2, false); +} + +void soldier_attack3_refire(edict_t *self) +{ + if (self->dmg) + monster_duck_hold(self); + else if ((level.time + 400_ms) < self->monsterinfo.duck_wait_time) + self->monsterinfo.nextframe = FRAME_attak303; +} + +mframe_t soldier_frames_attack3[] = { + { ai_charge, 0, monster_duck_down }, + { ai_charge, 0, soldierh_hyper_laser_sound_start }, + { ai_charge, 0, soldier_fire3 }, + { ai_charge, 0, soldierh_hyperripper3 }, + { ai_charge, 0, soldierh_hyperripper3 }, + { ai_charge, 0, soldier_attack3_refire }, + { ai_charge, 0, monster_duck_up }, + { ai_charge, 0, soldierh_hyper_laser_sound_end }, + { ai_charge } +}; +MMOVE_T(soldier_move_attack3) = { FRAME_attak301, FRAME_attak309, soldier_frames_attack3, soldier_run }; + +// ATTACK4 (machinegun) + +void soldier_fire4(edict_t *self) +{ + soldier_fire(self, 3, false); +} + +mframe_t soldier_frames_attack4[] = { + { ai_charge }, + { ai_charge, 0, soldierh_hyper_laser_sound_start }, + { ai_charge, 0, soldier_fire4 }, + { ai_charge, 0, soldierh_hyper_laser_sound_end }, + { ai_charge }, + { ai_charge } +}; +MMOVE_T(soldier_move_attack4) = { FRAME_attak401, FRAME_attak406, soldier_frames_attack4, soldier_run }; + +// ATTACK6 (run & shoot) + +void soldier_fire8(edict_t *self) +{ + soldier_fire(self, 7, true); +} + +void soldier_attack6_refire1(edict_t *self) +{ + // PMM - make sure dodge & charge bits are cleared + monster_done_dodge(self); + soldier_stop_charge(self); + + if (!self->enemy) + return; + + if (self->count > 1) + return; + + if (self->enemy->health <= 0 || + range_to(self, self->enemy) < RANGE_NEAR || + !visible(self, self->enemy)) // don't endlessly run into walls + { + soldier_run(self); + return; + } + + if (frandom() < 0.25f) + self->monsterinfo.nextframe = FRAME_runs03; + else + soldier_run(self); +} + +void soldier_attack6_refire2(edict_t *self) +{ + // PMM - make sure dodge & charge bits are cleared + monster_done_dodge(self); + soldier_stop_charge(self); + + if (!self->enemy || self->count <= 0) + return; + + if (self->enemy->health <= 0 || + (!self->radius_dmg && range_to(self, self->enemy) < RANGE_NEAR) || + !visible(self, self->enemy)) // don't endlessly run into walls + { + soldierh_hyper_laser_sound_end(self); + return; + } + + if (self->radius_dmg || frandom() < 0.25f) + { + self->monsterinfo.nextframe = FRAME_runs03; + self->radius_dmg = 0; + } +} + +static void soldier_attack6_shotgun_check(edict_t *self) +{ + if (self->dmg) + { + self->monsterinfo.nextframe = FRAME_runs09; + // [Paril-KEX] indicate that we should force a refire + self->radius_dmg = 1; + } +} + +void soldierh_hyperripper8(edict_t *self) +{ + if (self->s.skinnum >= 6 && self->count < 4) + soldier_fire(self, 7, true); +} + +mframe_t soldier_frames_attack6[] = { + { ai_run, 10, soldier_start_charge }, + { ai_run, 4, soldier_attack6_shotgun_check }, + { ai_run, 12, soldierh_hyper_laser_sound_start }, + { ai_run, 11, [](edict_t *self) { soldier_fire8(self); monster_footstep(self); } }, + { ai_run, 13, [](edict_t *self ) { soldierh_hyperripper8(self); monster_done_dodge(self); } }, + { ai_run, 18, soldierh_hyperripper8 }, + { ai_run, 15, monster_footstep }, + { ai_run, 14, soldier_attack6_refire1 }, + { ai_run, 11 }, + { ai_run, 8, monster_footstep }, + { ai_run, 11, soldier_cock }, + { ai_run, 12 }, + { ai_run, 12, monster_footstep }, + { ai_run, 17, soldier_attack6_refire2 } +}; +MMOVE_T(soldier_move_attack6) = { FRAME_runs01, FRAME_runs14, soldier_frames_attack6, soldier_run, 0.65f }; + +MONSTERINFO_ATTACK(soldier_attack) (edict_t *self) -> void +{ + float r, chance; + + monster_done_dodge(self); + + // PMM - blindfire! + if (self->monsterinfo.attack_state == AS_BLIND) + { + // setup shot probabilities + if (self->monsterinfo.blind_fire_delay < 1_sec) + chance = 1.0f; + else if (self->monsterinfo.blind_fire_delay < 7.5_sec) + chance = 0.4f; + else + chance = 0.1f; + + r = frandom(); + + // minimum of 4.1 seconds, plus 0-3, after the shots are done + self->monsterinfo.blind_fire_delay += 4.1_sec + random_time(3_sec); + + // don't shoot at the origin + if (!self->monsterinfo.blind_fire_target) + return; + + // don't shoot if the dice say not to + if (r > chance) + return; + + // turn on manual steering to signal both manual steering and blindfire + self->monsterinfo.aiflags |= AI_MANUAL_STEERING; + + // RAFAEL + if (self->style == 1) + M_SetAnimation(self, &soldierh_move_attack1); + else + // RAFAEL + M_SetAnimation(self, &soldier_move_attack1); + self->monsterinfo.attack_finished = level.time + random_time(1.5_sec, 2.5_sec); + return; + } + // pmm + + // PMM - added this so the soldiers now run toward you and shoot instead of just stopping and shooting + r = frandom(); + + // nb: run-shoot not limited by `M_CheckClearShot` since they will be far enough + // away that it doesn't matter + + if ((!(self->monsterinfo.aiflags & (AI_BLOCKED | AI_STAND_GROUND))) && + (r < 0.25f && + (self->count <= 3)) && + (range_to(self, self->enemy) >= (RANGE_NEAR * 0.5f))) + { + M_SetAnimation(self, &soldier_move_attack6); + } + else + { + if (self->count < 4) + { + bool attack1_possible; + + // [Paril-KEX] shotgun guard only uses attack2 at close range + if ((!self->style && self->count >= 2 && self->count <= 3) && range_to(self, self->enemy) <= (RANGE_NEAR * 0.65f)) + attack1_possible = false; + else + attack1_possible = M_CheckClearShot(self, monster_flash_offset[MZ2_SOLDIER_BLASTER_1]); + + bool attack2_possible = M_CheckClearShot(self, monster_flash_offset[MZ2_SOLDIER_BLASTER_2]); + + if (attack1_possible && (!attack2_possible || frandom() < 0.5f)) + { + // RAFAEL + if (self->style == 1) + M_SetAnimation(self, &soldierh_move_attack1); + else + // RAFAEL + M_SetAnimation(self, &soldier_move_attack1); + } + else if (attack2_possible) + { + // RAFAEL + if (self->style == 1) + M_SetAnimation(self, &soldierh_move_attack2); + else + // RAFAEL + M_SetAnimation(self, &soldier_move_attack2); + } + } + else if (M_CheckClearShot(self, monster_flash_offset[MZ2_SOLDIER_MACHINEGUN_4])) + { + M_SetAnimation(self, &soldier_move_attack4); + } + } +} + +// +// SIGHT +// + +MONSTERINFO_SIGHT(soldier_sight) (edict_t *self, edict_t *other) -> void +{ + if (frandom() < 0.5f) + gi.sound(self, CHAN_VOICE, sound_sight1, 1, ATTN_NORM, 0); + else + gi.sound(self, CHAN_VOICE, sound_sight2, 1, ATTN_NORM, 0); + + if (self->enemy && (range_to(self, self->enemy) >= RANGE_NEAR) && + visible(self, self->enemy) // Paril: don't run-shoot if we can't see them + ) + { + // RAFAEL + if (self->style == 1 || frandom() > 0.75f) + // RAFAEL + { + // RAFAEL + legacy bug fix + // don't use run+shoot for machinegun/laser because + // the animation is a bit weird + if (self->count < 4) + M_SetAnimation(self, &soldier_move_attack6); + else if (M_CheckClearShot(self, monster_flash_offset[MZ2_SOLDIER_MACHINEGUN_4])) + // RAFAEL + M_SetAnimation(self, &soldier_move_attack4); + } + } +} + +// +// DUCK +// +mframe_t soldier_frames_duck[] = { + { ai_move, 5, monster_duck_down }, + { ai_move, -1, monster_duck_hold }, + { ai_move, 1 }, + { ai_move, 0, monster_duck_up }, + { ai_move, 5 } +}; +MMOVE_T(soldier_move_duck) = { FRAME_duck01, FRAME_duck05, soldier_frames_duck, soldier_run }; + +extern const mmove_t soldier_move_trip; + +static void soldier_stand_up(edict_t *self) +{ + soldierh_hyper_laser_sound_end(self); + M_SetAnimation(self, &soldier_move_trip, false); + self->monsterinfo.nextframe = FRAME_runt08; +} + +static bool soldier_prone_shoot_ok(edict_t *self) +{ + if (!self->enemy || !self->enemy->inuse) + return false; + + vec3_t fwd; + AngleVectors(self->s.angles, fwd, nullptr, nullptr); + + vec3_t diff = self->enemy->s.origin - self->s.origin; + diff.z = 0; + diff.normalize(); + + float v = fwd.dot(diff); + + if (v < 0.80f) + return false; + + return true; +} + +static void ai_soldier_move(edict_t *self, float dist) +{ + ai_move(self, dist); + + if (!soldier_prone_shoot_ok(self)) + { + soldier_stand_up(self); + return; + } +} + +void soldier_fire5(edict_t *self) +{ + soldier_fire(self, 8, true); +} + +void soldierh_hyperripper5(edict_t *self) +{ + if (self->style && self->count < 4) + soldier_fire(self, 8, true); +} + +mframe_t soldier_frames_attack5[] = { + { ai_move, 18, monster_duck_down }, + { ai_move, 11, monster_footstep }, + { ai_move, 0, monster_footstep }, + { ai_soldier_move }, + { ai_soldier_move, 0, soldierh_hyper_laser_sound_start }, + { ai_soldier_move, 0, soldier_fire5 }, + { ai_soldier_move, 0, soldierh_hyperripper5 }, + { ai_soldier_move, 0, soldierh_hyperripper5 }, +}; +MMOVE_T(soldier_move_attack5) = { FRAME_attak501, FRAME_attak508, soldier_frames_attack5, soldier_stand_up }; + +static void monster_check_prone(edict_t *self) +{ + // we're a shotgun guard waiting to cock + if (!self->style && self->count >= 2 && self->count <= 3 && self->dmg) + return; + + // not going to shoot at this angle + if (!soldier_prone_shoot_ok(self)) + return; + + M_SetAnimation(self, &soldier_move_attack5, false); +} + +mframe_t soldier_frames_trip[] = { + { ai_move, 10 }, + { ai_move, 2, monster_check_prone }, + { ai_move, 18, monster_duck_down }, + { ai_move, 11, monster_footstep }, + { ai_move, 9 }, + { ai_move, -11, monster_footstep }, + { ai_move, -2 }, + { ai_move, 0 }, + { ai_move, 6 }, + { ai_move, -5 }, + { ai_move, 0 }, + { ai_move, 1 }, + { ai_move, 0, monster_footstep }, + { ai_move, 0, monster_duck_up }, + { ai_move, 3 }, + { ai_move, 2, monster_footstep }, + { ai_move, -1 }, + { ai_move, 2 }, + { ai_move, 0 }, +}; +MMOVE_T(soldier_move_trip) = { FRAME_runt01, FRAME_runt19, soldier_frames_trip, soldier_run }; + +// pmm - blocking code + +MONSTERINFO_BLOCKED(soldier_blocked) (edict_t *self, float dist) -> bool +{ + // don't do anything if you're dodging + if ((self->monsterinfo.aiflags & AI_DODGING) || (self->monsterinfo.aiflags & AI_DUCKED)) + return false; + + return blocked_checkplat(self, dist); +} + +// +// DEATH +// + +void soldier_fire6(edict_t *self) +{ + soldier_fire(self, 5, false); + + if (self->dmg) + self->monsterinfo.nextframe = FRAME_death126; +} + +void soldier_fire7(edict_t *self) +{ + soldier_fire(self, 6, false); +} + +void soldier_dead(edict_t *self) +{ + self->mins = { -16, -16, -24 }; + self->maxs = { 16, 16, -8 }; + monster_dead(self); +} + +static void soldier_death_shrink(edict_t *self) +{ + self->svflags |= SVF_DEADMONSTER; + self->maxs[2] = 0; + gi.linkentity(self); +} + +mframe_t soldier_frames_death1[] = { + { ai_move }, + { ai_move, -10 }, + { ai_move, -10 }, + { ai_move, -10, soldier_death_shrink }, + { ai_move, -5 }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + + { ai_move, 0, soldierh_hyper_laser_sound_start }, + { ai_move, 0, soldier_fire6 }, + { ai_move }, + { ai_move }, + { ai_move, 0, soldier_fire7 }, + { ai_move, 0, soldierh_hyper_laser_sound_end }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move } +}; +MMOVE_T(soldier_move_death1) = { FRAME_death101, FRAME_death136, soldier_frames_death1, soldier_dead }; + +mframe_t soldier_frames_death2[] = { + { ai_move, -5 }, + { ai_move, -5 }, + { ai_move, -5 }, + { ai_move, 0, soldier_death_shrink }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move } +}; +MMOVE_T(soldier_move_death2) = { FRAME_death201, FRAME_death235, soldier_frames_death2, soldier_dead }; + +mframe_t soldier_frames_death3[] = { + { ai_move, -5 }, + { ai_move, -5 }, + { ai_move, -5 }, + { ai_move, 0, soldier_death_shrink }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, +}; +MMOVE_T(soldier_move_death3) = { FRAME_death301, FRAME_death345, soldier_frames_death3, soldier_dead }; + +mframe_t soldier_frames_death4[] = { + { ai_move }, + { ai_move }, + { ai_move, 1.5f }, + { ai_move, 2.5f }, + { ai_move, -1.5f }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move, -0.5f }, + { ai_move }, + + { ai_move }, + { ai_move, 4.0f }, + { ai_move, 4.0f }, + { ai_move, 8.0f, soldier_death_shrink }, + { ai_move, 8.0f }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move, 5.5f }, + + { ai_move, 2.5f }, + { ai_move, -2.0f }, + { ai_move, -2.0f } +}; +MMOVE_T(soldier_move_death4) = { FRAME_death401, FRAME_death453, soldier_frames_death4, soldier_dead }; + +mframe_t soldier_frames_death5[] = { + { ai_move, -5 }, + { ai_move, -5 }, + { ai_move, -5 }, + { ai_move }, + { ai_move }, + { ai_move, 0, soldier_death_shrink }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move } +}; +MMOVE_T(soldier_move_death5) = { FRAME_death501, FRAME_death524, soldier_frames_death5, soldier_dead }; + +mframe_t soldier_frames_death6[] = { + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move, 0, soldier_death_shrink }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move } +}; +MMOVE_T(soldier_move_death6) = { FRAME_death601, FRAME_death610, soldier_frames_death6, soldier_dead }; + +DIE(soldier_die) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void +{ + int n; + + soldierh_hyper_laser_sound_end(self); + + // check for gib + if (M_CheckGib(self, mod)) + { + gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0); + + self->s.skinnum /= 2; + + if (self->beam) + { + G_FreeEdict(self->beam); + self->beam = nullptr; + } + + ThrowGibs(self, damage, { + { 3, "models/objects/gibs/sm_meat/tris.md2" }, + { "models/objects/gibs/bone2/tris.md2" }, + { "models/objects/gibs/bone/tris.md2" }, + { "models/monsters/soldier/gibs/arm.md2", GIB_SKINNED }, + { "models/monsters/soldier/gibs/gun.md2", GIB_SKINNED | GIB_UPRIGHT }, + { "models/monsters/soldier/gibs/chest.md2", GIB_SKINNED }, + { "models/monsters/soldier/gibs/head.md2", GIB_HEAD | GIB_SKINNED } + }); + self->deadflag = true; + return; + } + + if (self->deadflag) + return; + + // regular death + self->deadflag = true; + self->takedamage = true; + + n = self->count | 1; + + if (n == 1) + gi.sound(self, CHAN_VOICE, sound_death_light, 1, ATTN_NORM, 0); + else if (n == 3) + gi.sound(self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0); + else // (n == 5) + gi.sound(self, CHAN_VOICE, sound_death_ss, 1, ATTN_NORM, 0); + + if (fabsf((self->s.origin[2] + self->viewheight) - point[2]) <= 4 && + self->velocity.z < 65.f) + { + // head shot + M_SetAnimation(self, &soldier_move_death3); + return; + } + + // if we die while on the ground, do a quicker death4 + if (self->monsterinfo.active_move == &soldier_move_trip || + self->monsterinfo.active_move == &soldier_move_attack5) + { + M_SetAnimation(self, &soldier_move_death4); + self->monsterinfo.nextframe = FRAME_death413; + soldier_death_shrink(self); + return; + } + + // only do the spin-death if we have enough velocity to justify it + if (self->velocity.z > 65.f || self->velocity.length() > 150.f) + n = irandom(5); + else + n = irandom(4); + + if (n == 0) + M_SetAnimation(self, &soldier_move_death1); + else if (n == 1) + M_SetAnimation(self, &soldier_move_death2); + else if (n == 2) + M_SetAnimation(self, &soldier_move_death4); + else if (n == 3) + M_SetAnimation(self, &soldier_move_death5); + else + M_SetAnimation(self, &soldier_move_death6); +} + +// +// NEW DODGE CODE +// + +MONSTERINFO_SIDESTEP(soldier_sidestep) (edict_t *self) -> bool +{ + // don't sidestep during trip or up pain + if (self->monsterinfo.active_move == &soldier_move_trip || + self->monsterinfo.active_move == &soldier_move_attack5 || + self->monsterinfo.active_move == &soldier_move_pain4) + return false; + + if (self->count <= 3) + { + if (self->monsterinfo.active_move != &soldier_move_attack6) + { + M_SetAnimation(self, &soldier_move_attack6); + soldierh_hyper_laser_sound_end(self); + } + } + else + { + if (self->monsterinfo.active_move != &soldier_move_start_run && + self->monsterinfo.active_move != &soldier_move_run) + { + M_SetAnimation(self, &soldier_move_start_run); + soldierh_hyper_laser_sound_end(self); + } + } + + return true; +} + +MONSTERINFO_DUCK(soldier_duck) (edict_t *self, gtime_t eta) -> bool +{ + self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; + + if (self->monsterinfo.active_move == &soldier_move_attack6) + { + M_SetAnimation(self, &soldier_move_trip); + } + else if (self->dmg || brandom()) + { + M_SetAnimation(self, &soldier_move_duck); + } + else + { + M_SetAnimation(self, &soldier_move_attack3); + } + + soldierh_hyper_laser_sound_end(self); + return true; +} + +//========= +// ROGUE +void soldier_blind(edict_t *self); + +mframe_t soldier_frames_blind[] = { + { ai_move, 0, soldier_idle }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move } +}; +MMOVE_T(soldier_move_blind) = { FRAME_stand101, FRAME_stand130, soldier_frames_blind, soldier_blind }; + +MONSTERINFO_STAND(soldier_blind) (edict_t *self) -> void +{ + M_SetAnimation(self, &soldier_move_blind); +} +// ROGUE +//========= + +// +// SPAWN +// + +constexpr spawnflags_t SPAWNFLAG_SOLDIER_BLIND = 8_spawnflag; + +void SP_monster_soldier_x(edict_t *self) +{ + self->s.modelindex = gi.modelindex("models/monsters/soldier/tris.md2"); + self->monsterinfo.scale = MODEL_SCALE; + self->mins = { -16, -16, -24 }; + self->maxs = { 16, 16, 32 }; + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + + sound_idle = gi.soundindex("soldier/solidle1.wav"); + sound_sight1 = gi.soundindex("soldier/solsght1.wav"); + sound_sight2 = gi.soundindex("soldier/solsrch1.wav"); + sound_cock = gi.soundindex("infantry/infatck3.wav"); + + gi.modelindex("models/monsters/soldier/gibs/head.md2"); + gi.modelindex("models/monsters/soldier/gibs/gun.md2"); + gi.modelindex("models/monsters/soldier/gibs/arm.md2"); + gi.modelindex("models/monsters/soldier/gibs/chest.md2"); + + self->mass = 100; + + self->pain = soldier_pain; + self->die = soldier_die; + + self->monsterinfo.stand = soldier_stand; + self->monsterinfo.walk = soldier_walk; + self->monsterinfo.run = soldier_run; + self->monsterinfo.dodge = M_MonsterDodge; + self->monsterinfo.attack = soldier_attack; + self->monsterinfo.melee = nullptr; + self->monsterinfo.sight = soldier_sight; + self->monsterinfo.setskin = soldier_setskin; + + //===== + // ROGUE + self->monsterinfo.blocked = soldier_blocked; + self->monsterinfo.duck = soldier_duck; + self->monsterinfo.unduck = monster_duck_up; + self->monsterinfo.sidestep = soldier_sidestep; + + if (self->spawnflags.has(SPAWNFLAG_SOLDIER_BLIND)) // blind + self->monsterinfo.stand = soldier_blind; + // ROGUE + //===== + + gi.linkentity(self); + + self->monsterinfo.stand(self); + + walkmonster_start(self); +} + +void SP_monster_soldier_vanilla(edict_t *self) +{ + SP_monster_soldier_x(self); +} + +/*QUAKED monster_soldier_light (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight + */ +void SP_monster_soldier_light(edict_t *self) +{ + if ( !M_AllowSpawn( self ) ) { + G_FreeEdict( self ); + return; + } + + SP_monster_soldier_x(self); + + sound_pain_light = gi.soundindex("soldier/solpain2.wav"); + sound_death_light = gi.soundindex("soldier/soldeth2.wav"); + gi.modelindex("models/objects/laser/tris.md2"); + gi.soundindex("misc/lasfly.wav"); + gi.soundindex("soldier/solatck2.wav"); + + self->s.skinnum = 0; + self->count = self->s.skinnum; + self->health = self->max_health = 20 * st.health_multiplier; + self->gib_health = -30; + + // PMM - blindfire + self->monsterinfo.blindfire = true; +} + +/*QUAKED monster_soldier (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight + */ +void SP_monster_soldier(edict_t *self) +{ + if( !M_AllowSpawn( self ) ) { + G_FreeEdict( self ); + return; + } + + SP_monster_soldier_x(self); + + sound_pain = gi.soundindex("soldier/solpain1.wav"); + sound_death = gi.soundindex("soldier/soldeth1.wav"); + gi.soundindex("soldier/solatck1.wav"); + + self->s.skinnum = 2; + self->count = self->s.skinnum; + self->health = self->max_health = 30 * st.health_multiplier; + self->gib_health = -30; +} + +/*QUAKED monster_soldier_ss (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight + */ +void SP_monster_soldier_ss(edict_t *self) +{ + if ( !M_AllowSpawn( self ) ) { + G_FreeEdict( self ); + return; + } + + SP_monster_soldier_x(self); + + sound_pain_ss = gi.soundindex("soldier/solpain3.wav"); + sound_death_ss = gi.soundindex("soldier/soldeth3.wav"); + gi.soundindex("soldier/solatck3.wav"); + + self->s.skinnum = 4; + self->count = self->s.skinnum; + self->health = self->max_health = 40 * st.health_multiplier; + self->gib_health = -30; +} + +// +// SPAWN +// + +void SP_monster_soldier_h(edict_t *self) +{ + SP_monster_soldier_x(self); + self->style = 1; +} + +/*QUAKED monster_soldier_ripper (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight + */ +void SP_monster_soldier_ripper(edict_t *self) +{ + if ( !M_AllowSpawn( self ) ) { + G_FreeEdict( self ); + return; + } + + SP_monster_soldier_h(self); + + sound_pain_light = gi.soundindex("soldier/solpain2.wav"); + sound_death_light = gi.soundindex("soldier/soldeth2.wav"); + + gi.modelindex("models/objects/boomrang/tris.md2"); + gi.soundindex("misc/lasfly.wav"); + gi.soundindex("soldier/solatck2.wav"); + + self->s.skinnum = 6; + self->count = self->s.skinnum - 6; + self->health = self->max_health = 50 * st.health_multiplier; + self->gib_health = -30; + + // PMM - blindfire + self->monsterinfo.blindfire = true; +} + +/*QUAKED monster_soldier_hypergun (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight + */ +void SP_monster_soldier_hypergun(edict_t *self) +{ + if ( !M_AllowSpawn( self ) ) { + G_FreeEdict( self ); + return; + } + + SP_monster_soldier_h(self); + + gi.modelindex("models/objects/laser/tris.md2"); + sound_pain = gi.soundindex("soldier/solpain1.wav"); + sound_death = gi.soundindex("soldier/soldeth1.wav"); + gi.soundindex("soldier/solatck1.wav"); + gi.soundindex("weapons/hyprbd1a.wav"); + gi.soundindex("weapons/hyprbl1a.wav"); + + self->s.skinnum = 8; + self->count = self->s.skinnum - 6; + self->health = self->max_health = 60 * st.health_multiplier; + self->gib_health = -30; + + // PMM - blindfire + self->monsterinfo.blindfire = true; +} + +/*QUAKED monster_soldier_lasergun (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight + */ +void SP_monster_soldier_lasergun(edict_t *self) +{ + if ( !M_AllowSpawn( self ) ) { + G_FreeEdict( self ); + return; + } + + SP_monster_soldier_h(self); + + sound_pain_ss = gi.soundindex("soldier/solpain3.wav"); + sound_death_ss = gi.soundindex("soldier/soldeth3.wav"); + gi.soundindex("soldier/solatck3.wav"); + + self->s.skinnum = 10; + self->count = self->s.skinnum - 6; + self->health = self->max_health = 70 * st.health_multiplier; + self->gib_health = -30; +} + +// END 13-APR-98 diff --git a/rerelease/m_soldier.h b/rerelease/m_soldier.h new file mode 100644 index 0000000..862c32d --- /dev/null +++ b/rerelease/m_soldier.h @@ -0,0 +1,585 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// E:\G Drive\md2f\quake2\baseq2\models/monsters/soldier + +// This file generated by qdata - Do NOT Modify + +enum { + FRAME_attak101, + FRAME_attak102, + FRAME_attak103, + FRAME_attak104, + FRAME_attak105, + FRAME_attak106, + FRAME_attak107, + FRAME_attak108, + FRAME_attak109, + FRAME_attak110, + FRAME_attak111, + FRAME_attak112, + FRAME_attak201, + FRAME_attak202, + FRAME_attak203, + FRAME_attak204, + FRAME_attak205, + FRAME_attak206, + FRAME_attak207, + FRAME_attak208, + FRAME_attak209, + FRAME_attak210, + FRAME_attak211, + FRAME_attak212, + FRAME_attak213, + FRAME_attak214, + FRAME_attak215, + FRAME_attak216, + FRAME_attak217, + FRAME_attak218, + FRAME_attak301, + FRAME_attak302, + FRAME_attak303, + FRAME_attak304, + FRAME_attak305, + FRAME_attak306, + FRAME_attak307, + FRAME_attak308, + FRAME_attak309, + FRAME_attak401, + FRAME_attak402, + FRAME_attak403, + FRAME_attak404, + FRAME_attak405, + FRAME_attak406, + FRAME_duck01, + FRAME_duck02, + FRAME_duck03, + FRAME_duck04, + FRAME_duck05, + FRAME_pain101, + FRAME_pain102, + FRAME_pain103, + FRAME_pain104, + FRAME_pain105, + FRAME_pain201, + FRAME_pain202, + FRAME_pain203, + FRAME_pain204, + FRAME_pain205, + FRAME_pain206, + FRAME_pain207, + FRAME_pain301, + FRAME_pain302, + FRAME_pain303, + FRAME_pain304, + FRAME_pain305, + FRAME_pain306, + FRAME_pain307, + FRAME_pain308, + FRAME_pain309, + FRAME_pain310, + FRAME_pain311, + FRAME_pain312, + FRAME_pain313, + FRAME_pain314, + FRAME_pain315, + FRAME_pain316, + FRAME_pain317, + FRAME_pain318, + FRAME_pain401, + FRAME_pain402, + FRAME_pain403, + FRAME_pain404, + FRAME_pain405, + FRAME_pain406, + FRAME_pain407, + FRAME_pain408, + FRAME_pain409, + FRAME_pain410, + FRAME_pain411, + FRAME_pain412, + FRAME_pain413, + FRAME_pain414, + FRAME_pain415, + FRAME_pain416, + FRAME_pain417, + FRAME_run01, + FRAME_run02, + FRAME_run03, + FRAME_run04, + FRAME_run05, + FRAME_run06, + FRAME_run07, + FRAME_run08, + FRAME_run09, + FRAME_run10, + FRAME_run11, + FRAME_run12, + FRAME_runs01, + FRAME_runs02, + FRAME_runs03, + FRAME_runs04, + FRAME_runs05, + FRAME_runs06, + FRAME_runs07, + FRAME_runs08, + FRAME_runs09, + FRAME_runs10, + FRAME_runs11, + FRAME_runs12, + FRAME_runs13, + FRAME_runs14, + FRAME_runs15, + FRAME_runs16, + FRAME_runs17, + FRAME_runs18, + FRAME_runt01, + FRAME_runt02, + FRAME_runt03, + FRAME_runt04, + FRAME_runt05, + FRAME_runt06, + FRAME_runt07, + FRAME_runt08, + FRAME_runt09, + FRAME_runt10, + FRAME_runt11, + FRAME_runt12, + FRAME_runt13, + FRAME_runt14, + FRAME_runt15, + FRAME_runt16, + FRAME_runt17, + FRAME_runt18, + FRAME_runt19, + FRAME_stand101, + FRAME_stand102, + FRAME_stand103, + FRAME_stand104, + FRAME_stand105, + FRAME_stand106, + FRAME_stand107, + FRAME_stand108, + FRAME_stand109, + FRAME_stand110, + FRAME_stand111, + FRAME_stand112, + FRAME_stand113, + FRAME_stand114, + FRAME_stand115, + FRAME_stand116, + FRAME_stand117, + FRAME_stand118, + FRAME_stand119, + FRAME_stand120, + FRAME_stand121, + FRAME_stand122, + FRAME_stand123, + FRAME_stand124, + FRAME_stand125, + FRAME_stand126, + FRAME_stand127, + FRAME_stand128, + FRAME_stand129, + FRAME_stand130, + FRAME_stand301, + FRAME_stand302, + FRAME_stand303, + FRAME_stand304, + FRAME_stand305, + FRAME_stand306, + FRAME_stand307, + FRAME_stand308, + FRAME_stand309, + FRAME_stand310, + FRAME_stand311, + FRAME_stand312, + FRAME_stand313, + FRAME_stand314, + FRAME_stand315, + FRAME_stand316, + FRAME_stand317, + FRAME_stand318, + FRAME_stand319, + FRAME_stand320, + FRAME_stand321, + FRAME_stand322, + FRAME_stand323, + FRAME_stand324, + FRAME_stand325, + FRAME_stand326, + FRAME_stand327, + FRAME_stand328, + FRAME_stand329, + FRAME_stand330, + FRAME_stand331, + FRAME_stand332, + FRAME_stand333, + FRAME_stand334, + FRAME_stand335, + FRAME_stand336, + FRAME_stand337, + FRAME_stand338, + FRAME_stand339, + FRAME_walk101, + FRAME_walk102, + FRAME_walk103, + FRAME_walk104, + FRAME_walk105, + FRAME_walk106, + FRAME_walk107, + FRAME_walk108, + FRAME_walk109, + FRAME_walk110, + FRAME_walk111, + FRAME_walk112, + FRAME_walk113, + FRAME_walk114, + FRAME_walk115, + FRAME_walk116, + FRAME_walk117, + FRAME_walk118, + FRAME_walk119, + FRAME_walk120, + FRAME_walk121, + FRAME_walk122, + FRAME_walk123, + FRAME_walk124, + FRAME_walk125, + FRAME_walk126, + FRAME_walk127, + FRAME_walk128, + FRAME_walk129, + FRAME_walk130, + FRAME_walk131, + FRAME_walk132, + FRAME_walk133, + FRAME_walk201, + FRAME_walk202, + FRAME_walk203, + FRAME_walk204, + FRAME_walk205, + FRAME_walk206, + FRAME_walk207, + FRAME_walk208, + FRAME_walk209, + FRAME_walk210, + FRAME_walk211, + FRAME_walk212, + FRAME_walk213, + FRAME_walk214, + FRAME_walk215, + FRAME_walk216, + FRAME_walk217, + FRAME_walk218, + FRAME_walk219, + FRAME_walk220, + FRAME_walk221, + FRAME_walk222, + FRAME_walk223, + FRAME_walk224, + FRAME_death101, + FRAME_death102, + FRAME_death103, + FRAME_death104, + FRAME_death105, + FRAME_death106, + FRAME_death107, + FRAME_death108, + FRAME_death109, + FRAME_death110, + FRAME_death111, + FRAME_death112, + FRAME_death113, + FRAME_death114, + FRAME_death115, + FRAME_death116, + FRAME_death117, + FRAME_death118, + FRAME_death119, + FRAME_death120, + FRAME_death121, + FRAME_death122, + FRAME_death123, + FRAME_death124, + FRAME_death125, + FRAME_death126, + FRAME_death127, + FRAME_death128, + FRAME_death129, + FRAME_death130, + FRAME_death131, + FRAME_death132, + FRAME_death133, + FRAME_death134, + FRAME_death135, + FRAME_death136, + FRAME_death201, + FRAME_death202, + FRAME_death203, + FRAME_death204, + FRAME_death205, + FRAME_death206, + FRAME_death207, + FRAME_death208, + FRAME_death209, + FRAME_death210, + FRAME_death211, + FRAME_death212, + FRAME_death213, + FRAME_death214, + FRAME_death215, + FRAME_death216, + FRAME_death217, + FRAME_death218, + FRAME_death219, + FRAME_death220, + FRAME_death221, + FRAME_death222, + FRAME_death223, + FRAME_death224, + FRAME_death225, + FRAME_death226, + FRAME_death227, + FRAME_death228, + FRAME_death229, + FRAME_death230, + FRAME_death231, + FRAME_death232, + FRAME_death233, + FRAME_death234, + FRAME_death235, + FRAME_death301, + FRAME_death302, + FRAME_death303, + FRAME_death304, + FRAME_death305, + FRAME_death306, + FRAME_death307, + FRAME_death308, + FRAME_death309, + FRAME_death310, + FRAME_death311, + FRAME_death312, + FRAME_death313, + FRAME_death314, + FRAME_death315, + FRAME_death316, + FRAME_death317, + FRAME_death318, + FRAME_death319, + FRAME_death320, + FRAME_death321, + FRAME_death322, + FRAME_death323, + FRAME_death324, + FRAME_death325, + FRAME_death326, + FRAME_death327, + FRAME_death328, + FRAME_death329, + FRAME_death330, + FRAME_death331, + FRAME_death332, + FRAME_death333, + FRAME_death334, + FRAME_death335, + FRAME_death336, + FRAME_death337, + FRAME_death338, + FRAME_death339, + FRAME_death340, + FRAME_death341, + FRAME_death342, + FRAME_death343, + FRAME_death344, + FRAME_death345, + FRAME_death401, + FRAME_death402, + FRAME_death403, + FRAME_death404, + FRAME_death405, + FRAME_death406, + FRAME_death407, + FRAME_death408, + FRAME_death409, + FRAME_death410, + FRAME_death411, + FRAME_death412, + FRAME_death413, + FRAME_death414, + FRAME_death415, + FRAME_death416, + FRAME_death417, + FRAME_death418, + FRAME_death419, + FRAME_death420, + FRAME_death421, + FRAME_death422, + FRAME_death423, + FRAME_death424, + FRAME_death425, + FRAME_death426, + FRAME_death427, + FRAME_death428, + FRAME_death429, + FRAME_death430, + FRAME_death431, + FRAME_death432, + FRAME_death433, + FRAME_death434, + FRAME_death435, + FRAME_death436, + FRAME_death437, + FRAME_death438, + FRAME_death439, + FRAME_death440, + FRAME_death441, + FRAME_death442, + FRAME_death443, + FRAME_death444, + FRAME_death445, + FRAME_death446, + FRAME_death447, + FRAME_death448, + FRAME_death449, + FRAME_death450, + FRAME_death451, + FRAME_death452, + FRAME_death453, + FRAME_death501, + FRAME_death502, + FRAME_death503, + FRAME_death504, + FRAME_death505, + FRAME_death506, + FRAME_death507, + FRAME_death508, + FRAME_death509, + FRAME_death510, + FRAME_death511, + FRAME_death512, + FRAME_death513, + FRAME_death514, + FRAME_death515, + FRAME_death516, + FRAME_death517, + FRAME_death518, + FRAME_death519, + FRAME_death520, + FRAME_death521, + FRAME_death522, + FRAME_death523, + FRAME_death524, + FRAME_death601, + FRAME_death602, + FRAME_death603, + FRAME_death604, + FRAME_death605, + FRAME_death606, + FRAME_death607, + FRAME_death608, + FRAME_death609, + FRAME_death610, + FRAME_stand401, + FRAME_stand402, + FRAME_stand403, + FRAME_stand404, + FRAME_stand405, + FRAME_stand406, + FRAME_stand407, + FRAME_stand408, + FRAME_stand409, + FRAME_stand410, + FRAME_stand411, + FRAME_stand412, + FRAME_stand413, + FRAME_stand414, + FRAME_stand415, + FRAME_stand416, + FRAME_stand417, + FRAME_stand418, + FRAME_stand419, + FRAME_stand420, + FRAME_stand421, + FRAME_stand422, + FRAME_stand423, + FRAME_stand424, + FRAME_stand425, + FRAME_stand426, + FRAME_stand427, + FRAME_stand428, + FRAME_stand429, + FRAME_stand430, + FRAME_stand431, + FRAME_stand432, + FRAME_stand433, + FRAME_stand434, + FRAME_stand435, + FRAME_stand436, + FRAME_stand437, + FRAME_stand438, + FRAME_stand439, + FRAME_stand440, + FRAME_stand441, + FRAME_stand442, + FRAME_stand443, + FRAME_stand444, + FRAME_stand445, + FRAME_stand446, + FRAME_stand447, + FRAME_stand448, + FRAME_stand449, + FRAME_stand450, + FRAME_stand451, + FRAME_stand452, + FRAME_stand201, + FRAME_stand202, + FRAME_stand203, + FRAME_stand204, + FRAME_stand205, + FRAME_stand206, + FRAME_stand207, + FRAME_stand208, + FRAME_stand209, + FRAME_stand210, + FRAME_stand211, + FRAME_stand212, + FRAME_stand213, + FRAME_stand214, + FRAME_stand215, + FRAME_stand216, + FRAME_stand217, + FRAME_stand218, + FRAME_stand219, + FRAME_stand220, + FRAME_stand221, + FRAME_stand222, + FRAME_stand223, + FRAME_stand224, + FRAME_stand225, + FRAME_stand226, + FRAME_stand227, + FRAME_stand228, + FRAME_stand229, + FRAME_stand230, + FRAME_stand231, + FRAME_stand232, + FRAME_stand233, + FRAME_stand234, + FRAME_stand235, + FRAME_stand236, + FRAME_stand237, + FRAME_stand238, + FRAME_stand239, + FRAME_stand240, + FRAME_attak501, + FRAME_attak502, + FRAME_attak503, + FRAME_attak504, + FRAME_attak505, + FRAME_attak506, + FRAME_attak507, + FRAME_attak508 +}; + +constexpr float MODEL_SCALE = 1.2f; diff --git a/rerelease/m_supertank.cpp b/rerelease/m_supertank.cpp new file mode 100644 index 0000000..e01a90a --- /dev/null +++ b/rerelease/m_supertank.cpp @@ -0,0 +1,731 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +/* +============================================================================== + +SUPERTANK + +============================================================================== +*/ + +#include "g_local.h" +#include "m_supertank.h" +#include "m_flash.h" + +constexpr spawnflags_t SPAWNFLAG_SUPERTANK_POWERSHIELD = 8_spawnflag; +// n64 +constexpr spawnflags_t SPAWNFLAG_SUPERTANK_LONG_DEATH = 16_spawnflag; + +static int sound_pain1; +static int sound_pain2; +static int sound_pain3; +static int sound_death; +static int sound_search1; +static int sound_search2; + +static int tread_sound; + +void TreadSound(edict_t *self) +{ + gi.sound(self, CHAN_BODY, tread_sound, 1, ATTN_NORM, 0); +} + +MONSTERINFO_SEARCH(supertank_search) (edict_t *self) -> void +{ + if (frandom() < 0.5f) + gi.sound(self, CHAN_VOICE, sound_search1, 1, ATTN_NORM, 0); + else + gi.sound(self, CHAN_VOICE, sound_search2, 1, ATTN_NORM, 0); +} + +void supertank_dead(edict_t *self); +void supertankRocket(edict_t *self); +void supertankMachineGun(edict_t *self); +void supertank_reattack1(edict_t *self); + +// +// stand +// + +mframe_t supertank_frames_stand[] = { + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand } +}; +MMOVE_T(supertank_move_stand) = { FRAME_stand_1, FRAME_stand_60, supertank_frames_stand, nullptr }; + +MONSTERINFO_STAND(supertank_stand) (edict_t *self) -> void +{ + M_SetAnimation(self, &supertank_move_stand); +} + +mframe_t supertank_frames_run[] = { + { ai_run, 12, TreadSound }, + { ai_run, 12 }, + { ai_run, 12 }, + { ai_run, 12 }, + { ai_run, 12 }, + { ai_run, 12 }, + { ai_run, 12 }, + { ai_run, 12 }, + { ai_run, 12 }, + { ai_run, 12 }, + { ai_run, 12 }, + { ai_run, 12 }, + { ai_run, 12 }, + { ai_run, 12 }, + { ai_run, 12 }, + { ai_run, 12 }, + { ai_run, 12 }, + { ai_run, 12 } +}; +MMOVE_T(supertank_move_run) = { FRAME_forwrd_1, FRAME_forwrd_18, supertank_frames_run, nullptr }; + +// +// walk +// + +mframe_t supertank_frames_forward[] = { + { ai_walk, 4, TreadSound }, + { ai_walk, 4 }, + { ai_walk, 4 }, + { ai_walk, 4 }, + { ai_walk, 4 }, + { ai_walk, 4 }, + { ai_walk, 4 }, + { ai_walk, 4 }, + { ai_walk, 4 }, + { ai_walk, 4 }, + { ai_walk, 4 }, + { ai_walk, 4 }, + { ai_walk, 4 }, + { ai_walk, 4 }, + { ai_walk, 4 }, + { ai_walk, 4 }, + { ai_walk, 4 }, + { ai_walk, 4 } +}; +MMOVE_T(supertank_move_forward) = { FRAME_forwrd_1, FRAME_forwrd_18, supertank_frames_forward, nullptr }; + +void supertank_forward(edict_t *self) +{ + M_SetAnimation(self, &supertank_move_forward); +} + +MONSTERINFO_WALK(supertank_walk) (edict_t *self) -> void +{ + M_SetAnimation(self, &supertank_move_forward); +} + +MONSTERINFO_RUN(supertank_run) (edict_t *self) -> void +{ + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + M_SetAnimation(self, &supertank_move_stand); + else + M_SetAnimation(self, &supertank_move_run); +} + +#if 0 +mframe_t supertank_frames_turn_right[] = { + { ai_move, 0, TreadSound }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move } +}; +MMOVE_T(supertank_move_turn_right) = { FRAME_right_1, FRAME_right_18, supertank_frames_turn_right, supertank_run }; + +mframe_t supertank_frames_turn_left[] = { + { ai_move, 0, TreadSound }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move } +}; +MMOVE_T(supertank_move_turn_left) = { FRAME_left_1, FRAME_left_18, supertank_frames_turn_left, supertank_run }; +#endif + +mframe_t supertank_frames_pain3[] = { + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move } +}; +MMOVE_T(supertank_move_pain3) = { FRAME_pain3_9, FRAME_pain3_12, supertank_frames_pain3, supertank_run }; + +mframe_t supertank_frames_pain2[] = { + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move } +}; +MMOVE_T(supertank_move_pain2) = { FRAME_pain2_5, FRAME_pain2_8, supertank_frames_pain2, supertank_run }; + +mframe_t supertank_frames_pain1[] = { + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move } +}; +MMOVE_T(supertank_move_pain1) = { FRAME_pain1_1, FRAME_pain1_4, supertank_frames_pain1, supertank_run }; + +static void BossLoop(edict_t *self) +{ + if (!(self->spawnflags & SPAWNFLAG_SUPERTANK_LONG_DEATH)) + return; + + if (self->count) + self->count--; + else + self->spawnflags &= ~SPAWNFLAG_SUPERTANK_LONG_DEATH; + + self->monsterinfo.nextframe = FRAME_death_19; +} + +static void supertankGrenade(edict_t *self) +{ + vec3_t forward, right; + vec3_t start; + monster_muzzleflash_id_t flash_number; + + if (!self->enemy || !self->enemy->inuse) // PGM + return; // PGM + + if (self->s.frame == FRAME_attak4_1) + flash_number = MZ2_SUPERTANK_GRENADE_1; + else + flash_number = MZ2_SUPERTANK_GRENADE_2; + + AngleVectors(self->s.angles, forward, right, nullptr); + start = M_ProjectFlashSource(self, monster_flash_offset[flash_number], forward, right); + + vec3_t aim_point; + PredictAim(self, self->enemy, start, 0, false, crandom_open() * 0.1f, &forward, &aim_point); + + for (float speed = 500.f; speed < 1000.f; speed += 100.f) + { + if (!M_CalculatePitchToFire(self, aim_point, start, forward, speed, 2.5f, true)) + continue; + + monster_fire_grenade(self, start, forward, 50, speed, flash_number, 0.f, 0.f); + break; + } +} + +mframe_t supertank_frames_death1[] = { + { ai_move, 0, BossExplode }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move, 0, BossLoop } +}; +MMOVE_T(supertank_move_death) = { FRAME_death_1, FRAME_death_24, supertank_frames_death1, supertank_dead }; + +mframe_t supertank_frames_attack4[] = { + { ai_move, 0, supertankGrenade }, + { ai_move }, + { ai_move }, + { ai_move, 0, supertankGrenade }, + { ai_move }, + { ai_move } +}; +MMOVE_T(supertank_move_attack4) = { FRAME_attak4_1, FRAME_attak4_6, supertank_frames_attack4, supertank_run }; + +mframe_t supertank_frames_attack2[] = { + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge, 0, supertankRocket }, + { ai_charge }, + { ai_charge }, + { ai_charge, 0, supertankRocket }, + { ai_charge }, + { ai_charge }, + { ai_charge, 0, supertankRocket }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move } +}; +MMOVE_T(supertank_move_attack2) = { FRAME_attak2_1, FRAME_attak2_27, supertank_frames_attack2, supertank_run }; + +mframe_t supertank_frames_attack1[] = { + { ai_charge, 0, supertankMachineGun }, + { ai_charge, 0, supertankMachineGun }, + { ai_charge, 0, supertankMachineGun }, + { ai_charge, 0, supertankMachineGun }, + { ai_charge, 0, supertankMachineGun }, + { ai_charge, 0, supertankMachineGun }, +}; +MMOVE_T(supertank_move_attack1) = { FRAME_attak1_1, FRAME_attak1_6, supertank_frames_attack1, supertank_reattack1 }; + +mframe_t supertank_frames_end_attack1[] = { + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move } +}; +MMOVE_T(supertank_move_end_attack1) = { FRAME_attak1_7, FRAME_attak1_20, supertank_frames_end_attack1, supertank_run }; + +void supertank_reattack1(edict_t *self) +{ + if (visible(self, self->enemy)) + { + if (self->timestamp >= level.time || frandom() < 0.3f) + M_SetAnimation(self, &supertank_move_attack1); + else + M_SetAnimation(self, &supertank_move_end_attack1); + } + else + M_SetAnimation(self, &supertank_move_end_attack1); +} + +PAIN(supertank_pain) (edict_t *self, edict_t *other, float kick, int damage, const mod_t &mod) -> void +{ + if (level.time < self->pain_debounce_time) + return; + + // Lessen the chance of him going into his pain frames + if (mod.id != MOD_CHAINFIST) + { + if (damage <= 25) + if (frandom() < 0.2f) + return; + + // Don't go into pain if he's firing his rockets + if ((self->s.frame >= FRAME_attak2_1) && (self->s.frame <= FRAME_attak2_14)) + return; + } + + if (damage <= 10) + gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); + else if (damage <= 25) + gi.sound(self, CHAN_VOICE, sound_pain3, 1, ATTN_NORM, 0); + else + gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0); + + self->pain_debounce_time = level.time + 3_sec; + + if (!M_ShouldReactToPain(self, mod)) + return; // no pain anims in nightmare + + if (damage <= 10) + M_SetAnimation(self, &supertank_move_pain1); + else if (damage <= 25) + M_SetAnimation(self, &supertank_move_pain2); + else + M_SetAnimation(self, &supertank_move_pain3); +} + +MONSTERINFO_SETSKIN(supertank_setskin) (edict_t *self) -> void +{ + if (self->health < (self->max_health / 2)) + self->s.skinnum |= 1; + else + self->s.skinnum &= ~1; +} + +void supertankRocket(edict_t *self) +{ + vec3_t forward, right; + vec3_t start; + vec3_t dir; + vec3_t vec; + monster_muzzleflash_id_t flash_number; + + if (!self->enemy || !self->enemy->inuse) // PGM + return; // PGM + + if (self->s.frame == FRAME_attak2_8) + flash_number = MZ2_SUPERTANK_ROCKET_1; + else if (self->s.frame == FRAME_attak2_11) + flash_number = MZ2_SUPERTANK_ROCKET_2; + else // (self->s.frame == FRAME_attak2_14) + flash_number = MZ2_SUPERTANK_ROCKET_3; + + AngleVectors(self->s.angles, forward, right, nullptr); + start = M_ProjectFlashSource(self, monster_flash_offset[flash_number], forward, right); + + if (self->spawnflags.has(SPAWNFLAG_SUPERTANK_POWERSHIELD)) + { + vec = self->enemy->s.origin; + vec[2] += self->enemy->viewheight; + dir = vec - start; + dir.normalize(); + monster_fire_heat(self, start, dir, 40, 500, flash_number, 0.075f); + } + else + { + PredictAim(self, self->enemy, start, 750, false, 0.f, &forward, nullptr); + monster_fire_rocket(self, start, forward, 50, 750, flash_number); + } +} + +void supertankMachineGun(edict_t *self) +{ + vec3_t dir; + vec3_t start; + vec3_t forward, right; + monster_muzzleflash_id_t flash_number; + + if (!self->enemy || !self->enemy->inuse) // PGM + return; // PGM + + flash_number = static_cast(MZ2_SUPERTANK_MACHINEGUN_1 + (self->s.frame - FRAME_attak1_1)); + + dir[0] = 0; + dir[1] = self->s.angles[1]; + dir[2] = 0; + + AngleVectors(dir, forward, right, nullptr); + start = M_ProjectFlashSource(self, monster_flash_offset[flash_number], forward, right); + PredictAim(self, self->enemy, start, 0, true, -0.1f, &forward, nullptr); + monster_fire_bullet(self, start, forward, 6, 4, DEFAULT_BULLET_HSPREAD * 3, DEFAULT_BULLET_VSPREAD * 3, flash_number); +} + +MONSTERINFO_ATTACK(supertank_attack) (edict_t *self) -> void +{ + vec3_t vec; + float range; + + vec = self->enemy->s.origin - self->s.origin; + range = range_to(self, self->enemy); + + // Attack 1 == Chaingun + // Attack 2 == Rocket Launcher + // Attack 3 == Grenade Launcher + bool chaingun_good = M_CheckClearShot(self, monster_flash_offset[MZ2_SUPERTANK_MACHINEGUN_1]); + bool rocket_good = M_CheckClearShot(self, monster_flash_offset[MZ2_SUPERTANK_ROCKET_1]); + bool grenade_good = M_CheckClearShot(self, monster_flash_offset[MZ2_SUPERTANK_GRENADE_1]); + + // fire rockets more often at distance + if (chaingun_good && (!rocket_good || range <= 540 || frandom() < 0.3f)) + { + // prefer grenade if the enemy is above us + if (grenade_good && (range >= 350 || vec.z > 120.f || frandom() < 0.2f)) + M_SetAnimation(self, &supertank_move_attack4); + else + { + M_SetAnimation(self, &supertank_move_attack1); + self->timestamp = level.time + random_time(1500_ms, 2700_ms); + } + } + else if (rocket_good) + { + // prefer grenade if the enemy is above us + if (grenade_good && (vec.z > 120.f || frandom() < 0.2f)) + M_SetAnimation(self, &supertank_move_attack4); + else + M_SetAnimation(self, &supertank_move_attack2); + } + else if (grenade_good) + M_SetAnimation(self, &supertank_move_attack4); +} + +// +// death +// + +static void supertank_gib(edict_t *self) +{ + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_EXPLOSION1_BIG); + gi.WritePosition(self->s.origin); + gi.multicast(self->s.origin, MULTICAST_PHS, false); + + self->s.sound = 0; + self->s.skinnum /= 2; + + ThrowGibs(self, 500, { + { 2, "models/objects/gibs/sm_meat/tris.md2" }, + { 2, "models/objects/gibs/sm_metal/tris.md2", GIB_METALLIC }, + { "models/monsters/boss1/gibs/cgun.md2", GIB_SKINNED | GIB_METALLIC }, + { "models/monsters/boss1/gibs/chest.md2", GIB_SKINNED }, + { "models/monsters/boss1/gibs/core.md2", GIB_SKINNED }, + { "models/monsters/boss1/gibs/ltread.md2", GIB_SKINNED | GIB_UPRIGHT }, + { "models/monsters/boss1/gibs/rgun.md2", GIB_SKINNED | GIB_UPRIGHT }, + { "models/monsters/boss1/gibs/rtread.md2", GIB_SKINNED | GIB_UPRIGHT }, + { "models/monsters/boss1/gibs/tube.md2", GIB_SKINNED | GIB_UPRIGHT }, + { "models/monsters/boss1/gibs/head.md2", GIB_SKINNED | GIB_METALLIC | GIB_HEAD } + }); +} + +void supertank_dead(edict_t *self) +{ + // no blowy on deady + if (self->spawnflags.has(SPAWNFLAG_MONSTER_DEAD)) + { + self->deadflag = false; + self->takedamage = true; + return; + } + + supertank_gib(self); +} + +DIE(supertank_die) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void +{ + if (self->spawnflags.has(SPAWNFLAG_MONSTER_DEAD)) + { + // check for gib + if (M_CheckGib(self, mod)) + { + supertank_gib(self); + self->deadflag = true; + return; + } + + if (self->deadflag) + return; + } + else + { + gi.sound(self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0); + self->deadflag = true; + self->takedamage = false; + } + + M_SetAnimation(self, &supertank_move_death); +} + +//=========== +// PGM +MONSTERINFO_BLOCKED(supertank_blocked) (edict_t *self, float dist) -> bool +{ + if (blocked_checkplat(self, dist)) + return true; + + return false; +} +// PGM +//=========== + +// +// monster_supertank +// + +// RAFAEL (Powershield) + +/*QUAKED monster_supertank (1 .5 0) (-64 -64 0) (64 64 72) Ambush Trigger_Spawn Sight Powershield LongDeath + */ +void SP_monster_supertank(edict_t *self) +{ + if ( !M_AllowSpawn( self ) ) { + G_FreeEdict( self ); + return; + } + + sound_pain1 = gi.soundindex("bosstank/btkpain1.wav"); + sound_pain2 = gi.soundindex("bosstank/btkpain2.wav"); + sound_pain3 = gi.soundindex("bosstank/btkpain3.wav"); + sound_death = gi.soundindex("bosstank/btkdeth1.wav"); + sound_search1 = gi.soundindex("bosstank/btkunqv1.wav"); + sound_search2 = gi.soundindex("bosstank/btkunqv2.wav"); + + tread_sound = gi.soundindex("bosstank/btkengn1.wav"); + + gi.soundindex("gunner/gunatck3.wav"); + gi.soundindex("infantry/infatck1.wav"); + gi.soundindex("tank/rocket.wav"); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex("models/monsters/boss1/tris.md2"); + + gi.modelindex("models/monsters/boss1/gibs/cgun.md2"); + gi.modelindex("models/monsters/boss1/gibs/chest.md2"); + gi.modelindex("models/monsters/boss1/gibs/core.md2"); + gi.modelindex("models/monsters/boss1/gibs/head.md2"); + gi.modelindex("models/monsters/boss1/gibs/ltread.md2"); + gi.modelindex("models/monsters/boss1/gibs/rgun.md2"); + gi.modelindex("models/monsters/boss1/gibs/rtread.md2"); + gi.modelindex("models/monsters/boss1/gibs/tube.md2"); + + self->mins = { -64, -64, 0 }; + self->maxs = { 64, 64, 112 }; + + self->health = 1500 * st.health_multiplier; + self->gib_health = -500; + self->mass = 800; + + self->pain = supertank_pain; + self->die = supertank_die; + self->monsterinfo.stand = supertank_stand; + self->monsterinfo.walk = supertank_walk; + self->monsterinfo.run = supertank_run; + self->monsterinfo.dodge = nullptr; + self->monsterinfo.attack = supertank_attack; + self->monsterinfo.search = supertank_search; + self->monsterinfo.melee = nullptr; + self->monsterinfo.sight = nullptr; + self->monsterinfo.blocked = supertank_blocked; // PGM + self->monsterinfo.setskin = supertank_setskin; + + gi.linkentity(self); + + M_SetAnimation(self, &supertank_move_stand); + self->monsterinfo.scale = MODEL_SCALE; + + // RAFAEL + if (self->spawnflags.has(SPAWNFLAG_SUPERTANK_POWERSHIELD)) + { + if (!st.was_key_specified("power_armor_type")) + self->monsterinfo.power_armor_type = IT_ITEM_POWER_SHIELD; + if (!st.was_key_specified("power_armor_power")) + self->monsterinfo.power_armor_power = 400; + } + // RAFAEL + + walkmonster_start(self); + + // PMM + self->monsterinfo.aiflags |= AI_IGNORE_SHOTS; + // pmm + + // TODO + if (level.is_n64) + { + self->spawnflags |= SPAWNFLAG_SUPERTANK_LONG_DEATH; + self->count = 10; + } +} + +// +// monster_boss5 +// RAFAEL +// + +/*QUAKED monster_boss5 (1 .5 0) (-64 -64 0) (64 64 72) Ambush Trigger_Spawn Sight + */ +void SP_monster_boss5(edict_t *self) +{ + self->spawnflags |= SPAWNFLAG_SUPERTANK_POWERSHIELD; + SP_monster_supertank(self); + gi.soundindex("weapons/railgr1a.wav"); + self->s.skinnum = 2; +} \ No newline at end of file diff --git a/rerelease/m_supertank.h b/rerelease/m_supertank.h new file mode 100644 index 0000000..c4eb34a --- /dev/null +++ b/rerelease/m_supertank.h @@ -0,0 +1,265 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// G:\quake2\baseq2\models/monsters/boss1/backup + +// This file generated by ModelGen - Do NOT Modify + +enum +{ + FRAME_attak1_1, + FRAME_attak1_2, + FRAME_attak1_3, + FRAME_attak1_4, + FRAME_attak1_5, + FRAME_attak1_6, + FRAME_attak1_7, + FRAME_attak1_8, + FRAME_attak1_9, + FRAME_attak1_10, + FRAME_attak1_11, + FRAME_attak1_12, + FRAME_attak1_13, + FRAME_attak1_14, + FRAME_attak1_15, + FRAME_attak1_16, + FRAME_attak1_17, + FRAME_attak1_18, + FRAME_attak1_19, + FRAME_attak1_20, + FRAME_attak2_1, + FRAME_attak2_2, + FRAME_attak2_3, + FRAME_attak2_4, + FRAME_attak2_5, + FRAME_attak2_6, + FRAME_attak2_7, + FRAME_attak2_8, + FRAME_attak2_9, + FRAME_attak2_10, + FRAME_attak2_11, + FRAME_attak2_12, + FRAME_attak2_13, + FRAME_attak2_14, + FRAME_attak2_15, + FRAME_attak2_16, + FRAME_attak2_17, + FRAME_attak2_18, + FRAME_attak2_19, + FRAME_attak2_20, + FRAME_attak2_21, + FRAME_attak2_22, + FRAME_attak2_23, + FRAME_attak2_24, + FRAME_attak2_25, + FRAME_attak2_26, + FRAME_attak2_27, + FRAME_attak3_1, + FRAME_attak3_2, + FRAME_attak3_3, + FRAME_attak3_4, + FRAME_attak3_5, + FRAME_attak3_6, + FRAME_attak3_7, + FRAME_attak3_8, + FRAME_attak3_9, + FRAME_attak3_10, + FRAME_attak3_11, + FRAME_attak3_12, + FRAME_attak3_13, + FRAME_attak3_14, + FRAME_attak3_15, + FRAME_attak3_16, + FRAME_attak3_17, + FRAME_attak3_18, + FRAME_attak3_19, + FRAME_attak3_20, + FRAME_attak3_21, + FRAME_attak3_22, + FRAME_attak3_23, + FRAME_attak3_24, + FRAME_attak3_25, + FRAME_attak3_26, + FRAME_attak3_27, + FRAME_attak4_1, + FRAME_attak4_2, + FRAME_attak4_3, + FRAME_attak4_4, + FRAME_attak4_5, + FRAME_attak4_6, + FRAME_backwd_1, + FRAME_backwd_2, + FRAME_backwd_3, + FRAME_backwd_4, + FRAME_backwd_5, + FRAME_backwd_6, + FRAME_backwd_7, + FRAME_backwd_8, + FRAME_backwd_9, + FRAME_backwd_10, + FRAME_backwd_11, + FRAME_backwd_12, + FRAME_backwd_13, + FRAME_backwd_14, + FRAME_backwd_15, + FRAME_backwd_16, + FRAME_backwd_17, + FRAME_backwd_18, + FRAME_death_1, + FRAME_death_2, + FRAME_death_3, + FRAME_death_4, + FRAME_death_5, + FRAME_death_6, + FRAME_death_7, + FRAME_death_8, + FRAME_death_9, + FRAME_death_10, + FRAME_death_11, + FRAME_death_12, + FRAME_death_13, + FRAME_death_14, + FRAME_death_15, + FRAME_death_16, + FRAME_death_17, + FRAME_death_18, + FRAME_death_19, + FRAME_death_20, + FRAME_death_21, + FRAME_death_22, + FRAME_death_23, + FRAME_death_24, + FRAME_death_31, + FRAME_death_32, + FRAME_death_33, + FRAME_death_45, + FRAME_death_46, + FRAME_death_47, + FRAME_forwrd_1, + FRAME_forwrd_2, + FRAME_forwrd_3, + FRAME_forwrd_4, + FRAME_forwrd_5, + FRAME_forwrd_6, + FRAME_forwrd_7, + FRAME_forwrd_8, + FRAME_forwrd_9, + FRAME_forwrd_10, + FRAME_forwrd_11, + FRAME_forwrd_12, + FRAME_forwrd_13, + FRAME_forwrd_14, + FRAME_forwrd_15, + FRAME_forwrd_16, + FRAME_forwrd_17, + FRAME_forwrd_18, + FRAME_left_1, + FRAME_left_2, + FRAME_left_3, + FRAME_left_4, + FRAME_left_5, + FRAME_left_6, + FRAME_left_7, + FRAME_left_8, + FRAME_left_9, + FRAME_left_10, + FRAME_left_11, + FRAME_left_12, + FRAME_left_13, + FRAME_left_14, + FRAME_left_15, + FRAME_left_16, + FRAME_left_17, + FRAME_left_18, + FRAME_pain1_1, + FRAME_pain1_2, + FRAME_pain1_3, + FRAME_pain1_4, + FRAME_pain2_5, + FRAME_pain2_6, + FRAME_pain2_7, + FRAME_pain2_8, + FRAME_pain3_9, + FRAME_pain3_10, + FRAME_pain3_11, + FRAME_pain3_12, + FRAME_right_1, + FRAME_right_2, + FRAME_right_3, + FRAME_right_4, + FRAME_right_5, + FRAME_right_6, + FRAME_right_7, + FRAME_right_8, + FRAME_right_9, + FRAME_right_10, + FRAME_right_11, + FRAME_right_12, + FRAME_right_13, + FRAME_right_14, + FRAME_right_15, + FRAME_right_16, + FRAME_right_17, + FRAME_right_18, + FRAME_stand_1, + FRAME_stand_2, + FRAME_stand_3, + FRAME_stand_4, + FRAME_stand_5, + FRAME_stand_6, + FRAME_stand_7, + FRAME_stand_8, + FRAME_stand_9, + FRAME_stand_10, + FRAME_stand_11, + FRAME_stand_12, + FRAME_stand_13, + FRAME_stand_14, + FRAME_stand_15, + FRAME_stand_16, + FRAME_stand_17, + FRAME_stand_18, + FRAME_stand_19, + FRAME_stand_20, + FRAME_stand_21, + FRAME_stand_22, + FRAME_stand_23, + FRAME_stand_24, + FRAME_stand_25, + FRAME_stand_26, + FRAME_stand_27, + FRAME_stand_28, + FRAME_stand_29, + FRAME_stand_30, + FRAME_stand_31, + FRAME_stand_32, + FRAME_stand_33, + FRAME_stand_34, + FRAME_stand_35, + FRAME_stand_36, + FRAME_stand_37, + FRAME_stand_38, + FRAME_stand_39, + FRAME_stand_40, + FRAME_stand_41, + FRAME_stand_42, + FRAME_stand_43, + FRAME_stand_44, + FRAME_stand_45, + FRAME_stand_46, + FRAME_stand_47, + FRAME_stand_48, + FRAME_stand_49, + FRAME_stand_50, + FRAME_stand_51, + FRAME_stand_52, + FRAME_stand_53, + FRAME_stand_54, + FRAME_stand_55, + FRAME_stand_56, + FRAME_stand_57, + FRAME_stand_58, + FRAME_stand_59, + FRAME_stand_60 +}; + +constexpr float MODEL_SCALE = 1.000000f; diff --git a/rerelease/m_tank.cpp b/rerelease/m_tank.cpp new file mode 100644 index 0000000..95507f1 --- /dev/null +++ b/rerelease/m_tank.cpp @@ -0,0 +1,1162 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +/* +============================================================================== + +TANK + +============================================================================== +*/ + +#include "g_local.h" +#include "m_tank.h" +#include "m_flash.h" + +void tank_refire_rocket(edict_t *self); +void tank_doattack_rocket(edict_t *self); +void tank_reattack_blaster(edict_t *self); + +static int sound_thud; +static int sound_pain, sound_pain2; +static int sound_idle; +static int sound_die; +static int sound_step; +static int sound_sight; +static int sound_windup; +static int sound_strike; + +constexpr spawnflags_t SPAWNFLAG_TANK_COMMANDER_GUARDIAN = 8_spawnflag; +constexpr spawnflags_t SPAWNFLAG_TANK_COMMANDER_HEAT_SEEKING = 16_spawnflag; + +// +// misc +// + +MONSTERINFO_SIGHT(tank_sight) (edict_t *self, edict_t *other) -> void +{ + gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); +} + +void tank_footstep(edict_t *self) +{ + gi.sound(self, CHAN_BODY, sound_step, 1, ATTN_NORM, 0); +} + +void tank_thud(edict_t *self) +{ + gi.sound(self, CHAN_BODY, sound_thud, 1, ATTN_NORM, 0); +} + +void tank_windup(edict_t *self) +{ + gi.sound(self, CHAN_WEAPON, sound_windup, 1, ATTN_NORM, 0); +} + +MONSTERINFO_IDLE(tank_idle) (edict_t *self) -> void +{ + gi.sound(self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0); +} + +// +// stand +// + +mframe_t tank_frames_stand[] = { + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand } +}; +MMOVE_T(tank_move_stand) = { FRAME_stand01, FRAME_stand30, tank_frames_stand, nullptr }; + +MONSTERINFO_STAND(tank_stand) (edict_t *self) -> void +{ + M_SetAnimation(self, &tank_move_stand); +} + +// +// walk +// + +void tank_walk(edict_t *self); + +#if 0 +mframe_t tank_frames_start_walk[] = { + { ai_walk }, + { ai_walk, 6 }, + { ai_walk, 6 }, + { ai_walk, 11, tank_footstep } +}; +MMOVE_T(tank_move_start_walk) = { FRAME_walk01, FRAME_walk04, tank_frames_start_walk, tank_walk }; +#endif + +mframe_t tank_frames_walk[] = { + { ai_walk, 4 }, + { ai_walk, 5 }, + { ai_walk, 3 }, + { ai_walk, 2 }, + { ai_walk, 5 }, + { ai_walk, 5 }, + { ai_walk, 4 }, + { ai_walk, 4, tank_footstep }, + { ai_walk, 3 }, + { ai_walk, 5 }, + { ai_walk, 4 }, + { ai_walk, 5 }, + { ai_walk, 7 }, + { ai_walk, 7 }, + { ai_walk, 6 }, + { ai_walk, 6, tank_footstep } +}; +MMOVE_T(tank_move_walk) = { FRAME_walk05, FRAME_walk20, tank_frames_walk, nullptr }; + +#if 0 +mframe_t tank_frames_stop_walk[] = { + { ai_walk, 3 }, + { ai_walk, 3 }, + { ai_walk, 2 }, + { ai_walk, 2 }, + { ai_walk, 4, tank_footstep } +}; +MMOVE_T(tank_move_stop_walk) = { FRAME_walk21, FRAME_walk25, tank_frames_stop_walk, tank_stand }; +#endif + +MONSTERINFO_WALK(tank_walk) (edict_t *self) -> void +{ + M_SetAnimation(self, &tank_move_walk); +} + +// +// run +// + +void tank_run(edict_t *self); + +mframe_t tank_frames_start_run[] = { + { ai_run }, + { ai_run, 6 }, + { ai_run, 6 }, + { ai_run, 11, tank_footstep } +}; +MMOVE_T(tank_move_start_run) = { FRAME_walk01, FRAME_walk04, tank_frames_start_run, tank_run }; + +mframe_t tank_frames_run[] = { + { ai_run, 4 }, + { ai_run, 5 }, + { ai_run, 3 }, + { ai_run, 2 }, + { ai_run, 5 }, + { ai_run, 5 }, + { ai_run, 4 }, + { ai_run, 4, tank_footstep }, + { ai_run, 3 }, + { ai_run, 5 }, + { ai_run, 4 }, + { ai_run, 5 }, + { ai_run, 7 }, + { ai_run, 7 }, + { ai_run, 6 }, + { ai_run, 6, tank_footstep } +}; +MMOVE_T(tank_move_run) = { FRAME_walk05, FRAME_walk20, tank_frames_run, nullptr }; + +#if 0 +mframe_t tank_frames_stop_run[] = { + { ai_run, 3 }, + { ai_run, 3 }, + { ai_run, 2 }, + { ai_run, 2 }, + { ai_run, 4, tank_footstep } +}; +MMOVE_T(tank_move_stop_run) = { FRAME_walk21, FRAME_walk25, tank_frames_stop_run, tank_walk }; +#endif + +MONSTERINFO_RUN(tank_run) (edict_t *self) -> void +{ + if (self->enemy && self->enemy->client) + self->monsterinfo.aiflags |= AI_BRUTAL; + else + self->monsterinfo.aiflags &= ~AI_BRUTAL; + + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + { + M_SetAnimation(self, &tank_move_stand); + return; + } + + if (self->monsterinfo.active_move == &tank_move_walk || + self->monsterinfo.active_move == &tank_move_start_run) + { + M_SetAnimation(self, &tank_move_run); + } + else + { + M_SetAnimation(self, &tank_move_start_run); + } +} + +// +// pain +// + +mframe_t tank_frames_pain1[] = { + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move } +}; +MMOVE_T(tank_move_pain1) = { FRAME_pain101, FRAME_pain104, tank_frames_pain1, tank_run }; + +mframe_t tank_frames_pain2[] = { + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move } +}; +MMOVE_T(tank_move_pain2) = { FRAME_pain201, FRAME_pain205, tank_frames_pain2, tank_run }; + +mframe_t tank_frames_pain3[] = { + { ai_move, -7 }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move, 2 }, + { ai_move }, + { ai_move }, + { ai_move, 3 }, + { ai_move }, + { ai_move, 2 }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move, 0, tank_footstep } +}; +MMOVE_T(tank_move_pain3) = { FRAME_pain301, FRAME_pain316, tank_frames_pain3, tank_run }; + +PAIN(tank_pain) (edict_t *self, edict_t *other, float kick, int damage, const mod_t &mod) -> void +{ + if (mod.id != MOD_CHAINFIST && damage <= 10) + return; + + if (level.time < self->pain_debounce_time) + return; + + if (mod.id != MOD_CHAINFIST) + { + if (damage <= 30) + if (frandom() > 0.2f) + return; + + // don't go into pain while attacking + if ((self->s.frame >= FRAME_attak301) && (self->s.frame <= FRAME_attak330)) + return; + if ((self->s.frame >= FRAME_attak101) && (self->s.frame <= FRAME_attak116)) + return; + } + + self->pain_debounce_time = level.time + 3_sec; + + if (self->count) + gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0); + else + gi.sound(self, CHAN_VOICE, sound_pain, 1, ATTN_NORM, 0); + + if (!M_ShouldReactToPain(self, mod)) + return; // no pain anims in nightmare + + // PMM - blindfire cleanup + self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING; + // pmm + + if (damage <= 30) + M_SetAnimation(self, &tank_move_pain1); + else if (damage <= 60) + M_SetAnimation(self, &tank_move_pain2); + else + M_SetAnimation(self, &tank_move_pain3); +} + +MONSTERINFO_SETSKIN(tank_setskin) (edict_t *self) -> void +{ + if (self->health < (self->max_health / 2)) + self->s.skinnum |= 1; + else + self->s.skinnum &= ~1; +} + +// [Paril-KEX] +bool M_AdjustBlindfireTarget(edict_t *self, const vec3_t &start, const vec3_t &target, const vec3_t &right, vec3_t &out_dir) +{ + trace_t trace = gi.traceline(start, target, self, MASK_PROJECTILE); + + // blindfire has different fail criteria for the trace + if (!(trace.startsolid || trace.allsolid || (trace.fraction < 0.5f))) + { + out_dir = target - start; + out_dir.normalize(); + return true; + } + + // try shifting the target to the left a little (to help counter large offset) + vec3_t left_target = target + (right * -20); + trace = gi.traceline(start, left_target, self, MASK_PROJECTILE); + + if (!(trace.startsolid || trace.allsolid || (trace.fraction < 0.5f))) + { + out_dir = left_target - start; + out_dir.normalize(); + return true; + } + + // ok, that failed. try to the right + vec3_t right_target = target + (right * 20); + trace = gi.traceline(start, right_target, self, MASK_PROJECTILE); + if (!(trace.startsolid || trace.allsolid || (trace.fraction < 0.5f))) + { + out_dir = right_target - start; + out_dir.normalize(); + return true; + } + + return false; +} + +// +// attacks +// + +void TankBlaster(edict_t *self) +{ + vec3_t forward, right; + vec3_t start; + vec3_t dir; + monster_muzzleflash_id_t flash_number; + + if (!self->enemy || !self->enemy->inuse) // PGM + return; // PGM + + bool blindfire = self->monsterinfo.aiflags & AI_MANUAL_STEERING; + + if (self->s.frame == FRAME_attak110) + flash_number = MZ2_TANK_BLASTER_1; + else if (self->s.frame == FRAME_attak113) + flash_number = MZ2_TANK_BLASTER_2; + else // (self->s.frame == FRAME_attak116) + flash_number = MZ2_TANK_BLASTER_3; + + AngleVectors(self->s.angles, forward, right, nullptr); + start = M_ProjectFlashSource(self, monster_flash_offset[flash_number], forward, right); + + // pmm - blindfire support + vec3_t target; + + // PMM + if (blindfire) + { + target = self->monsterinfo.blind_fire_target; + + if (!M_AdjustBlindfireTarget(self, start, target, right, dir)) + return; + } + else + PredictAim(self, self->enemy, start, 0, false, 0.f, &dir, nullptr); + // pmm + + monster_fire_blaster(self, start, dir, 30, 800, flash_number, EF_BLASTER); +} + +void TankStrike(edict_t *self) +{ + gi.sound(self, CHAN_WEAPON, sound_strike, 1, ATTN_NORM, 0); +} + +void TankRocket(edict_t *self) +{ + vec3_t forward, right; + vec3_t start; + vec3_t dir; + vec3_t vec; + monster_muzzleflash_id_t flash_number; + int rocketSpeed; // PGM + // pmm - blindfire support + vec3_t target; + + if (!self->enemy || !self->enemy->inuse) // PGM + return; // PGM + + bool blindfire = self->monsterinfo.aiflags & AI_MANUAL_STEERING; + + if (self->s.frame == FRAME_attak324) + flash_number = MZ2_TANK_ROCKET_1; + else if (self->s.frame == FRAME_attak327) + flash_number = MZ2_TANK_ROCKET_2; + else // (self->s.frame == FRAME_attak330) + flash_number = MZ2_TANK_ROCKET_3; + + AngleVectors(self->s.angles, forward, right, nullptr); + + // [Paril-KEX] scale + start = M_ProjectFlashSource(self, monster_flash_offset[flash_number], forward, right); + + if (self->speed) + rocketSpeed = self->speed; + else if (self->spawnflags.has(SPAWNFLAG_TANK_COMMANDER_HEAT_SEEKING)) + rocketSpeed = 500; + else + rocketSpeed = 650; + + // PMM + if (blindfire) + target = self->monsterinfo.blind_fire_target; + else + target = self->enemy->s.origin; + // pmm + + // PGM + // PMM - blindfire shooting + if (blindfire) + { + vec = target; + dir = vec - start; + } + // pmm + // don't shoot at feet if they're above me. + else if (frandom() < 0.66f || (start[2] < self->enemy->absmin[2])) + { + vec = self->enemy->s.origin; + vec[2] += self->enemy->viewheight; + dir = vec - start; + } + else + { + vec = self->enemy->s.origin; + vec[2] = self->enemy->absmin[2] + 1; + dir = vec - start; + } + // PGM + + //====== + // PMM - lead target (not when blindfiring) + // 20, 35, 50, 65 chance of leading + if ((!blindfire) && ((frandom() < (0.2f + ((3 - skill->integer) * 0.15f))))) + PredictAim(self, self->enemy, start, rocketSpeed, false, 0, &dir, &vec); + // PMM - lead target + //====== + + dir.normalize(); + + // pmm blindfire doesn't check target (done in checkattack) + // paranoia, make sure we're not shooting a target right next to us + if (blindfire) + { + // blindfire has different fail criteria for the trace + if (M_AdjustBlindfireTarget(self, start, vec, right, dir)) + { + if (self->spawnflags.has(SPAWNFLAG_TANK_COMMANDER_HEAT_SEEKING)) + monster_fire_heat(self, start, dir, 50, rocketSpeed, flash_number, self->accel); + else + monster_fire_rocket(self, start, dir, 50, rocketSpeed, flash_number); + } + } + else + { + trace_t trace = gi.traceline(start, vec, self, MASK_PROJECTILE); + + if (trace.fraction > 0.5f || trace.ent->solid != SOLID_BSP) + { + if (self->spawnflags.has(SPAWNFLAG_TANK_COMMANDER_HEAT_SEEKING)) + monster_fire_heat(self, start, dir, 50, rocketSpeed, flash_number, self->accel); + else + monster_fire_rocket(self, start, dir, 50, rocketSpeed, flash_number); + } + } +} + +void TankMachineGun(edict_t *self) +{ + vec3_t dir; + vec3_t vec; + vec3_t start; + vec3_t forward, right; + monster_muzzleflash_id_t flash_number; + + if (!self->enemy || !self->enemy->inuse) // PGM + return; // PGM + + flash_number = static_cast(MZ2_TANK_MACHINEGUN_1 + (self->s.frame - FRAME_attak406)); + + AngleVectors(self->s.angles, forward, right, nullptr); + + start = M_ProjectFlashSource(self, monster_flash_offset[flash_number], forward, right); + + if (self->enemy) + { + vec = self->enemy->s.origin; + vec[2] += self->enemy->viewheight; + vec -= start; + vec = vectoangles(vec); + dir[0] = vec[0]; + } + else + { + dir[0] = 0; + } + if (self->s.frame <= FRAME_attak415) + dir[1] = self->s.angles[1] - 8 * (self->s.frame - FRAME_attak411); + else + dir[1] = self->s.angles[1] + 8 * (self->s.frame - FRAME_attak419); + dir[2] = 0; + + AngleVectors(dir, forward, nullptr, nullptr); + + monster_fire_bullet(self, start, forward, 20, 4, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, flash_number); +} + +static void tank_blind_check(edict_t *self) +{ + if (self->monsterinfo.aiflags & AI_MANUAL_STEERING) + { + vec3_t aim = self->monsterinfo.blind_fire_target - self->s.origin; + self->ideal_yaw = vectoyaw(aim); + } +} + +mframe_t tank_frames_attack_blast[] = { + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge, -1 }, + { ai_charge, -2 }, + { ai_charge, -1 }, + { ai_charge, -1, tank_blind_check }, + { ai_charge }, + { ai_charge, 0, TankBlaster }, // 10 + { ai_charge }, + { ai_charge }, + { ai_charge, 0, TankBlaster }, + { ai_charge }, + { ai_charge }, + { ai_charge, 0, TankBlaster } // 16 +}; +MMOVE_T(tank_move_attack_blast) = { FRAME_attak101, FRAME_attak116, tank_frames_attack_blast, tank_reattack_blaster }; + +mframe_t tank_frames_reattack_blast[] = { + { ai_charge }, + { ai_charge }, + { ai_charge, 0, TankBlaster }, + { ai_charge }, + { ai_charge }, + { ai_charge, 0, TankBlaster } // 16 +}; +MMOVE_T(tank_move_reattack_blast) = { FRAME_attak111, FRAME_attak116, tank_frames_reattack_blast, tank_reattack_blaster }; + +mframe_t tank_frames_attack_post_blast[] = { + { ai_move }, // 17 + { ai_move }, + { ai_move, 2 }, + { ai_move, 3 }, + { ai_move, 2 }, + { ai_move, -2, tank_footstep } // 22 +}; +MMOVE_T(tank_move_attack_post_blast) = { FRAME_attak117, FRAME_attak122, tank_frames_attack_post_blast, tank_run }; + +void tank_reattack_blaster(edict_t *self) +{ + if (self->monsterinfo.aiflags & AI_MANUAL_STEERING) + { + self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING; + M_SetAnimation(self, &tank_move_attack_post_blast); + return; + } + + if (visible(self, self->enemy)) + if (self->enemy->health > 0) + if (frandom() <= 0.6f) + { + M_SetAnimation(self, &tank_move_reattack_blast); + return; + } + M_SetAnimation(self, &tank_move_attack_post_blast); +} + +void tank_poststrike(edict_t *self) +{ + self->enemy = nullptr; + tank_run(self); +} + +mframe_t tank_frames_attack_strike[] = { + { ai_move, 3 }, + { ai_move, 2 }, + { ai_move, 2 }, + { ai_move, 1 }, + { ai_move, 6 }, + { ai_move, 7 }, + { ai_move, 9, tank_footstep }, + { ai_move, 2 }, + { ai_move, 1 }, + { ai_move, 2 }, + { ai_move, 2, tank_footstep }, + { ai_move, 2 }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move, -2 }, + { ai_move, -2 }, + { ai_move, 0, tank_windup }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move, 0, TankStrike }, + { ai_move }, + { ai_move, -1 }, + { ai_move, -1 }, + { ai_move, -1 }, + { ai_move, -1 }, + { ai_move, -1 }, + { ai_move, -3 }, + { ai_move, -10 }, + { ai_move, -10 }, + { ai_move, -2 }, + { ai_move, -3 }, + { ai_move, -2, tank_footstep } +}; +MMOVE_T(tank_move_attack_strike) = { FRAME_attak201, FRAME_attak238, tank_frames_attack_strike, tank_poststrike }; + +mframe_t tank_frames_attack_pre_rocket[] = { + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, // 10 + + { ai_charge }, + { ai_charge, 1 }, + { ai_charge, 2 }, + { ai_charge, 7 }, + { ai_charge, 7 }, + { ai_charge, 7, tank_footstep }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, // 20 + + { ai_charge, -3 } +}; +MMOVE_T(tank_move_attack_pre_rocket) = { FRAME_attak301, FRAME_attak321, tank_frames_attack_pre_rocket, tank_doattack_rocket }; + +mframe_t tank_frames_attack_fire_rocket[] = { + { ai_charge, -3, tank_blind_check }, // Loop Start 22 + { ai_charge }, + { ai_charge, 0, TankRocket }, // 24 + { ai_charge }, + { ai_charge }, + { ai_charge, 0, TankRocket }, + { ai_charge }, + { ai_charge }, + { ai_charge, -1, TankRocket } // 30 Loop End +}; +MMOVE_T(tank_move_attack_fire_rocket) = { FRAME_attak322, FRAME_attak330, tank_frames_attack_fire_rocket, tank_refire_rocket }; + +mframe_t tank_frames_attack_post_rocket[] = { + { ai_charge }, // 31 + { ai_charge, -1 }, + { ai_charge, -1 }, + { ai_charge }, + { ai_charge, 2 }, + { ai_charge, 3 }, + { ai_charge, 4 }, + { ai_charge, 2 }, + { ai_charge }, + { ai_charge }, // 40 + + { ai_charge }, + { ai_charge, -9 }, + { ai_charge, -8 }, + { ai_charge, -7 }, + { ai_charge, -1 }, + { ai_charge, -1, tank_footstep }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, // 50 + + { ai_charge }, + { ai_charge }, + { ai_charge } +}; +MMOVE_T(tank_move_attack_post_rocket) = { FRAME_attak331, FRAME_attak353, tank_frames_attack_post_rocket, tank_run }; + +mframe_t tank_frames_attack_chain[] = { + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { nullptr, 0, TankMachineGun }, + { nullptr, 0, TankMachineGun }, + { nullptr, 0, TankMachineGun }, + { nullptr, 0, TankMachineGun }, + { nullptr, 0, TankMachineGun }, + { nullptr, 0, TankMachineGun }, + { nullptr, 0, TankMachineGun }, + { nullptr, 0, TankMachineGun }, + { nullptr, 0, TankMachineGun }, + { nullptr, 0, TankMachineGun }, + { nullptr, 0, TankMachineGun }, + { nullptr, 0, TankMachineGun }, + { nullptr, 0, TankMachineGun }, + { nullptr, 0, TankMachineGun }, + { nullptr, 0, TankMachineGun }, + { nullptr, 0, TankMachineGun }, + { nullptr, 0, TankMachineGun }, + { nullptr, 0, TankMachineGun }, + { nullptr, 0, TankMachineGun }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge } +}; +MMOVE_T(tank_move_attack_chain) = { FRAME_attak401, FRAME_attak429, tank_frames_attack_chain, tank_run }; + +void tank_refire_rocket(edict_t *self) +{ + // PMM - blindfire cleanup + if (self->monsterinfo.aiflags & AI_MANUAL_STEERING) + { + self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING; + M_SetAnimation(self, &tank_move_attack_post_rocket); + return; + } + // pmm + + if (self->enemy->health > 0) + if (visible(self, self->enemy)) + if (frandom() <= 0.4f) + { + M_SetAnimation(self, &tank_move_attack_fire_rocket); + return; + } + M_SetAnimation(self, &tank_move_attack_post_rocket); +} + +void tank_doattack_rocket(edict_t *self) +{ + M_SetAnimation(self, &tank_move_attack_fire_rocket); +} + +MONSTERINFO_ATTACK(tank_attack) (edict_t *self) -> void +{ + vec3_t vec; + float range; + float r; + // PMM + float chance; + + // PMM + if (!self->enemy || !self->enemy->inuse) + return; + + if (self->enemy->health <= 0) + { + M_SetAnimation(self, &tank_move_attack_strike); + self->monsterinfo.aiflags &= ~AI_BRUTAL; + return; + } + + // PMM + if (self->monsterinfo.attack_state == AS_BLIND) + { + // setup shot probabilities + if (self->monsterinfo.blind_fire_delay < 1_sec) + chance = 1.0f; + else if (self->monsterinfo.blind_fire_delay < 7.5_sec) + chance = 0.4f; + else + chance = 0.1f; + + r = frandom(); + + self->monsterinfo.blind_fire_delay += 5.2_sec + random_time(3_sec); + + // don't shoot at the origin + if (!self->monsterinfo.blind_fire_target) + return; + + // don't shoot if the dice say not to + if (r > chance) + return; + + bool rocket_visible = M_CheckClearShot(self, monster_flash_offset[MZ2_TANK_ROCKET_1]); + bool blaster_visible = M_CheckClearShot(self, monster_flash_offset[MZ2_TANK_BLASTER_1]); + + if (!rocket_visible && !blaster_visible) + return; + + bool use_rocket = (rocket_visible && blaster_visible) ? brandom() : rocket_visible; + + // turn on manual steering to signal both manual steering and blindfire + self->monsterinfo.aiflags |= AI_MANUAL_STEERING; + + if (use_rocket) + M_SetAnimation(self, &tank_move_attack_fire_rocket); + else + { + M_SetAnimation(self, &tank_move_attack_blast); + self->monsterinfo.nextframe = FRAME_attak108; + } + + self->monsterinfo.attack_finished = level.time + random_time(3_sec, 5_sec); + self->pain_debounce_time = level.time + 5_sec; // no pain for a while + return; + } + // pmm + + vec = self->enemy->s.origin - self->s.origin; + range = vec.length(); + + r = frandom(); + + if (range <= 125) + { + bool can_machinegun = (!self->enemy->classname || strcmp(self->enemy->classname, "tesla_mine")) && M_CheckClearShot(self, monster_flash_offset[MZ2_TANK_MACHINEGUN_5]); + + if (can_machinegun && r < 0.5f) + M_SetAnimation(self, &tank_move_attack_chain); + else if (M_CheckClearShot(self, monster_flash_offset[MZ2_TANK_BLASTER_1])) + M_SetAnimation(self, &tank_move_attack_blast); + } + else if (range <= 250) + { + bool can_machinegun = (!self->enemy->classname || strcmp(self->enemy->classname, "tesla_mine")) && M_CheckClearShot(self, monster_flash_offset[MZ2_TANK_MACHINEGUN_5]); + + if (can_machinegun && r < 0.25f) + M_SetAnimation(self, &tank_move_attack_chain); + else if (M_CheckClearShot(self, monster_flash_offset[MZ2_TANK_BLASTER_1])) + M_SetAnimation(self, &tank_move_attack_blast); + } + else + { + bool can_machinegun = M_CheckClearShot(self, monster_flash_offset[MZ2_TANK_MACHINEGUN_5]); + bool can_rocket = M_CheckClearShot(self, monster_flash_offset[MZ2_TANK_ROCKET_1]); + + if (can_machinegun && r < 0.33f) + M_SetAnimation(self, &tank_move_attack_chain); + else if (can_rocket && r < 0.66f) + { + M_SetAnimation(self, &tank_move_attack_pre_rocket); + self->pain_debounce_time = level.time + 5_sec; // no pain for a while + } + else if (M_CheckClearShot(self, monster_flash_offset[MZ2_TANK_BLASTER_1])) + M_SetAnimation(self, &tank_move_attack_blast); + } +} + +// +// death +// + +void tank_dead(edict_t *self) +{ + self->mins = { -16, -16, -16 }; + self->maxs = { 16, 16, -0 }; + monster_dead(self); +} + +static void tank_shrink(edict_t *self) +{ + self->maxs[2] = 0; + self->svflags |= SVF_DEADMONSTER; + gi.linkentity(self); +} + +mframe_t tank_frames_death1[] = { + { ai_move, -7 }, + { ai_move, -2 }, + { ai_move, -2 }, + { ai_move, 1 }, + { ai_move, 3 }, + { ai_move, 6 }, + { ai_move, 1 }, + { ai_move, 1 }, + { ai_move, 2 }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move, -2 }, + { ai_move }, + { ai_move }, + { ai_move, -3 }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move, -4 }, + { ai_move, -6 }, + { ai_move, -4 }, + { ai_move, -5 }, + { ai_move, -7, tank_shrink }, + { ai_move, -15, tank_thud }, + { ai_move, -5 }, + { ai_move }, + { ai_move }, + { ai_move } +}; +MMOVE_T(tank_move_death) = { FRAME_death101, FRAME_death132, tank_frames_death1, tank_dead }; + +DIE(tank_die) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void +{ + // check for gib + if (M_CheckGib(self, mod)) + { + gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0); + + self->s.skinnum /= 2; + + ThrowGibs(self, damage, { + { "models/objects/gibs/sm_meat/tris.md2" }, + { 3, "models/objects/gibs/sm_metal/tris.md2", GIB_METALLIC }, + { "models/objects/gibs/gear/tris.md2", GIB_METALLIC }, + { 2, "models/monsters/tank/gibs/foot.md2", GIB_SKINNED | GIB_METALLIC }, + { 2, "models/monsters/tank/gibs/thigh.md2", GIB_SKINNED | GIB_METALLIC }, + { "models/monsters/tank/gibs/chest.md2", GIB_SKINNED }, + { "models/monsters/tank/gibs/head.md2", GIB_HEAD | GIB_SKINNED } + }); + + if (!self->style) + ThrowGib(self, "models/monsters/tank/gibs/barm.md2", damage, GIB_SKINNED | GIB_UPRIGHT, self->s.scale); + + self->deadflag = true; + return; + } + + if (self->deadflag) + return; + + // [Paril-KEX] dropped arm + if (!self->style) + { + self->style = 1; + + auto [ fwd, rgt, up] = AngleVectors(self->s.angles); + + edict_t *arm_gib = ThrowGib(self, "models/monsters/tank/gibs/barm.md2", damage, GIB_SKINNED | GIB_UPRIGHT, self->s.scale); + arm_gib->s.origin = self->s.origin + (rgt * -16.f) + (up * 23.f); + arm_gib->s.old_origin = arm_gib->s.origin; + arm_gib->avelocity = { crandom() * 15.f, crandom() * 15.f, 180.f }; + arm_gib->velocity = (up * 100.f) + (rgt * -120.f); + arm_gib->s.angles = self->s.angles; + arm_gib->s.angles[2] = -90.f; + arm_gib->s.skinnum /= 2; + gi.linkentity(arm_gib); + } + + // regular death + gi.sound(self, CHAN_VOICE, sound_die, 1, ATTN_NORM, 0); + self->deadflag = true; + self->takedamage = true; + + M_SetAnimation(self, &tank_move_death); +} + +//=========== +// PGM +MONSTERINFO_BLOCKED(tank_blocked) (edict_t *self, float dist) -> bool +{ + if (blocked_checkplat(self, dist)) + return true; + + return false; +} +// PGM +//=========== + +// +// monster_tank +// + +/*QUAKED monster_tank (1 .5 0) (-32 -32 -16) (32 32 72) Ambush Trigger_Spawn Sight +model="models/monsters/tank/tris.md2" +*/ +/*QUAKED monster_tank_commander (1 .5 0) (-32 -32 -16) (32 32 72) Ambush Trigger_Spawn Sight Guardian HeatSeeking + */ +void SP_monster_tank(edict_t *self) +{ + if ( !M_AllowSpawn( self ) ) { + G_FreeEdict( self ); + return; + } + + self->s.modelindex = gi.modelindex("models/monsters/tank/tris.md2"); + self->mins = { -32, -32, -16 }; + self->maxs = { 32, 32, 64 }; + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + + gi.modelindex("models/monsters/tank/gibs/barm.md2"); + gi.modelindex("models/monsters/tank/gibs/head.md2"); + gi.modelindex("models/monsters/tank/gibs/chest.md2"); + gi.modelindex("models/monsters/tank/gibs/foot.md2"); + gi.modelindex("models/monsters/tank/gibs/thigh.md2"); + + sound_thud = gi.soundindex("tank/tnkdeth2.wav"); + sound_idle = gi.soundindex("tank/tnkidle1.wav"); + sound_die = gi.soundindex("tank/death.wav"); + sound_step = gi.soundindex("tank/step.wav"); + sound_windup = gi.soundindex("tank/tnkatck4.wav"); + sound_strike = gi.soundindex("tank/tnkatck5.wav"); + sound_sight = gi.soundindex("tank/sight1.wav"); + + gi.soundindex("tank/tnkatck1.wav"); + gi.soundindex("tank/tnkatk2a.wav"); + gi.soundindex("tank/tnkatk2b.wav"); + gi.soundindex("tank/tnkatk2c.wav"); + gi.soundindex("tank/tnkatk2d.wav"); + gi.soundindex("tank/tnkatk2e.wav"); + gi.soundindex("tank/tnkatck3.wav"); + + if (strcmp(self->classname, "monster_tank_commander") == 0) + { + self->health = 1000 * st.health_multiplier; + self->gib_health = -225; + self->count = 1; + sound_pain2 = gi.soundindex("tank/pain.wav"); + } + else + { + self->health = 750 * st.health_multiplier; + self->gib_health = -200; + sound_pain = gi.soundindex("tank/tnkpain2.wav"); + } + + self->monsterinfo.scale = MODEL_SCALE; + + // [Paril-KEX] N64 tank commander is a chonky boy + if (self->spawnflags.has(SPAWNFLAG_TANK_COMMANDER_GUARDIAN)) + { + if (!self->s.scale) + self->s.scale = 1.5f; + self->health = 1500 * st.health_multiplier; + } + + // heat seekingness + if (!self->accel) + self->accel = 0.075f; + + self->mass = 500; + + self->pain = tank_pain; + self->die = tank_die; + self->monsterinfo.stand = tank_stand; + self->monsterinfo.walk = tank_walk; + self->monsterinfo.run = tank_run; + self->monsterinfo.dodge = nullptr; + self->monsterinfo.attack = tank_attack; + self->monsterinfo.melee = nullptr; + self->monsterinfo.sight = tank_sight; + self->monsterinfo.idle = tank_idle; + self->monsterinfo.blocked = tank_blocked; // PGM + self->monsterinfo.setskin = tank_setskin; + + gi.linkentity(self); + + M_SetAnimation(self, &tank_move_stand); + + walkmonster_start(self); + + // PMM + self->monsterinfo.aiflags |= AI_IGNORE_SHOTS; + self->monsterinfo.blindfire = true; + // pmm + if (strcmp(self->classname, "monster_tank_commander") == 0) + self->s.skinnum = 2; +} + +void Use_Boss3(edict_t *ent, edict_t *other, edict_t *activator); + +THINK(Think_TankStand) (edict_t *ent) -> void +{ + if (ent->s.frame == FRAME_stand30) + ent->s.frame = FRAME_stand01; + else + ent->s.frame++; + ent->nextthink = level.time + 10_hz; +} + +/*QUAKED monster_tank_stand (1 .5 0) (-32 -32 0) (32 32 90) + +Just stands and cycles in one place until targeted, then teleports away. +N64 edition! +*/ +void SP_monster_tank_stand(edict_t *self) +{ + if( !M_AllowSpawn( self ) ) { + G_FreeEdict( self ); + return; + } + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->model = "models/monsters/tank/tris.md2"; + self->s.modelindex = gi.modelindex(self->model); + self->s.frame = FRAME_stand01; + self->s.skinnum = 2; + + gi.soundindex("misc/bigtele.wav"); + + self->mins = { -32, -32, -16 }; + self->maxs = { 32, 32, 64 }; + + if (!self->s.scale) + self->s.scale = 1.5f; + + self->mins *= self->s.scale; + self->maxs *= self->s.scale; + + self->use = Use_Boss3; + self->think = Think_TankStand; + self->nextthink = level.time + 10_hz; + gi.linkentity(self); +} \ No newline at end of file diff --git a/rerelease/m_tank.h b/rerelease/m_tank.h new file mode 100644 index 0000000..601bf40 --- /dev/null +++ b/rerelease/m_tank.h @@ -0,0 +1,305 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// G:\quake2\baseq2\models/monsters/tank + +// This file generated by qdata - Do NOT Modify + +enum +{ + FRAME_stand01, + FRAME_stand02, + FRAME_stand03, + FRAME_stand04, + FRAME_stand05, + FRAME_stand06, + FRAME_stand07, + FRAME_stand08, + FRAME_stand09, + FRAME_stand10, + FRAME_stand11, + FRAME_stand12, + FRAME_stand13, + FRAME_stand14, + FRAME_stand15, + FRAME_stand16, + FRAME_stand17, + FRAME_stand18, + FRAME_stand19, + FRAME_stand20, + FRAME_stand21, + FRAME_stand22, + FRAME_stand23, + FRAME_stand24, + FRAME_stand25, + FRAME_stand26, + FRAME_stand27, + FRAME_stand28, + FRAME_stand29, + FRAME_stand30, + FRAME_walk01, + FRAME_walk02, + FRAME_walk03, + FRAME_walk04, + FRAME_walk05, + FRAME_walk06, + FRAME_walk07, + FRAME_walk08, + FRAME_walk09, + FRAME_walk10, + FRAME_walk11, + FRAME_walk12, + FRAME_walk13, + FRAME_walk14, + FRAME_walk15, + FRAME_walk16, + FRAME_walk17, + FRAME_walk18, + FRAME_walk19, + FRAME_walk20, + FRAME_walk21, + FRAME_walk22, + FRAME_walk23, + FRAME_walk24, + FRAME_walk25, + FRAME_attak101, + FRAME_attak102, + FRAME_attak103, + FRAME_attak104, + FRAME_attak105, + FRAME_attak106, + FRAME_attak107, + FRAME_attak108, + FRAME_attak109, + FRAME_attak110, + FRAME_attak111, + FRAME_attak112, + FRAME_attak113, + FRAME_attak114, + FRAME_attak115, + FRAME_attak116, + FRAME_attak117, + FRAME_attak118, + FRAME_attak119, + FRAME_attak120, + FRAME_attak121, + FRAME_attak122, + FRAME_attak201, + FRAME_attak202, + FRAME_attak203, + FRAME_attak204, + FRAME_attak205, + FRAME_attak206, + FRAME_attak207, + FRAME_attak208, + FRAME_attak209, + FRAME_attak210, + FRAME_attak211, + FRAME_attak212, + FRAME_attak213, + FRAME_attak214, + FRAME_attak215, + FRAME_attak216, + FRAME_attak217, + FRAME_attak218, + FRAME_attak219, + FRAME_attak220, + FRAME_attak221, + FRAME_attak222, + FRAME_attak223, + FRAME_attak224, + FRAME_attak225, + FRAME_attak226, + FRAME_attak227, + FRAME_attak228, + FRAME_attak229, + FRAME_attak230, + FRAME_attak231, + FRAME_attak232, + FRAME_attak233, + FRAME_attak234, + FRAME_attak235, + FRAME_attak236, + FRAME_attak237, + FRAME_attak238, + FRAME_attak301, + FRAME_attak302, + FRAME_attak303, + FRAME_attak304, + FRAME_attak305, + FRAME_attak306, + FRAME_attak307, + FRAME_attak308, + FRAME_attak309, + FRAME_attak310, + FRAME_attak311, + FRAME_attak312, + FRAME_attak313, + FRAME_attak314, + FRAME_attak315, + FRAME_attak316, + FRAME_attak317, + FRAME_attak318, + FRAME_attak319, + FRAME_attak320, + FRAME_attak321, + FRAME_attak322, + FRAME_attak323, + FRAME_attak324, + FRAME_attak325, + FRAME_attak326, + FRAME_attak327, + FRAME_attak328, + FRAME_attak329, + FRAME_attak330, + FRAME_attak331, + FRAME_attak332, + FRAME_attak333, + FRAME_attak334, + FRAME_attak335, + FRAME_attak336, + FRAME_attak337, + FRAME_attak338, + FRAME_attak339, + FRAME_attak340, + FRAME_attak341, + FRAME_attak342, + FRAME_attak343, + FRAME_attak344, + FRAME_attak345, + FRAME_attak346, + FRAME_attak347, + FRAME_attak348, + FRAME_attak349, + FRAME_attak350, + FRAME_attak351, + FRAME_attak352, + FRAME_attak353, + FRAME_attak401, + FRAME_attak402, + FRAME_attak403, + FRAME_attak404, + FRAME_attak405, + FRAME_attak406, + FRAME_attak407, + FRAME_attak408, + FRAME_attak409, + FRAME_attak410, + FRAME_attak411, + FRAME_attak412, + FRAME_attak413, + FRAME_attak414, + FRAME_attak415, + FRAME_attak416, + FRAME_attak417, + FRAME_attak418, + FRAME_attak419, + FRAME_attak420, + FRAME_attak421, + FRAME_attak422, + FRAME_attak423, + FRAME_attak424, + FRAME_attak425, + FRAME_attak426, + FRAME_attak427, + FRAME_attak428, + FRAME_attak429, + FRAME_pain101, + FRAME_pain102, + FRAME_pain103, + FRAME_pain104, + FRAME_pain201, + FRAME_pain202, + FRAME_pain203, + FRAME_pain204, + FRAME_pain205, + FRAME_pain301, + FRAME_pain302, + FRAME_pain303, + FRAME_pain304, + FRAME_pain305, + FRAME_pain306, + FRAME_pain307, + FRAME_pain308, + FRAME_pain309, + FRAME_pain310, + FRAME_pain311, + FRAME_pain312, + FRAME_pain313, + FRAME_pain314, + FRAME_pain315, + FRAME_pain316, + FRAME_death101, + FRAME_death102, + FRAME_death103, + FRAME_death104, + FRAME_death105, + FRAME_death106, + FRAME_death107, + FRAME_death108, + FRAME_death109, + FRAME_death110, + FRAME_death111, + FRAME_death112, + FRAME_death113, + FRAME_death114, + FRAME_death115, + FRAME_death116, + FRAME_death117, + FRAME_death118, + FRAME_death119, + FRAME_death120, + FRAME_death121, + FRAME_death122, + FRAME_death123, + FRAME_death124, + FRAME_death125, + FRAME_death126, + FRAME_death127, + FRAME_death128, + FRAME_death129, + FRAME_death130, + FRAME_death131, + FRAME_death132, + FRAME_recln101, + FRAME_recln102, + FRAME_recln103, + FRAME_recln104, + FRAME_recln105, + FRAME_recln106, + FRAME_recln107, + FRAME_recln108, + FRAME_recln109, + FRAME_recln110, + FRAME_recln111, + FRAME_recln112, + FRAME_recln113, + FRAME_recln114, + FRAME_recln115, + FRAME_recln116, + FRAME_recln117, + FRAME_recln118, + FRAME_recln119, + FRAME_recln120, + FRAME_recln121, + FRAME_recln122, + FRAME_recln123, + FRAME_recln124, + FRAME_recln125, + FRAME_recln126, + FRAME_recln127, + FRAME_recln128, + FRAME_recln129, + FRAME_recln130, + FRAME_recln131, + FRAME_recln132, + FRAME_recln133, + FRAME_recln134, + FRAME_recln135, + FRAME_recln136, + FRAME_recln137, + FRAME_recln138, + FRAME_recln139, + FRAME_recln140 +}; + +constexpr float MODEL_SCALE = 1.000000f; diff --git a/rerelease/p_client.cpp b/rerelease/p_client.cpp new file mode 100644 index 0000000..1f4dcd7 --- /dev/null +++ b/rerelease/p_client.cpp @@ -0,0 +1,3792 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +#include "g_local.h" +#include "m_player.h" +#include "bots/bot_includes.h" + +void SP_misc_teleporter_dest(edict_t *ent); + +THINK(info_player_start_drop) (edict_t *self) -> void +{ + // allow them to drop + self->solid = SOLID_TRIGGER; + self->movetype = MOVETYPE_TOSS; + self->mins = PLAYER_MINS; + self->maxs = PLAYER_MAXS; + gi.linkentity(self); +} + +/*QUAKED info_player_start (1 0 0) (-16 -16 -24) (16 16 32) +The normal starting point for a level. +*/ +void SP_info_player_start(edict_t *self) +{ + // fix stuck spawn points + if (gi.trace(self->s.origin, PLAYER_MINS, PLAYER_MAXS, self->s.origin, self, MASK_SOLID).startsolid) + G_FixStuckObject(self, self->s.origin); + + // [Paril-KEX] on n64, since these can spawn riding elevators, + // allow them to "ride" the elevators so respawning works + if (level.is_n64) + { + self->think = info_player_start_drop; + self->nextthink = level.time + FRAME_TIME_S; + } +} + +/*QUAKED info_player_deathmatch (1 0 1) (-16 -16 -24) (16 16 32) +potential spawning position for deathmatch games +*/ +void SP_info_player_deathmatch(edict_t *self) +{ + if (!deathmatch->integer) + { + G_FreeEdict(self); + return; + } + SP_misc_teleporter_dest(self); +} + +/*QUAKED info_player_coop (1 0 1) (-16 -16 -24) (16 16 32) +potential spawning position for coop games +*/ +void SP_info_player_coop(edict_t *self) +{ + if (!coop->integer) + { + G_FreeEdict(self); + return; + } + + SP_info_player_start(self); +} + +/*QUAKED info_player_coop_lava (1 0 1) (-16 -16 -24) (16 16 32) +potential spawning position for coop games on rmine2 where lava level +needs to be checked +*/ +void SP_info_player_coop_lava(edict_t *self) +{ + if (!coop->integer) + { + G_FreeEdict(self); + return; + } + + // fix stuck spawn points + if (gi.trace(self->s.origin, PLAYER_MINS, PLAYER_MAXS, self->s.origin, self, MASK_SOLID).startsolid) + G_FixStuckObject(self, self->s.origin); +} + +/*QUAKED info_player_intermission (1 0 1) (-16 -16 -24) (16 16 32) +The deathmatch intermission point will be at one of these +Use 'angles' instead of 'angle', so you can set pitch or roll as well as yaw. 'pitch yaw roll' +*/ +void SP_info_player_intermission(edict_t *ent) +{ +} + +// [Paril-KEX] whether instanced items should be used or not +bool P_UseCoopInstancedItems() +{ + // squad respawn forces instanced items on, since we don't + // want players to need to backtrack just to get their stuff. + return g_coop_instanced_items->integer || g_coop_squad_respawn->integer; +} + +//======================================================================= + +void ClientObituary(edict_t *self, edict_t *inflictor, edict_t *attacker, mod_t mod) +{ + const char *base = nullptr; + + if (coop->integer && attacker->client) + mod.friendly_fire = true; + + switch (mod.id) + { + case MOD_SUICIDE: + base = "$g_mod_generic_suicide"; + break; + case MOD_FALLING: + base = "$g_mod_generic_falling"; + break; + case MOD_CRUSH: + base = "$g_mod_generic_crush"; + break; + case MOD_WATER: + base = "$g_mod_generic_water"; + break; + case MOD_SLIME: + base = "$g_mod_generic_slime"; + break; + case MOD_LAVA: + base = "$g_mod_generic_lava"; + break; + case MOD_EXPLOSIVE: + case MOD_BARREL: + base = "$g_mod_generic_explosive"; + break; + case MOD_EXIT: + base = "$g_mod_generic_exit"; + break; + case MOD_TARGET_LASER: + base = "$g_mod_generic_laser"; + break; + case MOD_TARGET_BLASTER: + base = "$g_mod_generic_blaster"; + break; + case MOD_BOMB: + case MOD_SPLASH: + case MOD_TRIGGER_HURT: + base = "$g_mod_generic_hurt"; + break; + // RAFAEL + case MOD_GEKK: + case MOD_BRAINTENTACLE: + base = "$g_mod_generic_gekk"; + break; + // RAFAEL + default: + base = nullptr; + break; + } + + if (attacker == self) + { + switch (mod.id) + { + case MOD_HELD_GRENADE: + base = "$g_mod_self_held_grenade"; + break; + case MOD_HG_SPLASH: + case MOD_G_SPLASH: + base = "$g_mod_self_grenade_splash"; + break; + case MOD_R_SPLASH: + base = "$g_mod_self_rocket_splash"; + break; + case MOD_BFG_BLAST: + base = "$g_mod_self_bfg_blast"; + break; + // RAFAEL 03-MAY-98 + case MOD_TRAP: + base = "$g_mod_self_trap"; + break; + // RAFAEL + // ROGUE + case MOD_DOPPLE_EXPLODE: + base = "$g_mod_self_dopple_explode"; + break; + // ROGUE + default: + base = "$g_mod_self_default"; + break; + } + } + + // send generic/self + if (base) + { + gi.LocBroadcast_Print(PRINT_MEDIUM, base, self->client->pers.netname); + if (deathmatch->integer && !mod.no_point_loss) + { + self->client->resp.score--; + + if (teamplay->integer) + G_AdjustTeamScore(self->client->resp.ctf_team, -1); + } + self->enemy = nullptr; + return; + } + + // has a killer + self->enemy = attacker; + if (attacker && attacker->client) + { + switch (mod.id) + { + case MOD_BLASTER: + base = "$g_mod_kill_blaster"; + break; + case MOD_SHOTGUN: + base = "$g_mod_kill_shotgun"; + break; + case MOD_SSHOTGUN: + base = "$g_mod_kill_sshotgun"; + break; + case MOD_MACHINEGUN: + base = "$g_mod_kill_machinegun"; + break; + case MOD_CHAINGUN: + base = "$g_mod_kill_chaingun"; + break; + case MOD_GRENADE: + base = "$g_mod_kill_grenade"; + break; + case MOD_G_SPLASH: + base = "$g_mod_kill_grenade_splash"; + break; + case MOD_ROCKET: + base = "$g_mod_kill_rocket"; + break; + case MOD_R_SPLASH: + base = "$g_mod_kill_rocket_splash"; + break; + case MOD_HYPERBLASTER: + base = "$g_mod_kill_hyperblaster"; + break; + case MOD_RAILGUN: + base = "$g_mod_kill_railgun"; + break; + case MOD_BFG_LASER: + base = "$g_mod_kill_bfg_laser"; + break; + case MOD_BFG_BLAST: + base = "$g_mod_kill_bfg_blast"; + break; + case MOD_BFG_EFFECT: + base = "$g_mod_kill_bfg_effect"; + break; + case MOD_HANDGRENADE: + base = "$g_mod_kill_handgrenade"; + break; + case MOD_HG_SPLASH: + base = "$g_mod_kill_handgrenade_splash"; + break; + case MOD_HELD_GRENADE: + base = "$g_mod_kill_held_grenade"; + break; + case MOD_TELEFRAG: + case MOD_TELEFRAG_SPAWN: + base = "$g_mod_kill_telefrag"; + break; + // RAFAEL 14-APR-98 + case MOD_RIPPER: + base = "$g_mod_kill_ripper"; + break; + case MOD_PHALANX: + base = "$g_mod_kill_phalanx"; + break; + case MOD_TRAP: + base = "$g_mod_kill_trap"; + break; + // RAFAEL + //=============== + // ROGUE + case MOD_CHAINFIST: + base = "$g_mod_kill_chainfist"; + break; + case MOD_DISINTEGRATOR: + base = "$g_mod_kill_disintegrator"; + break; + case MOD_ETF_RIFLE: + base = "$g_mod_kill_etf_rifle"; + break; + case MOD_HEATBEAM: + base = "$g_mod_kill_heatbeam"; + break; + case MOD_TESLA: + base = "$g_mod_kill_tesla"; + break; + case MOD_PROX: + base = "$g_mod_kill_prox"; + break; + case MOD_NUKE: + base = "$g_mod_kill_nuke"; + break; + case MOD_VENGEANCE_SPHERE: + base = "$g_mod_kill_vengeance_sphere"; + break; + case MOD_DEFENDER_SPHERE: + base = "$g_mod_kill_defender_sphere"; + break; + case MOD_HUNTER_SPHERE: + base = "$g_mod_kill_hunter_sphere"; + break; + case MOD_TRACKER: + base = "$g_mod_kill_tracker"; + break; + case MOD_DOPPLE_EXPLODE: + base = "$g_mod_kill_dopple_explode"; + break; + case MOD_DOPPLE_VENGEANCE: + base = "$g_mod_kill_dopple_vengeance"; + break; + case MOD_DOPPLE_HUNTER: + base = "$g_mod_kill_dopple_hunter"; + break; + // ROGUE + //=============== + // ZOID + case MOD_GRAPPLE: + base = "$g_mod_kill_grapple"; + break; + // ZOID + default: + base = "$g_mod_kill_generic"; + break; + } + + gi.LocBroadcast_Print(PRINT_MEDIUM, base, self->client->pers.netname, attacker->client->pers.netname); + + if (G_TeamplayEnabled()) + { + // ZOID + // if at start and same team, clear. + // [Paril-KEX] moved here so it's not an outlier in player_die. + if (mod.id == MOD_TELEFRAG_SPAWN && + self->client->resp.ctf_state < 2 && + self->client->resp.ctf_team == attacker->client->resp.ctf_team) + { + self->client->resp.ctf_state = 0; + return; + } + } + + // ROGUE + if (gamerules->integer) + { + if (DMGame.Score) + { + if (mod.friendly_fire) + { + if (!mod.no_point_loss) + DMGame.Score(attacker, self, -1, mod); + } + else + DMGame.Score(attacker, self, 1, mod); + } + return; + } + // ROGUE + + if (deathmatch->integer) + { + if (mod.friendly_fire) + { + if (!mod.no_point_loss) + { + attacker->client->resp.score--; + + if (teamplay->integer) + G_AdjustTeamScore(attacker->client->resp.ctf_team, -1); + } + } + else + { + attacker->client->resp.score++; + + if (teamplay->integer) + G_AdjustTeamScore(attacker->client->resp.ctf_team, 1); + } + } + else if (!coop->integer) + self->client->resp.score--; + + return; + } + + gi.LocBroadcast_Print(PRINT_MEDIUM, "$g_mod_generic_died", self->client->pers.netname); + if (deathmatch->integer && !mod.no_point_loss) + // ROGUE + { + if (gamerules->integer) + { + if (DMGame.Score) + { + DMGame.Score(self, self, -1, mod); + } + return; + } + else + { + self->client->resp.score--; + + if (teamplay->integer) + G_AdjustTeamScore(attacker->client->resp.ctf_team, -1); + } + } + // ROGUE +} + +void TossClientWeapon(edict_t *self) +{ + gitem_t *item; + edict_t *drop; + bool quad; + // RAFAEL + bool quadfire; + // RAFAEL + float spread; + + if (!deathmatch->integer) + return; + + item = self->client->pers.weapon; + if (item && g_instagib->integer) + item = nullptr; + if (item && !self->client->pers.inventory[self->client->pers.weapon->ammo]) + item = nullptr; + if (item && !item->drop) + item = nullptr; + + if (g_dm_no_quad_drop->integer) + quad = false; + else + quad = (self->client->quad_time > (level.time + 1_sec)); + + // RAFAEL + if (g_dm_no_quadfire_drop->integer) + quadfire = false; + else + quadfire = (self->client->quadfire_time > (level.time + 1_sec)); + // RAFAEL + + if (item && quad) + spread = 22.5; + // RAFAEL + else if (item && quadfire) + spread = 12.5; + // RAFAEL + else + spread = 0.0; + + if (item) + { + self->client->v_angle[YAW] -= spread; + drop = Drop_Item(self, item); + self->client->v_angle[YAW] += spread; + drop->spawnflags |= SPAWNFLAG_ITEM_DROPPED_PLAYER; + drop->spawnflags &= ~SPAWNFLAG_ITEM_DROPPED; + drop->svflags &= ~SVF_INSTANCED; + } + + if (quad) + { + self->client->v_angle[YAW] += spread; + drop = Drop_Item(self, GetItemByIndex(IT_ITEM_QUAD)); + self->client->v_angle[YAW] -= spread; + drop->spawnflags |= SPAWNFLAG_ITEM_DROPPED_PLAYER; + drop->spawnflags &= ~SPAWNFLAG_ITEM_DROPPED; + drop->svflags &= ~SVF_INSTANCED; + + drop->touch = Touch_Item; + drop->nextthink = self->client->quad_time; + drop->think = G_FreeEdict; + } + + // RAFAEL + if (quadfire) + { + self->client->v_angle[YAW] += spread; + drop = Drop_Item(self, GetItemByIndex(IT_ITEM_QUADFIRE)); + self->client->v_angle[YAW] -= spread; + drop->spawnflags |= SPAWNFLAG_ITEM_DROPPED_PLAYER; + drop->spawnflags &= ~SPAWNFLAG_ITEM_DROPPED; + drop->svflags &= ~SVF_INSTANCED; + + drop->touch = Touch_Item; + drop->nextthink = self->client->quadfire_time; + drop->think = G_FreeEdict; + } + // RAFAEL +} + +/* +================== +LookAtKiller +================== +*/ +void LookAtKiller(edict_t *self, edict_t *inflictor, edict_t *attacker) +{ + vec3_t dir; + + if (attacker && attacker != world && attacker != self) + { + dir = attacker->s.origin - self->s.origin; + } + else if (inflictor && inflictor != world && inflictor != self) + { + dir = inflictor->s.origin - self->s.origin; + } + else + { + self->client->killer_yaw = self->s.angles[YAW]; + return; + } + // PMM - fixed to correct for pitch of 0 + if (dir[0]) + self->client->killer_yaw = 180 / PIf * atan2f(dir[1], dir[0]); + else if (dir[1] > 0) + self->client->killer_yaw = 90; + else if (dir[1] < 0) + self->client->killer_yaw = 270; + else + self->client->killer_yaw = 0; +} + +/* +================== +player_die +================== +*/ +DIE(player_die) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void +{ + PlayerTrail_Destroy(self); + + self->avelocity = {}; + + self->takedamage = true; + self->movetype = MOVETYPE_TOSS; + + self->s.modelindex2 = 0; // remove linked weapon model + // ZOID + self->s.modelindex3 = 0; // remove linked ctf flag + // ZOID + + self->s.angles[0] = 0; + self->s.angles[2] = 0; + + self->s.sound = 0; + self->client->weapon_sound = 0; + + self->maxs[2] = -8; + + // self->solid = SOLID_NOT; + self->svflags |= SVF_DEADMONSTER; + + if (!self->deadflag) + { + self->client->respawn_time = ( level.time + 1_sec ); + if ( deathmatch->integer && g_dm_force_respawn_time->integer ) { + self->client->respawn_time = ( level.time + gtime_t::from_sec( g_dm_force_respawn_time->value ) ); + } + + LookAtKiller(self, inflictor, attacker); + self->client->ps.pmove.pm_type = PM_DEAD; + ClientObituary(self, inflictor, attacker, mod); + + CTFFragBonuses(self, inflictor, attacker); + // ZOID + TossClientWeapon(self); + // ZOID + CTFPlayerResetGrapple(self); + CTFDeadDropFlag(self); + CTFDeadDropTech(self); + // ZOID + if (deathmatch->integer && !self->client->showscores) + Cmd_Help_f(self); // show scores + + if (coop->integer && !P_UseCoopInstancedItems()) + { + // clear inventory + // this is kind of ugly, but it's how we want to handle keys in coop + for (int n = 0; n < IT_TOTAL; n++) + { + if (coop->integer && (itemlist[n].flags & IF_KEY)) + self->client->resp.coop_respawn.inventory[n] = self->client->pers.inventory[n]; + self->client->pers.inventory[n] = 0; + } + } + } + + if (gamerules->integer) // if we're in a dm game, alert the game + { + if (DMGame.PlayerDeath) + DMGame.PlayerDeath(self, inflictor, attacker); + } + + // remove powerups + self->client->quad_time = 0_ms; + self->client->invincible_time = 0_ms; + self->client->breather_time = 0_ms; + self->client->enviro_time = 0_ms; + self->client->invisible_time = 0_ms; + self->flags &= ~FL_POWER_ARMOR; + + // clear inventory + if (G_TeamplayEnabled()) + self->client->pers.inventory.fill(0); + + // RAFAEL + self->client->quadfire_time = 0_ms; + // RAFAEL + + //============== + // ROGUE stuff + self->client->double_time = 0_ms; + + // if there's a sphere around, let it know the player died. + // vengeance and hunter will die if they're not attacking, + // defender should always die + if (self->client->owned_sphere) + { + edict_t *sphere; + + sphere = self->client->owned_sphere; + sphere->die(sphere, self, self, 0, vec3_origin, mod); + } + + // if we've been killed by the tracker, GIB! + if (mod.id == MOD_TRACKER) + { + self->health = -100; + damage = 400; + } + + // make sure no trackers are still hurting us. + if (self->client->tracker_pain_time) + { + RemoveAttackingPainDaemons(self); + } + + // if we got obliterated by the nuke, don't gib + if ((self->health < -80) && (mod.id == MOD_NUKE)) + self->flags |= FL_NOGIB; + + // ROGUE + //============== + + if (self->health < -40) + { + // PMM + // don't toss gibs if we got vaped by the nuke + if (!(self->flags & FL_NOGIB)) + { + // pmm + // gib + gi.sound(self, CHAN_BODY, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0); + + // more meaty gibs for your dollar! + if (deathmatch->integer && (self->health < -80)) + ThrowGibs(self, damage, { { 4, "models/objects/gibs/sm_meat/tris.md2" } }); + + ThrowGibs(self, damage, { { 4, "models/objects/gibs/sm_meat/tris.md2" } }); + // PMM + } + self->flags &= ~FL_NOGIB; + // pmm + + ThrowClientHead(self, damage); + // ZOID + self->client->anim_priority = ANIM_DEATH; + self->client->anim_end = 0; + // ZOID + self->takedamage = false; + } + else + { // normal death + if (!self->deadflag) + { + // start a death animation + self->client->anim_priority = ANIM_DEATH; + if (self->client->ps.pmove.pm_flags & PMF_DUCKED) + { + self->s.frame = FRAME_crdeath1 - 1; + self->client->anim_end = FRAME_crdeath5; + } + else + { + switch (irandom(3)) + { + case 0: + self->s.frame = FRAME_death101 - 1; + self->client->anim_end = FRAME_death106; + break; + case 1: + self->s.frame = FRAME_death201 - 1; + self->client->anim_end = FRAME_death206; + break; + case 2: + self->s.frame = FRAME_death301 - 1; + self->client->anim_end = FRAME_death308; + break; + } + } + static constexpr const char *death_sounds[] = { + "*death1.wav", + "*death2.wav", + "*death3.wav", + "*death4.wav" + }; + gi.sound(self, CHAN_VOICE, gi.soundindex(random_element(death_sounds)), 1, ATTN_NORM, 0); + self->client->anim_time = 0_ms; + } + } + + if (!self->deadflag) + { + if (coop->integer && (g_coop_squad_respawn->integer || g_coop_enable_lives->integer)) + { + if (g_coop_enable_lives->integer && self->client->pers.lives) + { + self->client->pers.lives--; + self->client->resp.coop_respawn.lives--; + } + + bool allPlayersDead = true; + + for (auto player : active_players()) + if (player->health > 0 || (!level.deadly_kill_box && g_coop_enable_lives->integer && player->client->pers.lives > 0)) + { + allPlayersDead = false; + break; + } + + if (allPlayersDead) // allow respawns for telefrags and weird shit + { + level.coop_level_restart_time = level.time + 5_sec; + + for (auto player : active_players()) + gi.LocCenter_Print(player, "$g_coop_lose"); + } + + // in 3 seconds, attempt a respawn or put us into + // spectator mode + if (!level.coop_level_restart_time) + self->client->respawn_time = level.time + 3_sec; + } + } + + self->deadflag = true; + + gi.linkentity(self); +} + +//======================================================================= + +#include +#include + +// [Paril-KEX] +static void Player_GiveStartItems(edict_t *ent, const char *ptr) +{ + char token_copy[MAX_TOKEN_CHARS]; + const char *token; + + while (*(token = COM_ParseEx(&ptr, ";"))) + { + Q_strlcpy(token_copy, token, sizeof(token_copy)); + const char *ptr_copy = token_copy; + + const char *item_name = COM_Parse(&ptr_copy); + gitem_t *item = FindItemByClassname(item_name); + + if (!item || !item->pickup) + gi.Com_ErrorFmt("Invalid g_start_item entry: {}\n", item_name); + + int32_t count = 1; + + if (*ptr_copy) + count = atoi(COM_Parse(&ptr_copy)); + + if (count == 0) + { + ent->client->pers.inventory[item->id] = 0; + continue; + } + + edict_t *dummy = G_Spawn(); + dummy->item = item; + dummy->count = count; + dummy->spawnflags |= SPAWNFLAG_ITEM_DROPPED; + item->pickup(dummy, ent); + G_FreeEdict(dummy); + } +} + +/* +============== +InitClientPersistant + +This is only called when the game first initializes in single player, +but is called after each death and level change in deathmatch +============== +*/ +void InitClientPersistant(edict_t *ent, gclient_t *client) +{ + // backup & restore userinfo + char userinfo[MAX_INFO_STRING]; + Q_strlcpy(userinfo, client->pers.userinfo, sizeof(userinfo)); + + memset(&client->pers, 0, sizeof(client->pers)); + ClientUserinfoChanged(ent, userinfo); + + client->pers.health = 100; + client->pers.max_health = 100; + + // don't give us weapons if we shouldn't have any + if ((G_TeamplayEnabled() && client->resp.ctf_team != CTF_NOTEAM) || + (!G_TeamplayEnabled() && !client->resp.spectator)) + { + // in coop, if there's already a player in the game and we're new, + // steal their loadout. this would fix a potential softlock where a new + // player may not have weapons at all. + bool taken_loadout = false; + + if (coop->integer) + { + for (auto player : active_players()) + { + if (player == ent || !player->client->pers.spawned || + player->client->resp.spectator || player->movetype == MOVETYPE_NOCLIP) + continue; + + client->pers.inventory = player->client->pers.inventory; + client->pers.max_ammo = player->client->pers.max_ammo; + client->pers.power_cubes = player->client->pers.power_cubes; + taken_loadout = true; + break; + } + } + + if (!taken_loadout) + { + // fill with 50s, since it's our most common value + client->pers.max_ammo.fill(50); + client->pers.max_ammo[AMMO_BULLETS] = 200; + client->pers.max_ammo[AMMO_SHELLS] = 100; + client->pers.max_ammo[AMMO_CELLS] = 200; + + // RAFAEL + client->pers.max_ammo[AMMO_TRAP] = 5; + // RAFAEL + // ROGUE + client->pers.max_ammo[AMMO_FLECHETTES] = 200; + client->pers.max_ammo[AMMO_DISRUPTOR] = 12; + client->pers.max_ammo[AMMO_TESLA] = 5; + // ROGUE + + if (!g_instagib->integer) + client->pers.inventory[IT_WEAPON_BLASTER] = 1; + + // [Kex] + // start items! + if (*g_start_items->string) + Player_GiveStartItems(ent, g_start_items->string); + else if (g_instagib->integer) + { + client->pers.inventory[IT_WEAPON_RAILGUN] = 1; + client->pers.inventory[IT_AMMO_SLUGS] = 99; + } + + if (level.start_items && *level.start_items) + Player_GiveStartItems(ent, level.start_items); + + if (!deathmatch->integer) + client->pers.inventory[IT_ITEM_COMPASS] = 1; + + // ZOID + bool give_grapple = (!strcmp(g_allow_grapple->string, "auto")) ? + (ctf->integer ? !level.no_grapple : 0) : + g_allow_grapple->integer; + + if (give_grapple) + client->pers.inventory[IT_WEAPON_GRAPPLE] = 1; + // ZOID + } + + NoAmmoWeaponChange(ent, false); + + client->pers.weapon = client->newweapon; + if (client->newweapon) + client->pers.selected_item = client->newweapon->id; + client->newweapon = nullptr; + // ZOID + client->pers.lastweapon = client->pers.weapon; + // ZOID + } + + if (coop->value && g_coop_enable_lives->integer) + client->pers.lives = g_coop_num_lives->integer + 1; + + if (ent->client->pers.autoshield >= AUTO_SHIELD_AUTO) + ent->flags |= FL_WANTS_POWER_ARMOR; + + client->pers.connected = true; + client->pers.spawned = true; +} + +void InitClientResp(gclient_t *client) +{ + // ZOID + ctfteam_t ctf_team = client->resp.ctf_team; + bool id_state = client->resp.id_state; + // ZOID + + memset(&client->resp, 0, sizeof(client->resp)); + + // ZOID + client->resp.ctf_team = ctf_team; + client->resp.id_state = id_state; + // ZOID + + client->resp.entertime = level.time; + client->resp.coop_respawn = client->pers; + + // ZOID + if (G_TeamplayEnabled() && client->pers.connected && client->resp.ctf_team < CTF_TEAM1) + CTFAssignTeam(client); + // ZOID +} + +/* +================== +SaveClientData + +Some information that should be persistant, like health, +is still stored in the edict structure, so it needs to +be mirrored out to the client structure before all the +edicts are wiped. +================== +*/ +void SaveClientData() +{ + edict_t *ent; + + for (uint32_t i = 0; i < game.maxclients; i++) + { + ent = &g_edicts[1 + i]; + if (!ent->inuse) + continue; + game.clients[i].pers.health = ent->health; + game.clients[i].pers.max_health = ent->max_health; + game.clients[i].pers.savedFlags = (ent->flags & (FL_FLASHLIGHT | FL_GODMODE | FL_NOTARGET | FL_POWER_ARMOR | FL_WANTS_POWER_ARMOR)); + if (coop->integer) + game.clients[i].pers.score = ent->client->resp.score; + } +} + +void FetchClientEntData(edict_t *ent) +{ + ent->health = ent->client->pers.health; + ent->max_health = ent->client->pers.max_health; + ent->flags |= ent->client->pers.savedFlags; + if (coop->integer) + ent->client->resp.score = ent->client->pers.score; +} + +/* +======================================================================= + + SelectSpawnPoint + +======================================================================= +*/ + +/* +================ +PlayersRangeFromSpot + +Returns the distance to the nearest player from the given spot +================ +*/ +float PlayersRangeFromSpot(edict_t *spot) +{ + edict_t *player; + float bestplayerdistance; + vec3_t v; + float playerdistance; + + bestplayerdistance = 9999999; + + for (uint32_t n = 1; n <= game.maxclients; n++) + { + player = &g_edicts[n]; + + if (!player->inuse) + continue; + + if (player->health <= 0) + continue; + + v = spot->s.origin - player->s.origin; + playerdistance = v.length(); + + if (playerdistance < bestplayerdistance) + bestplayerdistance = playerdistance; + } + + return bestplayerdistance; +} + +bool SpawnPointClear(edict_t *spot) +{ + vec3_t p = spot->s.origin + vec3_t{0, 0, 9.f}; + return !gi.trace(p, PLAYER_MINS, PLAYER_MAXS, p, spot, CONTENTS_PLAYER | CONTENTS_MONSTER).startsolid; +} + +select_spawn_result_t SelectDeathmatchSpawnPoint(bool farthest, bool force_spawn, bool fallback_to_ctf_or_start) +{ + struct spawn_point_t + { + edict_t *point; + float dist; + }; + + static std::vector spawn_points; + + spawn_points.clear(); + + // gather all spawn points + edict_t *spot = nullptr; + while ((spot = G_FindByString<&edict_t::classname>(spot, "info_player_deathmatch")) != nullptr) + spawn_points.push_back({ spot, PlayersRangeFromSpot(spot) }); + + // no points + if (spawn_points.size() == 0) + { + // try CTF spawns... + if (fallback_to_ctf_or_start) + { + spot = nullptr; + while ((spot = G_FindByString<&edict_t::classname>(spot, "info_player_team1")) != nullptr) + spawn_points.push_back({ spot, PlayersRangeFromSpot(spot) }); + spot = nullptr; + while ((spot = G_FindByString<&edict_t::classname>(spot, "info_player_team2")) != nullptr) + spawn_points.push_back({ spot, PlayersRangeFromSpot(spot) }); + + // we only have an info_player_start then + if (spawn_points.size() == 0) + { + spot = G_FindByString<&edict_t::classname>(nullptr, "info_player_start"); + spawn_points.push_back({ spot, PlayersRangeFromSpot(spot) }); + + // map is malformed + if (spawn_points.size() == 0) + return { nullptr, false }; + } + } + else + return { nullptr, false }; + } + + // if there's only one spawn point, that's the one. + if (spawn_points.size() == 1) + { + if (force_spawn || SpawnPointClear(spawn_points[0].point)) + return { spawn_points[0].point, true }; + + return { nullptr, true }; + } + + // order by distances ascending (top of list has closest players to point) + std::sort(spawn_points.begin(), spawn_points.end(), [](const spawn_point_t &a, const spawn_point_t &b) { return a.dist < b.dist; }); + + // farthest spawn is simple + if (farthest) + { + for (int32_t i = spawn_points.size() - 1; i >= 0; --i) + { + if (SpawnPointClear(spawn_points[i].point)) + return { spawn_points[i].point, true }; + } + + // none clear + } + else + { + // for random, select a random point other than the two + // that are closest to the player if possible. + // shuffle the non-distance-related spawn points + std::shuffle(spawn_points.begin() + 2, spawn_points.end(), mt_rand); + + // run down the list and pick the first one that we can use + for (auto it = spawn_points.begin() + 2; it != spawn_points.end(); ++it) + { + auto spot = it->point; + + if (SpawnPointClear(spot)) + return { spot, true }; + } + + // none clear, so we have to pick one of the other two + if (SpawnPointClear(spawn_points[1].point)) + return { spawn_points[1].point, true }; + else if (SpawnPointClear(spawn_points[0].point)) + return { spawn_points[0].point, true }; + } + + if (force_spawn) + return { random_element(spawn_points).point, true }; + + return { nullptr, true }; +} + +//=============== +// ROGUE +edict_t *SelectLavaCoopSpawnPoint(edict_t *ent) +{ + int index; + edict_t *spot = nullptr; + float lavatop; + edict_t *lava; + edict_t *pointWithLeastLava; + float lowest; + edict_t *spawnPoints[64]; + vec3_t center; + int numPoints; + edict_t *highestlava; + + lavatop = -99999; + highestlava = nullptr; + + // first, find the highest lava + // remember that some will stop moving when they've filled their + // areas... + lava = nullptr; + while (1) + { + lava = G_FindByString<&edict_t::classname>(lava, "func_water"); + if (!lava) + break; + + center = lava->absmax + lava->absmin; + center *= 0.5f; + + if (lava->spawnflags.has(SPAWNFLAG_WATER_SMART) && (gi.pointcontents(center) & MASK_WATER)) + { + if (lava->absmax[2] > lavatop) + { + lavatop = lava->absmax[2]; + highestlava = lava; + } + } + } + + // if we didn't find ANY lava, then return nullptr + if (!highestlava) + return nullptr; + + // find the top of the lava and include a small margin of error (plus bbox size) + lavatop = highestlava->absmax[2] + 64; + + // find all the lava spawn points and store them in spawnPoints[] + spot = nullptr; + numPoints = 0; + while ((spot = G_FindByString<&edict_t::classname>(spot, "info_player_coop_lava"))) + { + if (numPoints == 64) + break; + + spawnPoints[numPoints++] = spot; + } + + // walk up the sorted list and return the lowest, open, non-lava spawn point + spot = nullptr; + lowest = 999999; + pointWithLeastLava = nullptr; + for (index = 0; index < numPoints; index++) + { + if (spawnPoints[index]->s.origin[2] < lavatop) + continue; + + if (PlayersRangeFromSpot(spawnPoints[index]) > 32) + { + if (spawnPoints[index]->s.origin[2] < lowest) + { + // save the last point + pointWithLeastLava = spawnPoints[index]; + lowest = spawnPoints[index]->s.origin[2]; + } + } + } + + return pointWithLeastLava; +} +// ROGUE +//=============== + +// [Paril-KEX] +static edict_t *SelectSingleSpawnPoint(edict_t *ent) +{ + edict_t *spot = nullptr; + + while ((spot = G_FindByString<&edict_t::classname>(spot, "info_player_start")) != nullptr) + { + if (!game.spawnpoint[0] && !spot->targetname) + break; + + if (!game.spawnpoint[0] || !spot->targetname) + continue; + + if (Q_strcasecmp(game.spawnpoint, spot->targetname) == 0) + break; + } + + if (!spot) + { + // there wasn't a matching targeted spawnpoint, use one that has no targetname + while ((spot = G_FindByString<&edict_t::classname>(spot, "info_player_start")) != nullptr) + if (!spot->targetname) + return spot; + } + + // none at all, so just pick any + if (!spot) + return G_FindByString<&edict_t::classname>(spot, "info_player_start"); + + return spot; +} + +// [Paril-KEX] +static edict_t *G_UnsafeSpawnPosition(vec3_t spot) +{ + trace_t tr = gi.trace(spot, PLAYER_MINS, PLAYER_MAXS, spot, nullptr, MASK_PLAYERSOLID); + + // sometimes the spot is too close to the ground, give it a bit of slack + if (tr.startsolid && !tr.ent->client) + { + spot[2] += 1; + tr = gi.trace(spot, PLAYER_MINS, PLAYER_MAXS, spot, nullptr, MASK_PLAYERSOLID); + } + + // no idea why this happens in some maps.. + if (tr.startsolid && !tr.ent->client) + return tr.ent; + + if (tr.fraction == 1.f) + return nullptr; + else if (tr.ent->client) + return tr.ent; + + return nullptr; +} + +edict_t *SelectCoopSpawnPoint(edict_t *ent, bool force_spawn) +{ + edict_t *spot = nullptr; + const char *target; + + // ROGUE + // rogue hack, but not too gross... + if (!Q_strcasecmp(level.mapname, "rmine2")) + return SelectLavaCoopSpawnPoint(ent); + // ROGUE + + // try the main spawn point first + spot = SelectSingleSpawnPoint(ent); + + if (spot && !G_UnsafeSpawnPosition(spot->s.origin)) + return spot; + + spot = nullptr; + + // assume there are four coop spots at each spawnpoint + int32_t num_valid_spots = 0; + + while (1) + { + spot = G_FindByString<&edict_t::classname>(spot, "info_player_coop"); + if (!spot) + break; // we didn't have enough... + + target = spot->targetname; + if (!target) + target = ""; + if (Q_strcasecmp(game.spawnpoint, target) == 0) + { // this is a coop spawn point for one of the clients here + num_valid_spots++; + + if (!G_UnsafeSpawnPosition(spot->s.origin)) + return spot; // this is it + } + } + + bool use_targetname = true; + + // if we didn't find any spots, map is probably set up wrong. + // use empty targetname ones. + if (!num_valid_spots) + { + use_targetname = false; + + while (1) + { + spot = G_FindByString<&edict_t::classname>(spot, "info_player_coop"); + if (!spot) + break; // we didn't have enough... + + target = spot->targetname; + if (!target) + { + // this is a coop spawn point for one of the clients here + num_valid_spots++; + + if (!G_UnsafeSpawnPosition(spot->s.origin)) + return spot; // this is it + } + } + } + + // if player collision is disabled, just pick a random spot + if (!g_coop_player_collision->integer) + { + spot = nullptr; + + num_valid_spots = irandom(num_valid_spots); + + while (1) + { + spot = G_FindByString<&edict_t::classname>(spot, "info_player_coop"); + + if (!spot) + break; // we didn't have enough... + + target = spot->targetname; + if (use_targetname && !target) + target = ""; + if (use_targetname ? (Q_strcasecmp(game.spawnpoint, target) == 0) : !target) + { // this is a coop spawn point for one of the clients here + num_valid_spots++; + + if (!num_valid_spots) + return spot; + + --num_valid_spots; + } + } + + // if this fails, just fall through to some other spawn. + } + + // no safe spots..? + if (force_spawn || !g_coop_player_collision->integer) + return SelectSingleSpawnPoint(spot); + + return nullptr; +} + +bool TryLandmarkSpawn(edict_t* ent, vec3_t& origin, vec3_t& angles) +{ + // if transitioning from another level with a landmark seamless transition + // just set the location here + if (!ent->client->landmark_name || !strlen(ent->client->landmark_name)) + { + return false; + } + + edict_t* landmark = G_PickTarget(ent->client->landmark_name); + if (!landmark) + { + return false; + } + + vec3_t old_origin = origin; + vec3_t spot_origin = origin; + origin = ent->client->landmark_rel_pos; + + // rotate our relative landmark into our new landmark's frame of reference + origin = RotatePointAroundVector({ 1, 0, 0 }, origin, landmark->s.angles[0]); + origin = RotatePointAroundVector({ 0, 1, 0 }, origin, landmark->s.angles[2]); + origin = RotatePointAroundVector({ 0, 0, 1 }, origin, landmark->s.angles[1]); + + origin += landmark->s.origin; + + angles = ent->client->oldviewangles + landmark->s.angles; + + if (landmark->spawnflags.has(SPAWNFLAG_LANDMARK_KEEP_Z)) + origin[2] = spot_origin[2]; + + // sometimes, landmark spawns can cause slight inconsistencies in collision; + // we'll do a bit of tracing to make sure the bbox is clear + if (G_FixStuckObject_Generic(origin, PLAYER_MINS, PLAYER_MAXS, [ent] (const vec3_t &start, const vec3_t &mins, const vec3_t &maxs, const vec3_t &end) { + return gi.trace(start, mins, maxs, end, ent, MASK_PLAYERSOLID); + }) == stuck_result_t::NO_GOOD_POSITION) + { + origin = old_origin; + return false; + } + + ent->s.origin = origin; + + // rotate the velocity that we grabbed from the map + if (ent->velocity) + { + ent->velocity = RotatePointAroundVector({ 1, 0, 0 }, ent->velocity, landmark->s.angles[0]); + ent->velocity = RotatePointAroundVector({ 0, 1, 0 }, ent->velocity, landmark->s.angles[2]); + ent->velocity = RotatePointAroundVector({ 0, 0, 1 }, ent->velocity, landmark->s.angles[1]); + } + + return true; +} + +/* +=========== +SelectSpawnPoint + +Chooses a player start, deathmatch start, coop start, etc +============ +*/ +bool SelectSpawnPoint(edict_t *ent, vec3_t &origin, vec3_t &angles, bool force_spawn, bool &landmark) +{ + edict_t *spot = nullptr; + + // DM spots are simple + if (deathmatch->integer) + { + if (G_TeamplayEnabled()) + spot = SelectCTFSpawnPoint(ent, force_spawn); + else + { + select_spawn_result_t result = SelectDeathmatchSpawnPoint(g_dm_spawn_farthest->integer, force_spawn, true); + + if (!result.any_valid) + gi.Com_Error("no valid spawn points found"); + + spot = result.spot; + } + + if (spot) + { + origin = spot->s.origin + vec3_t{ 0, 0, 9 }; + angles = spot->s.angles; + + return true; + } + + return false; + } + + if (coop->integer) + { + spot = SelectCoopSpawnPoint(ent, force_spawn); + + // no open spot yet + if (!spot) + return false; + } + else + { + spot = SelectSingleSpawnPoint(ent); + + // in SP, just put us at the origin if spawn fails + if (!spot) + { + gi.Com_PrintFmt("Couldn't find spawn point {}\n", game.spawnpoint); + + origin = { 0, 0, 0 }; + angles = { 0, 0, 0 }; + + return true; + } + } + + // spot should always be non-null here + + origin = spot->s.origin; + angles = spot->s.angles; + + // check landmark + if (TryLandmarkSpawn(ent, origin, angles)) + landmark = true; + + return true; +} + +//====================================================================== + +void InitBodyQue() +{ + int i; + edict_t *ent; + + level.body_que = 0; + for (i = 0; i < BODY_QUEUE_SIZE; i++) + { + ent = G_Spawn(); + ent->classname = "bodyque"; + } +} + +DIE(body_die) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void +{ + if (self->s.modelindex == MODELINDEX_PLAYER && + self->health < self->gib_health) + { + gi.sound(self, CHAN_BODY, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0); + ThrowGibs(self, damage, { { 4, "models/objects/gibs/sm_meat/tris.md2" } }); + self->s.origin[2] -= 48; + ThrowClientHead(self, damage); + } + + if (mod.id == MOD_CRUSH) + { + // prevent explosion singularities + self->svflags = SVF_NOCLIENT; + self->takedamage = false; + self->solid = SOLID_NOT; + self->movetype = MOVETYPE_NOCLIP; + gi.linkentity(self); + } +} + +void CopyToBodyQue(edict_t *ent) +{ + // if we were completely removed, don't bother with a body + if (!ent->s.modelindex) + return; + + edict_t *body; + + // grab a body que and cycle to the next one + body = &g_edicts[game.maxclients + level.body_que + 1]; + level.body_que = (level.body_que + 1) % BODY_QUEUE_SIZE; + + // FIXME: send an effect on the removed body + + gi.unlinkentity(ent); + + gi.unlinkentity(body); + body->s = ent->s; + body->s.number = body - g_edicts; + body->s.skinnum = ent->s.skinnum & 0xFF; // only copy the client # + body->s.effects = EF_NONE; + body->s.renderfx = RF_NONE; + + body->svflags = ent->svflags; + body->absmin = ent->absmin; + body->absmax = ent->absmax; + body->size = ent->size; + body->solid = ent->solid; + body->clipmask = ent->clipmask; + body->owner = ent->owner; + body->movetype = ent->movetype; + body->health = ent->health; + body->gib_health = ent->gib_health; + body->s.event = EV_OTHER_TELEPORT; + body->velocity = ent->velocity; + body->avelocity = ent->avelocity; + body->groundentity = ent->groundentity; + body->groundentity_linkcount = ent->groundentity_linkcount; + + if (ent->takedamage) + { + body->mins = ent->mins; + body->maxs = ent->maxs; + } + else + body->mins = body->maxs = {}; + + body->die = body_die; + body->takedamage = true; + + gi.linkentity(body); +} + +void G_PostRespawn(edict_t *self) +{ + if (self->svflags & SVF_NOCLIENT) + return; + + // add a teleportation effect + self->s.event = EV_PLAYER_TELEPORT; + + // hold in place briefly + self->client->ps.pmove.pm_flags = PMF_TIME_TELEPORT; + self->client->ps.pmove.pm_time = 112; + + self->client->respawn_time = level.time; +} + +void respawn(edict_t *self) +{ + if (deathmatch->integer || coop->integer) + { + // spectators don't leave bodies + if (!self->client->resp.spectator) + CopyToBodyQue(self); + self->svflags &= ~SVF_NOCLIENT; + PutClientInServer(self); + + G_PostRespawn(self); + return; + } + + // restart the entire server + gi.AddCommandString("menu_loadgame\n"); +} + +/* + * only called when pers.spectator changes + * note that resp.spectator should be the opposite of pers.spectator here + */ +void spectator_respawn(edict_t *ent) +{ + uint32_t i, numspec; + + // if the user wants to become a spectator, make sure he doesn't + // exceed max_spectators + + if (ent->client->pers.spectator) + { + char value[MAX_INFO_VALUE] = { 0 }; + gi.Info_ValueForKey(ent->client->pers.userinfo, "spectator", value, sizeof(value)); + + if (*spectator_password->string && + strcmp(spectator_password->string, "none") && + strcmp(spectator_password->string, value)) + { + gi.LocClient_Print(ent, PRINT_HIGH, "Spectator password incorrect.\n"); + ent->client->pers.spectator = false; + gi.WriteByte(svc_stufftext); + gi.WriteString("spectator 0\n"); + gi.unicast(ent, true); + return; + } + + // count spectators + for (i = 1, numspec = 0; i <= game.maxclients; i++) + if (g_edicts[i].inuse && g_edicts[i].client->pers.spectator) + numspec++; + + if (numspec >= (uint32_t) maxspectators->integer) + { + gi.LocClient_Print(ent, PRINT_HIGH, "Server spectator limit is full."); + ent->client->pers.spectator = false; + // reset his spectator var + gi.WriteByte(svc_stufftext); + gi.WriteString("spectator 0\n"); + gi.unicast(ent, true); + return; + } + } + else + { + // he was a spectator and wants to join the game + // he must have the right password + char value[MAX_INFO_VALUE] = { 0 }; + gi.Info_ValueForKey(ent->client->pers.userinfo, "password", value, sizeof(value)); + + if (*password->string && strcmp(password->string, "none") && + strcmp(password->string, value)) + { + gi.LocClient_Print(ent, PRINT_HIGH, "Password incorrect.\n"); + ent->client->pers.spectator = true; + gi.WriteByte(svc_stufftext); + gi.WriteString("spectator 1\n"); + gi.unicast(ent, true); + return; + } + } + + // clear score on respawn + ent->client->resp.score = ent->client->pers.score = 0; + + // move us to no team + ent->client->resp.ctf_team = CTF_NOTEAM; + + // change spectator mode + ent->client->resp.spectator = ent->client->pers.spectator; + + ent->svflags &= ~SVF_NOCLIENT; + PutClientInServer(ent); + + // add a teleportation effect + if (!ent->client->pers.spectator) + { + // send effect + gi.WriteByte(svc_muzzleflash); + gi.WriteEntity(ent); + gi.WriteByte(MZ_LOGIN); + gi.multicast(ent->s.origin, MULTICAST_PVS, false); + + // hold in place briefly + ent->client->ps.pmove.pm_flags = PMF_TIME_TELEPORT; + ent->client->ps.pmove.pm_time = 112; + } + + ent->client->respawn_time = level.time; + + if (ent->client->pers.spectator) + gi.LocBroadcast_Print(PRINT_HIGH, "$g_observing", ent->client->pers.netname); + else + gi.LocBroadcast_Print(PRINT_HIGH, "$g_joined_game", ent->client->pers.netname); +} + +//============================================================== + +// [Paril-KEX] +// skinnum was historically used to pack data +// so we're going to build onto that. +void P_AssignClientSkinnum(edict_t *ent) +{ + if (ent->s.modelindex != 255) + return; + + player_skinnum_t packed; + + packed.client_num = ent->client - game.clients; + if (ent->client->pers.weapon) + packed.vwep_index = ent->client->pers.weapon->vwep_index - level.vwep_offset + 1; + else + packed.vwep_index = 0; + packed.viewheight = ent->client->ps.viewoffset.z + ent->client->ps.pmove.viewheight; + + if (coop->value) + packed.team_index = 1; // all players are teamed in coop + else if (G_TeamplayEnabled()) + packed.team_index = ent->client->resp.ctf_team; + else + packed.team_index = 0; + + if (ent->deadflag) + packed.poi_icon = 1; + else + packed.poi_icon = 0; + + ent->s.skinnum = packed.skinnum; +} + +// [Paril-KEX] send player level POI +void P_SendLevelPOI(edict_t *ent) +{ + if (!level.valid_poi) + return; + + gi.WriteByte(svc_poi); + gi.WriteShort(POI_OBJECTIVE); + gi.WriteShort(10000); + gi.WritePosition(ent->client->help_poi_location); + gi.WriteShort(ent->client->help_poi_image); + gi.WriteByte(208); + gi.WriteByte(POI_FLAG_NONE); + gi.unicast(ent, true); +} + +// [Paril-KEX] force the fog transition on the given player, +// optionally instantaneously (ignore any transition time) +void P_ForceFogTransition(edict_t *ent, bool instant) +{ + // sanity check; if we're not changing the values, don't bother + if (ent->client->fog == ent->client->pers.wanted_fog && + ent->client->heightfog == ent->client->pers.wanted_heightfog) + return; + + svc_fog_data_t fog {}; + + // check regular fog + if (ent->client->pers.wanted_fog[0] != ent->client->fog[0] || + ent->client->pers.wanted_fog[4] != ent->client->fog[4]) + { + fog.bits |= svc_fog_data_t::BIT_DENSITY; + fog.density = ent->client->pers.wanted_fog[0]; + fog.skyfactor = ent->client->pers.wanted_fog[4] * 255.f; + } + if (ent->client->pers.wanted_fog[1] != ent->client->fog[1]) + { + fog.bits |= svc_fog_data_t::BIT_R; + fog.red = ent->client->pers.wanted_fog[1] * 255.f; + } + if (ent->client->pers.wanted_fog[2] != ent->client->fog[2]) + { + fog.bits |= svc_fog_data_t::BIT_G; + fog.green = ent->client->pers.wanted_fog[2] * 255.f; + } + if (ent->client->pers.wanted_fog[3] != ent->client->fog[3]) + { + fog.bits |= svc_fog_data_t::BIT_B; + fog.blue = ent->client->pers.wanted_fog[3] * 255.f; + } + + if (!instant && ent->client->pers.fog_transition_time) + { + fog.bits |= svc_fog_data_t::BIT_TIME; + fog.time = clamp(ent->client->pers.fog_transition_time.milliseconds(), (int64_t) 0, (int64_t) std::numeric_limits::max()); + } + + // check heightfog stuff + auto &hf = ent->client->heightfog; + const auto &wanted_hf = ent->client->pers.wanted_heightfog; + + if (hf.falloff != wanted_hf.falloff) + { + fog.bits |= svc_fog_data_t::BIT_HEIGHTFOG_FALLOFF; + if (!wanted_hf.falloff) + fog.hf_falloff = 0; + else + fog.hf_falloff = wanted_hf.falloff; + } + if (hf.density != wanted_hf.density) + { + fog.bits |= svc_fog_data_t::BIT_HEIGHTFOG_DENSITY; + + if (!wanted_hf.density) + fog.hf_density = 0; + else + fog.hf_density = wanted_hf.density; + } + + if (hf.start[0] != wanted_hf.start[0]) + { + fog.bits |= svc_fog_data_t::BIT_HEIGHTFOG_START_R; + fog.hf_start_r = wanted_hf.start[0] * 255.f; + } + if (hf.start[1] != wanted_hf.start[1]) + { + fog.bits |= svc_fog_data_t::BIT_HEIGHTFOG_START_G; + fog.hf_start_g = wanted_hf.start[1] * 255.f; + } + if (hf.start[2] != wanted_hf.start[2]) + { + fog.bits |= svc_fog_data_t::BIT_HEIGHTFOG_START_B; + fog.hf_start_b = wanted_hf.start[2] * 255.f; + } + if (hf.start[3] != wanted_hf.start[3]) + { + fog.bits |= svc_fog_data_t::BIT_HEIGHTFOG_START_DIST; + fog.hf_start_dist = wanted_hf.start[3]; + } + + if (hf.end[0] != wanted_hf.end[0]) + { + fog.bits |= svc_fog_data_t::BIT_HEIGHTFOG_END_R; + fog.hf_end_r = wanted_hf.end[0] * 255.f; + } + if (hf.end[1] != wanted_hf.end[1]) + { + fog.bits |= svc_fog_data_t::BIT_HEIGHTFOG_END_G; + fog.hf_end_g = wanted_hf.end[1] * 255.f; + } + if (hf.end[2] != wanted_hf.end[2]) + { + fog.bits |= svc_fog_data_t::BIT_HEIGHTFOG_END_B; + fog.hf_end_b = wanted_hf.end[2] * 255.f; + } + if (hf.end[3] != wanted_hf.end[3]) + { + fog.bits |= svc_fog_data_t::BIT_HEIGHTFOG_END_DIST; + fog.hf_end_dist = wanted_hf.end[3]; + } + + if (fog.bits & 0xFF00) + fog.bits |= svc_fog_data_t::BIT_MORE_BITS; + + gi.WriteByte(svc_fog); + + if (fog.bits & svc_fog_data_t::BIT_MORE_BITS) + gi.WriteShort(fog.bits); + else + gi.WriteByte(fog.bits); + + if (fog.bits & svc_fog_data_t::BIT_DENSITY) + { + gi.WriteFloat(fog.density); + gi.WriteByte(fog.skyfactor); + } + if (fog.bits & svc_fog_data_t::BIT_R) + gi.WriteByte(fog.red); + if (fog.bits & svc_fog_data_t::BIT_G) + gi.WriteByte(fog.green); + if (fog.bits & svc_fog_data_t::BIT_B) + gi.WriteByte(fog.blue); + if (fog.bits & svc_fog_data_t::BIT_TIME) + gi.WriteShort(fog.time); + + if (fog.bits & svc_fog_data_t::BIT_HEIGHTFOG_FALLOFF) + gi.WriteFloat(fog.hf_falloff); + if (fog.bits & svc_fog_data_t::BIT_HEIGHTFOG_DENSITY) + gi.WriteFloat(fog.hf_density); + + if (fog.bits & svc_fog_data_t::BIT_HEIGHTFOG_START_R) + gi.WriteByte(fog.hf_start_r); + if (fog.bits & svc_fog_data_t::BIT_HEIGHTFOG_START_G) + gi.WriteByte(fog.hf_start_g); + if (fog.bits & svc_fog_data_t::BIT_HEIGHTFOG_START_B) + gi.WriteByte(fog.hf_start_b); + if (fog.bits & svc_fog_data_t::BIT_HEIGHTFOG_START_DIST) + gi.WriteLong(fog.hf_start_dist); + + if (fog.bits & svc_fog_data_t::BIT_HEIGHTFOG_END_R) + gi.WriteByte(fog.hf_end_r); + if (fog.bits & svc_fog_data_t::BIT_HEIGHTFOG_END_G) + gi.WriteByte(fog.hf_end_g); + if (fog.bits & svc_fog_data_t::BIT_HEIGHTFOG_END_B) + gi.WriteByte(fog.hf_end_b); + if (fog.bits & svc_fog_data_t::BIT_HEIGHTFOG_END_DIST) + gi.WriteLong(fog.hf_end_dist); + + gi.unicast(ent, true); + + ent->client->fog = ent->client->pers.wanted_fog; + hf = wanted_hf; +} + +// [Paril-KEX] ugly global to handle squad respawn origin +static bool use_squad_respawn = false; +static bool spawn_from_begin = false; +static vec3_t squad_respawn_position, squad_respawn_angles; + +inline void PutClientOnSpawnPoint(edict_t *ent, const vec3_t &spawn_origin, const vec3_t &spawn_angles) +{ + gclient_t *client = ent->client; + + client->ps.pmove.origin = spawn_origin; + + ent->s.origin = spawn_origin; + if (!use_squad_respawn) + ent->s.origin[2] += 1; // make sure off ground + ent->s.old_origin = ent->s.origin; + + // set the delta angle + client->ps.pmove.delta_angles = spawn_angles - client->resp.cmd_angles; + + ent->s.angles = spawn_angles; + ent->s.angles[PITCH] /= 3; + + client->ps.viewangles = ent->s.angles; + client->v_angle = ent->s.angles; + + AngleVectors(client->v_angle, client->v_forward, nullptr, nullptr); +} + +/* +=========== +PutClientInServer + +Called when a player connects to a server or respawns in +a deathmatch. +============ +*/ +void PutClientInServer(edict_t *ent) +{ + int index; + vec3_t spawn_origin, spawn_angles; + gclient_t *client; + client_persistant_t saved; + client_respawn_t resp; + + index = ent - g_edicts - 1; + client = ent->client; + + // clear velocity now, since landmark may change it + ent->velocity = {}; + + bool keepVelocity = client->landmark_name != nullptr; + + if (keepVelocity) + ent->velocity = client->oldvelocity; + + // find a spawn point + // do it before setting health back up, so farthest + // ranging doesn't count this client + bool valid_spawn = false; + bool force_spawn = client->awaiting_respawn && level.time > client->respawn_timeout; + bool is_landmark = false; + + if (use_squad_respawn) + { + spawn_origin = squad_respawn_position; + spawn_angles = squad_respawn_angles; + valid_spawn = true; + } + else if (gamerules->integer && DMGame.SelectSpawnPoint) // PGM + valid_spawn = DMGame.SelectSpawnPoint(ent, spawn_origin, spawn_angles, force_spawn); // PGM + else // PGM + valid_spawn = SelectSpawnPoint(ent, spawn_origin, spawn_angles, force_spawn, is_landmark); + + // [Paril-KEX] if we didn't get a valid spawn, hold us in + // limbo for a while until we do get one + if (!valid_spawn) + { + // only do this once per spawn + if (!client->awaiting_respawn) + { + char userinfo[MAX_INFO_STRING]; + memcpy(userinfo, client->pers.userinfo, sizeof(userinfo)); + ClientUserinfoChanged(ent, userinfo); + + client->respawn_timeout = level.time + 3_sec; + } + + // find a spot to place us + if (!level.respawn_intermission) + { + // find an intermission spot + edict_t *pt = G_FindByString<&edict_t::classname>(nullptr, "info_player_intermission"); + if (!pt) + { // the map creator forgot to put in an intermission point... + pt = G_FindByString<&edict_t::classname>(nullptr, "info_player_start"); + if (!pt) + pt = G_FindByString<&edict_t::classname>(nullptr, "info_player_deathmatch"); + } + else + { // choose one of four spots + int32_t i = irandom(4); + while (i--) + { + pt = G_FindByString<&edict_t::classname>(pt, "info_player_intermission"); + if (!pt) // wrap around the list + pt = G_FindByString<&edict_t::classname>(pt, "info_player_intermission"); + } + } + + level.intermission_origin = pt->s.origin; + level.intermission_angle = pt->s.angles; + level.respawn_intermission = true; + } + + ent->s.origin = level.intermission_origin; + ent->client->ps.pmove.origin = level.intermission_origin; + ent->client->ps.viewangles = level.intermission_angle; + + client->awaiting_respawn = true; + client->ps.pmove.pm_type = PM_FREEZE; + client->ps.rdflags = RDF_NONE; + ent->deadflag = false; + ent->solid = SOLID_NOT; + ent->movetype = MOVETYPE_NOCLIP; + ent->s.modelindex = 0; + ent->svflags |= SVF_NOCLIENT; + ent->client->ps.team_id = ent->client->resp.ctf_team; + gi.linkentity(ent); + + return; + } + + client->resp.ctf_state++; + + bool was_waiting_for_respawn = client->awaiting_respawn; + + if (client->awaiting_respawn) + ent->svflags &= ~SVF_NOCLIENT; + + client->awaiting_respawn = false; + client->respawn_timeout = 0_ms; + + char social_id[MAX_INFO_VALUE]; + Q_strlcpy(social_id, ent->client->pers.social_id, sizeof(social_id)); + + // deathmatch wipes most client data every spawn + if (deathmatch->integer) + { + client->pers.health = 0; + resp = client->resp; + } + else + { + // [Kex] Maintain user info in singleplayer to keep the player skin. + char userinfo[MAX_INFO_STRING]; + memcpy(userinfo, client->pers.userinfo, sizeof(userinfo)); + + if (coop->integer) + { + resp = client->resp; + + if (!P_UseCoopInstancedItems()) + { + resp.coop_respawn.game_help1changed = client->pers.game_help1changed; + resp.coop_respawn.game_help2changed = client->pers.game_help2changed; + resp.coop_respawn.helpchanged = client->pers.helpchanged; + client->pers = resp.coop_respawn; + } + else + { + // fix weapon + if (!client->pers.weapon) + client->pers.weapon = client->pers.lastweapon; + } + } + + ClientUserinfoChanged(ent, userinfo); + + if (coop->integer) + { + if (resp.score > client->pers.score) + client->pers.score = resp.score; + } + else + memset(&resp, 0, sizeof(resp)); + } + + // clear everything but the persistant data + saved = client->pers; + memset(client, 0, sizeof(*client)); + client->pers = saved; + client->resp = resp; + + // on a new, fresh spawn (always in DM, clear inventory + // or new spawns in SP/coop) + if (client->pers.health <= 0) + InitClientPersistant(ent, client); + + // restore social ID + Q_strlcpy(ent->client->pers.social_id, social_id, sizeof(social_id)); + + // fix level switch issue + ent->client->pers.connected = true; + + // slow time will be unset here + globals.server_flags &= ~SERVER_FLAG_SLOW_TIME; + + // copy some data from the client to the entity + FetchClientEntData(ent); + + // clear entity values + ent->groundentity = nullptr; + ent->client = &game.clients[index]; + ent->takedamage = true; + ent->movetype = MOVETYPE_WALK; + ent->viewheight = 22; + ent->inuse = true; + ent->classname = "player"; + ent->mass = 200; + ent->solid = SOLID_BBOX; + ent->deadflag = false; + ent->air_finished = level.time + 12_sec; + ent->clipmask = MASK_PLAYERSOLID; + ent->model = "players/male/tris.md2"; + ent->die = player_die; + ent->waterlevel = WATER_NONE; + ent->watertype = CONTENTS_NONE; + ent->flags &= ~( FL_NO_KNOCKBACK | FL_ALIVE_KNOCKBACK_ONLY | FL_NO_DAMAGE_EFFECTS ); + ent->svflags &= ~SVF_DEADMONSTER; + ent->svflags |= SVF_PLAYER; + + ent->flags &= ~FL_SAM_RAIMI; // PGM - turn off sam raimi flag + + ent->mins = PLAYER_MINS; + ent->maxs = PLAYER_MAXS; + + // clear playerstate values + memset(&ent->client->ps, 0, sizeof(client->ps)); + + char val[MAX_INFO_VALUE]; + gi.Info_ValueForKey(ent->client->pers.userinfo, "fov", val, sizeof(val)); + ent->client->ps.fov = clamp((float) atoi(val), 1.f, 160.f); + + ent->client->ps.pmove.viewheight = ent->viewheight; + ent->client->ps.team_id = ent->client->resp.ctf_team; + + if (!G_ShouldPlayersCollide(false)) + ent->clipmask &= ~CONTENTS_PLAYER; + + // PGM + if (client->pers.weapon) + client->ps.gunindex = gi.modelindex(client->pers.weapon->view_model); + else + client->ps.gunindex = 0; + client->ps.gunskin = 0; + // PGM + + // clear entity state values + ent->s.effects = EF_NONE; + ent->s.modelindex = MODELINDEX_PLAYER; // will use the skin specified model + ent->s.modelindex2 = MODELINDEX_PLAYER; // custom gun model + // sknum is player num and weapon number + // weapon number will be added in changeweapon + P_AssignClientSkinnum(ent); + + ent->s.frame = 0; + + PutClientOnSpawnPoint(ent, spawn_origin, spawn_angles); + + // [Paril-KEX] set up world fog & send it instantly + ent->client->pers.wanted_fog = { + world->fog.density, + world->fog.color[0], + world->fog.color[1], + world->fog.color[2], + world->fog.sky_factor + }; + ent->client->pers.wanted_heightfog = { + { world->heightfog.start_color[0], world->heightfog.start_color[1], world->heightfog.start_color[2], world->heightfog.start_dist }, + { world->heightfog.end_color[0], world->heightfog.end_color[1], world->heightfog.end_color[2], world->heightfog.end_dist }, + world->heightfog.falloff, + world->heightfog.density + }; + P_ForceFogTransition(ent, true); + + // ZOID + if (CTFStartClient(ent)) + return; + // ZOID + + // spawn a spectator + if (client->pers.spectator) + { + client->chase_target = nullptr; + + client->resp.spectator = true; + + ent->movetype = MOVETYPE_NOCLIP; + ent->solid = SOLID_NOT; + ent->svflags |= SVF_NOCLIENT; + ent->client->ps.gunindex = 0; + ent->client->ps.gunskin = 0; + gi.linkentity(ent); + return; + } + + client->resp.spectator = false; + + // [Paril-KEX] a bit of a hack, but landmark spawns can sometimes cause + // intersecting spawns, so we'll do a sanity check here... + if (spawn_from_begin) + { + if (coop->integer) + { + if (edict_t *collision = G_UnsafeSpawnPosition(ent->s.origin); collision && collision->client) + { + // link us early so that the other player sees us there + gi.linkentity(ent); + + // we spawned in somebody else, so we're going to change their spawn position + bool lm = false; + SelectSpawnPoint(collision, spawn_origin, spawn_angles, true, lm); + PutClientOnSpawnPoint(collision, spawn_origin, spawn_angles); + } + } + + // give us one (1) free fall ticket even if + // we didn't spawn from landmark + ent->client->landmark_free_fall = true; + } + + gi.linkentity(ent); + + if (!KillBox(ent, true, MOD_TELEFRAG_SPAWN)) + { // could't spawn in? + } + + // my tribute to cash's level-specific hacks. I hope I live + // up to his trailblazing cheese. + if (Q_strcasecmp(level.mapname, "rboss") == 0) + { + // if you get on to rboss in single player or coop, ensure + // the player has the nuke key. (not in DM) + if (!deathmatch->integer) + client->pers.inventory[IT_KEY_NUKE] = 1; + } + + // force the current weapon up + client->newweapon = client->pers.weapon; + ChangeWeapon(ent); + + if (was_waiting_for_respawn) + G_PostRespawn(ent); +} + +/* +===================== +ClientBeginDeathmatch + +A client has just connected to the server in +deathmatch mode, so clear everything out before starting them. +===================== +*/ +void ClientBeginDeathmatch(edict_t *ent) +{ + G_InitEdict(ent); + + // make sure we have a known default + ent->svflags |= SVF_PLAYER; + + InitClientResp(ent->client); + + // PGM + if (gamerules->integer && DMGame.ClientBegin) + { + DMGame.ClientBegin(ent); + } + // PGM + + // locate ent at a spawn point + PutClientInServer(ent); + + if (level.intermissiontime) + { + MoveClientToIntermission(ent); + } + else + { + if (!(ent->svflags & SVF_NOCLIENT)) + { + // send effect + gi.WriteByte(svc_muzzleflash); + gi.WriteEntity(ent); + gi.WriteByte(MZ_LOGIN); + gi.multicast(ent->s.origin, MULTICAST_PVS, false); + } + } + + gi.LocBroadcast_Print(PRINT_HIGH, "$g_entered_game", ent->client->pers.netname); + + // make sure all view stuff is valid + ClientEndServerFrame(ent); +} + +static void G_SetLevelEntry() +{ + if (deathmatch->integer) + return; + // map is a hub map, so we shouldn't bother tracking any of this. + // the next map will pick up as the start. + else if (level.hub_map) + return; + + level_entry_t *found_entry = nullptr; + int32_t highest_order = 0; + + for (size_t i = 0; i < MAX_LEVELS_PER_UNIT; i++) + { + level_entry_t *entry = &game.level_entries[i]; + + highest_order = max(highest_order, entry->visit_order); + + if (!strcmp(entry->map_name, level.mapname) || !*entry->map_name) + { + found_entry = entry; + break; + } + } + + if (!found_entry) + { + gi.Com_PrintFmt("WARNING: more than {} maps in unit, can't track the rest\n", MAX_LEVELS_PER_UNIT); + return; + } + + level.entry = found_entry; + Q_strlcpy(level.entry->map_name, level.mapname, sizeof(level.entry->map_name)); + + // we're visiting this map for the first time, so + // mark it in our order as being recent + if (!*level.entry->pretty_name) + { + Q_strlcpy(level.entry->pretty_name, level.level_name, sizeof(level.entry->pretty_name)); + level.entry->visit_order = highest_order + 1; + + // give all of the clients an extra life back + if (g_coop_enable_lives->integer) + for (size_t i = 0; i < game.maxclients; i++) + game.clients[i].pers.lives = min(g_coop_num_lives->integer + 1, game.clients[i].pers.lives + 1); + } + + // scan for all new maps we can go to, for secret levels + edict_t *changelevel = nullptr; + while ((changelevel = G_FindByString<&edict_t::classname>(changelevel, "target_changelevel"))) + { + if (!changelevel->map || !*changelevel->map) + continue; + + // next unit map, don't count it + if (strchr(changelevel->map, '*')) + continue; + + const char *level = strchr(changelevel->map, '+'); + + if (level) + level++; + else + level = changelevel->map; + + // don't include end screen levels + if (strstr(level, ".cin") || strstr(level, ".pcx")) + continue; + + size_t level_length; + + const char *spawnpoint = strchr(level, '$'); + + if (spawnpoint) + level_length = spawnpoint - level; + else + level_length = strlen(level); + + // make an entry for this level that we may or may not visit + level_entry_t *found_entry = nullptr; + + for (size_t i = 0; i < MAX_LEVELS_PER_UNIT; i++) + { + level_entry_t *entry = &game.level_entries[i]; + + if (!*entry->map_name || !strncmp(entry->map_name, level, level_length)) + { + found_entry = entry; + break; + } + } + + if (!found_entry) + { + gi.Com_PrintFmt("WARNING: more than {} maps in unit, can't track the rest\n", MAX_LEVELS_PER_UNIT); + return; + } + + Q_strlcpy(found_entry->map_name, level, min(level_length + 1, sizeof(found_entry->map_name))); + } +} + +/* +=========== +ClientBegin + +called when a client has finished connecting, and is ready +to be placed into the game. This will happen every level load. +============ +*/ +void ClientBegin(edict_t *ent) +{ + ent->client = game.clients + (ent - g_edicts - 1); + ent->client->awaiting_respawn = false; + ent->client->respawn_timeout = 0_ms; + + if (deathmatch->integer) + { + ClientBeginDeathmatch(ent); + return; + } + + // [Paril-KEX] set enter time now, so we can send messages slightly + // after somebody first joins + ent->client->resp.entertime = level.time; + ent->client->pers.spawned = true; + + // if there is already a body waiting for us (a loadgame), just + // take it, otherwise spawn one from scratch + if (ent->inuse) + { + // the client has cleared the client side viewangles upon + // connecting to the server, which is different than the + // state when the game is saved, so we need to compensate + // with deltaangles + ent->client->ps.pmove.delta_angles = ent->client->ps.viewangles; + } + else + { + // a spawn point will completely reinitialize the entity + // except for the persistant data that was initialized at + // ClientConnect() time + G_InitEdict(ent); + ent->classname = "player"; + InitClientResp(ent->client); + spawn_from_begin = true; + PutClientInServer(ent); + spawn_from_begin = false; + } + + // make sure we have a known default + ent->svflags |= SVF_PLAYER; + + if (level.intermissiontime) + { + MoveClientToIntermission(ent); + } + else + { + // send effect if in a multiplayer game + if (game.maxclients > 1 && !(ent->svflags & SVF_NOCLIENT)) + gi.LocBroadcast_Print(PRINT_HIGH, "$g_entered_game", ent->client->pers.netname); + } + + level.coop_scale_players++; + G_Monster_CheckCoopHealthScaling(); + + // make sure all view stuff is valid + ClientEndServerFrame(ent); + + // [Paril-KEX] send them goal, if needed + G_PlayerNotifyGoal(ent); + + // [Paril-KEX] we're going to set this here just to be certain + // that the level entry timer only starts when a player is actually + // *in* the level + G_SetLevelEntry(); +} + +/* +================ +P_GetLobbyUserNum +================ +*/ +unsigned int P_GetLobbyUserNum( const edict_t * player ) { + unsigned int playerNum = 0; + if ( player > g_edicts && player < g_edicts + MAX_EDICTS ) { + playerNum = ( player - g_edicts ) - 1; + if ( playerNum >= MAX_CLIENTS ) { + playerNum = 0; + } + } + return playerNum; +} + +/* +================ +G_EncodedPlayerName + +Gets a token version of the players "name" to be decoded on the client. +================ +*/ +std::string G_EncodedPlayerName(edict_t* player) +{ + unsigned int playernum = P_GetLobbyUserNum( player ); + return std::string("##P") + std::to_string(playernum); +} + +/* +=========== +ClientUserInfoChanged + +called whenever the player updates a userinfo variable. +============ +*/ +void ClientUserinfoChanged(edict_t *ent, const char *userinfo) +{ + // set name + if (!gi.Info_ValueForKey(userinfo, "name", ent->client->pers.netname, sizeof(ent->client->pers.netname))) + Q_strlcpy(ent->client->pers.netname, "badinfo", sizeof(ent->client->pers.netname)); + + // set spectator + char val[MAX_INFO_VALUE] = { 0 }; + gi.Info_ValueForKey(userinfo, "spectator", val, sizeof(val)); + + // spectators are only supported in deathmatch + if (deathmatch->integer && !G_TeamplayEnabled() && *val && strcmp(val, "0")) + ent->client->pers.spectator = true; + else + ent->client->pers.spectator = false; + + // set skin + if (!gi.Info_ValueForKey(userinfo, "skin", val, sizeof(val))) + Q_strlcpy(val, "male/grunt", sizeof(val)); + + int playernum = ent - g_edicts - 1; + + // combine name and skin into a configstring + // ZOID + if (G_TeamplayEnabled()) + CTFAssignSkin(ent, val); + else + { + // set dogtag + char dogtag[MAX_INFO_VALUE] = { 0 }; + gi.Info_ValueForKey(userinfo, "dogtag", dogtag, sizeof(dogtag)); + + // ZOID + gi.configstring(CS_PLAYERSKINS + playernum, G_Fmt("{}\\{}\\{}", ent->client->pers.netname, val, dogtag).data()); + } + + // ZOID + // set player name field (used in id_state view) + gi.configstring(CONFIG_CTF_PLAYER_NAME + playernum, ent->client->pers.netname); + // ZOID + + // [Kex] netname is used for a couple of other things, so we update this after those. + if ( ( ent->svflags & SVF_BOT ) == 0 ) { + Q_strlcpy( ent->client->pers.netname, G_EncodedPlayerName( ent ).c_str(), sizeof( ent->client->pers.netname ) ); + } + + // fov + gi.Info_ValueForKey(userinfo, "fov", val, sizeof(val)); + ent->client->ps.fov = clamp((float) atoi(val), 1.f, 160.f); + + // handedness + if (gi.Info_ValueForKey(userinfo, "hand", val, sizeof(val))) + { + ent->client->pers.hand = static_cast(clamp(atoi(val), (int32_t) RIGHT_HANDED, (int32_t) CENTER_HANDED)); + } + else + { + ent->client->pers.hand = RIGHT_HANDED; + } + + // [Paril-KEX] auto-switch + if (gi.Info_ValueForKey(userinfo, "autoswitch", val, sizeof(val))) + { + ent->client->pers.autoswitch = static_cast(clamp(atoi(val), (int32_t)auto_switch_t::SMART, (int32_t)auto_switch_t::NEVER)); + } + else + { + ent->client->pers.autoswitch = auto_switch_t::SMART; + } + + if (gi.Info_ValueForKey(userinfo, "autoshield", val, sizeof(val))) + { + ent->client->pers.autoshield = atoi(val); + } + else + { + ent->client->pers.autoshield = -1; + } + + // [Paril-KEX] wants bob + if (gi.Info_ValueForKey(userinfo, "bobskip", val, sizeof(val))) + { + ent->client->pers.bob_skip = val[0] == '1'; + } + else + { + ent->client->pers.bob_skip = false; + } + + // save off the userinfo in case we want to check something later + Q_strlcpy(ent->client->pers.userinfo, userinfo, sizeof(ent->client->pers.userinfo)); +} + +inline bool IsSlotIgnored(edict_t *slot, edict_t **ignore, size_t num_ignore) +{ + for (size_t i = 0; i < num_ignore; i++) + if (slot == ignore[i]) + return true; + + return false; +} + +inline edict_t *ClientChooseSlot_Any(edict_t **ignore, size_t num_ignore) +{ + for (size_t i = 0; i < game.maxclients; i++) + if (!IsSlotIgnored(globals.edicts + i + 1, ignore, num_ignore) && !game.clients[i].pers.connected) + return globals.edicts + i + 1; + + return nullptr; +} + +inline edict_t *ClientChooseSlot_Coop(const char *userinfo, const char *social_id, bool isBot, edict_t **ignore, size_t num_ignore) +{ + char name[MAX_INFO_VALUE] = { 0 }; + gi.Info_ValueForKey(userinfo, "name", name, sizeof(name)); + + // the host should always occupy slot 0, some systems rely on this + // (CHECK: is this true? is it just bots?) + { + size_t num_players = 0; + + for (size_t i = 0; i < game.maxclients; i++) + if (IsSlotIgnored(globals.edicts + i + 1, ignore, num_ignore) || game.clients[i].pers.connected) + num_players++; + + if (!num_players) + { + gi.Com_PrintFmt("coop slot {} is host {}+{}\n", 1, name, social_id); + return globals.edicts + 1; + } + } + + // grab matches from players that we have connected + using match_type_t = int32_t; + enum { + MATCH_USERNAME, + MATCH_SOCIAL, + MATCH_BOTH, + + MATCH_TYPES + }; + + struct { + edict_t *slot = nullptr; + size_t total = 0; + } matches[MATCH_TYPES]; + + for (size_t i = 0; i < game.maxclients; i++) + { + if (IsSlotIgnored(globals.edicts + i + 1, ignore, num_ignore) || game.clients[i].pers.connected) + continue; + + char check_name[MAX_INFO_VALUE] = { 0 }; + gi.Info_ValueForKey(game.clients[i].pers.userinfo, "name", check_name, sizeof(check_name)); + + bool username_match = game.clients[i].pers.userinfo[0] && + !strcmp(check_name, name); + + bool social_match = social_id && game.clients[i].pers.social_id[0] && + !strcmp(game.clients[i].pers.social_id, social_id); + + match_type_t type = (match_type_t) 0; + + if (username_match) + type |= MATCH_USERNAME; + if (social_match) + type |= MATCH_SOCIAL; + + if (!type) + continue; + + matches[type].slot = globals.edicts + i + 1; + matches[type].total++; + } + + // pick matches in descending order, only if the total matches + // is 1 in the particular set; this will prefer to pick + // social+username matches first, then social, then username last. + for (int32_t i = 2; i >= 0; i--) + { + if (matches[i].total == 1) + { + gi.Com_PrintFmt("coop slot {} restored for {}+{}\n", (ptrdiff_t) (matches[i].slot - globals.edicts), name, social_id); + + // spawn us a ghost now since we're gonna spawn eventually + if (!matches[i].slot->inuse) + { + matches[i].slot->s.modelindex = MODELINDEX_PLAYER; + matches[i].slot->solid = SOLID_BBOX; + + G_InitEdict(matches[i].slot); + matches[i].slot->classname = "player"; + InitClientResp(matches[i].slot->client); + spawn_from_begin = true; + PutClientInServer(matches[i].slot); + spawn_from_begin = false; + + // make sure we have a known default + matches[i].slot->svflags |= SVF_PLAYER; + + matches[i].slot->sv.init = true; + matches[i].slot->classname = "player"; + matches[i].slot->client->pers.connected = true; + matches[i].slot->client->pers.spawned = true; + P_AssignClientSkinnum(matches[i].slot); + gi.linkentity(matches[i].slot); + } + + return matches[i].slot; + } + } + + // in the case where we can't find a match, we're probably a new + // player, so pick a slot that hasn't been occupied yet + for (size_t i = 0; i < game.maxclients; i++) + if (!IsSlotIgnored(globals.edicts + i + 1, ignore, num_ignore) && !game.clients[i].pers.userinfo[0]) + { + gi.Com_PrintFmt("coop slot {} issuing new for {}+{}\n", i + 1, name, social_id); + return globals.edicts + i + 1; + } + + // all slots have some player data in them, we're forced to replace one. + edict_t *any_slot = ClientChooseSlot_Any(ignore, num_ignore); + + gi.Com_PrintFmt("coop slot {} any slot for {}+{}\n", !any_slot ? -1 : (ptrdiff_t) (any_slot - globals.edicts), name, social_id); + + return any_slot; +} + +// [Paril-KEX] for coop, we want to try to ensure that players will always get their +// proper slot back when they connect. +edict_t *ClientChooseSlot(const char *userinfo, const char *social_id, bool isBot, edict_t **ignore, size_t num_ignore, bool cinematic) +{ + // coop and non-bots is the only thing that we need to do special behavior on + if (!cinematic && coop->integer && !isBot) + return ClientChooseSlot_Coop(userinfo, social_id, isBot, ignore, num_ignore); + + // just find any free slot + return ClientChooseSlot_Any(ignore, num_ignore); +} + +/* +=========== +ClientConnect + +Called when a player begins connecting to the server. +The game can refuse entrance to a client by returning false. +If the client is allowed, the connection process will continue +and eventually get to ClientBegin() +Changing levels will NOT cause this to be called again, but +loadgames will. +============ +*/ +bool ClientConnect(edict_t *ent, char *userinfo, const char *social_id, bool isBot) +{ + // check to see if they are on the banned IP list +#if 0 + value = Info_ValueForKey(userinfo, "ip"); + if (SV_FilterPacket(value)) + { + Info_SetValueForKey(userinfo, "rejmsg", "Banned."); + return false; + } +#endif + + // check for a spectator + char value[MAX_INFO_VALUE] = { 0 }; + gi.Info_ValueForKey(userinfo, "spectator", value, sizeof(value)); + + if (deathmatch->integer && *value && strcmp(value, "0")) + { + uint32_t i, numspec; + + if (*spectator_password->string && + strcmp(spectator_password->string, "none") && + strcmp(spectator_password->string, value)) + { + gi.Info_SetValueForKey(userinfo, "rejmsg", "Spectator password required or incorrect."); + return false; + } + + // count spectators + for (i = numspec = 0; i < game.maxclients; i++) + if (g_edicts[i + 1].inuse && g_edicts[i + 1].client->pers.spectator) + numspec++; + + if (numspec >= (uint32_t) maxspectators->integer) + { + gi.Info_SetValueForKey(userinfo, "rejmsg", "Server spectator limit is full."); + return false; + } + } + else + { + // check for a password ( if not a bot! ) + gi.Info_ValueForKey(userinfo, "password", value, sizeof(value)); + if ( !isBot && *password->string && strcmp(password->string, "none") && + strcmp(password->string, value)) + { + gi.Info_SetValueForKey(userinfo, "rejmsg", "Password required or incorrect."); + return false; + } + } + + // they can connect + ent->client = game.clients + (ent - g_edicts - 1); + + // set up userinfo early + ClientUserinfoChanged(ent, userinfo); + + // if there is already a body waiting for us (a loadgame), just + // take it, otherwise spawn one from scratch + if (ent->inuse == false) + { + // clear the respawning variables + // ZOID -- force team join + ent->client->resp.ctf_team = CTF_NOTEAM; + ent->client->resp.id_state = true; + // ZOID + InitClientResp(ent->client); + if (!game.autosaved || !ent->client->pers.weapon) + InitClientPersistant(ent, ent->client); + } + + // make sure we start with known default(s) + ent->svflags = SVF_PLAYER; + if ( isBot ) { + ent->svflags |= SVF_BOT; + } + + Q_strlcpy(ent->client->pers.social_id, social_id, sizeof(ent->client->pers.social_id)); + + if (game.maxclients > 1) + { + // [Paril-KEX] fetch name because now netname is kinda unsuitable + gi.Info_ValueForKey(userinfo, "name", value, sizeof(value)); + gi.LocClient_Print(nullptr, PRINT_HIGH, "$g_player_connected", value); + } + + ent->client->pers.connected = true; + return true; +} + +/* +=========== +ClientDisconnect + +Called when a player drops from the server. +Will not be called between levels. +============ +*/ +void ClientDisconnect(edict_t *ent) +{ + if (!ent->client) + return; + + // ZOID + CTFDeadDropFlag(ent); + CTFDeadDropTech(ent); + // ZOID + + PlayerTrail_Destroy(ent); + + //============ + // ROGUE + // make sure no trackers are still hurting us. + if (ent->client->tracker_pain_time) + RemoveAttackingPainDaemons(ent); + + if (ent->client->owned_sphere) + { + if (ent->client->owned_sphere->inuse) + G_FreeEdict(ent->client->owned_sphere); + ent->client->owned_sphere = nullptr; + } + + if (gamerules->integer) + { + if (DMGame.PlayerDisconnect) + DMGame.PlayerDisconnect(ent); + } + // ROGUE + //============ + + // send effect + if (!(ent->svflags & SVF_NOCLIENT)) + { + gi.WriteByte(svc_muzzleflash); + gi.WriteEntity(ent); + gi.WriteByte(MZ_LOGOUT); + gi.multicast(ent->s.origin, MULTICAST_PVS, false); + } + + gi.unlinkentity(ent); + ent->s.modelindex = 0; + ent->solid = SOLID_NOT; + ent->inuse = false; + ent->sv.init = false; + ent->classname = "disconnected"; + ent->client->pers.connected = false; + ent->client->pers.spawned = false; + ent->timestamp = level.time + 1_sec; + + // update active scoreboards + if (deathmatch->integer) + for (auto player : active_players()) + if (player->client->showscores) + player->client->menutime = level.time; +} + +//============================================================== + +trace_t SV_PM_Clip(const vec3_t &start, const vec3_t *mins, const vec3_t *maxs, const vec3_t &end, contents_t mask) +{ + return gi.game_import_t::clip(world, start, mins, maxs, end, mask); +} + +bool G_ShouldPlayersCollide(bool weaponry) +{ + if (g_disable_player_collision->integer) + return false; // only for debugging. + + // always collide on dm + if (!coop->integer) + return true; + + // weaponry collides if friendly fire is enabled + if (weaponry && g_friendly_fire->integer) + return true; + + // check collision cvar + return g_coop_player_collision->integer; +} + +/* +================= +P_FallingDamage + +Paril-KEX: this is moved here and now reacts directly +to ClientThink rather than being delayed. +================= +*/ +void P_FallingDamage(edict_t *ent, const pmove_t &pm) +{ + int damage; + vec3_t dir; + + // dead stuff can't crater + if (ent->health <= 0 || ent->deadflag) + return; + + if (ent->s.modelindex != MODELINDEX_PLAYER) + return; // not in the player model + + if (ent->movetype == MOVETYPE_NOCLIP) + return; + + // never take falling damage if completely underwater + if (pm.waterlevel == WATER_UNDER) + return; + + // ZOID + // never take damage if just release grapple or on grapple + if (ent->client->ctf_grapplereleasetime >= level.time || + (ent->client->ctf_grapple && + ent->client->ctf_grapplestate > CTF_GRAPPLE_STATE_FLY)) + return; + // ZOID + + float delta = pm.impact_delta; + + delta = delta * delta * 0.0001f; + + if (pm.waterlevel == WATER_WAIST) + delta *= 0.25f; + if (pm.waterlevel == WATER_FEET) + delta *= 0.5f; + + if (delta < 1) + return; + + // restart footstep timer + ent->client->bobtime = 0; + + if (ent->client->landmark_free_fall) + { + delta = min(30.f, delta); + ent->client->landmark_free_fall = false; + ent->client->landmark_noise_time = level.time + 100_ms; + } + + if (delta < 15) + { + if (!(pm.s.pm_flags & PMF_ON_LADDER)) + ent->s.event = EV_FOOTSTEP; + return; + } + + ent->client->fall_value = delta * 0.5f; + if (ent->client->fall_value > 40) + ent->client->fall_value = 40; + ent->client->fall_time = level.time + FALL_TIME(); + + if (delta > 30) + { + if (delta >= 55) + ent->s.event = EV_FALLFAR; + else + ent->s.event = EV_FALL; + + ent->pain_debounce_time = level.time + FRAME_TIME_S; // no normal pain sound + damage = (int) ((delta - 30) / 2); + if (damage < 1) + damage = 1; + dir = { 0, 0, 1 }; + + if (!deathmatch->integer || !g_dm_no_fall_damage->integer) + T_Damage(ent, world, world, dir, ent->s.origin, vec3_origin, damage, 0, DAMAGE_NONE, MOD_FALLING); + } + else + ent->s.event = EV_FALLSHORT; + + // Paril: falling damage noises alert monsters + if (ent->health) + PlayerNoise(ent, pm.s.origin, PNOISE_SELF); +} + +bool HandleMenuMovement(edict_t *ent, usercmd_t *ucmd) +{ + if (!ent->client->menu) + return false; + + // [Paril-KEX] handle menu movement + int32_t menu_sign = ucmd->forwardmove > 0 ? 1 : ucmd->forwardmove < 0 ? -1 : 0; + + if (ent->client->menu_sign != menu_sign) + { + ent->client->menu_sign = menu_sign; + + if (menu_sign > 0) + { + PMenu_Prev(ent); + return true; + } + else if (menu_sign < 0) + { + PMenu_Next(ent); + return true; + } + } + + if (ent->client->latched_buttons & (BUTTON_ATTACK | BUTTON_JUMP)) + { + PMenu_Select(ent); + return true; + } + + return false; +} + +/* +============== +ClientThink + +This will be called once for each client frame, which will +usually be a couple times for each server frame. +============== +*/ +void ClientThink(edict_t *ent, usercmd_t *ucmd) +{ + gclient_t *client; + edict_t *other; + uint32_t i; + pmove_t pm; + + level.current_entity = ent; + client = ent->client; + + // [Paril-KEX] pass buttons through even if we are in intermission or + // chasing. + client->oldbuttons = client->buttons; + client->buttons = ucmd->buttons; + client->latched_buttons |= client->buttons & ~client->oldbuttons; + client->cmd = *ucmd; + + if ((ucmd->buttons & BUTTON_CROUCH) && pm_config.n64_physics) + { + if (client->pers.n64_crouch_warn_times < 12 && + client->pers.n64_crouch_warning < level.time && + (++client->pers.n64_crouch_warn_times % 3) == 0) + { + client->pers.n64_crouch_warning = level.time + 10_sec; + gi.LocClient_Print(ent, PRINT_CENTER, "$g_n64_crouching"); + } + } + + if (level.intermissiontime || ent->client->awaiting_respawn) + { + client->ps.pmove.pm_type = PM_FREEZE; + + bool n64_sp = false; + + if (level.intermissiontime) + { + n64_sp = !deathmatch->integer && level.is_n64; + + // can exit intermission after five seconds + // Paril: except in N64. the camera handles it. + // Paril again: except on unit exits, we can leave immediately after camera finishes + if (level.changemap && (!n64_sp || level.level_intermission_set) && level.time > level.intermissiontime + 5_sec && (ucmd->buttons & BUTTON_ANY)) + level.exitintermission = true; + } + + if (!n64_sp) + client->ps.pmove.viewheight = ent->viewheight = 22; + else + client->ps.pmove.viewheight = ent->viewheight = 0; + ent->movetype = MOVETYPE_NOCLIP; + return; + } + + if (ent->client->chase_target) + { + client->resp.cmd_angles = ucmd->angles; + ent->movetype = MOVETYPE_NOCLIP; + } + else + { + + // set up for pmove + memset(&pm, 0, sizeof(pm)); + + if (ent->movetype == MOVETYPE_NOCLIP) + { + if (ent->client->menu) + { + client->ps.pmove.pm_type = PM_FREEZE; + + // [Paril-KEX] handle menu movement + HandleMenuMovement(ent, ucmd); + } + else if (ent->client->awaiting_respawn) + client->ps.pmove.pm_type = PM_FREEZE; + else if (ent->client->resp.spectator || (G_TeamplayEnabled() && ent->client->resp.ctf_team == CTF_NOTEAM)) + client->ps.pmove.pm_type = PM_SPECTATOR; + else + client->ps.pmove.pm_type = PM_NOCLIP; + } + else if (ent->s.modelindex != MODELINDEX_PLAYER) + client->ps.pmove.pm_type = PM_GIB; + else if (ent->deadflag) + client->ps.pmove.pm_type = PM_DEAD; + else if (ent->client->ctf_grapplestate >= CTF_GRAPPLE_STATE_PULL) + client->ps.pmove.pm_type = PM_GRAPPLE; + else + client->ps.pmove.pm_type = PM_NORMAL; + + // [Paril-KEX] + if (!G_ShouldPlayersCollide(false)) + client->ps.pmove.pm_flags |= PMF_IGNORE_PLAYER_COLLISION; + else + client->ps.pmove.pm_flags &= ~PMF_IGNORE_PLAYER_COLLISION; + + // PGM trigger_gravity support + client->ps.pmove.gravity = (short) (level.gravity * ent->gravity); + pm.s = client->ps.pmove; + + pm.s.origin = ent->s.origin; + pm.s.velocity = ent->velocity; + + if (memcmp(&client->old_pmove, &pm.s, sizeof(pm.s))) + pm.snapinitial = true; + + pm.cmd = *ucmd; + pm.player = ent; + pm.trace = gi.game_import_t::trace; + pm.clip = SV_PM_Clip; + pm.pointcontents = gi.pointcontents; + pm.viewoffset = ent->client->ps.viewoffset; + + // perform a pmove + Pmove(&pm); + + if (pm.groundentity && ent->groundentity) + { + float stepsize = fabs(ent->s.origin[2] - pm.s.origin[2]); + + if (stepsize > 4.f && stepsize < STEPSIZE) + { + ent->s.renderfx |= RF_STAIR_STEP; + ent->client->step_frame = gi.ServerFrame() + 1; + } + } + + P_FallingDamage(ent, pm); + + if (ent->client->landmark_free_fall && pm.groundentity) + { + ent->client->landmark_free_fall = false; + ent->client->landmark_noise_time = level.time + 100_ms; + } + + // [Paril-KEX] save old position for G_TouchProjectiles + vec3_t old_origin = ent->s.origin; + + ent->s.origin = pm.s.origin; + ent->velocity = pm.s.velocity; + + // [Paril-KEX] if we stepped onto/off of a ladder, reset the + // last ladder pos + if ((pm.s.pm_flags & PMF_ON_LADDER) != (client->ps.pmove.pm_flags & PMF_ON_LADDER)) + { + client->last_ladder_pos = ent->s.origin; + + if (pm.s.pm_flags & PMF_ON_LADDER) + { + if (client->last_ladder_sound < level.time) + { + ent->s.event = EV_LADDER_STEP; + client->last_ladder_sound = level.time + LADDER_SOUND_TIME; + } + } + } + + // save results of pmove + client->ps.pmove = pm.s; + client->old_pmove = pm.s; + + ent->mins = pm.mins; + ent->maxs = pm.maxs; + + if (!ent->client->menu) + client->resp.cmd_angles = ucmd->angles; + + if (pm.jump_sound && !(pm.s.pm_flags & PMF_ON_LADDER)) + { + gi.sound(ent, CHAN_VOICE, gi.soundindex("*jump1.wav"), 1, ATTN_NORM, 0); + // Paril: removed to make ambushes more effective and to + // not have monsters around corners come to jumps + // PlayerNoise(ent, ent->s.origin, PNOISE_SELF); + } + + // ROGUE sam raimi cam support + if (ent->flags & FL_SAM_RAIMI) + ent->viewheight = 8; + else + ent->viewheight = (int) pm.s.viewheight; + // ROGUE + + ent->waterlevel = pm.waterlevel; + ent->watertype = pm.watertype; + ent->groundentity = pm.groundentity; + if (pm.groundentity) + ent->groundentity_linkcount = pm.groundentity->linkcount; + + if (ent->deadflag) + { + client->ps.viewangles[ROLL] = 40; + client->ps.viewangles[PITCH] = -15; + client->ps.viewangles[YAW] = client->killer_yaw; + } + else if (!ent->client->menu) + { + client->v_angle = pm.viewangles; + client->ps.viewangles = pm.viewangles; + AngleVectors(client->v_angle, client->v_forward, nullptr, nullptr); + } + + // ZOID + if (client->ctf_grapple) + CTFGrapplePull(client->ctf_grapple); + // ZOID + + gi.linkentity(ent); + + // PGM trigger_gravity support + ent->gravity = 1.0; + // PGM + + if (ent->movetype != MOVETYPE_NOCLIP) + { + G_TouchTriggers(ent); + G_TouchProjectiles(ent, old_origin); + } + + // touch other objects + for (i = 0; i < pm.touch.num; i++) + { + trace_t &tr = pm.touch.traces[i]; + other = tr.ent; + + if (other->touch) + other->touch(other, ent, tr, true); + } + } + + // fire weapon from final position if needed + if (client->latched_buttons & BUTTON_ATTACK) + { + if (client->resp.spectator) + { + client->latched_buttons = BUTTON_NONE; + + if (client->chase_target) + { + client->chase_target = nullptr; + client->ps.pmove.pm_flags &= ~(PMF_NO_POSITIONAL_PREDICTION | PMF_NO_ANGULAR_PREDICTION); + } + else + GetChaseTarget(ent); + } + else if (!ent->client->weapon_thunk) + { + // we can only do this during a ready state and + // if enough time has passed from last fire + if (ent->client->weaponstate == WEAPON_READY) + { + ent->client->weapon_fire_buffered = true; + + if (ent->client->weapon_fire_finished <= level.time) + { + ent->client->weapon_thunk = true; + Think_Weapon(ent); + } + } + } + } + + if (client->resp.spectator) + { + if (!HandleMenuMovement(ent, ucmd)) + { + if (ucmd->buttons & BUTTON_JUMP) + { + if (!(client->ps.pmove.pm_flags & PMF_JUMP_HELD)) + { + client->ps.pmove.pm_flags |= PMF_JUMP_HELD; + if (client->chase_target) + ChaseNext(ent); + else + GetChaseTarget(ent); + } + } + else + client->ps.pmove.pm_flags &= ~PMF_JUMP_HELD; + } + } + + // update chase cam if being followed + for (i = 1; i <= game.maxclients; i++) + { + other = g_edicts + i; + if (other->inuse && other->client->chase_target == ent) + UpdateChaseCam(other); + } +} + +// active monsters +struct active_monsters_filter_t +{ + inline bool operator()(edict_t *ent) const + { + return (ent->inuse && (ent->svflags & SVF_MONSTER) && ent->health > 0); + } +}; + +inline entity_iterable_t active_monsters() +{ + return entity_iterable_t { game.maxclients + (uint32_t)BODY_QUEUE_SIZE + 1U }; +} + +inline bool G_MonstersSearchingFor(edict_t *player) +{ + for (auto ent : active_monsters()) + { + // they're not targeting us, so who cares + if (ent->enemy != player) + continue; + + // they lost sight of us + if (ent->monsterinfo.aiflags & AI_LOST_SIGHT && level.time > ent->monsterinfo.trail_time + 5_sec) + continue; + + // no sir + return true; + } + + // yes sir + return false; +} + +// [Paril-KEX] from the given player, find a good spot to +// spawn a player +inline bool G_FindRespawnSpot(edict_t *player, vec3_t &spot) +{ + // sanity check; make sure there's enough room for ourselves. + // (crouching in a small area, etc) + trace_t tr = gi.trace(player->s.origin, PLAYER_MINS, PLAYER_MAXS, player->s.origin, player, MASK_PLAYERSOLID); + + if (tr.startsolid || tr.allsolid) + return false; + + // throw five boxes a short-ish distance from the player and see if they land in a good, visible spot + constexpr float yaw_spread[] = { 0, 90, 45, -45, -90 }; + constexpr float back_distance = 128.f; + constexpr float up_distance = 128.f; + constexpr float player_viewheight = 22.f; + + // we don't want to spawn inside of these + contents_t mask = MASK_PLAYERSOLID | CONTENTS_LAVA | CONTENTS_SLIME; + + for (auto &yaw : yaw_spread) + { + vec3_t angles = { 0, (player->s.angles[YAW] + 180) + yaw, 0 }; + + // throw the box three times: + // one up & back + // one back + // one up, then back + // pick the one that went the farthest + vec3_t start = player->s.origin; + vec3_t end = start + vec3_t { 0, 0, up_distance }; + + tr = gi.trace(start, PLAYER_MINS, PLAYER_MAXS, end, player, mask); + + // stuck + if (tr.startsolid || tr.allsolid || (tr.contents & (CONTENTS_LAVA | CONTENTS_SLIME))) + continue; + + vec3_t fwd; + AngleVectors(angles, fwd, nullptr, nullptr); + + start = tr.endpos; + end = start + fwd * back_distance; + + tr = gi.trace(start, PLAYER_MINS, PLAYER_MAXS, end, player, mask); + + // stuck + if (tr.startsolid || tr.allsolid || (tr.contents & (CONTENTS_LAVA | CONTENTS_SLIME))) + continue; + + // plop us down now + start = tr.endpos; + end = tr.endpos - vec3_t { 0, 0, up_distance * 4 }; + + tr = gi.trace(start, PLAYER_MINS, PLAYER_MAXS, end, player, mask); + + // stuck, or floating, or touching some other entity + if (tr.startsolid || tr.allsolid || (tr.contents & (CONTENTS_LAVA | CONTENTS_SLIME)) || tr.fraction == 1.0f || tr.ent != world) + continue; + + // don't spawn us *inside* liquids + if (gi.pointcontents(tr.endpos + vec3_t{0, 0, player_viewheight}) & MASK_WATER) + continue; + + // don't spawn us on steep slopes + if (tr.plane.normal.z < 0.7f) + continue; + + spot = tr.endpos; + + float z_diff = fabsf(player->s.origin[2] - tr.endpos[2]); + + // 5 steps is way too many steps + if (z_diff > STEPSIZE * 4.f) + continue; + + // if we went up or down 1 step, make sure we can still see their origin and their head + if (z_diff > STEPSIZE) + { + tr = gi.traceline(player->s.origin, tr.endpos, player, mask); + + if (tr.fraction != 1.0f) + continue; + + tr = gi.traceline(player->s.origin + vec3_t{0, 0, player_viewheight}, tr.endpos + vec3_t{0, 0, player_viewheight}, player, mask); + + if (tr.fraction != 1.0f) + continue; + } + + // good spot! + return true; + } + + return false; +} + +// [Paril-KEX] check each player to find a good +// respawn target & position +inline std::tuple G_FindSquadRespawnTarget() +{ + for (auto player : active_players()) + { + // no dead players + if (player->deadflag) + continue; + + // check combat state; we can't have taken damage recently + if (player->client->last_damage_time >= level.time) + { + player->client->coop_respawn_state = COOP_RESPAWN_IN_COMBAT; + continue; + } + + // check if any monsters are currently targeting us + // or searching for us + if (G_MonstersSearchingFor(player)) + { + player->client->coop_respawn_state = COOP_RESPAWN_IN_COMBAT; + continue; + } + + // check positioning; we must be on world ground + if (player->groundentity != world) + { + player->client->coop_respawn_state = COOP_RESPAWN_BAD_AREA; + continue; + } + + // can't be in liquid + if (player->waterlevel >= WATER_UNDER) + { + player->client->coop_respawn_state = COOP_RESPAWN_BAD_AREA; + continue; + } + + // good player; pick a spot + vec3_t spot; + + if (!G_FindRespawnSpot(player, spot)) + { + player->client->coop_respawn_state = COOP_RESPAWN_BLOCKED; + continue; + } + + // good player most likely + return { player, spot }; + } + + // no good player + return { nullptr, {} }; +} + +enum respawn_state_t +{ + RESPAWN_NONE, // invalid state + RESPAWN_SPECTATE, // move to spectator + RESPAWN_SQUAD, // move to good squad point + RESPAWN_START // move to start of map +}; + +// [Paril-KEX] return false to fall back to click-to-respawn behavior. +// note that this is only called if they are allowed to respawn (not +// restarting the level due to all being dead) +static bool G_CoopRespawn(edict_t *ent) +{ + // don't do this in non-coop + if (!coop->integer) + return false; + // if we don't have squad or lives, it doesn't matter + else if (!g_coop_squad_respawn->integer && !g_coop_enable_lives->integer) + return false; + + respawn_state_t state = RESPAWN_NONE; + + // first pass: if we have no lives left, just move to spectator + if (g_coop_enable_lives->integer) + { + if (ent->client->pers.lives == 0) + { + state = RESPAWN_SPECTATE; + ent->client->coop_respawn_state = COOP_RESPAWN_NO_LIVES; + } + } + + // second pass: check for where to spawn + if (state == RESPAWN_NONE) + { + // if squad respawn, don't respawn until we can find a good player to spawn on. + if (g_coop_squad_respawn->integer) + { + bool allDead = true; + + for (auto player : active_players()) + { + if (player->health > 0) + { + allDead = false; + break; + } + } + + // all dead, so if we ever get here we have lives enabled; + // we should just respawn at the start of the level + if (allDead) + state = RESPAWN_START; + else + { + auto [ good_player, good_spot ] = G_FindSquadRespawnTarget(); + + if (good_player) { + state = RESPAWN_SQUAD; + + squad_respawn_position = good_spot; + squad_respawn_angles = good_player->s.angles; + squad_respawn_angles[2] = 0; + + use_squad_respawn = true; + } else { + state = RESPAWN_SPECTATE; + } + } + } + else + state = RESPAWN_START; + } + + if (state == RESPAWN_SQUAD || state == RESPAWN_START) + { + // give us our max health back since it will reset + // to pers.health; in instanced items we'd lose the items + // we touched so we always want to respawn with our max. + if (P_UseCoopInstancedItems()) + ent->client->pers.health = ent->client->pers.max_health = ent->max_health; + + respawn(ent); + + ent->client->latched_buttons = BUTTON_NONE; + use_squad_respawn = false; + } + else if (state == RESPAWN_SPECTATE) + { + if (!ent->client->coop_respawn_state) + ent->client->coop_respawn_state = COOP_RESPAWN_WAITING; + + if (!ent->client->resp.spectator) + { + // move us to spectate just so we don't have to twiddle + // our thumbs forever + CopyToBodyQue(ent); + ent->client->resp.spectator = true; + ent->solid = SOLID_NOT; + ent->takedamage = false; + ent->s.modelindex = 0; + ent->svflags |= SVF_NOCLIENT; + ent->client->ps.damage_blend[3] = ent->client->ps.screen_blend[3] = 0; + ent->client->ps.rdflags = RDF_NONE; + ent->movetype = MOVETYPE_NOCLIP; + // TODO: check if anything else needs to be reset + gi.linkentity(ent); + GetChaseTarget(ent); + } + } + + return true; +} + +/* +============== +ClientBeginServerFrame + +This will be called once for each server frame, before running +any other entities in the world. +============== +*/ +void ClientBeginServerFrame(edict_t *ent) +{ + gclient_t *client; + int buttonMask; + + if (gi.ServerFrame() != ent->client->step_frame) + ent->s.renderfx &= ~RF_STAIR_STEP; + + if (level.intermissiontime) + return; + + client = ent->client; + + if (client->awaiting_respawn) + { + if ((level.time.milliseconds() % 500) == 0) + PutClientInServer(ent); + return; + } + + if ( ( ent->svflags & SVF_BOT ) != 0 ) { + Bot_BeginFrame( ent ); + } + + if (deathmatch->integer && !G_TeamplayEnabled() && + client->pers.spectator != client->resp.spectator && + (level.time - client->respawn_time) >= 5_sec) + { + spectator_respawn(ent); + return; + } + + // run weapon animations if it hasn't been done by a ucmd_t + if (!client->weapon_thunk && !client->resp.spectator) + Think_Weapon(ent); + else + client->weapon_thunk = false; + + if (ent->deadflag) + { + // don't respawn if level is waiting to restart + if (level.time > client->respawn_time && !level.coop_level_restart_time) + { + // check for coop handling + if (!G_CoopRespawn(ent)) + { + // in deathmatch, only wait for attack button + if (deathmatch->integer) + buttonMask = BUTTON_ATTACK; + else + buttonMask = -1; + + if ((client->latched_buttons & buttonMask) || + (deathmatch->integer && g_dm_force_respawn->integer)) + { + respawn(ent); + client->latched_buttons = BUTTON_NONE; + } + } + } + return; + } + + // add player trail so monsters can follow + if (!deathmatch->integer) + PlayerTrail_Add(ent); + + client->latched_buttons = BUTTON_NONE; +} +/* +============== +RemoveAttackingPainDaemons + +This is called to clean up the pain daemons that the disruptor attaches +to clients to damage them. +============== +*/ +void RemoveAttackingPainDaemons(edict_t *self) +{ + edict_t *tracker; + + tracker = G_FindByString<&edict_t::classname>(nullptr, "pain daemon"); + while (tracker) + { + if (tracker->enemy == self) + G_FreeEdict(tracker); + tracker = G_FindByString<&edict_t::classname>(tracker, "pain daemon"); + } + + if (self->client) + self->client->tracker_pain_time = 0_ms; +} diff --git a/rerelease/p_hud.cpp b/rerelease/p_hud.cpp new file mode 100644 index 0000000..44cd6e2 --- /dev/null +++ b/rerelease/p_hud.cpp @@ -0,0 +1,1124 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +#include "g_local.h" +#include "g_statusbar.h" + +/* +====================================================================== + +INTERMISSION + +====================================================================== +*/ + +void DeathmatchScoreboard(edict_t *ent); + +void MoveClientToIntermission(edict_t *ent) +{ + // [Paril-KEX] + if (ent->client->ps.pmove.pm_type != PM_FREEZE) + ent->s.event = EV_OTHER_TELEPORT; + if (deathmatch->integer) + ent->client->showscores = true; + ent->s.origin = level.intermission_origin; + ent->client->ps.pmove.origin = level.intermission_origin; + ent->client->ps.viewangles = level.intermission_angle; + ent->client->ps.pmove.pm_type = PM_FREEZE; + ent->client->ps.gunindex = 0; + ent->client->ps.gunskin = 0; + ent->client->ps.damage_blend[3] = ent->client->ps.screen_blend[3] = 0; + ent->client->ps.rdflags = RDF_NONE; + + // clean up powerup info + ent->client->quad_time = 0_ms; + ent->client->invincible_time = 0_ms; + ent->client->breather_time = 0_ms; + ent->client->enviro_time = 0_ms; + ent->client->invisible_time = 0_ms; + ent->client->grenade_blew_up = false; + ent->client->grenade_time = 0_ms; + + ent->client->showhelp = false; + ent->client->showscores = false; + + globals.server_flags &= ~SERVER_FLAG_SLOW_TIME; + + // RAFAEL + ent->client->quadfire_time = 0_ms; + // RAFAEL + // ROGUE + ent->client->ir_time = 0_ms; + ent->client->nuke_time = 0_ms; + ent->client->double_time = 0_ms; + ent->client->tracker_pain_time = 0_ms; + // ROGUE + + ent->viewheight = 0; + ent->s.modelindex = 0; + ent->s.modelindex2 = 0; + ent->s.modelindex3 = 0; + ent->s.modelindex = 0; + ent->s.effects = EF_NONE; + ent->s.sound = 0; + ent->solid = SOLID_NOT; + ent->movetype = MOVETYPE_NOCLIP; + + gi.linkentity(ent); + + // add the layout + + if (deathmatch->integer) + { + DeathmatchScoreboard(ent); + ent->client->showscores = true; + } +} + +// [Paril-KEX] update the level entry for end-of-unit screen +void G_UpdateLevelEntry() +{ + if (!level.entry) + return; + + level.entry->found_secrets = level.found_secrets; + level.entry->total_secrets = level.total_secrets; + level.entry->killed_monsters = level.killed_monsters; + level.entry->total_monsters = level.total_monsters; +} + +inline void G_EndOfUnitEntry(std::stringstream &layout, const int &y, const level_entry_t &entry) +{ + layout << G_Fmt("yv {} ", y); + + // we didn't visit this level, so print it as an unknown entry + if (!*entry.pretty_name) + { + layout << "table_row 1 ??? "; + return; + } + + layout << G_Fmt("table_row 4 \"{}\" ", entry.pretty_name) << + G_Fmt("{}/{} ", entry.killed_monsters, entry.total_monsters) << + G_Fmt("{}/{} ", entry.found_secrets, entry.total_secrets); + + int32_t minutes = entry.time.milliseconds() / 60000; + int32_t seconds = (entry.time.milliseconds() / 1000) % 60; + int32_t milliseconds = entry.time.milliseconds() % 1000; + + layout << G_Fmt("{:02}:{:02}:{:03} ", minutes, seconds, milliseconds); +} + +void G_EndOfUnitMessage() +{ + // [Paril-KEX] update game level entry + G_UpdateLevelEntry(); + + std::stringstream layout; + + // sort entries + std::sort(game.level_entries.begin(), game.level_entries.end(), [](const level_entry_t &a, const level_entry_t &b) { + int32_t a_order = a.visit_order ? a.visit_order : (*a.pretty_name ? (MAX_LEVELS_PER_UNIT + 1) : (MAX_LEVELS_PER_UNIT + 2)); + int32_t b_order = b.visit_order ? b.visit_order : (*b.pretty_name ? (MAX_LEVELS_PER_UNIT + 1) : (MAX_LEVELS_PER_UNIT + 2)); + + return a_order < b_order; + }); + + layout << "start_table 4 $m_eou_level $m_eou_kills $m_eou_secrets $m_eou_time "; + + int y = 16; + level_entry_t totals {}; + int32_t num_rows = 0; + + for (auto &entry : game.level_entries) + { + if (!*entry.map_name) + break; + + G_EndOfUnitEntry(layout, y, entry); + + y += 8; + + totals.found_secrets += entry.found_secrets; + totals.killed_monsters += entry.killed_monsters; + totals.time += entry.time; + totals.total_monsters += entry.total_monsters; + totals.total_secrets += entry.total_secrets; + + if (entry.visit_order) + num_rows++; + } + + y += 8; + + // make this a space so it prints totals + if (num_rows > 1) + { + layout << "table_row 0 "; // empty row to separate totals + totals.pretty_name[0] = ' '; + G_EndOfUnitEntry(layout, y, totals); + } + + layout << "xv 160 yt 0 draw_table "; + + layout << "ifgef " << (level.intermission_server_frame + (5_sec).frames()) << " yb -48 xv 0 loc_cstring2 0 \"$m_eou_press_button\" endif "; + + gi.WriteByte(svc_layout); + gi.WriteString(layout.str().c_str()); + gi.multicast(vec3_origin, MULTICAST_ALL, true); + + for (auto player : active_players()) + player->client->showeou = true; +} + +// data is binary now. +// u8 num_teams +// u8 num_players +// [ repeat num_teams: +// string team_name +// ] +// [ repeat num_players: +// u8 client_index +// s32 score +// u8 ranking +// (if num_teams > 0) +// u8 team +// ] +void G_ReportMatchDetails(bool is_end) +{ + static std::array player_ranks; + + player_ranks = {}; + + // CTF/TDM is simple + if (ctf->integer || teamplay->integer) + { + CTFCalcRankings(player_ranks); + + gi.WriteByte(2); + gi.WriteString("RED TEAM"); // team 0 + gi.WriteString("BLUE TEAM"); // team 1 + } + else + { + // sort players by score, then match everybody to + // the current highest score downwards until we run out of players. + static std::array sorted_players; + size_t num_active_players = 0; + + for (auto player : active_players()) + sorted_players[num_active_players++] = player; + + std::sort(sorted_players.begin(), sorted_players.begin() + num_active_players, [](const edict_t *a, const edict_t *b) { return b->client->resp.score < a->client->resp.score; }); + + int32_t current_score = INT_MIN; + int32_t current_rank = 0; + + for (size_t i = 0; i < num_active_players; i++) + { + if (!current_rank || sorted_players[i]->client->resp.score != current_score) + { + current_rank++; + current_score = sorted_players[i]->client->resp.score; + } + + player_ranks[sorted_players[i]->s.number - 1] = current_rank; + } + + gi.WriteByte(0); + } + + uint8_t num_players = 0; + + for (auto player : active_players()) + { + // leave spectators out of this data, they don't need to be seen. + if (player->client->pers.spawned && !player->client->resp.spectator) + { + // just in case... + if (G_TeamplayEnabled() && player->client->resp.ctf_team == CTF_NOTEAM) + continue; + + num_players++; + } + } + + gi.WriteByte(num_players); + + for (auto player : active_players()) + { + // leave spectators out of this data, they don't need to be seen. + if (player->client->pers.spawned && !player->client->resp.spectator) + { + // just in case... + if (G_TeamplayEnabled() && player->client->resp.ctf_team == CTF_NOTEAM) + continue; + + gi.WriteByte(player->s.number - 1); + gi.WriteLong(player->client->resp.score); + gi.WriteByte(player_ranks[player->s.number - 1]); + + if (G_TeamplayEnabled()) + gi.WriteByte(player->client->resp.ctf_team == CTF_TEAM1 ? 0 : 1); + } + } + + gi.ReportMatchDetails_Multicast(is_end); +} + +void BeginIntermission(edict_t *targ) +{ + edict_t *ent, *client; + + if (level.intermissiontime) + return; // already activated + + // ZOID + if (ctf->integer) + CTFCalcScores(); + // ZOID + + game.autosaved = false; + + // respawn any dead clients + for (uint32_t i = 0; i < game.maxclients; i++) + { + client = g_edicts + 1 + i; + if (!client->inuse) + continue; + if (client->health <= 0) + respawn(client); + } + + level.intermissiontime = level.time; + level.intermission_server_frame = gi.ServerFrame(); + level.changemap = targ->map; + level.intermission_clear = targ->spawnflags.has(SPAWNFLAG_CHANGELEVEL_CLEAR_INVENTORY); + level.intermission_eou = false; + level.intermission_fade = targ->spawnflags.has(SPAWNFLAG_CHANGELEVEL_FADE_OUT); + + // destroy all player trails + PlayerTrail_Destroy(nullptr); + + // [Paril-KEX] update game level entry + G_UpdateLevelEntry(); + + if (strstr(level.changemap, "*")) + { + if (coop->integer) + { + for (uint32_t i = 0; i < game.maxclients; i++) + { + client = g_edicts + 1 + i; + if (!client->inuse) + continue; + // strip players of all keys between units + for (uint32_t n = 0; n < IT_TOTAL; n++) + if (itemlist[n].flags & IF_KEY) + client->client->pers.inventory[n] = 0; + } + } + + if (level.achievement && level.achievement[0]) + { + gi.WriteByte(svc_achievement); + gi.WriteString(level.achievement); + gi.multicast(vec3_origin, MULTICAST_ALL, true); + } + + level.intermission_eou = true; + + // "no end of unit" maps handle intermission differently + if (!targ->spawnflags.has(SPAWNFLAG_CHANGELEVEL_NO_END_OF_UNIT)) + G_EndOfUnitMessage(); + else if (targ->spawnflags.has(SPAWNFLAG_CHANGELEVEL_IMMEDIATE_LEAVE) && !deathmatch->integer) + { + // Need to call this now + G_ReportMatchDetails(true); + level.exitintermission = 1; // go immediately to the next level + return; + } + } + else + { + if (!deathmatch->integer) + { + level.exitintermission = 1; // go immediately to the next level + return; + } + } + + // Call while intermission is running + G_ReportMatchDetails(true); + + level.exitintermission = 0; + + if (!level.level_intermission_set) + { + // find an intermission spot + ent = G_FindByString<&edict_t::classname>(nullptr, "info_player_intermission"); + if (!ent) + { // the map creator forgot to put in an intermission point... + ent = G_FindByString<&edict_t::classname>(nullptr, "info_player_start"); + if (!ent) + ent = G_FindByString<&edict_t::classname>(nullptr, "info_player_deathmatch"); + } + else + { // choose one of four spots + int32_t i = irandom(4); + while (i--) + { + ent = G_FindByString<&edict_t::classname>(ent, "info_player_intermission"); + if (!ent) // wrap around the list + ent = G_FindByString<&edict_t::classname>(ent, "info_player_intermission"); + } + } + + level.intermission_origin = ent->s.origin; + level.intermission_angle = ent->s.angles; + } + + // move all clients to the intermission point + for (uint32_t i = 0; i < game.maxclients; i++) + { + client = g_edicts + 1 + i; + if (!client->inuse) + continue; + MoveClientToIntermission(client); + } +} + +constexpr size_t MAX_SCOREBOARD_SIZE = 1024; + +/* +================== +DeathmatchScoreboardMessage + +================== +*/ +void DeathmatchScoreboardMessage(edict_t *ent, edict_t *killer) +{ + static std::string entry, string; + size_t j; + int sorted[MAX_CLIENTS]; + int sortedscores[MAX_CLIENTS]; + int score; + int x, y; + gclient_t *cl; + edict_t *cl_ent; + const char *tag; + + // ZOID + if (G_TeamplayEnabled()) + { + CTFScoreboardMessage(ent, killer); + return; + } + // ZOID + + entry.clear(); + string.clear(); + + // sort the clients by score + uint32_t total = 0; + for (uint32_t i = 0; i < game.maxclients; i++) + { + cl_ent = g_edicts + 1 + i; + if (!cl_ent->inuse || game.clients[i].resp.spectator) + continue; + score = game.clients[i].resp.score; + for (j = 0; j < total; j++) + { + if (score > sortedscores[j]) + break; + } + for (uint32_t k = total; k > j; k--) + { + sorted[k] = sorted[k - 1]; + sortedscores[k] = sortedscores[k - 1]; + } + sorted[j] = i; + sortedscores[j] = score; + total++; + } + + // add the clients in sorted order + if (total > 16) + total = 16; + + for (uint32_t i = 0; i < total; i++) + { + cl = &game.clients[sorted[i]]; + cl_ent = g_edicts + 1 + sorted[i]; + + x = (i >= 8) ? 130 : -72; + y = 0 + 32 * (i % 8); + + // add a dogtag + // [Paril-KEX] use dynamic dogtags + tag = nullptr; + + //=============== + // ROGUE + // allow new DM games to override the tag picture + if (gamerules->integer) + { + if (DMGame.DogTag) + DMGame.DogTag(cl_ent, killer, &tag); + } + // ROGUE + //=============== + + if (tag) + { + fmt::format_to(std::back_inserter(entry), FMT_STRING("xv {} yv {} picn {} "), x + 32, y, tag); + + if (string.length() + entry.length() > MAX_SCOREBOARD_SIZE) + break; + + string += entry; + } + else + { + fmt::format_to(std::back_inserter(entry), FMT_STRING("xv {} yv {} dogtag {} "), x + 32, y, sorted[i]); + + if (string.length() + entry.length() > MAX_SCOREBOARD_SIZE) + break; + + string += entry; + } + + entry.clear(); + + fmt::format_to(std::back_inserter(entry), + FMT_STRING("client {} {} {} {} {} {} "), + x, y, sorted[i], cl->resp.score, cl->ping, (int32_t) (level.time - cl->resp.entertime).minutes()); + + if (string.length() + entry.length() > MAX_SCOREBOARD_SIZE) + break; + + string += entry; + + entry.clear(); + } + + // [Paril-KEX] time & frags + if (fraglimit->integer) + { + fmt::format_to(std::back_inserter(string), FMT_STRING("xv -20 yv -10 loc_string2 1 $g_score_frags \"{}\" "), fraglimit->integer); + } + if (timelimit->value && !level.intermissiontime) + { + fmt::format_to(std::back_inserter(string), FMT_STRING("xv 340 yv -10 time_limit {} "), gi.ServerFrame() + ((gtime_t::from_min(timelimit->value) - level.time)).milliseconds() / gi.frame_time_ms); + } + + if (level.intermissiontime) + fmt::format_to(std::back_inserter(string), FMT_STRING("ifgef {} yb -48 xv 0 loc_cstring2 0 \"$m_eou_press_button\" endif "), (level.intermission_server_frame + (5_sec).frames())); + + gi.WriteByte(svc_layout); + gi.WriteString(string.c_str()); +} + +/* +================== +DeathmatchScoreboard + +Draw instead of help message. +Note that it isn't that hard to overflow the 1400 byte message limit! +================== +*/ +void DeathmatchScoreboard(edict_t *ent) +{ + DeathmatchScoreboardMessage(ent, ent->enemy); + gi.unicast(ent, true); + ent->client->menutime = level.time + 3_sec; +} + +/* +================== +Cmd_Score_f + +Display the scoreboard +================== +*/ +void Cmd_Score_f(edict_t *ent) +{ + if (level.intermissiontime) + return; + + ent->client->showinventory = false; + ent->client->showhelp = false; + + globals.server_flags &= ~SERVER_FLAG_SLOW_TIME; + + // ZOID + if (ent->client->menu) + PMenu_Close(ent); + // ZOID + + if (!deathmatch->integer && !coop->integer) + return; + + if (ent->client->showscores) + { + ent->client->showscores = false; + ent->client->update_chase = true; + return; + } + + ent->client->showscores = true; + DeathmatchScoreboard(ent); +} + +/* +================== +HelpComputer + +Draw help computer. +================== +*/ +void HelpComputer(edict_t *ent) +{ + const char *sk; + + if (skill->integer == 0) + sk = "$m_easy"; + else if (skill->integer == 1) + sk = "$m_medium"; + else if (skill->integer == 2) + sk = "$m_hard"; + else + sk = "$m_nightmare"; + + // send the layout + + std::string helpString = ""; + helpString += G_Fmt( + "xv 32 yv 8 picn help " // background + "xv 0 yv 25 cstring2 \"{}\" ", // level name + level.level_name); + + if (level.is_n64) + { + helpString += G_Fmt("xv 0 yv 54 loc_cstring 1 \"{{}}\" \"{}\" ", // help 1 + game.helpmessage1); + } + else + { + int y = 54; + if (strlen(game.helpmessage1)) + { + helpString += G_Fmt("xv 0 yv {} loc_cstring2 0 \"$g_pc_primary_objective\" " // title + "xv 0 yv {} loc_cstring 0 \"{}\" ", + y, + y + 11, + game.helpmessage1); + + y += 58; + } + + if (strlen(game.helpmessage2)) + { + helpString += G_Fmt("xv 0 yv {} loc_cstring2 0 \"$g_pc_secondary_objective\" " // title + "xv 0 yv {} loc_cstring 0 \"{}\" ", + y, + y + 11, + game.helpmessage2); + } + + } + + helpString += G_Fmt("xv 55 yv 164 loc_string2 0 \"{}\" " + "xv 265 yv 164 loc_rstring2 1 \"{{}}: {}/{}\" \"$g_pc_goals\" " + "xv 55 yv 172 loc_string2 1 \"{{}}: {}/{}\" \"$g_pc_kills\" " + "xv 265 yv 172 loc_rstring2 1 \"{{}}: {}/{}\" \"$g_pc_secrets\" ", + sk, + level.found_goals, level.total_goals, + level.killed_monsters, level.total_monsters, + level.found_secrets, level.total_secrets); + + gi.WriteByte(svc_layout); + gi.WriteString(helpString.c_str()); + gi.unicast(ent, true); +} + +/* +================== +Cmd_Help_f + +Display the current help message +================== +*/ +void Cmd_Help_f(edict_t *ent) +{ + // this is for backwards compatability + if (deathmatch->integer) + { + Cmd_Score_f(ent); + return; + } + + if (level.intermissiontime) + return; + + ent->client->showinventory = false; + ent->client->showscores = false; + + if (ent->client->showhelp && + (ent->client->pers.game_help1changed == game.help1changed || + ent->client->pers.game_help2changed == game.help2changed)) + { + ent->client->showhelp = false; + globals.server_flags &= ~SERVER_FLAG_SLOW_TIME; + return; + } + + ent->client->showhelp = true; + ent->client->pers.helpchanged = 0; + globals.server_flags |= SERVER_FLAG_SLOW_TIME; + HelpComputer(ent); +} + +//======================================================================= + +// [Paril-KEX] for stats we want to always be set in coop +// even if we're spectating +void G_SetCoopStats(edict_t *ent) +{ + if (coop->integer && g_coop_enable_lives->integer) + ent->client->ps.stats[STAT_LIVES] = ent->client->pers.lives + 1; + else + ent->client->ps.stats[STAT_LIVES] = 0; + + // stat for text on what we're doing for respawn + if (ent->client->coop_respawn_state) + ent->client->ps.stats[STAT_COOP_RESPAWN] = CONFIG_COOP_RESPAWN_STRING + (ent->client->coop_respawn_state - COOP_RESPAWN_IN_COMBAT); + else + ent->client->ps.stats[STAT_COOP_RESPAWN] = 0; +} + +struct powerup_info_t +{ + item_id_t item; + gtime_t gclient_t::*time_ptr = nullptr; + int32_t gclient_t::*count_ptr = nullptr; +} powerup_table[] = { + { IT_ITEM_QUAD, &gclient_t::quad_time }, + { IT_ITEM_QUADFIRE, &gclient_t::quadfire_time }, + { IT_ITEM_DOUBLE, &gclient_t::double_time }, + { IT_ITEM_INVULNERABILITY, &gclient_t::invincible_time }, + { IT_ITEM_INVISIBILITY, &gclient_t::invisible_time }, + { IT_ITEM_ENVIROSUIT, &gclient_t::enviro_time }, + { IT_ITEM_REBREATHER, &gclient_t::breather_time }, + { IT_ITEM_IR_GOGGLES, &gclient_t::ir_time }, + { IT_ITEM_SILENCER, nullptr, &gclient_t::silencer_shots } +}; + +/* +=============== +G_SetStats +=============== +*/ +void G_SetStats(edict_t *ent) +{ + gitem_t *item; + item_id_t index; + int cells = 0; + item_id_t power_armor_type; + unsigned int invIndex; + + // + // health + // + if (ent->s.renderfx & RF_USE_DISGUISE) + ent->client->ps.stats[STAT_HEALTH_ICON] = level.disguise_icon; + else + ent->client->ps.stats[STAT_HEALTH_ICON] = level.pic_health; + ent->client->ps.stats[STAT_HEALTH] = ent->health; + + // + // weapons + // + uint32_t weaponbits = 0; + + for (invIndex = IT_WEAPON_GRAPPLE; invIndex <= IT_WEAPON_DISRUPTOR; invIndex++) + { + if (ent->client->pers.inventory[invIndex]) + { + weaponbits |= 1 << GetItemByIndex((item_id_t) invIndex)->weapon_wheel_index; + } + } + + ent->client->ps.stats[STAT_WEAPONS_OWNED_1] = (weaponbits & 0xFFFF); + ent->client->ps.stats[STAT_WEAPONS_OWNED_2] = (weaponbits >> 16); + + ent->client->ps.stats[STAT_ACTIVE_WHEEL_WEAPON] = (ent->client->newweapon ? ent->client->newweapon->weapon_wheel_index : + ent->client->pers.weapon ? ent->client->pers.weapon->weapon_wheel_index : + -1); + + // + // ammo + // + ent->client->ps.stats[STAT_AMMO_ICON] = 0; + ent->client->ps.stats[STAT_AMMO] = 0; + + if (ent->client->pers.weapon && ent->client->pers.weapon->ammo) + { + item = GetItemByIndex(ent->client->pers.weapon->ammo); + + if (!G_CheckInfiniteAmmo(item)) + { + ent->client->ps.stats[STAT_AMMO_ICON] = gi.imageindex(item->icon); + ent->client->ps.stats[STAT_AMMO] = ent->client->pers.inventory[ent->client->pers.weapon->ammo]; + } + } + + memset(&ent->client->ps.stats[STAT_AMMO_INFO_START], 0, sizeof(uint16_t) * NUM_AMMO_STATS); + for (unsigned int ammoIndex = AMMO_BULLETS; ammoIndex < AMMO_MAX; ++ammoIndex) + { + gitem_t *ammo = GetItemByAmmo((ammo_t) ammoIndex); + uint16_t val = G_CheckInfiniteAmmo(ammo) ? AMMO_VALUE_INFINITE : clamp(ent->client->pers.inventory[ammo->id], 0, AMMO_VALUE_INFINITE - 1); + G_SetAmmoStat((uint16_t *) &ent->client->ps.stats[STAT_AMMO_INFO_START], ammo->ammo_wheel_index, val); + } + + // + // armor + // + power_armor_type = PowerArmorType(ent); + if (power_armor_type) + cells = ent->client->pers.inventory[IT_AMMO_CELLS]; + + index = ArmorIndex(ent); + if (power_armor_type && (!index || (level.time.milliseconds() % 3000) < 1500)) + { // flash between power armor and other armor icon + ent->client->ps.stats[STAT_ARMOR_ICON] = power_armor_type == IT_ITEM_POWER_SHIELD ? gi.imageindex("i_powershield") : gi.imageindex("i_powerscreen"); + ent->client->ps.stats[STAT_ARMOR] = cells; + } + else if (index) + { + item = GetItemByIndex(index); + ent->client->ps.stats[STAT_ARMOR_ICON] = gi.imageindex(item->icon); + ent->client->ps.stats[STAT_ARMOR] = ent->client->pers.inventory[index]; + } + else + { + ent->client->ps.stats[STAT_ARMOR_ICON] = 0; + ent->client->ps.stats[STAT_ARMOR] = 0; + } + + // + // pickup message + // + if (level.time > ent->client->pickup_msg_time) + { + ent->client->ps.stats[STAT_PICKUP_ICON] = 0; + ent->client->ps.stats[STAT_PICKUP_STRING] = 0; + } + + // owned powerups + memset(&ent->client->ps.stats[STAT_POWERUP_INFO_START], 0, sizeof(uint16_t) * NUM_POWERUP_STATS); + for (unsigned int powerupIndex = POWERUP_SCREEN; powerupIndex < POWERUP_MAX; ++powerupIndex) + { + gitem_t *powerup = GetItemByPowerup((powerup_t) powerupIndex); + uint16_t val; + + switch (powerup->id) + { + case IT_ITEM_POWER_SCREEN: + case IT_ITEM_POWER_SHIELD: + if (!ent->client->pers.inventory[powerup->id]) + val = 0; + else if (ent->flags & FL_POWER_ARMOR) + val = 2; + else + val = 1; + break; + case IT_ITEM_FLASHLIGHT: + if (!ent->client->pers.inventory[powerup->id]) + val = 0; + else if (ent->flags & FL_FLASHLIGHT) + val = 2; + else + val = 1; + break; + default: + val = clamp(ent->client->pers.inventory[powerup->id], 0, 3); + break; + } + + G_SetPowerupStat((uint16_t *) &ent->client->ps.stats[STAT_POWERUP_INFO_START], powerup->powerup_wheel_index, val); + } + + ent->client->ps.stats[STAT_TIMER_ICON] = 0; + ent->client->ps.stats[STAT_TIMER] = 0; + + // + // timers + // + // PGM + if (ent->client->owned_sphere) + { + if (ent->client->owned_sphere->spawnflags == SPHERE_DEFENDER) // defender + ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex("p_defender"); + else if (ent->client->owned_sphere->spawnflags == SPHERE_HUNTER) // hunter + ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex("p_hunter"); + else if (ent->client->owned_sphere->spawnflags == SPHERE_VENGEANCE) // vengeance + ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex("p_vengeance"); + else // error case + ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex("i_fixme"); + + ent->client->ps.stats[STAT_TIMER] = ceil(ent->client->owned_sphere->wait - level.time.seconds()); + } + else + { + powerup_info_t *best_powerup = nullptr; + + for (auto &powerup : powerup_table) + { + auto *powerup_time = powerup.time_ptr ? &(ent->client->*powerup.time_ptr) : nullptr; + auto *powerup_count = powerup.count_ptr ? &(ent->client->*powerup.count_ptr) : nullptr; + + if (powerup_time && *powerup_time <= level.time) + continue; + else if (powerup_count && !*powerup_count) + continue; + + if (!best_powerup) + { + best_powerup = &powerup; + continue; + } + + if (powerup_time && *powerup_time < ent->client->*best_powerup->time_ptr) + { + best_powerup = &powerup; + continue; + } + else if (powerup_count && !best_powerup->time_ptr) + { + best_powerup = &powerup; + continue; + } + } + + if (best_powerup) + { + int16_t value; + + if (best_powerup->count_ptr) + value = (ent->client->*best_powerup->count_ptr); + else + value = ceil((ent->client->*best_powerup->time_ptr - level.time).seconds()); + + ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex(GetItemByIndex(best_powerup->item)->icon); + ent->client->ps.stats[STAT_TIMER] = value; + } + } + // PGM + + // + // selected item + // + ent->client->ps.stats[STAT_SELECTED_ITEM] = ent->client->pers.selected_item; + + if (ent->client->pers.selected_item == IT_NULL) + ent->client->ps.stats[STAT_SELECTED_ICON] = 0; + else + { + ent->client->ps.stats[STAT_SELECTED_ICON] = gi.imageindex(itemlist[ent->client->pers.selected_item].icon); + + if (ent->client->pers.selected_item_time < level.time) + ent->client->ps.stats[STAT_SELECTED_ITEM_NAME] = 0; + } + + // + // layouts + // + ent->client->ps.stats[STAT_LAYOUTS] = 0; + + if (deathmatch->integer) + { + if (ent->client->pers.health <= 0 || level.intermissiontime || ent->client->showscores) + ent->client->ps.stats[STAT_LAYOUTS] |= LAYOUTS_LAYOUT; + if (ent->client->showinventory && ent->client->pers.health > 0) + ent->client->ps.stats[STAT_LAYOUTS] |= LAYOUTS_INVENTORY; + } + else + { + if (ent->client->showscores || ent->client->showhelp || ent->client->showeou) + ent->client->ps.stats[STAT_LAYOUTS] |= LAYOUTS_LAYOUT; + if (ent->client->showinventory && ent->client->pers.health > 0) + ent->client->ps.stats[STAT_LAYOUTS] |= LAYOUTS_INVENTORY; + + if (ent->client->showhelp) + ent->client->ps.stats[STAT_LAYOUTS] |= LAYOUTS_HELP; + } + + if (level.intermissiontime || ent->client->awaiting_respawn) + { + if (ent->client->awaiting_respawn || (level.intermission_eou || level.is_n64 || (deathmatch->integer && level.intermissiontime))) + ent->client->ps.stats[STAT_LAYOUTS] |= LAYOUTS_HIDE_HUD; + + // N64 always merges into one screen on level ends + if (level.intermission_eou || level.is_n64 || (deathmatch->integer && level.intermissiontime)) + ent->client->ps.stats[STAT_LAYOUTS] |= LAYOUTS_INTERMISSION; + } + + if (level.story_active) + ent->client->ps.stats[STAT_LAYOUTS] |= LAYOUTS_HIDE_CROSSHAIR; + else + ent->client->ps.stats[STAT_LAYOUTS] &= ~LAYOUTS_HIDE_CROSSHAIR; + + // [Paril-KEX] key display + if (!deathmatch->integer) + { + int32_t key_offset = 0; + player_stat_t stat = STAT_KEY_A; + + ent->client->ps.stats[STAT_KEY_A] = + ent->client->ps.stats[STAT_KEY_B] = + ent->client->ps.stats[STAT_KEY_C] = 0; + + // there's probably a way to do this in one pass but + // I'm lazy + std::array keys_held; + size_t num_keys_held = 0; + + for (auto &item : itemlist) + { + if (!(item.flags & IF_KEY)) + continue; + else if (!ent->client->pers.inventory[item.id]) + continue; + + keys_held[num_keys_held++] = item.id; + } + + if (num_keys_held > 3) + key_offset = (int32_t) (level.time.seconds() / 5); + + for (int32_t i = 0; i < min(num_keys_held, (size_t) 3); i++, stat = (player_stat_t) (stat + 1)) + ent->client->ps.stats[stat] = gi.imageindex(GetItemByIndex(keys_held[(i + key_offset) % num_keys_held])->icon); + } + + // + // frags + // + ent->client->ps.stats[STAT_FRAGS] = ent->client->resp.score; + + // + // help icon / current weapon if not shown + // + if (ent->client->pers.helpchanged >= 1 && ent->client->pers.helpchanged <= 2 && (level.time.milliseconds() % 1000) < 500) // haleyjd: time-limited + ent->client->ps.stats[STAT_HELPICON] = gi.imageindex("i_help"); + else if ((ent->client->pers.hand == CENTER_HANDED) && ent->client->pers.weapon) + ent->client->ps.stats[STAT_HELPICON] = gi.imageindex(ent->client->pers.weapon->icon); + else + ent->client->ps.stats[STAT_HELPICON] = 0; + + ent->client->ps.stats[STAT_SPECTATOR] = 0; + + // set & run the health bar stuff + for (size_t i = 0; i < MAX_HEALTH_BARS; i++) + { + byte *health_byte = reinterpret_cast(&ent->client->ps.stats[STAT_HEALTH_BARS]) + i; + + if (!level.health_bar_entities[i]) + *health_byte = 0; + else if (level.health_bar_entities[i]->timestamp) + { + if (level.health_bar_entities[i]->timestamp < level.time) + { + level.health_bar_entities[i] = nullptr; + *health_byte = 0; + continue; + } + + *health_byte = 0b10000000; + } + else + { + // enemy dead + if (!level.health_bar_entities[i]->enemy->inuse || level.health_bar_entities[i]->enemy->health <= 0) + { + // hack for Makron + if (level.health_bar_entities[i]->enemy->monsterinfo.aiflags & AI_DOUBLE_TROUBLE) + { + *health_byte = 0b10000000; + continue; + } + + if (level.health_bar_entities[i]->delay) + { + level.health_bar_entities[i]->timestamp = level.time + gtime_t::from_sec(level.health_bar_entities[i]->delay); + *health_byte = 0b10000000; + } + else + { + level.health_bar_entities[i] = nullptr; + *health_byte = 0; + } + + continue; + } + else if (level.health_bar_entities[i]->spawnflags.has(SPAWNFLAG_HEALTHBAR_PVS_ONLY) && !gi.inPVS(ent->s.origin, level.health_bar_entities[i]->enemy->s.origin, true)) + { + *health_byte = 0; + continue; + } + + float health_remaining = ((float) level.health_bar_entities[i]->enemy->health) / level.health_bar_entities[i]->enemy->max_health; + *health_byte = ((byte) (health_remaining * 0b01111111)) | 0b10000000; + } + } + + // ZOID + SetCTFStats(ent); + // ZOID +} + +/* +=============== +G_CheckChaseStats +=============== +*/ +void G_CheckChaseStats(edict_t *ent) +{ + gclient_t *cl; + + for (uint32_t i = 1; i <= game.maxclients; i++) + { + cl = g_edicts[i].client; + if (!g_edicts[i].inuse || cl->chase_target != ent) + continue; + cl->ps.stats = ent->client->ps.stats; + G_SetSpectatorStats(g_edicts + i); + } +} + +/* +=============== +G_SetSpectatorStats +=============== +*/ +void G_SetSpectatorStats(edict_t *ent) +{ + gclient_t *cl = ent->client; + + if (!cl->chase_target) + G_SetStats(ent); + + cl->ps.stats[STAT_SPECTATOR] = 1; + + // layouts are independant in spectator + cl->ps.stats[STAT_LAYOUTS] = 0; + if (cl->pers.health <= 0 || level.intermissiontime || cl->showscores) + cl->ps.stats[STAT_LAYOUTS] |= LAYOUTS_LAYOUT; + if (cl->showinventory && cl->pers.health > 0) + cl->ps.stats[STAT_LAYOUTS] |= LAYOUTS_INVENTORY; + + if (cl->chase_target && cl->chase_target->inuse) + cl->ps.stats[STAT_CHASE] = CS_PLAYERSKINS + + (cl->chase_target - g_edicts) - 1; + else + cl->ps.stats[STAT_CHASE] = 0; +} diff --git a/rerelease/p_move.cpp b/rerelease/p_move.cpp new file mode 100644 index 0000000..f6308d8 --- /dev/null +++ b/rerelease/p_move.cpp @@ -0,0 +1,1695 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. + +#include "q_std.h" + +#define GAME_INCLUDE +#include "bg_local.h" + +// [Paril-KEX] generic code to detect & fix a stuck object +stuck_result_t G_FixStuckObject_Generic(vec3_t &origin, const vec3_t &own_mins, const vec3_t &own_maxs, std::function trace) +{ + if (!trace(origin, own_mins, own_maxs, origin).startsolid) + return stuck_result_t::GOOD_POSITION; + + struct { + float distance; + vec3_t origin; + } good_positions[6]; + size_t num_good_positions = 0; + + constexpr struct { + std::array normal; + std::array mins, maxs; + } side_checks[] = { + { { 0, 0, 1 }, { -1, -1, 0 }, { 1, 1, 0 } }, + { { 0, 0, -1 }, { -1, -1, 0 }, { 1, 1, 0 } }, + { { 1, 0, 0 }, { 0, -1, -1 }, { 0, 1, 1 } }, + { { -1, 0, 0 }, { 0, -1, -1 }, { 0, 1, 1 } }, + { { 0, 1, 0 }, { -1, 0, -1 }, { 1, 0, 1 } }, + { { 0, -1, 0 }, { -1, 0, -1 }, { 1, 0, 1 } }, + }; + + for (size_t sn = 0; sn < q_countof(side_checks); sn++) + { + auto &side = side_checks[sn]; + vec3_t start = origin; + vec3_t mins {}, maxs {}; + + for (size_t n = 0; n < 3; n++) + { + if (side.normal[n] < 0) + start[n] += own_mins[n]; + else if (side.normal[n] > 0) + start[n] += own_maxs[n]; + + if (side.mins[n] == -1) + mins[n] = own_mins[n]; + else if (side.mins[n] == 1) + mins[n] = own_maxs[n]; + + if (side.maxs[n] == -1) + maxs[n] = own_mins[n]; + else if (side.maxs[n] == 1) + maxs[n] = own_maxs[n]; + } + + trace_t tr = trace(start, mins, maxs, start); + + int8_t needed_epsilon_fix = -1; + int8_t needed_epsilon_dir; + + if (tr.startsolid) + { + for (size_t e = 0; e < 3; e++) + { + if (side.normal[e] != 0) + continue; + + vec3_t ep_start = start; + ep_start[e] += 1; + + tr = trace(ep_start, mins, maxs, ep_start); + + if (!tr.startsolid) + { + start = ep_start; + needed_epsilon_fix = e; + needed_epsilon_dir = 1; + break; + } + + ep_start[e] -= 2; + tr = trace(ep_start, mins, maxs, ep_start); + + if (!tr.startsolid) + { + start = ep_start; + needed_epsilon_fix = e; + needed_epsilon_dir = -1; + break; + } + } + } + + // no good + if (tr.startsolid) + continue; + + vec3_t opposite_start = origin; + auto &other_side = side_checks[sn ^ 1]; + + for (size_t n = 0; n < 3; n++) + { + if (other_side.normal[n] < 0) + opposite_start[n] += own_mins[n]; + else if (other_side.normal[n] > 0) + opposite_start[n] += own_maxs[n]; + } + + if (needed_epsilon_fix >= 0) + opposite_start[needed_epsilon_fix] += needed_epsilon_dir; + + // potentially a good side; start from our center, push back to the opposite side + // to find how much clearance we have + tr = trace(start, mins, maxs, opposite_start); + + // ??? + if (tr.startsolid) + continue; + + // check the delta + vec3_t end = tr.endpos; + // push us very slightly away from the wall + end += vec3_t{(float) side.normal[0], (float) side.normal[1], (float) side.normal[2]} * 0.125f; + + // calculate delta + const vec3_t delta = end - opposite_start; + vec3_t new_origin = origin + delta; + + if (needed_epsilon_fix >= 0) + new_origin[needed_epsilon_fix] += needed_epsilon_dir; + + tr = trace(new_origin, own_mins, own_maxs, new_origin); + + // bad + if (tr.startsolid) + continue; + + good_positions[num_good_positions].origin = new_origin; + good_positions[num_good_positions].distance = delta.lengthSquared(); + num_good_positions++; + } + + if (num_good_positions) + { + std::sort(&good_positions[0], &good_positions[num_good_positions - 1], [](const auto &a, const auto &b) { return a.distance < b.distance; }); + + origin = good_positions[0].origin; + + return stuck_result_t::FIXED; + } + + return stuck_result_t::NO_GOOD_POSITION; +} + +// all of the locals will be zeroed before each +// pmove, just to make damn sure we don't have +// any differences when running on client or server + +struct pml_t +{ + vec3_t origin; // full float precision + vec3_t velocity; // full float precision + + vec3_t forward, right, up; + float frametime; + + csurface_t *groundsurface; + int groundcontents; + + vec3_t previous_origin; + vec3_t start_velocity; +}; + +pm_config_t pm_config; + +pmove_t *pm; +pml_t pml; + +// movement parameters +float pm_stopspeed = 100; +float pm_maxspeed = 300; +float pm_duckspeed = 100; +float pm_accelerate = 10; +float pm_wateraccelerate = 10; +float pm_friction = 6; +float pm_waterfriction = 1; +float pm_waterspeed = 400; +float pm_laddermod = 0.5f; + +/* + + walking up a step should kill some velocity + +*/ + +/* +================== +PM_ClipVelocity + +Slide off of the impacting object +returns the blocked flags (1 = floor, 2 = step / wall) +================== +*/ +void PM_ClipVelocity(const vec3_t &in, const vec3_t &normal, vec3_t &out, float overbounce) +{ + float backoff; + float change; + int i; + + backoff = in.dot(normal) * overbounce; + + for (i = 0; i < 3; i++) + { + change = normal[i] * backoff; + out[i] = in[i] - change; + if (out[i] > -STOP_EPSILON && out[i] < STOP_EPSILON) + out[i] = 0; + } +} + +trace_t PM_Clip(const vec3_t &start, const vec3_t &mins, const vec3_t &maxs, const vec3_t &end, contents_t mask) +{ + return pm->clip(start, &mins, &maxs, end, mask); +} + +trace_t PM_Trace(const vec3_t &start, const vec3_t &mins, const vec3_t &maxs, const vec3_t &end, contents_t mask = CONTENTS_NONE) +{ + if (pm->s.pm_type == PM_SPECTATOR) + return PM_Clip(start, mins, maxs, end, MASK_SOLID); + + if (mask == CONTENTS_NONE) + { + if (pm->s.pm_type == PM_DEAD || pm->s.pm_type == PM_GIB) + mask = MASK_DEADSOLID; + else if (pm->s.pm_type == PM_SPECTATOR) + mask = MASK_SOLID; + else + mask = MASK_PLAYERSOLID; + + if (pm->s.pm_flags & PMF_IGNORE_PLAYER_COLLISION) + mask &= ~CONTENTS_PLAYER; + } + + return pm->trace(start, &mins, &maxs, end, pm->player, mask); +} + +// only here to satisfy pm_trace_t +inline trace_t PM_Trace_Auto(const vec3_t &start, const vec3_t &mins, const vec3_t &maxs, const vec3_t &end) +{ + return PM_Trace(start, mins, maxs, end); +} + +/* +================== +PM_StepSlideMove + +Each intersection will try to step over the obstruction instead of +sliding along it. + +Returns a new origin, velocity, and contact entity +Does not modify any world state? +================== +*/ +constexpr float MIN_STEP_NORMAL = 0.7f; // can't step up onto very steep slopes +constexpr size_t MAX_CLIP_PLANES = 5; + +inline void PM_RecordTrace(touch_list_t &touch, trace_t &tr) +{ + if (touch.num == MAXTOUCH) + return; + + for (size_t i = 0; i < touch.num; i++) + if (touch.traces[i].ent == tr.ent) + return; + + touch.traces[touch.num++] = tr; +} + +// [Paril-KEX] made generic so you can run this without +// needing a pml/pm +void PM_StepSlideMove_Generic(vec3_t &origin, vec3_t &velocity, float frametime, const vec3_t &mins, const vec3_t &maxs, touch_list_t &touch, bool has_time, pm_trace_t trace_func) +{ + int bumpcount, numbumps; + vec3_t dir; + float d; + int numplanes; + vec3_t planes[MAX_CLIP_PLANES]; + vec3_t primal_velocity; + int i, j; + trace_t trace; + vec3_t end; + float time_left; + + numbumps = 4; + + primal_velocity = velocity; + numplanes = 0; + + time_left = frametime; + + for (bumpcount = 0; bumpcount < numbumps; bumpcount++) + { + for (i = 0; i < 3; i++) + end[i] = origin[i] + time_left * velocity[i]; + + trace = trace_func(origin, mins, maxs, end); + + if (trace.allsolid) + { // entity is trapped in another solid + velocity[2] = 0; // don't build up falling damage + + // save entity for contact + PM_RecordTrace(touch, trace); + return; + } + + // [Paril-KEX] experimental attempt to fix stray collisions on curved + // surfaces; easiest to see on q2dm1 by running/jumping against the sides + // of the curved map. + if (trace.surface2) + { + vec3_t clipped_a, clipped_b; + PM_ClipVelocity(velocity, trace.plane.normal, clipped_a, 1.01f); + PM_ClipVelocity(velocity, trace.plane2.normal, clipped_b, 1.01f); + + bool better = false; + + for (int i = 0; i < 3; i++) + { + if (fabsf(clipped_a[i]) < fabsf(clipped_b[i])) + { + better = true; + break; + } + } + + if (better) + { + trace.plane = trace.plane2; + trace.surface = trace.surface2; + } + } + + if (trace.fraction > 0) + { // actually covered some distance + origin = trace.endpos; + numplanes = 0; + } + + if (trace.fraction == 1) + break; // moved the entire distance + + // save entity for contact + PM_RecordTrace(touch, trace); + + time_left -= time_left * trace.fraction; + + // slide along this plane + if (numplanes >= MAX_CLIP_PLANES) + { // this shouldn't really happen + velocity = vec3_origin; + break; + } + + // + // if this is the same plane we hit before, nudge origin + // out along it, which fixes some epsilon issues with + // non-axial planes (xswamp, q2dm1 sometimes...) + // + for (i = 0; i < numplanes; i++) + { + if (trace.plane.normal.dot(planes[i]) > 0.99f) + { + pml.origin.x += trace.plane.normal.x * 0.01f; + pml.origin.y += trace.plane.normal.y * 0.01f; + G_FixStuckObject_Generic(pml.origin, mins, maxs, trace_func); + break; + } + } + + if (i < numplanes) + continue; + + planes[numplanes] = trace.plane.normal; + numplanes++; + + // + // modify original_velocity so it parallels all of the clip planes + // + for (i = 0; i < numplanes; i++) + { + PM_ClipVelocity(velocity, planes[i], velocity, 1.01f); + for (j = 0; j < numplanes; j++) + if (j != i) + { + if (velocity.dot(planes[j]) < 0) + break; // not ok + } + if (j == numplanes) + break; + } + + if (i != numplanes) + { // go along this plane + } + else + { // go along the crease + if (numplanes != 2) + { + velocity = vec3_origin; + break; + } + dir = planes[0].cross(planes[1]); + d = dir.dot(velocity); + velocity = dir * d; + } + + // + // if velocity is against the original velocity, stop dead + // to avoid tiny oscillations in sloping corners + // + if (velocity.dot(primal_velocity) <= 0) + { + velocity = vec3_origin; + break; + } + } + + if (has_time) + { + velocity = primal_velocity; + } +} + +inline void PM_StepSlideMove_() +{ + PM_StepSlideMove_Generic(pml.origin, pml.velocity, pml.frametime, pm->mins, pm->maxs, pm->touch, pm->s.pm_time, PM_Trace_Auto); +} + +/* +================== +PM_StepSlideMove + +================== +*/ +void PM_StepSlideMove() +{ + vec3_t start_o, start_v; + vec3_t down_o, down_v; + trace_t trace; + float down_dist, up_dist; + // vec3_t delta; + vec3_t up, down; + + start_o = pml.origin; + start_v = pml.velocity; + + PM_StepSlideMove_(); + + down_o = pml.origin; + down_v = pml.velocity; + + up = start_o; + up[2] += STEPSIZE; + + trace = PM_Trace(start_o, pm->mins, pm->maxs, up); + if (trace.allsolid) + return; // can't step up + + float stepSize = trace.endpos[2] - start_o[2]; + + // try sliding above + pml.origin = trace.endpos; + pml.velocity = start_v; + + PM_StepSlideMove_(); + + // push down the final amount + down = pml.origin; + down[2] -= stepSize; + trace = PM_Trace(pml.origin, pm->mins, pm->maxs, down); + if (!trace.allsolid) + { + pml.origin = trace.endpos; + } + + up = pml.origin; + + // decide which one went farther + down_dist = (down_o[0] - start_o[0]) * (down_o[0] - start_o[0]) + (down_o[1] - start_o[1]) * (down_o[1] - start_o[1]); + up_dist = (up[0] - start_o[0]) * (up[0] - start_o[0]) + (up[1] - start_o[1]) * (up[1] - start_o[1]); + + if (down_dist > up_dist || trace.plane.normal[2] < MIN_STEP_NORMAL) + { + pml.origin = down_o; + pml.velocity = down_v; + } + else if (pm->s.pm_flags & PMF_ON_GROUND) + //!! Special case + // if we were walking along a plane, then we need to copy the Z over + pml.velocity[2] = down_v[2]; + + // Paril: step down stairs/slopes + if ((pm->s.pm_flags & PMF_ON_GROUND) && !(pm->s.pm_flags & PMF_ON_LADDER) && + (pm->waterlevel < WATER_WAIST || (!(pm->cmd.buttons & BUTTON_JUMP) && pml.velocity.z <= 0))) + { + down = pml.origin; + down[2] -= STEPSIZE; + trace = PM_Trace(pml.origin, pm->mins, pm->maxs, down); + if (trace.fraction < 1.f) + { + pml.origin = trace.endpos; + } + } +} + +/* +================== +PM_Friction + +Handles both ground friction and water friction +================== +*/ +void PM_Friction() +{ + float *vel; + float speed, newspeed, control; + float friction; + float drop; + + vel = &pml.velocity.x; + + speed = sqrt(vel[0] * vel[0] + vel[1] * vel[1] + vel[2] * vel[2]); + if (speed < 1) + { + vel[0] = 0; + vel[1] = 0; + return; + } + + drop = 0; + + // apply ground friction + if ((pm->groundentity && pml.groundsurface && !(pml.groundsurface->flags & SURF_SLICK)) || (pm->s.pm_flags & PMF_ON_LADDER)) + { + friction = pm_friction; + control = speed < pm_stopspeed ? pm_stopspeed : speed; + drop += control * friction * pml.frametime; + } + + // apply water friction + if (pm->waterlevel && !(pm->s.pm_flags & PMF_ON_LADDER)) + drop += speed * pm_waterfriction * (float) pm->waterlevel * pml.frametime; + + // scale the velocity + newspeed = speed - drop; + if (newspeed < 0) + { + newspeed = 0; + } + newspeed /= speed; + + vel[0] = vel[0] * newspeed; + vel[1] = vel[1] * newspeed; + vel[2] = vel[2] * newspeed; +} + +/* +============== +PM_Accelerate + +Handles user intended acceleration +============== +*/ +void PM_Accelerate(const vec3_t &wishdir, float wishspeed, float accel) +{ + int i; + float addspeed, accelspeed, currentspeed; + + currentspeed = pml.velocity.dot(wishdir); + addspeed = wishspeed - currentspeed; + if (addspeed <= 0) + return; + accelspeed = accel * pml.frametime * wishspeed; + if (accelspeed > addspeed) + accelspeed = addspeed; + + for (i = 0; i < 3; i++) + pml.velocity[i] += accelspeed * wishdir[i]; +} + +void PM_AirAccelerate(const vec3_t &wishdir, float wishspeed, float accel) +{ + int i; + float addspeed, accelspeed, currentspeed, wishspd = wishspeed; + + if (wishspd > 30) + wishspd = 30; + currentspeed = pml.velocity.dot(wishdir); + addspeed = wishspd - currentspeed; + if (addspeed <= 0) + return; + accelspeed = accel * wishspeed * pml.frametime; + if (accelspeed > addspeed) + accelspeed = addspeed; + + for (i = 0; i < 3; i++) + pml.velocity[i] += accelspeed * wishdir[i]; +} + +/* +============= +PM_AddCurrents +============= +*/ +void PM_AddCurrents(vec3_t &wishvel) +{ + vec3_t v; + float s; + + // + // account for ladders + // + + if (pm->s.pm_flags & PMF_ON_LADDER) + { + if (pm->cmd.buttons & (BUTTON_JUMP | BUTTON_CROUCH)) + { + // [Paril-KEX]: if we're underwater, use full speed on ladders + float ladder_speed = pm->waterlevel >= WATER_WAIST ? pm_maxspeed : 200; + + if (pm->cmd.buttons & BUTTON_JUMP) + wishvel[2] = ladder_speed; + else if (pm->cmd.buttons & BUTTON_CROUCH) + wishvel[2] = -ladder_speed; + } + else if (pm->cmd.forwardmove) + { + // [Paril-KEX] clamp the speed a bit so we're not too fast + float ladder_speed = std::clamp(pm->cmd.forwardmove, -200.f, 200.f); + + if (pm->cmd.forwardmove > 0) + { + if (pm->viewangles[PITCH] < 15) + wishvel[2] = ladder_speed; + else + wishvel[2] = -ladder_speed; + } + // [Paril-KEX] allow using "back" arrow to go down on ladder + else if (pm->cmd.forwardmove < 0) + { + // if we haven't touched ground yet, remove x/y so we don't + // slide off of the ladder + if (!pm->groundentity) + wishvel[0] = wishvel[1] = 0; + + wishvel[2] = ladder_speed; + } + } + else + wishvel[2] = 0; + + // limit horizontal speed when on a ladder + // [Paril-KEX] unless we're on the ground + if (!pm->groundentity) + { + // [Paril-KEX] instead of left/right not doing anything, + // have them move you perpendicular to the ladder plane + if (pm->cmd.sidemove) + { + // clamp side speed so it's not jarring... + float ladder_speed = std::clamp(pm->cmd.sidemove, -150.f, 150.f); + + if (pm->waterlevel < WATER_WAIST) + ladder_speed *= pm_laddermod; + + // check for ladder + vec3_t flatforward, spot; + flatforward[0] = pml.forward[0]; + flatforward[1] = pml.forward[1]; + flatforward[2] = 0; + flatforward.normalize(); + + spot = pml.origin + (flatforward * 1); + trace_t trace = PM_Trace(pml.origin, pm->mins, pm->maxs, spot, CONTENTS_LADDER); + + if (trace.fraction != 1.f && (trace.contents & CONTENTS_LADDER)) + { + vec3_t right = trace.plane.normal.cross({ 0, 0, 1 }); + + wishvel[0] = wishvel[1] = 0; + wishvel += (right * -ladder_speed); + } + } + else + { + if (wishvel[0] < -25) + wishvel[0] = -25; + else if (wishvel[0] > 25) + wishvel[0] = 25; + + if (wishvel[1] < -25) + wishvel[1] = -25; + else if (wishvel[1] > 25) + wishvel[1] = 25; + } + } + } + + // + // add water currents + // + + if (pm->watertype & MASK_CURRENT) + { + v = {}; + + if (pm->watertype & CONTENTS_CURRENT_0) + v[0] += 1; + if (pm->watertype & CONTENTS_CURRENT_90) + v[1] += 1; + if (pm->watertype & CONTENTS_CURRENT_180) + v[0] -= 1; + if (pm->watertype & CONTENTS_CURRENT_270) + v[1] -= 1; + if (pm->watertype & CONTENTS_CURRENT_UP) + v[2] += 1; + if (pm->watertype & CONTENTS_CURRENT_DOWN) + v[2] -= 1; + + s = pm_waterspeed; + if ((pm->waterlevel == WATER_FEET) && (pm->groundentity)) + s /= 2; + + wishvel += (v * s); + } + + // + // add conveyor belt velocities + // + + if (pm->groundentity) + { + v = {}; + + if (pml.groundcontents & CONTENTS_CURRENT_0) + v[0] += 1; + if (pml.groundcontents & CONTENTS_CURRENT_90) + v[1] += 1; + if (pml.groundcontents & CONTENTS_CURRENT_180) + v[0] -= 1; + if (pml.groundcontents & CONTENTS_CURRENT_270) + v[1] -= 1; + if (pml.groundcontents & CONTENTS_CURRENT_UP) + v[2] += 1; + if (pml.groundcontents & CONTENTS_CURRENT_DOWN) + v[2] -= 1; + + wishvel += v * 100; + } +} + +/* +=================== +PM_WaterMove + +=================== +*/ +void PM_WaterMove() +{ + int i; + vec3_t wishvel; + float wishspeed; + vec3_t wishdir; + + // + // user intentions + // + for (i = 0; i < 3; i++) + wishvel[i] = pml.forward[i] * pm->cmd.forwardmove + pml.right[i] * pm->cmd.sidemove; + + if (!pm->cmd.forwardmove && !pm->cmd.sidemove && + !(pm->cmd.buttons & (BUTTON_JUMP | BUTTON_CROUCH))) + { + if (!pm->groundentity) + wishvel[2] -= 60; // drift towards bottom + } + else + { + if (pm->cmd.buttons & BUTTON_CROUCH) + wishvel[2] -= pm_waterspeed * 0.5f; + else if (pm->cmd.buttons & BUTTON_JUMP) + wishvel[2] += pm_waterspeed * 0.5f; + } + + PM_AddCurrents(wishvel); + + wishdir = wishvel; + wishspeed = wishdir.normalize(); + + if (wishspeed > pm_maxspeed) + { + wishvel *= pm_maxspeed / wishspeed; + wishspeed = pm_maxspeed; + } + wishspeed *= 0.5f; + + if ((pm->s.pm_flags & PMF_DUCKED) && wishspeed > pm_duckspeed) + { + wishvel *= pm_duckspeed / wishspeed; + wishspeed = pm_duckspeed; + } + + PM_Accelerate(wishdir, wishspeed, pm_wateraccelerate); + + PM_StepSlideMove(); +} + +/* +=================== +PM_AirMove + +=================== +*/ +void PM_AirMove() +{ + int i; + vec3_t wishvel; + float fmove, smove; + vec3_t wishdir; + float wishspeed; + float maxspeed; + + fmove = pm->cmd.forwardmove; + smove = pm->cmd.sidemove; + + for (i = 0; i < 2; i++) + wishvel[i] = pml.forward[i] * fmove + pml.right[i] * smove; + wishvel[2] = 0; + + PM_AddCurrents(wishvel); + + wishdir = wishvel; + wishspeed = wishdir.normalize(); + + // + // clamp to server defined max speed + // + maxspeed = (pm->s.pm_flags & PMF_DUCKED) ? pm_duckspeed : pm_maxspeed; + + if (wishspeed > maxspeed) + { + wishvel *= maxspeed / wishspeed; + wishspeed = maxspeed; + } + + if (pm->s.pm_flags & PMF_ON_LADDER) + { + PM_Accelerate(wishdir, wishspeed, pm_accelerate); + if (!wishvel[2]) + { + if (pml.velocity[2] > 0) + { + pml.velocity[2] -= pm->s.gravity * pml.frametime; + if (pml.velocity[2] < 0) + pml.velocity[2] = 0; + } + else + { + pml.velocity[2] += pm->s.gravity * pml.frametime; + if (pml.velocity[2] > 0) + pml.velocity[2] = 0; + } + } + PM_StepSlideMove(); + } + else if (pm->groundentity) + { // walking on ground + pml.velocity[2] = 0; //!!! this is before the accel + PM_Accelerate(wishdir, wishspeed, pm_accelerate); + + // PGM -- fix for negative trigger_gravity fields + // pml.velocity[2] = 0; + if (pm->s.gravity > 0) + pml.velocity[2] = 0; + else + pml.velocity[2] -= pm->s.gravity * pml.frametime; + // PGM + + if (!pml.velocity[0] && !pml.velocity[1]) + return; + PM_StepSlideMove(); + } + else + { // not on ground, so little effect on velocity + if (pm_config.airaccel) + PM_AirAccelerate(wishdir, wishspeed, pm_config.airaccel); + else + PM_Accelerate(wishdir, wishspeed, 1); + + // add gravity + if (pm->s.pm_type != PM_GRAPPLE) + pml.velocity[2] -= pm->s.gravity * pml.frametime; + + PM_StepSlideMove(); + } +} + +inline void PM_GetWaterLevel(const vec3_t &position, water_level_t &level, contents_t &type) +{ + // + // get waterlevel, accounting for ducking + // + level = WATER_NONE; + type = CONTENTS_NONE; + + int32_t sample2 = (int) (pm->s.viewheight - pm->mins[2]); + int32_t sample1 = sample2 / 2; + + vec3_t point = position; + + point[2] += pm->mins[2] + 1; + + contents_t cont = pm->pointcontents(point); + + if (cont & MASK_WATER) + { + type = cont; + level = WATER_FEET; + point[2] = pml.origin[2] + pm->mins[2] + sample1; + cont = pm->pointcontents(point); + if (cont & MASK_WATER) + { + level = WATER_WAIST; + point[2] = pml.origin[2] + pm->mins[2] + sample2; + cont = pm->pointcontents(point); + if (cont & MASK_WATER) + level = WATER_UNDER; + } + } +} + +/* +============= +PM_CatagorizePosition +============= +*/ +void PM_CatagorizePosition() +{ + vec3_t point; + trace_t trace; + + // if the player hull point one unit down is solid, the player + // is on ground + + // see if standing on something solid + point[0] = pml.origin[0]; + point[1] = pml.origin[1]; + point[2] = pml.origin[2] - 0.25f; + + if (pml.velocity[2] > 180 || pm->s.pm_type == PM_GRAPPLE) //!!ZOID changed from 100 to 180 (ramp accel) + { + pm->s.pm_flags &= ~PMF_ON_GROUND; + pm->groundentity = nullptr; + } + else + { + trace = PM_Trace(pml.origin, pm->mins, pm->maxs, point); + pm->groundplane = trace.plane; + pml.groundsurface = trace.surface; + pml.groundcontents = trace.contents; + + // [Paril-KEX] to attempt to fix edge cases where you get stuck + // wedged between a slope and a wall (which is irrecoverable + // most of the time), we'll allow the player to "stand" on + // slopes if they are right up against a wall + bool slanted_ground = trace.fraction < 1.0f && trace.plane.normal[2] < 0.7f; + + if (slanted_ground) + { + trace_t slant = PM_Trace(pml.origin, pm->mins, pm->maxs, pml.origin + trace.plane.normal); + + if (slant.fraction < 1.0f && !slant.startsolid) + slanted_ground = false; + } + + if (trace.fraction == 1.0f || (slanted_ground && !trace.startsolid)) + { + pm->groundentity = nullptr; + pm->s.pm_flags &= ~PMF_ON_GROUND; + } + else + { + pm->groundentity = trace.ent; + + // hitting solid ground will end a waterjump + if (pm->s.pm_flags & PMF_TIME_WATERJUMP) + { + pm->s.pm_flags &= ~(PMF_TIME_WATERJUMP | PMF_TIME_LAND | PMF_TIME_TELEPORT | PMF_TIME_TRICK); + pm->s.pm_time = 0; + } + + if (!(pm->s.pm_flags & PMF_ON_GROUND)) + { + // just hit the ground + + // [Paril-KEX] + if (!pm_config.n64_physics && pml.velocity[2] >= 100.f && pm->groundplane.normal[2] >= 0.9f && !(pm->s.pm_flags & PMF_DUCKED)) + { + pm->s.pm_flags |= PMF_TIME_TRICK; + pm->s.pm_time = 64; + } + + PM_ClipVelocity(pml.velocity, pm->groundplane.normal, pml.velocity, 1.01f); + pm->s.pm_flags |= PMF_ON_GROUND; + + if (pm_config.n64_physics || (pm->s.pm_flags & PMF_DUCKED)) + { + pm->s.pm_flags |= PMF_TIME_LAND; + pm->s.pm_time = 128; + } + } + } + + PM_RecordTrace(pm->touch, trace); + } + + // + // get waterlevel, accounting for ducking + // + PM_GetWaterLevel(pml.origin, pm->waterlevel, pm->watertype); +} + +/* +============= +PM_CheckJump +============= +*/ +void PM_CheckJump() +{ + if (pm->s.pm_flags & PMF_TIME_LAND) + { // hasn't been long enough since landing to jump again + return; + } + + if (!(pm->cmd.buttons & BUTTON_JUMP)) + { // not holding jump + pm->s.pm_flags &= ~PMF_JUMP_HELD; + return; + } + + // must wait for jump to be released + if (pm->s.pm_flags & PMF_JUMP_HELD) + return; + + if (pm->s.pm_type == PM_DEAD) + return; + + if (pm->waterlevel >= WATER_WAIST) + { // swimming, not jumping + pm->groundentity = nullptr; + return; + } + + if (pm->groundentity == nullptr) + return; // in air, so no effect + + pm->s.pm_flags |= PMF_JUMP_HELD; + pm->jump_sound = true; + pm->groundentity = nullptr; + pm->s.pm_flags &= ~PMF_ON_GROUND; + + float jump_height = 270.f; + + // [Paril-KEX] + if (pm->s.pm_flags & PMF_TIME_TRICK) + { + pm->s.pm_flags &= ~PMF_TIME_TRICK; + pm->s.pm_time = 0; + + jump_height *= 1.40f; + } + + pml.velocity[2] += jump_height; + if (pml.velocity[2] < jump_height) + pml.velocity[2] = jump_height; +} + +/* +============= +PM_CheckSpecialMovement +============= +*/ +void PM_CheckSpecialMovement() +{ + vec3_t spot; + vec3_t flatforward; + trace_t trace; + + if (pm->s.pm_time) + return; + + pm->s.pm_flags &= ~PMF_ON_LADDER; + + // check for ladder + flatforward[0] = pml.forward[0]; + flatforward[1] = pml.forward[1]; + flatforward[2] = 0; + flatforward.normalize(); + + spot = pml.origin + (flatforward * 1); + trace = PM_Trace(pml.origin, pm->mins, pm->maxs, spot, CONTENTS_LADDER); + if ((trace.fraction < 1) && (trace.contents & CONTENTS_LADDER) && pm->waterlevel < WATER_WAIST) + pm->s.pm_flags |= PMF_ON_LADDER; + + if (!pm->s.gravity) + return; + + // check for water jump + // [Paril-KEX] don't try waterjump if we're moving against where we'll hop + if (!(pm->cmd.buttons & BUTTON_JUMP) + && pm->cmd.forwardmove <= 0) + return; + + if (pm->waterlevel != WATER_WAIST) + return; + + // quick check that something is even blocking us forward + trace = PM_Trace(pml.origin, pm->mins, pm->maxs, pml.origin + (flatforward * 40), MASK_SOLID); + + // we aren't blocked, or what we're blocked by is something we can walk up + if (trace.fraction == 1.0f || trace.plane.normal.z >= 0.7f) + return; + + // [Paril-KEX] improved waterjump + vec3_t waterjump_vel = flatforward * 50; + waterjump_vel.z = 350; + + // simulate what would happen if we jumped out here, and + // if we land on a dry spot we're good! + // simulate 1 sec worth of movement + touch_list_t touches; + vec3_t waterjump_origin = pml.origin; + float time = 0.1f; + bool has_time = true; + + for (size_t i = 0; i < min(50, (int32_t) (10 * (800.f / pm->s.gravity))); i++) + { + waterjump_vel[2] -= pm->s.gravity * time; + + if (waterjump_vel[2] < 0) + has_time = false; + + PM_StepSlideMove_Generic(waterjump_origin, waterjump_vel, time, pm->mins, pm->maxs, touches, has_time, PM_Trace_Auto); + } + + // snap down to ground + trace = PM_Trace(waterjump_origin, pm->mins, pm->maxs, waterjump_origin - vec3_t { 0, 0, 2.f }, MASK_SOLID); + + // can't stand here + if (trace.fraction == 1.0f || trace.plane.normal.z < 0.7f || + trace.endpos.z < pml.origin.z) + return; + + // we're currently standing on ground, and the snapped position + // is a step + if (pm->groundentity && fabsf(pml.origin.z - trace.endpos.z) <= STEPSIZE) + return; + + water_level_t level; + contents_t type; + + PM_GetWaterLevel(trace.endpos, level, type); + + // the water jump spot will be under water, so we're + // probably hitting something weird that isn't important + if (level >= WATER_WAIST) + return; + + // valid waterjump! + // jump out of water + pml.velocity = flatforward * 50; + pml.velocity[2] = 350; + + pm->s.pm_flags |= PMF_TIME_WATERJUMP; + pm->s.pm_time = 2048; +} + +/* +=============== +PM_FlyMove +=============== +*/ +void PM_FlyMove(bool doclip) +{ + float speed, drop, friction, control, newspeed; + float currentspeed, addspeed, accelspeed; + int i; + vec3_t wishvel; + float fmove, smove; + vec3_t wishdir; + float wishspeed; + + pm->s.viewheight = doclip ? 0 : 22; + + // friction + + speed = pml.velocity.length(); + if (speed < 1) + { + pml.velocity = vec3_origin; + } + else + { + drop = 0; + + friction = pm_friction * 1.5f; // extra friction + control = speed < pm_stopspeed ? pm_stopspeed : speed; + drop += control * friction * pml.frametime; + + // scale the velocity + newspeed = speed - drop; + if (newspeed < 0) + newspeed = 0; + newspeed /= speed; + + pml.velocity *= newspeed; + } + + // accelerate + fmove = pm->cmd.forwardmove; + smove = pm->cmd.sidemove; + + pml.forward.normalize(); + pml.right.normalize(); + + for (i = 0; i < 3; i++) + wishvel[i] = pml.forward[i] * fmove + pml.right[i] * smove; + + if (pm->cmd.buttons & BUTTON_JUMP) + wishvel[2] += (pm_waterspeed * 0.5f); + if (pm->cmd.buttons & BUTTON_CROUCH) + wishvel[2] -= (pm_waterspeed * 0.5f); + + wishdir = wishvel; + wishspeed = wishdir.normalize(); + + // + // clamp to server defined max speed + // + if (wishspeed > pm_maxspeed) + { + wishvel *= pm_maxspeed / wishspeed; + wishspeed = pm_maxspeed; + } + + // Paril: newer clients do this + wishspeed *= 2; + + currentspeed = pml.velocity.dot(wishdir); + addspeed = wishspeed - currentspeed; + + if (addspeed > 0) + { + accelspeed = pm_accelerate * pml.frametime * wishspeed; + if (accelspeed > addspeed) + accelspeed = addspeed; + + for (i = 0; i < 3; i++) + pml.velocity[i] += accelspeed * wishdir[i]; + } + + if (doclip) + { + /*for (i = 0; i < 3; i++) + end[i] = pml.origin[i] + pml.frametime * pml.velocity[i]; + + trace = PM_Trace(pml.origin, pm->mins, pm->maxs, end); + + pml.origin = trace.endpos;*/ + + PM_StepSlideMove(); + } + else + { + // move + pml.origin += (pml.velocity * pml.frametime); + } +} + +void PM_SetDimensions() +{ + pm->mins[0] = -16; + pm->mins[1] = -16; + + pm->maxs[0] = 16; + pm->maxs[1] = 16; + + if (pm->s.pm_type == PM_GIB) + { + pm->mins[2] = 0; + pm->maxs[2] = 16; + pm->s.viewheight = 8; + return; + } + + pm->mins[2] = -24; + + if ((pm->s.pm_flags & PMF_DUCKED) || pm->s.pm_type == PM_DEAD) + { + pm->maxs[2] = 4; + pm->s.viewheight = -2; + } + else + { + pm->maxs[2] = 32; + pm->s.viewheight = 22; + } +} + +inline bool PM_AboveWater() +{ + const vec3_t below = pml.origin - vec3_t{ 0, 0, 8 }; + + bool solid_below = pm->trace(pml.origin, &pm->mins, &pm->maxs, below, pm->player, MASK_SOLID).fraction < 1.0f; + + if (solid_below) + return false; + + bool water_below = pm->trace(pml.origin, &pm->mins, &pm->maxs, below, pm->player, MASK_WATER).fraction < 1.0f; + + if (water_below) + return true; + + return false; +} + +/* +============== +PM_CheckDuck + +Sets mins, maxs, and pm->viewheight +============== +*/ +bool PM_CheckDuck() +{ + if (pm->s.pm_type == PM_GIB) + return false; + + trace_t trace; + bool flags_changed = false; + + if (pm->s.pm_type == PM_DEAD) + { + if (!(pm->s.pm_flags & PMF_DUCKED)) + { + pm->s.pm_flags |= PMF_DUCKED; + flags_changed = true; + } + } + else if ( + (pm->cmd.buttons & BUTTON_CROUCH) && + (pm->groundentity || (pm->waterlevel <= WATER_FEET && !PM_AboveWater())) && + !(pm->s.pm_flags & PMF_ON_LADDER) && + !pm_config.n64_physics) + { // duck + if (!(pm->s.pm_flags & PMF_DUCKED)) + { + // check that duck won't be blocked + vec3_t check_maxs = { pm->maxs[0], pm->maxs[1], 4 }; + trace = PM_Trace(pml.origin, pm->mins, check_maxs, pml.origin); + if (!trace.allsolid) + { + pm->s.pm_flags |= PMF_DUCKED; + flags_changed = true; + } + } + } + else + { // stand up if possible + if (pm->s.pm_flags & PMF_DUCKED) + { + // try to stand up + vec3_t check_maxs = { pm->maxs[0], pm->maxs[1], 32 }; + trace = PM_Trace(pml.origin, pm->mins, check_maxs, pml.origin); + if (!trace.allsolid) + { + pm->s.pm_flags &= ~PMF_DUCKED; + flags_changed = true; + } + } + } + + if (!flags_changed) + return false; + + PM_SetDimensions(); + return true; +} + +/* +============== +PM_DeadMove +============== +*/ +void PM_DeadMove() +{ + float forward; + + if (!pm->groundentity) + return; + + // extra friction + + forward = pml.velocity.length(); + forward -= 20; + if (forward <= 0) + { + pml.velocity = {}; + } + else + { + pml.velocity.normalize(); + pml.velocity *= forward; + } +} + +bool PM_GoodPosition() +{ + if (pm->s.pm_type == PM_NOCLIP) + return true; + + trace_t trace = PM_Trace(pm->s.origin, pm->mins, pm->maxs, pm->s.origin); + + return !trace.allsolid; +} + +/* +================ +PM_SnapPosition + +On exit, the origin will have a value that is pre-quantized to the PMove +precision of the network channel and in a valid position. +================ +*/ +void PM_SnapPosition() +{ + pm->s.velocity = pml.velocity; + pm->s.origin = pml.origin; + + if (PM_GoodPosition()) + return; + + if (G_FixStuckObject_Generic(pm->s.origin, pm->mins, pm->maxs, PM_Trace_Auto) == stuck_result_t::NO_GOOD_POSITION) { + pm->s.origin = pml.previous_origin; + return; + } +} + +/* +================ +PM_InitialSnapPosition + +================ +*/ +void PM_InitialSnapPosition() +{ + int x, y, z; + vec3_t base; + constexpr int offset[3] = { 0, -1, 1 }; + + base = pm->s.origin; + + for (z = 0; z < 3; z++) + { + pm->s.origin[2] = base[2] + offset[z]; + for (y = 0; y < 3; y++) + { + pm->s.origin[1] = base[1] + offset[y]; + for (x = 0; x < 3; x++) + { + pm->s.origin[0] = base[0] + offset[x]; + if (PM_GoodPosition()) + { + pml.origin = pm->s.origin; + pml.previous_origin = pm->s.origin; + return; + } + } + } + } +} + +/* +================ +PM_ClampAngles + +================ +*/ +void PM_ClampAngles() +{ + if (pm->s.pm_flags & PMF_TIME_TELEPORT) + { + pm->viewangles[YAW] = pm->cmd.angles[YAW] + pm->s.delta_angles[YAW]; + pm->viewangles[PITCH] = 0; + pm->viewangles[ROLL] = 0; + } + else + { + // circularly clamp the angles with deltas + pm->viewangles = pm->cmd.angles + pm->s.delta_angles; + + // don't let the player look up or down more than 90 degrees + if (pm->viewangles[PITCH] > 89 && pm->viewangles[PITCH] < 180) + pm->viewangles[PITCH] = 89; + else if (pm->viewangles[PITCH] < 271 && pm->viewangles[PITCH] >= 180) + pm->viewangles[PITCH] = 271; + } + AngleVectors(pm->viewangles, pml.forward, pml.right, pml.up); +} + +// [Paril-KEX] +static void PM_ScreenEffects() +{ + // add for contents + vec3_t vieworg = pml.origin + pm->viewoffset + vec3_t{ 0, 0, (float) pm->s.viewheight }; + contents_t contents = pm->pointcontents(vieworg); + + if (contents & (CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER)) + pm->rdflags |= RDF_UNDERWATER; + else + pm->rdflags &= ~RDF_UNDERWATER; + + if (contents & (CONTENTS_SOLID | CONTENTS_LAVA)) + G_AddBlend(1.0f, 0.3f, 0.0f, 0.6f, pm->screen_blend); + else if (contents & CONTENTS_SLIME) + G_AddBlend(0.0f, 0.1f, 0.05f, 0.6f, pm->screen_blend); + else if (contents & CONTENTS_WATER) + G_AddBlend(0.5f, 0.3f, 0.2f, 0.4f, pm->screen_blend); +} + +/* +================ +Pmove + +Can be called by either the server or the client +================ +*/ +void Pmove(pmove_t *pmove) +{ + pm = pmove; + + // clear results + pm->touch.num = 0; + pm->viewangles = {}; + pm->s.viewheight = 0; + pm->groundentity = nullptr; + pm->watertype = CONTENTS_NONE; + pm->waterlevel = WATER_NONE; + pm->screen_blend = {}; + pm->rdflags = RDF_NONE; + pm->jump_sound = false; + pm->impact_delta = 0; + + // clear all pmove local vars + pml = {}; + + // convert origin and velocity to float values + pml.origin = pm->s.origin; + pml.velocity = pm->s.velocity; + + pml.start_velocity = pml.velocity; + + // save old org in case we get stuck + pml.previous_origin = pm->s.origin; + + pml.frametime = pm->cmd.msec * 0.001f; + + PM_ClampAngles(); + + if (pm->s.pm_type == PM_SPECTATOR || pm->s.pm_type == PM_NOCLIP) + { + pm->s.pm_flags = PMF_NONE; + + if (pm->s.pm_type == PM_SPECTATOR) + { + pm->mins[0] = -8; + pm->mins[1] = -8; + pm->maxs[0] = 8; + pm->maxs[1] = 8; + pm->mins[2] = -8; + pm->maxs[2] = 8; + } + + PM_FlyMove(pm->s.pm_type == PM_SPECTATOR); + PM_SnapPosition(); + return; + } + + if (pm->s.pm_type >= PM_DEAD) + { + pm->cmd.forwardmove = 0; + pm->cmd.sidemove = 0; + pm->cmd.buttons &= ~(BUTTON_JUMP | BUTTON_CROUCH); + } + + if (pm->s.pm_type == PM_FREEZE) + return; // no movement at all + + // set mins, maxs, and viewheight + PM_SetDimensions(); + + // catagorize for ducking + PM_CatagorizePosition(); + + if (pm->snapinitial) + PM_InitialSnapPosition(); + + // set groundentity, watertype, and waterlevel + if (PM_CheckDuck()) + PM_CatagorizePosition(); + + bool was_on_ground = !!pm->groundentity; + + if (pm->s.pm_type == PM_DEAD) + PM_DeadMove(); + + PM_CheckSpecialMovement(); + + // drop timing counter + if (pm->s.pm_time) + { + if (pm->cmd.msec >= pm->s.pm_time) + { + pm->s.pm_flags &= ~(PMF_TIME_WATERJUMP | PMF_TIME_LAND | PMF_TIME_TELEPORT | PMF_TIME_TRICK); + pm->s.pm_time = 0; + } + else + pm->s.pm_time -= pm->cmd.msec; + } + + if (pm->s.pm_flags & PMF_TIME_TELEPORT) + { // teleport pause stays exactly in place + } + else if (pm->s.pm_flags & PMF_TIME_WATERJUMP) + { // waterjump has no control, but falls + pml.velocity[2] -= pm->s.gravity * pml.frametime; + if (pml.velocity[2] < 0) + { // cancel as soon as we are falling down again + pm->s.pm_flags &= ~(PMF_TIME_WATERJUMP | PMF_TIME_LAND | PMF_TIME_TELEPORT | PMF_TIME_TRICK); + pm->s.pm_time = 0; + } + + PM_StepSlideMove(); + } + else + { + PM_CheckJump(); + + PM_Friction(); + + if (pm->waterlevel >= WATER_WAIST) + PM_WaterMove(); + else + { + vec3_t angles; + + angles = pm->viewangles; + if (angles[PITCH] > 180) + angles[PITCH] = angles[PITCH] - 360; + angles[PITCH] /= 3; + + AngleVectors(angles, pml.forward, pml.right, pml.up); + + PM_AirMove(); + } + } + + // set groundentity, watertype, and waterlevel for final spot + PM_CatagorizePosition(); + + // impact + if (pm->groundentity && !was_on_ground) + pm->impact_delta = pml.start_velocity[2] - pml.velocity[2]; + + // trick jump + if (pm->s.pm_flags & PMF_TIME_TRICK) + PM_CheckJump(); + + // [Paril-KEX] + PM_ScreenEffects(); + + PM_SnapPosition(); +} diff --git a/rerelease/p_trail.cpp b/rerelease/p_trail.cpp new file mode 100644 index 0000000..24ac527 --- /dev/null +++ b/rerelease/p_trail.cpp @@ -0,0 +1,153 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +#include "g_local.h" + +/* +============================================================================== + +PLAYER TRAIL + +============================================================================== + +This is a two-way list containing the a list of points of where +the player has been recently. It is used by monsters for pursuit. + +This is improved from vanilla; now, the list itself is stored in +client data so it can be stored for multiple clients. + +chain = next +enemy = prev + +The head node will always have a null "chain", the tail node +will always have a null "enemy". +*/ + +constexpr size_t TRAIL_LENGTH = 8; + +// places a new entity at the head of the player trail. +// the tail entity may be moved to the front if the length +// is at the end. +static edict_t *PlayerTrail_Spawn(edict_t *owner) +{ + size_t len = 0; + + for (edict_t *tail = owner->client->trail_tail; tail; tail = tail->chain) + len++; + + edict_t *trail; + + // move the tail to the head + if (len == TRAIL_LENGTH) + { + // unlink the old tail + trail = owner->client->trail_tail; + owner->client->trail_tail = trail->chain; + owner->client->trail_tail->enemy = nullptr; + trail->chain = trail->enemy = nullptr; + } + else + { + // spawn a new head + trail = G_Spawn(); + trail->classname = "player_trail"; + } + + // link as new head + if (owner->client->trail_head) + owner->client->trail_head->chain = trail; + trail->enemy = owner->client->trail_head; + owner->client->trail_head = trail; + + // if there's no tail, we become the tail too + if (!owner->client->trail_tail) + owner->client->trail_tail = trail; + + return trail; +} + +// destroys all player trail entities in the map. +// we don't want these to stay around across level loads. +void PlayerTrail_Destroy(edict_t *player) +{ + for (size_t i = 0; i < globals.num_edicts; i++) + if (g_edicts[i].classname && strcmp(g_edicts[i].classname, "player_trail") == 0) + if (!player || g_edicts[i].owner == player) + G_FreeEdict(&g_edicts[i]); + + if (player) + player->client->trail_head = player->client->trail_tail = nullptr; + else for (size_t i = 0; i < game.maxclients; i++) + game.clients[i].trail_head = game.clients[i].trail_tail = nullptr; +} + +// check to see if we can add a new player trail spot +// for this player. +void PlayerTrail_Add(edict_t *player) +{ + // if we can still see the head, we don't want a new one. + if (player->client->trail_head && visible(player, player->client->trail_head)) + return; + // don't spawn trails in intermission, if we're dead, if we're noclipping or not on ground yet + else if (level.intermissiontime || player->health <= 0 || player->movetype == MOVETYPE_NOCLIP || + !player->groundentity) + return; + + edict_t *trail = PlayerTrail_Spawn(player); + trail->s.origin = player->s.old_origin; + trail->timestamp = level.time; + trail->owner = player; +} + +// pick a trail node that matches the player +// we're hunting that is visible to us. +edict_t *PlayerTrail_Pick(edict_t *self, bool next) +{ + // not player or doesn't have a trail yet + if (!self->enemy->client || !self->enemy->client->trail_head) + return nullptr; + + // find which marker head that was dropped while we + // were searching for this enemy + edict_t *marker; + + for (marker = self->enemy->client->trail_head; marker; marker = marker->enemy) + { + if (marker->timestamp <= self->monsterinfo.trail_time) + continue; + + break; + } + + if (next) + { + // find the marker we're closest to + float closest_dist = std::numeric_limits::infinity(); + edict_t *closest = nullptr; + + for (edict_t *m2 = marker; m2; m2 = m2->enemy) + { + float len = (m2->s.origin - self->s.origin).lengthSquared(); + + if (len < closest_dist) + { + closest_dist = len; + closest = m2; + } + } + + // should never happen + if (!closest) + return nullptr; + + // use the next one from the closest one + marker = closest->chain; + } + else + { + // from that marker, find the first one we can see + for (; marker && !visible(self, marker); marker = marker->enemy) + continue; + } + + return marker; +} diff --git a/rerelease/p_view.cpp b/rerelease/p_view.cpp new file mode 100644 index 0000000..5f2e4e7 --- /dev/null +++ b/rerelease/p_view.cpp @@ -0,0 +1,1527 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. + +#include "g_local.h" +#include "m_player.h" +#include "bots/bot_includes.h" + +static edict_t *current_player; +static gclient_t *current_client; + +static vec3_t forward, right, up; +float xyspeed; + +float bobmove; +int bobcycle, bobcycle_run; // odd cycles are right foot going forward +float bobfracsin; // sinf(bobfrac*M_PI) + +/* +=============== +SkipViewModifiers +=============== +*/ +inline bool SkipViewModifiers() { + if ( g_skipViewModifiers->integer && sv_cheats->integer ) { + return true; + } + // don't do bobbing, etc on grapple + if (current_client->ctf_grapple && + current_client->ctf_grapplestate > CTF_GRAPPLE_STATE_FLY) { + return true; + } + // spectator mode + if (current_client->resp.spectator || (G_TeamplayEnabled() && current_client->resp.ctf_team == CTF_NOTEAM)) { + return true; + } + return false; +} + +/* +=============== +SV_CalcRoll + +=============== +*/ +float SV_CalcRoll(const vec3_t &angles, const vec3_t &velocity) +{ + if ( SkipViewModifiers() ) { + return 0.0f; + } + + float sign; + float side; + float value; + + side = velocity.dot(right); + sign = side < 0 ? -1.f : 1.f; + side = fabsf(side); + + value = sv_rollangle->value; + + if (side < sv_rollspeed->value) + side = side * value / sv_rollspeed->value; + else + side = value; + + return side * sign; +} + +/* +=============== +P_DamageFeedback + +Handles color blends and view kicks +=============== +*/ +void P_DamageFeedback(edict_t *player) +{ + gclient_t *client; + float side; + float realcount, count, kick; + vec3_t v; + int l; + constexpr vec3_t armor_color = { 1.0, 1.0, 1.0 }; + constexpr vec3_t power_color = { 0.0, 1.0, 0.0 }; + constexpr vec3_t bcolor = { 1.0, 0.0, 0.0 }; + + client = player->client; + + // flash the backgrounds behind the status numbers + int16_t want_flashes = 0; + + if (client->damage_blood) + want_flashes |= 1; + if (client->damage_armor && !(player->flags & FL_GODMODE) && (client->invincible_time <= level.time)) + want_flashes |= 2; + + if (want_flashes) + { + client->flash_time = level.time + 100_ms; + client->ps.stats[STAT_FLASHES] = want_flashes; + } + else if (client->flash_time < level.time) + client->ps.stats[STAT_FLASHES] = 0; + + // total points of damage shot at the player this frame + count = (float) (client->damage_blood + client->damage_armor + client->damage_parmor); + if (count == 0) + return; // didn't take any damage + + // start a pain animation if still in the player model + if (client->anim_priority < ANIM_PAIN && player->s.modelindex == MODELINDEX_PLAYER) + { + static int i; + + client->anim_priority = ANIM_PAIN; + if (client->ps.pmove.pm_flags & PMF_DUCKED) + { + player->s.frame = FRAME_crpain1 - 1; + client->anim_end = FRAME_crpain4; + } + else + { + i = (i + 1) % 3; + switch (i) + { + case 0: + player->s.frame = FRAME_pain101 - 1; + client->anim_end = FRAME_pain104; + break; + case 1: + player->s.frame = FRAME_pain201 - 1; + client->anim_end = FRAME_pain204; + break; + case 2: + player->s.frame = FRAME_pain301 - 1; + client->anim_end = FRAME_pain304; + break; + } + } + + client->anim_time = 0_ms; + } + + realcount = count; + + // if we took health damage, do a minimum clamp + if (client->damage_blood) + { + if (count < 10) + count = 10; // always make a visible effect + } + else + { + if (count > 2) + count = 2; // don't go too deep + } + + // play an appropriate pain sound + if ((level.time > player->pain_debounce_time) && !(player->flags & FL_GODMODE) && (client->invincible_time <= level.time)) + { + player->pain_debounce_time = level.time + 700_ms; + + constexpr const char *pain_sounds[] = { + "*pain25_1.wav", + "*pain25_2.wav", + "*pain50_1.wav", + "*pain50_2.wav", + "*pain75_1.wav", + "*pain75_2.wav", + "*pain100_1.wav", + "*pain100_2.wav", + }; + + if (player->health < 25) + l = 0; + else if (player->health < 50) + l = 2; + else if (player->health < 75) + l = 4; + else + l = 6; + + if (brandom()) + l |= 1; + + gi.sound(player, CHAN_VOICE, gi.soundindex(pain_sounds[l]), 1, ATTN_NORM, 0); + // Paril: pain noises alert monsters + PlayerNoise(player, player->s.origin, PNOISE_SELF); + } + + // the total alpha of the blend is always proportional to count + if (client->damage_alpha < 0) + client->damage_alpha = 0; + + // [Paril-KEX] tweak the values to rely less on this + // and more on damage indicators + if (client->damage_blood || (client->damage_alpha + count * 0.06f) < 0.15f) + { + client->damage_alpha += count * 0.06f; + + if (client->damage_alpha < 0.06f) + client->damage_alpha = 0.06f; + if (client->damage_alpha > 0.4f) + client->damage_alpha = 0.4f; // don't go too saturated + } + + // mix in colors + v = {}; + + if (client->damage_parmor) + v += power_color * (client->damage_parmor / realcount); + if (client->damage_blood) + v += bcolor * max(15.0f, (client->damage_blood / realcount)); + if (client->damage_armor) + v += armor_color * (client->damage_armor / realcount); + client->damage_blend = v.normalized(); + + // + // calculate view angle kicks + // + kick = (float) abs(client->damage_knockback); + if (kick && player->health > 0) // kick of 0 means no view adjust at all + { + kick = kick * 100 / player->health; + + if (kick < count * 0.5f) + kick = count * 0.5f; + if (kick > 50) + kick = 50; + + v = client->damage_from - player->s.origin; + v.normalize(); + + side = v.dot(right); + client->v_dmg_roll = kick * side * 0.3f; + + side = -v.dot(forward); + client->v_dmg_pitch = kick * side * 0.3f; + + client->v_dmg_time = level.time + DAMAGE_TIME(); + } + + // [Paril-KEX] send view indicators + if (client->num_damage_indicators) + { + gi.WriteByte(svc_damage); + gi.WriteByte(client->num_damage_indicators); + + for (uint8_t i = 0; i < client->num_damage_indicators; i++) + { + auto &indicator = client->damage_indicators[i]; + + // encode total damage into 5 bits + uint8_t encoded = std::clamp((indicator.health + indicator.power + indicator.armor) / 3, 1, 0x1F); + + // encode types in the latter 3 bits + if (indicator.health) + encoded |= 0x20; + if (indicator.armor) + encoded |= 0x40; + if (indicator.power) + encoded |= 0x80; + + gi.WriteByte(encoded); + gi.WriteDir((player->s.origin - indicator.from).normalized()); + } + + gi.unicast(player, false); + } + + // + // clear totals + // + client->damage_blood = 0; + client->damage_armor = 0; + client->damage_parmor = 0; + client->damage_knockback = 0; + client->num_damage_indicators = 0; +} + +/* +=============== +SV_CalcViewOffset + +Auto pitching on slopes? + + fall from 128: 400 = 160000 + fall from 256: 580 = 336400 + fall from 384: 720 = 518400 + fall from 512: 800 = 640000 + fall from 640: 960 = + + damage = deltavelocity*deltavelocity * 0.0001 + +=============== +*/ +void SV_CalcViewOffset(edict_t *ent) +{ + float bob; + float ratio; + float delta; + vec3_t v; + + //=================================== + + // base angles + vec3_t &angles = ent->client->ps.kick_angles; + + // if dead, fix the angle and don't add any kick + if (ent->deadflag && !ent->client->resp.spectator) + { + angles = {}; + + if (ent->flags & FL_SAM_RAIMI) + { + ent->client->ps.viewangles[ROLL] = 0; + ent->client->ps.viewangles[PITCH] = 0; + } + else + { + ent->client->ps.viewangles[ROLL] = 40; + ent->client->ps.viewangles[PITCH] = -15; + } + ent->client->ps.viewangles[YAW] = ent->client->killer_yaw; + } + else + { + // add angles based on weapon kick + angles = P_CurrentKickAngles(ent); + + // add angles based on damage kick + if (ent->client->v_dmg_time > level.time) + { + // [Paril-KEX] 100ms of slack is added to account for + // visual difference in higher tickrates + gtime_t diff = ent->client->v_dmg_time - level.time; + + // slack time remaining + if (DAMAGE_TIME_SLACK()) + { + if (diff > DAMAGE_TIME() - DAMAGE_TIME_SLACK()) + ratio = (DAMAGE_TIME() - diff).seconds() / DAMAGE_TIME_SLACK().seconds(); + else + ratio = diff.seconds() / (DAMAGE_TIME() - DAMAGE_TIME_SLACK()).seconds(); + } + else + ratio = diff.seconds() / (DAMAGE_TIME() - DAMAGE_TIME_SLACK()).seconds(); + + angles[PITCH] += ratio * ent->client->v_dmg_pitch; + angles[ROLL] += ratio * ent->client->v_dmg_roll; + } + + // add pitch based on fall kick + if (ent->client->fall_time > level.time) + { + // [Paril-KEX] 100ms of slack is added to account for + // visual difference in higher tickrates + gtime_t diff = ent->client->fall_time - level.time; + + // slack time remaining + if (DAMAGE_TIME_SLACK()) + { + if (diff > FALL_TIME() - DAMAGE_TIME_SLACK()) + ratio = (FALL_TIME() - diff).seconds() / DAMAGE_TIME_SLACK().seconds(); + else + ratio = diff.seconds() / (FALL_TIME() - DAMAGE_TIME_SLACK()).seconds(); + } + else + ratio = diff.seconds() / (FALL_TIME() - DAMAGE_TIME_SLACK()).seconds(); + angles[PITCH] += ratio * ent->client->fall_value; + } + + // add angles based on velocity + if (!ent->client->pers.bob_skip && !SkipViewModifiers()) + { + delta = ent->velocity.dot(forward); + angles[PITCH] += delta * run_pitch->value; + + delta = ent->velocity.dot(right); + angles[ROLL] += delta * run_roll->value; + + // add angles based on bob + delta = bobfracsin * bob_pitch->value * xyspeed; + if ((ent->client->ps.pmove.pm_flags & PMF_DUCKED) && ent->groundentity) + delta *= 6; // crouching + delta = min(delta, 1.2f); + angles[PITCH] += delta; + delta = bobfracsin * bob_roll->value * xyspeed; + if ((ent->client->ps.pmove.pm_flags & PMF_DUCKED) && ent->groundentity) + delta *= 6; // crouching + delta = min(delta, 1.2f); + if (bobcycle & 1) + delta = -delta; + angles[ROLL] += delta; + } + + // add earthquake angles + if (ent->client->quake_time > level.time) + { + float factor = min(1.0f, (ent->client->quake_time.seconds() / level.time.seconds()) * 0.25f); + + angles.x += crandom_open() * factor; + angles.z += crandom_open() * factor; + angles.y += crandom_open() * factor; + } + } + + // [Paril-KEX] clamp angles + for (int i = 0; i < 3; i++) + angles[i] = clamp(angles[i], -31.f, 31.f); + + //=================================== + + // base origin + + v = {}; + + // add fall height + + if (ent->client->fall_time > level.time) + { + // [Paril-KEX] 100ms of slack is added to account for + // visual difference in higher tickrates + gtime_t diff = ent->client->fall_time - level.time; + + // slack time remaining + if (DAMAGE_TIME_SLACK()) + { + if (diff > FALL_TIME() - DAMAGE_TIME_SLACK()) + ratio = (FALL_TIME() - diff).seconds() / DAMAGE_TIME_SLACK().seconds(); + else + ratio = diff.seconds() / (FALL_TIME() - DAMAGE_TIME_SLACK()).seconds(); + } + else + ratio = diff.seconds() / (FALL_TIME() - DAMAGE_TIME_SLACK()).seconds(); + v[2] -= ratio * ent->client->fall_value * 0.4f; + } + + // add bob height + if (!ent->client->pers.bob_skip && !SkipViewModifiers()) + { + bob = bobfracsin * xyspeed * bob_up->value; + if (bob > 6) + bob = 6; + // gi.DebugGraph (bob *2, 255); + v[2] += bob; + } + + // add kick offset + + + v += P_CurrentKickOrigin(ent); + + // absolutely bound offsets + // so the view can never be outside the player box + + if (v[0] < -14) + v[0] = -14; + else if (v[0] > 14) + v[0] = 14; + if (v[1] < -14) + v[1] = -14; + else if (v[1] > 14) + v[1] = 14; + if (v[2] < -22) + v[2] = -22; + else if (v[2] > 30) + v[2] = 30; + + ent->client->ps.viewoffset = v; +} + +/* +============== +SV_CalcGunOffset +============== +*/ +void SV_CalcGunOffset(edict_t *ent) +{ + int i; + // ROGUE + + // ROGUE - heatbeam shouldn't bob so the beam looks right + if (ent->client->pers.weapon && + !((ent->client->pers.weapon->id == IT_WEAPON_PLASMABEAM || ent->client->pers.weapon->id == IT_WEAPON_GRAPPLE) && ent->client->weaponstate == WEAPON_FIRING) + && !SkipViewModifiers()) + { + // ROGUE + // gun angles from bobbing + ent->client->ps.gunangles[ROLL] = xyspeed * bobfracsin * 0.005f; + ent->client->ps.gunangles[YAW] = xyspeed * bobfracsin * 0.01f; + if (bobcycle & 1) + { + ent->client->ps.gunangles[ROLL] = -ent->client->ps.gunangles[ROLL]; + ent->client->ps.gunangles[YAW] = -ent->client->ps.gunangles[YAW]; + } + + ent->client->ps.gunangles[PITCH] = xyspeed * bobfracsin * 0.005f; + + vec3_t viewangles_delta = ent->client->oldviewangles - ent->client->ps.viewangles; + + for (i = 0; i < 3; i++) + ent->client->slow_view_angles[i] += viewangles_delta[i]; + + // gun angles from delta movement + for (i = 0; i < 3; i++) + { + float &d = ent->client->slow_view_angles[i]; + + if (!d) + continue; + + if (d > 180) + d -= 360; + if (d < -180) + d += 360; + if (d > 45) + d = 45; + if (d < -45) + d = -45; + + // [Sam-KEX] Apply only half-delta. Makes the weapons look less detatched from the player. + if (i == ROLL) + ent->client->ps.gunangles[i] += (0.1f * d) * 0.5f; + else + ent->client->ps.gunangles[i] += (0.2f * d) * 0.5f; + + float reduction_factor = viewangles_delta[i] ? 0.05f : 0.15f; + + if (d > 0) + d = max(0.f, d - gi.frame_time_ms * reduction_factor); + else if (d < 0) + d = min(0.f, d + gi.frame_time_ms * reduction_factor); + } + } + // ROGUE + else + { + for (i = 0; i < 3; i++) + ent->client->ps.gunangles[i] = 0; + } + // ROGUE + + // gun height + ent->client->ps.gunoffset = {}; + + // gun_x / gun_y / gun_z are development tools + for (i = 0; i < 3; i++) + { + ent->client->ps.gunoffset[i] += forward[i] * (gun_y->value); + ent->client->ps.gunoffset[i] += right[i] * gun_x->value; + ent->client->ps.gunoffset[i] += up[i] * (-gun_z->value); + } +} + +/* +============= +SV_CalcBlend +============= +*/ +void SV_CalcBlend(edict_t *ent) +{ + gtime_t remaining; + + ent->client->ps.damage_blend = ent->client->ps.screen_blend = {}; + + // add for powerups + if (ent->client->quad_time > level.time) + { + remaining = ent->client->quad_time - level.time; + if (remaining.milliseconds() == 3000) // beginning to fade + gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage2.wav"), 1, ATTN_NORM, 0); + if (G_PowerUpExpiringRelative(remaining)) + G_AddBlend(0, 0, 1, 0.08f, ent->client->ps.screen_blend); + } + // RAFAEL + else if (ent->client->quadfire_time > level.time) + { + remaining = ent->client->quadfire_time - level.time; + if (remaining.milliseconds() == 3000) // beginning to fade + gi.sound(ent, CHAN_ITEM, gi.soundindex("items/quadfire2.wav"), 1, ATTN_NORM, 0); + if (G_PowerUpExpiringRelative(remaining)) + G_AddBlend(1, 0.2f, 0.5f, 0.08f, ent->client->ps.screen_blend); + } + // RAFAEL + // PMM - double damage + else if (ent->client->double_time > level.time) + { + remaining = ent->client->double_time - level.time; + if (remaining.milliseconds() == 3000) // beginning to fade + gi.sound(ent, CHAN_ITEM, gi.soundindex("misc/ddamage2.wav"), 1, ATTN_NORM, 0); + if (G_PowerUpExpiringRelative(remaining)) + G_AddBlend(0.9f, 0.7f, 0, 0.08f, ent->client->ps.screen_blend); + } + // PMM + else if (ent->client->invincible_time > level.time) + { + remaining = ent->client->invincible_time - level.time; + if (remaining.milliseconds() == 3000) // beginning to fade + gi.sound(ent, CHAN_ITEM, gi.soundindex("items/protect2.wav"), 1, ATTN_NORM, 0); + if (G_PowerUpExpiringRelative(remaining)) + G_AddBlend(1, 1, 0, 0.08f, ent->client->ps.screen_blend); + } + else if (ent->client->invisible_time > level.time) + { + remaining = ent->client->invisible_time - level.time; + if (remaining.milliseconds() == 3000) // beginning to fade + gi.sound(ent, CHAN_ITEM, gi.soundindex("items/protect2.wav"), 1, ATTN_NORM, 0); + if (G_PowerUpExpiringRelative(remaining)) + G_AddBlend(0.8f, 0.8f, 0.8f, 0.08f, ent->client->ps.screen_blend); + } + else if (ent->client->enviro_time > level.time) + { + remaining = ent->client->enviro_time - level.time; + if (remaining.milliseconds() == 3000) // beginning to fade + gi.sound(ent, CHAN_ITEM, gi.soundindex("items/airout.wav"), 1, ATTN_NORM, 0); + if (G_PowerUpExpiringRelative(remaining)) + G_AddBlend(0, 1, 0, 0.08f, ent->client->ps.screen_blend); + } + else if (ent->client->breather_time > level.time) + { + remaining = ent->client->breather_time - level.time; + if (remaining.milliseconds() == 3000) // beginning to fade + gi.sound(ent, CHAN_ITEM, gi.soundindex("items/airout.wav"), 1, ATTN_NORM, 0); + if (G_PowerUpExpiringRelative(remaining)) + G_AddBlend(0.4f, 1, 0.4f, 0.04f, ent->client->ps.screen_blend); + } + + // PGM + if (ent->client->nuke_time > level.time) + { + float brightness = (ent->client->nuke_time - level.time).seconds() / 2.0f; + G_AddBlend(1, 1, 1, brightness, ent->client->ps.screen_blend); + } + if (ent->client->ir_time > level.time) + { + remaining = ent->client->ir_time - level.time; + if (G_PowerUpExpiringRelative(remaining)) + { + ent->client->ps.rdflags |= RDF_IRGOGGLES; + G_AddBlend(1, 0, 0, 0.2f, ent->client->ps.screen_blend); + } + else + ent->client->ps.rdflags &= ~RDF_IRGOGGLES; + } + else + { + ent->client->ps.rdflags &= ~RDF_IRGOGGLES; + } + // PGM + + // add for damage + if (ent->client->damage_alpha > 0) + G_AddBlend(ent->client->damage_blend[0], ent->client->damage_blend[1], ent->client->damage_blend[2], ent->client->damage_alpha, ent->client->ps.damage_blend); + + // [Paril-KEX] drowning visual indicator + if (ent->air_finished < level.time + 9_sec) + { + constexpr vec3_t drown_color = { 0.1f, 0.1f, 0.2f }; + constexpr float max_drown_alpha = 0.75f; + float alpha = (ent->air_finished < level.time) ? 1 : (1.f - ((ent->air_finished - level.time).seconds() / 9.0f)); + G_AddBlend(drown_color[0], drown_color[1], drown_color[2], min(alpha, max_drown_alpha), ent->client->ps.damage_blend); + } + +#if 0 + if (ent->client->bonus_alpha > 0) + G_AddBlend(0.85f, 0.7f, 0.3f, ent->client->bonus_alpha, ent->client->ps.damage_blend); +#endif + + // drop the damage value + ent->client->damage_alpha -= gi.frame_time_s * 0.6f; + if (ent->client->damage_alpha < 0) + ent->client->damage_alpha = 0; + + // drop the bonus value + ent->client->bonus_alpha -= gi.frame_time_s; + if (ent->client->bonus_alpha < 0) + ent->client->bonus_alpha = 0; +} + +/* +============= +P_WorldEffects +============= +*/ +void P_WorldEffects() +{ + bool breather; + bool envirosuit; + water_level_t waterlevel, old_waterlevel; + + if (current_player->movetype == MOVETYPE_NOCLIP) + { + current_player->air_finished = level.time + 12_sec; // don't need air + return; + } + + waterlevel = current_player->waterlevel; + old_waterlevel = current_client->old_waterlevel; + current_client->old_waterlevel = waterlevel; + + breather = current_client->breather_time > level.time; + envirosuit = current_client->enviro_time > level.time; + + // + // if just entered a water volume, play a sound + // + if (!old_waterlevel && waterlevel) + { + PlayerNoise(current_player, current_player->s.origin, PNOISE_SELF); + if (current_player->watertype & CONTENTS_LAVA) + gi.sound(current_player, CHAN_BODY, gi.soundindex("player/lava_in.wav"), 1, ATTN_NORM, 0); + else if (current_player->watertype & CONTENTS_SLIME) + gi.sound(current_player, CHAN_BODY, gi.soundindex("player/watr_in.wav"), 1, ATTN_NORM, 0); + else if (current_player->watertype & CONTENTS_WATER) + gi.sound(current_player, CHAN_BODY, gi.soundindex("player/watr_in.wav"), 1, ATTN_NORM, 0); + current_player->flags |= FL_INWATER; + + // clear damage_debounce, so the pain sound will play immediately + current_player->damage_debounce_time = level.time - 1_sec; + } + + // + // if just completely exited a water volume, play a sound + // + if (old_waterlevel && !waterlevel) + { + PlayerNoise(current_player, current_player->s.origin, PNOISE_SELF); + gi.sound(current_player, CHAN_BODY, gi.soundindex("player/watr_out.wav"), 1, ATTN_NORM, 0); + current_player->flags &= ~FL_INWATER; + } + + // + // check for head just going under water + // + if (old_waterlevel != WATER_UNDER && waterlevel == WATER_UNDER) + { + gi.sound(current_player, CHAN_BODY, gi.soundindex("player/watr_un.wav"), 1, ATTN_NORM, 0); + } + + // + // check for head just coming out of water + // + if (current_player->health > 0 && old_waterlevel == WATER_UNDER && waterlevel != WATER_UNDER) + { + if (current_player->air_finished < level.time) + { // gasp for air + gi.sound(current_player, CHAN_VOICE, gi.soundindex("player/gasp1.wav"), 1, ATTN_NORM, 0); + PlayerNoise(current_player, current_player->s.origin, PNOISE_SELF); + } + else if (current_player->air_finished < level.time + 11_sec) + { // just break surface + gi.sound(current_player, CHAN_VOICE, gi.soundindex("player/gasp2.wav"), 1, ATTN_NORM, 0); + } + } + + // + // check for drowning + // + if (waterlevel == WATER_UNDER) + { + // breather or envirosuit give air + if (breather || envirosuit) + { + current_player->air_finished = level.time + 10_sec; + + if (((current_client->breather_time - level.time).milliseconds() % 2500) == 0) + { + if (!current_client->breather_sound) + gi.sound(current_player, CHAN_AUTO, gi.soundindex("player/u_breath1.wav"), 1, ATTN_NORM, 0); + else + gi.sound(current_player, CHAN_AUTO, gi.soundindex("player/u_breath2.wav"), 1, ATTN_NORM, 0); + current_client->breather_sound ^= 1; + PlayerNoise(current_player, current_player->s.origin, PNOISE_SELF); + // FIXME: release a bubble? + } + } + + // if out of air, start drowning + if (current_player->air_finished < level.time) + { // drown! + if (current_player->client->next_drown_time < level.time && current_player->health > 0) + { + current_player->client->next_drown_time = level.time + 1_sec; + + // take more damage the longer underwater + current_player->dmg += 2; + if (current_player->dmg > 15) + current_player->dmg = 15; + + // play a gurp sound instead of a normal pain sound + if (current_player->health <= current_player->dmg) + gi.sound(current_player, CHAN_VOICE, gi.soundindex("player/drown1.wav"), 1, ATTN_NORM, 0); + else if (brandom()) + gi.sound(current_player, CHAN_VOICE, gi.soundindex("*gurp1.wav"), 1, ATTN_NORM, 0); + else + gi.sound(current_player, CHAN_VOICE, gi.soundindex("*gurp2.wav"), 1, ATTN_NORM, 0); + + current_player->pain_debounce_time = level.time; + + T_Damage(current_player, world, world, vec3_origin, current_player->s.origin, vec3_origin, current_player->dmg, 0, DAMAGE_NO_ARMOR, MOD_WATER); + } + } + // Paril: almost-drowning sounds + // FIXME use better sound + precache in worldspawn + else if (current_player->air_finished <= level.time + 3_sec) + { + if (current_player->client->next_drown_time < level.time) + { + gi.sound(current_player, CHAN_VOICE, gi.soundindex("player/wade1.wav"), 1, ATTN_NORM, 0); + current_player->client->next_drown_time = level.time + 1_sec; + } + } + } + else + { + current_player->air_finished = level.time + 12_sec; + current_player->dmg = 2; + } + + // + // check for sizzle damage + // + if (waterlevel && (current_player->watertype & (CONTENTS_LAVA | CONTENTS_SLIME)) && current_player->slime_debounce_time <= level.time) + { + if (current_player->watertype & CONTENTS_LAVA) + { + if (current_player->health > 0 && current_player->pain_debounce_time <= level.time && current_client->invincible_time < level.time) + { + if (brandom()) + gi.sound(current_player, CHAN_VOICE, gi.soundindex("player/burn1.wav"), 1, ATTN_NORM, 0); + else + gi.sound(current_player, CHAN_VOICE, gi.soundindex("player/burn2.wav"), 1, ATTN_NORM, 0); + current_player->pain_debounce_time = level.time + 1_sec; + } + + int dmg = (envirosuit ? 1 : 3) * waterlevel; // take 1/3 damage with envirosuit + + T_Damage(current_player, world, world, vec3_origin, current_player->s.origin, vec3_origin, dmg, 0, DAMAGE_NONE, MOD_LAVA); + current_player->slime_debounce_time = level.time + 10_hz; + } + + if (current_player->watertype & CONTENTS_SLIME) + { + if (!envirosuit) + { // no damage from slime with envirosuit + T_Damage(current_player, world, world, vec3_origin, current_player->s.origin, vec3_origin, 1 * waterlevel, 0, DAMAGE_NONE, MOD_SLIME); + current_player->slime_debounce_time = level.time + 10_hz; + } + } + } +} + +/* +=============== +G_SetClientEffects +=============== +*/ +void G_SetClientEffects(edict_t *ent) +{ + int pa_type; + + ent->s.effects = EF_NONE; + ent->s.renderfx &= RF_STAIR_STEP; + ent->s.renderfx |= RF_IR_VISIBLE; + ent->s.alpha = 1.0; + + if (ent->health <= 0 || level.intermissiontime) + return; + + if (ent->flags & FL_FLASHLIGHT) + ent->s.effects |= EF_FLASHLIGHT; + + //========= + // PGM + if (ent->flags & FL_DISGUISED) + ent->s.renderfx |= RF_USE_DISGUISE; + + if (gamerules->integer) + { + if (DMGame.PlayerEffects) + DMGame.PlayerEffects(ent); + } + // PGM + //========= + + if (ent->powerarmor_time > level.time) + { + pa_type = PowerArmorType(ent); + if (pa_type == IT_ITEM_POWER_SCREEN) + { + ent->s.effects |= EF_POWERSCREEN; + } + else if (pa_type == IT_ITEM_POWER_SHIELD) + { + ent->s.effects |= EF_COLOR_SHELL; + ent->s.renderfx |= RF_SHELL_GREEN; + } + } + + // ZOID + CTFEffects(ent); + // ZOID + + if (ent->client->quad_time > level.time) + { + if (G_PowerUpExpiring(ent->client->quad_time)) + CTFSetPowerUpEffect(ent, EF_QUAD); + } + + // RAFAEL + if (ent->client->quadfire_time > level.time) + {; + if (G_PowerUpExpiring(ent->client->quadfire_time)) + CTFSetPowerUpEffect(ent, EF_DUALFIRE); + } + // RAFAEL + //======= + // ROGUE + if (ent->client->double_time > level.time) + { + if (G_PowerUpExpiring(ent->client->double_time)) + CTFSetPowerUpEffect(ent, EF_DOUBLE); + } + if ((ent->client->owned_sphere) && (ent->client->owned_sphere->spawnflags == SPHERE_DEFENDER)) + { + CTFSetPowerUpEffect(ent, EF_HALF_DAMAGE); + } + if (ent->client->tracker_pain_time > level.time) + { + ent->s.effects |= EF_TRACKERTRAIL; + } + if (ent->client->invisible_time > level.time) + { + if (ent->client->invisibility_fade_time <= level.time) + ent->s.alpha = 0.1f; + else + { + float x = (ent->client->invisibility_fade_time - level.time).seconds() / INVISIBILITY_TIME.seconds(); + ent->s.alpha = std::clamp(x, 0.1f, 1.0f); + } + } + // ROGUE + //======= + + if (ent->client->invincible_time > level.time) + { + if (G_PowerUpExpiring(ent->client->invincible_time)) + CTFSetPowerUpEffect(ent, EF_PENT); + } + + // show cheaters!!! + if (ent->flags & FL_GODMODE) + { + ent->s.effects |= EF_COLOR_SHELL; + ent->s.renderfx |= (RF_SHELL_RED | RF_SHELL_GREEN | RF_SHELL_BLUE); + } + +#if 0 + // disintegrator stuff + if (ent->disintegrator_time) + { + if (ent->disintegrator_time > 100_sec) + { + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_BOSSTPORT); + gi.WritePosition(ent->s.origin); + gi.multicast(ent->s.origin, MULTICAST_PHS, false); + + Killed(ent, ent, ent, 999999, vec3_origin, MOD_NUKE); + ent->disintegrator_time = 0_ms; + ThrowClientHead(ent, 9999); + ent->s.modelindex = 0; + ent->solid = SOLID_NOT; + } + else + { + ent->disintegrator_time = max(0_ms, ent->disintegrator_time - 1500_ms); + + if (ent->disintegrator_time) + ent->s.alpha = max(1 / 255.f, 1.f - (ent->disintegrator_time.seconds() / 100)); + else + ent->s.alpha = 1; + } + } +#endif +} + +/* +=============== +G_SetClientEvent +=============== +*/ +void G_SetClientEvent(edict_t *ent) +{ + if (ent->s.event) + return; + + if (ent->client->ps.pmove.pm_flags & PMF_ON_LADDER) + { + if (current_client->last_ladder_sound < level.time && + (current_client->last_ladder_pos - ent->s.origin).length() > 48.f) + { + ent->s.event = EV_LADDER_STEP; + current_client->last_ladder_pos = ent->s.origin; + current_client->last_ladder_sound = level.time + LADDER_SOUND_TIME; + } + } + else if (ent->groundentity && xyspeed > 225) + { + if ((int) (current_client->bobtime + bobmove) != bobcycle_run) + ent->s.event = EV_FOOTSTEP; + } +} + +/* +=============== +G_SetClientSound +=============== +*/ +void G_SetClientSound(edict_t *ent) +{ + // help beep (no more than three times) + if (ent->client->pers.helpchanged && ent->client->pers.helpchanged <= 3 && ent->client->pers.help_time < level.time) + { + if (ent->client->pers.helpchanged == 1) // [KEX] haleyjd: once only + gi.sound(ent, CHAN_AUTO, gi.soundindex("misc/pc_up.wav"), 1, ATTN_STATIC, 0); + ent->client->pers.helpchanged++; + ent->client->pers.help_time = level.time + 5_sec; + } + + // reset defaults + ent->s.sound = 0; + ent->s.loop_attenuation = 0; + ent->s.loop_volume = 0; + + if (ent->waterlevel && (ent->watertype & (CONTENTS_LAVA | CONTENTS_SLIME))) + { + ent->s.sound = snd_fry; + return; + } + + if (ent->deadflag || ent->client->resp.spectator) + return; + + if (ent->client->weapon_sound) + ent->s.sound = ent->client->weapon_sound; + else if (ent->client->pers.weapon) + { + if (ent->client->pers.weapon->id == IT_WEAPON_RAILGUN) + ent->s.sound = gi.soundindex("weapons/rg_hum.wav"); + else if (ent->client->pers.weapon->id == IT_WEAPON_BFG) + ent->s.sound = gi.soundindex("weapons/bfg_hum.wav"); + // RAFAEL + else if (ent->client->pers.weapon->id == IT_WEAPON_PHALANX) + ent->s.sound = gi.soundindex("weapons/phaloop.wav"); + // RAFAEL + } + + // [Paril-KEX] if no other sound is playing, play appropriate grapple sounds + if (!ent->s.sound && ent->client->ctf_grapple) + { + if (ent->client->ctf_grapplestate == CTF_GRAPPLE_STATE_PULL) + ent->s.sound = gi.soundindex("weapons/grapple/grpull.wav"); + else if (ent->client->ctf_grapplestate == CTF_GRAPPLE_STATE_FLY) + ent->s.sound = gi.soundindex("weapons/grapple/grfly.wav"); + else if (ent->client->ctf_grapplestate == CTF_GRAPPLE_STATE_HANG) + ent->s.sound = gi.soundindex("weapons/grapple/grhang.wav"); + } + + // weapon sounds play at a higher attn + ent->s.loop_attenuation = ATTN_NORM; +} + +/* +=============== +G_SetClientFrame +=============== +*/ +void G_SetClientFrame(edict_t *ent) +{ + gclient_t *client; + bool duck, run; + + if (ent->s.modelindex != MODELINDEX_PLAYER) + return; // not in the player model + + client = ent->client; + + if (client->ps.pmove.pm_flags & PMF_DUCKED) + duck = true; + else + duck = false; + if (xyspeed) + run = true; + else + run = false; + + // check for stand/duck and stop/go transitions + if (duck != client->anim_duck && client->anim_priority < ANIM_DEATH) + goto newanim; + if (run != client->anim_run && client->anim_priority == ANIM_BASIC) + goto newanim; + if (!ent->groundentity && client->anim_priority <= ANIM_WAVE) + goto newanim; + + if (client->anim_time > level.time) + return; + else if ((client->anim_priority & ANIM_REVERSED) && (ent->s.frame > client->anim_end)) + { + if (client->anim_time <= level.time) + { + ent->s.frame--; + client->anim_time = level.time + 10_hz; + } + return; + } + else if (!(client->anim_priority & ANIM_REVERSED) && (ent->s.frame < client->anim_end)) + { + // continue an animation + if (client->anim_time <= level.time) + { + ent->s.frame++; + client->anim_time = level.time + 10_hz; + } + return; + } + + if (client->anim_priority == ANIM_DEATH) + return; // stay there + if (client->anim_priority == ANIM_JUMP) + { + if (!ent->groundentity) + return; // stay there + ent->client->anim_priority = ANIM_WAVE; + + if (duck) + { + ent->s.frame = FRAME_jump6; + ent->client->anim_end = FRAME_jump4; + ent->client->anim_priority |= ANIM_REVERSED; + } + else + { + ent->s.frame = FRAME_jump3; + ent->client->anim_end = FRAME_jump6; + } + ent->client->anim_time = level.time + 10_hz; + return; + } + +newanim: + // return to either a running or standing frame + client->anim_priority = ANIM_BASIC; + client->anim_duck = duck; + client->anim_run = run; + client->anim_time = level.time + 10_hz; + + if (!ent->groundentity) + { + // ZOID: if on grapple, don't go into jump frame, go into standing + // frame + if (client->ctf_grapple) + { + if (duck) + { + ent->s.frame = FRAME_crstnd01; + client->anim_end = FRAME_crstnd19; + } + else + { + ent->s.frame = FRAME_stand01; + client->anim_end = FRAME_stand40; + } + } + else + { + // ZOID + client->anim_priority = ANIM_JUMP; + + if (duck) + { + if (ent->s.frame != FRAME_crwalk2) + ent->s.frame = FRAME_crwalk1; + client->anim_end = FRAME_crwalk2; + } + else + { + if (ent->s.frame != FRAME_jump2) + ent->s.frame = FRAME_jump1; + client->anim_end = FRAME_jump2; + } + } + } + else if (run) + { // running + if (duck) + { + ent->s.frame = FRAME_crwalk1; + client->anim_end = FRAME_crwalk6; + } + else + { + ent->s.frame = FRAME_run1; + client->anim_end = FRAME_run6; + } + } + else + { // standing + if (duck) + { + ent->s.frame = FRAME_crstnd01; + client->anim_end = FRAME_crstnd19; + } + else + { + ent->s.frame = FRAME_stand01; + client->anim_end = FRAME_stand40; + } + } +} + +// [Paril-KEX] +static void P_RunMegaHealth(edict_t *ent) +{ + if (!ent->client->pers.megahealth_time) + return; + else if (ent->health <= ent->max_health) + { + ent->client->pers.megahealth_time = 0_ms; + return; + } + + ent->client->pers.megahealth_time -= FRAME_TIME_S; + + if (ent->client->pers.megahealth_time <= 0_ms) + { + ent->health--; + + if (ent->health > ent->max_health) + ent->client->pers.megahealth_time = 1000_ms; + else + ent->client->pers.megahealth_time = 0_ms; + } +} + +// [Paril-KEX] push all players' origins back to match their lag compensation +void G_LagCompensate(edict_t *from_player, const vec3_t &start, const vec3_t &dir) +{ + uint32_t current_frame = gi.ServerFrame(); + + // if you need this to fight monsters, you need help + if (!deathmatch->integer) + return; + else if (!g_lag_compensation->integer) + return; + // don't need this + else if (from_player->client->cmd.server_frame >= current_frame || + (from_player->svflags & SVF_BOT)) + return; + + int32_t frame_delta = (current_frame - from_player->client->cmd.server_frame) + 1; + + for (auto player : active_players()) + { + // we aren't gonna hit ourselves + if (player == from_player) + continue; + + // not enough data, spare them + if (player->client->num_lag_origins < frame_delta) + continue; + + // if they're way outside of cone of vision, they won't be captured in this + if ((player->s.origin - start).normalized().dot(dir) < 0.75f) + continue; + + int32_t lag_id = (player->client->next_lag_origin - 1) - (frame_delta - 1); + + if (lag_id < 0) + lag_id = game.max_lag_origins + lag_id; + + if (lag_id < 0 || lag_id >= player->client->num_lag_origins) + { + gi.Com_Print("lag compensation error\n"); + G_UnLagCompensate(); + return; + } + + const vec3_t &lag_origin = (game.lag_origins + ((player->s.number - 1) * game.max_lag_origins))[lag_id]; + + // no way they'd be hit if they aren't in the PVS + if (!gi.inPVS(lag_origin, start, false)) + continue; + + // only back up once + if (!player->client->is_lag_compensated) + { + player->client->is_lag_compensated = true; + player->client->lag_restore_origin = player->s.origin; + } + + player->s.origin = lag_origin; + + gi.linkentity(player); + } +} + +// [Paril-KEX] pop everybody's lag compensation values +void G_UnLagCompensate() +{ + for (auto player : active_players()) + { + if (player->client->is_lag_compensated) + { + player->client->is_lag_compensated = false; + player->s.origin = player->client->lag_restore_origin; + gi.linkentity(player); + } + } +} + +// [Paril-KEX] save the current lag compensation value +static void G_SaveLagCompensation(edict_t *ent) +{ + (game.lag_origins + ((ent->s.number - 1) * game.max_lag_origins))[ent->client->next_lag_origin] = ent->s.origin; + ent->client->next_lag_origin = (ent->client->next_lag_origin + 1) % game.max_lag_origins; + + if (ent->client->num_lag_origins < game.max_lag_origins) + ent->client->num_lag_origins++; +} + +/* +================= +ClientEndServerFrame + +Called for each player at the end of the server frame +and right after spawning +================= +*/ +void ClientEndServerFrame(edict_t *ent) +{ + // no player exists yet (load game) + if (!ent->client->pers.spawned) + return; + + float bobtime, bobtime_run; + + current_player = ent; + current_client = ent->client; + + // check fog changes + P_ForceFogTransition(ent, false); + + // check goals + G_PlayerNotifyGoal(ent); + + // mega health + P_RunMegaHealth(ent); + + // + // If the origin or velocity have changed since ClientThink(), + // update the pmove values. This will happen when the client + // is pushed by a bmodel or kicked by an explosion. + // + // If it wasn't updated here, the view position would lag a frame + // behind the body position when pushed -- "sinking into plats" + // + current_client->ps.pmove.origin = ent->s.origin; + current_client->ps.pmove.velocity = ent->velocity; + + // + // If the end of unit layout is displayed, don't give + // the player any normal movement attributes + // + if (level.intermissiontime || ent->client->awaiting_respawn) + { + if (ent->client->awaiting_respawn || (level.intermission_eou || level.is_n64 || (deathmatch->integer && level.intermissiontime))) + { + current_client->ps.screen_blend[3] = current_client->ps.damage_blend[3] = 0; + current_client->ps.fov = 90; + current_client->ps.gunindex = 0; + } + G_SetStats(ent); + G_SetCoopStats(ent); + + // if the scoreboard is up, update it if a client leaves + if (deathmatch->integer && ent->client->showscores && ent->client->menutime) + { + DeathmatchScoreboardMessage(ent, ent->enemy); + gi.unicast(ent, false); + ent->client->menutime = 0_ms; + } + + return; + } + + // ZOID + // regen tech + CTFApplyRegeneration(ent); + // ZOID + + AngleVectors(ent->client->v_angle, forward, right, up); + + // burn from lava, etc + P_WorldEffects(); + + // + // set model angles from view angles so other things in + // the world can tell which direction you are looking + // + if (ent->client->v_angle[PITCH] > 180) + ent->s.angles[PITCH] = (-360 + ent->client->v_angle[PITCH]) / 3; + else + ent->s.angles[PITCH] = ent->client->v_angle[PITCH] / 3; + + ent->s.angles[YAW] = ent->client->v_angle[YAW]; + ent->s.angles[ROLL] = 0; + ent->s.angles[ROLL] = SV_CalcRoll(ent->s.angles, ent->velocity) * 4; + + // + // calculate speed and cycle to be used for + // all cyclic walking effects + // + xyspeed = sqrt(ent->velocity[0] * ent->velocity[0] + ent->velocity[1] * ent->velocity[1]); + + if (xyspeed < 5) + { + bobmove = 0; + current_client->bobtime = 0; // start at beginning of cycle again + } + else if (ent->groundentity) + { // so bobbing only cycles when on ground + if (xyspeed > 210) + bobmove = gi.frame_time_ms / 400.f; + else if (xyspeed > 100) + bobmove = gi.frame_time_ms / 800.f; + else + bobmove = gi.frame_time_ms / 1600.f; + } + + bobtime = (current_client->bobtime += bobmove); + bobtime_run = bobtime; + + if ((current_client->ps.pmove.pm_flags & PMF_DUCKED) && ent->groundentity) + bobtime *= 4; + + bobcycle = (int) bobtime; + bobcycle_run = (int) bobtime_run; + bobfracsin = fabsf(sinf(bobtime * PIf)); + + // apply all the damage taken this frame + P_DamageFeedback(ent); + + // determine the view offsets + SV_CalcViewOffset(ent); + + // determine the gun offsets + SV_CalcGunOffset(ent); + + // determine the full screen color blend + // must be after viewoffset, so eye contents can be + // accurately determined + SV_CalcBlend(ent); + + // chase cam stuff + if (ent->client->resp.spectator) + G_SetSpectatorStats(ent); + else + G_SetStats(ent); + + G_CheckChaseStats(ent); + + G_SetCoopStats(ent); + + G_SetClientEvent(ent); + + G_SetClientEffects(ent); + + G_SetClientSound(ent); + + G_SetClientFrame(ent); + + ent->client->oldvelocity = ent->velocity; + ent->client->oldviewangles = ent->client->ps.viewangles; + ent->client->oldgroundentity = ent->groundentity; + + // ZOID + if (ent->client->menudirty && ent->client->menutime <= level.time) + { + if (ent->client->menu) + { + PMenu_Do_Update(ent); + gi.unicast(ent, true); + } + ent->client->menutime = level.time; + ent->client->menudirty = false; + } + // ZOID + + // if the scoreboard is up, update it + if (ent->client->showscores && ent->client->menutime <= level.time) + { + // ZOID + if (ent->client->menu) + { + PMenu_Do_Update(ent); + ent->client->menudirty = false; + } + else + // ZOID + DeathmatchScoreboardMessage(ent, ent->enemy); + gi.unicast(ent, false); + ent->client->menutime = level.time + 3_sec; + } + + if ( ( ent->svflags & SVF_BOT ) != 0 ) { + Bot_EndFrame( ent ); + } + + P_AssignClientSkinnum(ent); + + if (deathmatch->integer) + G_SaveLagCompensation(ent); + + Compass_Update(ent, false); +} \ No newline at end of file diff --git a/rerelease/p_weapon.cpp b/rerelease/p_weapon.cpp new file mode 100644 index 0000000..a166df7 --- /dev/null +++ b/rerelease/p_weapon.cpp @@ -0,0 +1,1907 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// g_weapon.c + +#include "g_local.h" +#include "m_player.h" + +bool is_quad; +// RAFAEL +bool is_quadfire; +// RAFAEL +player_muzzle_t is_silenced; + +// PGM +byte damage_multiplier; +// PGM + +void weapon_grenade_fire(edict_t *ent, bool held); +// RAFAEL +void weapon_trap_fire(edict_t *ent, bool held); +// RAFAEL + +//======== +// [Kex] +bool G_CheckInfiniteAmmo(gitem_t *item) +{ + if (item->flags & IF_NO_INFINITE_AMMO) + return false; + + return g_infinite_ammo->integer || g_instagib->integer; +} + +//======== +// ROGUE +byte P_DamageModifier(edict_t *ent) +{ + is_quad = 0; + damage_multiplier = 1; + + if (ent->client->quad_time > level.time) + { + damage_multiplier *= 4; + is_quad = 1; + + // if we're quad and DF_NO_STACK_DOUBLE is on, return now. + if (g_dm_no_stack_double->integer) + return damage_multiplier; + } + + if (ent->client->double_time > level.time) + { + damage_multiplier *= 2; + is_quad = 1; + } + + return damage_multiplier; +} +// ROGUE +//======== + +// [Paril-KEX] kicks in vanilla take place over 2 10hz server +// frames; this is to mimic that visual behavior on any tickrate. +inline float P_CurrentKickFactor(edict_t *ent) +{ + if (ent->client->kick.time < level.time) + return 0.f; + + float f = (ent->client->kick.time - level.time).seconds() / ent->client->kick.total.seconds(); + return f; +} + +// [Paril-KEX] +vec3_t P_CurrentKickAngles(edict_t *ent) +{ + return ent->client->kick.angles * P_CurrentKickFactor(ent); +} + +vec3_t P_CurrentKickOrigin(edict_t *ent) +{ + return ent->client->kick.origin * P_CurrentKickFactor(ent); +} + +void P_AddWeaponKick(edict_t *ent, const vec3_t &origin, const vec3_t &angles) +{ + ent->client->kick.origin = origin; + ent->client->kick.angles = angles; + ent->client->kick.total = 200_ms; + ent->client->kick.time = level.time + ent->client->kick.total; +} + +void P_ProjectSource(edict_t *ent, const vec3_t &angles, vec3_t distance, vec3_t &result_start, vec3_t &result_dir) +{ + if (ent->client->pers.hand == LEFT_HANDED) + distance[1] *= -1; + else if (ent->client->pers.hand == CENTER_HANDED) + distance[1] = 0; + + vec3_t forward, right, up; + vec3_t eye_position = (ent->s.origin + vec3_t{ 0, 0, (float) ent->viewheight }); + + AngleVectors(angles, forward, right, up); + + result_start = G_ProjectSource2(eye_position, distance, forward, right, up); + + vec3_t end = eye_position + forward * 8192; + contents_t mask = MASK_PROJECTILE & ~CONTENTS_DEADMONSTER; + + // [Paril-KEX] + if (!G_ShouldPlayersCollide(true)) + mask &= ~CONTENTS_PLAYER; + + trace_t tr = gi.traceline(eye_position, end, ent, mask); + + // if the point was a monster & close to us, use raw forward + // so railgun pierces properly + if (tr.startsolid || ((tr.contents & (CONTENTS_MONSTER | CONTENTS_PLAYER)) && (tr.fraction * 8192.f) < 128.f)) + result_dir = forward; + else + { + end = tr.endpos; + result_dir = (end - result_start).normalized(); + +#if 0 + // correction for blocked shots + trace_t eye_tr = gi.traceline(result_start, result_start + (result_dir * tr.fraction * 8192.f), ent, mask); + + if ((eye_tr.endpos - tr.endpos).length() > 32.f) + { + result_start = eye_position; + result_dir = (end - result_start).normalized(); + return; + } +#endif + } +} + +/* +=============== +PlayerNoise + +Each player can have two noise objects associated with it: +a personal noise (jumping, pain, weapon firing), and a weapon +target noise (bullet wall impacts) + +Monsters that don't directly see the player can move +to a noise in hopes of seeing the player from there. +=============== +*/ +void PlayerNoise(edict_t *who, const vec3_t &where, player_noise_t type) +{ + edict_t *noise; + + if (type == PNOISE_WEAPON) + { + if (who->client->silencer_shots) + who->client->invisibility_fade_time = level.time + (INVISIBILITY_TIME / 5); + else + who->client->invisibility_fade_time = level.time + INVISIBILITY_TIME; + + if (who->client->silencer_shots) + { + who->client->silencer_shots--; + return; + } + } + + if (deathmatch->integer) + return; + + if (who->flags & FL_NOTARGET) + return; + + if (type == PNOISE_SELF && + (who->client->landmark_free_fall || who->client->landmark_noise_time >= level.time)) + return; + + // ROGUE + if (who->flags & FL_DISGUISED) + { + if (type == PNOISE_WEAPON) + { + level.disguise_violator = who; + level.disguise_violation_time = level.time + 500_ms; + } + else + return; + } + // ROGUE + + if (!who->mynoise) + { + noise = G_Spawn(); + noise->classname = "player_noise"; + noise->mins = { -8, -8, -8 }; + noise->maxs = { 8, 8, 8 }; + noise->owner = who; + noise->svflags = SVF_NOCLIENT; + who->mynoise = noise; + + noise = G_Spawn(); + noise->classname = "player_noise"; + noise->mins = { -8, -8, -8 }; + noise->maxs = { 8, 8, 8 }; + noise->owner = who; + noise->svflags = SVF_NOCLIENT; + who->mynoise2 = noise; + } + + if (type == PNOISE_SELF || type == PNOISE_WEAPON) + { + noise = who->mynoise; + who->client->sound_entity = noise; + who->client->sound_entity_time = level.time; + } + else // type == PNOISE_IMPACT + { + noise = who->mynoise2; + who->client->sound2_entity = noise; + who->client->sound2_entity_time = level.time; + } + + noise->s.origin = where; + noise->absmin = where - noise->maxs; + noise->absmax = where + noise->maxs; + noise->teleport_time = level.time; + gi.linkentity(noise); +} + +inline bool G_WeaponShouldStay() +{ + if (deathmatch->integer) + return g_dm_weapons_stay->integer; + else if (coop->integer) + return !P_UseCoopInstancedItems(); + + return false; +} + +void G_CheckAutoSwitch(edict_t *ent, gitem_t *item, bool is_new); + +bool Pickup_Weapon(edict_t *ent, edict_t *other) +{ + item_id_t index; + gitem_t *ammo; + + index = ent->item->id; + + if (G_WeaponShouldStay() && other->client->pers.inventory[index]) + { + if (!(ent->spawnflags & (SPAWNFLAG_ITEM_DROPPED | SPAWNFLAG_ITEM_DROPPED_PLAYER))) + return false; // leave the weapon for others to pickup + } + + bool is_new = !other->client->pers.inventory[index]; + + other->client->pers.inventory[index]++; + + if (!(ent->spawnflags & SPAWNFLAG_ITEM_DROPPED)) + { + // give them some ammo with it + // PGM -- IF APPROPRIATE! + if (ent->item->ammo) // PGM + { + ammo = GetItemByIndex(ent->item->ammo); + // RAFAEL: Don't get infinite ammo with trap + if (G_CheckInfiniteAmmo(ammo)) + Add_Ammo(other, ammo, 1000); + else + Add_Ammo(other, ammo, ammo->quantity); + } + + if (!(ent->spawnflags & SPAWNFLAG_ITEM_DROPPED_PLAYER)) + { + if (deathmatch->integer) + { + if (g_dm_weapons_stay->integer) + ent->flags |= FL_RESPAWN; + + SetRespawn( ent, gtime_t::from_sec(g_weapon_respawn_time->integer), !g_dm_weapons_stay->integer); + } + if (coop->integer) + ent->flags |= FL_RESPAWN; + } + } + + G_CheckAutoSwitch(other, ent->item, is_new); + + return true; +} + +static void Weapon_RunThink(edict_t *ent) +{ + // call active weapon think routine + if (!ent->client->pers.weapon->weaponthink) + return; + + P_DamageModifier(ent); + // RAFAEL + is_quadfire = (ent->client->quadfire_time > level.time); + // RAFAEL + if (ent->client->silencer_shots) + is_silenced = MZ_SILENCED; + else + is_silenced = MZ_NONE; + ent->client->pers.weapon->weaponthink(ent); +} + +/* +=============== +ChangeWeapon + +The old weapon has been dropped all the way, so make the new one +current +=============== +*/ +void ChangeWeapon(edict_t *ent) +{ + // [Paril-KEX] + if (ent->health > 0 && !g_instant_weapon_switch->integer && ((ent->client->latched_buttons | ent->client->buttons) & BUTTON_HOLSTER)) + return; + + if (ent->client->grenade_time) + { + // force a weapon think to drop the held grenade + ent->client->weapon_sound = 0; + Weapon_RunThink(ent); + ent->client->grenade_time = 0_ms; + } + + if (ent->client->pers.weapon) + { + ent->client->pers.lastweapon = ent->client->pers.weapon; + + if (ent->client->newweapon && ent->client->newweapon != ent->client->pers.weapon) + gi.sound(ent, CHAN_WEAPON, gi.soundindex("weapons/change.wav"), 1, ATTN_NORM, 0); + } + + ent->client->pers.weapon = ent->client->newweapon; + ent->client->newweapon = nullptr; + ent->client->machinegun_shots = 0; + + // set visible model + if (ent->s.modelindex == MODELINDEX_PLAYER) + P_AssignClientSkinnum(ent); + + if (!ent->client->pers.weapon) + { // dead + ent->client->ps.gunindex = 0; + ent->client->ps.gunskin = 0; + return; + } + + ent->client->weaponstate = WEAPON_ACTIVATING; + ent->client->ps.gunframe = 0; + ent->client->ps.gunindex = gi.modelindex(ent->client->pers.weapon->view_model); + ent->client->ps.gunskin = 0; + ent->client->weapon_sound = 0; + + ent->client->anim_priority = ANIM_PAIN; + if (ent->client->ps.pmove.pm_flags & PMF_DUCKED) + { + ent->s.frame = FRAME_crpain1; + ent->client->anim_end = FRAME_crpain4; + } + else + { + ent->s.frame = FRAME_pain301; + ent->client->anim_end = FRAME_pain304; + } + ent->client->anim_time = 0_ms; + + // for instantweap, run think immediately + // to set up correct start frame + if (g_instant_weapon_switch->integer) + Weapon_RunThink(ent); +} + +/* +================= +NoAmmoWeaponChange +================= +*/ +void NoAmmoWeaponChange(edict_t *ent, bool sound) +{ + if (sound) + { + if (level.time >= ent->client->empty_click_sound) + { + gi.sound(ent, CHAN_WEAPON, gi.soundindex("weapons/noammo.wav"), 1, ATTN_NORM, 0); + ent->client->empty_click_sound = level.time + 1_sec; + } + } + + constexpr item_id_t no_ammo_order[] = { + IT_WEAPON_DISRUPTOR, + IT_WEAPON_RAILGUN, + IT_WEAPON_PLASMABEAM, + IT_WEAPON_IONRIPPER, + IT_WEAPON_HYPERBLASTER, + IT_WEAPON_ETF_RIFLE, + IT_WEAPON_CHAINGUN, + IT_WEAPON_MACHINEGUN, + IT_WEAPON_SSHOTGUN, + IT_WEAPON_SHOTGUN, + IT_WEAPON_PHALANX, + IT_WEAPON_RLAUNCHER, + IT_WEAPON_GLAUNCHER, + IT_WEAPON_PROXLAUNCHER, + IT_WEAPON_CHAINFIST, + IT_WEAPON_BLASTER + }; + + for (size_t i = 0; i < q_countof(no_ammo_order); i++) + { + gitem_t *item = GetItemByIndex(no_ammo_order[i]); + + if (!item) + gi.Com_ErrorFmt("Invalid no ammo weapon switch weapon {}\n", (int32_t) no_ammo_order[i]); + + if (!ent->client->pers.inventory[item->id]) + continue; + + if (item->ammo && ent->client->pers.inventory[item->ammo] < item->quantity) + continue; + + ent->client->newweapon = item; + return; + } +} + +void G_RemoveAmmo(edict_t *ent, int32_t quantity) +{ + if (G_CheckInfiniteAmmo(ent->client->pers.weapon)) + return; + + bool pre_warning = ent->client->pers.inventory[ent->client->pers.weapon->ammo] <= + ent->client->pers.weapon->quantity_warn; + + ent->client->pers.inventory[ent->client->pers.weapon->ammo] -= quantity; + + bool post_warning = ent->client->pers.inventory[ent->client->pers.weapon->ammo] <= + ent->client->pers.weapon->quantity_warn; + + if (!pre_warning && post_warning) + gi.local_sound(ent, CHAN_AUTO, gi.soundindex("weapons/lowammo.wav"), 1, ATTN_NORM, 0); + + if (ent->client->pers.weapon->ammo == IT_AMMO_CELLS) + G_CheckPowerArmor(ent); +} + +void G_RemoveAmmo(edict_t *ent) +{ + G_RemoveAmmo(ent, ent->client->pers.weapon->quantity); +} + +// [Paril-KEX] get time per animation frame +inline gtime_t Weapon_AnimationTime(edict_t *ent) +{ + if (g_quick_weapon_switch->integer && (gi.tick_rate == 20 || gi.tick_rate == 40) && + (ent->client->weaponstate == WEAPON_ACTIVATING || ent->client->weaponstate == WEAPON_DROPPING)) + ent->client->ps.gunrate = 20; + else + ent->client->ps.gunrate = 10; + + if (ent->client->ps.gunframe != 0 && (!(ent->client->pers.weapon->flags & IF_NO_HASTE) || ent->client->weaponstate != WEAPON_FIRING)) + { + if (is_quadfire) + ent->client->ps.gunrate *= 2; + if (CTFApplyHaste(ent)) + ent->client->ps.gunrate *= 2; + } + + // network optimization... + if (ent->client->ps.gunrate == 10) + { + ent->client->ps.gunrate = 0; + return 100_ms; + } + + return gtime_t::from_ms((1.f / ent->client->ps.gunrate) * 1000); +} + +/* +================= +Think_Weapon + +Called by ClientBeginServerFrame and ClientThink +================= +*/ +void Think_Weapon(edict_t *ent) +{ + if (ent->client->resp.spectator) + return; + + // if just died, put the weapon away + if (ent->health < 1) + { + ent->client->newweapon = nullptr; + ChangeWeapon(ent); + } + + if (!ent->client->pers.weapon) + { + if (ent->client->newweapon) + ChangeWeapon(ent); + return; + } + + // call active weapon think routine + Weapon_RunThink(ent); + + // check remainder from haste; on 100ms/50ms server frames we may have + // 'run next frame in' times that we can't possibly catch up to, + // so we have to run them now. + if (33_ms < FRAME_TIME_MS) + { + gtime_t relative_time = Weapon_AnimationTime(ent); + + if (relative_time < FRAME_TIME_MS) + { + // check how many we can't run before the next server tick + gtime_t next_frame = level.time + FRAME_TIME_S; + int64_t remaining_ms = (next_frame - ent->client->weapon_think_time).milliseconds(); + + while (remaining_ms > 0) + { + ent->client->weapon_think_time -= relative_time; + ent->client->weapon_fire_finished -= relative_time; + Weapon_RunThink(ent); + remaining_ms -= relative_time.milliseconds(); + } + } + } +} + +enum weap_switch_t +{ + WEAP_SWITCH_ALREADY_USING, + WEAP_SWITCH_NO_WEAPON, + WEAP_SWITCH_NO_AMMO, + WEAP_SWITCH_NOT_ENOUGH_AMMO, + WEAP_SWITCH_VALID +}; + +weap_switch_t Weapon_AttemptSwitch(edict_t *ent, gitem_t *item, bool silent) +{ + if (ent->client->pers.weapon == item) + return WEAP_SWITCH_ALREADY_USING; + else if (!ent->client->pers.inventory[item->id]) + return WEAP_SWITCH_NO_WEAPON; + + if (item->ammo && !g_select_empty->integer && !(item->flags & IF_AMMO)) + { + gitem_t *ammo_item = GetItemByIndex(item->ammo); + + if (!ent->client->pers.inventory[item->ammo]) + { + if (!silent) + gi.LocClient_Print(ent, PRINT_HIGH, "$g_no_ammo", ammo_item->pickup_name, item->pickup_name_definite); + return WEAP_SWITCH_NO_AMMO; + } + else if (ent->client->pers.inventory[item->ammo] < item->quantity) + { + if (!silent) + gi.LocClient_Print(ent, PRINT_HIGH, "$g_not_enough_ammo", ammo_item->pickup_name, item->pickup_name_definite); + return WEAP_SWITCH_NOT_ENOUGH_AMMO; + } + } + + return WEAP_SWITCH_VALID; +} + +inline bool Weapon_IsPartOfChain(gitem_t *item, gitem_t *other) +{ + return other && other->chain && item->chain && other->chain == item->chain; +} + +/* +================ +Use_Weapon + +Make the weapon ready if there is ammo +================ +*/ +void Use_Weapon(edict_t *ent, gitem_t *item) +{ + gitem_t *wanted, *root; + weap_switch_t result = WEAP_SWITCH_NO_WEAPON; + + // if we're switching to a weapon in this chain already, + // start from the weapon after this one in the chain + if (!ent->client->no_weapon_chains && Weapon_IsPartOfChain(item, ent->client->newweapon)) + { + root = ent->client->newweapon; + wanted = root->chain_next; + } + // if we're already holding a weapon in this chain, + // start from the weapon after that one + else if (!ent->client->no_weapon_chains && Weapon_IsPartOfChain(item, ent->client->pers.weapon)) + { + root = ent->client->pers.weapon; + wanted = root->chain_next; + } + // start from beginning of chain (if any) + else + wanted = root = item; + + while (true) + { + // try the weapon currently in the chain + if ((result = Weapon_AttemptSwitch(ent, wanted, false)) == WEAP_SWITCH_VALID) + break; + + // no chains + if (!wanted->chain_next || ent->client->no_weapon_chains) + break; + + wanted = wanted->chain_next; + + // we wrapped back to the root item + if (wanted == root) + break; + } + + if (result == WEAP_SWITCH_VALID) + ent->client->newweapon = wanted; // change to this weapon when down + else if ((result = Weapon_AttemptSwitch(ent, wanted, true)) == WEAP_SWITCH_NO_WEAPON && wanted != ent->client->pers.weapon && wanted != ent->client->newweapon) + gi.LocClient_Print(ent, PRINT_HIGH, "$g_out_of_item", wanted->pickup_name); +} + +/* +================ +Drop_Weapon +================ +*/ +void Drop_Weapon(edict_t *ent, gitem_t *item) +{ + item_id_t index = item->id; + // see if we're already using it + if (((item == ent->client->pers.weapon) || (item == ent->client->newweapon)) && (ent->client->pers.inventory[index] == 1)) + { + gi.LocClient_Print(ent, PRINT_HIGH, "$g_cant_drop_weapon"); + return; + } + + edict_t *drop = Drop_Item(ent, item); + drop->spawnflags |= SPAWNFLAG_ITEM_DROPPED_PLAYER; + drop->svflags &= ~SVF_INSTANCED; + ent->client->pers.inventory[index]--; +} + +void Weapon_PowerupSound(edict_t *ent) +{ + if (!CTFApplyStrengthSound(ent)) + { + if (ent->client->quad_time > level.time && ent->client->double_time > level.time) + gi.sound(ent, CHAN_ITEM, gi.soundindex("ctf/tech2x.wav"), 1, ATTN_NORM, 0); + else if (ent->client->quad_time > level.time) + gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage3.wav"), 1, ATTN_NORM, 0); + else if (ent->client->double_time > level.time) + gi.sound(ent, CHAN_ITEM, gi.soundindex("misc/ddamage3.wav"), 1, ATTN_NORM, 0); + else if (ent->client->quadfire_time > level.time + && ent->client->ctf_techsndtime < level.time) + { + ent->client->ctf_techsndtime = level.time + 1_sec; + gi.sound(ent, CHAN_ITEM, gi.soundindex("ctf/tech3.wav"), 1, ATTN_NORM, 0); + } + } + + CTFApplyHasteSound(ent); +} + +inline bool Weapon_CanAnimate(edict_t *ent) +{ + // VWep animations screw up corpses + return !ent->deadflag && ent->s.modelindex == MODELINDEX_PLAYER; +} + +// [Paril-KEX] called when finished to set time until +// we're allowed to switch to fire again +inline void Weapon_SetFinished(edict_t *ent) +{ + ent->client->weapon_fire_finished = level.time + Weapon_AnimationTime(ent); +} + +inline bool Weapon_HandleDropping(edict_t *ent, int FRAME_DEACTIVATE_LAST) +{ + if (ent->client->weaponstate == WEAPON_DROPPING) + { + if (ent->client->weapon_think_time <= level.time) + { + if (ent->client->ps.gunframe == FRAME_DEACTIVATE_LAST) + { + ChangeWeapon(ent); + return true; + } + else if ((FRAME_DEACTIVATE_LAST - ent->client->ps.gunframe) == 4) + { + ent->client->anim_priority = ANIM_ATTACK | ANIM_REVERSED; + if (ent->client->ps.pmove.pm_flags & PMF_DUCKED) + { + ent->s.frame = FRAME_crpain4 + 1; + ent->client->anim_end = FRAME_crpain1; + } + else + { + ent->s.frame = FRAME_pain304 + 1; + ent->client->anim_end = FRAME_pain301; + } + ent->client->anim_time = 0_ms; + } + + ent->client->ps.gunframe++; + ent->client->weapon_think_time = level.time + Weapon_AnimationTime(ent); + } + return true; + } + + return false; +} + +inline bool Weapon_HandleActivating(edict_t *ent, int FRAME_ACTIVATE_LAST, int FRAME_IDLE_FIRST) +{ + if (ent->client->weaponstate == WEAPON_ACTIVATING) + { + if (ent->client->weapon_think_time <= level.time || g_instant_weapon_switch->integer) + { + ent->client->weapon_think_time = level.time + Weapon_AnimationTime(ent); + + if (ent->client->ps.gunframe == FRAME_ACTIVATE_LAST || g_instant_weapon_switch->integer) + { + ent->client->weaponstate = WEAPON_READY; + ent->client->ps.gunframe = FRAME_IDLE_FIRST; + ent->client->weapon_fire_buffered = false; + if (!g_instant_weapon_switch->integer) + Weapon_SetFinished(ent); + else + ent->client->weapon_fire_finished = 0_ms; + return true; + } + + ent->client->ps.gunframe++; + return true; + } + } + + return false; +} + +inline bool Weapon_HandleNewWeapon(edict_t *ent, int FRAME_DEACTIVATE_FIRST, int FRAME_DEACTIVATE_LAST) +{ + bool is_holstering = false; + + if (!g_instant_weapon_switch->integer) + is_holstering = ((ent->client->latched_buttons | ent->client->buttons) & BUTTON_HOLSTER); + + if ((ent->client->newweapon || is_holstering) && (ent->client->weaponstate != WEAPON_FIRING)) + { + if (g_instant_weapon_switch->integer || ent->client->weapon_think_time <= level.time) + { + if (!ent->client->newweapon) + ent->client->newweapon = ent->client->pers.weapon; + + ent->client->weaponstate = WEAPON_DROPPING; + + if (g_instant_weapon_switch->integer) + { + ChangeWeapon(ent); + return true; + } + + ent->client->ps.gunframe = FRAME_DEACTIVATE_FIRST; + + if ((FRAME_DEACTIVATE_LAST - FRAME_DEACTIVATE_FIRST) < 4) + { + ent->client->anim_priority = ANIM_ATTACK | ANIM_REVERSED; + if (ent->client->ps.pmove.pm_flags & PMF_DUCKED) + { + ent->s.frame = FRAME_crpain4 + 1; + ent->client->anim_end = FRAME_crpain1; + } + else + { + ent->s.frame = FRAME_pain304 + 1; + ent->client->anim_end = FRAME_pain301; + } + ent->client->anim_time = 0_ms; + } + + ent->client->weapon_think_time = level.time + Weapon_AnimationTime(ent); + } + return true; + } + + return false; +} + +enum weapon_ready_state_t +{ + READY_NONE, + READY_CHANGING, + READY_FIRING +}; + +inline weapon_ready_state_t Weapon_HandleReady(edict_t *ent, int FRAME_FIRE_FIRST, int FRAME_IDLE_FIRST, int FRAME_IDLE_LAST, const int *pause_frames) +{ + if (ent->client->weaponstate == WEAPON_READY) + { + bool request_firing = ent->client->weapon_fire_buffered || ((ent->client->latched_buttons | ent->client->buttons) & BUTTON_ATTACK); + + if (request_firing && ent->client->weapon_fire_finished <= level.time) + { + ent->client->latched_buttons &= ~BUTTON_ATTACK; + ent->client->weapon_think_time = level.time; + + if ((!ent->client->pers.weapon->ammo) || + (ent->client->pers.inventory[ent->client->pers.weapon->ammo] >= ent->client->pers.weapon->quantity)) + { + ent->client->weaponstate = WEAPON_FIRING; + return READY_FIRING; + } + else + { + NoAmmoWeaponChange(ent, true); + return READY_CHANGING; + } + } + else if (ent->client->weapon_think_time <= level.time) + { + ent->client->weapon_think_time = level.time + Weapon_AnimationTime(ent); + + if (ent->client->ps.gunframe == FRAME_IDLE_LAST) + { + ent->client->ps.gunframe = FRAME_IDLE_FIRST; + return READY_CHANGING; + } + + if (pause_frames) + for (int n = 0; pause_frames[n]; n++) + if (ent->client->ps.gunframe == pause_frames[n]) + if (irandom(16)) + return READY_CHANGING; + + ent->client->ps.gunframe++; + return READY_CHANGING; + } + } + + return READY_NONE; +} + +inline void Weapon_HandleFiring(edict_t *ent, int32_t FRAME_IDLE_FIRST, std::function fire_handler) +{ + Weapon_SetFinished(ent); + + if (ent->client->weapon_fire_buffered) + { + ent->client->buttons |= BUTTON_ATTACK; + ent->client->weapon_fire_buffered = false; + } + + fire_handler(); + + if (ent->client->ps.gunframe == FRAME_IDLE_FIRST) + { + ent->client->weaponstate = WEAPON_READY; + ent->client->weapon_fire_buffered = false; + } + + ent->client->weapon_think_time = level.time + Weapon_AnimationTime(ent); +} + +void Weapon_Generic(edict_t *ent, int FRAME_ACTIVATE_LAST, int FRAME_FIRE_LAST, int FRAME_IDLE_LAST, int FRAME_DEACTIVATE_LAST, const int *pause_frames, const int *fire_frames, void (*fire)(edict_t *ent)) +{ + int FRAME_FIRE_FIRST = (FRAME_ACTIVATE_LAST + 1); + int FRAME_IDLE_FIRST = (FRAME_FIRE_LAST + 1); + int FRAME_DEACTIVATE_FIRST = (FRAME_IDLE_LAST + 1); + + if (!Weapon_CanAnimate(ent)) + return; + + if (Weapon_HandleDropping(ent, FRAME_DEACTIVATE_LAST)) + return; + else if (Weapon_HandleActivating(ent, FRAME_ACTIVATE_LAST, FRAME_IDLE_FIRST)) + return; + else if (Weapon_HandleNewWeapon(ent, FRAME_DEACTIVATE_FIRST, FRAME_DEACTIVATE_LAST)) + return; + else if (auto state = Weapon_HandleReady(ent, FRAME_FIRE_FIRST, FRAME_IDLE_FIRST, FRAME_IDLE_LAST, pause_frames)) + { + if (state == READY_FIRING) + { + ent->client->ps.gunframe = FRAME_FIRE_FIRST; + ent->client->weapon_fire_buffered = false; + + if (ent->client->weapon_thunk) + ent->client->weapon_think_time += FRAME_TIME_S; + + ent->client->weapon_think_time += Weapon_AnimationTime(ent); + Weapon_SetFinished(ent); + + for (int n = 0; fire_frames[n]; n++) + { + if (ent->client->ps.gunframe == fire_frames[n]) + { + Weapon_PowerupSound(ent); + fire(ent); + break; + } + } + + // start the animation + ent->client->anim_priority = ANIM_ATTACK; + if (ent->client->ps.pmove.pm_flags & PMF_DUCKED) + { + ent->s.frame = FRAME_crattak1 - 1; + ent->client->anim_end = FRAME_crattak9; + } + else + { + ent->s.frame = FRAME_attack1 - 1; + ent->client->anim_end = FRAME_attack8; + } + ent->client->anim_time = 0_ms; + } + + return; + } + + if (ent->client->weaponstate == WEAPON_FIRING && ent->client->weapon_think_time <= level.time) + { + ent->client->ps.gunframe++; + Weapon_HandleFiring(ent, FRAME_IDLE_FIRST, [&]() { + for (int n = 0; fire_frames[n]; n++) + { + if (ent->client->ps.gunframe == fire_frames[n]) + { + Weapon_PowerupSound(ent); + fire(ent); + break; + } + } + }); + } +} + +void Weapon_Repeating(edict_t *ent, int FRAME_ACTIVATE_LAST, int FRAME_FIRE_LAST, int FRAME_IDLE_LAST, int FRAME_DEACTIVATE_LAST, const int *pause_frames, void (*fire)(edict_t *ent)) +{ + int FRAME_FIRE_FIRST = (FRAME_ACTIVATE_LAST + 1); + int FRAME_IDLE_FIRST = (FRAME_FIRE_LAST + 1); + int FRAME_DEACTIVATE_FIRST = (FRAME_IDLE_LAST + 1); + + if (!Weapon_CanAnimate(ent)) + return; + + if (Weapon_HandleDropping(ent, FRAME_DEACTIVATE_LAST)) + return; + else if (Weapon_HandleActivating(ent, FRAME_ACTIVATE_LAST, FRAME_IDLE_FIRST)) + return; + else if (Weapon_HandleNewWeapon(ent, FRAME_DEACTIVATE_FIRST, FRAME_DEACTIVATE_LAST)) + return; + else if (Weapon_HandleReady(ent, FRAME_FIRE_FIRST, FRAME_IDLE_FIRST, FRAME_IDLE_LAST, pause_frames) == READY_CHANGING) + return; + + if (ent->client->weaponstate == WEAPON_FIRING && ent->client->weapon_think_time <= level.time) + { + Weapon_HandleFiring(ent, FRAME_IDLE_FIRST, [&]() { fire(ent); }); + + if (ent->client->weapon_thunk) + ent->client->weapon_think_time += FRAME_TIME_S; + } +} + +/* +====================================================================== + +GRENADE + +====================================================================== +*/ + +void weapon_grenade_fire(edict_t *ent, bool held) +{ + int damage = 125; + int speed; + float radius; + + radius = (float) (damage + 40); + if (is_quad) + damage *= damage_multiplier; + + vec3_t start, dir; + // Paril: kill sideways angle on grenades + // limit upwards angle so you don't throw behind you + P_ProjectSource(ent, { max(-62.5f, ent->client->v_angle[0]), ent->client->v_angle[1], ent->client->v_angle[2] }, { 2, 0, -14 }, start, dir); + + gtime_t timer = ent->client->grenade_time - level.time; + speed = (int) (ent->health <= 0 ? GRENADE_MINSPEED : min(GRENADE_MINSPEED + (GRENADE_TIMER - timer).seconds() * ((GRENADE_MAXSPEED - GRENADE_MINSPEED) / GRENADE_TIMER.seconds()), GRENADE_MAXSPEED)); + + ent->client->grenade_time = 0_ms; + + fire_grenade2(ent, start, dir, damage, speed, timer, radius, held); + + G_RemoveAmmo(ent, 1); +} + +void Throw_Generic(edict_t *ent, int FRAME_FIRE_LAST, int FRAME_IDLE_LAST, int FRAME_PRIME_SOUND, + const char *prime_sound, + int FRAME_THROW_HOLD, int FRAME_THROW_FIRE, const int *pause_frames, int EXPLODE, + const char *primed_sound, + void (*fire)(edict_t *ent, bool held), bool extra_idle_frame) +{ + // when we die, just toss what we had in our hands. + if (ent->health <= 0) + { + fire(ent, true); + return; + } + + int n; + int FRAME_IDLE_FIRST = (FRAME_FIRE_LAST + 1); + + if (ent->client->newweapon && (ent->client->weaponstate == WEAPON_READY)) + { + if (ent->client->weapon_think_time <= level.time) + { + ChangeWeapon(ent); + ent->client->weapon_think_time = level.time + Weapon_AnimationTime(ent); + } + return; + } + + if (ent->client->weaponstate == WEAPON_ACTIVATING) + { + if (ent->client->weapon_think_time <= level.time) + { + ent->client->weaponstate = WEAPON_READY; + if (!extra_idle_frame) + ent->client->ps.gunframe = FRAME_IDLE_FIRST; + else + ent->client->ps.gunframe = FRAME_IDLE_LAST + 1; + ent->client->weapon_think_time = level.time + Weapon_AnimationTime(ent); + Weapon_SetFinished(ent); + } + return; + } + + if (ent->client->weaponstate == WEAPON_READY) + { + bool request_firing = ent->client->weapon_fire_buffered || ((ent->client->latched_buttons | ent->client->buttons) & BUTTON_ATTACK); + + if (request_firing && ent->client->weapon_fire_finished <= level.time) + { + ent->client->latched_buttons &= ~BUTTON_ATTACK; + + if (ent->client->pers.inventory[ent->client->pers.weapon->ammo]) + { + ent->client->ps.gunframe = 1; + ent->client->weaponstate = WEAPON_FIRING; + ent->client->grenade_time = 0_ms; + ent->client->weapon_think_time = level.time + Weapon_AnimationTime(ent); + } + else + NoAmmoWeaponChange(ent, true); + return; + } + else if (ent->client->weapon_think_time <= level.time) + { + ent->client->weapon_think_time = level.time + Weapon_AnimationTime(ent); + + if (ent->client->ps.gunframe >= FRAME_IDLE_LAST) + { + ent->client->ps.gunframe = FRAME_IDLE_FIRST; + return; + } + + if (pause_frames) + { + for (n = 0; pause_frames[n]; n++) + { + if (ent->client->ps.gunframe == pause_frames[n]) + { + if (irandom(16)) + return; + } + } + } + + ent->client->ps.gunframe++; + } + return; + } + + if (ent->client->weaponstate == WEAPON_FIRING) + { + if (ent->client->weapon_think_time <= level.time) + { + if (prime_sound && ent->client->ps.gunframe == FRAME_PRIME_SOUND) + gi.sound(ent, CHAN_WEAPON, gi.soundindex(prime_sound), 1, ATTN_NORM, 0); + + // [Paril-KEX] dualfire/time accel + gtime_t grenade_wait_time = 1_sec; + + if (CTFApplyHaste(ent)) + grenade_wait_time *= 0.5f; + if (is_quadfire) + grenade_wait_time *= 0.5f; + + if (ent->client->ps.gunframe == FRAME_THROW_HOLD) + { + if (!ent->client->grenade_time && !ent->client->grenade_finished_time) + { + ent->client->grenade_time = level.time + GRENADE_TIMER + 200_ms; + + if (primed_sound) + ent->client->weapon_sound = gi.soundindex(primed_sound); + } + + // they waited too long, detonate it in their hand + if (EXPLODE && !ent->client->grenade_blew_up && level.time >= ent->client->grenade_time) + { + Weapon_PowerupSound(ent); + ent->client->weapon_sound = 0; + fire(ent, true); + ent->client->grenade_blew_up = true; + + ent->client->grenade_finished_time = level.time + grenade_wait_time; + } + + if (ent->client->buttons & BUTTON_ATTACK) + { + ent->client->weapon_think_time = level.time + 1_ms; + return; + } + + if (ent->client->grenade_blew_up) + { + if (level.time >= ent->client->grenade_finished_time) + { + ent->client->ps.gunframe = FRAME_FIRE_LAST; + ent->client->grenade_blew_up = false; + ent->client->weapon_think_time = level.time + Weapon_AnimationTime(ent); + } + else + { + return; + } + } + else + { + ent->client->ps.gunframe++; + + Weapon_PowerupSound(ent); + ent->client->weapon_sound = 0; + fire(ent, false); + + if (!EXPLODE || !ent->client->grenade_blew_up) + ent->client->grenade_finished_time = level.time + grenade_wait_time; + + if (!ent->deadflag && ent->s.modelindex == MODELINDEX_PLAYER && ent->health > 0) // VWep animations screw up corpses + { + if (ent->client->ps.pmove.pm_flags & PMF_DUCKED) + { + ent->client->anim_priority = ANIM_ATTACK; + ent->s.frame = FRAME_crattak1 - 1; + ent->client->anim_end = FRAME_crattak3; + } + else + { + ent->client->anim_priority = ANIM_ATTACK | ANIM_REVERSED; + ent->s.frame = FRAME_wave08; + ent->client->anim_end = FRAME_wave01; + } + ent->client->anim_time = 0_ms; + } + } + } + + ent->client->weapon_think_time = level.time + Weapon_AnimationTime(ent); + + if ((ent->client->ps.gunframe == FRAME_FIRE_LAST) && (level.time < ent->client->grenade_finished_time)) + return; + + ent->client->ps.gunframe++; + + if (ent->client->ps.gunframe == FRAME_IDLE_FIRST) + { + ent->client->grenade_finished_time = 0_ms; + ent->client->weaponstate = WEAPON_READY; + ent->client->weapon_fire_buffered = false; + Weapon_SetFinished(ent); + + if (extra_idle_frame) + ent->client->ps.gunframe = FRAME_IDLE_LAST + 1; + + // Paril: if we ran out of the throwable, switch + // so we don't appear to be holding one that we + // can't throw + if (!ent->client->pers.inventory[ent->client->pers.weapon->ammo]) + { + NoAmmoWeaponChange(ent, false); + ChangeWeapon(ent); + } + } + } + } +} + +void Weapon_Grenade(edict_t *ent) +{ + constexpr int pause_frames[] = { 29, 34, 39, 48, 0 }; + + Throw_Generic(ent, 15, 48, 5, "weapons/hgrena1b.wav", 11, 12, pause_frames, true, "weapons/hgrenc1b.wav", weapon_grenade_fire, true); + + // [Paril-KEX] skip the duped frame + if (ent->client->ps.gunframe == 1) + ent->client->ps.gunframe = 2; +} + +/* +====================================================================== + +GRENADE LAUNCHER + +====================================================================== +*/ + +void weapon_grenadelauncher_fire(edict_t *ent) +{ + int damage = 120; + float radius; + + radius = (float) (damage + 40); + if (is_quad) + damage *= damage_multiplier; + + vec3_t start, dir; + // Paril: kill sideways angle on grenades + // limit upwards angle so you don't fire it behind you + P_ProjectSource(ent, { max(-62.5f, ent->client->v_angle[0]), ent->client->v_angle[1], ent->client->v_angle[2] }, { 8, 0, -8 }, start, dir); + + P_AddWeaponKick(ent, ent->client->v_forward * -2, { -1.f, 0.f, 0.f }); + + fire_grenade(ent, start, dir, damage, 600, 2.5_sec, radius, (crandom_open() * 10.0f), (200 + crandom_open() * 10.0f), false); + + gi.WriteByte(svc_muzzleflash); + gi.WriteEntity(ent); + gi.WriteByte(MZ_GRENADE | is_silenced); + gi.multicast(ent->s.origin, MULTICAST_PVS, false); + + PlayerNoise(ent, start, PNOISE_WEAPON); + + G_RemoveAmmo(ent); +} + +void Weapon_GrenadeLauncher(edict_t *ent) +{ + constexpr int pause_frames[] = { 34, 51, 59, 0 }; + constexpr int fire_frames[] = { 6, 0 }; + + Weapon_Generic(ent, 5, 16, 59, 64, pause_frames, fire_frames, weapon_grenadelauncher_fire); +} + +/* +====================================================================== + +ROCKET + +====================================================================== +*/ + +void Weapon_RocketLauncher_Fire(edict_t *ent) +{ + int damage; + float damage_radius; + int radius_damage; + + damage = irandom(100, 120); + radius_damage = 120; + damage_radius = 120; + if (is_quad) + { + damage *= damage_multiplier; + radius_damage *= damage_multiplier; + } + + vec3_t start, dir; + P_ProjectSource(ent, ent->client->v_angle, { 8, 8, -8 }, start, dir); + fire_rocket(ent, start, dir, damage, 650, damage_radius, radius_damage); + + P_AddWeaponKick(ent, ent->client->v_forward * -2, { -1.f, 0.f, 0.f }); + + // send muzzle flash + gi.WriteByte(svc_muzzleflash); + gi.WriteEntity(ent); + gi.WriteByte(MZ_ROCKET | is_silenced); + gi.multicast(ent->s.origin, MULTICAST_PVS, false); + + PlayerNoise(ent, start, PNOISE_WEAPON); + + G_RemoveAmmo(ent); +} + +void Weapon_RocketLauncher(edict_t *ent) +{ + constexpr int pause_frames[] = { 25, 33, 42, 50, 0 }; + constexpr int fire_frames[] = { 5, 0 }; + + Weapon_Generic(ent, 4, 12, 50, 54, pause_frames, fire_frames, Weapon_RocketLauncher_Fire); +} + +/* +====================================================================== + +BLASTER / HYPERBLASTER + +====================================================================== +*/ + +void Blaster_Fire(edict_t *ent, const vec3_t &g_offset, int damage, bool hyper, effects_t effect) +{ + if (is_quad) + damage *= damage_multiplier; + + vec3_t start, dir; + P_ProjectSource(ent, ent->client->v_angle, vec3_t{ 24, 8, -8 } + g_offset, start, dir); + + if (hyper) + P_AddWeaponKick(ent, ent->client->v_forward * -2, { crandom() * 0.7f, crandom() * 0.7f, crandom() * 0.7f }); + else + P_AddWeaponKick(ent, ent->client->v_forward * -2, { -1.f, 0.f, 0.f }); + + // let the regular blaster projectiles travel a bit faster because it is a completely useless gun + int speed = hyper ? 1000 : 1500; + + fire_blaster(ent, start, dir, damage, speed, effect, hyper ? MOD_HYPERBLASTER : MOD_BLASTER); + + // send muzzle flash + gi.WriteByte(svc_muzzleflash); + gi.WriteEntity(ent); + if (hyper) + gi.WriteByte(MZ_HYPERBLASTER | is_silenced); + else + gi.WriteByte(MZ_BLASTER | is_silenced); + gi.multicast(ent->s.origin, MULTICAST_PVS, false); + + PlayerNoise(ent, start, PNOISE_WEAPON); +} + +void Weapon_Blaster_Fire(edict_t *ent) +{ + // give the blaster 15 across the board instead of just in dm + int damage = 15; + Blaster_Fire(ent, vec3_origin, damage, false, EF_BLASTER); +} + +void Weapon_Blaster(edict_t *ent) +{ + constexpr int pause_frames[] = { 19, 32, 0 }; + constexpr int fire_frames[] = { 5, 0 }; + + Weapon_Generic(ent, 4, 8, 52, 55, pause_frames, fire_frames, Weapon_Blaster_Fire); +} + +void Weapon_HyperBlaster_Fire(edict_t *ent) +{ + float rotation; + vec3_t offset; + int damage; + + // start on frame 6 + if (ent->client->ps.gunframe > 20) + ent->client->ps.gunframe = 6; + else + ent->client->ps.gunframe++; + + // if we reached end of loop, have ammo & holding attack, reset loop + // otherwise play wind down + if (ent->client->ps.gunframe == 12) + { + if (ent->client->pers.inventory[ent->client->pers.weapon->ammo] && (ent->client->buttons & BUTTON_ATTACK)) + ent->client->ps.gunframe = 6; + else + gi.sound(ent, CHAN_AUTO, gi.soundindex("weapons/hyprbd1a.wav"), 1, ATTN_NORM, 0); + } + + // play weapon sound for firing loop + if (ent->client->ps.gunframe >= 6 && ent->client->ps.gunframe <= 11) + ent->client->weapon_sound = gi.soundindex("weapons/hyprbl1a.wav"); + else + ent->client->weapon_sound = 0; + + // fire frames + bool request_firing = ent->client->weapon_fire_buffered || (ent->client->buttons & BUTTON_ATTACK); + + if (request_firing) + { + if (ent->client->ps.gunframe >= 6 && ent->client->ps.gunframe <= 11) + { + ent->client->weapon_fire_buffered = false; + + if (!ent->client->pers.inventory[ent->client->pers.weapon->ammo]) + { + NoAmmoWeaponChange(ent, true); + return; + } + + rotation = (ent->client->ps.gunframe - 5) * 2 * PIf / 6; + offset[0] = -4 * sinf(rotation); + offset[2] = 0; + offset[1] = 4 * cosf(rotation); + + if (deathmatch->integer) + damage = 15; + else + damage = 20; + Blaster_Fire(ent, offset, damage, true, (ent->client->ps.gunframe % 4) ? EF_NONE : EF_HYPERBLASTER); + Weapon_PowerupSound(ent); + + G_RemoveAmmo(ent); + + ent->client->anim_priority = ANIM_ATTACK; + if (ent->client->ps.pmove.pm_flags & PMF_DUCKED) + { + ent->s.frame = FRAME_crattak1 - (int) (frandom() + 0.25f); + ent->client->anim_end = FRAME_crattak9; + } + else + { + ent->s.frame = FRAME_attack1 - (int) (frandom() + 0.25f); + ent->client->anim_end = FRAME_attack8; + } + ent->client->anim_time = 0_ms; + } + } +} + +void Weapon_HyperBlaster(edict_t *ent) +{ + constexpr int pause_frames[] = { 0 }; + + Weapon_Repeating(ent, 5, 20, 49, 53, pause_frames, Weapon_HyperBlaster_Fire); +} + +/* +====================================================================== + +MACHINEGUN / CHAINGUN + +====================================================================== +*/ + +void Machinegun_Fire(edict_t *ent) +{ + int i; + int damage = 8; + int kick = 2; + + if (!(ent->client->buttons & BUTTON_ATTACK)) + { + ent->client->machinegun_shots = 0; + ent->client->ps.gunframe = 6; + return; + } + + if (ent->client->ps.gunframe == 4) + ent->client->ps.gunframe = 5; + else + ent->client->ps.gunframe = 4; + + if (ent->client->pers.inventory[ent->client->pers.weapon->ammo] < 1) + { + ent->client->ps.gunframe = 6; + NoAmmoWeaponChange(ent, true); + return; + } + + if (is_quad) + { + damage *= damage_multiplier; + kick *= damage_multiplier; + } + + vec3_t kick_origin {}, kick_angles {}; + for (i = 0; i < 3; i++) + { + kick_origin[i] = crandom() * 0.35f; + kick_angles[i] = crandom() * 0.7f; + } + //kick_angles[0] = ent->client->machinegun_shots * -1.5f; + P_AddWeaponKick(ent, kick_origin, kick_angles); + + // raise the gun as it is firing + // [Paril-KEX] disabled as this is a bit hard to do with high + // tickrate, but it also just sucks in general. + /*if (!deathmatch->integer) + { + ent->client->machinegun_shots++; + if (ent->client->machinegun_shots > 9) + ent->client->machinegun_shots = 9; + }*/ + + // get start / end positions + vec3_t start, dir; + // Paril: kill sideways angle on hitscan + P_ProjectSource(ent, ent->client->v_angle, { 0, 0, -8 }, start, dir); + G_LagCompensate(ent, start, dir); + fire_bullet(ent, start, dir, damage, kick, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, MOD_MACHINEGUN); + G_UnLagCompensate(); + Weapon_PowerupSound(ent); + + gi.WriteByte(svc_muzzleflash); + gi.WriteEntity(ent); + gi.WriteByte(MZ_MACHINEGUN | is_silenced); + gi.multicast(ent->s.origin, MULTICAST_PVS, false); + + PlayerNoise(ent, start, PNOISE_WEAPON); + + G_RemoveAmmo(ent); + + ent->client->anim_priority = ANIM_ATTACK; + if (ent->client->ps.pmove.pm_flags & PMF_DUCKED) + { + ent->s.frame = FRAME_crattak1 - (int) (frandom() + 0.25f); + ent->client->anim_end = FRAME_crattak9; + } + else + { + ent->s.frame = FRAME_attack1 - (int) (frandom() + 0.25f); + ent->client->anim_end = FRAME_attack8; + } + ent->client->anim_time = 0_ms; +} + +void Weapon_Machinegun(edict_t *ent) +{ + constexpr int pause_frames[] = { 23, 45, 0 }; + + Weapon_Repeating(ent, 3, 5, 45, 49, pause_frames, Machinegun_Fire); +} + +void Chaingun_Fire(edict_t *ent) +{ + int i; + int shots; + float r, u; + int damage; + int kick = 2; + + if (deathmatch->integer) + damage = 6; + else + damage = 8; + + if (ent->client->ps.gunframe > 31) + { + ent->client->ps.gunframe = 5; + gi.sound(ent, CHAN_AUTO, gi.soundindex("weapons/chngnu1a.wav"), 1, ATTN_IDLE, 0); + } + else if ((ent->client->ps.gunframe == 14) && !(ent->client->buttons & BUTTON_ATTACK)) + { + ent->client->ps.gunframe = 32; + ent->client->weapon_sound = 0; + return; + } + else if ((ent->client->ps.gunframe == 21) && (ent->client->buttons & BUTTON_ATTACK) && ent->client->pers.inventory[ent->client->pers.weapon->ammo]) + { + ent->client->ps.gunframe = 15; + } + else + { + ent->client->ps.gunframe++; + } + + if (ent->client->ps.gunframe == 22) + { + ent->client->weapon_sound = 0; + gi.sound(ent, CHAN_AUTO, gi.soundindex("weapons/chngnd1a.wav"), 1, ATTN_IDLE, 0); + } + + if (ent->client->ps.gunframe < 5 || ent->client->ps.gunframe > 21) + return; + + ent->client->weapon_sound = gi.soundindex("weapons/chngnl1a.wav"); + + ent->client->anim_priority = ANIM_ATTACK; + if (ent->client->ps.pmove.pm_flags & PMF_DUCKED) + { + ent->s.frame = FRAME_crattak1 - (ent->client->ps.gunframe & 1); + ent->client->anim_end = FRAME_crattak9; + } + else + { + ent->s.frame = FRAME_attack1 - (ent->client->ps.gunframe & 1); + ent->client->anim_end = FRAME_attack8; + } + ent->client->anim_time = 0_ms; + + if (ent->client->ps.gunframe <= 9) + shots = 1; + else if (ent->client->ps.gunframe <= 14) + { + if (ent->client->buttons & BUTTON_ATTACK) + shots = 2; + else + shots = 1; + } + else + shots = 3; + + if (ent->client->pers.inventory[ent->client->pers.weapon->ammo] < shots) + shots = ent->client->pers.inventory[ent->client->pers.weapon->ammo]; + + if (!shots) + { + NoAmmoWeaponChange(ent, true); + return; + } + + if (is_quad) + { + damage *= damage_multiplier; + kick *= damage_multiplier; + } + + vec3_t kick_origin {}, kick_angles {}; + for (i = 0; i < 3; i++) + { + kick_origin[i] = crandom() * 0.35f; + kick_angles[i] = crandom() * (0.5f + (shots * 0.15f)); + } + P_AddWeaponKick(ent, kick_origin, kick_angles); + + vec3_t start, dir; + P_ProjectSource(ent, ent->client->v_angle, { 0, 0, -8 }, start, dir); + + G_LagCompensate(ent, start, dir); + for (i = 0; i < shots; i++) + { + // get start / end positions + // Paril: kill sideways angle on hitscan + r = crandom() * 4; + u = crandom() * 4; + P_ProjectSource(ent, ent->client->v_angle, { 0, r, u + -8 }, start, dir); + + fire_bullet(ent, start, dir, damage, kick, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, MOD_CHAINGUN); + } + G_UnLagCompensate(); + + Weapon_PowerupSound(ent); + + // send muzzle flash + gi.WriteByte(svc_muzzleflash); + gi.WriteEntity(ent); + gi.WriteByte((MZ_CHAINGUN1 + shots - 1) | is_silenced); + gi.multicast(ent->s.origin, MULTICAST_PVS, false); + + PlayerNoise(ent, start, PNOISE_WEAPON); + + G_RemoveAmmo(ent, shots); +} + +void Weapon_Chaingun(edict_t *ent) +{ + constexpr int pause_frames[] = { 38, 43, 51, 61, 0 }; + + Weapon_Repeating(ent, 4, 31, 61, 64, pause_frames, Chaingun_Fire); +} + +/* +====================================================================== + +SHOTGUN / SUPERSHOTGUN + +====================================================================== +*/ + +void weapon_shotgun_fire(edict_t *ent) +{ + int damage = 4; + int kick = 8; + + vec3_t start, dir; + // Paril: kill sideways angle on hitscan + P_ProjectSource(ent, ent->client->v_angle, { 0, 0, -8 }, start, dir); + + P_AddWeaponKick(ent, ent->client->v_forward * -2, { -2.f, 0.f, 0.f }); + + if (is_quad) + { + damage *= damage_multiplier; + kick *= damage_multiplier; + } + + G_LagCompensate(ent, start, dir); + if (deathmatch->integer) + fire_shotgun(ent, start, dir, damage, kick, 500, 500, DEFAULT_DEATHMATCH_SHOTGUN_COUNT, MOD_SHOTGUN); + else + fire_shotgun(ent, start, dir, damage, kick, 500, 500, DEFAULT_SHOTGUN_COUNT, MOD_SHOTGUN); + G_UnLagCompensate(); + + // send muzzle flash + gi.WriteByte(svc_muzzleflash); + gi.WriteEntity(ent); + gi.WriteByte(MZ_SHOTGUN | is_silenced); + gi.multicast(ent->s.origin, MULTICAST_PVS, false); + + PlayerNoise(ent, start, PNOISE_WEAPON); + + G_RemoveAmmo(ent); +} + +void Weapon_Shotgun(edict_t *ent) +{ + constexpr int pause_frames[] = { 22, 28, 34, 0 }; + constexpr int fire_frames[] = { 8, 0 }; + + Weapon_Generic(ent, 7, 18, 36, 39, pause_frames, fire_frames, weapon_shotgun_fire); +} + +void weapon_supershotgun_fire(edict_t *ent) +{ + int damage = 6; + int kick = 12; + + if (is_quad) + { + damage *= damage_multiplier; + kick *= damage_multiplier; + } + + vec3_t start, dir; + // Paril: kill sideways angle on hitscan + P_ProjectSource(ent, ent->client->v_angle, { 0, 0, -8 }, start, dir); + G_LagCompensate(ent, start, dir); + vec3_t v; + v[PITCH] = ent->client->v_angle[PITCH]; + v[YAW] = ent->client->v_angle[YAW] - 5; + v[ROLL] = ent->client->v_angle[ROLL]; + // Paril: kill sideways angle on hitscan + P_ProjectSource(ent, v, { 0, 0, -8 }, start, dir); + fire_shotgun(ent, start, dir, damage, kick, DEFAULT_SHOTGUN_HSPREAD, DEFAULT_SHOTGUN_VSPREAD, DEFAULT_SSHOTGUN_COUNT / 2, MOD_SSHOTGUN); + v[YAW] = ent->client->v_angle[YAW] + 5; + P_ProjectSource(ent, v, { 0, 0, -8 }, start, dir); + fire_shotgun(ent, start, dir, damage, kick, DEFAULT_SHOTGUN_HSPREAD, DEFAULT_SHOTGUN_VSPREAD, DEFAULT_SSHOTGUN_COUNT / 2, MOD_SSHOTGUN); + G_UnLagCompensate(); + + P_AddWeaponKick(ent, ent->client->v_forward * -2, { -2.f, 0.f, 0.f }); + + // send muzzle flash + gi.WriteByte(svc_muzzleflash); + gi.WriteEntity(ent); + gi.WriteByte(MZ_SSHOTGUN | is_silenced); + gi.multicast(ent->s.origin, MULTICAST_PVS, false); + + PlayerNoise(ent, start, PNOISE_WEAPON); + + G_RemoveAmmo(ent); +} + +void Weapon_SuperShotgun(edict_t *ent) +{ + constexpr int pause_frames[] = { 29, 42, 57, 0 }; + constexpr int fire_frames[] = { 7, 0 }; + + Weapon_Generic(ent, 6, 17, 57, 61, pause_frames, fire_frames, weapon_supershotgun_fire); +} + +/* +====================================================================== + +RAILGUN + +====================================================================== +*/ + +void weapon_railgun_fire(edict_t *ent) +{ + int damage = 100; + int kick = 200; + + if (is_quad) + { + damage *= damage_multiplier; + kick *= damage_multiplier; + } + + vec3_t start, dir; + P_ProjectSource(ent, ent->client->v_angle, { 0, 7, -8 }, start, dir); + G_LagCompensate(ent, start, dir); + fire_rail(ent, start, dir, damage, kick); + G_UnLagCompensate(); + + P_AddWeaponKick(ent, ent->client->v_forward * -3, { -3.f, 0.f, 0.f }); + + // send muzzle flash + gi.WriteByte(svc_muzzleflash); + gi.WriteEntity(ent); + gi.WriteByte(MZ_RAILGUN | is_silenced); + gi.multicast(ent->s.origin, MULTICAST_PVS, false); + + PlayerNoise(ent, start, PNOISE_WEAPON); + + G_RemoveAmmo(ent); +} + +void Weapon_Railgun(edict_t *ent) +{ + constexpr int pause_frames[] = { 56, 0 }; + constexpr int fire_frames[] = { 4, 0 }; + + Weapon_Generic(ent, 3, 18, 56, 61, pause_frames, fire_frames, weapon_railgun_fire); +} + +/* +====================================================================== + +BFG10K + +====================================================================== +*/ + +void weapon_bfg_fire(edict_t *ent) +{ + int damage; + float damage_radius = 1000; + + if (deathmatch->integer) + damage = 200; + else + damage = 500; + + if (ent->client->ps.gunframe == 9) + { + // send muzzle flash + gi.WriteByte(svc_muzzleflash); + gi.WriteEntity(ent); + gi.WriteByte(MZ_BFG | is_silenced); + gi.multicast(ent->s.origin, MULTICAST_PVS, false); + + PlayerNoise(ent, ent->s.origin, PNOISE_WEAPON); + return; + } + + // cells can go down during windup (from power armor hits), so + // check again and abort firing if we don't have enough now + if (ent->client->pers.inventory[ent->client->pers.weapon->ammo] < 50) + return; + + if (is_quad) + damage *= damage_multiplier; + + vec3_t start, dir; + P_ProjectSource(ent, ent->client->v_angle, { 8, 8, -8 }, start, dir); + fire_bfg(ent, start, dir, damage, 400, damage_radius); + + P_AddWeaponKick(ent, ent->client->v_forward * -2, { -20.f, 0, crandom() * 8 }); + ent->client->kick.total = DAMAGE_TIME(); + ent->client->kick.time = level.time + ent->client->kick.total; + + // send muzzle flash + gi.WriteByte(svc_muzzleflash); + gi.WriteEntity(ent); + gi.WriteByte(MZ_BFG2 | is_silenced); + gi.multicast(ent->s.origin, MULTICAST_PVS, false); + + PlayerNoise(ent, start, PNOISE_WEAPON); + + G_RemoveAmmo(ent); +} + +void Weapon_BFG(edict_t *ent) +{ + constexpr int pause_frames[] = { 39, 45, 50, 55, 0 }; + constexpr int fire_frames[] = { 9, 17, 0 }; + + Weapon_Generic(ent, 8, 32, 54, 58, pause_frames, fire_frames, weapon_bfg_fire); +} + +//====================================================================== + +void weapon_disint_fire(edict_t *self) +{ + vec3_t start, dir; + P_ProjectSource(self, self->client->v_angle, { 24, 8, -8 }, start, dir); + + P_AddWeaponKick(self, self->client->v_forward * -2, { -1.f, 0.f, 0.f }); + + fire_disintegrator(self, start, dir, 800); + + // send muzzle flash + gi.WriteByte(svc_muzzleflash); + gi.WriteEntity(self); + gi.WriteByte(MZ_BLASTER2); + gi.multicast(self->s.origin, MULTICAST_PVS, false); + + PlayerNoise(self, start, PNOISE_WEAPON); + + G_RemoveAmmo(self); +} + +void Weapon_Beta_Disintegrator(edict_t *ent) +{ + constexpr int pause_frames[] = { 30, 37, 45, 0 }; + constexpr int fire_frames[] = { 17, 0 }; + + Weapon_Generic(ent, 16, 23, 46, 50, pause_frames, fire_frames, weapon_disint_fire); +} diff --git a/rerelease/q_std.cpp b/rerelease/q_std.cpp new file mode 100644 index 0000000..87c959f --- /dev/null +++ b/rerelease/q_std.cpp @@ -0,0 +1,287 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. + +// standard library stuff for game DLL + +#include "g_local.h" + +//==================================================================================== + +g_fmt_data_t g_fmt_data; + +bool COM_IsSeparator(char c, const char *seps) +{ + if (!c) + return true; + + for (const char *sep = seps; *sep; sep++) + if (*sep == c) + return true; + + return false; +} + +/* +============== +COM_ParseEx + +Parse a token out of a string +============== +*/ +char *COM_ParseEx(const char **data_p, const char *seps, char *buffer, size_t buffer_size) +{ + static char com_token[MAX_TOKEN_CHARS]; + + if (!buffer) + { + buffer = com_token; + buffer_size = MAX_TOKEN_CHARS; + } + + int c; + int len; + const char *data; + + data = *data_p; + len = 0; + buffer[0] = '\0'; + + if (!data) + { + *data_p = nullptr; + return buffer; + } + +// skip whitespace +skipwhite: + while (COM_IsSeparator(c = *data, seps)) + { + if (c == '\0') + { + *data_p = nullptr; + return buffer; + } + data++; + } + + // skip // comments + if (c == '/' && data[1] == '/') + { + while (*data && *data != '\n') + data++; + goto skipwhite; + } + + // handle quoted strings specially + if (c == '\"') + { + data++; + while (1) + { + c = *data++; + if (c == '\"' || !c) + { + const size_t endpos = std::min(len, buffer_size - 1); // [KEX] avoid overflow + buffer[endpos] = '\0'; + *data_p = data; + return buffer; + } + if (len < buffer_size) + { + buffer[len] = c; + len++; + } + } + } + + // parse a regular word + do + { + if (len < buffer_size) + { + buffer[len] = c; + len++; + } + data++; + c = *data; + } while (!COM_IsSeparator(c, seps)); + + if (len == buffer_size) + { + gi.Com_PrintFmt("Token exceeded {} chars, discarded.\n", buffer_size); + len = 0; + } + buffer[len] = '\0'; + + *data_p = data; + return buffer; +} + +/* +============================================================================ + + LIBRARY REPLACEMENT FUNCTIONS + +============================================================================ +*/ +// NB: these funcs are duplicated in the engine; this define gates us for +// static compilation. +#if defined(KEX_Q2GAME_DYNAMIC) +int Q_strcasecmp(const char *s1, const char *s2) +{ + int c1, c2; + + do + { + c1 = *s1++; + c2 = *s2++; + + if (c1 != c2) + { + if (c1 >= 'a' && c1 <= 'z') + c1 -= ('a' - 'A'); + if (c2 >= 'a' && c2 <= 'z') + c2 -= ('a' - 'A'); + if (c1 != c2) + return c1 < c2 ? -1 : 1; // strings not equal + } + } while (c1); + + return 0; // strings are equal +} + +int Q_strncasecmp(const char *s1, const char *s2, size_t n) +{ + int c1, c2; + + do + { + c1 = *s1++; + c2 = *s2++; + + if (!n--) + return 0; // strings are equal until end point + + if (c1 != c2) + { + if (c1 >= 'a' && c1 <= 'z') + c1 -= ('a' - 'A'); + if (c2 >= 'a' && c2 <= 'z') + c2 -= ('a' - 'A'); + if (c1 != c2) + return c1 < c2 ? -1 : 1; // strings not equal + } + } while (c1); + + return 0; // strings are equal +} + +/* +===================================================================== + + BSD STRING UTILITIES - haleyjd 20170610 + +===================================================================== +*/ +/* + * Copyright (c) 1998 Todd C. Miller + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * Copy src to string dst of size siz. At most siz-1 characters + * will be copied. Always NUL terminates (unless siz == 0). + * Returns strlen(src); if retval >= siz, truncation occurred. + */ +size_t Q_strlcpy(char *dst, const char *src, size_t siz) +{ + char *d = dst; + const char *s = src; + size_t n = siz; + + /* Copy as many bytes as will fit */ + if(n != 0 && --n != 0) + { + do + { + if((*d++ = *s++) == 0) + break; + } + while(--n != 0); + } + + /* Not enough room in dst, add NUL and traverse rest of src */ + if(n == 0) + { + if(siz != 0) + *d = '\0'; /* NUL-terminate dst */ + while(*s++) + ; // counter loop + } + + return (s - src - 1); /* count does not include NUL */ +} + +/* + * Appends src to string dst of size siz (unlike strncat, siz is the + * full size of dst, not space left). At most siz-1 characters + * will be copied. Always NUL terminates (unless siz == 0). + * Returns strlen(src); if retval >= siz, truncation occurred. + */ +size_t Q_strlcat(char *dst, const char *src, size_t siz) +{ + char *d = dst; + const char *s = src; + size_t n = siz; + size_t dlen; + + /* Find the end of dst and adjust bytes left but don't go past end */ + while(*d != '\0' && n-- != 0) + d++; + dlen = d - dst; + n = siz - dlen; + + if(n == 0) + return(dlen + strlen(s)); + while(*s != '\0') + { + if(n != 1) + { + *d++ = *s; + n--; + } + s++; + } + *d = '\0'; + + return (dlen + (s - src)); /* count does not include NUL */ +} + +#if !defined(USE_CPP20_FORMAT) && !defined(NO_FMT_SOURCE) +// fmt ugliness because we haven't figured out FMT_INCLUDE_ONLY +#include "../src/format.cc" +#endif +#endif +//==================================================================== diff --git a/rerelease/q_std.h b/rerelease/q_std.h new file mode 100644 index 0000000..9f976d6 --- /dev/null +++ b/rerelease/q_std.h @@ -0,0 +1,217 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. + +#pragma once + +// q_std.h -- 'standard' library stuff for game module +// not meant to be included by engine, etc + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// format! +#ifndef USE_CPP20_FORMAT +#ifdef __cpp_lib_format +#define USE_CPP20_FORMAT 1 +#endif +#endif + +#if USE_CPP20_FORMAT +#include +namespace fmt = std; +#define FMT_STRING(s) s +#else +#include +#endif + +struct g_fmt_data_t { + char string[2][4096]; + int istr; +}; + +// static data for fmt; internal, do not touch +extern g_fmt_data_t g_fmt_data; + +// like fmt::format_to_n, but automatically null terminates the output; +// returns the length of the string written (up to N) +#ifdef USE_CPP20_FORMAT +#define G_FmtTo_ G_FmtTo + +template +inline size_t G_FmtTo(char (&buffer)[N], std::format_string format_str, Args &&... args) +#else +#define G_FmtTo(buffer, str, ...) \ + G_FmtTo_(buffer, FMT_STRING(str), __VA_ARGS__) + +template +inline size_t G_FmtTo_(char (&buffer)[N], const S &format_str, Args &&... args) +#endif +{ + auto end = fmt::format_to_n(buffer, N - 1, format_str, std::forward(args)...); + + *(end.out) = '\0'; + + return end.out - buffer; +} + +// format to temp buffers; doesn't use heap allocation +// unlike `fmt::format` does directly +#ifdef USE_CPP20_FORMAT +template +[[nodiscard]] inline std::string_view G_Fmt(std::format_string format_str, Args &&... args) +#else + +#define G_Fmt(str, ...) \ + G_Fmt_(FMT_STRING(str), __VA_ARGS__) + +template +[[nodiscard]] inline std::string_view G_Fmt_(const S &format_str, Args &&... args) +#endif +{ + g_fmt_data.istr ^= 1; + + size_t len = G_FmtTo_(g_fmt_data.string[g_fmt_data.istr], format_str, std::forward(args)...); + + return std::string_view(g_fmt_data.string[g_fmt_data.istr], len); +} + +// fmt::join replacement +template +std::string join_strings(const T &cont, const char *separator) +{ + if (cont.empty()) + return ""; + + return std::accumulate(++cont.begin(), cont.end(), *cont.begin(), + [separator](auto &&a, auto &&b) -> auto & { + a += separator; + a += b; + return a; + }); +} + +using byte = uint8_t; + +// note: only works on actual arrays +#define q_countof(a) std::extent_v + +using std::max; +using std::min; +using std::clamp; + +template +constexpr T lerp(T from, T to, float t) +{ + return t * from + (1.f - t) * to; +} + +// angle indexes +enum +{ + PITCH, + YAW, + ROLL +}; + +/* +============================================================== + +MATHLIB + +============================================================== +*/ + +constexpr double PI = 3.14159265358979323846; // matches value in gcc v2 math.h +constexpr float PIf = static_cast(PI); + +[[nodiscard]] constexpr float RAD2DEG(float x) +{ + return (x * 180.0f / PIf); +} + +[[nodiscard]] constexpr float DEG2RAD(float x) +{ + return (x * PIf / 180.0f); +} + +/* +============= +G_AddBlend +============= +*/ +inline void G_AddBlend(float r, float g, float b, float a, std::array &v_blend) +{ + if (a <= 0) + return; + + float a2 = v_blend[3] + (1 - v_blend[3]) * a; // new total alpha + float a3 = v_blend[3] / a2; // fraction of color from old + + v_blend[0] = v_blend[0] * a3 + r * (1 - a3); + v_blend[1] = v_blend[1] * a3 + g * (1 - a3); + v_blend[2] = v_blend[2] * a3 + b * (1 - a3); + v_blend[3] = a2; +} + +//============================================================================ + +/* +=============== +LerpAngle + +=============== +*/ +[[nodiscard]] constexpr float LerpAngle(float a2, float a1, float frac) +{ + if (a1 - a2 > 180) + a1 -= 360; + if (a1 - a2 < -180) + a1 += 360; + return a2 + frac * (a1 - a2); +} + +[[nodiscard]] inline float anglemod(float a) +{ + float v = fmod(a, 360.0f); + + if (v < 0) + return 360.f + v; + + return v; +} + +#include "q_vec3.h" + +//============================================= + +char *COM_ParseEx(const char **data_p, const char *seps, char *buffer = nullptr, size_t buffer_size = 0); + +// data is an in/out parm, returns a parsed out token +inline char *COM_Parse(const char **data_p, char *buffer = nullptr, size_t buffer_size = 0) +{ + return COM_ParseEx(data_p, "\r\n\t ", buffer, buffer_size); +} + +//============================================= + +// portable case insensitive compare +[[nodiscard]] int Q_strcasecmp(const char *s1, const char *s2); +[[nodiscard]] int Q_strncasecmp(const char *s1, const char *s2, size_t n); + +// BSD string utils - haleyjd +size_t Q_strlcpy(char* dst, const char* src, size_t siz); +size_t Q_strlcat(char* dst, const char* src, size_t siz); + +// EOF diff --git a/rerelease/q_vec3.h b/rerelease/q_vec3.h new file mode 100644 index 0000000..0fa0158 --- /dev/null +++ b/rerelease/q_vec3.h @@ -0,0 +1,549 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. + +#pragma once + +// q_vec3 - vec3 stuff +#include +#include + +using nullptr_t = std::nullptr_t; + +struct vec3_t +{ + float x, y, z; + + [[nodiscard]] constexpr const float &operator[](size_t i) const + { + if (i == 0) + return x; + else if (i == 1) + return y; + else if (i == 2) + return z; + throw std::out_of_range("i"); + } + + [[nodiscard]] constexpr float &operator[](size_t i) + { + if (i == 0) + return x; + else if (i == 1) + return y; + else if (i == 2) + return z; + throw std::out_of_range("i"); + } + + // comparison + [[nodiscard]] constexpr bool equals(const vec3_t &v) const + { + return x == v.x && y == v.y && z == v.z; + } + [[nodiscard]] inline bool equals(const vec3_t &v, const float &epsilon) const + { + return fabsf(x - v.x) <= epsilon && fabsf(y - v.y) <= epsilon && fabsf(z - v.z) <= epsilon; + } + [[nodiscard]] constexpr bool operator==(const vec3_t &v) const + { + return equals(v); + } + [[nodiscard]] constexpr bool operator!=(const vec3_t &v) const + { + return !(*this == v); + } + [[nodiscard]] constexpr explicit operator bool() const + { + return x || y || z; + } + + // dot + [[nodiscard]] constexpr float dot(const vec3_t &v) const + { + return (x * v.x) + (y * v.y) + (z * v.z); + } + [[nodiscard]] constexpr vec3_t scaled(const vec3_t &v) const + { + return { x * v.x, y * v.y, z * v.z }; + } + constexpr vec3_t &scale(const vec3_t &v) + { + *this = this->scaled(v); + return *this; + } + + // basic operators + [[nodiscard]] constexpr vec3_t operator-(const vec3_t &v) const + { + return { x - v.x, y - v.y, z - v.z }; + } + [[nodiscard]] constexpr vec3_t operator+(const vec3_t &v) const + { + return { x + v.x, y + v.y, z + v.z }; + } + [[nodiscard]] constexpr vec3_t operator/(const vec3_t &v) const + { + return { x / v.x, y / v.y, z / v.z }; + } + template || std::is_integral_v>> + [[nodiscard]] constexpr vec3_t operator/(const T &v) const + { + return { static_cast(x / v), static_cast(y / v), static_cast(z / v) }; + } + template || std::is_integral_v>> + [[nodiscard]] constexpr vec3_t operator*(const T &v) const + { + return { static_cast(x * v), static_cast(y * v), static_cast(z * v) }; + } + [[nodiscard]] constexpr vec3_t operator-() const + { + return { -x, -y, -z }; + } + + constexpr vec3_t &operator-=(const vec3_t &v) + { + *this = *this - v; + return *this; + } + constexpr vec3_t &operator+=(const vec3_t &v) + { + *this = *this + v; + return *this; + } + constexpr vec3_t &operator/=(const vec3_t &v) + { + *this = *this / v; + return *this; + } + template || std::is_integral_v>> + constexpr vec3_t &operator/=(const T &v) + { + *this = *this / v; + return *this; + } + template || std::is_integral_v>> + constexpr vec3_t &operator*=(const T &v) + { + *this = *this * v; + return *this; + } + + // operations + [[nodiscard]] constexpr float lengthSquared() const + { + return this->dot(*this); + } + [[nodiscard]] inline float length() const + { + return sqrtf(lengthSquared()); + } + [[nodiscard]] inline vec3_t normalized() const + { + float len = length(); + return len ? (*this * (1.f / len)) : *this; + } + [[nodiscard]] inline vec3_t normalized(float &len) const + { + len = length(); + return len ? (*this * (1.f / len)) : *this; + } + inline float normalize() + { + float len = length(); + + if (len) + *this *= (1.f / len); + + return len; + } + [[nodiscard]] constexpr vec3_t cross(const vec3_t &v) const + { + return { + y * v.z - z * v.y, + z * v.x - x * v.z, + x * v.y - y * v.x + }; + } +}; + +constexpr vec3_t vec3_origin{}; + +inline void AngleVectors(const vec3_t &angles, vec3_t *forward, vec3_t *right, vec3_t *up) +{ + float angle = angles[YAW] * (PIf * 2 / 360); + float sy = sinf(angle); + float cy = cosf(angle); + angle = angles[PITCH] * (PIf * 2 / 360); + float sp = sinf(angle); + float cp = cosf(angle); + angle = angles[ROLL] * (PIf * 2 / 360); + float sr = sinf(angle); + float cr = cosf(angle); + + if (forward) + { + forward->x = cp * cy; + forward->y = cp * sy; + forward->z = -sp; + } + if (right) + { + right->x = (-1 * sr * sp * cy + -1 * cr * -sy); + right->y = (-1 * sr * sp * sy + -1 * cr * cy); + right->z = -1 * sr * cp; + } + if (up) + { + up->x = (cr * sp * cy + -sr * -sy); + up->y = (cr * sp * sy + -sr * cy); + up->z = cr * cp; + } +} + +struct angle_vectors_t { + vec3_t forward, right, up; +}; + +// for destructuring +inline angle_vectors_t AngleVectors(const vec3_t &angles) +{ + angle_vectors_t v; + AngleVectors(angles, &v.forward, &v.right, &v.up); + return v; +} + +// silly wrappers to allow old C code to work +inline void AngleVectors(const vec3_t &angles, vec3_t &forward, vec3_t &right, vec3_t &up) +{ + AngleVectors(angles, &forward, &right, &up); +} +inline void AngleVectors(const vec3_t &angles, vec3_t &forward, vec3_t &right, nullptr_t) +{ + AngleVectors(angles, &forward, &right, nullptr); +} +inline void AngleVectors(const vec3_t &angles, vec3_t &forward, nullptr_t, vec3_t &up) +{ + AngleVectors(angles, &forward, nullptr, &up); +} +inline void AngleVectors(const vec3_t &angles, vec3_t &forward, nullptr_t, nullptr_t) +{ + AngleVectors(angles, &forward, nullptr, nullptr); +} +inline void AngleVectors(const vec3_t &angles, nullptr_t, nullptr_t, vec3_t &up) +{ + AngleVectors(angles, nullptr, nullptr, &up); +} +inline void AngleVectors(const vec3_t &angles, nullptr_t, vec3_t &right, nullptr_t) +{ + AngleVectors(angles, nullptr, &right, nullptr); +} + +inline void ClearBounds(vec3_t &mins, vec3_t &maxs) +{ + mins[0] = mins[1] = mins[2] = std::numeric_limits::infinity(); + maxs[0] = maxs[1] = maxs[2] = -std::numeric_limits::infinity(); +} + +inline void AddPointToBounds(const vec3_t &v, vec3_t &mins, vec3_t &maxs) +{ + for (int i = 0; i < 3; i++) + { + float val = v[i]; + if (val < mins[i]) + mins[i] = val; + if (val > maxs[i]) + maxs[i] = val; + } +} + +[[nodiscard]] constexpr vec3_t ProjectPointOnPlane(const vec3_t &p, const vec3_t &normal) +{ + float inv_denom = 1.0f / normal.dot(normal); + float d = normal.dot(p) * inv_denom; + return p - ((normal * inv_denom) * d); +} + +/* +** assumes "src" is normalized +*/ +[[nodiscard]] inline vec3_t PerpendicularVector(const vec3_t &src) +{ + int pos; + int i; + float minelem = 1.0F; + vec3_t tempvec; + + /* + ** find the smallest magnitude axially aligned vector + */ + for (pos = 0, i = 0; i < 3; i++) + { + if (fabsf(src[i]) < minelem) + { + pos = i; + minelem = fabsf(src[i]); + } + } + tempvec[0] = tempvec[1] = tempvec[2] = 0.0F; + tempvec[pos] = 1.0F; + + /* + ** project the point onto the plane defined by src & normalize the result + */ + return ProjectPointOnPlane(tempvec, src).normalized(); +} + +using mat3_t = std::array, 3>; + +/* +================ +R_ConcatRotations +================ +*/ +[[nodiscard]] constexpr mat3_t R_ConcatRotations(const mat3_t &in1, const mat3_t &in2) +{ + return { + std::array { + in1[0][0] * in2[0][0] + in1[0][1] * in2[1][0] + in1[0][2] * in2[2][0], + in1[0][0] * in2[0][1] + in1[0][1] * in2[1][1] + in1[0][2] * in2[2][1], + in1[0][0] * in2[0][2] + in1[0][1] * in2[1][2] + in1[0][2] * in2[2][2] + }, + { + in1[1][0] * in2[0][0] + in1[1][1] * in2[1][0] + in1[1][2] * in2[2][0], + in1[1][0] * in2[0][1] + in1[1][1] * in2[1][1] + in1[1][2] * in2[2][1], + in1[1][0] * in2[0][2] + in1[1][1] * in2[1][2] + in1[1][2] * in2[2][2] + }, + { + in1[2][0] * in2[0][0] + in1[2][1] * in2[1][0] + in1[2][2] * in2[2][0], + in1[2][0] * in2[0][1] + in1[2][1] * in2[1][1] + in1[2][2] * in2[2][1], + in1[2][0] * in2[0][2] + in1[2][1] * in2[1][2] + in1[2][2] * in2[2][2] + } + }; +} + +[[nodiscard]] inline vec3_t RotatePointAroundVector(const vec3_t &dir, const vec3_t &point, float degrees) +{ + mat3_t m; + mat3_t im; + mat3_t zrot; + mat3_t rot; + vec3_t vr, vup, vf; + + vf = dir; + + vr = PerpendicularVector(dir); + vup = vr.cross(vf); + + m[0][0] = vr[0]; + m[1][0] = vr[1]; + m[2][0] = vr[2]; + + m[0][1] = vup[0]; + m[1][1] = vup[1]; + m[2][1] = vup[2]; + + m[0][2] = vf[0]; + m[1][2] = vf[1]; + m[2][2] = vf[2]; + + im = m; + + im[0][1] = m[1][0]; + im[0][2] = m[2][0]; + im[1][0] = m[0][1]; + im[1][2] = m[2][1]; + im[2][0] = m[0][2]; + im[2][1] = m[1][2]; + + zrot = {}; + zrot[0][0] = zrot[1][1] = zrot[2][2] = 1.0F; + + zrot[0][0] = cosf(DEG2RAD(degrees)); + zrot[0][1] = sinf(DEG2RAD(degrees)); + zrot[1][0] = -sinf(DEG2RAD(degrees)); + zrot[1][1] = cosf(DEG2RAD(degrees)); + + rot = R_ConcatRotations(R_ConcatRotations(m, zrot), im); + + return { + rot[0][0] * point[0] + rot[0][1] * point[1] + rot[0][2] * point[2], + rot[1][0] * point[0] + rot[1][1] * point[1] + rot[1][2] * point[2], + rot[2][0] * point[0] + rot[2][1] * point[1] + rot[2][2] * point[2] + }; +} + +[[nodiscard]] constexpr vec3_t closest_point_to_box(const vec3_t &from, const vec3_t &absmins, const vec3_t &absmaxs) +{ + return { + (from[0] < absmins[0]) ? absmins[0] : (from[0] > absmaxs[0]) ? absmaxs[0] : from[0], + (from[1] < absmins[1]) ? absmins[1] : (from[1] > absmaxs[1]) ? absmaxs[1] : from[1], + (from[2] < absmins[2]) ? absmins[2] : (from[2] > absmaxs[2]) ? absmaxs[2] : from[2] + }; +} + +[[nodiscard]] inline float distance_between_boxes(const vec3_t &absminsa, const vec3_t &absmaxsa, const vec3_t &absminsb, const vec3_t &absmaxsb) +{ + float len = 0; + + for (size_t i = 0; i < 3; i++) + { + if (absmaxsa[i] < absminsb[i]) + { + float d = absmaxsa[i] - absminsb[i]; + len += d * d; + } + else if (absminsa[i] > absmaxsb[i]) + { + float d = absminsa[i] - absmaxsb[i]; + len += d * d; + } + } + + return sqrt(len); +} + +[[nodiscard]] constexpr bool boxes_intersect(const vec3_t &amins, const vec3_t &amaxs, const vec3_t &bmins, const vec3_t &bmaxs) +{ + return amins.x <= bmaxs.x && + amaxs.x >= bmins.x && + amins.y <= bmaxs.y && + amaxs.y >= bmins.y && + amins.z <= bmaxs.z && + amaxs.z >= bmins.z; +} + +/* +================== +ClipVelocity + +Slide off of the impacting object +================== +*/ +constexpr float STOP_EPSILON = 0.1f; + +[[nodiscard]] constexpr vec3_t ClipVelocity(const vec3_t &in, const vec3_t &normal, float overbounce) +{ + float dot = in.dot(normal); + vec3_t out = in + (normal * (-2 * dot)); + out *= overbounce - 1.f; + + if (out.lengthSquared() < STOP_EPSILON * STOP_EPSILON) + out = {}; + + return out; +} + +[[nodiscard]] constexpr vec3_t SlideClipVelocity(const vec3_t &in, const vec3_t &normal, float overbounce) +{ + float backoff = in.dot(normal) * overbounce; + vec3_t out = in - (normal * backoff); + + for (int i = 0; i < 3; i++) + if (out[i] > -STOP_EPSILON && out[i] < STOP_EPSILON) + out[i] = 0; + + return out; +} + +[[nodiscard]] inline float vectoyaw(const vec3_t &vec) +{ + // PMM - fixed to correct for pitch of 0 + if (vec[PITCH] == 0) + { + if (vec[YAW] == 0) + return 0.f; + else if (vec[YAW] > 0) + return 90.f; + else + return 270.f; + } + + float yaw = (atan2(vec[YAW], vec[PITCH]) * (180.f / PIf)); + + if (yaw < 0) + yaw += 360; + + return yaw; +} + +[[nodiscard]] inline vec3_t vectoangles(const vec3_t &vec) +{ + float forward; + float yaw, pitch; + + if (vec[1] == 0 && vec[0] == 0) + { + if (vec[2] > 0) + return { -90.f, 0.f, 0.f }; + else + return { -270.f, 0.f, 0.f }; + } + + // PMM - fixed to correct for pitch of 0 + if (vec[0]) + yaw = (atan2(vec[1], vec[0]) * (180.f / PIf)); + else if (vec[1] > 0) + yaw = 90; + else + yaw = 270; + + if (yaw < 0) + yaw += 360; + + forward = sqrt(vec[0] * vec[0] + vec[1] * vec[1]); + pitch = (atan2(vec[2], forward) * (180.f / PIf)); + + if (pitch < 0) + pitch += 360; + + return { -pitch, yaw, 0 }; +} + +[[nodiscard]] constexpr vec3_t G_ProjectSource(const vec3_t &point, const vec3_t &distance, const vec3_t &forward, const vec3_t &right) +{ + return point + (forward * distance[0]) + (right * distance[1]) + vec3_t{0.f, 0.f, distance[2]}; +} + +[[nodiscard]] constexpr vec3_t G_ProjectSource2(const vec3_t &point, const vec3_t &distance, const vec3_t &forward, const vec3_t &right, const vec3_t &up) +{ + return point + (forward * distance[0]) + (right * distance[1]) + (up * distance[2]); +} + +[[nodiscard]] inline vec3_t slerp(const vec3_t &from, const vec3_t &to, float t) +{ + float dot = from.dot(to); + float aFactor; + float bFactor; + if (fabsf(dot) > 0.9995f) + { + aFactor = 1.0f - t; + bFactor = t; + } + else + { + float ang = acos(dot); + float sinOmega = sin(ang); + float sinAOmega = sin((1.0f - t) * ang); + float sinBOmega = sin(t * ang); + aFactor = sinAOmega / sinOmega; + bFactor = sinBOmega / sinOmega; + } + return from * aFactor + to * bFactor; +} + +// Fmt support +template<> +struct fmt::formatter : fmt::formatter +{ + template + auto format(const vec3_t &p, FormatContext &ctx) -> decltype(ctx.out()) + { + auto out = fmt::formatter::format(p.x, ctx); + out = fmt::format_to(out, " "); + ctx.advance_to(out); + out = fmt::formatter::format(p.y, ctx); + out = fmt::format_to(out, " "); + ctx.advance_to(out); + return fmt::formatter::format(p.z, ctx); + } +}; \ No newline at end of file diff --git a/rerelease/rogue/g_rogue_combat.cpp b/rerelease/rogue/g_rogue_combat.cpp new file mode 100644 index 0000000..a1f8174 --- /dev/null +++ b/rerelease/rogue/g_rogue_combat.cpp @@ -0,0 +1,146 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// g_combat.c + +#include "../g_local.h" + +void M_SetEffects(edict_t *self); + +/* +ROGUE +clean up heal targets for medic +*/ +void cleanupHealTarget(edict_t *ent) +{ + ent->monsterinfo.healer = nullptr; + ent->takedamage = true; + ent->monsterinfo.aiflags &= ~AI_RESURRECTING; + M_SetEffects(ent); +} + +// ********************** +// ROGUE + +/* +============ +T_RadiusNukeDamage + +Like T_RadiusDamage, but ignores walls (skips CanDamage check, among others) +// up to KILLZONE radius, do 10,000 points +// after that, do damage linearly out to KILLZONE2 radius +============ +*/ + +void T_RadiusNukeDamage(edict_t *inflictor, edict_t *attacker, float damage, edict_t *ignore, float radius, mod_t mod) +{ + float points; + edict_t *ent = nullptr; + vec3_t v; + vec3_t dir; + float len; + float killzone, killzone2; + trace_t tr; + float dist; + + killzone = radius; + killzone2 = radius * 2.0f; + + while ((ent = findradius(ent, inflictor->s.origin, killzone2)) != nullptr) + { + // ignore nobody + if (ent == ignore) + continue; + if (!ent->takedamage) + continue; + if (!ent->inuse) + continue; + if (!(ent->client || (ent->svflags & SVF_MONSTER) || (ent->flags & FL_DAMAGEABLE))) + continue; + + v = ent->mins + ent->maxs; + v = ent->s.origin + (v * 0.5f); + v = inflictor->s.origin - v; + len = v.length(); + if (len <= killzone) + { + if (ent->client) + ent->flags |= FL_NOGIB; + points = 10000; + } + else if (len <= killzone2) + points = (damage / killzone) * (killzone2 - len); + else + points = 0; + + if (points > 0) + { + if (ent->client) + ent->client->nuke_time = level.time + 2_sec; + dir = ent->s.origin - inflictor->s.origin; + T_Damage(ent, inflictor, attacker, dir, inflictor->s.origin, vec3_origin, (int) points, (int) points, DAMAGE_RADIUS, mod); + } + } + ent = g_edicts + 1; // skip the worldspawn + // cycle through players + while (ent) + { + if ((ent->client) && (ent->client->nuke_time != level.time + 2_sec) && (ent->inuse)) + { + tr = gi.traceline(inflictor->s.origin, ent->s.origin, inflictor, MASK_SOLID); + if (tr.fraction == 1.0f) + ent->client->nuke_time = level.time + 2_sec; + else + { + dist = realrange(ent, inflictor); + if (dist < 2048) + ent->client->nuke_time = max(ent->client->nuke_time, level.time + 1.5_sec); + else + ent->client->nuke_time = max(ent->client->nuke_time, level.time + 1_sec); + } + ent++; + } + else + ent = nullptr; + } +} + +/* +============ +T_RadiusClassDamage + +Like T_RadiusDamage, but ignores anything with classname=ignoreClass +============ +*/ +void T_RadiusClassDamage(edict_t *inflictor, edict_t *attacker, float damage, char *ignoreClass, float radius, mod_t mod) +{ + float points; + edict_t *ent = nullptr; + vec3_t v; + vec3_t dir; + + while ((ent = findradius(ent, inflictor->s.origin, radius)) != nullptr) + { + if (ent->classname && !strcmp(ent->classname, ignoreClass)) + continue; + if (!ent->takedamage) + continue; + + v = ent->mins + ent->maxs; + v = ent->s.origin + (v * 0.5f); + v = inflictor->s.origin - v; + points = damage - 0.5f * v.length(); + if (ent == attacker) + points = points * 0.5f; + if (points > 0) + { + if (CanDamage(ent, inflictor)) + { + dir = ent->s.origin - inflictor->s.origin; + T_Damage(ent, inflictor, attacker, dir, inflictor->s.origin, vec3_origin, (int) points, (int) points, DAMAGE_RADIUS, mod); + } + } + } +} + +// ROGUE +// ******************** \ No newline at end of file diff --git a/rerelease/rogue/g_rogue_func.cpp b/rerelease/rogue/g_rogue_func.cpp new file mode 100644 index 0000000..32b944c --- /dev/null +++ b/rerelease/rogue/g_rogue_func.cpp @@ -0,0 +1,430 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. + +#include "../g_local.h" + +//==== +// PGM +constexpr spawnflags_t SPAWNFLAGS_PLAT2_TOGGLE = 2_spawnflag; +constexpr spawnflags_t SPAWNFLAGS_PLAT2_TOP = 4_spawnflag; +constexpr spawnflags_t SPAWNFLAGS_PLAT2_START_ACTIVE = 8_spawnflag; +constexpr spawnflags_t SPAWNFLAGS_PLAT2_BOX_LIFT = 32_spawnflag; +// PGM +//==== + +void plat2_go_down(edict_t *ent); +void plat2_go_up(edict_t *ent); + +void plat2_spawn_danger_area(edict_t *ent) +{ + vec3_t mins, maxs; + + mins = ent->mins; + maxs = ent->maxs; + maxs[2] = ent->mins[2] + 64; + + SpawnBadArea(mins, maxs, 0_ms, ent); +} + +void plat2_kill_danger_area(edict_t *ent) +{ + edict_t *t; + + t = nullptr; + while ((t = G_FindByString<&edict_t::classname>(t, "bad_area"))) + { + if (t->owner == ent) + G_FreeEdict(t); + } +} + +MOVEINFO_ENDFUNC(plat2_hit_top) (edict_t *ent) -> void +{ + if (!(ent->flags & FL_TEAMSLAVE)) + { + if (ent->moveinfo.sound_end) + gi.sound(ent, CHAN_NO_PHS_ADD | CHAN_VOICE, ent->moveinfo.sound_end, 1, ATTN_STATIC, 0); + } + ent->s.sound = 0; + ent->moveinfo.state = STATE_TOP; + + if (ent->plat2flags & PLAT2_CALLED) + { + ent->plat2flags = PLAT2_WAITING; + if (!ent->spawnflags.has(SPAWNFLAGS_PLAT2_TOGGLE)) + { + ent->think = plat2_go_down; + ent->nextthink = level.time + 5_sec; + } + if (deathmatch->integer) + ent->last_move_time = level.time - 1_sec; + else + ent->last_move_time = level.time - 2_sec; + } + else if (!(ent->spawnflags & SPAWNFLAGS_PLAT2_TOP) && !ent->spawnflags.has(SPAWNFLAGS_PLAT2_TOGGLE)) + { + ent->plat2flags = PLAT2_NONE; + ent->think = plat2_go_down; + ent->nextthink = level.time + 2_sec; + ent->last_move_time = level.time; + } + else + { + ent->plat2flags = PLAT2_NONE; + ent->last_move_time = level.time; + } + + G_UseTargets(ent, ent); +} + +MOVEINFO_ENDFUNC(plat2_hit_bottom) (edict_t *ent) -> void +{ + if (!(ent->flags & FL_TEAMSLAVE)) + { + if (ent->moveinfo.sound_end) + gi.sound(ent, CHAN_NO_PHS_ADD | CHAN_VOICE, ent->moveinfo.sound_end, 1, ATTN_STATIC, 0); + } + ent->s.sound = 0; + ent->moveinfo.state = STATE_BOTTOM; + + if (ent->plat2flags & PLAT2_CALLED) + { + ent->plat2flags = PLAT2_WAITING; + if (!(ent->spawnflags & SPAWNFLAGS_PLAT2_TOGGLE)) + { + ent->think = plat2_go_up; + ent->nextthink = level.time + 5_sec; + } + if (deathmatch->integer) + ent->last_move_time = level.time - 1_sec; + else + ent->last_move_time = level.time - 2_sec; + } + else if (ent->spawnflags.has(SPAWNFLAGS_PLAT2_TOP) && !ent->spawnflags.has(SPAWNFLAGS_PLAT2_TOGGLE)) + { + ent->plat2flags = PLAT2_NONE; + ent->think = plat2_go_up; + ent->nextthink = level.time + 2_sec; + ent->last_move_time = level.time; + } + else + { + ent->plat2flags = PLAT2_NONE; + ent->last_move_time = level.time; + } + + plat2_kill_danger_area(ent); + G_UseTargets(ent, ent); +} + +THINK(plat2_go_down) (edict_t *ent) -> void +{ + if (!(ent->flags & FL_TEAMSLAVE)) + { + if (ent->moveinfo.sound_start) + gi.sound(ent, CHAN_NO_PHS_ADD | CHAN_VOICE, ent->moveinfo.sound_start, 1, ATTN_STATIC, 0); + } + + ent->s.sound = ent->moveinfo.sound_middle; + + ent->moveinfo.state = STATE_DOWN; + ent->plat2flags |= PLAT2_MOVING; + + Move_Calc(ent, ent->moveinfo.end_origin, plat2_hit_bottom); +} + +THINK(plat2_go_up) (edict_t *ent) -> void +{ + if (!(ent->flags & FL_TEAMSLAVE)) + { + if (ent->moveinfo.sound_start) + gi.sound(ent, CHAN_NO_PHS_ADD | CHAN_VOICE, ent->moveinfo.sound_start, 1, ATTN_STATIC, 0); + } + + ent->s.sound = ent->moveinfo.sound_middle; + + ent->moveinfo.state = STATE_UP; + ent->plat2flags |= PLAT2_MOVING; + + plat2_spawn_danger_area(ent); + + Move_Calc(ent, ent->moveinfo.start_origin, plat2_hit_top); +} + +void plat2_operate(edict_t *ent, edict_t *other) +{ + int otherState; + gtime_t pauseTime; + float platCenter; + edict_t *trigger; + + trigger = ent; + ent = ent->enemy; // now point at the plat, not the trigger + + if (ent->plat2flags & PLAT2_MOVING) + return; + + if ((ent->last_move_time + 2_sec) > level.time) + return; + + platCenter = (trigger->absmin[2] + trigger->absmax[2]) / 2; + + if (ent->moveinfo.state == STATE_TOP) + { + otherState = STATE_TOP; + if (ent->spawnflags.has(SPAWNFLAGS_PLAT2_BOX_LIFT)) + { + if (platCenter > other->s.origin[2]) + otherState = STATE_BOTTOM; + } + else + { + if (trigger->absmax[2] > other->s.origin[2]) + otherState = STATE_BOTTOM; + } + } + else + { + otherState = STATE_BOTTOM; + if (other->s.origin[2] > platCenter) + otherState = STATE_TOP; + } + + ent->plat2flags = PLAT2_MOVING; + + if (deathmatch->integer) + pauseTime = 300_ms; + else + pauseTime = 500_ms; + + if (ent->moveinfo.state != otherState) + { + ent->plat2flags |= PLAT2_CALLED; + pauseTime = 100_ms; + } + + ent->last_move_time = level.time; + + if (ent->moveinfo.state == STATE_BOTTOM) + { + ent->think = plat2_go_up; + ent->nextthink = level.time + pauseTime; + } + else + { + ent->think = plat2_go_down; + ent->nextthink = level.time + pauseTime; + } +} + +TOUCH(Touch_Plat_Center2) (edict_t *ent, edict_t *other, const trace_t &tr, bool other_touching_self) -> void +{ + // this requires monsters to actively trigger plats, not just step on them. + + // FIXME - commented out for E3 + // if (!other->client) + // return; + + if (other->health <= 0) + return; + + // PMM - don't let non-monsters activate plat2s + if ((!(other->svflags & SVF_MONSTER)) && (!other->client)) + return; + + plat2_operate(ent, other); +} + +MOVEINFO_BLOCKED(plat2_blocked) (edict_t *self, edict_t *other) -> void +{ + if (!(other->svflags & SVF_MONSTER) && (!other->client)) + { + // give it a chance to go away on it's own terms (like gibs) + T_Damage(other, self, self, vec3_origin, other->s.origin, vec3_origin, 100000, 1, DAMAGE_NONE, MOD_CRUSH); + // if it's still there, nuke it + if (other && other->inuse && other->solid) + BecomeExplosion1(other); + return; + } + + // gib dead things + if (other->health < 1) + { + T_Damage(other, self, self, vec3_origin, other->s.origin, vec3_origin, 100, 1, DAMAGE_NONE, MOD_CRUSH); + } + + T_Damage(other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, DAMAGE_NONE, MOD_CRUSH); + + // [Paril-KEX] killed, so don't change direction + if (!other->inuse || !other->solid) + return; + + if (self->moveinfo.state == STATE_UP) + plat2_go_down(self); + else if (self->moveinfo.state == STATE_DOWN) + plat2_go_up(self); +} + +USE(Use_Plat2) (edict_t *ent, edict_t *other, edict_t *activator) -> void +{ + edict_t *trigger; + + if (ent->moveinfo.state > STATE_BOTTOM) + return; + // [Paril-KEX] disabled this; causes confusing situations + //if ((ent->last_move_time + 2_sec) > level.time) + // return; + + uint32_t i; + for (i = 1, trigger = g_edicts + 1; i < globals.num_edicts; i++, trigger++) + { + if (!trigger->inuse) + continue; + if (trigger->touch == Touch_Plat_Center2) + { + if (trigger->enemy == ent) + { + // Touch_Plat_Center2 (trigger, activator, nullptr, nullptr); + plat2_operate(trigger, activator); + return; + } + } + } +} + +USE(plat2_activate) (edict_t *ent, edict_t *other, edict_t *activator) -> void +{ + edict_t *trigger; + + // if(ent->targetname) + // ent->targetname[0] = 0; + + ent->use = Use_Plat2; + + trigger = plat_spawn_inside_trigger(ent); // the "start moving" trigger + + trigger->maxs[0] += 10; + trigger->maxs[1] += 10; + trigger->mins[0] -= 10; + trigger->mins[1] -= 10; + + gi.linkentity(trigger); + + trigger->touch = Touch_Plat_Center2; // Override trigger touch function + + plat2_go_down(ent); +} + +/*QUAKED func_plat2 (0 .5 .8) ? PLAT_LOW_TRIGGER PLAT2_TOGGLE PLAT2_TOP PLAT2_START_ACTIVE UNUSED BOX_LIFT +speed default 150 + +PLAT_LOW_TRIGGER - creates a short trigger field at the bottom +PLAT2_TOGGLE - plat will not return to default position. +PLAT2_TOP - plat's default position will the the top. +PLAT2_START_ACTIVE - plat will trigger it's targets each time it hits top +UNUSED +BOX_LIFT - this indicates that the lift is a box, rather than just a platform + +Plats are always drawn in the extended position, so they will light correctly. + +If the plat is the target of another trigger or button, it will start out disabled in the extended position until it is trigger, when it will lower and become a normal plat. + +"speed" overrides default 200. +"accel" overrides default 500 +"lip" no default + +If the "height" key is set, that will determine the amount the plat moves, instead of being implicitly determoveinfoned by the model's height. + +*/ +void SP_func_plat2(edict_t *ent) +{ + edict_t *trigger; + + ent->s.angles = {}; + ent->solid = SOLID_BSP; + ent->movetype = MOVETYPE_PUSH; + + gi.setmodel(ent, ent->model); + + ent->moveinfo.blocked = plat2_blocked; + + if (!ent->speed) + ent->speed = 20; + else + ent->speed *= 0.1f; + + if (!ent->accel) + ent->accel = 5; + else + ent->accel *= 0.1f; + + if (!ent->decel) + ent->decel = 5; + else + ent->decel *= 0.1f; + + if (deathmatch->integer) + { + ent->speed *= 2; + ent->accel *= 2; + ent->decel *= 2; + } + + // PMM Added to kill things it's being blocked by + if (!ent->dmg) + ent->dmg = 2; + + // if (!st.lip) + // st.lip = 8; + + // pos1 is the top position, pos2 is the bottom + ent->pos1 = ent->s.origin; + ent->pos2 = ent->s.origin; + + if (st.height) + ent->pos2[2] -= (st.height - st.lip); + else + ent->pos2[2] -= (ent->maxs[2] - ent->mins[2]) - st.lip; + + ent->moveinfo.state = STATE_TOP; + + if (ent->targetname && !(ent->spawnflags & SPAWNFLAGS_PLAT2_START_ACTIVE)) + { + ent->use = plat2_activate; + } + else + { + ent->use = Use_Plat2; + + trigger = plat_spawn_inside_trigger(ent); // the "start moving" trigger + + // PGM - debugging?? + trigger->maxs[0] += 10; + trigger->maxs[1] += 10; + trigger->mins[0] -= 10; + trigger->mins[1] -= 10; + + gi.linkentity(trigger); + + trigger->touch = Touch_Plat_Center2; // Override trigger touch function + + if (!(ent->spawnflags & SPAWNFLAGS_PLAT2_TOP)) + { + ent->s.origin = ent->pos2; + ent->moveinfo.state = STATE_BOTTOM; + } + } + + gi.linkentity(ent); + + ent->moveinfo.speed = ent->speed; + ent->moveinfo.accel = ent->accel; + ent->moveinfo.decel = ent->decel; + ent->moveinfo.wait = ent->wait; + ent->moveinfo.start_origin = ent->pos1; + ent->moveinfo.start_angles = ent->s.angles; + ent->moveinfo.end_origin = ent->pos2; + ent->moveinfo.end_angles = ent->s.angles; + + G_SetMoveinfoSounds(ent, "plats/pt1_strt.wav", "plats/pt1_mid.wav", "plats/pt1_end.wav"); +} diff --git a/rerelease/rogue/g_rogue_items.cpp b/rerelease/rogue/g_rogue_items.cpp new file mode 100644 index 0000000..1e9c805 --- /dev/null +++ b/rerelease/rogue/g_rogue_items.cpp @@ -0,0 +1,228 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. + +#include "../g_local.h" + +// ================ +// PMM +bool Pickup_Nuke(edict_t *ent, edict_t *other) +{ + int quantity; + + quantity = other->client->pers.inventory[ent->item->id]; + + if (quantity >= 1) + return false; + + if (coop->integer && !P_UseCoopInstancedItems() && (ent->item->flags & IF_STAY_COOP) && (quantity > 0)) + return false; + + other->client->pers.inventory[ent->item->id]++; + + if (deathmatch->integer) + { + if (!(ent->spawnflags & SPAWNFLAG_ITEM_DROPPED)) + SetRespawn(ent, gtime_t::from_sec(ent->item->quantity)); + } + + return true; +} + +// ================ +// PGM +void Use_IR(edict_t *ent, gitem_t *item) +{ + ent->client->pers.inventory[item->id]--; + + ent->client->ir_time = max(level.time, ent->client->ir_time) + 60_sec; + + gi.sound(ent, CHAN_ITEM, gi.soundindex("misc/ir_start.wav"), 1, ATTN_NORM, 0); +} + +void Use_Double(edict_t *ent, gitem_t *item) +{ + ent->client->pers.inventory[item->id]--; + + ent->client->double_time = max(level.time, ent->client->double_time) + 30_sec; + + gi.sound(ent, CHAN_ITEM, gi.soundindex("misc/ddamage1.wav"), 1, ATTN_NORM, 0); +} + +void Use_Nuke(edict_t *ent, gitem_t *item) +{ + vec3_t forward, right, start; + int speed; + + ent->client->pers.inventory[item->id]--; + + AngleVectors(ent->client->v_angle, forward, right, nullptr); + + start = ent->s.origin; + speed = 100; + fire_nuke(ent, start, forward, speed); +} + +void Use_Doppleganger(edict_t *ent, gitem_t *item) +{ + vec3_t forward, right; + vec3_t createPt, spawnPt; + vec3_t ang; + + ang[PITCH] = 0; + ang[YAW] = ent->client->v_angle[YAW]; + ang[ROLL] = 0; + AngleVectors(ang, forward, right, nullptr); + + createPt = ent->s.origin + (forward * 48); + + if (!FindSpawnPoint(createPt, ent->mins, ent->maxs, spawnPt, 32)) + return; + + if (!CheckGroundSpawnPoint(spawnPt, ent->mins, ent->maxs, 64, -1)) + return; + + ent->client->pers.inventory[item->id]--; + + SpawnGrow_Spawn(spawnPt, 24.f, 48.f); + fire_doppleganger(ent, spawnPt, forward); +} + +bool Pickup_Doppleganger(edict_t *ent, edict_t *other) +{ + int quantity; + + if (!deathmatch->integer) // item is DM only + return false; + + quantity = other->client->pers.inventory[ent->item->id]; + if (quantity >= 1) // FIXME - apply max to dopplegangers + return false; + + other->client->pers.inventory[ent->item->id]++; + + if (!(ent->spawnflags & SPAWNFLAG_ITEM_DROPPED)) + SetRespawn(ent, gtime_t::from_sec(ent->item->quantity)); + + return true; +} + +bool Pickup_Sphere(edict_t *ent, edict_t *other) +{ + int quantity; + + if (other->client && other->client->owned_sphere) + { + // gi.LocClient_Print(other, PRINT_HIGH, "$g_only_one_sphere_customer"); + return false; + } + + quantity = other->client->pers.inventory[ent->item->id]; + if ((skill->integer == 1 && quantity >= 2) || (skill->integer >= 2 && quantity >= 1)) + return false; + + if ((coop->integer) && !P_UseCoopInstancedItems() && (ent->item->flags & IF_STAY_COOP) && (quantity > 0)) + return false; + + other->client->pers.inventory[ent->item->id]++; + + if (deathmatch->integer) + { + if (!(ent->spawnflags & SPAWNFLAG_ITEM_DROPPED)) + SetRespawn(ent, gtime_t::from_sec(ent->item->quantity)); + if (g_dm_instant_items->integer) + { + // PGM + if (ent->item->use) + ent->item->use(other, ent->item); + else + gi.Com_Print("Powerup has no use function!\n"); + // PGM + } + } + + return true; +} + +void Use_Defender(edict_t *ent, gitem_t *item) +{ + if (ent->client && ent->client->owned_sphere) + { + gi.LocClient_Print(ent, PRINT_HIGH, "$g_only_one_sphere_time"); + return; + } + + ent->client->pers.inventory[item->id]--; + + Defender_Launch(ent); +} + +void Use_Hunter(edict_t *ent, gitem_t *item) +{ + if (ent->client && ent->client->owned_sphere) + { + gi.LocClient_Print(ent, PRINT_HIGH, "$g_only_one_sphere_time"); + return; + } + + ent->client->pers.inventory[item->id]--; + + Hunter_Launch(ent); +} + +void Use_Vengeance(edict_t *ent, gitem_t *item) +{ + if (ent->client && ent->client->owned_sphere) + { + gi.LocClient_Print(ent, PRINT_HIGH, "$g_only_one_sphere_time"); + return; + } + + ent->client->pers.inventory[item->id]--; + + Vengeance_Launch(ent); +} + +// PGM +// ================ + +//================= +// Item_TriggeredSpawn - create the item marked for spawn creation +//================= +USE(Item_TriggeredSpawn) (edict_t *self, edict_t *other, edict_t *activator) -> void +{ + self->svflags &= ~SVF_NOCLIENT; + self->use = nullptr; + + if (self->spawnflags.has(SPAWNFLAG_ITEM_TOSS_SPAWN)) + { + self->movetype = MOVETYPE_TOSS; + vec3_t forward, right; + + AngleVectors(self->s.angles, forward, right, nullptr); + self->s.origin = self->s.origin; + self->s.origin[2] += 16; + self->velocity = forward * 100; + self->velocity[2] = 300; + } + + if (self->item->id != IT_KEY_POWER_CUBE && self->item->id != IT_KEY_EXPLOSIVE_CHARGES) // leave them be on key_power_cube.. + self->spawnflags &= SPAWNFLAG_ITEM_NO_TOUCH; + + droptofloor(self); +} + +//================= +// SetTriggeredSpawn - set up an item to spawn in later. +//================= +void SetTriggeredSpawn(edict_t *ent) +{ + // don't do anything on key_power_cubes. + if (ent->item->id == IT_KEY_POWER_CUBE || ent->item->id == IT_KEY_EXPLOSIVE_CHARGES) + return; + + ent->think = nullptr; + ent->nextthink = 0_ms; + ent->use = Item_TriggeredSpawn; + ent->svflags |= SVF_NOCLIENT; + ent->solid = SOLID_NOT; +} diff --git a/rerelease/rogue/g_rogue_misc.cpp b/rerelease/rogue/g_rogue_misc.cpp new file mode 100644 index 0000000..b234894 --- /dev/null +++ b/rerelease/rogue/g_rogue_misc.cpp @@ -0,0 +1,27 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. + +#include "../g_local.h" + +//====================== +// ROGUE +USE(misc_nuke_core_use) (edict_t *self, edict_t *other, edict_t *activator) -> void +{ + if (self->svflags & SVF_NOCLIENT) + self->svflags &= ~SVF_NOCLIENT; + else + self->svflags |= SVF_NOCLIENT; +} + +/*QUAKED misc_nuke_core (1 0 0) (-16 -16 -16) (16 16 16) +toggles visible/not visible. starts visible. +*/ +void SP_misc_nuke_core(edict_t *ent) +{ + gi.setmodel(ent, "models/objects/core/tris.md2"); + gi.linkentity(ent); + + ent->use = misc_nuke_core_use; +} +// ROGUE +//====================== diff --git a/rerelease/rogue/g_rogue_monster.cpp b/rerelease/rogue/g_rogue_monster.cpp new file mode 100644 index 0000000..d333a06 --- /dev/null +++ b/rerelease/rogue/g_rogue_monster.cpp @@ -0,0 +1,109 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. + +#include "../g_local.h" + +// ROGUE +void monster_fire_blaster2(edict_t *self, const vec3_t &start, const vec3_t &dir, int damage, int speed, monster_muzzleflash_id_t flashtype, effects_t effect) +{ + fire_blaster2(self, start, dir, damage, speed, effect, false); + monster_muzzleflash(self, start, flashtype); +} + +void monster_fire_tracker(edict_t *self, const vec3_t &start, const vec3_t &dir, int damage, int speed, edict_t *enemy, monster_muzzleflash_id_t flashtype) +{ + fire_tracker(self, start, dir, damage, speed, enemy); + monster_muzzleflash(self, start, flashtype); +} + +void monster_fire_heatbeam(edict_t *self, const vec3_t &start, const vec3_t &dir, const vec3_t &offset, int damage, int kick, monster_muzzleflash_id_t flashtype) +{ + fire_heatbeam(self, start, dir, offset, damage, kick, true); + monster_muzzleflash(self, start, flashtype); +} +// ROGUE + +// ROGUE + +void stationarymonster_start_go(edict_t *self); + +THINK(stationarymonster_triggered_spawn) (edict_t *self) -> void +{ + self->solid = SOLID_BBOX; + self->movetype = MOVETYPE_NONE; + self->svflags &= ~SVF_NOCLIENT; + self->air_finished = level.time + 12_sec; + gi.linkentity(self); + + KillBox(self, false); + + // FIXME - why doesn't this happen with real monsters? + self->spawnflags &= ~SPAWNFLAG_MONSTER_TRIGGER_SPAWN; + + stationarymonster_start_go(self); + + if (self->enemy && !(self->spawnflags & SPAWNFLAG_MONSTER_AMBUSH) && !(self->enemy->flags & FL_NOTARGET)) + { + if (!(self->enemy->flags & FL_DISGUISED)) // PGM + FoundTarget(self); + else // PMM - just in case, make sure to clear the enemy so FindTarget doesn't get confused + self->enemy = nullptr; + } + else + { + self->enemy = nullptr; + } +} + +USE(stationarymonster_triggered_spawn_use) (edict_t *self, edict_t *other, edict_t *activator) -> void +{ + // we have a one frame delay here so we don't telefrag the guy who activated us + self->think = stationarymonster_triggered_spawn; + self->nextthink = level.time + FRAME_TIME_S; + if (activator->client) + self->enemy = activator; + self->use = monster_use; +} + +void stationarymonster_triggered_start(edict_t *self) +{ + self->solid = SOLID_NOT; + self->movetype = MOVETYPE_NONE; + self->svflags |= SVF_NOCLIENT; + self->nextthink = 0_ms; + self->use = stationarymonster_triggered_spawn_use; +} + +THINK(stationarymonster_start_go) (edict_t *self) -> void +{ + if (!self->yaw_speed) + self->yaw_speed = 20; + + monster_start_go(self); + + if (self->spawnflags.has(SPAWNFLAG_MONSTER_TRIGGER_SPAWN)) + stationarymonster_triggered_start(self); +} + +void stationarymonster_start(edict_t *self) +{ + self->flags |= FL_STATIONARY; + self->think = stationarymonster_start_go; + monster_start(self); + + // fix viewheight + self->viewheight = 0; +} + +void monster_done_dodge(edict_t *self) +{ + self->monsterinfo.aiflags &= ~AI_DODGING; + if (self->monsterinfo.attack_state == AS_SLIDING) + self->monsterinfo.attack_state = AS_STRAIGHT; +} + +int32_t M_SlotsLeft(edict_t *self) +{ + return self->monsterinfo.monster_slots - self->monsterinfo.monster_used; +} +// ROGUE \ No newline at end of file diff --git a/rerelease/rogue/g_rogue_newai.cpp b/rerelease/rogue/g_rogue_newai.cpp new file mode 100644 index 0000000..6b00956 --- /dev/null +++ b/rerelease/rogue/g_rogue_newai.cpp @@ -0,0 +1,1613 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. + +#include "../g_local.h" + +//=============================== +// BLOCKED Logic +//=============================== + +bool face_wall(edict_t *self); + +// blocked_checkplat +// dist: how far they are trying to walk. +bool blocked_checkplat(edict_t *self, float dist) +{ + int playerPosition; + trace_t trace; + vec3_t pt1, pt2; + vec3_t forward; + edict_t *plat; + + if (!self->enemy) + return false; + + // check player's relative altitude + if (self->enemy->absmin[2] >= self->absmax[2]) + playerPosition = 1; + else if (self->enemy->absmax[2] <= self->absmin[2]) + playerPosition = -1; + else + playerPosition = 0; + + // if we're close to the same position, don't bother trying plats. + if (playerPosition == 0) + return false; + + plat = nullptr; + + // see if we're already standing on a plat. + if (self->groundentity && self->groundentity != world) + { + if (!strncmp(self->groundentity->classname, "func_plat", 8)) + plat = self->groundentity; + } + + // if we're not, check to see if we'll step onto one with this move + if (!plat) + { + AngleVectors(self->s.angles, forward, nullptr, nullptr); + pt1 = self->s.origin + (forward * dist); + pt2 = pt1; + pt2[2] -= 384; + + trace = gi.traceline(pt1, pt2, self, MASK_MONSTERSOLID); + if (trace.fraction < 1 && !trace.allsolid && !trace.startsolid) + { + if (!strncmp(trace.ent->classname, "func_plat", 8)) + { + plat = trace.ent; + } + } + } + + // if we've found a plat, trigger it. + if (plat && plat->use) + { + if (playerPosition == 1) + { + if ((self->groundentity == plat && plat->moveinfo.state == STATE_BOTTOM) || + (self->groundentity != plat && plat->moveinfo.state == STATE_TOP)) + { + plat->use(plat, self, self); + return true; + } + } + else if (playerPosition == -1) + { + if ((self->groundentity == plat && plat->moveinfo.state == STATE_TOP) || + (self->groundentity != plat && plat->moveinfo.state == STATE_BOTTOM)) + { + plat->use(plat, self, self); + return true; + } + } + } + + return false; +} + +//******************* +// JUMPING AIDS +//******************* + +inline void monster_jump_start(edict_t *self) +{ + monster_done_dodge(self); + + self->monsterinfo.jump_time = level.time + 3_sec; +} + +bool monster_jump_finished(edict_t *self) +{ + // if we lost our forward velocity, give us more + vec3_t forward; + + AngleVectors(self->s.angles, forward, nullptr, nullptr); + + vec3_t forward_velocity = self->velocity.scaled(forward); + + if (forward_velocity.length() < 150.f) + { + float z_velocity = self->velocity.z; + self->velocity = forward * 150.f; + self->velocity.z = z_velocity; + } + + return self->monsterinfo.jump_time < level.time; +} + +// blocked_checkjump +// dist: how far they are trying to walk. +// self->monsterinfo.drop_height/self->monsterinfo.jump_height: how far they'll ok a jump for. set to 0 to disable that direction. +blocked_jump_result_t blocked_checkjump(edict_t *self, float dist) +{ + // can't jump even if we physically can + if (!self->monsterinfo.can_jump) + return blocked_jump_result_t::NO_JUMP; + // no enemy to path to + else if (!self->enemy) + return blocked_jump_result_t::NO_JUMP; + + // we just jumped recently, don't try again + if (self->monsterinfo.jump_time > level.time) + return blocked_jump_result_t::NO_JUMP; + + // if we're pathing, the nodes will ensure we can reach the destination. + if (self->monsterinfo.aiflags & AI_PATHING) + { + if (self->monsterinfo.nav_path.returnCode != PathReturnCode::TraversalPending) + return blocked_jump_result_t::NO_JUMP; + + float yaw = vectoyaw((self->monsterinfo.nav_path.firstMovePoint - self->monsterinfo.nav_path.secondMovePoint).normalized()); + self->ideal_yaw = yaw + 180; + if (self->ideal_yaw > 360) + self->ideal_yaw -= 360; + + if (!FacingIdeal(self)) + { + M_ChangeYaw(self); + return blocked_jump_result_t::JUMP_TURN; + } + + monster_jump_start(self); + + if (self->monsterinfo.nav_path.secondMovePoint.z > self->monsterinfo.nav_path.firstMovePoint.z) + return blocked_jump_result_t::JUMP_JUMP_UP; + else + return blocked_jump_result_t::JUMP_JUMP_DOWN; + } + + int playerPosition; + trace_t trace; + vec3_t pt1, pt2; + vec3_t forward, up; + + AngleVectors(self->s.angles, forward, nullptr, up); + + if (self->monsterinfo.aiflags & AI_PATHING) + { + if (self->monsterinfo.nav_path.secondMovePoint[2] > (self->absmin[2] + STEPSIZE)) + playerPosition = 1; + else if (self->monsterinfo.nav_path.secondMovePoint[2] < (self->absmin[2] - STEPSIZE)) + playerPosition = -1; + else + playerPosition = 0; + } + else + { + if (self->enemy->absmin[2] > (self->absmin[2] + STEPSIZE)) + playerPosition = 1; + else if (self->enemy->absmin[2] < (self->absmin[2] - STEPSIZE)) + playerPosition = -1; + else + playerPosition = 0; + } + + if (playerPosition == -1 && self->monsterinfo.drop_height) + { + // check to make sure we can even get to the spot we're going to "fall" from + pt1 = self->s.origin + (forward * 48); + trace = gi.trace(self->s.origin, self->mins, self->maxs, pt1, self, MASK_MONSTERSOLID); + if (trace.fraction < 1) + return blocked_jump_result_t::NO_JUMP; + + pt2 = pt1; + pt2[2] = self->absmin[2] - self->monsterinfo.drop_height - 1; + + trace = gi.traceline(pt1, pt2, self, MASK_MONSTERSOLID | MASK_WATER); + if (trace.fraction < 1 && !trace.allsolid && !trace.startsolid) + { + // check how deep the water is + if (trace.contents & CONTENTS_WATER) + { + trace_t deep = gi.traceline(trace.endpos, pt2, self, MASK_MONSTERSOLID); + + water_level_t waterlevel; + contents_t watertype; + M_CatagorizePosition(self, deep.endpos, waterlevel, watertype); + + if (waterlevel > WATER_WAIST) + return blocked_jump_result_t::NO_JUMP; + } + + if ((self->absmin[2] - trace.endpos[2]) >= 24 && (trace.contents & (MASK_SOLID | CONTENTS_WATER))) + { + if (self->monsterinfo.aiflags & AI_PATHING) + { + if ((self->monsterinfo.nav_path.secondMovePoint[2] - trace.endpos[2]) > 32) + return blocked_jump_result_t::NO_JUMP; + } + else + { + if ((self->enemy->absmin[2] - trace.endpos[2]) > 32) + return blocked_jump_result_t::NO_JUMP; + + if (trace.plane.normal[2] < 0.9f) + return blocked_jump_result_t::NO_JUMP; + } + + monster_jump_start(self); + + return blocked_jump_result_t::JUMP_JUMP_DOWN; + } + } + } + else if (playerPosition == 1 && self->monsterinfo.jump_height) + { + pt1 = self->s.origin + (forward * 48); + pt2 = pt1; + pt1[2] = self->absmax[2] + self->monsterinfo.jump_height; + + trace = gi.traceline(pt1, pt2, self, MASK_MONSTERSOLID | MASK_WATER); + if (trace.fraction < 1 && !trace.allsolid && !trace.startsolid) + { + if ((trace.endpos[2] - self->absmin[2]) <= self->monsterinfo.jump_height && (trace.contents & (MASK_SOLID | CONTENTS_WATER))) + { + face_wall(self); + + monster_jump_start(self); + + return blocked_jump_result_t::JUMP_JUMP_UP; + } + } + } + + return blocked_jump_result_t::NO_JUMP; +} + +// ************************* +// HINT PATHS +// ************************* + +constexpr spawnflags_t SPAWNFLAG_HINT_ENDPOINT = 0x0001_spawnflag; +constexpr size_t MAX_HINT_CHAINS = 100; + +int hint_paths_present; +edict_t *hint_path_start[MAX_HINT_CHAINS]; +int num_hint_paths; + +// +// AI code +// + +// ============= +// hintpath_findstart - given any hintpath node, finds the start node +// ============= +edict_t *hintpath_findstart(edict_t *ent) +{ + edict_t *e; + edict_t *last; + + if (ent->target) // starting point + { + last = world; + e = G_FindByString<&edict_t::targetname>(nullptr, ent->target); + while (e) + { + last = e; + if (!e->target) + break; + e = G_FindByString<&edict_t::targetname>(nullptr, e->target); + } + } + else // end point + { + last = world; + e = G_FindByString<&edict_t::target>(nullptr, ent->targetname); + while (e) + { + last = e; + if (!e->targetname) + break; + e = G_FindByString<&edict_t::target>(nullptr, e->targetname); + } + } + + if (!last->spawnflags.has(SPAWNFLAG_HINT_ENDPOINT)) + return nullptr; + + if (last == world) + last = nullptr; + return last; +} + +// ============= +// hintpath_other_end - given one endpoint of a hintpath, returns the other end. +// ============= +edict_t *hintpath_other_end(edict_t *ent) +{ + edict_t *e; + edict_t *last; + + if (ent->target) // starting point + { + last = world; + e = G_FindByString<&edict_t::targetname>(nullptr, ent->target); + while (e) + { + last = e; + if (!e->target) + break; + e = G_FindByString<&edict_t::targetname>(nullptr, e->target); + } + } + else // end point + { + last = world; + e = G_FindByString<&edict_t::target>(nullptr, ent->targetname); + while (e) + { + last = e; + if (!e->targetname) + break; + e = G_FindByString<&edict_t::target>(nullptr, e->targetname); + } + } + + if (!(last->spawnflags & SPAWNFLAG_HINT_ENDPOINT)) + return nullptr; + + if (last == world) + last = nullptr; + return last; +} + +// ============= +// hintpath_go - starts a monster (self) moving towards the hintpath (point) +// disables all contrary AI flags. +// ============= +void hintpath_go(edict_t *self, edict_t *point) +{ + vec3_t dir; + + dir = point->s.origin - self->s.origin; + + self->ideal_yaw = vectoyaw(dir); + self->goalentity = self->movetarget = point; + self->monsterinfo.pausetime = 0_ms; + self->monsterinfo.aiflags |= AI_HINT_PATH; + self->monsterinfo.aiflags &= ~(AI_SOUND_TARGET | AI_PURSUIT_LAST_SEEN | AI_PURSUE_NEXT | AI_PURSUE_TEMP); + // run for it + self->monsterinfo.search_time = level.time; + self->monsterinfo.run(self); +} + +// ============= +// hintpath_stop - bails a monster out of following hint paths +// ============= +void hintpath_stop(edict_t *self) +{ + self->goalentity = nullptr; + self->movetarget = nullptr; + self->monsterinfo.last_hint_time = level.time; + self->monsterinfo.goal_hint = nullptr; + self->monsterinfo.aiflags &= ~AI_HINT_PATH; + if (has_valid_enemy(self)) + { + // if we can see our target, go nuts + if (visible(self, self->enemy)) + { + FoundTarget(self); + return; + } + // otherwise, keep chasing + HuntTarget(self); + return; + } + // if our enemy is no longer valid, forget about our enemy and go into stand + self->enemy = nullptr; + // we need the pausetime otherwise the stand code + // will just revert to walking with no target and + // the monsters will wonder around aimlessly trying + // to hunt the world entity + self->monsterinfo.pausetime = HOLD_FOREVER; + self->monsterinfo.stand(self); +} + +// ============= +// monsterlost_checkhint - the monster (self) will check around for valid hintpaths. +// a valid hintpath is one where the two endpoints can see both the monster +// and the monster's enemy. if only one person is visible from the endpoints, +// it will not go for it. +// ============= +bool monsterlost_checkhint(edict_t *self) +{ + edict_t *e, *monster_pathchain, *target_pathchain, *checkpoint = nullptr; + edict_t *closest; + float closest_range = 1000000; + edict_t *start, *destination; + int count5 = 0; + float r; + int i; + bool hint_path_represented[MAX_HINT_CHAINS]; + + // if there are no hint paths on this map, exit immediately. + if (!hint_paths_present) + return false; + + if (!self->enemy) + return false; + + // [Paril-KEX] don't do hint paths if we're using nav nodes + if (self->monsterinfo.aiflags & (AI_STAND_GROUND | AI_PATHING)) + return false; + + if (!strcmp(self->classname, "monster_turret")) + return false; + + monster_pathchain = nullptr; + + // find all the hint_paths. + // FIXME - can we not do this every time? + for (i = 0; i < num_hint_paths; i++) + { + e = hint_path_start[i]; + while (e) + { + if (e->monster_hint_chain) + e->monster_hint_chain = nullptr; + + if (monster_pathchain) + { + checkpoint->monster_hint_chain = e; + checkpoint = e; + } + else + { + monster_pathchain = e; + checkpoint = e; + } + e = e->hint_chain; + } + } + + // filter them by distance and visibility to the monster + e = monster_pathchain; + checkpoint = nullptr; + while (e) + { + r = realrange(self, e); + + if (r > 512) + { + if (checkpoint) + { + checkpoint->monster_hint_chain = e->monster_hint_chain; + e->monster_hint_chain = nullptr; + e = checkpoint->monster_hint_chain; + continue; + } + else + { + // use checkpoint as temp pointer + checkpoint = e; + e = e->monster_hint_chain; + checkpoint->monster_hint_chain = nullptr; + // and clear it again + checkpoint = nullptr; + // since we have yet to find a valid one (or else checkpoint would be set) move the + // start of monster_pathchain + monster_pathchain = e; + continue; + } + } + if (!visible(self, e)) + { + if (checkpoint) + { + checkpoint->monster_hint_chain = e->monster_hint_chain; + e->monster_hint_chain = nullptr; + e = checkpoint->monster_hint_chain; + continue; + } + else + { + // use checkpoint as temp pointer + checkpoint = e; + e = e->monster_hint_chain; + checkpoint->monster_hint_chain = nullptr; + // and clear it again + checkpoint = nullptr; + // since we have yet to find a valid one (or else checkpoint would be set) move the + // start of monster_pathchain + monster_pathchain = e; + continue; + } + } + + count5++; + checkpoint = e; + e = e->monster_hint_chain; + } + + // at this point, we have a list of all of the eligible hint nodes for the monster + // we now take them, figure out what hint chains they're on, and traverse down those chains, + // seeing whether any can see the player + // + // first, we figure out which hint chains we have represented in monster_pathchain + if (count5 == 0) + return false; + + for (i = 0; i < num_hint_paths; i++) + hint_path_represented[i] = false; + + e = monster_pathchain; + checkpoint = nullptr; + while (e) + { + if ((e->hint_chain_id < 0) || (e->hint_chain_id > num_hint_paths)) + return false; + + hint_path_represented[e->hint_chain_id] = true; + e = e->monster_hint_chain; + } + + count5 = 0; + + // now, build the target_pathchain which contains all of the hint_path nodes we need to check for + // validity (within range, visibility) + target_pathchain = nullptr; + checkpoint = nullptr; + for (i = 0; i < num_hint_paths; i++) + { + // if this hint chain is represented in the monster_hint_chain, add all of it's nodes to the target_pathchain + // for validity checking + if (hint_path_represented[i]) + { + e = hint_path_start[i]; + while (e) + { + if (target_pathchain) + { + checkpoint->target_hint_chain = e; + checkpoint = e; + } + else + { + target_pathchain = e; + checkpoint = e; + } + e = e->hint_chain; + } + } + } + + // target_pathchain is a list of all of the hint_path nodes we need to check for validity relative to the target + e = target_pathchain; + checkpoint = nullptr; + while (e) + { + r = realrange(self->enemy, e); + + if (r > 512) + { + if (checkpoint) + { + checkpoint->target_hint_chain = e->target_hint_chain; + e->target_hint_chain = nullptr; + e = checkpoint->target_hint_chain; + continue; + } + else + { + // use checkpoint as temp pointer + checkpoint = e; + e = e->target_hint_chain; + checkpoint->target_hint_chain = nullptr; + // and clear it again + checkpoint = nullptr; + target_pathchain = e; + continue; + } + } + if (!visible(self->enemy, e)) + { + if (checkpoint) + { + checkpoint->target_hint_chain = e->target_hint_chain; + e->target_hint_chain = nullptr; + e = checkpoint->target_hint_chain; + continue; + } + else + { + // use checkpoint as temp pointer + checkpoint = e; + e = e->target_hint_chain; + checkpoint->target_hint_chain = nullptr; + // and clear it again + checkpoint = nullptr; + target_pathchain = e; + continue; + } + } + + count5++; + checkpoint = e; + e = e->target_hint_chain; + } + + // at this point we should have: + // monster_pathchain - a list of "monster valid" hint_path nodes linked together by monster_hint_chain + // target_pathcain - a list of "target valid" hint_path nodes linked together by target_hint_chain. these + // are filtered such that only nodes which are on the same chain as "monster valid" nodes + // + // Now, we figure out which "monster valid" node we want to use + // + // To do this, we first off make sure we have some target nodes. If we don't, there are no valid hint_path nodes + // for us to take + // + // If we have some, we filter all of our "monster valid" nodes by which ones have "target valid" nodes on them + // + // Once this filter is finished, we select the closest "monster valid" node, and go to it. + + if (count5 == 0) + return false; + + // reuse the hint_chain_represented array, this time to see which chains are represented by the target + for (i = 0; i < num_hint_paths; i++) + hint_path_represented[i] = false; + + e = target_pathchain; + checkpoint = nullptr; + while (e) + { + if ((e->hint_chain_id < 0) || (e->hint_chain_id > num_hint_paths)) + return false; + + hint_path_represented[e->hint_chain_id] = true; + e = e->target_hint_chain; + } + + // traverse the monster_pathchain - if the hint_node isn't represented in the "target valid" chain list, + // remove it + // if it is on the list, check it for range from the monster. If the range is the closest, keep it + // + closest = nullptr; + e = monster_pathchain; + while (e) + { + if (!(hint_path_represented[e->hint_chain_id])) + { + checkpoint = e->monster_hint_chain; + e->monster_hint_chain = nullptr; + e = checkpoint; + continue; + } + r = realrange(self, e); + if (r < closest_range) + closest = e; + e = e->monster_hint_chain; + } + + if (!closest) + return false; + + start = closest; + // now we know which one is the closest to the monster .. this is the one the monster will go to + // we need to finally determine what the DESTINATION node is for the monster .. walk down the hint_chain, + // and find the closest one to the player + + closest = nullptr; + closest_range = 10000000; + e = target_pathchain; + while (e) + { + if (start->hint_chain_id == e->hint_chain_id) + { + r = realrange(self, e); + if (r < closest_range) + closest = e; + } + e = e->target_hint_chain; + } + + if (!closest) + return false; + + destination = closest; + + self->monsterinfo.goal_hint = destination; + hintpath_go(self, start); + + return true; +} + +// +// Path code +// + +// ============= +// hint_path_touch - someone's touched the hint_path +// ============= +TOUCH(hint_path_touch) (edict_t *self, edict_t *other, const trace_t &tr, bool other_touching_self) -> void +{ + edict_t *e, *goal, *next = nullptr; + // int chain; // direction - (-1) = upstream, (1) = downstream, (0) = done + bool goalFound = false; + + // make sure we're the target of it's obsession + if (other->movetarget == self) + { + goal = other->monsterinfo.goal_hint; + + // if the monster is where he wants to be + if (goal == self) + { + hintpath_stop(other); + return; + } + else + { + // if we aren't, figure out which way we want to go + e = hint_path_start[self->hint_chain_id]; + while (e) + { + // if we get up to ourselves on the hint chain, we're going down it + if (e == self) + { + next = e->hint_chain; + break; + } + if (e == goal) + goalFound = true; + // if we get to where the next link on the chain is this hint_path and have found the goal on the way + // we're going upstream, so remember who the previous link is + if ((e->hint_chain == self) && goalFound) + { + next = e; + break; + } + e = e->hint_chain; + } + } + + // if we couldn't find it, have the monster go back to normal hunting. + if (!next) + { + hintpath_stop(other); + return; + } + + // send him on his way + hintpath_go(other, next); + + // have the monster freeze if the hint path we just touched has a wait time + // on it, for example, when riding a plat. + if (self->wait) + other->nextthink = level.time + gtime_t::from_sec(self->wait); + } +} + +/*QUAKED hint_path (.5 .3 0) (-8 -8 -8) (8 8 8) END +Target: next hint path + +END - set this flag on the endpoints of each hintpath. + +"wait" - set this if you want the monster to freeze when they touch this hintpath +*/ +void SP_hint_path(edict_t *self) +{ + if (deathmatch->integer) + { + G_FreeEdict(self); + return; + } + + if (!self->targetname && !self->target) + { + gi.Com_PrintFmt("{}: unlinked\n", *self); + G_FreeEdict(self); + return; + } + + self->solid = SOLID_TRIGGER; + self->touch = hint_path_touch; + self->mins = { -8, -8, -8 }; + self->maxs = { 8, 8, 8 }; + self->svflags |= SVF_NOCLIENT; + gi.linkentity(self); +} + +// ============ +// InitHintPaths - Called by InitGame (g_save) to enable quick exits if valid +// ============ +void InitHintPaths() +{ + edict_t *e, *current; + int i; + + hint_paths_present = 0; + + // check all the hint_paths. + e = G_FindByString<&edict_t::classname>(nullptr, "hint_path"); + if (e) + hint_paths_present = 1; + else + return; + + memset(hint_path_start, 0, MAX_HINT_CHAINS * sizeof(edict_t *)); + num_hint_paths = 0; + while (e) + { + if (e->spawnflags.has(SPAWNFLAG_HINT_ENDPOINT)) + { + if (e->target) // start point + { + if (e->targetname) // this is a bad end, ignore it + { + gi.Com_PrintFmt("{}: marked as endpoint with both target ({}) and targetname ({})\n", + *e, e->target, e->targetname); + } + else + { + if (num_hint_paths >= MAX_HINT_CHAINS) + break; + + hint_path_start[num_hint_paths++] = e; + } + } + } + e = G_FindByString<&edict_t::classname>(e, "hint_path"); + } + + for (i = 0; i < num_hint_paths; i++) + { + current = hint_path_start[i]; + current->hint_chain_id = i; + e = G_FindByString<&edict_t::targetname>(nullptr, current->target); + if (G_FindByString<&edict_t::targetname>(e, current->target)) + { + gi.Com_PrintFmt("{}: Forked path detected for chain {}, target {}\n", + *current, num_hint_paths, current->target); + hint_path_start[i]->hint_chain = nullptr; + continue; + } + while (e) + { + if (e->hint_chain) + { + gi.Com_PrintFmt("{}: Circular path detected for chain {}, targetname {}\n", + *e, num_hint_paths, e->targetname); + hint_path_start[i]->hint_chain = nullptr; + break; + } + current->hint_chain = e; + current = e; + current->hint_chain_id = i; + if (!current->target) + break; + e = G_FindByString<&edict_t::targetname>(nullptr, current->target); + if (G_FindByString<&edict_t::targetname>(e, current->target)) + { + gi.Com_PrintFmt("{}: Forked path detected for chain {}, target {}\n", + *current, num_hint_paths, current->target); + hint_path_start[i]->hint_chain = nullptr; + break; + } + } + } +} + +// ***************************** +// MISCELLANEOUS STUFF +// ***************************** + +// PMM - inback +// use to see if opponent is behind you (not to side) +// if it looks a lot like infront, well, there's a reason + +bool inback(edict_t *self, edict_t *other) +{ + vec3_t vec; + float dot; + vec3_t forward; + + AngleVectors(self->s.angles, forward, nullptr, nullptr); + vec = other->s.origin - self->s.origin; + vec.normalize(); + dot = vec.dot(forward); + return dot < -0.3f; +} + +float realrange(edict_t *self, edict_t *other) +{ + vec3_t dir; + + dir = self->s.origin - other->s.origin; + + return dir.length(); +} + +bool face_wall(edict_t *self) +{ + vec3_t pt; + vec3_t forward; + vec3_t ang; + trace_t tr; + + AngleVectors(self->s.angles, forward, nullptr, nullptr); + pt = self->s.origin + (forward * 64); + tr = gi.traceline(self->s.origin, pt, self, MASK_MONSTERSOLID); + if (tr.fraction < 1 && !tr.allsolid && !tr.startsolid) + { + ang = vectoangles(tr.plane.normal); + self->ideal_yaw = ang[YAW] + 180; + if (self->ideal_yaw > 360) + self->ideal_yaw -= 360; + + M_ChangeYaw(self); + return true; + } + + return false; +} + +// +// Monster "Bad" Areas +// + +TOUCH(badarea_touch) (edict_t *ent, edict_t *other, const trace_t &tr, bool other_touching_self) -> void +{ +} + +edict_t *SpawnBadArea(const vec3_t &mins, const vec3_t &maxs, gtime_t lifespan, edict_t *owner) +{ + edict_t *badarea; + vec3_t origin; + + origin = mins + maxs; + origin *= 0.5f; + + badarea = G_Spawn(); + badarea->s.origin = origin; + + badarea->maxs = maxs - origin; + badarea->mins = mins - origin; + badarea->touch = badarea_touch; + badarea->movetype = MOVETYPE_NONE; + badarea->solid = SOLID_TRIGGER; + badarea->classname = "bad_area"; + gi.linkentity(badarea); + + if (lifespan) + { + badarea->think = G_FreeEdict; + badarea->nextthink = level.time + lifespan; + } + if (owner) + { + badarea->owner = owner; + } + + return badarea; +} + +static BoxEdictsResult_t CheckForBadArea_BoxFilter(edict_t *hit, void *data) +{ + edict_t *&result = (edict_t *&) data; + + if (hit->touch == badarea_touch) + { + result = hit; + return BoxEdictsResult_t::End; + } + + return BoxEdictsResult_t::Skip; +} + +// CheckForBadArea +// This is a customized version of G_TouchTriggers that will check +// for bad area triggers and return them if they're touched. +edict_t *CheckForBadArea(edict_t *ent) +{ + vec3_t mins, maxs; + + mins = ent->s.origin + ent->mins; + maxs = ent->s.origin + ent->maxs; + + edict_t *hit = nullptr; + + gi.BoxEdicts(mins, maxs, nullptr, 0, AREA_TRIGGERS, CheckForBadArea_BoxFilter, &hit); + + return hit; +} + +constexpr float TESLA_DAMAGE_RADIUS = 128; + +bool MarkTeslaArea(edict_t *self, edict_t *tesla) +{ + vec3_t mins, maxs; + edict_t *e; + edict_t *tail; + edict_t *area; + + if (!tesla || !self) + return false; + + area = nullptr; + + // make sure this tesla doesn't have a bad area around it already... + e = tesla->teamchain; + tail = tesla; + while (e) + { + tail = tail->teamchain; + if (!strcmp(e->classname, "bad_area")) + return false; + + e = e->teamchain; + } + + // see if we can grab the trigger directly + if (tesla->teamchain && tesla->teamchain->inuse) + { + edict_t *trigger; + + trigger = tesla->teamchain; + + mins = trigger->absmin; + maxs = trigger->absmax; + + if (tesla->air_finished) + area = SpawnBadArea(mins, maxs, tesla->air_finished, tesla); + else + area = SpawnBadArea(mins, maxs, tesla->nextthink, tesla); + } + // otherwise we just guess at how long it'll last. + else + { + + mins = { -TESLA_DAMAGE_RADIUS, -TESLA_DAMAGE_RADIUS, tesla->mins[2] }; + maxs = { TESLA_DAMAGE_RADIUS, TESLA_DAMAGE_RADIUS, TESLA_DAMAGE_RADIUS }; + + area = SpawnBadArea(mins, maxs, 30_sec, tesla); + } + + // if we spawned a bad area, then link it to the tesla + if (area) + tail->teamchain = area; + + return true; +} + +// predictive calculator +// target is who you want to shoot +// start is where the shot comes from +// bolt_speed is how fast the shot is (or 0 for hitscan) +// eye_height is a boolean to say whether or not to adjust to targets eye_height +// offset is how much time to miss by +// aimdir is the resulting aim direction (pass in nullptr if you don't want it) +// aimpoint is the resulting aimpoint (pass in nullptr if don't want it) +void PredictAim(edict_t *self, edict_t *target, const vec3_t &start, float bolt_speed, bool eye_height, float offset, vec3_t *aimdir, vec3_t *aimpoint) +{ + vec3_t dir, vec; + float dist, time; + + if (!target || !target->inuse) + { + *aimdir = {}; + return; + } + + dir = target->s.origin - start; + if (eye_height) + dir[2] += target->viewheight; + dist = dir.length(); + + // [Paril-KEX] if our current attempt is blocked, try the opposite one + trace_t tr = gi.traceline(start, start + dir, self, MASK_PROJECTILE); + + if (tr.ent != target) + { + eye_height = !eye_height; + dir = target->s.origin - start; + if (eye_height) + dir[2] += target->viewheight; + dist = dir.length(); + } + + if (bolt_speed) + time = dist / bolt_speed; + else + time = 0; + + vec = target->s.origin + (target->velocity * (time - offset)); + + // went backwards... + if (dir.normalized().dot((vec - start).normalized()) < 0) + vec = target->s.origin; + else + { + // if the shot is going to impact a nearby wall from our prediction, just fire it straight. + if (gi.traceline(start, vec, nullptr, MASK_SOLID).fraction < 0.9f) + vec = target->s.origin; + } + + if (eye_height) + vec[2] += target->viewheight; + + if (aimdir) + *aimdir = (vec - start).normalized(); + + if (aimpoint) + *aimpoint = vec; +} + +// [Paril-KEX] find a pitch that will at some point land on or near the player. +// very approximate. aim will be adjusted to the correct aim vector. +bool M_CalculatePitchToFire(edict_t *self, const vec3_t &target, const vec3_t &start, vec3_t &aim, float speed, float time_remaining, bool mortar, bool destroy_on_touch) +{ + constexpr float pitches[] = { -80.f, -70.f, -60.f, -50.f, -40.f, -30.f, -20.f, -10.f, -5.f }; + float best_pitch = 0.f; + float best_dist = std::numeric_limits::infinity(); + + constexpr float sim_time = 0.1f; + vec3_t pitched_aim = vectoangles(aim); + + for (auto &pitch : pitches) + { + if (mortar && pitch >= -30.f) + break; + + pitched_aim[PITCH] = pitch; + vec3_t fwd = AngleVectors(pitched_aim).forward; + + vec3_t velocity = fwd * speed; + vec3_t origin = start; + + float t = time_remaining; + + while (t > 0.f) + { + velocity += vec3_t{ 0, 0, -1 } * level.gravity * sim_time; + + vec3_t end = origin + (velocity * sim_time); + trace_t tr = gi.traceline(origin, end, nullptr, MASK_SHOT); + + origin = tr.endpos; + + if (tr.fraction < 1.0f) + { + if (tr.surface->flags & SURF_SKY) + break; + + origin += tr.plane.normal; + velocity = ClipVelocity(velocity, tr.plane.normal, 1.6f); + + float dist = (origin - target).lengthSquared(); + + if (tr.ent == self->enemy || tr.ent->client || (tr.plane.normal.z >= 0.7f && dist < (128.f * 128.f) && dist < best_dist)) + { + best_pitch = pitch; + best_dist = dist; + } + + if (destroy_on_touch || (tr.contents & (CONTENTS_MONSTER | CONTENTS_PLAYER | CONTENTS_DEADMONSTER))) + break; + } + + t -= sim_time; + } + } + + if (!isinf(best_dist)) + { + pitched_aim[PITCH] = best_pitch; + aim = AngleVectors(pitched_aim).forward; + return true; + } + + return false; +} + +bool below(edict_t *self, edict_t *other) +{ + vec3_t vec; + float dot; + vec3_t down; + + vec = other->s.origin - self->s.origin; + vec.normalize(); + down = { 0, 0, -1 }; + dot = vec.dot(down); + + if (dot > 0.95f) // 18 degree arc below + return true; + return false; +} + +void drawbbox(edict_t *self) +{ + int lines[4][3] = { + { 1, 2, 4 }, + { 1, 2, 7 }, + { 1, 4, 5 }, + { 2, 4, 7 } + }; + + int starts[4] = { 0, 3, 5, 6 }; + + vec3_t pt[8]; + int i, j, k; + vec3_t coords[2]; + vec3_t newbox; + vec3_t f, r, u, dir; + + coords[0] = self->absmin; + coords[1] = self->absmax; + + for (i = 0; i <= 1; i++) + { + for (j = 0; j <= 1; j++) + { + for (k = 0; k <= 1; k++) + { + pt[4 * i + 2 * j + k][0] = coords[i][0]; + pt[4 * i + 2 * j + k][1] = coords[j][1]; + pt[4 * i + 2 * j + k][2] = coords[k][2]; + } + } + } + + for (i = 0; i <= 3; i++) + { + for (j = 0; j <= 2; j++) + { + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_DEBUGTRAIL); + gi.WritePosition(pt[starts[i]]); + gi.WritePosition(pt[lines[i][j]]); + gi.multicast(pt[starts[i]], MULTICAST_ALL, false); + } + } + + dir = vectoangles(self->s.angles); + AngleVectors(dir, f, r, u); + + newbox = self->s.origin + (f * 50); + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_DEBUGTRAIL); + gi.WritePosition(self->s.origin); + gi.WritePosition(newbox); + gi.multicast(self->s.origin, MULTICAST_PVS, false); + + newbox = self->s.origin + (r * 50); + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_DEBUGTRAIL); + gi.WritePosition(self->s.origin); + gi.WritePosition(newbox); + gi.multicast(self->s.origin, MULTICAST_PVS, false); + + newbox = self->s.origin + (u * 50); + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_DEBUGTRAIL); + gi.WritePosition(self->s.origin); + gi.WritePosition(newbox); + gi.multicast(self->s.origin, MULTICAST_PVS, false); +} + +// [Paril-KEX] returns true if the skill check passes +inline bool G_SkillCheck(const std::initializer_list &skills) +{ + if (skills.size() < skill->integer) + return true; + + const float &skill_switch = *(skills.begin() + skill->integer); + return skill_switch == 1.0f ? true : frandom() < skill_switch; +} + +// +// New dodge code +// +MONSTERINFO_DODGE(M_MonsterDodge) (edict_t *self, edict_t *attacker, gtime_t eta, trace_t *tr, bool gravity) -> void +{ + float r = frandom(); + float height; + bool ducker = false, dodger = false; + + // this needs to be here since this can be called after the monster has "died" + if (self->health < 1) + return; + + if ((self->monsterinfo.duck) && (self->monsterinfo.unduck) && !gravity) + ducker = true; + if ((self->monsterinfo.sidestep) && !(self->monsterinfo.aiflags & AI_STAND_GROUND)) + dodger = true; + + if ((!ducker) && (!dodger)) + return; + + if (!self->enemy) + { + self->enemy = attacker; + FoundTarget(self); + } + + // PMM - don't bother if it's going to hit anyway; fix for weird in-your-face etas (I was + // seeing numbers like 13 and 14) + if ((eta < FRAME_TIME_MS) || (eta > 2.5_sec)) + return; + + // skill level determination.. + if (r > 0.50f) + return; + + if (ducker && tr) + { + height = self->absmax[2] - 32 - 1; // the -1 is because the absmax is s.origin + maxs + 1 + + if ((!dodger) && ((tr->endpos[2] <= height) || (self->monsterinfo.aiflags & AI_DUCKED))) + return; + } + else + height = self->absmax[2]; + + if (dodger) + { + // if we're already dodging, just finish the sequence, i.e. don't do anything else + if (self->monsterinfo.aiflags & AI_DODGING) + return; + + // if we're ducking already, or the shot is at our knees + if ((!ducker || !tr || tr->endpos[2] <= height) || (self->monsterinfo.aiflags & AI_DUCKED)) + { + // on Easy & Normal, don't sidestep as often (25% on Easy, 50% on Normal) + if (!G_SkillCheck({ 0.25f, 0.50f, 1.0f, 1.0f })) + { + self->monsterinfo.dodge_time = level.time + random_time(0.8_sec, 1.4_sec); + return; + } + else + { + if (tr) + { + vec3_t right, diff; + + AngleVectors(self->s.angles, nullptr, right, nullptr); + diff = tr->endpos - self->s.origin; + + if (right.dot(diff) < 0) + self->monsterinfo.lefty = false; + else + self->monsterinfo.lefty = true; + } + else + self->monsterinfo.lefty = brandom(); + + // call the monster specific code here + if (self->monsterinfo.sidestep(self)) + { + // if we are currently ducked, unduck + if ((ducker) && (self->monsterinfo.aiflags & AI_DUCKED)) + self->monsterinfo.unduck(self); + + self->monsterinfo.aiflags |= AI_DODGING; + self->monsterinfo.attack_state = AS_SLIDING; + + self->monsterinfo.dodge_time = level.time + random_time(0.4_sec, 2.0_sec); + } + return; + } + } + } + + // [Paril-KEX] we don't need to duck until projectiles are going to hit us very + // soon. + if (ducker && tr && eta < 0.5_sec) + { + if (self->monsterinfo.next_duck_time > level.time) + return; + + monster_done_dodge(self); + + if (self->monsterinfo.duck(self, eta)) + { + // if duck didn't set us yet, do it now + if (self->monsterinfo.duck_wait_time < level.time) + self->monsterinfo.duck_wait_time = level.time + eta; + + monster_duck_down(self); + + // on Easy & Normal mode, duck longer + if (skill->integer == 0) + self->monsterinfo.duck_wait_time += random_time(500_ms, 1000_ms); + else if (skill->integer == 1) + self->monsterinfo.duck_wait_time += random_time(100_ms, 350_ms); + } + + self->monsterinfo.dodge_time = level.time + random_time(0.2_sec, 0.7_sec); + } +} + +void monster_duck_down(edict_t *self) +{ + self->monsterinfo.aiflags |= AI_DUCKED; + + self->maxs[2] = self->monsterinfo.base_height - 32; + self->takedamage = true; + self->monsterinfo.next_duck_time = level.time + DUCK_INTERVAL; + gi.linkentity(self); +} + +void monster_duck_hold(edict_t *self) +{ + if (level.time >= self->monsterinfo.duck_wait_time) + self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; + else + self->monsterinfo.aiflags |= AI_HOLD_FRAME; +} + +MONSTERINFO_UNDUCK(monster_duck_up) (edict_t *self) -> void +{ + if (!(self->monsterinfo.aiflags & AI_DUCKED)) + return; + + self->monsterinfo.aiflags &= ~AI_DUCKED; + self->maxs[2] = self->monsterinfo.base_height; + self->takedamage = true; + // we finished a duck-up successfully, so cut the time remaining in half + if (self->monsterinfo.next_duck_time > level.time) + self->monsterinfo.next_duck_time = level.time + ((self->monsterinfo.next_duck_time - level.time) / 2); + gi.linkentity(self); +} + +//========================= +//========================= +bool has_valid_enemy(edict_t *self) +{ + if (!self->enemy) + return false; + + if (!self->enemy->inuse) + return false; + + if (self->enemy->health < 1) + return false; + + return true; +} + +void TargetTesla(edict_t *self, edict_t *tesla) +{ + if ((!self) || (!tesla)) + return; + + // PMM - medic bails on healing things + if (self->monsterinfo.aiflags & AI_MEDIC) + { + if (self->enemy) + cleanupHealTarget(self->enemy); + self->monsterinfo.aiflags &= ~AI_MEDIC; + } + + // store the player enemy in case we lose track of him. + if (self->enemy && self->enemy->client) + self->monsterinfo.last_player_enemy = self->enemy; + + if (self->enemy != tesla) + { + self->oldenemy = self->enemy; + self->enemy = tesla; + if (self->monsterinfo.attack) + { + if (self->health <= 0) + return; + + self->monsterinfo.attack(self); + } + else + FoundTarget(self); + } +} + +// this returns a randomly selected coop player who is visible to self +// returns nullptr if bad + +edict_t *PickCoopTarget(edict_t *self) +{ + edict_t **targets; + int num_targets = 0, targetID; + edict_t *ent; + + // if we're not in coop, this is a noop + if (!coop->integer) + return nullptr; + + targets = (edict_t **) alloca(sizeof(edict_t *) * game.maxclients); + + for (uint32_t player = 1; player <= game.maxclients; player++) + { + ent = &g_edicts[player]; + if (!ent->inuse) + continue; + if (!ent->client) + continue; + if (visible(self, ent)) + targets[num_targets++] = ent; + } + + if (!num_targets) + return nullptr; + + // get a number from 0 to (num_targets-1) + targetID = irandom(num_targets); + + return targets[targetID]; +} + +// only meant to be used in coop +int CountPlayers() +{ + edict_t *ent; + int count = 0; + + // if we're not in coop, this is a noop + if (!coop->integer) + return 1; + + for (uint32_t player = 1; player <= game.maxclients; player++) + { + ent = &g_edicts[player]; + if (!ent->inuse) + continue; + if (!ent->client) + continue; + count++; + } + /* + ent = g_edicts+1; // skip the worldspawn + while (ent) + { + if ((ent->client) && (ent->inuse)) + { + ent++; + count++; + } + else + ent = nullptr; + } + */ + return count; +} + +THINK(BossExplode_think) (edict_t *self) -> void +{ + // owner gone or changed + if (!self->owner->inuse || self->owner->s.modelindex != self->style || self->count != self->owner->spawn_count) + { + G_FreeEdict(self); + return; + } + + vec3_t org = self->owner->s.origin + self->owner->mins; + + org.x += frandom() * self->owner->size.x; + org.y += frandom() * self->owner->size.y; + org.z += frandom() * self->owner->size.z; + + gi.WriteByte(svc_temp_entity); + gi.WriteByte(!(self->viewheight % 3) ? TE_EXPLOSION1 : TE_EXPLOSION1_NL); + gi.WritePosition(org); + gi.multicast(org, MULTICAST_PVS, false); + + self->viewheight++; + + self->nextthink = level.time + random_time(50_ms, 200_ms); +} + +void BossExplode(edict_t *self) +{ + // no blowy on deady + if (self->spawnflags.has(SPAWNFLAG_MONSTER_DEAD)) + return; + + edict_t *exploder = G_Spawn(); + exploder->owner = self; + exploder->count = self->spawn_count; + exploder->style = self->s.modelindex; + exploder->think = BossExplode_think; + exploder->nextthink = level.time + random_time(75_ms, 250_ms); + exploder->viewheight = 0; +} \ No newline at end of file diff --git a/rerelease/rogue/g_rogue_newdm.cpp b/rerelease/rogue/g_rogue_newdm.cpp new file mode 100644 index 0000000..828c0a1 --- /dev/null +++ b/rerelease/rogue/g_rogue_newdm.cpp @@ -0,0 +1,360 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// g_newdm.c +// pmack +// june 1998 + +#include "../g_local.h" +#include "../m_player.h" + +dm_game_rt DMGame; + +//================= +//================= +constexpr item_flags_t IF_TYPE_MASK = (IF_WEAPON | IF_AMMO | IF_POWERUP | IF_ARMOR | IF_KEY); + +void ED_CallSpawn(edict_t *ent); +bool Pickup_Health(edict_t *ent, edict_t *other); +bool Pickup_Armor(edict_t *ent, edict_t *other); +bool Pickup_PowerArmor(edict_t *ent, edict_t *other); + +inline item_flags_t GetSubstituteItemFlags(item_id_t id) +{ + const gitem_t *item = GetItemByIndex(id); + + // we want to stay within the item class + item_flags_t flags = item->flags & IF_TYPE_MASK; + + if ((flags & (IF_WEAPON | IF_AMMO)) == (IF_WEAPON | IF_AMMO)) + flags = IF_AMMO; + // Adrenaline and Mega Health count as powerup + else if (id == IT_ITEM_ADRENALINE || id == IT_HEALTH_MEGA) + flags = IF_POWERUP; + + return flags; +} + +inline item_id_t FindSubstituteItem(edict_t *ent) +{ + // never replace flags + if (ent->item->id == IT_FLAG1 || ent->item->id == IT_FLAG2 || ent->item->id == IT_ITEM_TAG_TOKEN) + return IT_NULL; + + // stimpack/shard randomizes + if (ent->item->id == IT_HEALTH_SMALL || + ent->item->id == IT_ARMOR_SHARD) + return brandom() ? IT_HEALTH_SMALL : IT_ARMOR_SHARD; + + // health is special case + if (ent->item->id == IT_HEALTH_MEDIUM || + ent->item->id == IT_HEALTH_LARGE) + { + float rnd = frandom(); + + if (rnd < 0.6f) + return IT_HEALTH_MEDIUM; + else + return IT_HEALTH_LARGE; + } + // armor is also special case + else if (ent->item->id == IT_ARMOR_JACKET || + ent->item->id == IT_ARMOR_COMBAT || + ent->item->id == IT_ARMOR_BODY || + ent->item->id == IT_ITEM_POWER_SCREEN || + ent->item->id == IT_ITEM_POWER_SHIELD) + { + float rnd = frandom(); + + if (rnd < 0.4f) + return IT_ARMOR_JACKET; + else if (rnd < 0.6f) + return IT_ARMOR_COMBAT; + else if (rnd < 0.8f) + return IT_ARMOR_BODY; + else if (rnd < 0.9f) + return IT_ITEM_POWER_SCREEN; + else + return IT_ITEM_POWER_SHIELD; + } + + item_flags_t myflags = GetSubstituteItemFlags(ent->item->id); + + std::array possible_items; + size_t possible_item_count = 0; + + // gather matching items + for (item_id_t i = static_cast(IT_NULL + 1); i < IT_TOTAL; i = static_cast(static_cast(i) + 1)) + { + const gitem_t *it = GetItemByIndex(i); + item_flags_t itflags = it->flags; + + if (!itflags || (itflags & (IF_NOT_GIVEABLE | IF_TECH | IF_NOT_RANDOM)) || !it->pickup || !it->world_model) + continue; + + itflags = GetSubstituteItemFlags(i); + + // don't respawn spheres if they're dmflag disabled. + if (g_no_spheres->integer) + { + if (ent->item->id == IT_ITEM_SPHERE_VENGEANCE || + ent->item->id == IT_ITEM_SPHERE_HUNTER || + ent->item->id == IT_ITEM_SPHERE_DEFENDER) + { + continue; + } + } + + if (g_no_nukes->integer && ent->item->id == IT_AMMO_NUKE) + continue; + + if (g_no_mines->integer && + (ent->item->id == IT_AMMO_PROX || ent->item->id == IT_AMMO_TESLA || ent->item->id == IT_AMMO_TRAP)) + continue; + + if ((itflags & IF_TYPE_MASK) == (myflags & IF_TYPE_MASK)) + possible_items[possible_item_count++] = i; + } + + if (!possible_item_count) + return IT_NULL; + + return possible_items[irandom(possible_item_count)]; +} + +//================= +//================= +item_id_t DoRandomRespawn(edict_t *ent) +{ + if (!ent->item) + return IT_NULL; // why + + item_id_t id = FindSubstituteItem(ent); + + if (id == IT_NULL) + return IT_NULL; + + return id; +} + +//================= +//================= +void PrecacheForRandomRespawn() +{ + gitem_t *it; + int i; + int itflags; + + it = itemlist; + for (i = 0; i < IT_TOTAL; i++, it++) + { + itflags = it->flags; + + if (!itflags || (itflags & (IF_NOT_GIVEABLE | IF_TECH | IF_NOT_RANDOM)) || !it->pickup || !it->world_model) + continue; + + PrecacheItem(it); + } +} + +// *************************** +// DOPPLEGANGER +// *************************** + +edict_t *Sphere_Spawn(edict_t *owner, spawnflags_t spawnflags); + +DIE(doppleganger_die) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void +{ + edict_t *sphere; + float dist; + vec3_t dir; + + if ((self->enemy) && (self->enemy != self->teammaster)) + { + dir = self->enemy->s.origin - self->s.origin; + dist = dir.length(); + + if (dist > 80.f) + { + if (dist > 768) + { + sphere = Sphere_Spawn(self, SPHERE_HUNTER | SPHERE_DOPPLEGANGER); + sphere->pain(sphere, attacker, 0, 0, mod); + } + else + { + sphere = Sphere_Spawn(self, SPHERE_VENGEANCE | SPHERE_DOPPLEGANGER); + sphere->pain(sphere, attacker, 0, 0, mod); + } + } + } + + self->takedamage = DAMAGE_NONE; + + // [Paril-KEX] + T_RadiusDamage(self, self->teammaster, 160.f, self, 140.f, DAMAGE_NONE, MOD_DOPPLE_EXPLODE); + + if (self->teamchain) + BecomeExplosion1(self->teamchain); + BecomeExplosion1(self); +} + +PAIN(doppleganger_pain) (edict_t *self, edict_t *other, float kick, int damage, const mod_t &mod) -> void +{ + self->enemy = other; +} + +THINK(doppleganger_timeout) (edict_t *self) -> void +{ + doppleganger_die(self, self, self, 9999, self->s.origin, MOD_UNKNOWN); +} + +THINK(body_think) (edict_t *self) -> void +{ + float r; + + if (fabsf(self->ideal_yaw - anglemod(self->s.angles[YAW])) < 2) + { + if (self->timestamp < level.time) + { + r = frandom(); + if (r < 0.10f) + { + self->ideal_yaw = frandom(350.0f); + self->timestamp = level.time + 1_sec; + } + } + } + else + M_ChangeYaw(self); + + if (self->teleport_time <= level.time) + { + self->s.frame++; + if (self->s.frame > FRAME_stand40) + self->s.frame = FRAME_stand01; + + self->teleport_time = level.time + 10_hz; + } + + self->nextthink = level.time + FRAME_TIME_MS; +} + +void fire_doppleganger(edict_t *ent, const vec3_t &start, const vec3_t &aimdir) +{ + edict_t *base; + edict_t *body; + vec3_t dir; + vec3_t forward, right, up; + int number; + + dir = vectoangles(aimdir); + AngleVectors(dir, forward, right, up); + + base = G_Spawn(); + base->s.origin = start; + base->s.angles = dir; + base->movetype = MOVETYPE_TOSS; + base->solid = SOLID_BBOX; + base->s.renderfx |= RF_IR_VISIBLE; + base->s.angles[PITCH] = 0; + base->mins = { -16, -16, -24 }; + base->maxs = { 16, 16, 32 }; + base->s.modelindex = gi.modelindex ("models/objects/dopplebase/tris.md2"); + base->s.alpha = 0.1f; + base->teammaster = ent; + base->flags |= ( FL_DAMAGEABLE | FL_TRAP ); + base->takedamage = true; + base->health = 30; + base->pain = doppleganger_pain; + base->die = doppleganger_die; + + base->nextthink = level.time + 30_sec; + base->think = doppleganger_timeout; + + base->classname = "doppleganger"; + + gi.linkentity(base); + + body = G_Spawn(); + number = body->s.number; + body->s = ent->s; + body->s.sound = 0; + body->s.event = EV_NONE; + body->s.number = number; + body->yaw_speed = 30; + body->ideal_yaw = 0; + body->s.origin = start; + body->s.origin[2] += 8; + body->teleport_time = level.time + 10_hz; + body->think = body_think; + body->nextthink = level.time + FRAME_TIME_MS; + gi.linkentity(body); + + base->teamchain = body; + body->teammaster = base; + + // [Paril-KEX] + body->owner = ent; + gi.sound(body, CHAN_AUTO, gi.soundindex("medic_commander/monsterspawn1.wav"), 1.f, ATTN_NORM, 0.f); +} + +void Tag_GameInit(); +void Tag_PostInitSetup(); +void Tag_PlayerDeath(edict_t *targ, edict_t *inflictor, edict_t *attacker); +void Tag_Score(edict_t *attacker, edict_t *victim, int scoreChange, const mod_t &mod); +void Tag_PlayerEffects(edict_t *ent); +void Tag_DogTag(edict_t *ent, edict_t *killer, const char **pic); +void Tag_PlayerDisconnect(edict_t *ent); +int Tag_ChangeDamage(edict_t *targ, edict_t *attacker, int damage, mod_t mod); + +void DBall_GameInit(); +void DBall_ClientBegin(edict_t *ent); +bool DBall_SelectSpawnPoint(edict_t *ent, vec3_t &origin, vec3_t &angles, bool force_spawn); +int DBall_ChangeKnockback(edict_t *targ, edict_t *attacker, int knockback, mod_t mod); +int DBall_ChangeDamage(edict_t *targ, edict_t *attacker, int damage, mod_t mod); +void DBall_PostInitSetup(); +int DBall_CheckDMRules(); + +// **************************** +// General DM Stuff +// **************************** + +void InitGameRules() +{ + // clear out the game rule structure before we start + memset(&DMGame, 0, sizeof(dm_game_rt)); + + if (gamerules->integer) + { + switch (gamerules->integer) + { + case RDM_TAG: + DMGame.GameInit = Tag_GameInit; + DMGame.PostInitSetup = Tag_PostInitSetup; + DMGame.PlayerDeath = Tag_PlayerDeath; + DMGame.Score = Tag_Score; + DMGame.PlayerEffects = Tag_PlayerEffects; + DMGame.DogTag = Tag_DogTag; + DMGame.PlayerDisconnect = Tag_PlayerDisconnect; + DMGame.ChangeDamage = Tag_ChangeDamage; + break; + case RDM_DEATHBALL: + DMGame.GameInit = DBall_GameInit; + DMGame.ChangeKnockback = DBall_ChangeKnockback; + DMGame.ChangeDamage = DBall_ChangeDamage; + DMGame.ClientBegin = DBall_ClientBegin; + DMGame.SelectSpawnPoint = DBall_SelectSpawnPoint; + DMGame.PostInitSetup = DBall_PostInitSetup; + DMGame.CheckDMRules = DBall_CheckDMRules; + break; + // reset gamerules if it's not a valid number + default: + gi.cvar_forceset("gamerules", "0"); + break; + } + } + + // if we're set up to play, initialize the game as needed. + if (DMGame.GameInit) + DMGame.GameInit(); +} \ No newline at end of file diff --git a/rerelease/rogue/g_rogue_newfnc.cpp b/rerelease/rogue/g_rogue_newfnc.cpp new file mode 100644 index 0000000..10fd520 --- /dev/null +++ b/rerelease/rogue/g_rogue_newfnc.cpp @@ -0,0 +1,325 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. + +#include "../g_local.h" + +void fd_secret_move1(edict_t *self); +void fd_secret_move2(edict_t *self); +void fd_secret_move3(edict_t *self); +void fd_secret_move4(edict_t *self); +void fd_secret_move5(edict_t *self); +void fd_secret_move6(edict_t *self); +void fd_secret_done(edict_t *self); + +/* +============================================================================= + +SECRET DOORS + +============================================================================= +*/ + +constexpr spawnflags_t SPAWNFLAG_SEC_OPEN_ONCE = 1_spawnflag; // stays open +// unused +// constexpr uint32_t SPAWNFLAG_SEC_1ST_LEFT = 2; // 1st move is left of arrow +constexpr spawnflags_t SPAWNFLAG_SEC_1ST_DOWN = 4_spawnflag; // 1st move is down from arrow +// unused +// constexpr uint32_t SPAWNFLAG_SEC_NO_SHOOT = 8; // only opened by trigger +constexpr spawnflags_t SPAWNFLAG_SEC_YES_SHOOT = 16_spawnflag; // shootable even if targeted +constexpr spawnflags_t SPAWNFLAG_SEC_MOVE_RIGHT = 32_spawnflag; +constexpr spawnflags_t SPAWNFLAG_SEC_MOVE_FORWARD = 64_spawnflag; + +USE(fd_secret_use) (edict_t *self, edict_t *other, edict_t *activator) -> void +{ + edict_t *ent; + + if (self->flags & FL_TEAMSLAVE) + return; + + // trigger all paired doors + for (ent = self; ent; ent = ent->teamchain) + Move_Calc(ent, ent->moveinfo.start_origin, fd_secret_move1); +} + +DIE(fd_secret_killed) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void +{ + self->health = self->max_health; + self->takedamage = false; + + if (self->flags & FL_TEAMSLAVE && self->teammaster && self->teammaster->takedamage != false) + fd_secret_killed(self->teammaster, inflictor, attacker, damage, point, mod); + else + fd_secret_use(self, inflictor, attacker); +} + +// Wait after first movement... +MOVEINFO_ENDFUNC(fd_secret_move1) (edict_t *self) -> void +{ + self->nextthink = level.time + 1_sec; + self->think = fd_secret_move2; +} + +// Start moving sideways w/sound... +THINK(fd_secret_move2) (edict_t *self) -> void +{ + Move_Calc(self, self->moveinfo.end_origin, fd_secret_move3); +} + +// Wait here until time to go back... +MOVEINFO_ENDFUNC(fd_secret_move3) (edict_t *self) -> void +{ + if (!self->spawnflags.has(SPAWNFLAG_SEC_OPEN_ONCE)) + { + self->nextthink = level.time + gtime_t::from_sec(self->wait); + self->think = fd_secret_move4; + } +} + +// Move backward... +THINK(fd_secret_move4) (edict_t *self) -> void +{ + Move_Calc(self, self->moveinfo.start_origin, fd_secret_move5); +} + +// Wait 1 second... +MOVEINFO_ENDFUNC(fd_secret_move5) (edict_t *self) -> void +{ + self->nextthink = level.time + 1_sec; + self->think = fd_secret_move6; +} + +THINK(fd_secret_move6) (edict_t *self) -> void +{ + Move_Calc(self, self->move_origin, fd_secret_done); +} + +MOVEINFO_ENDFUNC(fd_secret_done) (edict_t *self) -> void +{ + if (!self->targetname || self->spawnflags.has(SPAWNFLAG_SEC_YES_SHOOT)) + { + self->health = 1; + self->takedamage = true; + self->die = fd_secret_killed; + } +} + +MOVEINFO_BLOCKED(secret_blocked) (edict_t *self, edict_t *other) -> void +{ + if (!(self->flags & FL_TEAMSLAVE)) + T_Damage(other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 0, DAMAGE_NONE, MOD_CRUSH); +} + +/* +================ +secret_touch + +Prints messages +================ +*/ +TOUCH(secret_touch) (edict_t *self, edict_t *other, const trace_t &tr, bool other_touching_self) -> void +{ + if (other->health <= 0) + return; + + if (!(other->client)) + return; + + if (self->monsterinfo.attack_finished > level.time) + return; + + self->monsterinfo.attack_finished = level.time + 2_sec; + + if (self->message) + gi.LocCenter_Print(other, self->message); +} + +/*QUAKED func_door_secret2 (0 .5 .8) ? open_once 1st_left 1st_down no_shoot always_shoot slide_right slide_forward +Basic secret door. Slides back, then to the left. Angle determines direction. + +FLAGS: +open_once = not implemented yet +1st_left = 1st move is left/right of arrow +1st_down = 1st move is forwards/backwards +no_shoot = not implemented yet +always_shoot = even if targeted, keep shootable +reverse_left = the sideways move will be to right of arrow +reverse_back = the to/fro move will be forward + +VALUES: +wait = # of seconds before coming back (5 default) +dmg = damage to inflict when blocked (2 default) + +*/ + +void SP_func_door_secret2(edict_t *ent) +{ + vec3_t forward, right, up; + float lrSize, fbSize; + + G_SetMoveinfoSounds(ent, "doors/dr1_strt.wav", "doors/dr1_mid.wav", "doors/dr1_end.wav"); + + if (!ent->dmg) + ent->dmg = 2; + + AngleVectors(ent->s.angles, forward, right, up); + ent->move_origin = ent->s.origin; + ent->move_angles = ent->s.angles; + + G_SetMovedir(ent->s.angles, ent->movedir); + ent->movetype = MOVETYPE_PUSH; + ent->solid = SOLID_BSP; + gi.setmodel(ent, ent->model); + + if (ent->move_angles[1] == 0 || ent->move_angles[1] == 180) + { + lrSize = ent->size[1]; + fbSize = ent->size[0]; + } + else if (ent->move_angles[1] == 90 || ent->move_angles[1] == 270) + { + lrSize = ent->size[0]; + fbSize = ent->size[1]; + } + else + { + gi.Com_Print("Secret door not at 0,90,180,270!\n"); + G_FreeEdict(ent); + return; + } + + if (ent->spawnflags.has(SPAWNFLAG_SEC_MOVE_FORWARD)) + forward *= fbSize; + else + forward *= fbSize * -1; + + if (ent->spawnflags.has(SPAWNFLAG_SEC_MOVE_RIGHT)) + right *= lrSize; + else + right *= lrSize * -1; + + if (ent->spawnflags.has(SPAWNFLAG_SEC_1ST_DOWN)) + { + ent->moveinfo.start_origin = ent->s.origin + forward; + ent->moveinfo.end_origin = ent->moveinfo.start_origin + right; + } + else + { + ent->moveinfo.start_origin = ent->s.origin + right; + ent->moveinfo.end_origin = ent->moveinfo.start_origin + forward; + } + + ent->touch = secret_touch; + ent->moveinfo.blocked = secret_blocked; + ent->use = fd_secret_use; + ent->moveinfo.speed = 50; + ent->moveinfo.accel = 50; + ent->moveinfo.decel = 50; + + if (!ent->targetname || ent->spawnflags.has(SPAWNFLAG_SEC_YES_SHOOT)) + { + ent->health = 1; + ent->max_health = ent->health; + ent->takedamage = true; + ent->die = fd_secret_killed; + } + if (!ent->wait) + ent->wait = 5; // 5 seconds before closing + + gi.linkentity(ent); +} + +// ================================================== + +constexpr spawnflags_t SPAWNFLAG_FORCEWALL_START_ON = 1_spawnflag; + +THINK(force_wall_think) (edict_t *self) -> void +{ + if (!self->wait) + { + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_FORCEWALL); + gi.WritePosition(self->pos1); + gi.WritePosition(self->pos2); + gi.WriteByte(self->style); + gi.multicast(self->offset, MULTICAST_PVS, false); + } + + self->think = force_wall_think; + self->nextthink = level.time + 10_hz; +} + +USE(force_wall_use) (edict_t *self, edict_t *other, edict_t *activator) -> void +{ + if (!self->wait) + { + self->wait = 1; + self->think = nullptr; + self->nextthink = 0_ms; + self->solid = SOLID_NOT; + gi.linkentity(self); + } + else + { + self->wait = 0; + self->think = force_wall_think; + self->nextthink = level.time + 10_hz; + self->solid = SOLID_BSP; + gi.linkentity(self); + KillBox(self, false); // Is this appropriate? + } +} + +/*QUAKED func_force_wall (1 0 1) ? start_on +A vertical particle force wall. Turns on and solid when triggered. +If someone is in the force wall when it turns on, they're telefragged. + +start_on - forcewall begins activated. triggering will turn it off. +style - color of particles to use. + 208: green, 240: red, 241: blue, 224: orange +*/ +void SP_func_force_wall(edict_t *ent) +{ + gi.setmodel(ent, ent->model); + + ent->offset[0] = (ent->absmax[0] + ent->absmin[0]) / 2; + ent->offset[1] = (ent->absmax[1] + ent->absmin[1]) / 2; + ent->offset[2] = (ent->absmax[2] + ent->absmin[2]) / 2; + + ent->pos1[2] = ent->absmax[2]; + ent->pos2[2] = ent->absmax[2]; + if (ent->size[0] > ent->size[1]) + { + ent->pos1[0] = ent->absmin[0]; + ent->pos2[0] = ent->absmax[0]; + ent->pos1[1] = ent->offset[1]; + ent->pos2[1] = ent->offset[1]; + } + else + { + ent->pos1[0] = ent->offset[0]; + ent->pos2[0] = ent->offset[0]; + ent->pos1[1] = ent->absmin[1]; + ent->pos2[1] = ent->absmax[1]; + } + + if (!ent->style) + ent->style = 208; + + ent->movetype = MOVETYPE_NONE; + ent->wait = 1; + + if (ent->spawnflags.has(SPAWNFLAG_FORCEWALL_START_ON)) + { + ent->solid = SOLID_BSP; + ent->think = force_wall_think; + ent->nextthink = level.time + 10_hz; + } + else + ent->solid = SOLID_NOT; + + ent->use = force_wall_use; + + ent->svflags = SVF_NOCLIENT; + + gi.linkentity(ent); +} diff --git a/rerelease/rogue/g_rogue_newtarg.cpp b/rerelease/rogue/g_rogue_newtarg.cpp new file mode 100644 index 0000000..06ad8a1 --- /dev/null +++ b/rerelease/rogue/g_rogue_newtarg.cpp @@ -0,0 +1,324 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. + +#include "../g_local.h" + +//========================================================== + +/*QUAKED target_steam (1 0 0) (-8 -8 -8) (8 8 8) +Creates a steam effect (particles w/ velocity in a line). + + speed = velocity of particles (default 50) + count = number of particles (default 32) + sounds = color of particles (default 8 for steam) + the color range is from this color to this color + 6 + wait = seconds to run before stopping (overrides default + value derived from func_timer) + + best way to use this is to tie it to a func_timer that "pokes" + it every second (or however long you set the wait time, above) + + note that the width of the base is proportional to the speed + good colors to use: + 6-9 - varying whites (darker to brighter) + 224 - sparks + 176 - blue water + 80 - brown water + 208 - slime + 232 - blood +*/ + +USE(use_target_steam) (edict_t *self, edict_t *other, edict_t *activator) -> void +{ + // FIXME - this needs to be a global + static int nextid; + vec3_t point; + + if (nextid > 20000) + nextid = nextid % 20000; + + nextid++; + + // automagically set wait from func_timer unless they set it already, or + // default to 1000 if not called by a func_timer (eek!) + if (!self->wait) + { + if (other) + self->wait = other->wait * 1000; + else + self->wait = 1000; + } + + if (self->enemy) + { + point = (self->enemy->absmin + self->enemy->absmax) * 0.5f; + self->movedir = point - self->s.origin; + self->movedir.normalize(); + } + + point = self->s.origin + (self->movedir * (self->style * 0.5f)); + if (self->wait > 100) + { + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_STEAM); + gi.WriteShort(nextid); + gi.WriteByte(self->count); + gi.WritePosition(self->s.origin); + gi.WriteDir(self->movedir); + gi.WriteByte(self->sounds & 0xff); + gi.WriteShort((short int) (self->style)); + gi.WriteLong((int) (self->wait)); + gi.multicast(self->s.origin, MULTICAST_PVS, false); + } + else + { + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_STEAM); + gi.WriteShort((short int) -1); + gi.WriteByte(self->count); + gi.WritePosition(self->s.origin); + gi.WriteDir(self->movedir); + gi.WriteByte(self->sounds & 0xff); + gi.WriteShort((short int) (self->style)); + gi.multicast(self->s.origin, MULTICAST_PVS, false); + } +} + +THINK(target_steam_start) (edict_t *self) -> void +{ + edict_t *ent; + + self->use = use_target_steam; + + if (self->target) + { + ent = G_FindByString<&edict_t::targetname>(nullptr, self->target); + if (!ent) + gi.Com_PrintFmt("{}: target {} not found\n", *self, self->target); + self->enemy = ent; + } + else + { + G_SetMovedir(self->s.angles, self->movedir); + } + + if (!self->count) + self->count = 32; + if (!self->style) + self->style = 75; + if (!self->sounds) + self->sounds = 8; + if (self->wait) + self->wait *= 1000; // we want it in milliseconds, not seconds + + // paranoia is good + self->sounds &= 0xff; + self->count &= 0xff; + + self->svflags = SVF_NOCLIENT; + + gi.linkentity(self); +} + +void SP_target_steam(edict_t *self) +{ + self->style = (int) self->speed; + + if (self->target) + { + self->think = target_steam_start; + self->nextthink = level.time + 1_sec; + } + else + target_steam_start(self); +} + +//========================================================== +// target_anger +//========================================================== + +USE(target_anger_use) (edict_t *self, edict_t *other, edict_t *activator) -> void +{ + edict_t *target; + edict_t *t; + + t = nullptr; + target = G_FindByString<&edict_t::targetname>(t, self->killtarget); + + if (target && self->target) + { + // Make whatever a "good guy" so the monster will try to kill it! + if (!(target->svflags & SVF_MONSTER)) + { + target->monsterinfo.aiflags |= AI_GOOD_GUY | AI_DO_NOT_COUNT; + target->svflags |= SVF_MONSTER; + target->health = 300; + } + + t = nullptr; + while ((t = G_FindByString<&edict_t::targetname>(t, self->target))) + { + if (t == self) + { + gi.Com_Print("WARNING: entity used itself.\n"); + } + else + { + if (t->use) + { + if (t->health <= 0) + return; + + t->enemy = target; + t->monsterinfo.aiflags |= AI_TARGET_ANGER; + FoundTarget(t); + } + } + if (!self->inuse) + { + gi.Com_Print("entity was removed while using targets\n"); + return; + } + } + } +} + +/*QUAKED target_anger (1 0 0) (-8 -8 -8) (8 8 8) +This trigger will cause an entity to be angry at another entity when a player touches it. Target the +entity you want to anger, and killtarget the entity you want it to be angry at. + +target - entity to piss off +killtarget - entity to be pissed off at +*/ +void SP_target_anger(edict_t *self) +{ + if (!self->target) + { + gi.Com_Print("target_anger without target!\n"); + G_FreeEdict(self); + return; + } + if (!self->killtarget) + { + gi.Com_Print("target_anger without killtarget!\n"); + G_FreeEdict(self); + return; + } + + self->use = target_anger_use; + self->svflags = SVF_NOCLIENT; +} + +// *********************************** +// target_killplayers +// *********************************** + +USE(target_killplayers_use) (edict_t *self, edict_t *other, edict_t *activator) -> void +{ + level.deadly_kill_box = true; + + edict_t *ent, *player; + + // kill any visible monsters + for (ent = g_edicts; ent < &g_edicts[globals.num_edicts]; ent++) + { + if (!ent->inuse) + continue; + if (ent->health < 1) + continue; + if (!ent->takedamage) + continue; + + for (uint32_t i = 0; i < game.maxclients; i++) + { + player = &g_edicts[1 + i]; + if (!player->inuse) + continue; + + if (gi.inPVS(player->s.origin, ent->s.origin, false)) + { + T_Damage(ent, self, self, vec3_origin, ent->s.origin, vec3_origin, + ent->health, 0, DAMAGE_NO_PROTECTION, MOD_TELEFRAG); + break; + } + } + } + + // kill the players + for (uint32_t i = 0; i < game.maxclients; i++) + { + player = &g_edicts[1 + i]; + if (!player->inuse) + continue; + + // nail it + T_Damage(player, self, self, vec3_origin, self->s.origin, vec3_origin, 100000, 0, DAMAGE_NO_PROTECTION, MOD_TELEFRAG); + } + + level.deadly_kill_box = false; +} + +/*QUAKED target_killplayers (1 0 0) (-8 -8 -8) (8 8 8) +When triggered, this will kill all the players on the map. +*/ +void SP_target_killplayers(edict_t *self) +{ + self->use = target_killplayers_use; + self->svflags = SVF_NOCLIENT; +} + +/*QUAKED target_blacklight (1 0 1) (-16 -16 -24) (16 16 24) +Pulsing black light with sphere in the center +*/ +THINK(blacklight_think) (edict_t *self) -> void +{ + self->s.angles[0] += frandom(10); + self->s.angles[1] += frandom(10); + self->s.angles[2] += frandom(10); + self->nextthink = level.time + FRAME_TIME_MS; +} + +void SP_target_blacklight(edict_t *ent) +{ + if (deathmatch->integer) + { // auto-remove for deathmatch + G_FreeEdict(ent); + return; + } + + ent->mins = {}; + ent->maxs = {}; + + ent->s.effects |= (EF_TRACKERTRAIL | EF_TRACKER); + ent->think = blacklight_think; + ent->s.modelindex = gi.modelindex("models/items/spawngro3/tris.md2"); + ent->s.scale = 6.f; + ent->s.skinnum = 0; + ent->nextthink = level.time + FRAME_TIME_MS; + gi.linkentity(ent); +} + +/*QUAKED target_orb (1 0 1) (-16 -16 -24) (16 16 24) +Translucent pulsing orb with speckles +*/ +void SP_target_orb(edict_t *ent) +{ + if (deathmatch->integer) + { // auto-remove for deathmatch + G_FreeEdict(ent); + return; + } + + ent->mins = {}; + ent->maxs = {}; + + // ent->s.effects |= EF_TRACKERTRAIL; + ent->think = blacklight_think; + ent->nextthink = level.time + 10_hz; + ent->s.skinnum = 1; + ent->s.modelindex = gi.modelindex("models/items/spawngro3/tris.md2"); + ent->s.frame = 2; + ent->s.scale = 8.f; + ent->s.effects |= EF_SPHERETRANS; + gi.linkentity(ent); +} diff --git a/rerelease/rogue/g_rogue_newtrig.cpp b/rerelease/rogue/g_rogue_newtrig.cpp new file mode 100644 index 0000000..ee8862e --- /dev/null +++ b/rerelease/rogue/g_rogue_newtrig.cpp @@ -0,0 +1,189 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// g_newtrig.c +// pmack +// october 1997 + +#include "../g_local.h" + +/*QUAKED info_teleport_destination (.5 .5 .5) (-16 -16 -24) (16 16 32) +Destination marker for a teleporter. +*/ +void SP_info_teleport_destination(edict_t *self) +{ +} + +// unused; broken? +// constexpr uint32_t SPAWNFLAG_TELEPORT_PLAYER_ONLY = 1; +// unused +// constexpr uint32_t SPAWNFLAG_TELEPORT_SILENT = 2; +// unused +// constexpr uint32_t SPAWNFLAG_TELEPORT_CTF_ONLY = 4; +constexpr spawnflags_t SPAWNFLAG_TELEPORT_START_ON = 8_spawnflag; + +/*QUAKED trigger_teleport (.5 .5 .5) ? player_only silent ctf_only start_on +Any object touching this will be transported to the corresponding +info_teleport_destination entity. You must set the "target" field, +and create an object with a "targetname" field that matches. + +If the trigger_teleport has a targetname, it will only teleport +entities when it has been fired. + +player_only: only players are teleported +silent: +ctf_only: +start_on: when trigger has targetname, start active, deactivate when used. +*/ +TOUCH(trigger_teleport_touch) (edict_t *self, edict_t *other, const trace_t &tr, bool other_touching_self) -> void +{ + edict_t *dest; + + if (/*(self->spawnflags & SPAWNFLAG_TELEPORT_PLAYER_ONLY) &&*/ !(other->client)) + return; + + if (self->delay) + return; + + dest = G_PickTarget(self->target); + if (!dest) + { + gi.Com_Print("Teleport Destination not found!\n"); + return; + } + + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_TELEPORT_EFFECT); + gi.WritePosition(other->s.origin); + gi.multicast(other->s.origin, MULTICAST_PVS, false); + + other->s.origin = dest->s.origin; + other->s.old_origin = dest->s.origin; + other->s.origin[2] += 10; + + // clear the velocity and hold them in place briefly + other->velocity = {}; + + if (other->client) + { + other->client->ps.pmove.pm_time = 160; // hold time + other->client->ps.pmove.pm_flags |= PMF_TIME_TELEPORT; + + // draw the teleport splash at source and on the player + other->s.event = EV_PLAYER_TELEPORT; + + // set angles + other->client->ps.pmove.delta_angles = dest->s.angles - other->client->resp.cmd_angles; + + other->client->ps.viewangles = {}; + other->client->v_angle = {}; + } + + other->s.angles = {}; + + gi.linkentity(other); + + // kill anything at the destination + KillBox(other, !!other->client); + + // [Paril-KEX] move sphere, if we own it + if (other->client && other->client->owned_sphere) + { + edict_t *sphere = other->client->owned_sphere; + sphere->s.origin = other->s.origin; + sphere->s.origin[2] = other->absmax[2]; + sphere->s.angles[YAW] = other->s.angles[YAW]; + gi.linkentity(sphere); + } +} + +USE(trigger_teleport_use) (edict_t *self, edict_t *other, edict_t *activator) -> void +{ + if (self->delay) + self->delay = 0; + else + self->delay = 1; +} + +void SP_trigger_teleport(edict_t *self) +{ + if (!self->wait) + self->wait = 0.2f; + + self->delay = 0; + + if (self->targetname) + { + self->use = trigger_teleport_use; + if (!self->spawnflags.has(SPAWNFLAG_TELEPORT_START_ON)) + self->delay = 1; + } + + self->touch = trigger_teleport_touch; + + self->solid = SOLID_TRIGGER; + self->movetype = MOVETYPE_NONE; + + if (self->s.angles) + G_SetMovedir(self->s.angles, self->movedir); + + gi.setmodel(self, self->model); + gi.linkentity(self); +} + +// *************************** +// TRIGGER_DISGUISE +// *************************** + +/*QUAKED trigger_disguise (.5 .5 .5) ? TOGGLE START_ON REMOVE +Anything passing through this trigger when it is active will +be marked as disguised. + +TOGGLE - field is turned off and on when used. (Paril N.B.: always the case) +START_ON - field is active when spawned. +REMOVE - field removes the disguise +*/ + +// unused +// constexpr uint32_t SPAWNFLAG_DISGUISE_TOGGLE = 1; +constexpr spawnflags_t SPAWNFLAG_DISGUISE_START_ON = 2_spawnflag; +constexpr spawnflags_t SPAWNFLAG_DISGUISE_REMOVE = 4_spawnflag; + +TOUCH(trigger_disguise_touch) (edict_t *self, edict_t *other, const trace_t &tr, bool other_touching_self) -> void +{ + if (other->client) + { + if (self->spawnflags.has(SPAWNFLAG_DISGUISE_REMOVE)) + other->flags &= ~FL_DISGUISED; + else + other->flags |= FL_DISGUISED; + } +} + +USE(trigger_disguise_use) (edict_t *self, edict_t *other, edict_t *activator) -> void +{ + if (self->solid == SOLID_NOT) + self->solid = SOLID_TRIGGER; + else + self->solid = SOLID_NOT; + + gi.linkentity(self); +} + +void SP_trigger_disguise(edict_t *self) +{ + if (!level.disguise_icon) + level.disguise_icon = gi.imageindex("i_disguise"); + + if (self->spawnflags.has(SPAWNFLAG_DISGUISE_START_ON)) + self->solid = SOLID_TRIGGER; + else + self->solid = SOLID_NOT; + + self->touch = trigger_disguise_touch; + self->use = trigger_disguise_use; + self->movetype = MOVETYPE_NONE; + self->svflags = SVF_NOCLIENT; + + gi.setmodel(self, self->model); + gi.linkentity(self); +} diff --git a/rerelease/rogue/g_rogue_newweap.cpp b/rerelease/rogue/g_rogue_newweap.cpp new file mode 100644 index 0000000..b23b3af --- /dev/null +++ b/rerelease/rogue/g_rogue_newweap.cpp @@ -0,0 +1,1644 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +#include "../g_local.h" + +/* +======================== +fire_flechette +======================== +*/ +TOUCH(flechette_touch) (edict_t *self, edict_t *other, const trace_t &tr, bool other_touching_self) -> void +{ + if (other == self->owner) + return; + + if (tr.surface && (tr.surface->flags & SURF_SKY)) + { + G_FreeEdict(self); + return; + } + + if (self->client) + PlayerNoise(self->owner, self->s.origin, PNOISE_IMPACT); + + if (other->takedamage) + { + T_Damage(other, self, self->owner, self->velocity, self->s.origin, tr.plane.normal, + self->dmg, (int) self->dmg_radius, DAMAGE_NO_REG_ARMOR, MOD_ETF_RIFLE); + } + else + { + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_FLECHETTE); + gi.WritePosition(self->s.origin); + gi.WriteDir(tr.plane.normal); + gi.multicast(self->s.origin, MULTICAST_PHS, false); + } + + G_FreeEdict(self); +} + +void fire_flechette(edict_t *self, const vec3_t &start, const vec3_t &dir, int damage, int speed, int kick) +{ + edict_t *flechette; + + flechette = G_Spawn(); + flechette->s.origin = start; + flechette->s.old_origin = start; + flechette->s.angles = vectoangles(dir); + flechette->velocity = dir * speed; + flechette->svflags |= SVF_PROJECTILE; + flechette->movetype = MOVETYPE_FLYMISSILE; + flechette->clipmask = MASK_PROJECTILE; + flechette->flags |= FL_DODGE; + + // [Paril-KEX] + if (self->client && !G_ShouldPlayersCollide(true)) + flechette->clipmask &= ~CONTENTS_PLAYER; + + flechette->solid = SOLID_BBOX; + flechette->s.renderfx = RF_FULLBRIGHT; + flechette->s.modelindex = gi.modelindex("models/proj/flechette/tris.md2"); + + flechette->owner = self; + flechette->touch = flechette_touch; + flechette->nextthink = level.time + gtime_t::from_sec(8000.f / speed); + flechette->think = G_FreeEdict; + flechette->dmg = damage; + flechette->dmg_radius = (float) kick; + + gi.linkentity(flechette); + + trace_t tr = gi.traceline(self->s.origin, flechette->s.origin, flechette, flechette->clipmask); + if (tr.fraction < 1.0f) + { + flechette->s.origin = tr.endpos + (tr.plane.normal * 1.f); + flechette->touch(flechette, tr.ent, tr, false); + } +} + +// ************************** +// PROX +// ************************** + +constexpr gtime_t PROX_TIME_TO_LIVE = 45_sec; // 45, 30, 15, 10 +constexpr gtime_t PROX_TIME_DELAY = 500_ms; +constexpr float PROX_BOUND_SIZE = 96; +constexpr float PROX_DAMAGE_RADIUS = 192; +constexpr int32_t PROX_HEALTH = 20; +constexpr int32_t PROX_DAMAGE = 90; + +//=============== +//=============== +THINK(Prox_Explode) (edict_t *ent) -> void +{ + vec3_t origin; + edict_t *owner; + + // free the trigger field + + // PMM - changed teammaster to "mover" .. owner of the field is the prox + if (ent->teamchain && ent->teamchain->owner == ent) + G_FreeEdict(ent->teamchain); + + owner = ent; + if (ent->teammaster) + { + owner = ent->teammaster; + PlayerNoise(owner, ent->s.origin, PNOISE_IMPACT); + } + + // play quad sound if appopriate + if (ent->dmg > PROX_DAMAGE) + gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage3.wav"), 1, ATTN_NORM, 0); + + ent->takedamage = false; + T_RadiusDamage(ent, owner, (float) ent->dmg, ent, PROX_DAMAGE_RADIUS, DAMAGE_NONE, MOD_PROX); + + origin = ent->s.origin + (ent->velocity * -0.02f); + gi.WriteByte(svc_temp_entity); + if (ent->groundentity) + gi.WriteByte(TE_GRENADE_EXPLOSION); + else + gi.WriteByte(TE_ROCKET_EXPLOSION); + gi.WritePosition(origin); + gi.multicast(ent->s.origin, MULTICAST_PHS, false); + + G_FreeEdict(ent); +} + +//=============== +//=============== +DIE(prox_die) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void +{ + // if set off by another prox, delay a little (chained explosions) + if (strcmp(inflictor->classname, "prox_mine")) + { + self->takedamage = false; + Prox_Explode(self); + } + else + { + self->takedamage = false; + self->think = Prox_Explode; + self->nextthink = level.time + FRAME_TIME_S; + } +} + +//=============== +//=============== +TOUCH(Prox_Field_Touch) (edict_t *ent, edict_t *other, const trace_t &tr, bool other_touching_self) -> void +{ + edict_t *prox; + + if (!(other->svflags & SVF_MONSTER) && !other->client) + return; + + // trigger the prox mine if it's still there, and still mine. + prox = ent->owner; + + // teammate avoidance + if (CheckTeamDamage(prox->teammaster, other)) + return; + + if (!deathmatch->integer && other->client) + return; + + if (other == prox) // don't set self off + return; + + if (prox->think == Prox_Explode) // we're set to blow! + return; + + if (prox->teamchain == ent) + { + gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/proxwarn.wav"), 1, ATTN_NORM, 0); + prox->think = Prox_Explode; + prox->nextthink = level.time + PROX_TIME_DELAY; + return; + } + + ent->solid = SOLID_NOT; + G_FreeEdict(ent); +} + +//=============== +//=============== +THINK(prox_seek) (edict_t *ent) -> void +{ + if (level.time > gtime_t::from_sec(ent->wait)) + { + Prox_Explode(ent); + } + else + { + ent->s.frame++; + if (ent->s.frame > 13) + ent->s.frame = 9; + ent->think = prox_seek; + ent->nextthink = level.time + 10_hz; + } +} + +//=============== +//=============== +THINK(prox_open) (edict_t *ent) -> void +{ + edict_t *search; + + search = nullptr; + + if (ent->s.frame == 9) // end of opening animation + { + // set the owner to nullptr so the owner can walk through it. needs to be done here so the owner + // doesn't get stuck on it while it's opening if fired at point blank wall + ent->s.sound = 0; + + if (deathmatch->integer) + ent->owner = nullptr; + + if (ent->teamchain) + ent->teamchain->touch = Prox_Field_Touch; + while ((search = findradius(search, ent->s.origin, PROX_DAMAGE_RADIUS + 10)) != nullptr) + { + if (!search->classname) // tag token and other weird shit + continue; + + // teammate avoidance + if (CheckTeamDamage(search, ent->teammaster)) + continue; + + // if it's a monster or player with health > 0 + // or it's a player start point + // and we can see it + // blow up + if ( + search != ent && + ( + (((search->svflags & SVF_MONSTER) || (deathmatch->integer && (search->client || (search->classname && !strcmp(search->classname, "prox_mine"))))) && (search->health > 0)) || + (deathmatch->integer && + ((!strncmp(search->classname, "info_player_", 12)) || + (!strcmp(search->classname, "misc_teleporter_dest")) || + (!strncmp(search->classname, "item_flag_", 10))))) && + (visible(search, ent))) + { + gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/proxwarn.wav"), 1, ATTN_NORM, 0); + Prox_Explode(ent); + return; + } + } + + if (g_dm_strong_mines->integer) + ent->wait = (level.time + PROX_TIME_TO_LIVE).seconds(); + else + { + switch (ent->dmg / PROX_DAMAGE) + { + case 1: + ent->wait = (level.time + PROX_TIME_TO_LIVE).seconds(); + break; + case 2: + ent->wait = (level.time + 30_sec).seconds(); + break; + case 4: + ent->wait = (level.time + 15_sec).seconds(); + break; + case 8: + ent->wait = (level.time + 10_sec).seconds(); + break; + default: + ent->wait = (level.time + PROX_TIME_TO_LIVE).seconds(); + break; + } + } + + ent->think = prox_seek; + ent->nextthink = level.time + 200_ms; + } + else + { + if (ent->s.frame == 0) + gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/proxopen.wav"), 1, ATTN_NORM, 0); + ent->s.frame++; + ent->think = prox_open; + ent->nextthink = level.time + 10_hz; + } +} + +//=============== +//=============== +TOUCH(prox_land) (edict_t *ent, edict_t *other, const trace_t &tr, bool other_touching_self) -> void +{ + edict_t *field; + vec3_t dir; + vec3_t forward, right, up; + movetype_t movetype = MOVETYPE_NONE; + int stick_ok = 0; + vec3_t land_point; + + // must turn off owner so owner can shoot it and set it off + // moved to prox_open so owner can get away from it if fired at pointblank range into + // wall + if (tr.surface && (tr.surface->flags & SURF_SKY)) + { + G_FreeEdict(ent); + return; + } + + if (tr.plane.normal) + { + land_point = ent->s.origin + (tr.plane.normal * -10.0f); + if (gi.pointcontents(land_point) & (CONTENTS_SLIME | CONTENTS_LAVA)) + { + Prox_Explode(ent); + return; + } + } + + constexpr float PROX_STOP_EPSILON = 0.1f; + + if (!tr.plane.normal || (other->svflags & SVF_MONSTER) || other->client || (other->flags & FL_DAMAGEABLE)) + { + if (other != ent->teammaster) + Prox_Explode(ent); + + return; + } + else if (other != world) + { + // Here we need to check to see if we can stop on this entity. + // Note that plane can be nullptr + + // PMM - code stolen from g_phys (ClipVelocity) + vec3_t out; + float backoff, change; + int i; + + if ((other->movetype == MOVETYPE_PUSH) && (tr.plane.normal[2] > 0.7f)) + stick_ok = 1; + else + stick_ok = 0; + + backoff = ent->velocity.dot(tr.plane.normal) * 1.5f; + for (i = 0; i < 3; i++) + { + change = tr.plane.normal[i] * backoff; + out[i] = ent->velocity[i] - change; + if (out[i] > -PROX_STOP_EPSILON && out[i] < PROX_STOP_EPSILON) + out[i] = 0; + } + + if (out[2] > 60) + return; + + movetype = MOVETYPE_BOUNCE; + + // if we're here, we're going to stop on an entity + if (stick_ok) + { // it's a happy entity + ent->velocity = {}; + ent->avelocity = {}; + } + else // no-stick. teflon time + { + if (tr.plane.normal[2] > 0.7f) + { + Prox_Explode(ent); + return; + } + return; + } + } + else if (other->s.modelindex != MODELINDEX_WORLD) + return; + + dir = vectoangles(tr.plane.normal); + AngleVectors(dir, forward, right, up); + + if (gi.pointcontents(ent->s.origin) & (CONTENTS_LAVA | CONTENTS_SLIME)) + { + Prox_Explode(ent); + return; + } + + ent->svflags &= ~SVF_PROJECTILE; + + field = G_Spawn(); + + field->s.origin = ent->s.origin; + field->mins = { -PROX_BOUND_SIZE, -PROX_BOUND_SIZE, -PROX_BOUND_SIZE }; + field->maxs = { PROX_BOUND_SIZE, PROX_BOUND_SIZE, PROX_BOUND_SIZE }; + field->movetype = MOVETYPE_NONE; + field->solid = SOLID_TRIGGER; + field->owner = ent; + field->classname = "prox_field"; + field->teammaster = ent; + gi.linkentity(field); + + ent->velocity = {}; + ent->avelocity = {}; + // rotate to vertical + dir[PITCH] = dir[PITCH] + 90; + ent->s.angles = dir; + ent->takedamage = true; + ent->movetype = movetype; // either bounce or none, depending on whether we stuck to something + ent->die = prox_die; + ent->teamchain = field; + ent->health = PROX_HEALTH; + ent->nextthink = level.time; + ent->think = prox_open; + ent->touch = nullptr; + ent->solid = SOLID_BBOX; + + gi.linkentity(ent); +} + +THINK(Prox_Think) (edict_t *self) -> void +{ + if (self->timestamp <= level.time) + { + Prox_Explode(self); + return; + } + + self->s.angles = vectoangles(self->velocity.normalized()); + self->s.angles[PITCH] -= 90; + self->nextthink = level.time; +} + +//=============== +//=============== +void fire_prox(edict_t *self, const vec3_t &start, const vec3_t &aimdir, int prox_damage_multiplier, int speed) +{ + edict_t *prox; + vec3_t dir; + vec3_t forward, right, up; + + dir = vectoangles(aimdir); + AngleVectors(dir, forward, right, up); + + prox = G_Spawn(); + prox->s.origin = start; + prox->velocity = aimdir * speed; + + float gravityAdjustment = level.gravity / 800.f; + + prox->velocity += up * (200 + crandom() * 10.0f) * gravityAdjustment; + prox->velocity += right * (crandom() * 10.0f); + + prox->s.angles = dir; + prox->s.angles[PITCH] -= 90; + prox->movetype = MOVETYPE_BOUNCE; + prox->solid = SOLID_BBOX; + prox->svflags |= SVF_PROJECTILE; + prox->s.effects |= EF_GRENADE; + prox->flags |= ( FL_DODGE | FL_TRAP ); + prox->clipmask = MASK_PROJECTILE | CONTENTS_LAVA | CONTENTS_SLIME; + + // [Paril-KEX] + if (self->client && !G_ShouldPlayersCollide(true)) + prox->clipmask &= ~CONTENTS_PLAYER; + + prox->s.renderfx |= RF_IR_VISIBLE; + // FIXME - this needs to be bigger. Has other effects, though. Maybe have to change origin to compensate + // so it sinks in correctly. Also in lavacheck, might have to up the distance + prox->mins = { -6, -6, -6 }; + prox->maxs = { 6, 6, 6 }; + prox->s.modelindex = gi.modelindex("models/weapons/g_prox/tris.md2"); + prox->owner = self; + prox->teammaster = self; + prox->touch = prox_land; + prox->think = Prox_Think; + prox->nextthink = level.time; + prox->dmg = PROX_DAMAGE * prox_damage_multiplier; + prox->classname = "prox_mine"; + prox->flags |= FL_DAMAGEABLE; + prox->flags |= FL_MECHANICAL; + + switch (prox_damage_multiplier) + { + case 1: + prox->timestamp = level.time + PROX_TIME_TO_LIVE; + break; + case 2: + prox->timestamp = level.time + 30_sec; + break; + case 4: + prox->timestamp = level.time + 15_sec; + break; + case 8: + prox->timestamp = level.time + 10_sec; + break; + default: + prox->timestamp = level.time + PROX_TIME_TO_LIVE; + break; + } + + gi.linkentity(prox); +} + +// ************************* +// MELEE WEAPONS +// ************************* + +struct player_melee_data_t +{ + edict_t *self; + const vec3_t &start; + const vec3_t &aim; + int reach; +}; + +static BoxEdictsResult_t fire_player_melee_BoxFilter(edict_t *check, void *data_v) +{ + const player_melee_data_t *data = (const player_melee_data_t *) data_v; + + if (!check->inuse || !check->takedamage || check == data->self) + return BoxEdictsResult_t::Skip; + + // check distance + vec3_t closest_point_to_check = closest_point_to_box(data->start, check->s.origin + check->mins, check->s.origin + check->maxs); + vec3_t closest_point_to_self = closest_point_to_box(closest_point_to_check, data->self->s.origin + data->self->mins, data->self->s.origin + data->self->maxs); + + vec3_t dir = (closest_point_to_check - closest_point_to_self); + float len = dir.normalize(); + + if (len > data->reach) + return BoxEdictsResult_t::Skip; + + // check angle if we aren't intersecting + vec3_t shrink { 2, 2, 2 }; + if (!boxes_intersect(check->absmin + shrink, check->absmax - shrink, data->self->absmin + shrink, data->self->absmax - shrink)) + { + dir = (((check->absmin + check->absmax) / 2) - data->start).normalized(); + + if (dir.dot(data->aim) < 0.70f) + return BoxEdictsResult_t::Skip; + } + + return BoxEdictsResult_t::Keep; +} + +bool fire_player_melee(edict_t *self, const vec3_t &start, const vec3_t &aim, int reach, int damage, int kick, mod_t mod) +{ + constexpr size_t MAX_HIT = 4; + + vec3_t reach_vec{ float(reach - 1), float(reach - 1), float(reach - 1) }; + edict_t *targets[MAX_HIT]; + + player_melee_data_t data { + self, + start, + aim, + reach + }; + + // find all the things we could maybe hit + size_t num = gi.BoxEdicts(self->absmin - reach_vec, self->absmax + reach_vec, targets, q_countof(targets), AREA_SOLID, fire_player_melee_BoxFilter, &data); + + if (!num) + return false; + + bool was_hit = false; + + for (size_t i = 0; i < num; i++) + { + edict_t *hit = targets[i]; + + if (!hit->inuse || !hit->takedamage) + continue; + + // do the damage + vec3_t closest_point_to_check = closest_point_to_box(start, hit->s.origin + hit->mins, hit->s.origin + hit->maxs); + + if (hit->svflags & SVF_MONSTER) + hit->pain_debounce_time -= random_time(5_ms, 75_ms); + + if (mod.id == MOD_CHAINFIST) + T_Damage(hit, self, self, aim, closest_point_to_check, -aim, damage, kick / 2, + DAMAGE_DESTROY_ARMOR | DAMAGE_NO_KNOCKBACK, mod); + else + T_Damage(hit, self, self, aim, closest_point_to_check, -aim, damage, kick / 2, DAMAGE_NO_KNOCKBACK, mod); + + was_hit = true; + } + + return was_hit; +} + +// ************************* +// NUKE +// ************************* + +constexpr gtime_t NUKE_DELAY = 4_sec; +constexpr gtime_t NUKE_TIME_TO_LIVE = 6_sec; +constexpr float NUKE_RADIUS = 512; +constexpr int32_t NUKE_DAMAGE = 400; +constexpr gtime_t NUKE_QUAKE_TIME = 3_sec; +constexpr float NUKE_QUAKE_STRENGTH = 100; + +THINK(Nuke_Quake) (edict_t *self) -> void +{ + uint32_t i; + edict_t *e; + + if (self->last_move_time < level.time) + { + gi.positioned_sound(self->s.origin, self, CHAN_AUTO, self->noise_index, 0.75, ATTN_NONE, 0); + self->last_move_time = level.time + 500_ms; + } + + for (i = 1, e = g_edicts + i; i < globals.num_edicts; i++, e++) + { + if (!e->inuse) + continue; + if (!e->client) + continue; + if (!e->groundentity) + continue; + + e->groundentity = nullptr; + e->velocity[0] += crandom() * 150; + e->velocity[1] += crandom() * 150; + e->velocity[2] = self->speed * (100.0f / e->mass); + } + + if (level.time < self->timestamp) + self->nextthink = level.time + FRAME_TIME_S; + else + G_FreeEdict(self); +} + +static void Nuke_Explode(edict_t *ent) +{ + + if (ent->teammaster->client) + PlayerNoise(ent->teammaster, ent->s.origin, PNOISE_IMPACT); + + T_RadiusNukeDamage(ent, ent->teammaster, (float) ent->dmg, ent, ent->dmg_radius, MOD_NUKE); + + if (ent->dmg > NUKE_DAMAGE) + gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage3.wav"), 1, ATTN_NORM, 0); + + gi.sound(ent, CHAN_NO_PHS_ADD | CHAN_VOICE, gi.soundindex("weapons/grenlx1a.wav"), 1, ATTN_NONE, 0); + + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_EXPLOSION1_BIG); + gi.WritePosition(ent->s.origin); + gi.multicast(ent->s.origin, MULTICAST_PHS, false); + + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_NUKEBLAST); + gi.WritePosition(ent->s.origin); + gi.multicast(ent->s.origin, MULTICAST_ALL, false); + + // become a quake + ent->svflags |= SVF_NOCLIENT; + ent->noise_index = gi.soundindex("world/rumble.wav"); + ent->think = Nuke_Quake; + ent->speed = NUKE_QUAKE_STRENGTH; + ent->timestamp = level.time + NUKE_QUAKE_TIME; + ent->nextthink = level.time + FRAME_TIME_S; + ent->last_move_time = 0_ms; +} + +DIE(nuke_die) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void +{ + self->takedamage = false; + if ((attacker) && !(strcmp(attacker->classname, "nuke"))) + { + G_FreeEdict(self); + return; + } + Nuke_Explode(self); +} + +THINK(Nuke_Think) (edict_t *ent) -> void +{ + float attenuation, default_atten = 1.8f; + int nuke_damage_multiplier; + player_muzzle_t muzzleflash; + + nuke_damage_multiplier = ent->dmg / NUKE_DAMAGE; + switch (nuke_damage_multiplier) + { + case 1: + attenuation = default_atten / 1.4f; + muzzleflash = MZ_NUKE1; + break; + case 2: + attenuation = default_atten / 2.0f; + muzzleflash = MZ_NUKE2; + break; + case 4: + attenuation = default_atten / 3.0f; + muzzleflash = MZ_NUKE4; + break; + case 8: + attenuation = default_atten / 5.0f; + muzzleflash = MZ_NUKE8; + break; + default: + attenuation = default_atten; + muzzleflash = MZ_NUKE1; + break; + } + + if (ent->wait < level.time.seconds()) + Nuke_Explode(ent); + else if (level.time >= (gtime_t::from_sec(ent->wait) - NUKE_TIME_TO_LIVE)) + { + ent->s.frame++; + + if (ent->s.frame > 11) + ent->s.frame = 6; + + if (gi.pointcontents(ent->s.origin) & (CONTENTS_SLIME | CONTENTS_LAVA)) + { + Nuke_Explode(ent); + return; + } + + ent->think = Nuke_Think; + ent->nextthink = level.time + 10_hz; + ent->health = 1; + ent->owner = nullptr; + + gi.WriteByte(svc_muzzleflash); + gi.WriteEntity(ent); + gi.WriteByte(muzzleflash); + gi.multicast(ent->s.origin, MULTICAST_PHS, false); + + if (ent->timestamp <= level.time) + { + if ((gtime_t::from_sec(ent->wait) - level.time) <= (NUKE_TIME_TO_LIVE / 2.0f)) + { + gi.sound(ent, CHAN_NO_PHS_ADD | CHAN_VOICE, gi.soundindex("weapons/nukewarn2.wav"), 1, attenuation, 0); + ent->timestamp = level.time + 300_ms; + } + else + { + gi.sound(ent, CHAN_NO_PHS_ADD | CHAN_VOICE, gi.soundindex("weapons/nukewarn2.wav"), 1, attenuation, 0); + ent->timestamp = level.time + 500_ms; + } + } + } + else + { + if (ent->timestamp <= level.time) + { + gi.sound(ent, CHAN_NO_PHS_ADD | CHAN_VOICE, gi.soundindex("weapons/nukewarn2.wav"), 1, attenuation, 0); + ent->timestamp = level.time + 1_sec; + } + ent->nextthink = level.time + FRAME_TIME_S; + } +} + +TOUCH(nuke_bounce) (edict_t *ent, edict_t *other, const trace_t &tr, bool other_touching_self) -> void +{ + if (tr.surface && tr.surface->id) + { + if (frandom() > 0.5f) + gi.sound(ent, CHAN_BODY, gi.soundindex("weapons/hgrenb1a.wav"), 1, ATTN_NORM, 0); + else + gi.sound(ent, CHAN_BODY, gi.soundindex("weapons/hgrenb2a.wav"), 1, ATTN_NORM, 0); + } +} + +void fire_nuke(edict_t *self, const vec3_t &start, const vec3_t &aimdir, int speed) +{ + edict_t *nuke; + vec3_t dir; + vec3_t forward, right, up; + int damage_modifier = P_DamageModifier(self); + + dir = vectoangles(aimdir); + AngleVectors(dir, forward, right, up); + + nuke = G_Spawn(); + nuke->s.origin = start; + nuke->velocity = aimdir * speed; + nuke->velocity += up * (200 + crandom() * 10.0f); + nuke->velocity += right * (crandom() * 10.0f); + nuke->movetype = MOVETYPE_BOUNCE; + nuke->clipmask = MASK_PROJECTILE; + nuke->solid = SOLID_BBOX; + nuke->s.effects |= EF_GRENADE; + nuke->s.renderfx |= RF_IR_VISIBLE; + nuke->mins = { -8, -8, 0 }; + nuke->maxs = { 8, 8, 16 }; + nuke->s.modelindex = gi.modelindex("models/weapons/g_nuke/tris.md2"); + nuke->owner = self; + nuke->teammaster = self; + nuke->nextthink = level.time + FRAME_TIME_S; + nuke->wait = (level.time + NUKE_DELAY + NUKE_TIME_TO_LIVE).seconds(); + nuke->think = Nuke_Think; + nuke->touch = nuke_bounce; + + nuke->health = 10000; + nuke->takedamage = true; + nuke->flags |= FL_DAMAGEABLE; + nuke->dmg = NUKE_DAMAGE * damage_modifier; + if (damage_modifier == 1) + nuke->dmg_radius = NUKE_RADIUS; + else + nuke->dmg_radius = NUKE_RADIUS + NUKE_RADIUS * (0.25f * (float) damage_modifier); + // this yields 1.0, 1.5, 2.0, 3.0 times radius + + nuke->classname = "nuke"; + nuke->die = nuke_die; + + gi.linkentity(nuke); +} + +// ************************* +// TESLA +// ************************* + +constexpr gtime_t TESLA_TIME_TO_LIVE = 30_sec; +constexpr float TESLA_DAMAGE_RADIUS = 128; +constexpr int32_t TESLA_DAMAGE = 3; +constexpr int32_t TESLA_KNOCKBACK = 8; + +constexpr gtime_t TESLA_ACTIVATE_TIME = 3_sec; + +constexpr int32_t TESLA_EXPLOSION_DAMAGE_MULT = 50; // this is the amount the damage is multiplied by for underwater explosions +constexpr float TESLA_EXPLOSION_RADIUS = 200; + +void tesla_remove(edict_t *self) +{ + edict_t *cur, *next; + + self->takedamage = false; + if (self->teamchain) + { + cur = self->teamchain; + while (cur) + { + next = cur->teamchain; + G_FreeEdict(cur); + cur = next; + } + } + else if (self->air_finished) + gi.Com_Print("tesla_mine without a field!\n"); + + self->owner = self->teammaster; // Going away, set the owner correctly. + // PGM - grenade explode does damage to self->enemy + self->enemy = nullptr; + + // play quad sound if quadded and an underwater explosion + if ((self->dmg_radius) && (self->dmg > (TESLA_DAMAGE * TESLA_EXPLOSION_DAMAGE_MULT))) + gi.sound(self, CHAN_ITEM, gi.soundindex("items/damage3.wav"), 1, ATTN_NORM, 0); + + Grenade_Explode(self); +} + +DIE(tesla_die) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void +{ + tesla_remove(self); +} + +void tesla_blow(edict_t *self) +{ + self->dmg *= TESLA_EXPLOSION_DAMAGE_MULT; + self->dmg_radius = TESLA_EXPLOSION_RADIUS; + tesla_remove(self); +} + +TOUCH(tesla_zap) (edict_t *self, edict_t *other, const trace_t &tr, bool other_touching_self) -> void +{ +} + +static BoxEdictsResult_t tesla_think_active_BoxFilter(edict_t *check, void *data) +{ + edict_t *self = (edict_t *) data; + + if (!check->inuse) + return BoxEdictsResult_t::Skip; + if (check == self) + return BoxEdictsResult_t::Skip; + if (check->health < 1) + return BoxEdictsResult_t::Skip; + // don't hit teammates + if (check->client) + { + if (!deathmatch->integer) + return BoxEdictsResult_t::Skip; + else if (CheckTeamDamage(check, self->teammaster)) + return BoxEdictsResult_t::Skip; + } + if (!(check->svflags & SVF_MONSTER) && !(check->flags & FL_DAMAGEABLE) && !check->client) + return BoxEdictsResult_t::Skip; + + // don't hit other teslas in SP/coop + if (!deathmatch->integer && check->classname && (check->flags & FL_TRAP)) + return BoxEdictsResult_t::Skip; + + return BoxEdictsResult_t::Keep; +} + +THINK(tesla_think_active) (edict_t *self) -> void +{ + int i, num; + static edict_t *touch[MAX_EDICTS]; + edict_t *hit; + vec3_t dir, start; + trace_t tr; + + if (level.time > self->air_finished) + { + tesla_remove(self); + return; + } + + start = self->s.origin; + start[2] += 16; + + num = gi.BoxEdicts(self->teamchain->absmin, self->teamchain->absmax, touch, MAX_EDICTS, AREA_SOLID, tesla_think_active_BoxFilter, self); + for (i = 0; i < num; i++) + { + // if the tesla died while zapping things, stop zapping. + if (!(self->inuse)) + break; + + hit = touch[i]; + if (!hit->inuse) + continue; + if (hit == self) + continue; + if (hit->health < 1) + continue; + // don't hit teammates + if (hit->client) + { + if (!deathmatch->integer) + continue; + else if (CheckTeamDamage(hit, self->teamchain->owner)) + continue; + } + if (!(hit->svflags & SVF_MONSTER) && !(hit->flags & FL_DAMAGEABLE) && !hit->client) + continue; + + tr = gi.traceline(start, hit->s.origin, self, MASK_PROJECTILE); + if (tr.fraction == 1 || tr.ent == hit) + { + dir = hit->s.origin - start; + + // PMM - play quad sound if it's above the "normal" damage + if (self->dmg > TESLA_DAMAGE) + gi.sound(self, CHAN_ITEM, gi.soundindex("items/damage3.wav"), 1, ATTN_NORM, 0); + + // PGM - don't do knockback to walking monsters + if ((hit->svflags & SVF_MONSTER) && !(hit->flags & (FL_FLY | FL_SWIM))) + T_Damage(hit, self, self->teammaster, dir, tr.endpos, tr.plane.normal, + self->dmg, 0, DAMAGE_NONE, MOD_TESLA); + else + T_Damage(hit, self, self->teammaster, dir, tr.endpos, tr.plane.normal, + self->dmg, TESLA_KNOCKBACK, DAMAGE_NONE, MOD_TESLA); + + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_LIGHTNING); + gi.WriteEntity(self); // source entity + gi.WriteEntity(hit); // destination entity + gi.WritePosition(start); + gi.WritePosition(tr.endpos); + gi.multicast(start, MULTICAST_PVS, false); + } + } + + if (self->inuse) + { + self->think = tesla_think_active; + self->nextthink = level.time + 10_hz; + } +} + +THINK(tesla_activate) (edict_t *self) -> void +{ + edict_t *trigger; + edict_t *search; + + if (gi.pointcontents(self->s.origin) & (CONTENTS_SLIME | CONTENTS_LAVA | CONTENTS_WATER)) + { + tesla_blow(self); + return; + } + + // only check for spawn points in deathmatch + if (deathmatch->integer) + { + search = nullptr; + while ((search = findradius(search, self->s.origin, 1.5f * TESLA_DAMAGE_RADIUS)) != nullptr) + { + // [Paril-KEX] don't allow traps to be placed near flags or teleporters + // if it's a monster or player with health > 0 + // or it's a player start point + // and we can see it + // blow up + if (search->classname && ((deathmatch->integer && + ((!strncmp(search->classname, "info_player_", 12)) || + (!strcmp(search->classname, "misc_teleporter_dest")) || + (!strncmp(search->classname, "item_flag_", 10))))) && + (visible(search, self))) + { + BecomeExplosion1(self); + return; + } + } + } + + trigger = G_Spawn(); + trigger->s.origin = self->s.origin; + trigger->mins = { -TESLA_DAMAGE_RADIUS, -TESLA_DAMAGE_RADIUS, self->mins[2] }; + trigger->maxs = { TESLA_DAMAGE_RADIUS, TESLA_DAMAGE_RADIUS, TESLA_DAMAGE_RADIUS }; + trigger->movetype = MOVETYPE_NONE; + trigger->solid = SOLID_TRIGGER; + trigger->owner = self; + trigger->touch = tesla_zap; + trigger->classname = "tesla trigger"; + // doesn't need to be marked as a teamslave since the move code for bounce looks for teamchains + gi.linkentity(trigger); + + self->s.angles = {}; + // clear the owner if in deathmatch + if (deathmatch->integer) + self->owner = nullptr; + self->teamchain = trigger; + self->think = tesla_think_active; + self->nextthink = level.time + FRAME_TIME_S; + self->air_finished = level.time + TESLA_TIME_TO_LIVE; +} + +THINK(tesla_think) (edict_t *ent) -> void +{ + if (gi.pointcontents(ent->s.origin) & (CONTENTS_SLIME | CONTENTS_LAVA)) + { + tesla_remove(ent); + return; + } + + ent->s.angles = {}; + + if (!(ent->s.frame)) + gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/teslaopen.wav"), 1, ATTN_NORM, 0); + + ent->s.frame++; + if (ent->s.frame > 14) + { + ent->s.frame = 14; + ent->think = tesla_activate; + ent->nextthink = level.time + 10_hz; + } + else + { + if (ent->s.frame > 9) + { + if (ent->s.frame == 10) + { + if (ent->owner && ent->owner->client) + { + PlayerNoise(ent->owner, ent->s.origin, PNOISE_WEAPON); // PGM + } + ent->s.skinnum = 1; + } + else if (ent->s.frame == 12) + ent->s.skinnum = 2; + else if (ent->s.frame == 14) + ent->s.skinnum = 3; + } + ent->think = tesla_think; + ent->nextthink = level.time + 10_hz; + } +} + +TOUCH(tesla_lava) (edict_t *ent, edict_t *other, const trace_t &tr, bool other_touching_self) -> void +{ + if (tr.contents & (CONTENTS_SLIME | CONTENTS_LAVA)) + { + tesla_blow(ent); + return; + } + + if (ent->velocity) + { + if (frandom() > 0.5f) + gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/hgrenb1a.wav"), 1, ATTN_NORM, 0); + else + gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/hgrenb2a.wav"), 1, ATTN_NORM, 0); + } +} + +void fire_tesla(edict_t *self, const vec3_t &start, const vec3_t &aimdir, int tesla_damage_multiplier, int speed) +{ + edict_t *tesla; + vec3_t dir; + vec3_t forward, right, up; + + dir = vectoangles(aimdir); + AngleVectors(dir, forward, right, up); + + tesla = G_Spawn(); + tesla->s.origin = start; + tesla->velocity = aimdir * speed; + + float gravityAdjustment = level.gravity / 800.f; + + tesla->velocity += up * (200 + crandom() * 10.0f) * gravityAdjustment; + tesla->velocity += right * (crandom() * 10.0f); + + tesla->s.angles = {}; + tesla->movetype = MOVETYPE_BOUNCE; + tesla->solid = SOLID_BBOX; + tesla->s.effects |= EF_GRENADE; + tesla->s.renderfx |= RF_IR_VISIBLE; + tesla->mins = { -12, -12, 0 }; + tesla->maxs = { 12, 12, 20 }; + tesla->s.modelindex = gi.modelindex("models/weapons/g_tesla/tris.md2"); + + tesla->owner = self; // PGM - we don't want it owned by self YET. + tesla->teammaster = self; + + tesla->wait = (level.time + TESLA_TIME_TO_LIVE).seconds(); + tesla->think = tesla_think; + tesla->nextthink = level.time + TESLA_ACTIVATE_TIME; + + // blow up on contact with lava & slime code + tesla->touch = tesla_lava; + + if (deathmatch->integer) + // PMM - lowered from 50 - 7/29/1998 + tesla->health = 20; + else + tesla->health = 50; // FIXME - change depending on skill? + + tesla->takedamage = true; + tesla->die = tesla_die; + tesla->dmg = TESLA_DAMAGE * tesla_damage_multiplier; + tesla->classname = "tesla_mine"; + tesla->flags |= ( FL_DAMAGEABLE | FL_TRAP ); + tesla->clipmask = (MASK_PROJECTILE | CONTENTS_SLIME | CONTENTS_LAVA) & ~CONTENTS_DEADMONSTER; + + // [Paril-KEX] + if (self->client && !G_ShouldPlayersCollide(true)) + tesla->clipmask &= ~CONTENTS_PLAYER; + + tesla->flags |= FL_MECHANICAL; + + gi.linkentity(tesla); +} + +// ************************* +// HEATBEAM +// ************************* + +static void fire_beams(edict_t *self, const vec3_t &start, const vec3_t &aimdir, const vec3_t &offset, int damage, int kick, int te_beam, int te_impact, mod_t mod) +{ + trace_t tr; + vec3_t dir; + vec3_t forward, right, up; + vec3_t end; + vec3_t water_start, endpoint; + bool water = false, underwater = false; + contents_t content_mask = MASK_PROJECTILE | MASK_WATER; + + // [Paril-KEX] + if (self->client && !G_ShouldPlayersCollide(true)) + content_mask &= ~CONTENTS_PLAYER; + + vec3_t beam_endpt; + + // tr = gi.traceline (self->s.origin, start, self, MASK_PROJECTILE); + // if (!(tr.fraction < 1.0)) + // { + dir = vectoangles(aimdir); + AngleVectors(dir, forward, right, up); + + end = start + (forward * 8192); + + if (gi.pointcontents(start) & MASK_WATER) + { + underwater = true; + water_start = start; + content_mask &= ~MASK_WATER; + } + + tr = gi.traceline(start, end, self, content_mask); + + // see if we hit water + if (tr.contents & MASK_WATER) + { + water = true; + water_start = tr.endpos; + + if (start != tr.endpos) + { + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_HEATBEAM_SPARKS); + // gi.WriteByte (50); + gi.WritePosition(water_start); + gi.WriteDir(tr.plane.normal); + // gi.WriteByte (8); + // gi.WriteShort (60); + gi.multicast(tr.endpos, MULTICAST_PVS, false); + } + // re-trace ignoring water this time + tr = gi.traceline(water_start, end, self, content_mask & ~MASK_WATER); + } + endpoint = tr.endpos; + // } + + // halve the damage if target underwater + if (water) + { + damage = damage / 2; + } + + // send gun puff / flash + if (!((tr.surface) && (tr.surface->flags & SURF_SKY))) + { + if (tr.fraction < 1.0f) + { + if (tr.ent->takedamage) + { + T_Damage(tr.ent, self, self, aimdir, tr.endpos, tr.plane.normal, damage, kick, DAMAGE_ENERGY, mod); + } + else + { + if ((!water) && !(tr.surface && (tr.surface->flags & SURF_SKY))) + { + // This is the truncated steam entry - uses 1+1+2 extra bytes of data + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_HEATBEAM_STEAM); + // gi.WriteByte (20); + gi.WritePosition(tr.endpos); + gi.WriteDir(tr.plane.normal); + // gi.WriteByte (0xe0); + // gi.WriteShort (60); + gi.multicast(tr.endpos, MULTICAST_PVS, false); + + if (self->client) + PlayerNoise(self, tr.endpos, PNOISE_IMPACT); + } + } + } + } + + // if went through water, determine where the end and make a bubble trail + if ((water) || (underwater)) + { + vec3_t pos; + + dir = tr.endpos - water_start; + dir.normalize(); + pos = tr.endpos + (dir * -2); + if (gi.pointcontents(pos) & MASK_WATER) + tr.endpos = pos; + else + tr = gi.traceline(pos, water_start, tr.ent, MASK_WATER); + + pos = water_start + tr.endpos; + pos *= 0.5f; + + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_BUBBLETRAIL2); + // gi.WriteByte (8); + gi.WritePosition(water_start); + gi.WritePosition(tr.endpos); + gi.multicast(pos, MULTICAST_PVS, false); + } + + if ((!underwater) && (!water)) + { + beam_endpt = tr.endpos; + } + else + { + beam_endpt = endpoint; + } + + gi.WriteByte(svc_temp_entity); + gi.WriteByte(te_beam); + gi.WriteEntity(self); + gi.WritePosition(start); + gi.WritePosition(beam_endpt); + gi.multicast(self->s.origin, MULTICAST_ALL, false); +} + +/* +================= +fire_heat + +Fires a single heat beam. Zap. +================= +*/ +void fire_heatbeam(edict_t *self, const vec3_t &start, const vec3_t &aimdir, const vec3_t &offset, int damage, int kick, bool monster) +{ + if (monster) + fire_beams(self, start, aimdir, offset, damage, kick, TE_MONSTER_HEATBEAM, TE_HEATBEAM_SPARKS, MOD_HEATBEAM); + else + fire_beams(self, start, aimdir, offset, damage, kick, TE_HEATBEAM, TE_HEATBEAM_SPARKS, MOD_HEATBEAM); +} + +// ************************* +// BLASTER 2 +// ************************* + +/* +================= +fire_blaster2 + +Fires a single green blaster bolt. Used by monsters, generally. +================= +*/ +TOUCH(blaster2_touch) (edict_t *self, edict_t *other, const trace_t &tr, bool other_touching_self) -> void +{ + mod_t mod; + int damagestat; + + if (other == self->owner) + return; + + if (tr.surface && (tr.surface->flags & SURF_SKY)) + { + G_FreeEdict(self); + return; + } + + if (self->owner && self->owner->client) + PlayerNoise(self->owner, self->s.origin, PNOISE_IMPACT); + + if (other->takedamage) + { + // the only time players will be firing blaster2 bolts will be from the + // defender sphere. + if (self->owner && self->owner->client) + mod = MOD_DEFENDER_SPHERE; + else + mod = MOD_BLASTER2; + + if (self->owner) + { + damagestat = self->owner->takedamage; + self->owner->takedamage = false; + if (self->dmg >= 5) + T_RadiusDamage(self, self->owner, (float) (self->dmg * 2), other, self->dmg_radius, DAMAGE_ENERGY, MOD_UNKNOWN); + T_Damage(other, self, self->owner, self->velocity, self->s.origin, tr.plane.normal, self->dmg, 1, DAMAGE_ENERGY, mod); + self->owner->takedamage = damagestat; + } + else + { + if (self->dmg >= 5) + T_RadiusDamage(self, self->owner, (float) (self->dmg * 2), other, self->dmg_radius, DAMAGE_ENERGY, MOD_UNKNOWN); + T_Damage(other, self, self->owner, self->velocity, self->s.origin, tr.plane.normal, self->dmg, 1, DAMAGE_ENERGY, mod); + } + } + else + { + // PMM - yeowch this will get expensive + if (self->dmg >= 5) + T_RadiusDamage(self, self->owner, (float) (self->dmg * 2), self->owner, self->dmg_radius, DAMAGE_ENERGY, MOD_UNKNOWN); + + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_BLASTER2); + gi.WritePosition(self->s.origin); + gi.WriteDir(tr.plane.normal); + gi.multicast(self->s.origin, MULTICAST_PHS, false); + } + + G_FreeEdict(self); +} + +void fire_blaster2(edict_t *self, const vec3_t &start, const vec3_t &dir, int damage, int speed, effects_t effect, bool hyper) +{ + edict_t *bolt; + trace_t tr; + + bolt = G_Spawn(); + bolt->s.origin = start; + bolt->s.old_origin = start; + bolt->s.angles = vectoangles(dir); + bolt->velocity = dir * speed; + bolt->svflags |= SVF_PROJECTILE; + bolt->movetype = MOVETYPE_FLYMISSILE; + bolt->clipmask = MASK_PROJECTILE; + bolt->flags |= FL_DODGE; + + // [Paril-KEX] + if (self->client && !G_ShouldPlayersCollide(true)) + bolt->clipmask &= ~CONTENTS_PLAYER; + + bolt->solid = SOLID_BBOX; + bolt->s.effects |= effect; + if (effect) + bolt->s.effects |= EF_TRACKER; + bolt->dmg_radius = 128; + bolt->s.modelindex = gi.modelindex("models/objects/laser/tris.md2"); + bolt->s.skinnum = 2; + bolt->s.scale = 2.5f; + bolt->touch = blaster2_touch; + + bolt->owner = self; + bolt->nextthink = level.time + 2_sec; + bolt->think = G_FreeEdict; + bolt->dmg = damage; + bolt->classname = "bolt"; + gi.linkentity(bolt); + + tr = gi.traceline(self->s.origin, bolt->s.origin, bolt, bolt->clipmask); + if (tr.fraction < 1.0f) + { + bolt->s.origin = tr.endpos + (tr.plane.normal * 1.f); + bolt->touch(bolt, tr.ent, tr, false); + } +} + +// ************************* +// tracker +// ************************* + +constexpr damageflags_t TRACKER_DAMAGE_FLAGS = (DAMAGE_NO_POWER_ARMOR | DAMAGE_ENERGY | DAMAGE_NO_KNOCKBACK); +constexpr damageflags_t TRACKER_IMPACT_FLAGS = (DAMAGE_NO_POWER_ARMOR | DAMAGE_ENERGY); + +constexpr gtime_t TRACKER_DAMAGE_TIME = 500_ms; + +THINK(tracker_pain_daemon_think) (edict_t *self) -> void +{ + constexpr vec3_t pain_normal = { 0, 0, 1 }; + int hurt; + + if (!self->inuse) + return; + + if ((level.time - self->timestamp) > TRACKER_DAMAGE_TIME) + { + if (!self->enemy->client) + self->enemy->s.effects &= ~EF_TRACKERTRAIL; + G_FreeEdict(self); + } + else + { + if (self->enemy->health > 0) + { + vec3_t center = (self->enemy->absmax + self->enemy->absmin) * 0.5f; + + T_Damage(self->enemy, self, self->owner, vec3_origin, center, pain_normal, + self->dmg, 0, TRACKER_DAMAGE_FLAGS, MOD_TRACKER); + + // if we kill the player, we'll be removed. + if (self->inuse) + { + // if we killed a monster, gib them. + if (self->enemy->health < 1) + { + if (self->enemy->gib_health) + hurt = -self->enemy->gib_health; + else + hurt = 500; + + T_Damage(self->enemy, self, self->owner, vec3_origin, center, + pain_normal, hurt, 0, TRACKER_DAMAGE_FLAGS, MOD_TRACKER); + } + + self->nextthink = level.time + 10_hz; + + if (self->enemy->client) + self->enemy->client->tracker_pain_time = self->nextthink; + else + self->enemy->s.effects |= EF_TRACKERTRAIL; + } + } + else + { + if (!self->enemy->client) + self->enemy->s.effects &= ~EF_TRACKERTRAIL; + G_FreeEdict(self); + } + } +} + +void tracker_pain_daemon_spawn(edict_t *owner, edict_t *enemy, int damage) +{ + edict_t *daemon; + + if (enemy == nullptr) + return; + + daemon = G_Spawn(); + daemon->classname = "pain daemon"; + daemon->think = tracker_pain_daemon_think; + daemon->nextthink = level.time; + daemon->timestamp = level.time; + daemon->owner = owner; + daemon->enemy = enemy; + daemon->dmg = damage; +} + +void tracker_explode(edict_t *self) +{ + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_TRACKER_EXPLOSION); + gi.WritePosition(self->s.origin); + gi.multicast(self->s.origin, MULTICAST_PHS, false); + + G_FreeEdict(self); +} + +TOUCH(tracker_touch) (edict_t *self, edict_t *other, const trace_t &tr, bool other_touching_self) -> void +{ + float damagetime; + + if (other == self->owner) + return; + + if (tr.surface && (tr.surface->flags & SURF_SKY)) + { + G_FreeEdict(self); + return; + } + + if (self->client) + PlayerNoise(self->owner, self->s.origin, PNOISE_IMPACT); + + if (other->takedamage) + { + if ((other->svflags & SVF_MONSTER) || other->client) + { + if (other->health > 0) // knockback only for living creatures + { + // PMM - kickback was times 4 .. reduced to 3 + // now this does no damage, just knockback + T_Damage(other, self, self->owner, self->velocity, self->s.origin, tr.plane.normal, + /* self->dmg */ 0, (self->dmg * 3), TRACKER_IMPACT_FLAGS, MOD_TRACKER); + + if (!(other->flags & (FL_FLY | FL_SWIM))) + other->velocity[2] += 140; + + damagetime = ((float) self->dmg) * 0.1f; + damagetime = damagetime / TRACKER_DAMAGE_TIME.seconds(); + + tracker_pain_daemon_spawn(self->owner, other, (int) damagetime); + } + else // lots of damage (almost autogib) for dead bodies + { + T_Damage(other, self, self->owner, self->velocity, self->s.origin, tr.plane.normal, + self->dmg * 4, (self->dmg * 3), TRACKER_IMPACT_FLAGS, MOD_TRACKER); + } + } + else // full damage in one shot for inanimate objects + { + T_Damage(other, self, self->owner, self->velocity, self->s.origin, tr.plane.normal, + self->dmg, (self->dmg * 3), TRACKER_IMPACT_FLAGS, MOD_TRACKER); + } + } + + tracker_explode(self); + return; +} + +THINK(tracker_fly) (edict_t *self) -> void +{ + vec3_t dest; + vec3_t dir; + vec3_t center; + + if ((!self->enemy) || (!self->enemy->inuse) || (self->enemy->health < 1)) + { + tracker_explode(self); + return; + } + + // PMM - try to hunt for center of enemy, if possible and not client + if (self->enemy->client) + { + dest = self->enemy->s.origin; + dest[2] += self->enemy->viewheight; + } + // paranoia + else if (!self->enemy->absmin || !self->enemy->absmax) + { + dest = self->enemy->s.origin; + } + else + { + center = (self->enemy->absmin + self->enemy->absmax) * 0.5f; + dest = center; + } + + dir = dest - self->s.origin; + dir.normalize(); + self->s.angles = vectoangles(dir); + self->velocity = dir * self->speed; + self->monsterinfo.saved_goal = dest; + + self->nextthink = level.time + 10_hz; +} + +void fire_tracker(edict_t *self, const vec3_t &start, const vec3_t &dir, int damage, int speed, edict_t *enemy) +{ + edict_t *bolt; + trace_t tr; + + bolt = G_Spawn(); + bolt->s.origin = start; + bolt->s.old_origin = start; + bolt->s.angles = vectoangles(dir); + bolt->velocity = dir * speed; + bolt->svflags |= SVF_PROJECTILE; + bolt->movetype = MOVETYPE_FLYMISSILE; + bolt->clipmask = MASK_PROJECTILE; + + // [Paril-KEX] + if (self->client && !G_ShouldPlayersCollide(true)) + bolt->clipmask &= ~CONTENTS_PLAYER; + + bolt->solid = SOLID_BBOX; + bolt->speed = (float) speed; + bolt->s.effects = EF_TRACKER; + bolt->s.sound = gi.soundindex("weapons/disrupt.wav"); + bolt->s.modelindex = gi.modelindex("models/proj/disintegrator/tris.md2"); + bolt->touch = tracker_touch; + bolt->enemy = enemy; + bolt->owner = self; + bolt->dmg = damage; + bolt->classname = "tracker"; + gi.linkentity(bolt); + + if (enemy) + { + bolt->nextthink = level.time + 10_hz; + bolt->think = tracker_fly; + } + else + { + bolt->nextthink = level.time + 10_sec; + bolt->think = G_FreeEdict; + } + + tr = gi.traceline(self->s.origin, bolt->s.origin, bolt, bolt->clipmask); + if (tr.fraction < 1.0f) + { + bolt->s.origin = tr.endpos + (tr.plane.normal * 1.f); + bolt->touch(bolt, tr.ent, tr, false); + } +} diff --git a/rerelease/rogue/g_rogue_phys.cpp b/rerelease/rogue/g_rogue_phys.cpp new file mode 100644 index 0000000..9c3a294 --- /dev/null +++ b/rerelease/rogue/g_rogue_phys.cpp @@ -0,0 +1,121 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. + +#include "../g_local.h" + +//============ +// ROGUE +/* +============= +SV_Physics_NewToss + +Toss, bounce, and fly movement. When on ground and no velocity, do nothing. With velocity, +slide. +============= +*/ +void SV_Physics_NewToss(edict_t *ent) +{ + trace_t trace; + vec3_t move; + // float backoff; + edict_t *slave; + bool wasinwater; + bool isinwater; + float speed, newspeed; + vec3_t old_origin; + // float firstmove; + // int mask; + + // regular thinking + SV_RunThink(ent); + + // if not a team captain, so movement will be handled elsewhere + if (ent->flags & FL_TEAMSLAVE) + return; + + wasinwater = ent->waterlevel; + + // find out what we're sitting on. + move = ent->s.origin; + move[2] -= 0.25f; + trace = gi.trace(ent->s.origin, ent->mins, ent->maxs, move, ent, ent->clipmask); + if (ent->groundentity && ent->groundentity->inuse) + ent->groundentity = trace.ent; + else + ent->groundentity = nullptr; + + // if we're sitting on something flat and have no velocity of our own, return. + if (ent->groundentity && (trace.plane.normal[2] == 1.0f) && + !ent->velocity[0] && !ent->velocity[1] && !ent->velocity[2]) + { + return; + } + + // store the old origin + old_origin = ent->s.origin; + + SV_CheckVelocity(ent); + + // add gravity + SV_AddGravity(ent); + + if (ent->avelocity[0] || ent->avelocity[1] || ent->avelocity[2]) + SV_AddRotationalFriction(ent); + + // add friction + speed = ent->velocity.length(); + if (ent->waterlevel) // friction for water movement + { + newspeed = speed - (sv_waterfriction * 6 * (float) ent->waterlevel); + if (newspeed < 0) + newspeed = 0; + newspeed /= speed; + ent->velocity *= newspeed; + } + else if (!ent->groundentity) // friction for air movement + { + newspeed = speed - ((sv_friction)); + if (newspeed < 0) + newspeed = 0; + newspeed /= speed; + ent->velocity *= newspeed; + } + else // use ground friction + { + newspeed = speed - (sv_friction * 6); + if (newspeed < 0) + newspeed = 0; + newspeed /= speed; + ent->velocity *= newspeed; + } + + SV_FlyMove(ent, gi.frame_time_s, ent->clipmask); + gi.linkentity(ent); + + G_TouchTriggers(ent); + + // check for water transition + wasinwater = (ent->watertype & MASK_WATER); + ent->watertype = gi.pointcontents(ent->s.origin); + isinwater = ent->watertype & MASK_WATER; + + if (isinwater) + ent->waterlevel = WATER_FEET; + else + ent->waterlevel = WATER_NONE; + + if (!wasinwater && isinwater) + gi.positioned_sound(old_origin, g_edicts, CHAN_AUTO, gi.soundindex("misc/h2ohit1.wav"), 1, 1, 0); + else if (wasinwater && !isinwater) + gi.positioned_sound(ent->s.origin, g_edicts, CHAN_AUTO, gi.soundindex("misc/h2ohit1.wav"), 1, 1, 0); + + // move teamslaves + for (slave = ent->teamchain; slave; slave = slave->teamchain) + { + slave->s.origin = ent->s.origin; + gi.linkentity(slave); + } +} + +// ROGUE +//============ \ No newline at end of file diff --git a/rerelease/rogue/g_rogue_spawn.cpp b/rerelease/rogue/g_rogue_spawn.cpp new file mode 100644 index 0000000..58bdd92 --- /dev/null +++ b/rerelease/rogue/g_rogue_spawn.cpp @@ -0,0 +1,367 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. + +#include "../g_local.h" + +// +// ROGUE +// + +// +// Monster spawning code +// +// Used by the carrier, the medic_commander, and the black widow +// +// The sequence to create a flying monster is: +// +// FindSpawnPoint - tries to find suitable spot to spawn the monster in +// CreateFlyMonster - this verifies the point as good and creates the monster + +// To create a ground walking monster: +// +// FindSpawnPoint - same thing +// CreateGroundMonster - this checks the volume and makes sure the floor under the volume is suitable +// + +// FIXME - for the black widow, if we want the stalkers coming in on the roof, we'll have to tweak some things + +// +// CreateMonster +// +edict_t *CreateMonster(const vec3_t &origin, const vec3_t &angles, const char *classname) +{ + edict_t *newEnt; + + newEnt = G_Spawn(); + + newEnt->s.origin = origin; + newEnt->s.angles = angles; + newEnt->classname = classname; + newEnt->monsterinfo.aiflags |= AI_DO_NOT_COUNT; + + newEnt->gravityVector = { 0, 0, -1 }; + ED_CallSpawn(newEnt); + newEnt->s.renderfx |= RF_IR_VISIBLE; + + return newEnt; +} + +edict_t *CreateFlyMonster(const vec3_t &origin, const vec3_t &angles, const vec3_t &mins, const vec3_t &maxs, const char *classname) +{ + if (!CheckSpawnPoint(origin, mins, maxs)) + return nullptr; + + return (CreateMonster(origin, angles, classname)); +} + +// This is just a wrapper for CreateMonster that looks down height # of CMUs and sees if there +// are bad things down there or not + +edict_t *CreateGroundMonster(const vec3_t &origin, const vec3_t &angles, const vec3_t &entMins, const vec3_t &entMaxs, const char *classname, float height) +{ + edict_t *newEnt; + + // check the ground to make sure it's there, it's relatively flat, and it's not toxic + if (!CheckGroundSpawnPoint(origin, entMins, entMaxs, height, -1.f)) + return nullptr; + + newEnt = CreateMonster(origin, angles, classname); + if (!newEnt) + return nullptr; + + return newEnt; +} + +// FindSpawnPoint +// PMM - this is used by the medic commander (possibly by the carrier) to find a good spawn point +// if the startpoint is bad, try above the startpoint for a bit + +bool FindSpawnPoint(const vec3_t &startpoint, const vec3_t &mins, const vec3_t &maxs, vec3_t &spawnpoint, float maxMoveUp, bool drop) +{ + spawnpoint = startpoint; + + // drop first + if (!drop || !M_droptofloor_generic(spawnpoint, mins, maxs, false, nullptr, MASK_MONSTERSOLID, false)) + { + spawnpoint = startpoint; + + // fix stuck if we couldn't drop initially + if (G_FixStuckObject_Generic(spawnpoint, mins, maxs, [] (const vec3_t &start, const vec3_t &mins, const vec3_t &maxs, const vec3_t &end) { + return gi.trace(start, mins, maxs, end, nullptr, MASK_MONSTERSOLID); + }) == stuck_result_t::NO_GOOD_POSITION) + return false; + + // fixed, so drop again + if (drop && !M_droptofloor_generic(spawnpoint, mins, maxs, false, nullptr, MASK_MONSTERSOLID, false)) + return false; // ??? + } + + return true; +} + +// FIXME - all of this needs to be tweaked to handle the new gravity rules +// if we ever want to spawn stuff on the roof + +// +// CheckSpawnPoint +// +// PMM - checks volume to make sure we can spawn a monster there (is it solid?) +// +// This is all fliers should need + +bool CheckSpawnPoint(const vec3_t &origin, const vec3_t &mins, const vec3_t &maxs) +{ + trace_t tr; + + if (!mins || !maxs) + return false; + + tr = gi.trace(origin, mins, maxs, origin, nullptr, MASK_MONSTERSOLID); + if (tr.startsolid || tr.allsolid) + return false; + + if (tr.ent != world) + return false; + + return true; +} + +// +// CheckGroundSpawnPoint +// +// PMM - used for walking monsters +// checks: +// 1) is there a ground within the specified height of the origin? +// 2) is the ground non-water? +// 3) is the ground flat enough to walk on? +// + +bool CheckGroundSpawnPoint(const vec3_t &origin, const vec3_t &entMins, const vec3_t &entMaxs, float height, float gravity) +{ + if (!CheckSpawnPoint(origin, entMins, entMaxs)) + return false; + + if (M_CheckBottom_Fast_Generic(origin + entMins, origin + entMaxs, false)) + return true; + + if (M_CheckBottom_Slow_Generic(origin, entMins, entMaxs, nullptr, MASK_MONSTERSOLID, false, false)) + return true; + + return false; +} + +// **************************** +// SPAWNGROW stuff +// **************************** + +constexpr gtime_t SPAWNGROW_LIFESPAN = 1000_ms; + +THINK(spawngrow_think) (edict_t *self) -> void +{ + if (level.time >= self->timestamp) + { + G_FreeEdict(self->target_ent); + G_FreeEdict(self); + return; + } + + self->s.angles += self->avelocity * gi.frame_time_s; + + float t = 1.f - ((level.time - self->teleport_time).seconds() / self->wait); + + self->s.scale = clamp(lerp(self->accel, self->decel, t) / 16.f, 0.001f, 16.f); + self->s.alpha = t * t; + + self->nextthink += FRAME_TIME_MS; +} + +static vec3_t SpawnGro_laser_pos(edict_t *ent) +{ + // pick random direction + float theta = frandom(2 * PIf); + float phi = acos(crandom()); + + vec3_t d { + sin(phi) * cos(theta), + sin(phi) * sin(theta), + cos(phi) + }; + + return ent->s.origin + (d * ent->owner->s.scale * 9.f); +} + +THINK(SpawnGro_laser_think) (edict_t *self) -> void +{ + self->s.old_origin = SpawnGro_laser_pos(self); + gi.linkentity(self); + self->nextthink = level.time + 1_ms; +} + +void SpawnGrow_Spawn(const vec3_t &startpos, float start_size, float end_size) +{ + edict_t *ent; + + ent = G_Spawn(); + ent->s.origin = startpos; + + ent->s.angles[0] = (float) irandom(360); + ent->s.angles[1] = (float) irandom(360); + ent->s.angles[2] = (float) irandom(360); + + ent->avelocity[0] = frandom(280.f, 360.f) * 2.f; + ent->avelocity[1] = frandom(280.f, 360.f) * 2.f; + ent->avelocity[2] = frandom(280.f, 360.f) * 2.f; + + ent->solid = SOLID_NOT; + ent->s.renderfx |= RF_IR_VISIBLE; + ent->movetype = MOVETYPE_NONE; + ent->classname = "spawngro"; + + ent->s.modelindex = gi.modelindex("models/items/spawngro3/tris.md2"); + ent->s.skinnum = 1; + + ent->accel = start_size; + ent->decel = end_size; + ent->think = spawngrow_think; + + ent->s.scale = clamp(start_size / 16.f, 0.001f, 8.f); + + ent->teleport_time = level.time; + ent->wait = SPAWNGROW_LIFESPAN.seconds(); + ent->timestamp = level.time + SPAWNGROW_LIFESPAN; + + ent->nextthink = level.time + FRAME_TIME_MS; + + gi.linkentity(ent); + + // [Paril-KEX] + edict_t *beam = ent->target_ent = G_Spawn(); + beam->s.modelindex = MODELINDEX_WORLD; + beam->s.renderfx = RF_BEAM_LIGHTNING | RF_NO_ORIGIN_LERP; + beam->s.frame = 1; + beam->s.skinnum = 0x30303030; + beam->classname = "spawngro_beam"; + beam->angle = end_size; + beam->owner = ent; + beam->s.origin = ent->s.origin; + beam->think = SpawnGro_laser_think; + beam->nextthink = level.time + 1_ms; + beam->s.old_origin = SpawnGro_laser_pos(beam); + gi.linkentity(beam); +} + +// **************************** +// WidowLeg stuff +// **************************** + +constexpr int32_t MAX_LEGSFRAME = 23; +constexpr gtime_t LEG_WAIT_TIME = 1_sec; + +void ThrowMoreStuff(edict_t *self, const vec3_t &point); +void ThrowSmallStuff(edict_t *self, const vec3_t &point); +void ThrowWidowGibLoc(edict_t *self, const char *gibname, int damage, gib_type_t type, const vec3_t *startpos, bool fade); +void ThrowWidowGibSized(edict_t *self, const char *gibname, int damage, gib_type_t type, const vec3_t *startpos, int hitsound, bool fade); + +THINK(widowlegs_think) (edict_t *self) -> void +{ + vec3_t offset; + vec3_t point; + vec3_t f, r, u; + + if (self->s.frame == 17) + { + offset = { 11.77f, -7.24f, 23.31f }; + AngleVectors(self->s.angles, f, r, u); + point = G_ProjectSource2(self->s.origin, offset, f, r, u); + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_EXPLOSION1); + gi.WritePosition(point); + gi.multicast(point, MULTICAST_ALL, false); + ThrowSmallStuff(self, point); + } + + if (self->s.frame < MAX_LEGSFRAME) + { + self->s.frame++; + self->nextthink = level.time + 10_hz; + return; + } + else if (self->wait == 0) + { + self->wait = (level.time + LEG_WAIT_TIME).seconds(); + } + if (level.time > gtime_t::from_sec(self->wait)) + { + AngleVectors(self->s.angles, f, r, u); + + offset = { -65.6f, -8.44f, 28.59f }; + point = G_ProjectSource2(self->s.origin, offset, f, r, u); + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_EXPLOSION1); + gi.WritePosition(point); + gi.multicast(point, MULTICAST_ALL, false); + ThrowSmallStuff(self, point); + + ThrowWidowGibSized(self, "models/monsters/blackwidow/gib1/tris.md2", 80 + (int) frandom(20.0f), GIB_METALLIC, &point, 0, true); + ThrowWidowGibSized(self, "models/monsters/blackwidow/gib2/tris.md2", 80 + (int) frandom(20.0f), GIB_METALLIC, &point, 0, true); + + offset = { -1.04f, -51.18f, 7.04f }; + point = G_ProjectSource2(self->s.origin, offset, f, r, u); + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_EXPLOSION1); + gi.WritePosition(point); + gi.multicast(point, MULTICAST_ALL, false); + ThrowSmallStuff(self, point); + + ThrowWidowGibSized(self, "models/monsters/blackwidow/gib1/tris.md2", 80 + (int) frandom(20.0f), GIB_METALLIC, &point, 0, true); + ThrowWidowGibSized(self, "models/monsters/blackwidow/gib2/tris.md2", 80 + (int) frandom(20.0f), GIB_METALLIC, &point, 0, true); + ThrowWidowGibSized(self, "models/monsters/blackwidow/gib3/tris.md2", 80 + (int) frandom(20.0f), GIB_METALLIC, &point, 0, true); + + G_FreeEdict(self); + return; + } + if ((level.time > gtime_t::from_sec(self->wait - 0.5f)) && (self->count == 0)) + { + self->count = 1; + AngleVectors(self->s.angles, f, r, u); + + offset = { 31, -88.7f, 10.96f }; + point = G_ProjectSource2(self->s.origin, offset, f, r, u); + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_EXPLOSION1); + gi.WritePosition(point); + gi.multicast(point, MULTICAST_ALL, false); + // ThrowSmallStuff (self, point); + + offset = { -12.67f, -4.39f, 15.68f }; + point = G_ProjectSource2(self->s.origin, offset, f, r, u); + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_EXPLOSION1); + gi.WritePosition(point); + gi.multicast(point, MULTICAST_ALL, false); + // ThrowSmallStuff (self, point); + + self->nextthink = level.time + 10_hz; + return; + } + self->nextthink = level.time + 10_hz; +} + +void Widowlegs_Spawn(const vec3_t &startpos, const vec3_t &angles) +{ + edict_t *ent; + + ent = G_Spawn(); + ent->s.origin = startpos; + ent->s.angles = angles; + ent->solid = SOLID_NOT; + ent->s.renderfx = RF_IR_VISIBLE; + ent->movetype = MOVETYPE_NONE; + ent->classname = "widowlegs"; + + ent->s.modelindex = gi.modelindex("models/monsters/legs/tris.md2"); + ent->think = widowlegs_think; + + ent->nextthink = level.time + 10_hz; + gi.linkentity(ent); +} diff --git a/rerelease/rogue/g_rogue_sphere.cpp b/rerelease/rogue/g_rogue_sphere.cpp new file mode 100644 index 0000000..edfa77e --- /dev/null +++ b/rerelease/rogue/g_rogue_sphere.cpp @@ -0,0 +1,710 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// g_sphere.c +// pmack +// april 1998 + +// defender - actively finds and shoots at enemies +// hunter - waits until < 25% health and vore ball tracks person who hurt you +// vengeance - kills person who killed you. + +#include "../g_local.h" + +constexpr gtime_t DEFENDER_LIFESPAN = 30_sec; +constexpr gtime_t HUNTER_LIFESPAN = 30_sec; +constexpr gtime_t VENGEANCE_LIFESPAN = 30_sec; +constexpr gtime_t MINIMUM_FLY_TIME = 15_sec; + +void LookAtKiller(edict_t *self, edict_t *inflictor, edict_t *attacker); + +void vengeance_touch(edict_t *self, edict_t *other, const trace_t &tr, bool other_touching_self); +void hunter_touch(edict_t *self, edict_t *other, const trace_t &tr, bool other_touching_self); + +// ************************* +// General Sphere Code +// ************************* + +// ================= +// ================= +THINK(sphere_think_explode) (edict_t *self) -> void +{ + if (self->owner && self->owner->client && !(self->spawnflags & SPHERE_DOPPLEGANGER)) + { + self->owner->client->owned_sphere = nullptr; + } + BecomeExplosion1(self); +} + +// ================= +// sphere_explode +// ================= +DIE(sphere_explode) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void +{ + sphere_think_explode(self); +} + +// ================= +// sphere_if_idle_die - if the sphere is not currently attacking, blow up. +// ================= +DIE(sphere_if_idle_die) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void +{ + if (!self->enemy) + sphere_think_explode(self); +} + +// ************************* +// Sphere Movement +// ************************* + +// ================= +// ================= +void sphere_fly(edict_t *self) +{ + vec3_t dest; + vec3_t dir; + + if (level.time >= gtime_t::from_sec(self->wait)) + { + sphere_think_explode(self); + return; + } + + dest = self->owner->s.origin; + dest[2] = self->owner->absmax[2] + 4; + + if (level.time.seconds() == level.time.seconds()) + { + if (!visible(self, self->owner)) + { + self->s.origin = dest; + gi.linkentity(self); + return; + } + } + + dir = dest - self->s.origin; + self->velocity = dir * 5; +} + +// ================= +// ================= +void sphere_chase(edict_t *self, int stupidChase) +{ + vec3_t dest; + vec3_t dir; + float dist; + + if (level.time >= gtime_t::from_sec(self->wait) || (self->enemy && self->enemy->health < 1)) + { + sphere_think_explode(self); + return; + } + + dest = self->enemy->s.origin; + if (self->enemy->client) + dest[2] += self->enemy->viewheight; + + if (visible(self, self->enemy) || stupidChase) + { + // if moving, hunter sphere uses active sound + if (!stupidChase) + self->s.sound = gi.soundindex("spheres/h_active.wav"); + + dir = dest - self->s.origin; + dir.normalize(); + self->s.angles = vectoangles(dir); + self->velocity = dir * 500; + self->monsterinfo.saved_goal = dest; + } + else if (!self->monsterinfo.saved_goal) + { + dir = self->enemy->s.origin - self->s.origin; + dist = dir.normalize(); + self->s.angles = vectoangles(dir); + + // if lurking, hunter sphere uses lurking sound + self->s.sound = gi.soundindex("spheres/h_lurk.wav"); + self->velocity = {}; + } + else + { + dir = self->monsterinfo.saved_goal - self->s.origin; + dist = dir.normalize(); + + if (dist > 1) + { + self->s.angles = vectoangles(dir); + + if (dist > 500) + self->velocity = dir * 500; + else if (dist < 20) + self->velocity = dir * (dist / gi.frame_time_s); + else + self->velocity = dir * dist; + + // if moving, hunter sphere uses active sound + if (!stupidChase) + self->s.sound = gi.soundindex("spheres/h_active.wav"); + } + else + { + dir = self->enemy->s.origin - self->s.origin; + dist = dir.normalize(); + self->s.angles = vectoangles(dir); + + // if not moving, hunter sphere uses lurk sound + if (!stupidChase) + self->s.sound = gi.soundindex("spheres/h_lurk.wav"); + + self->velocity = {}; + } + } +} + +// ************************* +// Attack related stuff +// ************************* + +// ================= +// ================= +void sphere_fire(edict_t *self, edict_t *enemy) +{ + vec3_t dest; + vec3_t dir; + + if (!enemy || level.time >= gtime_t::from_sec(self->wait)) + { + sphere_think_explode(self); + return; + } + + dest = enemy->s.origin; + self->s.effects |= EF_ROCKET; + + dir = dest - self->s.origin; + dir.normalize(); + self->s.angles = vectoangles(dir); + self->velocity = dir * 1000; + + self->touch = vengeance_touch; + self->think = sphere_think_explode; + self->nextthink = gtime_t::from_sec(self->wait); +} + +// ================= +// ================= +void sphere_touch(edict_t *self, edict_t *other, const trace_t &tr, mod_t mod) +{ + if (self->spawnflags.has(SPHERE_DOPPLEGANGER)) + { + if (other == self->teammaster) + return; + + self->takedamage = false; + self->owner = self->teammaster; + self->teammaster = nullptr; + } + else + { + if (other == self->owner) + return; + // PMM - don't blow up on bodies + if (!strcmp(other->classname, "bodyque")) + return; + } + + if (tr.surface && (tr.surface->flags & SURF_SKY)) + { + G_FreeEdict(self); + return; + } + + if (self->owner) + { + if (other->takedamage) + { + T_Damage(other, self, self->owner, self->velocity, self->s.origin, tr.plane.normal, + 10000, 1, DAMAGE_DESTROY_ARMOR, mod); + } + else + { + T_RadiusDamage(self, self->owner, 512, self->owner, 256, DAMAGE_NONE, mod); + } + } + + sphere_think_explode(self); +} + +// ================= +// ================= +TOUCH(vengeance_touch) (edict_t *self, edict_t *other, const trace_t &tr, bool other_touching_self) -> void +{ + if (self->spawnflags.has(SPHERE_DOPPLEGANGER)) + sphere_touch(self, other, tr, MOD_DOPPLE_VENGEANCE); + else + sphere_touch(self, other, tr, MOD_VENGEANCE_SPHERE); +} + +// ================= +// ================= +TOUCH(hunter_touch) (edict_t *self, edict_t *other, const trace_t &tr, bool other_touching_self) -> void +{ + edict_t *owner; + + // don't blow up if you hit the world.... sheesh. + if (other == world) + return; + + if (self->owner) + { + // if owner is flying with us, make sure they stop too. + owner = self->owner; + if (owner->flags & FL_SAM_RAIMI) + { + owner->velocity = {}; + owner->movetype = MOVETYPE_NONE; + gi.linkentity(owner); + } + } + + if (self->spawnflags.has(SPHERE_DOPPLEGANGER)) + sphere_touch(self, other, tr, MOD_DOPPLE_HUNTER); + else + sphere_touch(self, other, tr, MOD_HUNTER_SPHERE); +} + +// ================= +// ================= +void defender_shoot(edict_t *self, edict_t *enemy) +{ + vec3_t dir; + vec3_t start; + + if (!(enemy->inuse) || enemy->health <= 0) + return; + + if (enemy == self->owner) + return; + + dir = enemy->s.origin - self->s.origin; + dir.normalize(); + + if (self->monsterinfo.attack_finished > level.time) + return; + + if (!visible(self, self->enemy)) + return; + + start = self->s.origin; + start[2] += 2; + fire_blaster2(self->owner, start, dir, 10, 1000, EF_BLASTER, 0); + + self->monsterinfo.attack_finished = level.time + 400_ms; +} + +// ************************* +// Activation Related Stuff +// ************************* + +// ================= +// ================= +void body_gib(edict_t *self) +{ + gi.sound(self, CHAN_BODY, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0); + ThrowGibs(self, 50, { + { 4, "models/objects/gibs/sm_meat/tris.md2" }, + { "models/objects/gibs/skull/tris.md2" } + }); +} + +// ================= +// ================= +PAIN(hunter_pain) (edict_t *self, edict_t *other, float kick, int damage, const mod_t &mod) -> void +{ + edict_t *owner; + float dist; + vec3_t dir; + + if (self->enemy) + return; + + owner = self->owner; + + if (!(self->spawnflags & SPHERE_DOPPLEGANGER)) + { + if (owner && (owner->health > 0)) + return; + + // PMM + if (other == owner) + return; + // pmm + } + else + { + // if fired by a doppleganger, set it to 10 second timeout + self->wait = (level.time + MINIMUM_FLY_TIME).seconds(); + } + + if ((gtime_t::from_sec(self->wait) - level.time) < MINIMUM_FLY_TIME) + self->wait = (level.time + MINIMUM_FLY_TIME).seconds(); + self->s.effects |= EF_BLASTER | EF_TRACKER; + self->touch = hunter_touch; + self->enemy = other; + + // if we're not owned by a player, no sam raimi + // if we're spawned by a doppleganger, no sam raimi + if (self->spawnflags.has(SPHERE_DOPPLEGANGER) || !(owner && owner->client)) + return; + + // sam raimi cam is disabled if FORCE_RESPAWN is set. + // sam raimi cam is also disabled if huntercam->value is 0. + if (!g_dm_force_respawn->integer && huntercam->integer) + { + dir = other->s.origin - self->s.origin; + dist = dir.length(); + + if (owner && (dist >= 192)) + { + // detach owner from body and send him flying + owner->movetype = MOVETYPE_FLYMISSILE; + + // gib like we just died, even though we didn't, really. + body_gib(owner); + + // move the sphere to the owner's current viewpoint. + // we know it's a valid spot (or will be momentarily) + self->s.origin = owner->s.origin; + self->s.origin[2] += owner->viewheight; + + // move the player's origin to the sphere's new origin + owner->s.origin = self->s.origin; + owner->s.angles = self->s.angles; + owner->client->v_angle = self->s.angles; + owner->mins = { -5, -5, -5 }; + owner->maxs = { 5, 5, 5 }; + owner->client->ps.fov = 140; + owner->s.modelindex = 0; + owner->s.modelindex2 = 0; + owner->viewheight = 8; + owner->solid = SOLID_NOT; + owner->flags |= FL_SAM_RAIMI; + gi.linkentity(owner); + + self->solid = SOLID_BBOX; + gi.linkentity(self); + } + } +} + +// ================= +// ================= +PAIN(defender_pain) (edict_t *self, edict_t *other, float kick, int damage, const mod_t &mod) -> void +{ + // PMM + if (other == self->owner) + return; + + // pmm + self->enemy = other; +} + +// ================= +// ================= +PAIN(vengeance_pain) (edict_t *self, edict_t *other, float kick, int damage, const mod_t &mod) -> void +{ + if (self->enemy) + return; + + if (!(self->spawnflags & SPHERE_DOPPLEGANGER)) + { + if (self->owner && self->owner->health >= 25) + return; + + // PMM + if (other == self->owner) + return; + // pmm + } + else + { + self->wait = (level.time + MINIMUM_FLY_TIME).seconds(); + } + + if ((gtime_t::from_sec(self->wait) - level.time) < MINIMUM_FLY_TIME) + self->wait = (level.time + MINIMUM_FLY_TIME).seconds(); + self->s.effects |= EF_ROCKET; + self->touch = vengeance_touch; + self->enemy = other; +} + +// ************************* +// Think Functions +// ************************* + +// =================== +// =================== +THINK(defender_think) (edict_t *self) -> void +{ + if (!self->owner) + { + G_FreeEdict(self); + return; + } + + // if we've exited the level, just remove ourselves. + if (level.intermissiontime) + { + sphere_think_explode(self); + return; + } + + if (self->owner->health <= 0) + { + sphere_think_explode(self); + return; + } + + self->s.frame++; + if (self->s.frame > 19) + self->s.frame = 0; + + if (self->enemy) + { + if (self->enemy->health > 0) + defender_shoot(self, self->enemy); + else + self->enemy = nullptr; + } + + sphere_fly(self); + + if (self->inuse) + self->nextthink = level.time + 10_hz; +} + +// ================= +// ================= +THINK(hunter_think) (edict_t *self) -> void +{ + // if we've exited the level, just remove ourselves. + if (level.intermissiontime) + { + sphere_think_explode(self); + return; + } + + edict_t *owner = self->owner; + + if (!owner && !(self->spawnflags & SPHERE_DOPPLEGANGER)) + { + G_FreeEdict(self); + return; + } + + if (owner) + self->ideal_yaw = owner->s.angles[YAW]; + else if (self->enemy) // fired by doppleganger + { + vec3_t dir = self->enemy->s.origin - self->s.origin; + self->ideal_yaw = vectoyaw(dir); + } + + M_ChangeYaw(self); + + if (self->enemy) + { + sphere_chase(self, 0); + + // deal with sam raimi cam + if (owner && (owner->flags & FL_SAM_RAIMI)) + { + if (self->inuse) + { + owner->movetype = MOVETYPE_FLYMISSILE; + LookAtKiller(owner, self, self->enemy); + // owner is flying with us, move him too + owner->movetype = MOVETYPE_FLYMISSILE; + owner->viewheight = (int) (self->s.origin[2] - owner->s.origin[2]); + owner->s.origin = self->s.origin; + owner->velocity = self->velocity; + owner->mins = {}; + owner->maxs = {}; + gi.linkentity(owner); + } + else // sphere timed out + { + owner->velocity = {}; + owner->movetype = MOVETYPE_NONE; + gi.linkentity(owner); + } + } + } + else + sphere_fly(self); + + if (self->inuse) + self->nextthink = level.time + 10_hz; +} + +// ================= +// ================= +THINK(vengeance_think) (edict_t *self) -> void +{ + // if we've exited the level, just remove ourselves. + if (level.intermissiontime) + { + sphere_think_explode(self); + return; + } + + if (!(self->owner) && !(self->spawnflags & SPHERE_DOPPLEGANGER)) + { + G_FreeEdict(self); + return; + } + + if (self->enemy) + sphere_chase(self, 1); + else + sphere_fly(self); + + if (self->inuse) + self->nextthink = level.time + 10_hz; +} + +// ************************* +// Spawning / Creation +// ************************* + +// monsterinfo_t +// ================= +// ================= +edict_t *Sphere_Spawn(edict_t *owner, spawnflags_t spawnflags) +{ + edict_t *sphere; + + sphere = G_Spawn(); + sphere->s.origin = owner->s.origin; + sphere->s.origin[2] = owner->absmax[2]; + sphere->s.angles[YAW] = owner->s.angles[YAW]; + sphere->solid = SOLID_BBOX; + sphere->clipmask = MASK_PROJECTILE; + sphere->s.renderfx = RF_FULLBRIGHT | RF_IR_VISIBLE; + sphere->movetype = MOVETYPE_FLYMISSILE; + + if (spawnflags.has(SPHERE_DOPPLEGANGER)) + sphere->teammaster = owner->teammaster; + else + sphere->owner = owner; + + sphere->classname = "sphere"; + sphere->yaw_speed = 40; + sphere->monsterinfo.attack_finished = 0_ms; + sphere->spawnflags = spawnflags; // need this for the HUD to recognize sphere + // PMM + sphere->takedamage = false; + + switch ((spawnflags & SPHERE_TYPE).value) + { + case SPHERE_DEFENDER.value: + sphere->s.modelindex = gi.modelindex("models/items/defender/tris.md2"); + sphere->s.modelindex2 = gi.modelindex("models/items/shell/tris.md2"); + sphere->s.sound = gi.soundindex("spheres/d_idle.wav"); + sphere->pain = defender_pain; + sphere->wait = (level.time + DEFENDER_LIFESPAN).seconds(); + sphere->die = sphere_explode; + sphere->think = defender_think; + break; + case SPHERE_HUNTER.value: + sphere->s.modelindex = gi.modelindex("models/items/hunter/tris.md2"); + sphere->s.sound = gi.soundindex("spheres/h_idle.wav"); + sphere->wait = (level.time + HUNTER_LIFESPAN).seconds(); + sphere->pain = hunter_pain; + sphere->die = sphere_if_idle_die; + sphere->think = hunter_think; + break; + case SPHERE_VENGEANCE.value: + sphere->s.modelindex = gi.modelindex("models/items/vengnce/tris.md2"); + sphere->s.sound = gi.soundindex("spheres/v_idle.wav"); + sphere->wait = (level.time + VENGEANCE_LIFESPAN).seconds(); + sphere->pain = vengeance_pain; + sphere->die = sphere_if_idle_die; + sphere->think = vengeance_think; + sphere->avelocity = { 30, 30, 0 }; + break; + default: + gi.Com_Print("Tried to create an invalid sphere\n"); + G_FreeEdict(sphere); + return nullptr; + } + + sphere->nextthink = level.time + 10_hz; + + gi.linkentity(sphere); + + return sphere; +} + +// ================= +// Own_Sphere - attach the sphere to the client so we can +// directly access it later +// ================= +void Own_Sphere(edict_t *self, edict_t *sphere) +{ + if (!sphere) + return; + + // ownership only for players + if (self->client) + { + // if they don't have one + if (!(self->client->owned_sphere)) + { + self->client->owned_sphere = sphere; + } + // they already have one, take care of the old one + else + { + if (self->client->owned_sphere->inuse) + { + G_FreeEdict(self->client->owned_sphere); + self->client->owned_sphere = sphere; + } + else + { + self->client->owned_sphere = sphere; + } + } + } +} + +// ================= +// ================= +void Defender_Launch(edict_t *self) +{ + edict_t *sphere; + + sphere = Sphere_Spawn(self, SPHERE_DEFENDER); + Own_Sphere(self, sphere); +} + +// ================= +// ================= +void Hunter_Launch(edict_t *self) +{ + edict_t *sphere; + + sphere = Sphere_Spawn(self, SPHERE_HUNTER); + Own_Sphere(self, sphere); +} + +// ================= +// ================= +void Vengeance_Launch(edict_t *self) +{ + edict_t *sphere; + + sphere = Sphere_Spawn(self, SPHERE_VENGEANCE); + Own_Sphere(self, sphere); +} diff --git a/rerelease/rogue/g_rogue_utils.cpp b/rerelease/rogue/g_rogue_utils.cpp new file mode 100644 index 0000000..f9dfcc7 --- /dev/null +++ b/rerelease/rogue/g_rogue_utils.cpp @@ -0,0 +1,47 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. + +#include "../g_local.h" + +/* +================= +findradius2 + +Returns entities that have origins within a spherical area + +ROGUE - tweaks for performance for tesla specific code +only returns entities that can be damaged +only returns entities that are FL_DAMAGEABLE + +findradius2 (origin, radius) +================= +*/ +edict_t *findradius2(edict_t *from, const vec3_t &org, float rad) +{ + // rad must be positive + vec3_t eorg; + int j; + + if (!from) + from = g_edicts; + else + from++; + for (; from < &g_edicts[globals.num_edicts]; from++) + { + if (!from->inuse) + continue; + if (from->solid == SOLID_NOT) + continue; + if (!from->takedamage) + continue; + if (!(from->flags & FL_DAMAGEABLE)) + continue; + for (j = 0; j < 3; j++) + eorg[j] = org[j] - (from->s.origin[j] + (from->mins[j] + from->maxs[j]) * 0.5f); + if (eorg.length() > rad) + continue; + return from; + } + + return nullptr; +} \ No newline at end of file diff --git a/rerelease/rogue/m_rogue_carrier.cpp b/rerelease/rogue/m_rogue_carrier.cpp new file mode 100644 index 0000000..d4d56ab --- /dev/null +++ b/rerelease/rogue/m_rogue_carrier.cpp @@ -0,0 +1,1238 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. + +/* +============================================================================== + +carrier + +============================================================================== +*/ + +// self->timestamp used for frame calculations in grenade & spawn code +// self->monsterinfo.fire_wait used to prevent rapid refire of rocket launcher + +#include "../g_local.h" +#include "m_rogue_carrier.h" +#include "../m_flash.h" + +// nb: specifying flyer multiple times so it has a higher chance +constexpr const char *default_reinforcements = "monster_flyer 1;monster_flyer 1;monster_flyer 1;monster_kamikaze 1"; +constexpr int32_t default_monster_slots_base = 3; + +constexpr gtime_t CARRIER_ROCKET_TIME = 2_sec; // number of seconds between rocket shots +constexpr int32_t CARRIER_ROCKET_SPEED = 750; +constexpr gtime_t RAIL_FIRE_TIME = 3_sec; + +bool infront(edict_t *self, edict_t *other); +bool inback(edict_t *self, edict_t *other); +bool below(edict_t *self, edict_t *other); +void drawbbox(edict_t *self); + +void ED_CallSpawn(edict_t *ent); + +static int sound_pain1; +static int sound_pain2; +static int sound_pain3; +static int sound_death; +static int sound_sight; +static int sound_rail; +static int sound_spawn; + +static int sound_cg_down, sound_cg_loop, sound_cg_up; + +float orig_yaw_speed; + +void M_SetupReinforcements(const char *reinforcements, reinforcement_list_t &list); +std::array M_PickReinforcements(edict_t *self, int32_t &num_chosen, int32_t max_slots); + +extern const mmove_t flyer_move_attack2, flyer_move_attack3, flyer_move_kamikaze; + +void carrier_run(edict_t *self); +void carrier_dead(edict_t *self); +void carrier_attack_mg(edict_t *self); +void carrier_reattack_mg(edict_t *self); + +void carrier_attack_gren(edict_t *self); +void carrier_reattack_gren(edict_t *self); + +void carrier_start_spawn(edict_t *self); +void carrier_spawn_check(edict_t *self); +void carrier_prep_spawn(edict_t *self); + +void CarrierMachineGunHold(edict_t *self); +void CarrierRocket(edict_t *self); + +MONSTERINFO_SIGHT(carrier_sight) (edict_t *self, edict_t *other) -> void +{ + gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); +} + +// +// this is the smarts for the rocket launcher in coop +// +// if there is a player behind/below the carrier, and we can shoot, and we can trace a LOS to them .. +// pick one of the group, and let it rip +void CarrierCoopCheck(edict_t *self) +{ + // no more than 8 players in coop, so.. + std::array targets; + uint32_t num_targets = 0; + int32_t target; + edict_t *ent; + trace_t tr; + + // if we're not in coop, this is a noop + // [Paril-KEX] might as well let this work in SP too, so he fires it + // if you get below him + //if (!coop->integer) + // return; + // if we are, and we have recently fired, bail + if (self->monsterinfo.fire_wait > level.time) + return; + + targets = {}; + + // cycle through players + for (uint32_t player = 1; player <= game.maxclients; player++) + { + ent = &g_edicts[player]; + if (!ent->inuse) + continue; + if (!ent->client) + continue; + if (inback(self, ent) || below(self, ent)) + { + tr = gi.traceline(self->s.origin, ent->s.origin, self, MASK_SOLID); + if (tr.fraction == 1.0f) + targets[num_targets++] = ent; + } + } + + if (!num_targets) + return; + + // get a number from 0 to (num_targets-1) + target = irandom(num_targets); + + // make sure to prevent rapid fire rockets + self->monsterinfo.fire_wait = level.time + CARRIER_ROCKET_TIME; + + // save off the real enemy + ent = self->enemy; + // set the new guy as temporary enemy + self->enemy = targets[target]; + CarrierRocket(self); + // put the real enemy back + self->enemy = ent; + + // we're done + return; +} + +void CarrierGrenade(edict_t *self) +{ + vec3_t start; + vec3_t forward, right, up; + vec3_t aim; + monster_muzzleflash_id_t flash_number; + float direction; // from lower left to upper right, or lower right to upper left + float spreadR, spreadU; + int mytime; + + CarrierCoopCheck(self); + + if (!self->enemy) + return; + + if (frandom() < 0.5f) + direction = -1.0f; + else + direction = 1.0f; + + mytime = (int) ((level.time - self->timestamp) / 0.4f).seconds(); + + if (mytime == 0) + { + spreadR = 0.15f * direction; + spreadU = 0.1f - 0.1f * direction; + } + else if (mytime == 1) + { + spreadR = 0; + spreadU = 0.1f; + } + else if (mytime == 2) + { + spreadR = -0.15f * direction; + spreadU = 0.1f - -0.1f * direction; + } + else if (mytime == 3) + { + spreadR = 0; + spreadU = 0.1f; + } + else + { + // error, shoot straight + spreadR = 0; + spreadU = 0; + } + + AngleVectors(self->s.angles, forward, right, up); + start = M_ProjectFlashSource(self, monster_flash_offset[MZ2_CARRIER_GRENADE], forward, right); + + aim = self->enemy->s.origin - start; + aim.normalize(); + + aim += (right * spreadR); + aim += (up * spreadU); + + if (aim[2] > 0.15f) + aim[2] = 0.15f; + else if (aim[2] < -0.5f) + aim[2] = -0.5f; + + flash_number = MZ2_GUNNER_GRENADE_1; + monster_fire_grenade(self, start, aim, 50, 600, flash_number, (crandom_open() * 10.0f), 200.f + (crandom_open() * 10.0f)); +} + +void CarrierPredictiveRocket(edict_t *self) +{ + vec3_t forward, right; + vec3_t start; + vec3_t dir; + + AngleVectors(self->s.angles, forward, right, nullptr); + + // 1 + start = M_ProjectFlashSource(self, monster_flash_offset[MZ2_CARRIER_ROCKET_1], forward, right); + PredictAim(self, self->enemy, start, CARRIER_ROCKET_SPEED, false, -0.3f, &dir, nullptr); + monster_fire_rocket(self, start, dir, 50, CARRIER_ROCKET_SPEED, MZ2_CARRIER_ROCKET_1); + + // 2 + start = M_ProjectFlashSource(self, monster_flash_offset[MZ2_CARRIER_ROCKET_2], forward, right); + PredictAim(self, self->enemy, start, CARRIER_ROCKET_SPEED, false, -0.15f, &dir, nullptr); + monster_fire_rocket(self, start, dir, 50, CARRIER_ROCKET_SPEED, MZ2_CARRIER_ROCKET_2); + + // 3 + start = M_ProjectFlashSource(self, monster_flash_offset[MZ2_CARRIER_ROCKET_3], forward, right); + PredictAim(self, self->enemy, start, CARRIER_ROCKET_SPEED, false, 0, &dir, nullptr); + monster_fire_rocket(self, start, dir, 50, CARRIER_ROCKET_SPEED, MZ2_CARRIER_ROCKET_3); + + // 4 + start = M_ProjectFlashSource(self, monster_flash_offset[MZ2_CARRIER_ROCKET_4], forward, right); + PredictAim(self, self->enemy, start, CARRIER_ROCKET_SPEED, false, 0.15f, &dir, nullptr); + monster_fire_rocket(self, start, dir, 50, CARRIER_ROCKET_SPEED, MZ2_CARRIER_ROCKET_4); +} + +void CarrierRocket(edict_t *self) +{ + vec3_t forward, right; + vec3_t start; + vec3_t dir; + vec3_t vec; + + if (self->enemy) + { + if (self->enemy->client && frandom() < 0.5f) + { + CarrierPredictiveRocket(self); + return; + } + } + else + return; + + AngleVectors(self->s.angles, forward, right, nullptr); + + // 1 + start = M_ProjectFlashSource(self, monster_flash_offset[MZ2_CARRIER_ROCKET_1], forward, right); + vec = self->enemy->s.origin; + vec[2] -= 15; + dir = vec - start; + dir.normalize(); + dir += (right * 0.4f); + dir.normalize(); + monster_fire_rocket(self, start, dir, 50, 500, MZ2_CARRIER_ROCKET_1); + + // 2 + start = M_ProjectFlashSource(self, monster_flash_offset[MZ2_CARRIER_ROCKET_2], forward, right); + vec = self->enemy->s.origin; + dir = vec - start; + dir.normalize(); + dir += (right * 0.025f); + dir.normalize(); + monster_fire_rocket(self, start, dir, 50, 500, MZ2_CARRIER_ROCKET_2); + + // 3 + start = M_ProjectFlashSource(self, monster_flash_offset[MZ2_CARRIER_ROCKET_3], forward, right); + vec = self->enemy->s.origin; + dir = vec - start; + dir.normalize(); + dir += (right * -0.025f); + dir.normalize(); + monster_fire_rocket(self, start, dir, 50, 500, MZ2_CARRIER_ROCKET_3); + + // 4 + start = M_ProjectFlashSource(self, monster_flash_offset[MZ2_CARRIER_ROCKET_4], forward, right); + vec = self->enemy->s.origin; + vec[2] -= 15; + dir = vec - start; + dir.normalize(); + dir += (right * -0.4f); + dir.normalize(); + monster_fire_rocket(self, start, dir, 50, 500, MZ2_CARRIER_ROCKET_4); +} + +void carrier_firebullet_right(edict_t *self) +{ + vec3_t forward, right, start; + monster_muzzleflash_id_t flashnum; + + // if we're in manual steering mode, it means we're leaning down .. use the lower shot + if (self->monsterinfo.aiflags & AI_MANUAL_STEERING) + flashnum = MZ2_CARRIER_MACHINEGUN_R2; + else + flashnum = MZ2_CARRIER_MACHINEGUN_R1; + + AngleVectors(self->s.angles, forward, right, nullptr); + start = M_ProjectFlashSource(self, monster_flash_offset[flashnum], forward, right); + PredictAim(self, self->enemy, start, 0, true, -0.3f, &forward, nullptr); + monster_fire_bullet(self, start, forward, 6, 4, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, flashnum); +} + +void carrier_firebullet_left(edict_t *self) +{ + vec3_t forward, right, start; + monster_muzzleflash_id_t flashnum; + + // if we're in manual steering mode, it means we're leaning down .. use the lower shot + if (self->monsterinfo.aiflags & AI_MANUAL_STEERING) + flashnum = MZ2_CARRIER_MACHINEGUN_L2; + else + flashnum = MZ2_CARRIER_MACHINEGUN_L1; + + AngleVectors(self->s.angles, forward, right, nullptr); + start = M_ProjectFlashSource(self, monster_flash_offset[flashnum], forward, right); + PredictAim(self, self->enemy, start, 0, true, -0.3f, &forward, nullptr); + monster_fire_bullet(self, start, forward, 6, 4, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, flashnum); +} + +void CarrierMachineGun(edict_t *self) +{ + CarrierCoopCheck(self); + if (self->enemy) + carrier_firebullet_left(self); + if (self->enemy) + carrier_firebullet_right(self); +} + +void CarrierSpawn(edict_t *self) +{ + vec3_t f, r, offset, startpoint, spawnpoint; + edict_t *ent; + + // offset = { 105, 0, -30 }; // real distance needed is (sqrt (56*56*2) + sqrt(16*16*2)) or 101.8 + offset = { 105, 0, -58 }; // real distance needed is (sqrt (56*56*2) + sqrt(16*16*2)) or 101.8 + AngleVectors(self->s.angles, f, r, nullptr); + + startpoint = M_ProjectFlashSource(self, offset, f, r); + + if (self->monsterinfo.chosen_reinforcements[0] == 255) + return; + + auto &reinforcement = self->monsterinfo.reinforcements.reinforcements[self->monsterinfo.chosen_reinforcements[0]]; + + if (FindSpawnPoint(startpoint, reinforcement.mins, reinforcement.maxs, spawnpoint, 32, false)) + { + ent = CreateFlyMonster(spawnpoint, self->s.angles, reinforcement.mins, reinforcement.maxs, reinforcement.classname); + + if (!ent) + return; + + gi.sound(self, CHAN_BODY, sound_spawn, 1, ATTN_NONE, 0); + + ent->nextthink = level.time; + ent->think(ent); + + ent->monsterinfo.aiflags |= AI_SPAWNED_CARRIER | AI_DO_NOT_COUNT | AI_IGNORE_SHOTS; + ent->monsterinfo.commander = self; + ent->monsterinfo.monster_slots = reinforcement.strength; + self->monsterinfo.monster_used += reinforcement.strength; + + if ((self->enemy->inuse) && (self->enemy->health > 0)) + { + ent->enemy = self->enemy; + FoundTarget(ent); + + if (!strcmp(ent->classname, "monster_kamikaze")) + { + ent->monsterinfo.lefty = false; + ent->monsterinfo.attack_state = AS_STRAIGHT; + M_SetAnimation(ent, &flyer_move_kamikaze); + ent->monsterinfo.aiflags |= AI_CHARGING; + ent->owner = self; + } + else if (!strcmp(ent->classname, "monster_flyer")) + { + if (brandom()) + { + ent->monsterinfo.lefty = false; + ent->monsterinfo.attack_state = AS_SLIDING; + M_SetAnimation(ent, &flyer_move_attack3); + } + else + { + ent->monsterinfo.lefty = true; + ent->monsterinfo.attack_state = AS_SLIDING; + M_SetAnimation(ent, &flyer_move_attack3); + } + } + } + } +} + +void carrier_prep_spawn(edict_t *self) +{ + CarrierCoopCheck(self); + self->monsterinfo.aiflags |= AI_MANUAL_STEERING; + self->timestamp = level.time; + self->yaw_speed = 10; +} + +void carrier_spawn_check(edict_t *self) +{ + CarrierCoopCheck(self); + CarrierSpawn(self); + + if (level.time > (self->timestamp + 2.0_sec)) // 0.5 seconds per flyer. this gets three + { + self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING; + self->yaw_speed = orig_yaw_speed; + } + else + self->monsterinfo.nextframe = FRAME_spawn08; +} + +void carrier_ready_spawn(edict_t *self) +{ + float current_yaw; + vec3_t offset, f, r, startpoint, spawnpoint; + + CarrierCoopCheck(self); + + current_yaw = anglemod(self->s.angles[YAW]); + + if (fabsf(current_yaw - self->ideal_yaw) > 0.1f) + { + self->monsterinfo.aiflags |= AI_HOLD_FRAME; + self->timestamp += FRAME_TIME_S; + return; + } + + self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; + + int num_summoned; + self->monsterinfo.chosen_reinforcements = M_PickReinforcements(self, num_summoned, 1); + + if (!num_summoned) + return; + + auto &reinforcement = self->monsterinfo.reinforcements.reinforcements[self->monsterinfo.chosen_reinforcements[0]]; + + offset = { 105, 0, -58 }; + AngleVectors(self->s.angles, f, r, nullptr); + startpoint = M_ProjectFlashSource(self, offset, f, r); + if (FindSpawnPoint(startpoint, reinforcement.mins, reinforcement.maxs, spawnpoint, 32, false)) + { + float radius = (reinforcement.maxs - reinforcement.mins).length() * 0.5f; + + SpawnGrow_Spawn(spawnpoint + (reinforcement.mins + reinforcement.maxs), radius, radius * 2.f); + } +} + +void carrier_start_spawn(edict_t *self) +{ + int mytime; + float enemy_yaw; + vec3_t temp; + + CarrierCoopCheck(self); + if (!orig_yaw_speed) + orig_yaw_speed = self->yaw_speed; + + if (!self->enemy) + return; + + mytime = (int) ((level.time - self->timestamp) / 0.5).seconds(); + + temp = self->enemy->s.origin - self->s.origin; + enemy_yaw = vectoyaw(temp); + + // note that the offsets are based on a forward of 105 from the end angle + if (mytime == 0) + self->ideal_yaw = anglemod(enemy_yaw - 30); + else if (mytime == 1) + self->ideal_yaw = anglemod(enemy_yaw); + else if (mytime == 2) + self->ideal_yaw = anglemod(enemy_yaw + 30); +} + +mframe_t carrier_frames_stand[] = { + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand } +}; +MMOVE_T(carrier_move_stand) = { FRAME_search01, FRAME_search13, carrier_frames_stand, nullptr }; + +mframe_t carrier_frames_walk[] = { + { ai_walk, 4 }, + { ai_walk, 4 }, + { ai_walk, 4 }, + { ai_walk, 4 }, + { ai_walk, 4 }, + { ai_walk, 4 }, + { ai_walk, 4 }, + { ai_walk, 4 }, + { ai_walk, 4 }, + { ai_walk, 4 }, + { ai_walk, 4 }, + { ai_walk, 4 }, + { ai_walk, 4 } +}; +MMOVE_T(carrier_move_walk) = { FRAME_search01, FRAME_search13, carrier_frames_walk, nullptr }; + +mframe_t carrier_frames_run[] = { + { ai_run, 6, CarrierCoopCheck }, + { ai_run, 6, CarrierCoopCheck }, + { ai_run, 6, CarrierCoopCheck }, + { ai_run, 6, CarrierCoopCheck }, + { ai_run, 6, CarrierCoopCheck }, + { ai_run, 6, CarrierCoopCheck }, + { ai_run, 6, CarrierCoopCheck }, + { ai_run, 6, CarrierCoopCheck }, + { ai_run, 6, CarrierCoopCheck }, + { ai_run, 6, CarrierCoopCheck }, + { ai_run, 6, CarrierCoopCheck }, + { ai_run, 6, CarrierCoopCheck }, + { ai_run, 6, CarrierCoopCheck } +}; +MMOVE_T(carrier_move_run) = { FRAME_search01, FRAME_search13, carrier_frames_run, nullptr }; + +static void CarrierSpool(edict_t *self) +{ + CarrierCoopCheck(self); + gi.sound(self, CHAN_BODY, sound_cg_up, 1, 0.5f, 0); + + self->monsterinfo.weapon_sound = sound_cg_loop; +} + +mframe_t carrier_frames_attack_pre_mg[] = { + { ai_charge, 4, CarrierSpool }, + { ai_charge, 4, CarrierCoopCheck }, + { ai_charge, 4, CarrierCoopCheck }, + { ai_charge, 4, CarrierCoopCheck }, + { ai_charge, 4, CarrierCoopCheck }, + { ai_charge, 4, CarrierCoopCheck }, + { ai_charge, 4, CarrierCoopCheck }, + { ai_charge, 4, carrier_attack_mg } +}; +MMOVE_T(carrier_move_attack_pre_mg) = { FRAME_firea01, FRAME_firea08, carrier_frames_attack_pre_mg, nullptr }; + +// Loop this +mframe_t carrier_frames_attack_mg[] = { + { ai_charge, -2, CarrierMachineGun }, + { ai_charge, -2, CarrierMachineGun }, + { ai_charge, -2, carrier_reattack_mg } +}; +MMOVE_T(carrier_move_attack_mg) = { FRAME_firea09, FRAME_firea11, carrier_frames_attack_mg, nullptr }; + +mframe_t carrier_frames_attack_post_mg[] = { + { ai_charge, 4, CarrierCoopCheck }, + { ai_charge, 4, CarrierCoopCheck }, + { ai_charge, 4, CarrierCoopCheck }, + { ai_charge, 4, CarrierCoopCheck } +}; +MMOVE_T(carrier_move_attack_post_mg) = { FRAME_firea12, FRAME_firea15, carrier_frames_attack_post_mg, carrier_run }; + +mframe_t carrier_frames_attack_pre_gren[] = { + { ai_charge, 4, CarrierCoopCheck }, + { ai_charge, 4, CarrierCoopCheck }, + { ai_charge, 4, CarrierCoopCheck }, + { ai_charge, 4, CarrierCoopCheck }, + { ai_charge, 4, CarrierCoopCheck }, + { ai_charge, 4, carrier_attack_gren } +}; +MMOVE_T(carrier_move_attack_pre_gren) = { FRAME_fireb01, FRAME_fireb06, carrier_frames_attack_pre_gren, nullptr }; + +mframe_t carrier_frames_attack_gren[] = { + { ai_charge, -15, CarrierGrenade }, + { ai_charge, 4, CarrierCoopCheck }, + { ai_charge, 4, CarrierCoopCheck }, + { ai_charge, 4, carrier_reattack_gren } +}; +MMOVE_T(carrier_move_attack_gren) = { FRAME_fireb07, FRAME_fireb10, carrier_frames_attack_gren, nullptr }; + +mframe_t carrier_frames_attack_post_gren[] = { + { ai_charge, 4, CarrierCoopCheck }, + { ai_charge, 4, CarrierCoopCheck }, + { ai_charge, 4, CarrierCoopCheck }, + { ai_charge, 4, CarrierCoopCheck }, + { ai_charge, 4, CarrierCoopCheck }, + { ai_charge, 4, CarrierCoopCheck } +}; +MMOVE_T(carrier_move_attack_post_gren) = { FRAME_fireb11, FRAME_fireb16, carrier_frames_attack_post_gren, carrier_run }; + +mframe_t carrier_frames_attack_rocket[] = { + { ai_charge, 15, CarrierRocket } +}; +MMOVE_T(carrier_move_attack_rocket) = { FRAME_fireb01, FRAME_fireb01, carrier_frames_attack_rocket, carrier_run }; + +void CarrierRail(edict_t *self) +{ + vec3_t start; + vec3_t dir; + vec3_t forward, right; + + CarrierCoopCheck(self); + AngleVectors(self->s.angles, forward, right, nullptr); + start = M_ProjectFlashSource(self, monster_flash_offset[MZ2_CARRIER_RAILGUN], forward, right); + + // calc direction to where we targeted + dir = self->pos1 - start; + dir.normalize(); + + monster_fire_railgun(self, start, dir, 50, 100, MZ2_CARRIER_RAILGUN); + self->monsterinfo.attack_finished = level.time + RAIL_FIRE_TIME; +} + +void CarrierSaveLoc(edict_t *self) +{ + CarrierCoopCheck(self); + self->pos1 = self->enemy->s.origin; // save for aiming the shot + self->pos1[2] += self->enemy->viewheight; +}; + +mframe_t carrier_frames_attack_rail[] = { + { ai_charge, 2, CarrierCoopCheck }, + { ai_charge, 2, CarrierSaveLoc }, + { ai_charge, 2, CarrierCoopCheck }, + { ai_charge, -20, CarrierRail }, + { ai_charge, 2, CarrierCoopCheck }, + { ai_charge, 2, CarrierCoopCheck }, + { ai_charge, 2, CarrierCoopCheck }, + { ai_charge, 2, CarrierCoopCheck }, + { ai_charge, 2, CarrierCoopCheck } +}; +MMOVE_T(carrier_move_attack_rail) = { FRAME_search01, FRAME_search09, carrier_frames_attack_rail, carrier_run }; + +mframe_t carrier_frames_spawn[] = { + { ai_charge, -2 }, + { ai_charge, -2 }, + { ai_charge, -2 }, + { ai_charge, -2 }, + { ai_charge, -2 }, + { ai_charge, -2 }, + { ai_charge, -2, carrier_prep_spawn }, // 7 - end of wind down + { ai_charge, -2, carrier_start_spawn }, // 8 - start of spawn + { ai_charge, -2, carrier_ready_spawn }, + { ai_charge, -2 }, + { ai_charge, -2 }, + { ai_charge, -10, carrier_spawn_check }, // 12 - actual spawn + { ai_charge, -2 }, // 13 - begin of wind down + { ai_charge, -2 }, + { ai_charge, -2 }, + { ai_charge, -2 }, + { ai_charge, -2 }, + { ai_charge, -2 } // 18 - end of wind down +}; +MMOVE_T(carrier_move_spawn) = { FRAME_spawn01, FRAME_spawn18, carrier_frames_spawn, carrier_run }; + +mframe_t carrier_frames_pain_heavy[] = { + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move } +}; +MMOVE_T(carrier_move_pain_heavy) = { FRAME_death01, FRAME_death10, carrier_frames_pain_heavy, carrier_run }; + +mframe_t carrier_frames_pain_light[] = { + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move } +}; +MMOVE_T(carrier_move_pain_light) = { FRAME_spawn01, FRAME_spawn04, carrier_frames_pain_light, carrier_run }; + +mframe_t carrier_frames_death[] = { + { ai_move, 0, BossExplode }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move } +}; +MMOVE_T(carrier_move_death) = { FRAME_death01, FRAME_death16, carrier_frames_death, carrier_dead }; + +MONSTERINFO_STAND(carrier_stand) (edict_t *self) -> void +{ + M_SetAnimation(self, &carrier_move_stand); +} + +MONSTERINFO_RUN(carrier_run) (edict_t *self) -> void +{ + self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; + + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + M_SetAnimation(self, &carrier_move_stand); + else + M_SetAnimation(self, &carrier_move_run); +} + +MONSTERINFO_WALK(carrier_walk) (edict_t *self) -> void +{ + M_SetAnimation(self, &carrier_move_walk); +} + +void CarrierMachineGunHold(edict_t *self) +{ + CarrierMachineGun(self); +} + +MONSTERINFO_ATTACK(carrier_attack) (edict_t *self) -> void +{ + vec3_t vec; + float range, luck; + bool enemy_inback, enemy_infront, enemy_below; + + self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; + + if ((!self->enemy) || (!self->enemy->inuse)) + return; + + enemy_inback = inback(self, self->enemy); + enemy_infront = infront(self, self->enemy); + enemy_below = below(self, self->enemy); + + if (self->bad_area) + { + if ((enemy_inback) || (enemy_below)) + M_SetAnimation(self, &carrier_move_attack_rocket); + else if ((frandom() < 0.1f) || (level.time < self->monsterinfo.attack_finished)) + M_SetAnimation(self, &carrier_move_attack_pre_mg); + else + { + gi.sound(self, CHAN_WEAPON, sound_rail, 1, ATTN_NORM, 0); + M_SetAnimation(self, &carrier_move_attack_rail); + } + return; + } + + if (self->monsterinfo.attack_state == AS_BLIND) + { + M_SetAnimation(self, &carrier_move_spawn); + return; + } + + if (!enemy_inback && !enemy_infront && !enemy_below) // to side and not under + { + if ((frandom() < 0.1f) || (level.time < self->monsterinfo.attack_finished)) + M_SetAnimation(self, &carrier_move_attack_pre_mg); + else + { + gi.sound(self, CHAN_WEAPON, sound_rail, 1, ATTN_NORM, 0); + M_SetAnimation(self, &carrier_move_attack_rail); + } + return; + } + + if (enemy_infront) + { + vec = self->enemy->s.origin - self->s.origin; + range = vec.length(); + if (range <= 125) + { + if ((frandom() < 0.8f) || (level.time < self->monsterinfo.attack_finished)) + M_SetAnimation(self, &carrier_move_attack_pre_mg); + else + { + gi.sound(self, CHAN_WEAPON, sound_rail, 1, ATTN_NORM, 0); + M_SetAnimation(self, &carrier_move_attack_rail); + } + } + else if (range < 600) + { + luck = frandom(); + if (M_SlotsLeft(self) > 2) + { + if (luck <= 0.20f) + M_SetAnimation(self, &carrier_move_attack_pre_mg); + else if (luck <= 0.40f) + M_SetAnimation(self, &carrier_move_attack_pre_gren); + else if ((luck <= 0.7f) && !(level.time < self->monsterinfo.attack_finished)) + { + gi.sound(self, CHAN_WEAPON, sound_rail, 1, ATTN_NORM, 0); + M_SetAnimation(self, &carrier_move_attack_rail); + } + else + M_SetAnimation(self, &carrier_move_spawn); + } + else + { + if (luck <= 0.30f) + M_SetAnimation(self, &carrier_move_attack_pre_mg); + else if (luck <= 0.65f) + M_SetAnimation(self, &carrier_move_attack_pre_gren); + else if (level.time >= self->monsterinfo.attack_finished) + { + gi.sound(self, CHAN_WEAPON, sound_rail, 1, ATTN_NORM, 0); + M_SetAnimation(self, &carrier_move_attack_rail); + } + else + M_SetAnimation(self, &carrier_move_attack_pre_mg); + } + } + else // won't use grenades at this range + { + luck = frandom(); + if (M_SlotsLeft(self) > 2) + { + if (luck < 0.3f) + M_SetAnimation(self, &carrier_move_attack_pre_mg); + else if ((luck < 0.65f) && !(level.time < self->monsterinfo.attack_finished)) + { + gi.sound(self, CHAN_WEAPON, sound_rail, 1, ATTN_NORM, 0); + self->pos1 = self->enemy->s.origin; // save for aiming the shot + self->pos1[2] += self->enemy->viewheight; + M_SetAnimation(self, &carrier_move_attack_rail); + } + else + M_SetAnimation(self, &carrier_move_spawn); + } + else + { + if ((luck < 0.45f) || (level.time < self->monsterinfo.attack_finished)) + M_SetAnimation(self, &carrier_move_attack_pre_mg); + else + { + gi.sound(self, CHAN_WEAPON, sound_rail, 1, ATTN_NORM, 0); + M_SetAnimation(self, &carrier_move_attack_rail); + } + } + } + } + else if ((enemy_below) || (enemy_inback)) + { + M_SetAnimation(self, &carrier_move_attack_rocket); + } +} + +void carrier_attack_mg(edict_t *self) +{ + CarrierCoopCheck(self); + M_SetAnimation(self, &carrier_move_attack_mg); + self->monsterinfo.melee_debounce_time = level.time + random_time(1.2_sec, 2_sec); +} + +void carrier_reattack_mg(edict_t *self) +{ + CarrierMachineGun(self); + + CarrierCoopCheck(self); + if (visible(self, self->enemy) && infront(self, self->enemy)) + { + if (frandom() < 0.6f) + { + self->monsterinfo.melee_debounce_time += random_time(250_ms, 500_ms); + M_SetAnimation(self, &carrier_move_attack_mg); + return; + } + else if (self->monsterinfo.melee_debounce_time > level.time) + { + M_SetAnimation(self, &carrier_move_attack_mg); + return; + } + } + + M_SetAnimation(self, &carrier_move_attack_post_mg); + self->monsterinfo.weapon_sound = 0; + gi.sound(self, CHAN_BODY, sound_cg_down, 1, 0.5f, 0); +} + +void carrier_attack_gren(edict_t *self) +{ + CarrierCoopCheck(self); + self->timestamp = level.time; + M_SetAnimation(self, &carrier_move_attack_gren); +} + +void carrier_reattack_gren(edict_t *self) +{ + CarrierCoopCheck(self); + if (infront(self, self->enemy)) + if (self->timestamp + 1.3_sec > level.time) // four grenades + { + M_SetAnimation(self, &carrier_move_attack_gren); + return; + } + M_SetAnimation(self, &carrier_move_attack_post_gren); +} + +PAIN(carrier_pain) (edict_t *self, edict_t *other, float kick, int damage, const mod_t &mod) -> void +{ + bool changed = false; + + if (level.time < self->pain_debounce_time) + return; + + self->pain_debounce_time = level.time + 5_sec; + + if (damage < 10) + gi.sound(self, CHAN_VOICE, sound_pain3, 1, ATTN_NONE, 0); + else if (damage < 30) + gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NONE, 0); + else + gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NONE, 0); + + if (!M_ShouldReactToPain(self, mod)) + return; // no pain anims in nightmare + + self->monsterinfo.weapon_sound = 0; + + if (damage >= 10) + { + if (damage < 30) + { + if (mod.id == MOD_CHAINFIST || frandom() < 0.5f) + { + changed = true; + M_SetAnimation(self, &carrier_move_pain_light); + } + } + else + { + M_SetAnimation(self, &carrier_move_pain_heavy); + changed = true; + } + } + + // if we changed frames, clean up our little messes + if (changed) + { + self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; + self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING; + self->yaw_speed = orig_yaw_speed; + } +} + +MONSTERINFO_SETSKIN(carrier_setskin) (edict_t *self) -> void +{ + if (self->health < (self->max_health / 2)) + self->s.skinnum = 1; + else + self->s.skinnum = 0; +} + +void carrier_dead(edict_t *self) +{ + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_EXPLOSION1_BIG); + gi.WritePosition(self->s.origin); + gi.multicast(self->s.origin, MULTICAST_PHS, false); + + self->s.sound = 0; + self->s.skinnum /= 2; + + self->gravityVector.z = -1.0f; + + ThrowGibs(self, 500, { + { 2, "models/objects/gibs/sm_meat/tris.md2" }, + { 3, "models/objects/gibs/sm_metal/tris.md2", GIB_METALLIC }, + { "models/monsters/carrier/gibs/base.md2", GIB_SKINNED }, + { "models/monsters/carrier/gibs/chest.md2", GIB_SKINNED | GIB_UPRIGHT }, + { "models/monsters/carrier/gibs/gl.md2", GIB_SKINNED }, + { "models/monsters/carrier/gibs/lcg.md2", GIB_SKINNED | GIB_UPRIGHT }, + { "models/monsters/carrier/gibs/lwing.md2", GIB_SKINNED | GIB_UPRIGHT }, + { "models/monsters/carrier/gibs/rcg.md2", GIB_SKINNED | GIB_UPRIGHT }, + { "models/monsters/carrier/gibs/rwing.md2", GIB_SKINNED | GIB_UPRIGHT }, + { 2, "models/monsters/carrier/gibs/spawner.md2", GIB_SKINNED }, + { 2, "models/monsters/carrier/gibs/thigh.md2", GIB_SKINNED }, + { "models/monsters/carrier/gibs/head.md2", GIB_SKINNED | GIB_METALLIC | GIB_HEAD } + }); +} + +DIE(carrier_die) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void +{ + gi.sound(self, CHAN_VOICE, sound_death, 1, ATTN_NONE, 0); + self->deadflag = true; + self->takedamage = false; + self->count = 0; + M_SetAnimation(self, &carrier_move_death); + self->velocity = {}; + self->gravityVector.z *= 0.01f; + self->monsterinfo.weapon_sound = 0; +} + +MONSTERINFO_CHECKATTACK(Carrier_CheckAttack) (edict_t *self) -> bool +{ + vec3_t spot1, spot2; + vec3_t temp; + float chance; + trace_t tr; + bool enemy_infront, enemy_inback, enemy_below; + float enemy_yaw; + + if (self->enemy->health > 0) + { + // see if any entities are in the way of the shot + spot1 = self->s.origin; + spot1[2] += self->viewheight; + spot2 = self->enemy->s.origin; + spot2[2] += self->enemy->viewheight; + + tr = gi.traceline(spot1, spot2, self, CONTENTS_SOLID | CONTENTS_PLAYER | CONTENTS_MONSTER | CONTENTS_SLIME | CONTENTS_LAVA); + + // do we have a clear shot? + if (tr.ent != self->enemy && !(tr.ent->svflags & SVF_PLAYER)) + { + // go ahead and spawn stuff if we're mad a a client + if (self->enemy->client && M_SlotsLeft(self) > 2) + { + self->monsterinfo.attack_state = AS_BLIND; + return true; + } + + // PGM - we want them to go ahead and shoot at info_notnulls if they can. + if (self->enemy->solid != SOLID_NOT || tr.fraction < 1.0f) // PGM + return false; + } + } + + enemy_infront = infront(self, self->enemy); + enemy_inback = inback(self, self->enemy); + enemy_below = below(self, self->enemy); + + float enemy_range = range_to(self, self->enemy); + temp = self->enemy->s.origin - self->s.origin; + enemy_yaw = vectoyaw(temp); + + self->ideal_yaw = enemy_yaw; + + // PMM - shoot out the back if appropriate + if ((enemy_inback) || (!enemy_infront && enemy_below)) + { + // this is using wait because the attack is supposed to be independent + if (level.time >= self->monsterinfo.fire_wait) + { + self->monsterinfo.fire_wait = level.time + CARRIER_ROCKET_TIME; + self->monsterinfo.attack(self); + if (frandom() < 0.6f) + self->monsterinfo.attack_state = AS_SLIDING; + else + self->monsterinfo.attack_state = AS_STRAIGHT; + return true; + } + } + + // melee attack + if (enemy_range <= RANGE_MELEE) + { + self->monsterinfo.attack_state = AS_MISSILE; + return true; + } + + // if (level.time < self->monsterinfo.attack_finished) + // return false; + + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + { + chance = 0.4f; + } + else if (enemy_range <= RANGE_MELEE) + { + chance = 0.8f; + } + else if (enemy_range <= RANGE_NEAR) + { + chance = 0.8f; + } + else if (enemy_range <= RANGE_MID) + { + chance = 0.8f; + } + else + { + chance = 0.5f; + } + + // PGM - go ahead and shoot every time if it's a info_notnull + if ((frandom() < chance) || (self->enemy->solid == SOLID_NOT)) + { + self->monsterinfo.attack_state = AS_MISSILE; + return true; + } + + if (self->flags & FL_FLY) + { + if (frandom() < 0.6f) + self->monsterinfo.attack_state = AS_SLIDING; + else + self->monsterinfo.attack_state = AS_STRAIGHT; + } + + return false; +} + +void CarrierPrecache() +{ + gi.soundindex("flyer/flysght1.wav"); + gi.soundindex("flyer/flysrch1.wav"); + gi.soundindex("flyer/flypain1.wav"); + gi.soundindex("flyer/flypain2.wav"); + gi.soundindex("flyer/flyatck2.wav"); + gi.soundindex("flyer/flyatck1.wav"); + gi.soundindex("flyer/flydeth1.wav"); + gi.soundindex("flyer/flyatck3.wav"); + gi.soundindex("flyer/flyidle1.wav"); + gi.soundindex("weapons/rockfly.wav"); + gi.soundindex("infantry/infatck1.wav"); + gi.soundindex("gunner/gunatck3.wav"); + gi.soundindex("weapons/grenlb1b.wav"); + gi.soundindex("tank/rocket.wav"); + + gi.modelindex("models/monsters/flyer/tris.md2"); + gi.modelindex("models/objects/rocket/tris.md2"); + gi.modelindex("models/objects/debris2/tris.md2"); + gi.modelindex("models/objects/grenade/tris.md2"); + gi.modelindex("models/items/spawngro3/tris.md2"); + gi.modelindex("models/objects/gibs/sm_metal/tris.md2"); + gi.modelindex("models/objects/gibs/gear/tris.md2"); +} + +/*QUAKED monster_carrier (1 .5 0) (-56 -56 -44) (56 56 44) Ambush Trigger_Spawn Sight + */ +void SP_monster_carrier(edict_t *self) +{ + if ( !M_AllowSpawn( self ) ) { + G_FreeEdict( self ); + return; + } + + sound_pain1 = gi.soundindex("carrier/pain_md.wav"); + sound_pain2 = gi.soundindex("carrier/pain_lg.wav"); + sound_pain3 = gi.soundindex("carrier/pain_sm.wav"); + sound_death = gi.soundindex("carrier/death.wav"); + sound_rail = gi.soundindex("gladiator/railgun.wav"); + sound_sight = gi.soundindex("carrier/sight.wav"); + sound_spawn = gi.soundindex("medic_commander/monsterspawn1.wav"); + + sound_cg_down = gi.soundindex("weapons/chngnd1a.wav"); + sound_cg_loop = gi.soundindex("weapons/chngnl1a.wav"); + sound_cg_up = gi.soundindex("weapons/chngnu1a.wav"); + + self->monsterinfo.engine_sound = gi.soundindex("bosshovr/bhvengn1.wav"); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex("models/monsters/carrier/tris.md2"); + + gi.modelindex("models/monsters/carrier/gibs/base.md2"); + gi.modelindex("models/monsters/carrier/gibs/chest.md2"); + gi.modelindex("models/monsters/carrier/gibs/gl.md2"); + gi.modelindex("models/monsters/carrier/gibs/head.md2"); + gi.modelindex("models/monsters/carrier/gibs/lcg.md2"); + gi.modelindex("models/monsters/carrier/gibs/lwing.md2"); + gi.modelindex("models/monsters/carrier/gibs/rcg.md2"); + gi.modelindex("models/monsters/carrier/gibs/rwing.md2"); + gi.modelindex("models/monsters/carrier/gibs/spawner.md2"); + gi.modelindex("models/monsters/carrier/gibs/thigh.md2"); + + self->mins = { -56, -56, -44 }; + self->maxs = { 56, 56, 44 }; + + // 2000 - 4000 health + self->health = max(2000, 2000 + 1000 * (skill->integer - 1)) * st.health_multiplier; + // add health in coop (500 * skill) + if (coop->integer) + self->health += 500 * skill->integer; + + self->gib_health = -200; + self->mass = 1000; + + self->yaw_speed = 15; + orig_yaw_speed = self->yaw_speed; + + self->flags |= FL_IMMUNE_LASER; + self->monsterinfo.aiflags |= AI_IGNORE_SHOTS; + + self->pain = carrier_pain; + self->die = carrier_die; + + self->monsterinfo.melee = nullptr; + self->monsterinfo.stand = carrier_stand; + self->monsterinfo.walk = carrier_walk; + self->monsterinfo.run = carrier_run; + self->monsterinfo.attack = carrier_attack; + self->monsterinfo.sight = carrier_sight; + self->monsterinfo.checkattack = Carrier_CheckAttack; + self->monsterinfo.setskin = carrier_setskin; + gi.linkentity(self); + + M_SetAnimation(self, &carrier_move_stand); + self->monsterinfo.scale = MODEL_SCALE; + + CarrierPrecache(); + + flymonster_start(self); + + self->monsterinfo.attack_finished = 0_ms; + + const char *reinforcements = default_reinforcements; + + if (!st.was_key_specified("monster_slots")) + self->monsterinfo.monster_slots = default_monster_slots_base; + if (st.was_key_specified("reinforcements")) + reinforcements = st.reinforcements; + + if (self->monsterinfo.monster_slots && reinforcements && *reinforcements) + { + if (skill->integer) + self->monsterinfo.monster_slots += floor(self->monsterinfo.monster_slots * (skill->value / 2.f)); + + M_SetupReinforcements(reinforcements, self->monsterinfo.reinforcements); + } + + self->monsterinfo.aiflags |= AI_ALTERNATE_FLY; + self->monsterinfo.fly_acceleration = 5.f; + self->monsterinfo.fly_speed = 50.f; + self->monsterinfo.fly_above = true; + self->monsterinfo.fly_min_distance = 1000.f; + self->monsterinfo.fly_max_distance = 1000.f; +} diff --git a/rerelease/rogue/m_rogue_carrier.h b/rerelease/rogue/m_rogue_carrier.h new file mode 100644 index 0000000..a7a9066 --- /dev/null +++ b/rerelease/rogue/m_rogue_carrier.h @@ -0,0 +1,88 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// G:\quake2\xpack\models/monsters/carrier + +// This file generated by qdata - Do NOT Modify +enum +{ + FRAME_search01, + FRAME_search02, + FRAME_search03, + FRAME_search04, + FRAME_search05, + FRAME_search06, + FRAME_search07, + FRAME_search08, + FRAME_search09, + FRAME_search10, + FRAME_search11, + FRAME_search12, + FRAME_search13, + FRAME_firea01, + FRAME_firea02, + FRAME_firea03, + FRAME_firea04, + FRAME_firea05, + FRAME_firea06, + FRAME_firea07, + FRAME_firea08, + FRAME_firea09, + FRAME_firea10, + FRAME_firea11, + FRAME_firea12, + FRAME_firea13, + FRAME_firea14, + FRAME_firea15, + FRAME_fireb01, + FRAME_fireb02, + FRAME_fireb03, + FRAME_fireb04, + FRAME_fireb05, + FRAME_fireb06, + FRAME_fireb07, + FRAME_fireb08, + FRAME_fireb09, + FRAME_fireb10, + FRAME_fireb11, + FRAME_fireb12, + FRAME_fireb13, + FRAME_fireb14, + FRAME_fireb15, + FRAME_fireb16, + FRAME_spawn01, + FRAME_spawn02, + FRAME_spawn03, + FRAME_spawn04, + FRAME_spawn05, + FRAME_spawn06, + FRAME_spawn07, + FRAME_spawn08, + FRAME_spawn09, + FRAME_spawn10, + FRAME_spawn11, + FRAME_spawn12, + FRAME_spawn13, + FRAME_spawn14, + FRAME_spawn15, + FRAME_spawn16, + FRAME_spawn17, + FRAME_spawn18, + FRAME_death01, + FRAME_death02, + FRAME_death03, + FRAME_death04, + FRAME_death05, + FRAME_death06, + FRAME_death07, + FRAME_death08, + FRAME_death09, + FRAME_death10, + FRAME_death11, + FRAME_death12, + FRAME_death13, + FRAME_death14, + FRAME_death15, + FRAME_death16 +}; + +constexpr float MODEL_SCALE = 1.000000f; diff --git a/rerelease/rogue/m_rogue_stalker.cpp b/rerelease/rogue/m_rogue_stalker.cpp new file mode 100644 index 0000000..951c605 --- /dev/null +++ b/rerelease/rogue/m_rogue_stalker.cpp @@ -0,0 +1,1041 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +/* +============================================================================== + +stalker + +============================================================================== +*/ + +#include "../g_local.h" +#include "m_rogue_stalker.h" +#include + +static int sound_pain; +static int sound_die; +static int sound_sight; +static int sound_punch_hit1; +static int sound_punch_hit2; +static int sound_idle; + +bool stalker_do_pounce(edict_t *self, const vec3_t &dest); +void stalker_walk(edict_t *self); +void stalker_dodge_jump(edict_t *self); +void stalker_swing_attack(edict_t *self); +void stalker_jump_straightup(edict_t *self); +void stalker_jump_wait_land(edict_t *self); +void stalker_false_death(edict_t *self); +void stalker_false_death_start(edict_t *self); +bool stalker_ok_to_transition(edict_t *self); +void stalker_stand(edict_t *self); + +inline bool STALKER_ON_CEILING(edict_t *ent) +{ + return (ent->gravityVector[2] > 0); +} + +//========================= +//========================= +bool stalker_ok_to_transition(edict_t *self) +{ + trace_t trace; + vec3_t pt, start; + float max_dist; + float margin; + float end_height; + + if (STALKER_ON_CEILING(self)) + { + // [Paril-KEX] if we get knocked off the ceiling, always + // fall downwards + if (!self->groundentity) + return true; + + max_dist = -384; + margin = self->mins[2] - 8; + } + else + { + // her stalkers are just better + if (self->monsterinfo.aiflags & AI_SPAWNED_WIDOW) + max_dist = 256; + else + max_dist = 180; + margin = self->maxs[2] + 8; + } + + pt = self->s.origin; + pt[2] += max_dist; + trace = gi.trace(self->s.origin, self->mins, self->maxs, pt, self, MASK_MONSTERSOLID); + + if (trace.fraction == 1.0f || + !(trace.contents & CONTENTS_SOLID) || + (trace.ent != world)) + { + if (STALKER_ON_CEILING(self)) + { + if (trace.plane.normal[2] < 0.9f) + return false; + } + else + { + if (trace.plane.normal[2] > -0.9f) + return false; + } + } + + end_height = trace.endpos[2]; + + // check the four corners, tracing only to the endpoint of the center trace (vertically). + pt[0] = self->absmin[0]; + pt[1] = self->absmin[1]; + pt[2] = trace.endpos[2] + margin; // give a little margin of error to allow slight inclines + start = pt; + start[2] = self->s.origin[2]; + trace = gi.traceline(start, pt, self, MASK_MONSTERSOLID); + if (trace.fraction == 1.0f || !(trace.contents & CONTENTS_SOLID) || (trace.ent != world)) + return false; + if (fabsf(end_height + margin - trace.endpos[2]) > 8) + return false; + + pt[0] = self->absmax[0]; + pt[1] = self->absmin[1]; + start = pt; + start[2] = self->s.origin[2]; + trace = gi.traceline(start, pt, self, MASK_MONSTERSOLID); + if (trace.fraction == 1.0f || !(trace.contents & CONTENTS_SOLID) || (trace.ent != world)) + return false; + if (fabsf(end_height + margin - trace.endpos[2]) > 8) + return false; + + pt[0] = self->absmax[0]; + pt[1] = self->absmax[1]; + start = pt; + start[2] = self->s.origin[2]; + trace = gi.traceline(start, pt, self, MASK_MONSTERSOLID); + if (trace.fraction == 1.0f || !(trace.contents & CONTENTS_SOLID) || (trace.ent != world)) + return false; + if (fabsf(end_height + margin - trace.endpos[2]) > 8) + return false; + + pt[0] = self->absmin[0]; + pt[1] = self->absmax[1]; + start = pt; + start[2] = self->s.origin[2]; + trace = gi.traceline(start, pt, self, MASK_MONSTERSOLID); + if (trace.fraction == 1.0f || !(trace.contents & CONTENTS_SOLID) || (trace.ent != world)) + return false; + if (fabsf(end_height + margin - trace.endpos[2]) > 8) + return false; + + return true; +} + +//========================= +//========================= +MONSTERINFO_SIGHT(stalker_sight) (edict_t *self, edict_t *other) -> void +{ + gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); +} + +// ****************** +// IDLE +// ****************** + +void stalker_idle_noise(edict_t *self) +{ + gi.sound(self, CHAN_VOICE, sound_idle, 0.5, ATTN_IDLE, 0); +} + +mframe_t stalker_frames_idle[] = { + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + + { ai_stand }, + { ai_stand, 0, stalker_idle_noise }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + + { ai_stand } +}; +MMOVE_T(stalker_move_idle) = { FRAME_idle01, FRAME_idle21, stalker_frames_idle, stalker_stand }; + +mframe_t stalker_frames_idle2[] = { + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + + { ai_stand }, + { ai_stand }, + { ai_stand } +}; +MMOVE_T(stalker_move_idle2) = { FRAME_idle201, FRAME_idle213, stalker_frames_idle2, stalker_stand }; + +MONSTERINFO_IDLE(stalker_idle) (edict_t *self) -> void +{ + if (frandom() < 0.35f) + M_SetAnimation(self, &stalker_move_idle); + else + M_SetAnimation(self, &stalker_move_idle2); +} + +// ****************** +// STAND +// ****************** + +mframe_t stalker_frames_stand[] = { + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + + { ai_stand }, + { ai_stand, 0, stalker_idle_noise }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + + { ai_stand } +}; +MMOVE_T(stalker_move_stand) = { FRAME_idle01, FRAME_idle21, stalker_frames_stand, stalker_stand }; + +MONSTERINFO_STAND(stalker_stand) (edict_t *self) -> void +{ + if (frandom() < 0.25f) + M_SetAnimation(self, &stalker_move_stand); + else + M_SetAnimation(self, &stalker_move_idle2); +} + +// ****************** +// RUN +// ****************** + +mframe_t stalker_frames_run[] = { + { ai_run, 13, monster_footstep }, + { ai_run, 17 }, + { ai_run, 21, monster_footstep }, + { ai_run, 18 } +}; +MMOVE_T(stalker_move_run) = { FRAME_run01, FRAME_run04, stalker_frames_run, nullptr }; + +MONSTERINFO_RUN(stalker_run) (edict_t *self) -> void +{ + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + M_SetAnimation(self, &stalker_move_stand); + else + M_SetAnimation(self, &stalker_move_run); +} + +// ****************** +// WALK +// ****************** + +mframe_t stalker_frames_walk[] = { + { ai_walk, 4, monster_footstep }, + { ai_walk, 6 }, + { ai_walk, 8 }, + { ai_walk, 5 }, + + { ai_walk, 4, monster_footstep }, + { ai_walk, 6 }, + { ai_walk, 8 }, + { ai_walk, 4 } +}; +MMOVE_T(stalker_move_walk) = { FRAME_walk01, FRAME_walk08, stalker_frames_walk, stalker_walk }; + +MONSTERINFO_WALK(stalker_walk) (edict_t *self) -> void +{ + M_SetAnimation(self, &stalker_move_walk); +} + +// ****************** +// false death +// ****************** +mframe_t stalker_frames_reactivate[] = { + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move, 0, monster_footstep } +}; +MMOVE_T(stalker_move_false_death_end) = { FRAME_reactive01, FRAME_reactive04, stalker_frames_reactivate, stalker_run }; + +void stalker_reactivate(edict_t *self) +{ + self->monsterinfo.aiflags &= ~AI_STAND_GROUND; + M_SetAnimation(self, &stalker_move_false_death_end); +} + +void stalker_heal(edict_t *self) +{ + if (skill->integer == 2) + self->health += 2; + else if (skill->integer == 3) + self->health += 3; + else + self->health++; + + self->monsterinfo.setskin(self); + + if (self->health >= self->max_health) + { + self->health = self->max_health; + stalker_reactivate(self); + } +} + +mframe_t stalker_frames_false_death[] = { + { ai_move, 0, stalker_heal }, + { ai_move, 0, stalker_heal }, + { ai_move, 0, stalker_heal }, + { ai_move, 0, stalker_heal }, + { ai_move, 0, stalker_heal }, + + { ai_move, 0, stalker_heal }, + { ai_move, 0, stalker_heal }, + { ai_move, 0, stalker_heal }, + { ai_move, 0, stalker_heal }, + { ai_move, 0, stalker_heal } +}; +MMOVE_T(stalker_move_false_death) = { FRAME_twitch01, FRAME_twitch10, stalker_frames_false_death, stalker_false_death }; + +void stalker_false_death(edict_t *self) +{ + M_SetAnimation(self, &stalker_move_false_death); +} + +mframe_t stalker_frames_false_death_start[] = { + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, +}; +MMOVE_T(stalker_move_false_death_start) = { FRAME_death01, FRAME_death09, stalker_frames_false_death_start, stalker_false_death }; + +void stalker_false_death_start(edict_t *self) +{ + self->s.angles[2] = 0; + self->gravityVector = { 0, 0, -1 }; + + self->monsterinfo.aiflags |= AI_STAND_GROUND; + M_SetAnimation(self, &stalker_move_false_death_start); +} + +// ****************** +// PAIN +// ****************** + +mframe_t stalker_frames_pain[] = { + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move } +}; +MMOVE_T(stalker_move_pain) = { FRAME_pain01, FRAME_pain04, stalker_frames_pain, stalker_run }; + +PAIN(stalker_pain) (edict_t *self, edict_t *other, float kick, int damage, const mod_t &mod) -> void +{ + if (self->deadflag) + return; + + if (self->groundentity == nullptr) + return; + + // if we're reactivating or false dying, ignore the pain. + if (self->monsterinfo.active_move == &stalker_move_false_death_end || + self->monsterinfo.active_move == &stalker_move_false_death_start) + return; + + if (self->monsterinfo.active_move == &stalker_move_false_death) + { + stalker_reactivate(self); + return; + } + + if ((self->health > 0) && (self->health < (self->max_health / 4))) + { + if (frandom() < 0.30f) + { + if (!STALKER_ON_CEILING(self) || stalker_ok_to_transition(self)) + { + stalker_false_death_start(self); + return; + } + } + } + + if (level.time < self->pain_debounce_time) + return; + + self->pain_debounce_time = level.time + 3_sec; + + gi.sound(self, CHAN_VOICE, sound_pain, 1, ATTN_NORM, 0); + + if (mod.id == MOD_CHAINFIST || damage > 10) // don't react unless the damage was significant + { + // stalker should dodge jump periodically to help avoid damage. + if (self->groundentity && (frandom() < 0.5f)) + stalker_dodge_jump(self); + else if (M_ShouldReactToPain(self, mod)) // no pain anims in nightmare + M_SetAnimation(self, &stalker_move_pain); + } +} + +MONSTERINFO_SETSKIN(stalker_setskin) (edict_t *self) -> void +{ + if (self->health < (self->max_health / 2)) + self->s.skinnum = 1; + else + self->s.skinnum = 0; +} + +// ****************** +// STALKER ATTACK +// ****************** + +void stalker_shoot_attack(edict_t *self) +{ + vec3_t offset, start, f, r, dir; + vec3_t end; + float dist; + trace_t trace; + + if (!has_valid_enemy(self)) + return; + + if (self->groundentity && frandom() < 0.33f) + { + dir = self->enemy->s.origin - self->s.origin; + dist = dir.length(); + + if ((dist > 256) || (frandom() < 0.5f)) + stalker_do_pounce(self, self->enemy->s.origin); + else + stalker_jump_straightup(self); + } + + AngleVectors(self->s.angles, f, r, nullptr); + offset = { 24, 0, 6 }; + start = M_ProjectFlashSource(self, offset, f, r); + + dir = self->enemy->s.origin - start; + if (frandom() < 0.3f) + PredictAim(self, self->enemy, start, 1000, true, 0, &dir, &end); + else + end = self->enemy->s.origin; + + trace = gi.traceline(start, end, self, MASK_PROJECTILE); + if (trace.ent == self->enemy || trace.ent == world) + { + dir.normalize(); + monster_fire_blaster2(self, start, dir, 5, 800, MZ2_STALKER_BLASTER, EF_BLASTER); + } +} + +void stalker_shoot_attack2(edict_t *self) +{ + if (frandom() < 0.5) + stalker_shoot_attack(self); +} + +mframe_t stalker_frames_shoot[] = { + { ai_charge, 13 }, + { ai_charge, 17, stalker_shoot_attack }, + { ai_charge, 21 }, + { ai_charge, 18, stalker_shoot_attack2 } +}; +MMOVE_T(stalker_move_shoot) = { FRAME_run01, FRAME_run04, stalker_frames_shoot, stalker_run }; + +MONSTERINFO_ATTACK(stalker_attack_ranged) (edict_t *self) -> void +{ + if (!has_valid_enemy(self)) + return; + + // PMM - circle strafe stuff + if (frandom() > 0.5f) + { + self->monsterinfo.attack_state = AS_STRAIGHT; + } + else + { + if (frandom() <= 0.5f) // switch directions + self->monsterinfo.lefty = !self->monsterinfo.lefty; + self->monsterinfo.attack_state = AS_SLIDING; + } + M_SetAnimation(self, &stalker_move_shoot); +} + +// ****************** +// close combat +// ****************** + +void stalker_swing_attack(edict_t *self) +{ + vec3_t aim = { MELEE_DISTANCE, 0, 0 }; + if (fire_hit(self, aim, irandom(5, 10), 50)) + { + if (self->s.frame < FRAME_attack08) + gi.sound(self, CHAN_WEAPON, sound_punch_hit2, 1, ATTN_NORM, 0); + else + gi.sound(self, CHAN_WEAPON, sound_punch_hit1, 1, ATTN_NORM, 0); + } + else + self->monsterinfo.melee_debounce_time = level.time + 0.8_sec; +} + +mframe_t stalker_frames_swing_l[] = { + { ai_charge, 2 }, + { ai_charge, 4 }, + { ai_charge, 6 }, + { ai_charge, 10, monster_footstep }, + + { ai_charge, 5, stalker_swing_attack }, + { ai_charge, 5 }, + { ai_charge, 5 }, + { ai_charge, 5, monster_footstep } // stalker_swing_check_l +}; +MMOVE_T(stalker_move_swing_l) = { FRAME_attack01, FRAME_attack08, stalker_frames_swing_l, stalker_run }; + +mframe_t stalker_frames_swing_r[] = { + { ai_charge, 4 }, + { ai_charge, 6, monster_footstep }, + { ai_charge, 6, stalker_swing_attack }, + { ai_charge, 10 }, + { ai_charge, 5, monster_footstep } // stalker_swing_check_r +}; +MMOVE_T(stalker_move_swing_r) = { FRAME_attack11, FRAME_attack15, stalker_frames_swing_r, stalker_run }; + +MONSTERINFO_MELEE(stalker_attack_melee) (edict_t *self) -> void +{ + if (!has_valid_enemy(self)) + return; + + if (frandom() < 0.5f) + M_SetAnimation(self, &stalker_move_swing_l); + else + M_SetAnimation(self, &stalker_move_swing_r); +} + +// ****************** +// POUNCE +// ****************** + +// ==================== +// ==================== +bool stalker_check_lz(edict_t *self, edict_t *target, const vec3_t &dest) +{ + if ((gi.pointcontents(dest) & MASK_WATER) || (target->waterlevel)) + return false; + + if (!target->groundentity) + return false; + + vec3_t jumpLZ; + + // check under the player's four corners + // if they're not solid, bail. + jumpLZ[0] = self->enemy->mins[0]; + jumpLZ[1] = self->enemy->mins[1]; + jumpLZ[2] = self->enemy->mins[2] - 0.25f; + if (!(gi.pointcontents(jumpLZ) & MASK_SOLID)) + return false; + + jumpLZ[0] = self->enemy->maxs[0]; + jumpLZ[1] = self->enemy->mins[1]; + if (!(gi.pointcontents(jumpLZ) & MASK_SOLID)) + return false; + + jumpLZ[0] = self->enemy->maxs[0]; + jumpLZ[1] = self->enemy->maxs[1]; + if (!(gi.pointcontents(jumpLZ) & MASK_SOLID)) + return false; + + jumpLZ[0] = self->enemy->mins[0]; + jumpLZ[1] = self->enemy->maxs[1]; + if (!(gi.pointcontents(jumpLZ) & MASK_SOLID)) + return false; + + return true; +} + +// ==================== +// ==================== +bool stalker_do_pounce(edict_t *self, const vec3_t &dest) +{ + vec3_t dist; + float length; + vec3_t jumpAngles; + vec3_t jumpLZ; + float velocity = 400.1f; + + // don't pounce when we're on the ceiling + if (STALKER_ON_CEILING(self)) + return false; + + if (!stalker_check_lz(self, self->enemy, dest)) + return false; + + dist = dest - self->s.origin; + + // make sure we're pointing in that direction 15deg margin of error. + jumpAngles = vectoangles(dist); + if (fabsf(jumpAngles[YAW] - self->s.angles[YAW]) > 45) + return false; // not facing the player... + + if (isnan(jumpAngles[YAW])) + return false; // Switch why + + self->ideal_yaw = jumpAngles[YAW]; + M_ChangeYaw(self); + + length = dist.length(); + if (length > 450) + return false; // can't jump that far... + + jumpLZ = dest; + vec3_t dir = dist.normalized(); + + // find a valid angle/velocity combination + while (velocity <= 800) + { + if (M_CalculatePitchToFire(self, jumpLZ, self->s.origin, dir, velocity, 3, false, true)) + break; + + velocity += 200; + } + + // nothing found + if (velocity > 800) + return false; + + self->velocity = dir * velocity; + return true; +} + +// ****************** +// DODGE +// ****************** + +//=================== +// stalker_jump_straightup +//=================== +void stalker_jump_straightup(edict_t *self) +{ + if (self->deadflag) + return; + + if (STALKER_ON_CEILING(self)) + { + if (stalker_ok_to_transition(self)) + { + self->gravityVector[2] = -1; + self->s.angles[2] += 180.0f; + if (self->s.angles[2] > 360.0f) + self->s.angles[2] -= 360.0f; + self->groundentity = nullptr; + } + } + else if (self->groundentity) // make sure we're standing on SOMETHING... + { + self->velocity[0] += crandom() * 5; + self->velocity[1] += crandom() * 5; + self->velocity[2] += -400 * self->gravityVector[2]; + if (stalker_ok_to_transition(self)) + { + self->gravityVector[2] = 1; + self->s.angles[2] = 180.0; + self->groundentity = nullptr; + } + } +} + +mframe_t stalker_frames_jump_straightup[] = { + { ai_move, 1, stalker_jump_straightup }, + { ai_move, 1, stalker_jump_wait_land }, + { ai_move, -1, monster_footstep }, + { ai_move, -1 } +}; + +MMOVE_T(stalker_move_jump_straightup) = { FRAME_jump04, FRAME_jump07, stalker_frames_jump_straightup, stalker_run }; + +//=================== +// stalker_dodge_jump - abstraction so pain function can trigger a dodge jump too without +// faking the inputs to stalker_dodge +//=================== +void stalker_dodge_jump(edict_t *self) +{ + M_SetAnimation(self, &stalker_move_jump_straightup); +} + +#if 0 +mframe_t stalker_frames_dodge_run[] = { + { ai_run, 13 }, + { ai_run, 17 }, + { ai_run, 21 }, + { ai_run, 18, monster_done_dodge } +}; +MMOVE_T(stalker_move_dodge_run) = { FRAME_run01, FRAME_run04, stalker_frames_dodge_run, nullptr }; +#endif + +MONSTERINFO_DODGE(stalker_dodge) (edict_t *self, edict_t *attacker, gtime_t eta, trace_t *tr, bool gravity) -> void +{ + if (!self->groundentity || self->health <= 0) + return; + + if (!self->enemy) + { + self->enemy = attacker; + FoundTarget(self); + return; + } + + // PMM - don't bother if it's going to hit anyway; fix for weird in-your-face etas (I was + // seeing numbers like 13 and 14) + if ((eta < FRAME_TIME_MS) || (eta > 5_sec)) + return; + + if (self->timestamp > level.time) + return; + + self->timestamp = level.time + random_time(1_sec, 5_sec); + // this will override the foundtarget call of stalker_run + stalker_dodge_jump(self); +} + +// ****************** +// Jump onto / off of things +// ****************** + +//=================== +//=================== +void stalker_jump_down(edict_t *self) +{ + vec3_t forward, up; + + AngleVectors(self->s.angles, forward, nullptr, up); + self->velocity += (forward * 100); + self->velocity += (up * 300); +} + +//=================== +//=================== +void stalker_jump_up(edict_t *self) +{ + vec3_t forward, up; + + AngleVectors(self->s.angles, forward, nullptr, up); + self->velocity += (forward * 200); + self->velocity += (up * 450); +} + +//=================== +//=================== +void stalker_jump_wait_land(edict_t *self) +{ + if ((frandom() < 0.4f) && (level.time >= self->monsterinfo.attack_finished)) + { + self->monsterinfo.attack_finished = level.time + 300_ms; + stalker_shoot_attack(self); + } + + if (self->groundentity == nullptr) + { + self->gravity = 1.3f; + self->monsterinfo.nextframe = self->s.frame; + + if (monster_jump_finished(self)) + { + self->gravity = 1; + self->monsterinfo.nextframe = self->s.frame + 1; + } + } + else + { + self->gravity = 1; + self->monsterinfo.nextframe = self->s.frame + 1; + } +} + +mframe_t stalker_frames_jump_up[] = { + { ai_move, -8 }, + { ai_move, -8 }, + { ai_move, -8 }, + { ai_move, -8 }, + + { ai_move, 0, stalker_jump_up }, + { ai_move, 0, stalker_jump_wait_land }, + { ai_move, 0, monster_footstep } +}; +MMOVE_T(stalker_move_jump_up) = { FRAME_jump01, FRAME_jump07, stalker_frames_jump_up, stalker_run }; + +mframe_t stalker_frames_jump_down[] = { + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + + { ai_move, 0, stalker_jump_down }, + { ai_move, 0, stalker_jump_wait_land }, + { ai_move, 0, monster_footstep } +}; +MMOVE_T(stalker_move_jump_down) = { FRAME_jump01, FRAME_jump07, stalker_frames_jump_down, stalker_run }; + +//============ +// stalker_jump - this is only used for jumping onto or off of things. for dodge jumping, +// use stalker_dodge_jump +//============ +void stalker_jump(edict_t *self, blocked_jump_result_t result) +{ + if (!self->enemy) + return; + + if (result == blocked_jump_result_t::JUMP_JUMP_UP) + M_SetAnimation(self, &stalker_move_jump_up); + else + M_SetAnimation(self, &stalker_move_jump_down); +} + +// ****************** +// Blocked +// ****************** +MONSTERINFO_BLOCKED(stalker_blocked) (edict_t *self, float dist) -> bool +{ + if (!has_valid_enemy(self)) + return false; + + bool onCeiling = STALKER_ON_CEILING(self); + + if (!onCeiling) + { + if (auto result = blocked_checkjump(self, dist); result != blocked_jump_result_t::NO_JUMP) + { + if (result != blocked_jump_result_t::JUMP_TURN) + stalker_jump(self, result); + return true; + } + + if (blocked_checkplat(self, dist)) + return true; + + if (visible(self, self->enemy) && frandom() < 0.1f) + { + stalker_do_pounce(self, self->enemy->s.origin); + return true; + } + } + else + { + if (stalker_ok_to_transition(self)) + { + self->gravityVector[2] = -1; + self->s.angles[2] += 180.0f; + if (self->s.angles[2] > 360.0f) + self->s.angles[2] -= 360.0f; + self->groundentity = nullptr; + return true; + } + } + + return false; +} + +// [Paril-KEX] quick patch-job to fix stalkers endlessly floating up into the sky +MONSTERINFO_PHYSCHANGED(stalker_physics_change) (edict_t *self) -> void +{ + if (STALKER_ON_CEILING(self) && !self->groundentity) + { + self->gravityVector[2] = -1; + self->s.angles[2] += 180.0f; + if (self->s.angles[2] > 360.0f) + self->s.angles[2] -= 360.0f; + } +} + +// ****************** +// Death +// ****************** + +void stalker_dead(edict_t *self) +{ + self->mins = { -28, -28, -18 }; + self->maxs = { 28, 28, -4 }; + monster_dead(self); +} + +mframe_t stalker_frames_death[] = { + { ai_move }, + { ai_move, -5 }, + { ai_move, -10 }, + { ai_move, -20 }, + + { ai_move, -10 }, + { ai_move, -10 }, + { ai_move, -5 }, + { ai_move, -5 }, + + { ai_move, 0, monster_footstep } +}; +MMOVE_T(stalker_move_death) = { FRAME_death01, FRAME_death09, stalker_frames_death, stalker_dead }; + +DIE(stalker_die) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void +{ + // dude bit it, make him fall! + self->movetype = MOVETYPE_TOSS; + self->s.angles[2] = 0; + self->gravityVector = { 0, 0, -1 }; + + // check for gib + if (M_CheckGib(self, mod)) + { + gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0); + + self->s.skinnum /= 2; + + ThrowGibs(self, damage, { + { 2, "models/objects/gibs/sm_meat/tris.md2" }, + { 2, "models/objects/gibs/sm_metal/tris.md2", GIB_METALLIC }, + { "models/monsters/stalker/gibs/bodya.md2", GIB_SKINNED }, + { "models/monsters/stalker/gibs/bodyb.md2", GIB_SKINNED }, + { 2, "models/monsters/stalker/gibs/claw.md2", GIB_SKINNED | GIB_UPRIGHT }, + { 2, "models/monsters/stalker/gibs/leg.md2", GIB_SKINNED | GIB_UPRIGHT }, + { 2, "models/monsters/stalker/gibs/foot.md2", GIB_SKINNED }, + { "models/monsters/stalker/gibs/head.md2", GIB_SKINNED | GIB_HEAD } + }); + self->deadflag = true; + return; + } + + if (self->deadflag) + return; + + // regular death + gi.sound(self, CHAN_VOICE, sound_die, 1, ATTN_NORM, 0); + self->deadflag = true; + self->takedamage = true; + M_SetAnimation(self, &stalker_move_death); +} + +// ****************** +// SPAWN +// ****************** + +/*QUAKED monster_stalker (1 .5 0) (-28 -28 -18) (28 28 18) Ambush Trigger_Spawn Sight OnRoof NoJumping +Spider Monster + + ONROOF - Monster starts sticking to the roof. +*/ + +constexpr spawnflags_t SPAWNFLAG_STALKER_ONROOF = 8_spawnflag; +constexpr spawnflags_t SPAWNFLAG_STALKER_NOJUMPING = 16_spawnflag; + +void SP_monster_stalker(edict_t *self) +{ + if ( !M_AllowSpawn( self ) ) { + G_FreeEdict( self ); + return; + } + + sound_pain = gi.soundindex("stalker/pain.wav"); + sound_die = gi.soundindex("stalker/death.wav"); + sound_sight = gi.soundindex("stalker/sight.wav"); + sound_punch_hit1 = gi.soundindex("stalker/melee1.wav"); + sound_punch_hit2 = gi.soundindex("stalker/melee2.wav"); + sound_idle = gi.soundindex("stalker/idle.wav"); + + // PMM - precache bolt2 + gi.modelindex("models/objects/laser/tris.md2"); + + self->s.modelindex = gi.modelindex("models/monsters/stalker/tris.md2"); + + gi.modelindex("models/monsters/stalker/gibs/bodya.md2"); + gi.modelindex("models/monsters/stalker/gibs/bodyb.md2"); + gi.modelindex("models/monsters/stalker/gibs/claw.md2"); + gi.modelindex("models/monsters/stalker/gibs/foot.md2"); + gi.modelindex("models/monsters/stalker/gibs/head.md2"); + gi.modelindex("models/monsters/stalker/gibs/leg.md2"); + + self->mins = { -28, -28, -18 }; + self->maxs = { 28, 28, 18 }; + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + + self->health = 250 * st.health_multiplier; + self->gib_health = -50; + self->mass = 250; + + self->pain = stalker_pain; + self->die = stalker_die; + + self->monsterinfo.stand = stalker_stand; + self->monsterinfo.walk = stalker_walk; + self->monsterinfo.run = stalker_run; + self->monsterinfo.attack = stalker_attack_ranged; + self->monsterinfo.sight = stalker_sight; + self->monsterinfo.idle = stalker_idle; + self->monsterinfo.dodge = stalker_dodge; + self->monsterinfo.blocked = stalker_blocked; + self->monsterinfo.melee = stalker_attack_melee; + self->monsterinfo.setskin = stalker_setskin; + self->monsterinfo.physics_change = stalker_physics_change; + + gi.linkentity(self); + + M_SetAnimation(self, &stalker_move_stand); + self->monsterinfo.scale = MODEL_SCALE; + + if (self->spawnflags.has(SPAWNFLAG_STALKER_ONROOF)) + { + self->s.angles[2] = 180; + self->gravityVector[2] = 1; + } + + self->monsterinfo.can_jump = !self->spawnflags.has(SPAWNFLAG_STALKER_NOJUMPING); + self->monsterinfo.drop_height = 256; + self->monsterinfo.jump_height = 68; + + walkmonster_start(self); +} diff --git a/rerelease/rogue/m_rogue_stalker.h b/rerelease/rogue/m_rogue_stalker.h new file mode 100644 index 0000000..160851a --- /dev/null +++ b/rerelease/rogue/m_rogue_stalker.h @@ -0,0 +1,104 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// /expanse/quake2/xpack/models/monsters/stalker + +// This file generated by qdata - Do NOT Modify + +enum +{ + FRAME_idle01, + FRAME_idle02, + FRAME_idle03, + FRAME_idle04, + FRAME_idle05, + FRAME_idle06, + FRAME_idle07, + FRAME_idle08, + FRAME_idle09, + FRAME_idle10, + FRAME_idle11, + FRAME_idle12, + FRAME_idle13, + FRAME_idle14, + FRAME_idle15, + FRAME_idle16, + FRAME_idle17, + FRAME_idle18, + FRAME_idle19, + FRAME_idle20, + FRAME_idle21, + FRAME_idle201, + FRAME_idle202, + FRAME_idle203, + FRAME_idle204, + FRAME_idle205, + FRAME_idle206, + FRAME_idle207, + FRAME_idle208, + FRAME_idle209, + FRAME_idle210, + FRAME_idle211, + FRAME_idle212, + FRAME_idle213, + FRAME_walk01, + FRAME_walk02, + FRAME_walk03, + FRAME_walk04, + FRAME_walk05, + FRAME_walk06, + FRAME_walk07, + FRAME_walk08, + FRAME_jump01, + FRAME_jump02, + FRAME_jump03, + FRAME_jump04, + FRAME_jump05, + FRAME_jump06, + FRAME_jump07, + FRAME_run01, + FRAME_run02, + FRAME_run03, + FRAME_run04, + FRAME_attack01, + FRAME_attack02, + FRAME_attack03, + FRAME_attack04, + FRAME_attack05, + FRAME_attack06, + FRAME_attack07, + FRAME_attack08, + FRAME_attack11, + FRAME_attack12, + FRAME_attack13, + FRAME_attack14, + FRAME_attack15, + FRAME_pain01, + FRAME_pain02, + FRAME_pain03, + FRAME_pain04, + FRAME_death01, + FRAME_death02, + FRAME_death03, + FRAME_death04, + FRAME_death05, + FRAME_death06, + FRAME_death07, + FRAME_death08, + FRAME_death09, + FRAME_twitch01, + FRAME_twitch02, + FRAME_twitch03, + FRAME_twitch04, + FRAME_twitch05, + FRAME_twitch06, + FRAME_twitch07, + FRAME_twitch08, + FRAME_twitch09, + FRAME_twitch10, + FRAME_reactive01, + FRAME_reactive02, + FRAME_reactive03, + FRAME_reactive04 +}; + +constexpr float MODEL_SCALE = 1.000000f; diff --git a/rerelease/rogue/m_rogue_turret.cpp b/rerelease/rogue/m_rogue_turret.cpp new file mode 100644 index 0000000..b5e06b0 --- /dev/null +++ b/rerelease/rogue/m_rogue_turret.cpp @@ -0,0 +1,1083 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +/* +============================================================================== + +TURRET + +============================================================================== +*/ + +#include "../g_local.h" +#include "m_rogue_turret.h" + +constexpr spawnflags_t SPAWNFLAG_TURRET_BLASTER = 0x0008_spawnflag; +constexpr spawnflags_t SPAWNFLAG_TURRET_MACHINEGUN = 0x0010_spawnflag; +constexpr spawnflags_t SPAWNFLAG_TURRET_ROCKET = 0x0020_spawnflag; +constexpr spawnflags_t SPAWNFLAG_TURRET_HEATBEAM = 0x0040_spawnflag; +constexpr spawnflags_t SPAWNFLAG_TURRET_WEAPONCHOICE = SPAWNFLAG_TURRET_HEATBEAM | SPAWNFLAG_TURRET_ROCKET | SPAWNFLAG_TURRET_MACHINEGUN | SPAWNFLAG_TURRET_BLASTER; +constexpr spawnflags_t SPAWNFLAG_TURRET_WALL_UNIT = 0x0080_spawnflag; +constexpr spawnflags_t SPAWNFLAG_TURRET_NO_LASERSIGHT = 18_spawnflag_bit; + +bool FindTarget(edict_t *self); + +void TurretAim(edict_t *self); +void turret_ready_gun(edict_t *self); +void turret_run(edict_t *self); + +extern const mmove_t turret_move_fire; +extern const mmove_t turret_move_fire_blind; + +static int sound_moved, sound_moving; + +void TurretAim(edict_t *self) +{ + vec3_t end, dir; + vec3_t ang; + float move, idealPitch, idealYaw, current, speed; + int orientation; + + if (!self->enemy || self->enemy == world) + { + if (!FindTarget(self)) + return; + } + + // if turret is still in inactive mode, ready the gun, but don't aim + if (self->s.frame < FRAME_active01) + { + turret_ready_gun(self); + return; + } + // if turret is still readying, don't aim. + if (self->s.frame < FRAME_run01) + return; + + // PMM - blindfire aiming here + if (self->monsterinfo.active_move == &turret_move_fire_blind) + { + end = self->monsterinfo.blind_fire_target; + if (self->enemy->s.origin[2] < self->monsterinfo.blind_fire_target[2]) + end[2] += self->enemy->viewheight + 10; + else + end[2] += self->enemy->mins[2] - 10; + } + else + { + end = self->enemy->s.origin; + if (self->enemy->client) + end[2] += self->enemy->viewheight; + } + + dir = end - self->s.origin; + ang = vectoangles(dir); + + // + // Clamp first + // + + idealPitch = ang[PITCH]; + idealYaw = ang[YAW]; + + orientation = (int) self->offset[1]; + switch (orientation) + { + case -1: // up pitch: 0 to 90 + if (idealPitch < -90) + idealPitch += 360; + if (idealPitch > -5) + idealPitch = -5; + break; + case -2: // down pitch: -180 to -360 + if (idealPitch > -90) + idealPitch -= 360; + if (idealPitch < -355) + idealPitch = -355; + else if (idealPitch > -185) + idealPitch = -185; + break; + case 0: // +X pitch: 0 to -90, -270 to -360 (or 0 to 90) + if (idealPitch < -180) + idealPitch += 360; + + if (idealPitch > 85) + idealPitch = 85; + else if (idealPitch < -85) + idealPitch = -85; + + // yaw: 270 to 360, 0 to 90 + // yaw: -90 to 90 (270-360 == -90-0) + if (idealYaw > 180) + idealYaw -= 360; + if (idealYaw > 85) + idealYaw = 85; + else if (idealYaw < -85) + idealYaw = -85; + break; + case 90: // +Y pitch: 0 to 90, -270 to -360 (or 0 to 90) + if (idealPitch < -180) + idealPitch += 360; + + if (idealPitch > 85) + idealPitch = 85; + else if (idealPitch < -85) + idealPitch = -85; + + // yaw: 0 to 180 + if (idealYaw > 270) + idealYaw -= 360; + if (idealYaw > 175) + idealYaw = 175; + else if (idealYaw < 5) + idealYaw = 5; + + break; + case 180: // -X pitch: 0 to 90, -270 to -360 (or 0 to 90) + if (idealPitch < -180) + idealPitch += 360; + + if (idealPitch > 85) + idealPitch = 85; + else if (idealPitch < -85) + idealPitch = -85; + + // yaw: 90 to 270 + if (idealYaw > 265) + idealYaw = 265; + else if (idealYaw < 95) + idealYaw = 95; + + break; + case 270: // -Y pitch: 0 to 90, -270 to -360 (or 0 to 90) + if (idealPitch < -180) + idealPitch += 360; + + if (idealPitch > 85) + idealPitch = 85; + else if (idealPitch < -85) + idealPitch = -85; + + // yaw: 180 to 360 + if (idealYaw < 90) + idealYaw += 360; + if (idealYaw > 355) + idealYaw = 355; + else if (idealYaw < 185) + idealYaw = 185; + break; + } + + // + // adjust pitch + // + current = self->s.angles[PITCH]; + speed = self->yaw_speed / (gi.tick_rate / 10); + + if (idealPitch != current) + { + move = idealPitch - current; + + while (move >= 360) + move -= 360; + if (move >= 90) + { + move = move - 360; + } + + while (move <= -360) + move += 360; + if (move <= -90) + { + move = move + 360; + } + + if (move > 0) + { + if (move > speed) + move = speed; + } + else + { + if (move < -speed) + move = -speed; + } + + self->s.angles[PITCH] = anglemod(current + move); + } + + // + // adjust yaw + // + current = self->s.angles[YAW]; + + if (idealYaw != current) + { + move = idealYaw - current; + + // while(move >= 360) + // move -= 360; + if (move >= 180) + { + move = move - 360; + } + + // while(move <= -360) + // move += 360; + if (move <= -180) + { + move = move + 360; + } + + if (move > 0) + { + if (move > speed) + move = speed; + } + else + { + if (move < -speed) + move = -speed; + } + + self->s.angles[YAW] = anglemod(current + move); + } + + if (self->spawnflags.has(SPAWNFLAG_TURRET_NO_LASERSIGHT)) + return; + + // Paril: improved turrets; draw lasersight + if (!self->target_ent) + { + self->target_ent = G_Spawn(); + self->target_ent->s.modelindex = MODELINDEX_WORLD; + self->target_ent->s.renderfx = RF_BEAM; + self->target_ent->s.frame = 1; + self->target_ent->s.skinnum = 0xf0f0f0f0; + self->target_ent->classname = "turret_lasersight"; + self->target_ent->s.origin = self->s.origin; + } + + vec3_t forward; + AngleVectors(self->s.angles, forward, nullptr, nullptr); + end = self->s.origin + (forward * 8192); + trace_t tr = gi.traceline(self->s.origin, end, self, MASK_SOLID); + + float scan_range = 64.f; + + if (visible(self, self->enemy)) + scan_range = 12.f; + + tr.endpos[0] += sinf(level.time.seconds() + self->s.number) * scan_range; + tr.endpos[1] += cosf((level.time.seconds() - self->s.number) * 3.f) * scan_range; + tr.endpos[2] += sinf((level.time.seconds() - self->s.number) * 2.5f) * scan_range; + + forward = tr.endpos - self->s.origin; + forward.normalize(); + + end = self->s.origin + (forward * 8192); + tr = gi.traceline(self->s.origin, end, self, MASK_SOLID); + + self->target_ent->s.old_origin = tr.endpos; + gi.linkentity(self->target_ent); +} + +MONSTERINFO_SIGHT(turret_sight) (edict_t *self, edict_t *other) -> void +{ +} + +MONSTERINFO_SEARCH(turret_search) (edict_t *self) -> void +{ +} + +mframe_t turret_frames_stand[] = { + { ai_stand }, + { ai_stand } +}; +MMOVE_T(turret_move_stand) = { FRAME_stand01, FRAME_stand02, turret_frames_stand, nullptr }; + +MONSTERINFO_STAND(turret_stand) (edict_t *self) -> void +{ + M_SetAnimation(self, &turret_move_stand); + if (self->target_ent) + { + G_FreeEdict(self->target_ent); + self->target_ent = nullptr; + } +} + +mframe_t turret_frames_ready_gun[] = { + { ai_stand }, + { ai_stand }, + { ai_stand }, + + { ai_stand }, + { ai_stand }, + { ai_stand }, + + { ai_stand } +}; +MMOVE_T(turret_move_ready_gun) = { FRAME_active01, FRAME_run01, turret_frames_ready_gun, turret_run }; + +void turret_ready_gun(edict_t *self) +{ + if (self->monsterinfo.active_move != &turret_move_ready_gun) + { + M_SetAnimation(self, &turret_move_ready_gun); + self->monsterinfo.weapon_sound = sound_moving; + } +} + +mframe_t turret_frames_seek[] = { + { ai_walk, 0, TurretAim }, + { ai_walk, 0, TurretAim } +}; +MMOVE_T(turret_move_seek) = { FRAME_run01, FRAME_run02, turret_frames_seek, nullptr }; + +MONSTERINFO_WALK(turret_walk) (edict_t *self) -> void +{ + if (self->s.frame < FRAME_run01) + turret_ready_gun(self); + else + M_SetAnimation(self, &turret_move_seek); +} + +mframe_t turret_frames_run[] = { + { ai_run, 0, TurretAim }, + { ai_run, 0, TurretAim } +}; +MMOVE_T(turret_move_run) = { FRAME_run01, FRAME_run02, turret_frames_run, turret_run }; + +MONSTERINFO_RUN(turret_run) (edict_t *self) -> void +{ + if (self->s.frame < FRAME_run01) + turret_ready_gun(self); + else + { + self->monsterinfo.aiflags |= AI_HIGH_TICK_RATE; + M_SetAnimation(self, &turret_move_run); + + if (self->monsterinfo.weapon_sound) + { + self->monsterinfo.weapon_sound = 0; + gi.sound(self, CHAN_WEAPON, sound_moved, 1.0f, ATTN_NORM, 0.f); + } + } +} + +// ********************** +// ATTACK +// ********************** + +constexpr int32_t TURRET_BLASTER_DAMAGE = 8; +constexpr int32_t TURRET_BULLET_DAMAGE = 2; +// unused +// constexpr int32_t TURRET_HEAT_DAMAGE = 4; + +void TurretFire(edict_t *self) +{ + vec3_t forward; + vec3_t start, end, dir; + float dist, chance; + trace_t trace; + int rocketSpeed; + + TurretAim(self); + + if (!self->enemy || !self->enemy->inuse) + return; + + if (self->monsterinfo.aiflags & AI_LOST_SIGHT) + end = self->monsterinfo.blind_fire_target; + else + end = self->enemy->s.origin; + dir = end - self->s.origin; + dir.normalize(); + AngleVectors(self->s.angles, forward, nullptr, nullptr); + chance = dir.dot(forward); + if (chance < 0.98f) + return; + + chance = frandom(); + + // rockets fire less often than the others do. + if (self->spawnflags.has(SPAWNFLAG_TURRET_ROCKET)) + { + chance = chance * 3; + + rocketSpeed = 650; + } + else if (self->spawnflags.has(SPAWNFLAG_TURRET_BLASTER)) + { + rocketSpeed = 800; + chance = chance * 2; + } + else + rocketSpeed = 0; + + if (self->spawnflags.has(SPAWNFLAG_TURRET_MACHINEGUN) || visible(self, self->enemy)) + { + start = self->s.origin; + + // aim for the head. + if (!(self->monsterinfo.aiflags & AI_LOST_SIGHT)) + { + if ((self->enemy) && (self->enemy->client)) + end[2] += self->enemy->viewheight; + else + end[2] += 22; + } + + dir = end - start; + dist = dir.length(); + + // check for predictive fire + // Paril: adjusted to be a bit more fair + if (!(self->monsterinfo.aiflags & AI_LOST_SIGHT)) + { + // on harder difficulties, randomly fire directly at enemy + // more often; makes them more unpredictable + if (self->spawnflags.has(SPAWNFLAG_TURRET_MACHINEGUN)) + PredictAim(self, self->enemy, start, 0, true, 0.3f, &dir, nullptr); + else if (frandom() < skill->integer / 5.f) + PredictAim(self, self->enemy, start, (float) rocketSpeed, true, (frandom(3.f - skill->integer) / 3.f) - frandom(0.05f * (3.f - skill->integer)), &dir, nullptr); + } + + dir.normalize(); + trace = gi.traceline(start, end, self, MASK_PROJECTILE); + if (trace.ent == self->enemy || trace.ent == world) + { + if (self->spawnflags.has(SPAWNFLAG_TURRET_BLASTER)) + monster_fire_blaster(self, start, dir, TURRET_BLASTER_DAMAGE, rocketSpeed, MZ2_TURRET_BLASTER, EF_BLASTER); + else if (self->spawnflags.has(SPAWNFLAG_TURRET_MACHINEGUN)) + { + if (!(self->monsterinfo.aiflags & AI_HOLD_FRAME)) + { + self->monsterinfo.aiflags |= AI_HOLD_FRAME; + self->monsterinfo.duck_wait_time = level.time + 2_sec + gtime_t::from_sec(frandom(skill->value)); + self->monsterinfo.next_duck_time = level.time + 1_sec; + gi.sound(self, CHAN_VOICE, gi.soundindex("weapons/chngnu1a.wav"), 1, ATTN_NORM, 0); + } + else + { + if (self->monsterinfo.next_duck_time < level.time && + self->monsterinfo.melee_debounce_time <= level.time) + { + monster_fire_bullet(self, start, dir, TURRET_BULLET_DAMAGE, 0, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, MZ2_TURRET_MACHINEGUN); + self->monsterinfo.melee_debounce_time = level.time + 10_hz; + } + + if (self->monsterinfo.duck_wait_time < level.time) + self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; + } + } + else if (self->spawnflags.has(SPAWNFLAG_TURRET_ROCKET)) + { + if (dist * trace.fraction > 72) + monster_fire_rocket(self, start, dir, 40, rocketSpeed, MZ2_TURRET_ROCKET); + } + } + } +} + +// PMM +void TurretFireBlind(edict_t *self) +{ + vec3_t forward; + vec3_t start, end, dir; + float chance; + int rocketSpeed = 550; + + TurretAim(self); + + if (!self->enemy || !self->enemy->inuse) + return; + + dir = self->monsterinfo.blind_fire_target - self->s.origin; + dir.normalize(); + AngleVectors(self->s.angles, forward, nullptr, nullptr); + chance = dir.dot(forward); + if (chance < 0.98f) + return; + + if (self->spawnflags.has(SPAWNFLAG_TURRET_ROCKET)) + { + if (skill->integer == 2) + { + rocketSpeed += (int) frandom(200); + } + else if (skill->integer == 3) + { + rocketSpeed += (int) frandom(100, 300); + } + } + + start = self->s.origin; + end = self->monsterinfo.blind_fire_target; + + if (self->enemy->s.origin[2] < self->monsterinfo.blind_fire_target[2]) + end[2] += self->enemy->viewheight + 10; + else + end[2] += self->enemy->mins[2] - 10; + + dir = end - start; + + dir.normalize(); + + if (self->spawnflags.has(SPAWNFLAG_TURRET_BLASTER)) + monster_fire_blaster(self, start, dir, 20, 1000, MZ2_TURRET_BLASTER, EF_BLASTER); + else if (self->spawnflags.has(SPAWNFLAG_TURRET_ROCKET)) + monster_fire_rocket(self, start, dir, 50, rocketSpeed, MZ2_TURRET_ROCKET); +} +// pmm + +mframe_t turret_frames_fire[] = { + { ai_run, 0, TurretFire }, + { ai_run, 0, TurretAim }, + { ai_run, 0, TurretAim }, + { ai_run, 0, TurretAim } +}; +MMOVE_T(turret_move_fire) = { FRAME_pow01, FRAME_pow04, turret_frames_fire, turret_run }; + +// PMM + +// the blind frames need to aim first +mframe_t turret_frames_fire_blind[] = { + { ai_run, 0, TurretAim }, + { ai_run, 0, TurretAim }, + { ai_run, 0, TurretAim }, + { ai_run, 0, TurretFireBlind } +}; +MMOVE_T(turret_move_fire_blind) = { FRAME_pow01, FRAME_pow04, turret_frames_fire_blind, turret_run }; +// pmm + +MONSTERINFO_ATTACK(turret_attack) (edict_t *self) -> void +{ + float r, chance; + + if (self->s.frame < FRAME_run01) + turret_ready_gun(self); + // PMM + else if (self->monsterinfo.attack_state != AS_BLIND) + { + M_SetAnimation(self, &turret_move_fire); + } + else + { + // setup shot probabilities + if (self->monsterinfo.blind_fire_delay < 1_sec) + chance = 1.0; + else if (self->monsterinfo.blind_fire_delay < 7.5_sec) + chance = 0.4f; + else + chance = 0.1f; + + r = frandom(); + + // minimum of 3 seconds, plus 0-4, after the shots are done - total time should be max less than 7.5 + self->monsterinfo.blind_fire_delay += random_time(3.4_sec, 7.4_sec); + // don't shoot at the origin + if (!self->monsterinfo.blind_fire_target) + return; + + // don't shoot if the dice say not to + if (r > chance) + return; + + M_SetAnimation(self, &turret_move_fire_blind); + } + // pmm +} + +// ********************** +// PAIN +// ********************** + +PAIN(turret_pain) (edict_t *self, edict_t *other, float kick, int damage, const mod_t &mod) -> void +{ +} + +// ********************** +// DEATH +// ********************** + +DIE(turret_die) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void +{ + vec3_t forward; + edict_t *base; + + AngleVectors(self->s.angles, forward, nullptr, nullptr); + self->s.origin += (forward * 1); + + ThrowGibs(self, 2, { + { 2, "models/objects/debris1/tris.md2", GIB_METALLIC | GIB_DEBRIS } + }); + ThrowGibs(self, 1, { + { 2, "models/objects/debris1/tris.md2", GIB_METALLIC | GIB_DEBRIS } + }); + + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_PLAIN_EXPLOSION); + gi.WritePosition(self->s.origin); + gi.multicast(self->s.origin, MULTICAST_PHS, false); + + if (self->teamchain) + { + base = self->teamchain; + base->solid = SOLID_BBOX; + base->takedamage = false; + base->movetype = MOVETYPE_NONE; + base->teammaster = base; + base->teamchain = nullptr; + base->flags &= ~FL_TEAMSLAVE; + base->flags |= FL_TEAMMASTER; + gi.linkentity(base); + + self->teammaster = self->teamchain = nullptr; + self->flags &= ~(FL_TEAMSLAVE | FL_TEAMMASTER); + } + + if (self->target) + { + if (self->enemy && self->enemy->inuse) + G_UseTargets(self, self->enemy); + else + G_UseTargets(self, self); + } + + if (self->target_ent) + { + G_FreeEdict(self->target_ent); + self->target_ent = nullptr; + } + + edict_t *gib = ThrowGib(self, "models/monsters/turret/tris.md2", damage, GIB_SKINNED | GIB_METALLIC | GIB_HEAD | GIB_DEBRIS, self->s.scale); + gib->s.frame = 14; +} + +// ********************** +// WALL SPAWN +// ********************** + +void turret_wall_spawn(edict_t *turret) +{ + edict_t *ent; + int angle; + + ent = G_Spawn(); + ent->s.origin = turret->s.origin; + ent->s.angles = turret->s.angles; + + angle = (int) ent->s.angles[1]; + if (ent->s.angles[0] == 90) + angle = -1; + else if (ent->s.angles[0] == 270) + angle = -2; + switch (angle) + { + case -1: + ent->mins = { -16, -16, -8 }; + ent->maxs = { 16, 16, 0 }; + break; + case -2: + ent->mins = { -16, -16, 0 }; + ent->maxs = { 16, 16, 8 }; + break; + case 0: + ent->mins = { -8, -16, -16 }; + ent->maxs = { 0, 16, 16 }; + break; + case 90: + ent->mins = { -16, -8, -16 }; + ent->maxs = { 16, 0, 16 }; + break; + case 180: + ent->mins = { 0, -16, -16 }; + ent->maxs = { 8, 16, 16 }; + break; + case 270: + ent->mins = { -16, 0, -16 }; + ent->maxs = { 16, 8, 16 }; + break; + } + + ent->movetype = MOVETYPE_PUSH; + ent->solid = SOLID_NOT; + + ent->teammaster = turret; + turret->flags |= FL_TEAMMASTER; + turret->teammaster = turret; + turret->teamchain = ent; + ent->teamchain = nullptr; + ent->flags |= FL_TEAMSLAVE; + ent->owner = turret; + + ent->s.modelindex = gi.modelindex("models/monsters/turretbase/tris.md2"); + + gi.linkentity(ent); +} + +MOVEINFO_ENDFUNC(turret_wake) (edict_t *ent) -> void +{ + // the wall section will call this when it stops moving. + // just return without doing anything. easiest way to have a null function. + if (ent->flags & FL_TEAMSLAVE) + { + ent->s.sound = 0; + return; + } + + ent->monsterinfo.stand = turret_stand; + ent->monsterinfo.walk = turret_walk; + ent->monsterinfo.run = turret_run; + ent->monsterinfo.dodge = nullptr; + ent->monsterinfo.attack = turret_attack; + ent->monsterinfo.melee = nullptr; + ent->monsterinfo.sight = turret_sight; + ent->monsterinfo.search = turret_search; + M_SetAnimation(ent, &turret_move_stand); + ent->takedamage = true; + ent->movetype = MOVETYPE_NONE; + // prevent counting twice + ent->monsterinfo.aiflags |= AI_DO_NOT_COUNT; + + gi.linkentity(ent); + + stationarymonster_start(ent); + + if (ent->spawnflags.has(SPAWNFLAG_TURRET_MACHINEGUN)) + { + ent->s.skinnum = 1; + } + else if (ent->spawnflags.has(SPAWNFLAG_TURRET_ROCKET)) + { + ent->s.skinnum = 2; + } + + // but we do want the death to count + ent->monsterinfo.aiflags &= ~AI_DO_NOT_COUNT; +} + +USE(turret_activate) (edict_t *self, edict_t *other, edict_t *activator) -> void +{ + vec3_t endpos; + vec3_t forward; + edict_t *base; + + self->movetype = MOVETYPE_PUSH; + if (!self->speed) + self->speed = 15; + self->moveinfo.speed = self->speed; + self->moveinfo.accel = self->speed; + self->moveinfo.decel = self->speed; + + if (self->s.angles[0] == 270) + { + forward = { 0, 0, 1 }; + } + else if (self->s.angles[0] == 90) + { + forward = { 0, 0, -1 }; + } + else if (self->s.angles[1] == 0) + { + forward = { 1, 0, 0 }; + } + else if (self->s.angles[1] == 90) + { + forward = { 0, 1, 0 }; + } + else if (self->s.angles[1] == 180) + { + forward = { -1, 0, 0 }; + } + else if (self->s.angles[1] == 270) + { + forward = { 0, -1, 0 }; + } + + // start up the turret + endpos = self->s.origin + (forward * 32); + Move_Calc(self, endpos, turret_wake); + + base = self->teamchain; + if (base) + { + base->movetype = MOVETYPE_PUSH; + base->speed = self->speed; + base->moveinfo.speed = base->speed; + base->moveinfo.accel = base->speed; + base->moveinfo.decel = base->speed; + + // start up the wall section + endpos = self->teamchain->s.origin + (forward * 32); + Move_Calc(self->teamchain, endpos, turret_wake); + + base->s.sound = sound_moving; + base->s.loop_attenuation = ATTN_NORM; + } +} + +// PMM +// checkattack .. ignore range, just attack if available +MONSTERINFO_CHECKATTACK(turret_checkattack) (edict_t *self) -> bool +{ + vec3_t spot1, spot2; + float chance; + trace_t tr; + + if (self->enemy->health > 0) + { + // see if any entities are in the way of the shot + spot1 = self->s.origin; + spot1[2] += self->viewheight; + spot2 = self->enemy->s.origin; + spot2[2] += self->enemy->viewheight; + + tr = gi.traceline(spot1, spot2, self, CONTENTS_SOLID | CONTENTS_PLAYER | CONTENTS_MONSTER | CONTENTS_SLIME | CONTENTS_LAVA | CONTENTS_WINDOW); + + // do we have a clear shot? + if (tr.ent != self->enemy && !(tr.ent->svflags & SVF_PLAYER)) + { + // PGM - we want them to go ahead and shoot at info_notnulls if they can. + if (self->enemy->solid != SOLID_NOT || tr.fraction < 1.0f) // PGM + { + // PMM - if we can't see our target, and we're not blocked by a monster, go into blind fire if available + if ((!(tr.ent->svflags & SVF_MONSTER)) && (!visible(self, self->enemy))) + { + if ((self->monsterinfo.blindfire) && (self->monsterinfo.blind_fire_delay <= 10_sec)) + { + if (level.time < self->monsterinfo.attack_finished) + { + return false; + } + if (level.time < (self->monsterinfo.trail_time + self->monsterinfo.blind_fire_delay)) + { + // wait for our time + return false; + } + else + { + // make sure we're not going to shoot something we don't want to shoot + tr = gi.traceline(spot1, self->monsterinfo.blind_fire_target, self, CONTENTS_MONSTER | CONTENTS_PLAYER); + if (tr.allsolid || tr.startsolid || ((tr.fraction < 1.0f) && (tr.ent != self->enemy && !(tr.ent->svflags & SVF_PLAYER)))) + { + return false; + } + + self->monsterinfo.attack_state = AS_BLIND; + self->monsterinfo.attack_finished = level.time + random_time(500_ms, 2.5_sec); + return true; + } + } + } + // pmm + return false; + } + } + } + + if (level.time < self->monsterinfo.attack_finished) + return false; + + gtime_t nexttime; + + if (self->spawnflags.has(SPAWNFLAG_TURRET_ROCKET)) + { + chance = 0.10f; + nexttime = (1.8_sec - (0.2_sec * skill->integer)); + } + else if (self->spawnflags.has(SPAWNFLAG_TURRET_BLASTER)) + { + chance = 0.35f; + nexttime = (1.2_sec - (0.2_sec * skill->integer)); + } + else + { + chance = 0.50f; + nexttime = (0.8_sec - (0.1_sec * skill->integer)); + } + + if (skill->integer == 0) + chance *= 0.5f; + else if (skill->integer > 1) + chance *= 2; + + // PGM - go ahead and shoot every time if it's a info_notnull + // PMM - added visibility check + if (((frandom() < chance) && (visible(self, self->enemy))) || (self->enemy->solid == SOLID_NOT)) + { + self->monsterinfo.attack_state = AS_MISSILE; + self->monsterinfo.attack_finished = level.time + nexttime; + return true; + } + + self->monsterinfo.attack_state = AS_STRAIGHT; + + return false; +} + +// ********************** +// SPAWN +// ********************** + +/*QUAKED monster_turret (1 .5 0) (-16 -16 -16) (16 16 16) Ambush Trigger_Spawn Sight Blaster MachineGun Rocket Heatbeam WallUnit + +The automated defense turret that mounts on walls. +Check the weapon you want it to use: blaster, machinegun, rocket, heatbeam. +Default weapon is blaster. +When activated, wall units move 32 units in the direction they're facing. +*/ +void SP_monster_turret(edict_t *self) +{ + int angle; + + if ( !M_AllowSpawn( self ) ) { + G_FreeEdict( self ); + return; + } + + // pre-caches + sound_moved = gi.soundindex("turret/moved.wav"); + sound_moving = gi.soundindex("turret/moving.wav"); + gi.modelindex("models/objects/debris1/tris.md2"); + + self->s.modelindex = gi.modelindex("models/monsters/turret/tris.md2"); + + self->mins = { -12, -12, -12 }; + self->maxs = { 12, 12, 12 }; + self->movetype = MOVETYPE_NONE; + self->solid = SOLID_BBOX; + + self->health = 50 * st.health_multiplier; + self->gib_health = -100; + self->mass = 250; + self->yaw_speed = 10 * skill->integer; + + self->monsterinfo.armor_type = IT_ARMOR_COMBAT; + self->monsterinfo.armor_power = 50; + + self->flags |= FL_MECHANICAL; + + self->pain = turret_pain; + self->die = turret_die; + + // map designer didn't specify weapon type. set it now. + if (!self->spawnflags.has(SPAWNFLAG_TURRET_WEAPONCHOICE)) + self->spawnflags |= SPAWNFLAG_TURRET_BLASTER; + + if (self->spawnflags.has(SPAWNFLAG_TURRET_HEATBEAM)) + { + self->spawnflags &= ~SPAWNFLAG_TURRET_HEATBEAM; + self->spawnflags |= SPAWNFLAG_TURRET_BLASTER; + } + + if (!self->spawnflags.has(SPAWNFLAG_TURRET_WALL_UNIT)) + { + self->monsterinfo.stand = turret_stand; + self->monsterinfo.walk = turret_walk; + self->monsterinfo.run = turret_run; + self->monsterinfo.dodge = nullptr; + self->monsterinfo.attack = turret_attack; + self->monsterinfo.melee = nullptr; + self->monsterinfo.sight = turret_sight; + self->monsterinfo.search = turret_search; + M_SetAnimation(self, &turret_move_stand); + } + + // PMM + self->monsterinfo.checkattack = turret_checkattack; + + self->monsterinfo.aiflags |= AI_MANUAL_STEERING; + self->monsterinfo.scale = MODEL_SCALE; + self->gravity = 0; + + self->offset = self->s.angles; + angle = (int) self->s.angles[1]; + switch (angle) + { + case -1: // up + self->s.angles[0] = 270; + self->s.angles[1] = 0; + self->s.origin[2] += 2; + break; + case -2: // down + self->s.angles[0] = 90; + self->s.angles[1] = 0; + self->s.origin[2] -= 2; + break; + case 0: + self->s.origin[0] += 2; + break; + case 90: + self->s.origin[1] += 2; + break; + case 180: + self->s.origin[0] -= 2; + break; + case 270: + self->s.origin[1] -= 2; + break; + default: + break; + } + + gi.linkentity(self); + + if (self->spawnflags.has(SPAWNFLAG_TURRET_WALL_UNIT)) + { + if (!self->targetname) + { + G_FreeEdict(self); + return; + } + + self->takedamage = false; + self->use = turret_activate; + turret_wall_spawn(self); + if (!(self->monsterinfo.aiflags & AI_DO_NOT_COUNT)) + { + if (g_debug_monster_kills->integer) + level.monsters_registered[level.total_monsters] = self; + level.total_monsters++; + } + } + else + { + stationarymonster_start(self); + } + + if (self->spawnflags.has(SPAWNFLAG_TURRET_MACHINEGUN)) + { + gi.soundindex("infantry/infatck1.wav"); + gi.soundindex("weapons/chngnu1a.wav"); + self->s.skinnum = 1; + + self->spawnflags &= ~SPAWNFLAG_TURRET_WEAPONCHOICE; + self->spawnflags |= SPAWNFLAG_TURRET_MACHINEGUN; + } + else if (self->spawnflags.has(SPAWNFLAG_TURRET_ROCKET)) + { + gi.soundindex("weapons/rockfly.wav"); + gi.modelindex("models/objects/rocket/tris.md2"); + gi.soundindex("chick/chkatck2.wav"); + self->s.skinnum = 2; + + self->spawnflags &= ~SPAWNFLAG_TURRET_WEAPONCHOICE; + self->spawnflags |= SPAWNFLAG_TURRET_ROCKET; + } + else + { + gi.modelindex("models/objects/laser/tris.md2"); + gi.soundindex("misc/lasfly.wav"); + gi.soundindex("soldier/solatck2.wav"); + + self->spawnflags &= ~SPAWNFLAG_TURRET_WEAPONCHOICE; + self->spawnflags |= SPAWNFLAG_TURRET_BLASTER; + } + + // PMM - turrets don't get mad at monsters, and visa versa + self->monsterinfo.aiflags |= AI_IGNORE_SHOTS; + // PMM - blindfire + if (self->spawnflags.has(SPAWNFLAG_TURRET_ROCKET | SPAWNFLAG_TURRET_BLASTER)) + self->monsterinfo.blindfire = true; +} diff --git a/rerelease/rogue/m_rogue_turret.h b/rerelease/rogue/m_rogue_turret.h new file mode 100644 index 0000000..e7044a0 --- /dev/null +++ b/rerelease/rogue/m_rogue_turret.h @@ -0,0 +1,27 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// G:\quake2\xpack\models/monsters/turret + +// This file generated by qdata - Do NOT Modify + +enum +{ + FRAME_stand01, + FRAME_stand02, + FRAME_active01, + FRAME_active02, + FRAME_active03, + FRAME_active04, + FRAME_active05, + FRAME_active06, + FRAME_run01, + FRAME_run02, + FRAME_pow01, + FRAME_pow02, + FRAME_pow03, + FRAME_pow04, + FRAME_death01, + FRAME_death02 +}; + +constexpr float MODEL_SCALE = 3.500000f; diff --git a/rerelease/rogue/m_rogue_widow.cpp b/rerelease/rogue/m_rogue_widow.cpp new file mode 100644 index 0000000..0ed1d46 --- /dev/null +++ b/rerelease/rogue/m_rogue_widow.cpp @@ -0,0 +1,1390 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +/* +============================================================================== + +black widow + +============================================================================== +*/ + +// self->timestamp used to prevent rapid fire of railgun +// self->plat2flags used for fire count (flashes) + +#include "../g_local.h" +#include "m_rogue_widow.h" +#include "../m_flash.h" + +constexpr gtime_t RAIL_TIME = 3_sec; +constexpr gtime_t BLASTER_TIME = 2_sec; +constexpr int BLASTER2_DAMAGE = 10; +constexpr int WIDOW_RAIL_DAMAGE = 50; + +bool infront(edict_t *self, edict_t *other); + +static int sound_pain1; +static int sound_pain2; +static int sound_pain3; +static int sound_rail; + +static uint32_t shotsfired; + +constexpr vec3_t spawnpoints[] = { + { 30, 100, 16 }, + { 30, -100, 16 } +}; + +constexpr vec3_t beameffects[] = { + { 12.58f, -43.71f, 68.88f }, + { 3.43f, 58.72f, 68.41f } +}; + +constexpr float sweep_angles[] = { + 32.f, 26.f, 20.f, 10.f, 0.f, -6.5f, -13.f, -27.f, -41.f +}; + +constexpr vec3_t stalker_mins = { -28, -28, -18 }; +constexpr vec3_t stalker_maxs = { 28, 28, 18 }; + +unsigned int widow_damage_multiplier; + +void widow_run(edict_t *self); +void widow_dead(edict_t *self); +void widow_attack_blaster(edict_t *self); +void widow_reattack_blaster(edict_t *self); + +void widow_start_spawn(edict_t *self); +void widow_done_spawn(edict_t *self); +void widow_spawn_check(edict_t *self); +void widow_prep_spawn(edict_t *self); +void widow_attack_rail(edict_t *self); + +void widow_start_run_5(edict_t *self); +void widow_start_run_10(edict_t *self); +void widow_start_run_12(edict_t *self); + +void WidowCalcSlots(edict_t *self); + +MONSTERINFO_SEARCH(widow_search) (edict_t *self) -> void +{ +} + +MONSTERINFO_SIGHT(widow_sight) (edict_t *self, edict_t *other) -> void +{ + self->monsterinfo.fire_wait = 0_ms; +} + +extern const mmove_t widow_move_attack_post_blaster; +extern const mmove_t widow_move_attack_post_blaster_r; +extern const mmove_t widow_move_attack_post_blaster_l; +extern const mmove_t widow_move_attack_blaster; + +float target_angle(edict_t *self) +{ + vec3_t target; + float enemy_yaw; + + target = self->s.origin - self->enemy->s.origin; + enemy_yaw = self->s.angles[YAW] - vectoyaw(target); + if (enemy_yaw < 0) + enemy_yaw += 360.0f; + + // this gets me 0 degrees = forward + enemy_yaw -= 180.0f; + // positive is to right, negative to left + + return enemy_yaw; +} + +int WidowTorso(edict_t *self) +{ + float enemy_yaw = target_angle(self); + + if (enemy_yaw >= 105) + { + M_SetAnimation(self, &widow_move_attack_post_blaster_r); + self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING; + return 0; + } + + if (enemy_yaw <= -75.0f) + { + M_SetAnimation(self, &widow_move_attack_post_blaster_l); + self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING; + return 0; + } + + if (enemy_yaw >= 95) + return FRAME_fired03; + else if (enemy_yaw >= 85) + return FRAME_fired04; + else if (enemy_yaw >= 75) + return FRAME_fired05; + else if (enemy_yaw >= 65) + return FRAME_fired06; + else if (enemy_yaw >= 55) + return FRAME_fired07; + else if (enemy_yaw >= 45) + return FRAME_fired08; + else if (enemy_yaw >= 35) + return FRAME_fired09; + else if (enemy_yaw >= 25) + return FRAME_fired10; + else if (enemy_yaw >= 15) + return FRAME_fired11; + else if (enemy_yaw >= 5) + return FRAME_fired12; + else if (enemy_yaw >= -5) + return FRAME_fired13; + else if (enemy_yaw >= -15) + return FRAME_fired14; + else if (enemy_yaw >= -25) + return FRAME_fired15; + else if (enemy_yaw >= -35) + return FRAME_fired16; + else if (enemy_yaw >= -45) + return FRAME_fired17; + else if (enemy_yaw >= -55) + return FRAME_fired18; + else if (enemy_yaw >= -65) + return FRAME_fired19; + else if (enemy_yaw >= -75) + return FRAME_fired20; + + return 0; +} + +constexpr float VARIANCE = 15.0f; + +void WidowBlaster(edict_t *self) +{ + vec3_t forward, right, target, vec, targ_angles; + vec3_t start; + monster_muzzleflash_id_t flashnum; + effects_t effect; + + if (!self->enemy) + return; + + shotsfired++; + if (!(shotsfired % 4)) + effect = EF_BLASTER; + else + effect = EF_NONE; + + AngleVectors(self->s.angles, forward, right, nullptr); + if ((self->s.frame >= FRAME_spawn05) && (self->s.frame <= FRAME_spawn13)) + { + // sweep + flashnum = static_cast(MZ2_WIDOW_BLASTER_SWEEP1 + self->s.frame - FRAME_spawn05); + start = G_ProjectSource(self->s.origin, monster_flash_offset[flashnum], forward, right); + target = self->enemy->s.origin - start; + targ_angles = vectoangles(target); + + vec = self->s.angles; + + vec[PITCH] += targ_angles[PITCH]; + vec[YAW] -= sweep_angles[flashnum - MZ2_WIDOW_BLASTER_SWEEP1]; + + AngleVectors(vec, forward, nullptr, nullptr); + monster_fire_blaster2(self, start, forward, BLASTER2_DAMAGE * widow_damage_multiplier, 1000, flashnum, effect); + } + else if ((self->s.frame >= FRAME_fired02a) && (self->s.frame <= FRAME_fired20)) + { + vec3_t angles; + float aim_angle, target_angle; + float error; + + self->monsterinfo.aiflags |= AI_MANUAL_STEERING; + + self->monsterinfo.nextframe = WidowTorso(self); + + if (!self->monsterinfo.nextframe) + self->monsterinfo.nextframe = self->s.frame; + + if (self->s.frame == FRAME_fired02a) + flashnum = MZ2_WIDOW_BLASTER_0; + else + flashnum = static_cast(MZ2_WIDOW_BLASTER_100 + self->s.frame - FRAME_fired03); + + start = G_ProjectSource(self->s.origin, monster_flash_offset[flashnum], forward, right); + + PredictAim(self, self->enemy, start, 1000, true, crandom() * 0.1f, &forward, nullptr); + + // clamp it to within 10 degrees of the aiming angle (where she's facing) + angles = vectoangles(forward); + // give me 100 -> -70 + aim_angle = (float) (100 - (10 * (flashnum - MZ2_WIDOW_BLASTER_100))); + if (aim_angle <= 0) + aim_angle += 360; + target_angle = self->s.angles[YAW] - angles[YAW]; + if (target_angle <= 0) + target_angle += 360; + + error = aim_angle - target_angle; + + // positive error is to entity's left, aka positive direction in engine + // unfortunately, I decided that for the aim_angle, positive was right. *sigh* + if (error > VARIANCE) + { + angles[YAW] = (self->s.angles[YAW] - aim_angle) + VARIANCE; + AngleVectors(angles, forward, nullptr, nullptr); + } + else if (error < -VARIANCE) + { + angles[YAW] = (self->s.angles[YAW] - aim_angle) - VARIANCE; + AngleVectors(angles, forward, nullptr, nullptr); + } + + monster_fire_blaster2(self, start, forward, BLASTER2_DAMAGE * widow_damage_multiplier, 1000, flashnum, effect); + } + else if ((self->s.frame >= FRAME_run01) && (self->s.frame <= FRAME_run08)) + { + flashnum = static_cast(MZ2_WIDOW_RUN_1 + self->s.frame - FRAME_run01); + start = G_ProjectSource(self->s.origin, monster_flash_offset[flashnum], forward, right); + + target = self->enemy->s.origin - start; + target[2] += self->enemy->viewheight; + target.normalize(); + + monster_fire_blaster2(self, start, target, BLASTER2_DAMAGE * widow_damage_multiplier, 1000, flashnum, effect); + } +} + +void WidowSpawn(edict_t *self) +{ + vec3_t f, r, u, offset, startpoint, spawnpoint; + edict_t *ent, *designated_enemy; + int i; + + AngleVectors(self->s.angles, f, r, u); + + for (i = 0; i < 2; i++) + { + offset = spawnpoints[i]; + + startpoint = G_ProjectSource2(self->s.origin, offset, f, r, u); + + if (FindSpawnPoint(startpoint, stalker_mins, stalker_maxs, spawnpoint, 64)) + { + ent = CreateGroundMonster(spawnpoint, self->s.angles, stalker_mins, stalker_maxs, "monster_stalker", 256); + + if (!ent) + continue; + + self->monsterinfo.monster_used++; + ent->monsterinfo.commander = self; + + ent->nextthink = level.time; + ent->think(ent); + + ent->monsterinfo.aiflags |= AI_SPAWNED_WIDOW | AI_DO_NOT_COUNT | AI_IGNORE_SHOTS; + + if (!coop->integer) + { + designated_enemy = self->enemy; + } + else + { + designated_enemy = PickCoopTarget(ent); + if (designated_enemy) + { + // try to avoid using my enemy + if (designated_enemy == self->enemy) + { + designated_enemy = PickCoopTarget(ent); + if (!designated_enemy) + designated_enemy = self->enemy; + } + } + else + designated_enemy = self->enemy; + } + + if ((designated_enemy->inuse) && (designated_enemy->health > 0)) + { + ent->enemy = designated_enemy; + FoundTarget(ent); + ent->monsterinfo.attack(ent); + } + } + } +} + +void widow_spawn_check(edict_t *self) +{ + WidowBlaster(self); + WidowSpawn(self); +} + +void widow_ready_spawn(edict_t *self) +{ + vec3_t f, r, u, offset, startpoint, spawnpoint; + int i; + + WidowBlaster(self); + AngleVectors(self->s.angles, f, r, u); + + for (i = 0; i < 2; i++) + { + offset = spawnpoints[i]; + startpoint = G_ProjectSource2(self->s.origin, offset, f, r, u); + if (FindSpawnPoint(startpoint, stalker_mins, stalker_maxs, spawnpoint, 64)) + { + float radius = (stalker_maxs - stalker_mins).length() * 0.5f; + + SpawnGrow_Spawn(spawnpoint + (stalker_mins + stalker_maxs), radius, radius * 2.f); + } + } +} + +void widow_step(edict_t *self) +{ + gi.sound(self, CHAN_BODY, gi.soundindex("widow/bwstep3.wav"), 1, ATTN_NORM, 0); +} + +mframe_t widow_frames_stand[] = { + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand } +}; +MMOVE_T(widow_move_stand) = { FRAME_idle01, FRAME_idle11, widow_frames_stand, nullptr }; + +mframe_t widow_frames_walk[] = { + { ai_walk, 2.79f, widow_step }, + { ai_walk, 2.77f }, + { ai_walk, 3.53f }, + { ai_walk, 3.97f }, + { ai_walk, 4.13f }, // 5 + { ai_walk, 4.09f }, + { ai_walk, 3.84f }, + { ai_walk, 3.62f, widow_step }, + { ai_walk, 3.29f }, + { ai_walk, 6.08f }, // 10 + { ai_walk, 6.94f }, + { ai_walk, 5.73f }, + { ai_walk, 2.85f } +}; +MMOVE_T(widow_move_walk) = { FRAME_walk01, FRAME_walk13, widow_frames_walk, nullptr }; + +mframe_t widow_frames_run[] = { + { ai_run, 2.79f, widow_step }, + { ai_run, 2.77f }, + { ai_run, 3.53f }, + { ai_run, 3.97f }, + { ai_run, 4.13f }, // 5 + { ai_run, 4.09f }, + { ai_run, 3.84f }, + { ai_run, 3.62f, widow_step }, + { ai_run, 3.29f }, + { ai_run, 6.08f }, // 10 + { ai_run, 6.94f }, + { ai_run, 5.73f }, + { ai_run, 2.85f } +}; +MMOVE_T(widow_move_run) = { FRAME_walk01, FRAME_walk13, widow_frames_run, nullptr }; + +void widow_stepshoot(edict_t *self) +{ + gi.sound(self, CHAN_BODY, gi.soundindex("widow/bwstep2.wav"), 1, ATTN_NORM, 0); + WidowBlaster(self); +} + +mframe_t widow_frames_run_attack[] = { + { ai_charge, 13, widow_stepshoot }, + { ai_charge, 11.72f, WidowBlaster }, + { ai_charge, 18.04f, WidowBlaster }, + { ai_charge, 14.58f, WidowBlaster }, + { ai_charge, 13, widow_stepshoot }, // 5 + { ai_charge, 12.12f, WidowBlaster }, + { ai_charge, 19.63f, WidowBlaster }, + { ai_charge, 11.37f, WidowBlaster } +}; +MMOVE_T(widow_move_run_attack) = { FRAME_run01, FRAME_run08, widow_frames_run_attack, widow_run }; + +// +// These three allow specific entry into the run sequence +// + +void widow_start_run_5(edict_t *self) +{ + M_SetAnimation(self, &widow_move_run); + self->monsterinfo.nextframe = FRAME_walk05; +} + +void widow_start_run_10(edict_t *self) +{ + M_SetAnimation(self, &widow_move_run); + self->monsterinfo.nextframe = FRAME_walk10; +} + +void widow_start_run_12(edict_t *self) +{ + M_SetAnimation(self, &widow_move_run); + self->monsterinfo.nextframe = FRAME_walk12; +} + +mframe_t widow_frames_attack_pre_blaster[] = { + { ai_charge }, + { ai_charge }, + { ai_charge, 0, widow_attack_blaster } +}; +MMOVE_T(widow_move_attack_pre_blaster) = { FRAME_fired01, FRAME_fired02a, widow_frames_attack_pre_blaster, nullptr }; + +// Loop this +mframe_t widow_frames_attack_blaster[] = { + { ai_charge, 0, widow_reattack_blaster }, // straight ahead + { ai_charge, 0, widow_reattack_blaster }, // 100 degrees right + { ai_charge, 0, widow_reattack_blaster }, + { ai_charge, 0, widow_reattack_blaster }, + { ai_charge, 0, widow_reattack_blaster }, + { ai_charge, 0, widow_reattack_blaster }, + { ai_charge, 0, widow_reattack_blaster }, // 50 degrees right + { ai_charge, 0, widow_reattack_blaster }, + { ai_charge, 0, widow_reattack_blaster }, + { ai_charge, 0, widow_reattack_blaster }, + { ai_charge, 0, widow_reattack_blaster }, + { ai_charge, 0, widow_reattack_blaster }, // straight + { ai_charge, 0, widow_reattack_blaster }, + { ai_charge, 0, widow_reattack_blaster }, + { ai_charge, 0, widow_reattack_blaster }, + { ai_charge, 0, widow_reattack_blaster }, + { ai_charge, 0, widow_reattack_blaster }, // 50 degrees left + { ai_charge, 0, widow_reattack_blaster }, + { ai_charge, 0, widow_reattack_blaster } // 70 degrees left +}; +MMOVE_T(widow_move_attack_blaster) = { FRAME_fired02a, FRAME_fired20, widow_frames_attack_blaster, nullptr }; + +mframe_t widow_frames_attack_post_blaster[] = { + { ai_charge }, + { ai_charge } +}; +MMOVE_T(widow_move_attack_post_blaster) = { FRAME_fired21, FRAME_fired22, widow_frames_attack_post_blaster, widow_run }; + +mframe_t widow_frames_attack_post_blaster_r[] = { + { ai_charge, -2 }, + { ai_charge, -10 }, + { ai_charge, -2 }, + { ai_charge }, + { ai_charge, 0, widow_start_run_12 } +}; +MMOVE_T(widow_move_attack_post_blaster_r) = { FRAME_transa01, FRAME_transa05, widow_frames_attack_post_blaster_r, nullptr }; + +mframe_t widow_frames_attack_post_blaster_l[] = { + { ai_charge }, + { ai_charge, 14 }, + { ai_charge, -2 }, + { ai_charge, 10 }, + { ai_charge, 10, widow_start_run_12 } +}; +MMOVE_T(widow_move_attack_post_blaster_l) = { FRAME_transb01, FRAME_transb05, widow_frames_attack_post_blaster_l, nullptr }; + +extern const mmove_t widow_move_attack_rail; +extern const mmove_t widow_move_attack_rail_l; +extern const mmove_t widow_move_attack_rail_r; + +void WidowRail(edict_t *self) +{ + vec3_t start; + vec3_t dir; + vec3_t forward, right; + monster_muzzleflash_id_t flash; + + AngleVectors(self->s.angles, forward, right, nullptr); + + if (self->monsterinfo.active_move == &widow_move_attack_rail_l) + { + flash = MZ2_WIDOW_RAIL_LEFT; + } + else if (self->monsterinfo.active_move == &widow_move_attack_rail_r) + { + flash = MZ2_WIDOW_RAIL_RIGHT; + } + else + flash = MZ2_WIDOW_RAIL; + + start = G_ProjectSource(self->s.origin, monster_flash_offset[flash], forward, right); + + // calc direction to where we targeted + dir = self->pos1 - start; + dir.normalize(); + + monster_fire_railgun(self, start, dir, WIDOW_RAIL_DAMAGE * widow_damage_multiplier, 100, flash); + self->timestamp = level.time + RAIL_TIME; +} + +void WidowSaveLoc(edict_t *self) +{ + self->pos1 = self->enemy->s.origin; // save for aiming the shot + self->pos1[2] += self->enemy->viewheight; +}; + +void widow_start_rail(edict_t *self) +{ + self->monsterinfo.aiflags |= AI_MANUAL_STEERING; +} + +void widow_rail_done(edict_t *self) +{ + self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING; +} + +mframe_t widow_frames_attack_pre_rail[] = { + { ai_charge, 0, widow_start_rail }, + { ai_charge }, + { ai_charge }, + { ai_charge, 0, widow_attack_rail } +}; +MMOVE_T(widow_move_attack_pre_rail) = { FRAME_transc01, FRAME_transc04, widow_frames_attack_pre_rail, nullptr }; + +mframe_t widow_frames_attack_rail[] = { + { ai_charge }, + { ai_charge }, + { ai_charge, 0, WidowSaveLoc }, + { ai_charge, -10, WidowRail }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge, 0, widow_rail_done } +}; +MMOVE_T(widow_move_attack_rail) = { FRAME_firea01, FRAME_firea09, widow_frames_attack_rail, widow_run }; + +mframe_t widow_frames_attack_rail_r[] = { + { ai_charge }, + { ai_charge }, + { ai_charge, 0, WidowSaveLoc }, + { ai_charge, -10, WidowRail }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge, 0, widow_rail_done } +}; +MMOVE_T(widow_move_attack_rail_r) = { FRAME_fireb01, FRAME_fireb09, widow_frames_attack_rail_r, widow_run }; + +mframe_t widow_frames_attack_rail_l[] = { + { ai_charge }, + { ai_charge }, + { ai_charge, 0, WidowSaveLoc }, + { ai_charge, -10, WidowRail }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge, 0, widow_rail_done } +}; +MMOVE_T(widow_move_attack_rail_l) = { FRAME_firec01, FRAME_firec09, widow_frames_attack_rail_l, widow_run }; + +void widow_attack_rail(edict_t *self) +{ + float enemy_angle; + + enemy_angle = target_angle(self); + + if (enemy_angle < -15) + M_SetAnimation(self, &widow_move_attack_rail_l); + else if (enemy_angle > 15) + M_SetAnimation(self, &widow_move_attack_rail_r); + else + M_SetAnimation(self, &widow_move_attack_rail); +} + +void widow_start_spawn(edict_t *self) +{ + self->monsterinfo.aiflags |= AI_MANUAL_STEERING; +} + +void widow_done_spawn(edict_t *self) +{ + self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING; +} + +mframe_t widow_frames_spawn[] = { + { ai_charge }, // 1 + { ai_charge }, + { ai_charge }, + { ai_charge, 0, widow_start_spawn }, + { ai_charge }, // 5 + { ai_charge, 0, WidowBlaster }, // 6 + { ai_charge, 0, widow_ready_spawn }, // 7 + { ai_charge, 0, WidowBlaster }, + { ai_charge, 0, WidowBlaster }, // 9 + { ai_charge, 0, widow_spawn_check }, + { ai_charge, 0, WidowBlaster }, // 11 + { ai_charge, 0, WidowBlaster }, + { ai_charge, 0, WidowBlaster }, // 13 + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge, 0, widow_done_spawn } +}; +MMOVE_T(widow_move_spawn) = { FRAME_spawn01, FRAME_spawn18, widow_frames_spawn, widow_run }; + +mframe_t widow_frames_pain_heavy[] = { + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move } +}; +MMOVE_T(widow_move_pain_heavy) = { FRAME_pain01, FRAME_pain13, widow_frames_pain_heavy, widow_run }; + +mframe_t widow_frames_pain_light[] = { + { ai_move }, + { ai_move }, + { ai_move } +}; +MMOVE_T(widow_move_pain_light) = { FRAME_pain201, FRAME_pain203, widow_frames_pain_light, widow_run }; + +void spawn_out_start(edict_t *self) +{ + vec3_t startpoint, f, r, u; + + // gi.sound (self, CHAN_VOICE, sound_death, 1, ATTN_NONE, 0); + AngleVectors(self->s.angles, f, r, u); + + startpoint = G_ProjectSource2(self->s.origin, beameffects[0], f, r, u); + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_WIDOWBEAMOUT); + gi.WriteShort(20001); + gi.WritePosition(startpoint); + gi.multicast(startpoint, MULTICAST_ALL, false); + + startpoint = G_ProjectSource2(self->s.origin, beameffects[1], f, r, u); + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_WIDOWBEAMOUT); + gi.WriteShort(20002); + gi.WritePosition(startpoint); + gi.multicast(startpoint, MULTICAST_ALL, false); + + gi.sound(self, CHAN_VOICE, gi.soundindex("misc/bwidowbeamout.wav"), 1, ATTN_NORM, 0); +} + +void spawn_out_do(edict_t *self) +{ + vec3_t startpoint, f, r, u; + + AngleVectors(self->s.angles, f, r, u); + startpoint = G_ProjectSource2(self->s.origin, beameffects[0], f, r, u); + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_WIDOWSPLASH); + gi.WritePosition(startpoint); + gi.multicast(startpoint, MULTICAST_ALL, false); + + startpoint = G_ProjectSource2(self->s.origin, beameffects[1], f, r, u); + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_WIDOWSPLASH); + gi.WritePosition(startpoint); + gi.multicast(startpoint, MULTICAST_ALL, false); + + startpoint = self->s.origin; + startpoint[2] += 36; + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_BOSSTPORT); + gi.WritePosition(startpoint); + gi.multicast(startpoint, MULTICAST_PHS, false); + + Widowlegs_Spawn(self->s.origin, self->s.angles); + + G_FreeEdict(self); +} + +mframe_t widow_frames_death[] = { + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, // 5 + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move, 0, spawn_out_start }, // 10 + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, // 15 + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, // 20 + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, // 25 + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, // 30 + { ai_move, 0, spawn_out_do } +}; +MMOVE_T(widow_move_death) = { FRAME_death01, FRAME_death31, widow_frames_death, nullptr }; + +void widow_attack_kick(edict_t *self) +{ + vec3_t aim = { 100, 0, 4 }; + if (self->enemy->groundentity) + fire_hit(self, aim, irandom(50, 56), 500); + else // not as much kick if they're in the air .. makes it harder to land on her head + fire_hit(self, aim, irandom(50, 56), 250); +} + +mframe_t widow_frames_attack_kick[] = { + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move, 0, widow_attack_kick }, + { ai_move }, // 5 + { ai_move }, + { ai_move }, + { ai_move } +}; + +MMOVE_T(widow_move_attack_kick) = { FRAME_kick01, FRAME_kick08, widow_frames_attack_kick, widow_run }; + +MONSTERINFO_STAND(widow_stand) (edict_t *self) -> void +{ + gi.sound(self, CHAN_WEAPON, gi.soundindex("widow/laugh.wav"), 1, ATTN_NORM, 0); + M_SetAnimation(self, &widow_move_stand); +} + +MONSTERINFO_RUN(widow_run) (edict_t *self) -> void +{ + self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; + + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + M_SetAnimation(self, &widow_move_stand); + else + M_SetAnimation(self, &widow_move_run); +} + +MONSTERINFO_WALK(widow_walk) (edict_t *self) -> void +{ + M_SetAnimation(self, &widow_move_walk); +} + +MONSTERINFO_ATTACK(widow_attack) (edict_t *self) -> void +{ + float luck; + bool rail_frames = false, blaster_frames = false, blocked = false, anger = false; + + self->movetarget = nullptr; + + if (self->monsterinfo.aiflags & AI_BLOCKED) + { + blocked = true; + self->monsterinfo.aiflags &= ~AI_BLOCKED; + } + + if (self->monsterinfo.aiflags & AI_TARGET_ANGER) + { + anger = true; + self->monsterinfo.aiflags &= ~AI_TARGET_ANGER; + } + + if ((!self->enemy) || (!self->enemy->inuse)) + return; + + if (self->bad_area) + { + if ((frandom() < 0.1f) || (level.time < self->timestamp)) + M_SetAnimation(self, &widow_move_attack_pre_blaster); + else + { + gi.sound(self, CHAN_WEAPON, sound_rail, 1, ATTN_NORM, 0); + M_SetAnimation(self, &widow_move_attack_pre_rail); + } + return; + } + + // frames FRAME_walk13, FRAME_walk01, FRAME_walk02, FRAME_walk03 are rail gun start frames + // frames FRAME_walk09, FRAME_walk10, FRAME_walk11, FRAME_walk12 are spawn & blaster start frames + + if ((self->s.frame == FRAME_walk13) || ((self->s.frame >= FRAME_walk01) && (self->s.frame <= FRAME_walk03))) + rail_frames = true; + + if ((self->s.frame >= FRAME_walk09) && (self->s.frame <= FRAME_walk12)) + blaster_frames = true; + + WidowCalcSlots(self); + + // if we can't see the target, spawn stuff regardless of frame + if ((self->monsterinfo.attack_state == AS_BLIND) && (M_SlotsLeft(self) >= 2)) + { + M_SetAnimation(self, &widow_move_spawn); + return; + } + + // accept bias towards spawning regardless of frame + if (blocked && (M_SlotsLeft(self) >= 2)) + { + M_SetAnimation(self, &widow_move_spawn); + return; + } + + if ((realrange(self, self->enemy) > 300) && (!anger) && (frandom() < 0.5f) && (!blocked)) + { + M_SetAnimation(self, &widow_move_run_attack); + return; + } + + if (blaster_frames) + { + if (M_SlotsLeft(self) >= 2) + { + M_SetAnimation(self, &widow_move_spawn); + return; + } + else if (self->monsterinfo.fire_wait + BLASTER_TIME <= level.time) + { + M_SetAnimation(self, &widow_move_attack_pre_blaster); + return; + } + } + + if (rail_frames) + { + if (!(level.time < self->timestamp)) + { + gi.sound(self, CHAN_WEAPON, sound_rail, 1, ATTN_NORM, 0); + M_SetAnimation(self, &widow_move_attack_pre_rail); + } + } + + if ((rail_frames) || (blaster_frames)) + return; + + luck = frandom(); + if (M_SlotsLeft(self) >= 2) + { + if ((luck <= 0.40f) && (self->monsterinfo.fire_wait + BLASTER_TIME <= level.time)) + M_SetAnimation(self, &widow_move_attack_pre_blaster); + else if ((luck <= 0.7f) && !(level.time < self->timestamp)) + { + gi.sound(self, CHAN_WEAPON, sound_rail, 1, ATTN_NORM, 0); + M_SetAnimation(self, &widow_move_attack_pre_rail); + } + else + M_SetAnimation(self, &widow_move_spawn); + } + else + { + if (level.time < self->timestamp) + M_SetAnimation(self, &widow_move_attack_pre_blaster); + else if ((luck <= 0.50f) || (level.time + BLASTER_TIME >= self->monsterinfo.fire_wait)) + { + gi.sound(self, CHAN_WEAPON, sound_rail, 1, ATTN_NORM, 0); + M_SetAnimation(self, &widow_move_attack_pre_rail); + } + else // holdout to blaster + M_SetAnimation(self, &widow_move_attack_pre_blaster); + } +} + +void widow_attack_blaster(edict_t *self) +{ + self->monsterinfo.fire_wait = level.time + random_time(1_sec, 3_sec); + M_SetAnimation(self, &widow_move_attack_blaster); + self->monsterinfo.nextframe = WidowTorso(self); +} + +void widow_reattack_blaster(edict_t *self) +{ + WidowBlaster(self); + + // if WidowBlaster bailed us out of the frames, just bail + if ((self->monsterinfo.active_move == &widow_move_attack_post_blaster_l) || + (self->monsterinfo.active_move == &widow_move_attack_post_blaster_r)) + return; + + // if we're not done with the attack, don't leave the sequence + if (self->monsterinfo.fire_wait >= level.time) + return; + + self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING; + + M_SetAnimation(self, &widow_move_attack_post_blaster); +} + +PAIN(widow_pain) (edict_t *self, edict_t *other, float kick, int damage, const mod_t &mod) -> void +{ + if (level.time < self->pain_debounce_time) + return; + + self->pain_debounce_time = level.time + 5_sec; + + if (damage < 15) + gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NONE, 0); + else if (damage < 75) + gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NONE, 0); + else + gi.sound(self, CHAN_VOICE, sound_pain3, 1, ATTN_NONE, 0); + + if (!M_ShouldReactToPain(self, mod)) + return; // no pain anims in nightmare + + self->monsterinfo.fire_wait = 0_ms; + + if (damage >= 15) + { + if (damage < 75) + { + if ((skill->integer < 3) && (frandom() < (0.6f - (0.2f * skill->integer)))) + { + M_SetAnimation(self, &widow_move_pain_light); + self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING; + } + } + else + { + if ((skill->integer < 3) && (frandom() < (0.75f - (0.1f * skill->integer)))) + { + M_SetAnimation(self, &widow_move_pain_heavy); + self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING; + } + } + } +} + +MONSTERINFO_SETSKIN(widow_setskin) (edict_t *self) -> void +{ + if (self->health < (self->max_health / 2)) + self->s.skinnum = 1; + else + self->s.skinnum = 0; +} + +void widow_dead(edict_t *self) +{ + self->mins = { -56, -56, 0 }; + self->maxs = { 56, 56, 80 }; + self->movetype = MOVETYPE_TOSS; + self->svflags |= SVF_DEADMONSTER; + self->nextthink = 0_ms; + gi.linkentity(self); +} + +DIE(widow_die) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void +{ + self->deadflag = true; + self->takedamage = false; + self->count = 0; + self->monsterinfo.quad_time = 0_ms; + self->monsterinfo.double_time = 0_ms; + self->monsterinfo.invincible_time = 0_ms; + M_SetAnimation(self, &widow_move_death); +} + +MONSTERINFO_MELEE(widow_melee) (edict_t *self) -> void +{ + // monster_done_dodge (self); + M_SetAnimation(self, &widow_move_attack_kick); +} + +void WidowGoinQuad(edict_t *self, gtime_t time) +{ + self->monsterinfo.quad_time = time; + widow_damage_multiplier = 4; +} + +void WidowDouble(edict_t *self, gtime_t time) +{ + self->monsterinfo.double_time = time; + widow_damage_multiplier = 2; +} + +void WidowPent(edict_t *self, gtime_t time) +{ + self->monsterinfo.invincible_time = time; +} + +void WidowPowerArmor(edict_t *self) +{ + self->monsterinfo.power_armor_type = IT_ITEM_POWER_SHIELD; + // I don't like this, but it works + if (self->monsterinfo.power_armor_power <= 0) + self->monsterinfo.power_armor_power += 250 * skill->integer; +} + +void WidowRespondPowerup(edict_t *self, edict_t *other) +{ + if (other->s.effects & EF_QUAD) + { + if (skill->integer == 1) + WidowDouble(self, other->client->quad_time); + else if (skill->integer == 2) + WidowGoinQuad(self, other->client->quad_time); + else if (skill->integer == 3) + { + WidowGoinQuad(self, other->client->quad_time); + WidowPowerArmor(self); + } + } + else if (other->s.effects & EF_DOUBLE) + { + if (skill->integer == 2) + WidowDouble(self, other->client->double_time); + else if (skill->integer == 3) + { + WidowDouble(self, other->client->double_time); + WidowPowerArmor(self); + } + } + else + widow_damage_multiplier = 1; + + if (other->s.effects & EF_PENT) + { + if (skill->integer == 1) + WidowPowerArmor(self); + else if (skill->integer == 2) + WidowPent(self, other->client->invincible_time); + else if (skill->integer == 3) + { + WidowPent(self, other->client->invincible_time); + WidowPowerArmor(self); + } + } +} + +void WidowPowerups(edict_t *self) +{ + edict_t *ent; + + if (!coop->integer) + { + WidowRespondPowerup(self, self->enemy); + } + else + { + // in coop, check for pents, then quads, then doubles + for (uint32_t player = 1; player <= game.maxclients; player++) + { + ent = &g_edicts[player]; + if (!ent->inuse) + continue; + if (!ent->client) + continue; + if (ent->s.effects & EF_PENT) + { + WidowRespondPowerup(self, ent); + return; + } + } + + for (uint32_t player = 1; player <= game.maxclients; player++) + { + ent = &g_edicts[player]; + if (!ent->inuse) + continue; + if (!ent->client) + continue; + if (ent->s.effects & EF_QUAD) + { + WidowRespondPowerup(self, ent); + return; + } + } + + for (uint32_t player = 1; player <= game.maxclients; player++) + { + ent = &g_edicts[player]; + if (!ent->inuse) + continue; + if (!ent->client) + continue; + if (ent->s.effects & EF_DOUBLE) + { + WidowRespondPowerup(self, ent); + return; + } + } + } +} + +MONSTERINFO_CHECKATTACK(Widow_CheckAttack) (edict_t *self) -> bool +{ + vec3_t spot1, spot2; + vec3_t temp; + float chance; + trace_t tr; + float enemy_yaw; + float real_enemy_range; + + if (!self->enemy) + return false; + + WidowPowerups(self); + + if (self->monsterinfo.active_move == &widow_move_run) + { + // if we're in run, make sure we're in a good frame for attacking before doing anything else + // frames 1,2,3,9,10,11,13 good to fire + switch (self->s.frame) + { + case FRAME_walk04: + case FRAME_walk05: + case FRAME_walk06: + case FRAME_walk07: + case FRAME_walk08: + case FRAME_walk12: + return false; + default: + break; + } + } + + // give a LARGE bias to spawning things when we have room + // use AI_BLOCKED as a signal to attack to spawn + if ((frandom() < 0.8f) && (M_SlotsLeft(self) >= 2) && (realrange(self, self->enemy) > 150)) + { + self->monsterinfo.aiflags |= AI_BLOCKED; + self->monsterinfo.attack_state = AS_MISSILE; + return true; + } + + if (self->enemy->health > 0) + { + // see if any entities are in the way of the shot + spot1 = self->s.origin; + spot1[2] += self->viewheight; + spot2 = self->enemy->s.origin; + spot2[2] += self->enemy->viewheight; + + tr = gi.traceline(spot1, spot2, self, CONTENTS_SOLID | CONTENTS_MONSTER | CONTENTS_PLAYER | CONTENTS_SLIME | CONTENTS_LAVA); + + // do we have a clear shot? + if (tr.ent != self->enemy && !(tr.ent->svflags & SVF_PLAYER)) + { + // go ahead and spawn stuff if we're mad a a client + if (self->enemy->client && M_SlotsLeft(self) >= 2) + { + self->monsterinfo.attack_state = AS_BLIND; + return true; + } + + // PGM - we want them to go ahead and shoot at info_notnulls if they can. + if (self->enemy->solid != SOLID_NOT || tr.fraction < 1.0f) // PGM + return false; + } + } + + float enemy_range = range_to(self, self->enemy); + temp = self->enemy->s.origin - self->s.origin; + enemy_yaw = vectoyaw(temp); + + self->ideal_yaw = enemy_yaw; + + real_enemy_range = realrange(self, self->enemy); + + // melee attack + if (real_enemy_range <= (MELEE_DISTANCE + 20)) + { + // don't always melee in easy mode + if (skill->integer == 0 && irandom(4)) + return false; + if (self->monsterinfo.melee) + self->monsterinfo.attack_state = AS_MELEE; + else + self->monsterinfo.attack_state = AS_MISSILE; + return true; + } + + if (level.time < self->monsterinfo.attack_finished) + return false; + + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + { + chance = 0.4f; + } + else if (enemy_range <= RANGE_MELEE) + { + chance = 0.8f; + } + else if (enemy_range <= RANGE_NEAR) + { + chance = 0.7f; + } + else if (enemy_range <= RANGE_MID) + { + chance = 0.6f; + } + else + { + chance = 0.5f; + } + + // PGM - go ahead and shoot every time if it's a info_notnull + if ((frandom() < chance) || (self->enemy->solid == SOLID_NOT)) + { + self->monsterinfo.attack_state = AS_MISSILE; + return true; + } + + return false; +} + +MONSTERINFO_BLOCKED(widow_blocked) (edict_t *self, float dist) -> bool +{ + // if we get blocked while we're in our run/attack mode, turn on a meaningless (in this context)AI flag, + // and call attack to get a new attack sequence. make sure to turn it off when we're done. + // + // I'm using AI_TARGET_ANGER for this purpose + + if (self->monsterinfo.active_move == &widow_move_run_attack) + { + self->monsterinfo.aiflags |= AI_TARGET_ANGER; + if (self->monsterinfo.checkattack(self)) + self->monsterinfo.attack(self); + else + self->monsterinfo.run(self); + return true; + } + + return false; +} + +void WidowCalcSlots(edict_t *self) +{ + switch (skill->integer) + { + case 0: + case 1: + self->monsterinfo.monster_slots = 3; + break; + case 2: + self->monsterinfo.monster_slots = 4; + break; + case 3: + self->monsterinfo.monster_slots = 6; + break; + default: + self->monsterinfo.monster_slots = 3; + break; + } + if (coop->integer) + { + self->monsterinfo.monster_slots = min(6, self->monsterinfo.monster_slots + (skill->integer * (CountPlayers() - 1))); + } +} + +void WidowPrecache() +{ + // cache in all of the stalker stuff, widow stuff, spawngro stuff, gibs + gi.soundindex("stalker/pain.wav"); + gi.soundindex("stalker/death.wav"); + gi.soundindex("stalker/sight.wav"); + gi.soundindex("stalker/melee1.wav"); + gi.soundindex("stalker/melee2.wav"); + gi.soundindex("stalker/idle.wav"); + + gi.soundindex("tank/tnkatck3.wav"); + gi.modelindex("models/objects/laser/tris.md2"); + + gi.modelindex("models/monsters/stalker/tris.md2"); + gi.modelindex("models/items/spawngro3/tris.md2"); + gi.modelindex("models/objects/gibs/sm_metal/tris.md2"); + gi.modelindex("models/objects/gibs/gear/tris.md2"); + gi.modelindex("models/monsters/blackwidow/gib1/tris.md2"); + gi.modelindex("models/monsters/blackwidow/gib2/tris.md2"); + gi.modelindex("models/monsters/blackwidow/gib3/tris.md2"); + gi.modelindex("models/monsters/blackwidow/gib4/tris.md2"); + gi.modelindex("models/monsters/blackwidow2/gib1/tris.md2"); + gi.modelindex("models/monsters/blackwidow2/gib2/tris.md2"); + gi.modelindex("models/monsters/blackwidow2/gib3/tris.md2"); + gi.modelindex("models/monsters/blackwidow2/gib4/tris.md2"); + gi.modelindex("models/monsters/legs/tris.md2"); + gi.soundindex("misc/bwidowbeamout.wav"); + + gi.soundindex("misc/bigtele.wav"); + gi.soundindex("widow/bwstep3.wav"); + gi.soundindex("widow/bwstep2.wav"); + gi.soundindex("widow/bwstep1.wav"); +} + +/*QUAKED monster_widow (1 .5 0) (-40 -40 0) (40 40 144) Ambush Trigger_Spawn Sight + */ +void SP_monster_widow(edict_t *self) +{ + if ( !M_AllowSpawn( self ) ) { + G_FreeEdict( self ); + return; + } + + sound_pain1 = gi.soundindex("widow/bw1pain1.wav"); + sound_pain2 = gi.soundindex("widow/bw1pain2.wav"); + sound_pain3 = gi.soundindex("widow/bw1pain3.wav"); + sound_rail = gi.soundindex("gladiator/railgun.wav"); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex("models/monsters/blackwidow/tris.md2"); + self->mins = { -40, -40, 0 }; + self->maxs = { 40, 40, 144 }; + + self->health = (2000 + 1000 * skill->integer) * st.health_multiplier; + if (coop->integer) + self->health += 500 * skill->integer; + self->gib_health = -5000; + self->mass = 1500; + + if (skill->integer == 3) + { + if (!st.was_key_specified("power_armor_type")) + self->monsterinfo.power_armor_type = IT_ITEM_POWER_SHIELD; + if (!st.was_key_specified("power_armor_power")) + self->monsterinfo.power_armor_power = 500; + } + + self->yaw_speed = 30; + + self->flags |= FL_IMMUNE_LASER; + self->monsterinfo.aiflags |= AI_IGNORE_SHOTS; + + self->pain = widow_pain; + self->die = widow_die; + + self->monsterinfo.melee = widow_melee; + self->monsterinfo.stand = widow_stand; + self->monsterinfo.walk = widow_walk; + self->monsterinfo.run = widow_run; + self->monsterinfo.attack = widow_attack; + self->monsterinfo.search = widow_search; + self->monsterinfo.checkattack = Widow_CheckAttack; + self->monsterinfo.sight = widow_sight; + self->monsterinfo.setskin = widow_setskin; + self->monsterinfo.blocked = widow_blocked; + + gi.linkentity(self); + + M_SetAnimation(self, &widow_move_stand); + self->monsterinfo.scale = MODEL_SCALE; + + WidowPrecache(); + WidowCalcSlots(self); + widow_damage_multiplier = 1; + + walkmonster_start(self); +} \ No newline at end of file diff --git a/rerelease/rogue/m_rogue_widow.h b/rerelease/rogue/m_rogue_widow.h new file mode 100644 index 0000000..12ae629 --- /dev/null +++ b/rerelease/rogue/m_rogue_widow.h @@ -0,0 +1,180 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// G:\quake2\xpack\models/monsters/blackwidow + +// This file generated by qdata - Do NOT Modify + +enum +{ + FRAME_idle01, + FRAME_idle02, + FRAME_idle03, + FRAME_idle04, + FRAME_idle05, + FRAME_idle06, + FRAME_idle07, + FRAME_idle08, + FRAME_idle09, + FRAME_idle10, + FRAME_idle11, + FRAME_walk01, + FRAME_walk02, + FRAME_walk03, + FRAME_walk04, + FRAME_walk05, + FRAME_walk06, + FRAME_walk07, + FRAME_walk08, + FRAME_walk09, + FRAME_walk10, + FRAME_walk11, + FRAME_walk12, + FRAME_walk13, + FRAME_run01, + FRAME_run02, + FRAME_run03, + FRAME_run04, + FRAME_run05, + FRAME_run06, + FRAME_run07, + FRAME_run08, + FRAME_firea01, + FRAME_firea02, + FRAME_firea03, + FRAME_firea04, + FRAME_firea05, + FRAME_firea06, + FRAME_firea07, + FRAME_firea08, + FRAME_firea09, + FRAME_fireb01, + FRAME_fireb02, + FRAME_fireb03, + FRAME_fireb04, + FRAME_fireb05, + FRAME_fireb06, + FRAME_fireb07, + FRAME_fireb08, + FRAME_fireb09, + FRAME_firec01, + FRAME_firec02, + FRAME_firec03, + FRAME_firec04, + FRAME_firec05, + FRAME_firec06, + FRAME_firec07, + FRAME_firec08, + FRAME_firec09, + FRAME_fired01, + FRAME_fired02, + FRAME_fired02a, + FRAME_fired03, + FRAME_fired04, + FRAME_fired05, + FRAME_fired06, + FRAME_fired07, + FRAME_fired08, + FRAME_fired09, + FRAME_fired10, + FRAME_fired11, + FRAME_fired12, + FRAME_fired13, + FRAME_fired14, + FRAME_fired15, + FRAME_fired16, + FRAME_fired17, + FRAME_fired18, + FRAME_fired19, + FRAME_fired20, + FRAME_fired21, + FRAME_fired22, + FRAME_spawn01, + FRAME_spawn02, + FRAME_spawn03, + FRAME_spawn04, + FRAME_spawn05, + FRAME_spawn06, + FRAME_spawn07, + FRAME_spawn08, + FRAME_spawn09, + FRAME_spawn10, + FRAME_spawn11, + FRAME_spawn12, + FRAME_spawn13, + FRAME_spawn14, + FRAME_spawn15, + FRAME_spawn16, + FRAME_spawn17, + FRAME_spawn18, + FRAME_pain01, + FRAME_pain02, + FRAME_pain03, + FRAME_pain04, + FRAME_pain05, + FRAME_pain06, + FRAME_pain07, + FRAME_pain08, + FRAME_pain09, + FRAME_pain10, + FRAME_pain11, + FRAME_pain12, + FRAME_pain13, + FRAME_pain201, + FRAME_pain202, + FRAME_pain203, + FRAME_transa01, + FRAME_transa02, + FRAME_transa03, + FRAME_transa04, + FRAME_transa05, + FRAME_transb01, + FRAME_transb02, + FRAME_transb03, + FRAME_transb04, + FRAME_transb05, + FRAME_transc01, + FRAME_transc02, + FRAME_transc03, + FRAME_transc04, + FRAME_death01, + FRAME_death02, + FRAME_death03, + FRAME_death04, + FRAME_death05, + FRAME_death06, + FRAME_death07, + FRAME_death08, + FRAME_death09, + FRAME_death10, + FRAME_death11, + FRAME_death12, + FRAME_death13, + FRAME_death14, + FRAME_death15, + FRAME_death16, + FRAME_death17, + FRAME_death18, + FRAME_death19, + FRAME_death20, + FRAME_death21, + FRAME_death22, + FRAME_death23, + FRAME_death24, + FRAME_death25, + FRAME_death26, + FRAME_death27, + FRAME_death28, + FRAME_death29, + FRAME_death30, + FRAME_death31, + FRAME_kick01, + FRAME_kick02, + FRAME_kick03, + FRAME_kick04, + FRAME_kick05, + FRAME_kick06, + FRAME_kick07, + FRAME_kick08 +}; + +constexpr float MODEL_SCALE = 2.000000f; diff --git a/rerelease/rogue/m_rogue_widow2.cpp b/rerelease/rogue/m_rogue_widow2.cpp new file mode 100644 index 0000000..4417508 --- /dev/null +++ b/rerelease/rogue/m_rogue_widow2.cpp @@ -0,0 +1,1635 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +/* +============================================================================== + +black widow, part 2 + +============================================================================== +*/ + +// timestamp used to prevent rapid fire of melee attack + +#include "../g_local.h" +#include "m_rogue_widow2.h" +#include "../m_flash.h" + +static int sound_pain1; +static int sound_pain2; +static int sound_pain3; +static int sound_death; +static int sound_search1; +static int sound_tentacles_retract; + +// sqrt(64*64*2) + sqrt(28*28*2) => 130.1 +constexpr vec3_t spawnpoints[] = { + { 30, 135, 0 }, + { 30, -135, 0 } +}; + +constexpr float sweep_angles[] = { + -40.0, -32.0, -24.0, -16.0, -8.0, 0.0, 8.0, 16.0, 24.0, 32.0, 40.0 +}; + +constexpr vec3_t stalker_mins = { -28, -28, -18 }; +constexpr vec3_t stalker_maxs = { 28, 28, 18 }; + +bool infront(edict_t *self, edict_t *other); +void WidowCalcSlots(edict_t *self); +void WidowPowerups(edict_t *self); + +void widow2_run(edict_t *self); +void widow2_dead(edict_t *self); +void widow2_attack_beam(edict_t *self); +void widow2_reattack_beam(edict_t *self); +void widow_start_spawn(edict_t *self); +void widow_done_spawn(edict_t *self); +void widow2_spawn_check(edict_t *self); +void Widow2SaveBeamTarget(edict_t *self); + +// death stuff +void WidowExplode(edict_t *self); +void ThrowWidowGibReal(edict_t *self, const char *gibname, int damage, gib_type_t type, const vec3_t *startpos, bool large, int hitsound, bool fade); +void ThrowWidowGibSized(edict_t *self, const char *gibname, int damage, gib_type_t type, const vec3_t *startpos, int hitsound, bool fade); +void ThrowWidowGibLoc(edict_t *self, const char *gibname, int damage, gib_type_t type, const vec3_t *startpos, bool fade); +void WidowExplosion1(edict_t *self); +void WidowExplosion2(edict_t *self); +void WidowExplosion3(edict_t *self); +void WidowExplosion4(edict_t *self); +void WidowExplosion5(edict_t *self); +void WidowExplosion6(edict_t *self); +void WidowExplosion7(edict_t *self); +void WidowExplosionLeg(edict_t *self); +void ThrowArm1(edict_t *self); +void ThrowArm2(edict_t *self); +void ClipGibVelocity(edict_t *ent); +// end of death stuff + +// these offsets used by the tongue +constexpr vec3_t offsets[] = { + { 17.48f, 0.10f, 68.92f }, + { 17.47f, 0.29f, 68.91f }, + { 17.45f, 0.53f, 68.87f }, + { 17.42f, 0.78f, 68.81f }, + { 17.39f, 1.02f, 68.75f }, + { 17.37f, 1.20f, 68.70f }, + { 17.36f, 1.24f, 68.71f }, + { 17.37f, 1.21f, 68.72f }, +}; + +void showme(edict_t *self); + +void pauseme(edict_t *self) +{ + self->monsterinfo.aiflags |= AI_HOLD_FRAME; +} + +MONSTERINFO_SEARCH(widow2_search) (edict_t *self) -> void +{ + if (frandom() < 0.5f) + gi.sound(self, CHAN_VOICE, sound_search1, 1, ATTN_NONE, 0); +} + +void Widow2Beam(edict_t *self) +{ + vec3_t forward, right, target; + vec3_t start, targ_angles, vec; + monster_muzzleflash_id_t flashnum; + + if ((!self->enemy) || (!self->enemy->inuse)) + return; + + AngleVectors(self->s.angles, forward, right, nullptr); + + if ((self->s.frame >= FRAME_fireb05) && (self->s.frame <= FRAME_fireb09)) + { + // regular beam attack + Widow2SaveBeamTarget(self); + flashnum = static_cast(MZ2_WIDOW2_BEAMER_1 + self->s.frame - FRAME_fireb05); + start = G_ProjectSource(self->s.origin, monster_flash_offset[flashnum], forward, right); + target = self->pos2; + target[2] += self->enemy->viewheight - 10; + forward = target - start; + forward.normalize(); + monster_fire_heatbeam(self, start, forward, vec3_origin, 10, 50, flashnum); + } + else if ((self->s.frame >= FRAME_spawn04) && (self->s.frame <= FRAME_spawn14)) + { + // sweep + flashnum = static_cast(MZ2_WIDOW2_BEAM_SWEEP_1 + self->s.frame - FRAME_spawn04); + start = G_ProjectSource(self->s.origin, monster_flash_offset[flashnum], forward, right); + target = self->enemy->s.origin - start; + targ_angles = vectoangles(target); + + vec = self->s.angles; + + vec[PITCH] += targ_angles[PITCH]; + vec[YAW] -= sweep_angles[flashnum - MZ2_WIDOW2_BEAM_SWEEP_1]; + + AngleVectors(vec, forward, nullptr, nullptr); + monster_fire_heatbeam(self, start, forward, vec3_origin, 10, 50, flashnum); + } + else + { + Widow2SaveBeamTarget(self); + start = G_ProjectSource(self->s.origin, monster_flash_offset[MZ2_WIDOW2_BEAMER_1], forward, right); + + target = self->pos2; + target[2] += self->enemy->viewheight - 10; + + forward = target - start; + forward.normalize(); + + monster_fire_heatbeam(self, start, forward, vec3_origin, 10, 50, MZ2_WIDOW2_BEAM_SWEEP_1); + } +} + +void Widow2Spawn(edict_t *self) +{ + vec3_t f, r, u, offset, startpoint, spawnpoint; + edict_t *ent, *designated_enemy; + int i; + + AngleVectors(self->s.angles, f, r, u); + + for (i = 0; i < 2; i++) + { + offset = spawnpoints[i]; + + startpoint = G_ProjectSource2(self->s.origin, offset, f, r, u); + + if (FindSpawnPoint(startpoint, stalker_mins, stalker_maxs, spawnpoint, 64)) + { + ent = CreateGroundMonster(spawnpoint, self->s.angles, stalker_mins, stalker_maxs, "monster_stalker", 256); + + if (!ent) + continue; + + self->monsterinfo.monster_used++; + ent->monsterinfo.commander = self; + + ent->nextthink = level.time; + ent->think(ent); + + ent->monsterinfo.aiflags |= AI_SPAWNED_WIDOW | AI_DO_NOT_COUNT | AI_IGNORE_SHOTS; + + if (!coop->integer) + { + designated_enemy = self->enemy; + } + else + { + designated_enemy = PickCoopTarget(ent); + if (designated_enemy) + { + // try to avoid using my enemy + if (designated_enemy == self->enemy) + { + designated_enemy = PickCoopTarget(ent); + if (!designated_enemy) + designated_enemy = self->enemy; + } + } + else + designated_enemy = self->enemy; + } + + if ((designated_enemy->inuse) && (designated_enemy->health > 0)) + { + ent->enemy = designated_enemy; + FoundTarget(ent); + ent->monsterinfo.attack(ent); + } + } + } +} + +void widow2_spawn_check(edict_t *self) +{ + Widow2Beam(self); + Widow2Spawn(self); +} + +void widow2_ready_spawn(edict_t *self) +{ + vec3_t f, r, u, offset, startpoint, spawnpoint; + int i; + + Widow2Beam(self); + AngleVectors(self->s.angles, f, r, u); + + for (i = 0; i < 2; i++) + { + offset = spawnpoints[i]; + startpoint = G_ProjectSource2(self->s.origin, offset, f, r, u); + if (FindSpawnPoint(startpoint, stalker_mins, stalker_maxs, spawnpoint, 64)) + { + float radius = (stalker_maxs - stalker_mins).length() * 0.5f; + + SpawnGrow_Spawn(spawnpoint + (stalker_mins + stalker_maxs), radius, radius * 2.f); + } + } +} + +void widow2_step(edict_t *self) +{ + gi.sound(self, CHAN_BODY, gi.soundindex("widow/bwstep1.wav"), 1, ATTN_NORM, 0); +} + +mframe_t widow2_frames_stand[] = { + { ai_stand } +}; +MMOVE_T(widow2_move_stand) = { FRAME_blackwidow3, FRAME_blackwidow3, widow2_frames_stand, nullptr }; + +mframe_t widow2_frames_walk[] = { + { ai_walk, 9.01f, widow2_step }, + { ai_walk, 7.55f }, + { ai_walk, 7.01f }, + { ai_walk, 6.66f }, + { ai_walk, 6.20f }, + { ai_walk, 5.78f, widow2_step }, + { ai_walk, 7.25f }, + { ai_walk, 8.37f }, + { ai_walk, 10.41f } +}; +MMOVE_T(widow2_move_walk) = { FRAME_walk01, FRAME_walk09, widow2_frames_walk, nullptr }; + +mframe_t widow2_frames_run[] = { + { ai_run, 9.01f, widow2_step }, + { ai_run, 7.55f }, + { ai_run, 7.01f }, + { ai_run, 6.66f }, + { ai_run, 6.20f }, + { ai_run, 5.78f, widow2_step }, + { ai_run, 7.25f }, + { ai_run, 8.37f }, + { ai_run, 10.41f } +}; +MMOVE_T(widow2_move_run) = { FRAME_walk01, FRAME_walk09, widow2_frames_run, nullptr }; + +mframe_t widow2_frames_attack_pre_beam[] = { + { ai_charge, 4 }, + { ai_charge, 4, widow2_step }, + { ai_charge, 4 }, + { ai_charge, 4, widow2_attack_beam } +}; +MMOVE_T(widow2_move_attack_pre_beam) = { FRAME_fireb01, FRAME_fireb04, widow2_frames_attack_pre_beam, nullptr }; + +// Loop this +mframe_t widow2_frames_attack_beam[] = { + { ai_charge, 0, Widow2Beam }, + { ai_charge, 0, Widow2Beam }, + { ai_charge, 0, Widow2Beam }, + { ai_charge, 0, Widow2Beam }, + { ai_charge, 0, widow2_reattack_beam } +}; +MMOVE_T(widow2_move_attack_beam) = { FRAME_fireb05, FRAME_fireb09, widow2_frames_attack_beam, nullptr }; + +mframe_t widow2_frames_attack_post_beam[] = { + { ai_charge, 4 }, + { ai_charge, 4 } +}; +MMOVE_T(widow2_move_attack_post_beam) = { FRAME_fireb06, FRAME_fireb07, widow2_frames_attack_post_beam, widow2_run }; + +void WidowDisrupt(edict_t *self) +{ + vec3_t start; + vec3_t dir; + vec3_t forward, right; + float len; + + AngleVectors(self->s.angles, forward, right, nullptr); + start = G_ProjectSource(self->s.origin, monster_flash_offset[MZ2_WIDOW_DISRUPTOR], forward, right); + + dir = self->pos1 - self->enemy->s.origin; + len = dir.length(); + + if (len < 30) + { + // calc direction to where we targeted + dir = self->pos1 - start; + dir.normalize(); + + monster_fire_tracker(self, start, dir, 20, 500, self->enemy, MZ2_WIDOW_DISRUPTOR); + } + else + { + PredictAim(self, self->enemy, start, 1200, true, 0, &dir, nullptr); + monster_fire_tracker(self, start, dir, 20, 1200, nullptr, MZ2_WIDOW_DISRUPTOR); + } + + widow2_step(self); +} + +void Widow2SaveDisruptLoc(edict_t *self) +{ + if (self->enemy && self->enemy->inuse) + { + self->pos1 = self->enemy->s.origin; // save for aiming the shot + self->pos1[2] += self->enemy->viewheight; + } + else + self->pos1 = {}; +} + +void widow2_disrupt_reattack(edict_t *self) +{ + float luck = frandom(); + + if (luck < (0.25f + (skill->integer * 0.15f))) + self->monsterinfo.nextframe = FRAME_firea01; +} + +mframe_t widow2_frames_attack_disrupt[] = { + { ai_charge, 2 }, + { ai_charge, 2 }, + { ai_charge, 2, Widow2SaveDisruptLoc }, + { ai_charge, -20, WidowDisrupt }, + { ai_charge, 2 }, + { ai_charge, 2 }, + { ai_charge, 2, widow2_disrupt_reattack } +}; +MMOVE_T(widow2_move_attack_disrupt) = { FRAME_firea01, FRAME_firea07, widow2_frames_attack_disrupt, widow2_run }; + +void Widow2SaveBeamTarget(edict_t *self) +{ + if (self->enemy && self->enemy->inuse) + { + self->pos2 = self->pos1; + self->pos1 = self->enemy->s.origin; // save for aiming the shot + } + else + { + self->pos1 = {}; + self->pos2 = {}; + } +} + +void Widow2BeamTargetRemove(edict_t *self) +{ + self->pos1 = {}; + self->pos2 = {}; +} + +void Widow2StartSweep(edict_t *self) +{ + Widow2SaveBeamTarget(self); +} + +mframe_t widow2_frames_spawn[] = { + { ai_charge }, + { ai_charge }, + { ai_charge, 0, [](edict_t *self) { widow_start_spawn(self); widow2_step(self); } }, + { ai_charge, 0, Widow2Beam }, + { ai_charge, 0, Widow2Beam }, // 5 + { ai_charge, 0, Widow2Beam }, + { ai_charge, 0, Widow2Beam }, + { ai_charge, 0, Widow2Beam }, + { ai_charge, 0, Widow2Beam }, + { ai_charge, 0, widow2_ready_spawn }, // 10 + { ai_charge, 0, Widow2Beam }, + { ai_charge, 0, Widow2Beam }, + { ai_charge, 0, Widow2Beam }, + { ai_charge, 0, widow2_spawn_check }, + { ai_charge }, // 15 + { ai_charge }, + { ai_charge }, + { ai_charge, 0, widow2_reattack_beam } +}; +MMOVE_T(widow2_move_spawn) = { FRAME_spawn01, FRAME_spawn18, widow2_frames_spawn, nullptr }; + +static bool widow2_tongue_attack_ok(const vec3_t &start, const vec3_t &end, float range) +{ + vec3_t dir, angles; + + // check for max distance + dir = start - end; + if (dir.length() > range) + return false; + + // check for min/max pitch + angles = vectoangles(dir); + if (angles[0] < -180) + angles[0] += 360; + if (fabsf(angles[0]) > 30) + return false; + + return true; +} + +void Widow2Tongue(edict_t *self) +{ + vec3_t f, r, u; + vec3_t start, end, dir; + trace_t tr; + + AngleVectors(self->s.angles, f, r, u); + start = G_ProjectSource2(self->s.origin, offsets[self->s.frame - FRAME_tongs01], f, r, u); + end = self->enemy->s.origin; + if (!widow2_tongue_attack_ok(start, end, 256)) + { + end[2] = self->enemy->s.origin[2] + self->enemy->maxs[2] - 8; + if (!widow2_tongue_attack_ok(start, end, 256)) + { + end[2] = self->enemy->s.origin[2] + self->enemy->mins[2] + 8; + if (!widow2_tongue_attack_ok(start, end, 256)) + return; + } + } + end = self->enemy->s.origin; + + tr = gi.traceline(start, end, self, MASK_PROJECTILE); + if (tr.ent != self->enemy) + return; + + gi.sound(self, CHAN_WEAPON, sound_tentacles_retract, 1, ATTN_NORM, 0); + + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_PARASITE_ATTACK); + gi.WriteEntity(self); + gi.WritePosition(start); + gi.WritePosition(end); + gi.multicast(self->s.origin, MULTICAST_PVS, false); + + dir = start - end; + T_Damage(self->enemy, self, self, dir, self->enemy->s.origin, vec3_origin, 2, 0, DAMAGE_NO_KNOCKBACK, MOD_UNKNOWN); +} + +void Widow2TonguePull(edict_t *self) +{ + vec3_t vec; + vec3_t f, r, u; + vec3_t start, end; + + if ((!self->enemy) || (!self->enemy->inuse)) + { + self->monsterinfo.run(self); + return; + } + + AngleVectors(self->s.angles, f, r, u); + start = G_ProjectSource2(self->s.origin, offsets[self->s.frame - FRAME_tongs01], f, r, u); + end = self->enemy->s.origin; + + if (!widow2_tongue_attack_ok(start, end, 256)) + return; + + if (self->enemy->groundentity) + { + self->enemy->s.origin[2] += 1; + self->enemy->groundentity = nullptr; + // interesting, you don't have to relink the player + } + + vec = self->s.origin - self->enemy->s.origin; + + if (self->enemy->client) + { + vec.normalize(); + self->enemy->velocity += (vec * 1000); + } + else + { + self->enemy->ideal_yaw = vectoyaw(vec); + M_ChangeYaw(self->enemy); + self->enemy->velocity = f * 1000; + } +} + +void Widow2Crunch(edict_t *self) +{ + vec3_t aim; + + if ((!self->enemy) || (!self->enemy->inuse)) + { + self->monsterinfo.run(self); + return; + } + + Widow2TonguePull(self); + + // 70 + 32 + aim = { 150, 0, 4 }; + if (self->s.frame != FRAME_tongs07) + fire_hit(self, aim, irandom(20, 26), 0); + else if (self->enemy->groundentity) + fire_hit(self, aim, irandom(20, 26), 500); + else // not as much kick if they're in the air .. makes it harder to land on her head + fire_hit(self, aim, irandom(20, 26), 250); +} + +void Widow2Toss(edict_t *self) +{ + self->timestamp = level.time + 3_sec; +} + +mframe_t widow2_frames_tongs[] = { + { ai_charge, 0, Widow2Tongue }, + { ai_charge, 0, Widow2Tongue }, + { ai_charge, 0, Widow2Tongue }, + { ai_charge, 0, Widow2TonguePull }, + { ai_charge, 0, Widow2TonguePull }, // 5 + { ai_charge, 0, Widow2TonguePull }, + { ai_charge, 0, Widow2Crunch }, + { ai_charge, 0, Widow2Toss } +}; +MMOVE_T(widow2_move_tongs) = { FRAME_tongs01, FRAME_tongs08, widow2_frames_tongs, widow2_run }; + +mframe_t widow2_frames_pain[] = { + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move } +}; +MMOVE_T(widow2_move_pain) = { FRAME_pain01, FRAME_pain05, widow2_frames_pain, widow2_run }; + +mframe_t widow2_frames_death[] = { + { ai_move }, + { ai_move }, + { ai_move, 0, WidowExplosion1 }, // 3 boom + { ai_move }, + { ai_move }, // 5 + + { ai_move, 0, WidowExplosion2 }, // 6 boom + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, // 10 + + { ai_move }, + { ai_move }, // 12 + { ai_move }, + { ai_move }, + { ai_move }, // 15 + + { ai_move }, + { ai_move }, + { ai_move, 0, WidowExplosion3 }, // 18 + { ai_move }, // 19 + { ai_move }, // 20 + + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move, 0, WidowExplosion4 }, // 25 + + { ai_move }, // 26 + { ai_move }, + { ai_move }, + { ai_move, 0, WidowExplosion5 }, + { ai_move, 0, WidowExplosionLeg }, // 30 + + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move, 0, WidowExplosion6 }, + { ai_move }, // 35 + + { ai_move }, + { ai_move }, + { ai_move, 0, WidowExplosion7 }, + { ai_move }, + { ai_move }, // 40 + + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move, 0, WidowExplode } // 44 +}; +MMOVE_T(widow2_move_death) = { FRAME_death01, FRAME_death44, widow2_frames_death, nullptr }; + +void widow2_start_searching(edict_t *self); +void widow2_keep_searching(edict_t *self); +void widow2_finaldeath(edict_t *self); + +mframe_t widow2_frames_dead[] = { + { ai_move, 0, widow2_start_searching }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move, 0, widow2_keep_searching } +}; +MMOVE_T(widow2_move_dead) = { FRAME_dthsrh01, FRAME_dthsrh15, widow2_frames_dead, nullptr }; + +mframe_t widow2_frames_really_dead[] = { + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + + { ai_move }, + { ai_move, 0, widow2_finaldeath } +}; +MMOVE_T(widow2_move_really_dead) = { FRAME_dthsrh16, FRAME_dthsrh22, widow2_frames_really_dead, nullptr }; + +void widow2_start_searching(edict_t *self) +{ + self->count = 0; +} + +void widow2_keep_searching(edict_t *self) +{ + if (self->count <= 2) + { + M_SetAnimation(self, &widow2_move_dead); + self->s.frame = FRAME_dthsrh01; + self->count++; + return; + } + + M_SetAnimation(self, &widow2_move_really_dead); +} + +void widow2_finaldeath(edict_t *self) +{ + self->mins = { -70, -70, 0 }; + self->maxs = { 70, 70, 80 }; + self->movetype = MOVETYPE_TOSS; + self->takedamage = true; + self->nextthink = 0_ms; + gi.linkentity(self); +} + +MONSTERINFO_STAND(widow2_stand) (edict_t *self) -> void +{ + M_SetAnimation(self, &widow2_move_stand); +} + +MONSTERINFO_RUN(widow2_run) (edict_t *self) -> void +{ + self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; + + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + M_SetAnimation(self, &widow2_move_stand); + else + M_SetAnimation(self, &widow2_move_run); +} + +MONSTERINFO_WALK(widow2_walk) (edict_t *self) -> void +{ + M_SetAnimation(self, &widow2_move_walk); +} + +MONSTERINFO_MELEE(widow2_melee) (edict_t *self) -> void +{ + M_SetAnimation(self, &widow2_move_tongs); +} + +MONSTERINFO_ATTACK(widow2_attack) (edict_t *self) -> void +{ + float range, luck; + bool blocked = false; + + if (self->monsterinfo.aiflags & AI_BLOCKED) + { + blocked = true; + self->monsterinfo.aiflags &= ~AI_BLOCKED; + } + + if (!self->enemy) + return; + + if (self->bad_area) + { + if ((frandom() < 0.75f) || (level.time < self->monsterinfo.attack_finished)) + M_SetAnimation(self, &widow2_move_attack_pre_beam); + else + { + M_SetAnimation(self, &widow2_move_attack_disrupt); + } + return; + } + + WidowCalcSlots(self); + + // if we can't see the target, spawn stuff + if ((self->monsterinfo.attack_state == AS_BLIND) && (M_SlotsLeft(self) >= 2)) + { + M_SetAnimation(self, &widow2_move_spawn); + return; + } + + // accept bias towards spawning + if (blocked && (M_SlotsLeft(self) >= 2)) + { + M_SetAnimation(self, &widow2_move_spawn); + return; + } + + range = realrange(self, self->enemy); + + if (range < 600) + { + luck = frandom(); + if (M_SlotsLeft(self) >= 2) + { + if (luck <= 0.40f) + M_SetAnimation(self, &widow2_move_attack_pre_beam); + else if ((luck <= 0.7f) && !(level.time < self->monsterinfo.attack_finished)) + { + M_SetAnimation(self, &widow2_move_attack_disrupt); + } + else + M_SetAnimation(self, &widow2_move_spawn); + } + else + { + if ((luck <= 0.50f) || (level.time < self->monsterinfo.attack_finished)) + M_SetAnimation(self, &widow2_move_attack_pre_beam); + else + { + M_SetAnimation(self, &widow2_move_attack_disrupt); + } + } + } + else + { + luck = frandom(); + if (M_SlotsLeft(self) >= 2) + { + if (luck < 0.3f) + M_SetAnimation(self, &widow2_move_attack_pre_beam); + else if ((luck < 0.65f) || (level.time < self->monsterinfo.attack_finished)) + M_SetAnimation(self, &widow2_move_spawn); + else + { + M_SetAnimation(self, &widow2_move_attack_disrupt); + } + } + else + { + if ((luck < 0.45f) || (level.time < self->monsterinfo.attack_finished)) + M_SetAnimation(self, &widow2_move_attack_pre_beam); + else + { + M_SetAnimation(self, &widow2_move_attack_disrupt); + } + } + } +} + +void widow2_attack_beam(edict_t *self) +{ + M_SetAnimation(self, &widow2_move_attack_beam); + widow2_step(self); +} + +void widow2_reattack_beam(edict_t *self) +{ + self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING; + + if (infront(self, self->enemy)) + if (frandom() <= 0.5f) + if ((frandom() < 0.7f) || (M_SlotsLeft(self) < 2)) + M_SetAnimation(self, &widow2_move_attack_beam); + else + M_SetAnimation(self, &widow2_move_spawn); + else + M_SetAnimation(self, &widow2_move_attack_post_beam); + else + M_SetAnimation(self, &widow2_move_attack_post_beam); +} + +PAIN(widow2_pain) (edict_t *self, edict_t *other, float kick, int damage, const mod_t &mod) -> void +{ + if (level.time < self->pain_debounce_time) + return; + + self->pain_debounce_time = level.time + 5_sec; + + if (damage < 15) + gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NONE, 0); + else if (damage < 75) + gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NONE, 0); + else + gi.sound(self, CHAN_VOICE, sound_pain3, 1, ATTN_NONE, 0); + + if (!M_ShouldReactToPain(self, mod)) + return; // no pain anims in nightmare + + if (damage >= 15) + { + if (damage < 75) + { + if ((skill->integer < 3) && (frandom() < (0.6f - (0.2f * skill->integer)))) + { + self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING; + M_SetAnimation(self, &widow2_move_pain); + } + } + else + { + if ((skill->integer < 3) && (frandom() < (0.75f - (0.1f * skill->integer)))) + { + self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING; + M_SetAnimation(self, &widow2_move_pain); + } + } + } +} + +MONSTERINFO_SETSKIN(widow2_setskin) (edict_t *self) -> void +{ + if (self->health < (self->max_health / 2)) + self->s.skinnum = 1; + else + self->s.skinnum = 0; +} + +void widow2_dead(edict_t *self) +{ +} + +void KillChildren(edict_t *self) +{ + edict_t *ent = nullptr; + + while (1) + { + ent = G_FindByString<&edict_t::classname>(ent, "monster_stalker"); + if (!ent) + return; + + // FIXME - may need to stagger + if ((ent->inuse) && (ent->health > 0)) + T_Damage(ent, self, self, vec3_origin, self->enemy->s.origin, vec3_origin, (ent->health + 1), 0, DAMAGE_NO_KNOCKBACK, MOD_UNKNOWN); + } +} + +DIE(widow2_die) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void +{ + int n; + int clipped; + + // check for gib + if (self->deadflag && M_CheckGib(self, mod)) + { + clipped = min(damage, 100); + + gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0); + for (n = 0; n < 2; n++) + ThrowWidowGibLoc(self, "models/objects/gibs/bone/tris.md2", clipped, GIB_NONE, nullptr, false); + for (n = 0; n < 3; n++) + ThrowWidowGibLoc(self, "models/objects/gibs/sm_meat/tris.md2", clipped, GIB_NONE, nullptr, false); + for (n = 0; n < 3; n++) + { + ThrowWidowGibSized(self, "models/monsters/blackwidow2/gib1/tris.md2", clipped, GIB_METALLIC, nullptr, + 0, false); + ThrowWidowGibSized(self, "models/monsters/blackwidow2/gib2/tris.md2", clipped, GIB_METALLIC, nullptr, + gi.soundindex("misc/fhit3.wav"), false); + } + for (n = 0; n < 2; n++) + { + ThrowWidowGibSized(self, "models/monsters/blackwidow2/gib3/tris.md2", clipped, GIB_METALLIC, nullptr, + 0, false); + ThrowWidowGibSized(self, "models/monsters/blackwidow/gib3/tris.md2", clipped, GIB_METALLIC, nullptr, + 0, false); + } + ThrowGibs(self, damage, { + { "models/objects/gibs/chest/tris.md2" }, + { "models/objects/gibs/head2/tris.md2", GIB_HEAD } + }); + + return; + } + + if (self->deadflag) + return; + + gi.sound(self, CHAN_VOICE, sound_death, 1, ATTN_NONE, 0); + self->deadflag = true; + self->takedamage = false; + self->count = 0; + KillChildren(self); + self->monsterinfo.quad_time = 0_ms; + self->monsterinfo.double_time = 0_ms; + self->monsterinfo.invincible_time = 0_ms; + M_SetAnimation(self, &widow2_move_death); +} + +MONSTERINFO_CHECKATTACK(Widow2_CheckAttack) (edict_t *self) -> bool +{ + vec3_t spot1, spot2; + vec3_t temp; + float chance; + trace_t tr; + float enemy_yaw; + float real_enemy_range; + vec3_t f, r, u; + + if (!self->enemy) + return false; + + WidowPowerups(self); + + if ((frandom() < 0.8f) && (M_SlotsLeft(self) >= 2) && (realrange(self, self->enemy) > 150)) + { + self->monsterinfo.aiflags |= AI_BLOCKED; + self->monsterinfo.attack_state = AS_MISSILE; + return true; + } + + if (self->enemy->health > 0) + { + // see if any entities are in the way of the shot + spot1 = self->s.origin; + spot1[2] += self->viewheight; + spot2 = self->enemy->s.origin; + spot2[2] += self->enemy->viewheight; + + tr = gi.traceline(spot1, spot2, self, CONTENTS_SOLID | CONTENTS_MONSTER | CONTENTS_PLAYER | CONTENTS_SLIME | CONTENTS_LAVA); + + // do we have a clear shot? + if (tr.ent != self->enemy && !(tr.ent->svflags & SVF_PLAYER)) + { + // go ahead and spawn stuff if we're mad a a client + if (self->enemy->client && M_SlotsLeft(self) >= 2) + { + self->monsterinfo.attack_state = AS_BLIND; + return true; + } + + // PGM - we want them to go ahead and shoot at info_notnulls if they can. + if (self->enemy->solid != SOLID_NOT || tr.fraction < 1.0f) // PGM + return false; + } + } + + float enemy_range = range_to(self, self->enemy); + temp = self->enemy->s.origin - self->s.origin; + enemy_yaw = vectoyaw(temp); + + self->ideal_yaw = enemy_yaw; + + // melee attack + if (self->timestamp < level.time) + { + real_enemy_range = realrange(self, self->enemy); + if (real_enemy_range < 300) + { + AngleVectors(self->s.angles, f, r, u); + spot1 = G_ProjectSource2(self->s.origin, offsets[0], f, r, u); + spot2 = self->enemy->s.origin; + if (widow2_tongue_attack_ok(spot1, spot2, 256)) + { + // melee attack ok + + // be nice in easy mode + if (skill->integer == 0 && irandom(4)) + return false; + + if (self->monsterinfo.melee) + self->monsterinfo.attack_state = AS_MELEE; + else + self->monsterinfo.attack_state = AS_MISSILE; + return true; + } + } + } + + if (level.time < self->monsterinfo.attack_finished) + return false; + + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + { + chance = 0.4f; + } + else if (enemy_range <= RANGE_NEAR) + { + chance = 0.8f; + } + else if (enemy_range <= RANGE_MID) + { + chance = 0.8f; + } + else + { + chance = 0.5f; + } + + // PGM - go ahead and shoot every time if it's a info_notnull + if ((frandom() < chance) || (self->enemy->solid == SOLID_NOT)) + { + self->monsterinfo.attack_state = AS_MISSILE; + return true; + } + + return false; +} + +void Widow2Precache() +{ + // cache in all of the stalker stuff, widow stuff, spawngro stuff, gibs + gi.soundindex("parasite/parpain1.wav"); + gi.soundindex("parasite/parpain2.wav"); + gi.soundindex("parasite/pardeth1.wav"); + gi.soundindex("parasite/paratck1.wav"); + gi.soundindex("parasite/parsght1.wav"); + gi.soundindex("infantry/melee2.wav"); + gi.soundindex("misc/fhit3.wav"); + + gi.soundindex("tank/tnkatck3.wav"); + gi.soundindex("weapons/disrupt.wav"); + gi.soundindex("weapons/disint2.wav"); + + gi.modelindex("models/monsters/stalker/tris.md2"); + gi.modelindex("models/items/spawngro3/tris.md2"); + gi.modelindex("models/objects/gibs/sm_metal/tris.md2"); + gi.modelindex("models/objects/laser/tris.md2"); + gi.modelindex("models/proj/disintegrator/tris.md2"); + + gi.modelindex("models/monsters/blackwidow/gib1/tris.md2"); + gi.modelindex("models/monsters/blackwidow/gib2/tris.md2"); + gi.modelindex("models/monsters/blackwidow/gib3/tris.md2"); + gi.modelindex("models/monsters/blackwidow/gib4/tris.md2"); + gi.modelindex("models/monsters/blackwidow2/gib1/tris.md2"); + gi.modelindex("models/monsters/blackwidow2/gib2/tris.md2"); + gi.modelindex("models/monsters/blackwidow2/gib3/tris.md2"); + gi.modelindex("models/monsters/blackwidow2/gib4/tris.md2"); +} + +/*QUAKED monster_widow2 (1 .5 0) (-70 -70 0) (70 70 144) Ambush Trigger_Spawn Sight + */ +void SP_monster_widow2(edict_t *self) +{ + if ( !M_AllowSpawn( self ) ) { + G_FreeEdict( self ); + return; + } + + sound_pain1 = gi.soundindex("widow/bw2pain1.wav"); + sound_pain2 = gi.soundindex("widow/bw2pain2.wav"); + sound_pain3 = gi.soundindex("widow/bw2pain3.wav"); + sound_death = gi.soundindex("widow/death.wav"); + sound_search1 = gi.soundindex("bosshovr/bhvunqv1.wav"); + sound_tentacles_retract = gi.soundindex("brain/brnatck3.wav"); + + // self->s.sound = gi.soundindex ("bosshovr/bhvengn1.wav"); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex("models/monsters/blackwidow2/tris.md2"); + self->mins = { -70, -70, 0 }; + self->maxs = { 70, 70, 144 }; + + self->health = (2000 + 800 + 1000 * skill->integer) * st.health_multiplier; + if (coop->integer) + self->health += 500 * skill->integer; + // self->health = 1; + self->gib_health = -900; + self->mass = 2500; + + /* if (skill->integer == 2) + { + self->monsterinfo.power_armor_type = IT_ITEM_POWER_SHIELD; + self->monsterinfo.power_armor_power = 500; + } + else */ + if (skill->integer == 3) + { + if (!st.was_key_specified("power_armor_type")) + self->monsterinfo.power_armor_type = IT_ITEM_POWER_SHIELD; + if (!st.was_key_specified("power_armor_power")) + self->monsterinfo.power_armor_power = 750; + } + + self->yaw_speed = 30; + + self->flags |= FL_IMMUNE_LASER; + self->monsterinfo.aiflags |= AI_IGNORE_SHOTS; + + self->pain = widow2_pain; + self->die = widow2_die; + + self->monsterinfo.melee = widow2_melee; + self->monsterinfo.stand = widow2_stand; + self->monsterinfo.walk = widow2_walk; + self->monsterinfo.run = widow2_run; + self->monsterinfo.attack = widow2_attack; + self->monsterinfo.search = widow2_search; + self->monsterinfo.checkattack = Widow2_CheckAttack; + self->monsterinfo.setskin = widow2_setskin; + gi.linkentity(self); + + M_SetAnimation(self, &widow2_move_stand); + self->monsterinfo.scale = MODEL_SCALE; + + Widow2Precache(); + WidowCalcSlots(self); + walkmonster_start(self); +} + +// +// Death sequence stuff +// + +void WidowVelocityForDamage(int damage, vec3_t &v) +{ + v[0] = damage * crandom(); + v[1] = damage * crandom(); + v[2] = damage * crandom() + 200.0f; +} + +TOUCH(widow_gib_touch) (edict_t *self, edict_t *other, const trace_t &tr, bool other_touching_self) -> void +{ + self->solid = SOLID_NOT; + self->touch = nullptr; + self->s.angles[PITCH] = 0; + self->s.angles[ROLL] = 0; + self->avelocity = {}; + + if (self->style) + gi.sound(self, CHAN_VOICE, self->style, 1, ATTN_NORM, 0); +} + +void ThrowWidowGib(edict_t *self, const char *gibname, int damage, gib_type_t type) +{ + ThrowWidowGibReal(self, gibname, damage, type, nullptr, false, 0, true); +} + +void ThrowWidowGibLoc(edict_t *self, const char *gibname, int damage, gib_type_t type, const vec3_t *startpos, bool fade) +{ + ThrowWidowGibReal(self, gibname, damage, type, startpos, false, 0, fade); +} + +void ThrowWidowGibSized(edict_t *self, const char *gibname, int damage, gib_type_t type, const vec3_t *startpos, int hitsound, bool fade) +{ + ThrowWidowGibReal(self, gibname, damage, type, startpos, true, hitsound, fade); +} + +void ThrowWidowGibReal(edict_t *self, const char *gibname, int damage, gib_type_t type, const vec3_t *startpos, bool sized, int hitsound, bool fade) +{ + edict_t *gib; + vec3_t vd; + vec3_t origin; + vec3_t size; + float vscale; + + if (!gibname) + return; + + gib = G_Spawn(); + + if (startpos) + gib->s.origin = *startpos; + else + { + origin = (self->absmin + self->absmax) * 0.5f; + gib->s.origin[0] = origin[0] + crandom() * size[0]; + gib->s.origin[1] = origin[1] + crandom() * size[1]; + gib->s.origin[2] = origin[2] + crandom() * size[2]; + } + + gib->solid = SOLID_NOT; + gib->s.effects |= EF_GIB; + gib->flags |= FL_NO_KNOCKBACK; + gib->takedamage = true; + gib->die = gib_die; + gib->s.renderfx |= RF_IR_VISIBLE; + gib->s.renderfx &= ~RF_DOT_SHADOW; + + if (fade) + { + gib->think = G_FreeEdict; + // sized gibs last longer + if (sized) + gib->nextthink = level.time + random_time(20_sec, 35_sec); + else + gib->nextthink = level.time + random_time(5_sec, 15_sec); + } + else + { + gib->think = G_FreeEdict; + // sized gibs last longer + if (sized) + gib->nextthink = level.time + random_time(60_sec, 75_sec); + else + gib->nextthink = level.time + random_time(25_sec, 35_sec); + } + + if (!(type & GIB_METALLIC)) + { + gib->movetype = MOVETYPE_TOSS; + vscale = 0.5; + } + else + { + gib->movetype = MOVETYPE_BOUNCE; + vscale = 1.0; + } + + WidowVelocityForDamage(damage, vd); + gib->velocity = self->velocity + (vd * vscale); + ClipGibVelocity(gib); + + gi.setmodel(gib, gibname); + + if (sized) + { + gib->style = hitsound; + gib->solid = SOLID_BBOX; + gib->avelocity[0] = frandom(400); + gib->avelocity[1] = frandom(400); + gib->avelocity[2] = frandom(400); + if (gib->velocity[2] < 0) + gib->velocity[2] *= -1; + gib->velocity[0] *= 2; + gib->velocity[1] *= 2; + ClipGibVelocity(gib); + gib->velocity[2] = max(frandom(350.f, 450.f), gib->velocity[2]); + gib->gravity = 0.25; + gib->touch = widow_gib_touch; + gib->owner = self; + if (gib->s.modelindex == gi.modelindex("models/monsters/blackwidow2/gib2/tris.md2")) + { + gib->mins = { -10, -10, 0 }; + gib->maxs = { 10, 10, 10 }; + } + else + { + gib->mins = { -5, -5, 0 }; + gib->maxs = { 5, 5, 5 }; + } + } + else + { + gib->velocity[0] *= 2; + gib->velocity[1] *= 2; + gib->avelocity[0] = frandom(600); + gib->avelocity[1] = frandom(600); + gib->avelocity[2] = frandom(600); + } + + gi.linkentity(gib); +} + +void ThrowSmallStuff(edict_t *self, const vec3_t &point) +{ + int n; + + for (n = 0; n < 2; n++) + ThrowWidowGibLoc(self, "models/objects/gibs/sm_meat/tris.md2", 300, GIB_NONE, &point, false); + ThrowWidowGibLoc(self, "models/objects/gibs/sm_metal/tris.md2", 300, GIB_METALLIC, &point, false); + ThrowWidowGibLoc(self, "models/objects/gibs/sm_metal/tris.md2", 100, GIB_METALLIC, &point, false); +} + +void ThrowMoreStuff(edict_t *self, const vec3_t &point) +{ + int n; + + if (coop->integer) + { + ThrowSmallStuff(self, point); + return; + } + + for (n = 0; n < 1; n++) + ThrowWidowGibLoc(self, "models/objects/gibs/sm_meat/tris.md2", 300, GIB_NONE, &point, false); + for (n = 0; n < 2; n++) + ThrowWidowGibLoc(self, "models/objects/gibs/sm_metal/tris.md2", 300, GIB_METALLIC, &point, false); + for (n = 0; n < 3; n++) + ThrowWidowGibLoc(self, "models/objects/gibs/sm_metal/tris.md2", 100, GIB_METALLIC, &point, false); +} + +THINK(WidowExplode) (edict_t *self) -> void +{ + vec3_t org; + int n; + + self->think = WidowExplode; + + // redo: + org = self->s.origin; + org[2] += irandom(24, 40); + if (self->count < 8) + org[2] += irandom(24, 56); + switch (self->count) + { + case 0: + org[0] -= 24; + org[1] -= 24; + break; + case 1: + org[0] += 24; + org[1] += 24; + ThrowSmallStuff(self, org); + break; + case 2: + org[0] += 24; + org[1] -= 24; + break; + case 3: + org[0] -= 24; + org[1] += 24; + ThrowMoreStuff(self, org); + break; + case 4: + org[0] -= 48; + org[1] -= 48; + break; + case 5: + org[0] += 48; + org[1] += 48; + ThrowArm1(self); + break; + case 6: + org[0] -= 48; + org[1] += 48; + ThrowArm2(self); + break; + case 7: + org[0] += 48; + org[1] -= 48; + ThrowSmallStuff(self, org); + break; + case 8: + org[0] += 18; + org[1] += 18; + org[2] = self->s.origin[2] + 48; + ThrowMoreStuff(self, org); + break; + case 9: + org[0] -= 18; + org[1] += 18; + org[2] = self->s.origin[2] + 48; + break; + case 10: + org[0] += 18; + org[1] -= 18; + org[2] = self->s.origin[2] + 48; + break; + case 11: + org[0] -= 18; + org[1] -= 18; + org[2] = self->s.origin[2] + 48; + break; + case 12: + self->s.sound = 0; + for (n = 0; n < 1; n++) + ThrowWidowGib(self, "models/objects/gibs/sm_meat/tris.md2", 400, GIB_NONE); + for (n = 0; n < 2; n++) + ThrowWidowGib(self, "models/objects/gibs/sm_metal/tris.md2", 100, GIB_METALLIC); + for (n = 0; n < 2; n++) + ThrowWidowGib(self, "models/objects/gibs/sm_metal/tris.md2", 400, GIB_METALLIC); + self->deadflag = true; + self->think = monster_think; + self->nextthink = level.time + 10_hz; + M_SetAnimation(self, &widow2_move_dead); + return; + } + + self->count++; + if (self->count >= 9 && self->count <= 12) + { + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_EXPLOSION1_BIG); + gi.WritePosition(org); + gi.multicast(self->s.origin, MULTICAST_ALL, false); + // goto redo; + } + else + { + // else + gi.WriteByte(svc_temp_entity); + if (self->count % 2) + gi.WriteByte(TE_EXPLOSION1); + else + gi.WriteByte(TE_EXPLOSION1_NP); + gi.WritePosition(org); + gi.multicast(self->s.origin, MULTICAST_ALL, false); + } + + self->nextthink = level.time + 10_hz; +} + +void WidowExplosion1(edict_t *self) +{ + int n; + vec3_t f, r, u, startpoint; + vec3_t offset = { 23.74f, -37.67f, 76.96f }; + + AngleVectors(self->s.angles, f, r, u); + startpoint = G_ProjectSource2(self->s.origin, offset, f, r, u); + + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_EXPLOSION1); + gi.WritePosition(startpoint); + gi.multicast(self->s.origin, MULTICAST_ALL, false); + + for (n = 0; n < 1; n++) + ThrowWidowGibLoc(self, "models/objects/gibs/sm_meat/tris.md2", 300, GIB_NONE, &startpoint, false); + for (n = 0; n < 1; n++) + ThrowWidowGibLoc(self, "models/objects/gibs/sm_metal/tris.md2", 100, GIB_METALLIC, &startpoint, false); + for (n = 0; n < 2; n++) + ThrowWidowGibLoc(self, "models/objects/gibs/sm_metal/tris.md2", 300, GIB_METALLIC, &startpoint, false); +} + +void WidowExplosion2(edict_t *self) +{ + int n; + vec3_t f, r, u, startpoint; + vec3_t offset = { -20.49f, 36.92f, 73.52f }; + + AngleVectors(self->s.angles, f, r, u); + startpoint = G_ProjectSource2(self->s.origin, offset, f, r, u); + + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_EXPLOSION1); + gi.WritePosition(startpoint); + gi.multicast(self->s.origin, MULTICAST_ALL, false); + + for (n = 0; n < 1; n++) + ThrowWidowGibLoc(self, "models/objects/gibs/sm_meat/tris.md2", 300, GIB_NONE, &startpoint, false); + for (n = 0; n < 1; n++) + ThrowWidowGibLoc(self, "models/objects/gibs/sm_metal/tris.md2", 100, GIB_METALLIC, &startpoint, false); + for (n = 0; n < 2; n++) + ThrowWidowGibLoc(self, "models/objects/gibs/sm_metal/tris.md2", 300, GIB_METALLIC, &startpoint, false); +} + +void WidowExplosion3(edict_t *self) +{ + int n; + vec3_t f, r, u, startpoint; + vec3_t offset = { 2.11f, 0.05f, 92.20f }; + + AngleVectors(self->s.angles, f, r, u); + startpoint = G_ProjectSource2(self->s.origin, offset, f, r, u); + + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_EXPLOSION1); + gi.WritePosition(startpoint); + gi.multicast(self->s.origin, MULTICAST_ALL, false); + + for (n = 0; n < 1; n++) + ThrowWidowGibLoc(self, "models/objects/gibs/sm_meat/tris.md2", 300, GIB_NONE, &startpoint, false); + for (n = 0; n < 1; n++) + ThrowWidowGibLoc(self, "models/objects/gibs/sm_metal/tris.md2", 100, GIB_METALLIC, &startpoint, false); + for (n = 0; n < 2; n++) + ThrowWidowGibLoc(self, "models/objects/gibs/sm_metal/tris.md2", 300, GIB_METALLIC, &startpoint, false); +} + +void WidowExplosion4(edict_t *self) +{ + int n; + vec3_t f, r, u, startpoint; + vec3_t offset = { -28.04f, -35.57f, -77.56f }; + + AngleVectors(self->s.angles, f, r, u); + startpoint = G_ProjectSource2(self->s.origin, offset, f, r, u); + + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_EXPLOSION1); + gi.WritePosition(startpoint); + gi.multicast(self->s.origin, MULTICAST_ALL, false); + + for (n = 0; n < 1; n++) + ThrowWidowGibLoc(self, "models/objects/gibs/sm_meat/tris.md2", 300, GIB_NONE, &startpoint, false); + for (n = 0; n < 1; n++) + ThrowWidowGibLoc(self, "models/objects/gibs/sm_metal/tris.md2", 100, GIB_METALLIC, &startpoint, false); + for (n = 0; n < 2; n++) + ThrowWidowGibLoc(self, "models/objects/gibs/sm_metal/tris.md2", 300, GIB_METALLIC, &startpoint, false); +} + +void WidowExplosion5(edict_t *self) +{ + int n; + vec3_t f, r, u, startpoint; + vec3_t offset = { -20.11f, -1.11f, 40.76f }; + + AngleVectors(self->s.angles, f, r, u); + startpoint = G_ProjectSource2(self->s.origin, offset, f, r, u); + + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_EXPLOSION1); + gi.WritePosition(startpoint); + gi.multicast(self->s.origin, MULTICAST_ALL, false); + + for (n = 0; n < 1; n++) + ThrowWidowGibLoc(self, "models/objects/gibs/sm_meat/tris.md2", 300, GIB_NONE, &startpoint, false); + for (n = 0; n < 1; n++) + ThrowWidowGibLoc(self, "models/objects/gibs/sm_metal/tris.md2", 100, GIB_METALLIC, &startpoint, false); + for (n = 0; n < 2; n++) + ThrowWidowGibLoc(self, "models/objects/gibs/sm_metal/tris.md2", 300, GIB_METALLIC, &startpoint, false); +} + +void WidowExplosion6(edict_t *self) +{ + int n; + vec3_t f, r, u, startpoint; + vec3_t offset = { -20.11f, -1.11f, 40.76f }; + + AngleVectors(self->s.angles, f, r, u); + startpoint = G_ProjectSource2(self->s.origin, offset, f, r, u); + + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_EXPLOSION1); + gi.WritePosition(startpoint); + gi.multicast(self->s.origin, MULTICAST_ALL, false); + + for (n = 0; n < 1; n++) + ThrowWidowGibLoc(self, "models/objects/gibs/sm_meat/tris.md2", 300, GIB_NONE, &startpoint, false); + for (n = 0; n < 1; n++) + ThrowWidowGibLoc(self, "models/objects/gibs/sm_metal/tris.md2", 100, GIB_METALLIC, &startpoint, false); + for (n = 0; n < 2; n++) + ThrowWidowGibLoc(self, "models/objects/gibs/sm_metal/tris.md2", 300, GIB_METALLIC, &startpoint, false); +} + +void WidowExplosion7(edict_t *self) +{ + int n; + vec3_t f, r, u, startpoint; + vec3_t offset = { -20.11f, -1.11f, 40.76f }; + + AngleVectors(self->s.angles, f, r, u); + startpoint = G_ProjectSource2(self->s.origin, offset, f, r, u); + + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_EXPLOSION1); + gi.WritePosition(startpoint); + gi.multicast(self->s.origin, MULTICAST_ALL, false); + + for (n = 0; n < 1; n++) + ThrowWidowGibLoc(self, "models/objects/gibs/sm_meat/tris.md2", 300, GIB_NONE, &startpoint, false); + for (n = 0; n < 1; n++) + ThrowWidowGibLoc(self, "models/objects/gibs/sm_metal/tris.md2", 100, GIB_METALLIC, &startpoint, false); + for (n = 0; n < 2; n++) + ThrowWidowGibLoc(self, "models/objects/gibs/sm_metal/tris.md2", 300, GIB_METALLIC, &startpoint, false); +} + +void WidowExplosionLeg(edict_t *self) +{ + vec3_t f, r, u, startpoint; + vec3_t offset1 = { -31.89f, -47.86f, 67.02f }; + vec3_t offset2 = { -44.9f, -82.14f, 54.72f }; + + AngleVectors(self->s.angles, f, r, u); + startpoint = G_ProjectSource2(self->s.origin, offset1, f, r, u); + + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_EXPLOSION1_BIG); + gi.WritePosition(startpoint); + gi.multicast(self->s.origin, MULTICAST_ALL, false); + + ThrowWidowGibSized(self, "models/monsters/blackwidow2/gib2/tris.md2", 200, GIB_METALLIC, &startpoint, + gi.soundindex("misc/fhit3.wav"), false); + ThrowWidowGibLoc(self, "models/objects/gibs/sm_meat/tris.md2", 300, GIB_NONE, &startpoint, false); + ThrowWidowGibLoc(self, "models/objects/gibs/sm_metal/tris.md2", 100, GIB_METALLIC, &startpoint, false); + + startpoint = G_ProjectSource2(self->s.origin, offset2, f, r, u); + + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_EXPLOSION1); + gi.WritePosition(startpoint); + gi.multicast(self->s.origin, MULTICAST_ALL, false); + + ThrowWidowGibSized(self, "models/monsters/blackwidow2/gib1/tris.md2", 300, GIB_METALLIC, &startpoint, + gi.soundindex("misc/fhit3.wav"), false); + ThrowWidowGibLoc(self, "models/objects/gibs/sm_meat/tris.md2", 300, GIB_NONE, &startpoint, false); + ThrowWidowGibLoc(self, "models/objects/gibs/sm_metal/tris.md2", 100, GIB_METALLIC, &startpoint, false); +} + +void ThrowArm1(edict_t *self) +{ + int n; + vec3_t f, r, u, startpoint; + vec3_t offset1 = { 65.76f, 17.52f, 7.56f }; + + AngleVectors(self->s.angles, f, r, u); + startpoint = G_ProjectSource2(self->s.origin, offset1, f, r, u); + + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_EXPLOSION1_BIG); + gi.WritePosition(startpoint); + gi.multicast(self->s.origin, MULTICAST_ALL, false); + + for (n = 0; n < 2; n++) + ThrowWidowGibLoc(self, "models/objects/gibs/sm_metal/tris.md2", 100, GIB_METALLIC, &startpoint, false); +} + +void ThrowArm2(edict_t *self) +{ + vec3_t f, r, u, startpoint; + vec3_t offset1 = { 65.76f, 17.52f, 7.56f }; + + AngleVectors(self->s.angles, f, r, u); + startpoint = G_ProjectSource2(self->s.origin, offset1, f, r, u); + + ThrowWidowGibSized(self, "models/monsters/blackwidow2/gib4/tris.md2", 200, GIB_METALLIC, &startpoint, + gi.soundindex("misc/fhit3.wav"), false); + ThrowWidowGibLoc(self, "models/objects/gibs/sm_meat/tris.md2", 300, GIB_NONE, &startpoint, false); +} diff --git a/rerelease/rogue/m_rogue_widow2.h b/rerelease/rogue/m_rogue_widow2.h new file mode 100644 index 0000000..fcd1a8d --- /dev/null +++ b/rerelease/rogue/m_rogue_widow2.h @@ -0,0 +1,137 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// G:\quake2\xpack\models/monsters/blackwidow2 + +// This file generated by qdata - Do NOT Modify + +enum +{ + FRAME_blackwidow3, + FRAME_walk01, + FRAME_walk02, + FRAME_walk03, + FRAME_walk04, + FRAME_walk05, + FRAME_walk06, + FRAME_walk07, + FRAME_walk08, + FRAME_walk09, + FRAME_spawn01, + FRAME_spawn02, + FRAME_spawn03, + FRAME_spawn04, + FRAME_spawn05, + FRAME_spawn06, + FRAME_spawn07, + FRAME_spawn08, + FRAME_spawn09, + FRAME_spawn10, + FRAME_spawn11, + FRAME_spawn12, + FRAME_spawn13, + FRAME_spawn14, + FRAME_spawn15, + FRAME_spawn16, + FRAME_spawn17, + FRAME_spawn18, + FRAME_firea01, + FRAME_firea02, + FRAME_firea03, + FRAME_firea04, + FRAME_firea05, + FRAME_firea06, + FRAME_firea07, + FRAME_fireb01, + FRAME_fireb02, + FRAME_fireb03, + FRAME_fireb04, + FRAME_fireb05, + FRAME_fireb06, + FRAME_fireb07, + FRAME_fireb08, + FRAME_fireb09, + FRAME_fireb10, + FRAME_fireb11, + FRAME_fireb12, + FRAME_tongs01, + FRAME_tongs02, + FRAME_tongs03, + FRAME_tongs04, + FRAME_tongs05, + FRAME_tongs06, + FRAME_tongs07, + FRAME_tongs08, + FRAME_pain01, + FRAME_pain02, + FRAME_pain03, + FRAME_pain04, + FRAME_pain05, + FRAME_death01, + FRAME_death02, + FRAME_death03, + FRAME_death04, + FRAME_death05, + FRAME_death06, + FRAME_death07, + FRAME_death08, + FRAME_death09, + FRAME_death10, + FRAME_death11, + FRAME_death12, + FRAME_death13, + FRAME_death14, + FRAME_death15, + FRAME_death16, + FRAME_death17, + FRAME_death18, + FRAME_death19, + FRAME_death20, + FRAME_death21, + FRAME_death22, + FRAME_death23, + FRAME_death24, + FRAME_death25, + FRAME_death26, + FRAME_death27, + FRAME_death28, + FRAME_death29, + FRAME_death30, + FRAME_death31, + FRAME_death32, + FRAME_death33, + FRAME_death34, + FRAME_death35, + FRAME_death36, + FRAME_death37, + FRAME_death38, + FRAME_death39, + FRAME_death40, + FRAME_death41, + FRAME_death42, + FRAME_death43, + FRAME_death44, + FRAME_dthsrh01, + FRAME_dthsrh02, + FRAME_dthsrh03, + FRAME_dthsrh04, + FRAME_dthsrh05, + FRAME_dthsrh06, + FRAME_dthsrh07, + FRAME_dthsrh08, + FRAME_dthsrh09, + FRAME_dthsrh10, + FRAME_dthsrh11, + FRAME_dthsrh12, + FRAME_dthsrh13, + FRAME_dthsrh14, + FRAME_dthsrh15, + FRAME_dthsrh16, + FRAME_dthsrh17, + FRAME_dthsrh18, + FRAME_dthsrh19, + FRAME_dthsrh20, + FRAME_dthsrh21, + FRAME_dthsrh22 +}; + +constexpr float MODEL_SCALE = 2.000000f; diff --git a/rerelease/rogue/p_rogue_weapon.cpp b/rerelease/rogue/p_rogue_weapon.cpp new file mode 100644 index 0000000..b87f662 --- /dev/null +++ b/rerelease/rogue/p_rogue_weapon.cpp @@ -0,0 +1,446 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. + +#include "../g_local.h" +#include "../m_player.h" + +void weapon_prox_fire(edict_t *ent) +{ + vec3_t start, dir; + // Paril: kill sideways angle on grenades + // limit upwards angle so you don't fire behind you + P_ProjectSource(ent, { max(-62.5f, ent->client->v_angle[0]), ent->client->v_angle[1], ent->client->v_angle[2] }, { 8, 0, -8 }, start, dir); + + P_AddWeaponKick(ent, ent->client->v_forward * -2, { -1.f, 0.f, 0.f }); + + fire_prox(ent, start, dir, damage_multiplier, 600); + + gi.WriteByte(svc_muzzleflash); + gi.WriteEntity(ent); + gi.WriteByte(MZ_PROX | is_silenced); + gi.multicast(ent->s.origin, MULTICAST_PVS, false); + + PlayerNoise(ent, start, PNOISE_WEAPON); + + G_RemoveAmmo(ent); +} + +void Weapon_ProxLauncher(edict_t *ent) +{ + constexpr int pause_frames[] = { 34, 51, 59, 0 }; + constexpr int fire_frames[] = { 6, 0 }; + + Weapon_Generic(ent, 5, 16, 59, 64, pause_frames, fire_frames, weapon_prox_fire); +} + +void weapon_tesla_fire(edict_t *ent, bool held) +{ + vec3_t start, dir; + // Paril: kill sideways angle on grenades + // limit upwards angle so you don't throw behind you + P_ProjectSource(ent, { max(-62.5f, ent->client->v_angle[0]), ent->client->v_angle[1], ent->client->v_angle[2] }, { 0, 0, -22 }, start, dir); + + gtime_t timer = ent->client->grenade_time - level.time; + int speed = (int) (ent->health <= 0 ? GRENADE_MINSPEED : min(GRENADE_MINSPEED + (GRENADE_TIMER - timer).seconds() * ((GRENADE_MAXSPEED - GRENADE_MINSPEED) / GRENADE_TIMER.seconds()), GRENADE_MAXSPEED)); + + ent->client->grenade_time = 0_ms; + + fire_tesla(ent, start, dir, damage_multiplier, speed); + + G_RemoveAmmo(ent, 1); +} + +void Weapon_Tesla(edict_t *ent) +{ + constexpr int pause_frames[] = { 21, 0 }; + + Throw_Generic(ent, 8, 32, -1, nullptr, 1, 2, pause_frames, false, nullptr, weapon_tesla_fire, false); +} + +//====================================================================== +// ROGUE MODS BELOW +//====================================================================== + +// +// CHAINFIST +// +constexpr int32_t CHAINFIST_REACH = 24; + +void weapon_chainfist_fire(edict_t *ent) +{ + if (!(ent->client->buttons & BUTTON_ATTACK)) + { + if (ent->client->ps.gunframe == 13 || + ent->client->ps.gunframe == 23 || + ent->client->ps.gunframe >= 32) + { + ent->client->ps.gunframe = 33; + return; + } + } + + int damage = 7; + + if (deathmatch->integer) + damage = 15; + + if (is_quad) + damage *= damage_multiplier; + + // set start point + vec3_t start, dir; + + P_ProjectSource(ent, ent->client->v_angle, { 0, 0, -4 }, start, dir); + + if (fire_player_melee(ent, start, dir, CHAINFIST_REACH, damage, 100, MOD_CHAINFIST)) + { + if (ent->client->empty_click_sound < level.time) + { + ent->client->empty_click_sound = level.time + 500_ms; + gi.sound(ent, CHAN_WEAPON, gi.soundindex("weapons/sawslice.wav"), 1.f, ATTN_NORM, 0.f); + } + } + + PlayerNoise(ent, start, PNOISE_WEAPON); + + ent->client->ps.gunframe++; + + if (ent->client->buttons & BUTTON_ATTACK) + { + if (ent->client->ps.gunframe == 12) + ent->client->ps.gunframe = 14; + else if (ent->client->ps.gunframe == 22) + ent->client->ps.gunframe = 24; + else if (ent->client->ps.gunframe >= 32) + ent->client->ps.gunframe = 7; + } + + // start the animation + if (ent->client->anim_priority != ANIM_ATTACK || frandom() < 0.25f) + { + ent->client->anim_priority = ANIM_ATTACK; + if (ent->client->ps.pmove.pm_flags & PMF_DUCKED) + { + ent->s.frame = FRAME_crattak1 - 1; + ent->client->anim_end = FRAME_crattak9; + } + else + { + ent->s.frame = FRAME_attack1 - 1; + ent->client->anim_end = FRAME_attack8; + } + ent->client->anim_time = 0_ms; + } +} + +// this spits out some smoke from the motor. it's a two-stroke, you know. +void chainfist_smoke(edict_t *ent) +{ + vec3_t tempVec, dir; + P_ProjectSource(ent, ent->client->v_angle, { 8, 8, -4 }, tempVec, dir); + + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_CHAINFIST_SMOKE); + gi.WritePosition(tempVec); + gi.unicast(ent, 0); +} + +void Weapon_ChainFist(edict_t *ent) +{ + constexpr int pause_frames[] = { 0 }; + + Weapon_Repeating(ent, 4, 32, 57, 60, pause_frames, weapon_chainfist_fire); + + // smoke on idle sequence + if (ent->client->ps.gunframe == 42 && irandom(8)) + { + if ((ent->client->pers.hand != CENTER_HANDED) && frandom() < 0.4f) + chainfist_smoke(ent); + } + else if (ent->client->ps.gunframe == 51 && irandom(8)) + { + if ((ent->client->pers.hand != CENTER_HANDED) && frandom() < 0.4f) + chainfist_smoke(ent); + } + + // set the appropriate weapon sound. + if (ent->client->weaponstate == WEAPON_FIRING) + ent->client->weapon_sound = gi.soundindex("weapons/sawhit.wav"); + else if (ent->client->weaponstate == WEAPON_DROPPING) + ent->client->weapon_sound = 0; + else if (ent->client->pers.weapon->id == IT_WEAPON_CHAINFIST) + ent->client->weapon_sound = gi.soundindex("weapons/sawidle.wav"); +} + +// +// Disintegrator +// + +void weapon_tracker_fire(edict_t *self) +{ + vec3_t end; + edict_t *enemy; + trace_t tr; + int damage; + vec3_t mins, maxs; + + // PMM - felt a little high at 25 + if (deathmatch->integer) + damage = 45; + else + damage = 135; + + if (is_quad) + damage *= damage_multiplier; // pgm + + mins = { -16, -16, -16 }; + maxs = { 16, 16, 16 }; + + vec3_t start, dir; + P_ProjectSource(self, self->client->v_angle, { 24, 8, -8 }, start, dir); + + end = start + (dir * 8192); + enemy = nullptr; + // PMM - doing two traces .. one point and one box. + contents_t mask = MASK_PROJECTILE; + + // [Paril-KEX] + if (!G_ShouldPlayersCollide(true)) + mask &= ~CONTENTS_PLAYER; + + G_LagCompensate(self, start, dir); + tr = gi.traceline(start, end, self, mask); + G_UnLagCompensate(); + if (tr.ent != world) + { + if ((tr.ent->svflags & SVF_MONSTER) || tr.ent->client || (tr.ent->flags & FL_DAMAGEABLE)) + { + if (tr.ent->health > 0) + enemy = tr.ent; + } + } + else + { + tr = gi.trace(start, mins, maxs, end, self, mask); + if (tr.ent != world) + { + if ((tr.ent->svflags & SVF_MONSTER) || tr.ent->client || (tr.ent->flags & FL_DAMAGEABLE)) + { + if (tr.ent->health > 0) + enemy = tr.ent; + } + } + } + + P_AddWeaponKick(self, self->client->v_forward * -2, { -1.f, 0.f, 0.f }); + + fire_tracker(self, start, dir, damage, 1000, enemy); + + // send muzzle flash + gi.WriteByte(svc_muzzleflash); + gi.WriteEntity(self); + gi.WriteByte(MZ_TRACKER | is_silenced); + gi.multicast(self->s.origin, MULTICAST_PVS, false); + + PlayerNoise(self, start, PNOISE_WEAPON); + + G_RemoveAmmo(self); +} + +void Weapon_Disintegrator(edict_t *ent) +{ + constexpr int pause_frames[] = { 14, 19, 23, 0 }; + constexpr int fire_frames[] = { 5, 0 }; + + Weapon_Generic(ent, 4, 9, 29, 34, pause_frames, fire_frames, weapon_tracker_fire); +} + +/* +====================================================================== + +ETF RIFLE + +====================================================================== +*/ +void weapon_etf_rifle_fire(edict_t *ent) +{ + int damage; + int kick = 3; + int i; + vec3_t offset; + + if (deathmatch->integer) + damage = 10; + else + damage = 10; + + if (!(ent->client->buttons & BUTTON_ATTACK)) + { + ent->client->ps.gunframe = 8; + return; + } + + if (ent->client->ps.gunframe == 6) + ent->client->ps.gunframe = 7; + else + ent->client->ps.gunframe = 6; + + // PGM - adjusted to use the quantity entry in the weapon structure. + if (ent->client->pers.inventory[ent->client->pers.weapon->ammo] < ent->client->pers.weapon->quantity) + { + ent->client->ps.gunframe = 8; + NoAmmoWeaponChange(ent, true); + return; + } + + if (is_quad) + { + damage *= damage_multiplier; + kick *= damage_multiplier; + } + + vec3_t kick_origin {}, kick_angles {}; + for (i = 0; i < 3; i++) + { + kick_origin[i] = crandom() * 0.85f; + kick_angles[i] = crandom() * 0.85f; + } + P_AddWeaponKick(ent, kick_origin, kick_angles); + + // get start / end positions + if (ent->client->ps.gunframe == 6) + offset = { 15, 8, -8 }; + else + offset = { 15, 6, -8 }; + + vec3_t start, dir; + P_ProjectSource(ent, ent->client->v_angle + kick_angles, offset, start, dir); + fire_flechette(ent, start, dir, damage, 1150, kick); + Weapon_PowerupSound(ent); + + // send muzzle flash + gi.WriteByte(svc_muzzleflash); + gi.WriteEntity(ent); + gi.WriteByte((ent->client->ps.gunframe == 6 ? MZ_ETF_RIFLE : MZ_ETF_RIFLE_2) | is_silenced); + gi.multicast(ent->s.origin, MULTICAST_PVS, false); + + PlayerNoise(ent, start, PNOISE_WEAPON); + + G_RemoveAmmo(ent); + + ent->client->anim_priority = ANIM_ATTACK; + if (ent->client->ps.pmove.pm_flags & PMF_DUCKED) + { + ent->s.frame = FRAME_crattak1 - (int) (frandom() + 0.25f); + ent->client->anim_end = FRAME_crattak9; + } + else + { + ent->s.frame = FRAME_attack1 - (int) (frandom() + 0.25f); + ent->client->anim_end = FRAME_attack8; + } + ent->client->anim_time = 0_ms; +} + +void Weapon_ETF_Rifle(edict_t *ent) +{ + constexpr int pause_frames[] = { 18, 28, 0 }; + + Weapon_Repeating(ent, 4, 7, 37, 41, pause_frames, weapon_etf_rifle_fire); +} + +constexpr int32_t HEATBEAM_DM_DMG = 15; +constexpr int32_t HEATBEAM_SP_DMG = 15; + +void Heatbeam_Fire(edict_t *ent) +{ + bool firing = (ent->client->buttons & BUTTON_ATTACK); + bool has_ammo = ent->client->pers.inventory[ent->client->pers.weapon->ammo] >= ent->client->pers.weapon->quantity; + + if (!firing || !has_ammo) + { + ent->client->ps.gunframe = 13; + ent->client->weapon_sound = 0; + ent->client->ps.gunskin = 0; + + if (firing && !has_ammo) + NoAmmoWeaponChange(ent, true); + return; + } + + // start on frame 8 + if (ent->client->ps.gunframe > 12) + ent->client->ps.gunframe = 8; + else + ent->client->ps.gunframe++; + + if (ent->client->ps.gunframe == 12) + ent->client->ps.gunframe = 8; + + // play weapon sound for firing + ent->client->weapon_sound = gi.soundindex("weapons/bfg__l1a.wav"); + ent->client->ps.gunskin = 1; + + int damage; + int kick; + + // for comparison, the hyperblaster is 15/20 + // jim requested more damage, so try 15/15 --- PGM 07/23/98 + if (deathmatch->integer) + damage = HEATBEAM_DM_DMG; + else + damage = HEATBEAM_SP_DMG; + + if (deathmatch->integer) // really knock 'em around in deathmatch + kick = 75; + else + kick = 30; + + if (is_quad) + { + damage *= damage_multiplier; + kick *= damage_multiplier; + } + + ent->client->kick.time = 0_ms; + + // This offset is the "view" offset for the beam start (used by trace) + vec3_t start, dir; + P_ProjectSource(ent, ent->client->v_angle, { 7, 2, -3 }, start, dir); + + // This offset is the entity offset + G_LagCompensate(ent, start, dir); + fire_heatbeam(ent, start, dir, { 2, 7, -3 }, damage, kick, false); + G_UnLagCompensate(); + Weapon_PowerupSound(ent); + + // send muzzle flash + gi.WriteByte(svc_muzzleflash); + gi.WriteEntity(ent); + gi.WriteByte(MZ_HEATBEAM | is_silenced); + gi.multicast(ent->s.origin, MULTICAST_PVS, false); + + PlayerNoise(ent, start, PNOISE_WEAPON); + + G_RemoveAmmo(ent); + + ent->client->anim_priority = ANIM_ATTACK; + if (ent->client->ps.pmove.pm_flags & PMF_DUCKED) + { + ent->s.frame = FRAME_crattak1 - (int) (frandom() + 0.25f); + ent->client->anim_end = FRAME_crattak9; + } + else + { + ent->s.frame = FRAME_attack1 - (int) (frandom() + 0.25f); + ent->client->anim_end = FRAME_attack8; + } + ent->client->anim_time = 0_ms; +} + +void Weapon_Heatbeam(edict_t *ent) +{ + constexpr int pause_frames[] = { 35, 0 }; + + Weapon_Repeating(ent, 8, 12, 42, 47, pause_frames, Heatbeam_Fire); +} \ No newline at end of file diff --git a/rerelease/rogue/rogue_dm_ball.cpp b/rerelease/rogue/rogue_dm_ball.cpp new file mode 100644 index 0000000..e20fb09 --- /dev/null +++ b/rerelease/rogue/rogue_dm_ball.cpp @@ -0,0 +1,687 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// dm_ball.c +// pmack +// june 98 + +#include "../g_local.h" + +// defines + +constexpr spawnflags_t SPAWNFLAG_DBALL_GOAL_TEAM1 = 0x0001_spawnflag; +// unused; assumed by not being team1 +// constexpr uint32_t SPAWNFLAG_DBALL_GOAL_TEAM2 = 0x0002; + +// globals + +edict_t *dball_ball_entity = nullptr; +int dball_ball_startpt_count; +int dball_team1_goalscore; +int dball_team2_goalscore; + +cvar_t *dball_team1_skin; +cvar_t *dball_team2_skin; +cvar_t *goallimit; + +// prototypes + +void EndDMLevel(); +float PlayersRangeFromSpot(edict_t *spot); + +void DBall_BallDie(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod); +void DBall_BallRespawn(edict_t *self); + +// ************************** +// Game rules +// ************************** + +int DBall_CheckDMRules() +{ + if (goallimit->integer) + { + if (dball_team1_goalscore >= goallimit->integer) + gi.LocBroadcast_Print(PRINT_HIGH, "Team 1 Wins.\n"); + else if (dball_team2_goalscore >= goallimit->integer) + gi.LocBroadcast_Print(PRINT_HIGH, "Team 2 Wins.\n"); + else + return 0; + + EndDMLevel(); + return 1; + } + + return 0; +} + +//================== +//================== +void DBall_ClientBegin(edict_t *ent) +{ +#if 0 + uint32_t team1, team2, unassigned; + edict_t *other; + char *p; + static char value[512]; + + team1 = 0; + team2 = 0; + unassigned = 0; + + for (uint32_t j = 1; j <= game.maxclients; j++) + { + other = &g_edicts[j]; + if (!other->inuse) + continue; + if (!other->client) + continue; + if (other == ent) // don't count the new player + continue; + + Q_strlcpy(value, Info_ValueForKey(other->client->pers.userinfo, "skin"), sizeof(value)); + p = strchr(value, '/'); + if (p) + { + if (!strcmp(dball_team1_skin->string, value)) + team1++; + else if (!strcmp(dball_team2_skin->string, value)) + team2++; + else + unassigned++; + } + else + unassigned++; + } + + if (team1 > team2) + { + gi.Com_Print("assigned to team 2\n"); + Info_SetValueForKey(ent->client->pers.userinfo, "skin", dball_team2_skin->string); + } + else + { + gi.Com_Print("assigned to team 1\n"); + Info_SetValueForKey(ent->client->pers.userinfo, "skin", dball_team1_skin->string); + } + + ClientUserinfoChanged(ent, ent->client->pers.userinfo); + + if (unassigned) + gi.Com_PrintFmt("{} unassigned players present!\n", unassigned); +#endif +} + +//================== +//================== +bool DBall_SelectSpawnPoint(edict_t *ent, vec3_t &origin, vec3_t &angles, bool force_spawn) +{ +#if 0 + edict_t *bestspot; + float bestdistance, bestplayerdistance; + edict_t *spot; + const char *spottype; + static char skin[512]; + + Q_strlcpy(skin, Info_ValueForKey(ent->client->pers.userinfo, "skin"), sizeof(skin)); + if (!strcmp(dball_team1_skin->string, skin)) + spottype = "dm_dball_team1_start"; + else if (!strcmp(dball_team2_skin->string, skin)) + spottype = "dm_dball_team2_start"; + else + spottype = "info_player_deathmatch"; + + spot = nullptr; + bestspot = nullptr; + bestdistance = 0; + while ((spot = G_FindByString<&edict_t::classname>(spot, spottype)) != nullptr) + { + bestplayerdistance = PlayersRangeFromSpot(spot); + + if (bestplayerdistance > bestdistance) + { + bestspot = spot; + bestdistance = bestplayerdistance; + } + } + + if (bestspot) + { + origin = bestspot->s.origin; + origin[2] += 9; + angles = bestspot->s.angles; + return true; + } + + // if we didn't find an appropriate spawnpoint, just + // call the standard one. +#endif + bool lm = false; + return SelectSpawnPoint(ent, origin, angles, force_spawn, lm); +} + +//================== +//================== +void DBall_GameInit() +{ + // we don't want a minimum speed for friction to take effect. + // this will allow any knockback to move stuff. + gi.cvar_forceset("sv_stopspeed", "0"); + dball_team1_goalscore = 0; + dball_team2_goalscore = 0; + + gi.cvar_forceset(g_no_mines->name, "1"); + gi.cvar_forceset(g_no_nukes->name, "1"); + gi.cvar_forceset(g_dm_no_stack_double->name, "1"); + gi.cvar_forceset(g_friendly_fire->name, "0"); + //gi.cvar_forceset(g_no_mines->name, "1"); note: skin teams gone... + + dball_team1_skin = gi.cvar("dball_team1_skin", "male/ctf_r", CVAR_NOFLAGS); + dball_team2_skin = gi.cvar("dball_team2_skin", "male/ctf_b", CVAR_NOFLAGS); + goallimit = gi.cvar("goallimit", "0", CVAR_NOFLAGS); +} + +//================== +//================== +void DBall_PostInitSetup() +{ + edict_t *e; + + e = nullptr; + // turn teleporter destinations nonsolid. + while ((e = G_FindByString<&edict_t::classname>(e, "misc_teleporter_dest"))) + { + e->solid = SOLID_NOT; + gi.linkentity(e); + } + + // count the ball start points + dball_ball_startpt_count = 0; + e = nullptr; + while ((e = G_FindByString<&edict_t::classname>(e, "dm_dball_ball_start"))) + { + dball_ball_startpt_count++; + } + + if (dball_ball_startpt_count == 0) + gi.Com_Print("No Deathball start points!\n"); +} + +//================== +// DBall_ChangeDamage - half damage between players. full if it involves +// the ball entity +//================== +int DBall_ChangeDamage(edict_t *targ, edict_t *attacker, int damage, mod_t mod) +{ + // cut player -> ball damage to 1 + if (targ == dball_ball_entity) + return 1; + + // damage player -> player is halved + if (attacker != dball_ball_entity) + return damage / 2; + + return damage; +} + +//================== +//================== +int DBall_ChangeKnockback(edict_t *targ, edict_t *attacker, int knockback, mod_t mod) +{ + if (targ != dball_ball_entity) + return knockback; + + if (knockback < 1) + { + // FIXME - these don't account for quad/double + if (mod.id == MOD_ROCKET) // rocket + knockback = 70; + else if (mod.id == MOD_BFG_EFFECT) // bfg + knockback = 90; + else + gi.Com_PrintFmt("zero knockback, mod {}\n", (int32_t) mod.id); + } + else + { + // FIXME - change this to an array? + switch (mod.id) + { + case MOD_BLASTER: + knockback *= 3; + break; + case MOD_SHOTGUN: + knockback = (knockback * 3) / 8; + break; + case MOD_SSHOTGUN: + knockback = knockback / 3; + break; + case MOD_MACHINEGUN: + knockback = (knockback * 3) / 2; + break; + case MOD_HYPERBLASTER: + knockback *= 4; + break; + case MOD_GRENADE: + case MOD_HANDGRENADE: + case MOD_PROX: + case MOD_G_SPLASH: + case MOD_HG_SPLASH: + case MOD_HELD_GRENADE: + case MOD_TRACKER: + case MOD_DISINTEGRATOR: + knockback /= 2; + break; + case MOD_R_SPLASH: + knockback = (knockback * 3) / 2; + break; + case MOD_RAILGUN: + case MOD_HEATBEAM: + knockback /= 3; + break; + default: + break; + } + } + + // gi.dprintf("mod: %d knockback: %d\n", mod, knockback); + return knockback; +} + +// ************************** +// Goals +// ************************** + +TOUCH(DBall_GoalTouch) (edict_t *self, edict_t *other, const trace_t &tr, bool other_touching_self) -> void +{ +#if 0 + static char value[512]; + int team_score; + int scorechange; + char *p; + edict_t *ent; + + if (other != dball_ball_entity) + return; + + self->health = self->max_health; + + // determine which team scored, and bump the team score + if (self->spawnflags.has(SPAWNFLAG_DBALL_GOAL_TEAM1)) + { + dball_team1_goalscore += (int) self->wait; + team_score = 1; + } + else + { + dball_team2_goalscore += (int) self->wait; + team_score = 2; + } + + // bump the score for everyone on the correct team. + for (uint32_t j = 1; j <= game.maxclients; j++) + { + ent = &g_edicts[j]; + if (!ent->inuse) + continue; + if (!ent->client) + continue; + + if (ent == other->enemy) + scorechange = (int) self->wait + 5; + else + scorechange = (int) self->wait; + + Q_strlcpy(value, Info_ValueForKey(ent->client->pers.userinfo, "skin"), sizeof(value)); + p = strchr(value, '/'); + if (p) + { + if (!strcmp(dball_team1_skin->string, value)) + { + if (team_score == 1) + ent->client->resp.score += scorechange; + else if (other->enemy == ent) + ent->client->resp.score -= scorechange; + } + else if (!strcmp(dball_team2_skin->string, value)) + { + if (team_score == 2) + ent->client->resp.score += scorechange; + else if (other->enemy == ent) + ent->client->resp.score -= scorechange; + } + else + gi.Com_Print("unassigned player!!!!\n"); + } + } + + if (other->enemy) + gi.Com_PrintFmt("score for team {} by {}\n", team_score, other->enemy->client->pers.netname); + else + gi.Com_PrintFmt("score for team {} by someone\n", team_score); + + DBall_BallDie(other, other->enemy, other->enemy, 0, vec3_origin, MOD_SUICIDE); + + G_UseTargets(self, other); +#endif +} + +// ************************** +// Ball +// ************************** + +edict_t *PickBallStart(edict_t *ent) +{ + int which, current; + edict_t *e; + + which = irandom(dball_ball_startpt_count); + e = nullptr; + current = 0; + + while ((e = G_FindByString<&edict_t::classname>(e, "dm_dball_ball_start"))) + { + current++; + if (current == which) + return e; + } + + if (current == 0) + gi.Com_Print("No ball start points found!\n"); + + return G_FindByString<&edict_t::classname>(nullptr, "dm_dball_ball_start"); +} + +//================== +// DBall_BallTouch - if the ball hit another player, hurt them +//================== +TOUCH(DBall_BallTouch) (edict_t *ent, edict_t *other, const trace_t &tr, bool other_touching_self) -> void +{ + vec3_t dir; + float dot; + float speed; + + if (other->takedamage == false) + return; + + // hit a player + if (other->client) + { + if (ent->velocity[0] || ent->velocity[1] || ent->velocity[2]) + { + speed = ent->velocity.length(); + + dir = ent->s.origin - other->s.origin; + dot = dir.dot(ent->velocity); + + if (dot > 0.7f) + { + T_Damage(other, ent, ent, vec3_origin, ent->s.origin, vec3_origin, + (int) (speed / 10), (int) (speed / 10), DAMAGE_NONE, MOD_DBALL_CRUSH); + } + } + } +} + +//================== +// DBall_BallPain +//================== +PAIN(DBall_BallPain) (edict_t *self, edict_t *other, float kick, int damage, const mod_t &mod) -> void +{ + self->enemy = other; + self->health = self->max_health; + // if(other->classname) + // gi.Com_PrintFmt("hurt by {} -- {}\n", other->classname, self->health); +} + +DIE(DBall_BallDie) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void +{ + // do the splash effect + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_DBALL_GOAL); + gi.WritePosition(self->s.origin); + gi.multicast(self->s.origin, MULTICAST_PVS, false); + + self->s.angles = {}; + self->velocity = {}; + self->avelocity = {}; + + // make it invisible and desolid until respawn time + self->solid = SOLID_NOT; + // self->s.modelindex = 0; + self->think = DBall_BallRespawn; + self->nextthink = level.time + 2_sec; + gi.linkentity(self); +} + +THINK(DBall_BallRespawn) (edict_t *self) -> void +{ + edict_t *start; + + // do the splash effect + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_DBALL_GOAL); + gi.WritePosition(self->s.origin); + gi.multicast(self->s.origin, MULTICAST_PVS, false); + + // move the ball and stop it + start = PickBallStart(self); + if (start) + { + self->s.origin = start->s.origin; + self->s.old_origin = start->s.origin; + } + + self->s.angles = {}; + self->velocity = {}; + self->avelocity = {}; + + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex("models/objects/dball/tris.md2"); + self->s.event = EV_PLAYER_TELEPORT; + self->groundentity = nullptr; + + gi.linkentity(self); + + // kill anything at the destination + KillBox(self, false); +} + +// ************************ +// SPEED CHANGES +// ************************ + +constexpr spawnflags_t SPAWNFLAG_DBALL_SPEED_ONEWAY = 1_spawnflag; + +TOUCH(DBall_SpeedTouch) (edict_t *self, edict_t *other, const trace_t &tr, bool other_touching_self) -> void +{ + float dot; + vec3_t vel; + + if (other != dball_ball_entity) + return; + + if (self->timestamp >= level.time) + return; + + if (other->velocity.length() < 1) + return; + + if (self->spawnflags.has(SPAWNFLAG_DBALL_SPEED_ONEWAY)) + { + vel = other->velocity; + vel.normalize(); + dot = vel.dot(self->movedir); + if (dot < 0.8f) + return; + } + + self->timestamp = level.time + gtime_t::from_sec(self->delay); + other->velocity *= self->speed; +} + +// ************************ +// SPAWN FUNCTIONS +// ************************ + +/*QUAKED dm_dball_ball (1 .5 .5) (-48 -48 -48) (48 48 48) ONEWAY +Deathball Ball +*/ +void SP_dm_dball_ball(edict_t *self) +{ + if (!deathmatch->integer) + { + G_FreeEdict(self); + return; + } + + if (gamerules->integer != RDM_DEATHBALL) + { + G_FreeEdict(self); + return; + } + + dball_ball_entity = self; + // dball_ball_startpt = self->s.origin; + + self->s.modelindex = gi.modelindex("models/objects/dball/tris.md2"); + self->mins = { -32, -32, -32 }; + self->maxs = { 32, 32, 32 }; + self->solid = SOLID_BBOX; + self->movetype = MOVETYPE_NEWTOSS; + self->clipmask = MASK_MONSTERSOLID; + + self->takedamage = true; + self->mass = 50; + self->health = 50000; + self->max_health = 50000; + self->pain = DBall_BallPain; + self->die = DBall_BallDie; + self->touch = DBall_BallTouch; + + gi.linkentity(self); +} + +/*QUAKED dm_dball_team1_start (1 .5 .5) (-16 -16 -24) (16 16 32) +Deathball team 1 start point +*/ +void SP_dm_dball_team1_start(edict_t *self) +{ + if (!deathmatch->integer) + { + G_FreeEdict(self); + return; + } + if (gamerules->integer != RDM_DEATHBALL) + { + G_FreeEdict(self); + return; + } +} + +/*QUAKED dm_dball_team2_start (1 .5 .5) (-16 -16 -24) (16 16 32) +Deathball team 2 start point +*/ +void SP_dm_dball_team2_start(edict_t *self) +{ + if (!deathmatch->integer) + { + G_FreeEdict(self); + return; + } + if (gamerules->integer != RDM_DEATHBALL) + { + G_FreeEdict(self); + return; + } +} + +/*QUAKED dm_dball_ball_start (1 .5 .5) (-48 -48 -48) (48 48 48) +Deathball ball start point +*/ +void SP_dm_dball_ball_start(edict_t *self) +{ + if (!deathmatch->integer) + { + G_FreeEdict(self); + return; + } + if (gamerules->integer != RDM_DEATHBALL) + { + G_FreeEdict(self); + return; + } +} + +/*QUAKED dm_dball_speed_change (1 .5 .5) ? ONEWAY +Deathball ball speed changing field. + +speed: multiplier for speed (.5 = half, 2 = double, etc) (default = double) +angle: used with ONEWAY so speed change is only one way. +delay: time between speed changes (default: 0.2 sec) +*/ +void SP_dm_dball_speed_change(edict_t *self) +{ + if (!deathmatch->integer) + { + G_FreeEdict(self); + return; + } + if (gamerules->integer != RDM_DEATHBALL) + { + G_FreeEdict(self); + return; + } + + if (!self->speed) + self->speed = 2; + + if (!self->delay) + self->delay = 0.2f; + + self->touch = DBall_SpeedTouch; + self->solid = SOLID_TRIGGER; + self->movetype = MOVETYPE_NONE; + self->svflags |= SVF_NOCLIENT; + + if (self->s.angles) + G_SetMovedir(self->s.angles, self->movedir); + else + self->movedir = { 1, 0, 0 }; + + gi.setmodel(self, self->model); + gi.linkentity(self); +} + +/*QUAKED dm_dball_goal (1 .5 .5) ? TEAM1 TEAM2 +Deathball goal + +Team1/Team2 - beneficiary of this goal. when the ball enters this goal, the beneficiary team will score. + +"wait": score to be given for this goal (default 10) player gets score+5. +*/ +void SP_dm_dball_goal(edict_t *self) +{ + if (!deathmatch->integer) + { + G_FreeEdict(self); + return; + } + + if (gamerules->integer != RDM_DEATHBALL) + { + G_FreeEdict(self); + return; + } + + if (!self->wait) + self->wait = 10; + + self->touch = DBall_GoalTouch; + self->solid = SOLID_TRIGGER; + self->movetype = MOVETYPE_NONE; + self->svflags |= SVF_NOCLIENT; + + if (self->s.angles) + G_SetMovedir(self->s.angles, self->movedir); + + gi.setmodel(self, self->model); + gi.linkentity(self); +} diff --git a/rerelease/rogue/rogue_dm_tag.cpp b/rerelease/rogue/rogue_dm_tag.cpp new file mode 100644 index 0000000..2a3c92a --- /dev/null +++ b/rerelease/rogue/rogue_dm_tag.cpp @@ -0,0 +1,297 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// dm_tag +// pmack +// june 1998 + +#include "../g_local.h" + +void SP_dm_tag_token(edict_t *self); + +// *********************** +// Tag Specific Stuff +// *********************** + +edict_t *tag_token; +edict_t *tag_owner; +int tag_count; + +//================= +//================= +void Tag_PlayerDeath(edict_t *targ, edict_t *inflictor, edict_t *attacker) +{ + if (tag_token && targ && (targ == tag_owner)) + { + Tag_DropToken(targ, GetItemByIndex(IT_ITEM_TAG_TOKEN)); + tag_owner = nullptr; + tag_count = 0; + } +} + +//================= +//================= +void Tag_KillItBonus(edict_t *self) +{ + edict_t *armor; + + // if the player is hurt, boost them up to max. + if (self->health < self->max_health) + { + self->health += 200; + if (self->health > self->max_health) + self->health = self->max_health; + } + + // give the player a body armor + armor = G_Spawn(); + armor->spawnflags |= SPAWNFLAG_ITEM_DROPPED; + armor->item = GetItemByIndex(IT_ARMOR_BODY); + Touch_Item(armor, self, null_trace, true); + if (armor->inuse) + G_FreeEdict(armor); +} + +//================= +//================= +void Tag_PlayerDisconnect(edict_t *self) +{ + if (tag_token && self && (self == tag_owner)) + { + Tag_DropToken(self, GetItemByIndex(IT_ITEM_TAG_TOKEN)); + tag_owner = nullptr; + tag_count = 0; + } +} + +//================= +//================= +void Tag_Score(edict_t *attacker, edict_t *victim, int scoreChange, const mod_t &mod) +{ + gitem_t *quad; + + if (tag_token && tag_owner) + { + // owner killed somone else + if ((scoreChange > 0) && tag_owner == attacker) + { + scoreChange = 3; + tag_count++; + if (tag_count == 5) + { + quad = GetItemByIndex(IT_ITEM_QUAD); + attacker->client->pers.inventory[IT_ITEM_QUAD]++; + quad->use(attacker, quad); + tag_count = 0; + } + } + // owner got killed. 5 points and switch owners + else if (tag_owner == victim && tag_owner != attacker) + { + scoreChange = 5; + if ((mod.id == MOD_HUNTER_SPHERE) || (mod.id == MOD_DOPPLE_EXPLODE) || + (mod.id == MOD_DOPPLE_VENGEANCE) || (mod.id == MOD_DOPPLE_HUNTER) || + (attacker->health <= 0)) + { + Tag_DropToken(tag_owner, GetItemByIndex(IT_ITEM_TAG_TOKEN)); + tag_owner = nullptr; + tag_count = 0; + } + else + { + Tag_KillItBonus(attacker); + tag_owner = attacker; + tag_count = 0; + } + } + } + + attacker->client->resp.score += scoreChange; +} + +//================= +//================= +bool Tag_PickupToken(edict_t *ent, edict_t *other) +{ + if (gamerules->integer != RDM_TAG) + { + return false; + } + + // sanity checking is good. + if (tag_token != ent) + tag_token = ent; + + other->client->pers.inventory[ent->item->id]++; + + tag_owner = other; + tag_count = 0; + + Tag_KillItBonus(other); + + return true; +} + +//================= +//================= +THINK(Tag_Respawn) (edict_t *ent) -> void +{ + edict_t *spot; + + spot = SelectDeathmatchSpawnPoint(true, false, true).spot; + if (spot == nullptr) + { + ent->nextthink = level.time + 1_sec; + return; + } + + ent->s.origin = spot->s.origin; + gi.linkentity(ent); +} + +//================= +//================= +THINK(Tag_MakeTouchable) (edict_t *ent) -> void +{ + ent->touch = Touch_Item; + + tag_token->think = Tag_Respawn; + + // check here to see if it's in lava or slime. if so, do a respawn sooner + if (gi.pointcontents(ent->s.origin) & (CONTENTS_LAVA | CONTENTS_SLIME)) + tag_token->nextthink = level.time + 3_sec; + else + tag_token->nextthink = level.time + 30_sec; +} + +//================= +//================= +void Tag_DropToken(edict_t *ent, gitem_t *item) +{ + trace_t trace; + vec3_t forward, right; + vec3_t offset; + + // reset the score count for next player + tag_count = 0; + tag_owner = nullptr; + + tag_token = G_Spawn(); + + tag_token->classname = item->classname; + tag_token->item = item; + tag_token->spawnflags = SPAWNFLAG_ITEM_DROPPED; + tag_token->s.effects = EF_ROTATE | EF_TAGTRAIL; + tag_token->s.renderfx = RF_GLOW | RF_NO_LOD; + tag_token->mins = { -15, -15, -15 }; + tag_token->maxs = { 15, 15, 15 }; + gi.setmodel(tag_token, tag_token->item->world_model); + tag_token->solid = SOLID_TRIGGER; + tag_token->movetype = MOVETYPE_TOSS; + tag_token->touch = nullptr; + tag_token->owner = ent; + + AngleVectors(ent->client->v_angle, forward, right, nullptr); + offset = { 24, 0, -16 }; + tag_token->s.origin = G_ProjectSource(ent->s.origin, offset, forward, right); + trace = gi.trace(ent->s.origin, tag_token->mins, tag_token->maxs, + tag_token->s.origin, ent, CONTENTS_SOLID); + tag_token->s.origin = trace.endpos; + + tag_token->velocity = forward * 100; + tag_token->velocity[2] = 300; + + tag_token->think = Tag_MakeTouchable; + tag_token->nextthink = level.time + 1_sec; + + gi.linkentity(tag_token); + + // tag_token = Drop_Item (ent, item); + ent->client->pers.inventory[item->id]--; +} + +//================= +//================= +void Tag_PlayerEffects(edict_t *ent) +{ + if (ent == tag_owner) + ent->s.effects |= EF_TAGTRAIL; +} + +//================= +//================= +void Tag_DogTag(edict_t *ent, edict_t *killer, const char **pic) +{ + if (ent == tag_owner) + (*pic) = "tag3"; +} + +//================= +// Tag_ChangeDamage - damage done that does not involve the tag owner +// is at 75% original to encourage folks to go after the tag owner. +//================= +int Tag_ChangeDamage(edict_t *targ, edict_t *attacker, int damage, mod_t mod) +{ + if ((targ != tag_owner) && (attacker != tag_owner)) + return (damage * 3 / 4); + + return damage; +} + +//================= +//================= +void Tag_GameInit() +{ + tag_token = nullptr; + tag_owner = nullptr; + tag_count = 0; +} + +//================= +//================= +void Tag_PostInitSetup() +{ + edict_t *e; + vec3_t origin, angles; + + // automatic spawning of tag token if one is not present on map. + e = G_FindByString<&edict_t::classname>(nullptr, "dm_tag_token"); + if (e == nullptr) + { + e = G_Spawn(); + e->classname = "dm_tag_token"; + + bool lm = false; + SelectSpawnPoint(e, origin, angles, true, lm); + e->s.origin = origin; + e->s.old_origin = origin; + e->s.angles = angles; + SP_dm_tag_token(e); + } +} + +/*QUAKED dm_tag_token (.3 .3 1) (-16 -16 -16) (16 16 16) +The tag token for deathmatch tag games. +*/ +void SP_dm_tag_token(edict_t *self) +{ + if (!deathmatch->integer) + { + G_FreeEdict(self); + return; + } + + if (gamerules->integer != RDM_TAG) + { + G_FreeEdict(self); + return; + } + + // store the tag token edict pointer for later use. + tag_token = self; + tag_count = 0; + + self->classname = "dm_tag_token"; + self->model = "models/items/tagtoken/tris.md2"; + self->count = 1; + SpawnItem(self, GetItemByIndex(IT_ITEM_TAG_TOKEN)); +} diff --git a/rerelease/vcpkg.json b/rerelease/vcpkg.json new file mode 100644 index 0000000..757973d --- /dev/null +++ b/rerelease/vcpkg.json @@ -0,0 +1,9 @@ +{ + "$schema": "https://raw.githubusercontent.com/microsoft/vcpkg-tool/main/docs/vcpkg.schema.json", + "name": "q2-game-dll", + "version": "2022", + "dependencies": [ + "fmt", + "jsoncpp" + ] +} \ No newline at end of file diff --git a/rerelease/xatrix/g_xatrix_func.cpp b/rerelease/xatrix/g_xatrix_func.cpp new file mode 100644 index 0000000..5b97a10 --- /dev/null +++ b/rerelease/xatrix/g_xatrix_func.cpp @@ -0,0 +1,178 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. + +#include "../g_local.h" + +/*QUAKED rotating_light (0 .5 .8) (-8 -8 -8) (8 8 8) START_OFF ALARM +"health" if set, the light may be killed. +*/ + +// RAFAEL +// note to self +// the lights will take damage from explosions +// this could leave a player in total darkness very bad + +constexpr spawnflags_t SPAWNFLAG_ROTATING_LIGHT_START_OFF = 1_spawnflag; +constexpr spawnflags_t SPAWNFLAG_ROTATING_LIGHT_ALARM = 2_spawnflag; + +THINK(rotating_light_alarm) (edict_t *self) -> void +{ + if (self->spawnflags.has(SPAWNFLAG_ROTATING_LIGHT_START_OFF)) + { + self->think = nullptr; + self->nextthink = 0_ms; + } + else + { + gi.sound(self, CHAN_NO_PHS_ADD | CHAN_VOICE, self->moveinfo.sound_start, 1, ATTN_STATIC, 0); + self->nextthink = level.time + 1_sec; + } +} + +DIE(rotating_light_killed) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void +{ + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_WELDING_SPARKS); + gi.WriteByte(30); + gi.WritePosition(self->s.origin); + gi.WriteDir(vec3_origin); + gi.WriteByte(irandom(0xe0, 0xe8)); + gi.multicast(self->s.origin, MULTICAST_PVS, false); + + self->s.effects &= ~EF_SPINNINGLIGHTS; + self->use = nullptr; + + self->think = G_FreeEdict; + self->nextthink = level.time + FRAME_TIME_S; +} + +USE(rotating_light_use) (edict_t *self, edict_t *other, edict_t *activator) -> void +{ + if (self->spawnflags.has(SPAWNFLAG_ROTATING_LIGHT_START_OFF)) + { + self->spawnflags &= ~SPAWNFLAG_ROTATING_LIGHT_START_OFF; + self->s.effects |= EF_SPINNINGLIGHTS; + + if (self->spawnflags.has(SPAWNFLAG_ROTATING_LIGHT_ALARM)) + { + self->think = rotating_light_alarm; + self->nextthink = level.time + FRAME_TIME_S; + } + } + else + { + self->spawnflags |= SPAWNFLAG_ROTATING_LIGHT_START_OFF; + self->s.effects &= ~EF_SPINNINGLIGHTS; + } +} + +void SP_rotating_light(edict_t *self) +{ + self->movetype = MOVETYPE_STOP; + self->solid = SOLID_BBOX; + + self->s.modelindex = gi.modelindex("models/objects/light/tris.md2"); + + self->s.frame = 0; + + self->use = rotating_light_use; + + if (self->spawnflags.has(SPAWNFLAG_ROTATING_LIGHT_START_OFF)) + self->s.effects &= ~EF_SPINNINGLIGHTS; + else + { + self->s.effects |= EF_SPINNINGLIGHTS; + } + + if (!self->speed) + self->speed = 32; + // this is a real cheap way + // to set the radius of the light + // self->s.frame = self->speed; + + if (!self->health) + { + self->health = 10; + self->max_health = self->health; + self->die = rotating_light_killed; + self->takedamage = true; + } + else + { + self->max_health = self->health; + self->die = rotating_light_killed; + self->takedamage = true; + } + + if (self->spawnflags.has(SPAWNFLAG_ROTATING_LIGHT_ALARM)) + { + self->moveinfo.sound_start = gi.soundindex("misc/alarm.wav"); + } + + gi.linkentity(self); +} + +/*QUAKED func_object_repair (1 .5 0) (-8 -8 -8) (8 8 8) +object to be repaired. +The default delay is 1 second +"delay" the delay in seconds for spark to occur +*/ + +THINK(object_repair_fx) (edict_t *ent) -> void +{ + ent->nextthink = level.time + gtime_t::from_sec(ent->delay); + + if (ent->health <= 100) + ent->health++; + else + { + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_WELDING_SPARKS); + gi.WriteByte(10); + gi.WritePosition(ent->s.origin); + gi.WriteDir(vec3_origin); + gi.WriteByte(irandom(0xe0, 0xe8)); + gi.multicast(ent->s.origin, MULTICAST_PVS, false); + } +} + +THINK(object_repair_dead) (edict_t *ent) -> void +{ + G_UseTargets(ent, ent); + ent->nextthink = level.time + 10_hz; + ent->think = object_repair_fx; +} + +THINK(object_repair_sparks) (edict_t *ent) -> void +{ + if (ent->health <= 0) + { + ent->nextthink = level.time + 10_hz; + ent->think = object_repair_dead; + return; + } + + ent->nextthink = level.time + gtime_t::from_sec(ent->delay); + + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_WELDING_SPARKS); + gi.WriteByte(10); + gi.WritePosition(ent->s.origin); + gi.WriteDir(vec3_origin); + gi.WriteByte(irandom(0xe0, 0xe8)); + gi.multicast(ent->s.origin, MULTICAST_PVS, false); +} + +void SP_object_repair(edict_t *ent) +{ + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_BBOX; + ent->classname = "object_repair"; + ent->mins = { -8, -8, 8 }; + ent->maxs = { 8, 8, 8 }; + ent->think = object_repair_sparks; + ent->nextthink = level.time + 1_sec; + ent->health = 100; + if (!ent->delay) + ent->delay = 1.0; +} diff --git a/rerelease/xatrix/g_xatrix_items.cpp b/rerelease/xatrix/g_xatrix_items.cpp new file mode 100644 index 0000000..ed1d834 --- /dev/null +++ b/rerelease/xatrix/g_xatrix_items.cpp @@ -0,0 +1,31 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. + +#include "../g_local.h" + +// RAFAEL +void SP_item_foodcube(edict_t *self) +{ + if (deathmatch->integer && g_no_health->integer) + { + G_FreeEdict(self); + return; + } + + self->model = "models/objects/trapfx/tris.md2"; + SpawnItem(self, GetItemByIndex(IT_HEALTH_SMALL)); + self->spawnflags |= SPAWNFLAG_ITEM_DROPPED; + self->style = HEALTH_IGNORE_MAX; + self->classname = "item_foodcube"; + self->s.effects |= EF_GIB; + + // Paril: set pickup noise for foodcube based on amount + if (self->count < 10) + self->noise_index = gi.soundindex("items/s_health.wav"); + else if (self->count < 25) + self->noise_index = gi.soundindex("items/n_health.wav"); + else if (self->count < 50) + self->noise_index = gi.soundindex("items/l_health.wav"); + else + self->noise_index = gi.soundindex("items/m_health.wav"); +} \ No newline at end of file diff --git a/rerelease/xatrix/g_xatrix_misc.cpp b/rerelease/xatrix/g_xatrix_misc.cpp new file mode 100644 index 0000000..10277ce --- /dev/null +++ b/rerelease/xatrix/g_xatrix_misc.cpp @@ -0,0 +1,143 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. + +#include "../g_local.h" + +/*QUAKED misc_crashviper (1 .5 0) (-176 -120 -24) (176 120 72) +This is a large viper about to crash +*/ +void SP_misc_crashviper(edict_t *ent) +{ + if (!ent->target) + { + gi.Com_PrintFmt("{}: no target\n", *ent); + G_FreeEdict(ent); + return; + } + + if (!ent->speed) + ent->speed = 300; + + ent->movetype = MOVETYPE_PUSH; + ent->solid = SOLID_NOT; + ent->s.modelindex = gi.modelindex("models/ships/bigviper/tris.md2"); + ent->mins = { -16, -16, 0 }; + ent->maxs = { 16, 16, 32 }; + + ent->think = func_train_find; + ent->nextthink = level.time + 10_hz; + ent->use = misc_viper_use; + ent->svflags |= SVF_NOCLIENT; + ent->moveinfo.accel = ent->moveinfo.decel = ent->moveinfo.speed = ent->speed; + + gi.linkentity(ent); +} + +// RAFAEL +/*QUAKED misc_viper_missile (1 0 0) (-8 -8 -8) (8 8 8) +"dmg" how much boom should the bomb make? the default value is 250 +*/ + +USE(misc_viper_missile_use) (edict_t *self, edict_t *other, edict_t *activator) -> void +{ + vec3_t forward, right, up; + vec3_t start, dir; + vec3_t vec; + + AngleVectors(self->s.angles, forward, right, up); + + self->enemy = G_FindByString<&edict_t::targetname>(nullptr, self->target); + + vec = self->enemy->s.origin; + + start = self->s.origin; + dir = vec - start; + dir.normalize(); + + monster_fire_rocket(self, start, dir, self->dmg, 500, MZ2_CHICK_ROCKET_1); + + self->nextthink = level.time + 10_hz; + self->think = G_FreeEdict; +} + +void SP_misc_viper_missile(edict_t *self) +{ + self->movetype = MOVETYPE_NONE; + self->solid = SOLID_NOT; + self->mins = { -8, -8, -8 }; + self->maxs = { 8, 8, 8 }; + + if (!self->dmg) + self->dmg = 250; + + self->s.modelindex = gi.modelindex("models/objects/bomb/tris.md2"); + + self->use = misc_viper_missile_use; + self->svflags |= SVF_NOCLIENT; + + gi.linkentity(self); +} + +// RAFAEL 17-APR-98 +/*QUAKED misc_transport (1 0 0) (-8 -8 -8) (8 8 8) +Maxx's transport at end of game +*/ +void SP_misc_transport(edict_t *ent) +{ + if (!ent->target) + { + gi.Com_PrintFmt("{}: no target\n", *ent); + G_FreeEdict(ent); + return; + } + + if (!ent->speed) + ent->speed = 300; + + ent->movetype = MOVETYPE_PUSH; + ent->solid = SOLID_NOT; + ent->s.modelindex = gi.modelindex("models/objects/ship/tris.md2"); + + ent->mins = { -16, -16, 0 }; + ent->maxs = { 16, 16, 32 }; + + ent->think = func_train_find; + ent->nextthink = level.time + 10_hz; + ent->use = misc_strogg_ship_use; + ent->svflags |= SVF_NOCLIENT; + ent->moveinfo.accel = ent->moveinfo.decel = ent->moveinfo.speed = ent->speed; + + if (!(ent->spawnflags & SPAWNFLAG_TRAIN_START_ON)) + ent->spawnflags |= SPAWNFLAG_TRAIN_START_ON; + + gi.linkentity(ent); +} +// END 17-APR-98 + +/*QUAKED misc_amb4 (1 0 0) (-16 -16 -16) (16 16 16) +Mal's amb4 loop entity +*/ +static int amb4sound; + +THINK(amb4_think) (edict_t *ent) -> void +{ + ent->nextthink = level.time + 2.7_sec; + gi.sound(ent, CHAN_VOICE, amb4sound, 1, ATTN_NONE, 0); +} + +void SP_misc_amb4(edict_t *ent) +{ + ent->think = amb4_think; + ent->nextthink = level.time + 1_sec; + amb4sound = gi.soundindex("world/amb4.wav"); + gi.linkentity(ent); +} + +/*QUAKED misc_nuke (1 0 0) (-16 -16 -16) (16 16 16) + */ +extern void target_killplayers_use(edict_t *self, edict_t *other, edict_t *activator); + +void SP_misc_nuke(edict_t *ent) +{ + ent->use = target_killplayers_use; +} \ No newline at end of file diff --git a/rerelease/xatrix/g_xatrix_monster.cpp b/rerelease/xatrix/g_xatrix_monster.cpp new file mode 100644 index 0000000..2a4f653 --- /dev/null +++ b/rerelease/xatrix/g_xatrix_monster.cpp @@ -0,0 +1,142 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. + +#include "../g_local.h" + +// RAFAEL +void monster_fire_blueblaster(edict_t *self, const vec3_t &start, const vec3_t &dir, int damage, int speed, monster_muzzleflash_id_t flashtype, effects_t effect) +{ + fire_blueblaster(self, start, dir, damage, speed, effect); + monster_muzzleflash(self, start, flashtype); +} + +// RAFAEL +void monster_fire_ionripper(edict_t *self, const vec3_t &start, const vec3_t &dir, int damage, int speed, monster_muzzleflash_id_t flashtype, effects_t effect) +{ + fire_ionripper(self, start, dir, damage, speed, effect); + monster_muzzleflash(self, start, flashtype); +} + +// RAFAEL +void monster_fire_heat(edict_t *self, const vec3_t &start, const vec3_t &dir, int damage, int speed, monster_muzzleflash_id_t flashtype, float turn_fraction) +{ + fire_heat(self, start, dir, damage, speed, (float) damage, damage, turn_fraction); + monster_muzzleflash(self, start, flashtype); +} + +// RAFAEL +struct dabeam_pierce_t : pierce_args_t +{ + edict_t *self; + bool damage; + + inline dabeam_pierce_t(edict_t *self, bool damage) : + pierce_args_t(), + self(self), + damage(damage) + { + } + + // we hit an entity; return false to stop the piercing. + // you can adjust the mask for the re-trace (for water, etc). + virtual bool hit(contents_t &mask, vec3_t &end) override + { + if (damage) + { + // hurt it if we can + if (self->dmg > 0 && (tr.ent->takedamage) && !(tr.ent->flags & FL_IMMUNE_LASER) && (tr.ent != self->owner)) + T_Damage(tr.ent, self, self->owner, self->movedir, tr.endpos, vec3_origin, self->dmg, skill->integer, DAMAGE_ENERGY, MOD_TARGET_LASER); + + if (self->dmg < 0) // healer ray + { + // when player is at 100 health + // just undo health fix + // keeping fx + if (tr.ent->health < tr.ent->max_health) + tr.ent->health = min(tr.ent->max_health, tr.ent->health - self->dmg); + } + } + + // if we hit something that's not a monster or player or is immune to lasers, we're done + if (!(tr.ent->svflags & SVF_MONSTER) && (!tr.ent->client)) + { + if (damage) + { + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_LASER_SPARKS); + gi.WriteByte(10); + gi.WritePosition(tr.endpos); + gi.WriteDir(tr.plane.normal); + gi.WriteByte(self->s.skinnum); + gi.multicast(tr.endpos, MULTICAST_PVS, false); + } + + return false; + } + + if (!mark(tr.ent)) + return false; + + return true; + } +}; + +void dabeam_update(edict_t *self, bool damage) +{ + vec3_t start = self->s.origin; + vec3_t end = start + (self->movedir * 2048); + + dabeam_pierce_t args { + self, + damage + }; + + pierce_trace(start, end, self, args, CONTENTS_SOLID | CONTENTS_MONSTER | CONTENTS_PLAYER | CONTENTS_DEADMONSTER); + + self->s.old_origin = args.tr.endpos + (args.tr.plane.normal * 1.f); + gi.linkentity(self); +} + +constexpr spawnflags_t SPAWNFLAG_DABEAM_SECONDARY = 1_spawnflag; + +THINK(beam_think) (edict_t *self) -> void +{ + if (self->spawnflags.has(SPAWNFLAG_DABEAM_SECONDARY)) + self->owner->beam2 = nullptr; + else + self->owner->beam = nullptr; + G_FreeEdict(self); +} + +// RAFAEL +void monster_fire_dabeam(edict_t *self, int damage, bool secondary, void(*update_func)(edict_t *self)) +{ + edict_t *&beam_ptr = secondary ? self->beam2 : self->beam; + + if (!beam_ptr) + { + beam_ptr = G_Spawn(); + + beam_ptr->movetype = MOVETYPE_NONE; + beam_ptr->solid = SOLID_NOT; + beam_ptr->s.renderfx |= RF_BEAM; + beam_ptr->s.modelindex = MODELINDEX_WORLD; + beam_ptr->owner = self; + beam_ptr->dmg = damage; + beam_ptr->s.frame = 2; + beam_ptr->spawnflags = secondary ? SPAWNFLAG_DABEAM_SECONDARY : SPAWNFLAG_NONE; + + if (self->monsterinfo.aiflags & AI_MEDIC) + beam_ptr->s.skinnum = 0xf3f3f1f1; + else + beam_ptr->s.skinnum = 0xf2f2f0f0; + + beam_ptr->think = beam_think; + beam_ptr->s.sound = gi.soundindex("misc/lasfly.wav"); + beam_ptr->postthink = update_func; + } + + beam_ptr->nextthink = level.time + 200_ms; + update_func(beam_ptr); + dabeam_update(beam_ptr, true); +} \ No newline at end of file diff --git a/rerelease/xatrix/g_xatrix_target.cpp b/rerelease/xatrix/g_xatrix_target.cpp new file mode 100644 index 0000000..4961489 --- /dev/null +++ b/rerelease/xatrix/g_xatrix_target.cpp @@ -0,0 +1,99 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. + +#include "../g_local.h" + +/*QUAKED target_mal_laser (1 0 0) (-4 -4 -4) (4 4 4) START_ON RED GREEN BLUE YELLOW ORANGE FAT +Mal's laser +*/ +void target_mal_laser_on(edict_t *self) +{ + if (!self->activator) + self->activator = self; + self->spawnflags |= SPAWNFLAG_LASER_ZAP | SPAWNFLAG_LASER_ON; + self->svflags &= ~SVF_NOCLIENT; + self->flags |= FL_TRAP; + // target_laser_think (self); + self->nextthink = level.time + gtime_t::from_sec(self->wait + self->delay); +} + +USE(target_mal_laser_use) (edict_t *self, edict_t *other, edict_t *activator) -> void +{ + self->activator = activator; + if (self->spawnflags.has(SPAWNFLAG_LASER_ON)) + target_laser_off(self); + else + target_mal_laser_on(self); +} + +void mal_laser_think(edict_t *self); + +THINK(mal_laser_think2) (edict_t *self) -> void +{ + self->svflags |= SVF_NOCLIENT; + self->think = mal_laser_think; + self->nextthink = level.time + gtime_t::from_sec(self->wait); + self->spawnflags |= SPAWNFLAG_LASER_ZAP; +} + +THINK(mal_laser_think) (edict_t *self) -> void +{ + self->svflags &= ~SVF_NOCLIENT; + target_laser_think(self); + self->think = mal_laser_think2; + self->nextthink = level.time + 100_ms; +} + +void SP_target_mal_laser(edict_t *self) +{ + self->movetype = MOVETYPE_NONE; + self->solid = SOLID_NOT; + self->s.renderfx |= RF_BEAM; + self->s.modelindex = MODELINDEX_WORLD; // must be non-zero + self->flags |= FL_TRAP_LASER_FIELD; + + // set the beam diameter + if (self->spawnflags.has(SPAWNFLAG_LASER_FAT)) + self->s.frame = 16; + else + self->s.frame = 4; + + // set the color + if (self->spawnflags.has(SPAWNFLAG_LASER_RED)) + self->s.skinnum = 0xf2f2f0f0; + else if (self->spawnflags.has(SPAWNFLAG_LASER_GREEN)) + self->s.skinnum = 0xd0d1d2d3; + else if (self->spawnflags.has(SPAWNFLAG_LASER_BLUE)) + self->s.skinnum = 0xf3f3f1f1; + else if (self->spawnflags.has(SPAWNFLAG_LASER_YELLOW)) + self->s.skinnum = 0xdcdddedf; + else if (self->spawnflags.has(SPAWNFLAG_LASER_ORANGE)) + self->s.skinnum = 0xe0e1e2e3; + + G_SetMovedir(self->s.angles, self->movedir); + + if (!self->delay) + self->delay = 0.1f; + + if (!self->wait) + self->wait = 0.1f; + + if (!self->dmg) + self->dmg = 5; + + self->mins = { -8, -8, -8 }; + self->maxs = { 8, 8, 8 }; + + self->nextthink = level.time + gtime_t::from_sec(self->delay); + self->think = mal_laser_think; + + self->use = target_mal_laser_use; + + gi.linkentity(self); + + if (self->spawnflags.has(SPAWNFLAG_LASER_ON)) + target_mal_laser_on(self); + else + target_laser_off(self); +} +// END 15-APR-98 diff --git a/rerelease/xatrix/g_xatrix_weapon.cpp b/rerelease/xatrix/g_xatrix_weapon.cpp new file mode 100644 index 0000000..73358a5 --- /dev/null +++ b/rerelease/xatrix/g_xatrix_weapon.cpp @@ -0,0 +1,605 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. + +#include "../g_local.h" + +// RAFAEL +void fire_blueblaster(edict_t *self, const vec3_t &start, const vec3_t &dir, int damage, int speed, effects_t effect) +{ + edict_t *bolt; + trace_t tr; + + bolt = G_Spawn(); + bolt->s.origin = start; + bolt->s.old_origin = start; + bolt->s.angles = vectoangles(dir); + bolt->velocity = dir * speed; + bolt->svflags |= SVF_PROJECTILE; + bolt->movetype = MOVETYPE_FLYMISSILE; + bolt->flags |= FL_DODGE; + bolt->clipmask = MASK_PROJECTILE; + bolt->solid = SOLID_BBOX; + bolt->s.effects |= effect; + bolt->s.modelindex = gi.modelindex("models/objects/laser/tris.md2"); + bolt->s.skinnum = 1; + bolt->s.sound = gi.soundindex("misc/lasfly.wav"); + bolt->owner = self; + bolt->touch = blaster_touch; + bolt->nextthink = level.time + 2_sec; + bolt->think = G_FreeEdict; + bolt->dmg = damage; + bolt->classname = "bolt"; + bolt->style = MOD_BLUEBLASTER; + gi.linkentity(bolt); + + tr = gi.traceline(self->s.origin, bolt->s.origin, bolt, bolt->clipmask); + + if (tr.fraction < 1.0f) + { + bolt->s.origin = tr.endpos + (tr.plane.normal * 1.f); + bolt->touch(bolt, tr.ent, tr, false); + } +} + +// RAFAEL + +/* +fire_ionripper +*/ + +THINK(ionripper_sparks) (edict_t *self) -> void +{ + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_WELDING_SPARKS); + gi.WriteByte(0); + gi.WritePosition(self->s.origin); + gi.WriteDir(vec3_origin); + gi.WriteByte(irandom(0xe4, 0xe8)); + gi.multicast(self->s.origin, MULTICAST_PVS, false); + + G_FreeEdict(self); +} + +// RAFAEL +TOUCH(ionripper_touch) (edict_t *self, edict_t *other, const trace_t &tr, bool other_touching_self) -> void +{ + if (other == self->owner) + return; + + if (tr.surface && (tr.surface->flags & SURF_SKY)) + { + G_FreeEdict(self); + return; + } + + if (self->owner->client) + PlayerNoise(self->owner, self->s.origin, PNOISE_IMPACT); + + if (other->takedamage) + { + T_Damage(other, self, self->owner, self->velocity, self->s.origin, tr.plane.normal, self->dmg, 1, DAMAGE_ENERGY, MOD_RIPPER); + } + else + { + return; + } + + G_FreeEdict(self); +} + +// RAFAEL +void fire_ionripper(edict_t *self, const vec3_t &start, const vec3_t &dir, int damage, int speed, effects_t effect) +{ + edict_t *ion; + trace_t tr; + + ion = G_Spawn(); + ion->s.origin = start; + ion->s.old_origin = start; + ion->s.angles = vectoangles(dir); + ion->velocity = dir * speed; + ion->movetype = MOVETYPE_WALLBOUNCE; + ion->clipmask = MASK_PROJECTILE; + + // [Paril-KEX] + if (self->client && !G_ShouldPlayersCollide(true)) + ion->clipmask &= ~CONTENTS_PLAYER; + + ion->solid = SOLID_BBOX; + ion->s.effects |= effect; + ion->svflags |= SVF_PROJECTILE; + ion->flags |= FL_DODGE; + ion->s.renderfx |= RF_FULLBRIGHT; + ion->s.modelindex = gi.modelindex("models/objects/boomrang/tris.md2"); + ion->s.sound = gi.soundindex("misc/lasfly.wav"); + ion->owner = self; + ion->touch = ionripper_touch; + ion->nextthink = level.time + 3_sec; + ion->think = ionripper_sparks; + ion->dmg = damage; + ion->dmg_radius = 100; + gi.linkentity(ion); + + tr = gi.traceline(self->s.origin, ion->s.origin, ion, ion->clipmask); + if (tr.fraction < 1.0f) + { + ion->s.origin = tr.endpos + (tr.plane.normal * 1.f); + ion->touch(ion, tr.ent, tr, false); + } +} + +// RAFAEL +/* +fire_heat +*/ + +THINK(heat_think) (edict_t *self) -> void +{ + edict_t *target = nullptr; + edict_t *acquire = nullptr; + vec3_t vec; + vec3_t oldang; + float len; + float oldlen = 0; + float dot, olddot = 1; + + vec3_t fwd = AngleVectors(self->s.angles).forward; + + // acquire new target + while ((target = findradius(target, self->s.origin, 1024)) != nullptr) + { + if (self->owner == target) + continue; + if (!target->client) + continue; + if (target->health <= 0) + continue; + if (!visible(self, target)) + continue; + //if (!infront(self, target)) + // continue; + + vec = self->s.origin - target->s.origin; + len = vec.length(); + + dot = vec.normalized().dot(fwd); + + // targets that require us to turn less are preferred + if (dot >= olddot) + continue; + + if (acquire == nullptr || dot < olddot || len < oldlen) + { + acquire = target; + oldlen = len; + olddot = dot; + } + } + + if (acquire != nullptr) + { + oldang = self->s.angles; + vec = (acquire->s.origin - self->s.origin).normalized(); + float t = self->accel; + + float d = self->movedir.dot(vec); + + if (d < 0.45f && d > -0.45f) + vec = -vec; + + self->movedir = slerp(self->movedir, vec, t).normalized(); + self->s.angles = vectoangles(self->movedir); + + if (!self->enemy) + { + gi.sound(self, CHAN_WEAPON, gi.soundindex("weapons/railgr1a.wav"), 1.f, 0.25f, 0); + self->enemy = acquire; + } + } + else + self->enemy = nullptr; + + self->velocity = self->movedir * self->speed; + self->nextthink = level.time + FRAME_TIME_MS; +} + +// RAFAEL +void fire_heat(edict_t *self, const vec3_t &start, const vec3_t &dir, int damage, int speed, float damage_radius, int radius_damage, float turn_fraction) +{ + edict_t *heat; + + heat = G_Spawn(); + heat->s.origin = start; + heat->movedir = dir; + heat->s.angles = vectoangles(dir); + heat->velocity = dir * speed; + heat->flags |= FL_DODGE; + heat->movetype = MOVETYPE_FLYMISSILE; + heat->svflags |= SVF_PROJECTILE; + heat->clipmask = MASK_PROJECTILE; + heat->solid = SOLID_BBOX; + heat->s.effects |= EF_ROCKET; + heat->s.modelindex = gi.modelindex("models/objects/rocket/tris.md2"); + heat->owner = self; + heat->touch = rocket_touch; + heat->speed = speed; + heat->accel = turn_fraction; + + heat->nextthink = level.time + FRAME_TIME_MS; + heat->think = heat_think; + + heat->dmg = damage; + heat->radius_dmg = radius_damage; + heat->dmg_radius = damage_radius; + heat->s.sound = gi.soundindex("weapons/rockfly.wav"); + + gi.linkentity(heat); +} + +// RAFAEL + +/* +fire_plasma +*/ + +TOUCH(plasma_touch) (edict_t *ent, edict_t *other, const trace_t &tr, bool other_touching_self) -> void +{ + vec3_t origin; + + if (other == ent->owner) + return; + + if (tr.surface && (tr.surface->flags & SURF_SKY)) + { + G_FreeEdict(ent); + return; + } + + if (ent->owner->client) + PlayerNoise(ent->owner, ent->s.origin, PNOISE_IMPACT); + + // calculate position for the explosion entity + origin = ent->s.origin + (ent->velocity * -0.02f); + + if (other->takedamage) + { + T_Damage(other, ent, ent->owner, ent->velocity, ent->s.origin, tr.plane.normal, ent->dmg, 0, DAMAGE_ENERGY, MOD_PHALANX); + } + + T_RadiusDamage(ent, ent->owner, (float) ent->radius_dmg, other, ent->dmg_radius, DAMAGE_ENERGY, MOD_PHALANX); + + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_PLASMA_EXPLOSION); + gi.WritePosition(origin); + gi.multicast(ent->s.origin, MULTICAST_PHS, false); + + G_FreeEdict(ent); +} + +// RAFAEL +void fire_plasma(edict_t *self, const vec3_t &start, const vec3_t &dir, int damage, int speed, float damage_radius, int radius_damage) +{ + edict_t *plasma; + + plasma = G_Spawn(); + plasma->s.origin = start; + plasma->movedir = dir; + plasma->s.angles = vectoangles(dir); + plasma->velocity = dir * speed; + plasma->movetype = MOVETYPE_FLYMISSILE; + plasma->clipmask = MASK_PROJECTILE; + + // [Paril-KEX] + if (self->client && !G_ShouldPlayersCollide(true)) + plasma->clipmask &= ~CONTENTS_PLAYER; + + plasma->solid = SOLID_BBOX; + plasma->svflags |= SVF_PROJECTILE; + plasma->flags |= FL_DODGE; + plasma->owner = self; + plasma->touch = plasma_touch; + plasma->nextthink = level.time + gtime_t::from_sec(8000.f / speed); + plasma->think = G_FreeEdict; + plasma->dmg = damage; + plasma->radius_dmg = radius_damage; + plasma->dmg_radius = damage_radius; + plasma->s.sound = gi.soundindex("weapons/rockfly.wav"); + + plasma->s.modelindex = gi.modelindex("sprites/s_photon.sp2"); + plasma->s.effects |= EF_PLASMA | EF_ANIM_ALLFAST; + + gi.linkentity(plasma); +} + +THINK(Trap_Gib_Think) (edict_t *ent) -> void +{ + if (ent->owner->s.frame != 5) + { + G_FreeEdict(ent); + return; + } + + vec3_t forward, right, up; + vec3_t vec; + + AngleVectors(ent->owner->s.angles, forward, right, up); + + // rotate us around the center + float degrees = (150.f * gi.frame_time_s) + ent->owner->delay; + vec3_t diff = ent->owner->s.origin - ent->s.origin; + vec = RotatePointAroundVector(up, diff, degrees); + ent->s.angles[1] += degrees; + vec3_t new_origin = ent->owner->s.origin - vec; + + trace_t tr = gi.traceline(ent->s.origin, new_origin, ent, MASK_SOLID); + ent->s.origin = tr.endpos; + + // pull us towards the trap's center + diff.normalize(); + ent->s.origin += diff * (15.0f * gi.frame_time_s); + + ent->watertype = gi.pointcontents(ent->s.origin); + if (ent->watertype & MASK_WATER) + ent->waterlevel = WATER_FEET; + + ent->nextthink = level.time + FRAME_TIME_S; + gi.linkentity(ent); +} + +DIE(trap_die) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void +{ + BecomeExplosion1(self); +} + +// RAFAEL +void SP_item_foodcube(edict_t *best); +void SpawnDamage(int type, const vec3_t &origin, const vec3_t &normal, int damage); +// RAFAEL +THINK(Trap_Think) (edict_t *ent) -> void +{ + edict_t *target = nullptr; + edict_t *best = nullptr; + vec3_t vec; + float len; + float oldlen = 8000; + + if (ent->timestamp < level.time) + { + BecomeExplosion1(ent); + // note to self + // cause explosion damage??? + return; + } + + ent->nextthink = level.time + 10_hz; + + if (!ent->groundentity) + return; + + // ok lets do the blood effect + if (ent->s.frame > 4) + { + if (ent->s.frame == 5) + { + bool spawn = ent->wait == 64; + + ent->wait -= 2; + + if (spawn) + gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/trapdown.wav"), 1, ATTN_IDLE, 0); + + ent->delay += 2.f; + + if (ent->wait < 19) + ent->s.frame++; + + return; + } + ent->s.frame++; + if (ent->s.frame == 8) + { + ent->nextthink = level.time + 1_sec; + ent->think = G_FreeEdict; + ent->s.effects &= ~EF_TRAP; + + best = G_Spawn(); + best->count = ent->mass; + best->s.scale = 1.f + ((ent->accel - 100.f) / 300.f) * 1.0f; + SP_item_foodcube(best); + best->s.origin = ent->s.origin; + best->s.origin[2] += 24 * best->s.scale; + best->s.angles[YAW] = frandom() * 360; + best->velocity[2] = 400; + best->think(best); + best->nextthink = 0_ms; + best->s.old_origin = best->s.origin; + gi.linkentity(best); + + gi.sound(best, CHAN_AUTO, gi.soundindex("misc/fhit3.wav"), 1.f, ATTN_NORM, 0.f); + + return; + } + return; + } + + ent->s.effects &= ~EF_TRAP; + if (ent->s.frame >= 4) + { + ent->s.effects |= EF_TRAP; + // clear the owner if in deathmatch + if (deathmatch->integer) + ent->owner = nullptr; + } + + if (ent->s.frame < 4) + { + ent->s.frame++; + return; + } + + while ((target = findradius(target, ent->s.origin, 256)) != nullptr) + { + if (target == ent) + continue; + + // [Paril-KEX] don't allow traps to be placed near flags or teleporters + // if it's a monster or player with health > 0 + // or it's a player start point + // and we can see it + // blow up + if (target->classname && ((deathmatch->integer && + ((!strncmp(target->classname, "info_player_", 12)) || + (!strcmp(target->classname, "misc_teleporter_dest")) || + (!strncmp(target->classname, "item_flag_", 10))))) && + (visible(target, ent))) + { + BecomeExplosion1(ent); + return; + } + + if (!(target->svflags & SVF_MONSTER) && !target->client) + continue; + if (target != ent->teammaster && CheckTeamDamage(target, ent->teammaster)) + continue; + // [Paril-KEX] + if (!deathmatch->integer && target->client) + continue; + if (target->health <= 0) + continue; + if (!visible(ent, target)) + continue; + vec = ent->s.origin - target->s.origin; + len = vec.length(); + if (!best) + { + best = target; + oldlen = len; + continue; + } + if (len < oldlen) + { + oldlen = len; + best = target; + } + } + + // pull the enemy in + if (best) + { + if (best->groundentity) + { + best->s.origin[2] += 1; + best->groundentity = nullptr; + } + vec = ent->s.origin - best->s.origin; + len = vec.normalize(); + + float max_speed = best->client ? 290.f : 150.f; + + best->velocity += (vec * clamp(max_speed - len, 64.f, max_speed)); + + ent->s.sound = gi.soundindex("weapons/trapsuck.wav"); + + if (len < 48) + { + if (best->mass < 400) + { + ent->takedamage = false; + ent->solid = SOLID_NOT; + ent->die = nullptr; + + T_Damage(best, ent, ent->teammaster, vec3_origin, best->s.origin, vec3_origin, 100000, 1, DAMAGE_NONE, MOD_TRAP); + + if (best->svflags & SVF_MONSTER) + M_ProcessPain(best); + + ent->enemy = best; + ent->wait = 64; + ent->s.old_origin = ent->s.origin; + ent->timestamp = level.time + 30_sec; + ent->accel = best->mass; + if (deathmatch->integer) + ent->mass = best->mass / 4; + else + ent->mass = best->mass / 10; + // ok spawn the food cube + ent->s.frame = 5; + + // link up any gibs that this monster may have spawned + for (uint32_t i = 0; i < globals.num_edicts; i++) + { + edict_t *e = &g_edicts[i]; + + if (!e->inuse) + continue; + else if (strcmp(e->classname, "gib")) + continue; + else if ((e->s.origin - ent->s.origin).length() > 128.f) + continue; + + e->movetype = MOVETYPE_NONE; + e->nextthink = level.time + FRAME_TIME_S; + e->think = Trap_Gib_Think; + e->owner = ent; + Trap_Gib_Think(e); + } + } + else + { + BecomeExplosion1(ent); + // note to self + // cause explosion damage??? + return; + } + } + } +} + +// RAFAEL +void fire_trap(edict_t *self, const vec3_t &start, const vec3_t &aimdir, int speed) +{ + edict_t *trap; + vec3_t dir; + vec3_t forward, right, up; + + dir = vectoangles(aimdir); + AngleVectors(dir, forward, right, up); + + trap = G_Spawn(); + trap->s.origin = start; + trap->velocity = aimdir * speed; + + float gravityAdjustment = level.gravity / 800.f; + + trap->velocity += up * (200 + crandom() * 10.0f) * gravityAdjustment; + trap->velocity += right * (crandom() * 10.0f); + + trap->avelocity = { 0, 300, 0 }; + trap->movetype = MOVETYPE_BOUNCE; + + trap->solid = SOLID_BBOX; + trap->takedamage = true; + trap->mins = { -4, -4, 0 }; + trap->maxs = { 4, 4, 8 }; + trap->die = trap_die; + trap->health = 20; + trap->s.modelindex = gi.modelindex("models/weapons/z_trap/tris.md2"); + trap->owner = trap->teammaster = self; + trap->nextthink = level.time + 1_sec; + trap->think = Trap_Think; + trap->classname = "food_cube_trap"; + // RAFAEL 16-APR-98 + trap->s.sound = gi.soundindex("weapons/traploop.wav"); + // END 16-APR-98 + + trap->flags |= ( FL_DAMAGEABLE | FL_MECHANICAL | FL_TRAP ); + trap->clipmask = MASK_PROJECTILE & ~CONTENTS_DEADMONSTER; + + // [Paril-KEX] + if (self->client && !G_ShouldPlayersCollide(true)) + trap->clipmask &= ~CONTENTS_PLAYER; + + gi.linkentity(trap); + + trap->timestamp = level.time + 30_sec; +} diff --git a/rerelease/xatrix/m_xatrix_fixbot.cpp b/rerelease/xatrix/m_xatrix_fixbot.cpp new file mode 100644 index 0000000..c446a53 --- /dev/null +++ b/rerelease/xatrix/m_xatrix_fixbot.cpp @@ -0,0 +1,1401 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +/* + fixbot.c +*/ + +#include "../g_local.h" +#include "m_xatrix_fixbot.h" +#include "../m_flash.h" + +bool infront(edict_t *self, edict_t *other); +bool FindTarget(edict_t *self); + +static int sound_pain1; +static int sound_die; +static int sound_weld1; +static int sound_weld2; +static int sound_weld3; + +void fixbot_run(edict_t *self); +void fixbot_attack(edict_t *self); +void fixbot_dead(edict_t *self); +void fixbot_fire_blaster(edict_t *self); +void fixbot_fire_welder(edict_t *self); + +void use_scanner(edict_t *self); +void change_to_roam(edict_t *self); +void fly_vertical(edict_t *self); + +void fixbot_stand(edict_t *self); + +extern const mmove_t fixbot_move_forward; +extern const mmove_t fixbot_move_stand; +extern const mmove_t fixbot_move_stand2; +extern const mmove_t fixbot_move_roamgoal; + +extern const mmove_t fixbot_move_weld_start; +extern const mmove_t fixbot_move_weld; +extern const mmove_t fixbot_move_weld_end; +extern const mmove_t fixbot_move_takeoff; +extern const mmove_t fixbot_move_landing; +extern const mmove_t fixbot_move_turn; + +void roam_goal(edict_t *self); + +// [Paril-KEX] clean up bot goals if we get interrupted +THINK(bot_goal_check) (edict_t *self) -> void +{ + if (!self->owner || !self->owner->inuse || self->owner->goalentity != self) + { + G_FreeEdict(self); + return; + } + + self->nextthink = level.time + 1_ms; +} + +void ED_CallSpawn(edict_t *ent); + +edict_t *fixbot_FindDeadMonster(edict_t *self) +{ + edict_t *ent = nullptr; + edict_t *best = nullptr; + + while ((ent = findradius(ent, self->s.origin, 1024)) != nullptr) + { + if (ent == self) + continue; + if (!(ent->svflags & SVF_MONSTER)) + continue; + if (ent->monsterinfo.aiflags & AI_GOOD_GUY) + continue; + // check to make sure we haven't bailed on this guy already + if ((ent->monsterinfo.badMedic1 == self) || (ent->monsterinfo.badMedic2 == self)) + continue; + if (ent->monsterinfo.healer) + // FIXME - this is correcting a bug that is somewhere else + // if the healer is a monster, and it's in medic mode .. continue .. otherwise + // we will override the healer, if it passes all the other tests + if ((ent->monsterinfo.healer->inuse) && (ent->monsterinfo.healer->health > 0) && + (ent->monsterinfo.healer->svflags & SVF_MONSTER) && (ent->monsterinfo.healer->monsterinfo.aiflags & AI_MEDIC)) + continue; + if (ent->health > 0) + continue; + if ((ent->nextthink) && (ent->think != monster_dead_think)) + continue; + if (!visible(self, ent)) + continue; + if (!best) + { + best = ent; + continue; + } + if (ent->max_health <= best->max_health) + continue; + best = ent; + } + + return best; +} + +static void fixbot_set_fly_parameters(edict_t *self, bool heal, bool weld) +{ + self->monsterinfo.fly_position_time = 0_sec; + self->monsterinfo.fly_acceleration = 5.f; + self->monsterinfo.fly_speed = 110.f; + self->monsterinfo.fly_buzzard = false; + + if (heal) + { + self->monsterinfo.fly_min_distance = 100.f; + self->monsterinfo.fly_max_distance = 100.f; + self->monsterinfo.fly_thrusters = true; + } + else if (weld) + { + self->monsterinfo.fly_min_distance = 24.f; + self->monsterinfo.fly_max_distance = 24.f; + } + else + { + // timid bot + self->monsterinfo.fly_min_distance = 300.f; + self->monsterinfo.fly_max_distance = 500.f; + } +} + +int fixbot_search(edict_t *self) +{ + edict_t *ent; + + if (!self->enemy) + { + ent = fixbot_FindDeadMonster(self); + if (ent) + { + self->oldenemy = self->enemy; + self->enemy = ent; + self->enemy->monsterinfo.healer = self; + self->monsterinfo.aiflags |= AI_MEDIC; + FoundTarget(self); + fixbot_set_fly_parameters(self, true, false); + return (1); + } + } + return (0); +} + +void landing_goal(edict_t *self) +{ + trace_t tr; + vec3_t forward, right, up; + vec3_t end; + edict_t *ent; + + ent = G_Spawn(); + ent->classname = "bot_goal"; + ent->solid = SOLID_BBOX; + ent->owner = self; + ent->think = bot_goal_check; + gi.linkentity(ent); + + ent->mins = { -32, -32, -24 }; + ent->maxs = { 32, 32, 24 }; + + AngleVectors(self->s.angles, forward, right, up); + end = self->s.origin + (forward * 32); + end = self->s.origin + (up * -8096); + + tr = gi.trace(self->s.origin, ent->mins, ent->maxs, end, self, MASK_MONSTERSOLID); + + ent->s.origin = tr.endpos; + + self->goalentity = self->enemy = ent; + M_SetAnimation(self, &fixbot_move_landing); +} + +void takeoff_goal(edict_t *self) +{ + trace_t tr; + vec3_t forward, right, up; + vec3_t end; + edict_t *ent; + + ent = G_Spawn(); + ent->classname = "bot_goal"; + ent->solid = SOLID_BBOX; + ent->owner = self; + ent->think = bot_goal_check; + gi.linkentity(ent); + + ent->mins = { -32, -32, -24 }; + ent->maxs = { 32, 32, 24 }; + + AngleVectors(self->s.angles, forward, right, up); + end = self->s.origin + (forward * 32); + end = self->s.origin + (up * 128); + + tr = gi.trace(self->s.origin, ent->mins, ent->maxs, end, self, MASK_MONSTERSOLID); + + ent->s.origin = tr.endpos; + + self->goalentity = self->enemy = ent; + M_SetAnimation(self, &fixbot_move_takeoff); +} + +void change_to_roam(edict_t *self) +{ + + if (fixbot_search(self)) + return; + + M_SetAnimation(self, &fixbot_move_roamgoal); + + if (self->spawnflags.has(SPAWNFLAG_FIXBOT_LANDING)) + { + landing_goal(self); + M_SetAnimation(self, &fixbot_move_landing); + self->spawnflags &= ~SPAWNFLAG_FIXBOT_LANDING; + self->spawnflags = SPAWNFLAG_FIXBOT_WORKING; + } + if (self->spawnflags.has(SPAWNFLAG_FIXBOT_TAKEOFF)) + { + takeoff_goal(self); + M_SetAnimation(self, &fixbot_move_takeoff); + self->spawnflags &= ~SPAWNFLAG_FIXBOT_TAKEOFF; + self->spawnflags = SPAWNFLAG_FIXBOT_WORKING; + } + if (self->spawnflags.has(SPAWNFLAG_FIXBOT_FIXIT)) + { + M_SetAnimation(self, &fixbot_move_roamgoal); + self->spawnflags &= ~SPAWNFLAG_FIXBOT_FIXIT; + self->spawnflags = SPAWNFLAG_FIXBOT_WORKING; + } + if (!self->spawnflags) + { + M_SetAnimation(self, &fixbot_move_stand2); + } +} + +void roam_goal(edict_t *self) +{ + + trace_t tr; + vec3_t forward, right, up; + vec3_t end; + edict_t *ent; + vec3_t dang; + float len, oldlen; + int i; + vec3_t vec; + vec3_t whichvec {}; + + ent = G_Spawn(); + ent->classname = "bot_goal"; + ent->solid = SOLID_BBOX; + ent->owner = self; + ent->think = bot_goal_check; + ent->nextthink = level.time + 1_ms; + gi.linkentity(ent); + + oldlen = 0; + + for (i = 0; i < 12; i++) + { + + dang = self->s.angles; + + if (i < 6) + dang[YAW] += 30 * i; + else + dang[YAW] -= 30 * (i - 6); + + AngleVectors(dang, forward, right, up); + end = self->s.origin + (forward * 8192); + + tr = gi.traceline(self->s.origin, end, self, MASK_PROJECTILE); + + vec = self->s.origin - tr.endpos; + len = vec.normalize(); + + if (len > oldlen) + { + oldlen = len; + whichvec = tr.endpos; + } + } + + ent->s.origin = whichvec; + self->goalentity = self->enemy = ent; + + M_SetAnimation(self, &fixbot_move_turn); +} + +void use_scanner(edict_t *self) +{ + edict_t *ent = nullptr; + + float radius = 1024; + vec3_t vec; + + float len; + + while ((ent = findradius(ent, self->s.origin, radius)) != nullptr) + { + if (ent->health >= 100) + { + if (strcmp(ent->classname, "object_repair") == 0) + { + if (visible(self, ent)) + { + // remove the old one + if (strcmp(self->goalentity->classname, "bot_goal") == 0) + { + self->goalentity->nextthink = level.time + 100_ms; + self->goalentity->think = G_FreeEdict; + } + + self->goalentity = self->enemy = ent; + + vec = self->s.origin - self->goalentity->s.origin; + len = vec.normalize(); + + fixbot_set_fly_parameters(self, false, true); + + if (len < 32) + { + M_SetAnimation(self, &fixbot_move_weld_start); + return; + } + return; + } + } + } + } + + if (!self->goalentity) + { + M_SetAnimation(self, &fixbot_move_stand); + return; + } + + vec = self->s.origin - self->goalentity->s.origin; + len = vec.length(); + + if (len < 32) + { + if (strcmp(self->goalentity->classname, "object_repair") == 0) + { + M_SetAnimation(self, &fixbot_move_weld_start); + } + else + { + self->goalentity->nextthink = level.time + 100_ms; + self->goalentity->think = G_FreeEdict; + self->goalentity = self->enemy = nullptr; + M_SetAnimation(self, &fixbot_move_stand); + } + return; + } + + vec = self->s.origin - self->s.old_origin; + len = vec.length(); + + /* + bot is stuck get new goalentity + */ + if (len == 0) + { + if (strcmp(self->goalentity->classname, "object_repair") == 0) + { + M_SetAnimation(self, &fixbot_move_stand); + } + else + { + self->goalentity->nextthink = level.time + 100_ms; + self->goalentity->think = G_FreeEdict; + self->goalentity = self->enemy = nullptr; + M_SetAnimation(self, &fixbot_move_stand); + } + } +} + +/* + when the bot has found a landing pad + it will proceed to its goalentity + just above the landing pad and + decend translated along the z the current + frames are at 10fps +*/ +void blastoff(edict_t *self, const vec3_t &start, const vec3_t &aimdir, int damage, int kick, int te_impact, int hspread, int vspread) +{ + trace_t tr; + vec3_t dir; + vec3_t forward, right, up; + vec3_t end; + float r; + float u; + vec3_t water_start; + bool water = false; + contents_t content_mask = MASK_PROJECTILE | MASK_WATER; + + hspread += (self->s.frame - FRAME_takeoff_01); + vspread += (self->s.frame - FRAME_takeoff_01); + + tr = gi.traceline(self->s.origin, start, self, MASK_PROJECTILE); + if (!(tr.fraction < 1.0f)) + { + dir = vectoangles(aimdir); + AngleVectors(dir, forward, right, up); + + r = crandom() * hspread; + u = crandom() * vspread; + end = start + (forward * 8192); + end += (right * r); + end += (up * u); + + if (gi.pointcontents(start) & MASK_WATER) + { + water = true; + water_start = start; + content_mask &= ~MASK_WATER; + } + + tr = gi.traceline(start, end, self, content_mask); + + // see if we hit water + if (tr.contents & MASK_WATER) + { + int color; + + water = true; + water_start = tr.endpos; + + if (start != tr.endpos) + { + if (tr.contents & CONTENTS_WATER) + { + if (strcmp(tr.surface->name, "*brwater") == 0) + color = SPLASH_BROWN_WATER; + else + color = SPLASH_BLUE_WATER; + } + else if (tr.contents & CONTENTS_SLIME) + color = SPLASH_SLIME; + else if (tr.contents & CONTENTS_LAVA) + color = SPLASH_LAVA; + else + color = SPLASH_UNKNOWN; + + if (color != SPLASH_UNKNOWN) + { + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_SPLASH); + gi.WriteByte(8); + gi.WritePosition(tr.endpos); + gi.WriteDir(tr.plane.normal); + gi.WriteByte(color); + gi.multicast(tr.endpos, MULTICAST_PVS, false); + } + + // change bullet's course when it enters water + dir = end - start; + dir = vectoangles(dir); + AngleVectors(dir, forward, right, up); + r = crandom() * hspread * 2; + u = crandom() * vspread * 2; + end = water_start + (forward * 8192); + end += (right * r); + end += (up * u); + } + + // re-trace ignoring water this time + tr = gi.traceline(water_start, end, self, MASK_PROJECTILE); + } + } + + // send gun puff / flash + if (!((tr.surface) && (tr.surface->flags & SURF_SKY))) + { + if (tr.fraction < 1.0f) + { + if (tr.ent->takedamage) + { + T_Damage(tr.ent, self, self, aimdir, tr.endpos, tr.plane.normal, damage, kick, DAMAGE_BULLET, MOD_BLASTOFF); + } + else + { + if (!(tr.surface->flags & SURF_SKY)) + { + gi.WriteByte(svc_temp_entity); + gi.WriteByte(te_impact); + gi.WritePosition(tr.endpos); + gi.WriteDir(tr.plane.normal); + gi.multicast(tr.endpos, MULTICAST_PVS, false); + + if (self->client) + PlayerNoise(self, tr.endpos, PNOISE_IMPACT); + } + } + } + } + + // if went through water, determine where the end and make a bubble trail + if (water) + { + vec3_t pos; + + dir = tr.endpos - water_start; + dir.normalize(); + pos = tr.endpos + (dir * -2); + if (gi.pointcontents(pos) & MASK_WATER) + tr.endpos = pos; + else + tr = gi.traceline(pos, water_start, tr.ent, MASK_WATER); + + pos = water_start + tr.endpos; + pos *= 0.5f; + + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_BUBBLETRAIL); + gi.WritePosition(water_start); + gi.WritePosition(tr.endpos); + gi.multicast(pos, MULTICAST_PVS, false); + } +} + +void fly_vertical(edict_t *self) +{ + int i; + vec3_t v; + vec3_t forward, right, up; + vec3_t start; + vec3_t tempvec; + + v = self->goalentity->s.origin - self->s.origin; + self->ideal_yaw = vectoyaw(v); + M_ChangeYaw(self); + + if (self->s.frame == FRAME_landing_58 || self->s.frame == FRAME_takeoff_16) + { + self->goalentity->nextthink = level.time + 100_ms; + self->goalentity->think = G_FreeEdict; + M_SetAnimation(self, &fixbot_move_stand); + self->goalentity = self->enemy = nullptr; + } + + // kick up some particles + tempvec = self->s.angles; + tempvec[PITCH] += 90; + + AngleVectors(tempvec, forward, right, up); + start = self->s.origin; + + for (i = 0; i < 10; i++) + blastoff(self, start, forward, 2, 1, TE_SHOTGUN, DEFAULT_SHOTGUN_HSPREAD, DEFAULT_SHOTGUN_VSPREAD); + + // needs sound +} + +void fly_vertical2(edict_t *self) +{ + vec3_t v; + float len; + + v = self->goalentity->s.origin - self->s.origin; + len = v.length(); + self->ideal_yaw = vectoyaw(v); + M_ChangeYaw(self); + + if (len < 32) + { + self->goalentity->nextthink = level.time + 100_ms; + self->goalentity->think = G_FreeEdict; + M_SetAnimation(self, &fixbot_move_stand); + self->goalentity = self->enemy = nullptr; + } + + // needs sound +} + +mframe_t fixbot_frames_landing[] = { + { ai_move }, + { ai_move, 0, fly_vertical2 }, + { ai_move, 0, fly_vertical2 }, + { ai_move, 0, fly_vertical2 }, + { ai_move, 0, fly_vertical2 }, + { ai_move, 0, fly_vertical2 }, + { ai_move, 0, fly_vertical2 }, + { ai_move, 0, fly_vertical2 }, + { ai_move, 0, fly_vertical2 }, + { ai_move, 0, fly_vertical2 }, + + { ai_move, 0, fly_vertical2 }, + { ai_move, 0, fly_vertical2 }, + { ai_move, 0, fly_vertical2 }, + { ai_move, 0, fly_vertical2 }, + { ai_move, 0, fly_vertical2 }, + { ai_move, 0, fly_vertical2 }, + { ai_move, 0, fly_vertical2 }, + { ai_move, 0, fly_vertical2 }, + { ai_move, 0, fly_vertical2 }, + { ai_move, 0, fly_vertical2 }, + + { ai_move, 0, fly_vertical2 }, + { ai_move, 0, fly_vertical2 }, + { ai_move, 0, fly_vertical2 }, + { ai_move, 0, fly_vertical2 }, + { ai_move, 0, fly_vertical2 }, + { ai_move, 0, fly_vertical2 }, + { ai_move, 0, fly_vertical2 }, + { ai_move, 0, fly_vertical2 }, + { ai_move, 0, fly_vertical2 }, + { ai_move, 0, fly_vertical2 }, + + { ai_move, 0, fly_vertical2 }, + { ai_move, 0, fly_vertical2 }, + { ai_move, 0, fly_vertical2 }, + { ai_move, 0, fly_vertical2 }, + { ai_move, 0, fly_vertical2 }, + { ai_move, 0, fly_vertical2 }, + { ai_move, 0, fly_vertical2 }, + { ai_move, 0, fly_vertical2 }, + { ai_move, 0, fly_vertical2 }, + { ai_move, 0, fly_vertical2 }, + + { ai_move, 0, fly_vertical2 }, + { ai_move, 0, fly_vertical2 }, + { ai_move, 0, fly_vertical2 }, + { ai_move, 0, fly_vertical2 }, + { ai_move, 0, fly_vertical2 }, + { ai_move, 0, fly_vertical2 }, + { ai_move, 0, fly_vertical2 }, + { ai_move, 0, fly_vertical2 }, + { ai_move, 0, fly_vertical2 }, + { ai_move, 0, fly_vertical2 }, + + { ai_move, 0, fly_vertical2 }, + { ai_move, 0, fly_vertical2 }, + { ai_move, 0, fly_vertical2 }, + { ai_move, 0, fly_vertical2 }, + { ai_move, 0, fly_vertical2 }, + { ai_move, 0, fly_vertical2 }, + { ai_move, 0, fly_vertical2 }, + { ai_move, 0, fly_vertical2 } +}; +MMOVE_T(fixbot_move_landing) = { FRAME_landing_01, FRAME_landing_58, fixbot_frames_landing, nullptr }; + +/* + generic ambient stand +*/ +mframe_t fixbot_frames_stand[] = { + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move, 0, change_to_roam } + +}; +MMOVE_T(fixbot_move_stand) = { FRAME_ambient_01, FRAME_ambient_19, fixbot_frames_stand, nullptr }; + +mframe_t fixbot_frames_stand2[] = { + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand, 0, change_to_roam } +}; +MMOVE_T(fixbot_move_stand2) = { FRAME_ambient_01, FRAME_ambient_19, fixbot_frames_stand2, nullptr }; + +#if 0 +/* + will need the pickup offset for the front pincers + object will need to stop forward of the object + and take the object with it ( this may require a variant of liftoff and landing ) +*/ +mframe_t fixbot_frames_pickup[] = { + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move } + +}; +MMOVE_T(fixbot_move_pickup) = { FRAME_pickup_01, FRAME_pickup_27, fixbot_frames_pickup, nullptr }; +#endif + +/* + generic frame to move bot +*/ +mframe_t fixbot_frames_roamgoal[] = { + { ai_move, 0, roam_goal } +}; +MMOVE_T(fixbot_move_roamgoal) = { FRAME_freeze_01, FRAME_freeze_01, fixbot_frames_roamgoal, nullptr }; + +void ai_facing(edict_t *self, float dist) +{ + if (!self->goalentity) + { + fixbot_stand(self); + return; + } + + vec3_t v; + + if (infront(self, self->goalentity)) + M_SetAnimation(self, &fixbot_move_forward); + else + { + v = self->goalentity->s.origin - self->s.origin; + self->ideal_yaw = vectoyaw(v); + M_ChangeYaw(self); + } +}; + +mframe_t fixbot_frames_turn[] = { + { ai_facing } +}; +MMOVE_T(fixbot_move_turn) = { FRAME_freeze_01, FRAME_freeze_01, fixbot_frames_turn, nullptr }; + +void go_roam(edict_t *self) +{ + M_SetAnimation(self, &fixbot_move_stand); +} + +/* + takeoff +*/ +mframe_t fixbot_frames_takeoff[] = { + { ai_move, 0.01f, fly_vertical }, + { ai_move, 0.01f, fly_vertical }, + { ai_move, 0.01f, fly_vertical }, + { ai_move, 0.01f, fly_vertical }, + { ai_move, 0.01f, fly_vertical }, + { ai_move, 0.01f, fly_vertical }, + { ai_move, 0.01f, fly_vertical }, + { ai_move, 0.01f, fly_vertical }, + { ai_move, 0.01f, fly_vertical }, + { ai_move, 0.01f, fly_vertical }, + + { ai_move, 0.01f, fly_vertical }, + { ai_move, 0.01f, fly_vertical }, + { ai_move, 0.01f, fly_vertical }, + { ai_move, 0.01f, fly_vertical }, + { ai_move, 0.01f, fly_vertical }, + { ai_move, 0.01f, fly_vertical } +}; +MMOVE_T(fixbot_move_takeoff) = { FRAME_takeoff_01, FRAME_takeoff_16, fixbot_frames_takeoff, nullptr }; + +/* findout what this is */ +mframe_t fixbot_frames_paina[] = { + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move } +}; +MMOVE_T(fixbot_move_paina) = { FRAME_paina_01, FRAME_paina_06, fixbot_frames_paina, fixbot_run }; + +/* findout what this is */ +mframe_t fixbot_frames_painb[] = { + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move } +}; +MMOVE_T(fixbot_move_painb) = { FRAME_painb_01, FRAME_painb_08, fixbot_frames_painb, fixbot_run }; + +/* + backup from pain + call a generic painsound + some spark effects +*/ +mframe_t fixbot_frames_pain3[] = { + { ai_move, -1 } +}; +MMOVE_T(fixbot_move_pain3) = { FRAME_freeze_01, FRAME_freeze_01, fixbot_frames_pain3, fixbot_run }; + +#if 0 +/* + bot has compleated landing + and is now on the grownd + ( may need second land if the bot is releasing jib into jib vat ) +*/ +mframe_t fixbot_frames_land[] = { + { ai_move } +}; +MMOVE_T(fixbot_move_land) = { FRAME_freeze_01, FRAME_freeze_01, fixbot_frames_land, nullptr }; +#endif + +void M_MoveToGoal(edict_t *ent, float dist); + +void ai_movetogoal(edict_t *self, float dist) +{ + M_MoveToGoal(self, dist); +} +/* + +*/ +mframe_t fixbot_frames_forward[] = { + { ai_movetogoal, 5, use_scanner } +}; +MMOVE_T(fixbot_move_forward) = { FRAME_freeze_01, FRAME_freeze_01, fixbot_frames_forward, nullptr }; + +/* + +*/ +mframe_t fixbot_frames_walk[] = { + { ai_walk, 5 } +}; +MMOVE_T(fixbot_move_walk) = { FRAME_freeze_01, FRAME_freeze_01, fixbot_frames_walk, nullptr }; + +/* + +*/ +mframe_t fixbot_frames_run[] = { + { ai_run, 10 } +}; +MMOVE_T(fixbot_move_run) = { FRAME_freeze_01, FRAME_freeze_01, fixbot_frames_run, nullptr }; + +#if 0 +/* + raf + note to self + they could have a timer that will cause + the bot to explode on countdown +*/ +mframe_t fixbot_frames_death1[] = { + { ai_move } +}; +MMOVE_T(fixbot_move_death1) = { FRAME_freeze_01, FRAME_freeze_01, fixbot_frames_death1, fixbot_dead }; + +// +mframe_t fixbot_frames_backward[] = { + { ai_move } +}; +MMOVE_T(fixbot_move_backward) = { FRAME_freeze_01, FRAME_freeze_01, fixbot_frames_backward, nullptr }; +#endif + +// +mframe_t fixbot_frames_start_attack[] = { + { ai_charge } +}; +MMOVE_T(fixbot_move_start_attack) = { FRAME_freeze_01, FRAME_freeze_01, fixbot_frames_start_attack, fixbot_attack }; + +#if 0 +/* + TBD: + need to get laser attack anim + attack with the laser blast +*/ +mframe_t fixbot_frames_attack1[] = { + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge, -10, fixbot_fire_blaster } +}; +MMOVE_T(fixbot_move_attack1) = { FRAME_shoot_01, FRAME_shoot_06, fixbot_frames_attack1, nullptr }; +#endif + +void abortHeal(edict_t *self, bool change_frame, bool gib, bool mark); + +PRETHINK(fixbot_laser_update) (edict_t *laser) -> void +{ + edict_t *self = laser->owner; + + vec3_t start, dir; + AngleVectors(self->s.angles, dir, nullptr, nullptr); + start = self->s.origin + (dir * 16); + + if (self->enemy && self->health > 0) + { + vec3_t point; + point = (self->enemy->absmin + self->enemy->absmax) * 0.5f; + if (self->monsterinfo.aiflags & AI_MEDIC) + point[0] += sinf(level.time.seconds()) * 8; + dir = point - self->s.origin; + dir.normalize(); + } + + laser->s.origin = start; + laser->movedir = dir; + gi.linkentity(laser); + dabeam_update(laser, true); +} + +void fixbot_fire_laser(edict_t *self) +{ + // critter dun got blown up while bein' fixed + if (!self->enemy || !self->enemy->inuse || self->enemy->health <= self->enemy->gib_health) + { + M_SetAnimation(self, &fixbot_move_stand); + self->monsterinfo.aiflags &= ~AI_MEDIC; + return; + } + + monster_fire_dabeam(self, -1, false, fixbot_laser_update); + + if (self->enemy->health > (self->enemy->mass / 10)) + { + vec3_t maxs; + self->enemy->spawnflags = SPAWNFLAG_NONE; + self->enemy->monsterinfo.aiflags &= AI_STINKY | AI_SPAWNED_MASK; + self->enemy->target = nullptr; + self->enemy->targetname = nullptr; + self->enemy->combattarget = nullptr; + self->enemy->deathtarget = nullptr; + self->enemy->healthtarget = nullptr; + self->enemy->itemtarget = nullptr; + self->enemy->monsterinfo.healer = self; + + maxs = self->enemy->maxs; + maxs[2] += 48; // compensate for change when they die + + trace_t tr = gi.trace(self->enemy->s.origin, self->enemy->mins, maxs, self->enemy->s.origin, self->enemy, MASK_MONSTERSOLID); + if (tr.startsolid || tr.allsolid) + { + abortHeal(self, false, true, false); + return; + } + else if (tr.ent != world) + { + abortHeal(self, false, true, false); + return; + } + else + { + self->enemy->monsterinfo.aiflags |= AI_IGNORE_SHOTS | AI_DO_NOT_COUNT; + + // backup & restore health stuff, because of multipliers + int32_t old_max_health = self->enemy->max_health; + item_id_t old_power_armor_type = self->enemy->monsterinfo.initial_power_armor_type; + int32_t old_power_armor_power = self->enemy->monsterinfo.max_power_armor_power; + int32_t old_base_health = self->enemy->monsterinfo.base_health; + int32_t old_health_scaling = self->enemy->monsterinfo.health_scaling; + auto reinforcements = self->enemy->monsterinfo.reinforcements; + int32_t monster_slots = self->enemy->monsterinfo.monster_slots; + int32_t monster_used = self->enemy->monsterinfo.monster_used; + int32_t old_gib_health = self->enemy->gib_health; + + st = {}; + st.keys_specified.emplace("reinforcements"); + st.reinforcements = ""; + + ED_CallSpawn(self->enemy); + + self->enemy->monsterinfo.reinforcements = reinforcements; + self->enemy->monsterinfo.monster_slots = monster_slots; + self->enemy->monsterinfo.monster_used = monster_used; + + self->enemy->gib_health = old_gib_health / 2; + self->enemy->health = self->enemy->max_health = old_max_health; + self->enemy->monsterinfo.power_armor_power = self->enemy->monsterinfo.max_power_armor_power = old_power_armor_power; + self->enemy->monsterinfo.power_armor_type = self->enemy->monsterinfo.initial_power_armor_type = old_power_armor_type; + self->enemy->monsterinfo.base_health = old_base_health; + self->enemy->monsterinfo.health_scaling = old_health_scaling; + + if (self->enemy->monsterinfo.setskin) + self->enemy->monsterinfo.setskin(self->enemy); + + if (self->enemy->think) + { + self->enemy->nextthink = level.time; + self->enemy->think(self->enemy); + } + self->enemy->monsterinfo.aiflags &= ~AI_RESURRECTING; + self->enemy->monsterinfo.aiflags |= AI_IGNORE_SHOTS | AI_DO_NOT_COUNT; + // turn off flies + self->enemy->s.effects &= ~EF_FLIES; + self->enemy->monsterinfo.healer = nullptr; + + // clean up target, if we have one and it's legit + if (self->enemy && self->enemy->inuse) + { + cleanupHealTarget(self->enemy); + + if ((self->oldenemy) && (self->oldenemy->inuse) && (self->oldenemy->health > 0)) + { + self->enemy->enemy = self->oldenemy; + FoundTarget(self->enemy); + } + else + { + self->enemy->enemy = nullptr; + if (!FindTarget(self->enemy)) + { + // no valid enemy, so stop acting + self->enemy->monsterinfo.pausetime = HOLD_FOREVER; + self->enemy->monsterinfo.stand(self->enemy); + } + self->enemy = nullptr; + self->oldenemy = nullptr; + if (!FindTarget(self)) + { + // no valid enemy, so stop acting + self->monsterinfo.pausetime = HOLD_FOREVER; + self->monsterinfo.stand(self); + return; + } + } + } + } + + M_SetAnimation(self, &fixbot_move_stand); + } + else + self->enemy->monsterinfo.aiflags |= AI_RESURRECTING; +} + +mframe_t fixbot_frames_laserattack[] = { + { ai_charge, 0, fixbot_fire_laser }, + { ai_charge, 0, fixbot_fire_laser }, + { ai_charge, 0, fixbot_fire_laser }, + { ai_charge, 0, fixbot_fire_laser }, + { ai_charge, 0, fixbot_fire_laser }, + { ai_charge, 0, fixbot_fire_laser } +}; +MMOVE_T(fixbot_move_laserattack) = { FRAME_shoot_01, FRAME_shoot_06, fixbot_frames_laserattack, nullptr }; + +/* + need to get forward translation data + for the charge attack +*/ +mframe_t fixbot_frames_attack2[] = { + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + + { ai_charge, -10 }, + { ai_charge, -10 }, + { ai_charge, -10 }, + { ai_charge, -10 }, + { ai_charge, -10 }, + { ai_charge, -10 }, + { ai_charge, -10 }, + { ai_charge, -10 }, + { ai_charge, -10 }, + { ai_charge, -10 }, + + { ai_charge, 0, fixbot_fire_blaster }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + + { ai_charge } +}; +MMOVE_T(fixbot_move_attack2) = { FRAME_charging_01, FRAME_charging_31, fixbot_frames_attack2, fixbot_run }; + +void weldstate(edict_t *self) +{ + if (self->s.frame == FRAME_weldstart_10) + M_SetAnimation(self, &fixbot_move_weld); + else if (self->goalentity && self->s.frame == FRAME_weldmiddle_07) + { + if (self->goalentity->health <= 0) + { + self->enemy->owner = nullptr; + M_SetAnimation(self, &fixbot_move_weld_end); + } + else + self->goalentity->health -= 10; + } + else + { + self->goalentity = self->enemy = nullptr; + M_SetAnimation(self, &fixbot_move_stand); + } +} + +void ai_move2(edict_t *self, float dist) +{ + if (!self->goalentity) + { + fixbot_stand(self); + return; + } + + vec3_t v; + + M_walkmove(self, self->s.angles[YAW], dist); + + v = self->goalentity->s.origin - self->s.origin; + self->ideal_yaw = vectoyaw(v); + M_ChangeYaw(self); +}; + +mframe_t fixbot_frames_weld_start[] = { + { ai_move2, 0 }, + { ai_move2, 0 }, + { ai_move2, 0 }, + { ai_move2, 0 }, + { ai_move2, 0 }, + { ai_move2, 0 }, + { ai_move2, 0 }, + { ai_move2, 0 }, + { ai_move2, 0 }, + { ai_move2, 0, weldstate } +}; +MMOVE_T(fixbot_move_weld_start) = { FRAME_weldstart_01, FRAME_weldstart_10, fixbot_frames_weld_start, nullptr }; + +mframe_t fixbot_frames_weld[] = { + { ai_move2, 0, fixbot_fire_welder }, + { ai_move2, 0, fixbot_fire_welder }, + { ai_move2, 0, fixbot_fire_welder }, + { ai_move2, 0, fixbot_fire_welder }, + { ai_move2, 0, fixbot_fire_welder }, + { ai_move2, 0, fixbot_fire_welder }, + { ai_move2, 0, weldstate } +}; +MMOVE_T(fixbot_move_weld) = { FRAME_weldmiddle_01, FRAME_weldmiddle_07, fixbot_frames_weld, nullptr }; + +mframe_t fixbot_frames_weld_end[] = { + { ai_move2, -2 }, + { ai_move2, -2 }, + { ai_move2, -2 }, + { ai_move2, -2 }, + { ai_move2, -2 }, + { ai_move2, -2 }, + { ai_move2, -2, weldstate } +}; +MMOVE_T(fixbot_move_weld_end) = { FRAME_weldend_01, FRAME_weldend_07, fixbot_frames_weld_end, nullptr }; + +void fixbot_fire_welder(edict_t *self) +{ + vec3_t start; + vec3_t forward, right, up; + vec3_t end; + vec3_t dir; + vec3_t vec; + float r; + + if (!self->enemy) + return; + + vec[0] = 24.0; + vec[1] = -0.8f; + vec[2] = -10.0; + + AngleVectors(self->s.angles, forward, right, up); + start = M_ProjectFlashSource(self, vec, forward, right); + + end = self->enemy->s.origin; + + dir = end - start; + + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_WELDING_SPARKS); + gi.WriteByte(10); + gi.WritePosition(start); + gi.WriteDir(vec3_origin); + gi.WriteByte(irandom(0xe0, 0xe8)); + gi.multicast(self->s.origin, MULTICAST_PVS, false); + + if (frandom() > 0.8f) + { + r = frandom(); + + if (r < 0.33f) + gi.sound(self, CHAN_VOICE, sound_weld1, 1, ATTN_IDLE, 0); + else if (r < 0.66f) + gi.sound(self, CHAN_VOICE, sound_weld2, 1, ATTN_IDLE, 0); + else + gi.sound(self, CHAN_VOICE, sound_weld3, 1, ATTN_IDLE, 0); + } +} + +void fixbot_fire_blaster(edict_t *self) +{ + vec3_t start; + vec3_t forward, right, up; + vec3_t end; + vec3_t dir; + + if (!visible(self, self->enemy)) + { + M_SetAnimation(self, &fixbot_move_run); + } + + AngleVectors(self->s.angles, forward, right, up); + start = M_ProjectFlashSource(self, monster_flash_offset[MZ2_HOVER_BLASTER_1], forward, right); + + end = self->enemy->s.origin; + end[2] += self->enemy->viewheight; + dir = end - start; + dir.normalize(); + + monster_fire_blaster(self, start, dir, 15, 1000, MZ2_HOVER_BLASTER_1, EF_BLASTER); +} + +MONSTERINFO_STAND(fixbot_stand) (edict_t *self) -> void +{ + M_SetAnimation(self, &fixbot_move_stand); +} + +MONSTERINFO_RUN(fixbot_run) (edict_t *self) -> void +{ + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + M_SetAnimation(self, &fixbot_move_stand); + else + M_SetAnimation(self, &fixbot_move_run); +} + +MONSTERINFO_WALK(fixbot_walk) (edict_t *self) -> void +{ + vec3_t vec; + float len; + + if (self->goalentity && strcmp(self->goalentity->classname, "object_repair") == 0) + { + vec = self->s.origin - self->goalentity->s.origin; + len = vec.length(); + if (len < 32) + { + M_SetAnimation(self, &fixbot_move_weld_start); + return; + } + } + M_SetAnimation(self, &fixbot_move_walk); +} + +void fixbot_start_attack(edict_t *self) +{ + M_SetAnimation(self, &fixbot_move_start_attack); +} + +MONSTERINFO_ATTACK(fixbot_attack) (edict_t *self) -> void +{ + vec3_t vec; + float len; + + if (self->monsterinfo.aiflags & AI_MEDIC) + { + if (!visible(self, self->enemy)) + return; + vec = self->s.origin - self->enemy->s.origin; + len = vec.length(); + if (len > 128) + return; + else + M_SetAnimation(self, &fixbot_move_laserattack); + } + else + { + fixbot_set_fly_parameters(self, false, false); + M_SetAnimation(self, &fixbot_move_attack2); + } +} + +PAIN(fixbot_pain) (edict_t *self, edict_t *other, float kick, int damage, const mod_t &mod) -> void +{ + if (level.time < self->pain_debounce_time) + return; + + fixbot_set_fly_parameters(self, false, false); + self->pain_debounce_time = level.time + 3_sec; + gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); + + if (damage <= 10) + M_SetAnimation(self, &fixbot_move_pain3); + else if (damage <= 25) + M_SetAnimation(self, &fixbot_move_painb); + else + M_SetAnimation(self, &fixbot_move_paina); +} + +void fixbot_dead(edict_t *self) +{ + self->mins = { -16, -16, -24 }; + self->maxs = { 16, 16, -8 }; + self->movetype = MOVETYPE_TOSS; + self->svflags |= SVF_DEADMONSTER; + self->nextthink = 0_ms; + gi.linkentity(self); +} + +DIE(fixbot_die) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void +{ + gi.sound(self, CHAN_VOICE, sound_die, 1, ATTN_NORM, 0); + BecomeExplosion1(self); + + // shards +} + +/*QUAKED monster_fixbot (1 .5 0) (-32 -32 -24) (32 32 24) Ambush Trigger_Spawn Fixit Takeoff Landing + */ +void SP_monster_fixbot(edict_t *self) +{ + if ( !M_AllowSpawn( self ) ) { + G_FreeEdict( self ); + return; + } + + sound_pain1 = gi.soundindex("flyer/flypain1.wav"); + sound_die = gi.soundindex("flyer/flydeth1.wav"); + + sound_weld1 = gi.soundindex("misc/welder1.wav"); + sound_weld2 = gi.soundindex("misc/welder2.wav"); + sound_weld3 = gi.soundindex("misc/welder3.wav"); + + self->s.modelindex = gi.modelindex("models/monsters/fixbot/tris.md2"); + + self->mins = { -32, -32, -24 }; + self->maxs = { 32, 32, 24 }; + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + + self->health = 150 * st.health_multiplier; + self->mass = 150; + + self->pain = fixbot_pain; + self->die = fixbot_die; + + self->monsterinfo.stand = fixbot_stand; + self->monsterinfo.walk = fixbot_walk; + self->monsterinfo.run = fixbot_run; + self->monsterinfo.attack = fixbot_attack; + + gi.linkentity(self); + + M_SetAnimation(self, &fixbot_move_stand); + self->monsterinfo.scale = MODEL_SCALE; + self->monsterinfo.aiflags |= AI_ALTERNATE_FLY; + fixbot_set_fly_parameters(self, false, false); + + flymonster_start(self); +} diff --git a/rerelease/xatrix/m_xatrix_fixbot.h b/rerelease/xatrix/m_xatrix_fixbot.h new file mode 100644 index 0000000..0cb4c21 --- /dev/null +++ b/rerelease/xatrix/m_xatrix_fixbot.h @@ -0,0 +1,223 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// ./fixbot + +// This file generated by ModelGen - Do NOT Modify + +enum +{ + FRAME_charging_01, + FRAME_charging_02, + FRAME_charging_03, + FRAME_charging_04, + FRAME_charging_05, + FRAME_charging_06, + FRAME_charging_07, + FRAME_charging_08, + FRAME_charging_09, + FRAME_charging_10, + FRAME_charging_11, + FRAME_charging_12, + FRAME_charging_13, + FRAME_charging_14, + FRAME_charging_15, + FRAME_charging_16, + FRAME_charging_17, + FRAME_charging_18, + FRAME_charging_19, + FRAME_charging_20, + FRAME_charging_21, + FRAME_charging_22, + FRAME_charging_23, + FRAME_charging_24, + FRAME_charging_25, + FRAME_charging_26, + FRAME_charging_27, + FRAME_charging_28, + FRAME_charging_29, + FRAME_charging_30, + FRAME_charging_31, + FRAME_landing_01, + FRAME_landing_02, + FRAME_landing_03, + FRAME_landing_04, + FRAME_landing_05, + FRAME_landing_06, + FRAME_landing_07, + FRAME_landing_08, + FRAME_landing_09, + FRAME_landing_10, + FRAME_landing_11, + FRAME_landing_12, + FRAME_landing_13, + FRAME_landing_14, + FRAME_landing_15, + FRAME_landing_16, + FRAME_landing_17, + FRAME_landing_18, + FRAME_landing_19, + FRAME_landing_20, + FRAME_landing_21, + FRAME_landing_22, + FRAME_landing_23, + FRAME_landing_24, + FRAME_landing_25, + FRAME_landing_26, + FRAME_landing_27, + FRAME_landing_28, + FRAME_landing_29, + FRAME_landing_30, + FRAME_landing_31, + FRAME_landing_32, + FRAME_landing_33, + FRAME_landing_34, + FRAME_landing_35, + FRAME_landing_36, + FRAME_landing_37, + FRAME_landing_38, + FRAME_landing_39, + FRAME_landing_40, + FRAME_landing_41, + FRAME_landing_42, + FRAME_landing_43, + FRAME_landing_44, + FRAME_landing_45, + FRAME_landing_46, + FRAME_landing_47, + FRAME_landing_48, + FRAME_landing_49, + FRAME_landing_50, + FRAME_landing_51, + FRAME_landing_52, + FRAME_landing_53, + FRAME_landing_54, + FRAME_landing_55, + FRAME_landing_56, + FRAME_landing_57, + FRAME_landing_58, + FRAME_pushback_01, + FRAME_pushback_02, + FRAME_pushback_03, + FRAME_pushback_04, + FRAME_pushback_05, + FRAME_pushback_06, + FRAME_pushback_07, + FRAME_pushback_08, + FRAME_pushback_09, + FRAME_pushback_10, + FRAME_pushback_11, + FRAME_pushback_12, + FRAME_pushback_13, + FRAME_pushback_14, + FRAME_pushback_15, + FRAME_pushback_16, + FRAME_takeoff_01, + FRAME_takeoff_02, + FRAME_takeoff_03, + FRAME_takeoff_04, + FRAME_takeoff_05, + FRAME_takeoff_06, + FRAME_takeoff_07, + FRAME_takeoff_08, + FRAME_takeoff_09, + FRAME_takeoff_10, + FRAME_takeoff_11, + FRAME_takeoff_12, + FRAME_takeoff_13, + FRAME_takeoff_14, + FRAME_takeoff_15, + FRAME_takeoff_16, + FRAME_ambient_01, + FRAME_ambient_02, + FRAME_ambient_03, + FRAME_ambient_04, + FRAME_ambient_05, + FRAME_ambient_06, + FRAME_ambient_07, + FRAME_ambient_08, + FRAME_ambient_09, + FRAME_ambient_10, + FRAME_ambient_11, + FRAME_ambient_12, + FRAME_ambient_13, + FRAME_ambient_14, + FRAME_ambient_15, + FRAME_ambient_16, + FRAME_ambient_17, + FRAME_ambient_18, + FRAME_ambient_19, + FRAME_paina_01, + FRAME_paina_02, + FRAME_paina_03, + FRAME_paina_04, + FRAME_paina_05, + FRAME_paina_06, + FRAME_painb_01, + FRAME_painb_02, + FRAME_painb_03, + FRAME_painb_04, + FRAME_painb_05, + FRAME_painb_06, + FRAME_painb_07, + FRAME_painb_08, + FRAME_pickup_01, + FRAME_pickup_02, + FRAME_pickup_03, + FRAME_pickup_04, + FRAME_pickup_05, + FRAME_pickup_06, + FRAME_pickup_07, + FRAME_pickup_08, + FRAME_pickup_09, + FRAME_pickup_10, + FRAME_pickup_11, + FRAME_pickup_12, + FRAME_pickup_13, + FRAME_pickup_14, + FRAME_pickup_15, + FRAME_pickup_16, + FRAME_pickup_17, + FRAME_pickup_18, + FRAME_pickup_19, + FRAME_pickup_20, + FRAME_pickup_21, + FRAME_pickup_22, + FRAME_pickup_23, + FRAME_pickup_24, + FRAME_pickup_25, + FRAME_pickup_26, + FRAME_pickup_27, + FRAME_freeze_01, + FRAME_shoot_01, + FRAME_shoot_02, + FRAME_shoot_03, + FRAME_shoot_04, + FRAME_shoot_05, + FRAME_shoot_06, + FRAME_weldstart_01, + FRAME_weldstart_02, + FRAME_weldstart_03, + FRAME_weldstart_04, + FRAME_weldstart_05, + FRAME_weldstart_06, + FRAME_weldstart_07, + FRAME_weldstart_08, + FRAME_weldstart_09, + FRAME_weldstart_10, + FRAME_weldmiddle_01, + FRAME_weldmiddle_02, + FRAME_weldmiddle_03, + FRAME_weldmiddle_04, + FRAME_weldmiddle_05, + FRAME_weldmiddle_06, + FRAME_weldmiddle_07, + FRAME_weldend_01, + FRAME_weldend_02, + FRAME_weldend_03, + FRAME_weldend_04, + FRAME_weldend_05, + FRAME_weldend_06, + FRAME_weldend_07 +}; + +constexpr float MODEL_SCALE = 1.000000f; diff --git a/rerelease/xatrix/m_xatrix_gekk.cpp b/rerelease/xatrix/m_xatrix_gekk.cpp new file mode 100644 index 0000000..5e3055b --- /dev/null +++ b/rerelease/xatrix/m_xatrix_gekk.cpp @@ -0,0 +1,1686 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +/* + xatrix + gekk.c +*/ + +#include "../g_local.h" +#include "m_xatrix_gekk.h" + +constexpr spawnflags_t SPAWNFLAG_GEKK_CHANT = 8_spawnflag; +constexpr spawnflags_t SPAWNFLAG_GEKK_NOJUMPING = 16_spawnflag; +constexpr spawnflags_t SPAWNFLAG_GEKK_NOSWIM = 32_spawnflag; + +static int sound_swing; +static int sound_hit; +static int sound_hit2; +static int sound_speet; +static int loogie_hit; +static int sound_death; +static int sound_pain1; +static int sound_sight; +static int sound_search; +static int sound_step1; +static int sound_step2; +static int sound_step3; +static int sound_thud; +static int sound_chantlow; +static int sound_chantmid; +static int sound_chanthigh; + +void gekk_swim(edict_t *self); + +void gekk_jump_takeoff(edict_t *self); +void gekk_jump_takeoff2(edict_t *self); +void gekk_check_landing(edict_t *self); +void gekk_stop_skid(edict_t *self); + +void water_to_land(edict_t *self); +void land_to_water(edict_t *self); + +void gekk_check_underwater(edict_t *self); +void gekk_bite(edict_t *self); + +void gekk_hit_left(edict_t *self); +void gekk_hit_right(edict_t *self); + +extern const mmove_t gekk_move_attack1; +extern const mmove_t gekk_move_attack2; +extern const mmove_t gekk_move_chant; +extern const mmove_t gekk_move_swim_start; +extern const mmove_t gekk_move_swim_loop; +extern const mmove_t gekk_move_spit; +extern const mmove_t gekk_move_run_start; +extern const mmove_t gekk_move_run; + +bool gekk_check_jump(edict_t *self); + +// +// CHECKATTACK +// + +bool gekk_check_melee(edict_t *self) +{ + if (!self->enemy || self->enemy->health <= 0 || self->monsterinfo.melee_debounce_time > level.time) + return false; + + return range_to(self, self->enemy) <= RANGE_MELEE; +} + +bool gekk_check_jump(edict_t *self) +{ + vec3_t v; + float distance; + + // don't jump if there's no way we can reach standing height + if (self->absmin[2] + 125 < self->enemy->absmin[2]) + return false; + + v[0] = self->s.origin[0] - self->enemy->s.origin[0]; + v[1] = self->s.origin[1] - self->enemy->s.origin[1]; + v[2] = 0; + distance = v.length(); + + if (distance < 100) + { + return false; + } + if (distance > 100) + { + if (frandom() < (self->waterlevel >= WATER_WAIST ? 0.2f : 0.9f)) + return false; + } + + return true; +} + +bool gekk_check_jump_close(edict_t *self) +{ + vec3_t v; + float distance; + + v[0] = self->s.origin[0] - self->enemy->s.origin[0]; + v[1] = self->s.origin[1] - self->enemy->s.origin[1]; + v[2] = 0; + + distance = v.length(); + + if (distance < 100) + { + // don't do this if our head is below their feet + if (self->absmax[2] <= self->enemy->absmin[2]) + return false; + } + + return true; +} + +MONSTERINFO_CHECKATTACK(gekk_checkattack) (edict_t *self) -> bool +{ + if (!self->enemy || self->enemy->health <= 0) + return false; + + if (gekk_check_melee(self)) + { + self->monsterinfo.attack_state = AS_MELEE; + return true; + } + + if (self->monsterinfo.attack_state == AS_STRAIGHT && self->monsterinfo.attack_finished > level.time) + { + // keep running fool + return false; + } + + if (visible(self, self->enemy, false)) + { + if (gekk_check_jump(self)) + { + self->monsterinfo.attack_state = AS_MISSILE; + return true; + } + + if (gekk_check_jump_close(self) && !(self->flags & FL_SWIM)) + { + self->monsterinfo.attack_state = AS_MISSILE; + return true; + } + } + + return false; +} + +// +// SOUNDS +// + +void gekk_step(edict_t *self) +{ + int n = irandom(3); + if (n == 0) + gi.sound(self, CHAN_VOICE, sound_step1, 1, ATTN_NORM, 0); + else if (n == 1) + gi.sound(self, CHAN_VOICE, sound_step2, 1, ATTN_NORM, 0); + else + gi.sound(self, CHAN_VOICE, sound_step3, 1, ATTN_NORM, 0); +} + +MONSTERINFO_SIGHT(gekk_sight) (edict_t *self, edict_t *other) -> void +{ + gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); +} + +MONSTERINFO_SEARCH(gekk_search) (edict_t *self) -> void +{ + float r; + + if (self->spawnflags.has(SPAWNFLAG_GEKK_CHANT)) + { + r = frandom(); + if (r < 0.33f) + gi.sound(self, CHAN_VOICE, sound_chantlow, 1, ATTN_NORM, 0); + else if (r < 0.66f) + gi.sound(self, CHAN_VOICE, sound_chantmid, 1, ATTN_NORM, 0); + else + gi.sound(self, CHAN_VOICE, sound_chanthigh, 1, ATTN_NORM, 0); + } + else + gi.sound(self, CHAN_VOICE, sound_search, 1, ATTN_NORM, 0); + + self->health += irandom(10, 20); + if (self->health > self->max_health) + self->health = self->max_health; + + self->monsterinfo.setskin(self); +} + +MONSTERINFO_SETSKIN(gekk_setskin) (edict_t *self) -> void +{ + if (self->health < (self->max_health / 4)) + self->s.skinnum = 2; + else if (self->health < (self->max_health / 2)) + self->s.skinnum = 1; + else + self->s.skinnum = 0; +} + +void gekk_swing(edict_t *self) +{ + gi.sound(self, CHAN_VOICE, sound_swing, 1, ATTN_NORM, 0); +} + +void gekk_face(edict_t *self) +{ + M_SetAnimation(self, &gekk_move_run); +} + +// +// STAND +// + +void ai_stand_gekk(edict_t *self, float dist) +{ + if (self->spawnflags.has(SPAWNFLAG_GEKK_CHANT)) + { + ai_move(self, dist); + if (!self->spawnflags.has(SPAWNFLAG_MONSTER_AMBUSH) && (self->monsterinfo.idle) && (level.time > self->monsterinfo.idle_time)) + { + if (self->monsterinfo.idle_time) + { + self->monsterinfo.idle(self); + self->monsterinfo.idle_time = level.time + random_time(15_sec, 30_sec); + } + else + { + self->monsterinfo.idle_time = level.time + random_time(15_sec); + } + } + } + else + ai_stand(self, dist); +} + +mframe_t gekk_frames_stand[] = { + { ai_stand_gekk }, + { ai_stand_gekk }, + { ai_stand_gekk }, + { ai_stand_gekk }, + { ai_stand_gekk }, + { ai_stand_gekk }, + { ai_stand_gekk }, + { ai_stand_gekk }, + { ai_stand_gekk }, + { ai_stand_gekk }, // 10 + + { ai_stand_gekk }, + { ai_stand_gekk }, + { ai_stand_gekk }, + { ai_stand_gekk }, + { ai_stand_gekk }, + { ai_stand_gekk }, + { ai_stand_gekk }, + { ai_stand_gekk }, + { ai_stand_gekk }, + { ai_stand_gekk }, // 20 + + { ai_stand_gekk }, + { ai_stand_gekk }, + { ai_stand_gekk }, + { ai_stand_gekk }, + { ai_stand_gekk }, + { ai_stand_gekk }, + { ai_stand_gekk }, + { ai_stand_gekk }, + { ai_stand_gekk }, + { ai_stand_gekk }, // 30 + + { ai_stand_gekk }, + { ai_stand_gekk }, + { ai_stand_gekk }, + { ai_stand_gekk }, + { ai_stand_gekk }, + { ai_stand_gekk }, + { ai_stand_gekk }, + { ai_stand_gekk }, + + { ai_stand_gekk, 0, gekk_check_underwater }, +}; +MMOVE_T(gekk_move_stand) = { FRAME_stand_01, FRAME_stand_39, gekk_frames_stand, nullptr }; + +mframe_t gekk_frames_standunderwater[] = { + { ai_stand_gekk, 14 }, + { ai_stand_gekk, 14 }, + { ai_stand_gekk, 14 }, + { ai_stand_gekk, 14 }, + { ai_stand_gekk, 16 }, + { ai_stand_gekk, 16 }, + { ai_stand_gekk, 16 }, + { ai_stand_gekk, 18 }, + { ai_stand_gekk, 18 }, + { ai_stand_gekk, 18 }, + + { ai_stand_gekk, 20 }, + { ai_stand_gekk, 20 }, + { ai_stand_gekk, 22 }, + { ai_stand_gekk, 22 }, + { ai_stand_gekk, 24 }, + { ai_stand_gekk, 24 }, + { ai_stand_gekk, 26 }, + { ai_stand_gekk, 26 }, + { ai_stand_gekk, 24 }, + { ai_stand_gekk, 24 }, + + { ai_stand_gekk, 22 }, + { ai_stand_gekk, 22 }, + { ai_stand_gekk, 22 }, + { ai_stand_gekk, 22 }, + { ai_stand_gekk, 22 }, + { ai_stand_gekk, 22 }, + { ai_stand_gekk, 22 }, + { ai_stand_gekk, 22 }, + { ai_stand_gekk, 18 }, + { ai_stand_gekk, 18 }, + + { ai_stand_gekk, 18 }, + { ai_stand_gekk, 18 } +}; + +MMOVE_T(gekk_move_standunderwater) = { FRAME_swim_01, FRAME_swim_32, gekk_frames_standunderwater, nullptr }; + +void gekk_swim_loop(edict_t *self) +{ + self->monsterinfo.aiflags |= AI_ALTERNATE_FLY; + self->flags |= FL_SWIM; + M_SetAnimation(self, &gekk_move_swim_loop); +} + +mframe_t gekk_frames_swim[] = { + { ai_run, 14 }, + { ai_run, 14 }, + { ai_run, 14 }, + { ai_run, 14 }, + { ai_run, 16 }, + { ai_run, 16 }, + { ai_run, 16 }, + { ai_run, 18 }, + { ai_run, 18 }, + { ai_run, 18 }, + + { ai_run, 20 }, + { ai_run, 20 }, + { ai_run, 22 }, + { ai_run, 22 }, + { ai_run, 24 }, + { ai_run, 24 }, + { ai_run, 26 }, + { ai_run, 26 }, + { ai_run, 24 }, + { ai_run, 24 }, + + { ai_run, 22 }, + { ai_run, 22 }, + { ai_run, 22 }, + { ai_run, 22 }, + { ai_run, 22 }, + { ai_run, 22 }, + { ai_run, 22 }, + { ai_run, 22 }, + { ai_run, 18 }, + { ai_run, 18 }, + + { ai_run, 18 }, + { ai_run, 18 } +}; +MMOVE_T(gekk_move_swim_loop) = { FRAME_swim_01, FRAME_swim_32, gekk_frames_swim, gekk_swim_loop }; + +mframe_t gekk_frames_swim_start[] = { + { ai_run, 14 }, + { ai_run, 14 }, + { ai_run, 14 }, + { ai_run, 14 }, + { ai_run, 16 }, + { ai_run, 16 }, + { ai_run, 16 }, + { ai_run, 18 }, + { ai_run, 18, gekk_hit_left }, + { ai_run, 18 }, + + { ai_run, 20 }, + { ai_run, 20 }, + { ai_run, 22 }, + { ai_run, 22 }, + { ai_run, 24, gekk_hit_right }, + { ai_run, 24 }, + { ai_run, 26 }, + { ai_run, 26 }, + { ai_run, 24 }, + { ai_run, 24 }, + + { ai_run, 22, gekk_bite }, + { ai_run, 22 }, + { ai_run, 22 }, + { ai_run, 22 }, + { ai_run, 22 }, + { ai_run, 22 }, + { ai_run, 22 }, + { ai_run, 22 }, + { ai_run, 18 }, + { ai_run, 18 }, + + { ai_run, 18 }, + { ai_run, 18 } +}; +MMOVE_T(gekk_move_swim_start) = { FRAME_swim_01, FRAME_swim_32, gekk_frames_swim_start, gekk_swim_loop }; + +void gekk_swim(edict_t *self) +{ + if (gekk_checkattack(self)) + { + if (self->enemy->waterlevel < WATER_WAIST && frandom() > 0.7f) + water_to_land(self); + else + M_SetAnimation(self, &gekk_move_swim_start); + } + else + M_SetAnimation(self, &gekk_move_swim_start); +} + +MONSTERINFO_STAND(gekk_stand) (edict_t *self) -> void +{ + if (self->waterlevel >= WATER_WAIST) + { + self->flags |= FL_SWIM; + self->monsterinfo.aiflags |= AI_ALTERNATE_FLY; + M_SetAnimation(self, &gekk_move_standunderwater); + } + else + // Don't break out of the chant loop, which is initiated in the spawn function + if (self->monsterinfo.active_move != &gekk_move_chant) + M_SetAnimation(self, &gekk_move_stand); +} + +void gekk_chant(edict_t *self) +{ + M_SetAnimation(self, &gekk_move_chant); +} + +// +// IDLE +// + +void gekk_idle_loop(edict_t *self) +{ + if (frandom() > 0.75f && self->health < self->max_health) + self->monsterinfo.nextframe = FRAME_idle_01; +} + +mframe_t gekk_frames_idle[] = { + { ai_stand_gekk, 0, gekk_search }, + { ai_stand_gekk }, + { ai_stand_gekk }, + { ai_stand_gekk }, + { ai_stand_gekk }, + { ai_stand_gekk }, + { ai_stand_gekk }, + { ai_stand_gekk }, + { ai_stand_gekk }, + { ai_stand_gekk }, + + { ai_stand_gekk }, + { ai_stand_gekk }, + { ai_stand_gekk }, + { ai_stand_gekk }, + { ai_stand_gekk }, + { ai_stand_gekk }, + { ai_stand_gekk }, + { ai_stand_gekk }, + { ai_stand_gekk }, + { ai_stand_gekk }, + + { ai_stand_gekk }, + { ai_stand_gekk }, + { ai_stand_gekk }, + { ai_stand_gekk }, + { ai_stand_gekk }, + { ai_stand_gekk }, + { ai_stand_gekk }, + { ai_stand_gekk }, + { ai_stand_gekk }, + { ai_stand_gekk }, + + { ai_stand_gekk }, + { ai_stand_gekk, 0, gekk_idle_loop } +}; +MMOVE_T(gekk_move_idle) = { FRAME_idle_01, FRAME_idle_32, gekk_frames_idle, gekk_stand }; +MMOVE_T(gekk_move_idle2) = { FRAME_idle_01, FRAME_idle_32, gekk_frames_idle, gekk_face }; + +mframe_t gekk_frames_idle2[] = { + { ai_move, 0, gekk_search }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + + { ai_move }, + { ai_move, 0, gekk_idle_loop } +}; +MMOVE_T(gekk_move_chant) = { FRAME_idle_01, FRAME_idle_32, gekk_frames_idle2, gekk_chant }; + +MONSTERINFO_IDLE(gekk_idle) (edict_t *self) -> void +{ + if (self->spawnflags.has(SPAWNFLAG_GEKK_NOSWIM) || self->waterlevel < WATER_WAIST) + M_SetAnimation(self, &gekk_move_idle); + else + M_SetAnimation(self, &gekk_move_swim_start); + // gi.sound (self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0); +} + +// +// WALK +// + +mframe_t gekk_frames_walk[] = { + { ai_walk, 3.849f, gekk_check_underwater }, // frame 0 + { ai_walk, 19.606f }, // frame 1 + { ai_walk, 25.583f }, // frame 2 + { ai_walk, 34.625f, gekk_step }, // frame 3 + { ai_walk, 27.365f }, // frame 4 + { ai_walk, 28.480f }, // frame 5 +}; + +MMOVE_T(gekk_move_walk) = { FRAME_run_01, FRAME_run_06, gekk_frames_walk, nullptr }; + +MONSTERINFO_WALK(gekk_walk) (edict_t *self) -> void +{ + M_SetAnimation(self, &gekk_move_walk); +} + +// +// RUN +// + +MONSTERINFO_RUN(gekk_run_start) (edict_t *self) -> void +{ + if (!self->spawnflags.has(SPAWNFLAG_GEKK_NOSWIM) && self->waterlevel >= WATER_WAIST) + { + M_SetAnimation(self, &gekk_move_swim_start); + } + else + { + M_SetAnimation(self, &gekk_move_run_start); + } +} + +void gekk_run(edict_t *self) +{ + + if (!self->spawnflags.has(SPAWNFLAG_GEKK_NOSWIM) && self->waterlevel >= WATER_WAIST) + { + M_SetAnimation(self, &gekk_move_swim_start); + return; + } + else + { + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + M_SetAnimation(self, &gekk_move_stand); + else + M_SetAnimation(self, &gekk_move_run); + } +} + +mframe_t gekk_frames_run[] = { + { ai_run, 3.849f, gekk_check_underwater }, // frame 0 + { ai_run, 19.606f }, // frame 1 + { ai_run, 25.583f }, // frame 2 + { ai_run, 34.625f, gekk_step }, // frame 3 + { ai_run, 27.365f }, // frame 4 + { ai_run, 28.480f }, // frame 5 +}; +MMOVE_T(gekk_move_run) = { FRAME_run_01, FRAME_run_06, gekk_frames_run, nullptr }; + +mframe_t gekk_frames_run_st[] = { + { ai_run, 0.212f }, // frame 0 + { ai_run, 19.753f }, // frame 1 +}; +MMOVE_T(gekk_move_run_start) = { FRAME_stand_01, FRAME_stand_02, gekk_frames_run_st, gekk_run }; + +// +// MELEE +// + +void gekk_hit_left(edict_t *self) +{ + if (!self->enemy) + return; + + vec3_t aim = { MELEE_DISTANCE, self->mins[0], 8 }; + if (fire_hit(self, aim, irandom(5, 10), 100)) + gi.sound(self, CHAN_WEAPON, sound_hit, 1, ATTN_NORM, 0); + else + { + gi.sound(self, CHAN_WEAPON, sound_swing, 1, ATTN_NORM, 0); + self->monsterinfo.melee_debounce_time = level.time + 1.5_sec; + } +} + +void gekk_hit_right(edict_t *self) +{ + if (!self->enemy) + return; + + vec3_t aim = { MELEE_DISTANCE, self->maxs[0], 8 }; + if (fire_hit(self, aim, irandom(5, 10), 100)) + gi.sound(self, CHAN_WEAPON, sound_hit2, 1, ATTN_NORM, 0); + else + { + gi.sound(self, CHAN_WEAPON, sound_swing, 1, ATTN_NORM, 0); + self->monsterinfo.melee_debounce_time = level.time + 1.5_sec; + } +} + +void gekk_check_refire(edict_t *self) +{ + if (!self->enemy || !self->enemy->inuse || self->enemy->health <= 0) + return; + + if (range_to(self, self->enemy) <= RANGE_MELEE && + self->monsterinfo.melee_debounce_time <= level.time) + { + if (self->s.frame == FRAME_clawatk3_09) + M_SetAnimation(self, &gekk_move_attack2); + else if (self->s.frame == FRAME_clawatk5_09) + M_SetAnimation(self, &gekk_move_attack1); + } +} + +TOUCH(loogie_touch) (edict_t *self, edict_t *other, const trace_t &tr, bool other_touching_self) -> void +{ + + if (other == self->owner) + return; + + if (tr.surface && (tr.surface->flags & SURF_SKY)) + { + G_FreeEdict(self); + return; + } + + if (self->owner->client) + PlayerNoise(self->owner, self->s.origin, PNOISE_IMPACT); + + if (other->takedamage) + T_Damage(other, self, self->owner, self->velocity, self->s.origin, tr.plane.normal, self->dmg, 1, DAMAGE_ENERGY, MOD_GEKK); + + gi.sound(self, CHAN_AUTO, loogie_hit, 1.0f, ATTN_NORM, 0); + + G_FreeEdict(self); +}; + +void fire_loogie(edict_t *self, const vec3_t &start, const vec3_t &dir, int damage, int speed) +{ + edict_t *loogie; + trace_t tr; + + loogie = G_Spawn(); + loogie->s.origin = start; + loogie->s.old_origin = start; + loogie->s.angles = vectoangles(dir); + loogie->velocity = dir * speed; + loogie->movetype = MOVETYPE_FLYMISSILE; + loogie->clipmask = MASK_PROJECTILE; + loogie->solid = SOLID_BBOX; + // Paril: this was originally the wrong effect, + // but it makes it look more acid-y. + loogie->s.effects |= EF_BLASTER; + loogie->s.renderfx |= RF_FULLBRIGHT; + loogie->s.modelindex = gi.modelindex("models/objects/loogy/tris.md2"); + loogie->owner = self; + loogie->touch = loogie_touch; + loogie->nextthink = level.time + 2_sec; + loogie->think = G_FreeEdict; + loogie->dmg = damage; + loogie->svflags |= SVF_PROJECTILE; + gi.linkentity(loogie); + + tr = gi.traceline(self->s.origin, loogie->s.origin, loogie, MASK_PROJECTILE); + if (tr.fraction < 1.0f) + { + loogie->s.origin = tr.endpos + (tr.plane.normal * 1.f); + loogie->touch(loogie, tr.ent, tr, false); + } +} + +void loogie(edict_t *self) +{ + vec3_t start; + vec3_t forward, right, up; + vec3_t end; + vec3_t dir; + vec3_t gekkoffset = { -18, -0.8f, 24 }; + + if (!self->enemy || self->enemy->health <= 0) + return; + + AngleVectors(self->s.angles, forward, right, up); + start = M_ProjectFlashSource(self, gekkoffset, forward, right); + + start += (up * 2); + + end = self->enemy->s.origin; + end[2] += self->enemy->viewheight; + dir = end - start; + dir.normalize(); + + fire_loogie(self, start, dir, 5, 550); + + gi.sound(self, CHAN_BODY, sound_speet, 1.0f, ATTN_NORM, 0); +} + +void reloogie(edict_t *self) +{ + if (frandom() > 0.8f && self->health < self->max_health) + { + M_SetAnimation(self, &gekk_move_idle2); + return; + } + + if (self->enemy->health >= 0) + if (frandom() > 0.7f && (range_to(self, self->enemy) <= RANGE_NEAR)) + M_SetAnimation(self, &gekk_move_spit); +} + +mframe_t gekk_frames_spit[] = { + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + + { ai_charge, 0, loogie }, + { ai_charge, 0, reloogie } +}; +MMOVE_T(gekk_move_spit) = { FRAME_spit_01, FRAME_spit_07, gekk_frames_spit, gekk_run_start }; + +mframe_t gekk_frames_attack1[] = { + { ai_charge }, + { ai_charge }, + { ai_charge }, + + { ai_charge, 0, gekk_hit_left }, + { ai_charge }, + { ai_charge }, + + { ai_charge }, + { ai_charge }, + { ai_charge, 0, gekk_check_refire } +}; +MMOVE_T(gekk_move_attack1) = { FRAME_clawatk3_01, FRAME_clawatk3_09, gekk_frames_attack1, gekk_run_start }; + +mframe_t gekk_frames_attack2[] = { + { ai_charge }, + { ai_charge }, + { ai_charge, 0, gekk_hit_left }, + + { ai_charge }, + { ai_charge }, + { ai_charge, 0, gekk_hit_right }, + + { ai_charge }, + { ai_charge }, + { ai_charge, 0, gekk_check_refire } +}; +MMOVE_T(gekk_move_attack2) = { FRAME_clawatk5_01, FRAME_clawatk5_09, gekk_frames_attack2, gekk_run_start }; + +void gekk_check_underwater(edict_t *self) +{ + if (!self->spawnflags.has(SPAWNFLAG_GEKK_NOSWIM) && self->waterlevel >= WATER_WAIST) + land_to_water(self); +} + +mframe_t gekk_frames_leapatk[] = { + { ai_charge }, // frame 0 + { ai_charge, -0.387f }, // frame 1 + { ai_charge, -1.113f }, // frame 2 + { ai_charge, -0.237f }, // frame 3 + { ai_charge, 6.720f, gekk_jump_takeoff }, // frame 4 last frame on ground + { ai_charge, 6.414f }, // frame 5 leaves ground + { ai_charge, 0.163f }, // frame 6 + { ai_charge, 28.316f }, // frame 7 + { ai_charge, 24.198f }, // frame 8 + { ai_charge, 31.742f }, // frame 9 + { ai_charge, 35.977f, gekk_check_landing }, // frame 10 last frame in air + { ai_charge, 12.303f, gekk_stop_skid }, // frame 11 feet back on ground + { ai_charge, 20.122f, gekk_stop_skid }, // frame 12 + { ai_charge, -1.042f, gekk_stop_skid }, // frame 13 + { ai_charge, 2.556f, gekk_stop_skid }, // frame 14 + { ai_charge, 0.544f, gekk_stop_skid }, // frame 15 + { ai_charge, 1.862f, gekk_stop_skid }, // frame 16 + { ai_charge, 1.224f, gekk_stop_skid }, // frame 17 + + { ai_charge, -0.457f, gekk_check_underwater }, // frame 18 +}; +MMOVE_T(gekk_move_leapatk) = { FRAME_leapatk_01, FRAME_leapatk_19, gekk_frames_leapatk, gekk_run_start }; + +mframe_t gekk_frames_leapatk2[] = { + { ai_charge }, // frame 0 + { ai_charge, -0.387f }, // frame 1 + { ai_charge, -1.113f }, // frame 2 + { ai_charge, -0.237f }, // frame 3 + { ai_charge, 6.720f, gekk_jump_takeoff2 }, // frame 4 last frame on ground + { ai_charge, 6.414f }, // frame 5 leaves ground + { ai_charge, 0.163f }, // frame 6 + { ai_charge, 28.316f }, // frame 7 + { ai_charge, 24.198f }, // frame 8 + { ai_charge, 31.742f }, // frame 9 + { ai_charge, 35.977f, gekk_check_landing }, // frame 10 last frame in air + { ai_charge, 12.303f, gekk_stop_skid }, // frame 11 feet back on ground + { ai_charge, 20.122f, gekk_stop_skid }, // frame 12 + { ai_charge, -1.042f, gekk_stop_skid }, // frame 13 + { ai_charge, 2.556f, gekk_stop_skid }, // frame 14 + { ai_charge, 0.544f, gekk_stop_skid }, // frame 15 + { ai_charge, 1.862f, gekk_stop_skid }, // frame 16 + { ai_charge, 1.224f, gekk_stop_skid }, // frame 17 + + { ai_charge, -0.457f, gekk_check_underwater }, // frame 18 +}; +MMOVE_T(gekk_move_leapatk2) = { FRAME_leapatk_01, FRAME_leapatk_19, gekk_frames_leapatk2, gekk_run_start }; + +void gekk_bite(edict_t *self) +{ + if (!self->enemy) + return; + + vec3_t aim = { MELEE_DISTANCE, 0, 0 }; + fire_hit(self, aim, 5, 0); +} + +void gekk_preattack(edict_t *self) +{ + // underwater attack sound + // gi.sound (self, CHAN_WEAPON, something something underwater sound, 1, ATTN_NORM, 0); + return; +} + +mframe_t gekk_frames_attack[] = { + { ai_charge, 16, gekk_preattack }, + { ai_charge, 16 }, + { ai_charge, 16 }, + { ai_charge, 16 }, + { ai_charge, 16, gekk_bite }, + { ai_charge, 16 }, + { ai_charge, 16 }, + { ai_charge, 16 }, + { ai_charge, 16 }, + { ai_charge, 16, gekk_bite }, + + { ai_charge, 16 }, + { ai_charge, 16 }, + { ai_charge, 16 }, + { ai_charge, 16, gekk_hit_left }, + { ai_charge, 16 }, + { ai_charge, 16 }, + { ai_charge, 16 }, + { ai_charge, 16 }, + { ai_charge, 16, gekk_hit_right }, + { ai_charge, 16 }, + + { ai_charge, 16 } +}; +MMOVE_T(gekk_move_attack) = { FRAME_attack_01, FRAME_attack_21, gekk_frames_attack, gekk_run_start }; + +MONSTERINFO_MELEE(gekk_melee) (edict_t *self) -> void +{ + if (self->waterlevel >= WATER_WAIST) + { + M_SetAnimation(self, &gekk_move_attack); + } + else + { + float r = frandom(); + + if (r > 0.66f) + M_SetAnimation(self, &gekk_move_attack1); + else + M_SetAnimation(self, &gekk_move_attack2); + } +} + +// +// ATTACK +// + +TOUCH(gekk_jump_touch) (edict_t *self, edict_t *other, const trace_t &tr, bool other_touching_self) -> void +{ + if (self->health <= 0) + { + self->touch = nullptr; + return; + } + + if (self->style == 1 && other->takedamage) + { + if (self->velocity.length() > 200) + { + vec3_t point; + vec3_t normal; + int damage; + + normal = self->velocity; + normal.normalize(); + point = self->s.origin + (normal * self->maxs[0]); + damage = irandom(10, 20); + T_Damage(other, self, self, self->velocity, point, normal, damage, damage, DAMAGE_NONE, MOD_GEKK); + self->style = 0; + } + } + + if (!M_CheckBottom(self)) + { + if (self->groundentity) + { + self->monsterinfo.nextframe = FRAME_leapatk_11; + self->touch = nullptr; + } + return; + } + + self->touch = nullptr; +} + +void gekk_jump_takeoff(edict_t *self) +{ + vec3_t forward; + + gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); + AngleVectors(self->s.angles, forward, nullptr, nullptr); + self->s.origin[2] += 1; + + // high jump + if (gekk_check_jump(self)) + { + self->velocity = forward * 700; + self->velocity[2] = 250; + } + else + { + self->velocity = forward * 250; + self->velocity[2] = 400; + } + + self->groundentity = nullptr; + self->monsterinfo.aiflags |= AI_DUCKED; + self->monsterinfo.attack_finished = level.time + 3_sec; + self->touch = gekk_jump_touch; + self->style = 1; +} + +void gekk_jump_takeoff2(edict_t *self) +{ + vec3_t forward; + + gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); + AngleVectors(self->s.angles, forward, nullptr, nullptr); + self->s.origin[2] = self->enemy->s.origin[2]; + + if (gekk_check_jump(self)) + { + self->velocity = forward * 300; + self->velocity[2] = 250; + } + else + { + self->velocity = forward * 150; + self->velocity[2] = 300; + } + + self->groundentity = nullptr; + self->monsterinfo.aiflags |= AI_DUCKED; + self->monsterinfo.attack_finished = level.time + 3_sec; + self->touch = gekk_jump_touch; + self->style = 1; +} + +void gekk_stop_skid(edict_t *self) +{ + if (self->groundentity) + self->velocity = {}; +} + +void gekk_check_landing(edict_t *self) +{ + if (self->groundentity) + { + gi.sound(self, CHAN_WEAPON, sound_thud, 1, ATTN_NORM, 0); + self->monsterinfo.attack_finished = 0_ms; + + if (self->monsterinfo.unduck) + self->monsterinfo.unduck(self); + + self->velocity = {}; + return; + } + + // Paril: allow them to "pull" up ledges + vec3_t fwd; + AngleVectors(self->s.angles, fwd, nullptr, nullptr); + + if (fwd.dot(self->velocity) < 200) + self->velocity += (fwd * 200.f); + + // note to self + // causing skid + if (level.time > self->monsterinfo.attack_finished) + self->monsterinfo.nextframe = FRAME_leapatk_11; + else + { + self->monsterinfo.nextframe = FRAME_leapatk_12; + } +} + +MONSTERINFO_ATTACK(gekk_attack) (edict_t *self) -> void +{ + float r = range_to(self, self->enemy); + + if (self->flags & FL_SWIM) + { + if (self->enemy && self->enemy->waterlevel >= WATER_WAIST && r <= RANGE_NEAR) + return; + + self->flags &= ~FL_SWIM; + self->monsterinfo.aiflags &= ~AI_ALTERNATE_FLY; + M_SetAnimation(self, &gekk_move_leapatk); + self->monsterinfo.nextframe = FRAME_leapatk_05; + } + else + { + if (r >= RANGE_MID) { + if (frandom() > 0.5f) { + M_SetAnimation(self, &gekk_move_spit); + } else { + M_SetAnimation(self, &gekk_move_run_start); + self->monsterinfo.attack_finished = level.time + 2_sec; + } + } else if (frandom() > 0.7f) { + M_SetAnimation(self, &gekk_move_spit); + } else { + if (self->spawnflags.has(SPAWNFLAG_GEKK_NOJUMPING) || frandom() > 0.7f) { + M_SetAnimation(self, &gekk_move_run_start); + self->monsterinfo.attack_finished = level.time + 1.4_sec; + } else { + M_SetAnimation(self, &gekk_move_leapatk); + } + } + } +} + +// +// PAIN +// + +mframe_t gekk_frames_pain[] = { + { ai_move }, // frame 0 + { ai_move }, // frame 1 + { ai_move }, // frame 2 + { ai_move }, // frame 3 + { ai_move }, // frame 4 + { ai_move }, // frame 5 +}; +MMOVE_T(gekk_move_pain) = { FRAME_pain_01, FRAME_pain_06, gekk_frames_pain, gekk_run_start }; + +mframe_t gekk_frames_pain1[] = { + { ai_move }, // frame 0 + { ai_move }, // frame 1 + { ai_move }, // frame 2 + { ai_move }, // frame 3 + { ai_move }, // frame 4 + { ai_move }, // frame 5 + { ai_move }, // frame 6 + { ai_move }, // frame 7 + { ai_move }, // frame 8 + { ai_move }, // frame 9 + + { ai_move, 0, gekk_check_underwater } +}; +MMOVE_T(gekk_move_pain1) = { FRAME_pain3_01, FRAME_pain3_11, gekk_frames_pain1, gekk_run_start }; + +mframe_t gekk_frames_pain2[] = { + { ai_move }, // frame 0 + { ai_move }, // frame 1 + { ai_move }, // frame 2 + { ai_move }, // frame 3 + { ai_move }, // frame 4 + { ai_move }, // frame 5 + { ai_move }, // frame 6 + { ai_move }, // frame 7 + { ai_move }, // frame 8 + { ai_move }, // frame 9 + + { ai_move }, // frame 10 + { ai_move }, // frame 11 + { ai_move, 0, gekk_check_underwater }, +}; +MMOVE_T(gekk_move_pain2) = { FRAME_pain4_01, FRAME_pain4_13, gekk_frames_pain2, gekk_run_start }; + +PAIN(gekk_pain) (edict_t *self, edict_t *other, float kick, int damage, const mod_t &mod) -> void +{ + float r; + + if (self->spawnflags.has(SPAWNFLAG_GEKK_CHANT)) + { + self->spawnflags &= ~SPAWNFLAG_GEKK_CHANT; + return; + } + + if (level.time < self->pain_debounce_time) + return; + + self->pain_debounce_time = level.time + 3_sec; + + gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); + + if (self->waterlevel >= WATER_WAIST) + { + if (!(self->flags & FL_SWIM)) + { + self->monsterinfo.aiflags |= AI_ALTERNATE_FLY; + self->flags |= FL_SWIM; + } + + if (M_ShouldReactToPain(self, mod)) // no pain anims in nightmare + M_SetAnimation(self, &gekk_move_pain); + } + else if (M_ShouldReactToPain(self, mod)) // no pain anims in nightmare + { + r = frandom(); + + if (r > 0.5f) + M_SetAnimation(self, &gekk_move_pain1); + else + M_SetAnimation(self, &gekk_move_pain2); + } +} + +// +// DEATH +// + +void gekk_dead(edict_t *self) +{ + self->mins = { -16, -16, -24 }; + self->maxs = { 16, 16, -8 }; + monster_dead(self); +} + +void gekk_gib(edict_t *self, int damage) +{ + gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0); + + ThrowGibs(self, damage, { + { "models/objects/gekkgib/pelvis/tris.md2", GIB_ACID }, + { 2, "models/objects/gekkgib/arm/tris.md2", GIB_ACID }, + { "models/objects/gekkgib/torso/tris.md2", GIB_ACID }, + { "models/objects/gekkgib/claw/tris.md2", GIB_ACID }, + { 2, "models/objects/gekkgib/leg/tris.md2", GIB_ACID }, + { "models/objects/gekkgib/head/tris.md2", GIB_ACID | GIB_HEAD } + }); +} + +void gekk_gibfest(edict_t *self) +{ + gekk_gib(self, 20); + self->deadflag = true; +} + +void isgibfest(edict_t *self) +{ + if (frandom() > 0.9f) + gekk_gibfest(self); +} + +static void gekk_shrink(edict_t *self) +{ + self->maxs[2] = 0; + self->svflags |= SVF_DEADMONSTER; + gi.linkentity(self); +} + +mframe_t gekk_frames_death1[] = { + { ai_move, -5.151f }, // frame 0 + { ai_move, -12.223f }, // frame 1 + { ai_move, -11.484f }, // frame 2 + { ai_move, -17.952f }, // frame 3 + { ai_move, -6.953f }, // frame 4 + { ai_move, -7.393f, gekk_shrink }, // frame 5 + { ai_move, -10.713f }, // frame 6 + { ai_move, -17.464f }, // frame 7 + { ai_move, -11.678f }, // frame 8 + { ai_move, -11.678f } // frame 9 +}; +MMOVE_T(gekk_move_death1) = { FRAME_death1_01, FRAME_death1_10, gekk_frames_death1, gekk_dead }; + +mframe_t gekk_frames_death3[] = { + { ai_move }, // frame 0 + { ai_move, 0.022f }, // frame 1 + { ai_move, 0.169f }, // frame 2 + { ai_move, -0.710f }, // frame 3 + { ai_move, -13.446f }, // frame 4 + { ai_move, -7.654f, isgibfest }, // frame 5 + { ai_move, -31.951f }, // frame 6 +}; +MMOVE_T(gekk_move_death3) = { FRAME_death3_01, FRAME_death3_07, gekk_frames_death3, gekk_dead }; + +mframe_t gekk_frames_death4[] = { + { ai_move, 5.103f }, // frame 0 + { ai_move, -4.808f }, // frame 1 + { ai_move, -10.509f }, // frame 2 + { ai_move, -9.899f }, // frame 3 + { ai_move, 4.033f, isgibfest }, // frame 4 + { ai_move, -5.197f }, // frame 5 + { ai_move, -0.919f }, // frame 6 + { ai_move, -8.821f }, // frame 7 + { ai_move, -5.626f }, // frame 8 + { ai_move, -8.865f, isgibfest }, // frame 9 + { ai_move, -0.845f }, // frame 10 + { ai_move, 1.986f }, // frame 11 + { ai_move, 0.170f }, // frame 12 + { ai_move, 1.339f, isgibfest }, // frame 13 + { ai_move, -0.922f }, // frame 14 + { ai_move, 0.818f }, // frame 15 + { ai_move, -1.288f }, // frame 16 + { ai_move, -1.408f, isgibfest }, // frame 17 + { ai_move, -7.787f }, // frame 18 + { ai_move, -3.995f }, // frame 19 + { ai_move, -4.604f }, // frame 20 + { ai_move, -1.715f, isgibfest }, // frame 21 + { ai_move, -0.564f }, // frame 22 + { ai_move, -0.597f }, // frame 23 + { ai_move, 0.074f }, // frame 24 + { ai_move, -0.309f, isgibfest }, // frame 25 + { ai_move, -0.395f }, // frame 26 + { ai_move, -0.501f }, // frame 27 + { ai_move, -0.325f }, // frame 28 + { ai_move, -0.931f, isgibfest }, // frame 29 + { ai_move, -1.433f }, // frame 30 + { ai_move, -1.626f }, // frame 31 + { ai_move, 4.680f }, // frame 32 + { ai_move, 0.560f }, // frame 33 + { ai_move, -0.549f, gekk_gibfest } // frame 34 +}; +MMOVE_T(gekk_move_death4) = { FRAME_death4_01, FRAME_death4_35, gekk_frames_death4, gekk_dead }; + +mframe_t gekk_frames_wdeath[] = { + { ai_move }, // frame 0 + { ai_move }, // frame 1 + { ai_move }, // frame 2 + { ai_move }, // frame 3 + { ai_move }, // frame 4 + { ai_move }, // frame 5 + { ai_move }, // frame 6 + { ai_move }, // frame 7 + { ai_move }, // frame 8 + { ai_move }, // frame 9 + { ai_move }, // frame 10 + { ai_move }, // frame 11 + { ai_move }, // frame 12 + { ai_move }, // frame 13 + { ai_move }, // frame 14 + { ai_move }, // frame 15 + { ai_move }, // frame 16 + { ai_move }, // frame 17 + { ai_move }, // frame 18 + { ai_move }, // frame 19 + { ai_move }, // frame 20 + { ai_move }, // frame 21 + { ai_move }, // frame 22 + { ai_move }, // frame 23 + { ai_move }, // frame 24 + { ai_move }, // frame 25 + { ai_move }, // frame 26 + { ai_move }, // frame 27 + { ai_move }, // frame 28 + { ai_move }, // frame 29 + { ai_move }, // frame 30 + { ai_move }, // frame 31 + { ai_move }, // frame 32 + { ai_move }, // frame 33 + { ai_move }, // frame 34 + { ai_move }, // frame 35 + { ai_move }, // frame 36 + { ai_move }, // frame 37 + { ai_move }, // frame 38 + { ai_move }, // frame 39 + { ai_move }, // frame 40 + { ai_move }, // frame 41 + { ai_move }, // frame 42 + { ai_move }, // frame 43 + { ai_move } // frame 44 +}; +MMOVE_T(gekk_move_wdeath) = { FRAME_wdeath_01, FRAME_wdeath_45, gekk_frames_wdeath, gekk_dead }; + +DIE(gekk_die) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void +{ + float r; + + if (M_CheckGib(self, mod)) + { + gekk_gib(self, damage); + self->deadflag = true; + return; + } + + if (self->deadflag) + return; + + gi.sound(self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0); + self->deadflag = true; + self->takedamage = true; + + if (self->waterlevel >= WATER_WAIST) + { + gekk_shrink(self); + M_SetAnimation(self, &gekk_move_wdeath); + } + else + { + r = frandom(); + if (r > 0.66f) + M_SetAnimation(self, &gekk_move_death1); + else if (r > 0.33f) + M_SetAnimation(self, &gekk_move_death3); + else + M_SetAnimation(self, &gekk_move_death4); + } +} + +/* + duck +*/ +mframe_t gekk_frames_lduck[] = { + { ai_move }, // frame 0 + { ai_move }, // frame 1 + { ai_move }, // frame 2 + { ai_move }, // frame 3 + { ai_move }, // frame 4 + { ai_move }, // frame 5 + { ai_move }, // frame 6 + { ai_move }, // frame 7 + { ai_move }, // frame 8 + { ai_move }, // frame 9 + + { ai_move }, // frame 10 + { ai_move }, // frame 11 + { ai_move } // frame 12 +}; +MMOVE_T(gekk_move_lduck) = { FRAME_lduck_01, FRAME_lduck_13, gekk_frames_lduck, gekk_run_start }; + +mframe_t gekk_frames_rduck[] = { + { ai_move }, // frame 0 + { ai_move }, // frame 1 + { ai_move }, // frame 2 + { ai_move }, // frame 3 + { ai_move }, // frame 4 + { ai_move }, // frame 5 + { ai_move }, // frame 6 + { ai_move }, // frame 7 + { ai_move }, // frame 8 + { ai_move }, // frame 9 + { ai_move }, // frame 10 + { ai_move }, // frame 11 + { ai_move } // frame 12 +}; +MMOVE_T(gekk_move_rduck) = { FRAME_rduck_01, FRAME_rduck_13, gekk_frames_rduck, gekk_run_start }; + +MONSTERINFO_DODGE(gekk_dodge) (edict_t *self, edict_t *attacker, gtime_t eta, trace_t *tr, bool gravity) -> void +{ + // [Paril-KEX] this dodge is bad +#if 0 + float r; + + r = frandom(); + if (r > 0.25f) + return; + + if (!self->enemy) + self->enemy = attacker; + + if (self->waterlevel) + { + M_SetAnimation(self, &gekk_move_attack); + return; + } + + if (skill->integer == 0) + { + r = frandom(); + if (r > 0.5f) + M_SetAnimation(self, &gekk_move_lduck); + else + M_SetAnimation(self, &gekk_move_rduck); + return; + } + + self->monsterinfo.pausetime = level.time + eta + 300_ms; + r = frandom(); + + if (skill->integer == 1) + { + if (r > 0.33f) + { + r = frandom(); + if (r > 0.5f) + M_SetAnimation(self, &gekk_move_lduck); + else + M_SetAnimation(self, &gekk_move_rduck); + } + else + { + r = frandom(); + if (r > 0.66f) + M_SetAnimation(self, &gekk_move_attack1); + else + M_SetAnimation(self, &gekk_move_attack2); + } + return; + } + + if (skill->integer == 2) + { + if (r > 0.66f) + { + r = frandom(); + if (r > 0.5f) + M_SetAnimation(self, &gekk_move_lduck); + else + M_SetAnimation(self, &gekk_move_rduck); + } + else + { + r = frandom(); + if (r > 0.66f) + M_SetAnimation(self, &gekk_move_attack1); + else + M_SetAnimation(self, &gekk_move_attack2); + } + return; + } + + r = frandom(); + if (r > 0.66f) + M_SetAnimation(self, &gekk_move_attack1); + else + M_SetAnimation(self, &gekk_move_attack2); +#endif +} + +// +// SPAWN +// + +static void gekk_set_fly_parameters(edict_t *self) +{ + self->monsterinfo.fly_thrusters = false; + self->monsterinfo.fly_acceleration = 25.f; + self->monsterinfo.fly_speed = 150.f; + // only melee, so get in close + self->monsterinfo.fly_min_distance = 10.f; + self->monsterinfo.fly_max_distance = 10.f; +} + + +//================ +// ROGUE +void gekk_jump_down(edict_t *self) +{ + vec3_t forward, up; + + AngleVectors(self->s.angles, forward, nullptr, up); + self->velocity += (forward * 100); + self->velocity += (up * 300); +} + +void gekk_jump_up(edict_t *self) +{ + vec3_t forward, up; + + AngleVectors(self->s.angles, forward, nullptr, up); + self->velocity += (forward * 200); + self->velocity += (up * 450); +} + +void gekk_jump_wait_land(edict_t *self) +{ + if (!monster_jump_finished(self) && self->groundentity == nullptr) + self->monsterinfo.nextframe = self->s.frame; + else + self->monsterinfo.nextframe = self->s.frame + 1; +} + +mframe_t gekk_frames_jump_up[] = { + { ai_move, -8, gekk_jump_up }, + { ai_move, -8 }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move, 0, gekk_jump_wait_land }, + { ai_move } +}; +MMOVE_T(gekk_move_jump_up) = { FRAME_leapatk_04, FRAME_leapatk_11, gekk_frames_jump_up, gekk_run }; + +mframe_t gekk_frames_jump_down[] = { + { ai_move, 0, gekk_jump_down }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move, 0, gekk_jump_wait_land }, + { ai_move } +}; +MMOVE_T(gekk_move_jump_down) = { FRAME_leapatk_04, FRAME_leapatk_11, gekk_frames_jump_down, gekk_run }; + +void gekk_jump_updown(edict_t *self, blocked_jump_result_t result) +{ + if (!self->enemy) + return; + + if (result == blocked_jump_result_t::JUMP_JUMP_UP) + M_SetAnimation(self, &gekk_move_jump_up); + else + M_SetAnimation(self, &gekk_move_jump_down); +} + +/* +=== +Blocked +=== +*/ +MONSTERINFO_BLOCKED(gekk_blocked) (edict_t *self, float dist) -> bool +{ + if (auto result = blocked_checkjump(self, dist); result != blocked_jump_result_t::NO_JUMP) + { + if (result != blocked_jump_result_t::JUMP_TURN) + gekk_jump_updown(self, result); + return true; + } + + if (blocked_checkplat(self, dist)) + return true; + + return false; +} +// ROGUE +//================ + +/*QUAKED monster_gekk (1 .5 0) (-16 -16 -24) (16 16 24) Ambush Trigger_Spawn Sight Chant NoJumping + */ +void SP_monster_gekk(edict_t *self) +{ + if ( !M_AllowSpawn( self ) ) { + G_FreeEdict( self ); + return; + } + + sound_swing = gi.soundindex("gek/gk_atck1.wav"); + sound_hit = gi.soundindex("gek/gk_atck2.wav"); + sound_hit2 = gi.soundindex("gek/gk_atck3.wav"); + sound_speet = gi.soundindex("gek/gk_atck4.wav"); + loogie_hit = gi.soundindex("gek/loogie_hit.wav"); + sound_death = gi.soundindex("gek/gk_deth1.wav"); + sound_pain1 = gi.soundindex("gek/gk_pain1.wav"); + sound_sight = gi.soundindex("gek/gk_sght1.wav"); + sound_search = gi.soundindex("gek/gk_idle1.wav"); + sound_step1 = gi.soundindex("gek/gk_step1.wav"); + sound_step2 = gi.soundindex("gek/gk_step2.wav"); + sound_step3 = gi.soundindex("gek/gk_step3.wav"); + sound_thud = gi.soundindex("mutant/thud1.wav"); + + sound_chantlow = gi.soundindex("gek/gek_low.wav"); + sound_chantmid = gi.soundindex("gek/gek_mid.wav"); + sound_chanthigh = gi.soundindex("gek/gek_high.wav"); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex("models/monsters/gekk/tris.md2"); + self->mins = { -18, -18, -24 }; + self->maxs = { 18, 18, 24 }; + + gi.modelindex("models/objects/gekkgib/pelvis/tris.md2"); + gi.modelindex("models/objects/gekkgib/arm/tris.md2"); + gi.modelindex("models/objects/gekkgib/torso/tris.md2"); + gi.modelindex("models/objects/gekkgib/claw/tris.md2"); + gi.modelindex("models/objects/gekkgib/leg/tris.md2"); + gi.modelindex("models/objects/gekkgib/head/tris.md2"); + + self->health = 125 * st.health_multiplier; + self->gib_health = -30; + self->mass = 300; + + self->pain = gekk_pain; + self->die = gekk_die; + + self->monsterinfo.stand = gekk_stand; + + self->monsterinfo.walk = gekk_walk; + self->monsterinfo.run = gekk_run_start; + self->monsterinfo.dodge = gekk_dodge; + self->monsterinfo.attack = gekk_attack; + self->monsterinfo.melee = gekk_melee; + self->monsterinfo.sight = gekk_sight; + self->monsterinfo.search = gekk_search; + self->monsterinfo.idle = gekk_idle; + self->monsterinfo.checkattack = gekk_checkattack; + self->monsterinfo.setskin = gekk_setskin; + + gi.linkentity(self); + + M_SetAnimation(self, &gekk_move_stand); + + self->monsterinfo.scale = MODEL_SCALE; + + walkmonster_start(self); + + if (self->spawnflags.has(SPAWNFLAG_GEKK_CHANT)) + M_SetAnimation(self, &gekk_move_chant); + + self->monsterinfo.can_jump = !(self->spawnflags & SPAWNFLAG_GEKK_NOJUMPING); + self->monsterinfo.drop_height = 256; + self->monsterinfo.jump_height = 68; + self->monsterinfo.blocked = gekk_blocked; + + gekk_set_fly_parameters(self); +} + +void water_to_land(edict_t *self) +{ + self->monsterinfo.aiflags &= ~AI_ALTERNATE_FLY; + self->flags &= ~FL_SWIM; + self->yaw_speed = 20; + self->viewheight = 25; + + M_SetAnimation(self, &gekk_move_leapatk2); + + self->mins = { -18, -18, -24 }; + self->maxs = { 18, 18, 24 }; +} + +void land_to_water(edict_t *self) +{ + self->monsterinfo.aiflags |= AI_ALTERNATE_FLY; + self->flags |= FL_SWIM; + self->yaw_speed = 10; + self->viewheight = 10; + + M_SetAnimation(self, &gekk_move_swim_start); + + self->mins = { -18, -18, -24 }; + self->maxs = { 18, 18, 16 }; +} \ No newline at end of file diff --git a/rerelease/xatrix/m_xatrix_gekk.h b/rerelease/xatrix/m_xatrix_gekk.h new file mode 100644 index 0000000..db7ebb8 --- /dev/null +++ b/rerelease/xatrix/m_xatrix_gekk.h @@ -0,0 +1,361 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// ./gekk + +// This file generated by ModelGen - Do NOT Modify + +enum +{ + FRAME_stand_01, + FRAME_stand_02, + FRAME_stand_03, + FRAME_stand_04, + FRAME_stand_05, + FRAME_stand_06, + FRAME_stand_07, + FRAME_stand_08, + FRAME_stand_09, + FRAME_stand_10, + FRAME_stand_11, + FRAME_stand_12, + FRAME_stand_13, + FRAME_stand_14, + FRAME_stand_15, + FRAME_stand_16, + FRAME_stand_17, + FRAME_stand_18, + FRAME_stand_19, + FRAME_stand_20, + FRAME_stand_21, + FRAME_stand_22, + FRAME_stand_23, + FRAME_stand_24, + FRAME_stand_25, + FRAME_stand_26, + FRAME_stand_27, + FRAME_stand_28, + FRAME_stand_29, + FRAME_stand_30, + FRAME_stand_31, + FRAME_stand_32, + FRAME_stand_33, + FRAME_stand_34, + FRAME_stand_35, + FRAME_stand_36, + FRAME_stand_37, + FRAME_stand_38, + FRAME_stand_39, + FRAME_run_01, + FRAME_run_02, + FRAME_run_03, + FRAME_run_04, + FRAME_run_05, + FRAME_run_06, + FRAME_clawatk3_01, + FRAME_clawatk3_02, + FRAME_clawatk3_03, + FRAME_clawatk3_04, + FRAME_clawatk3_05, + FRAME_clawatk3_06, + FRAME_clawatk3_07, + FRAME_clawatk3_08, + FRAME_clawatk3_09, + FRAME_clawatk4_01, + FRAME_clawatk4_02, + FRAME_clawatk4_03, + FRAME_clawatk4_04, + FRAME_clawatk4_05, + FRAME_clawatk4_06, + FRAME_clawatk4_07, + FRAME_clawatk4_08, + FRAME_clawatk5_01, + FRAME_clawatk5_02, + FRAME_clawatk5_03, + FRAME_clawatk5_04, + FRAME_clawatk5_05, + FRAME_clawatk5_06, + FRAME_clawatk5_07, + FRAME_clawatk5_08, + FRAME_clawatk5_09, + FRAME_leapatk_01, + FRAME_leapatk_02, + FRAME_leapatk_03, + FRAME_leapatk_04, + FRAME_leapatk_05, + FRAME_leapatk_06, + FRAME_leapatk_07, + FRAME_leapatk_08, + FRAME_leapatk_09, + FRAME_leapatk_10, + FRAME_leapatk_11, + FRAME_leapatk_12, + FRAME_leapatk_13, + FRAME_leapatk_14, + FRAME_leapatk_15, + FRAME_leapatk_16, + FRAME_leapatk_17, + FRAME_leapatk_18, + FRAME_leapatk_19, + FRAME_pain3_01, + FRAME_pain3_02, + FRAME_pain3_03, + FRAME_pain3_04, + FRAME_pain3_05, + FRAME_pain3_06, + FRAME_pain3_07, + FRAME_pain3_08, + FRAME_pain3_09, + FRAME_pain3_10, + FRAME_pain3_11, + FRAME_pain4_01, + FRAME_pain4_02, + FRAME_pain4_03, + FRAME_pain4_04, + FRAME_pain4_05, + FRAME_pain4_06, + FRAME_pain4_07, + FRAME_pain4_08, + FRAME_pain4_09, + FRAME_pain4_10, + FRAME_pain4_11, + FRAME_pain4_12, + FRAME_pain4_13, + FRAME_death1_01, + FRAME_death1_02, + FRAME_death1_03, + FRAME_death1_04, + FRAME_death1_05, + FRAME_death1_06, + FRAME_death1_07, + FRAME_death1_08, + FRAME_death1_09, + FRAME_death1_10, + FRAME_death2_01, + FRAME_death2_02, + FRAME_death2_03, + FRAME_death2_04, + FRAME_death2_05, + FRAME_death2_06, + FRAME_death2_07, + FRAME_death2_08, + FRAME_death2_09, + FRAME_death2_10, + FRAME_death2_11, + FRAME_death3_01, + FRAME_death3_02, + FRAME_death3_03, + FRAME_death3_04, + FRAME_death3_05, + FRAME_death3_06, + FRAME_death3_07, + FRAME_death4_01, + FRAME_death4_02, + FRAME_death4_03, + FRAME_death4_04, + FRAME_death4_05, + FRAME_death4_06, + FRAME_death4_07, + FRAME_death4_08, + FRAME_death4_09, + FRAME_death4_10, + FRAME_death4_11, + FRAME_death4_12, + FRAME_death4_13, + FRAME_death4_14, + FRAME_death4_15, + FRAME_death4_16, + FRAME_death4_17, + FRAME_death4_18, + FRAME_death4_19, + FRAME_death4_20, + FRAME_death4_21, + FRAME_death4_22, + FRAME_death4_23, + FRAME_death4_24, + FRAME_death4_25, + FRAME_death4_26, + FRAME_death4_27, + FRAME_death4_28, + FRAME_death4_29, + FRAME_death4_30, + FRAME_death4_31, + FRAME_death4_32, + FRAME_death4_33, + FRAME_death4_34, + FRAME_death4_35, + FRAME_rduck_01, + FRAME_rduck_02, + FRAME_rduck_03, + FRAME_rduck_04, + FRAME_rduck_05, + FRAME_rduck_06, + FRAME_rduck_07, + FRAME_rduck_08, + FRAME_rduck_09, + FRAME_rduck_10, + FRAME_rduck_11, + FRAME_rduck_12, + FRAME_rduck_13, + FRAME_lduck_01, + FRAME_lduck_02, + FRAME_lduck_03, + FRAME_lduck_04, + FRAME_lduck_05, + FRAME_lduck_06, + FRAME_lduck_07, + FRAME_lduck_08, + FRAME_lduck_09, + FRAME_lduck_10, + FRAME_lduck_11, + FRAME_lduck_12, + FRAME_lduck_13, + FRAME_idle_01, + FRAME_idle_02, + FRAME_idle_03, + FRAME_idle_04, + FRAME_idle_05, + FRAME_idle_06, + FRAME_idle_07, + FRAME_idle_08, + FRAME_idle_09, + FRAME_idle_10, + FRAME_idle_11, + FRAME_idle_12, + FRAME_idle_13, + FRAME_idle_14, + FRAME_idle_15, + FRAME_idle_16, + FRAME_idle_17, + FRAME_idle_18, + FRAME_idle_19, + FRAME_idle_20, + FRAME_idle_21, + FRAME_idle_22, + FRAME_idle_23, + FRAME_idle_24, + FRAME_idle_25, + FRAME_idle_26, + FRAME_idle_27, + FRAME_idle_28, + FRAME_idle_29, + FRAME_idle_30, + FRAME_idle_31, + FRAME_idle_32, + FRAME_spit_01, + FRAME_spit_02, + FRAME_spit_03, + FRAME_spit_04, + FRAME_spit_05, + FRAME_spit_06, + FRAME_spit_07, + FRAME_amb_01, + FRAME_amb_02, + FRAME_amb_03, + FRAME_amb_04, + FRAME_wdeath_01, + FRAME_wdeath_02, + FRAME_wdeath_03, + FRAME_wdeath_04, + FRAME_wdeath_05, + FRAME_wdeath_06, + FRAME_wdeath_07, + FRAME_wdeath_08, + FRAME_wdeath_09, + FRAME_wdeath_10, + FRAME_wdeath_11, + FRAME_wdeath_12, + FRAME_wdeath_13, + FRAME_wdeath_14, + FRAME_wdeath_15, + FRAME_wdeath_16, + FRAME_wdeath_17, + FRAME_wdeath_18, + FRAME_wdeath_19, + FRAME_wdeath_20, + FRAME_wdeath_21, + FRAME_wdeath_22, + FRAME_wdeath_23, + FRAME_wdeath_24, + FRAME_wdeath_25, + FRAME_wdeath_26, + FRAME_wdeath_27, + FRAME_wdeath_28, + FRAME_wdeath_29, + FRAME_wdeath_30, + FRAME_wdeath_31, + FRAME_wdeath_32, + FRAME_wdeath_33, + FRAME_wdeath_34, + FRAME_wdeath_35, + FRAME_wdeath_36, + FRAME_wdeath_37, + FRAME_wdeath_38, + FRAME_wdeath_39, + FRAME_wdeath_40, + FRAME_wdeath_41, + FRAME_wdeath_42, + FRAME_wdeath_43, + FRAME_wdeath_44, + FRAME_wdeath_45, + FRAME_swim_01, + FRAME_swim_02, + FRAME_swim_03, + FRAME_swim_04, + FRAME_swim_05, + FRAME_swim_06, + FRAME_swim_07, + FRAME_swim_08, + FRAME_swim_09, + FRAME_swim_10, + FRAME_swim_11, + FRAME_swim_12, + FRAME_swim_13, + FRAME_swim_14, + FRAME_swim_15, + FRAME_swim_16, + FRAME_swim_17, + FRAME_swim_18, + FRAME_swim_19, + FRAME_swim_20, + FRAME_swim_21, + FRAME_swim_22, + FRAME_swim_23, + FRAME_swim_24, + FRAME_swim_25, + FRAME_swim_26, + FRAME_swim_27, + FRAME_swim_28, + FRAME_swim_29, + FRAME_swim_30, + FRAME_swim_31, + FRAME_swim_32, + FRAME_attack_01, + FRAME_attack_02, + FRAME_attack_03, + FRAME_attack_04, + FRAME_attack_05, + FRAME_attack_06, + FRAME_attack_07, + FRAME_attack_08, + FRAME_attack_09, + FRAME_attack_10, + FRAME_attack_11, + FRAME_attack_12, + FRAME_attack_13, + FRAME_attack_14, + FRAME_attack_15, + FRAME_attack_16, + FRAME_attack_17, + FRAME_attack_18, + FRAME_attack_19, + FRAME_attack_20, + FRAME_attack_21, + FRAME_pain_01, + FRAME_pain_02, + FRAME_pain_03, + FRAME_pain_04, + FRAME_pain_05, + FRAME_pain_06 +}; + +constexpr float MODEL_SCALE = 1.000000f; diff --git a/rerelease/xatrix/p_xatrix_weapon.cpp b/rerelease/xatrix/p_xatrix_weapon.cpp new file mode 100644 index 0000000..9317af8 --- /dev/null +++ b/rerelease/xatrix/p_xatrix_weapon.cpp @@ -0,0 +1,165 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +#include "../g_local.h" + +// RAFAEL +/* + RipperGun +*/ + +void weapon_ionripper_fire(edict_t *ent) +{ + vec3_t tempang; + int damage; + + if (deathmatch->integer) + // tone down for deathmatch + damage = 30; + else + damage = 50; + + if (is_quad) + damage *= damage_multiplier; + + tempang = ent->client->v_angle; + tempang[YAW] += crandom(); + + vec3_t start, dir; + P_ProjectSource(ent, tempang, { 16, 7, -8 }, start, dir); + + P_AddWeaponKick(ent, ent->client->v_forward * -3, { -3.f, 0.f, 0.f }); + + fire_ionripper(ent, start, dir, damage, 500, EF_IONRIPPER); + + // send muzzle flash + gi.WriteByte(svc_muzzleflash); + gi.WriteEntity(ent); + gi.WriteByte(MZ_IONRIPPER | is_silenced); + gi.multicast(ent->s.origin, MULTICAST_PVS, false); + + PlayerNoise(ent, start, PNOISE_WEAPON); + + G_RemoveAmmo(ent); +} + +void Weapon_Ionripper(edict_t *ent) +{ + constexpr int pause_frames[] = { 36, 0 }; + constexpr int fire_frames[] = { 6, 0 }; + + Weapon_Generic(ent, 5, 7, 36, 39, pause_frames, fire_frames, weapon_ionripper_fire); +} + +// +// Phalanx +// + +void weapon_phalanx_fire(edict_t *ent) +{ + vec3_t v; + int damage; + float damage_radius; + int radius_damage; + + damage = irandom(70, 80); + radius_damage = 120; + damage_radius = 120; + + if (is_quad) + { + damage *= damage_multiplier; + radius_damage *= damage_multiplier; + } + + vec3_t dir; + + if (ent->client->ps.gunframe == 8) + { + v[PITCH] = ent->client->v_angle[PITCH]; + v[YAW] = ent->client->v_angle[YAW] - 1.5f; + v[ROLL] = ent->client->v_angle[ROLL]; + + vec3_t start; + P_ProjectSource(ent, v, { 0, 8, -8 }, start, dir); + + radius_damage = 30; + damage_radius = 120; + + fire_plasma(ent, start, dir, damage, 725, damage_radius, radius_damage); + + // send muzzle flash + gi.WriteByte(svc_muzzleflash); + gi.WriteEntity(ent); + gi.WriteByte(MZ_PHALANX2 | is_silenced); + gi.multicast(ent->s.origin, MULTICAST_PVS, false); + + G_RemoveAmmo(ent); + } + else + { + v[PITCH] = ent->client->v_angle[PITCH]; + v[YAW] = ent->client->v_angle[YAW] + 1.5f; + v[ROLL] = ent->client->v_angle[ROLL]; + + vec3_t start; + P_ProjectSource(ent, v, { 0, 8, -8 }, start, dir); + + fire_plasma(ent, start, dir, damage, 725, damage_radius, radius_damage); + + // send muzzle flash + gi.WriteByte(svc_muzzleflash); + gi.WriteEntity(ent); + gi.WriteByte(MZ_PHALANX | is_silenced); + gi.multicast(ent->s.origin, MULTICAST_PVS, false); + + PlayerNoise(ent, start, PNOISE_WEAPON); + } + + P_AddWeaponKick(ent, ent->client->v_forward * -2, { -2.f, 0.f, 0.f }); +} + +void Weapon_Phalanx(edict_t *ent) +{ + constexpr int pause_frames[] = { 29, 42, 55, 0 }; + constexpr int fire_frames[] = { 7, 8, 0 }; + + Weapon_Generic(ent, 5, 20, 58, 63, pause_frames, fire_frames, weapon_phalanx_fire); +} + +/* +====================================================================== + +TRAP + +====================================================================== +*/ + +constexpr gtime_t TRAP_TIMER = 5_sec; +constexpr float TRAP_MINSPEED = 300.f; +constexpr float TRAP_MAXSPEED = 700.f; + +void weapon_trap_fire(edict_t *ent, bool held) +{ + int speed; + + vec3_t start, dir; + // Paril: kill sideways angle on grenades + // limit upwards angle so you don't throw behind you + P_ProjectSource(ent, { max(-62.5f, ent->client->v_angle[0]), ent->client->v_angle[1], ent->client->v_angle[2] }, { 8, 0, -8 }, start, dir); + + gtime_t timer = ent->client->grenade_time - level.time; + speed = (int) (ent->health <= 0 ? TRAP_MINSPEED : min(TRAP_MINSPEED + (TRAP_TIMER - timer).seconds() * ((TRAP_MAXSPEED - TRAP_MINSPEED) / TRAP_TIMER.seconds()), TRAP_MAXSPEED)); + + ent->client->grenade_time = 0_ms; + + fire_trap(ent, start, dir, speed); + + G_RemoveAmmo(ent, 1); +} + +void Weapon_Trap(edict_t *ent) +{ + constexpr int pause_frames[] = { 29, 34, 39, 48, 0 }; + + Throw_Generic(ent, 15, 48, 5, "weapons/trapcock.wav", 11, 12, pause_frames, false, "weapons/traploop.wav", weapon_trap_fire, false); +} \ No newline at end of file